From 878ede3080ab2cfb627aca505c397d9765052996 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Tue, 27 Oct 2009 14:38:53 +0000 Subject: major update with stuff --- src/engine/client/ec_client.c | 2061 ----------------------------------- src/engine/client/ec_client.cpp | 2087 ++++++++++++++++++++++++++++++++++++ src/engine/client/ec_gfx.c | 1020 ------------------ src/engine/client/ec_gfx.cpp | 992 +++++++++++++++++ src/engine/client/ec_gfx_text.c | 668 ------------ src/engine/client/ec_gfx_text.cpp | 669 ++++++++++++ src/engine/client/ec_inp.c | 230 ---- src/engine/client/ec_inp.cpp | 234 ++++ src/engine/client/ec_snd.c | 465 -------- src/engine/client/ec_snd.cpp | 471 ++++++++ src/engine/client/ec_srvbrowse.c | 737 ------------- src/engine/client/ec_srvbrowse.cpp | 736 +++++++++++++ src/engine/e_client_interface.h | 8 - src/engine/e_common_interface.h | 8 - src/engine/e_compression.c | 148 --- src/engine/e_compression.cpp | 86 ++ src/engine/e_compression.h | 4 - src/engine/e_config.c | 49 - src/engine/e_config.cpp | 49 + src/engine/e_config.h | 8 - src/engine/e_console.c | 464 -------- src/engine/e_console.cpp | 464 ++++++++ src/engine/e_console.h | 14 +- src/engine/e_datafile.c | 677 ------------ src/engine/e_datafile.cpp | 677 ++++++++++++ src/engine/e_demorec.c | 639 ----------- src/engine/e_demorec.cpp | 640 +++++++++++ src/engine/e_demorec.h | 8 - src/engine/e_engine.c | 596 ---------- src/engine/e_engine.cpp | 596 ++++++++++ src/engine/e_huffman.c | 276 ----- src/engine/e_huffman.cpp | 276 +++++ src/engine/e_huffman.h | 8 - src/engine/e_if_other.h | 3 +- src/engine/e_jobs.c | 76 -- src/engine/e_jobs.cpp | 76 ++ src/engine/e_keynames.c | 524 --------- src/engine/e_keynames.cpp | 524 +++++++++ src/engine/e_linereader.c | 62 -- src/engine/e_linereader.cpp | 62 ++ src/engine/e_map.c | 66 -- src/engine/e_map.cpp | 66 ++ src/engine/e_memheap.c | 102 -- src/engine/e_memheap.cpp | 102 ++ src/engine/e_memheap.h | 2 +- src/engine/e_msg.c | 70 -- src/engine/e_msg.cpp | 70 ++ src/engine/e_network.c | 351 ------ src/engine/e_network.cpp | 347 ++++++ src/engine/e_network.h | 395 +++++-- src/engine/e_network_client.c | 154 --- src/engine/e_network_client.cpp | 139 +++ src/engine/e_network_conn.c | 366 ------- src/engine/e_network_conn.cpp | 349 ++++++ src/engine/e_network_internal.h | 156 --- src/engine/e_network_server.c | 484 --------- src/engine/e_network_server.cpp | 408 +++++++ src/engine/e_packer.c | 213 ---- src/engine/e_packer.cpp | 155 +++ src/engine/e_packer.h | 58 +- src/engine/e_ringbuffer.c | 362 ------- src/engine/e_ringbuffer.cpp | 194 ++++ src/engine/e_ringbuffer.h | 67 +- src/engine/e_server_interface.h | 8 - src/engine/e_snapshot.c | 604 ----------- src/engine/e_snapshot.cpp | 571 ++++++++++ src/engine/e_snapshot.h | 160 +-- src/engine/server/es_register.c | 272 ----- src/engine/server/es_register.cpp | 271 +++++ src/engine/server/es_server.c | 1380 ------------------------ src/engine/server/es_server.cpp | 1969 ++++++++++++++++++++++++++++++++++ 71 files changed, 13762 insertions(+), 13541 deletions(-) delete mode 100644 src/engine/client/ec_client.c create mode 100644 src/engine/client/ec_client.cpp delete mode 100644 src/engine/client/ec_gfx.c create mode 100644 src/engine/client/ec_gfx.cpp delete mode 100644 src/engine/client/ec_gfx_text.c create mode 100644 src/engine/client/ec_gfx_text.cpp delete mode 100644 src/engine/client/ec_inp.c create mode 100644 src/engine/client/ec_inp.cpp delete mode 100644 src/engine/client/ec_snd.c create mode 100644 src/engine/client/ec_snd.cpp delete mode 100644 src/engine/client/ec_srvbrowse.c create mode 100644 src/engine/client/ec_srvbrowse.cpp delete mode 100644 src/engine/e_compression.c create mode 100644 src/engine/e_compression.cpp delete mode 100644 src/engine/e_config.c create mode 100644 src/engine/e_config.cpp delete mode 100644 src/engine/e_console.c create mode 100644 src/engine/e_console.cpp delete mode 100644 src/engine/e_datafile.c create mode 100644 src/engine/e_datafile.cpp delete mode 100644 src/engine/e_demorec.c create mode 100644 src/engine/e_demorec.cpp delete mode 100644 src/engine/e_engine.c create mode 100644 src/engine/e_engine.cpp delete mode 100644 src/engine/e_huffman.c create mode 100644 src/engine/e_huffman.cpp delete mode 100644 src/engine/e_jobs.c create mode 100644 src/engine/e_jobs.cpp delete mode 100644 src/engine/e_keynames.c create mode 100644 src/engine/e_keynames.cpp delete mode 100644 src/engine/e_linereader.c create mode 100644 src/engine/e_linereader.cpp delete mode 100644 src/engine/e_map.c create mode 100644 src/engine/e_map.cpp delete mode 100644 src/engine/e_memheap.c create mode 100644 src/engine/e_memheap.cpp delete mode 100644 src/engine/e_msg.c create mode 100644 src/engine/e_msg.cpp delete mode 100644 src/engine/e_network.c create mode 100644 src/engine/e_network.cpp delete mode 100644 src/engine/e_network_client.c create mode 100644 src/engine/e_network_client.cpp delete mode 100644 src/engine/e_network_conn.c create mode 100644 src/engine/e_network_conn.cpp delete mode 100644 src/engine/e_network_internal.h delete mode 100644 src/engine/e_network_server.c create mode 100644 src/engine/e_network_server.cpp delete mode 100644 src/engine/e_packer.c create mode 100644 src/engine/e_packer.cpp delete mode 100644 src/engine/e_ringbuffer.c create mode 100644 src/engine/e_ringbuffer.cpp delete mode 100644 src/engine/e_snapshot.c create mode 100644 src/engine/e_snapshot.cpp delete mode 100644 src/engine/server/es_register.c create mode 100644 src/engine/server/es_register.cpp delete mode 100644 src/engine/server/es_server.c create mode 100644 src/engine/server/es_server.cpp (limited to 'src/engine') diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c deleted file mode 100644 index 0c707ccf..00000000 --- a/src/engine/client/ec_client.c +++ /dev/null @@ -1,2061 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -const int prediction_margin = 1000/50/2; /* magic network prediction value */ - -/* - Server Time - Client Mirror Time - Client Predicted Time - - Snapshot Latency - Downstream latency - - Prediction Latency - Upstream latency -*/ - -/* network client, must be accessible from other parts like the server browser */ -NETCLIENT *net; - -/* TODO: ugly, fix me */ -extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); -extern void client_serverbrowse_save(); - -static unsigned snapshot_parts; -static int64 local_start_time; - -static int debug_font; -static float frametime = 0.0001f; -static float frametime_low = 1.0f; -static float frametime_high = 0.0f; -static int frames = 0; -static NETADDR server_address; -static int window_must_refocus = 0; -static int snapcrcerrors = 0; - -static int ack_game_tick = -1; -static int current_recv_tick = 0; -static int rcon_authed = 0; - -/* version-checking */ -static char versionstr[10] = "0"; - -/* pinging */ -static int64 ping_start_time = 0; - -/* */ -static char current_map[256] = {0}; -static int current_map_crc = 0; - -/* */ -static char cmd_connect[256] = {0}; - -/* map download */ -static char mapdownload_filename[256] = {0}; -static char mapdownload_name[256] = {0}; -static IOHANDLE mapdownload_file = 0; -static int mapdownload_chunk = 0; -static int mapdownload_crc = 0; -static int mapdownload_amount = -1; -static int mapdownload_totalsize = -1; - -/* */ -static SERVER_INFO current_server_info = {0}; -static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ - -/* current time */ -static int current_tick = 0; -static float intratick = 0; -static float ticktime = 0; -static int prev_tick = 0; - -/* */ -/*static int predictiontime_pingspikecounter = 0; -static int gametime_pingspikecounter = 0;*/ - -/* predicted time */ -static int current_predtick = 0; -static float predintratick = 0; -static int last_input_timeleft = 0; - -static struct /* TODO: handle input better */ -{ - int data[MAX_INPUT_SIZE]; /* the input data */ - int tick; /* the tick that the input is for */ - int64 predicted_time; /* prediction latency when we sent this input */ - int64 time; -} inputs[200]; -static int current_input = 0; - -enum -{ - GRAPH_MAX=128 -}; - -typedef struct -{ - float min, max; - float values[GRAPH_MAX]; - float colors[GRAPH_MAX][3]; - int index; -} GRAPH; - -static void graph_init(GRAPH *g, float min, float max) -{ - g->min = min; - g->max = max; - g->index = 0; -} - -static void graph_scale_max(GRAPH *g) -{ - int i = 0; - g->max = 0; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] > g->max) - g->max = g->values[i]; - } -} - -static void graph_scale_min(GRAPH *g) -{ - int i = 0; - g->min = g->max; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] < g->min) - g->min = g->values[i]; - } -} - -static void graph_add(GRAPH *graph, float v, float r, float g, float b) -{ - graph->index = (graph->index+1)&(GRAPH_MAX-1); - graph->values[graph->index] = v; - graph->colors[graph->index][0] = r; - graph->colors[graph->index][1] = g; - graph->colors[graph->index][2] = b; -} - -static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) -{ - char buf[32]; - int i; - - gfx_blend_normal(); - - - gfx_texture_set(-1); - - gfx_quads_begin(); - gfx_setcolor(0, 0, 0, 0.75f); - gfx_quads_drawTL(x, y, w, h); - gfx_quads_end(); - - gfx_lines_begin(); - gfx_setcolor(0.95f, 0.95f, 0.95f, 1.00f); - gfx_lines_draw(x, y+h/2, x+w, y+h/2); - gfx_setcolor(0.5f, 0.5f, 0.5f, 0.75f); - gfx_lines_draw(x, y+(h*3)/4, x+w, y+(h*3)/4); - gfx_lines_draw(x, y+h/4, x+w, y+h/4); - for(i = 1; i < GRAPH_MAX; i++) - { - float a0 = (i-1)/(float)GRAPH_MAX; - float a1 = i/(float)GRAPH_MAX; - int i0 = (g->index+i-1)&(GRAPH_MAX-1); - int i1 = (g->index+i)&(GRAPH_MAX-1); - - float v0 = (g->values[i0]-g->min) / (g->max-g->min); - float v1 = (g->values[i1]-g->min) / (g->max-g->min); - - gfx_setcolorvertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); - gfx_setcolorvertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); - gfx_lines_draw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); - - } - gfx_lines_end(); - - - gfx_texture_set(debug_font); - gfx_quads_text(x+2, y+h-16, 16, 1,1,1,1, description); - - str_format(buf, sizeof(buf), "%.2f", g->max); - gfx_quads_text(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); - - str_format(buf, sizeof(buf), "%.2f", g->min); - gfx_quads_text(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); - -} - -typedef struct -{ - int64 snap; - int64 current; - int64 target; - - int64 rlast; - int64 tlast; - GRAPH graph; - - int spikecounter; - - float adjustspeed[2]; /* 0 = down, 1 = up */ -} SMOOTHTIME; - -static void st_init(SMOOTHTIME *st, int64 target) -{ - st->snap = time_get(); - st->current = target; - st->target = target; - st->adjustspeed[0] = 0.3f; - st->adjustspeed[1] = 0.3f; - graph_init(&st->graph, 0.0f, 0.5f); -} - -static int64 st_get(SMOOTHTIME *st, int64 now) -{ - float adjust_speed, a; - int64 c = st->current + (now - st->snap); - int64 t = st->target + (now - st->snap); - int64 r; - - /* it's faster to adjust upward instead of downward */ - /* we might need to adjust these abit */ - - adjust_speed = st->adjustspeed[0]; - if(t > c) - adjust_speed = st->adjustspeed[1]; - - a = ((now-st->snap)/(float)time_freq()) * adjust_speed; - if(a > 1.0f) - a = 1.0f; - - r = c + (int64)((t-c)*a); - - graph_add(&st->graph, a+0.5f,1,1,1); - - return r; -} - -static void st_update_int(SMOOTHTIME *st, int64 target) -{ - int64 now = time_get(); - st->current = st_get(st, now); - st->snap = now; - st->target = target; -} - -static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) -{ - int update_timer = 1; - - if(time_left < 0) - { - int is_spike = 0; - if(time_left < -50) - { - is_spike = 1; - - st->spikecounter += 5; - if(st->spikecounter > 50) - st->spikecounter = 50; - } - - if(is_spike && st->spikecounter < 15) - { - /* ignore this ping spike */ - update_timer = 0; - graph_add(graph, time_left, 1,1,0); - } - else - { - graph_add(graph, time_left, 1,0,0); - if(st->adjustspeed[adjust_direction] < 30.0f) - st->adjustspeed[adjust_direction] *= 2.0f; - } - } - else - { - if(st->spikecounter) - st->spikecounter--; - - graph_add(graph, time_left, 0,1,0); - - st->adjustspeed[adjust_direction] *= 0.95f; - if(st->adjustspeed[adjust_direction] < 2.0f) - st->adjustspeed[adjust_direction] = 2.0f; - } - - last_input_timeleft = time_left; - - if(update_timer) - st_update_int(st, target); -} - -static SMOOTHTIME game_time; -static SMOOTHTIME predicted_time; - -/* graphs */ -static GRAPH inputtime_margin_graph; -static GRAPH gametime_margin_graph; -static GRAPH fps_graph; - -/* -- snapshot handling --- */ -enum -{ - NUM_SNAPSHOT_TYPES=2 -}; - -/* the game snapshots are modifiable by the game */ -SNAPSTORAGE snapshot_storage; -static SNAPSTORAGE_HOLDER *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; - -static int recived_snapshots = 0; -static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE]; - -static SNAPSTORAGE_HOLDER demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; -static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][MAX_SNAPSHOT_SIZE]; - -/* --- */ - -void *snap_get_item(int snapid, int index, SNAP_ITEM *item) -{ - SNAPSHOT_ITEM *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshot_get_item(snapshots[snapid]->alt_snap, index); - item->datasize = snapshot_get_item_datasize(snapshots[snapid]->alt_snap, index); - item->type = snapitem_type(i); - item->id = snapitem_id(i); - return (void *)snapitem_data(i); -} - -void snap_invalidate_item(int snapid, int index) -{ - SNAPSHOT_ITEM *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshot_get_item(snapshots[snapid]->alt_snap, index); - if(i) - { - if((char *)i < (char *)snapshots[snapid]->alt_snap || (char *)i > (char *)snapshots[snapid]->alt_snap + snapshots[snapid]->snap_size) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - if((char *)i >= (char *)snapshots[snapid]->snap && (char *)i < (char *)snapshots[snapid]->snap + snapshots[snapid]->snap_size) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - i->type_and_id = -1; - } -} - -void *snap_find_item(int snapid, int type, int id) -{ - /* TODO: linear search. should be fixed. */ - int i; - - if(!snapshots[snapid]) - return 0x0; - - for(i = 0; i < snapshots[snapid]->snap->num_items; i++) - { - SNAPSHOT_ITEM *itm = snapshot_get_item(snapshots[snapid]->alt_snap, i); - if(snapitem_type(itm) == type && snapitem_id(itm) == id) - return (void *)snapitem_data(itm); - } - return 0x0; -} - -int snap_num_items(int snapid) -{ - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - if(!snapshots[snapid]) - return 0; - return snapshots[snapid]->snap->num_items; -} - -/* ------ time functions ------ */ -float client_intratick() { return intratick; } -float client_predintratick() { return predintratick; } -float client_ticktime() { return ticktime; } -int client_tick() { return current_tick; } -int client_prevtick() { return prev_tick; } -int client_predtick() { return current_predtick; } -int client_tickspeed() { return SERVER_TICK_SPEED; } -float client_frametime() { return frametime; } -float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } - -/* ----- send functions ----- */ -int client_send_msg() -{ - const MSG_INFO *info = msg_get_info(); - NETCHUNK packet; - - if(!info) - return -1; - - if(client_state() == CLIENTSTATE_OFFLINE) - return 0; - - - mem_zero(&packet, sizeof(NETCHUNK)); - - packet.client_id = 0; - packet.data = info->data; - packet.data_size = info->size; - - if(info->flags&MSGFLAG_VITAL) - packet.flags |= NETSENDFLAG_VITAL; - if(info->flags&MSGFLAG_FLUSH) - packet.flags |= NETSENDFLAG_FLUSH; - - if(info->flags&MSGFLAG_RECORD) - { - if(demorec_isrecording()) - demorec_record_message(packet.data, packet.data_size); - } - - if(!(info->flags&MSGFLAG_NOSEND)) - netclient_send(net, &packet); - return 0; -} - -static void client_send_info() -{ - msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(modc_net_version(), 128); - msg_pack_string(config.player_name, 128); - msg_pack_string(config.clan_name, 128); - msg_pack_string(config.password, 128); - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_entergame() -{ - msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -static void client_send_ready() -{ - msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -int client_rcon_authed() -{ - return rcon_authed; -} - -void client_rcon_auth(const char *name, const char *password) -{ - msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); - msg_pack_string(name, 32); - msg_pack_string(password, 32); - msg_pack_end(); - client_send_msg(); -} - -void client_rcon(const char *cmd) -{ - msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); - msg_pack_string(cmd, 256); - msg_pack_end(); - client_send_msg(); -} - -int client_connection_problems() -{ - return netclient_gotproblems(net); -} - -void client_direct_input(int *input, int size) -{ - int i; - msg_pack_start_system(NETMSG_INPUT, 0); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - for(i = 0; i < size/4; i++) - msg_pack_int(input[i]); - - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_input() -{ - int64 now = time_get(); - int i, size; - - if(current_predtick <= 0) - return; - - /* fetch input */ - size = modc_snap_input(inputs[current_input].data); - - if(!size) - return; - - /* pack input */ - msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - inputs[current_input].tick = current_predtick; - inputs[current_input].predicted_time = st_get(&predicted_time, now); - inputs[current_input].time = now; - - /* pack it */ - for(i = 0; i < size/4; i++) - msg_pack_int(inputs[current_input].data[i]); - - current_input++; - current_input%=200; - - msg_pack_end(); - client_send_msg(); -} - -const char *client_latestversion() -{ - return versionstr; -} - -/* TODO: OPT: do this alot smarter! */ -int *client_get_input(int tick) -{ - int i; - int best = -1; - for(i = 0; i < 200; i++) - { - if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) - best = i; - } - - if(best != -1) - return (int *)inputs[best].data; - return 0; -} - -/* ------ state handling ----- */ -static int state = CLIENTSTATE_OFFLINE; -int client_state() { return state; } -static void client_set_state(int s) -{ - int old = state; - if(config.debug) - dbg_msg("client", "state change. last=%d current=%d", state, s); - state = s; - if(old != s) - modc_statechange(state, old); -} - -/* called when the map is loaded and we should init for a new round */ -static void client_on_enter_game() -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - inputs[i].tick = -1; - current_input = 0; - - /* reset snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - snapstorage_purge_all(&snapshot_storage); - recived_snapshots = 0; - snapshot_parts = 0; - current_predtick = 0; - current_recv_tick = 0; -} - -void client_entergame() -{ - if(state == CLIENTSTATE_DEMOPLAYBACK) - return; - - /* now we will wait for two snapshots */ - /* to finish the connection */ - client_send_entergame(); - client_on_enter_game(); -} - -void client_connect(const char *server_address_str) -{ - char buf[512]; - const char *port_str = 0; - int k; - int port = 8303; - - client_disconnect(); - - dbg_msg("client", "connecting to '%s'", server_address_str); - - //client_serverinfo_request(); - str_copy(buf, server_address_str, sizeof(buf)); - - for(k = 0; buf[k]; k++) - { - if(buf[k] == ':') - { - port_str = &(buf[k+1]); - buf[k] = 0; - break; - } - } - - if(port_str) - port = atoi(port_str); - - /* TODO: IPv6 support */ - if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) - dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); - - rcon_authed = 0; - server_address.port = port; - netclient_connect(net, &server_address); - client_set_state(CLIENTSTATE_CONNECTING); - - graph_init(&inputtime_margin_graph, -150.0f, 150.0f); - graph_init(&gametime_margin_graph, -150.0f, 150.0f); -} - -void client_disconnect_with_reason(const char *reason) -{ - /* stop demo playback */ - demorec_playback_stop(); - - /* */ - rcon_authed = 0; - netclient_disconnect(net, reason); - client_set_state(CLIENTSTATE_OFFLINE); - map_unload(); - - /* disable all downloads */ - mapdownload_chunk = 0; - if(mapdownload_file) - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_crc = 0; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - /* clear the current server info */ - mem_zero(¤t_server_info, sizeof(current_server_info)); - mem_zero(&server_address, sizeof(server_address)); - - /* clear snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - recived_snapshots = 0; -} - -void client_disconnect() -{ - client_disconnect_with_reason(0); -} - - -void client_serverinfo(SERVER_INFO *serverinfo) -{ - mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); -} - -void client_serverinfo_request() -{ - mem_zero(¤t_server_info, sizeof(current_server_info)); - current_server_info_requesttime = 0; -} - -static int client_load_data() -{ - debug_font = gfx_load_texture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); - return 1; -} - -extern int snapshot_data_rate[0xffff]; -extern int snapshot_data_updates[0xffff]; - -const char *modc_getitemname(int type); - -static void client_debug_render() -{ - static NETSTATS prev, current; - static int64 last_snap = 0; - static float frametime_avg = 0; - int64 now = time_get(); - char buffer[512]; - - if(!config.debug) - return; - - gfx_blend_normal(); - gfx_texture_set(debug_font); - gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); - - if(time_get()-last_snap > time_freq()) - { - last_snap = time_get(); - prev = current; - net_stats(¤t); - } - - /* - eth = 14 - ip = 20 - udp = 8 - total = 42 - */ - frametime_avg = frametime_avg*0.9f + frametime*0.1f; - str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d", - current_tick, current_predtick, - mem_stats()->allocated/1024, - mem_stats()->total_allocations, - gfx_memory_usage()/1024, - (int)(1.0f/frametime_avg)); - gfx_quads_text(2, 2, 16, 1,1,1,1, buffer); - - - { - int send_packets = (current.sent_packets-prev.sent_packets); - int send_bytes = (current.sent_bytes-prev.sent_bytes); - int send_total = send_bytes + send_packets*42; - int recv_packets = (current.recv_packets-prev.recv_packets); - int recv_bytes = (current.recv_bytes-prev.recv_bytes); - int recv_total = recv_bytes + recv_packets*42; - - if(!send_packets) send_packets++; - if(!recv_packets) recv_packets++; - str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", - send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, - recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); - gfx_quads_text(2, 14, 16, 1,1,1,1, buffer); - } - - /* render rates */ - { - int y = 0; - int i; - for(i = 0; i < 256; i++) - { - if(snapshot_data_rate[i]) - { - str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], - (snapshot_data_rate[i]/snapshot_data_updates[i])/8); - gfx_quads_text(2, 100+y*12, 16, 1,1,1,1, buffer); - y++; - } - } - } - - str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", - (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), - predicted_time.adjustspeed[1]); - gfx_quads_text(2, 70, 16, 1,1,1,1, buffer); - - /* render graphs */ - if(config.dbg_graphs) - { - //gfx_mapscreen(0,0,400.0f,300.0f); - float w = gfx_screenwidth()/4.0f; - float h = gfx_screenheight()/6.0f; - float sp = gfx_screenwidth()/100.0f; - float x = gfx_screenwidth()-w-sp; - - graph_scale_max(&fps_graph); - graph_scale_min(&fps_graph); - graph_render(&fps_graph, x, sp*5, w, h, "FPS"); - graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); - graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); - } -} - -void client_quit() -{ - client_set_state(CLIENTSTATE_QUITING); -} - -const char *client_error_string() -{ - return netclient_error_string(net); -} - -static void client_render() -{ - if(config.gfx_clear) - gfx_clear(1,1,0); - - modc_render(); - client_debug_render(); -} - -static const char *client_load_map(const char *name, const char *filename, int wanted_crc) -{ - static char errormsg[128]; - DATAFILE *df; - int crc; - - client_set_state(CLIENTSTATE_LOADING); - - df = datafile_load(filename); - if(!df) - { - str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); - return errormsg; - } - - /* get the crc of the map */ - crc = datafile_crc(filename); - if(crc != wanted_crc) - { - datafile_unload(df); - str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); - return errormsg; - } - - // stop demo recording if we loaded a new map - demorec_record_stop(); - - dbg_msg("client", "loaded map '%s'", filename); - recived_snapshots = 0; - map_set(df); - - str_copy(current_map, name, sizeof(current_map)); - current_map_crc = crc; - - return NULL; -} - -static const char *client_load_map_search(const char *mapname, int wanted_crc) -{ - const char *error = 0; - char buf[512]; - dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); - client_set_state(CLIENTSTATE_LOADING); - - /* try the normal maps folder */ - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - error = client_load_map(mapname, buf, wanted_crc); - if(!error) - return error; - - /* try the downloaded maps */ - str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); - error = client_load_map(mapname, buf, wanted_crc); - return error; -} - -static int player_score_comp(const void *a, const void *b) -{ - SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; - SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; - if(p0->score == p1->score) - return 0; - if(p0->score < p1->score) - return 1; - return -1; -} - -static void client_process_packet(NETCHUNK *packet) -{ - if(packet->client_id == -1) - { - /* connectionlesss */ - if(packet->data_size == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && - memcmp(packet->data, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) - { - unsigned char *versiondata = (unsigned char*) packet->data + sizeof(VERSIONSRV_VERSION); - int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); - - dbg_msg("client/version", "version does %s (%d.%d.%d)", - version_match ? "match" : "NOT match", - versiondata[1], versiondata[2], versiondata[3]); - - /* assume version is out of date when version-data doesn't match */ - if (!version_match) - { - sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); - } - } - - if(packet->data_size >= (int)sizeof(SERVERBROWSE_LIST) && - memcmp(packet->data, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) - { - int size = packet->data_size-sizeof(SERVERBROWSE_LIST); - int num = size/sizeof(MASTERSRV_ADDR); - MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)packet->data+sizeof(SERVERBROWSE_LIST)); - int i; - - for(i = 0; i < num; i++) - { - NETADDR addr; - - /* convert address */ - mem_zero(&addr, sizeof(addr)); - addr.type = NETTYPE_IPV4; - addr.ip[0] = addrs[i].ip[0]; - addr.ip[1] = addrs[i].ip[1]; - addr.ip[2] = addrs[i].ip[2]; - addr.ip[3] = addrs[i].ip[3]; - addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; - - client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); - } - } - - { - int packet_type = 0; - if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) - packet_type = 2; - - if(packet->data_size >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(packet->data, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) - packet_type = 1; - - if(packet_type) - { - /* we got ze info */ - UNPACKER up; - SERVER_INFO info = {0}; - int i; - int token = -1; - - unpacker_reset(&up, (unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO)); - if(packet_type >= 2) - token = atol(unpacker_get_string(&up)); - str_copy(info.version, unpacker_get_string(&up), sizeof(info.version)); - str_copy(info.name, unpacker_get_string(&up), sizeof(info.name)); - str_copy(info.map, unpacker_get_string(&up), sizeof(info.map)); - str_copy(info.gametype, unpacker_get_string(&up), sizeof(info.gametype)); - info.flags = atol(unpacker_get_string(&up)); - info.progression = atol(unpacker_get_string(&up)); - info.num_players = atol(unpacker_get_string(&up)); - info.max_players = atol(unpacker_get_string(&up)); - str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d", - packet->address.ip[0], packet->address.ip[1], packet->address.ip[2], - packet->address.ip[3], packet->address.port); - - for(i = 0; i < info.num_players; i++) - { - str_copy(info.players[i].name, unpacker_get_string(&up), sizeof(info.players[i].name)); - info.players[i].score = atol(unpacker_get_string(&up)); - } - - if(!up.error) - { - /* sort players */ - qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); - - if(net_addr_comp(&server_address, &packet->address) == 0) - { - mem_copy(¤t_server_info, &info, sizeof(current_server_info)); - current_server_info.netaddr = server_address; - current_server_info_requesttime = -1; - } - else - { - if(packet_type == 2) - client_serverbrowse_set(&packet->address, BROWSESET_TOKEN, token, &info); - else - client_serverbrowse_set(&packet->address, BROWSESET_OLD_INTERNET, -1, &info); - } - } - } - } - } - else - { - int sys; - int msg = msg_unpack_start(packet->data, packet->data_size, &sys); - - if(sys) - { - /* system message */ - if(msg == NETMSG_MAP_CHANGE) - { - const char *map = msg_unpack_string(); - int map_crc = msg_unpack_int(); - const char *error = 0; - int i; - - if(msg_unpack_error()) - return; - - for(i = 0; map[i]; i++) /* protect the player from nasty map names */ - { - if(map[i] == '/' || map[i] == '\\') - error = "strange character in map name"; - } - - if(error) - client_disconnect_with_reason(error); - else - { - error = client_load_map_search(map, map_crc); - - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - { - str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); - - dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); - - mapdownload_chunk = 0; - str_copy(mapdownload_name, map, sizeof(mapdownload_name)); - mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); - mapdownload_crc = map_crc; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - } - else if(msg == NETMSG_MAP_DATA) - { - int last = msg_unpack_int(); - int total_size = msg_unpack_int(); - int size = msg_unpack_int(); - const unsigned char *data = msg_unpack_raw(size); - - /* check fior errors */ - if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) - return; - - io_write(mapdownload_file, data, size); - - mapdownload_totalsize = total_size; - mapdownload_amount += size; - - if(last) - { - const char *error; - dbg_msg("client/network", "download complete, loading map"); - - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_amount = 0; - mapdownload_totalsize = -1; - - /* load map */ - error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - client_disconnect_with_reason(error); - } - else - { - /* request new chunk */ - mapdownload_chunk++; - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - client_send_msg(); - } - else if(msg == NETMSG_RCON_AUTH_STATUS) - { - int result = msg_unpack_int(); - if(msg_unpack_error() == 0) - rcon_authed = result; - } - else if(msg == NETMSG_RCON_LINE) - { - const char *line = msg_unpack_string(); - if(msg_unpack_error() == 0) - { - /*dbg_msg("remote", "%s", line);*/ - modc_rcon_line(line); - } - } - else if(msg == NETMSG_PING_REPLY) - dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); - else if(msg == NETMSG_INPUTTIMING) - { - int input_predtick = msg_unpack_int(); - int time_left = msg_unpack_int(); - - /* adjust our prediction time */ - int k; - int64 target = 0; - for(k = 0; k < 200; k++) - { - if(inputs[k].tick == input_predtick) - { - target = inputs[k].predicted_time + (time_get() - inputs[k].time); - target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); - //st_update(&predicted_time, ); - break; - } - } - - if(target) - st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); - } - else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) - { - /*dbg_msg("client/network", "got snapshot"); */ - int num_parts = 1; - int part = 0; - int game_tick = msg_unpack_int(); - int delta_tick = game_tick-msg_unpack_int(); - int part_size = 0; - int crc = 0; - int complete_size = 0; - const char *data = 0; - - /* we are not allowed to process snapshot yet */ - if(client_state() < CLIENTSTATE_LOADING) - return; - - if(msg == NETMSG_SNAP) - { - num_parts = msg_unpack_int(); - part = msg_unpack_int(); - } - - if(msg != NETMSG_SNAPEMPTY) - { - crc = msg_unpack_int(); - part_size = msg_unpack_int(); - } - - data = (const char *)msg_unpack_raw(part_size); - - if(msg_unpack_error()) - return; - - if(game_tick >= current_recv_tick) - { - if(game_tick != current_recv_tick) - { - snapshot_parts = 0; - current_recv_tick = game_tick; - } - - /* TODO: clean this up abit */ - mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); - snapshot_parts |= 1<= 0) - { - int deltashot_size = snapstorage_get(&snapshot_storage, delta_tick, 0, &deltashot, 0); - - if(deltashot_size < 0) - { - /* couldn't find the delta snapshots that the server used */ - /* to compress this snapshot. force the server to resync */ - if(config.debug) - dbg_msg("client", "error, couldn't find the delta snapshot"); - - /* ack snapshot */ - /* TODO: combine this with the input message */ - ack_game_tick = -1; - return; - } - } - - /* decompress snapshot */ - deltadata = snapshot_empty_delta(); - deltasize = sizeof(int)*3; - - if(complete_size) - { - int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); - - if(intsize < 0) /* failure during decompression, bail */ - return; - - deltadata = tmpbuffer2; - deltasize = intsize; - } - - /* unpack delta */ - purgetick = delta_tick; - snapsize = snapshot_unpack_delta(deltashot, (SNAPSHOT*)tmpbuffer3, deltadata, deltasize); - if(snapsize < 0) - { - dbg_msg("client", "delta unpack failed!"); - return; - } - - if(msg != NETMSG_SNAPEMPTY && snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc) - { - if(config.debug) - { - dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", - snapcrcerrors, game_tick, crc, snapshot_crc((SNAPSHOT*)tmpbuffer3), complete_size, delta_tick); - } - - snapcrcerrors++; - if(snapcrcerrors > 10) - { - /* to many errors, send reset */ - ack_game_tick = -1; - client_send_input(); - snapcrcerrors = 0; - } - return; - } - else - { - if(snapcrcerrors) - snapcrcerrors--; - } - - /* purge old snapshots */ - purgetick = delta_tick; - if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->tick < purgetick) - purgetick = snapshots[SNAP_PREV]->tick; - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->tick < purgetick) - purgetick = snapshots[SNAP_PREV]->tick; - snapstorage_purge_until(&snapshot_storage, purgetick); - - /* add new */ - snapstorage_add(&snapshot_storage, game_tick, time_get(), snapsize, (SNAPSHOT*)tmpbuffer3, 1); - - /* add snapshot to demo */ - if(demorec_isrecording()) - { - - /* write tick marker */ - /* - DEMOREC_TICKMARKER marker; - marker.tick = game_tick; - swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); - demorec_record_write("TICK", sizeof(marker), &marker); - demorec_record_write("SNAP", snapsize, tmpbuffer3); - */ - - /* write snapshot */ - demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); - } - - /* apply snapshot, cycle pointers */ - recived_snapshots++; - - current_recv_tick = game_tick; - - /* we got two snapshots until we see us self as connected */ - if(recived_snapshots == 2) - { - /* start at 200ms and work from there */ - st_init(&predicted_time, game_tick*time_freq()/50); - predicted_time.adjustspeed[1] = 1000.0f; - st_init(&game_time, (game_tick-1)*time_freq()/50); - snapshots[SNAP_PREV] = snapshot_storage.first; - snapshots[SNAP_CURRENT] = snapshot_storage.last; - local_start_time = time_get(); - client_set_state(CLIENTSTATE_ONLINE); - } - - /* adjust game time */ - { - int64 now = st_get(&game_time, time_get()); - int64 tickstart = game_tick*time_freq()/50; - int64 time_left = (tickstart-now)*1000 / time_freq(); - /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ - st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); - } - - /* ack snapshot */ - ack_game_tick = game_tick; - } - } - } - } - else - { - /* game message */ - if(demorec_isrecording()) - demorec_record_message(packet->data, packet->data_size); - /* demorec_record_write("MESG", packet->data_size, ); */ - - modc_message(msg); - } - } -} - -int client_mapdownload_amount() { return mapdownload_amount; } -int client_mapdownload_totalsize() { return mapdownload_totalsize; } - -static void client_pump_network() -{ - NETCHUNK packet; - - netclient_update(net); - - if(client_state() != CLIENTSTATE_DEMOPLAYBACK) - { - /* check for errors */ - if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) - { - client_set_state(CLIENTSTATE_OFFLINE); - client_disconnect(); - dbg_msg("client", "offline error='%s'", netclient_error_string(net)); - } - - /* */ - if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE) - { - /* we switched to online */ - dbg_msg("client", "connected, sending info"); - client_set_state(CLIENTSTATE_LOADING); - client_send_info(); - } - } - - /* process packets */ - while(netclient_recv(net, &packet)) - client_process_packet(&packet); -} - -static void client_democallback_snapshot(void *data, int size) -{ - /* update ticks, they could have changed */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - SNAPSTORAGE_HOLDER *temp; - current_tick = info->current_tick; - prev_tick = info->previous_tick; - - /* handle snapshots */ - temp = snapshots[SNAP_PREV]; - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = temp; - - mem_copy(snapshots[SNAP_CURRENT]->snap, data, size); - mem_copy(snapshots[SNAP_CURRENT]->alt_snap, data, size); - - modc_newsnapshot(); - /*modc_predict();*/ -} - -static void client_democallback_message(void *data, int size) -{ - int sys = 0; - int msg = msg_unpack_start(data, size, &sys); - if(!sys) - modc_message(msg); -} - - -const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() -{ - static DEMOPLAYBACK_INFO ret; - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - ret.first_tick = info->first_tick; - ret.last_tick = info->last_tick; - ret.current_tick = info->current_tick; - ret.paused = info->paused; - ret.speed = info->speed; - return &ret; -} - -void client_demoplayer_setpos(float percent) -{ - demorec_playback_set(percent); -} - -void client_demoplayer_setspeed(float speed) -{ - demorec_playback_setspeed(speed); -} - -void client_demoplayer_setpause(int paused) -{ - if(paused) - demorec_playback_pause(); - else - demorec_playback_unpause(); -} - -static void client_update() -{ - if(client_state() == CLIENTSTATE_DEMOPLAYBACK) - { - demorec_playback_update(); - if(demorec_isplaying()) - { - /* update timers */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - current_tick = info->current_tick; - prev_tick = info->previous_tick; - intratick = info->intratick; - ticktime = info->ticktime; - } - else - { - /* disconnect on error */ - client_disconnect(); - } - } - else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) - { - /* switch snapshot */ - int repredict = 0; - int64 freq = time_freq(); - int64 now = st_get(&game_time, time_get()); - int64 pred_now = st_get(&predicted_time, time_get()); - - while(1) - { - SNAPSTORAGE_HOLDER *cur = snapshots[SNAP_CURRENT]; - int64 tickstart = (cur->tick)*time_freq()/50; - - if(tickstart < now) - { - SNAPSTORAGE_HOLDER *next = snapshots[SNAP_CURRENT]->next; - if(next) - { - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = next; - - /* set ticks */ - current_tick = snapshots[SNAP_CURRENT]->tick; - prev_tick = snapshots[SNAP_PREV]->tick; - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - modc_newsnapshot(); - repredict = 1; - } - } - else - break; - } - else - break; - } - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - int64 curtick_start = (snapshots[SNAP_CURRENT]->tick)*time_freq()/50; - int64 prevtick_start = (snapshots[SNAP_PREV]->tick)*time_freq()/50; - /*tg_add(&predicted_time_graph, pred_now, 0); */ - int prev_pred_tick = (int)(pred_now*50/time_freq()); - int new_pred_tick = prev_pred_tick+1; - static float last_predintra = 0; - - intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); - ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ - - curtick_start = new_pred_tick*time_freq()/50; - prevtick_start = prev_pred_tick*time_freq()/50; - predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); - - if(new_pred_tick < snapshots[SNAP_PREV]->tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->tick+SERVER_TICK_SPEED) - { - dbg_msg("client", "prediction time reset!"); - st_init(&predicted_time, snapshots[SNAP_CURRENT]->tick*time_freq()/50); - } - - if(new_pred_tick > current_predtick) - { - last_predintra = predintratick; - current_predtick = new_pred_tick; - repredict = 1; - - /* send input */ - client_send_input(); - } - - last_predintra = predintratick; - } - - /* only do sane predictions */ - if(repredict) - { - if(current_predtick > current_tick && current_predtick < current_tick+50) - modc_predict(); - } - - /* fetch server info if we don't have it */ - if(client_state() >= CLIENTSTATE_LOADING && - current_server_info_requesttime >= 0 && - time_get() > current_server_info_requesttime) - { - client_serverbrowse_request(&server_address); - current_server_info_requesttime = time_get()+time_freq()*2; - } - } - - /* STRESS TEST: join the server again */ - if(config.dbg_stress) - { - static int64 action_taken = 0; - int64 now = time_get(); - if(client_state() == CLIENTSTATE_OFFLINE) - { - if(now > action_taken+time_freq()*2) - { - dbg_msg("stress", "reconnecting!"); - client_connect(config.dbg_stress_server); - action_taken = now; - } - } - else - { - /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) - { - dbg_msg("stress", "disconnecting!"); - client_disconnect(); - action_taken = now; - }*/ - } - } - - /* pump the network */ - client_pump_network(); - - /* update the maser server registry */ - mastersrv_update(); - - /* update the server browser */ - client_serverbrowse_update(); -} - - -static void client_versionupdate() -{ - static int state = 0; - static HOSTLOOKUP version_serveraddr; - - if(state == 0) - { - engine_hostlookup(&version_serveraddr, config.cl_version_server); - state++; - } - else if(state == 1) - { - if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) - { - NETCHUNK packet; - - mem_zero(&packet, sizeof(NETCHUNK)); - - version_serveraddr.addr.port = VERSIONSRV_PORT; - - packet.client_id = -1; - packet.address = version_serveraddr.addr; - packet.data = VERSIONSRV_GETVERSION; - packet.data_size = sizeof(VERSIONSRV_GETVERSION); - packet.flags = NETSENDFLAG_CONNLESS; - - netclient_send(net, &packet); - state++; - } - } -} - -extern int editor_update_and_render(); -extern void editor_init(); - -static void client_run() -{ - NETADDR bindaddr; - int64 reporttime = time_get(); - int64 reportinterval = time_freq()*1; - - static PERFORMACE_INFO rootscope = {"root", 0}; - perf_start(&rootscope); - - local_start_time = time_get(); - snapshot_parts = 0; - - /* init graphics and sound */ - if(gfx_init() != 0) - return; - - /* start refreshing addresses while we load */ - mastersrv_refresh_addresses(); - - /* init the editor */ - editor_init(); - - /* sound is allowed to fail */ - snd_init(); - - /* load data */ - if(!client_load_data()) - return; - - /* init the mod */ - modc_init(); - dbg_msg("client", "version %s", modc_net_version()); - - /* open socket */ - mem_zero(&bindaddr, sizeof(bindaddr)); - net = netclient_open(bindaddr, 0); - - /* connect to the server if wanted */ - /* - if(config.cl_connect[0] != 0) - client_connect(config.cl_connect); - config.cl_connect[0] = 0; - */ - - /* */ - graph_init(&fps_graph, 0.0f, 200.0f); - - /* never start with the editor */ - config.cl_editor = 0; - - inp_mouse_mode_relative(); - - while (1) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 frame_start_time = time_get(); - frames++; - - perf_start(&rootscope); - - /* */ - client_versionupdate(); - - /* handle pending connects */ - if(cmd_connect[0]) - { - client_connect(cmd_connect); - cmd_connect[0] = 0; - } - - /* update input */ - { - static PERFORMACE_INFO scope = {"inp_update", 0}; - perf_start(&scope); - inp_update(); - perf_end(); - } - - /* update sound */ - { - static PERFORMACE_INFO scope = {"snd_update", 0}; - perf_start(&scope); - snd_update(); - perf_end(); - } - - /* release focus */ - if(!gfx_window_active()) - { - if(window_must_refocus == 0) - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) - { - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - - /* refocus */ - if(window_must_refocus && gfx_window_active()) - { - if(window_must_refocus < 3) - { - inp_mouse_mode_absolute(); - window_must_refocus++; - } - - if(inp_key_pressed(KEY_MOUSE_1)) - { - inp_mouse_mode_relative(); - window_must_refocus = 0; - } - } - - /* panic quit button */ - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) - break; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) - config.debug ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) - config.dbg_graphs ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) - { - config.cl_editor = config.cl_editor^1; - inp_mouse_mode_relative(); - } - - /* - if(!gfx_window_open()) - break; - */ - - /* render */ - if(config.cl_editor) - { - client_update(); - editor_update_and_render(); - gfx_swap(); - } - else - { - { - static PERFORMACE_INFO scope = {"client_update", 0}; - perf_start(&scope); - client_update(); - perf_end(); - } - - if(config.dbg_stress) - { - if((frames%10) == 0) - { - client_render(); - gfx_swap(); - } - } - else - { - { - static PERFORMACE_INFO scope = {"client_render", 0}; - perf_start(&scope); - client_render(); - perf_end(); - } - - { - static PERFORMACE_INFO scope = {"gfx_swap", 0}; - perf_start(&scope); - gfx_swap(); - perf_end(); - } - } - } - - perf_end(); - - - /* check conditions */ - if(client_state() == CLIENTSTATE_QUITING) - break; - - /* be nice */ - if(config.dbg_stress) - thread_sleep(5); - else if(config.cl_cpu_throttle || !gfx_window_active()) - thread_sleep(1); - - if(config.dbg_hitch) - { - thread_sleep(config.dbg_hitch); - config.dbg_hitch = 0; - } - - if(reporttime < time_get()) - { - if(0 && config.debug) - { - dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", - frames/(float)(reportinterval/time_freq()), - 1.0f/frametime_high, - 1.0f/frametime_low, - netclient_state(net)); - } - frametime_low = 1; - frametime_high = 0; - frames = 0; - reporttime += reportinterval; - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - } - - /* update frametime */ - frametime = (time_get()-frame_start_time)/(float)time_freq(); - if(frametime < frametime_low) - frametime_low = frametime; - if(frametime > frametime_high) - frametime_high = frametime; - - graph_add(&fps_graph, 1.0f/frametime, 1,1,1); - } - - modc_shutdown(); - client_disconnect(); - - gfx_shutdown(); - snd_shutdown(); -} - -static void con_connect(void *result, void *user_data) -{ - str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); -} - -static void con_disconnect(void *result, void *user_data) -{ - client_disconnect(); -} - -static void con_quit(void *result, void *user_data) -{ - client_quit(); -} - -static void con_ping(void *result, void *user_data) -{ - msg_pack_start_system(NETMSG_PING, 0); - msg_pack_end(); - client_send_msg(); - ping_start_time = time_get(); -} - -static void con_screenshot(void *result, void *user_data) -{ - gfx_screenshot(); -} - -static void con_rcon(void *result, void *user_data) -{ - client_rcon(console_arg_string(result, 0)); -} - -static void con_rcon_auth(void *result, void *user_data) -{ - client_rcon_auth("", console_arg_string(result, 0)); -} - -static void con_addfavorite(void *result, void *user_data) -{ - NETADDR addr; - if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) - client_serverbrowse_addfavorite(addr); -} - -const char *client_demoplayer_play(const char *filename) -{ - int crc; - const char *error; - client_disconnect(); - netclient_error_string_reset(net); - - /* try to start playback */ - demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); - - if(demorec_playback_load(filename)) - return "error loading demo"; - - /* load map */ - crc = (demorec_playback_info()->header.crc[0]<<24)| - (demorec_playback_info()->header.crc[1]<<16)| - (demorec_playback_info()->header.crc[2]<<8)| - (demorec_playback_info()->header.crc[3]); - error = client_load_map_search(demorec_playback_info()->header.map, crc); - if(error) - { - client_disconnect_with_reason(error); - return error; - } - - modc_connected(); - - /* setup buffers */ - mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); - - snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; - snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; - - snapshots[SNAP_CURRENT]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][0]; - snapshots[SNAP_CURRENT]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][1]; - snapshots[SNAP_CURRENT]->snap_size = 0; - snapshots[SNAP_CURRENT]->tick = -1; - - snapshots[SNAP_PREV]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][0]; - snapshots[SNAP_PREV]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][1]; - snapshots[SNAP_PREV]->snap_size = 0; - snapshots[SNAP_PREV]->tick = -1; - - /* enter demo playback state */ - client_set_state(CLIENTSTATE_DEMOPLAYBACK); - - demorec_playback_play(); - modc_entergame(); - - return 0; -} - -static void con_play(void *result, void *user_data) -{ - client_demoplayer_play(console_arg_string(result, 0)); -} - -static void con_record(void *result, void *user_data) -{ - if(state != CLIENTSTATE_ONLINE) - dbg_msg("demorec/record", "client is not online"); - else - { - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); - demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); - } -} - -static void con_stoprecord(void *result, void *user_data) -{ - demorec_record_stop(); -} - -static void con_serverdummy(void *result, void *user_data) -{ - dbg_msg("client", "this command is not available on the client"); -} - -static void client_register_commands() -{ - MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); - MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); - MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); - MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); - MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); - MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); - - MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); - - MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); - - /* register server dummy commands for tab completion */ - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); - /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ -} - -void client_save_line(const char *line) -{ - engine_config_write_line(line); -} - -const char *client_user_directory() -{ - static char path[1024] = {0}; - fs_storage_path("Teeworlds", path, sizeof(path)); - return path; -} - -#if defined(CONF_PLATFORM_MACOSX) -int SDL_main(int argc, char **argv) -#else -int main(int argc, char **argv) -#endif -{ - /* init the engine */ - dbg_msg("client", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - client_register_commands(); - modc_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* execute config file */ - console_execute_file("settings.cfg"); - - /* run the client*/ - client_run(); - - /* write down the config and quit */ - if(engine_config_write_start() == 0) - { - config_save(); - client_serverbrowse_save(); - modc_save_config(); - engine_config_write_stop(); - } - - return 0; -} diff --git a/src/engine/client/ec_client.cpp b/src/engine/client/ec_client.cpp new file mode 100644 index 00000000..7ba0a2bb --- /dev/null +++ b/src/engine/client/ec_client.cpp @@ -0,0 +1,2087 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "editor.h" +#include "graphics.h" +#include "client.h" + +static IEditor *m_pEditor = 0; +static IEngineGraphics *m_pGraphics = 0; +IEngineGraphics *Graphics() { return m_pGraphics; } + +static IGameClient *m_pGameClient = 0; + + +class CClient : public IEngine +{ +public: + virtual class IGraphics *Graphics() + { + return m_pGraphics; + } +}; + +static CClient m_Client; + +const int prediction_margin = 1000/50/2; /* magic network prediction value */ + +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ + +/* network client, must be accessible from other parts like the server browser */ +CNetClient m_NetClient; + +/* TODO: ugly, fix me */ +extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); +extern void client_serverbrowse_save(); + +static unsigned snapshot_parts; +static int64 local_start_time; + +static int debug_font; +static float frametime = 0.0001f; +static float frametime_low = 1.0f; +static float frametime_high = 0.0f; +static int frames = 0; +static NETADDR server_address; +static int window_must_refocus = 0; +static int snapcrcerrors = 0; + +static int ack_game_tick = -1; +static int current_recv_tick = 0; +static int rcon_authed = 0; + +/* version-checking */ +static char versionstr[10] = "0"; + +/* pinging */ +static int64 ping_start_time = 0; + +/* */ +static char current_map[256] = {0}; +static int current_map_crc = 0; + +/* */ +static char cmd_connect[256] = {0}; + +/* map download */ +static char mapdownload_filename[256] = {0}; +static char mapdownload_name[256] = {0}; +static IOHANDLE mapdownload_file = 0; +static int mapdownload_chunk = 0; +static int mapdownload_crc = 0; +static int mapdownload_amount = -1; +static int mapdownload_totalsize = -1; + +/* */ +static SERVER_INFO current_server_info = {0}; +static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ + +/* current time */ +static int current_tick = 0; +static float intratick = 0; +static float ticktime = 0; +static int prev_tick = 0; + +/* */ +/*static int predictiontime_pingspikecounter = 0; +static int gametime_pingspikecounter = 0;*/ + +/* predicted time */ +static int current_predtick = 0; +static float predintratick = 0; +static int last_input_timeleft = 0; + +static struct /* TODO: handle input better */ +{ + int data[MAX_INPUT_SIZE]; /* the input data */ + int tick; /* the tick that the input is for */ + int64 predicted_time; /* prediction latency when we sent this input */ + int64 time; +} inputs[200]; +static int current_input = 0; + +enum +{ + GRAPH_MAX=128 +}; + +typedef struct +{ + float min, max; + float values[GRAPH_MAX]; + float colors[GRAPH_MAX][3]; + int index; +} GRAPH; + +static void graph_init(GRAPH *g, float min, float max) +{ + g->min = min; + g->max = max; + g->index = 0; +} + +static void graph_scale_max(GRAPH *g) +{ + int i = 0; + g->max = 0; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] > g->max) + g->max = g->values[i]; + } +} + +static void graph_scale_min(GRAPH *g) +{ + int i = 0; + g->min = g->max; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] < g->min) + g->min = g->values[i]; + } +} + +static void graph_add(GRAPH *graph, float v, float r, float g, float b) +{ + graph->index = (graph->index+1)&(GRAPH_MAX-1); + graph->values[graph->index] = v; + graph->colors[graph->index][0] = r; + graph->colors[graph->index][1] = g; + graph->colors[graph->index][2] = b; +} + +static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) +{ + char buf[32]; + int i; + + //m_pGraphics->BlendNormal(); + + + Graphics()->TextureSet(-1); + + m_pGraphics->QuadsBegin(); + Graphics()->SetColor(0, 0, 0, 0.75f); + Graphics()->QuadsDrawTL(x, y, w, h); + m_pGraphics->QuadsEnd(); + + Graphics()->LinesBegin(); + Graphics()->SetColor(0.95f, 0.95f, 0.95f, 1.00f); + Graphics()->LinesDraw(x, y+h/2, x+w, y+h/2); + Graphics()->SetColor(0.5f, 0.5f, 0.5f, 0.75f); + Graphics()->LinesDraw(x, y+(h*3)/4, x+w, y+(h*3)/4); + Graphics()->LinesDraw(x, y+h/4, x+w, y+h/4); + for(i = 1; i < GRAPH_MAX; i++) + { + float a0 = (i-1)/(float)GRAPH_MAX; + float a1 = i/(float)GRAPH_MAX; + int i0 = (g->index+i-1)&(GRAPH_MAX-1); + int i1 = (g->index+i)&(GRAPH_MAX-1); + + float v0 = (g->values[i0]-g->min) / (g->max-g->min); + float v1 = (g->values[i1]-g->min) / (g->max-g->min); + + Graphics()->SetColorVertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); + Graphics()->SetColorVertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); + Graphics()->LinesDraw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); + + } + Graphics()->LinesEnd(); + + + Graphics()->TextureSet(debug_font); + Graphics()->QuadsText(x+2, y+h-16, 16, 1,1,1,1, description); + + str_format(buf, sizeof(buf), "%.2f", g->max); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); + + str_format(buf, sizeof(buf), "%.2f", g->min); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); + +} + +typedef struct +{ + int64 snap; + int64 current; + int64 target; + + int64 rlast; + int64 tlast; + GRAPH graph; + + int spikecounter; + + float adjustspeed[2]; /* 0 = down, 1 = up */ +} SMOOTHTIME; + +static void st_init(SMOOTHTIME *st, int64 target) +{ + st->snap = time_get(); + st->current = target; + st->target = target; + st->adjustspeed[0] = 0.3f; + st->adjustspeed[1] = 0.3f; + graph_init(&st->graph, 0.0f, 0.5f); +} + +static int64 st_get(SMOOTHTIME *st, int64 now) +{ + float adjust_speed, a; + int64 c = st->current + (now - st->snap); + int64 t = st->target + (now - st->snap); + int64 r; + + /* it's faster to adjust upward instead of downward */ + /* we might need to adjust these abit */ + + adjust_speed = st->adjustspeed[0]; + if(t > c) + adjust_speed = st->adjustspeed[1]; + + a = ((now-st->snap)/(float)time_freq()) * adjust_speed; + if(a > 1.0f) + a = 1.0f; + + r = c + (int64)((t-c)*a); + + graph_add(&st->graph, a+0.5f,1,1,1); + + return r; +} + +static void st_update_int(SMOOTHTIME *st, int64 target) +{ + int64 now = time_get(); + st->current = st_get(st, now); + st->snap = now; + st->target = target; +} + +static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) +{ + int update_timer = 1; + + if(time_left < 0) + { + int is_spike = 0; + if(time_left < -50) + { + is_spike = 1; + + st->spikecounter += 5; + if(st->spikecounter > 50) + st->spikecounter = 50; + } + + if(is_spike && st->spikecounter < 15) + { + /* ignore this ping spike */ + update_timer = 0; + graph_add(graph, time_left, 1,1,0); + } + else + { + graph_add(graph, time_left, 1,0,0); + if(st->adjustspeed[adjust_direction] < 30.0f) + st->adjustspeed[adjust_direction] *= 2.0f; + } + } + else + { + if(st->spikecounter) + st->spikecounter--; + + graph_add(graph, time_left, 0,1,0); + + st->adjustspeed[adjust_direction] *= 0.95f; + if(st->adjustspeed[adjust_direction] < 2.0f) + st->adjustspeed[adjust_direction] = 2.0f; + } + + last_input_timeleft = time_left; + + if(update_timer) + st_update_int(st, target); +} + +static SMOOTHTIME game_time; +static SMOOTHTIME predicted_time; + +/* graphs */ +static GRAPH inputtime_margin_graph; +static GRAPH gametime_margin_graph; +static GRAPH fps_graph; + +/* -- snapshot handling --- */ +enum +{ + NUM_SNAPSHOT_TYPES=2 +}; + +/* the game snapshots are modifiable by the game */ +CSnapshotStorage snapshot_storage; +static CSnapshotStorage::CHolder *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; + +static int recived_snapshots = 0; +static char snapshot_incomming_data[CSnapshot::MAX_SIZE]; + +static CSnapshotStorage::CHolder demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; +static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + +/* --- */ + +void *snap_get_item(int snapid, int index, SNAP_ITEM *item) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + item->datasize = snapshots[snapid]->m_pAltSnap->GetItemSize(index); + item->type = i->Type(); + item->id = i->ID(); + return (void *)i->Data(); +} + +void snap_invalidate_item(int snapid, int index) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + if(i) + { + if((char *)i < (char *)snapshots[snapid]->m_pAltSnap || (char *)i > (char *)snapshots[snapid]->m_pAltSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + if((char *)i >= (char *)snapshots[snapid]->m_pSnap && (char *)i < (char *)snapshots[snapid]->m_pSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + i->m_TypeAndID = -1; + } +} + +void *snap_find_item(int snapid, int type, int id) +{ + /* TODO: linear search. should be fixed. */ + int i; + + if(!snapshots[snapid]) + return 0x0; + + for(i = 0; i < snapshots[snapid]->m_pSnap->m_NumItems; i++) + { + CSnapshotItem *itm = snapshots[snapid]->m_pAltSnap->GetItem(i); + if(itm->Type() == type && itm->ID() == id) + return (void *)itm->Data(); + } + return 0x0; +} + +int snap_num_items(int snapid) +{ + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + if(!snapshots[snapid]) + return 0; + return snapshots[snapid]->m_pSnap->m_NumItems; +} + +/* ------ time functions ------ */ +float client_intratick() { return intratick; } +float client_predintratick() { return predintratick; } +float client_ticktime() { return ticktime; } +int client_tick() { return current_tick; } +int client_prevtick() { return prev_tick; } +int client_predtick() { return current_predtick; } +int client_tickspeed() { return SERVER_TICK_SPEED; } +float client_frametime() { return frametime; } +float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } + +/* ----- send functions ----- */ +int client_send_msg() +{ + const MSG_INFO *pInfo = msg_get_info(); + CNetChunk Packet; + + if(!pInfo) + return -1; + + if(client_state() == CLIENTSTATE_OFFLINE) + return 0; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = 0; + Packet.m_pData = pInfo->data; + Packet.m_DataSize = pInfo->size; + + if(pInfo->flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(pInfo->flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + if(pInfo->flags&MSGFLAG_RECORD) + { + if(demorec_isrecording()) + demorec_record_message(Packet.m_pData, Packet.m_DataSize); + } + + if(!(pInfo->flags&MSGFLAG_NOSEND)) + m_NetClient.Send(&Packet); + return 0; +} + +static void client_send_info() +{ + msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(modc_net_version(), 128); + msg_pack_string(config.player_name, 128); + msg_pack_string(config.clan_name, 128); + msg_pack_string(config.password, 128); + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_entergame() +{ + msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +static void client_send_ready() +{ + msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +int client_rcon_authed() +{ + return rcon_authed; +} + +void client_rcon_auth(const char *name, const char *password) +{ + msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); + msg_pack_string(name, 32); + msg_pack_string(password, 32); + msg_pack_end(); + client_send_msg(); +} + +void client_rcon(const char *cmd) +{ + msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); + msg_pack_string(cmd, 256); + msg_pack_end(); + client_send_msg(); +} + +int client_connection_problems() +{ + return m_NetClient.GotProblems(); +} + +void client_direct_input(int *input, int size) +{ + int i; + msg_pack_start_system(NETMSG_INPUT, 0); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + for(i = 0; i < size/4; i++) + msg_pack_int(input[i]); + + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_input() +{ + int64 now = time_get(); + int i, size; + + if(current_predtick <= 0) + return; + + /* fetch input */ + size = modc_snap_input(inputs[current_input].data); + + if(!size) + return; + + /* pack input */ + msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + inputs[current_input].tick = current_predtick; + inputs[current_input].predicted_time = st_get(&predicted_time, now); + inputs[current_input].time = now; + + /* pack it */ + for(i = 0; i < size/4; i++) + msg_pack_int(inputs[current_input].data[i]); + + current_input++; + current_input%=200; + + msg_pack_end(); + client_send_msg(); +} + +const char *client_latestversion() +{ + return versionstr; +} + +/* TODO: OPT: do this alot smarter! */ +int *client_get_input(int tick) +{ + int i; + int best = -1; + for(i = 0; i < 200; i++) + { + if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) + best = i; + } + + if(best != -1) + return (int *)inputs[best].data; + return 0; +} + +/* ------ state handling ----- */ +static int state = CLIENTSTATE_OFFLINE; +int client_state() { return state; } +static void client_set_state(int s) +{ + int old = state; + if(config.debug) + dbg_msg("client", "state change. last=%d current=%d", state, s); + state = s; + if(old != s) + modc_statechange(state, old); +} + +/* called when the map is loaded and we should init for a new round */ +static void client_on_enter_game() +{ + /* reset input */ + int i; + for(i = 0; i < 200; i++) + inputs[i].tick = -1; + current_input = 0; + + /* reset snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + snapshot_storage.PurgeAll(); + recived_snapshots = 0; + snapshot_parts = 0; + current_predtick = 0; + current_recv_tick = 0; +} + +void client_entergame() +{ + if(state == CLIENTSTATE_DEMOPLAYBACK) + return; + + /* now we will wait for two snapshots */ + /* to finish the connection */ + client_send_entergame(); + client_on_enter_game(); +} + +void client_connect(const char *server_address_str) +{ + char buf[512]; + const char *port_str = 0; + int k; + int port = 8303; + + client_disconnect(); + + dbg_msg("client", "connecting to '%s'", server_address_str); + + //client_serverinfo_request(); + str_copy(buf, server_address_str, sizeof(buf)); + + for(k = 0; buf[k]; k++) + { + if(buf[k] == ':') + { + port_str = &(buf[k+1]); + buf[k] = 0; + break; + } + } + + if(port_str) + port = atoi(port_str); + + /* TODO: IPv6 support */ + if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) + dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); + + rcon_authed = 0; + server_address.port = port; + m_NetClient.Connect(&server_address); + client_set_state(CLIENTSTATE_CONNECTING); + + graph_init(&inputtime_margin_graph, -150.0f, 150.0f); + graph_init(&gametime_margin_graph, -150.0f, 150.0f); +} + +void client_disconnect_with_reason(const char *reason) +{ + /* stop demo playback */ + demorec_playback_stop(); + + /* */ + rcon_authed = 0; + m_NetClient.Disconnect(reason); + client_set_state(CLIENTSTATE_OFFLINE); + map_unload(); + + /* disable all downloads */ + mapdownload_chunk = 0; + if(mapdownload_file) + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_crc = 0; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + /* clear the current server info */ + mem_zero(¤t_server_info, sizeof(current_server_info)); + mem_zero(&server_address, sizeof(server_address)); + + /* clear snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + recived_snapshots = 0; +} + +void client_disconnect() +{ + client_disconnect_with_reason(0); +} + + +void client_serverinfo(SERVER_INFO *serverinfo) +{ + mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); +} + +void client_serverinfo_request() +{ + mem_zero(¤t_server_info, sizeof(current_server_info)); + current_server_info_requesttime = 0; +} + +static int client_load_data() +{ + debug_font = Graphics()->LoadTexture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); + return 1; +} + +extern int snapshot_data_rate[0xffff]; +extern int snapshot_data_updates[0xffff]; + +const char *modc_getitemname(int type); + +static void client_debug_render() +{ + static NETSTATS prev, current; + static int64 last_snap = 0; + static float frametime_avg = 0; + int64 now = time_get(); + char buffer[512]; + + if(!config.debug) + return; + + //m_pGraphics->BlendNormal(); + Graphics()->TextureSet(debug_font); + Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); + + if(time_get()-last_snap > time_freq()) + { + last_snap = time_get(); + prev = current; + net_stats(¤t); + } + + /* + eth = 14 + ip = 20 + udp = 8 + total = 42 + */ + frametime_avg = frametime_avg*0.9f + frametime*0.1f; + str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: N/A fps: %3d", + current_tick, current_predtick, + mem_stats()->allocated/1024, + mem_stats()->total_allocations, + /*gfx_memory_usage()/1024, */ // TODO: Refactor: Reenable this + (int)(1.0f/frametime_avg)); + Graphics()->QuadsText(2, 2, 16, 1,1,1,1, buffer); + + + { + int send_packets = (current.sent_packets-prev.sent_packets); + int send_bytes = (current.sent_bytes-prev.sent_bytes); + int send_total = send_bytes + send_packets*42; + int recv_packets = (current.recv_packets-prev.recv_packets); + int recv_bytes = (current.recv_bytes-prev.recv_bytes); + int recv_total = recv_bytes + recv_packets*42; + + if(!send_packets) send_packets++; + if(!recv_packets) recv_packets++; + str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", + send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, + recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); + Graphics()->QuadsText(2, 14, 16, 1,1,1,1, buffer); + } + + /* render rates */ + { + int y = 0; + int i; + for(i = 0; i < 256; i++) + { + if(snapshot_data_rate[i]) + { + str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], + (snapshot_data_rate[i]/snapshot_data_updates[i])/8); + Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, buffer); + y++; + } + } + } + + str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", + (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), + predicted_time.adjustspeed[1]); + Graphics()->QuadsText(2, 70, 16, 1,1,1,1, buffer); + + /* render graphs */ + if(config.dbg_graphs) + { + //Graphics()->MapScreen(0,0,400.0f,300.0f); + float w = Graphics()->ScreenWidth()/4.0f; + float h = Graphics()->ScreenHeight()/6.0f; + float sp = Graphics()->ScreenWidth()/100.0f; + float x = Graphics()->ScreenWidth()-w-sp; + + graph_scale_max(&fps_graph); + graph_scale_min(&fps_graph); + graph_render(&fps_graph, x, sp*5, w, h, "FPS"); + graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); + graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); + } +} + +void client_quit() +{ + client_set_state(CLIENTSTATE_QUITING); +} + +const char *client_error_string() +{ + return m_NetClient.ErrorString(); +} + +static void client_render() +{ + if(config.gfx_clear) + Graphics()->Clear(1,1,0); + + modc_render(); + client_debug_render(); +} + +static const char *client_load_map(const char *name, const char *filename, int wanted_crc) +{ + static char errormsg[128]; + DATAFILE *df; + int crc; + + client_set_state(CLIENTSTATE_LOADING); + + df = datafile_load(filename); + if(!df) + { + str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); + return errormsg; + } + + /* get the crc of the map */ + crc = datafile_crc(filename); + if(crc != wanted_crc) + { + datafile_unload(df); + str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); + return errormsg; + } + + // stop demo recording if we loaded a new map + demorec_record_stop(); + + dbg_msg("client", "loaded map '%s'", filename); + recived_snapshots = 0; + map_set(df); + + str_copy(current_map, name, sizeof(current_map)); + current_map_crc = crc; + + return NULL; +} + +static const char *client_load_map_search(const char *mapname, int wanted_crc) +{ + const char *error = 0; + char buf[512]; + dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); + client_set_state(CLIENTSTATE_LOADING); + + /* try the normal maps folder */ + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + error = client_load_map(mapname, buf, wanted_crc); + if(!error) + return error; + + /* try the downloaded maps */ + str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); + error = client_load_map(mapname, buf, wanted_crc); + return error; +} + +static int player_score_comp(const void *a, const void *b) +{ + SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; + SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; + if(p0->score == p1->score) + return 0; + if(p0->score < p1->score) + return 1; + return -1; +} + +static void client_process_packet(CNetChunk *pPacket) +{ + if(pPacket->m_ClientID == -1) + { + /* connectionlesss */ + if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && + memcmp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) + { + unsigned char *versiondata = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); + int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); + + dbg_msg("client/version", "version does %s (%d.%d.%d)", + version_match ? "match" : "NOT match", + versiondata[1], versiondata[2], versiondata[3]); + + /* assume version is out of date when version-data doesn't match */ + if (!version_match) + { + sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); + } + } + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && + memcmp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + int size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); + int num = size/sizeof(MASTERSRV_ADDR); + MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); + int i; + + for(i = 0; i < num; i++) + { + NETADDR addr; + + /* convert address */ + mem_zero(&addr, sizeof(addr)); + addr.type = NETTYPE_IPV4; + addr.ip[0] = addrs[i].ip[0]; + addr.ip[1] = addrs[i].ip[1]; + addr.ip[2] = addrs[i].ip[2]; + addr.ip[3] = addrs[i].ip[3]; + addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; + + client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); + } + } + + { + int packet_type = 0; + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + packet_type = 2; + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) + packet_type = 1; + + if(packet_type) + { + /* we got ze info */ + CUnpacker up; + SERVER_INFO info = {0}; + int i; + int token = -1; + + up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); + if(packet_type >= 2) + token = atol(up.GetString()); + str_copy(info.version, up.GetString(), sizeof(info.version)); + str_copy(info.name, up.GetString(), sizeof(info.name)); + str_copy(info.map, up.GetString(), sizeof(info.map)); + str_copy(info.gametype, up.GetString(), sizeof(info.gametype)); + info.flags = atol(up.GetString()); + info.progression = atol(up.GetString()); + info.num_players = atol(up.GetString()); + info.max_players = atol(up.GetString()); + str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d", + pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], + pPacket->m_Address.ip[3], pPacket->m_Address.port); + + for(i = 0; i < info.num_players; i++) + { + str_copy(info.players[i].name, up.GetString(), sizeof(info.players[i].name)); + info.players[i].score = atol(up.GetString()); + } + + if(!up.Error()) + { + /* sort players */ + qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); + + if(net_addr_comp(&server_address, &pPacket->m_Address) == 0) + { + mem_copy(¤t_server_info, &info, sizeof(current_server_info)); + current_server_info.netaddr = server_address; + current_server_info_requesttime = -1; + } + else + { + if(packet_type == 2) + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_TOKEN, token, &info); + else + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_OLD_INTERNET, -1, &info); + } + } + } + } + } + else + { + int sys; + int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); + + if(sys) + { + /* system message */ + if(msg == NETMSG_MAP_CHANGE) + { + const char *map = msg_unpack_string(); + int map_crc = msg_unpack_int(); + const char *error = 0; + int i; + + if(msg_unpack_error()) + return; + + for(i = 0; map[i]; i++) /* protect the player from nasty map names */ + { + if(map[i] == '/' || map[i] == '\\') + error = "strange character in map name"; + } + + if(error) + client_disconnect_with_reason(error); + else + { + error = client_load_map_search(map, map_crc); + + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + { + str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); + + dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); + + mapdownload_chunk = 0; + str_copy(mapdownload_name, map, sizeof(mapdownload_name)); + mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); + mapdownload_crc = map_crc; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + } + else if(msg == NETMSG_MAP_DATA) + { + int last = msg_unpack_int(); + int total_size = msg_unpack_int(); + int size = msg_unpack_int(); + const unsigned char *data = msg_unpack_raw(size); + + /* check fior errors */ + if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) + return; + + io_write(mapdownload_file, data, size); + + mapdownload_totalsize = total_size; + mapdownload_amount += size; + + if(last) + { + const char *error; + dbg_msg("client/network", "download complete, loading map"); + + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_amount = 0; + mapdownload_totalsize = -1; + + /* load map */ + error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + client_disconnect_with_reason(error); + } + else + { + /* request new chunk */ + mapdownload_chunk++; + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + else if(msg == NETMSG_PING) + { + msg_pack_start_system(NETMSG_PING_REPLY, 0); + msg_pack_end(); + client_send_msg(); + } + else if(msg == NETMSG_RCON_AUTH_STATUS) + { + int result = msg_unpack_int(); + if(msg_unpack_error() == 0) + rcon_authed = result; + } + else if(msg == NETMSG_RCON_LINE) + { + const char *line = msg_unpack_string(); + if(msg_unpack_error() == 0) + { + /*dbg_msg("remote", "%s", line);*/ + modc_rcon_line(line); + } + } + else if(msg == NETMSG_PING_REPLY) + dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); + else if(msg == NETMSG_INPUTTIMING) + { + int input_predtick = msg_unpack_int(); + int time_left = msg_unpack_int(); + + /* adjust our prediction time */ + int k; + int64 target = 0; + for(k = 0; k < 200; k++) + { + if(inputs[k].tick == input_predtick) + { + target = inputs[k].predicted_time + (time_get() - inputs[k].time); + target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); + //st_update(&predicted_time, ); + break; + } + } + + if(target) + st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); + } + else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) + { + /*dbg_msg("client/network", "got snapshot"); */ + int num_parts = 1; + int part = 0; + int game_tick = msg_unpack_int(); + int delta_tick = game_tick-msg_unpack_int(); + int part_size = 0; + int crc = 0; + int complete_size = 0; + const char *data = 0; + + /* we are not allowed to process snapshot yet */ + if(client_state() < CLIENTSTATE_LOADING) + return; + + if(msg == NETMSG_SNAP) + { + num_parts = msg_unpack_int(); + part = msg_unpack_int(); + } + + if(msg != NETMSG_SNAPEMPTY) + { + crc = msg_unpack_int(); + part_size = msg_unpack_int(); + } + + data = (const char *)msg_unpack_raw(part_size); + + if(msg_unpack_error()) + return; + + if(game_tick >= current_recv_tick) + { + if(game_tick != current_recv_tick) + { + snapshot_parts = 0; + current_recv_tick = game_tick; + } + + /* TODO: clean this up abit */ + mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); + snapshot_parts |= 1<= 0) + { + int deltashot_size = snapshot_storage.Get(delta_tick, 0, &deltashot, 0); + + if(deltashot_size < 0) + { + /* couldn't find the delta snapshots that the server used */ + /* to compress this snapshot. force the server to resync */ + if(config.debug) + dbg_msg("client", "error, couldn't find the delta snapshot"); + + /* ack snapshot */ + /* TODO: combine this with the input message */ + ack_game_tick = -1; + return; + } + } + + /* decompress snapshot */ + deltadata = CSnapshot::EmptyDelta(); + deltasize = sizeof(int)*3; + + if(complete_size) + { + int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); + + if(intsize < 0) /* failure during decompression, bail */ + return; + + deltadata = tmpbuffer2; + deltasize = intsize; + } + + /* unpack delta */ + purgetick = delta_tick; + snapsize = CSnapshot::UnpackDelta(deltashot, (CSnapshot*)tmpbuffer3, deltadata, deltasize); + if(snapsize < 0) + { + dbg_msg("client", "delta unpack failed!"); + return; + } + + if(msg != NETMSG_SNAPEMPTY && ((CSnapshot*)tmpbuffer3)->Crc() != crc) + { + if(config.debug) + { + dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + snapcrcerrors, game_tick, crc, ((CSnapshot*)tmpbuffer3)->Crc(), complete_size, delta_tick); + } + + snapcrcerrors++; + if(snapcrcerrors > 10) + { + /* to many errors, send reset */ + ack_game_tick = -1; + client_send_input(); + snapcrcerrors = 0; + } + return; + } + else + { + if(snapcrcerrors) + snapcrcerrors--; + } + + /* purge old snapshots */ + purgetick = delta_tick; + if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + snapshot_storage.PurgeUntil(purgetick); + + /* add new */ + snapshot_storage.Add(game_tick, time_get(), snapsize, (CSnapshot*)tmpbuffer3, 1); + + /* add snapshot to demo */ + if(demorec_isrecording()) + { + + /* write tick marker */ + /* + DEMOREC_TICKMARKER marker; + marker.tick = game_tick; + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + demorec_record_write("SNAP", snapsize, tmpbuffer3); + */ + + /* write snapshot */ + demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); + } + + /* apply snapshot, cycle pointers */ + recived_snapshots++; + + current_recv_tick = game_tick; + + /* we got two snapshots until we see us self as connected */ + if(recived_snapshots == 2) + { + /* start at 200ms and work from there */ + st_init(&predicted_time, game_tick*time_freq()/50); + predicted_time.adjustspeed[1] = 1000.0f; + st_init(&game_time, (game_tick-1)*time_freq()/50); + snapshots[SNAP_PREV] = snapshot_storage.m_pFirst; + snapshots[SNAP_CURRENT] = snapshot_storage.m_pLast; + local_start_time = time_get(); + client_set_state(CLIENTSTATE_ONLINE); + } + + /* adjust game time */ + { + int64 now = st_get(&game_time, time_get()); + int64 tickstart = game_tick*time_freq()/50; + int64 time_left = (tickstart-now)*1000 / time_freq(); + /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ + st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); + } + + /* ack snapshot */ + ack_game_tick = game_tick; + } + } + } + } + else + { + /* game message */ + if(demorec_isrecording()) + demorec_record_message(pPacket->m_pData, pPacket->m_DataSize); + /* demorec_record_write("MESG", pPacket->data_size, ); */ + + modc_message(msg); + } + } +} + +int client_mapdownload_amount() { return mapdownload_amount; } +int client_mapdownload_totalsize() { return mapdownload_totalsize; } + +static void client_pump_network() +{ + + m_NetClient.Update(); + + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) + { + /* check for errors */ + if(client_state() != CLIENTSTATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE) + { + client_set_state(CLIENTSTATE_OFFLINE); + client_disconnect(); + dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString()); + } + + /* */ + if(client_state() == CLIENTSTATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE) + { + /* we switched to online */ + dbg_msg("client", "connected, sending info"); + client_set_state(CLIENTSTATE_LOADING); + client_send_info(); + } + } + + /* process packets */ + CNetChunk Packet; + while(m_NetClient.Recv(&Packet)) + client_process_packet(&Packet); +} + +static void client_democallback_snapshot(void *pData, int Size) +{ + /* update ticks, they could have changed */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + CSnapshotStorage::CHolder *temp; + current_tick = info->current_tick; + prev_tick = info->previous_tick; + + /* handle snapshots */ + temp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = temp; + + mem_copy(snapshots[SNAP_CURRENT]->m_pSnap, pData, Size); + mem_copy(snapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size); + + modc_newsnapshot(); + /*modc_predict();*/ +} + +static void client_democallback_message(void *data, int size) +{ + int sys = 0; + int msg = msg_unpack_start(data, size, &sys); + if(!sys) + modc_message(msg); +} + + +const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() +{ + static DEMOPLAYBACK_INFO ret; + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + ret.first_tick = info->first_tick; + ret.last_tick = info->last_tick; + ret.current_tick = info->current_tick; + ret.paused = info->paused; + ret.speed = info->speed; + return &ret; +} + +void client_demoplayer_setpos(float percent) +{ + demorec_playback_set(percent); +} + +void client_demoplayer_setspeed(float speed) +{ + demorec_playback_setspeed(speed); +} + +void client_demoplayer_setpause(int paused) +{ + if(paused) + demorec_playback_pause(); + else + demorec_playback_unpause(); +} + +static void client_update() +{ + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + { + demorec_playback_update(); + if(demorec_isplaying()) + { + /* update timers */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + current_tick = info->current_tick; + prev_tick = info->previous_tick; + intratick = info->intratick; + ticktime = info->ticktime; + } + else + { + /* disconnect on error */ + client_disconnect(); + } + } + else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) + { + /* switch snapshot */ + int repredict = 0; + int64 freq = time_freq(); + int64 now = st_get(&game_time, time_get()); + int64 pred_now = st_get(&predicted_time, time_get()); + + while(1) + { + CSnapshotStorage::CHolder *cur = snapshots[SNAP_CURRENT]; + int64 tickstart = (cur->m_Tick)*time_freq()/50; + + if(tickstart < now) + { + CSnapshotStorage::CHolder *next = snapshots[SNAP_CURRENT]->m_pNext; + if(next) + { + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = next; + + /* set ticks */ + current_tick = snapshots[SNAP_CURRENT]->m_Tick; + prev_tick = snapshots[SNAP_PREV]->m_Tick; + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + modc_newsnapshot(); + repredict = 1; + } + } + else + break; + } + else + break; + } + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + int64 curtick_start = (snapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50; + int64 prevtick_start = (snapshots[SNAP_PREV]->m_Tick)*time_freq()/50; + /*tg_add(&predicted_time_graph, pred_now, 0); */ + int prev_pred_tick = (int)(pred_now*50/time_freq()); + int new_pred_tick = prev_pred_tick+1; + static float last_predintra = 0; + + intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); + ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ + + curtick_start = new_pred_tick*time_freq()/50; + prevtick_start = prev_pred_tick*time_freq()/50; + predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); + + if(new_pred_tick < snapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) + { + dbg_msg("client", "prediction time reset!"); + st_init(&predicted_time, snapshots[SNAP_CURRENT]->m_Tick*time_freq()/50); + } + + if(new_pred_tick > current_predtick) + { + last_predintra = predintratick; + current_predtick = new_pred_tick; + repredict = 1; + + /* send input */ + client_send_input(); + } + + last_predintra = predintratick; + } + + /* only do sane predictions */ + if(repredict) + { + if(current_predtick > current_tick && current_predtick < current_tick+50) + modc_predict(); + } + + /* fetch server info if we don't have it */ + if(client_state() >= CLIENTSTATE_LOADING && + current_server_info_requesttime >= 0 && + time_get() > current_server_info_requesttime) + { + client_serverbrowse_request(&server_address); + current_server_info_requesttime = time_get()+time_freq()*2; + } + } + + /* STRESS TEST: join the server again */ + if(config.dbg_stress) + { + static int64 action_taken = 0; + int64 now = time_get(); + if(client_state() == CLIENTSTATE_OFFLINE) + { + if(now > action_taken+time_freq()*2) + { + dbg_msg("stress", "reconnecting!"); + client_connect(config.dbg_stress_server); + action_taken = now; + } + } + else + { + /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) + { + dbg_msg("stress", "disconnecting!"); + client_disconnect(); + action_taken = now; + }*/ + } + } + + /* pump the network */ + client_pump_network(); + + /* update the maser server registry */ + mastersrv_update(); + + /* update the server browser */ + client_serverbrowse_update(); +} + + +static void client_versionupdate() +{ + static int state = 0; + static HOSTLOOKUP version_serveraddr; + + if(state == 0) + { + engine_hostlookup(&version_serveraddr, config.cl_version_server); + state++; + } + else if(state == 1) + { + if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) + { + CNetChunk Packet; + + mem_zero(&Packet, sizeof(Packet)); + + version_serveraddr.addr.port = VERSIONSRV_PORT; + + Packet.m_ClientID = -1; + Packet.m_Address = version_serveraddr.addr; + Packet.m_pData = VERSIONSRV_GETVERSION; + Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION); + Packet.m_Flags = NETSENDFLAG_CONNLESS; + + m_NetClient.Send(&Packet); + state++; + } + } +} + +static void client_run() +{ + NETADDR bindaddr; + int64 reporttime = time_get(); + int64 reportinterval = time_freq()*1; + + static PERFORMACE_INFO rootscope = {"root", 0}; + perf_start(&rootscope); + + local_start_time = time_get(); + snapshot_parts = 0; + + /* init graphics and sound */ + m_pGraphics = CreateEngineGraphics(); + if(m_pGraphics->Init() != 0) + return; + + /* start refreshing addresses while we load */ + mastersrv_refresh_addresses(); + + /* init the editor */ + m_pEditor = CreateEditor(); + m_pEditor->Init(m_pGraphics); + + /* sound is allowed to fail */ + snd_init(); + + /* load data */ + if(!client_load_data()) + return; + + /* init the mod */ + m_pGameClient = CreateGameClient(&m_Client); + modc_init(); + dbg_msg("client", "version %s", modc_net_version()); + + /* open socket */ + mem_zero(&bindaddr, sizeof(bindaddr)); + m_NetClient.Open(bindaddr, 0); + + /* connect to the server if wanted */ + /* + if(config.cl_connect[0] != 0) + client_connect(config.cl_connect); + config.cl_connect[0] = 0; + */ + + /* */ + graph_init(&fps_graph, 0.0f, 200.0f); + + /* never start with the editor */ + config.cl_editor = 0; + + inp_mouse_mode_relative(); + + while (1) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 frame_start_time = time_get(); + frames++; + + perf_start(&rootscope); + + /* */ + client_versionupdate(); + + /* handle pending connects */ + if(cmd_connect[0]) + { + client_connect(cmd_connect); + cmd_connect[0] = 0; + } + + /* update input */ + { + static PERFORMACE_INFO scope = {"inp_update", 0}; + perf_start(&scope); + inp_update(); + perf_end(); + } + + /* update sound */ + { + static PERFORMACE_INFO scope = {"snd_update", 0}; + perf_start(&scope); + snd_update(); + perf_end(); + } + + /* release focus */ + if(!Graphics()->WindowActive()) + { + if(window_must_refocus == 0) + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) + { + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + + /* refocus */ + if(window_must_refocus && Graphics()->WindowActive()) + { + if(window_must_refocus < 3) + { + inp_mouse_mode_absolute(); + window_must_refocus++; + } + + if(inp_key_pressed(KEY_MOUSE_1)) + { + inp_mouse_mode_relative(); + window_must_refocus = 0; + } + } + + /* panic quit button */ + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) + break; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) + config.debug ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) + config.dbg_graphs ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) + { + config.cl_editor = config.cl_editor^1; + inp_mouse_mode_relative(); + } + + /* + if(!gfx_window_open()) + break; + */ + + /* render */ + if(config.cl_editor) + { + client_update(); + m_pEditor->UpdateAndRender(); + m_pGraphics->Swap(); + } + else + { + { + static PERFORMACE_INFO scope = {"client_update", 0}; + perf_start(&scope); + client_update(); + perf_end(); + } + + if(config.dbg_stress) + { + if((frames%10) == 0) + { + client_render(); + m_pGraphics->Swap(); + } + } + else + { + { + static PERFORMACE_INFO scope = {"client_render", 0}; + perf_start(&scope); + client_render(); + perf_end(); + } + + { + static PERFORMACE_INFO scope = {"gfx_swap", 0}; + perf_start(&scope); + m_pGraphics->Swap(); + perf_end(); + } + } + } + + perf_end(); + + + /* check conditions */ + if(client_state() == CLIENTSTATE_QUITING) + break; + + /* be nice */ + if(config.dbg_stress) + thread_sleep(5); + else if(config.cl_cpu_throttle || !Graphics()->WindowActive()) + thread_sleep(1); + + if(config.dbg_hitch) + { + thread_sleep(config.dbg_hitch); + config.dbg_hitch = 0; + } + + if(reporttime < time_get()) + { + if(0 && config.debug) + { + dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", + frames/(float)(reportinterval/time_freq()), + 1.0f/frametime_high, + 1.0f/frametime_low, + m_NetClient.State()); + } + frametime_low = 1; + frametime_high = 0; + frames = 0; + reporttime += reportinterval; + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + } + + /* update frametime */ + frametime = (time_get()-frame_start_time)/(float)time_freq(); + if(frametime < frametime_low) + frametime_low = frametime; + if(frametime > frametime_high) + frametime_high = frametime; + + graph_add(&fps_graph, 1.0f/frametime, 1,1,1); + } + + modc_shutdown(); + client_disconnect(); + + m_pGraphics->Shutdown(); + snd_shutdown(); +} + +void gfx_swap() +{ + m_pGraphics->Swap(); +} + +static void con_connect(void *result, void *user_data) +{ + str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); +} + +static void con_disconnect(void *result, void *user_data) +{ + client_disconnect(); +} + +static void con_quit(void *result, void *user_data) +{ + client_quit(); +} + +static void con_ping(void *result, void *user_data) +{ + msg_pack_start_system(NETMSG_PING, 0); + msg_pack_end(); + client_send_msg(); + ping_start_time = time_get(); +} + +static void con_screenshot(void *result, void *user_data) +{ + Graphics()->TakeScreenshot(); +} + +static void con_rcon(void *result, void *user_data) +{ + client_rcon(console_arg_string(result, 0)); +} + +static void con_rcon_auth(void *result, void *user_data) +{ + client_rcon_auth("", console_arg_string(result, 0)); +} + +static void con_addfavorite(void *result, void *user_data) +{ + NETADDR addr; + if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) + client_serverbrowse_addfavorite(addr); +} + +const char *client_demoplayer_play(const char *filename) +{ + int crc; + const char *error; + client_disconnect(); + m_NetClient.ResetErrorString(); + + /* try to start playback */ + demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); + + if(demorec_playback_load(filename)) + return "error loading demo"; + + /* load map */ + crc = (demorec_playback_info()->header.crc[0]<<24)| + (demorec_playback_info()->header.crc[1]<<16)| + (demorec_playback_info()->header.crc[2]<<8)| + (demorec_playback_info()->header.crc[3]); + error = client_load_map_search(demorec_playback_info()->header.map, crc); + if(error) + { + client_disconnect_with_reason(error); + return error; + } + + modc_connected(); + + /* setup buffers */ + mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); + + snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; + snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; + + snapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][0]; + snapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][1]; + snapshots[SNAP_CURRENT]->m_SnapSize = 0; + snapshots[SNAP_CURRENT]->m_Tick = -1; + + snapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][0]; + snapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][1]; + snapshots[SNAP_PREV]->m_SnapSize = 0; + snapshots[SNAP_PREV]->m_Tick = -1; + + /* enter demo playback state */ + client_set_state(CLIENTSTATE_DEMOPLAYBACK); + + demorec_playback_play(); + modc_entergame(); + + return 0; +} + +static void con_play(void *result, void *user_data) +{ + client_demoplayer_play(console_arg_string(result, 0)); +} + +static void con_record(void *result, void *user_data) +{ + if(state != CLIENTSTATE_ONLINE) + dbg_msg("demorec/record", "client is not online"); + else + { + char filename[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); + demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); + } +} + +static void con_stoprecord(void *result, void *user_data) +{ + demorec_record_stop(); +} + +static void con_serverdummy(void *result, void *user_data) +{ + dbg_msg("client", "this command is not available on the client"); +} + +static void client_register_commands() +{ + MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); + MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); + MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); + MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); + MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); + MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); + + MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); + MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); + + MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); + + /* register server dummy commands for tab completion */ + MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); + MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); + MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); + MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); + MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); + MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); + /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ +} + +void client_save_line(const char *line) +{ + engine_config_write_line(line); +} + +const char *client_user_directory() +{ + static char path[1024] = {0}; + fs_storage_path("Teeworlds", path, sizeof(path)); + return path; +} + +#if defined(CONF_PLATFORM_MACOSX) +int SDL_main(int argc, char **argv) +#else +int main(int argc, char **argv) +#endif +{ + /* init the engine */ + dbg_msg("client", "starting..."); + engine_init("Teeworlds"); + + /* register all console commands */ + client_register_commands(); + modc_console_init(); + + /* parse the command line arguments */ + engine_parse_arguments(argc, argv); + + /* execute config file */ + console_execute_file("settings.cfg"); + + /* run the client*/ + client_run(); + + /* write down the config and quit */ + if(engine_config_write_start() == 0) + { + config_save(); + client_serverbrowse_save(); + modc_save_config(); + engine_config_write_stop(); + } + + return 0; +} diff --git a/src/engine/client/ec_gfx.c b/src/engine/client/ec_gfx.c deleted file mode 100644 index 1ea9f407..00000000 --- a/src/engine/client/ec_gfx.c +++ /dev/null @@ -1,1020 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include - -#include "SDL.h" - -#ifdef CONF_FAMILY_WINDOWS - #define WIN32_LEAN_AND_MEAN - #include -#endif - -#ifdef CONF_PLATFORM_MACOSX - #include - #include -#else - #include - #include -#endif - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -/* compressed textures */ -#define GL_COMPRESSED_RGB_ARB 0x84ED -#define GL_COMPRESSED_RGBA_ARB 0x84EE -#define GL_COMPRESSED_ALPHA_ARB 0x84E9 - -#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE - -enum -{ - DRAWING_QUADS=1, - DRAWING_LINES=2 -}; - -/* */ -typedef struct { float x, y, z; } VEC3; -typedef struct { float u, v; } TEXCOORD; -typedef struct { float r, g, b, a; } COLOR; - -typedef struct -{ - VEC3 pos; - TEXCOORD tex; - COLOR color; -} VERTEX; - -const int vertex_buffer_size = 32*1024; -static VERTEX *vertices = 0; -static int num_vertices = 0; - -static int no_gfx = 0; - -static COLOR color[4]; -static TEXCOORD texture[4]; - -static int do_screenshot = 0; -static int render_enable = 1; - -static int screen_width = -1; -static int screen_height = -1; -static float rotation = 0; -static int drawing = 0; - -static float screen_x0 = 0; -static float screen_y0 = 0; -static float screen_x1 = 0; -static float screen_y1 = 0; - -static int invalid_texture = 0; - -typedef struct -{ - GLuint tex; - int memsize; - int flags; - int next; -} TEXTURE; - -enum -{ - MAX_TEXTURES = 1024*4 -}; - -static TEXTURE textures[MAX_TEXTURES]; -static int first_free_texture; -static int memory_usage = 0; - -static SDL_Surface *screen_surface; - -static const unsigned char null_texture_data[] = { - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, -}; - -static void flush() -{ - if(num_vertices == 0) - return; - - if(no_gfx) - { - num_vertices = 0; - return; - } - - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glVertexPointer(3, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices); - glTexCoordPointer(2, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices + sizeof(float)*3); - glColorPointer(4, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices + sizeof(float)*5); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(render_enable) - { - if(drawing == DRAWING_QUADS) - glDrawArrays(GL_QUADS, 0, num_vertices); - else if(drawing == DRAWING_LINES) - glDrawArrays(GL_LINES, 0, num_vertices); - } - - /* Reset pointer */ - num_vertices = 0; -} - -static void add_vertices(int count) -{ - num_vertices += count; - if((num_vertices + count) >= vertex_buffer_size) - flush(); -} - -static int try_init() -{ - const SDL_VideoInfo *info; - int flags = SDL_OPENGL; - - screen_width = config.gfx_screen_width; - screen_height = config.gfx_screen_height; - - info = SDL_GetVideoInfo(); - - /* set flags */ - flags = SDL_OPENGL; - flags |= SDL_GL_DOUBLEBUFFER; - flags |= SDL_HWPALETTE; - if(config.dbg_resizable) - flags |= SDL_RESIZABLE; - - if(info->hw_available) - flags |= SDL_HWSURFACE; - else - flags |= SDL_SWSURFACE; - - if(info->blit_hw) - flags |= SDL_HWACCEL; - - if(config.gfx_fullscreen) - flags |= SDL_FULLSCREEN; - - /* set gl attributes */ - if(config.gfx_fsaa_samples) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples); - } - else - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, config.gfx_vsync); - - /* set caption */ - SDL_WM_SetCaption("Teeworlds", "Teeworlds"); - - /* create window */ - screen_surface = SDL_SetVideoMode(screen_width, screen_height, 0, flags); - if(screen_surface == NULL) - { - dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); - return -1; - } - - return 0; -} - -void gfx_font_init(); - -static int gfx_init_window() -{ - if(try_init() == 0) - return 0; - - /* try disabling fsaa */ - while(config.gfx_fsaa_samples) - { - config.gfx_fsaa_samples--; - - if(config.gfx_fsaa_samples) - dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples); - else - dbg_msg("gfx", "disabling FSAA and trying again"); - - if(try_init() == 0) - return 0; - } - - /* try lowering the resolution */ - if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480) - { - dbg_msg("gfx", "setting resolution to 640x480 and trying again"); - config.gfx_screen_width = 640; - config.gfx_screen_height = 480; - - if(try_init() == 0) - return 0; - } - - dbg_msg("gfx", "out of ideas. failed to init graphics"); - - return -1; -} - -int gfx_init() -{ - int i; - - if(config.dbg_stress) - no_gfx = 1; - - { - int systems = 0; - - if(!no_gfx) - systems |= SDL_INIT_VIDEO; - - if(config.snd_enable) - systems |= SDL_INIT_AUDIO; - - if(config.cl_eventthread) - systems |= SDL_INIT_EVENTTHREAD; - - if(SDL_Init(systems) < 0) - { - dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); - return -1; - } - } - - atexit(SDL_Quit); - - if(!no_gfx) - { - #ifdef CONF_FAMILY_WINDOWS - if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) - putenv("SDL_VIDEO_WINDOW_POS=8,27"); - #endif - - if(gfx_init_window() != 0) - return -1; - } - - /* Init vertices */ - if (vertices) - mem_free(vertices); - vertices = (VERTEX*)mem_alloc(sizeof(VERTEX) * vertex_buffer_size, 1); - num_vertices = 0; - - - /* - dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(), - context.version_minor(), - context.version_rev());*/ - - - /* Set all z to -5.0f */ - for (i = 0; i < vertex_buffer_size; i++) - vertices[i].pos.z = -5.0f; - - /* init textures */ - first_free_texture = 0; - for(i = 0; i < MAX_TEXTURES; i++) - textures[i].next = i+1; - textures[MAX_TEXTURES-1].next = -1; - - if(!no_gfx) - { - SDL_ShowCursor(0); - gfx_mapscreen(0,0,config.gfx_screen_width, config.gfx_screen_height); - - /* set some default settings */ - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); - - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - /* init input */ - inp_init(); - - /* create null texture, will get id=0 */ - invalid_texture = gfx_load_texture_raw(4,4,IMG_RGBA,null_texture_data,IMG_RGBA,TEXLOAD_NORESAMPLE); - dbg_msg("", "invalid texture id: %d %d", invalid_texture, textures[invalid_texture].tex); - - - /* font init */ - gfx_font_init(); - /* perform some tests */ - /* pixeltest_dotesting(); */ - - /*if(config.dbg_stress) - gfx_minimize();*/ - - return 0; -} - -float gfx_screenaspect() -{ - return gfx_screenwidth()/(float)gfx_screenheight(); -} - -int gfx_window_active() -{ - return SDL_GetAppState()&SDL_APPINPUTFOCUS; -} - -int gfx_window_open() -{ - return SDL_GetAppState()&SDL_APPACTIVE; -} - -VIDEO_MODE fakemodes[] = { - {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, - {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, - {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, - {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, - {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, - {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, - {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, - {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, - {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, - {2048,1536,8,8,8}, - - {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, - {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, - {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, - {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, - {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, - {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, - {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, - {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, - {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, - {2048,1536,5,6,5} -}; - -int gfx_get_video_modes(VIDEO_MODE *list, int maxcount) -{ - int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE); - SDL_Rect **modes; - - if(config.gfx_display_all_modes) - { - int count = sizeof(fakemodes)/sizeof(VIDEO_MODE); - mem_copy(list, fakemodes, sizeof(fakemodes)); - if(maxcount < count) - count = maxcount; - return count; - } - - /* TODO: fix this code on osx or windows */ - - modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); - if(modes == NULL) - { - /* no modes */ - num_modes = 0; - } - else if(modes == (SDL_Rect**)-1) - { - /* all modes */ - } - else - { - int i; - num_modes = 0; - for(i = 0; modes[i]; ++i) - { - if(num_modes == maxcount) - break; - list[num_modes].width = modes[i]->w; - list[num_modes].height = modes[i]->h; - list[num_modes].red = 8; - list[num_modes].green = 8; - list[num_modes].blue = 8; - num_modes++; - } - } - - return num_modes; -} - -int gfx_unload_texture(int index) -{ - if(index == invalid_texture) - return 0; - - if(index < 0) - return 0; - - glDeleteTextures(1, &textures[index].tex); - textures[index].next = first_free_texture; - memory_usage -= textures[index].memsize; - first_free_texture = index; - return 0; -} - -void gfx_blend_none() -{ - if(no_gfx) return; - glDisable(GL_BLEND); -} - -void gfx_blend_normal() -{ - if(no_gfx) return; - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -void gfx_blend_additive() -{ - if(no_gfx) return; - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); -} - -int gfx_memory_usage() { return memory_usage; } - -static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset) -{ - return (data[(v*w+u)*4+offset]+ - data[(v*w+u+1)*4+offset]+ - data[((v+1)*w+u)*4+offset]+ - data[((v+1)*w+u+1)*4+offset])/4; -} - -int gfx_load_texture_raw(int w, int h, int format, const void *data, int store_format, int flags) -{ - int mipmap = 1; - unsigned char *texdata = (unsigned char *)data; - unsigned char *tmpdata = 0; - int oglformat = 0; - int store_oglformat = 0; - int tex = 0; - - /* don't waste memory on texture if we are stress testing */ - if(config.dbg_stress || no_gfx) - return invalid_texture; - - /* grab texture */ - tex = first_free_texture; - first_free_texture = textures[tex].next; - textures[tex].next = -1; - - /* resample if needed */ - if(!(flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0) - { - if(w > 16 && h > 16 && format == IMG_RGBA) - { - unsigned char *tmpdata; - int c = 0; - int x, y; - - tmpdata = (unsigned char *)mem_alloc(w*h*4, 1); - - w/=2; - h/=2; - - for(y = 0; y < h; y++) - for(x = 0; x < w; x++, c++) - { - tmpdata[c*4] = sample(w*2, h*2, texdata, x*2,y*2, 0); - tmpdata[c*4+1] = sample(w*2, h*2, texdata, x*2,y*2, 1); - tmpdata[c*4+2] = sample(w*2, h*2, texdata, x*2,y*2, 2); - tmpdata[c*4+3] = sample(w*2, h*2, texdata, x*2,y*2, 3); - } - texdata = tmpdata; - } - } - - oglformat = GL_RGBA; - if(format == IMG_RGB) - oglformat = GL_RGB; - else if(format == IMG_ALPHA) - oglformat = GL_ALPHA; - - /* upload texture */ - if(config.gfx_texture_compression) - { - store_oglformat = GL_COMPRESSED_RGBA_ARB; - if(store_format == IMG_RGB) - store_oglformat = GL_COMPRESSED_RGB_ARB; - else if(store_format == IMG_ALPHA) - store_oglformat = GL_COMPRESSED_ALPHA_ARB; - } - else - { - store_oglformat = GL_RGBA; - if(store_format == IMG_RGB) - store_oglformat = GL_RGB; - else if(store_format == IMG_ALPHA) - store_oglformat = GL_ALPHA; - } - - glGenTextures(1, &textures[tex].tex); - glBindTexture(GL_TEXTURE_2D, textures[tex].tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, w, h, oglformat, GL_UNSIGNED_BYTE, texdata); - - /* calculate memory usage */ - { - int pixel_size = 4; - if(store_format == IMG_RGB) - pixel_size = 3; - else if(store_format == IMG_ALPHA) - pixel_size = 1; - - textures[tex].memsize = w*h*pixel_size; - if(mipmap) - { - while(w > 2 && h > 2) - { - w>>=1; - h>>=1; - textures[tex].memsize += w*h*pixel_size; - } - } - } - - memory_usage += textures[tex].memsize; - mem_free(tmpdata); - return tex; -} - -/* simple uncompressed RGBA loaders */ -int gfx_load_texture(const char *filename, int store_format, int flags) -{ - int l = strlen(filename); - int id; - IMAGE_INFO img; - - if(l < 3) - return -1; - if(gfx_load_png(&img, filename)) - { - if (store_format == IMG_AUTO) - store_format = img.format; - - id = gfx_load_texture_raw(img.width, img.height, img.format, img.data, store_format, flags); - mem_free(img.data); - return id; - } - - return invalid_texture; -} - -int gfx_load_png(IMAGE_INFO *img, const char *filename) -{ - char completefilename[512]; - unsigned char *buffer; - png_t png; - - /* open file for reading */ - png_init(0,0); - - engine_getpath(completefilename, sizeof(completefilename), filename, IOFLAG_READ); - - if(png_open_file(&png, completefilename) != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s'", completefilename); - return 0; - } - - if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA)) - { - dbg_msg("game/png", "invalid format. filename='%s'", completefilename); - png_close_file(&png); - return 0; - } - - buffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1); - png_get_data(&png, buffer); - png_close_file(&png); - - img->width = png.width; - img->height = png.height; - if(png.color_type == PNG_TRUECOLOR) - img->format = IMG_RGB; - else if(png.color_type == PNG_TRUECOLOR_ALPHA) - img->format = IMG_RGBA; - img->data = buffer; - return 1; -} - -void gfx_shutdown() -{ - if (vertices) - mem_free(vertices); - - /* TODO: SDL, is this correct? */ - SDL_Quit(); -} - -void gfx_screenshot() -{ - do_screenshot = 1; -} - - -extern int text_render_codepaths_usage[5]; - -void gfx_swap() -{ - /*dbg_msg("", "%d %d %d %d %d", - text_render_codepaths_usage[0], - text_render_codepaths_usage[1], - text_render_codepaths_usage[2], - text_render_codepaths_usage[3], - text_render_codepaths_usage[4]); - - text_render_codepaths_usage[0] = 0; - text_render_codepaths_usage[1] = 0; - text_render_codepaths_usage[2] = 0; - text_render_codepaths_usage[3] = 0; - text_render_codepaths_usage[4] = 0;*/ - - if(do_screenshot) - { - /* find filename */ - char filename[128]; - static int index = 1; - - for(; index < 1000; index++) - { - IOHANDLE io; - str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index); - io = engine_openfile(filename, IOFLAG_READ); - if(io) - io_close(io); - else - break; - } - - gfx_screenshot_direct(filename); - - do_screenshot = 0; - } - - { - static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0}; - perf_start(&pscope); - SDL_GL_SwapBuffers(); - perf_end(); - } - - if(render_enable && config.gfx_finish) - glFinish(); -} - -void gfx_screenshot_direct(const char *filename) -{ - /* fetch image data */ - int y; - int w = screen_width; - int h = screen_height; - unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1); - unsigned char *temp_row = pixel_data+w*h*4; - glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); - - /* flip the pixel because opengl works from bottom left corner */ - for(y = 0; y < h/2; y++) - { - mem_copy(temp_row, pixel_data+y*w*4, w*4); - mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4); - mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4); - } - - /* find filename */ - { - char wholepath[1024]; - png_t png; - - engine_savepath(filename, wholepath, sizeof(wholepath)); - - /* save png */ - dbg_msg("client", "saved screenshot to '%s'", wholepath); - png_open_file_write(&png, wholepath); - png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data); - png_close_file(&png); - } - - /* clean up */ - mem_free(pixel_data); -} - -int gfx_screenwidth() -{ - return screen_width; -} - -int gfx_screenheight() -{ - return screen_height; -} - -void gfx_texture_set(int slot) -{ - dbg_assert(drawing == 0, "called gfx_texture_set within begin"); - if(no_gfx) return; - if(slot == -1) - { - glDisable(GL_TEXTURE_2D); - } - else - { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, textures[slot].tex); - } -} - -void gfx_clear(float r, float g, float b) -{ - if(no_gfx) return; - glClearColor(r,g,b,0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y) -{ - screen_x0 = tl_x; - screen_y0 = tl_y; - screen_x1 = br_x; - screen_y1 = br_y; - if(no_gfx) return; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); -} - -void gfx_getscreen(float *tl_x, float *tl_y, float *br_x, float *br_y) -{ - *tl_x = screen_x0; - *tl_y = screen_y0; - *br_x = screen_x1; - *br_y = screen_y1; -} - -void gfx_quads_begin() -{ - dbg_assert(drawing == 0, "called quads_begin twice"); - drawing = DRAWING_QUADS; - - gfx_quads_setsubset(0,0,1,1); - gfx_quads_setrotation(0); - gfx_setcolor(1,1,1,1); -} - -void gfx_quads_end() -{ - dbg_assert(drawing == DRAWING_QUADS, "called quads_end without begin"); - flush(); - drawing = 0; -} - - -void gfx_quads_setrotation(float angle) -{ - dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setrotation without begin"); - rotation = angle; -} - -void gfx_setcolorvertex(int i, float r, float g, float b, float a) -{ - dbg_assert(drawing != 0, "called gfx_quads_setcolorvertex without begin"); - color[i].r = r; - color[i].g = g; - color[i].b = b; - color[i].a = a; -} - -void gfx_setcolor(float r, float g, float b, float a) -{ - dbg_assert(drawing != 0, "called gfx_quads_setcolor without begin"); - gfx_setcolorvertex(0, r, g, b, a); - gfx_setcolorvertex(1, r, g, b, a); - gfx_setcolorvertex(2, r, g, b, a); - gfx_setcolorvertex(3, r, g, b, a); -} - -void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v) -{ - dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setsubset without begin"); - - texture[0].u = tl_u; texture[1].u = br_u; - texture[0].v = tl_v; texture[1].v = tl_v; - - texture[3].u = tl_u; texture[2].u = br_u; - texture[3].v = br_v; texture[2].v = br_v; -} - -void gfx_quads_setsubset_free( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - texture[0].u = x0; texture[0].v = y0; - texture[1].u = x1; texture[1].v = y1; - texture[2].u = x2; texture[2].v = y2; - texture[3].u = x3; texture[3].v = y3; -} - - -static void rotate(VEC3 *center, VEC3 *point) -{ - float x = point->x - center->x; - float y = point->y - center->y; - point->x = x * cosf(rotation) - y * sinf(rotation) + center->x; - point->y = x * sinf(rotation) + y * cosf(rotation) + center->y; -} - -void gfx_quads_draw(float x, float y, float w, float h) -{ - gfx_quads_drawTL(x-w/2, y-h/2,w,h); -} - -void gfx_quads_drawTL(float x, float y, float width, float height) -{ - VEC3 center; - - dbg_assert(drawing == DRAWING_QUADS, "called quads_draw without begin"); - - center.x = x + width/2; - center.y = y + height/2; - center.z = 0; - - vertices[num_vertices].pos.x = x; - vertices[num_vertices].pos.y = y; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - rotate(¢er, &vertices[num_vertices].pos); - - vertices[num_vertices + 1].pos.x = x+width; - vertices[num_vertices + 1].pos.y = y; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - rotate(¢er, &vertices[num_vertices + 1].pos); - - vertices[num_vertices + 2].pos.x = x + width; - vertices[num_vertices + 2].pos.y = y+height; - vertices[num_vertices + 2].tex = texture[2]; - vertices[num_vertices + 2].color = color[2]; - rotate(¢er, &vertices[num_vertices + 2].pos); - - vertices[num_vertices + 3].pos.x = x; - vertices[num_vertices + 3].pos.y = y+height; - vertices[num_vertices + 3].tex = texture[3]; - vertices[num_vertices + 3].color = color[3]; - rotate(¢er, &vertices[num_vertices + 3].pos); - - add_vertices(4); -} - -void gfx_quads_draw_freeform( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - dbg_assert(drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); - - vertices[num_vertices].pos.x = x0; - vertices[num_vertices].pos.y = y0; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - - vertices[num_vertices + 1].pos.x = x1; - vertices[num_vertices + 1].pos.y = y1; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - - vertices[num_vertices + 2].pos.x = x3; - vertices[num_vertices + 2].pos.y = y3; - vertices[num_vertices + 2].tex = texture[3]; - vertices[num_vertices + 2].color = color[3]; - - vertices[num_vertices + 3].pos.x = x2; - vertices[num_vertices + 3].pos.y = y2; - vertices[num_vertices + 3].tex = texture[2]; - vertices[num_vertices + 3].color = color[2]; - - add_vertices(4); -} - -void gfx_quads_text(float x, float y, float size, float r, float g, float b, float a, const char *text) -{ - float startx = x; - - gfx_quads_begin(); - gfx_setcolor(r,g,b,a); - - while(*text) - { - char c = *text; - text++; - - if(c == '\n') - { - x = startx; - y += size; - } - else - { - gfx_quads_setsubset( - (c%16)/16.0f, - (c/16)/16.0f, - (c%16)/16.0f+1.0f/16.0f, - (c/16)/16.0f+1.0f/16.0f); - - gfx_quads_drawTL(x,y,size,size); - x += size/2; - } - } - - gfx_quads_end(); -} - -void gfx_lines_begin() -{ - dbg_assert(drawing == 0, "called begin twice"); - drawing = DRAWING_LINES; - gfx_setcolor(1,1,1,1); -} - -void gfx_lines_end() -{ - dbg_assert(drawing == DRAWING_LINES, "called end without begin"); - flush(); - drawing = 0; -} - -void gfx_lines_draw(float x0, float y0, float x1, float y1) -{ - dbg_assert(drawing == DRAWING_LINES, "called draw without begin"); - - vertices[num_vertices].pos.x = x0; - vertices[num_vertices].pos.y = y0; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - - vertices[num_vertices + 1].pos.x = x1; - vertices[num_vertices + 1].pos.y = y1; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - - add_vertices(2); -} - -void gfx_clip_enable(int x, int y, int w, int h) -{ - if(no_gfx) return; - glScissor(x, gfx_screenheight()-(y+h), w, h); - glEnable(GL_SCISSOR_TEST); -} - -void gfx_clip_disable() -{ - if(no_gfx) return; - glDisable(GL_SCISSOR_TEST); -} - -void gfx_minimize() -{ - SDL_WM_IconifyWindow(); -} - -void gfx_maximize() -{ - /* TODO: SDL */ -} diff --git a/src/engine/client/ec_gfx.cpp b/src/engine/client/ec_gfx.cpp new file mode 100644 index 00000000..5632581a --- /dev/null +++ b/src/engine/client/ec_gfx.cpp @@ -0,0 +1,992 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include + +#include "SDL.h" + +#ifdef CONF_FAMILY_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include +#endif + +#ifdef CONF_PLATFORM_MACOSX + #include + #include +#else + #include + #include +#endif + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +/* compressed textures */ +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 + +#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + + +void gfx_font_init(); + +VIDEO_MODE fakemodes[] = { + {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, + {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, + {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, + {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, + {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, + {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, + {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, + {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, + {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, + {2048,1536,8,8,8}, + + {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, + {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, + {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, + {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, + {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, + {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, + {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, + {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, + {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, + {2048,1536,5,6,5} +}; + +int gfx_get_video_modes(VIDEO_MODE *list, int maxcount) +{ + int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE); + SDL_Rect **modes; + + if(config.gfx_display_all_modes) + { + int count = sizeof(fakemodes)/sizeof(VIDEO_MODE); + mem_copy(list, fakemodes, sizeof(fakemodes)); + if(maxcount < count) + count = maxcount; + return count; + } + + /* TODO: fix this code on osx or windows */ + + modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); + if(modes == NULL) + { + /* no modes */ + num_modes = 0; + } + else if(modes == (SDL_Rect**)-1) + { + /* all modes */ + } + else + { + int i; + num_modes = 0; + for(i = 0; modes[i]; ++i) + { + if(num_modes == maxcount) + break; + list[num_modes].width = modes[i]->w; + list[num_modes].height = modes[i]->h; + list[num_modes].red = 8; + list[num_modes].green = 8; + list[num_modes].blue = 8; + num_modes++; + } + } + + return num_modes; +} + + +#include "graphics.h" + +class CGraphics_OpenGL : public IEngineGraphics +{ +protected: + /* */ + typedef struct { float x, y, z; } CPoint; + typedef struct { float u, v; } CTexCoord; + typedef struct { float r, g, b, a; } CColor; + + typedef struct + { + CPoint m_Pos; + CTexCoord m_Tex; + CColor m_Color; + } CVertex; + + enum + { + MAX_VERTICES = 32*1024, + MAX_TEXTURES = 1024*4, + + DRAWING_QUADS=1, + DRAWING_LINES=2 + }; + + CVertex m_aVertices[MAX_VERTICES]; + int m_NumVertices; + + CColor m_aColor[4]; + CTexCoord m_aTexture[4]; + + bool m_RenderEnable; + + float m_Rotation; + int m_Drawing; + bool m_DoScreenshot; + + float m_ScreenX0; + float m_ScreenY0; + float m_ScreenX1; + float m_ScreenY1; + + int m_InvalidTexture; + + struct CTexture + { + GLuint tex; + int memsize; + int flags; + int next; + }; + + enum + { + + }; + + CTexture m_aTextures[MAX_TEXTURES]; + int m_FirstFreeTexture; + int m_TextureMemoryUsage; + + + void Flush() + { + if(m_NumVertices == 0) + return; + + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glVertexPointer(3, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices); + glTexCoordPointer(2, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*3); + glColorPointer(4, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*5); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(m_RenderEnable) + { + if(m_Drawing == DRAWING_QUADS) + glDrawArrays(GL_QUADS, 0, m_NumVertices); + else if(m_Drawing == DRAWING_LINES) + glDrawArrays(GL_LINES, 0, m_NumVertices); + } + + /* Reset pointer */ + m_NumVertices = 0; + } + + void AddVertices(int count) + { + m_NumVertices += count; + if((m_NumVertices + count) >= MAX_VERTICES) + Flush(); + } + + void Rotate(CPoint *pCenter, CPoint *pPoint) + { + float x = pPoint->x - pCenter->x; + float y = pPoint->y - pCenter->y; + pPoint->x = x * cosf(m_Rotation) - y * sinf(m_Rotation) + pCenter->x; + pPoint->y = x * sinf(m_Rotation) + y * cosf(m_Rotation) + pCenter->y; + } + + + + + static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset) + { + return (data[(v*w+u)*4+offset]+ + data[(v*w+u+1)*4+offset]+ + data[((v+1)*w+u)*4+offset]+ + data[((v+1)*w+u+1)*4+offset])/4; + } +public: + CGraphics_OpenGL() + { + m_NumVertices = 0; + + m_ScreenX0 = 0; + m_ScreenY0 = 0; + m_ScreenX1 = 0; + m_ScreenY1 = 0; + + m_ScreenWidth = -1; + m_ScreenHeight = -1; + + m_Rotation = 0; + m_Drawing = 0; + m_InvalidTexture = 0; + + m_TextureMemoryUsage = 0; + + m_RenderEnable = true; + m_DoScreenshot = false; + } + + + virtual void ClipEnable(int x, int y, int w, int h) + { + //if(no_gfx) return; + glScissor(x, ScreenHeight()-(y+h), w, h); + glEnable(GL_SCISSOR_TEST); + } + + virtual void ClipDisable() + { + //if(no_gfx) return; + glDisable(GL_SCISSOR_TEST); + } + + + virtual void BlendNone() + { + glDisable(GL_BLEND); + } + + virtual void BlendNormal() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + virtual void BlendAdditive() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + + //int gfx_memory_usage() { return m_MemoryUsage; } + + virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y) + { + m_ScreenX0 = tl_x; + m_ScreenY0 = tl_y; + m_ScreenX1 = br_x; + m_ScreenY1 = br_y; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); + } + + virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y) + { + *tl_x = m_ScreenX0; + *tl_y = m_ScreenY0; + *br_x = m_ScreenX1; + *br_y = m_ScreenY1; + } + + virtual void LinesBegin() + { + dbg_assert(m_Drawing == 0, "called begin twice"); + m_Drawing = DRAWING_LINES; + SetColor(1,1,1,1); + } + + virtual void LinesEnd() + { + dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin"); + Flush(); + m_Drawing = 0; + } + + virtual void LinesDraw(float x0, float y0, float x1, float y1) + { + dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin"); + + m_aVertices[m_NumVertices].m_Pos.x = x0; + m_aVertices[m_NumVertices].m_Pos.y = y0; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 1].m_Pos.x = x1; + m_aVertices[m_NumVertices + 1].m_Pos.y = y1; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + + AddVertices(2); + } + + + + virtual int UnloadTexture(int Index) + { + if(Index == m_InvalidTexture) + return 0; + + if(Index < 0) + return 0; + + glDeleteTextures(1, &m_aTextures[Index].tex); + m_aTextures[Index].next = m_FirstFreeTexture; + m_TextureMemoryUsage -= m_aTextures[Index].memsize; + m_FirstFreeTexture = Index; + return 0; + } + + + virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) + { + int mipmap = 1; + unsigned char *texdata = (unsigned char *)pData; + unsigned char *tmpdata = 0; + int oglformat = 0; + int store_oglformat = 0; + int tex = 0; + + /* don't waste memory on texture if we are stress testing */ + if(config.dbg_stress) + return m_InvalidTexture; + + /* grab texture */ + tex = m_FirstFreeTexture; + m_FirstFreeTexture = m_aTextures[tex].next; + m_aTextures[tex].next = -1; + + /* resample if needed */ + if(!(Flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0) + { + if(Width > 16 && Height > 16 && Format == IMG_RGBA) + { + unsigned char *tmpdata; + int c = 0; + int x, y; + + tmpdata = (unsigned char *)mem_alloc(Width*Height*4, 1); + + Width/=2; + Height/=2; + + for(y = 0; y < Height; y++) + for(x = 0; x < Width; x++, c++) + { + tmpdata[c*4] = sample(Width*2, Height*2, texdata, x*2,y*2, 0); + tmpdata[c*4+1] = sample(Width*2, Height*2, texdata, x*2,y*2, 1); + tmpdata[c*4+2] = sample(Width*2, Height*2, texdata, x*2,y*2, 2); + tmpdata[c*4+3] = sample(Width*2, Height*2, texdata, x*2,y*2, 3); + } + texdata = tmpdata; + } + } + + oglformat = GL_RGBA; + if(Format == IMG_RGB) + oglformat = GL_RGB; + else if(Format == IMG_ALPHA) + oglformat = GL_ALPHA; + + /* upload texture */ + if(config.gfx_texture_compression) + { + store_oglformat = GL_COMPRESSED_RGBA_ARB; + if(StoreFormat == IMG_RGB) + store_oglformat = GL_COMPRESSED_RGB_ARB; + else if(StoreFormat == IMG_ALPHA) + store_oglformat = GL_COMPRESSED_ALPHA_ARB; + } + else + { + store_oglformat = GL_RGBA; + if(StoreFormat == IMG_RGB) + store_oglformat = GL_RGB; + else if(StoreFormat == IMG_ALPHA) + store_oglformat = GL_ALPHA; + } + + glGenTextures(1, &m_aTextures[tex].tex); + glBindTexture(GL_TEXTURE_2D, m_aTextures[tex].tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, Width, Height, oglformat, GL_UNSIGNED_BYTE, texdata); + + /* calculate memory usage */ + { + int pixel_size = 4; + if(StoreFormat == IMG_RGB) + pixel_size = 3; + else if(StoreFormat == IMG_ALPHA) + pixel_size = 1; + + m_aTextures[tex].memsize = Width*Height*pixel_size; + if(mipmap) + { + while(Width > 2 && Height > 2) + { + Width>>=1; + Height>>=1; + m_aTextures[tex].memsize += Width*Height*pixel_size; + } + } + } + + m_TextureMemoryUsage += m_aTextures[tex].memsize; + mem_free(tmpdata); + return tex; + } + + /* simple uncompressed RGBA loaders */ + virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags) + { + int l = strlen(pFilename); + int id; + IMAGE_INFO img; + + if(l < 3) + return -1; + if(LoadPNG(&img, pFilename)) + { + if (StoreFormat == IMG_AUTO) + StoreFormat = img.format; + + id = LoadTextureRaw(img.width, img.height, img.format, img.data, StoreFormat, Flags); + mem_free(img.data); + return id; + } + + return m_InvalidTexture; + } + + virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename) + { + char aCompleteFilename[512]; + unsigned char *pBuffer; + png_t png; + + /* open file for reading */ + png_init(0,0); + + engine_getpath(aCompleteFilename, sizeof(aCompleteFilename), pFilename, IOFLAG_READ); + + if(png_open_file(&png, aCompleteFilename) != PNG_NO_ERROR) + { + dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); + return 0; + } + + if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA)) + { + dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); + png_close_file(&png); + return 0; + } + + pBuffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1); + png_get_data(&png, pBuffer); + png_close_file(&png); + + pImg->width = png.width; + pImg->height = png.height; + if(png.color_type == PNG_TRUECOLOR) + pImg->format = IMG_RGB; + else if(png.color_type == PNG_TRUECOLOR_ALPHA) + pImg->format = IMG_RGBA; + pImg->data = pBuffer; + return 1; + } + + void ScreenshotDirect(const char *filename) + { + /* fetch image data */ + int y; + int w = m_ScreenWidth; + int h = m_ScreenHeight; + unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1); + unsigned char *temp_row = pixel_data+w*h*4; + glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); + + /* flip the pixel because opengl works from bottom left corner */ + for(y = 0; y < h/2; y++) + { + mem_copy(temp_row, pixel_data+y*w*4, w*4); + mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4); + mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4); + } + + /* find filename */ + { + char wholepath[1024]; + png_t png; + + engine_savepath(filename, wholepath, sizeof(wholepath)); + + /* save png */ + dbg_msg("client", "saved screenshot to '%s'", wholepath); + png_open_file_write(&png, wholepath); + png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data); + png_close_file(&png); + } + + /* clean up */ + mem_free(pixel_data); + } + + virtual void TextureSet(int TextureID) + { + dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); + if(TextureID == -1) + { + glDisable(GL_TEXTURE_2D); + } + else + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].tex); + } + } + + virtual void Clear(float r, float g, float b) + { + glClearColor(r,g,b,0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + virtual void QuadsBegin() + { + dbg_assert(m_Drawing == 0, "called quads_begin twice"); + m_Drawing = DRAWING_QUADS; + + QuadsSetSubset(0,0,1,1); + QuadsSetRotation(0); + SetColor(1,1,1,1); + } + + virtual void QuadsEnd() + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin"); + Flush(); + m_Drawing = 0; + } + + virtual void QuadsSetRotation(float Angle) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); + m_Rotation = Angle; + } + + virtual void SetColorVertex(int i, float r, float g, float b, float a) + { + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin"); + m_aColor[i].r = r; + m_aColor[i].g = g; + m_aColor[i].b = b; + m_aColor[i].a = a; + } + + virtual void SetColor(float r, float g, float b, float a) + { + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin"); + SetColorVertex(0, r, g, b, a); + SetColorVertex(1, r, g, b, a); + SetColorVertex(2, r, g, b, a); + SetColorVertex(3, r, g, b, a); + } + + virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); + + m_aTexture[0].u = tl_u; m_aTexture[1].u = br_u; + m_aTexture[0].v = tl_v; m_aTexture[1].v = tl_v; + + m_aTexture[3].u = tl_u; m_aTexture[2].u = br_u; + m_aTexture[3].v = br_v; m_aTexture[2].v = br_v; + } + + virtual void QuadsSetSubsetFree( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) + { + m_aTexture[0].u = x0; m_aTexture[0].v = y0; + m_aTexture[1].u = x1; m_aTexture[1].v = y1; + m_aTexture[2].u = x2; m_aTexture[2].v = y2; + m_aTexture[3].u = x3; m_aTexture[3].v = y3; + } + + virtual void QuadsDraw(float x, float y, float w, float h) + { + QuadsDrawTL(x-w/2, y-h/2,w,h); + } + + virtual void QuadsDrawTL(float x, float y, float w, float h) + { + CPoint Center; + + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin"); + + Center.x = x + w/2; + Center.y = y + h/2; + Center.z = 0; + + m_aVertices[m_NumVertices].m_Pos.x = x; + m_aVertices[m_NumVertices].m_Pos.y = y; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + Rotate(&Center, &m_aVertices[m_NumVertices].m_Pos); + + m_aVertices[m_NumVertices + 1].m_Pos.x = x+w; + m_aVertices[m_NumVertices + 1].m_Pos.y = y; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + Rotate(&Center, &m_aVertices[m_NumVertices + 1].m_Pos); + + m_aVertices[m_NumVertices + 2].m_Pos.x = x + w; + m_aVertices[m_NumVertices + 2].m_Pos.y = y+h; + m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 2].m_Color = m_aColor[2]; + Rotate(&Center, &m_aVertices[m_NumVertices + 2].m_Pos); + + m_aVertices[m_NumVertices + 3].m_Pos.x = x; + m_aVertices[m_NumVertices + 3].m_Pos.y = y+h; + m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 3].m_Color = m_aColor[3]; + Rotate(&Center, &m_aVertices[m_NumVertices + 3].m_Pos); + + AddVertices(4); + } + + void QuadsDrawFreeform( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); + + m_aVertices[m_NumVertices].m_Pos.x = x0; + m_aVertices[m_NumVertices].m_Pos.y = y0; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 1].m_Pos.x = x1; + m_aVertices[m_NumVertices + 1].m_Pos.y = y1; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + + m_aVertices[m_NumVertices + 2].m_Pos.x = x3; + m_aVertices[m_NumVertices + 2].m_Pos.y = y3; + m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 2].m_Color = m_aColor[3]; + + m_aVertices[m_NumVertices + 3].m_Pos.x = x2; + m_aVertices[m_NumVertices + 3].m_Pos.y = y2; + m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 3].m_Color = m_aColor[2]; + + AddVertices(4); + } + + virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) + { + float startx = x; + + QuadsBegin(); + SetColor(r,g,b,a); + + while(*pText) + { + char c = *pText; + pText++; + + if(c == '\n') + { + x = startx; + y += Size; + } + else + { + QuadsSetSubset( + (c%16)/16.0f, + (c/16)/16.0f, + (c%16)/16.0f+1.0f/16.0f, + (c/16)/16.0f+1.0f/16.0f); + + QuadsDrawTL(x,y,Size,Size); + x += Size/2; + } + } + + QuadsEnd(); + } + + virtual bool Init() + { + /* Set all z to -5.0f */ + for(int i = 0; i < MAX_VERTICES; i++) + m_aVertices[i].m_Pos.z = -5.0f; + + /* init textures */ + m_FirstFreeTexture = 0; + for(int i = 0; i < MAX_TEXTURES; i++) + m_aTextures[i].next = i+1; + m_aTextures[MAX_TEXTURES-1].next = -1; + + /* set some default settings */ + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glAlphaFunc(GL_GREATER, 0); + glEnable(GL_ALPHA_TEST); + glDepthMask(0); + + /* create null texture, will get id=0 */ + static const unsigned char aNullTextureData[] = { + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + }; + + m_InvalidTexture = LoadTextureRaw(4,4,IMG_RGBA,aNullTextureData,IMG_RGBA,TEXLOAD_NORESAMPLE); + dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].tex); + + return true; + } +}; + +class CGraphics_SDL : public CGraphics_OpenGL +{ + SDL_Surface *m_pScreenSurface; + + int TryInit() + { + const SDL_VideoInfo *pInfo; + int Flags = SDL_OPENGL; + + m_ScreenWidth = config.gfx_screen_width; + m_ScreenHeight = config.gfx_screen_height; + + pInfo = SDL_GetVideoInfo(); + + /* set flags */ + Flags = SDL_OPENGL; + Flags |= SDL_GL_DOUBLEBUFFER; + Flags |= SDL_HWPALETTE; + if(config.dbg_resizable) + Flags |= SDL_RESIZABLE; + + if(pInfo->hw_available) + Flags |= SDL_HWSURFACE; + else + Flags |= SDL_SWSURFACE; + + if(pInfo->blit_hw) + Flags |= SDL_HWACCEL; + + if(config.gfx_fullscreen) + Flags |= SDL_FULLSCREEN; + + /* set gl attributes */ + if(config.gfx_fsaa_samples) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples); + } + else + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + } + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, config.gfx_vsync); + + /* set caption */ + SDL_WM_SetCaption("Teeworlds", "Teeworlds"); + + /* create window */ + m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags); + if(m_pScreenSurface == NULL) + { + dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); + return -1; + } + + return 0; + } + + + int InitWindow() + { + if(TryInit() == 0) + return 0; + + /* try disabling fsaa */ + while(config.gfx_fsaa_samples) + { + config.gfx_fsaa_samples--; + + if(config.gfx_fsaa_samples) + dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples); + else + dbg_msg("gfx", "disabling FSAA and trying again"); + + if(TryInit() == 0) + return 0; + } + + /* try lowering the resolution */ + if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480) + { + dbg_msg("gfx", "setting resolution to 640x480 and trying again"); + config.gfx_screen_width = 640; + config.gfx_screen_height = 480; + + if(TryInit() == 0) + return 0; + } + + dbg_msg("gfx", "out of ideas. failed to init graphics"); + + return -1; + } + + +public: + CGraphics_SDL() + { + m_pScreenSurface = 0; + } + + virtual bool Init() + { + { + int Systems = SDL_INIT_VIDEO; + + if(config.snd_enable) + Systems |= SDL_INIT_AUDIO; + + if(config.cl_eventthread) + Systems |= SDL_INIT_EVENTTHREAD; + + if(SDL_Init(Systems) < 0) + { + dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); + return -1; + } + } + + atexit(SDL_Quit); + + #ifdef CONF_FAMILY_WINDOWS + if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) + putenv("SDL_VIDEO_WINDOW_POS=8,27"); + #endif + + if(InitWindow() != 0) + return -1; + + SDL_ShowCursor(0); + + CGraphics_OpenGL::Init(); + + MapScreen(0,0,config.gfx_screen_width, config.gfx_screen_height); + + /* init input */ + inp_init(); + + /* font init */ + gfx_font_init(); + + return 0; + } + + virtual void Shutdown() + { + /* TODO: SDL, is this correct? */ + SDL_Quit(); + } + + virtual void Minimize() + { + SDL_WM_IconifyWindow(); + } + + virtual void Maximize() + { + /* TODO: SDL */ + } + + virtual int WindowActive() + { + return SDL_GetAppState()&SDL_APPINPUTFOCUS; + } + + virtual int WindowOpen() + { + return SDL_GetAppState()&SDL_APPACTIVE; + + } + + virtual void TakeScreenshot() + { + m_DoScreenshot = true; + } + + virtual void Swap() + { + if(m_DoScreenshot) + { + /* find filename */ + char filename[128]; + static int index = 1; + + for(; index < 1000; index++) + { + IOHANDLE io; + str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index); + io = engine_openfile(filename, IOFLAG_READ); + if(io) + io_close(io); + else + break; + } + + ScreenshotDirect(filename); + m_DoScreenshot = false; + } + + { + static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0}; + perf_start(&pscope); + SDL_GL_SwapBuffers(); + perf_end(); + } + + if(config.gfx_finish) + glFinish(); + } +}; + +extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); } diff --git a/src/engine/client/ec_gfx_text.c b/src/engine/client/ec_gfx_text.c deleted file mode 100644 index de40e391..00000000 --- a/src/engine/client/ec_gfx_text.c +++ /dev/null @@ -1,668 +0,0 @@ -#include -#include -#include - - -#ifdef CONF_PLATFORM_MACOSX - #include - #include -#else - #include - #include -#endif - -static int word_length(const char *text) -{ - int s = 1; - while(1) - { - if(*text == 0) - return s-1; - if(*text == '\n' || *text == '\t' || *text == ' ') - return s; - text++; - s++; - } -} - -static float text_r=1; -static float text_g=1; -static float text_b=1; -static float text_a=1; - -static struct FONT *default_font = 0; -void gfx_text_set_default_font(struct FONT *font) -{ - default_font = font; -} - - -void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) -{ - mem_zero(cursor, sizeof(*cursor)); - cursor->font_size = font_size; - cursor->start_x = x; - cursor->start_y = y; - cursor->x = x; - cursor->y = y; - cursor->line_count = 1; - cursor->line_width = -1; - cursor->flags = flags; - cursor->charcount = 0; -} - - -void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); - cursor.line_width = max_width; - gfx_text_ex(&cursor, text, -1); -} - -float gfx_text_width(void *font_set_v, float size, const char *text, int length) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, 0, 0, size, 0); - gfx_text_ex(&cursor, text, length); - return cursor.x; -} - -void gfx_text_color(float r, float g, float b, float a) -{ - text_r = r; - text_g = g; - text_b = b; - text_a = a; -} - -/* ft2 texture */ -#include -#include FT_FREETYPE_H - -static FT_Library ft_library; - -#define MAX_CHARACTERS 64 - - -/* GL_LUMINANCE can be good for debugging*/ -static int font_texture_format = GL_ALPHA; - - -static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; -#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) - - -typedef struct FONTCHAR -{ - int id; - - /* these values are scaled to the font size */ - /* width * font_size == real_size */ - float width; - float height; - float offset_x; - float offset_y; - float advance_x; - - float uvs[4]; - int64 touch_time; -} FONTCHAR; - -typedef struct FONTSIZEDATA -{ - int font_size; - FT_Face *face; - - unsigned textures[2]; - int texture_width; - int texture_height; - - int num_x_chars; - int num_y_chars; - - int char_max_width; - int char_max_height; - - FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; - - int current_character; -} FONTSIZEDATA; - -typedef struct FONT -{ - char filename[128]; - FT_Face ft_face; - FONTSIZEDATA sizes[NUM_FONT_SIZES]; -} FONT; - -static int font_get_index(int pixelsize) -{ - int i; - for(i = 0; i < NUM_FONT_SIZES; i++) - { - if(font_sizes[i] >= pixelsize) - return i; - } - - return NUM_FONT_SIZES-1; -} - -FONT *gfx_font_load(const char *filename) -{ - int i; - FONT *font = mem_alloc(sizeof(FONT), 1); - - mem_zero(font, sizeof(*font)); - str_copy(font->filename, filename, sizeof(font->filename)); - - if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) - { - mem_free(font); - return NULL; - } - - for(i = 0; i < NUM_FONT_SIZES; i++) - font->sizes[i].font_size = -1; - - return font; -}; - -void gfx_font_destroy(FONT *font) -{ - mem_free(font); -} - -void gfx_font_init() -{ - FT_Init_FreeType(&ft_library); -} - -static void grow(unsigned char *in, unsigned char *out, int w, int h) -{ - int y, x; - for(y = 0; y < h; y++) - for(x = 0; x < w; x++) - { - int c = in[y*w+x]; - int s_y, s_x; - - for(s_y = -1; s_y <= 1; s_y++) - for(s_x = -1; s_x <= 1; s_x++) - { - int get_x = x+s_x; - int get_y = y+s_y; - if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) - { - int index = get_y*w+get_x; - if(in[index] > c) - c = in[index]; - } - } - - out[y*w+x] = c; - } -} - -static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars) -{ - static int font_memory_usage = 0; - int i; - int width = charwidth*xchars; - int height = charheight*ychars; - void *mem = mem_alloc(width*height, 1); - mem_zero(mem, width*height); - - if(sizedata->textures[0] == 0) - glGenTextures(2, sizedata->textures); - else - font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2; - - sizedata->num_x_chars = xchars; - sizedata->num_y_chars = ychars; - sizedata->texture_width = width; - sizedata->texture_height = height; - sizedata->current_character = 0; - - for(i = 0; i < 2; i++) - { - glBindTexture(GL_TEXTURE_2D, sizedata->textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); - font_memory_usage += width*height; - } - - dbg_msg("", "font memory usage: %d", font_memory_usage); - - mem_free(mem); -} - -static void font_increase_texture_size(FONTSIZEDATA *sizedata) -{ - if(sizedata->texture_width < sizedata->texture_height) - sizedata->num_x_chars <<= 1; - else - sizedata->num_y_chars <<= 1; - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars); -} - -static void font_init_index(FONT *font, int index) -{ - int outline_thickness = 1; - FONTSIZEDATA *sizedata = &font->sizes[index]; - - sizedata->font_size = font_sizes[index]; - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(sizedata->font_size >= 18) - outline_thickness = 2; - - { - unsigned glyph_index; - int charcode; - int max_h = 0; - int max_w = 0; - - charcode = FT_Get_First_Char(font->ft_face, &glyph_index); - while(glyph_index != 0) - { - /* do stuff */ - FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT); - - if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width; - if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height; - charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index); - } - - max_w = (max_w>>6)+2+outline_thickness*2; - max_h = (max_h>>6)+2+outline_thickness*2; - - for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1); - for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1); - } - - //dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h); - //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8); -} - -static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) -{ - int index = font_get_index(pixelsize); - if(font->sizes[index].font_size != font_sizes[index]) - font_init_index(font, index); - return &font->sizes[index]; -} - - -static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) -{ - int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); - int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); - - glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, - sizedata->texture_width/sizedata->num_x_chars, - sizedata->texture_height/sizedata->num_y_chars, - font_texture_format, GL_UNSIGNED_BYTE, data); -} - -/* 8k of data used for rendering glyphs */ -static unsigned char glyphdata[(4096/64) * (4096/64)]; -static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; - -static int font_get_slot(FONTSIZEDATA *sizedata) -{ - int char_count = sizedata->num_x_chars*sizedata->num_y_chars; - if(sizedata->current_character < char_count) - { - int i = sizedata->current_character; - sizedata->current_character++; - return i; - } - - /* kick out the oldest */ - /* TODO: remove this linear search */ - { - int oldest = 0; - int i; - for(i = 1; i < char_count; i++) - { - if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) - oldest = i; - } - - if(time_get()-sizedata->characters[oldest].touch_time < time_freq()) - { - font_increase_texture_size(sizedata); - return font_get_slot(sizedata); - } - - return oldest; - } -} - -static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FT_Bitmap *bitmap; - int slot_id = 0; - int slot_w = sizedata->texture_width / sizedata->num_x_chars; - int slot_h = sizedata->texture_height / sizedata->num_y_chars; - int slot_size = slot_w*slot_h; - int outline_thickness = 1; - int x = 1; - int y = 1; - int px, py; - - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) - { - dbg_msg("font", "error loading glyph %d", chr); - return -1; - } - - bitmap = &font->ft_face->glyph->bitmap; - - /* fetch slot */ - slot_id = font_get_slot(sizedata); - if(slot_id < 0) - return -1; - - /* adjust spacing */ - if(sizedata->font_size >= 18) - outline_thickness = 2; - x += outline_thickness; - y += outline_thickness; - - /* prepare glyph data */ - mem_zero(glyphdata, slot_size); - - if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; - } - else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - { - if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) - glyphdata[(py+y)*slot_w+px+x] = 255; - } - } - - if(0) for(py = 0; py < slot_w; py++) - for(px = 0; px < slot_h; px++) - glyphdata[py*slot_w+px] = 255; - - /* upload the glyph */ - font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); - - if(outline_thickness == 1) - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); - } - else - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - grow(glyphdata_outlined, glyphdata, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); - } - - /* set char info */ - { - FONTCHAR *fontchr = &sizedata->characters[slot_id]; - float scale = 1.0f/sizedata->font_size; - float uscale = 1.0f/sizedata->texture_width; - float vscale = 1.0f/sizedata->texture_height; - int height = bitmap->rows + outline_thickness*2 + 2; - int width = bitmap->width + outline_thickness*2 + 2; - - fontchr->id = chr; - fontchr->height = height * scale; - fontchr->width = width * scale; - fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; - fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; - fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; - - fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); - fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); - fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; - fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; - } - - return slot_id; -} - -static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FONTCHAR *fontchr = NULL; - - /* search for the character */ - /* TODO: remove this linear search */ - int i; - for(i = 0; i < sizedata->current_character; i++) - { - if(sizedata->characters[i].id == chr) - { - fontchr = &sizedata->characters[i]; - break; - } - } - - /* check if we need to render the character */ - if(!fontchr) - { - int index = font_render_glyph(font, sizedata, chr); - if(index >= 0) - fontchr = &sizedata->characters[index]; - } - - /* touch the character */ - /* TODO: don't call time_get here */ - if(fontchr) - fontchr->touch_time = time_get(); - - return fontchr; -} - -/* must only be called from the rendering function as the font must be set to the correct size */ -static void font_render_setup(FONT *font, int size) -{ - FT_Set_Pixel_Sizes(font->ft_face, 0, size); -} - -static float font_kerning(FONT *font, int left, int right) -{ - FT_Vector kerning = {0,0}; - FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning); - return (kerning.x>>6); -} - -void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) -{ - FONT *font = cursor->font; - FONTSIZEDATA *sizedata = NULL; - - float screen_x0, screen_y0, screen_x1, screen_y1; - float fake_to_screen_x, fake_to_screen_y; - int actual_x, actual_y; - - int actual_size; - int i; - int got_new_line = 0; - float draw_x, draw_y; - float cursor_x, cursor_y; - const char *end; - - float size = cursor->font_size; - - /* to correct coords, convert to screen coords, round, and convert back */ - gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); - - fake_to_screen_x = (gfx_screenwidth()/(screen_x1-screen_x0)); - fake_to_screen_y = (gfx_screenheight()/(screen_y1-screen_y0)); - actual_x = cursor->x * fake_to_screen_x; - actual_y = cursor->y * fake_to_screen_y; - - cursor_x = actual_x / fake_to_screen_x; - cursor_y = actual_y / fake_to_screen_y; - - /* same with size */ - actual_size = size * fake_to_screen_y; - size = actual_size / fake_to_screen_y; - - /* fetch font data */ - if(!font) - font = default_font; - - if(!font) - return; - - sizedata = font_get_size(font, actual_size); - font_render_setup(font, actual_size); - - /* set length */ - if(length < 0) - length = strlen(text); - - end = text + length; - - /* if we don't want to render, we can just skip the first outline pass */ - i = 1; - if(cursor->flags&TEXTFLAG_RENDER) - i = 0; - - for(;i < 2; i++) - { - const char *current = (char *)text; - const char *end = current+length; - draw_x = cursor_x; - draw_y = cursor_y; - - if(cursor->flags&TEXTFLAG_RENDER) - { - // TODO: Make this better - glEnable(GL_TEXTURE_2D); - if (i == 0) - glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); - else - glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); - - gfx_quads_begin(); - if (i == 0) - gfx_setcolor(0.0f, 0.0f, 0.0f, 0.3f*text_a); - else - gfx_setcolor(text_r, text_g, text_b, text_a); - } - - while(current < end) - { - int new_line = 0; - const char *batch_end = end; - if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) - { - int wlen = word_length((char *)current); - TEXT_CURSOR compare = *cursor; - compare.x = draw_x; - compare.y = draw_y; - compare.flags &= ~TEXTFLAG_RENDER; - compare.line_width = -1; - gfx_text_ex(&compare, text, wlen); - - if(compare.x-draw_x > cursor->line_width) - { - /* word can't be fitted in one line, cut it */ - TEXT_CURSOR cutter = *cursor; - cutter.charcount = 0; - cutter.x = draw_x; - cutter.y = draw_y; - cutter.flags &= ~TEXTFLAG_RENDER; - cutter.flags |= TEXTFLAG_STOP_AT_END; - - gfx_text_ex(&cutter, (const char *)current, wlen); - wlen = cutter.charcount; - new_line = 1; - - if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ - wlen = 0; - } - else if(compare.x-cursor->start_x > cursor->line_width) - { - new_line = 1; - wlen = 0; - } - - batch_end = current + wlen; - } - - while(current < batch_end) - { - const char *tmp; - float advance = 0; - int character = 0; - int nextcharacter = 0; - FONTCHAR *chr; - - // TODO: UTF-8 decode - character = str_utf8_decode(¤t); - tmp = current; - nextcharacter = str_utf8_decode(&tmp); - - if(character == '\n') - { - draw_x = cursor->start_x; - draw_y += size; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - continue; - } - - chr = font_get_char(font, sizedata, character); - - if(chr) - { - if(cursor->flags&TEXTFLAG_RENDER) - { - gfx_quads_setsubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); - gfx_quads_drawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); - } - - advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size; - } - - if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) - { - /* we hit the end of the line, no more to render or count */ - current = end; - break; - } - - draw_x += advance*size; - cursor->charcount++; - } - - if(new_line) - { - draw_x = cursor->start_x; - draw_y += size; - got_new_line = 1; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - } - } - - if(cursor->flags&TEXTFLAG_RENDER) - gfx_quads_end(); - } - - cursor->x = draw_x; - - if(got_new_line) - cursor->y = draw_y; -} diff --git a/src/engine/client/ec_gfx_text.cpp b/src/engine/client/ec_gfx_text.cpp new file mode 100644 index 00000000..d17d1bed --- /dev/null +++ b/src/engine/client/ec_gfx_text.cpp @@ -0,0 +1,669 @@ +#include +#include +#include +#include + +extern IEngineGraphics *Graphics(); + +#ifdef CONF_PLATFORM_MACOSX + #include + #include +#else + #include + #include +#endif + +static int word_length(const char *text) +{ + int s = 1; + while(1) + { + if(*text == 0) + return s-1; + if(*text == '\n' || *text == '\t' || *text == ' ') + return s; + text++; + s++; + } +} + +static float text_r=1; +static float text_g=1; +static float text_b=1; +static float text_a=1; + +static struct FONT *default_font = 0; +void gfx_text_set_default_font(struct FONT *font) +{ + default_font = font; +} + + +void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) +{ + mem_zero(cursor, sizeof(*cursor)); + cursor->font_size = font_size; + cursor->start_x = x; + cursor->start_y = y; + cursor->x = x; + cursor->y = y; + cursor->line_count = 1; + cursor->line_width = -1; + cursor->flags = flags; + cursor->charcount = 0; +} + + +void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) +{ + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); + cursor.line_width = max_width; + gfx_text_ex(&cursor, text, -1); +} + +float gfx_text_width(void *font_set_v, float size, const char *text, int length) +{ + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, 0, 0, size, 0); + gfx_text_ex(&cursor, text, length); + return cursor.x; +} + +void gfx_text_color(float r, float g, float b, float a) +{ + text_r = r; + text_g = g; + text_b = b; + text_a = a; +} + +/* ft2 texture */ +#include +#include FT_FREETYPE_H + +static FT_Library ft_library; + +#define MAX_CHARACTERS 64 + + +/* GL_LUMINANCE can be good for debugging*/ +static int font_texture_format = GL_ALPHA; + + +static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; +#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) + + +typedef struct FONTCHAR +{ + int id; + + /* these values are scaled to the font size */ + /* width * font_size == real_size */ + float width; + float height; + float offset_x; + float offset_y; + float advance_x; + + float uvs[4]; + int64 touch_time; +} FONTCHAR; + +typedef struct FONTSIZEDATA +{ + int font_size; + FT_Face *face; + + unsigned textures[2]; + int texture_width; + int texture_height; + + int num_x_chars; + int num_y_chars; + + int char_max_width; + int char_max_height; + + FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; + + int current_character; +} FONTSIZEDATA; + +typedef struct FONT +{ + char filename[128]; + FT_Face ft_face; + FONTSIZEDATA sizes[NUM_FONT_SIZES]; +} FONT; + +static int font_get_index(int pixelsize) +{ + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + { + if(font_sizes[i] >= pixelsize) + return i; + } + + return NUM_FONT_SIZES-1; +} + +FONT *gfx_font_load(const char *filename) +{ + FONT *font = (FONT *)mem_alloc(sizeof(FONT), 1); + + mem_zero(font, sizeof(*font)); + str_copy(font->filename, filename, sizeof(font->filename)); + + if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) + { + mem_free(font); + return NULL; + } + + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + font->sizes[i].font_size = -1; + + return font; +}; + +void gfx_font_destroy(FONT *font) +{ + mem_free(font); +} + +void gfx_font_init() +{ + FT_Init_FreeType(&ft_library); +} + +static void grow(unsigned char *in, unsigned char *out, int w, int h) +{ + int y, x; + for(y = 0; y < h; y++) + for(x = 0; x < w; x++) + { + int c = in[y*w+x]; + int s_y, s_x; + + for(s_y = -1; s_y <= 1; s_y++) + for(s_x = -1; s_x <= 1; s_x++) + { + int get_x = x+s_x; + int get_y = y+s_y; + if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) + { + int index = get_y*w+get_x; + if(in[index] > c) + c = in[index]; + } + } + + out[y*w+x] = c; + } +} + +static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars) +{ + static int font_memory_usage = 0; + int i; + int width = charwidth*xchars; + int height = charheight*ychars; + void *mem = mem_alloc(width*height, 1); + mem_zero(mem, width*height); + + if(sizedata->textures[0] == 0) + glGenTextures(2, sizedata->textures); + else + font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2; + + sizedata->num_x_chars = xchars; + sizedata->num_y_chars = ychars; + sizedata->texture_width = width; + sizedata->texture_height = height; + sizedata->current_character = 0; + + for(i = 0; i < 2; i++) + { + glBindTexture(GL_TEXTURE_2D, sizedata->textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); + font_memory_usage += width*height; + } + + dbg_msg("", "font memory usage: %d", font_memory_usage); + + mem_free(mem); +} + +static void font_increase_texture_size(FONTSIZEDATA *sizedata) +{ + if(sizedata->texture_width < sizedata->texture_height) + sizedata->num_x_chars <<= 1; + else + sizedata->num_y_chars <<= 1; + font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars); +} + +static void font_init_index(FONT *font, int index) +{ + int outline_thickness = 1; + FONTSIZEDATA *sizedata = &font->sizes[index]; + + sizedata->font_size = font_sizes[index]; + FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); + + if(sizedata->font_size >= 18) + outline_thickness = 2; + + { + unsigned glyph_index; + int charcode; + int max_h = 0; + int max_w = 0; + + charcode = FT_Get_First_Char(font->ft_face, &glyph_index); + while(glyph_index != 0) + { + /* do stuff */ + FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT); + + if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width; + if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height; + charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index); + } + + max_w = (max_w>>6)+2+outline_thickness*2; + max_h = (max_h>>6)+2+outline_thickness*2; + + for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1); + for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1); + } + + //dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h); + //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); + font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8); +} + +static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) +{ + int index = font_get_index(pixelsize); + if(font->sizes[index].font_size != font_sizes[index]) + font_init_index(font, index); + return &font->sizes[index]; +} + + +static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) +{ + int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); + int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); + + glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, + sizedata->texture_width/sizedata->num_x_chars, + sizedata->texture_height/sizedata->num_y_chars, + font_texture_format, GL_UNSIGNED_BYTE, data); +} + +/* 8k of data used for rendering glyphs */ +static unsigned char glyphdata[(4096/64) * (4096/64)]; +static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; + +static int font_get_slot(FONTSIZEDATA *sizedata) +{ + int char_count = sizedata->num_x_chars*sizedata->num_y_chars; + if(sizedata->current_character < char_count) + { + int i = sizedata->current_character; + sizedata->current_character++; + return i; + } + + /* kick out the oldest */ + /* TODO: remove this linear search */ + { + int oldest = 0; + int i; + for(i = 1; i < char_count; i++) + { + if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) + oldest = i; + } + + if(time_get()-sizedata->characters[oldest].touch_time < time_freq()) + { + font_increase_texture_size(sizedata); + return font_get_slot(sizedata); + } + + return oldest; + } +} + +static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) +{ + FT_Bitmap *bitmap; + int slot_id = 0; + int slot_w = sizedata->texture_width / sizedata->num_x_chars; + int slot_h = sizedata->texture_height / sizedata->num_y_chars; + int slot_size = slot_w*slot_h; + int outline_thickness = 1; + int x = 1; + int y = 1; + int px, py; + + FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); + + if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) + { + dbg_msg("font", "error loading glyph %d", chr); + return -1; + } + + bitmap = &font->ft_face->glyph->bitmap; + + /* fetch slot */ + slot_id = font_get_slot(sizedata); + if(slot_id < 0) + return -1; + + /* adjust spacing */ + if(sizedata->font_size >= 18) + outline_thickness = 2; + x += outline_thickness; + y += outline_thickness; + + /* prepare glyph data */ + mem_zero(glyphdata, slot_size); + + if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) + { + for(py = 0; py < bitmap->rows; py++) + for(px = 0; px < bitmap->width; px++) + glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; + } + else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) + { + for(py = 0; py < bitmap->rows; py++) + for(px = 0; px < bitmap->width; px++) + { + if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) + glyphdata[(py+y)*slot_w+px+x] = 255; + } + } + + if(0) for(py = 0; py < slot_w; py++) + for(px = 0; px < slot_h; px++) + glyphdata[py*slot_w+px] = 255; + + /* upload the glyph */ + font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); + + if(outline_thickness == 1) + { + grow(glyphdata, glyphdata_outlined, slot_w, slot_h); + font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); + } + else + { + grow(glyphdata, glyphdata_outlined, slot_w, slot_h); + grow(glyphdata_outlined, glyphdata, slot_w, slot_h); + font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); + } + + /* set char info */ + { + FONTCHAR *fontchr = &sizedata->characters[slot_id]; + float scale = 1.0f/sizedata->font_size; + float uscale = 1.0f/sizedata->texture_width; + float vscale = 1.0f/sizedata->texture_height; + int height = bitmap->rows + outline_thickness*2 + 2; + int width = bitmap->width + outline_thickness*2 + 2; + + fontchr->id = chr; + fontchr->height = height * scale; + fontchr->width = width * scale; + fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; + fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; + fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; + + fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); + fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); + fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; + fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; + } + + return slot_id; +} + +static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) +{ + FONTCHAR *fontchr = NULL; + + /* search for the character */ + /* TODO: remove this linear search */ + int i; + for(i = 0; i < sizedata->current_character; i++) + { + if(sizedata->characters[i].id == chr) + { + fontchr = &sizedata->characters[i]; + break; + } + } + + /* check if we need to render the character */ + if(!fontchr) + { + int index = font_render_glyph(font, sizedata, chr); + if(index >= 0) + fontchr = &sizedata->characters[index]; + } + + /* touch the character */ + /* TODO: don't call time_get here */ + if(fontchr) + fontchr->touch_time = time_get(); + + return fontchr; +} + +/* must only be called from the rendering function as the font must be set to the correct size */ +static void font_render_setup(FONT *font, int size) +{ + FT_Set_Pixel_Sizes(font->ft_face, 0, size); +} + +static float font_kerning(FONT *font, int left, int right) +{ + FT_Vector kerning = {0,0}; + FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning); + return (kerning.x>>6); +} + + +void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) +{ + FONT *font = cursor->font; + FONTSIZEDATA *sizedata = NULL; + + float screen_x0, screen_y0, screen_x1, screen_y1; + float fake_to_screen_x, fake_to_screen_y; + int actual_x, actual_y; + + int actual_size; + int i; + int got_new_line = 0; + float draw_x, draw_y; + float cursor_x, cursor_y; + const char *end; + + float size = cursor->font_size; + + /* to correct coords, convert to screen coords, round, and convert back */ + Graphics()->GetScreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); + + fake_to_screen_x = (Graphics()->ScreenWidth()/(screen_x1-screen_x0)); + fake_to_screen_y = (Graphics()->ScreenHeight()/(screen_y1-screen_y0)); + actual_x = cursor->x * fake_to_screen_x; + actual_y = cursor->y * fake_to_screen_y; + + cursor_x = actual_x / fake_to_screen_x; + cursor_y = actual_y / fake_to_screen_y; + + /* same with size */ + actual_size = size * fake_to_screen_y; + size = actual_size / fake_to_screen_y; + + /* fetch font data */ + if(!font) + font = default_font; + + if(!font) + return; + + sizedata = font_get_size(font, actual_size); + font_render_setup(font, actual_size); + + /* set length */ + if(length < 0) + length = strlen(text); + + end = text + length; + + /* if we don't want to render, we can just skip the first outline pass */ + i = 1; + if(cursor->flags&TEXTFLAG_RENDER) + i = 0; + + for(;i < 2; i++) + { + const char *current = (char *)text; + const char *end = current+length; + draw_x = cursor_x; + draw_y = cursor_y; + + if(cursor->flags&TEXTFLAG_RENDER) + { + // TODO: Make this better + glEnable(GL_TEXTURE_2D); + if (i == 0) + glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); + else + glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); + + Graphics()->QuadsBegin(); + if (i == 0) + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*text_a); + else + Graphics()->SetColor(text_r, text_g, text_b, text_a); + } + + while(current < end) + { + int new_line = 0; + const char *batch_end = end; + if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) + { + int wlen = word_length((char *)current); + TEXT_CURSOR compare = *cursor; + compare.x = draw_x; + compare.y = draw_y; + compare.flags &= ~TEXTFLAG_RENDER; + compare.line_width = -1; + gfx_text_ex(&compare, text, wlen); + + if(compare.x-draw_x > cursor->line_width) + { + /* word can't be fitted in one line, cut it */ + TEXT_CURSOR cutter = *cursor; + cutter.charcount = 0; + cutter.x = draw_x; + cutter.y = draw_y; + cutter.flags &= ~TEXTFLAG_RENDER; + cutter.flags |= TEXTFLAG_STOP_AT_END; + + gfx_text_ex(&cutter, (const char *)current, wlen); + wlen = cutter.charcount; + new_line = 1; + + if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ + wlen = 0; + } + else if(compare.x-cursor->start_x > cursor->line_width) + { + new_line = 1; + wlen = 0; + } + + batch_end = current + wlen; + } + + while(current < batch_end) + { + const char *tmp; + float advance = 0; + int character = 0; + int nextcharacter = 0; + FONTCHAR *chr; + + // TODO: UTF-8 decode + character = str_utf8_decode(¤t); + tmp = current; + nextcharacter = str_utf8_decode(&tmp); + + if(character == '\n') + { + draw_x = cursor->start_x; + draw_y += size; + draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ + draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; + continue; + } + + chr = font_get_char(font, sizedata, character); + + if(chr) + { + if(cursor->flags&TEXTFLAG_RENDER) + { + Graphics()->QuadsSetSubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); + Graphics()->QuadsDrawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); + } + + advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size; + } + + if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) + { + /* we hit the end of the line, no more to render or count */ + current = end; + break; + } + + draw_x += advance*size; + cursor->charcount++; + } + + if(new_line) + { + draw_x = cursor->start_x; + draw_y += size; + got_new_line = 1; + draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ + draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; + } + } + + if(cursor->flags&TEXTFLAG_RENDER) + Graphics()->QuadsEnd(); + } + + cursor->x = draw_x; + + if(got_new_line) + cursor->y = draw_y; +} diff --git a/src/engine/client/ec_inp.c b/src/engine/client/ec_inp.c deleted file mode 100644 index 495614d6..00000000 --- a/src/engine/client/ec_inp.c +++ /dev/null @@ -1,230 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "SDL.h" - -#include -#include -#include - -static struct -{ - unsigned char presses; - unsigned char releases; -} input_count[2][1024] = {{{0}}, {{0}}}; - -static unsigned char input_state[2][1024] = {{0}, {0}}; - -static int input_current = 0; -static int input_grabbed = 0; - -static unsigned int last_release = 0; -static unsigned int release_delta = -1; - -void inp_mouse_relative(int *x, int *y) -{ - int nx = 0, ny = 0; - float sens = config.inp_mousesens/100.0f; - - if(config.inp_grab) - SDL_GetRelativeMouseState(&nx, &ny); - else - { - if(input_grabbed) - { - SDL_GetMouseState(&nx,&ny); - SDL_WarpMouse(gfx_screenwidth()/2,gfx_screenheight()/2); - nx -= gfx_screenwidth()/2; ny -= gfx_screenheight()/2; - } - } - - *x = nx*sens; - *y = ny*sens; -} - -enum -{ - INPUT_BUFFER_SIZE=32 -}; - -static INPUT_EVENT input_events[INPUT_BUFFER_SIZE]; -static int num_events = 0; - -static void add_event(int unicode, int key, int flags) -{ - if(num_events != INPUT_BUFFER_SIZE) - { - input_events[num_events].unicode = unicode; - input_events[num_events].key = key; - input_events[num_events].flags = flags; - num_events++; - } -} - -int inp_num_events() -{ - return num_events; -} - -void inp_clear_events() -{ - num_events = 0; -} - -INPUT_EVENT inp_get_event(int index) -{ - if(index < 0 || index >= num_events) - { - INPUT_EVENT e = {0,0}; - return e; - } - - return input_events[index]; -} - -void inp_init() -{ - SDL_EnableUNICODE(1); - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); -} - -void inp_mouse_mode_absolute() -{ - SDL_ShowCursor(1); - input_grabbed = 0; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_OFF); -} - -void inp_mouse_mode_relative() -{ - SDL_ShowCursor(0); - input_grabbed = 1; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_ON); -} - -int inp_mouse_doubleclick() -{ - return release_delta < (time_freq() >> 2); -} - -void inp_clear_key_states() -{ - mem_zero(input_state, sizeof(input_state)); - mem_zero(input_count, sizeof(input_count)); -} - -int inp_key_presses(int key) -{ - return input_count[input_current][key].presses; -} - -int inp_key_releases(int key) -{ - return input_count[input_current][key].releases; -} - -int inp_key_state(int key) -{ - return input_state[input_current][key]; -} - -int inp_key_pressed(int key) { return input_state[input_current][key]; } -int inp_key_was_pressed(int key) { return input_state[input_current^1][key]; } -int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); } -int inp_button_pressed(int button) { return input_state[input_current][button]; } - -void inp_update() -{ - int i; - - if(input_grabbed && !gfx_window_active()) - inp_mouse_mode_absolute(); - - /*if(!input_grabbed && gfx_window_active()) - inp_mouse_mode_relative();*/ - - /* clear and begin count on the other one */ - input_current^=1; - mem_zero(&input_count[input_current], sizeof(input_count[input_current])); - mem_zero(&input_state[input_current], sizeof(input_state[input_current])); - - { - Uint8 *state = SDL_GetKeyState(&i); - if(i >= KEY_LAST) - i = KEY_LAST-1; - mem_copy(input_state[input_current], state, i); - } - - /* these states must always be updated manually because they are not in the GetKeyState from SDL */ - i = SDL_GetMouseState(NULL, NULL); - if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ - if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ - if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ - if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; - if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; - if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; - if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; - if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; - - { - SDL_Event event; - - while(SDL_PollEvent(&event)) - { - int key = -1; - int action = INPFLAG_PRESS; - switch (event.type) - { - /* handle keys */ - case SDL_KEYDOWN: - /*if(event.key.keysym.unicode < 255) */ - add_event(event.key.keysym.unicode, 0, 0); - key = event.key.keysym.sym; - break; - case SDL_KEYUP: - action = INPFLAG_RELEASE; - key = event.key.keysym.sym; - break; - - /* handle mouse buttons */ - case SDL_MOUSEBUTTONUP: - action = INPFLAG_RELEASE; - - if(event.button.button == 1) - { - release_delta = time_get() - last_release; - last_release = time_get(); - } - - /* fall through */ - case SDL_MOUSEBUTTONDOWN: - if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1; - if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2; - if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3; - if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP; - if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN; - if(event.button.button == 6) key = KEY_MOUSE_6; - if(event.button.button == 7) key = KEY_MOUSE_7; - if(event.button.button == 8) key = KEY_MOUSE_8; - break; - - /* other messages */ - case SDL_QUIT: - /* TODO: cleaner exit */ - exit(0); - break; - } - - /* */ - if(key != -1) - { - input_count[input_current][key].presses++; - if(action == INPFLAG_PRESS) - input_state[input_current][key] = 1; - add_event(0, key, action); - } - - } - } -} diff --git a/src/engine/client/ec_inp.cpp b/src/engine/client/ec_inp.cpp new file mode 100644 index 00000000..cf956471 --- /dev/null +++ b/src/engine/client/ec_inp.cpp @@ -0,0 +1,234 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "SDL.h" + +#include +#include +#include +#include + +static struct +{ + unsigned char presses; + unsigned char releases; +} input_count[2][1024] = {{{0}}, {{0}}}; + +static unsigned char input_state[2][1024] = {{0}, {0}}; + +static int input_current = 0; +static int input_grabbed = 0; + +static unsigned int last_release = 0; +static unsigned int release_delta = -1; + +// TODO: Refactor: Remove this +extern IEngineGraphics *Graphics(); + +void inp_mouse_relative(int *x, int *y) +{ + int nx = 0, ny = 0; + float sens = config.inp_mousesens/100.0f; + + if(config.inp_grab) + SDL_GetRelativeMouseState(&nx, &ny); + else + { + if(input_grabbed) + { + SDL_GetMouseState(&nx,&ny); + SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2); + nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2; + } + } + + *x = nx*sens; + *y = ny*sens; +} + +enum +{ + INPUT_BUFFER_SIZE=32 +}; + +static INPUT_EVENT input_events[INPUT_BUFFER_SIZE]; +static int num_events = 0; + +static void add_event(int unicode, int key, int flags) +{ + if(num_events != INPUT_BUFFER_SIZE) + { + input_events[num_events].unicode = unicode; + input_events[num_events].key = key; + input_events[num_events].flags = flags; + num_events++; + } +} + +int inp_num_events() +{ + return num_events; +} + +void inp_clear_events() +{ + num_events = 0; +} + +INPUT_EVENT inp_get_event(int index) +{ + if(index < 0 || index >= num_events) + { + INPUT_EVENT e = {0,0}; + return e; + } + + return input_events[index]; +} + +void inp_init() +{ + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +} + +void inp_mouse_mode_absolute() +{ + SDL_ShowCursor(1); + input_grabbed = 0; + if(config.inp_grab) + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +void inp_mouse_mode_relative() +{ + SDL_ShowCursor(0); + input_grabbed = 1; + if(config.inp_grab) + SDL_WM_GrabInput(SDL_GRAB_ON); +} + +int inp_mouse_doubleclick() +{ + return release_delta < (time_freq() >> 2); +} + +void inp_clear_key_states() +{ + mem_zero(input_state, sizeof(input_state)); + mem_zero(input_count, sizeof(input_count)); +} + +int inp_key_presses(int key) +{ + return input_count[input_current][key].presses; +} + +int inp_key_releases(int key) +{ + return input_count[input_current][key].releases; +} + +int inp_key_state(int key) +{ + return input_state[input_current][key]; +} + +int inp_key_pressed(int key) { return input_state[input_current][key]; } +int inp_key_was_pressed(int key) { return input_state[input_current^1][key]; } +int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); } +int inp_button_pressed(int button) { return input_state[input_current][button]; } + +void inp_update() +{ + int i; + + if(input_grabbed && !Graphics()->WindowActive()) + inp_mouse_mode_absolute(); + + /*if(!input_grabbed && Graphics()->WindowActive()) + inp_mouse_mode_relative();*/ + + /* clear and begin count on the other one */ + input_current^=1; + mem_zero(&input_count[input_current], sizeof(input_count[input_current])); + mem_zero(&input_state[input_current], sizeof(input_state[input_current])); + + { + Uint8 *state = SDL_GetKeyState(&i); + if(i >= KEY_LAST) + i = KEY_LAST-1; + mem_copy(input_state[input_current], state, i); + } + + /* these states must always be updated manually because they are not in the GetKeyState from SDL */ + i = SDL_GetMouseState(NULL, NULL); + if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ + if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ + if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ + if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; + if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; + if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; + if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; + if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; + + { + SDL_Event event; + + while(SDL_PollEvent(&event)) + { + int key = -1; + int action = INPFLAG_PRESS; + switch (event.type) + { + /* handle keys */ + case SDL_KEYDOWN: + /*if(event.key.keysym.unicode < 255) */ + add_event(event.key.keysym.unicode, 0, 0); + key = event.key.keysym.sym; + break; + case SDL_KEYUP: + action = INPFLAG_RELEASE; + key = event.key.keysym.sym; + break; + + /* handle mouse buttons */ + case SDL_MOUSEBUTTONUP: + action = INPFLAG_RELEASE; + + if(event.button.button == 1) + { + release_delta = time_get() - last_release; + last_release = time_get(); + } + + /* fall through */ + case SDL_MOUSEBUTTONDOWN: + if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1; + if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2; + if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3; + if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP; + if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN; + if(event.button.button == 6) key = KEY_MOUSE_6; + if(event.button.button == 7) key = KEY_MOUSE_7; + if(event.button.button == 8) key = KEY_MOUSE_8; + break; + + /* other messages */ + case SDL_QUIT: + /* TODO: cleaner exit */ + exit(0); + break; + } + + /* */ + if(key != -1) + { + input_count[input_current][key].presses++; + if(action == INPFLAG_PRESS) + input_state[input_current][key] = 1; + add_event(0, key, action); + } + + } + } +} diff --git a/src/engine/client/ec_snd.c b/src/engine/client/ec_snd.c deleted file mode 100644 index ac41ec59..00000000 --- a/src/engine/client/ec_snd.c +++ /dev/null @@ -1,465 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include - -#include "SDL.h" - -#include -#include -#include -#include - -enum -{ - NUM_SAMPLES = 512, - NUM_VOICES = 64, - NUM_CHANNELS = 16, - - MAX_FRAMES = 1024 -}; - -typedef struct -{ - short *data; - int num_frames; - int rate; - int channels; - int loop_start; - int loop_end; -} SAMPLE; - -typedef struct -{ - int vol; - int pan; -} CHANNEL; - -typedef struct -{ - SAMPLE *snd; - CHANNEL *channel; - int tick; - int vol; /* 0 - 255 */ - int flags; - int x, y; -} VOICE; - -static SAMPLE samples[NUM_SAMPLES] = { {0} }; -static VOICE voices[NUM_VOICES] = { {0} }; -static CHANNEL channels[NUM_CHANNELS] = { {255, 0} }; - -static LOCK sound_lock = 0; -static int sound_enabled = 0; - -static int center_x = 0; -static int center_y = 0; - -static int mixing_rate = 48000; -static volatile int sound_volume = 100; - -static int next_voice = 0; - -void snd_set_channel(int cid, float vol, float pan) -{ - channels[cid].vol = (int)(vol*255.0f); - channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */ -} - -static int play(int cid, int sid, int flags, float x, float y) -{ - int vid = -1; - int i; - - lock_wait(sound_lock); - - /* search for voice */ - for(i = 0; i < NUM_VOICES; i++) - { - int id = (next_voice + i) % NUM_VOICES; - if(!voices[id].snd) - { - vid = id; - next_voice = id+1; - break; - } - } - - /* voice found, use it */ - if(vid != -1) - { - voices[vid].snd = &samples[sid]; - voices[vid].channel = &channels[cid]; - voices[vid].tick = 0; - voices[vid].vol = 255; - voices[vid].flags = flags; - voices[vid].x = (int)x; - voices[vid].y = (int)y; - } - - lock_release(sound_lock); - return vid; -} - -int snd_play_at(int cid, int sid, int flags, float x, float y) -{ - return play(cid, sid, flags|SNDFLAG_POS, x, y); -} - -int snd_play(int cid, int sid, int flags) -{ - return play(cid, sid, flags, 0, 0); -} - -void snd_stop(int vid) -{ - /* TODO: a nice fade out */ - lock_wait(sound_lock); - voices[vid].snd = 0; - lock_release(sound_lock); -} - -/* TODO: there should be a faster way todo this */ -static short int2short(int i) -{ - if(i > 0x7fff) - return 0x7fff; - else if(i < -0x7fff) - return -0x7fff; - return i; -} - -static int iabs(int i) -{ - if(i<0) - return -i; - return i; -} - -static void mix(short *final_out, unsigned frames) -{ - int mix_buffer[MAX_FRAMES*2] = {0}; - int i, s; - int master_vol; - - /* aquire lock while we are mixing */ - lock_wait(sound_lock); - - master_vol = sound_volume; - - for(i = 0; i < NUM_VOICES; i++) - { - if(voices[i].snd) - { - /* mix voice */ - VOICE *v = &voices[i]; - int *out = mix_buffer; - - int step = v->snd->channels; /* setup input sources */ - short *in_l = &v->snd->data[v->tick*step]; - short *in_r = &v->snd->data[v->tick*step+1]; - - int end = v->snd->num_frames-v->tick; - - int rvol = v->channel->vol; - int lvol = v->channel->vol; - - /* make sure that we don't go outside the sound data */ - if(frames < end) - end = frames; - - /* check if we have a mono sound */ - if(v->snd->channels == 1) - in_r = in_l; - - /* volume calculation */ - if(v->flags&SNDFLAG_POS && v->channel->pan) - { - /* TODO: we should respect the channel panning value */ - const int range = 1500; /* magic value, remove */ - int dx = v->x - center_x; - int dy = v->y - center_y; - int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */ - int p = iabs(dx); - if(dist < range) - { - /* panning */ - if(dx > 0) - lvol = ((range-p)*lvol)/range; - else - rvol = ((range-p)*rvol)/range; - - /* falloff */ - lvol = (lvol*(range-dist))/range; - rvol = (rvol*(range-dist))/range; - } - else - { - lvol = 0; - rvol = 0; - } - } - - /* process all frames */ - for(s = 0; s < end; s++) - { - *out++ += (*in_l)*lvol; - *out++ += (*in_r)*rvol; - in_l += step; - in_r += step; - v->tick++; - } - - /* free voice if not used any more */ - if(v->tick == v->snd->num_frames) - v->snd = 0; - - } - } - - - /* release the lock */ - lock_release(sound_lock); - - { - /* clamp accumulated values */ - /* TODO: this seams slow */ - for(i = 0; i < frames; i++) - { - int j = i<<1; - int vl = ((mix_buffer[j]*master_vol)/101)>>8; - int vr = ((mix_buffer[j+1]*master_vol)/101)>>8; - - final_out[j] = int2short(vl); - final_out[j+1] = int2short(vr); - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(final_out, sizeof(short), frames * 2); -#endif -} - -static void sdlcallback(void *unused, Uint8 *stream, int len) -{ - mix((short *)stream, len/2/2); -} - -int snd_init() -{ - SDL_AudioSpec format; - - sound_lock = lock_create(); - - if(!config.snd_enable) - return 0; - - mixing_rate = config.snd_rate; - - /* Set 16-bit stereo audio at 22Khz */ - format.freq = config.snd_rate; - format.format = AUDIO_S16; - format.channels = 2; - format.samples = config.snd_buffer_size; - format.callback = sdlcallback; - format.userdata = NULL; - - /* Open the audio device and start playing sound! */ - if(SDL_OpenAudio(&format, NULL) < 0) - { - dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); - return -1; - } - else - dbg_msg("client/sound", "sound init successful"); - - SDL_PauseAudio(0); - - sound_enabled = 1; - snd_update(); /* update the volume */ - return 0; -} - -int snd_update() -{ - /* update volume */ - int wanted_volume = config.snd_volume; - - if(!gfx_window_active() && config.snd_nonactive_mute) - wanted_volume = 0; - - if(wanted_volume != sound_volume) - { - lock_wait(sound_lock); - sound_volume = wanted_volume; - lock_release(sound_lock); - } - - return 0; -} - -int snd_shutdown() -{ - SDL_CloseAudio(); - lock_destroy(sound_lock); - return 0; -} - -int snd_alloc_id() -{ - /* TODO: linear search, get rid of it */ - unsigned sid; - for(sid = 0; sid < NUM_SAMPLES; sid++) - { - if(samples[sid].data == 0x0) - return sid; - } - - return -1; -} - -static void rate_convert(int sid) -{ - SAMPLE *snd = &samples[sid]; - int num_frames = 0; - short *new_data = 0; - int i; - - /* make sure that we need to convert this sound */ - if(!snd->data || snd->rate == mixing_rate) - return; - - /* allocate new data */ - num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate); - new_data = mem_alloc(num_frames*snd->channels*sizeof(short), 1); - - for(i = 0; i < num_frames; i++) - { - /* resample TODO: this should be done better, like linear atleast */ - float a = i/(float)num_frames; - int f = (int)(a*snd->num_frames); - if(f >= snd->num_frames) - f = snd->num_frames-1; - - /* set new data */ - if(snd->channels == 1) - new_data[i] = snd->data[f]; - else if(snd->channels == 2) - { - new_data[i*2] = snd->data[f*2]; - new_data[i*2+1] = snd->data[f*2+1]; - } - } - - /* free old data and apply new */ - mem_free(snd->data); - snd->data = new_data; - snd->num_frames = num_frames; -} - - -static IOHANDLE file = NULL; - -static int read_data(void *buffer, int size) -{ - return io_read(file, buffer, size); -} - -int snd_load_wv(const char *filename) -{ - SAMPLE *snd; - int sid = -1; - char error[100]; - WavpackContext *context; - - /* don't waste memory on sound when we are stress testing */ - if(config.dbg_stress) - return -1; - - /* no need to load sound when we are running with no sound */ - if(!sound_enabled) - return 1; - - file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */ - if(!file) - { - dbg_msg("sound/wv", "failed to open %s", filename); - return -1; - } - - sid = snd_alloc_id(); - if(sid < 0) - return -1; - snd = &samples[sid]; - - context = WavpackOpenFileInput(read_data, error); - if (context) - { - int samples = WavpackGetNumSamples(context); - int bitspersample = WavpackGetBitsPerSample(context); - unsigned int samplerate = WavpackGetSampleRate(context); - int channels = WavpackGetNumChannels(context); - int *data; - int *src; - short *dst; - int i; - - snd->channels = channels; - snd->rate = samplerate; - - if(snd->channels > 2) - { - dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename); - return -1; - } - - /* - if(snd->rate != 44100) - { - dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); - return -1; - }*/ - - if(bitspersample != 16) - { - dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename); - return -1; - } - - data = (int *)mem_alloc(4*samples*channels, 1); - WavpackUnpackSamples(context, data, samples); /* TODO: check return value */ - src = data; - - snd->data = (short *)mem_alloc(2*samples*channels, 1); - dst = snd->data; - - for (i = 0; i < samples*channels; i++) - *dst++ = (short)*src++; - - mem_free(data); - - snd->num_frames = samples; - snd->loop_start = -1; - snd->loop_end = -1; - } - else - { - dbg_msg("sound/wv", "failed to open %s: %s", filename, error); - } - - io_close(file); - file = NULL; - - if(config.debug) - dbg_msg("sound/wv", "loaded %s", filename); - - rate_convert(sid); - return sid; -} - -void snd_set_listener_pos(float x, float y) -{ - center_x = (int)x; - center_y = (int)y; -} diff --git a/src/engine/client/ec_snd.cpp b/src/engine/client/ec_snd.cpp new file mode 100644 index 00000000..3baea982 --- /dev/null +++ b/src/engine/client/ec_snd.cpp @@ -0,0 +1,471 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include + +#include "SDL.h" + +extern "C" { // wavpack + #include +} +#include +#include +#include + +enum +{ + NUM_SAMPLES = 512, + NUM_VOICES = 64, + NUM_CHANNELS = 16, + + MAX_FRAMES = 1024 +}; + +typedef struct +{ + short *data; + int num_frames; + int rate; + int channels; + int loop_start; + int loop_end; +} SAMPLE; + +typedef struct +{ + int vol; + int pan; +} CHANNEL; + +typedef struct +{ + SAMPLE *snd; + CHANNEL *channel; + int tick; + int vol; /* 0 - 255 */ + int flags; + int x, y; +} VOICE; + +static SAMPLE samples[NUM_SAMPLES] = { {0} }; +static VOICE voices[NUM_VOICES] = { {0} }; +static CHANNEL channels[NUM_CHANNELS] = { {255, 0} }; + +static LOCK sound_lock = 0; +static int sound_enabled = 0; + +static int center_x = 0; +static int center_y = 0; + +static int mixing_rate = 48000; +static volatile int sound_volume = 100; + +static int next_voice = 0; + +void snd_set_channel(int cid, float vol, float pan) +{ + channels[cid].vol = (int)(vol*255.0f); + channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */ +} + +static int play(int cid, int sid, int flags, float x, float y) +{ + int vid = -1; + int i; + + lock_wait(sound_lock); + + /* search for voice */ + for(i = 0; i < NUM_VOICES; i++) + { + int id = (next_voice + i) % NUM_VOICES; + if(!voices[id].snd) + { + vid = id; + next_voice = id+1; + break; + } + } + + /* voice found, use it */ + if(vid != -1) + { + voices[vid].snd = &samples[sid]; + voices[vid].channel = &channels[cid]; + voices[vid].tick = 0; + voices[vid].vol = 255; + voices[vid].flags = flags; + voices[vid].x = (int)x; + voices[vid].y = (int)y; + } + + lock_release(sound_lock); + return vid; +} + +int snd_play_at(int cid, int sid, int flags, float x, float y) +{ + return play(cid, sid, flags|SNDFLAG_POS, x, y); +} + +int snd_play(int cid, int sid, int flags) +{ + return play(cid, sid, flags, 0, 0); +} + +void snd_stop(int vid) +{ + /* TODO: a nice fade out */ + lock_wait(sound_lock); + voices[vid].snd = 0; + lock_release(sound_lock); +} + +/* TODO: there should be a faster way todo this */ +static short int2short(int i) +{ + if(i > 0x7fff) + return 0x7fff; + else if(i < -0x7fff) + return -0x7fff; + return i; +} + +static int iabs(int i) +{ + if(i<0) + return -i; + return i; +} + +static void mix(short *final_out, unsigned frames) +{ + int mix_buffer[MAX_FRAMES*2] = {0}; + int master_vol; + + /* aquire lock while we are mixing */ + lock_wait(sound_lock); + + master_vol = sound_volume; + + for(unsigned i = 0; i < NUM_VOICES; i++) + { + if(voices[i].snd) + { + /* mix voice */ + VOICE *v = &voices[i]; + int *out = mix_buffer; + + int step = v->snd->channels; /* setup input sources */ + short *in_l = &v->snd->data[v->tick*step]; + short *in_r = &v->snd->data[v->tick*step+1]; + + unsigned end = v->snd->num_frames-v->tick; + + int rvol = v->channel->vol; + int lvol = v->channel->vol; + + /* make sure that we don't go outside the sound data */ + if(frames < end) + end = frames; + + /* check if we have a mono sound */ + if(v->snd->channels == 1) + in_r = in_l; + + /* volume calculation */ + if(v->flags&SNDFLAG_POS && v->channel->pan) + { + /* TODO: we should respect the channel panning value */ + const int range = 1500; /* magic value, remove */ + int dx = v->x - center_x; + int dy = v->y - center_y; + int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */ + int p = iabs(dx); + if(dist < range) + { + /* panning */ + if(dx > 0) + lvol = ((range-p)*lvol)/range; + else + rvol = ((range-p)*rvol)/range; + + /* falloff */ + lvol = (lvol*(range-dist))/range; + rvol = (rvol*(range-dist))/range; + } + else + { + lvol = 0; + rvol = 0; + } + } + + /* process all frames */ + for(unsigned s = 0; s < end; s++) + { + *out++ += (*in_l)*lvol; + *out++ += (*in_r)*rvol; + in_l += step; + in_r += step; + v->tick++; + } + + /* free voice if not used any more */ + if(v->tick == v->snd->num_frames) + v->snd = 0; + + } + } + + + /* release the lock */ + lock_release(sound_lock); + + { + /* clamp accumulated values */ + /* TODO: this seams slow */ + for(unsigned i = 0; i < frames; i++) + { + int j = i<<1; + int vl = ((mix_buffer[j]*master_vol)/101)>>8; + int vr = ((mix_buffer[j+1]*master_vol)/101)>>8; + + final_out[j] = int2short(vl); + final_out[j+1] = int2short(vr); + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(final_out, sizeof(short), frames * 2); +#endif +} + +static void sdlcallback(void *unused, Uint8 *stream, int len) +{ + mix((short *)stream, len/2/2); +} + +int snd_init() +{ + SDL_AudioSpec format; + + sound_lock = lock_create(); + + if(!config.snd_enable) + return 0; + + mixing_rate = config.snd_rate; + + /* Set 16-bit stereo audio at 22Khz */ + format.freq = config.snd_rate; + format.format = AUDIO_S16; + format.channels = 2; + format.samples = config.snd_buffer_size; + format.callback = sdlcallback; + format.userdata = NULL; + + /* Open the audio device and start playing sound! */ + if(SDL_OpenAudio(&format, NULL) < 0) + { + dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); + return -1; + } + else + dbg_msg("client/sound", "sound init successful"); + + SDL_PauseAudio(0); + + sound_enabled = 1; + snd_update(); /* update the volume */ + return 0; +} + +// TODO: Refactor: Remove this +extern IEngineGraphics *Graphics(); + + +int snd_update() +{ + /* update volume */ + int wanted_volume = config.snd_volume; + + if(!Graphics()->WindowActive() && config.snd_nonactive_mute) + wanted_volume = 0; + + if(wanted_volume != sound_volume) + { + lock_wait(sound_lock); + sound_volume = wanted_volume; + lock_release(sound_lock); + } + + return 0; +} + +int snd_shutdown() +{ + SDL_CloseAudio(); + lock_destroy(sound_lock); + return 0; +} + +int snd_alloc_id() +{ + /* TODO: linear search, get rid of it */ + unsigned sid; + for(sid = 0; sid < NUM_SAMPLES; sid++) + { + if(samples[sid].data == 0x0) + return sid; + } + + return -1; +} + +static void rate_convert(int sid) +{ + SAMPLE *snd = &samples[sid]; + int num_frames = 0; + short *new_data = 0; + int i; + + /* make sure that we need to convert this sound */ + if(!snd->data || snd->rate == mixing_rate) + return; + + /* allocate new data */ + num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate); + new_data = (short *)mem_alloc(num_frames*snd->channels*sizeof(short), 1); + + for(i = 0; i < num_frames; i++) + { + /* resample TODO: this should be done better, like linear atleast */ + float a = i/(float)num_frames; + int f = (int)(a*snd->num_frames); + if(f >= snd->num_frames) + f = snd->num_frames-1; + + /* set new data */ + if(snd->channels == 1) + new_data[i] = snd->data[f]; + else if(snd->channels == 2) + { + new_data[i*2] = snd->data[f*2]; + new_data[i*2+1] = snd->data[f*2+1]; + } + } + + /* free old data and apply new */ + mem_free(snd->data); + snd->data = new_data; + snd->num_frames = num_frames; +} + + +static IOHANDLE file = NULL; + +static int read_data(void *buffer, int size) +{ + return io_read(file, buffer, size); +} + +int snd_load_wv(const char *filename) +{ + SAMPLE *snd; + int sid = -1; + char error[100]; + WavpackContext *context; + + /* don't waste memory on sound when we are stress testing */ + if(config.dbg_stress) + return -1; + + /* no need to load sound when we are running with no sound */ + if(!sound_enabled) + return 1; + + file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */ + if(!file) + { + dbg_msg("sound/wv", "failed to open %s", filename); + return -1; + } + + sid = snd_alloc_id(); + if(sid < 0) + return -1; + snd = &samples[sid]; + + context = WavpackOpenFileInput(read_data, error); + if (context) + { + int samples = WavpackGetNumSamples(context); + int bitspersample = WavpackGetBitsPerSample(context); + unsigned int samplerate = WavpackGetSampleRate(context); + int channels = WavpackGetNumChannels(context); + int *data; + int *src; + short *dst; + int i; + + snd->channels = channels; + snd->rate = samplerate; + + if(snd->channels > 2) + { + dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename); + return -1; + } + + /* + if(snd->rate != 44100) + { + dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); + return -1; + }*/ + + if(bitspersample != 16) + { + dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename); + return -1; + } + + data = (int *)mem_alloc(4*samples*channels, 1); + WavpackUnpackSamples(context, data, samples); /* TODO: check return value */ + src = data; + + snd->data = (short *)mem_alloc(2*samples*channels, 1); + dst = snd->data; + + for (i = 0; i < samples*channels; i++) + *dst++ = (short)*src++; + + mem_free(data); + + snd->num_frames = samples; + snd->loop_start = -1; + snd->loop_end = -1; + } + else + { + dbg_msg("sound/wv", "failed to open %s: %s", filename, error); + } + + io_close(file); + file = NULL; + + if(config.debug) + dbg_msg("sound/wv", "loaded %s", filename); + + rate_convert(sid); + return sid; +} + +void snd_set_listener_pos(float x, float y) +{ + center_x = (int)x; + center_y = (int)y; +} diff --git a/src/engine/client/ec_srvbrowse.c b/src/engine/client/ec_srvbrowse.c deleted file mode 100644 index 4a85e778..00000000 --- a/src/engine/client/ec_srvbrowse.c +++ /dev/null @@ -1,737 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -extern NETCLIENT *net; - - -/* ------ server browse ---- */ -/* TODO: move all this to a separate file */ - -typedef struct SERVERENTRY_t SERVERENTRY; -struct SERVERENTRY_t -{ - NETADDR addr; - int64 request_time; - int got_info; - SERVER_INFO info; - - SERVERENTRY *next_ip; /* ip hashed list */ - - SERVERENTRY *prev_req; /* request list */ - SERVERENTRY *next_req; -}; - -static HEAP *serverlist_heap = 0; -static SERVERENTRY **serverlist = 0; -static int *sorted_serverlist = 0; - -enum -{ - MAX_FAVORITES=256 -}; - -static NETADDR favorite_servers[MAX_FAVORITES]; -static int num_favorite_servers = 0; - -static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */ - -static SERVERENTRY *first_req_server = 0; /* request list */ -static SERVERENTRY *last_req_server = 0; -static int num_requests = 0; - -static int need_refresh = 0; - -static int num_sorted_servers = 0; -static int num_sorted_servers_capacity = 0; -static int num_servers = 0; -static int num_server_capacity = 0; - -static int sorthash = 0; -static char filterstring[64] = {0}; -static char filtergametypestring[128] = {0}; - -/* the token is to keep server refresh separated from each other */ -static int current_token = 1; - -static int serverlist_type = 0; -static int64 broadcast_time = 0; - -int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; } -int client_serverbrowse_num() { return num_servers; } - -SERVER_INFO *client_serverbrowse_get(int index) -{ - if(index < 0 || index >= num_servers) - return 0; - return &serverlist[index]->info; -} - -int client_serverbrowse_sorted_num() { return num_sorted_servers; } - -SERVER_INFO *client_serverbrowse_sorted_get(int index) -{ - if(index < 0 || index >= num_sorted_servers) - return 0; - return &serverlist[sorted_serverlist[index]]->info; -} - - -int client_serverbrowse_num_requests() -{ - return num_requests; -} - -static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.name, b->info.name); -} - -static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.map, b->info.map); -} - -static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.latency > b->info.latency) return 1; - if(a->info.latency < b->info.latency) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.gametype, b->info.gametype); -} - -static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.progression > b->info.progression) return 1; - if(a->info.progression < b->info.progression) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.num_players > b->info.num_players) return 1; - if(a->info.num_players < b->info.num_players) return -1; - return 0; -} - -static void client_serverbrowse_filter() -{ - int i = 0, p = 0; - num_sorted_servers = 0; - - /* allocate the sorted list */ - if(num_sorted_servers_capacity < num_servers) - { - if(sorted_serverlist) - mem_free(sorted_serverlist); - num_sorted_servers_capacity = num_servers; - sorted_serverlist = mem_alloc(num_sorted_servers_capacity*sizeof(int), 1); - } - - /* filter the servers */ - for(i = 0; i < num_servers; i++) - { - int filtered = 0; - - if(config.b_filter_empty && serverlist[i]->info.num_players == 0) - filtered = 1; - else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players) - filtered = 1; - else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD) - filtered = 1; - else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0)) - filtered = 1; - else if(config.b_filter_pure_map && - !(strcmp(serverlist[i]->info.map, "dm1") == 0 || - strcmp(serverlist[i]->info.map, "dm2") == 0 || - strcmp(serverlist[i]->info.map, "dm6") == 0 || - strcmp(serverlist[i]->info.map, "dm7") == 0 || - strcmp(serverlist[i]->info.map, "dm8") == 0 || - strcmp(serverlist[i]->info.map, "dm9") == 0 || - strcmp(serverlist[i]->info.map, "ctf1") == 0 || - strcmp(serverlist[i]->info.map, "ctf2") == 0 || - strcmp(serverlist[i]->info.map, "ctf3") == 0 || - strcmp(serverlist[i]->info.map, "ctf4") == 0 || - strcmp(serverlist[i]->info.map, "ctf5") == 0) - ) - { - filtered = 1; - } - else if(config.b_filter_ping < serverlist[i]->info.latency) - filtered = 1; - else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0) - filtered = 1; - else - { - if(config.b_filter_string[0] != 0) - { - int matchfound = 0; - - serverlist[i]->info.quicksearch_hit = 0; - - /* match against server name */ - if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME; - } - - /* match against players */ - for(p = 0; p < serverlist[i]->info.num_players; p++) - { - if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME; - break; - } - } - - /* match against map */ - if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME; - } - - if(!matchfound) - filtered = 1; - } - - if(!filtered && config.b_filter_gametype[0] != 0) - { - /* match against game type */ - if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype)) - filtered = 1; - } - } - - if(filtered == 0) - sorted_serverlist[num_sorted_servers++] = i; - } -} - -static int client_serverbrowse_sorthash() -{ - int i = config.b_sort&0xf; - i |= config.b_filter_empty<<4; - i |= config.b_filter_full<<5; - i |= config.b_filter_pw<<6; - i |= config.b_sort_order<<7; - i |= config.b_filter_compatversion<<8; - i |= config.b_filter_pure<<9; - i |= config.b_filter_pure_map<<10; - i |= config.b_filter_ping<<16; - return i; -} - -static void client_serverbrowse_sort() -{ - int i; - - /* create filtered list */ - client_serverbrowse_filter(); - - /* sort */ - if(config.b_sort == BROWSESORT_NAME) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name); - else if(config.b_sort == BROWSESORT_PING) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping); - else if(config.b_sort == BROWSESORT_MAP) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map); - else if(config.b_sort == BROWSESORT_NUMPLAYERS) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers); - else if(config.b_sort == BROWSESORT_GAMETYPE) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype); - else if(config.b_sort == BROWSESORT_PROGRESSION) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression); - - /* invert the list if requested */ - if(config.b_sort_order) - { - for(i = 0; i < num_sorted_servers/2; i++) - { - int temp = sorted_serverlist[i]; - sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1]; - sorted_serverlist[num_sorted_servers-i-1] = temp; - } - } - - /* set indexes */ - for(i = 0; i < num_sorted_servers; i++) - serverlist[sorted_serverlist[i]]->info.sorted_index = i; - - str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); - str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); - sorthash = client_serverbrowse_sorthash(); -} - -static void client_serverbrowse_remove_request(SERVERENTRY *entry) -{ - if(entry->prev_req || entry->next_req || first_req_server == entry) - { - if(entry->prev_req) - entry->prev_req->next_req = entry->next_req; - else - first_req_server = entry->next_req; - - if(entry->next_req) - entry->next_req->prev_req = entry->prev_req; - else - last_req_server = entry->prev_req; - - entry->prev_req = 0; - entry->next_req = 0; - num_requests--; - } -} - -static SERVERENTRY *client_serverbrowse_find(NETADDR *addr) -{ - SERVERENTRY *entry = serverlist_ip[addr->ip[0]]; - - for(; entry; entry = entry->next_ip) - { - if(net_addr_comp(&entry->addr, addr) == 0) - return entry; - } - return (SERVERENTRY*)0; -} - -void client_serverbrowse_queuerequest(SERVERENTRY *entry) -{ - /* add it to the list of servers that we should request info from */ - entry->prev_req = last_req_server; - if(last_req_server) - last_req_server->next_req = entry; - else - first_req_server = entry; - last_req_server = entry; - - num_requests++; -} - -void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info) -{ - int fav = entry->info.favorite; - entry->info = *info; - entry->info.favorite = fav; - entry->info.netaddr = entry->addr; - - // all these are just for nice compability - if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype)); - - /*if(!request) - { - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - }*/ - - entry->got_info = 1; - client_serverbrowse_sort(); -} - -SERVERENTRY *client_serverbrowse_add(NETADDR *addr) -{ - int hash = addr->ip[0]; - SERVERENTRY *entry = 0; - int i; - - /* create new entry */ - entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY)); - mem_zero(entry, sizeof(SERVERENTRY)); - - /* set the info */ - entry->addr = *addr; - entry->info.netaddr = *addr; - - entry->info.latency = 999; - str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - - /*if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ - - /* check if it's a favorite */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(addr, &favorite_servers[i]) == 0) - entry->info.favorite = 1; - } - - /* add to the hash list */ - entry->next_ip = serverlist_ip[hash]; - serverlist_ip[hash] = entry; - - if(num_servers == num_server_capacity) - { - SERVERENTRY **newlist; - num_server_capacity += 100; - newlist = mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1); - mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*)); - mem_free(serverlist); - serverlist = newlist; - } - - /* add to list */ - serverlist[num_servers] = entry; - entry->info.server_index = num_servers; - num_servers++; - - return entry; -} - -void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info) -{ - SERVERENTRY *entry = 0; - if(type == BROWSESET_MASTER_ADD) - { - if(serverlist_type != BROWSETYPE_INTERNET) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_FAV_ADD) - { - if(serverlist_type != BROWSETYPE_FAVORITES) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_TOKEN) - { - if(token != current_token) - return; - - entry = client_serverbrowse_find(addr); - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_INTERNET) - { - entry = client_serverbrowse_find(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_LAN) - { - entry = client_serverbrowse_find(addr); - if(entry) - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - client_serverbrowse_setinfo(entry, info); - } - - client_serverbrowse_sort(); -} - -void client_serverbrowse_refresh(int type) -{ - /* clear out everything */ - if(serverlist_heap) - memheap_destroy(serverlist_heap); - serverlist_heap = memheap_create(); - num_servers = 0; - num_sorted_servers = 0; - mem_zero(serverlist_ip, sizeof(serverlist_ip)); - first_req_server = 0; - last_req_server = 0; - num_requests = 0; - - /* next token */ - current_token = (current_token+1)&0xff; - - /* */ - serverlist_type = type; - - if(type == BROWSETYPE_LAN) - { - unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1]; - NETCHUNK packet; - int i; - - mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token; - - packet.client_id = -1; - mem_zero(&packet, sizeof(packet)); - packet.address.ip[0] = 255; - packet.address.ip[1] = 255; - packet.address.ip[2] = 255; - packet.address.ip[3] = 255; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(buffer); - packet.data = buffer; - broadcast_time = time_get(); - - for(i = 8303; i <= 8310; i++) - { - packet.address.port = i; - netclient_send(net, &packet); - } - - if(config.debug) - dbg_msg("client", "broadcasting for servers"); - } - else if(type == BROWSETYPE_INTERNET) - need_refresh = 1; - else if(type == BROWSETYPE_FAVORITES) - { - int i; - for(i = 0; i < num_favorite_servers; i++) - client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0); - } -} - -static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry) -{ - /*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/ - NETCHUNK p; - - if(config.debug) - { - dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - } - - /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ - - p.client_id = -1; - p.address = *addr; - p.flags = NETSENDFLAG_CONNLESS; - /*p.data_size = sizeof(buffer); - p.data = buffer; - netclient_send(net, &p);*/ - - /* send old requtest style aswell */ - p.data_size = sizeof(SERVERBROWSE_OLD_GETINFO); - p.data = SERVERBROWSE_OLD_GETINFO; - netclient_send(net, &p); - - if(entry) - entry->request_time = time_get(); -} - -void client_serverbrowse_request(NETADDR *addr) -{ - client_serverbrowse_request_impl(addr, 0); -} - - -void client_serverbrowse_update() -{ - int64 timeout = time_freq(); - int64 now = time_get(); - int count; - SERVERENTRY *entry, *next; - - /* do server list requests */ - if(need_refresh && !mastersrv_refreshing()) - { - NETADDR addr; - NETCHUNK p; - int i; - - need_refresh = 0; - - mem_zero(&p, sizeof(p)); - p.client_id = -1; - p.flags = NETSENDFLAG_CONNLESS; - p.data_size = sizeof(SERVERBROWSE_GETLIST); - p.data = SERVERBROWSE_GETLIST; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - addr = mastersrv_get(i); - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - continue; - - p.address = addr; - netclient_send(net, &p); - } - - if(config.debug) - dbg_msg("client", "requesting server list"); - } - - /* do timeouts */ - entry = first_req_server; - while(1) - { - if(!entry) /* no more entries */ - break; - - next = entry->next_req; - - if(entry->request_time && entry->request_time+timeout < now) - { - /* timeout */ - client_serverbrowse_remove_request(entry); - num_requests--; - } - - entry = next; - } - - /* do timeouts */ - entry = first_req_server; - count = 0; - while(1) - { - if(!entry) /* no more entries */ - break; - - /* no more then 10 concurrent requests */ - if(count == config.b_max_requests) - break; - - if(entry->request_time == 0) - client_serverbrowse_request_impl(&entry->addr, entry); - - count++; - entry = entry->next_req; - } - - /* check if we need to resort */ - /* TODO: remove the strcmp */ - if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0) - client_serverbrowse_sort(); -} - - -int client_serverbrowse_isfavorite(NETADDR addr) -{ - /* search for the address */ - int i; - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return 1; - } - return 0; -} - -void client_serverbrowse_addfavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - if(num_favorite_servers == MAX_FAVORITES) - return; - - /* make sure that we don't already have the server in our list */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return; - } - - /* add the server to the list */ - favorite_servers[num_favorite_servers++] = addr; - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 1; - dbg_msg("", "added fav, %p", entry); -} - -void client_serverbrowse_removefavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - { - mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1)); - num_favorite_servers--; - - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 0; - - return; - } - } -} - -void client_serverbrowse_save() -{ - int i; - char addrstr[128]; - char buffer[256]; - for(i = 0; i < num_favorite_servers; i++) - { - net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr)); - str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr); - engine_config_write_line(buffer); - } -} - - -int client_serverbrowse_refreshingmasters() -{ - return mastersrv_refreshing(); -} diff --git a/src/engine/client/ec_srvbrowse.cpp b/src/engine/client/ec_srvbrowse.cpp new file mode 100644 index 00000000..1b04937a --- /dev/null +++ b/src/engine/client/ec_srvbrowse.cpp @@ -0,0 +1,736 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +extern CNetClient m_NetClient; + + +/* ------ server browse ---- */ +/* TODO: move all this to a separate file */ + +typedef struct SERVERENTRY_t SERVERENTRY; +struct SERVERENTRY_t +{ + NETADDR addr; + int64 request_time; + int got_info; + SERVER_INFO info; + + SERVERENTRY *next_ip; /* ip hashed list */ + + SERVERENTRY *prev_req; /* request list */ + SERVERENTRY *next_req; +}; + +static HEAP *serverlist_heap = 0; +static SERVERENTRY **serverlist = 0; +static int *sorted_serverlist = 0; + +enum +{ + MAX_FAVORITES=256 +}; + +static NETADDR favorite_servers[MAX_FAVORITES]; +static int num_favorite_servers = 0; + +static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */ + +static SERVERENTRY *first_req_server = 0; /* request list */ +static SERVERENTRY *last_req_server = 0; +static int num_requests = 0; + +static int need_refresh = 0; + +static int num_sorted_servers = 0; +static int num_sorted_servers_capacity = 0; +static int num_servers = 0; +static int num_server_capacity = 0; + +static int sorthash = 0; +static char filterstring[64] = {0}; +static char filtergametypestring[128] = {0}; + +/* the token is to keep server refresh separated from each other */ +static int current_token = 1; + +static int serverlist_type = 0; +static int64 broadcast_time = 0; + +int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; } +int client_serverbrowse_num() { return num_servers; } + +SERVER_INFO *client_serverbrowse_get(int index) +{ + if(index < 0 || index >= num_servers) + return 0; + return &serverlist[index]->info; +} + +int client_serverbrowse_sorted_num() { return num_sorted_servers; } + +SERVER_INFO *client_serverbrowse_sorted_get(int index) +{ + if(index < 0 || index >= num_sorted_servers) + return 0; + return &serverlist[sorted_serverlist[index]]->info; +} + + +int client_serverbrowse_num_requests() +{ + return num_requests; +} + +static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.name, b->info.name); +} + +static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.map, b->info.map); +} + +static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.latency > b->info.latency) return 1; + if(a->info.latency < b->info.latency) return -1; + return 0; +} + +static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.gametype, b->info.gametype); +} + +static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.progression > b->info.progression) return 1; + if(a->info.progression < b->info.progression) return -1; + return 0; +} + +static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.num_players > b->info.num_players) return 1; + if(a->info.num_players < b->info.num_players) return -1; + return 0; +} + +static void client_serverbrowse_filter() +{ + int i = 0, p = 0; + num_sorted_servers = 0; + + /* allocate the sorted list */ + if(num_sorted_servers_capacity < num_servers) + { + if(sorted_serverlist) + mem_free(sorted_serverlist); + num_sorted_servers_capacity = num_servers; + sorted_serverlist = (int *)mem_alloc(num_sorted_servers_capacity*sizeof(int), 1); + } + + /* filter the servers */ + for(i = 0; i < num_servers; i++) + { + int filtered = 0; + + if(config.b_filter_empty && serverlist[i]->info.num_players == 0) + filtered = 1; + else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players) + filtered = 1; + else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD) + filtered = 1; + else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0)) + filtered = 1; + else if(config.b_filter_pure_map && + !(strcmp(serverlist[i]->info.map, "dm1") == 0 || + strcmp(serverlist[i]->info.map, "dm2") == 0 || + strcmp(serverlist[i]->info.map, "dm6") == 0 || + strcmp(serverlist[i]->info.map, "dm7") == 0 || + strcmp(serverlist[i]->info.map, "dm8") == 0 || + strcmp(serverlist[i]->info.map, "dm9") == 0 || + strcmp(serverlist[i]->info.map, "ctf1") == 0 || + strcmp(serverlist[i]->info.map, "ctf2") == 0 || + strcmp(serverlist[i]->info.map, "ctf3") == 0 || + strcmp(serverlist[i]->info.map, "ctf4") == 0 || + strcmp(serverlist[i]->info.map, "ctf5") == 0) + ) + { + filtered = 1; + } + else if(config.b_filter_ping < serverlist[i]->info.latency) + filtered = 1; + else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0) + filtered = 1; + else + { + if(config.b_filter_string[0] != 0) + { + int matchfound = 0; + + serverlist[i]->info.quicksearch_hit = 0; + + /* match against server name */ + if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME; + } + + /* match against players */ + for(p = 0; p < serverlist[i]->info.num_players; p++) + { + if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME; + break; + } + } + + /* match against map */ + if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME; + } + + if(!matchfound) + filtered = 1; + } + + if(!filtered && config.b_filter_gametype[0] != 0) + { + /* match against game type */ + if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype)) + filtered = 1; + } + } + + if(filtered == 0) + sorted_serverlist[num_sorted_servers++] = i; + } +} + +static int client_serverbrowse_sorthash() +{ + int i = config.b_sort&0xf; + i |= config.b_filter_empty<<4; + i |= config.b_filter_full<<5; + i |= config.b_filter_pw<<6; + i |= config.b_sort_order<<7; + i |= config.b_filter_compatversion<<8; + i |= config.b_filter_pure<<9; + i |= config.b_filter_pure_map<<10; + i |= config.b_filter_ping<<16; + return i; +} + +static void client_serverbrowse_sort() +{ + int i; + + /* create filtered list */ + client_serverbrowse_filter(); + + /* sort */ + if(config.b_sort == BROWSESORT_NAME) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name); + else if(config.b_sort == BROWSESORT_PING) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping); + else if(config.b_sort == BROWSESORT_MAP) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map); + else if(config.b_sort == BROWSESORT_NUMPLAYERS) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers); + else if(config.b_sort == BROWSESORT_GAMETYPE) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype); + else if(config.b_sort == BROWSESORT_PROGRESSION) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression); + + /* invert the list if requested */ + if(config.b_sort_order) + { + for(i = 0; i < num_sorted_servers/2; i++) + { + int temp = sorted_serverlist[i]; + sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1]; + sorted_serverlist[num_sorted_servers-i-1] = temp; + } + } + + /* set indexes */ + for(i = 0; i < num_sorted_servers; i++) + serverlist[sorted_serverlist[i]]->info.sorted_index = i; + + str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); + str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); + sorthash = client_serverbrowse_sorthash(); +} + +static void client_serverbrowse_remove_request(SERVERENTRY *entry) +{ + if(entry->prev_req || entry->next_req || first_req_server == entry) + { + if(entry->prev_req) + entry->prev_req->next_req = entry->next_req; + else + first_req_server = entry->next_req; + + if(entry->next_req) + entry->next_req->prev_req = entry->prev_req; + else + last_req_server = entry->prev_req; + + entry->prev_req = 0; + entry->next_req = 0; + num_requests--; + } +} + +static SERVERENTRY *client_serverbrowse_find(NETADDR *addr) +{ + SERVERENTRY *entry = serverlist_ip[addr->ip[0]]; + + for(; entry; entry = entry->next_ip) + { + if(net_addr_comp(&entry->addr, addr) == 0) + return entry; + } + return (SERVERENTRY*)0; +} + +void client_serverbrowse_queuerequest(SERVERENTRY *entry) +{ + /* add it to the list of servers that we should request info from */ + entry->prev_req = last_req_server; + if(last_req_server) + last_req_server->next_req = entry; + else + first_req_server = entry; + last_req_server = entry; + + num_requests++; +} + +void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info) +{ + int fav = entry->info.favorite; + entry->info = *info; + entry->info.favorite = fav; + entry->info.netaddr = entry->addr; + + // all these are just for nice compability + if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype)); + else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype)); + else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype)); + + /*if(!request) + { + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + }*/ + + entry->got_info = 1; + client_serverbrowse_sort(); +} + +SERVERENTRY *client_serverbrowse_add(NETADDR *addr) +{ + int hash = addr->ip[0]; + SERVERENTRY *entry = 0; + int i; + + /* create new entry */ + entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY)); + mem_zero(entry, sizeof(SERVERENTRY)); + + /* set the info */ + entry->addr = *addr; + entry->info.netaddr = *addr; + + entry->info.latency = 999; + str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d", + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + + /*if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ + + /* check if it's a favorite */ + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(addr, &favorite_servers[i]) == 0) + entry->info.favorite = 1; + } + + /* add to the hash list */ + entry->next_ip = serverlist_ip[hash]; + serverlist_ip[hash] = entry; + + if(num_servers == num_server_capacity) + { + SERVERENTRY **newlist; + num_server_capacity += 100; + newlist = (SERVERENTRY **)mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1); + mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*)); + mem_free(serverlist); + serverlist = newlist; + } + + /* add to list */ + serverlist[num_servers] = entry; + entry->info.server_index = num_servers; + num_servers++; + + return entry; +} + +void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info) +{ + SERVERENTRY *entry = 0; + if(type == BROWSESET_MASTER_ADD) + { + if(serverlist_type != BROWSETYPE_INTERNET) + return; + + if(!client_serverbrowse_find(addr)) + { + entry = client_serverbrowse_add(addr); + client_serverbrowse_queuerequest(entry); + } + } + else if(type == BROWSESET_FAV_ADD) + { + if(serverlist_type != BROWSETYPE_FAVORITES) + return; + + if(!client_serverbrowse_find(addr)) + { + entry = client_serverbrowse_add(addr); + client_serverbrowse_queuerequest(entry); + } + } + else if(type == BROWSESET_TOKEN) + { + if(token != current_token) + return; + + entry = client_serverbrowse_find(addr); + if(!entry) + entry = client_serverbrowse_add(addr); + if(entry) + { + client_serverbrowse_setinfo(entry, info); + if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); + else + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + } + } + else if(type == BROWSESET_OLD_INTERNET) + { + entry = client_serverbrowse_find(addr); + if(entry) + { + client_serverbrowse_setinfo(entry, info); + + if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); + else + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + } + } + else if(type == BROWSESET_OLD_LAN) + { + entry = client_serverbrowse_find(addr); + if(entry) + if(!entry) + entry = client_serverbrowse_add(addr); + if(entry) + client_serverbrowse_setinfo(entry, info); + } + + client_serverbrowse_sort(); +} + +void client_serverbrowse_refresh(int type) +{ + /* clear out everything */ + if(serverlist_heap) + memheap_destroy(serverlist_heap); + serverlist_heap = memheap_create(); + num_servers = 0; + num_sorted_servers = 0; + mem_zero(serverlist_ip, sizeof(serverlist_ip)); + first_req_server = 0; + last_req_server = 0; + num_requests = 0; + + /* next token */ + current_token = (current_token+1)&0xff; + + /* */ + serverlist_type = type; + + if(type == BROWSETYPE_LAN) + { + unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; + CNetChunk Packet; + int i; + + mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + Buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token; + + Packet.m_ClientID = -1; + mem_zero(&Packet, sizeof(Packet)); + Packet.m_Address.ip[0] = 255; + Packet.m_Address.ip[1] = 255; + Packet.m_Address.ip[2] = 255; + Packet.m_Address.ip[3] = 255; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(Buffer); + Packet.m_pData = Buffer; + broadcast_time = time_get(); + + for(i = 8303; i <= 8310; i++) + { + Packet.m_Address.port = i; + m_NetClient.Send(&Packet); + } + + if(config.debug) + dbg_msg("client", "broadcasting for servers"); + } + else if(type == BROWSETYPE_INTERNET) + need_refresh = 1; + else if(type == BROWSETYPE_FAVORITES) + { + for(int i = 0; i < num_favorite_servers; i++) + client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0); + } +} + +static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry) +{ + /*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/ + CNetChunk Packet; + + if(config.debug) + { + dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + } + + /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ + + Packet.m_ClientID = -1; + Packet.m_Address = *addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + /*p.data_size = sizeof(buffer); + p.data = buffer; + netclient_send(net, &p);*/ + + /* send old requtest style aswell */ + Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO); + Packet.m_pData = SERVERBROWSE_OLD_GETINFO; + m_NetClient.Send(&Packet); + + if(entry) + entry->request_time = time_get(); +} + +void client_serverbrowse_request(NETADDR *addr) +{ + client_serverbrowse_request_impl(addr, 0); +} + + +void client_serverbrowse_update() +{ + int64 timeout = time_freq(); + int64 now = time_get(); + int count; + SERVERENTRY *entry, *next; + + /* do server list requests */ + if(need_refresh && !mastersrv_refreshing()) + { + NETADDR addr; + CNetChunk Packet; + int i; + + need_refresh = 0; + + mem_zero(&Packet, sizeof(Packet)); + Packet.m_ClientID = -1; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); + Packet.m_pData = SERVERBROWSE_GETLIST; + + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + addr = mastersrv_get(i); + if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) + continue; + + Packet.m_Address = addr; + m_NetClient.Send(&Packet); + } + + if(config.debug) + dbg_msg("client", "requesting server list"); + } + + /* do timeouts */ + entry = first_req_server; + while(1) + { + if(!entry) /* no more entries */ + break; + + next = entry->next_req; + + if(entry->request_time && entry->request_time+timeout < now) + { + /* timeout */ + client_serverbrowse_remove_request(entry); + num_requests--; + } + + entry = next; + } + + /* do timeouts */ + entry = first_req_server; + count = 0; + while(1) + { + if(!entry) /* no more entries */ + break; + + /* no more then 10 concurrent requests */ + if(count == config.b_max_requests) + break; + + if(entry->request_time == 0) + client_serverbrowse_request_impl(&entry->addr, entry); + + count++; + entry = entry->next_req; + } + + /* check if we need to resort */ + /* TODO: remove the strcmp */ + if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0) + client_serverbrowse_sort(); +} + + +int client_serverbrowse_isfavorite(NETADDR addr) +{ + /* search for the address */ + int i; + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + return 1; + } + return 0; +} + +void client_serverbrowse_addfavorite(NETADDR addr) +{ + int i; + SERVERENTRY *entry; + + if(num_favorite_servers == MAX_FAVORITES) + return; + + /* make sure that we don't already have the server in our list */ + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + return; + } + + /* add the server to the list */ + favorite_servers[num_favorite_servers++] = addr; + entry = client_serverbrowse_find(&addr); + if(entry) + entry->info.favorite = 1; + dbg_msg("", "added fav, %p", entry); +} + +void client_serverbrowse_removefavorite(NETADDR addr) +{ + int i; + SERVERENTRY *entry; + + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + { + mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1)); + num_favorite_servers--; + + entry = client_serverbrowse_find(&addr); + if(entry) + entry->info.favorite = 0; + + return; + } + } +} + +void client_serverbrowse_save() +{ + int i; + char addrstr[128]; + char buffer[256]; + for(i = 0; i < num_favorite_servers; i++) + { + net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr)); + str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr); + engine_config_write_line(buffer); + } +} + + +int client_serverbrowse_refreshingmasters() +{ + return mastersrv_refreshing(); +} diff --git a/src/engine/e_client_interface.h b/src/engine/e_client_interface.h index 7ec5c93d..079eabca 100644 --- a/src/engine/e_client_interface.h +++ b/src/engine/e_client_interface.h @@ -2,10 +2,6 @@ #ifndef ENGINE_CLIENT_INTERFACE_H #define ENGINE_CLIENT_INTERFACE_H -#ifdef __cplusplus -extern "C" { -#endif - #include "e_if_other.h" #include "e_if_client.h" #include "e_if_snd.h" @@ -17,8 +13,4 @@ extern "C" { #include "e_console.h" #include "e_config.h" -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_common_interface.h b/src/engine/e_common_interface.h index 498bb6b4..9c95b48b 100644 --- a/src/engine/e_common_interface.h +++ b/src/engine/e_common_interface.h @@ -2,15 +2,7 @@ #ifndef ENGINE_COMMON_INTERFACE_H #define ENGINE_COMMON_INTERFACE_H -#ifdef __cplusplus -extern "C" { -#endif - #include "e_if_other.h" #include "e_if_msg.h" -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_compression.c b/src/engine/e_compression.c deleted file mode 100644 index 6a534feb..00000000 --- a/src/engine/e_compression.c +++ /dev/null @@ -1,148 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include - -/* Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign */ -unsigned char *vint_pack(unsigned char *dst, int i) -{ - *dst = (i>>25)&0x40; /* set sign bit if i<0 */ - i = i^(i>>31); /* if(i<0) i = ~i */ - - *dst |= i&0x3F; /* pack 6bit into dst */ - i >>= 6; /* discard 6 bits */ - if(i) - { - *dst |= 0x80; /* set extend bit */ - while(1) - { - dst++; - *dst = i&(0x7F); /* pack 7bit */ - i >>= 7; /* discard 7 bits */ - *dst |= (i!=0)<<7; /* set extend bit (may branch) */ - if(!i) - break; - } - } - - dst++; - return dst; -} - -const unsigned char *vint_unpack(const unsigned char *src, int *i) -{ - int sign = (*src>>6)&1; - *i = *src&0x3F; - - do - { - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7+7); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7+7+7); - } while(0); - - src++; - *i ^= -sign; /* if(sign) *i = ~(*i) */ - return src; -} - - -long intpack_decompress(const void *src_, int size, void *dst_) -{ - const unsigned char *src = (unsigned char *)src_; - const unsigned char *end = src + size; - int *dst = (int *)dst_; - while(src < end) - { - src = vint_unpack(src, dst); - dst++; - } - return (long)((unsigned char *)dst-(unsigned char *)dst_); -} - -long intpack_compress(const void *src_, int size, void *dst_) -{ - int *src = (int *)src_; - unsigned char *dst = (unsigned char *)dst_; - size /= 4; - while(size) - { - dst = vint_pack(dst, *src); - size--; - src++; - } - return (long)(dst-(unsigned char *)dst_); -} - - -/* */ -long zerobit_compress(const void *src_, int size, void *dst_) -{ - unsigned char *src = (unsigned char *)src_; - unsigned char *dst = (unsigned char *)dst_; - - while(size) - { - unsigned char bit = 0x80; - unsigned char mask = 0; - int dst_move = 1; - int chunk = size < 8 ? size : 8; - int b; - size -= chunk; - - for(b = 0; b < chunk; b++, bit>>=1) - { - if(*src) - { - dst[dst_move] = *src; - mask |= bit; - dst_move++; - } - - src++; - } - - *dst = mask; - dst += dst_move; - } - - return (long)(dst-(unsigned char *)dst_); -} - -long zerobit_decompress(const void *src_, int size, void *dst_) -{ - unsigned char *src = (unsigned char *)src_; - unsigned char *dst = (unsigned char *)dst_; - unsigned char *end = src + size; - - while(src < end) - { - unsigned char bit = 0x80; - unsigned char mask = *src++; - int b; - - for(b = 0; b < 8; b++, bit>>=1) - { - if(mask&bit) - *dst++ = *src++; - else - *dst++ = 0; - } - - if(src > end) - return -1; - } - - return (long)(dst-(unsigned char *)dst_); -} - diff --git a/src/engine/e_compression.cpp b/src/engine/e_compression.cpp new file mode 100644 index 00000000..f4d6e0c0 --- /dev/null +++ b/src/engine/e_compression.cpp @@ -0,0 +1,86 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include + +/* Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign */ +unsigned char *vint_pack(unsigned char *dst, int i) +{ + *dst = (i>>25)&0x40; /* set sign bit if i<0 */ + i = i^(i>>31); /* if(i<0) i = ~i */ + + *dst |= i&0x3F; /* pack 6bit into dst */ + i >>= 6; /* discard 6 bits */ + if(i) + { + *dst |= 0x80; /* set extend bit */ + while(1) + { + dst++; + *dst = i&(0x7F); /* pack 7bit */ + i >>= 7; /* discard 7 bits */ + *dst |= (i!=0)<<7; /* set extend bit (may branch) */ + if(!i) + break; + } + } + + dst++; + return dst; +} + +const unsigned char *vint_unpack(const unsigned char *src, int *i) +{ + int sign = (*src>>6)&1; + *i = *src&0x3F; + + do + { + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7+7); + } while(0); + + src++; + *i ^= -sign; /* if(sign) *i = ~(*i) */ + return src; +} + + +long intpack_decompress(const void *src_, int size, void *dst_) +{ + const unsigned char *src = (unsigned char *)src_; + const unsigned char *end = src + size; + int *dst = (int *)dst_; + while(src < end) + { + src = vint_unpack(src, dst); + dst++; + } + return (long)((unsigned char *)dst-(unsigned char *)dst_); +} + +long intpack_compress(const void *src_, int size, void *dst_) +{ + int *src = (int *)src_; + unsigned char *dst = (unsigned char *)dst_; + size /= 4; + while(size) + { + dst = vint_pack(dst, *src); + size--; + src++; + } + return (long)(dst-(unsigned char *)dst_); +} + diff --git a/src/engine/e_compression.h b/src/engine/e_compression.h index 39ffd596..be5bf78f 100644 --- a/src/engine/e_compression.h +++ b/src/engine/e_compression.h @@ -5,7 +5,3 @@ unsigned char *vint_pack(unsigned char *dst, int i); const unsigned char *vint_unpack(const unsigned char *src, int *inout); long intpack_compress(const void *src, int size, void *dst); long intpack_decompress(const void *src, int size, void *dst); - -/* zerobit packing */ -long zerobit_compress(const void *src, int size, void *dst); -long zerobit_decompress(const void *src, int size, void *dst); diff --git a/src/engine/e_config.c b/src/engine/e_config.c deleted file mode 100644 index 67a4c81a..00000000 --- a/src/engine/e_config.c +++ /dev/null @@ -1,49 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include -#include -#include - -#include -#include "e_if_other.h" -#include "e_config.h" -#include "e_linereader.h" -#include "e_engine.h" - -CONFIGURATION config; - -void config_reset() -{ - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) config.name = def; - #define MACRO_CONFIG_STR(name,len,def,flags,desc) str_copy(config.name, def, len); - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} - -void config_save() -{ - char linebuf[1024*2]; - - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %i", #name, config.name); engine_config_write_line(linebuf); } - #define MACRO_CONFIG_STR(name,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %s", #name, config.name); engine_config_write_line(linebuf); } - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) int config_get_ ## name (CONFIGURATION *c) { return c->name; } -#define MACRO_CONFIG_STR(name,len,def,flags,desc) const char *config_get_ ## name (CONFIGURATION *c) { return c->name; } -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) void config_set_ ## name (CONFIGURATION *c, int val) { if(min != max) { if (val < min) val = min; if (max != 0 && val > max) val = max; } c->name = val; } -#define MACRO_CONFIG_STR(name,len,def,flags,desc) void config_set_ ## name (CONFIGURATION *c, const char *str) { str_copy(c->name, str, len-1); c->name[sizeof(c->name)-1] = 0; } -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR diff --git a/src/engine/e_config.cpp b/src/engine/e_config.cpp new file mode 100644 index 00000000..67a4c81a --- /dev/null +++ b/src/engine/e_config.cpp @@ -0,0 +1,49 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include +#include +#include + +#include +#include "e_if_other.h" +#include "e_config.h" +#include "e_linereader.h" +#include "e_engine.h" + +CONFIGURATION config; + +void config_reset() +{ + #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) config.name = def; + #define MACRO_CONFIG_STR(name,len,def,flags,desc) str_copy(config.name, def, len); + + #include "e_config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} + +void config_save() +{ + char linebuf[1024*2]; + + #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %i", #name, config.name); engine_config_write_line(linebuf); } + #define MACRO_CONFIG_STR(name,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %s", #name, config.name); engine_config_write_line(linebuf); } + + #include "e_config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} + +#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) int config_get_ ## name (CONFIGURATION *c) { return c->name; } +#define MACRO_CONFIG_STR(name,len,def,flags,desc) const char *config_get_ ## name (CONFIGURATION *c) { return c->name; } +#include "e_config_variables.h" +#undef MACRO_CONFIG_INT +#undef MACRO_CONFIG_STR + +#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) void config_set_ ## name (CONFIGURATION *c, int val) { if(min != max) { if (val < min) val = min; if (max != 0 && val > max) val = max; } c->name = val; } +#define MACRO_CONFIG_STR(name,len,def,flags,desc) void config_set_ ## name (CONFIGURATION *c, const char *str) { str_copy(c->name, str, len-1); c->name[sizeof(c->name)-1] = 0; } +#include "e_config_variables.h" +#undef MACRO_CONFIG_INT +#undef MACRO_CONFIG_STR diff --git a/src/engine/e_config.h b/src/engine/e_config.h index 965e08f0..6ca7a8ee 100644 --- a/src/engine/e_config.h +++ b/src/engine/e_config.h @@ -2,10 +2,6 @@ #ifndef _CONFIG_H #define _CONFIG_H -#ifdef __cplusplus -extern "C"{ -#endif - typedef struct { #define MACRO_CONFIG_INT(name,def,min,max,save,desc) int name; @@ -45,8 +41,4 @@ typedef void (*CONFIG_STR_SETTER)(CONFIGURATION *c, const char *str); #undef MACRO_CONFIG_INT #undef MACRO_CONFIG_STR -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_console.c b/src/engine/e_console.c deleted file mode 100644 index 5c0f37f3..00000000 --- a/src/engine/e_console.c +++ /dev/null @@ -1,464 +0,0 @@ -#include -#include "e_if_other.h" -#include "e_console.h" -#include "e_config.h" -#include "e_engine.h" -#include "e_linereader.h" -#include -#include -#include - - -#define CONSOLE_MAX_STR_LENGTH 1024 -/* the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces */ -#define MAX_PARTS (CONSOLE_MAX_STR_LENGTH+1)/2 - -typedef struct -{ - char string_storage[CONSOLE_MAX_STR_LENGTH+1]; - char *args_start; - - const char *command; - const char *args[MAX_PARTS]; - unsigned int num_args; -} PARSE_RESULT; - -static char *str_skipblanks(char *str) -{ - while(*str && (*str == ' ' || *str == '\t' || *str == '\n')) - str++; - return str; -} - -static char *str_skiptoblank(char *str) -{ - while(*str && (*str != ' ' && *str != '\t' && *str != '\n')) - str++; - return str; -} - -/* static int digit(char c) { return '0' <= c && c <= '9'; } */ - -static int console_parse_start(PARSE_RESULT *result, const char *string, int length) -{ - char *str; - int len = sizeof(result->string_storage); - if(length < len) - len = length; - - str_copy(result->string_storage, string, length); - str = result->string_storage; - - /* get command */ - str = str_skipblanks(str); - result->command = str; - str = str_skiptoblank(str); - - if(*str) - { - str[0] = 0; - str++; - } - - result->args_start = str; - result->num_args = 0; - return 0; -} - -static int console_parse_args(PARSE_RESULT *result, const char *format) -{ - char command; - char *str; - int optional = 0; - int error = 0; - - str = result->args_start; - - while(1) - { - /* fetch command */ - command = *format; - format++; - - if(!command) - break; - - if(command == '?') - optional = 1; - else - { - str = str_skipblanks(str); - - if(!(*str)) /* error, non optional command needs value */ - { - if(!optional) - error = 1; - break; - } - - /* add token */ - if(*str == '"') - { - char *dst; - str++; - result->args[result->num_args++] = str; - - dst = str; /* we might have to process escape data */ - while(1) - { - if(str[0] == '"') - break; - else if(str[0] == '\\') - { - if(str[1] == '\\') - str++; /* skip due to escape */ - else if(str[1] == '"') - str++; /* skip due to escape */ - } - else if(str[0] == 0) - return 1; /* return error */ - - *dst = *str; - dst++; - str++; - } - - /* write null termination */ - *dst = 0; - } - else - { - result->args[result->num_args++] = str; - - if(command == 'r') /* rest of the string */ - break; - else if(command == 'i') /* validate int */ - str = str_skiptoblank(str); - else if(command == 'f') /* validate float */ - str = str_skiptoblank(str); - else if(command == 's') /* validate string */ - str = str_skiptoblank(str); - - if(str[0] != 0) /* check for end of string */ - { - str[0] = 0; - str++; - } - } - } - } - - return error; -} - -const char *console_arg_string(void *res, int index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return ""; - return result->args[index]; -} - -int console_arg_int(void *res, int index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return 0; - return atoi(result->args[index]); -} - -float console_arg_float(void *res, int index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return 0.0f; - return atof(result->args[index]); -} - -int console_arg_num(void *result) -{ - return ((PARSE_RESULT *)result)->num_args; -} - -static COMMAND *first_command = 0x0; - -COMMAND *console_find_command(const char *name) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if (strcmp(cmd->name, name) == 0) - return cmd; - } - - return 0x0; -} - -void console_register(COMMAND *cmd) -{ - cmd->next = first_command; - first_command = cmd; -} - -static void (*print_callback)(const char *, void *) = 0x0; -static void *print_callback_userdata; - -void console_register_print_callback(void (*callback)(const char *, void *), void *user_data) -{ - print_callback = callback; - print_callback_userdata = user_data; -} - -void console_print(const char *str) -{ - if (print_callback) - print_callback(str, print_callback_userdata); -} - -void console_execute_line_stroked(int stroke, const char *str) -{ - PARSE_RESULT result; - COMMAND *command; - - char strokestr[2] = {'0', 0}; - if(stroke) - strokestr[0] = '1'; - - while(str) - { - const char *end = str; - const char *next_part = 0; - int in_string = 0; - - while(*end) - { - if(*end == '"') - in_string ^= 1; - else if(*end == '\\') /* escape sequences */ - { - if(end[1] == '"') - end++; - } - else if(!in_string) - { - if(*end == ';') /* command separator */ - { - next_part = end+1; - break; - } - else if(*end == '#') /* comment, no need to do anything more */ - break; - } - - end++; - } - - if(console_parse_start(&result, str, (end-str) + 1) != 0) - return; - - command = console_find_command(result.command); - - if(command) - { - int is_stroke_command = 0; - if(result.command[0] == '+') - { - /* insert the stroke direction token */ - result.args[result.num_args] = strokestr; - result.num_args++; - is_stroke_command = 1; - } - - if(stroke || is_stroke_command) - { - if(console_parse_args(&result, command->params)) - { - char buf[256]; - str_format(buf, sizeof(buf), "Invalid arguments... Usage: %s %s", command->name, command->params); - console_print(buf); - } - else - command->callback(&result, command->user_data); - } - } - else - { - char buf[256]; - str_format(buf, sizeof(buf), "No such command: %s.", result.command); - console_print(buf); - } - - str = next_part; - } -} - -void console_possible_commands(const char *str, int flagmask, void (*callback)(const char *cmd, void *user), void *user) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if(cmd->flags&flagmask) - { - if(str_find_nocase(cmd->name, str)) - callback(cmd->name, user); - } - } -} - - -COMMAND *console_get_command(const char *str) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if(str_comp_nocase(cmd->name, str) == 0) - return cmd; - } - - return 0x0; -} - -void console_execute_line(const char *str) -{ - console_execute_line_stroked(1, str); -} - -static void console_execute_file_real(const char *filename) -{ - IOHANDLE file; - file = engine_openfile(filename, IOFLAG_READ); - - if(file) - { - char *line; - LINEREADER lr; - - dbg_msg("console", "executing '%s'", filename); - linereader_init(&lr, file); - - while((line = linereader_get(&lr))) - console_execute_line(line); - - io_close(file); - } - else - dbg_msg("console", "failed to open '%s'", filename); -} - -struct EXECFILE -{ - const char *filename; - struct EXECFILE *next; -}; - -void console_execute_file(const char *filename) -{ - static struct EXECFILE *first = 0; - struct EXECFILE this; - struct EXECFILE *cur; - struct EXECFILE *prev; - - /* make sure that this isn't being executed already */ - for(cur = first; cur; cur = cur->next) - if(strcmp(filename, cur->filename) == 0) - return; - - /* push this one to the stack */ - prev = first; - this.filename = filename; - this.next = first; - first = &this; - - /* execute file */ - console_execute_file_real(filename); - - /* pop this one from the stack */ - first = prev; -} - -static void con_echo(void *result, void *user_data) -{ - console_print(console_arg_string(result, 0)); -} - -static void con_exec(void *result, void *user_data) -{ - console_execute_file(console_arg_string(result, 0)); - -} - - -typedef struct -{ - CONFIG_INT_GETTER getter; - CONFIG_INT_SETTER setter; -} INT_VARIABLE_DATA; - -typedef struct -{ - CONFIG_STR_GETTER getter; - CONFIG_STR_SETTER setter; -} STR_VARIABLE_DATA; - -static void int_variable_command(void *result, void *user_data) -{ - INT_VARIABLE_DATA *data = (INT_VARIABLE_DATA *)user_data; - - if(console_arg_num(result)) - data->setter(&config, console_arg_int(result, 0)); - else - { - char buf[1024]; - str_format(buf, sizeof(buf), "Value: %d", data->getter(&config)); - console_print(buf); - } -} - -static void str_variable_command(void *result, void *user_data) -{ - STR_VARIABLE_DATA *data = (STR_VARIABLE_DATA *)user_data; - - if(console_arg_num(result)) - data->setter(&config, console_arg_string(result, 0)); - else - { - char buf[1024]; - str_format(buf, sizeof(buf), "Value: %s", data->getter(&config)); - console_print(buf); - } -} - -static void console_chain(void *result, void *user_data) -{ - COMMANDCHAIN *info = (COMMANDCHAIN *)user_data; - info->chain_callback(result, info->user_data, info->callback, info->callback_user_data); -} - -void console_chain_command(const char *cmd, COMMANDCHAIN *chaininfo, CONSOLE_CHAIN_CALLBACK cb, void *user) -{ - COMMAND *command = console_get_command(cmd); - - /* store info */ - chaininfo->chain_callback = cb; - chaininfo->callback = command->callback; - chaininfo->callback_user_data = command->user_data; - chaininfo->user_data = user; - - /* chain */ - command->callback = console_chain; - command->user_data = chaininfo; -} - -void console_init() -{ - MACRO_REGISTER_COMMAND("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_echo, 0x0, "Echo the text"); - MACRO_REGISTER_COMMAND("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_exec, 0x0, "Execute the specified file"); - - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) { static INT_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", flags, int_variable_command, &data, desc) } - #define MACRO_CONFIG_STR(name,len,def,flags,desc) { static STR_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?r", flags, str_variable_command, &data, desc) } - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} diff --git a/src/engine/e_console.cpp b/src/engine/e_console.cpp new file mode 100644 index 00000000..c641289d --- /dev/null +++ b/src/engine/e_console.cpp @@ -0,0 +1,464 @@ +#include +#include "e_if_other.h" +#include "e_console.h" +#include "e_config.h" +#include "e_engine.h" +#include "e_linereader.h" +#include +#include +#include + + +#define CONSOLE_MAX_STR_LENGTH 1024 +/* the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces */ +#define MAX_PARTS (CONSOLE_MAX_STR_LENGTH+1)/2 + +typedef struct +{ + char string_storage[CONSOLE_MAX_STR_LENGTH+1]; + char *args_start; + + const char *command; + const char *args[MAX_PARTS]; + unsigned int num_args; +} PARSE_RESULT; + +static char *str_skipblanks(char *str) +{ + while(*str && (*str == ' ' || *str == '\t' || *str == '\n')) + str++; + return str; +} + +static char *str_skiptoblank(char *str) +{ + while(*str && (*str != ' ' && *str != '\t' && *str != '\n')) + str++; + return str; +} + +/* static int digit(char c) { return '0' <= c && c <= '9'; } */ + +static int console_parse_start(PARSE_RESULT *result, const char *string, int length) +{ + char *str; + int len = sizeof(result->string_storage); + if(length < len) + len = length; + + str_copy(result->string_storage, string, length); + str = result->string_storage; + + /* get command */ + str = str_skipblanks(str); + result->command = str; + str = str_skiptoblank(str); + + if(*str) + { + str[0] = 0; + str++; + } + + result->args_start = str; + result->num_args = 0; + return 0; +} + +static int console_parse_args(PARSE_RESULT *result, const char *format) +{ + char command; + char *str; + int optional = 0; + int error = 0; + + str = result->args_start; + + while(1) + { + /* fetch command */ + command = *format; + format++; + + if(!command) + break; + + if(command == '?') + optional = 1; + else + { + str = str_skipblanks(str); + + if(!(*str)) /* error, non optional command needs value */ + { + if(!optional) + error = 1; + break; + } + + /* add token */ + if(*str == '"') + { + char *dst; + str++; + result->args[result->num_args++] = str; + + dst = str; /* we might have to process escape data */ + while(1) + { + if(str[0] == '"') + break; + else if(str[0] == '\\') + { + if(str[1] == '\\') + str++; /* skip due to escape */ + else if(str[1] == '"') + str++; /* skip due to escape */ + } + else if(str[0] == 0) + return 1; /* return error */ + + *dst = *str; + dst++; + str++; + } + + /* write null termination */ + *dst = 0; + } + else + { + result->args[result->num_args++] = str; + + if(command == 'r') /* rest of the string */ + break; + else if(command == 'i') /* validate int */ + str = str_skiptoblank(str); + else if(command == 'f') /* validate float */ + str = str_skiptoblank(str); + else if(command == 's') /* validate string */ + str = str_skiptoblank(str); + + if(str[0] != 0) /* check for end of string */ + { + str[0] = 0; + str++; + } + } + } + } + + return error; +} + +const char *console_arg_string(void *res, unsigned index) +{ + PARSE_RESULT *result = (PARSE_RESULT *)res; + if (index < 0 || index >= result->num_args) + return ""; + return result->args[index]; +} + +int console_arg_int(void *res, unsigned index) +{ + PARSE_RESULT *result = (PARSE_RESULT *)res; + if (index < 0 || index >= result->num_args) + return 0; + return atoi(result->args[index]); +} + +float console_arg_float(void *res, unsigned index) +{ + PARSE_RESULT *result = (PARSE_RESULT *)res; + if (index < 0 || index >= result->num_args) + return 0.0f; + return atof(result->args[index]); +} + +int console_arg_num(void *result) +{ + return ((PARSE_RESULT *)result)->num_args; +} + +static COMMAND *first_command = 0x0; + +COMMAND *console_find_command(const char *name) +{ + COMMAND *cmd; + for (cmd = first_command; cmd; cmd = cmd->next) + { + if (strcmp(cmd->name, name) == 0) + return cmd; + } + + return 0x0; +} + +void console_register(COMMAND *cmd) +{ + cmd->next = first_command; + first_command = cmd; +} + +static void (*print_callback)(const char *, void *) = 0x0; +static void *print_callback_userdata; + +void console_register_print_callback(void (*callback)(const char *, void *), void *user_data) +{ + print_callback = callback; + print_callback_userdata = user_data; +} + +void console_print(const char *str) +{ + if (print_callback) + print_callback(str, print_callback_userdata); +} + +void console_execute_line_stroked(int stroke, const char *str) +{ + PARSE_RESULT result; + COMMAND *command; + + char strokestr[2] = {'0', 0}; + if(stroke) + strokestr[0] = '1'; + + while(str) + { + const char *end = str; + const char *next_part = 0; + int in_string = 0; + + while(*end) + { + if(*end == '"') + in_string ^= 1; + else if(*end == '\\') /* escape sequences */ + { + if(end[1] == '"') + end++; + } + else if(!in_string) + { + if(*end == ';') /* command separator */ + { + next_part = end+1; + break; + } + else if(*end == '#') /* comment, no need to do anything more */ + break; + } + + end++; + } + + if(console_parse_start(&result, str, (end-str) + 1) != 0) + return; + + command = console_find_command(result.command); + + if(command) + { + int is_stroke_command = 0; + if(result.command[0] == '+') + { + /* insert the stroke direction token */ + result.args[result.num_args] = strokestr; + result.num_args++; + is_stroke_command = 1; + } + + if(stroke || is_stroke_command) + { + if(console_parse_args(&result, command->params)) + { + char buf[256]; + str_format(buf, sizeof(buf), "Invalid arguments... Usage: %s %s", command->name, command->params); + console_print(buf); + } + else + command->callback(&result, command->user_data); + } + } + else + { + char buf[256]; + str_format(buf, sizeof(buf), "No such command: %s.", result.command); + console_print(buf); + } + + str = next_part; + } +} + +void console_possible_commands(const char *str, int flagmask, void (*callback)(const char *cmd, void *user), void *user) +{ + COMMAND *cmd; + for (cmd = first_command; cmd; cmd = cmd->next) + { + if(cmd->flags&flagmask) + { + if(str_find_nocase(cmd->name, str)) + callback(cmd->name, user); + } + } +} + + +COMMAND *console_get_command(const char *str) +{ + COMMAND *cmd; + for (cmd = first_command; cmd; cmd = cmd->next) + { + if(str_comp_nocase(cmd->name, str) == 0) + return cmd; + } + + return 0x0; +} + +void console_execute_line(const char *str) +{ + console_execute_line_stroked(1, str); +} + +static void console_execute_file_real(const char *filename) +{ + IOHANDLE file; + file = engine_openfile(filename, IOFLAG_READ); + + if(file) + { + char *line; + LINEREADER lr; + + dbg_msg("console", "executing '%s'", filename); + linereader_init(&lr, file); + + while((line = linereader_get(&lr))) + console_execute_line(line); + + io_close(file); + } + else + dbg_msg("console", "failed to open '%s'", filename); +} + +struct EXECFILE +{ + const char *filename; + struct EXECFILE *next; +}; + +void console_execute_file(const char *filename) +{ + static struct EXECFILE *first = 0; + struct EXECFILE this_file; + struct EXECFILE *cur; + struct EXECFILE *prev; + + /* make sure that this isn't being executed already */ + for(cur = first; cur; cur = cur->next) + if(strcmp(filename, cur->filename) == 0) + return; + + /* push this one to the stack */ + prev = first; + this_file.filename = filename; + this_file.next = first; + first = &this_file; + + /* execute file */ + console_execute_file_real(filename); + + /* pop this one from the stack */ + first = prev; +} + +static void con_echo(void *result, void *user_data) +{ + console_print(console_arg_string(result, 0)); +} + +static void con_exec(void *result, void *user_data) +{ + console_execute_file(console_arg_string(result, 0)); + +} + + +typedef struct +{ + CONFIG_INT_GETTER getter; + CONFIG_INT_SETTER setter; +} INT_VARIABLE_DATA; + +typedef struct +{ + CONFIG_STR_GETTER getter; + CONFIG_STR_SETTER setter; +} STR_VARIABLE_DATA; + +static void int_variable_command(void *result, void *user_data) +{ + INT_VARIABLE_DATA *data = (INT_VARIABLE_DATA *)user_data; + + if(console_arg_num(result)) + data->setter(&config, console_arg_int(result, 0)); + else + { + char buf[1024]; + str_format(buf, sizeof(buf), "Value: %d", data->getter(&config)); + console_print(buf); + } +} + +static void str_variable_command(void *result, void *user_data) +{ + STR_VARIABLE_DATA *data = (STR_VARIABLE_DATA *)user_data; + + if(console_arg_num(result)) + data->setter(&config, console_arg_string(result, 0)); + else + { + char buf[1024]; + str_format(buf, sizeof(buf), "Value: %s", data->getter(&config)); + console_print(buf); + } +} + +static void console_chain(void *result, void *user_data) +{ + COMMANDCHAIN *info = (COMMANDCHAIN *)user_data; + info->chain_callback(result, info->user_data, info->callback, info->callback_user_data); +} + +void console_chain_command(const char *cmd, COMMANDCHAIN *chaininfo, CONSOLE_CHAIN_CALLBACK cb, void *user) +{ + COMMAND *command = console_get_command(cmd); + + /* store info */ + chaininfo->chain_callback = cb; + chaininfo->callback = command->callback; + chaininfo->callback_user_data = command->user_data; + chaininfo->user_data = user; + + /* chain */ + command->callback = console_chain; + command->user_data = chaininfo; +} + +void console_init() +{ + MACRO_REGISTER_COMMAND("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_echo, 0x0, "Echo the text"); + MACRO_REGISTER_COMMAND("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_exec, 0x0, "Execute the specified file"); + + #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) { static INT_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", flags, int_variable_command, &data, desc) } + #define MACRO_CONFIG_STR(name,len,def,flags,desc) { static STR_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?r", flags, str_variable_command, &data, desc) } + + #include "e_config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} diff --git a/src/engine/e_console.h b/src/engine/e_console.h index b37931b2..a45bac10 100644 --- a/src/engine/e_console.h +++ b/src/engine/e_console.h @@ -1,10 +1,6 @@ #ifndef _CONSOLE_H #define _CONSOLE_H -#ifdef __cplusplus -extern "C"{ -#endif - typedef void (*CONSOLE_CALLBACK)(void *result, void *user_data); typedef void (*CONSOLE_CHAIN_CALLBACK)(void *result, void *user_data, CONSOLE_CALLBACK cb, void *cbuser); @@ -42,15 +38,11 @@ void console_register_print_callback(void (*callback)(const char *, void *user_d int console_result_int(void *result, int index, int *i); int console_result_float(void *result, int index, float *f);*/ -const char *console_arg_string(void *result, int index); -int console_arg_int(void *result, int index); -float console_arg_float(void *result, int index); +const char *console_arg_string(void *result, unsigned index); +int console_arg_int(void *result, unsigned index); +float console_arg_float(void *result, unsigned index); int console_arg_num(void *result); #define MACRO_REGISTER_COMMAND(name, params, flags, func, ptr, help) { static COMMAND cmd = { name, params, flags, func, ptr, help, 0x0}; console_register(&cmd); } -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_datafile.c b/src/engine/e_datafile.c deleted file mode 100644 index 0317f9d0..00000000 --- a/src/engine/e_datafile.c +++ /dev/null @@ -1,677 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "e_datafile.h" -#include "e_engine.h" -#include - -static const int DEBUG=0; - -typedef struct -{ - int type; - int start; - int num; -} DATAFILE_ITEM_TYPE; - -typedef struct -{ - int type_and_id; - int size; -} DATAFILE_ITEM; - -typedef struct -{ - char id[4]; - int version; - int size; - int swaplen; - int num_item_types; - int num_items; - int num_raw_data; - int item_size; - int data_size; -} DATAFILE_HEADER; - -typedef struct -{ - int num_item_types; - int num_items; - int num_raw_data; - int item_size; - int data_size; - char start[4]; -} DATAFILE_DATA; - -typedef struct -{ - DATAFILE_ITEM_TYPE *item_types; - int *item_offsets; - int *data_offsets; - int *data_sizes; - - char *item_start; - char *data_start; -} DATAFILE_INFO; - -struct DATAFILE_t -{ - IOHANDLE file; - DATAFILE_INFO info; - DATAFILE_HEADER header; - int data_start_offset; - char **data_ptrs; - char *data; -}; - -DATAFILE *datafile_load(const char *filename) -{ - DATAFILE *df; - IOHANDLE file; - DATAFILE_HEADER header; - unsigned readsize; - - unsigned *dst; - unsigned char *src; - unsigned j; - int size = 0; - int allocsize = 0; - - (void)dst; - (void)src; - (void)j; - - - dbg_msg("datafile", "datafile loading. filename='%s'", filename); - - file = engine_openfile(filename, IOFLAG_READ); - if(!file) - return 0; - - /* TODO: change this header */ - io_read(file, &header, sizeof(header)); - if(header.id[0] != 'A' || header.id[1] != 'T' || header.id[2] != 'A' || header.id[3] != 'D') - { - if(header.id[0] != 'D' || header.id[1] != 'A' || header.id[2] != 'T' || header.id[3] != 'A') - { - dbg_msg("datafile", "wrong signature. %x %x %x %x", header.id[0], header.id[1], header.id[2], header.id[3]); - return 0; - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(&header, sizeof(int), sizeof(header)/sizeof(int)); -#endif - if(header.version != 3 && header.version != 4) - { - dbg_msg("datafile", "wrong version. version=%x", header.version); - return 0; - } - - /* read in the rest except the data */ - size = 0; - size += header.num_item_types*sizeof(DATAFILE_ITEM_TYPE); - size += (header.num_items+header.num_raw_data)*sizeof(int); - if(header.version == 4) - size += header.num_raw_data*sizeof(int); /* v4 has uncompressed data sizes aswell */ - size += header.item_size; - - allocsize = size; - allocsize += sizeof(DATAFILE); /* add space for info structure */ - allocsize += header.num_raw_data*sizeof(void*); /* add space for data pointers */ - - df = (DATAFILE*)mem_alloc(allocsize, 1); - df->header = header; - df->data_start_offset = sizeof(DATAFILE_HEADER) + size; - df->data_ptrs = (char**)(df+1); - df->data = (char *)(df+1)+header.num_raw_data*sizeof(char *); - df->file = file; - - /* clear the data pointers */ - mem_zero(df->data_ptrs, header.num_raw_data*sizeof(void*)); - - /* read types, offsets, sizes and item data */ - readsize = io_read(file, df->data, size); - if(readsize != size) - { - dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize); - return 0; - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(df->data, sizeof(int), header.swaplen / sizeof(int)); -#endif - - if(DEBUG) - { - dbg_msg("datafile", "allocsize=%d", allocsize); - dbg_msg("datafile", "readsize=%d", readsize); - dbg_msg("datafile", "swaplen=%d", header.swaplen); - dbg_msg("datafile", "item_size=%d", df->header.item_size); - } - - df->info.item_types = (DATAFILE_ITEM_TYPE *)df->data; - df->info.item_offsets = (int *)&df->info.item_types[df->header.num_item_types]; - df->info.data_offsets = (int *)&df->info.item_offsets[df->header.num_items]; - df->info.data_sizes = (int *)&df->info.data_offsets[df->header.num_raw_data]; - - if(header.version == 4) - df->info.item_start = (char *)&df->info.data_sizes[df->header.num_raw_data]; - else - df->info.item_start = (char *)&df->info.data_offsets[df->header.num_raw_data]; - df->info.data_start = df->info.item_start + df->header.item_size; - - if(DEBUG) - dbg_msg("datafile", "datafile loading done. datafile='%s'", filename); - - if(DEBUG) - { - /* - for(int i = 0; i < df->data.num_raw_data; i++) - { - void *p = datafile_get_data(df, i); - dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->data)), size); - } - - for(int i = 0; i < datafile_num_items(df); i++) - { - int type, id; - void *data = datafile_get_item(df, i, &type, &id); - dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, df->info.item_offsets[i]); - int *idata = (int*)data; - for(int k = 0; k < 3; k++) - dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); - } - - for(int i = 0; i < df->data.num_item_types; i++) - { - dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, - df->info.item_types[i].type, - df->info.item_types[i].start, - df->info.item_types[i].num); - for(int k = 0; k < df->info.item_types[i].num; k++) - { - int type, id; - datafile_get_item(df, df->info.item_types[i].start+k, &type, &id); - if(type != df->info.item_types[i].type) - dbg_msg("map", "\tERROR"); - } - } - */ - } - - return df; -} - -int datafile_num_data(DATAFILE *df) -{ - return df->header.num_raw_data; -} - -/* always returns the size in the file */ -int datafile_get_datasize(DATAFILE *df, int index) -{ - if(index == df->header.num_raw_data-1) - return df->header.data_size-df->info.data_offsets[index]; - return df->info.data_offsets[index+1]-df->info.data_offsets[index]; -} - -static void *datafile_get_data_impl(DATAFILE *df, int index, int swap) -{ - /* load it if needed */ - if(!df->data_ptrs[index]) - { - /* fetch the data size */ - int datasize = datafile_get_datasize(df, index); - int swapsize = datasize; - - if(df->header.version == 4) - { - /* v4 has compressed data */ - void *temp = (char *)mem_alloc(datasize, 1); - unsigned long uncompressed_size = df->info.data_sizes[index]; - unsigned long s; - - dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", index, datasize, uncompressed_size); - df->data_ptrs[index] = (char *)mem_alloc(uncompressed_size, 1); - - /* read the compressed data */ - io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); - io_read(df->file, temp, datasize); - - /* decompress the data, TODO: check for errors */ - s = uncompressed_size; - uncompress((Bytef*)df->data_ptrs[index], &s, (Bytef*)temp, datasize); - swapsize = s; - - /* clean up the temporary buffers */ - mem_free(temp); - } - else - { - /* load the data */ - dbg_msg("datafile", "loading data index=%d size=%d", index, datasize); - df->data_ptrs[index] = (char *)mem_alloc(datasize, 1); - io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); - io_read(df->file, df->data_ptrs[index], datasize); - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - if(swap && swapsize) - swap_endian(df->data_ptrs[index], sizeof(int), swapsize/sizeof(int)); -#endif - } - - return df->data_ptrs[index]; -} - -void *datafile_get_data(DATAFILE *df, int index) -{ - return datafile_get_data_impl(df, index, 0); -} - -void *datafile_get_data_swapped(DATAFILE *df, int index) -{ - return datafile_get_data_impl(df, index, 1); -} - -void datafile_unload_data(DATAFILE *df, int index) -{ - if(index < 0) - return; - - /* */ - mem_free(df->data_ptrs[index]); - df->data_ptrs[index] = 0x0; -} - -int datafile_get_itemsize(DATAFILE *df, int index) -{ - if(index == df->header.num_items-1) - return df->header.item_size-df->info.item_offsets[index]; - return df->info.item_offsets[index+1]-df->info.item_offsets[index]; -} - -void *datafile_get_item(DATAFILE *df, int index, int *type, int *id) -{ - DATAFILE_ITEM *i = (DATAFILE_ITEM *)(df->info.item_start+df->info.item_offsets[index]); - if(type) - *type = (i->type_and_id>>16)&0xffff; /* remove sign extention */ - if(id) - *id = i->type_and_id&0xffff; - return (void *)(i+1); -} - -void datafile_get_type(DATAFILE *df, int type, int *start, int *num) -{ - int i; - for(i = 0; i < df->header.num_item_types; i++) - { - if(df->info.item_types[i].type == type) - { - *start = df->info.item_types[i].start; - *num = df->info.item_types[i].num; - return; - } - } - - *start = 0; - *num = 0; -} - -void *datafile_find_item(DATAFILE *df, int type, int id) -{ - int start, num,i ; - int item_id; - void *item; - - datafile_get_type(df, type, &start, &num); - for(i = 0; i < num; i++) - { - item = datafile_get_item(df, start+i,0, &item_id); - if(id == item_id) - return item; - } - return 0; -} - -int datafile_num_items(DATAFILE *df) -{ - return df->header.num_items; -} - -void datafile_unload(DATAFILE *df) -{ - if(df) - { - /* free the data that is loaded */ - int i; - for(i = 0; i < df->header.num_raw_data; i++) - mem_free(df->data_ptrs[i]); - - io_close(df->file); - mem_free(df); - } -} - -/* DATAFILE output */ -typedef struct -{ - int uncompressed_size; - int compressed_size; - void *compressed_data; -} DATA_INFO; - -typedef struct -{ - int type; - int id; - int size; - int next; - int prev; - void *data; -} ITEM_INFO; - -typedef struct -{ - int num; - int first; - int last; -} ITEMTYPE_INFO; - -/* */ -struct DATAFILE_OUT_t -{ - IOHANDLE file; - int num_items; - int num_datas; - int num_item_types; - ITEMTYPE_INFO item_types[0xffff]; - ITEM_INFO items[1024]; - DATA_INFO datas[1024]; -}; - -DATAFILE_OUT *datafile_create(const char *filename) -{ - int i; - DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1); - df->file = engine_openfile(filename, IOFLAG_WRITE); - if(!df->file) - { - mem_free(df); - return 0; - } - - df->num_items = 0; - df->num_datas = 0; - df->num_item_types = 0; - mem_zero(&df->item_types, sizeof(df->item_types)); - - for(i = 0; i < 0xffff; i++) - { - df->item_types[i].first = -1; - df->item_types[i].last = -1; - } - - return df; -} - -int datafile_add_item(DATAFILE_OUT *df, int type, int id, int size, void *data) -{ - df->items[df->num_items].type = type; - df->items[df->num_items].id = id; - df->items[df->num_items].size = size; - - /* - dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size); - int i; - for(i = 0; i < size/4; i++) - dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]); - */ - - /* copy data */ - df->items[df->num_items].data = mem_alloc(size, 1); - mem_copy(df->items[df->num_items].data, data, size); - - if(!df->item_types[type].num) /* count item types */ - df->num_item_types++; - - /* link */ - df->items[df->num_items].prev = df->item_types[type].last; - df->items[df->num_items].next = -1; - - if(df->item_types[type].last != -1) - df->items[df->item_types[type].last].next = df->num_items; - df->item_types[type].last = df->num_items; - - if(df->item_types[type].first == -1) - df->item_types[type].first = df->num_items; - - df->item_types[type].num++; - - df->num_items++; - return df->num_items-1; -} - -int datafile_add_data(DATAFILE_OUT *df, int size, void *data) -{ - DATA_INFO *info = &df->datas[df->num_datas]; - unsigned long s = compressBound(size); - void *compdata = mem_alloc(s, 1); /* temporary buffer that we use duing compression */ - - int result = compress((Bytef*)compdata, &s, (Bytef*)data, size); - if(result != Z_OK) - { - dbg_msg("datafile", "compression error %d", result); - dbg_assert(0, "zlib error"); - } - - info->uncompressed_size = size; - info->compressed_size = (int)s; - info->compressed_data = mem_alloc(info->compressed_size, 1); - mem_copy(info->compressed_data, compdata, info->compressed_size); - mem_free(compdata); - - df->num_datas++; - return df->num_datas-1; -} - -int datafile_add_data_swapped(DATAFILE_OUT *df, int size, void *data) -{ -#if defined(CONF_ARCH_ENDIAN_BIG) - void *swapped = mem_alloc(size, 1); /* temporary buffer that we use duing compression */ - int index; - mem_copy(swapped, data, size); - swap_endian(&swapped, sizeof(int), size/sizeof(int)); - index = datafile_add_data(df, size, swapped); - mem_free(swapped); - return index; -#else - return datafile_add_data(df, size, data); -#endif -} - - -int datafile_finish(DATAFILE_OUT *df) -{ - int itemsize = 0; - int i, count, offset; - int typessize, headersize, offsetsize, filesize, swapsize; - int datasize = 0; - DATAFILE_ITEM_TYPE info; - DATAFILE_ITEM itm; - DATAFILE_HEADER header; - - /* we should now write this file! */ - if(DEBUG) - dbg_msg("datafile", "writing"); - - /* calculate sizes */ - for(i = 0; i < df->num_items; i++) - { - if(DEBUG) - dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(DATAFILE_ITEM)); - itemsize += df->items[i].size + sizeof(DATAFILE_ITEM); - } - - - for(i = 0; i < df->num_datas; i++) - datasize += df->datas[i].compressed_size; - - /* calculate the complete size */ - typessize = df->num_item_types*sizeof(DATAFILE_ITEM_TYPE); - headersize = sizeof(DATAFILE_HEADER); - offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int); - filesize = headersize + typessize + offsetsize + itemsize + datasize; - swapsize = filesize - datasize; - - (void)swapsize; - - if(DEBUG) - dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize); - - /* construct header */ - { - header.id[0] = 'D'; - header.id[1] = 'A'; - header.id[2] = 'T'; - header.id[3] = 'A'; - header.version = 4; - header.size = filesize - 16; - header.swaplen = swapsize - 16; - header.num_item_types = df->num_item_types; - header.num_items = df->num_items; - header.num_raw_data = df->num_datas; - header.item_size = itemsize; - header.data_size = datasize; - - /* TODO: apply swapping */ - /* write header */ - if(DEBUG) - dbg_msg("datafile", "headersize=%d", sizeof(header)); - io_write(df->file, &header, sizeof(header)); - } - - /* write types */ - for(i = 0, count = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write info */ - info.type = i; - info.start = count; - info.num = df->item_types[i].num; - if(DEBUG) - dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num); - io_write(df->file, &info, sizeof(info)); - count += df->item_types[i].num; - } - } - - /* write item offsets */ - for(i = 0, offset = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write all items in of this type */ - int k = df->item_types[i].first; - while(k != -1) - { - if(DEBUG) - dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset); - io_write(df->file, &offset, sizeof(offset)); - offset += df->items[k].size + sizeof(DATAFILE_ITEM); - - /* next */ - k = df->items[k].next; - } - } - } - - /* write data offsets */ - for(i = 0, offset = 0; i < df->num_datas; i++) - { - if(DEBUG) - dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); - io_write(df->file, &offset, sizeof(offset)); - offset += df->datas[i].compressed_size; - } - - /* write data uncompressed sizes */ - for(i = 0, offset = 0; i < df->num_datas; i++) - { - /* - if(DEBUG) - dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); - */ - io_write(df->file, &df->datas[i].uncompressed_size, sizeof(int)); - } - - /* write items */ - for(i = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write all items in of this type */ - int k = df->item_types[i].first; - while(k != -1) - { - itm.type_and_id = (i<<16)|df->items[k].id; - itm.size = df->items[k].size; - if(DEBUG) - dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size); - - io_write(df->file, &itm, sizeof(itm)); - io_write(df->file, df->items[k].data, df->items[k].size); - - /* next */ - k = df->items[k].next; - } - } - } - - /* write data */ - for(i = 0; i < df->num_datas; i++) - { - if(DEBUG) - dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].compressed_size); - io_write(df->file, df->datas[i].compressed_data, df->datas[i].compressed_size); - } - - /* free data */ - for(i = 0; i < df->num_items; i++) - mem_free(df->items[i].data); - - - io_close(df->file); - mem_free(df); - - if(DEBUG) - dbg_msg("datafile", "done"); - return 0; -} - -#define BUFFER_SIZE 64*1024 - -int datafile_crc(const char *filename) -{ - unsigned char buffer[BUFFER_SIZE]; - IOHANDLE file; - int crc = 0; - unsigned bytes = 0; - - file = engine_openfile(filename, IOFLAG_READ); - if(!file) - return 0; - - while(1) - { - bytes = io_read(file, buffer, BUFFER_SIZE); - if(bytes <= 0) - break; - crc = crc32(crc, buffer, bytes); - } - - io_close(file); - - return crc; -} diff --git a/src/engine/e_datafile.cpp b/src/engine/e_datafile.cpp new file mode 100644 index 00000000..0317f9d0 --- /dev/null +++ b/src/engine/e_datafile.cpp @@ -0,0 +1,677 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "e_datafile.h" +#include "e_engine.h" +#include + +static const int DEBUG=0; + +typedef struct +{ + int type; + int start; + int num; +} DATAFILE_ITEM_TYPE; + +typedef struct +{ + int type_and_id; + int size; +} DATAFILE_ITEM; + +typedef struct +{ + char id[4]; + int version; + int size; + int swaplen; + int num_item_types; + int num_items; + int num_raw_data; + int item_size; + int data_size; +} DATAFILE_HEADER; + +typedef struct +{ + int num_item_types; + int num_items; + int num_raw_data; + int item_size; + int data_size; + char start[4]; +} DATAFILE_DATA; + +typedef struct +{ + DATAFILE_ITEM_TYPE *item_types; + int *item_offsets; + int *data_offsets; + int *data_sizes; + + char *item_start; + char *data_start; +} DATAFILE_INFO; + +struct DATAFILE_t +{ + IOHANDLE file; + DATAFILE_INFO info; + DATAFILE_HEADER header; + int data_start_offset; + char **data_ptrs; + char *data; +}; + +DATAFILE *datafile_load(const char *filename) +{ + DATAFILE *df; + IOHANDLE file; + DATAFILE_HEADER header; + unsigned readsize; + + unsigned *dst; + unsigned char *src; + unsigned j; + int size = 0; + int allocsize = 0; + + (void)dst; + (void)src; + (void)j; + + + dbg_msg("datafile", "datafile loading. filename='%s'", filename); + + file = engine_openfile(filename, IOFLAG_READ); + if(!file) + return 0; + + /* TODO: change this header */ + io_read(file, &header, sizeof(header)); + if(header.id[0] != 'A' || header.id[1] != 'T' || header.id[2] != 'A' || header.id[3] != 'D') + { + if(header.id[0] != 'D' || header.id[1] != 'A' || header.id[2] != 'T' || header.id[3] != 'A') + { + dbg_msg("datafile", "wrong signature. %x %x %x %x", header.id[0], header.id[1], header.id[2], header.id[3]); + return 0; + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(&header, sizeof(int), sizeof(header)/sizeof(int)); +#endif + if(header.version != 3 && header.version != 4) + { + dbg_msg("datafile", "wrong version. version=%x", header.version); + return 0; + } + + /* read in the rest except the data */ + size = 0; + size += header.num_item_types*sizeof(DATAFILE_ITEM_TYPE); + size += (header.num_items+header.num_raw_data)*sizeof(int); + if(header.version == 4) + size += header.num_raw_data*sizeof(int); /* v4 has uncompressed data sizes aswell */ + size += header.item_size; + + allocsize = size; + allocsize += sizeof(DATAFILE); /* add space for info structure */ + allocsize += header.num_raw_data*sizeof(void*); /* add space for data pointers */ + + df = (DATAFILE*)mem_alloc(allocsize, 1); + df->header = header; + df->data_start_offset = sizeof(DATAFILE_HEADER) + size; + df->data_ptrs = (char**)(df+1); + df->data = (char *)(df+1)+header.num_raw_data*sizeof(char *); + df->file = file; + + /* clear the data pointers */ + mem_zero(df->data_ptrs, header.num_raw_data*sizeof(void*)); + + /* read types, offsets, sizes and item data */ + readsize = io_read(file, df->data, size); + if(readsize != size) + { + dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize); + return 0; + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(df->data, sizeof(int), header.swaplen / sizeof(int)); +#endif + + if(DEBUG) + { + dbg_msg("datafile", "allocsize=%d", allocsize); + dbg_msg("datafile", "readsize=%d", readsize); + dbg_msg("datafile", "swaplen=%d", header.swaplen); + dbg_msg("datafile", "item_size=%d", df->header.item_size); + } + + df->info.item_types = (DATAFILE_ITEM_TYPE *)df->data; + df->info.item_offsets = (int *)&df->info.item_types[df->header.num_item_types]; + df->info.data_offsets = (int *)&df->info.item_offsets[df->header.num_items]; + df->info.data_sizes = (int *)&df->info.data_offsets[df->header.num_raw_data]; + + if(header.version == 4) + df->info.item_start = (char *)&df->info.data_sizes[df->header.num_raw_data]; + else + df->info.item_start = (char *)&df->info.data_offsets[df->header.num_raw_data]; + df->info.data_start = df->info.item_start + df->header.item_size; + + if(DEBUG) + dbg_msg("datafile", "datafile loading done. datafile='%s'", filename); + + if(DEBUG) + { + /* + for(int i = 0; i < df->data.num_raw_data; i++) + { + void *p = datafile_get_data(df, i); + dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->data)), size); + } + + for(int i = 0; i < datafile_num_items(df); i++) + { + int type, id; + void *data = datafile_get_item(df, i, &type, &id); + dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, df->info.item_offsets[i]); + int *idata = (int*)data; + for(int k = 0; k < 3; k++) + dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); + } + + for(int i = 0; i < df->data.num_item_types; i++) + { + dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, + df->info.item_types[i].type, + df->info.item_types[i].start, + df->info.item_types[i].num); + for(int k = 0; k < df->info.item_types[i].num; k++) + { + int type, id; + datafile_get_item(df, df->info.item_types[i].start+k, &type, &id); + if(type != df->info.item_types[i].type) + dbg_msg("map", "\tERROR"); + } + } + */ + } + + return df; +} + +int datafile_num_data(DATAFILE *df) +{ + return df->header.num_raw_data; +} + +/* always returns the size in the file */ +int datafile_get_datasize(DATAFILE *df, int index) +{ + if(index == df->header.num_raw_data-1) + return df->header.data_size-df->info.data_offsets[index]; + return df->info.data_offsets[index+1]-df->info.data_offsets[index]; +} + +static void *datafile_get_data_impl(DATAFILE *df, int index, int swap) +{ + /* load it if needed */ + if(!df->data_ptrs[index]) + { + /* fetch the data size */ + int datasize = datafile_get_datasize(df, index); + int swapsize = datasize; + + if(df->header.version == 4) + { + /* v4 has compressed data */ + void *temp = (char *)mem_alloc(datasize, 1); + unsigned long uncompressed_size = df->info.data_sizes[index]; + unsigned long s; + + dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", index, datasize, uncompressed_size); + df->data_ptrs[index] = (char *)mem_alloc(uncompressed_size, 1); + + /* read the compressed data */ + io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); + io_read(df->file, temp, datasize); + + /* decompress the data, TODO: check for errors */ + s = uncompressed_size; + uncompress((Bytef*)df->data_ptrs[index], &s, (Bytef*)temp, datasize); + swapsize = s; + + /* clean up the temporary buffers */ + mem_free(temp); + } + else + { + /* load the data */ + dbg_msg("datafile", "loading data index=%d size=%d", index, datasize); + df->data_ptrs[index] = (char *)mem_alloc(datasize, 1); + io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); + io_read(df->file, df->data_ptrs[index], datasize); + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + if(swap && swapsize) + swap_endian(df->data_ptrs[index], sizeof(int), swapsize/sizeof(int)); +#endif + } + + return df->data_ptrs[index]; +} + +void *datafile_get_data(DATAFILE *df, int index) +{ + return datafile_get_data_impl(df, index, 0); +} + +void *datafile_get_data_swapped(DATAFILE *df, int index) +{ + return datafile_get_data_impl(df, index, 1); +} + +void datafile_unload_data(DATAFILE *df, int index) +{ + if(index < 0) + return; + + /* */ + mem_free(df->data_ptrs[index]); + df->data_ptrs[index] = 0x0; +} + +int datafile_get_itemsize(DATAFILE *df, int index) +{ + if(index == df->header.num_items-1) + return df->header.item_size-df->info.item_offsets[index]; + return df->info.item_offsets[index+1]-df->info.item_offsets[index]; +} + +void *datafile_get_item(DATAFILE *df, int index, int *type, int *id) +{ + DATAFILE_ITEM *i = (DATAFILE_ITEM *)(df->info.item_start+df->info.item_offsets[index]); + if(type) + *type = (i->type_and_id>>16)&0xffff; /* remove sign extention */ + if(id) + *id = i->type_and_id&0xffff; + return (void *)(i+1); +} + +void datafile_get_type(DATAFILE *df, int type, int *start, int *num) +{ + int i; + for(i = 0; i < df->header.num_item_types; i++) + { + if(df->info.item_types[i].type == type) + { + *start = df->info.item_types[i].start; + *num = df->info.item_types[i].num; + return; + } + } + + *start = 0; + *num = 0; +} + +void *datafile_find_item(DATAFILE *df, int type, int id) +{ + int start, num,i ; + int item_id; + void *item; + + datafile_get_type(df, type, &start, &num); + for(i = 0; i < num; i++) + { + item = datafile_get_item(df, start+i,0, &item_id); + if(id == item_id) + return item; + } + return 0; +} + +int datafile_num_items(DATAFILE *df) +{ + return df->header.num_items; +} + +void datafile_unload(DATAFILE *df) +{ + if(df) + { + /* free the data that is loaded */ + int i; + for(i = 0; i < df->header.num_raw_data; i++) + mem_free(df->data_ptrs[i]); + + io_close(df->file); + mem_free(df); + } +} + +/* DATAFILE output */ +typedef struct +{ + int uncompressed_size; + int compressed_size; + void *compressed_data; +} DATA_INFO; + +typedef struct +{ + int type; + int id; + int size; + int next; + int prev; + void *data; +} ITEM_INFO; + +typedef struct +{ + int num; + int first; + int last; +} ITEMTYPE_INFO; + +/* */ +struct DATAFILE_OUT_t +{ + IOHANDLE file; + int num_items; + int num_datas; + int num_item_types; + ITEMTYPE_INFO item_types[0xffff]; + ITEM_INFO items[1024]; + DATA_INFO datas[1024]; +}; + +DATAFILE_OUT *datafile_create(const char *filename) +{ + int i; + DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1); + df->file = engine_openfile(filename, IOFLAG_WRITE); + if(!df->file) + { + mem_free(df); + return 0; + } + + df->num_items = 0; + df->num_datas = 0; + df->num_item_types = 0; + mem_zero(&df->item_types, sizeof(df->item_types)); + + for(i = 0; i < 0xffff; i++) + { + df->item_types[i].first = -1; + df->item_types[i].last = -1; + } + + return df; +} + +int datafile_add_item(DATAFILE_OUT *df, int type, int id, int size, void *data) +{ + df->items[df->num_items].type = type; + df->items[df->num_items].id = id; + df->items[df->num_items].size = size; + + /* + dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size); + int i; + for(i = 0; i < size/4; i++) + dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]); + */ + + /* copy data */ + df->items[df->num_items].data = mem_alloc(size, 1); + mem_copy(df->items[df->num_items].data, data, size); + + if(!df->item_types[type].num) /* count item types */ + df->num_item_types++; + + /* link */ + df->items[df->num_items].prev = df->item_types[type].last; + df->items[df->num_items].next = -1; + + if(df->item_types[type].last != -1) + df->items[df->item_types[type].last].next = df->num_items; + df->item_types[type].last = df->num_items; + + if(df->item_types[type].first == -1) + df->item_types[type].first = df->num_items; + + df->item_types[type].num++; + + df->num_items++; + return df->num_items-1; +} + +int datafile_add_data(DATAFILE_OUT *df, int size, void *data) +{ + DATA_INFO *info = &df->datas[df->num_datas]; + unsigned long s = compressBound(size); + void *compdata = mem_alloc(s, 1); /* temporary buffer that we use duing compression */ + + int result = compress((Bytef*)compdata, &s, (Bytef*)data, size); + if(result != Z_OK) + { + dbg_msg("datafile", "compression error %d", result); + dbg_assert(0, "zlib error"); + } + + info->uncompressed_size = size; + info->compressed_size = (int)s; + info->compressed_data = mem_alloc(info->compressed_size, 1); + mem_copy(info->compressed_data, compdata, info->compressed_size); + mem_free(compdata); + + df->num_datas++; + return df->num_datas-1; +} + +int datafile_add_data_swapped(DATAFILE_OUT *df, int size, void *data) +{ +#if defined(CONF_ARCH_ENDIAN_BIG) + void *swapped = mem_alloc(size, 1); /* temporary buffer that we use duing compression */ + int index; + mem_copy(swapped, data, size); + swap_endian(&swapped, sizeof(int), size/sizeof(int)); + index = datafile_add_data(df, size, swapped); + mem_free(swapped); + return index; +#else + return datafile_add_data(df, size, data); +#endif +} + + +int datafile_finish(DATAFILE_OUT *df) +{ + int itemsize = 0; + int i, count, offset; + int typessize, headersize, offsetsize, filesize, swapsize; + int datasize = 0; + DATAFILE_ITEM_TYPE info; + DATAFILE_ITEM itm; + DATAFILE_HEADER header; + + /* we should now write this file! */ + if(DEBUG) + dbg_msg("datafile", "writing"); + + /* calculate sizes */ + for(i = 0; i < df->num_items; i++) + { + if(DEBUG) + dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(DATAFILE_ITEM)); + itemsize += df->items[i].size + sizeof(DATAFILE_ITEM); + } + + + for(i = 0; i < df->num_datas; i++) + datasize += df->datas[i].compressed_size; + + /* calculate the complete size */ + typessize = df->num_item_types*sizeof(DATAFILE_ITEM_TYPE); + headersize = sizeof(DATAFILE_HEADER); + offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int); + filesize = headersize + typessize + offsetsize + itemsize + datasize; + swapsize = filesize - datasize; + + (void)swapsize; + + if(DEBUG) + dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize); + + /* construct header */ + { + header.id[0] = 'D'; + header.id[1] = 'A'; + header.id[2] = 'T'; + header.id[3] = 'A'; + header.version = 4; + header.size = filesize - 16; + header.swaplen = swapsize - 16; + header.num_item_types = df->num_item_types; + header.num_items = df->num_items; + header.num_raw_data = df->num_datas; + header.item_size = itemsize; + header.data_size = datasize; + + /* TODO: apply swapping */ + /* write header */ + if(DEBUG) + dbg_msg("datafile", "headersize=%d", sizeof(header)); + io_write(df->file, &header, sizeof(header)); + } + + /* write types */ + for(i = 0, count = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + /* write info */ + info.type = i; + info.start = count; + info.num = df->item_types[i].num; + if(DEBUG) + dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num); + io_write(df->file, &info, sizeof(info)); + count += df->item_types[i].num; + } + } + + /* write item offsets */ + for(i = 0, offset = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + /* write all items in of this type */ + int k = df->item_types[i].first; + while(k != -1) + { + if(DEBUG) + dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset); + io_write(df->file, &offset, sizeof(offset)); + offset += df->items[k].size + sizeof(DATAFILE_ITEM); + + /* next */ + k = df->items[k].next; + } + } + } + + /* write data offsets */ + for(i = 0, offset = 0; i < df->num_datas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); + io_write(df->file, &offset, sizeof(offset)); + offset += df->datas[i].compressed_size; + } + + /* write data uncompressed sizes */ + for(i = 0, offset = 0; i < df->num_datas; i++) + { + /* + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); + */ + io_write(df->file, &df->datas[i].uncompressed_size, sizeof(int)); + } + + /* write items */ + for(i = 0; i < 0xffff; i++) + { + if(df->item_types[i].num) + { + /* write all items in of this type */ + int k = df->item_types[i].first; + while(k != -1) + { + itm.type_and_id = (i<<16)|df->items[k].id; + itm.size = df->items[k].size; + if(DEBUG) + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size); + + io_write(df->file, &itm, sizeof(itm)); + io_write(df->file, df->items[k].data, df->items[k].size); + + /* next */ + k = df->items[k].next; + } + } + } + + /* write data */ + for(i = 0; i < df->num_datas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].compressed_size); + io_write(df->file, df->datas[i].compressed_data, df->datas[i].compressed_size); + } + + /* free data */ + for(i = 0; i < df->num_items; i++) + mem_free(df->items[i].data); + + + io_close(df->file); + mem_free(df); + + if(DEBUG) + dbg_msg("datafile", "done"); + return 0; +} + +#define BUFFER_SIZE 64*1024 + +int datafile_crc(const char *filename) +{ + unsigned char buffer[BUFFER_SIZE]; + IOHANDLE file; + int crc = 0; + unsigned bytes = 0; + + file = engine_openfile(filename, IOFLAG_READ); + if(!file) + return 0; + + while(1) + { + bytes = io_read(file, buffer, BUFFER_SIZE); + if(bytes <= 0) + break; + crc = crc32(crc, buffer, bytes); + } + + io_close(file); + + return crc; +} diff --git a/src/engine/e_demorec.c b/src/engine/e_demorec.c deleted file mode 100644 index e66173e4..00000000 --- a/src/engine/e_demorec.c +++ /dev/null @@ -1,639 +0,0 @@ - -#include -#include "e_demorec.h" -#include "e_memheap.h" -#include "e_snapshot.h" -#include "e_compression.h" -#include "e_network.h" -#include "e_engine.h" -#include "e_if_other.h" - -static IOHANDLE record_file = 0; -static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; - -/* Record */ -static int record_lasttickmarker = -1; -static int record_lastkeyframe; -static unsigned char record_lastsnapshotdata[MAX_SNAPSHOT_SIZE]; - -int demorec_isrecording() { return record_file != 0; } - -int demorec_record_start(const char *filename, const char *netversion, const char *map, int crc, const char *type) -{ - DEMOREC_HEADER header; - if(record_file) - return -1; - - record_file = engine_openfile(filename, IOFLAG_WRITE); - - if(!record_file) - { - dbg_msg("demorec/record", "Unable to open '%s' for recording", filename); - return -1; - } - - /* write header */ - mem_zero(&header, sizeof(header)); - mem_copy(header.marker, header_marker, sizeof(header.marker)); - str_copy(header.netversion, netversion, sizeof(header.netversion)); - str_copy(header.map, map, sizeof(header.map)); - str_copy(header.type, type, sizeof(header.type)); - header.crc[0] = (crc>>24)&0xff; - header.crc[1] = (crc>>16)&0xff; - header.crc[2] = (crc>>8)&0xff; - header.crc[3] = (crc)&0xff; - io_write(record_file, &header, sizeof(header)); - - record_lastkeyframe = -1; - record_lasttickmarker = -1; - - dbg_msg("demorec/record", "Recording to '%s'", filename); - return 0; -} - -/* - Tickmarker - 7 = Always set - 6 = Keyframe flag - 0-5 = Delta tick - - Normal - 7 = Not set - 5-6 = Type - 0-4 = Size -*/ - -enum -{ - CHUNKTYPEFLAG_TICKMARKER = 0x80, - CHUNKTICKFLAG_KEYFRAME = 0x40, /* only when tickmarker is set*/ - - CHUNKMASK_TICK = 0x3f, - CHUNKMASK_TYPE = 0x60, - CHUNKMASK_SIZE = 0x1f, - - CHUNKTYPE_SNAPSHOT = 1, - CHUNKTYPE_MESSAGE = 2, - CHUNKTYPE_DELTA = 3, - - CHUNKFLAG_BIGSIZE = 0x10 -}; - -static void demorec_record_write_tickmarker(int tick, int keyframe) -{ - if(record_lasttickmarker == -1 || tick-record_lasttickmarker > 63 || keyframe) - { - unsigned char chunk[5]; - chunk[0] = CHUNKTYPEFLAG_TICKMARKER; - chunk[1] = (tick>>24)&0xff; - chunk[2] = (tick>>16)&0xff; - chunk[3] = (tick>>8)&0xff; - chunk[4] = (tick)&0xff; - - if(keyframe) - chunk[0] |= CHUNKTICKFLAG_KEYFRAME; - - io_write(record_file, chunk, sizeof(chunk)); - } - else - { - unsigned char chunk[1]; - chunk[0] = CHUNKTYPEFLAG_TICKMARKER | (tick-record_lasttickmarker); - io_write(record_file, chunk, sizeof(chunk)); - } - - record_lasttickmarker = tick; -} - -static void demorec_record_write(int type, const void *data, int size) -{ - char buffer[64*1024]; - char buffer2[64*1024]; - unsigned char chunk[3]; - - if(!record_file) - return; - - - /* pad the data with 0 so we get an alignment of 4, - else the compression won't work and miss some bytes */ - mem_copy(buffer2, data, size); - while(size&3) - buffer2[size++] = 0; - size = intpack_compress(buffer2, size, buffer); /* buffer2 -> buffer */ - size = netcommon_compress(buffer, size, buffer2, sizeof(buffer2)); /* buffer -> buffer2 */ - - - chunk[0] = ((type&0x3)<<5); - if(size < 30) - { - chunk[0] |= size; - io_write(record_file, chunk, 1); - } - else - { - if(size < 256) - { - chunk[0] |= 30; - chunk[1] = size&0xff; - io_write(record_file, chunk, 2); - } - else - { - chunk[0] |= 31; - chunk[1] = size&0xff; - chunk[2] = size>>8; - io_write(record_file, chunk, 3); - } - } - - io_write(record_file, buffer2, size); -} - -void demorec_record_snapshot(int tick, const void *data, int size) -{ - if(record_lastkeyframe == -1 || (tick-record_lastkeyframe) > SERVER_TICK_SPEED*5) - { - /* write full tickmarker */ - demorec_record_write_tickmarker(tick, 1); - - /* write snapshot */ - demorec_record_write(CHUNKTYPE_SNAPSHOT, data, size); - - record_lastkeyframe = tick; - mem_copy(record_lastsnapshotdata, data, size); - } - else - { - /* create delta, prepend tick */ - char delta_data[MAX_SNAPSHOT_SIZE+sizeof(int)]; - int delta_size; - - /* write tickmarker */ - demorec_record_write_tickmarker(tick, 0); - - delta_size = snapshot_create_delta((SNAPSHOT*)record_lastsnapshotdata, (SNAPSHOT*)data, &delta_data); - if(delta_size) - { - /* record delta */ - demorec_record_write(CHUNKTYPE_DELTA, delta_data, delta_size); - mem_copy(record_lastsnapshotdata, data, size); - } - } -} - -void demorec_record_message(const void *data, int size) -{ - demorec_record_write(CHUNKTYPE_MESSAGE, data, size); -} - -int demorec_record_stop() -{ - if(!record_file) - return -1; - - dbg_msg("demorec/record", "Stopped recording"); - io_close(record_file); - record_file = 0; - return 0; -} - -/* Playback */ -typedef struct KEYFRAME -{ - long filepos; - int tick; -} KEYFRAME; - -typedef struct KEYFRAME_SEARCH -{ - KEYFRAME frame; - struct KEYFRAME_SEARCH *next; -} KEYFRAME_SEARCH; - -static IOHANDLE play_file = 0; -static DEMOREC_PLAYCALLBACK play_callback_snapshot = 0; -static DEMOREC_PLAYCALLBACK play_callback_message = 0; -static KEYFRAME *keyframes = 0; - -static DEMOREC_PLAYBACKINFO playbackinfo; -static unsigned char playback_lastsnapshotdata[MAX_SNAPSHOT_SIZE]; -static int playback_lastsnapshotdata_size = -1; - - -const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; } -int demorec_isplaying() { return play_file != 0; } - -int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb) -{ - play_callback_snapshot = snapshot_cb; - play_callback_message = message_cb; - return 0; -} - -static int read_chunk_header(int *type, int *size, int *tick) -{ - unsigned char chunk = 0; - - *size = 0; - *type = 0; - - if(io_read(play_file, &chunk, sizeof(chunk)) != sizeof(chunk)) - return -1; - - if(chunk&CHUNKTYPEFLAG_TICKMARKER) - { - /* decode tick marker */ - int tickdelta = chunk&(CHUNKMASK_TICK); - *type = chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); - - if(tickdelta == 0) - { - unsigned char tickdata[4]; - if(io_read(play_file, tickdata, sizeof(tickdata)) != sizeof(tickdata)) - return -1; - *tick = (tickdata[0]<<24) | (tickdata[1]<<16) | (tickdata[2]<<8) | tickdata[3]; - } - else - { - *tick += tickdelta; - } - - } - else - { - /* decode normal chunk */ - *type = (chunk&CHUNKMASK_TYPE)>>5; - *size = chunk&CHUNKMASK_SIZE; - - if(*size == 30) - { - unsigned char sizedata[1]; - if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) - return -1; - *size = sizedata[0]; - - } - else if(*size == 31) - { - unsigned char sizedata[2]; - if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) - return -1; - *size = (sizedata[1]<<8) | sizedata[0]; - } - } - - return 0; -} - -static void scan_file() -{ - long start_pos; - HEAP *heap = 0; - KEYFRAME_SEARCH *first_key = 0; - KEYFRAME_SEARCH *current_key = 0; - /*DEMOREC_CHUNK chunk;*/ - int chunk_size, chunk_type, chunk_tick = 0; - int i; - - heap = memheap_create(); - - start_pos = io_tell(play_file); - playbackinfo.seekable_points = 0; - - while(1) - { - long current_pos = io_tell(play_file); - - if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) - break; - - /* read the chunk */ - if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) - { - if(chunk_type&CHUNKTICKFLAG_KEYFRAME) - { - KEYFRAME_SEARCH *key; - - /* save the position */ - key = memheap_allocate(heap, sizeof(KEYFRAME_SEARCH)); - key->frame.filepos = current_pos; - key->frame.tick = chunk_tick; - key->next = 0; - if(current_key) - current_key->next = key; - if(!first_key) - first_key = key; - current_key = key; - playbackinfo.seekable_points++; - } - - if(playbackinfo.first_tick == -1) - playbackinfo.first_tick = chunk_tick; - playbackinfo.last_tick = chunk_tick; - } - else if(chunk_size) - io_skip(play_file, chunk_size); - - } - - /* copy all the frames to an array instead for fast access */ - keyframes = (KEYFRAME*)mem_alloc(playbackinfo.seekable_points*sizeof(KEYFRAME), 1); - for(current_key = first_key, i = 0; current_key; current_key = current_key->next, i++) - keyframes[i] = current_key->frame; - - /* destroy the temporary heap and seek back to the start */ - memheap_destroy(heap); - io_seek(play_file, start_pos, IOSEEK_START); -} - -static void do_tick() -{ - static char compresseddata[MAX_SNAPSHOT_SIZE]; - static char decompressed[MAX_SNAPSHOT_SIZE]; - static char data[MAX_SNAPSHOT_SIZE]; - int chunk_size, chunk_type, chunk_tick; - int data_size; - int got_snapshot = 0; - - /* update ticks */ - playbackinfo.previous_tick = playbackinfo.current_tick; - playbackinfo.current_tick = playbackinfo.next_tick; - chunk_tick = playbackinfo.current_tick; - - while(1) - { - if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) - { - /* stop on error or eof */ - dbg_msg("demorec", "end of file"); - demorec_playback_pause(); - break; - } - - /* read the chunk */ - if(chunk_size) - { - if(io_read(play_file, compresseddata, chunk_size) != chunk_size) - { - /* stop on error or eof */ - dbg_msg("demorec", "error reading chunk"); - demorec_playback_stop(); - break; - } - - data_size = netcommon_decompress(compresseddata, chunk_size, decompressed, sizeof(decompressed)); - if(data_size < 0) - { - /* stop on error or eof */ - dbg_msg("demorec", "error during network decompression"); - demorec_playback_stop(); - break; - } - - data_size = intpack_decompress(decompressed, data_size, data); - - if(data_size < 0) - { - dbg_msg("demorec", "error during intpack decompression"); - demorec_playback_stop(); - break; - } - } - - if(chunk_type == CHUNKTYPE_DELTA) - { - /* process delta snapshot */ - static char newsnap[MAX_SNAPSHOT_SIZE]; - - got_snapshot = 1; - - data_size = snapshot_unpack_delta((SNAPSHOT*)playback_lastsnapshotdata, (SNAPSHOT*)newsnap, data, data_size); - - if(data_size >= 0) - { - if(play_callback_snapshot) - play_callback_snapshot(newsnap, data_size); - - playback_lastsnapshotdata_size = data_size; - mem_copy(playback_lastsnapshotdata, newsnap, data_size); - } - else - dbg_msg("demorec", "error duing unpacking of delta, err=%d", data_size); - } - else if(chunk_type == CHUNKTYPE_SNAPSHOT) - { - /* process full snapshot */ - got_snapshot = 1; - - playback_lastsnapshotdata_size = data_size; - mem_copy(playback_lastsnapshotdata, data, data_size); - if(play_callback_snapshot) - play_callback_snapshot(data, data_size); - } - else - { - /* if there were no snapshots in this tick, replay the last one */ - if(!got_snapshot && play_callback_snapshot && playback_lastsnapshotdata_size != -1) - { - got_snapshot = 1; - play_callback_snapshot(playback_lastsnapshotdata, playback_lastsnapshotdata_size); - } - - /* check the remaining types */ - if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) - { - playbackinfo.next_tick = chunk_tick; - break; - } - else if(chunk_type == CHUNKTYPE_MESSAGE) - { - if(play_callback_message) - play_callback_message(data, data_size); - } - } - } -} - -void demorec_playback_pause() -{ - playbackinfo.paused = 1; -} - -void demorec_playback_unpause() -{ - if(playbackinfo.paused) - { - /*playbackinfo.start_tick = playbackinfo.current_tick; - playbackinfo.start_time = time_get();*/ - playbackinfo.paused = 0; - } -} - -int demorec_playback_load(const char *filename) -{ - play_file = engine_openfile(filename, IOFLAG_READ); - if(!play_file) - { - dbg_msg("demorec/playback", "could not open '%s'", filename); - return -1; - } - - /* clear the playback info */ - mem_zero(&playbackinfo, sizeof(playbackinfo)); - playbackinfo.first_tick = -1; - playbackinfo.last_tick = -1; - /*playbackinfo.start_tick = -1;*/ - playbackinfo.next_tick = -1; - playbackinfo.current_tick = -1; - playbackinfo.previous_tick = -1; - playbackinfo.speed = 1; - - playback_lastsnapshotdata_size = -1; - - /* read the header */ - io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header)); - if(mem_comp(playbackinfo.header.marker, header_marker, sizeof(header_marker)) != 0) - { - dbg_msg("demorec/playback", "'%s' is not a demo file", filename); - io_close(play_file); - play_file = 0; - return -1; - } - - /* scan the file for interessting points */ - scan_file(); - - /* ready for playback */ - return 0; -} - -int demorec_playback_nextframe() -{ - do_tick(); - return demorec_isplaying(); -} - -int demorec_playback_play() -{ - /* fill in previous and next tick */ - while(playbackinfo.previous_tick == -1 && demorec_isplaying()) - do_tick(); - - /* set start info */ - /*playbackinfo.start_tick = playbackinfo.previous_tick; - playbackinfo.start_time = time_get();*/ - playbackinfo.current_time = playbackinfo.previous_tick*time_freq()/SERVER_TICK_SPEED; - playbackinfo.last_update = time_get(); - return 0; -} - -int demorec_playback_set(float percent) -{ - int keyframe; - int wanted_tick; - if(!play_file) - return -1; - - /* -5 because we have to have a current tick and previous tick when we do the playback */ - wanted_tick = playbackinfo.first_tick + (int)((playbackinfo.last_tick-playbackinfo.first_tick)*percent) - 5; - - keyframe = (int)(playbackinfo.seekable_points*percent); - - if(keyframe < 0 || keyframe >= playbackinfo.seekable_points) - return -1; - - /* get correct key frame */ - if(keyframes[keyframe].tick < wanted_tick) - while(keyframe < playbackinfo.seekable_points-1 && keyframes[keyframe].tick < wanted_tick) - keyframe++; - - while(keyframe && keyframes[keyframe].tick > wanted_tick) - keyframe--; - - /* seek to the correct keyframe */ - io_seek(play_file, keyframes[keyframe].filepos, IOSEEK_START); - - /*playbackinfo.start_tick = -1;*/ - playbackinfo.next_tick = -1; - playbackinfo.current_tick = -1; - playbackinfo.previous_tick = -1; - - /* playback everything until we hit our tick */ - while(playbackinfo.previous_tick < wanted_tick) - do_tick(); - - demorec_playback_play(); - - return 0; -} - -void demorec_playback_setspeed(float speed) -{ - playbackinfo.speed = speed; -} - -int demorec_playback_update() -{ - int64 now = time_get(); - int64 deltatime = now-playbackinfo.last_update; - playbackinfo.last_update = now; - - if(!demorec_isplaying()) - return 0; - - if(playbackinfo.paused) - { - - } - else - { - int64 freq = time_freq(); - playbackinfo.current_time += (int64)(deltatime*(double)playbackinfo.speed); - - while(1) - { - int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; - - /* break if we are ready */ - if(curtick_start > playbackinfo.current_time) - break; - - /* do one more tick */ - do_tick(); - - if(playbackinfo.paused) - return 0; - } - - /* update intratick */ - { - int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; - int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED; - playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start); - playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq; - } - - if(playbackinfo.current_tick == playbackinfo.previous_tick || - playbackinfo.current_tick == playbackinfo.next_tick) - { - dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", - playbackinfo.previous_tick, playbackinfo.current_tick, playbackinfo.next_tick); - } - } - - return 0; -} - -int demorec_playback_stop(const char *filename) -{ - if(!play_file) - return -1; - - dbg_msg("demorec/playback", "Stopped playback"); - io_close(play_file); - play_file = 0; - mem_free(keyframes); - keyframes = 0; - return 0; -} diff --git a/src/engine/e_demorec.cpp b/src/engine/e_demorec.cpp new file mode 100644 index 00000000..1bab1b8d --- /dev/null +++ b/src/engine/e_demorec.cpp @@ -0,0 +1,640 @@ +#include +#include "e_demorec.h" +#include "e_memheap.h" +#include "e_snapshot.h" +#include "e_compression.h" +#include "e_network.h" +#include "e_engine.h" +#include "e_if_other.h" + +static IOHANDLE record_file = 0; +static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; + +/* Record */ +static int record_lasttickmarker = -1; +static int record_lastkeyframe; +static unsigned char record_lastsnapshotdata[CSnapshot::MAX_SIZE]; + +int demorec_isrecording() { return record_file != 0; } + +int demorec_record_start(const char *filename, const char *netversion, const char *map, int crc, const char *type) +{ + DEMOREC_HEADER header; + if(record_file) + return -1; + + record_file = engine_openfile(filename, IOFLAG_WRITE); + + if(!record_file) + { + dbg_msg("demorec/record", "Unable to open '%s' for recording", filename); + return -1; + } + + /* write header */ + mem_zero(&header, sizeof(header)); + mem_copy(header.marker, header_marker, sizeof(header.marker)); + str_copy(header.netversion, netversion, sizeof(header.netversion)); + str_copy(header.map, map, sizeof(header.map)); + str_copy(header.type, type, sizeof(header.type)); + header.crc[0] = (crc>>24)&0xff; + header.crc[1] = (crc>>16)&0xff; + header.crc[2] = (crc>>8)&0xff; + header.crc[3] = (crc)&0xff; + io_write(record_file, &header, sizeof(header)); + + record_lastkeyframe = -1; + record_lasttickmarker = -1; + + dbg_msg("demorec/record", "Recording to '%s'", filename); + return 0; +} + +/* + Tickmarker + 7 = Always set + 6 = Keyframe flag + 0-5 = Delta tick + + Normal + 7 = Not set + 5-6 = Type + 0-4 = Size +*/ + +enum +{ + CHUNKTYPEFLAG_TICKMARKER = 0x80, + CHUNKTICKFLAG_KEYFRAME = 0x40, /* only when tickmarker is set*/ + + CHUNKMASK_TICK = 0x3f, + CHUNKMASK_TYPE = 0x60, + CHUNKMASK_SIZE = 0x1f, + + CHUNKTYPE_SNAPSHOT = 1, + CHUNKTYPE_MESSAGE = 2, + CHUNKTYPE_DELTA = 3, + + CHUNKFLAG_BIGSIZE = 0x10 +}; + +static void demorec_record_write_tickmarker(int tick, int keyframe) +{ + if(record_lasttickmarker == -1 || tick-record_lasttickmarker > 63 || keyframe) + { + unsigned char chunk[5]; + chunk[0] = CHUNKTYPEFLAG_TICKMARKER; + chunk[1] = (tick>>24)&0xff; + chunk[2] = (tick>>16)&0xff; + chunk[3] = (tick>>8)&0xff; + chunk[4] = (tick)&0xff; + + if(keyframe) + chunk[0] |= CHUNKTICKFLAG_KEYFRAME; + + io_write(record_file, chunk, sizeof(chunk)); + } + else + { + unsigned char chunk[1]; + chunk[0] = CHUNKTYPEFLAG_TICKMARKER | (tick-record_lasttickmarker); + io_write(record_file, chunk, sizeof(chunk)); + } + + record_lasttickmarker = tick; +} + +static void demorec_record_write(int type, const void *data, int size) +{ + char buffer[64*1024]; + char buffer2[64*1024]; + unsigned char chunk[3]; + + if(!record_file) + return; + + + /* pad the data with 0 so we get an alignment of 4, + else the compression won't work and miss some bytes */ + mem_copy(buffer2, data, size); + while(size&3) + buffer2[size++] = 0; + size = intpack_compress(buffer2, size, buffer); /* buffer2 -> buffer */ + size = CNetBase::Compress(buffer, size, buffer2, sizeof(buffer2)); /* buffer -> buffer2 */ + + + chunk[0] = ((type&0x3)<<5); + if(size < 30) + { + chunk[0] |= size; + io_write(record_file, chunk, 1); + } + else + { + if(size < 256) + { + chunk[0] |= 30; + chunk[1] = size&0xff; + io_write(record_file, chunk, 2); + } + else + { + chunk[0] |= 31; + chunk[1] = size&0xff; + chunk[2] = size>>8; + io_write(record_file, chunk, 3); + } + } + + io_write(record_file, buffer2, size); +} + +void demorec_record_snapshot(int tick, const void *data, int size) +{ + if(record_lastkeyframe == -1 || (tick-record_lastkeyframe) > SERVER_TICK_SPEED*5) + { + /* write full tickmarker */ + demorec_record_write_tickmarker(tick, 1); + + /* write snapshot */ + demorec_record_write(CHUNKTYPE_SNAPSHOT, data, size); + + record_lastkeyframe = tick; + mem_copy(record_lastsnapshotdata, data, size); + } + else + { + /* create delta, prepend tick */ + char delta_data[CSnapshot::MAX_SIZE+sizeof(int)]; + int delta_size; + + /* write tickmarker */ + demorec_record_write_tickmarker(tick, 0); + + delta_size = CSnapshot::CreateDelta((CSnapshot*)record_lastsnapshotdata, (CSnapshot*)data, &delta_data); + if(delta_size) + { + /* record delta */ + demorec_record_write(CHUNKTYPE_DELTA, delta_data, delta_size); + mem_copy(record_lastsnapshotdata, data, size); + } + } +} + +void demorec_record_message(const void *data, int size) +{ + demorec_record_write(CHUNKTYPE_MESSAGE, data, size); +} + +int demorec_record_stop() +{ + if(!record_file) + return -1; + + dbg_msg("demorec/record", "Stopped recording"); + io_close(record_file); + record_file = 0; + return 0; +} + +/* Playback */ +typedef struct KEYFRAME +{ + long filepos; + int tick; +} KEYFRAME; + +typedef struct KEYFRAME_SEARCH +{ + KEYFRAME frame; + struct KEYFRAME_SEARCH *next; +} KEYFRAME_SEARCH; + +static IOHANDLE play_file = 0; +static DEMOREC_PLAYCALLBACK play_callback_snapshot = 0; +static DEMOREC_PLAYCALLBACK play_callback_message = 0; +static KEYFRAME *keyframes = 0; + +static DEMOREC_PLAYBACKINFO playbackinfo; +static unsigned char playback_lastsnapshotdata[CSnapshot::MAX_SIZE]; +static int playback_lastsnapshotdata_size = -1; + + +const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; } +int demorec_isplaying() { return play_file != 0; } + +int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb) +{ + play_callback_snapshot = snapshot_cb; + play_callback_message = message_cb; + return 0; +} + +static int read_chunk_header(int *type, int *size, int *tick) +{ + unsigned char chunk = 0; + + *size = 0; + *type = 0; + + if(io_read(play_file, &chunk, sizeof(chunk)) != sizeof(chunk)) + return -1; + + if(chunk&CHUNKTYPEFLAG_TICKMARKER) + { + /* decode tick marker */ + int tickdelta = chunk&(CHUNKMASK_TICK); + *type = chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); + + if(tickdelta == 0) + { + unsigned char tickdata[4]; + if(io_read(play_file, tickdata, sizeof(tickdata)) != sizeof(tickdata)) + return -1; + *tick = (tickdata[0]<<24) | (tickdata[1]<<16) | (tickdata[2]<<8) | tickdata[3]; + } + else + { + *tick += tickdelta; + } + + } + else + { + /* decode normal chunk */ + *type = (chunk&CHUNKMASK_TYPE)>>5; + *size = chunk&CHUNKMASK_SIZE; + + if(*size == 30) + { + unsigned char sizedata[1]; + if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) + return -1; + *size = sizedata[0]; + + } + else if(*size == 31) + { + unsigned char sizedata[2]; + if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) + return -1; + *size = (sizedata[1]<<8) | sizedata[0]; + } + } + + return 0; +} + +static void scan_file() +{ + long start_pos; + HEAP *heap = 0; + KEYFRAME_SEARCH *first_key = 0; + KEYFRAME_SEARCH *current_key = 0; + /*DEMOREC_CHUNK chunk;*/ + int chunk_size, chunk_type, chunk_tick = 0; + int i; + + heap = memheap_create(); + + start_pos = io_tell(play_file); + playbackinfo.seekable_points = 0; + + while(1) + { + long current_pos = io_tell(play_file); + + if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) + break; + + /* read the chunk */ + if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) + { + if(chunk_type&CHUNKTICKFLAG_KEYFRAME) + { + KEYFRAME_SEARCH *key; + + /* save the position */ + key = (KEYFRAME_SEARCH *)memheap_allocate(heap, sizeof(KEYFRAME_SEARCH)); + key->frame.filepos = current_pos; + key->frame.tick = chunk_tick; + key->next = 0; + if(current_key) + current_key->next = key; + if(!first_key) + first_key = key; + current_key = key; + playbackinfo.seekable_points++; + } + + if(playbackinfo.first_tick == -1) + playbackinfo.first_tick = chunk_tick; + playbackinfo.last_tick = chunk_tick; + } + else if(chunk_size) + io_skip(play_file, chunk_size); + + } + + /* copy all the frames to an array instead for fast access */ + keyframes = (KEYFRAME*)mem_alloc(playbackinfo.seekable_points*sizeof(KEYFRAME), 1); + for(current_key = first_key, i = 0; current_key; current_key = current_key->next, i++) + keyframes[i] = current_key->frame; + + /* destroy the temporary heap and seek back to the start */ + memheap_destroy(heap); + io_seek(play_file, start_pos, IOSEEK_START); +} + +static void do_tick() +{ + static char compresseddata[CSnapshot::MAX_SIZE]; + static char decompressed[CSnapshot::MAX_SIZE]; + static char data[CSnapshot::MAX_SIZE]; + int chunk_type, chunk_tick, chunk_size; + int data_size; + int got_snapshot = 0; + + /* update ticks */ + playbackinfo.previous_tick = playbackinfo.current_tick; + playbackinfo.current_tick = playbackinfo.next_tick; + chunk_tick = playbackinfo.current_tick; + + while(1) + { + if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) + { + /* stop on error or eof */ + dbg_msg("demorec", "end of file"); + demorec_playback_pause(); + break; + } + + /* read the chunk */ + if(chunk_size) + { + if(io_read(play_file, compresseddata, chunk_size) != (unsigned)chunk_size) + { + /* stop on error or eof */ + dbg_msg("demorec", "error reading chunk"); + demorec_playback_stop(); + break; + } + + data_size = CNetBase::Decompress(compresseddata, chunk_size, decompressed, sizeof(decompressed)); + if(data_size < 0) + { + /* stop on error or eof */ + dbg_msg("demorec", "error during network decompression"); + demorec_playback_stop(); + break; + } + + data_size = intpack_decompress(decompressed, data_size, data); + + if(data_size < 0) + { + dbg_msg("demorec", "error during intpack decompression"); + demorec_playback_stop(); + break; + } + } + + if(chunk_type == CHUNKTYPE_DELTA) + { + /* process delta snapshot */ + static char newsnap[CSnapshot::MAX_SIZE]; + + got_snapshot = 1; + + data_size = CSnapshot::UnpackDelta((CSnapshot*)playback_lastsnapshotdata, (CSnapshot*)newsnap, data, data_size); + + if(data_size >= 0) + { + if(play_callback_snapshot) + play_callback_snapshot(newsnap, data_size); + + playback_lastsnapshotdata_size = data_size; + mem_copy(playback_lastsnapshotdata, newsnap, data_size); + } + else + dbg_msg("demorec", "error duing unpacking of delta, err=%d", data_size); + } + else if(chunk_type == CHUNKTYPE_SNAPSHOT) + { + /* process full snapshot */ + got_snapshot = 1; + + playback_lastsnapshotdata_size = data_size; + mem_copy(playback_lastsnapshotdata, data, data_size); + if(play_callback_snapshot) + play_callback_snapshot(data, data_size); + } + else + { + /* if there were no snapshots in this tick, replay the last one */ + if(!got_snapshot && play_callback_snapshot && playback_lastsnapshotdata_size != -1) + { + got_snapshot = 1; + play_callback_snapshot(playback_lastsnapshotdata, playback_lastsnapshotdata_size); + } + + /* check the remaining types */ + if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) + { + playbackinfo.next_tick = chunk_tick; + break; + } + else if(chunk_type == CHUNKTYPE_MESSAGE) + { + if(play_callback_message) + play_callback_message(data, data_size); + } + } + } +} + +void demorec_playback_pause() +{ + playbackinfo.paused = 1; +} + +void demorec_playback_unpause() +{ + if(playbackinfo.paused) + { + /*playbackinfo.start_tick = playbackinfo.current_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.paused = 0; + } +} + +int demorec_playback_load(const char *filename) +{ + play_file = engine_openfile(filename, IOFLAG_READ); + if(!play_file) + { + dbg_msg("demorec/playback", "could not open '%s'", filename); + return -1; + } + + /* clear the playback info */ + mem_zero(&playbackinfo, sizeof(playbackinfo)); + playbackinfo.first_tick = -1; + playbackinfo.last_tick = -1; + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + playbackinfo.speed = 1; + + playback_lastsnapshotdata_size = -1; + + /* read the header */ + io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header)); + if(mem_comp(playbackinfo.header.marker, header_marker, sizeof(header_marker)) != 0) + { + dbg_msg("demorec/playback", "'%s' is not a demo file", filename); + io_close(play_file); + play_file = 0; + return -1; + } + + /* scan the file for interessting points */ + scan_file(); + + /* ready for playback */ + return 0; +} + +int demorec_playback_nextframe() +{ + do_tick(); + return demorec_isplaying(); +} + +int demorec_playback_play() +{ + /* fill in previous and next tick */ + while(playbackinfo.previous_tick == -1 && demorec_isplaying()) + do_tick(); + + /* set start info */ + /*playbackinfo.start_tick = playbackinfo.previous_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.current_time = playbackinfo.previous_tick*time_freq()/SERVER_TICK_SPEED; + playbackinfo.last_update = time_get(); + return 0; +} + +int demorec_playback_set(float percent) +{ + int keyframe; + int wanted_tick; + if(!play_file) + return -1; + + /* -5 because we have to have a current tick and previous tick when we do the playback */ + wanted_tick = playbackinfo.first_tick + (int)((playbackinfo.last_tick-playbackinfo.first_tick)*percent) - 5; + + keyframe = (int)(playbackinfo.seekable_points*percent); + + if(keyframe < 0 || keyframe >= playbackinfo.seekable_points) + return -1; + + /* get correct key frame */ + if(keyframes[keyframe].tick < wanted_tick) + while(keyframe < playbackinfo.seekable_points-1 && keyframes[keyframe].tick < wanted_tick) + keyframe++; + + while(keyframe && keyframes[keyframe].tick > wanted_tick) + keyframe--; + + /* seek to the correct keyframe */ + io_seek(play_file, keyframes[keyframe].filepos, IOSEEK_START); + + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + + /* playback everything until we hit our tick */ + while(playbackinfo.previous_tick < wanted_tick) + do_tick(); + + demorec_playback_play(); + + return 0; +} + +void demorec_playback_setspeed(float speed) +{ + playbackinfo.speed = speed; +} + +int demorec_playback_update() +{ + int64 now = time_get(); + int64 deltatime = now-playbackinfo.last_update; + playbackinfo.last_update = now; + + if(!demorec_isplaying()) + return 0; + + if(playbackinfo.paused) + { + + } + else + { + int64 freq = time_freq(); + playbackinfo.current_time += (int64)(deltatime*(double)playbackinfo.speed); + + while(1) + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + + /* break if we are ready */ + if(curtick_start > playbackinfo.current_time) + break; + + /* do one more tick */ + do_tick(); + + if(playbackinfo.paused) + return 0; + } + + /* update intratick */ + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED; + playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start); + playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq; + } + + if(playbackinfo.current_tick == playbackinfo.previous_tick || + playbackinfo.current_tick == playbackinfo.next_tick) + { + dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", + playbackinfo.previous_tick, playbackinfo.current_tick, playbackinfo.next_tick); + } + } + + return 0; +} + +int demorec_playback_stop() +{ + if(!play_file) + return -1; + + dbg_msg("demorec/playback", "Stopped playback"); + io_close(play_file); + play_file = 0; + mem_free(keyframes); + keyframes = 0; + return 0; +} + + diff --git a/src/engine/e_demorec.h b/src/engine/e_demorec.h index 482debb4..9716b463 100644 --- a/src/engine/e_demorec.h +++ b/src/engine/e_demorec.h @@ -2,10 +2,6 @@ #ifndef _DEMOREC_H #define _DEMOREC_H -#ifdef __cplusplus -extern "C"{ -#endif - typedef struct DEMOREC_HEADER { char marker[8]; @@ -70,8 +66,4 @@ const DEMOREC_PLAYBACKINFO *demorec_playback_info(); int demorec_isplaying(); int demorec_playback_stop(); -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_engine.c b/src/engine/e_engine.c deleted file mode 100644 index 7afbb2ce..00000000 --- a/src/engine/e_engine.c +++ /dev/null @@ -1,596 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include "e_linereader.h" - -/* compiled-in data-dir path */ -#define DATA_DIR "data" - -static JOBPOOL hostlookuppool; -static int engine_find_datadir(char *argv0); - -static void con_dbg_dumpmem(void *result, void *user_data) -{ - mem_debug_dump(); -} - -static void con_dbg_lognetwork(void *result, void *user_data) -{ - netcommon_openlog("network_sent.dat", "network_recv.dat"); -} - - -static char application_save_path[512] = {0}; -static char datadir[512] = {0}; - -const char *engine_savepath(const char *filename, char *buffer, int max) -{ - str_format(buffer, max, "%s/%s", application_save_path, filename); - return buffer; -} - -void engine_init(const char *appname) -{ - dbg_logger_stdout(); - dbg_logger_debugger(); - - /* */ - dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); -#ifdef CONF_ARCH_ENDIAN_LITTLE - dbg_msg("engine", "arch is little endian"); -#elif defined(CONF_ARCH_ENDIAN_BIG) - dbg_msg("engine", "arch is big endian"); -#else - dbg_msg("engine", "unknown endian"); -#endif - - /* init the network */ - net_init(); - netcommon_init(); - - /* create storage location */ - { - char path[1024] = {0}; - fs_storage_path(appname, application_save_path, sizeof(application_save_path)); - if(fs_makedir(application_save_path) == 0) - { - str_format(path, sizeof(path), "%s/screenshots", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/maps", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/downloadedmaps", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/demos", application_save_path); - fs_makedir(path); - } - } - - /* init console and add the console logger */ - console_init(); - dbg_logger(console_print); - - jobs_initpool(&hostlookuppool, 1); - - MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory"); - MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network"); - - /* reset the config */ - config_reset(); -} - - -void engine_listdir(int types, const char *path, FS_LISTDIR_CALLBACK cb, void *user) -{ - char buffer[1024]; - - /* list current directory */ - if(types&LISTDIRTYPE_CURRENT) - { - fs_listdir(path, cb, user); - } - - /* list users directory */ - if(types&LISTDIRTYPE_SAVE) - { - engine_savepath(path, buffer, sizeof(buffer)); - fs_listdir(buffer, cb, user); - } - - /* list datadir directory */ - if(types&LISTDIRTYPE_DATA) - { - str_format(buffer, sizeof(buffer), "%s/%s", datadir, path); - fs_listdir(buffer, cb, user); - } -} - -void engine_getpath(char *buffer, int buffer_size, const char *filename, int flags) -{ - if(flags&IOFLAG_WRITE) - engine_savepath(filename, buffer, buffer_size); - else - { - IOHANDLE handle = 0; - - /* check current directory */ - handle = io_open(filename, flags); - if(handle) - { - str_copy(buffer, filename, buffer_size); - io_close(handle); - return; - } - - /* check user directory */ - engine_savepath(filename, buffer, buffer_size); - handle = io_open(buffer, flags); - if(handle) - { - io_close(handle); - return; - } - - /* check normal data directory */ - str_format(buffer, buffer_size, "%s/%s", datadir, filename); - handle = io_open(buffer, flags); - if(handle) - { - io_close(handle); - return; - } - } - - buffer[0] = 0; -} - -IOHANDLE engine_openfile(const char *filename, int flags) -{ - char buffer[1024]; - - if(flags&IOFLAG_WRITE) - { - engine_savepath(filename, buffer, sizeof(buffer)); - return io_open(buffer, flags); - } - else - { - IOHANDLE handle = 0; - - /* check current directory */ - handle = io_open(filename, flags); - if(handle) - return handle; - - /* check user directory */ - engine_savepath(filename, buffer, sizeof(buffer)); - handle = io_open(buffer, flags); - if(handle) - return handle; - - /* check normal data directory */ - str_format(buffer, sizeof(buffer), "%s/%s", datadir, filename); - handle = io_open(buffer, flags); - if(handle) - return handle; - } - return 0; -} - -void engine_parse_arguments(int argc, char **argv) -{ - /* load the configuration */ - int i; - - /* check for datadir override */ - for(i = 1; i < argc; i++) - { - if(argv[i][0] == '-' && argv[i][1] == 'd' && argv[i][2] == 0 && argc - i > 1) - { - str_copy(datadir, argv[i+1], sizeof(datadir)); - i++; - } - } - - /* search for data directory */ - engine_find_datadir(argv[0]); - - dbg_msg("engine/datadir", "paths used:"); - dbg_msg("engine/datadir", "\t."); - dbg_msg("engine/datadir", "\t%s", application_save_path); - dbg_msg("engine/datadir", "\t%s", datadir); - dbg_msg("engine/datadir", "saving files to: %s", application_save_path); - - - /* check for scripts to execute */ - for(i = 1; i < argc; i++) - { - if(argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0 && argc - i > 1) - { - console_execute_file(argv[i+1]); - i++; - } - } - - /* search arguments for overrides */ - { - int i; - for(i = 1; i < argc; i++) - console_execute_line(argv[i]); - } - - console_execute_file("autoexec.cfg"); - - /* open logfile if needed */ - if(config.logfile[0]) - dbg_logger_file(config.logfile); - - /* set default servers and load from disk*/ - mastersrv_default(); - mastersrv_load(); -} - - -static IOHANDLE config_file = 0; - -int engine_config_write_start() -{ - config_save("settings.cfg"); - config_file = engine_openfile("settings.cfg", IOFLAG_WRITE); - if(config_file == 0) - return -1; - return 0; -} - -void engine_config_write_line(const char *line) -{ - if(config_file) - { -#if defined(CONF_FAMILY_WINDOWS) - static const char newline[] = "\r\n"; -#else - static const char newline[] = "\n"; -#endif - io_write(config_file, line, strlen(line)); - io_write(config_file, newline, sizeof(newline)-1); - } -} - -void engine_config_write_stop() -{ - io_close(config_file); - config_file = 0; -} -/* -void engine_writeconfig() -{ -}*/ - -static int perf_tick = 1; -static PERFORMACE_INFO *current = 0; - -void perf_init() -{ -} - -void perf_next() -{ - perf_tick++; - current = 0; -} - -void perf_start(PERFORMACE_INFO *info) -{ - if(info->tick != perf_tick) - { - info->parent = current; - info->first_child = 0; - info->next_child = 0; - - if(info->parent) - { - info->next_child = info->parent->first_child; - info->parent->first_child = info; - } - - info->tick = perf_tick; - info->biggest = 0; - info->total = 0; - } - - current = info; - current->start = time_get(); -} - -void perf_end() -{ - if(!current) - return; - - current->last_delta = time_get()-current->start; - current->total += current->last_delta; - - if(current->last_delta > current->biggest) - current->biggest = current->last_delta; - - current = current->parent; -} - -static void perf_dump_imp(PERFORMACE_INFO *info, int indent) -{ - char buf[512] = {0}; - int64 freq = time_freq(); - int i; - - for(i = 0; i < indent; i++) - buf[i] = ' '; - - str_format(&buf[indent], sizeof(buf)-indent, "%-20s %8.2f %8.2f", info->name, info->total*1000/(float)freq, info->biggest*1000/(float)freq); - dbg_msg("perf", "%s", buf); - - info = info->first_child; - while(info) - { - perf_dump_imp(info, indent+2); - info = info->next_child; - } -} - -void perf_dump(PERFORMACE_INFO *top) -{ - perf_dump_imp(top, 0); -} - -/* master server functions */ -typedef struct -{ - char hostname[128]; - NETADDR addr; - - HOSTLOOKUP lookup; -} MASTER_INFO; - -static MASTER_INFO master_servers[MAX_MASTERSERVERS] = {{{0}}}; -static int needs_update = -1; - -int mastersrv_refresh_addresses() -{ - int i; - - if(needs_update != -1) - return 0; - - dbg_msg("engine/mastersrv", "refreshing master server addresses"); - - /* add lookup jobs */ - for(i = 0; i < MAX_MASTERSERVERS; i++) - engine_hostlookup(&master_servers[i].lookup, master_servers[i].hostname); - - needs_update = 1; - return 0; -} - -void mastersrv_update() -{ - int i; - - /* check if we need to update */ - if(needs_update != 1) - return; - needs_update = 0; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(jobs_status(&master_servers[i].lookup.job) != JOBSTATUS_DONE) - needs_update = 1; - else - { - master_servers[i].addr = master_servers[i].lookup.addr; - master_servers[i].addr.port = 8300; - } - } - - if(!needs_update) - { - dbg_msg("engine/mastersrv", "saving addresses"); - mastersrv_save(); - } -} - -int mastersrv_refreshing() -{ - return needs_update; -} - -NETADDR mastersrv_get(int index) -{ - return master_servers[index].addr; -} - -const char *mastersrv_name(int index) -{ - return master_servers[index].hostname; -} - -void mastersrv_dump_servers() -{ - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i, - master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], - master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); - } -} - -void mastersrv_default() -{ - int i; - mem_zero(master_servers, sizeof(master_servers)); - for(i = 0; i < MAX_MASTERSERVERS; i++) - sprintf(master_servers[i].hostname, "master%d.teeworlds.com", i+1); -} - -int mastersrv_load() -{ - LINEREADER lr; - IOHANDLE file; - int count = 0; - - /* try to open file */ - file = engine_openfile("masters.cfg", IOFLAG_READ); - if(!file) - return -1; - - linereader_init(&lr, file); - while(1) - { - MASTER_INFO info = {{0}}; - int ip[4]; - const char *line = linereader_get(&lr); - if(!line) - break; - - /* parse line */ - if(sscanf(line, "%s %d.%d.%d.%d", info.hostname, &ip[0], &ip[1], &ip[2], &ip[3]) == 5) - { - info.addr.ip[0] = (unsigned char)ip[0]; - info.addr.ip[1] = (unsigned char)ip[1]; - info.addr.ip[2] = (unsigned char)ip[2]; - info.addr.ip[3] = (unsigned char)ip[3]; - info.addr.port = 8300; - if(count != MAX_MASTERSERVERS) - { - master_servers[count] = info; - count++; - } - else - dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", line, MAX_MASTERSERVERS); - } - else - dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", line); - } - - io_close(file); - return 0; -} - -int mastersrv_save() -{ - IOHANDLE file; - int i; - - /* try to open file */ - file = engine_openfile("masters.cfg", IOFLAG_WRITE); - if(!file) - return -1; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - char buf[1024]; - str_format(buf, sizeof(buf), "%s %d.%d.%d.%d\n", master_servers[i].hostname, - master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], - master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); - - io_write(file, buf, strlen(buf)); - } - - io_close(file); - return 0; -} - - -int hostlookup_thread(void *user) -{ - HOSTLOOKUP *lookup = (HOSTLOOKUP *)user; - net_host_lookup(lookup->hostname, &lookup->addr, NETTYPE_IPV4); - return 0; -} - -void engine_hostlookup(HOSTLOOKUP *lookup, const char *hostname) -{ - str_copy(lookup->hostname, hostname, sizeof(lookup->hostname)); - jobs_add(&hostlookuppool, &lookup->job, hostlookup_thread, lookup); -} - -static int engine_find_datadir(char *argv0) -{ - /* 1) use provided data-dir override */ - if(datadir[0]) - { - if(fs_is_dir(datadir)) - return 0; - else - { - dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", datadir); - return -1; - } - } - - /* 2) use data-dir in PWD if present */ - if(fs_is_dir("data")) - { - strcpy(datadir, "data"); - return 0; - } - - /* 3) use compiled-in data-dir if present */ - if (fs_is_dir(DATA_DIR)) - { - strcpy(datadir, DATA_DIR); - return 0; - } - - /* 4) check for usable path in argv[0] */ - { - unsigned int pos = strrchr(argv0, '/') - argv0; - - if (pos < sizeof(datadir)) - { - char basedir[sizeof(datadir)]; - strncpy(basedir, argv0, pos); - basedir[pos] = '\0'; - str_format(datadir, sizeof(datadir), "%s/data", basedir); - - if (fs_is_dir(datadir)) - return 0; - } - } - -#if defined(CONF_FAMILY_UNIX) - /* 5) check for all default locations */ - { - const char *sdirs[] = { - "/usr/share/teeworlds", - "/usr/local/share/teeworlds" - "/opt/teeworlds" - }; - const int sdirs_count = sizeof(sdirs) / sizeof(sdirs[0]); - - int i; - for (i = 0; i < sdirs_count; i++) - { - if (fs_is_dir(sdirs[i])) - { - strcpy(datadir, sdirs[i]); - return 0; - } - } - } -#endif - - /* no data-dir found */ - dbg_msg("engine/datadir", "warning no data directory found"); - return -1; -} diff --git a/src/engine/e_engine.cpp b/src/engine/e_engine.cpp new file mode 100644 index 00000000..4475478a --- /dev/null +++ b/src/engine/e_engine.cpp @@ -0,0 +1,596 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include "e_linereader.h" + +/* compiled-in data-dir path */ +#define DATA_DIR "data" + +static JOBPOOL hostlookuppool; +static int engine_find_datadir(char *argv0); + +static void con_dbg_dumpmem(void *result, void *user_data) +{ + mem_debug_dump(); +} + +static void con_dbg_lognetwork(void *result, void *user_data) +{ + CNetBase::OpenLog("network_sent.dat", "network_recv.dat"); +} + + +static char application_save_path[512] = {0}; +static char datadir[512] = {0}; + +const char *engine_savepath(const char *filename, char *buffer, int max) +{ + str_format(buffer, max, "%s/%s", application_save_path, filename); + return buffer; +} + +void engine_init(const char *appname) +{ + dbg_logger_stdout(); + dbg_logger_debugger(); + + /* */ + dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); +#ifdef CONF_ARCH_ENDIAN_LITTLE + dbg_msg("engine", "arch is little endian"); +#elif defined(CONF_ARCH_ENDIAN_BIG) + dbg_msg("engine", "arch is big endian"); +#else + dbg_msg("engine", "unknown endian"); +#endif + + /* init the network */ + net_init(); + CNetBase::Init(); + + /* create storage location */ + { + char path[1024] = {0}; + fs_storage_path(appname, application_save_path, sizeof(application_save_path)); + if(fs_makedir(application_save_path) == 0) + { + str_format(path, sizeof(path), "%s/screenshots", application_save_path); + fs_makedir(path); + + str_format(path, sizeof(path), "%s/maps", application_save_path); + fs_makedir(path); + + str_format(path, sizeof(path), "%s/downloadedmaps", application_save_path); + fs_makedir(path); + + str_format(path, sizeof(path), "%s/demos", application_save_path); + fs_makedir(path); + } + } + + /* init console and add the console logger */ + console_init(); + dbg_logger(console_print); + + jobs_initpool(&hostlookuppool, 1); + + MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory"); + MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network"); + + /* reset the config */ + config_reset(); +} + + +void engine_listdir(int types, const char *path, FS_LISTDIR_CALLBACK cb, void *user) +{ + char buffer[1024]; + + /* list current directory */ + if(types&LISTDIRTYPE_CURRENT) + { + fs_listdir(path, cb, user); + } + + /* list users directory */ + if(types&LISTDIRTYPE_SAVE) + { + engine_savepath(path, buffer, sizeof(buffer)); + fs_listdir(buffer, cb, user); + } + + /* list datadir directory */ + if(types&LISTDIRTYPE_DATA) + { + str_format(buffer, sizeof(buffer), "%s/%s", datadir, path); + fs_listdir(buffer, cb, user); + } +} + +void engine_getpath(char *buffer, int buffer_size, const char *filename, int flags) +{ + if(flags&IOFLAG_WRITE) + engine_savepath(filename, buffer, buffer_size); + else + { + IOHANDLE handle = 0; + + /* check current directory */ + handle = io_open(filename, flags); + if(handle) + { + str_copy(buffer, filename, buffer_size); + io_close(handle); + return; + } + + /* check user directory */ + engine_savepath(filename, buffer, buffer_size); + handle = io_open(buffer, flags); + if(handle) + { + io_close(handle); + return; + } + + /* check normal data directory */ + str_format(buffer, buffer_size, "%s/%s", datadir, filename); + handle = io_open(buffer, flags); + if(handle) + { + io_close(handle); + return; + } + } + + buffer[0] = 0; +} + +IOHANDLE engine_openfile(const char *filename, int flags) +{ + char buffer[1024]; + + if(flags&IOFLAG_WRITE) + { + engine_savepath(filename, buffer, sizeof(buffer)); + return io_open(buffer, flags); + } + else + { + IOHANDLE handle = 0; + + /* check current directory */ + handle = io_open(filename, flags); + if(handle) + return handle; + + /* check user directory */ + engine_savepath(filename, buffer, sizeof(buffer)); + handle = io_open(buffer, flags); + if(handle) + return handle; + + /* check normal data directory */ + str_format(buffer, sizeof(buffer), "%s/%s", datadir, filename); + handle = io_open(buffer, flags); + if(handle) + return handle; + } + return 0; +} + +void engine_parse_arguments(int argc, char **argv) +{ + /* load the configuration */ + int i; + + /* check for datadir override */ + for(i = 1; i < argc; i++) + { + if(argv[i][0] == '-' && argv[i][1] == 'd' && argv[i][2] == 0 && argc - i > 1) + { + str_copy(datadir, argv[i+1], sizeof(datadir)); + i++; + } + } + + /* search for data directory */ + engine_find_datadir(argv[0]); + + dbg_msg("engine/datadir", "paths used:"); + dbg_msg("engine/datadir", "\t."); + dbg_msg("engine/datadir", "\t%s", application_save_path); + dbg_msg("engine/datadir", "\t%s", datadir); + dbg_msg("engine/datadir", "saving files to: %s", application_save_path); + + + /* check for scripts to execute */ + for(i = 1; i < argc; i++) + { + if(argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0 && argc - i > 1) + { + console_execute_file(argv[i+1]); + i++; + } + } + + /* search arguments for overrides */ + { + int i; + for(i = 1; i < argc; i++) + console_execute_line(argv[i]); + } + + console_execute_file("autoexec.cfg"); + + /* open logfile if needed */ + if(config.logfile[0]) + dbg_logger_file(config.logfile); + + /* set default servers and load from disk*/ + mastersrv_default(); + mastersrv_load(); +} + + +static IOHANDLE config_file = 0; + +int engine_config_write_start() +{ + config_save(); + config_file = engine_openfile("settings.cfg", IOFLAG_WRITE); + if(config_file == 0) + return -1; + return 0; +} + +void engine_config_write_line(const char *line) +{ + if(config_file) + { +#if defined(CONF_FAMILY_WINDOWS) + static const char newline[] = "\r\n"; +#else + static const char newline[] = "\n"; +#endif + io_write(config_file, line, strlen(line)); + io_write(config_file, newline, sizeof(newline)-1); + } +} + +void engine_config_write_stop() +{ + io_close(config_file); + config_file = 0; +} +/* +void engine_writeconfig() +{ +}*/ + +static int perf_tick = 1; +static PERFORMACE_INFO *current = 0; + +void perf_init() +{ +} + +void perf_next() +{ + perf_tick++; + current = 0; +} + +void perf_start(PERFORMACE_INFO *info) +{ + if(info->tick != perf_tick) + { + info->parent = current; + info->first_child = 0; + info->next_child = 0; + + if(info->parent) + { + info->next_child = info->parent->first_child; + info->parent->first_child = info; + } + + info->tick = perf_tick; + info->biggest = 0; + info->total = 0; + } + + current = info; + current->start = time_get(); +} + +void perf_end() +{ + if(!current) + return; + + current->last_delta = time_get()-current->start; + current->total += current->last_delta; + + if(current->last_delta > current->biggest) + current->biggest = current->last_delta; + + current = current->parent; +} + +static void perf_dump_imp(PERFORMACE_INFO *info, int indent) +{ + char buf[512] = {0}; + int64 freq = time_freq(); + int i; + + for(i = 0; i < indent; i++) + buf[i] = ' '; + + str_format(&buf[indent], sizeof(buf)-indent, "%-20s %8.2f %8.2f", info->name, info->total*1000/(float)freq, info->biggest*1000/(float)freq); + dbg_msg("perf", "%s", buf); + + info = info->first_child; + while(info) + { + perf_dump_imp(info, indent+2); + info = info->next_child; + } +} + +void perf_dump(PERFORMACE_INFO *top) +{ + perf_dump_imp(top, 0); +} + +/* master server functions */ +typedef struct +{ + char hostname[128]; + NETADDR addr; + + HOSTLOOKUP lookup; +} MASTER_INFO; + +static MASTER_INFO master_servers[MAX_MASTERSERVERS] = {{{0}}}; +static int needs_update = -1; + +int mastersrv_refresh_addresses() +{ + int i; + + if(needs_update != -1) + return 0; + + dbg_msg("engine/mastersrv", "refreshing master server addresses"); + + /* add lookup jobs */ + for(i = 0; i < MAX_MASTERSERVERS; i++) + engine_hostlookup(&master_servers[i].lookup, master_servers[i].hostname); + + needs_update = 1; + return 0; +} + +void mastersrv_update() +{ + int i; + + /* check if we need to update */ + if(needs_update != 1) + return; + needs_update = 0; + + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + if(jobs_status(&master_servers[i].lookup.job) != JOBSTATUS_DONE) + needs_update = 1; + else + { + master_servers[i].addr = master_servers[i].lookup.addr; + master_servers[i].addr.port = 8300; + } + } + + if(!needs_update) + { + dbg_msg("engine/mastersrv", "saving addresses"); + mastersrv_save(); + } +} + +int mastersrv_refreshing() +{ + return needs_update; +} + +NETADDR mastersrv_get(int index) +{ + return master_servers[index].addr; +} + +const char *mastersrv_name(int index) +{ + return master_servers[index].hostname; +} + +void mastersrv_dump_servers() +{ + int i; + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i, + master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], + master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); + } +} + +void mastersrv_default() +{ + int i; + mem_zero(master_servers, sizeof(master_servers)); + for(i = 0; i < MAX_MASTERSERVERS; i++) + sprintf(master_servers[i].hostname, "master%d.teeworlds.com", i+1); +} + +int mastersrv_load() +{ + LINEREADER lr; + IOHANDLE file; + int count = 0; + + /* try to open file */ + file = engine_openfile("masters.cfg", IOFLAG_READ); + if(!file) + return -1; + + linereader_init(&lr, file); + while(1) + { + MASTER_INFO info = {{0}}; + int ip[4]; + const char *line = linereader_get(&lr); + if(!line) + break; + + /* parse line */ + if(sscanf(line, "%s %d.%d.%d.%d", info.hostname, &ip[0], &ip[1], &ip[2], &ip[3]) == 5) + { + info.addr.ip[0] = (unsigned char)ip[0]; + info.addr.ip[1] = (unsigned char)ip[1]; + info.addr.ip[2] = (unsigned char)ip[2]; + info.addr.ip[3] = (unsigned char)ip[3]; + info.addr.port = 8300; + if(count != MAX_MASTERSERVERS) + { + master_servers[count] = info; + count++; + } + else + dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", line, MAX_MASTERSERVERS); + } + else + dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", line); + } + + io_close(file); + return 0; +} + +int mastersrv_save() +{ + IOHANDLE file; + int i; + + /* try to open file */ + file = engine_openfile("masters.cfg", IOFLAG_WRITE); + if(!file) + return -1; + + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + char buf[1024]; + str_format(buf, sizeof(buf), "%s %d.%d.%d.%d\n", master_servers[i].hostname, + master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], + master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); + + io_write(file, buf, strlen(buf)); + } + + io_close(file); + return 0; +} + + +int hostlookup_thread(void *user) +{ + HOSTLOOKUP *lookup = (HOSTLOOKUP *)user; + net_host_lookup(lookup->hostname, &lookup->addr, NETTYPE_IPV4); + return 0; +} + +void engine_hostlookup(HOSTLOOKUP *lookup, const char *hostname) +{ + str_copy(lookup->hostname, hostname, sizeof(lookup->hostname)); + jobs_add(&hostlookuppool, &lookup->job, hostlookup_thread, lookup); +} + +static int engine_find_datadir(char *argv0) +{ + /* 1) use provided data-dir override */ + if(datadir[0]) + { + if(fs_is_dir(datadir)) + return 0; + else + { + dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", datadir); + return -1; + } + } + + /* 2) use data-dir in PWD if present */ + if(fs_is_dir("data")) + { + strcpy(datadir, "data"); + return 0; + } + + /* 3) use compiled-in data-dir if present */ + if (fs_is_dir(DATA_DIR)) + { + strcpy(datadir, DATA_DIR); + return 0; + } + + /* 4) check for usable path in argv[0] */ + { + unsigned int pos = strrchr(argv0, '/') - argv0; + + if (pos < sizeof(datadir)) + { + char basedir[sizeof(datadir)]; + strncpy(basedir, argv0, pos); + basedir[pos] = '\0'; + str_format(datadir, sizeof(datadir), "%s/data", basedir); + + if (fs_is_dir(datadir)) + return 0; + } + } + +#if defined(CONF_FAMILY_UNIX) + /* 5) check for all default locations */ + { + const char *sdirs[] = { + "/usr/share/teeworlds", + "/usr/local/share/teeworlds" + "/opt/teeworlds" + }; + const int sdirs_count = sizeof(sdirs) / sizeof(sdirs[0]); + + int i; + for (i = 0; i < sdirs_count; i++) + { + if (fs_is_dir(sdirs[i])) + { + strcpy(datadir, sdirs[i]); + return 0; + } + } + } +#endif + + /* no data-dir found */ + dbg_msg("engine/datadir", "warning no data directory found"); + return -1; +} diff --git a/src/engine/e_huffman.c b/src/engine/e_huffman.c deleted file mode 100644 index 43914010..00000000 --- a/src/engine/e_huffman.c +++ /dev/null @@ -1,276 +0,0 @@ -#include /* memset */ -#include "e_huffman.h" - -typedef struct HUFFMAN_CONSTRUCT_NODE -{ - unsigned short node_id; - int frequency; -} HUFFMAN_CONSTRUCT_NODE; - -static void huffman_setbits_r(HUFFMAN_STATE *huff, HUFFMAN_NODE *node, int bits, int depth) -{ - if(node->leafs[1] != 0xffff) - huffman_setbits_r(huff, &huff->nodes[node->leafs[1]], bits|(1<leafs[0] != 0xffff) - huffman_setbits_r(huff, &huff->nodes[node->leafs[0]], bits, depth+1); - - if(node->num_bits) - { - node->bits = bits; - node->num_bits = depth; - } -} - -/* TODO: this should be something faster, but it's enough for now */ -void bubblesort(HUFFMAN_CONSTRUCT_NODE **list, int size) -{ - int i, changed = 1; - HUFFMAN_CONSTRUCT_NODE *temp; - - while(changed) - { - changed = 0; - for(i = 0; i < size-1; i++) - { - if(list[i]->frequency < list[i+1]->frequency) - { - temp = list[i]; - list[i] = list[i+1]; - list[i+1] = temp; - changed = 1; - } - } - } -} - -static void huffman_construct_tree(HUFFMAN_STATE *huff, const unsigned *frequencies) -{ - HUFFMAN_CONSTRUCT_NODE nodes_left_storage[HUFFMAN_MAX_SYMBOLS]; - HUFFMAN_CONSTRUCT_NODE *nodes_left[HUFFMAN_MAX_SYMBOLS]; - int num_nodes_left = HUFFMAN_MAX_SYMBOLS; - int i; - - /* add the symbols */ - for(i = 0; i < HUFFMAN_MAX_SYMBOLS; i++) - { - huff->nodes[i].num_bits = -1; - huff->nodes[i].symbol = i; - huff->nodes[i].leafs[0] = -1; - huff->nodes[i].leafs[1] = -1; - - if(i == HUFFMAN_EOF_SYMBOL) - nodes_left_storage[i].frequency = 1; - else - nodes_left_storage[i].frequency = frequencies[i]; - nodes_left_storage[i].node_id = i; - nodes_left[i] = &nodes_left_storage[i]; - - } - huff->num_nodes = HUFFMAN_MAX_SYMBOLS; - - /* construct the table */ - while(num_nodes_left > 1) - { - /* we can't rely on stdlib's qsort for this, it can generate different results on different implementations */ - bubblesort(nodes_left, num_nodes_left); - - huff->nodes[huff->num_nodes].num_bits = 0; - huff->nodes[huff->num_nodes].leafs[0] = nodes_left[num_nodes_left-1]->node_id; - huff->nodes[huff->num_nodes].leafs[1] = nodes_left[num_nodes_left-2]->node_id; - nodes_left[num_nodes_left-2]->node_id = huff->num_nodes; - nodes_left[num_nodes_left-2]->frequency = nodes_left[num_nodes_left-1]->frequency + nodes_left[num_nodes_left-2]->frequency; - - huff->num_nodes++; - num_nodes_left--; - } - - /* set start node */ - huff->start_node = &huff->nodes[huff->num_nodes-1]; - - /* build symbol bits */ - huffman_setbits_r(huff, huff->start_node, 0, 0); -} - -void huffman_init(HUFFMAN_STATE *huff, const unsigned *frequencies) -{ - int i; - - /* make sure to cleanout every thing */ - memset(huff, 0, sizeof(HUFFMAN_STATE)); - - /* construct the tree */ - huffman_construct_tree(huff, frequencies); - - /* build decode LUT */ - for(i = 0; i < HUFFMAN_LUTSIZE; i++) - { - unsigned bits = i; - int k; - HUFFMAN_NODE *node = huff->start_node; - for(k = 0; k < HUFFMAN_LUTBITS; k++) - { - node = &huff->nodes[node->leafs[bits&1]]; - bits >>= 1; - - if(!node) - break; - - if(node->num_bits) - { - huff->decode_lut[i] = node; - break; - } - } - - if(k == HUFFMAN_LUTBITS) - huff->decode_lut[i] = node; - } - -} - -/*****************************************************************/ -int huffman_compress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) -{ - /* this macro loads a symbol for a byte into bits and bitcount */ -#define HUFFMAN_MACRO_LOADSYMBOL(sym) \ - bits |= huff->nodes[sym].bits << bitcount; \ - bitcount += huff->nodes[sym].num_bits; - - /* this macro writes the symbol stored in bits and bitcount to the dst pointer */ -#define HUFFMAN_MACRO_WRITE() \ - while(bitcount >= 8) \ - { \ - *dst++ = (unsigned char)(bits&0xff); \ - if(dst == dst_end) \ - return -1; \ - bits >>= 8; \ - bitcount -= 8; \ - } - - /* setup buffer pointers */ - const unsigned char *src = (const unsigned char *)input; - const unsigned char *src_end = src + input_size; - unsigned char *dst = (unsigned char *)output; - unsigned char *dst_end = dst + output_size; - - /* symbol variables */ - unsigned bits = 0; - unsigned bitcount = 0; - - /* make sure that we have data that we want to compress */ - if(input_size) - { - /* {A} load the first symbol */ - int symbol = *src++; - - while(src != src_end) - { - /* {B} load the symbol */ - HUFFMAN_MACRO_LOADSYMBOL(symbol) - - /* {C} fetch next symbol, this is done here because it will reduce dependency in the code */ - symbol = *src++; - - /* {B} write the symbol loaded at */ - HUFFMAN_MACRO_WRITE() - } - - /* write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer */ - HUFFMAN_MACRO_LOADSYMBOL(symbol) - HUFFMAN_MACRO_WRITE() - } - - /* write EOF symbol */ - HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL) - HUFFMAN_MACRO_WRITE() - - /* write out the last bits */ - *dst++ = bits; - - /* return the size of the output */ - return (int)(dst - (const unsigned char *)output); - - /* remove macros */ -#undef HUFFMAN_MACRO_LOADSYMBOL -#undef HUFFMAN_MACRO_WRITE -} - -/*****************************************************************/ -int huffman_decompress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) -{ - /* setup buffer pointers */ - unsigned char *dst = (unsigned char *)output; - unsigned char *src = (unsigned char *)input; - unsigned char *dst_end = dst + output_size; - unsigned char *src_end = src + input_size; - - unsigned bits = 0; - unsigned bitcount = 0; - - HUFFMAN_NODE *eof = &huff->nodes[HUFFMAN_EOF_SYMBOL]; - HUFFMAN_NODE *node = 0; - - while(1) - { - /* {A} try to load a node now, this will reduce dependency at location {D} */ - node = 0; - if(bitcount >= HUFFMAN_LUTBITS) - node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; - - /* {B} fill with new bits */ - while(bitcount < 24 && src != src_end) - { - bits |= (*src++) << bitcount; - bitcount += 8; - } - - /* {C} load symbol now if we didn't that earlier at location {A} */ - if(!node) - node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; - - /* {D} check if we hit a symbol already */ - if(node->num_bits) - { - /* remove the bits for that symbol */ - bits >>= node->num_bits; - bitcount -= node->num_bits; - } - else - { - /* remove the bits that the lut checked up for us */ - bits >>= HUFFMAN_LUTBITS; - bitcount -= HUFFMAN_LUTBITS; - - /* walk the tree bit by bit */ - while(1) - { - /* traverse tree */ - node = &huff->nodes[node->leafs[bits&1]]; - - /* remove bit */ - bitcount--; - bits >>= 1; - - /* check if we hit a symbol */ - if(node->num_bits) - break; - - /* no more bits, decoding error */ - if(bitcount == 0) - return -1; - } - } - - /* check for eof */ - if(node == eof) - break; - - /* output character */ - if(dst == dst_end) - return -1; - *dst++ = node->symbol; - } - - /* return the size of the decompressed buffer */ - return (int)(dst - (const unsigned char *)output); -} diff --git a/src/engine/e_huffman.cpp b/src/engine/e_huffman.cpp new file mode 100644 index 00000000..43914010 --- /dev/null +++ b/src/engine/e_huffman.cpp @@ -0,0 +1,276 @@ +#include /* memset */ +#include "e_huffman.h" + +typedef struct HUFFMAN_CONSTRUCT_NODE +{ + unsigned short node_id; + int frequency; +} HUFFMAN_CONSTRUCT_NODE; + +static void huffman_setbits_r(HUFFMAN_STATE *huff, HUFFMAN_NODE *node, int bits, int depth) +{ + if(node->leafs[1] != 0xffff) + huffman_setbits_r(huff, &huff->nodes[node->leafs[1]], bits|(1<leafs[0] != 0xffff) + huffman_setbits_r(huff, &huff->nodes[node->leafs[0]], bits, depth+1); + + if(node->num_bits) + { + node->bits = bits; + node->num_bits = depth; + } +} + +/* TODO: this should be something faster, but it's enough for now */ +void bubblesort(HUFFMAN_CONSTRUCT_NODE **list, int size) +{ + int i, changed = 1; + HUFFMAN_CONSTRUCT_NODE *temp; + + while(changed) + { + changed = 0; + for(i = 0; i < size-1; i++) + { + if(list[i]->frequency < list[i+1]->frequency) + { + temp = list[i]; + list[i] = list[i+1]; + list[i+1] = temp; + changed = 1; + } + } + } +} + +static void huffman_construct_tree(HUFFMAN_STATE *huff, const unsigned *frequencies) +{ + HUFFMAN_CONSTRUCT_NODE nodes_left_storage[HUFFMAN_MAX_SYMBOLS]; + HUFFMAN_CONSTRUCT_NODE *nodes_left[HUFFMAN_MAX_SYMBOLS]; + int num_nodes_left = HUFFMAN_MAX_SYMBOLS; + int i; + + /* add the symbols */ + for(i = 0; i < HUFFMAN_MAX_SYMBOLS; i++) + { + huff->nodes[i].num_bits = -1; + huff->nodes[i].symbol = i; + huff->nodes[i].leafs[0] = -1; + huff->nodes[i].leafs[1] = -1; + + if(i == HUFFMAN_EOF_SYMBOL) + nodes_left_storage[i].frequency = 1; + else + nodes_left_storage[i].frequency = frequencies[i]; + nodes_left_storage[i].node_id = i; + nodes_left[i] = &nodes_left_storage[i]; + + } + huff->num_nodes = HUFFMAN_MAX_SYMBOLS; + + /* construct the table */ + while(num_nodes_left > 1) + { + /* we can't rely on stdlib's qsort for this, it can generate different results on different implementations */ + bubblesort(nodes_left, num_nodes_left); + + huff->nodes[huff->num_nodes].num_bits = 0; + huff->nodes[huff->num_nodes].leafs[0] = nodes_left[num_nodes_left-1]->node_id; + huff->nodes[huff->num_nodes].leafs[1] = nodes_left[num_nodes_left-2]->node_id; + nodes_left[num_nodes_left-2]->node_id = huff->num_nodes; + nodes_left[num_nodes_left-2]->frequency = nodes_left[num_nodes_left-1]->frequency + nodes_left[num_nodes_left-2]->frequency; + + huff->num_nodes++; + num_nodes_left--; + } + + /* set start node */ + huff->start_node = &huff->nodes[huff->num_nodes-1]; + + /* build symbol bits */ + huffman_setbits_r(huff, huff->start_node, 0, 0); +} + +void huffman_init(HUFFMAN_STATE *huff, const unsigned *frequencies) +{ + int i; + + /* make sure to cleanout every thing */ + memset(huff, 0, sizeof(HUFFMAN_STATE)); + + /* construct the tree */ + huffman_construct_tree(huff, frequencies); + + /* build decode LUT */ + for(i = 0; i < HUFFMAN_LUTSIZE; i++) + { + unsigned bits = i; + int k; + HUFFMAN_NODE *node = huff->start_node; + for(k = 0; k < HUFFMAN_LUTBITS; k++) + { + node = &huff->nodes[node->leafs[bits&1]]; + bits >>= 1; + + if(!node) + break; + + if(node->num_bits) + { + huff->decode_lut[i] = node; + break; + } + } + + if(k == HUFFMAN_LUTBITS) + huff->decode_lut[i] = node; + } + +} + +/*****************************************************************/ +int huffman_compress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) +{ + /* this macro loads a symbol for a byte into bits and bitcount */ +#define HUFFMAN_MACRO_LOADSYMBOL(sym) \ + bits |= huff->nodes[sym].bits << bitcount; \ + bitcount += huff->nodes[sym].num_bits; + + /* this macro writes the symbol stored in bits and bitcount to the dst pointer */ +#define HUFFMAN_MACRO_WRITE() \ + while(bitcount >= 8) \ + { \ + *dst++ = (unsigned char)(bits&0xff); \ + if(dst == dst_end) \ + return -1; \ + bits >>= 8; \ + bitcount -= 8; \ + } + + /* setup buffer pointers */ + const unsigned char *src = (const unsigned char *)input; + const unsigned char *src_end = src + input_size; + unsigned char *dst = (unsigned char *)output; + unsigned char *dst_end = dst + output_size; + + /* symbol variables */ + unsigned bits = 0; + unsigned bitcount = 0; + + /* make sure that we have data that we want to compress */ + if(input_size) + { + /* {A} load the first symbol */ + int symbol = *src++; + + while(src != src_end) + { + /* {B} load the symbol */ + HUFFMAN_MACRO_LOADSYMBOL(symbol) + + /* {C} fetch next symbol, this is done here because it will reduce dependency in the code */ + symbol = *src++; + + /* {B} write the symbol loaded at */ + HUFFMAN_MACRO_WRITE() + } + + /* write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer */ + HUFFMAN_MACRO_LOADSYMBOL(symbol) + HUFFMAN_MACRO_WRITE() + } + + /* write EOF symbol */ + HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL) + HUFFMAN_MACRO_WRITE() + + /* write out the last bits */ + *dst++ = bits; + + /* return the size of the output */ + return (int)(dst - (const unsigned char *)output); + + /* remove macros */ +#undef HUFFMAN_MACRO_LOADSYMBOL +#undef HUFFMAN_MACRO_WRITE +} + +/*****************************************************************/ +int huffman_decompress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) +{ + /* setup buffer pointers */ + unsigned char *dst = (unsigned char *)output; + unsigned char *src = (unsigned char *)input; + unsigned char *dst_end = dst + output_size; + unsigned char *src_end = src + input_size; + + unsigned bits = 0; + unsigned bitcount = 0; + + HUFFMAN_NODE *eof = &huff->nodes[HUFFMAN_EOF_SYMBOL]; + HUFFMAN_NODE *node = 0; + + while(1) + { + /* {A} try to load a node now, this will reduce dependency at location {D} */ + node = 0; + if(bitcount >= HUFFMAN_LUTBITS) + node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; + + /* {B} fill with new bits */ + while(bitcount < 24 && src != src_end) + { + bits |= (*src++) << bitcount; + bitcount += 8; + } + + /* {C} load symbol now if we didn't that earlier at location {A} */ + if(!node) + node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; + + /* {D} check if we hit a symbol already */ + if(node->num_bits) + { + /* remove the bits for that symbol */ + bits >>= node->num_bits; + bitcount -= node->num_bits; + } + else + { + /* remove the bits that the lut checked up for us */ + bits >>= HUFFMAN_LUTBITS; + bitcount -= HUFFMAN_LUTBITS; + + /* walk the tree bit by bit */ + while(1) + { + /* traverse tree */ + node = &huff->nodes[node->leafs[bits&1]]; + + /* remove bit */ + bitcount--; + bits >>= 1; + + /* check if we hit a symbol */ + if(node->num_bits) + break; + + /* no more bits, decoding error */ + if(bitcount == 0) + return -1; + } + } + + /* check for eof */ + if(node == eof) + break; + + /* output character */ + if(dst == dst_end) + return -1; + *dst++ = node->symbol; + } + + /* return the size of the decompressed buffer */ + return (int)(dst - (const unsigned char *)output); +} diff --git a/src/engine/e_huffman.h b/src/engine/e_huffman.h index c4e20223..635c74a1 100644 --- a/src/engine/e_huffman.h +++ b/src/engine/e_huffman.h @@ -1,10 +1,6 @@ #ifndef __HUFFMAN_HEADER__ #define __HUFFMAN_HEADER__ -#ifdef __cplusplus -extern "C" { -#endif - enum { HUFFMAN_EOF_SYMBOL = 256, @@ -84,8 +80,4 @@ int huffman_compress(HUFFMAN_STATE *huff, const void *input, int input_size, voi */ int huffman_decompress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size); -#ifdef __cplusplus -} -#endif - #endif /* __HUFFMAN_HEADER__ */ diff --git a/src/engine/e_if_other.h b/src/engine/e_if_other.h index 5c839750..3fbbd81e 100644 --- a/src/engine/e_if_other.h +++ b/src/engine/e_if_other.h @@ -69,11 +69,12 @@ void perf_init(); void perf_next(); void perf_start(PERFORMACE_INFO *info); void perf_end(); -void perf_dump(); +void perf_dump(PERFORMACE_INFO *top); int gfx_init(); void gfx_shutdown(); void gfx_swap(); + int gfx_window_active(); int gfx_window_open(); diff --git a/src/engine/e_jobs.c b/src/engine/e_jobs.c deleted file mode 100644 index e87b395a..00000000 --- a/src/engine/e_jobs.c +++ /dev/null @@ -1,76 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "e_jobs.h" - -void worker_thread(void *user) -{ - JOBPOOL *pool = (JOBPOOL *)user; - - while(1) - { - JOB *job = 0; - - /* fetch job from queue */ - lock_wait(pool->lock); - if(pool->first_job) - { - job = pool->first_job; - pool->first_job = pool->first_job->next; - if(pool->first_job) - pool->first_job->prev = 0; - else - pool->last_job = 0; - } - lock_release(pool->lock); - - /* do the job if we have one */ - if(job) - { - job->status = JOBSTATUS_RUNNING; - job->result = job->func(job->func_data); - job->status = JOBSTATUS_DONE; - } - else - thread_sleep(10); - } - -} - -int jobs_initpool(JOBPOOL *pool, int num_threads) -{ - int i; - - /* empty the pool */ - mem_zero(pool, sizeof(JOBPOOL)); - pool->lock = lock_create(); - - /* start threads */ - for(i = 0; i < num_threads; i++) - thread_create(worker_thread, pool); - return 0; -} - -int jobs_add(JOBPOOL *pool, JOB *job, JOBFUNC func, void *data) -{ - mem_zero(job, sizeof(JOB)); - job->func = func; - job->func_data = data; - - lock_wait(pool->lock); - - /* add job to queue */ - job->prev = pool->last_job; - if(pool->last_job) - pool->last_job->next = job; - pool->last_job = job; - if(!pool->first_job) - pool->first_job = job; - - lock_release(pool->lock); - return 0; -} - -int jobs_status(JOB *job) -{ - return job->status; -} diff --git a/src/engine/e_jobs.cpp b/src/engine/e_jobs.cpp new file mode 100644 index 00000000..e87b395a --- /dev/null +++ b/src/engine/e_jobs.cpp @@ -0,0 +1,76 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "e_jobs.h" + +void worker_thread(void *user) +{ + JOBPOOL *pool = (JOBPOOL *)user; + + while(1) + { + JOB *job = 0; + + /* fetch job from queue */ + lock_wait(pool->lock); + if(pool->first_job) + { + job = pool->first_job; + pool->first_job = pool->first_job->next; + if(pool->first_job) + pool->first_job->prev = 0; + else + pool->last_job = 0; + } + lock_release(pool->lock); + + /* do the job if we have one */ + if(job) + { + job->status = JOBSTATUS_RUNNING; + job->result = job->func(job->func_data); + job->status = JOBSTATUS_DONE; + } + else + thread_sleep(10); + } + +} + +int jobs_initpool(JOBPOOL *pool, int num_threads) +{ + int i; + + /* empty the pool */ + mem_zero(pool, sizeof(JOBPOOL)); + pool->lock = lock_create(); + + /* start threads */ + for(i = 0; i < num_threads; i++) + thread_create(worker_thread, pool); + return 0; +} + +int jobs_add(JOBPOOL *pool, JOB *job, JOBFUNC func, void *data) +{ + mem_zero(job, sizeof(JOB)); + job->func = func; + job->func_data = data; + + lock_wait(pool->lock); + + /* add job to queue */ + job->prev = pool->last_job; + if(pool->last_job) + pool->last_job->next = job; + pool->last_job = job; + if(!pool->first_job) + pool->first_job = job; + + lock_release(pool->lock); + return 0; +} + +int jobs_status(JOB *job) +{ + return job->status; +} diff --git a/src/engine/e_keynames.c b/src/engine/e_keynames.c deleted file mode 100644 index c81744b9..00000000 --- a/src/engine/e_keynames.c +++ /dev/null @@ -1,524 +0,0 @@ -/* AUTO GENERATED! DO NOT EDIT MANUALLY! */ - -#include - -static const char key_strings[512][16] = -{ - - "first", - "&1", - "&2", - "&3", - "&4", - "&5", - "&6", - "&7", - "backspace", - "tab", - "&10", - "&11", - "clear", - "return", - "&14", - "&15", - "&16", - "&17", - "&18", - "pause", - "&20", - "&21", - "&22", - "&23", - "&24", - "&25", - "&26", - "escape", - "&28", - "&29", - "&30", - "&31", - "space", - "exclaim", - "quotedbl", - "hash", - "dollar", - "&37", - "ampersand", - "quote", - "leftparen", - "rightparen", - "asterisk", - "plus", - "comma", - "minus", - "period", - "slash", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "colon", - "semicolon", - "less", - "equals", - "greater", - "question", - "at", - "&65", - "&66", - "&67", - "&68", - "&69", - "&70", - "&71", - "&72", - "&73", - "&74", - "&75", - "&76", - "&77", - "&78", - "&79", - "&80", - "&81", - "&82", - "&83", - "&84", - "&85", - "&86", - "&87", - "&88", - "&89", - "&90", - "leftbracket", - "backslash", - "rightbracket", - "caret", - "underscore", - "backquote", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "&123", - "&124", - "&125", - "&126", - "delete", - "&128", - "&129", - "&130", - "&131", - "&132", - "&133", - "&134", - "&135", - "&136", - "&137", - "&138", - "&139", - "&140", - "&141", - "&142", - "&143", - "&144", - "&145", - "&146", - "&147", - "&148", - "&149", - "&150", - "&151", - "&152", - "&153", - "&154", - "&155", - "&156", - "&157", - "&158", - "&159", - "world_0", - "world_1", - "world_2", - "world_3", - "world_4", - "world_5", - "world_6", - "world_7", - "world_8", - "world_9", - "world_10", - "world_11", - "world_12", - "world_13", - "world_14", - "world_15", - "world_16", - "world_17", - "world_18", - "world_19", - "world_20", - "world_21", - "world_22", - "world_23", - "world_24", - "world_25", - "world_26", - "world_27", - "world_28", - "world_29", - "world_30", - "world_31", - "world_32", - "world_33", - "world_34", - "world_35", - "world_36", - "world_37", - "world_38", - "world_39", - "world_40", - "world_41", - "world_42", - "world_43", - "world_44", - "world_45", - "world_46", - "world_47", - "world_48", - "world_49", - "world_50", - "world_51", - "world_52", - "world_53", - "world_54", - "world_55", - "world_56", - "world_57", - "world_58", - "world_59", - "world_60", - "world_61", - "world_62", - "world_63", - "world_64", - "world_65", - "world_66", - "world_67", - "world_68", - "world_69", - "world_70", - "world_71", - "world_72", - "world_73", - "world_74", - "world_75", - "world_76", - "world_77", - "world_78", - "world_79", - "world_80", - "world_81", - "world_82", - "world_83", - "world_84", - "world_85", - "world_86", - "world_87", - "world_88", - "world_89", - "world_90", - "world_91", - "world_92", - "world_93", - "world_94", - "world_95", - "kp0", - "kp1", - "kp2", - "kp3", - "kp4", - "kp5", - "kp6", - "kp7", - "kp8", - "kp9", - "kp_period", - "kp_divide", - "kp_multiply", - "kp_minus", - "kp_plus", - "kp_enter", - "kp_equals", - "up", - "down", - "right", - "left", - "insert", - "home", - "end", - "pageup", - "pagedown", - "f1", - "f2", - "f3", - "f4", - "f5", - "f6", - "f7", - "f8", - "f9", - "f10", - "f11", - "f12", - "f13", - "f14", - "f15", - "&297", - "&298", - "&299", - "numlock", - "capslock", - "scrollock", - "rshift", - "lshift", - "rctrl", - "lctrl", - "ralt", - "lalt", - "rmeta", - "lmeta", - "lsuper", - "rsuper", - "mode", - "compose", - "help", - "print", - "sysreq", - "break", - "menu", - "power", - "euro", - "undo", - "mouse1", - "mouse2", - "mouse3", - "mouse4", - "mouse5", - "mouse6", - "mouse7", - "mouse8", - "mousewheelup", - "mousewheeldown", - "&333", - "&334", - "&335", - "&336", - "&337", - "&338", - "&339", - "&340", - "&341", - "&342", - "&343", - "&344", - "&345", - "&346", - "&347", - "&348", - "&349", - "&350", - "&351", - "&352", - "&353", - "&354", - "&355", - "&356", - "&357", - "&358", - "&359", - "&360", - "&361", - "&362", - "&363", - "&364", - "&365", - "&366", - "&367", - "&368", - "&369", - "&370", - "&371", - "&372", - "&373", - "&374", - "&375", - "&376", - "&377", - "&378", - "&379", - "&380", - "&381", - "&382", - "&383", - "&384", - "&385", - "&386", - "&387", - "&388", - "&389", - "&390", - "&391", - "&392", - "&393", - "&394", - "&395", - "&396", - "&397", - "&398", - "&399", - "&400", - "&401", - "&402", - "&403", - "&404", - "&405", - "&406", - "&407", - "&408", - "&409", - "&410", - "&411", - "&412", - "&413", - "&414", - "&415", - "&416", - "&417", - "&418", - "&419", - "&420", - "&421", - "&422", - "&423", - "&424", - "&425", - "&426", - "&427", - "&428", - "&429", - "&430", - "&431", - "&432", - "&433", - "&434", - "&435", - "&436", - "&437", - "&438", - "&439", - "&440", - "&441", - "&442", - "&443", - "&444", - "&445", - "&446", - "&447", - "&448", - "&449", - "&450", - "&451", - "&452", - "&453", - "&454", - "&455", - "&456", - "&457", - "&458", - "&459", - "&460", - "&461", - "&462", - "&463", - "&464", - "&465", - "&466", - "&467", - "&468", - "&469", - "&470", - "&471", - "&472", - "&473", - "&474", - "&475", - "&476", - "&477", - "&478", - "&479", - "&480", - "&481", - "&482", - "&483", - "&484", - "&485", - "&486", - "&487", - "&488", - "&489", - "&490", - "&491", - "&492", - "&493", - "&494", - "&495", - "&496", - "&497", - "&498", - "&499", - "&500", - "&501", - "&502", - "&503", - "&504", - "&505", - "&506", - "&507", - "&508", - "&509", - "&510", - "&511", -}; - -const char *inp_key_name(int k) { if (k >= 0 && k < 512) return key_strings[k]; else return key_strings[0]; } -int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, "-?-")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; } - diff --git a/src/engine/e_keynames.cpp b/src/engine/e_keynames.cpp new file mode 100644 index 00000000..c81744b9 --- /dev/null +++ b/src/engine/e_keynames.cpp @@ -0,0 +1,524 @@ +/* AUTO GENERATED! DO NOT EDIT MANUALLY! */ + +#include + +static const char key_strings[512][16] = +{ + + "first", + "&1", + "&2", + "&3", + "&4", + "&5", + "&6", + "&7", + "backspace", + "tab", + "&10", + "&11", + "clear", + "return", + "&14", + "&15", + "&16", + "&17", + "&18", + "pause", + "&20", + "&21", + "&22", + "&23", + "&24", + "&25", + "&26", + "escape", + "&28", + "&29", + "&30", + "&31", + "space", + "exclaim", + "quotedbl", + "hash", + "dollar", + "&37", + "ampersand", + "quote", + "leftparen", + "rightparen", + "asterisk", + "plus", + "comma", + "minus", + "period", + "slash", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "colon", + "semicolon", + "less", + "equals", + "greater", + "question", + "at", + "&65", + "&66", + "&67", + "&68", + "&69", + "&70", + "&71", + "&72", + "&73", + "&74", + "&75", + "&76", + "&77", + "&78", + "&79", + "&80", + "&81", + "&82", + "&83", + "&84", + "&85", + "&86", + "&87", + "&88", + "&89", + "&90", + "leftbracket", + "backslash", + "rightbracket", + "caret", + "underscore", + "backquote", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "&123", + "&124", + "&125", + "&126", + "delete", + "&128", + "&129", + "&130", + "&131", + "&132", + "&133", + "&134", + "&135", + "&136", + "&137", + "&138", + "&139", + "&140", + "&141", + "&142", + "&143", + "&144", + "&145", + "&146", + "&147", + "&148", + "&149", + "&150", + "&151", + "&152", + "&153", + "&154", + "&155", + "&156", + "&157", + "&158", + "&159", + "world_0", + "world_1", + "world_2", + "world_3", + "world_4", + "world_5", + "world_6", + "world_7", + "world_8", + "world_9", + "world_10", + "world_11", + "world_12", + "world_13", + "world_14", + "world_15", + "world_16", + "world_17", + "world_18", + "world_19", + "world_20", + "world_21", + "world_22", + "world_23", + "world_24", + "world_25", + "world_26", + "world_27", + "world_28", + "world_29", + "world_30", + "world_31", + "world_32", + "world_33", + "world_34", + "world_35", + "world_36", + "world_37", + "world_38", + "world_39", + "world_40", + "world_41", + "world_42", + "world_43", + "world_44", + "world_45", + "world_46", + "world_47", + "world_48", + "world_49", + "world_50", + "world_51", + "world_52", + "world_53", + "world_54", + "world_55", + "world_56", + "world_57", + "world_58", + "world_59", + "world_60", + "world_61", + "world_62", + "world_63", + "world_64", + "world_65", + "world_66", + "world_67", + "world_68", + "world_69", + "world_70", + "world_71", + "world_72", + "world_73", + "world_74", + "world_75", + "world_76", + "world_77", + "world_78", + "world_79", + "world_80", + "world_81", + "world_82", + "world_83", + "world_84", + "world_85", + "world_86", + "world_87", + "world_88", + "world_89", + "world_90", + "world_91", + "world_92", + "world_93", + "world_94", + "world_95", + "kp0", + "kp1", + "kp2", + "kp3", + "kp4", + "kp5", + "kp6", + "kp7", + "kp8", + "kp9", + "kp_period", + "kp_divide", + "kp_multiply", + "kp_minus", + "kp_plus", + "kp_enter", + "kp_equals", + "up", + "down", + "right", + "left", + "insert", + "home", + "end", + "pageup", + "pagedown", + "f1", + "f2", + "f3", + "f4", + "f5", + "f6", + "f7", + "f8", + "f9", + "f10", + "f11", + "f12", + "f13", + "f14", + "f15", + "&297", + "&298", + "&299", + "numlock", + "capslock", + "scrollock", + "rshift", + "lshift", + "rctrl", + "lctrl", + "ralt", + "lalt", + "rmeta", + "lmeta", + "lsuper", + "rsuper", + "mode", + "compose", + "help", + "print", + "sysreq", + "break", + "menu", + "power", + "euro", + "undo", + "mouse1", + "mouse2", + "mouse3", + "mouse4", + "mouse5", + "mouse6", + "mouse7", + "mouse8", + "mousewheelup", + "mousewheeldown", + "&333", + "&334", + "&335", + "&336", + "&337", + "&338", + "&339", + "&340", + "&341", + "&342", + "&343", + "&344", + "&345", + "&346", + "&347", + "&348", + "&349", + "&350", + "&351", + "&352", + "&353", + "&354", + "&355", + "&356", + "&357", + "&358", + "&359", + "&360", + "&361", + "&362", + "&363", + "&364", + "&365", + "&366", + "&367", + "&368", + "&369", + "&370", + "&371", + "&372", + "&373", + "&374", + "&375", + "&376", + "&377", + "&378", + "&379", + "&380", + "&381", + "&382", + "&383", + "&384", + "&385", + "&386", + "&387", + "&388", + "&389", + "&390", + "&391", + "&392", + "&393", + "&394", + "&395", + "&396", + "&397", + "&398", + "&399", + "&400", + "&401", + "&402", + "&403", + "&404", + "&405", + "&406", + "&407", + "&408", + "&409", + "&410", + "&411", + "&412", + "&413", + "&414", + "&415", + "&416", + "&417", + "&418", + "&419", + "&420", + "&421", + "&422", + "&423", + "&424", + "&425", + "&426", + "&427", + "&428", + "&429", + "&430", + "&431", + "&432", + "&433", + "&434", + "&435", + "&436", + "&437", + "&438", + "&439", + "&440", + "&441", + "&442", + "&443", + "&444", + "&445", + "&446", + "&447", + "&448", + "&449", + "&450", + "&451", + "&452", + "&453", + "&454", + "&455", + "&456", + "&457", + "&458", + "&459", + "&460", + "&461", + "&462", + "&463", + "&464", + "&465", + "&466", + "&467", + "&468", + "&469", + "&470", + "&471", + "&472", + "&473", + "&474", + "&475", + "&476", + "&477", + "&478", + "&479", + "&480", + "&481", + "&482", + "&483", + "&484", + "&485", + "&486", + "&487", + "&488", + "&489", + "&490", + "&491", + "&492", + "&493", + "&494", + "&495", + "&496", + "&497", + "&498", + "&499", + "&500", + "&501", + "&502", + "&503", + "&504", + "&505", + "&506", + "&507", + "&508", + "&509", + "&510", + "&511", +}; + +const char *inp_key_name(int k) { if (k >= 0 && k < 512) return key_strings[k]; else return key_strings[0]; } +int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, "-?-")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; } + diff --git a/src/engine/e_linereader.c b/src/engine/e_linereader.c deleted file mode 100644 index 57ba9a85..00000000 --- a/src/engine/e_linereader.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "e_linereader.h" - -void linereader_init(LINEREADER *lr, IOHANDLE io) -{ - lr->buffer_max_size = 4*1024; - lr->buffer_size = 0; - lr->buffer_pos = 0; - lr->io = io; -} - -char *linereader_get(LINEREADER *lr) -{ - unsigned line_start = lr->buffer_pos; - - while(1) - { - if(lr->buffer_pos >= lr->buffer_size) - { - /* fetch more */ - - /* move the remaining part to the front */ - unsigned read; - unsigned left = lr->buffer_size - line_start; - - if(line_start > lr->buffer_size) - left = 0; - if(left) - mem_move(lr->buffer, &lr->buffer[line_start], left); - lr->buffer_pos = left; - - /* fill the buffer */ - read = io_read(lr->io, &lr->buffer[lr->buffer_pos], lr->buffer_max_size-lr->buffer_pos); - lr->buffer_size = left + read; - line_start = 0; - - if(!read) - { - if(left) - { - lr->buffer[left] = 0; /* return the last line */ - lr->buffer_pos = left; - lr->buffer_size = left; - return lr->buffer; - } - else - return 0x0; /* we are done! */ - } - } - else - { - if(lr->buffer[lr->buffer_pos] == '\n' || lr->buffer[lr->buffer_pos] == '\r') - { - /* line found */ - lr->buffer[lr->buffer_pos] = 0; - lr->buffer_pos++; - return &lr->buffer[line_start]; - } - else - lr->buffer_pos++; - } - } -} diff --git a/src/engine/e_linereader.cpp b/src/engine/e_linereader.cpp new file mode 100644 index 00000000..57ba9a85 --- /dev/null +++ b/src/engine/e_linereader.cpp @@ -0,0 +1,62 @@ +#include "e_linereader.h" + +void linereader_init(LINEREADER *lr, IOHANDLE io) +{ + lr->buffer_max_size = 4*1024; + lr->buffer_size = 0; + lr->buffer_pos = 0; + lr->io = io; +} + +char *linereader_get(LINEREADER *lr) +{ + unsigned line_start = lr->buffer_pos; + + while(1) + { + if(lr->buffer_pos >= lr->buffer_size) + { + /* fetch more */ + + /* move the remaining part to the front */ + unsigned read; + unsigned left = lr->buffer_size - line_start; + + if(line_start > lr->buffer_size) + left = 0; + if(left) + mem_move(lr->buffer, &lr->buffer[line_start], left); + lr->buffer_pos = left; + + /* fill the buffer */ + read = io_read(lr->io, &lr->buffer[lr->buffer_pos], lr->buffer_max_size-lr->buffer_pos); + lr->buffer_size = left + read; + line_start = 0; + + if(!read) + { + if(left) + { + lr->buffer[left] = 0; /* return the last line */ + lr->buffer_pos = left; + lr->buffer_size = left; + return lr->buffer; + } + else + return 0x0; /* we are done! */ + } + } + else + { + if(lr->buffer[lr->buffer_pos] == '\n' || lr->buffer[lr->buffer_pos] == '\r') + { + /* line found */ + lr->buffer[lr->buffer_pos] = 0; + lr->buffer_pos++; + return &lr->buffer[line_start]; + } + else + lr->buffer_pos++; + } + } +} diff --git a/src/engine/e_map.c b/src/engine/e_map.c deleted file mode 100644 index a2048310..00000000 --- a/src/engine/e_map.c +++ /dev/null @@ -1,66 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "e_datafile.h" - -static DATAFILE *map = 0; - -void *map_get_data(int index) -{ - return datafile_get_data(map, index); -} - -void *map_get_data_swapped(int index) -{ - return datafile_get_data_swapped(map, index); -} - -void map_unload_data(int index) -{ - datafile_unload_data(map, index); -} - -void *map_get_item(int index, int *type, int *id) -{ - return datafile_get_item(map, index, type, id); -} - -void map_get_type(int type, int *start, int *num) -{ - datafile_get_type(map, type, start, num); -} - -void *map_find_item(int type, int id) -{ - return datafile_find_item(map, type, id); -} - -int map_num_items() -{ - return datafile_num_items(map); -} - -void map_unload() -{ - datafile_unload(map); - map = 0x0; -} - -int map_is_loaded() -{ - return map != 0; -} - -int map_load(const char *mapname) -{ - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - map = datafile_load(buf); - return map != 0; -} - -void map_set(void *m) -{ - if(map) - map_unload(); - map = (DATAFILE*)m; -} diff --git a/src/engine/e_map.cpp b/src/engine/e_map.cpp new file mode 100644 index 00000000..a2048310 --- /dev/null +++ b/src/engine/e_map.cpp @@ -0,0 +1,66 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "e_datafile.h" + +static DATAFILE *map = 0; + +void *map_get_data(int index) +{ + return datafile_get_data(map, index); +} + +void *map_get_data_swapped(int index) +{ + return datafile_get_data_swapped(map, index); +} + +void map_unload_data(int index) +{ + datafile_unload_data(map, index); +} + +void *map_get_item(int index, int *type, int *id) +{ + return datafile_get_item(map, index, type, id); +} + +void map_get_type(int type, int *start, int *num) +{ + datafile_get_type(map, type, start, num); +} + +void *map_find_item(int type, int id) +{ + return datafile_find_item(map, type, id); +} + +int map_num_items() +{ + return datafile_num_items(map); +} + +void map_unload() +{ + datafile_unload(map); + map = 0x0; +} + +int map_is_loaded() +{ + return map != 0; +} + +int map_load(const char *mapname) +{ + char buf[512]; + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + map = datafile_load(buf); + return map != 0; +} + +void map_set(void *m) +{ + if(map) + map_unload(); + map = (DATAFILE*)m; +} diff --git a/src/engine/e_memheap.c b/src/engine/e_memheap.c deleted file mode 100644 index 7589e2c1..00000000 --- a/src/engine/e_memheap.c +++ /dev/null @@ -1,102 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include - -typedef struct CHUNK_t -{ - char *memory; - char *current; - char *end; - struct CHUNK_t *next; -} CHUNK; - -typedef struct -{ - CHUNK *current; -} HEAP; - -/* how large each chunk should be */ -static const int chunksize = 1024*64; - -/* allocates a new chunk to be used */ -static CHUNK *memheap_newchunk() -{ - CHUNK *chunk; - char *mem; - - /* allocate memory */ - mem = (char*)mem_alloc(sizeof(CHUNK)+chunksize, 1); - if(!mem) - return 0x0; - - /* the chunk structure is located in the begining of the chunk */ - /* init it and return the chunk */ - chunk = (CHUNK*)mem; - chunk->memory = (char*)(chunk+1); - chunk->current = chunk->memory; - chunk->end = chunk->memory + chunksize; - chunk->next = (CHUNK *)0x0; - return chunk; -} - -/******************/ -static void *memheap_allocate_from_chunk(CHUNK *chunk, unsigned int size) -{ - char *mem; - - /* check if we need can fit the allocation */ - if(chunk->current + size > chunk->end) - return (void*)0x0; - - /* get memory and move the pointer forward */ - mem = chunk->current; - chunk->current += size; - return mem; -} - -/* creates a heap */ -HEAP *memheap_create() -{ - CHUNK *chunk; - HEAP *heap; - - /* allocate a chunk and allocate the heap structure on that chunk */ - chunk = memheap_newchunk(); - heap = (HEAP *)memheap_allocate_from_chunk(chunk, sizeof(HEAP)); - heap->current = chunk; - return heap; -} - -/* destroys the heap */ -void memheap_destroy(HEAP *heap) -{ - CHUNK *chunk = heap->current; - CHUNK *next; - - while(chunk) - { - next = chunk->next; - mem_free(chunk); - chunk = next; - } -} - -/* */ -void *memheap_allocate(HEAP *heap, unsigned int size) -{ - char *mem; - - /* try to allocate from current chunk */ - mem = (char *)memheap_allocate_from_chunk(heap->current, size); - if(!mem) - { - /* allocate new chunk and add it to the heap */ - CHUNK *chunk = memheap_newchunk(); - chunk->next = heap->current; - heap->current = chunk; - - /* try to allocate again */ - mem = (char *)memheap_allocate_from_chunk(heap->current, size); - } - - return mem; -} diff --git a/src/engine/e_memheap.cpp b/src/engine/e_memheap.cpp new file mode 100644 index 00000000..fe157e86 --- /dev/null +++ b/src/engine/e_memheap.cpp @@ -0,0 +1,102 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include + +struct CHUNK +{ + char *memory; + char *current; + char *end; + CHUNK *next; +}; + +struct HEAP +{ + CHUNK *current; +}; + +/* how large each chunk should be */ +static const int chunksize = 1024*64; + +/* allocates a new chunk to be used */ +static CHUNK *memheap_newchunk() +{ + CHUNK *chunk; + char *mem; + + /* allocate memory */ + mem = (char*)mem_alloc(sizeof(CHUNK)+chunksize, 1); + if(!mem) + return 0x0; + + /* the chunk structure is located in the begining of the chunk */ + /* init it and return the chunk */ + chunk = (CHUNK*)mem; + chunk->memory = (char*)(chunk+1); + chunk->current = chunk->memory; + chunk->end = chunk->memory + chunksize; + chunk->next = (CHUNK *)0x0; + return chunk; +} + +/******************/ +static void *memheap_allocate_from_chunk(CHUNK *chunk, unsigned int size) +{ + char *mem; + + /* check if we need can fit the allocation */ + if(chunk->current + size > chunk->end) + return (void*)0x0; + + /* get memory and move the pointer forward */ + mem = chunk->current; + chunk->current += size; + return mem; +} + +/* creates a heap */ +HEAP *memheap_create() +{ + CHUNK *chunk; + HEAP *heap; + + /* allocate a chunk and allocate the heap structure on that chunk */ + chunk = memheap_newchunk(); + heap = (HEAP *)memheap_allocate_from_chunk(chunk, sizeof(HEAP)); + heap->current = chunk; + return heap; +} + +/* destroys the heap */ +void memheap_destroy(HEAP *heap) +{ + CHUNK *chunk = heap->current; + CHUNK *next; + + while(chunk) + { + next = chunk->next; + mem_free(chunk); + chunk = next; + } +} + +/* */ +void *memheap_allocate(HEAP *heap, unsigned int size) +{ + char *mem; + + /* try to allocate from current chunk */ + mem = (char *)memheap_allocate_from_chunk(heap->current, size); + if(!mem) + { + /* allocate new chunk and add it to the heap */ + CHUNK *chunk = memheap_newchunk(); + chunk->next = heap->current; + heap->current = chunk; + + /* try to allocate again */ + mem = (char *)memheap_allocate_from_chunk(heap->current, size); + } + + return mem; +} diff --git a/src/engine/e_memheap.h b/src/engine/e_memheap.h index af3c0b29..b4391ec7 100644 --- a/src/engine/e_memheap.h +++ b/src/engine/e_memheap.h @@ -1,6 +1,6 @@ /* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -typedef struct HEAP_t HEAP; +struct HEAP; HEAP *memheap_create(); void memheap_destroy(HEAP *heap); void *memheap_allocate(HEAP *heap, unsigned int size); diff --git a/src/engine/e_msg.c b/src/engine/e_msg.c deleted file mode 100644 index f9efc2bf..00000000 --- a/src/engine/e_msg.c +++ /dev/null @@ -1,70 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "e_common_interface.h" -#include "e_packer.h" - -/* message packing */ -static PACKER msg_packer; -static MSG_INFO pack_info; -static int packer_failed = 0; - -void msg_pack_int(int i) { packer_add_int(&msg_packer, i); } -void msg_pack_string(const char *p, int limit) { packer_add_string(&msg_packer, p, limit); } -void msg_pack_raw(const void *data, int size) { packer_add_raw(&msg_packer, (const unsigned char *)data, size); } - -void msg_pack_start_system(int msg, int flags) -{ - packer_reset(&msg_packer); - pack_info.msg = (msg<<1)|1; - pack_info.flags = flags; - packer_failed = 0; - - msg_pack_int(pack_info.msg); -} - -void msg_pack_start(int msg, int flags) -{ - packer_reset(&msg_packer); - pack_info.msg = msg<<1; - pack_info.flags = flags; - packer_failed = 0; - - msg_pack_int(pack_info.msg); -} - -void msg_pack_end() -{ - if(msg_packer.error) - { - packer_failed = 1; - pack_info.size = 0; - pack_info.data = (unsigned char *)""; - } - else - { - pack_info.size = packer_size(&msg_packer); - pack_info.data = packer_data(&msg_packer); - } -} - -const MSG_INFO *msg_get_info() -{ - if(packer_failed) - return 0; - return &pack_info; -} - -/* message unpacking */ -static UNPACKER msg_unpacker; -int msg_unpack_start(const void *data, int data_size, int *system) -{ - int msg; - unpacker_reset(&msg_unpacker, (const unsigned char *)data, data_size); - msg = msg_unpack_int(); - *system = msg&1; - return msg>>1; -} - -int msg_unpack_int() { return unpacker_get_int(&msg_unpacker); } -const char *msg_unpack_string() { return unpacker_get_string(&msg_unpacker); } -const unsigned char *msg_unpack_raw(int size) { return unpacker_get_raw(&msg_unpacker, size); } -int msg_unpack_error() { return msg_unpacker.error; } diff --git a/src/engine/e_msg.cpp b/src/engine/e_msg.cpp new file mode 100644 index 00000000..999a0ff0 --- /dev/null +++ b/src/engine/e_msg.cpp @@ -0,0 +1,70 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "e_common_interface.h" +#include "e_packer.h" + +/* message packing */ +static CPacker msg_packer; +static MSG_INFO pack_info; +static int packer_failed = 0; + +void msg_pack_int(int i) { msg_packer.AddInt(i); } +void msg_pack_string(const char *p, int limit) { msg_packer.AddString(p, limit); } +void msg_pack_raw(const void *data, int size) { msg_packer.AddRaw((const unsigned char *)data, size); } + +void msg_pack_start_system(int msg, int flags) +{ + msg_packer.Reset(); + pack_info.msg = (msg<<1)|1; + pack_info.flags = flags; + packer_failed = 0; + + msg_pack_int(pack_info.msg); +} + +void msg_pack_start(int msg, int flags) +{ + msg_packer.Reset(); + pack_info.msg = msg<<1; + pack_info.flags = flags; + packer_failed = 0; + + msg_pack_int(pack_info.msg); +} + +void msg_pack_end() +{ + if(msg_packer.Error()) + { + packer_failed = 1; + pack_info.size = 0; + pack_info.data = (unsigned char *)""; + } + else + { + pack_info.size = msg_packer.Size(); + pack_info.data = msg_packer.Data(); + } +} + +const MSG_INFO *msg_get_info() +{ + if(packer_failed) + return 0; + return &pack_info; +} + +/* message unpacking */ +static CUnpacker msg_unpacker; +int msg_unpack_start(const void *data, int data_size, int *system) +{ + int msg; + msg_unpacker.Reset((const unsigned char *)data, data_size); + msg = msg_unpack_int(); + *system = msg&1; + return msg>>1; +} + +int msg_unpack_int() { return msg_unpacker.GetInt(); } +const char *msg_unpack_string() { return msg_unpacker.GetString(); } +const unsigned char *msg_unpack_raw(int size) { return msg_unpacker.GetRaw(size); } +int msg_unpack_error() { return msg_unpacker.Error(); } diff --git a/src/engine/e_network.c b/src/engine/e_network.c deleted file mode 100644 index 3c32de1d..00000000 --- a/src/engine/e_network.c +++ /dev/null @@ -1,351 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include - -#include /* strlen */ - -#include "e_config.h" -#include "e_engine.h" -#include "e_network.h" -#include "e_network_internal.h" -#include "e_huffman.h" - -void recvinfo_clear(NETRECVINFO *info) -{ - info->valid = 0; -} - -void recvinfo_start(NETRECVINFO *info, NETADDR *addr, NETCONNECTION *conn, int cid) -{ - info->addr = *addr; - info->conn = conn; - info->client_id = cid; - info->current_chunk = 0; - info->valid = 1; -} - - -int seq_in_backroom(int seq, int ack) -{ - int bottom = (ack-NET_MAX_SEQUENCE/2); - if(bottom < 0) - { - if(seq <= ack) - return 1; - if(seq >= (bottom + NET_MAX_SEQUENCE)) - return 1; - } - else - { - if(seq <= ack && seq >= bottom) - return 1; - } - - return 0; -} - -/* TODO: rename this function */ -int recvinfo_fetch_chunk(NETRECVINFO *info, NETCHUNK *chunk) -{ - NETCHUNKHEADER header; - unsigned char *end = info->data.chunk_data + info->data.data_size; - int i; - - while(1) - { - unsigned char *data = info->data.chunk_data; - - /* check for old data to unpack */ - if(!info->valid || info->current_chunk >= info->data.num_chunks) - { - recvinfo_clear(info); - return 0; - } - - /* TODO: add checking here so we don't read too far */ - for(i = 0; i < info->current_chunk; i++) - { - data = unpack_chunk_header(data, &header); - data += header.size; - } - - /* unpack the header */ - data = unpack_chunk_header(data, &header); - info->current_chunk++; - - if(data+header.size > end) - { - recvinfo_clear(info); - return 0; - } - - /* handle sequence stuff */ - if(info->conn && (header.flags&NET_CHUNKFLAG_VITAL)) - { - if(header.sequence == (info->conn->ack+1)%NET_MAX_SEQUENCE) - { - /* in sequence */ - info->conn->ack = (info->conn->ack+1)%NET_MAX_SEQUENCE; - } - else - { - /* old packet that we already got */ - if(seq_in_backroom(header.sequence, info->conn->ack)) - continue; - - /* out of sequence, request resend */ - if(config.debug) - dbg_msg("conn", "asking for resend %d %d", header.sequence, (info->conn->ack+1)%NET_MAX_SEQUENCE); - conn_want_resend(info->conn); - continue; /* take the next chunk in the packet */ - } - } - - /* fill in the info */ - chunk->client_id = info->client_id; - chunk->address = info->addr; - chunk->flags = 0; - chunk->data_size = header.size; - chunk->data = data; - return 1; - } -} - - -static IOHANDLE datalog_sent = 0; -static IOHANDLE datalog_recv = 0; -static HUFFMAN_STATE huffmanstate; - -#define COMPRESSION 1 - -/* packs the data tight and sends it */ -void send_packet_connless(NETSOCKET socket, NETADDR *addr, const void *data, int data_size) -{ - unsigned char buffer[NET_MAX_PACKETSIZE]; - buffer[0] = 0xff; - buffer[1] = 0xff; - buffer[2] = 0xff; - buffer[3] = 0xff; - buffer[4] = 0xff; - buffer[5] = 0xff; - mem_copy(&buffer[6], data, data_size); - net_udp_send(socket, addr, buffer, 6+data_size); -} - -int netcommon_compress(const void *data, int data_size, void *output, int output_size) -{ - return huffman_compress(&huffmanstate, data, data_size, output, output_size); -} - -int netcommon_decompress(const void *data, int data_size, void *output, int output_size) -{ - return huffman_decompress(&huffmanstate, data, data_size, output, output_size); -} - -void send_packet(NETSOCKET socket, NETADDR *addr, NETPACKETCONSTRUCT *packet) -{ - unsigned char buffer[NET_MAX_PACKETSIZE]; - int compressed_size = -1; - int final_size = -1; - - /* log the data */ - if(datalog_sent) - { - int type = 1; - io_write(datalog_sent, &type, sizeof(type)); - io_write(datalog_sent, &packet->data_size, sizeof(packet->data_size)); - io_write(datalog_sent, &packet->chunk_data, packet->data_size); - io_flush(datalog_sent); - } - - /* compress if its enabled */ - if(COMPRESSION) - compressed_size = huffman_compress(&huffmanstate, packet->chunk_data, packet->data_size, &buffer[3], NET_MAX_PACKETSIZE-4); - - /* check if the compression was enabled, successful and good enough */ - if(compressed_size > 0 && compressed_size < packet->data_size) - { - final_size = compressed_size; - packet->flags |= NET_PACKETFLAG_COMPRESSION; - } - else - { - /* use uncompressed data */ - final_size = packet->data_size; - mem_copy(&buffer[3], packet->chunk_data, packet->data_size); - packet->flags &= ~NET_PACKETFLAG_COMPRESSION; - } - - /* set header and send the packet if all things are good */ - if(final_size >= 0) - { - final_size += NET_PACKETHEADERSIZE; - buffer[0] = ((packet->flags<<4)&0xf0)|((packet->ack>>8)&0xf); - buffer[1] = packet->ack&0xff; - buffer[2] = packet->num_chunks; - net_udp_send(socket, addr, buffer, final_size); - - /* log raw socket data */ - if(datalog_sent) - { - int type = 0; - io_write(datalog_sent, &type, sizeof(type)); - io_write(datalog_sent, &final_size, sizeof(final_size)); - io_write(datalog_sent, buffer, final_size); - io_flush(datalog_sent); - } - } -} - -/* TODO: rename this function */ -int unpack_packet(unsigned char *buffer, int size, NETPACKETCONSTRUCT *packet) -{ - /* check the size */ - if(size < NET_PACKETHEADERSIZE || size > NET_MAX_PACKETSIZE) - { - dbg_msg("", "packet too small, %d", size); - return -1; - } - - /* log the data */ - if(datalog_recv) - { - int type = 0; - io_write(datalog_recv, &type, sizeof(type)); - io_write(datalog_recv, &size, sizeof(size)); - io_write(datalog_recv, buffer, size); - io_flush(datalog_recv); - } - - /* read the packet */ - packet->flags = buffer[0]>>4; - packet->ack = ((buffer[0]&0xf)<<8) | buffer[1]; - packet->num_chunks = buffer[2]; - packet->data_size = size - NET_PACKETHEADERSIZE; - - if(packet->flags&NET_PACKETFLAG_CONNLESS) - { - if(size < 6) - { - dbg_msg("", "connection less packet too small, %d", size); - return -1; - } - - packet->flags = NET_PACKETFLAG_CONNLESS; - packet->ack = 0; - packet->num_chunks = 0; - packet->data_size = size - 6; - mem_copy(packet->chunk_data, &buffer[6], packet->data_size); - } - else - { - if(packet->flags&NET_PACKETFLAG_COMPRESSION) - packet->data_size = huffman_decompress(&huffmanstate, &buffer[3], packet->data_size, packet->chunk_data, sizeof(packet->chunk_data)); - else - mem_copy(packet->chunk_data, &buffer[3], packet->data_size); - } - - /* check for errors */ - if(packet->data_size < 0) - { - if(config.debug) - dbg_msg("network", "error during packet decoding"); - return -1; - } - - /* log the data */ - if(datalog_recv) - { - int type = 1; - io_write(datalog_recv, &type, sizeof(type)); - io_write(datalog_recv, &packet->data_size, sizeof(packet->data_size)); - io_write(datalog_recv, packet->chunk_data, packet->data_size); - io_flush(datalog_recv); - } - - /* return success */ - return 0; -} - - -/* TODO: change the arguments of this function */ -unsigned char *pack_chunk_header(unsigned char *data, int flags, int size, int sequence) -{ - data[0] = ((flags&3)<<6)|((size>>4)&0x3f); - data[1] = (size&0xf); - if(flags&NET_CHUNKFLAG_VITAL) - { - data[1] |= (sequence>>2)&0xf0; - data[2] = sequence&0xff; - return data + 3; - } - return data + 2; -} - -unsigned char *unpack_chunk_header(unsigned char *data, NETCHUNKHEADER *header) -{ - header->flags = (data[0]>>6)&3; - header->size = ((data[0]&0x3f)<<4) | (data[1]&0xf); - header->sequence = -1; - if(header->flags&NET_CHUNKFLAG_VITAL) - { - header->sequence = ((data[1]&0xf0)<<2) | data[2]; - return data + 3; - } - return data + 2; -} - - -void send_controlmsg(NETSOCKET socket, NETADDR *addr, int ack, int controlmsg, const void *extra, int extra_size) -{ - NETPACKETCONSTRUCT construct; - construct.flags = NET_PACKETFLAG_CONTROL; - construct.ack = ack; - construct.num_chunks = 0; - construct.data_size = 1+extra_size; - construct.chunk_data[0] = controlmsg; - mem_copy(&construct.chunk_data[1], extra, extra_size); - - /* send the control message */ - send_packet(socket, addr, &construct); -} - -void netcommon_openlog(const char *sentlog, const char *recvlog) -{ - if(sentlog) - { - datalog_sent = engine_openfile(sentlog, IOFLAG_WRITE); - if(datalog_sent) - dbg_msg("network", "logging sent packages to '%s'", sentlog); - else - dbg_msg("network", "failed to open for logging '%s'", sentlog); - } - - if(recvlog) - { - datalog_recv = engine_openfile(recvlog, IOFLAG_WRITE); - if(recvlog) - dbg_msg("network", "logging recv packages to '%s'", recvlog); - else - dbg_msg("network", "failed to open for logging '%s'", recvlog); - } -} - -static const unsigned freq_table[256+1] = { - 1<<30,4545,2657,431,1950,919,444,482,2244,617,838,542,715,1814,304,240,754,212,647,186, - 283,131,146,166,543,164,167,136,179,859,363,113,157,154,204,108,137,180,202,176, - 872,404,168,134,151,111,113,109,120,126,129,100,41,20,16,22,18,18,17,19, - 16,37,13,21,362,166,99,78,95,88,81,70,83,284,91,187,77,68,52,68, - 59,66,61,638,71,157,50,46,69,43,11,24,13,19,10,12,12,20,14,9, - 20,20,10,10,15,15,12,12,7,19,15,14,13,18,35,19,17,14,8,5, - 15,17,9,15,14,18,8,10,2173,134,157,68,188,60,170,60,194,62,175,71, - 148,67,167,78,211,67,156,69,1674,90,174,53,147,89,181,51,174,63,163,80, - 167,94,128,122,223,153,218,77,200,110,190,73,174,69,145,66,277,143,141,60, - 136,53,180,57,142,57,158,61,166,112,152,92,26,22,21,28,20,26,30,21, - 32,27,20,17,23,21,30,22,22,21,27,25,17,27,23,18,39,26,15,21, - 12,18,18,27,20,18,15,19,11,17,33,12,18,15,19,18,16,26,17,18, - 9,10,25,22,22,17,20,16,6,16,15,20,14,18,24,335,1517}; - -void netcommon_init() -{ - huffman_init(&huffmanstate, freq_table); -} diff --git a/src/engine/e_network.cpp b/src/engine/e_network.cpp new file mode 100644 index 00000000..ac753e50 --- /dev/null +++ b/src/engine/e_network.cpp @@ -0,0 +1,347 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include + +#include /* strlen */ + +#include "e_config.h" +#include "e_engine.h" +#include "e_network.h" +#include "e_huffman.h" + +void CNetRecvUnpacker::Clear() +{ + m_Valid = false; +} + +void CNetRecvUnpacker::Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID) +{ + m_Addr = *pAddr; + m_pConnection = pConnection; + m_ClientID = ClientID; + m_CurrentChunk = 0; + m_Valid = true; +} + +/* TODO: rename this function */ +int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) +{ + CNetChunkHeader Header; + unsigned char *pEnd = m_Data.m_aChunkData + m_Data.m_DataSize; + + while(1) + { + unsigned char *pData = m_Data.m_aChunkData; + + /* check for old data to unpack */ + if(!m_Valid || m_CurrentChunk >= m_Data.m_NumChunks) + { + Clear(); + return 0; + } + + /* TODO: add checking here so we don't read too far */ + for(int i = 0; i < m_CurrentChunk; i++) + { + pData = Header.Unpack(pData); + pData += Header.m_Size; + } + + /* unpack the header */ + pData = Header.Unpack(pData); + m_CurrentChunk++; + + if(pData+Header.m_Size > pEnd) + { + Clear(); + return 0; + } + + /* handle sequence stuff */ + if(m_pConnection && (Header.m_Flags&NET_CHUNKFLAG_VITAL)) + { + if(Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE) + { + /* in sequence */ + m_pConnection->m_Ack = (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE; + } + else + { + /* old packet that we already got */ + if(CNetBase::IsSeqInBackroom(Header.m_Sequence, m_pConnection->m_Ack)) + continue; + + /* out of sequence, request resend */ + if(config.debug) + dbg_msg("conn", "asking for resend %d %d", Header.m_Sequence, (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE); + m_pConnection->SignalResend(); + continue; /* take the next chunk in the packet */ + } + } + + /* fill in the info */ + pChunk->m_ClientID = m_ClientID; + pChunk->m_Address = m_Addr; + pChunk->m_Flags = 0; + pChunk->m_DataSize = Header.m_Size; + pChunk->m_pData = pData; + return 1; + } +} + +/* packs the data tight and sends it */ +void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize) +{ + unsigned char aBuffer[NET_MAX_PACKETSIZE]; + aBuffer[0] = 0xff; + aBuffer[1] = 0xff; + aBuffer[2] = 0xff; + aBuffer[3] = 0xff; + aBuffer[4] = 0xff; + aBuffer[5] = 0xff; + mem_copy(&aBuffer[6], pData, DataSize); + net_udp_send(Socket, pAddr, aBuffer, 6+DataSize); +} + +void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket) +{ + unsigned char aBuffer[NET_MAX_PACKETSIZE]; + int CompressedSize = -1; + int FinalSize = -1; + + /* log the data */ + if(ms_DataLogSent) + { + int type = 1; + io_write(ms_DataLogSent, &type, sizeof(type)); + io_write(ms_DataLogSent, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); + io_write(ms_DataLogSent, &pPacket->m_aChunkData, pPacket->m_DataSize); + io_flush(ms_DataLogSent); + } + + /* compress */ + CompressedSize = huffman_compress(&ms_HuffmanState, pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); + + /* check if the compression was enabled, successful and good enough */ + if(CompressedSize > 0 && CompressedSize < pPacket->m_DataSize) + { + FinalSize = CompressedSize; + pPacket->m_Flags |= NET_PACKETFLAG_COMPRESSION; + } + else + { + /* use uncompressed data */ + FinalSize = pPacket->m_DataSize; + mem_copy(&aBuffer[3], pPacket->m_aChunkData, pPacket->m_DataSize); + pPacket->m_Flags &= ~NET_PACKETFLAG_COMPRESSION; + } + + /* set header and send the packet if all things are good */ + if(FinalSize >= 0) + { + FinalSize += NET_PACKETHEADERSIZE; + aBuffer[0] = ((pPacket->m_Flags<<4)&0xf0)|((pPacket->m_Ack>>8)&0xf); + aBuffer[1] = pPacket->m_Ack&0xff; + aBuffer[2] = pPacket->m_NumChunks; + net_udp_send(Socket, pAddr, aBuffer, FinalSize); + + /* log raw socket data */ + if(ms_DataLogSent) + { + int type = 0; + io_write(ms_DataLogSent, &type, sizeof(type)); + io_write(ms_DataLogSent, &FinalSize, sizeof(FinalSize)); + io_write(ms_DataLogSent, aBuffer, FinalSize); + io_flush(ms_DataLogSent); + } + } +} + +/* TODO: rename this function */ +int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket) +{ + /* check the size */ + if(Size < NET_PACKETHEADERSIZE || Size > NET_MAX_PACKETSIZE) + { + dbg_msg("", "packet too small, %d", Size); + return -1; + } + + /* log the data */ + if(ms_DataLogRecv) + { + int type = 0; + io_write(ms_DataLogRecv, &type, sizeof(type)); + io_write(ms_DataLogRecv, &Size, sizeof(Size)); + io_write(ms_DataLogRecv, pBuffer, Size); + io_flush(ms_DataLogRecv); + } + + /* read the packet */ + pPacket->m_Flags = pBuffer[0]>>4; + pPacket->m_Ack = ((pBuffer[0]&0xf)<<8) | pBuffer[1]; + pPacket->m_NumChunks = pBuffer[2]; + pPacket->m_DataSize = Size - NET_PACKETHEADERSIZE; + + if(pPacket->m_Flags&NET_PACKETFLAG_CONNLESS) + { + if(Size < 6) + { + dbg_msg("", "connection less packet too small, %d", Size); + return -1; + } + + pPacket->m_Flags = NET_PACKETFLAG_CONNLESS; + pPacket->m_Ack = 0; + pPacket->m_NumChunks = 0; + pPacket->m_DataSize = Size - 6; + mem_copy(pPacket->m_aChunkData, &pBuffer[6], pPacket->m_DataSize); + } + else + { + if(pPacket->m_Flags&NET_PACKETFLAG_COMPRESSION) + pPacket->m_DataSize = huffman_decompress(&ms_HuffmanState, &pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData)); + else + mem_copy(pPacket->m_aChunkData, &pBuffer[3], pPacket->m_DataSize); + } + + /* check for errors */ + if(pPacket->m_DataSize < 0) + { + if(config.debug) + dbg_msg("network", "error during packet decoding"); + return -1; + } + + /* log the data */ + if(ms_DataLogRecv) + { + int type = 1; + io_write(ms_DataLogRecv, &type, sizeof(type)); + io_write(ms_DataLogRecv, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); + io_write(ms_DataLogRecv, pPacket->m_aChunkData, pPacket->m_DataSize); + io_flush(ms_DataLogRecv); + } + + /* return success */ + return 0; +} + + +void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize) +{ + CNetPacketConstruct Construct; + Construct.m_Flags = NET_PACKETFLAG_CONTROL; + Construct.m_Ack = Ack; + Construct.m_NumChunks = 0; + Construct.m_DataSize = 1+ExtraSize; + Construct.m_aChunkData[0] = ControlMsg; + mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize); + + /* send the control message */ + CNetBase::SendPacket(Socket, pAddr, &Construct); +} + + + +unsigned char *CNetChunkHeader::Pack(unsigned char *pData) +{ + pData[0] = ((m_Flags&3)<<6)|((m_Size>>4)&0x3f); + pData[1] = (m_Size&0xf); + if(m_Flags&NET_CHUNKFLAG_VITAL) + { + pData[1] |= (m_Sequence>>2)&0xf0; + pData[2] = m_Sequence&0xff; + return pData + 3; + } + return pData + 2; +} + +unsigned char *CNetChunkHeader::Unpack(unsigned char *pData) +{ + m_Flags = (pData[0]>>6)&3; + m_Size = ((pData[0]&0x3f)<<4) | (pData[1]&0xf); + m_Sequence = -1; + if(m_Flags&NET_CHUNKFLAG_VITAL) + { + m_Sequence = ((pData[1]&0xf0)<<2) | pData[2]; + return pData + 3; + } + return pData + 2; +} + + +int CNetBase::IsSeqInBackroom(int Seq, int Ack) +{ + int Bottom = (Ack-NET_MAX_SEQUENCE/2); + if(Bottom < 0) + { + if(Seq <= Ack) + return 1; + if(Seq >= (Bottom + NET_MAX_SEQUENCE)) + return 1; + } + else + { + if(Seq <= Ack && Seq >= Bottom) + return 1; + } + + return 0; +} + +IOHANDLE CNetBase::ms_DataLogSent = 0; +IOHANDLE CNetBase::ms_DataLogRecv = 0; +HUFFMAN_STATE CNetBase::ms_HuffmanState; + + +void CNetBase::OpenLog(const char *pSentLog, const char *pRecvLog) +{ + if(pSentLog) + { + ms_DataLogSent = engine_openfile(pSentLog, IOFLAG_WRITE); + if(ms_DataLogSent) + dbg_msg("network", "logging sent packages to '%s'", pSentLog); + else + dbg_msg("network", "failed to open for logging '%s'", pSentLog); + } + + if(pRecvLog) + { + ms_DataLogRecv = engine_openfile(pRecvLog, IOFLAG_WRITE); + if(ms_DataLogRecv) + dbg_msg("network", "logging recv packages to '%s'", pRecvLog); + else + dbg_msg("network", "failed to open for logging '%s'", pRecvLog); + } +} + +int CNetBase::Compress(const void *pData, int DataSize, void *pOutput, int OutputSize) +{ + return huffman_compress(&ms_HuffmanState, pData, DataSize, pOutput, OutputSize); +} + +int CNetBase::Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize) +{ + return huffman_decompress(&ms_HuffmanState, pData, DataSize, pOutput, OutputSize); +} + + +static const unsigned gs_aFreqTable[256+1] = { + 1<<30,4545,2657,431,1950,919,444,482,2244,617,838,542,715,1814,304,240,754,212,647,186, + 283,131,146,166,543,164,167,136,179,859,363,113,157,154,204,108,137,180,202,176, + 872,404,168,134,151,111,113,109,120,126,129,100,41,20,16,22,18,18,17,19, + 16,37,13,21,362,166,99,78,95,88,81,70,83,284,91,187,77,68,52,68, + 59,66,61,638,71,157,50,46,69,43,11,24,13,19,10,12,12,20,14,9, + 20,20,10,10,15,15,12,12,7,19,15,14,13,18,35,19,17,14,8,5, + 15,17,9,15,14,18,8,10,2173,134,157,68,188,60,170,60,194,62,175,71, + 148,67,167,78,211,67,156,69,1674,90,174,53,147,89,181,51,174,63,163,80, + 167,94,128,122,223,153,218,77,200,110,190,73,174,69,145,66,277,143,141,60, + 136,53,180,57,142,57,158,61,166,112,152,92,26,22,21,28,20,26,30,21, + 32,27,20,17,23,21,30,22,22,21,27,25,17,27,23,18,39,26,15,21, + 12,18,18,27,20,18,15,19,11,17,33,12,18,15,19,18,16,26,17,18, + 9,10,25,22,22,17,20,16,6,16,15,20,14,18,24,335,1517}; + +void CNetBase::Init() +{ + huffman_init(&ms_HuffmanState, gs_aFreqTable); +} diff --git a/src/engine/e_network.h b/src/engine/e_network.h index 04453cf9..19eca8da 100644 --- a/src/engine/e_network.h +++ b/src/engine/e_network.h @@ -2,38 +2,26 @@ #define ENGINE_NETWORK_H /* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "e_ringbuffer.h" +#include "e_huffman.h" -typedef struct -{ - /* -1 means that it's a stateless packet */ - /* 0 on the client means the server */ - int client_id; - NETADDR address; /* only used when client_id == -1 */ - int flags; - int data_size; - const void *data; -} NETCHUNK; - - -typedef struct -{ - NETADDR addr; - int expires; -} NETBANINFO; +/* -/*typedef struct -{ - int send_bytes; - int recv_bytes; - int send_packets; - int recv_packets; - - int resend_packets; - int resend_bytes; -} NETSTATS;*/ +CURRENT: + packet header: 3 bytes + unsigned char flags_ack; // 4bit flags, 4bit ack + unsigned char ack; // 8 bit ack + unsigned char num_chunks; // 8 bit chunks + + (unsigned char padding[3]) // 24 bit extra incase it's a connection less packet + // this is to make sure that it's compatible with the + // old protocol -typedef struct NETSERVER NETSERVER; -typedef struct NETCLIENT NETCLIENT; + chunk header: 2-3 bytes + unsigned char flags_size; // 2bit flags, 6 bit size + unsigned char size_seq; // 4bit size, 4bit seq + (unsigned char seq;) // 8bit seq, if vital flag is set +*/ enum { @@ -50,97 +38,310 @@ enum NETBANTYPE_DROP=2 }; + +enum +{ + NET_VERSION = 2, + + NET_MAX_CHUNKSIZE = 1024, + NET_MAX_PAYLOAD = NET_MAX_CHUNKSIZE+16, + NET_MAX_PACKETSIZE = NET_MAX_PAYLOAD+16, + NET_MAX_CHUNKHEADERSIZE = 5, + NET_PACKETHEADERSIZE = 3, + NET_MAX_CLIENTS = 16, + NET_MAX_SEQUENCE = 1<<10, + NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1, + + NET_CONNSTATE_OFFLINE=0, + NET_CONNSTATE_CONNECT=1, + NET_CONNSTATE_PENDING=2, + NET_CONNSTATE_ONLINE=3, + NET_CONNSTATE_ERROR=4, + + NET_PACKETFLAG_CONTROL=1, + NET_PACKETFLAG_CONNLESS=2, + NET_PACKETFLAG_RESEND=4, + NET_PACKETFLAG_COMPRESSION=8, + + NET_CHUNKFLAG_VITAL=1, + NET_CHUNKFLAG_RESEND=2, + + NET_CTRLMSG_KEEPALIVE=0, + NET_CTRLMSG_CONNECT=1, + NET_CTRLMSG_CONNECTACCEPT=2, + NET_CTRLMSG_ACCEPT=3, + NET_CTRLMSG_CLOSE=4, + + NET_SERVER_MAXBANS=1024, + + NET_CONN_BUFFERSIZE=1024*16, + + NET_ENUM_TERMINATOR +}; + + typedef int (*NETFUNC_DELCLIENT)(int cid, void *user); typedef int (*NETFUNC_NEWCLIENT)(int cid, void *user); -/* both */ -void netcommon_openlog(const char *sentlog, const char *recvlog); -void netcommon_init(); -int netcommon_compress(const void *data, int data_size, void *output, int output_size); -int netcommon_decompress(const void *data, int data_size, void *output, int output_size); +struct CNetChunk +{ + /* -1 means that it's a stateless packet */ + /* 0 on the client means the server */ + int m_ClientID; + NETADDR m_Address; /* only used when client_id == -1 */ + int m_Flags; + int m_DataSize; + const void *m_pData; +}; -/* server side */ -NETSERVER *netserver_open(NETADDR bindaddr, int max_clients, int flags); -int netserver_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user); -int netserver_recv(NETSERVER *s, NETCHUNK *chunk); -int netserver_send(NETSERVER *s, NETCHUNK *chunk); -int netserver_close(NETSERVER *s); -int netserver_update(NETSERVER *s); -NETSOCKET netserver_socket(NETSERVER *s); -int netserver_drop(NETSERVER *s, int client_id, const char *reason); -int netserver_client_addr(NETSERVER *s, int client_id, NETADDR *addr); -int netserver_max_clients(NETSERVER *s); - -int netserver_ban_add(NETSERVER *s, NETADDR addr, int seconds); -int netserver_ban_remove(NETSERVER *s, NETADDR addr); -int netserver_ban_num(NETSERVER *s); /* caution, slow */ -int netserver_ban_get(NETSERVER *s, int index, NETBANINFO *info); /* caution, slow */ - -/*void netserver_stats(NETSERVER *s, NETSTATS *stats);*/ +class CNetChunkHeader +{ +public: + int m_Flags; + int m_Size; + int m_Sequence; + + unsigned char *Pack(unsigned char *pData); + unsigned char *Unpack(unsigned char *pData); +}; -/* client side */ -NETCLIENT *netclient_open(NETADDR bindaddr, int flags); -int netclient_disconnect(NETCLIENT *c, const char *reason); -int netclient_connect(NETCLIENT *c, NETADDR *addr); -int netclient_recv(NETCLIENT *c, NETCHUNK *chunk); -int netclient_send(NETCLIENT *c, NETCHUNK *chunk); -int netclient_close(NETCLIENT *c); -int netclient_update(NETCLIENT *c); -int netclient_state(NETCLIENT *c); -int netclient_flush(NETCLIENT *c); -int netclient_gotproblems(NETCLIENT *c); -/*void netclient_stats(NETCLIENT *c, NETSTATS *stats);*/ -int netclient_error_string_reset(NETCLIENT *c); -const char *netclient_error_string(NETCLIENT *c); - -#ifdef __cplusplus -class net_server +class CNetChunkResend +{ +public: + int m_Flags; + int m_DataSize; + unsigned char *m_pData; + + int m_Sequence; + int64 m_LastSendTime; + int64 m_FirstSendTime; +}; + +class CNetPacketConstruct { - NETSERVER *ptr; public: - net_server() : ptr(0) {} - ~net_server() { close(); } + int m_Flags; + int m_Ack; + int m_NumChunks; + int m_DataSize; + unsigned char m_aChunkData[NET_MAX_PAYLOAD]; +}; + + +class CNetConnection +{ + // TODO: is this needed because this needs to be aware of + // the ack sequencing number and is also responible for updating + // that. this should be fixed. + friend class CNetRecvUnpacker; +private: + unsigned short m_Sequence; + unsigned short m_Ack; + unsigned m_State; + + int m_Token; + int m_RemoteClosed; + + TStaticRingBuffer m_Buffer; + + int64 m_LastUpdateTime; + int64 m_LastRecvTime; + int64 m_LastSendTime; + + char m_ErrorString[256]; - int open(NETADDR bindaddr, int max, int flags) { ptr = netserver_open(bindaddr, max, flags); return ptr != 0; } - int close() { int r = netserver_close(ptr); ptr = 0; return r; } + CNetPacketConstruct m_Construct; - int set_callbacks(NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user) - { return netserver_set_callbacks(ptr, new_client, del_client, user); } + NETADDR m_PeerAddr; + NETSOCKET m_Socket; + NETSTATS m_Stats; - int recv(NETCHUNK *chunk) { return netserver_recv(ptr, chunk); } - int send(NETCHUNK *chunk) { return netserver_send(ptr, chunk); } - int update() { return netserver_update(ptr); } + // + void Reset(); + void ResetStats(); + void SetError(const char *pString); + void AckChunks(int Ack); - int drop(int client_id, const char *reason) { return netserver_drop(ptr, client_id, reason); } + void QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence); + void SendControl(int ControlMsg, const void *pExtra, int ExtraSize); + void ResendChunk(CNetChunkResend *pResend); + void Resend(); - int max_clients() { return netserver_max_clients(ptr); } - /*void stats(NETSTATS *stats) { netserver_stats(ptr, stats); }*/ +public: + void Init(NETSOCKET Socket); + int Connect(NETADDR *pAddr); + void Disconnect(const char *pReason); + + int Update(); + int Flush(); + + int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr); + void QueueChunk(int Flags, int DataSize, const void *pData); + + const char *ErrorString(); + void SignalResend(); + int State() const { return m_State; } + NETADDR PeerAddress() const { return m_PeerAddr; } + + void ResetErrorString() { m_ErrorString[0] = 0; } + const char *ErrorString() const { return m_ErrorString; } + + // Needed for GotProblems in NetClient + int64 LastRecvTime() const { return m_LastRecvTime; } + + int AckSequence() const { return m_Ack; } +}; + +struct CNetRecvUnpacker +{ +public: + bool m_Valid; + + NETADDR m_Addr; + CNetConnection *m_pConnection; + int m_CurrentChunk; + int m_ClientID; + CNetPacketConstruct m_Data; + unsigned char m_aBuffer[NET_MAX_PACKETSIZE]; + + CNetRecvUnpacker() { Clear(); } + void Clear(); + void Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID); + int FetchChunk(CNetChunk *pChunk); +}; + +/* server side */ +class CNetServer +{ +public: + struct CBanInfo + { + NETADDR m_Addr; + int m_Expires; + }; + +private: + class CSlot + { + public: + CNetConnection m_Connection; + }; + + class CBan + { + public: + CBanInfo m_Info; + + /* hash list */ + CBan *m_pHashNext; + CBan *m_pHashPrev; + + /* used or free list */ + CBan *m_pNext; + CBan *m_pPrev; + }; + + + NETSOCKET m_Socket; + CSlot m_aSlots[NET_MAX_CLIENTS]; + int m_MaxClients; + + CBan *m_aBans[256]; + CBan m_BanPool[NET_SERVER_MAXBANS]; + CBan *m_BanPool_FirstFree; + CBan *m_BanPool_FirstUsed; + + NETFUNC_NEWCLIENT m_pfnNewClient; + NETFUNC_DELCLIENT m_pfnDelClient; + void *m_UserPtr; + + CNetRecvUnpacker m_RecvUnpacker; + + void BanRemoveByObject(CBan *ban); + +public: + int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + + // + bool Open(NETADDR bindaddr, int MaxClients, int Flags); + int Close(); + + // + int Recv(CNetChunk *pChunk); + int Send(CNetChunk *pChunk); + int Update(); + + // + int Drop(int ClientID, const char *Reason); + + // banning + int BanAdd(NETADDR Addr, int Seconds); + int BanRemove(NETADDR Addr); + int BanNum(); /* caution, slow */ + int BanGet(int Index, CBanInfo *pInfo); /* caution, slow */ + + // status requests + NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } + NETSOCKET Socket() const { return m_Socket; } + int MaxClients() const { return m_MaxClients; } }; -class net_client + +/* client side */ +class CNetClient { - NETCLIENT *ptr; + NETADDR m_ServerAddr; + CNetConnection m_Connection; + CNetRecvUnpacker m_RecvUnpacker; + NETSOCKET m_Socket; public: - net_client() : ptr(0) {} - ~net_client() { close(); } + // openness + bool Open(NETADDR BindAddr, int Flags); + int Close(); - int open(NETADDR bindaddr, int flags) { ptr = netclient_open(bindaddr, flags); return ptr != 0; } - int close() { int r = netclient_close(ptr); ptr = 0; return r; } + // connection state + int Disconnect(const char *Reason); + int Connect(NETADDR *Addr); - int connect(NETADDR *addr) { return netclient_connect(ptr, addr); } - int disconnect(const char *reason) { return netclient_disconnect(ptr, reason); } + // communication + int Recv(CNetChunk *Chunk); + int Send(CNetChunk *Chunk); - int recv(NETCHUNK *chunk) { return netclient_recv(ptr, chunk); } - int send(NETCHUNK *chunk) { return netclient_send(ptr, chunk); } - int update() { return netclient_update(ptr); } + // pumping + int Update(); + int Flush(); + + int ResetErrorString(); - const char *error_string() { return netclient_error_string(ptr); } + // error and state + int State(); + int GotProblems(); + const char *ErrorString(); +}; + + + +// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere +class CNetBase +{ + static IOHANDLE ms_DataLogSent; + static IOHANDLE ms_DataLogRecv; + static HUFFMAN_STATE ms_HuffmanState; +public: + static void OpenLog(const char *pSentlog, const char *pRecvlog); + static void Init(); + static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize); + static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize); - int state() { return netclient_state(ptr); } - /*void stats(NETSTATS *stats) { netclient_stats(ptr, stats); }*/ + static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize); + static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize); + static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket); + static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket); + + /* The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not */ + static int IsSeqInBackroom(int Seq, int Ack); }; -#endif #endif diff --git a/src/engine/e_network_client.c b/src/engine/e_network_client.c deleted file mode 100644 index 75f7c538..00000000 --- a/src/engine/e_network_client.c +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include "e_network.h" -#include "e_network_internal.h" - -struct NETCLIENT -{ - NETADDR server_addr; - NETSOCKET socket; - - NETRECVINFO recv; - NETCONNECTION conn; -}; - -NETCLIENT *netclient_open(NETADDR bindaddr, int flags) -{ - NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1); - mem_zero(client, sizeof(NETCLIENT)); - client->socket = net_udp_create(bindaddr); - conn_init(&client->conn, client->socket); - return client; -} - -int netclient_close(NETCLIENT *c) -{ - /* TODO: implement me */ - return 0; -} - - -int netclient_disconnect(NETCLIENT *c, const char *reason) -{ - dbg_msg("netclient", "disconnected. reason=\"%s\"", reason); - conn_disconnect(&c->conn, reason); - return 0; -} - -int netclient_update(NETCLIENT *c) -{ - conn_update(&c->conn); - if(c->conn.state == NET_CONNSTATE_ERROR) - netclient_disconnect(c, conn_error(&c->conn)); - return 0; -} - -int netclient_connect(NETCLIENT *c, NETADDR *addr) -{ - conn_connect(&c->conn, addr); - return 0; -} - -int netclient_error_string_reset(NETCLIENT *c) -{ - mem_zero(c->conn.error_string, sizeof(c->conn.error_string)); - return 0; -} - -int netclient_recv(NETCLIENT *c, NETCHUNK *chunk) -{ - while(1) - { - NETADDR addr; - int bytes; - - /* check for a chunk */ - if(recvinfo_fetch_chunk(&c->recv, chunk)) - return 1; - - /* TODO: empty the recvinfo */ - bytes = net_udp_recv(c->socket, &addr, c->recv.buffer, NET_MAX_PACKETSIZE); - - /* no more packets for now */ - if(bytes <= 0) - break; - - if(unpack_packet(c->recv.buffer, bytes, &c->recv.data) == 0) - { - if(c->recv.data.flags&NET_PACKETFLAG_CONNLESS) - { - chunk->flags = NETSENDFLAG_CONNLESS; - chunk->client_id = -1; - chunk->address = addr; - chunk->data_size = c->recv.data.data_size; - chunk->data = c->recv.data.chunk_data; - return 1; - } - else - { - if(conn_feed(&c->conn, &c->recv.data, &addr)) - recvinfo_start(&c->recv, &addr, &c->conn, 0); - } - } - } - return 0; -} - -int netclient_send(NETCLIENT *c, NETCHUNK *chunk) -{ - if(chunk->data_size >= NET_MAX_PAYLOAD) - { - dbg_msg("netclient", "chunk payload too big. %d. dropping chunk", chunk->data_size); - return -1; - } - - if(chunk->flags&NETSENDFLAG_CONNLESS) - { - /* send connectionless packet */ - send_packet_connless(c->socket, &chunk->address, chunk->data, chunk->data_size); - } - else - { - int f = 0; - dbg_assert(chunk->client_id == 0, "errornous client id"); - - if(chunk->flags&NETSENDFLAG_VITAL) - f = NET_CHUNKFLAG_VITAL; - - conn_queue_chunk(&c->conn, f, chunk->data_size, chunk->data); - - if(chunk->flags&NETSENDFLAG_FLUSH) - conn_flush(&c->conn); - } - return 0; -} - -int netclient_state(NETCLIENT *c) -{ - if(c->conn.state == NET_CONNSTATE_ONLINE) - return NETSTATE_ONLINE; - if(c->conn.state == NET_CONNSTATE_OFFLINE) - return NETSTATE_OFFLINE; - return NETSTATE_CONNECTING; -} - -int netclient_flush(NETCLIENT *c) -{ - return conn_flush(&c->conn); -} - -int netclient_gotproblems(NETCLIENT *c) -{ - if(time_get() - c->conn.last_recv_time > time_freq()) - return 1; - return 0; -} - -void netclient_stats(NETCLIENT *c, NETSTATS *stats) -{ - *stats = c->conn.stats; -} - -const char *netclient_error_string(NETCLIENT *c) -{ - return conn_error(&c->conn); -} diff --git a/src/engine/e_network_client.cpp b/src/engine/e_network_client.cpp new file mode 100644 index 00000000..ce243b32 --- /dev/null +++ b/src/engine/e_network_client.cpp @@ -0,0 +1,139 @@ +#include +#include "e_network.h" + +bool CNetClient::Open(NETADDR BindAddr, int Flags) +{ + // clean it + mem_zero(this, sizeof(*this)); + + // open socket + m_Socket = net_udp_create(BindAddr); + m_Connection.Init(m_Socket); + return true; +} + +int CNetClient::Close() +{ + /* TODO: implement me */ + return 0; +} + + +int CNetClient::Disconnect(const char *pReason) +{ + dbg_msg("netclient", "disconnected. reason=\"%s\"", pReason); + m_Connection.Disconnect(pReason); + return 0; +} + +int CNetClient::Update() +{ + m_Connection.Update(); + if(m_Connection.State() == NET_CONNSTATE_ERROR) + Disconnect(m_Connection.ErrorString()); + return 0; +} + +int CNetClient::Connect(NETADDR *pAddr) +{ + m_Connection.Connect(pAddr); + return 0; +} + +int CNetClient::ResetErrorString() +{ + m_Connection.ResetErrorString(); + return 0; +} + +int CNetClient::Recv(CNetChunk *pChunk) +{ + while(1) + { + /* check for a chunk */ + if(m_RecvUnpacker.FetchChunk(pChunk)) + return 1; + + /* TODO: empty the recvinfo */ + NETADDR Addr; + int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); + + /* no more packets for now */ + if(Bytes <= 0) + break; + + if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0) + { + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS) + { + pChunk->m_Flags = NETSENDFLAG_CONNLESS; + pChunk->m_ClientID = -1; + pChunk->m_Address = Addr; + pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize; + pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData; + return 1; + } + else + { + if(m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + m_RecvUnpacker.Start(&Addr, &m_Connection, 0); + } + } + } + return 0; +} + +int CNetClient::Send(CNetChunk *pChunk) +{ + if(pChunk->m_DataSize >= NET_MAX_PAYLOAD) + { + dbg_msg("netclient", "chunk payload too big. %d. dropping chunk", pChunk->m_DataSize); + return -1; + } + + if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) + { + /* send connectionless packet */ + CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); + } + else + { + int Flags = 0; + dbg_assert(pChunk->m_ClientID == 0, "errornous client id"); + + if(pChunk->m_Flags&NETSENDFLAG_VITAL) + Flags = NET_CHUNKFLAG_VITAL; + + m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData); + + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_Connection.Flush(); + } + return 0; +} + +int CNetClient::State() +{ + if(m_Connection.State() == NET_CONNSTATE_ONLINE) + return NETSTATE_ONLINE; + if(m_Connection.State() == NET_CONNSTATE_OFFLINE) + return NETSTATE_OFFLINE; + return NETSTATE_CONNECTING; +} + +int CNetClient::Flush() +{ + return m_Connection.Flush(); +} + +int CNetClient::GotProblems() +{ + if(time_get() - m_Connection.LastRecvTime() > time_freq()) + return 1; + return 0; +} + +const char *CNetClient::ErrorString() +{ + return m_Connection.ErrorString(); +} diff --git a/src/engine/e_network_conn.c b/src/engine/e_network_conn.c deleted file mode 100644 index 583dbe65..00000000 --- a/src/engine/e_network_conn.c +++ /dev/null @@ -1,366 +0,0 @@ -#include -#include -#include "e_config.h" -#include "e_network_internal.h" - -static void conn_reset_stats(NETCONNECTION *conn) -{ - mem_zero(&conn->stats, sizeof(conn->stats)); -} - -static void conn_reset(NETCONNECTION *conn) -{ - conn->seq = 0; - conn->ack = 0; - conn->remote_closed = 0; - - conn->state = NET_CONNSTATE_OFFLINE; - conn->last_send_time = 0; - conn->last_recv_time = 0; - conn->last_update_time = 0; - conn->token = -1; - mem_zero(&conn->peeraddr, sizeof(conn->peeraddr)); - - conn->buffer = ringbuf_init(conn->buffer_memory, sizeof(conn->buffer_memory), 0); - - mem_zero(&conn->construct, sizeof(conn->construct)); -} - - -const char *conn_error(NETCONNECTION *conn) -{ - return conn->error_string; -} - -static void conn_set_error(NETCONNECTION *conn, const char *str) -{ - str_copy(conn->error_string, str, sizeof(conn->error_string)); -} - -void conn_init(NETCONNECTION *conn, NETSOCKET socket) -{ - conn_reset(conn); - conn_reset_stats(conn); - conn->socket = socket; - mem_zero(conn->error_string, sizeof(conn->error_string)); -} - - -static void conn_ack(NETCONNECTION *conn, int ack) -{ - - while(1) - { - NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_first(conn->buffer); - if(!resend) - break; - - if(seq_in_backroom(resend->sequence, ack)) - ringbuf_popfirst(conn->buffer); - else - break; - } -} - -void conn_want_resend(NETCONNECTION *conn) -{ - conn->construct.flags |= NET_PACKETFLAG_RESEND; -} - -int conn_flush(NETCONNECTION *conn) -{ - int num_chunks = conn->construct.num_chunks; - if(!num_chunks && !conn->construct.flags) - return 0; - - /* send of the packets */ - conn->construct.ack = conn->ack; - send_packet(conn->socket, &conn->peeraddr, &conn->construct); - - /* update send times */ - conn->last_send_time = time_get(); - - /* clear construct so we can start building a new package */ - mem_zero(&conn->construct, sizeof(conn->construct)); - return num_chunks; -} - -static void conn_queue_chunk_ex(NETCONNECTION *conn, int flags, int data_size, const void *data, int sequence) -{ - unsigned char *chunk_data; - - /* check if we have space for it, if not, flush the connection */ - if(conn->construct.data_size + data_size + NET_MAX_CHUNKHEADERSIZE > sizeof(conn->construct.chunk_data)) - conn_flush(conn); - - /* pack all the data */ - chunk_data = &conn->construct.chunk_data[conn->construct.data_size]; - chunk_data = pack_chunk_header(chunk_data, flags, data_size, sequence); - mem_copy(chunk_data, data, data_size); - chunk_data += data_size; - - /* */ - conn->construct.num_chunks++; - conn->construct.data_size = (int)(chunk_data-conn->construct.chunk_data); - - /* set packet flags aswell */ - - if(flags&NET_CHUNKFLAG_VITAL && !(flags&NET_CHUNKFLAG_RESEND)) - { - /* save packet if we need to resend */ - NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_allocate(conn->buffer, sizeof(NETCHUNKDATA)+data_size); - if(resend) - { - resend->sequence = sequence; - resend->flags = flags; - resend->data_size = data_size; - resend->data = (unsigned char *)(resend+1); - resend->first_send_time = time_get(); - resend->last_send_time = resend->first_send_time; - mem_copy(resend->data, data, data_size); - } - else - { - /* out of buffer */ - conn_disconnect(conn, "too weak connection (out of buffer)"); - } - } -} - -void conn_queue_chunk(NETCONNECTION *conn, int flags, int data_size, const void *data) -{ - if(flags&NET_CHUNKFLAG_VITAL) - conn->seq = (conn->seq+1)%NET_MAX_SEQUENCE; - conn_queue_chunk_ex(conn, flags, data_size, data, conn->seq); -} - - -static void conn_send_control(NETCONNECTION *conn, int controlmsg, const void *extra, int extra_size) -{ - /* send the control message */ - conn->last_send_time = time_get(); - send_controlmsg(conn->socket, &conn->peeraddr, conn->ack, controlmsg, extra, extra_size); -} - -static void conn_resend_chunk(NETCONNECTION *conn, NETCHUNKDATA *resend) -{ - conn_queue_chunk_ex(conn, resend->flags|NET_CHUNKFLAG_RESEND, resend->data_size, resend->data, resend->sequence); - resend->last_send_time = time_get(); -} - -static void conn_resend(NETCONNECTION *conn) -{ - int resend_count = 0; - int first = 0, last = 0; - void *item = ringbuf_first(conn->buffer); - - while(item) - { - NETCHUNKDATA *resend = item; - - if(resend_count == 0) - first = resend->sequence; - last = resend->sequence; - - conn_resend_chunk(conn, resend); - item = ringbuf_next(conn->buffer, item); - resend_count++; - } - - if(config.debug) - dbg_msg("conn", "resent %d packets (%d to %d)", resend_count, first, last); -} - -int conn_connect(NETCONNECTION *conn, NETADDR *addr) -{ - if(conn->state != NET_CONNSTATE_OFFLINE) - return -1; - - /* init connection */ - conn_reset(conn); - conn->peeraddr = *addr; - mem_zero(conn->error_string, sizeof(conn->error_string)); - conn->state = NET_CONNSTATE_CONNECT; - conn_send_control(conn, NET_CTRLMSG_CONNECT, 0, 0); - return 0; -} - -void conn_disconnect(NETCONNECTION *conn, const char *reason) -{ - if(conn->state == NET_CONNSTATE_OFFLINE) - return; - - if(conn->remote_closed == 0) - { - if(reason) - conn_send_control(conn, NET_CTRLMSG_CLOSE, reason, strlen(reason)+1); - else - conn_send_control(conn, NET_CTRLMSG_CLOSE, 0, 0); - - conn->error_string[0] = 0; - if(reason) - str_copy(conn->error_string, reason, sizeof(conn->error_string)); - } - - conn_reset(conn); -} - -int conn_feed(NETCONNECTION *conn, NETPACKETCONSTRUCT *packet, NETADDR *addr) -{ - int64 now = time_get(); - conn->last_recv_time = now; - - /* check if resend is requested */ - if(packet->flags&NET_PACKETFLAG_RESEND) - conn_resend(conn); - - /* */ - if(packet->flags&NET_PACKETFLAG_CONTROL) - { - int ctrlmsg = packet->chunk_data[0]; - - if(ctrlmsg == NET_CTRLMSG_CLOSE) - { - conn->state = NET_CONNSTATE_ERROR; - conn->remote_closed = 1; - - if(packet->data_size) - { - /* make sure to sanitize the error string form the other party*/ - char str[128]; - if(packet->data_size < 128) - str_copy(str, (char *)packet->chunk_data, packet->data_size); - else - str_copy(str, (char *)packet->chunk_data, 128); - str_sanitize_strong(str); - - /* set the error string */ - conn_set_error(conn, str); - } - else - conn_set_error(conn, "no reason given"); - - if(config.debug) - dbg_msg("conn", "closed reason='%s'", conn_error(conn)); - return 0; - } - else - { - if(conn->state == NET_CONNSTATE_OFFLINE) - { - if(ctrlmsg == NET_CTRLMSG_CONNECT) - { - /* send response and init connection */ - conn_reset(conn); - conn->state = NET_CONNSTATE_PENDING; - conn->peeraddr = *addr; - conn->last_send_time = now; - conn->last_recv_time = now; - conn->last_update_time = now; - conn_send_control(conn, NET_CTRLMSG_CONNECTACCEPT, 0, 0); - if(config.debug) - dbg_msg("connection", "got connection, sending connect+accept"); - } - } - else if(conn->state == NET_CONNSTATE_CONNECT) - { - /* connection made */ - if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT) - { - conn_send_control(conn, NET_CTRLMSG_ACCEPT, 0, 0); - conn->state = NET_CONNSTATE_ONLINE; - if(config.debug) - dbg_msg("connection", "got connect+accept, sending accept. connection online"); - } - } - else if(conn->state == NET_CONNSTATE_ONLINE) - { - /* connection made */ - /* - if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT) - { - - }*/ - } - } - } - else - { - if(conn->state == NET_CONNSTATE_PENDING) - { - conn->state = NET_CONNSTATE_ONLINE; - if(config.debug) - dbg_msg("connection", "connecting online"); - } - } - - if(conn->state == NET_CONNSTATE_ONLINE) - { - - conn_ack(conn, packet->ack); - } - - return 1; -} - -int conn_update(NETCONNECTION *conn) -{ - int64 now = time_get(); - - if(conn->state == NET_CONNSTATE_OFFLINE || conn->state == NET_CONNSTATE_ERROR) - return 0; - - /* check for timeout */ - if(conn->state != NET_CONNSTATE_OFFLINE && - conn->state != NET_CONNSTATE_CONNECT && - (now-conn->last_recv_time) > time_freq()*10) - { - conn->state = NET_CONNSTATE_ERROR; - conn_set_error(conn, "timeout"); - } - - /* fix resends */ - if(ringbuf_first(conn->buffer)) - { - NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_first(conn->buffer); - - /* check if we have some really old stuff laying around and abort if not acked */ - if(now-resend->first_send_time > time_freq()*10) - { - conn->state = NET_CONNSTATE_ERROR; - conn_set_error(conn, "too weak connection (not acked for 10 seconds)"); - } - else - { - /* resend packet if we havn't got it acked in 1 second */ - if(now-resend->last_send_time > time_freq()) - conn_resend_chunk(conn, resend); - } - } - - /* send keep alives if nothing has happend for 250ms */ - if(conn->state == NET_CONNSTATE_ONLINE) - { - if(time_get()-conn->last_send_time > time_freq()/2) /* flush connection after 500ms if needed */ - { - int num_flushed_chunks = conn_flush(conn); - if(num_flushed_chunks && config.debug) - dbg_msg("connection", "flushed connection due to timeout. %d chunks.", num_flushed_chunks); - } - - if(time_get()-conn->last_send_time > time_freq()) - conn_send_control(conn, NET_CTRLMSG_KEEPALIVE, 0, 0); - } - else if(conn->state == NET_CONNSTATE_CONNECT) - { - if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect every 500ms */ - conn_send_control(conn, NET_CTRLMSG_CONNECT, 0, 0); - } - else if(conn->state == NET_CONNSTATE_PENDING) - { - if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect/accept every 500ms */ - conn_send_control(conn, NET_CTRLMSG_CONNECTACCEPT, 0, 0); - } - - return 0; -} diff --git a/src/engine/e_network_conn.cpp b/src/engine/e_network_conn.cpp new file mode 100644 index 00000000..a54c9ce3 --- /dev/null +++ b/src/engine/e_network_conn.cpp @@ -0,0 +1,349 @@ +#include +#include +#include "e_config.h" +#include "e_network.h" + +void CNetConnection::ResetStats() +{ + mem_zero(&m_Stats, sizeof(m_Stats)); +} + +void CNetConnection::Reset() +{ + m_Sequence = 0; + m_Ack = 0; + m_RemoteClosed = 0; + + m_State = NET_CONNSTATE_OFFLINE; + m_LastSendTime = 0; + m_LastRecvTime = 0; + m_LastUpdateTime = 0; + m_Token = -1; + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + + m_Buffer.Init(); + + mem_zero(&m_Construct, sizeof(m_Construct)); +} + +const char *CNetConnection::ErrorString() +{ + return m_ErrorString; +} + +void CNetConnection::SetError(const char *pString) +{ + str_copy(m_ErrorString, pString, sizeof(m_ErrorString)); +} + +void CNetConnection::Init(NETSOCKET Socket) +{ + Reset(); + ResetStats(); + + m_Socket = Socket; + mem_zero(m_ErrorString, sizeof(m_ErrorString)); +} + +void CNetConnection::AckChunks(int Ack) +{ + while(1) + { + CNetChunkResend *pResend = m_Buffer.First(); + if(!pResend) + break; + + if(CNetBase::IsSeqInBackroom(pResend->m_Sequence, Ack)) + m_Buffer.PopFirst(); + else + break; + } +} + +void CNetConnection::SignalResend() +{ + m_Construct.m_Flags |= NET_PACKETFLAG_RESEND; +} + +int CNetConnection::Flush() +{ + int NumChunks = m_Construct.m_NumChunks; + if(!NumChunks && !m_Construct.m_Flags) + return 0; + + /* send of the packets */ + m_Construct.m_Ack = m_Ack; + CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct); + + /* update send times */ + m_LastSendTime = time_get(); + + /* clear construct so we can start building a new package */ + mem_zero(&m_Construct, sizeof(m_Construct)); + return NumChunks; +} + +void CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) +{ + unsigned char *pChunkData; + + /* check if we have space for it, if not, flush the connection */ + if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData)) + Flush(); + + /* pack all the data */ + CNetChunkHeader Header; + Header.m_Flags = Flags; + Header.m_Size = DataSize; + Header.m_Sequence = Sequence; + pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize]; + pChunkData = Header.Pack(pChunkData); + mem_copy(pChunkData, pData, DataSize); + pChunkData += DataSize; + + /* */ + m_Construct.m_NumChunks++; + m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); + + /* set packet flags aswell */ + + if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND)) + { + /* save packet if we need to resend */ + CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize); + if(pResend) + { + pResend->m_Sequence = Sequence; + pResend->m_Flags = Flags; + pResend->m_DataSize = DataSize; + pResend->m_pData = (unsigned char *)(pResend+1); + pResend->m_FirstSendTime = time_get(); + pResend->m_LastSendTime = pResend->m_FirstSendTime; + mem_copy(pResend->m_pData, pData, DataSize); + } + else + { + /* out of buffer */ + Disconnect("too weak connection (out of buffer)"); + } + } +} + +void CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData) +{ + if(Flags&NET_CHUNKFLAG_VITAL) + m_Sequence = (m_Sequence+1)%NET_MAX_SEQUENCE; + QueueChunkEx(Flags, DataSize, pData, m_Sequence); +} + +void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSize) +{ + /* send the control message */ + m_LastSendTime = time_get(); + CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize); +} + +void CNetConnection::ResendChunk(CNetChunkResend *pResend) +{ + QueueChunkEx(pResend->m_Flags|NET_CHUNKFLAG_RESEND, pResend->m_DataSize, pResend->m_pData, pResend->m_Sequence); + pResend->m_LastSendTime = time_get(); +} + +void CNetConnection::Resend() +{ + for(CNetChunkResend *pResend = m_Buffer.First(); pResend; m_Buffer.Next(pResend)) + ResendChunk(pResend); +} + +int CNetConnection::Connect(NETADDR *pAddr) +{ + if(State() != NET_CONNSTATE_OFFLINE) + return -1; + + /* init connection */ + Reset(); + m_PeerAddr = *pAddr; + mem_zero(m_ErrorString, sizeof(m_ErrorString)); + m_State = NET_CONNSTATE_CONNECT; + SendControl(NET_CTRLMSG_CONNECT, 0, 0); + return 0; +} + +void CNetConnection::Disconnect(const char *pReason) +{ + if(State() == NET_CONNSTATE_OFFLINE) + return; + + if(m_RemoteClosed == 0) + { + if(pReason) + SendControl(NET_CTRLMSG_CLOSE, pReason, strlen(pReason)+1); + else + SendControl(NET_CTRLMSG_CLOSE, 0, 0); + + m_ErrorString[0] = 0; + if(pReason) + str_copy(m_ErrorString, pReason, sizeof(m_ErrorString)); + } + + Reset(); +} + +int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) +{ + int64 now = time_get(); + m_LastRecvTime = now; + + /* check if resend is requested */ + if(pPacket->m_Flags&NET_PACKETFLAG_RESEND) + Resend(); + + /* */ + if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL) + { + int CtrlMsg = pPacket->m_aChunkData[0]; + + if(CtrlMsg == NET_CTRLMSG_CLOSE) + { + m_State = NET_CONNSTATE_ERROR; + m_RemoteClosed = 1; + + if(pPacket->m_DataSize) + { + /* make sure to sanitize the error string form the other party*/ + char Str[128]; + if(pPacket->m_DataSize < 128) + str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize); + else + str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str)); + str_sanitize_strong(Str); + + /* set the error string */ + SetError(Str); + } + else + SetError("no reason given"); + + if(config.debug) + dbg_msg("conn", "closed reason='%s'", ErrorString()); + return 0; + } + else + { + if(State() == NET_CONNSTATE_OFFLINE) + { + if(CtrlMsg == NET_CTRLMSG_CONNECT) + { + /* send response and init connection */ + Reset(); + m_State = NET_CONNSTATE_PENDING; + m_PeerAddr = *pAddr; + m_LastSendTime = now; + m_LastRecvTime = now; + m_LastUpdateTime = now; + SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + if(config.debug) + dbg_msg("connection", "got connection, sending connect+accept"); + } + } + else if(State() == NET_CONNSTATE_CONNECT) + { + /* connection made */ + if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) + { + SendControl(NET_CTRLMSG_ACCEPT, 0, 0); + m_State = NET_CONNSTATE_ONLINE; + if(config.debug) + dbg_msg("connection", "got connect+accept, sending accept. connection online"); + } + } + else if(State() == NET_CONNSTATE_ONLINE) + { + /* connection made */ + /* + if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT) + { + + }*/ + } + } + } + else + { + if(State() == NET_CONNSTATE_PENDING) + { + m_State = NET_CONNSTATE_ONLINE; + if(config.debug) + dbg_msg("connection", "connecting online"); + } + } + + if(State() == NET_CONNSTATE_ONLINE) + { + AckChunks(pPacket->m_Ack); + } + + return 1; +} + +int CNetConnection::Update() +{ + int64 now = time_get(); + + if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR) + return 0; + + /* check for timeout */ + if(State() != NET_CONNSTATE_OFFLINE && + State() != NET_CONNSTATE_CONNECT && + (now-m_LastRecvTime) > time_freq()*10) + { + m_State = NET_CONNSTATE_ERROR; + SetError("timeout"); + } + + /* fix resends */ + if(m_Buffer.First()) + { + CNetChunkResend *pResend = m_Buffer.First(); + + /* check if we have some really old stuff laying around and abort if not acked */ + if(now-pResend->m_FirstSendTime > time_freq()*10) + { + m_State = NET_CONNSTATE_ERROR; + SetError("too weak connection (not acked for 10 seconds)"); + } + else + { + /* resend packet if we havn't got it acked in 1 second */ + if(now-pResend->m_LastSendTime > time_freq()) + ResendChunk(pResend); + } + } + + /* send keep alives if nothing has happend for 250ms */ + if(State() == NET_CONNSTATE_ONLINE) + { + if(time_get()-m_LastSendTime > time_freq()/2) /* flush connection after 500ms if needed */ + { + int NumFlushedChunks = Flush(); + if(NumFlushedChunks && config.debug) + dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks); + } + + if(time_get()-m_LastSendTime > time_freq()) + SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0); + } + else if(State() == NET_CONNSTATE_CONNECT) + { + if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect every 500ms */ + SendControl(NET_CTRLMSG_CONNECT, 0, 0); + } + else if(State() == NET_CONNSTATE_PENDING) + { + if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect/accept every 500ms */ + SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + } + + return 0; +} diff --git a/src/engine/e_network_internal.h b/src/engine/e_network_internal.h deleted file mode 100644 index f1a25b31..00000000 --- a/src/engine/e_network_internal.h +++ /dev/null @@ -1,156 +0,0 @@ -#include -#include "e_network.h" -#include "e_ringbuffer.h" - -/* - -CURRENT: - packet header: 3 bytes - unsigned char flags_ack; // 4bit flags, 4bit ack - unsigned char ack; // 8 bit ack - unsigned char num_chunks; // 8 bit chunks - - (unsigned char padding[3]) // 24 bit extra incase it's a connection less packet - // this is to make sure that it's compatible with the - // old protocol - - chunk header: 2-3 bytes - unsigned char flags_size; // 2bit flags, 6 bit size - unsigned char size_seq; // 4bit size, 4bit seq - (unsigned char seq;) // 8bit seq, if vital flag is set - - -*/ - -enum -{ - NET_VERSION = 2, - - NET_MAX_CHUNKSIZE = 1024, - NET_MAX_PAYLOAD = NET_MAX_CHUNKSIZE+16, - NET_MAX_PACKETSIZE = NET_MAX_PAYLOAD+16, - NET_MAX_CHUNKHEADERSIZE = 5, - NET_PACKETHEADERSIZE = 3, - NET_MAX_CLIENTS = 16, - NET_MAX_SEQUENCE = 1<<10, - NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1, - - NET_CONNSTATE_OFFLINE=0, - NET_CONNSTATE_CONNECT=1, - NET_CONNSTATE_PENDING=2, - NET_CONNSTATE_ONLINE=3, - NET_CONNSTATE_ERROR=4, - - NET_PACKETFLAG_CONTROL=1, - NET_PACKETFLAG_CONNLESS=2, - NET_PACKETFLAG_RESEND=4, - NET_PACKETFLAG_COMPRESSION=8, - - NET_CHUNKFLAG_VITAL=1, - NET_CHUNKFLAG_RESEND=2, - - NET_CTRLMSG_KEEPALIVE=0, - NET_CTRLMSG_CONNECT=1, - NET_CTRLMSG_CONNECTACCEPT=2, - NET_CTRLMSG_ACCEPT=3, - NET_CTRLMSG_CLOSE=4, - - NET_SERVER_MAXBANS=1024, - - NET_CONN_BUFFERSIZE=1024*16, - - NET_ENUM_TERMINATOR -}; - - -typedef struct NETPACKETCONSTRUCT -{ - int flags; - int ack; - int num_chunks; - int data_size; - unsigned char chunk_data[NET_MAX_PAYLOAD]; -} NETPACKETCONSTRUCT; - -typedef struct NETCHUNKHEADER -{ - int flags; - int size; - int sequence; -} NETCHUNKHEADER; - -typedef struct -{ - int flags; - int data_size; - unsigned char *data; - - int sequence; - int64 last_send_time; - int64 first_send_time; -} NETCHUNKDATA; - -typedef struct -{ - unsigned short seq; - unsigned short ack; - unsigned state; - - int token; - int remote_closed; - - RINGBUFFER *buffer; - - int64 last_update_time; - int64 last_recv_time; - int64 last_send_time; - - char error_string[256]; - - NETPACKETCONSTRUCT construct; - - NETADDR peeraddr; - NETSOCKET socket; - NETSTATS stats; - - char buffer_memory[NET_CONN_BUFFERSIZE]; -} NETCONNECTION; - -typedef struct NETRECVINFO -{ - NETADDR addr; - NETCONNECTION *conn; - int current_chunk; - int client_id; - int valid; - NETPACKETCONSTRUCT data; - unsigned char buffer[NET_MAX_PACKETSIZE]; -} NETRECVINFO; - -/* */ - -/* connection functions */ -void conn_init(NETCONNECTION *conn, NETSOCKET socket); -int conn_connect(NETCONNECTION *conn, NETADDR *addr); -void conn_disconnect(NETCONNECTION *conn, const char *reason); -int conn_update(NETCONNECTION *conn); -int conn_feed(NETCONNECTION *conn, NETPACKETCONSTRUCT *packet, NETADDR *addr); -void conn_queue_chunk(NETCONNECTION *conn, int flags, int data_size, const void *data); -const char *conn_error(NETCONNECTION *conn); -void conn_want_resend(NETCONNECTION *conn); -int conn_flush(NETCONNECTION *conn); - -/* recvinfo functions */ -void recvinfo_clear(NETRECVINFO *info); -void recvinfo_start(NETRECVINFO *info, NETADDR *addr, NETCONNECTION *conn, int cid); -int recvinfo_fetch_chunk(NETRECVINFO *info, NETCHUNK *chunk); - -/* misc helper functions */ -/* The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not */ -int seq_in_backroom(int seq, int ack); -void send_controlmsg(NETSOCKET socket, NETADDR *addr, int ack, int controlmsg, const void *extra, int extra_size); -void send_packet_connless(NETSOCKET socket, NETADDR *addr, const void *data, int data_size); -void send_packet(NETSOCKET socket, NETADDR *addr, NETPACKETCONSTRUCT *packet); -int unpack_packet(unsigned char *buffer, int size, NETPACKETCONSTRUCT *packet); -unsigned char *pack_chunk_header(unsigned char *data, int flags, int size, int sequence); -unsigned char *unpack_chunk_header(unsigned char *data, NETCHUNKHEADER *header); diff --git a/src/engine/e_network_server.c b/src/engine/e_network_server.c deleted file mode 100644 index 8ec65504..00000000 --- a/src/engine/e_network_server.c +++ /dev/null @@ -1,484 +0,0 @@ -#include -#include "e_network.h" -#include "e_network_internal.h" - -typedef struct -{ - NETCONNECTION conn; -} NETSLOT; - -typedef struct NETBAN -{ - NETBANINFO info; - - /* hash list */ - struct NETBAN *hashnext; - struct NETBAN *hashprev; - - /* used or free list */ - struct NETBAN *next; - struct NETBAN *prev; -} NETBAN; - -#define MACRO_LIST_LINK_FIRST(object, first, prev, next) \ - { if(first) first->prev = object; \ - object->prev = (void*)0; \ - object->next = first; \ - first = object; } - -#define MACRO_LIST_LINK_AFTER(object, after, prev, next) \ - { object->prev = after; \ - object->next = after->next; \ - after->next = object; \ - if(object->next) \ - object->next->prev = object; \ - } - -#define MACRO_LIST_UNLINK(object, first, prev, next) \ - { if(object->next) object->next->prev = object->prev; \ - if(object->prev) object->prev->next = object->next; \ - else first = object->next; \ - object->next = 0; object->prev = 0; } - -#define MACRO_LIST_FIND(start, next, expression) \ - { while(start && !(expression)) start = start->next; } - -struct NETSERVER -{ - NETSOCKET socket; - NETSLOT slots[NET_MAX_CLIENTS]; - int max_clients; - - NETBAN *bans[256]; - NETBAN banpool[NET_SERVER_MAXBANS]; - NETBAN *banpool_firstfree; - NETBAN *banpool_firstused; - - NETFUNC_NEWCLIENT new_client; - NETFUNC_NEWCLIENT del_client; - void *user_ptr; - - NETRECVINFO recv; -}; - -NETSERVER *netserver_open(NETADDR bindaddr, int max_clients, int flags) -{ - int i; - NETSERVER *server; - NETSOCKET socket = net_udp_create(bindaddr); - if(socket == NETSOCKET_INVALID) - return 0; - - server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1); - mem_zero(server, sizeof(NETSERVER)); - server->socket = socket; - server->max_clients = max_clients; - if(server->max_clients > NET_MAX_CLIENTS) - server->max_clients = NET_MAX_CLIENTS; - if(server->max_clients < 1) - server->max_clients = 1; - - for(i = 0; i < NET_MAX_CLIENTS; i++) - conn_init(&server->slots[i].conn, server->socket); - - /* setup all pointers for bans */ - for(i = 1; i < NET_SERVER_MAXBANS-1; i++) - { - server->banpool[i].next = &server->banpool[i+1]; - server->banpool[i].prev = &server->banpool[i-1]; - } - - server->banpool[0].next = &server->banpool[1]; - server->banpool[NET_SERVER_MAXBANS-1].prev = &server->banpool[NET_SERVER_MAXBANS-2]; - server->banpool_firstfree = &server->banpool[0]; - - return server; -} - -int netserver_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user) -{ - s->new_client = new_client; - s->del_client = del_client; - s->user_ptr = user; - return 0; -} - -int netserver_max_clients(NETSERVER *s) -{ - return s->max_clients; -} - -int netserver_close(NETSERVER *s) -{ - /* TODO: implement me */ - return 0; -} - -int netserver_drop(NETSERVER *s, int client_id, const char *reason) -{ - /* TODO: insert lots of checks here */ - NETADDR addr; - netserver_client_addr(s, client_id, &addr); - - dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", - client_id, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], - reason - ); - conn_disconnect(&s->slots[client_id].conn, reason); - - if(s->del_client) - s->del_client(client_id, s->user_ptr); - - return 0; -} - -int netserver_ban_get(NETSERVER *s, int index, NETBANINFO *info) -{ - NETBAN *ban; - for(ban = s->banpool_firstused; ban && index; ban = ban->next, index--) - {} - - if(!ban) - return 0; - *info = ban->info; - return 1; -} - -int netserver_ban_num(NETSERVER *s) -{ - int count = 0; - NETBAN *ban; - for(ban = s->banpool_firstused; ban; ban = ban->next) - count++; - return count; -} - -static void netserver_ban_remove_by_object(NETSERVER *s, NETBAN *ban) -{ - int iphash = (ban->info.addr.ip[0]+ban->info.addr.ip[1]+ban->info.addr.ip[2]+ban->info.addr.ip[3])&0xff; - dbg_msg("netserver", "removing ban on %d.%d.%d.%d", - ban->info.addr.ip[0], ban->info.addr.ip[1], ban->info.addr.ip[2], ban->info.addr.ip[3]); - MACRO_LIST_UNLINK(ban, s->banpool_firstused, prev, next); - MACRO_LIST_UNLINK(ban, s->bans[iphash], hashprev, hashnext); - MACRO_LIST_LINK_FIRST(ban, s->banpool_firstfree, prev, next); -} - -int netserver_ban_remove(NETSERVER *s, NETADDR addr) -{ - int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff; - NETBAN *ban = s->bans[iphash]; - - MACRO_LIST_FIND(ban, hashnext, net_addr_comp(&ban->info.addr, &addr) == 0); - - if(ban) - { - netserver_ban_remove_by_object(s, ban); - return 0; - } - - return -1; -} - -int netserver_ban_add(NETSERVER *s, NETADDR addr, int seconds) -{ - int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff; - unsigned stamp = 0xffffffff; - NETBAN *ban; - - /* remove the port */ - addr.port = 0; - - if(seconds) - stamp = time_timestamp() + seconds; - - /* search to see if it already exists */ - ban = s->bans[iphash]; - MACRO_LIST_FIND(ban, hashnext, net_addr_comp(&ban->info.addr, &addr) == 0); - if(ban) - { - /* adjust the ban */ - ban->info.expires = stamp; - return 0; - } - - if(!s->banpool_firstfree) - return -1; - - /* fetch and clear the new ban */ - ban = s->banpool_firstfree; - MACRO_LIST_UNLINK(ban, s->banpool_firstfree, prev, next); - - /* setup the ban info */ - ban->info.expires = stamp; - ban->info.addr = addr; - - /* add it to the ban hash */ - MACRO_LIST_LINK_FIRST(ban, s->bans[iphash], hashprev, hashnext); - - /* insert it into the used list */ - { - if(s->banpool_firstused) - { - NETBAN *insert_after = s->banpool_firstused; - MACRO_LIST_FIND(insert_after, next, stamp < insert_after->info.expires); - - if(insert_after) - insert_after = insert_after->prev; - else - { - /* add to last */ - insert_after = s->banpool_firstused; - while(insert_after->next) - insert_after = insert_after->next; - } - - if(insert_after) - { - MACRO_LIST_LINK_AFTER(ban, insert_after, prev, next); - } - else - { - MACRO_LIST_LINK_FIRST(ban, s->banpool_firstused, prev, next); - } - } - else - { - MACRO_LIST_LINK_FIRST(ban, s->banpool_firstused, prev, next); - } - } - - /* drop banned clients */ - { - char buf[128]; - int i; - NETADDR banaddr; - - if(seconds) - str_format(buf, sizeof(buf), "you have been banned for %d minutes", seconds/60); - else - str_format(buf, sizeof(buf), "you have been banned for life"); - - for(i = 0; i < s->max_clients; i++) - { - banaddr = s->slots[i].conn.peeraddr; - banaddr.port = 0; - - if(net_addr_comp(&addr, &banaddr) == 0) - netserver_drop(s, i, buf); - } - } - return 0; -} - -int netserver_update(NETSERVER *s) -{ - unsigned now = time_timestamp(); - - int i; - for(i = 0; i < s->max_clients; i++) - { - conn_update(&s->slots[i].conn); - if(s->slots[i].conn.state == NET_CONNSTATE_ERROR) - netserver_drop(s, i, conn_error(&s->slots[i].conn)); - } - - /* remove expired bans */ - while(s->banpool_firstused && s->banpool_firstused->info.expires < now) - { - NETBAN *ban = s->banpool_firstused; - netserver_ban_remove_by_object(s, ban); - } - - (void)now; - - return 0; -} - -/* - TODO: chopp up this function into smaller working parts -*/ -int netserver_recv(NETSERVER *s, NETCHUNK *chunk) -{ - unsigned now = time_timestamp(); - - while(1) - { - NETADDR addr; - int i, bytes, found; - - /* check for a chunk */ - if(recvinfo_fetch_chunk(&s->recv, chunk)) - return 1; - - /* TODO: empty the recvinfo */ - bytes = net_udp_recv(s->socket, &addr, s->recv.buffer, NET_MAX_PACKETSIZE); - - /* no more packets for now */ - if(bytes <= 0) - break; - - if(unpack_packet(s->recv.buffer, bytes, &s->recv.data) == 0) - { - NETBAN *ban = 0; - NETADDR banaddr = addr; - int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff; - found = 0; - banaddr.port = 0; - - /* search a ban */ - for(ban = s->bans[iphash]; ban; ban = ban->hashnext) - { - if(net_addr_comp(&ban->info.addr, &banaddr) == 0) - break; - } - - /* check if we just should drop the packet */ - if(ban) - { - // banned, reply with a message - char banstr[128]; - if(ban->info.expires) - { - int mins = ((ban->info.expires - now)+59)/60; - if(mins == 1) - str_format(banstr, sizeof(banstr), "banned for %d minute", mins); - else - str_format(banstr, sizeof(banstr), "banned for %d minutes", mins); - } - else - str_format(banstr, sizeof(banstr), "banned for life"); - send_controlmsg(s->socket, &addr, 0, NET_CTRLMSG_CLOSE, banstr, str_length(banstr)+1); - continue; - } - - if(s->recv.data.flags&NET_PACKETFLAG_CONNLESS) - { - chunk->flags = NETSENDFLAG_CONNLESS; - chunk->client_id = -1; - chunk->address = addr; - chunk->data_size = s->recv.data.data_size; - chunk->data = s->recv.data.chunk_data; - return 1; - } - else - { - /* TODO: check size here */ - if(s->recv.data.flags&NET_PACKETFLAG_CONTROL && s->recv.data.chunk_data[0] == NET_CTRLMSG_CONNECT) - { - found = 0; - - /* check if we already got this client */ - for(i = 0; i < s->max_clients; i++) - { - if(s->slots[i].conn.state != NET_CONNSTATE_OFFLINE && - net_addr_comp(&s->slots[i].conn.peeraddr, &addr) == 0) - { - found = 1; /* silent ignore.. we got this client already */ - break; - } - } - - /* client that wants to connect */ - if(!found) - { - for(i = 0; i < s->max_clients; i++) - { - if(s->slots[i].conn.state == NET_CONNSTATE_OFFLINE) - { - found = 1; - conn_feed(&s->slots[i].conn, &s->recv.data, &addr); - if(s->new_client) - s->new_client(i, s->user_ptr); - break; - } - } - - if(!found) - { - const char fullmsg[] = "server is full"; - send_controlmsg(s->socket, &addr, 0, NET_CTRLMSG_CLOSE, fullmsg, sizeof(fullmsg)); - } - } - } - else - { - /* normal packet, find matching slot */ - for(i = 0; i < s->max_clients; i++) - { - if(net_addr_comp(&s->slots[i].conn.peeraddr, &addr) == 0) - { - if(conn_feed(&s->slots[i].conn, &s->recv.data, &addr)) - { - if(s->recv.data.data_size) - recvinfo_start(&s->recv, &addr, &s->slots[i].conn, i); - } - } - } - } - } - } - } - return 0; -} - -int netserver_send(NETSERVER *s, NETCHUNK *chunk) -{ - if(chunk->data_size >= NET_MAX_PAYLOAD) - { - dbg_msg("netserver", "packet payload too big. %d. dropping packet", chunk->data_size); - return -1; - } - - if(chunk->flags&NETSENDFLAG_CONNLESS) - { - /* send connectionless packet */ - send_packet_connless(s->socket, &chunk->address, chunk->data, chunk->data_size); - } - else - { - int f = 0; - dbg_assert(chunk->client_id >= 0, "errornous client id"); - dbg_assert(chunk->client_id < s->max_clients, "errornous client id"); - - if(chunk->flags&NETSENDFLAG_VITAL) - f = NET_CHUNKFLAG_VITAL; - - conn_queue_chunk(&s->slots[chunk->client_id].conn, f, chunk->data_size, chunk->data); - - if(chunk->flags&NETSENDFLAG_FLUSH) - conn_flush(&s->slots[chunk->client_id].conn); - } - return 0; -} - -void netserver_stats(NETSERVER *s, NETSTATS *stats) -{ - int num_stats = sizeof(NETSTATS)/sizeof(int); - int *istats = (int *)stats; - int c, i; - - mem_zero(stats, sizeof(NETSTATS)); - - for(c = 0; c < s->max_clients; c++) - { - if(s->slots[c].conn.state != NET_CONNSTATE_OFFLINE) - { - int *sstats = (int *)(&(s->slots[c].conn.stats)); - for(i = 0; i < num_stats; i++) - istats[i] += sstats[i]; - } - } -} - -NETSOCKET netserver_socket(NETSERVER *s) -{ - return s->socket; -} - - -int netserver_client_addr(NETSERVER *s, int client_id, NETADDR *addr) -{ - *addr = s->slots[client_id].conn.peeraddr; - return 1; -} diff --git a/src/engine/e_network_server.cpp b/src/engine/e_network_server.cpp new file mode 100644 index 00000000..995290ef --- /dev/null +++ b/src/engine/e_network_server.cpp @@ -0,0 +1,408 @@ +#include +#include "e_network.h" + +#define MACRO_LIST_LINK_FIRST(object, first, prev, next) \ + { if(first) first->prev = object; \ + object->prev = (struct CBan *)0; \ + object->next = first; \ + first = object; } + +#define MACRO_LIST_LINK_AFTER(object, after, prev, next) \ + { object->prev = after; \ + object->next = after->next; \ + after->next = object; \ + if(object->next) \ + object->next->prev = object; \ + } + +#define MACRO_LIST_UNLINK(object, first, prev, next) \ + { if(object->next) object->next->prev = object->prev; \ + if(object->prev) object->prev->next = object->next; \ + else first = object->next; \ + object->next = 0; object->prev = 0; } + +#define MACRO_LIST_FIND(start, next, expression) \ + { while(start && !(expression)) start = start->next; } + +bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags) +{ + // zero out the whole structure + mem_zero(this, sizeof(*this)); + + // open socket + m_Socket = net_udp_create(BindAddr); + if(m_Socket == NETSOCKET_INVALID) + return false; + + // clamp clients + m_MaxClients = MaxClients; + if(m_MaxClients > NET_MAX_CLIENTS) + m_MaxClients = NET_MAX_CLIENTS; + if(m_MaxClients < 1) + m_MaxClients = 1; + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aSlots[i].m_Connection.Init(m_Socket); + + /* setup all pointers for bans */ + for(int i = 1; i < NET_SERVER_MAXBANS-1; i++) + { + m_BanPool[i].m_pNext = &m_BanPool[i+1]; + m_BanPool[i].m_pPrev = &m_BanPool[i-1]; + } + + m_BanPool[0].m_pNext = &m_BanPool[1]; + m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2]; + m_BanPool_FirstFree = &m_BanPool[0]; + + return true; +} + +int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; + return 0; +} + +int CNetServer::Close() +{ + /* TODO: implement me */ + return 0; +} + +int CNetServer::Drop(int ClientID, const char *pReason) +{ + /* TODO: insert lots of checks here */ + NETADDR Addr = ClientAddr(ClientID); + + dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", + ClientID, + Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], + pReason + ); + + m_aSlots[ClientID].m_Connection.Disconnect(pReason); + + if(m_pfnDelClient) + m_pfnDelClient(ClientID, m_UserPtr); + + return 0; +} + +int CNetServer::BanGet(int Index, CBanInfo *pInfo) +{ + CBan *pBan; + for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--) + {} + + if(!pBan) + return 0; + *pInfo = pBan->m_Info; + return 1; +} + +int CNetServer::BanNum() +{ + int Count = 0; + CBan *pBan; + for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext) + Count++; + return Count; +} + +void CNetServer::BanRemoveByObject(CBan *pBan) +{ + int iphash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff; + dbg_msg("netserver", "removing ban on %d.%d.%d.%d", + pBan->m_Info.m_Addr.ip[0], pBan->m_Info.m_Addr.ip[1], pBan->m_Info.m_Addr.ip[2], pBan->m_Info.m_Addr.ip[3]); + MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + MACRO_LIST_UNLINK(pBan, m_aBans[iphash], m_pHashPrev, m_pHashNext); + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); +} + +int CNetServer::BanRemove(NETADDR Addr) +{ + int iphash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + CBan *pBan = m_aBans[iphash]; + + MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); + + if(pBan) + { + BanRemoveByObject(pBan); + return 0; + } + + return -1; +} + +int CNetServer::BanAdd(NETADDR Addr, int Seconds) +{ + int iphash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + int Stamp = -1; + CBan *pBan; + + /* remove the port */ + Addr.port = 0; + + if(Seconds) + Stamp = time_timestamp() + Seconds; + + /* search to see if it already exists */ + pBan = m_aBans[iphash]; + MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); + if(pBan) + { + /* adjust the ban */ + pBan->m_Info.m_Expires = Stamp; + return 0; + } + + if(!m_BanPool_FirstFree) + return -1; + + /* fetch and clear the new ban */ + pBan = m_BanPool_FirstFree; + MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); + + /* setup the ban info */ + pBan->m_Info.m_Expires = Stamp; + pBan->m_Info.m_Addr = Addr; + + /* add it to the ban hash */ + MACRO_LIST_LINK_FIRST(pBan, m_aBans[iphash], m_pHashPrev, m_pHashNext); + + /* insert it into the used list */ + { + if(m_BanPool_FirstUsed) + { + CBan *pInsertAfter = m_BanPool_FirstUsed; + MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires); + + if(pInsertAfter) + pInsertAfter = pInsertAfter->m_pPrev; + else + { + /* add to last */ + pInsertAfter = m_BanPool_FirstUsed; + while(pInsertAfter->m_pNext) + pInsertAfter = pInsertAfter->m_pNext; + } + + if(pInsertAfter) + { + MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext); + } + else + { + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + } + } + else + { + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + } + } + + /* drop banned clients */ + { + char Buf[128]; + NETADDR BanAddr; + + if(Seconds) + str_format(Buf, sizeof(Buf), "you have been banned for %d minutes", Seconds/60); + else + str_format(Buf, sizeof(Buf), "you have been banned for life"); + + for(int i = 0; i < MaxClients(); i++) + { + BanAddr = m_aSlots[i].m_Connection.PeerAddress(); + BanAddr.port = 0; + + if(net_addr_comp(&Addr, &BanAddr) == 0) + Drop(i, Buf); + } + } + return 0; +} + +int CNetServer::Update() +{ + int Now = time_timestamp(); + for(int i = 0; i < MaxClients(); i++) + { + m_aSlots[i].m_Connection.Update(); + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) + Drop(i, m_aSlots[i].m_Connection.ErrorString()); + } + + /* remove expired bans */ + while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now) + { + CBan *pBan = m_BanPool_FirstUsed; + BanRemoveByObject(pBan); + } + + return 0; +} + +/* + TODO: chopp up this function into smaller working parts +*/ +int CNetServer::Recv(CNetChunk *pChunk) +{ + unsigned now = time_timestamp(); + + while(1) + { + NETADDR Addr; + + /* check for a chunk */ + if(m_RecvUnpacker.FetchChunk(pChunk)) + return 1; + + /* TODO: empty the recvinfo */ + int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); + + /* no more packets for now */ + if(Bytes <= 0) + break; + + if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0) + { + CBan *pBan = 0; + NETADDR BanAddr = Addr; + int iphash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff; + int Found = 0; + BanAddr.port = 0; + + /* search a ban */ + for(pBan = m_aBans[iphash]; pBan; pBan = pBan->m_pHashNext) + { + if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0) + break; + } + + /* check if we just should drop the packet */ + if(pBan) + { + // banned, reply with a message + char BanStr[128]; + if(pBan->m_Info.m_Expires) + { + int Mins = ((pBan->m_Info.m_Expires - now)+59)/60; + if(Mins == 1) + str_format(BanStr, sizeof(BanStr), "banned for %d minute", Mins); + else + str_format(BanStr, sizeof(BanStr), "banned for %d minutes", Mins); + } + else + str_format(BanStr, sizeof(BanStr), "banned for life"); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1); + continue; + } + + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS) + { + pChunk->m_Flags = NETSENDFLAG_CONNLESS; + pChunk->m_ClientID = -1; + pChunk->m_Address = Addr; + pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize; + pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData; + return 1; + } + else + { + /* TODO: check size here */ + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT) + { + Found = 0; + + /* check if we already got this client */ + for(int i = 0; i < MaxClients(); i++) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE && + net_addr_comp(&PeerAddr, &Addr) == 0) + { + Found = 1; /* silent ignore.. we got this client already */ + break; + } + } + + /* client that wants to connect */ + if(!Found) + { + for(int i = 0; i < MaxClients(); i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + { + Found = 1; + m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr); + if(m_pfnNewClient) + m_pfnNewClient(i, m_UserPtr); + break; + } + } + + if(!Found) + { + const char FullMsg[] = "server is full"; + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg)); + } + } + } + else + { + /* normal packet, find matching slot */ + for(int i = 0; i < MaxClients(); i++) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(net_addr_comp(&PeerAddr, &Addr) == 0) + { + if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + { + if(m_RecvUnpacker.m_Data.m_DataSize) + m_RecvUnpacker.Start(&Addr, &m_aSlots[i].m_Connection, i); + } + } + } + } + } + } + } + return 0; +} + +int CNetServer::Send(CNetChunk *pChunk) +{ + if(pChunk->m_DataSize >= NET_MAX_PAYLOAD) + { + dbg_msg("netserver", "packet payload too big. %d. dropping packet", pChunk->m_DataSize); + return -1; + } + + if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) + { + /* send connectionless packet */ + CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); + } + else + { + int Flags = 0; + dbg_assert(pChunk->m_ClientID >= 0, "errornous client id"); + dbg_assert(pChunk->m_ClientID < MaxClients(), "errornous client id"); + + if(pChunk->m_Flags&NETSENDFLAG_VITAL) + Flags = NET_CHUNKFLAG_VITAL; + + m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData); + + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_aSlots[pChunk->m_ClientID].m_Connection.Flush(); + } + return 0; +} + diff --git a/src/engine/e_packer.c b/src/engine/e_packer.c deleted file mode 100644 index aee08aed..00000000 --- a/src/engine/e_packer.c +++ /dev/null @@ -1,213 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include /* rand() */ -#include - -#include "e_packer.h" -#include "e_compression.h" -#include "e_engine.h" -#include "e_config.h" - -/* useful for debugging */ -#if 0 - #define packing_error(p) p->error = 1; dbg_break() -#else - #define packing_error(p) p->error = 1 -#endif - -static int stress_get_int() -{ - static const int nasty[] = {-1, 0, 1, 66000, -66000, (-1<<31), 0x7fffffff}; - if(rand()&1) - return rand(); - return nasty[rand()%6]; -} - -static const char *stress_get_string(int *size) -{ - static char noise[1024]; - int i; - int s; - s = (rand()%1024)-1; - for(i = 0; i < s; i++) - noise[i] = (rand()%254)+1; - noise[s] = 0; - if(size) - *size = s; - return noise; -} - - -static int stress_prob(float probability) -{ - if(!config.dbg_stress_network) - return 0; - if(rand()/(float)RAND_MAX < probability) - return 1; - return 0; -} - - -void packer_reset(PACKER *p) -{ - p->error = 0; - p->current = p->buffer; - p->end = p->current + PACKER_BUFFER_SIZE; -} - -void packer_add_int(PACKER *p, int i) -{ - if(p->error) - return; - - if(stress_prob(0.025f)) - i = stress_get_int(); - - /* make sure that we have space enough */ - if(p->end - p->current < 6) - { - dbg_break(); - p->error = 1; - } - else - p->current = vint_pack(p->current, i); -} - -void packer_add_string(PACKER *p, const char *str, int limit) -{ - if(p->error) - return; - - if(stress_prob(0.1f)) - { - str = stress_get_string(0); - limit = 0; - } - - /* */ - if(limit > 0) - { - while(*str && limit != 0) - { - *p->current++ = *str++; - limit--; - - if(p->current >= p->end) - { - packing_error(p); - break; - } - } - *p->current++ = 0; - } - else - { - while(*str) - { - *p->current++ = *str++; - - if(p->current >= p->end) - { - packing_error(p); - break; - } - } - *p->current++ = 0; - } -} - -void packer_add_raw(PACKER *p, const unsigned char *data, int size) -{ - if(p->error) - return; - - if(p->current+size >= p->end) - { - packing_error(p); - return; - } - - while(size) - { - *p->current++ = *data++; - size--; - } -} - -int packer_size(PACKER *p) -{ - return (const unsigned char *)p->current-(const unsigned char *)p->buffer; -} - -const unsigned char *packer_data(PACKER *p) -{ - return (const unsigned char *)p->buffer; -} - -void unpacker_reset(UNPACKER *p, const unsigned char *data, int size) -{ - p->error = 0; - p->start = data; - p->end = p->start + size; - p->current = p->start; -} - -int unpacker_get_int(UNPACKER *p) -{ - int i; - if(p->error) - return 0; - if(p->current >= p->end) - { - packing_error(p); - return 0; - } - - p->current = vint_unpack(p->current, &i); - if(p->current > p->end) - { - packing_error(p); - return 0; - } - return i; -} - -const char *unpacker_get_string(UNPACKER *p) -{ - char *ptr; - if(p->error || p->current >= p->end) - return ""; - - ptr = (char *)p->current; - while(*p->current) /* skip the string */ - { - p->current++; - if(p->current == p->end) - { - packing_error(p); - return ""; - } - } - p->current++; - - /* sanitize all strings */ - str_sanitize(ptr); - return ptr; -} - -const unsigned char *unpacker_get_raw(UNPACKER *p, int size) -{ - const unsigned char *ptr = p->current; - if(p->error) - return 0; - - /* check for nasty sizes */ - if(size < 0 || p->current+size > p->end) - { - packing_error(p); - return 0; - } - - /* "unpack" the data */ - p->current += size; - return ptr; -} diff --git a/src/engine/e_packer.cpp b/src/engine/e_packer.cpp new file mode 100644 index 00000000..0d8aeab3 --- /dev/null +++ b/src/engine/e_packer.cpp @@ -0,0 +1,155 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include /* rand() */ +#include + +#include "e_packer.h" +#include "e_compression.h" +#include "e_engine.h" +#include "e_config.h" + +void CPacker::Reset() +{ + m_Error = 0; + m_pCurrent = m_aBuffer; + m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE; +} + +void CPacker::AddInt(int i) +{ + if(m_Error) + return; + + /* make sure that we have space enough */ + if(m_pEnd - m_pCurrent < 6) + { + dbg_break(); + m_Error = 1; + } + else + m_pCurrent = vint_pack(m_pCurrent, i); +} + +void CPacker::AddString(const char *pStr, int Limit) +{ + if(m_Error) + return; + + /* */ + if(Limit > 0) + { + while(*pStr && Limit != 0) + { + *m_pCurrent++ = *pStr++; + Limit--; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + break; + } + } + *m_pCurrent++ = 0; + } + else + { + while(*pStr) + { + *m_pCurrent++ = *pStr++; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + break; + } + } + *m_pCurrent++ = 0; + } +} + +void CPacker::AddRaw(const unsigned char *pData, int Size) +{ + if(m_Error) + return; + + if(m_pCurrent+Size >= m_pEnd) + { + m_Error = 1; + return; + } + + while(Size) + { + *m_pCurrent++ = *pData++; + Size--; + } +} + + +void CUnpacker::Reset(const unsigned char *pData, int Size) +{ + m_Error = 0; + m_pStart = pData; + m_pEnd = m_pStart + Size; + m_pCurrent = m_pStart; +} + +int CUnpacker::GetInt() +{ + if(m_Error) + return 0; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + return 0; + } + + int i; + m_pCurrent = vint_unpack(m_pCurrent, &i); + if(m_pCurrent > m_pEnd) + { + m_Error = 1; + return 0; + } + return i; +} + +const char *CUnpacker::GetString() +{ + if(m_Error || m_pCurrent >= m_pEnd) + return ""; + + char *pPtr = (char *)m_pCurrent; + while(*m_pCurrent) /* skip the string */ + { + m_pCurrent++; + if(m_pCurrent == m_pEnd) + { + m_Error = 1;; + return ""; + } + } + m_pCurrent++; + + /* sanitize all strings */ + str_sanitize(pPtr); + return pPtr; +} + +const unsigned char *CUnpacker::GetRaw(int Size) +{ + const unsigned char *pPtr = m_pCurrent; + if(m_Error) + return 0; + + /* check for nasty sizes */ + if(Size < 0 || m_pCurrent+Size > m_pEnd) + { + m_Error = 1; + return 0; + } + + /* "unpack" the data */ + m_pCurrent += Size; + return pPtr; +} diff --git a/src/engine/e_packer.h b/src/engine/e_packer.h index 71b99ff7..5dc80e7a 100644 --- a/src/engine/e_packer.h +++ b/src/engine/e_packer.h @@ -1,35 +1,37 @@ /* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -enum +class CPacker { - PACKER_BUFFER_SIZE=1024*2 -}; + enum + { + PACKER_BUFFER_SIZE=1024*2 + }; -typedef struct -{ + unsigned char m_aBuffer[PACKER_BUFFER_SIZE]; + unsigned char *m_pCurrent; + unsigned char *m_pEnd; + int m_Error; +public: + void Reset(); + void AddInt(int i); + void AddString(const char *pStr, int Limit); + void AddRaw(const unsigned char *pData, int Size); - unsigned char buffer[PACKER_BUFFER_SIZE]; - unsigned char *current; - unsigned char *end; - int error; -} PACKER; + int Size() const { return (int)(m_pCurrent-m_aBuffer); } + const unsigned char *Data() const { return m_aBuffer; } + bool Error() const { return m_Error; } +}; -typedef struct +class CUnpacker { - const unsigned char *current; - const unsigned char *start; - const unsigned char *end; - int error; -} UNPACKER; - -void packer_reset(PACKER *p); -void packer_add_int(PACKER *p, int i); -void packer_add_string(PACKER *p, const char *str, int limit); -void packer_add_raw(PACKER *p, const unsigned char *data, int size); -int packer_size(PACKER *p); -const unsigned char *packer_data(PACKER *p); - -void unpacker_reset(UNPACKER *p, const unsigned char *data, int size); -int unpacker_get_int(UNPACKER *p); -const char *unpacker_get_string(UNPACKER *p); -const unsigned char *unpacker_get_raw(UNPACKER *p, int size); + const unsigned char *m_pStart; + const unsigned char *m_pCurrent; + const unsigned char *m_pEnd; + int m_Error; +public: + void Reset(const unsigned char *pData, int Size); + int GetInt(); + const char *GetString(); + const unsigned char *GetRaw(int Size); + bool Error() const { return m_Error; } +}; diff --git a/src/engine/e_ringbuffer.c b/src/engine/e_ringbuffer.c deleted file mode 100644 index 39dfa3e9..00000000 --- a/src/engine/e_ringbuffer.c +++ /dev/null @@ -1,362 +0,0 @@ -#include - -#include "e_ringbuffer.h" - -typedef struct RBITEM -{ - struct RBITEM *prev; - struct RBITEM *next; - int free; - int size; -} RBITEM; - -/* - -*/ -struct RINGBUFFER -{ - RBITEM *produce; - RBITEM *consume; - - RBITEM *first; - RBITEM *last; - void *memory; - int size; - int flags; -}; - -RINGBUFFER *ringbuf_init(void *memory, int size, int flags) -{ - RINGBUFFER *rb = (RINGBUFFER *)memory; - mem_zero(memory, size); - - rb->memory = rb+1; - rb->size = (size-sizeof(RINGBUFFER))/sizeof(RBITEM)*sizeof(RBITEM); - rb->first = (RBITEM *)rb->memory; - rb->first->free = 1; - rb->first->size = rb->size; - rb->last = rb->first; - rb->produce = rb->first; - rb->consume = rb->first; - - rb->flags = flags; - - return rb; -} - -static RBITEM *ringbuf_nextblock(RINGBUFFER *rb, RBITEM *item) -{ - if(item->next) - return item->next; - return rb->first; -} - -static RBITEM *ringbuf_prevblock(RINGBUFFER *rb, RBITEM *item) -{ - if(item->prev) - return item->prev; - return rb->last; -} - -static RBITEM *ringbuf_mergeback(RINGBUFFER *rb, RBITEM *item) -{ - /* make sure that this block and previous block is free */ - if(!item->free || !item->prev || !item->prev->free) - return item; - - /* merge the blocks */ - item->prev->size += item->size; - item->prev->next = item->next; - - /* fixup pointers */ - if(item->next) - item->next->prev = item->prev; - else - rb->last = item->prev; - - if(item == rb->produce) - rb->produce = item->prev; - - if(item == rb->consume) - rb->consume = item->prev; - - /* return the current block */ - return item->prev; -} - -int ringbuf_popfirst(RINGBUFFER *rb) -{ - if(rb->consume->free) - return 0; - - /* set the free flag */ - rb->consume->free = 1; - - /* previous block is also free, merge them */ - rb->consume = ringbuf_mergeback(rb, rb->consume); - - /* advance the consume pointer */ - rb->consume = ringbuf_nextblock(rb, rb->consume); - while(rb->consume->free && rb->consume != rb->produce) - { - rb->consume = ringbuf_mergeback(rb, rb->consume); - rb->consume = ringbuf_nextblock(rb, rb->consume); - } - - /* in the case that we have catched up with the produce pointer */ - /* we might stand on a free block so merge em */ - ringbuf_mergeback(rb, rb->consume); - return 1; -} - -void *ringbuf_allocate(RINGBUFFER *rb, int size) -{ - int wanted_size = (size+sizeof(RBITEM)+sizeof(RBITEM)-1)/sizeof(RBITEM)*sizeof(RBITEM); - RBITEM *block = 0; - - /* check if we even can fit this block */ - if(wanted_size > rb->size) - return 0; - - while(1) - { - /* check for space */ - if(rb->produce->free) - { - if(rb->produce->size >= wanted_size) - block = rb->produce; - else - { - /* wrap around to try to find a block */ - if(rb->first->free && rb->first->size >= wanted_size) - block = rb->first; - } - } - - if(block) - break; - else - { - /* we have no block, check our policy and see what todo */ - if(rb->flags&RINGBUF_FLAG_RECYCLE) - { - if(!ringbuf_popfirst(rb)) - return 0; - } - else - return 0; - } - } - - /* okey, we have our block */ - - /* split the block if needed */ - if(block->size > wanted_size) - { - RBITEM *new_item = (RBITEM *)((char *)block + wanted_size); - new_item->prev = block; - new_item->next = block->next; - if(new_item->next) - new_item->next->prev = new_item; - block->next = new_item; - - new_item->free = 1; - new_item->size = block->size - wanted_size; - block->size = wanted_size; - - if(!new_item->next) - rb->last = new_item; - } - - - /* set next block */ - rb->produce = ringbuf_nextblock(rb, block); - - /* set as used and return the item pointer */ - block->free = 0; - return block+1; -} - -void *ringbuf_prev(RINGBUFFER *rb, void *current) -{ - RBITEM *item = ((RBITEM *)current) - 1; - - while(1) - { - item = ringbuf_prevblock(rb, item); - if(item == rb->produce) - return 0; - if(!item->free) - return item+1; - } -} - -void *ringbuf_next(RINGBUFFER *rb, void *current) -{ - RBITEM *item = ((RBITEM *)current) - 1; - - while(1) - { - item = ringbuf_nextblock(rb, item); - if(item == rb->produce) - return 0; - if(!item->free) - return item+1; - } -} - -void *ringbuf_first(RINGBUFFER *rb) -{ - if(rb->consume->free) - return 0; - return rb->consume+1; -} - -void *ringbuf_last(RINGBUFFER *rb) -{ - if(!rb->produce->free) - return rb->produce+1; - return ringbuf_prev(rb, rb->produce+1); -} - - -/* debugging and testing stuff */ - -static void ringbuf_debugdump(RINGBUFFER *rb, const char *msg) -{ - RBITEM *cur = rb->first; - - dbg_msg("ringbuf", "-- dumping --"); - - while(cur) - { - char flags[4] = " "; - if(cur->free) - flags[0] = 'F'; - if(cur == rb->consume) - flags[1] = '>'; - if(cur == rb->produce) - flags[2] = '<'; - dbg_msg("ringbuf", "%s %d", flags, cur->size); - cur = cur->next; - } - - dbg_msg("ringbuf", "-- --"); - - if(msg) - dbg_assert(0, msg); -} - - - -static void ringbuf_validate(RINGBUFFER *rb) -{ - RBITEM *prev = 0; - RBITEM *cur = rb->first; - int freechunks = 0; - int got_consume = 0; - int got_produce = 0; - - while(cur) - { - - if(cur->free) - freechunks++; - - if(freechunks > 2) ringbuf_debugdump(rb, "too many free chunks"); - if(prev && prev->free && cur->free) ringbuf_debugdump(rb, "two free chunks next to each other"); - if(cur == rb->consume) got_consume = 1; - if(cur == rb->produce) got_produce = 1; - - dbg_assert(cur->prev == prev, "prev pointers doesn't match"); - dbg_assert(!prev || prev->next == cur, "next pointers doesn't match"); - dbg_assert(cur->next || cur == rb->last, "last isn't last"); - - prev = cur; - cur = cur->next; - } - - if(!got_consume) ringbuf_debugdump(rb, "consume pointer isn't pointing at a valid block"); - if(!got_produce) ringbuf_debugdump(rb, "produce pointer isn't pointing at a valid block"); -} - -int ringbuf_test() -{ - char buffer[256]; - RINGBUFFER *rb; - int i, s, k, m; - int count; - int testcount = 0; - - void *item; - int before; - - - for(k = 100; k < sizeof(buffer); k++) - { - if((k%10) == 0) - dbg_msg("ringbuf", "testing at %d", k); - rb = ringbuf_init(buffer, k, 0); - count = 0; - - for(s = 1; s < sizeof(buffer); s++) - { - for(i = 0; i < k*8; i++, testcount++) - { - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - before = m; - - if(ringbuf_allocate(rb, s)) - { - count++; - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - if(before+1 != m) ringbuf_debugdump(rb, "alloc error"); - if(count != m) ringbuf_debugdump(rb, "count error"); - } - else - { - if(ringbuf_popfirst(rb)) - { - count--; - - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - if(before-1 != m) dbg_msg("", "popping error %d %d", before, m); - if(count != m) ringbuf_debugdump(rb, "count error"); - } - } - - /* remove an item every 10 */ - if((i%10) == 0) - { - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - before = m; - - if(ringbuf_popfirst(rb)) - { - count--; - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - if(before-1 != m) dbg_msg("", "popping error %d %d", before, m); - dbg_assert(count == m, "count error"); - } - } - - /* count items */ - for(m = 0, item = ringbuf_first(rb); item; item = ringbuf_next(rb, item), m++); - if(m != count) ringbuf_debugdump(rb, "wrong number of items during forward count"); - - for(m = 0, item = ringbuf_last(rb); item; item = ringbuf_prev(rb, item), m++); - if(m != count) ringbuf_debugdump(rb, "wrong number of items during backward count"); - - ringbuf_validate(rb); - } - - /* empty the ring buffer */ - while(ringbuf_first(rb)) - ringbuf_popfirst(rb); - ringbuf_validate(rb); - count = 0; - } - } - - return 0; -} diff --git a/src/engine/e_ringbuffer.cpp b/src/engine/e_ringbuffer.cpp new file mode 100644 index 00000000..eb8a8af4 --- /dev/null +++ b/src/engine/e_ringbuffer.cpp @@ -0,0 +1,194 @@ +#include + +#include "e_ringbuffer.h" + +CRingBufferBase::CItem *CRingBufferBase::NextBlock(CItem *pItem) +{ + if(pItem->m_pNext) + return pItem->m_pNext; + return m_pFirst; +} + +CRingBufferBase::CItem *CRingBufferBase::PrevBlock(CItem *pItem) +{ + if(pItem->m_pPrev) + return pItem->m_pPrev; + return m_pLast; +} + +CRingBufferBase::CItem *CRingBufferBase::MergeBack(CItem *pItem) +{ + /* make sure that this block and previous block is free */ + if(!pItem->m_Free || !pItem->m_pPrev || !pItem->m_pPrev->m_Free) + return pItem; + + /* merge the blocks */ + pItem->m_pPrev->m_Size += pItem->m_Size; + pItem->m_pPrev->m_pNext = pItem->m_pNext; + + /* fixup pointers */ + if(pItem->m_pNext) + pItem->m_pNext->m_pPrev = pItem->m_pPrev; + else + m_pLast = pItem->m_pPrev; + + if(pItem == m_pProduce) + m_pProduce = pItem->m_pPrev; + + if(pItem == m_pConsume) + m_pConsume = pItem->m_pPrev; + + /* return the current block */ + return pItem->m_pPrev; +} + +void CRingBufferBase::Init(void *pMemory, int Size, int Flags) +{ + mem_zero(pMemory, Size); + m_Size = (Size)/sizeof(CItem)*sizeof(CItem); + m_pFirst = (CItem *)pMemory; + m_pFirst->m_Free = 1; + m_pFirst->m_Size = m_Size; + m_pLast = m_pFirst; + m_pProduce = m_pFirst; + m_pConsume = m_pFirst; + m_Flags = Flags; + +} + +void *CRingBufferBase::Allocate(int Size) +{ + int WantedSize = (Size+sizeof(CItem)+sizeof(CItem)-1)/sizeof(CItem)*sizeof(CItem); + CItem *pBlock = 0; + + /* check if we even can fit this block */ + if(WantedSize > m_Size) + return 0; + + while(1) + { + /* check for space */ + if(m_pProduce->m_Free) + { + if(m_pProduce->m_Size >= WantedSize) + pBlock = m_pProduce; + else + { + /* wrap around to try to find a block */ + if(m_pFirst->m_Free && m_pFirst->m_Size >= WantedSize) + pBlock = m_pFirst; + } + } + + if(pBlock) + break; + else + { + /* we have no block, check our policy and see what todo */ + if(m_Flags&FLAG_RECYCLE) + { + if(!PopFirst()) + return 0; + } + else + return 0; + } + } + + /* okey, we have our block */ + + /* split the block if needed */ + if(pBlock->m_Size > WantedSize) + { + CItem *pNewItem = (CItem *)((char *)pBlock + WantedSize); + pNewItem->m_pPrev = pBlock; + pNewItem->m_pNext = pBlock->m_pNext; + if(pNewItem->m_pNext) + pNewItem->m_pNext->m_pPrev = pNewItem; + pBlock->m_pNext = pNewItem; + + pNewItem->m_Free = 1; + pNewItem->m_Size = pBlock->m_Size - WantedSize; + pBlock->m_Size = WantedSize; + + if(!pNewItem->m_pNext) + m_pLast = pNewItem; + } + + + /* set next block */ + m_pProduce = NextBlock(pBlock); + + /* set as used and return the item pointer */ + pBlock->m_Free = 0; + return (void *)(pBlock+1); +} + +int CRingBufferBase::PopFirst() +{ + if(m_pConsume->m_Free) + return 0; + + /* set the free flag */ + m_pConsume->m_Free = 1; + + /* previous block is also free, merge them */ + m_pConsume = MergeBack(m_pConsume); + + /* advance the consume pointer */ + m_pConsume = NextBlock(m_pConsume); + while(m_pConsume->m_Free && m_pConsume != m_pProduce) + { + m_pConsume = MergeBack(m_pConsume); + m_pConsume = NextBlock(m_pConsume); + } + + /* in the case that we have catched up with the produce pointer */ + /* we might stand on a free block so merge em */ + MergeBack(m_pConsume); + return 1; +} + + +void *CRingBufferBase::Prev(void *pCurrent) +{ + CItem *pItem = ((CItem *)pCurrent) - 1; + + while(1) + { + pItem = PrevBlock(pItem); + if(pItem == m_pProduce) + return 0; + if(!pItem->m_Free) + return pItem+1; + } +} + +void *CRingBufferBase::Next(void *pCurrent) +{ + CItem *pItem = ((CItem *)pCurrent) - 1; + + while(1) + { + pItem = NextBlock(pItem); + if(pItem == m_pProduce) + return 0; + if(!pItem->m_Free) + return pItem+1; + } +} + +void *CRingBufferBase::First() +{ + if(m_pConsume->m_Free) + return 0; + return (void *)(m_pConsume+1); +} + +void *CRingBufferBase::Last() +{ + if(!m_pProduce->m_Free) + return m_pProduce+1; + return Prev(m_pProduce+1); +} + diff --git a/src/engine/e_ringbuffer.h b/src/engine/e_ringbuffer.h index 6113c19b..b9f7219c 100644 --- a/src/engine/e_ringbuffer.h +++ b/src/engine/e_ringbuffer.h @@ -3,21 +3,64 @@ typedef struct RINGBUFFER RINGBUFFER; -enum +class CRingBufferBase { - /* Will start to destroy items to try to fit the next one */ - RINGBUF_FLAG_RECYCLE=1 + class CItem + { + public: + CItem *m_pPrev; + CItem *m_pNext; + int m_Free; + int m_Size; + }; + + CItem *m_pProduce; + CItem *m_pConsume; + + CItem *m_pFirst; + CItem *m_pLast; + + void *m_pMemory; + int m_Size; + int m_Flags; + + CItem *NextBlock(CItem *pItem); + CItem *PrevBlock(CItem *pItem); + CItem *MergeBack(CItem *pItem); +protected: + void *Allocate(int Size); + + void *Prev(void *pCurrent); + void *Next(void *pCurrent); + void *First(); + void *Last(); + + void Init(void *pMemory, int Size, int Flags); + int PopFirst(); +public: + enum + { + /* Will start to destroy items to try to fit the next one */ + FLAG_RECYCLE=1 + }; }; - -RINGBUFFER *ringbuf_init(void *memory, int size, int flags); -void ringbuf_clear(RINGBUFFER *rb); -void *ringbuf_allocate(RINGBUFFER *rb, int size); -void *ringbuf_prev(RINGBUFFER *rb, void *current); -void *ringbuf_next(RINGBUFFER *rb, void *current); -void *ringbuf_first(RINGBUFFER *rb); -void *ringbuf_last(RINGBUFFER *rb); +template +class TStaticRingBuffer : public CRingBufferBase +{ + unsigned char m_aBuffer[TSIZE]; +public: + TStaticRingBuffer() { Init(); } + + void Init() { CRingBufferBase::Init(m_aBuffer, TSIZE, TFLAGS); } + + T *Allocate(int Size) { return (T*)CRingBufferBase::Allocate(Size); } + int PopFirst() { return CRingBufferBase::PopFirst(); } -int ringbuf_popfirst(RINGBUFFER *rb); + T *Prev(T *pCurrent) { return (T*)CRingBufferBase::Prev(pCurrent); } + T *Next(T *pCurrent) { return (T*)CRingBufferBase::Next(pCurrent); } + T *First() { return (T*)CRingBufferBase::First(); } + T *Last() { return (T*)CRingBufferBase::Last(); } +}; #endif diff --git a/src/engine/e_server_interface.h b/src/engine/e_server_interface.h index 5b1e6327..c325b9a1 100644 --- a/src/engine/e_server_interface.h +++ b/src/engine/e_server_interface.h @@ -2,10 +2,6 @@ #ifndef ENGINE_SERVER_INTERFACE_H #define ENGINE_SERVER_INTERFACE_H -#ifdef __cplusplus -extern "C" { -#endif - #include "e_if_other.h" #include "e_if_server.h" #include "e_if_msg.h" @@ -13,8 +9,4 @@ extern "C" { #include "e_console.h" /* TODO: clean this up*/ -#ifdef __cplusplus -} -#endif - #endif diff --git a/src/engine/e_snapshot.c b/src/engine/e_snapshot.c deleted file mode 100644 index cce8a06e..00000000 --- a/src/engine/e_snapshot.c +++ /dev/null @@ -1,604 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "e_snapshot.h" -#include "e_engine.h" -#include "e_compression.h" -#include "e_common_interface.h" - - -/* TODO: strange arbitrary number */ -static short item_sizes[64] = {0}; - -void snap_set_staticsize(int itemtype, int size) -{ - item_sizes[itemtype] = size; -} - -int *snapitem_data(SNAPSHOT_ITEM *item) { return (int *)(item+1); } -int snapitem_type(SNAPSHOT_ITEM *item) { return item->type_and_id>>16; } -int snapitem_id(SNAPSHOT_ITEM *item) { return item->type_and_id&0xffff; } -int snapitem_key(SNAPSHOT_ITEM *item) { return item->type_and_id; } - -int *snapshot_offsets(SNAPSHOT *snap) { return (int *)(snap+1); } -char *snapshot_datastart(SNAPSHOT *snap) { return (char*)(snapshot_offsets(snap)+snap->num_items); } - -SNAPSHOT_ITEM *snapshot_get_item(SNAPSHOT *snap, int index) -{ return (SNAPSHOT_ITEM *)(snapshot_datastart(snap) + snapshot_offsets(snap)[index]); } - -int snapshot_get_item_datasize(SNAPSHOT *snap, int index) -{ - if(index == snap->num_items-1) - return (snap->data_size - snapshot_offsets(snap)[index]) - sizeof(SNAPSHOT_ITEM); - return (snapshot_offsets(snap)[index+1] - snapshot_offsets(snap)[index]) - sizeof(SNAPSHOT_ITEM); -} - -int snapshot_get_item_index(SNAPSHOT *snap, int key) -{ - /* TODO: OPT: this should not be a linear search. very bad */ - int i; - for(i = 0; i < snap->num_items; i++) - { - if(snapitem_key(snapshot_get_item(snap, i)) == key) - return i; - } - return -1; -} -typedef struct -{ - int num; - int keys[64]; - int index[64]; -} ITEMLIST; -static ITEMLIST sorted[256]; - -static int snapshot_generate_hash(SNAPSHOT *snap) -{ - int i, key, hashid; - - for(i = 0; i < 256; i++) - sorted[i].num = 0; - - for(i = 0; i < snap->num_items; i++) - { - key = snapitem_key(snapshot_get_item(snap, i)); - hashid = ((key>>8)&0xf0) | (key&0xf); - if(sorted[hashid].num != 64) - { - sorted[hashid].index[sorted[hashid].num] = i; - sorted[hashid].keys[sorted[hashid].num] = key; - sorted[hashid].num++; - } - } - return 0; -} - -int snapshot_get_item_index_hashed(SNAPSHOT *snap, int key) -{ - int hashid = ((key>>8)&0xf0) | (key&0xf); - int i; - for(i = 0; i < sorted[hashid].num; i++) - { - if(sorted[hashid].keys[i] == key) - return sorted[hashid].index[i]; - } - - return -1; -} - -typedef struct -{ - int num_deleted_items; - int num_update_items; - int num_temp_items; /* needed? */ - int data[1]; - - /* - char *data_start() { return (char *)&offsets[num_deleted_items+num_update_items+num_temp_items]; } - - int deleted_item(int index) { return offsets[index]; } - item *update_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+index]); } - item *temp_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+num_update_items+index]); } - */ -} SNAPSHOT_DELTA; - - -static const int MAX_ITEMS = 512; -static SNAPSHOT_DELTA empty = {0,0,0,{0}}; - -void *snapshot_empty_delta() -{ - return ∅ -} - -int snapshot_crc(SNAPSHOT *snap) -{ - int crc = 0; - int i, b; - SNAPSHOT_ITEM *item; - int size; - - for(i = 0; i < snap->num_items; i++) - { - item = snapshot_get_item(snap, i); - size = snapshot_get_item_datasize(snap, i); - - for(b = 0; b < size/4; b++) - crc += snapitem_data(item)[b]; - } - return crc; -} - -void snapshot_debug_dump(SNAPSHOT *snap) -{ - int size, i, b; - SNAPSHOT_ITEM *item; - - dbg_msg("snapshot", "data_size=%d num_items=%d", snap->data_size, snap->num_items); - for(i = 0; i < snap->num_items; i++) - { - item = snapshot_get_item(snap, i); - size = snapshot_get_item_datasize(snap, i); - dbg_msg("snapshot", "\ttype=%d id=%d", snapitem_type(item), snapitem_id(item)); - for(b = 0; b < size/4; b++) - dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, snapitem_data(item)[b], snapitem_data(item)[b]); - } -} - -static int diff_item(int *past, int *current, int *out, int size) -{ - int needed = 0; - while(size) - { - *out = *current-*past; - needed |= *out; - out++; - past++; - current++; - size--; - } - - return needed; -} - -int snapshot_data_rate[0xffff] = {0}; -int snapshot_data_updates[0xffff] = {0}; -static int snapshot_current = 0; - -static void undiff_item(int *past, int *diff, int *out, int size) -{ - while(size) - { - *out = *past+*diff; - - if(*diff == 0) - snapshot_data_rate[snapshot_current] += 1; - else - { - unsigned char buf[16]; - unsigned char *end = vint_pack(buf, *diff); - snapshot_data_rate[snapshot_current] += (int)(end - (unsigned char*)buf) * 8; - } - - out++; - past++; - diff++; - size--; - } -} - - -/* TODO: OPT: this should be made much faster */ -int snapshot_create_delta(SNAPSHOT *from, SNAPSHOT *to, void *dstdata) -{ - static PERFORMACE_INFO hash_scope = {"hash", 0}; - SNAPSHOT_DELTA *delta = (SNAPSHOT_DELTA *)dstdata; - int *data = (int *)delta->data; - int i, itemsize, pastindex; - SNAPSHOT_ITEM *fromitem; - SNAPSHOT_ITEM *curitem; - SNAPSHOT_ITEM *pastitem; - int count = 0; - int size_count = 0; - - delta->num_deleted_items = 0; - delta->num_update_items = 0; - delta->num_temp_items = 0; - - perf_start(&hash_scope); - snapshot_generate_hash(to); - perf_end(); - - /* pack deleted stuff */ - { - static PERFORMACE_INFO scope = {"delete", 0}; - perf_start(&scope); - - for(i = 0; i < from->num_items; i++) - { - fromitem = snapshot_get_item(from, i); - if(snapshot_get_item_index_hashed(to, (snapitem_key(fromitem))) == -1) - { - /* deleted */ - delta->num_deleted_items++; - *data = snapitem_key(fromitem); - data++; - } - } - - perf_end(); - } - - perf_start(&hash_scope); - snapshot_generate_hash(from); - perf_end(); - - /* pack updated stuff */ - { - static PERFORMACE_INFO scope = {"update", 0}; - int pastindecies[1024]; - perf_start(&scope); - - /* fetch previous indices */ - /* we do this as a separate pass because it helps the cache */ - { - static PERFORMACE_INFO scope = {"find", 0}; - perf_start(&scope); - for(i = 0; i < to->num_items; i++) - { - curitem = snapshot_get_item(to, i); /* O(1) .. O(n) */ - pastindecies[i] = snapshot_get_item_index_hashed(from, snapitem_key(curitem)); /* O(n) .. O(n^n)*/ - } - perf_end(); - } - - for(i = 0; i < to->num_items; i++) - { - /* do delta */ - itemsize = snapshot_get_item_datasize(to, i); /* O(1) .. O(n) */ - curitem = snapshot_get_item(to, i); /* O(1) .. O(n) */ - pastindex = pastindecies[i]; - - if(pastindex != -1) - { - static PERFORMACE_INFO scope = {"diff", 0}; - int *item_data_dst = data+3; - perf_start(&scope); - - pastitem = snapshot_get_item(from, pastindex); - - if(item_sizes[snapitem_type(curitem)]) - item_data_dst = data+2; - - if(diff_item((int*)snapitem_data(pastitem), (int*)snapitem_data(curitem), item_data_dst, itemsize/4)) - { - - *data++ = snapitem_type(curitem); - *data++ = snapitem_id(curitem); - if(!item_sizes[snapitem_type(curitem)]) - *data++ = itemsize/4; - data += itemsize/4; - delta->num_update_items++; - } - perf_end(); - } - else - { - static PERFORMACE_INFO scope = {"copy", 0}; - perf_start(&scope); - - *data++ = snapitem_type(curitem); - *data++ = snapitem_id(curitem); - if(!item_sizes[snapitem_type(curitem)]) - *data++ = itemsize/4; - - mem_copy(data, snapitem_data(curitem), itemsize); - size_count += itemsize; - data += itemsize/4; - delta->num_update_items++; - count++; - - perf_end(); - } - } - - perf_end(); - } - - if(0) - { - dbg_msg("snapshot", "%d %d %d", - delta->num_deleted_items, - delta->num_update_items, - delta->num_temp_items); - } - - /* - // TODO: pack temp stuff - - // finish - //mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int)); - //mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int)); - //mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int)); - //mem_copy(delta->data_start(), data, data_size); - //delta->data_size = data_size; - * */ - - if(!delta->num_deleted_items && !delta->num_update_items && !delta->num_temp_items) - return 0; - - return (int)((char*)data-(char*)dstdata); -} - -static int range_check(void *end, void *ptr, int size) -{ - if((const char *)ptr + size > (const char *)end) - return -1; - return 0; -} - -int snapshot_unpack_delta(SNAPSHOT *from, SNAPSHOT *to, void *srcdata, int data_size) -{ - SNAPBUILD builder; - SNAPSHOT_DELTA *delta = (SNAPSHOT_DELTA *)srcdata; - int *data = (int *)delta->data; - int *end = (int *)(((char *)srcdata + data_size)); - - SNAPSHOT_ITEM *fromitem; - int i, d, keep, itemsize; - int *deleted; - int id, type, key; - int fromindex; - int *newdata; - - snapbuild_init(&builder); - - /* unpack deleted stuff */ - deleted = data; - data += delta->num_deleted_items; - if(data > end) - return -1; - - /* copy all non deleted stuff */ - for(i = 0; i < from->num_items; i++) - { - /* dbg_assert(0, "fail!"); */ - fromitem = snapshot_get_item(from, i); - itemsize = snapshot_get_item_datasize(from, i); - keep = 1; - for(d = 0; d < delta->num_deleted_items; d++) - { - if(deleted[d] == snapitem_key(fromitem)) - { - keep = 0; - break; - } - } - - if(keep) - { - /* keep it */ - mem_copy( - snapbuild_new_item(&builder, snapitem_type(fromitem), snapitem_id(fromitem), itemsize), - snapitem_data(fromitem), itemsize); - } - } - - /* unpack updated stuff */ - for(i = 0; i < delta->num_update_items; i++) - { - if(data+2 > end) - return -1; - - type = *data++; - id = *data++; - if(item_sizes[type]) - itemsize = item_sizes[type]; - else - { - if(data+1 > end) - return -2; - itemsize = (*data++) * 4; - } - snapshot_current = type; - - if(range_check(end, data, itemsize) || itemsize < 0) return -3; - - key = (type<<16)|id; - - /* create the item if needed */ - newdata = snapbuild_get_item_data(&builder, key); - if(!newdata) - newdata = (int *)snapbuild_new_item(&builder, key>>16, key&0xffff, itemsize); - - /*if(range_check(end, newdata, itemsize)) return -4;*/ - - fromindex = snapshot_get_item_index(from, key); - if(fromindex != -1) - { - /* we got an update so we need to apply the diff */ - undiff_item((int *)snapitem_data(snapshot_get_item(from, fromindex)), data, newdata, itemsize/4); - snapshot_data_updates[snapshot_current]++; - } - else /* no previous, just copy the data */ - { - mem_copy(newdata, data, itemsize); - snapshot_data_rate[snapshot_current] += itemsize*8; - snapshot_data_updates[snapshot_current]++; - } - - data += itemsize/4; - } - - /* finish up */ - return snapbuild_finish(&builder, to); -} - -/* SNAPSTORAGE */ - -void snapstorage_init(SNAPSTORAGE *ss) -{ - ss->first = 0; -} - -void snapstorage_purge_all(SNAPSTORAGE *ss) -{ - SNAPSTORAGE_HOLDER *h = ss->first; - SNAPSTORAGE_HOLDER *next; - - while(h) - { - next = h->next; - mem_free(h); - h = next; - } - - /* no more snapshots in storage */ - ss->first = 0; - ss->last = 0; -} - -void snapstorage_purge_until(SNAPSTORAGE *ss, int tick) -{ - SNAPSTORAGE_HOLDER *next; - SNAPSTORAGE_HOLDER *h = ss->first; - - while(h) - { - next = h->next; - if(h->tick >= tick) - return; /* no more to remove */ - mem_free(h); - - /* did we come to the end of the list? */ - if (!next) - break; - - ss->first = next; - next->prev = 0x0; - - h = next; - } - - /* no more snapshots in storage */ - ss->first = 0; - ss->last = 0; -} - -void snapstorage_add(SNAPSTORAGE *ss, int tick, int64 tagtime, int data_size, void *data, int create_alt) -{ - /* allocate memory for holder + snapshot_data */ - SNAPSTORAGE_HOLDER *h; - int total_size = sizeof(SNAPSTORAGE_HOLDER)+data_size; - - if(create_alt) - total_size += data_size; - - h = (SNAPSTORAGE_HOLDER *)mem_alloc(total_size, 1); - - /* set data */ - h->tick = tick; - h->tagtime = tagtime; - h->snap_size = data_size; - h->snap = (SNAPSHOT*)(h+1); - mem_copy(h->snap, data, data_size); - - if(create_alt) /* create alternative if wanted */ - { - h->alt_snap = (SNAPSHOT*)(((char *)h->snap) + data_size); - mem_copy(h->alt_snap, data, data_size); - } - else - h->alt_snap = 0; - - - /* link */ - h->next = 0; - h->prev = ss->last; - if(ss->last) - ss->last->next = h; - else - ss->first = h; - ss->last = h; -} - -int snapstorage_get(SNAPSTORAGE *ss, int tick, int64 *tagtime, SNAPSHOT **data, SNAPSHOT **alt_data) -{ - SNAPSTORAGE_HOLDER *h = ss->first; - - while(h) - { - if(h->tick == tick) - { - if(tagtime) - *tagtime = h->tagtime; - if(data) - *data = h->snap; - if(alt_data) - *alt_data = h->alt_snap; - return h->snap_size; - } - - h = h->next; - } - - return -1; -} - -/* SNAPBUILD */ - -void snapbuild_init(SNAPBUILD *sb) -{ - sb->data_size = 0; - sb->num_items = 0; -} - -SNAPSHOT_ITEM *snapbuild_get_item(SNAPBUILD *sb, int index) -{ - return (SNAPSHOT_ITEM *)&(sb->data[sb->offsets[index]]); -} - -int *snapbuild_get_item_data(SNAPBUILD *sb, int key) -{ - int i; - for(i = 0; i < sb->num_items; i++) - { - if(snapitem_key(snapbuild_get_item(sb, i)) == key) - return (int *)snapitem_data(snapbuild_get_item(sb, i)); - } - return 0; -} - -int snapbuild_finish(SNAPBUILD *sb, void *snapdata) -{ - /* flattern and make the snapshot */ - SNAPSHOT *snap = (SNAPSHOT *)snapdata; - int offset_size = sizeof(int)*sb->num_items; - snap->data_size = sb->data_size; - snap->num_items = sb->num_items; - mem_copy(snapshot_offsets(snap), sb->offsets, offset_size); - mem_copy(snapshot_datastart(snap), sb->data, sb->data_size); - return sizeof(SNAPSHOT) + offset_size + sb->data_size; -} - -void *snapbuild_new_item(SNAPBUILD *sb, int type, int id, int size) -{ - SNAPSHOT_ITEM *obj = (SNAPSHOT_ITEM *)(sb->data+sb->data_size); - - /*if(stress_prob(0.01f)) - { - size += ((rand()%5) - 2)*4; - if(size < 0) - size = 0; - }*/ - - mem_zero(obj, sizeof(SNAPSHOT_ITEM) + size); - obj->type_and_id = (type<<16)|id; - sb->offsets[sb->num_items] = sb->data_size; - sb->data_size += sizeof(SNAPSHOT_ITEM) + size; - sb->num_items++; - - dbg_assert(sb->data_size < MAX_SNAPSHOT_SIZE, "too much data"); - dbg_assert(sb->num_items < SNAPBUILD_MAX_ITEMS, "too many items"); - - return snapitem_data(obj); -} diff --git a/src/engine/e_snapshot.cpp b/src/engine/e_snapshot.cpp new file mode 100644 index 00000000..65487665 --- /dev/null +++ b/src/engine/e_snapshot.cpp @@ -0,0 +1,571 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "e_snapshot.h" +#include "e_engine.h" +#include "e_compression.h" +#include "e_common_interface.h" + + +/* TODO: strange arbitrary number */ +static short item_sizes[64] = {0}; + +void snap_set_staticsize(int itemtype, int size) +{ + item_sizes[itemtype] = size; +} + +CSnapshotItem *CSnapshot::GetItem(int Index) +{ + return (CSnapshotItem *)(DataStart() + Offsets()[Index]); +} + +int CSnapshot::GetItemSize(int Index) +{ + if(Index == m_NumItems-1) + return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem); + return (Offsets()[Index+1] - Offsets()[Index]) - sizeof(CSnapshotItem); +} + +int CSnapshot::GetItemIndex(int Key) +{ + /* TODO: OPT: this should not be a linear search. very bad */ + for(int i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == Key) + return i; + } + return -1; +} + +// TODO: clean up this +typedef struct +{ + int num; + int keys[64]; + int index[64]; +} ITEMLIST; + +static ITEMLIST sorted[256]; + +int CSnapshot::GenerateHash() +{ + for(int i = 0; i < 256; i++) + sorted[i].num = 0; + + for(int i = 0; i < m_NumItems; i++) + { + int Key = GetItem(i)->Key(); + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + if(sorted[HashID].num != 64) + { + sorted[HashID].index[sorted[HashID].num] = i; + sorted[HashID].keys[sorted[HashID].num] = Key; + sorted[HashID].num++; + } + } + return 0; +} + +int CSnapshot::GetItemIndexHashed(int Key) +{ + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + for(int i = 0; i < sorted[HashID].num; i++) + { + if(sorted[HashID].keys[i] == Key) + return sorted[HashID].index[i]; + } + + return -1; +} + + + +static const int MAX_ITEMS = 512; +static CSnapshotDelta empty = {0,0,0,{0}}; + +CSnapshotDelta *CSnapshot::EmptyDelta() +{ + return ∅ +} + +int CSnapshot::Crc() +{ + int crc = 0; + + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + + for(int b = 0; b < Size/4; b++) + crc += pItem->Data()[b]; + } + return crc; +} + +void CSnapshot::DebugDump() +{ + dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems); + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID()); + for(int b = 0; b < Size/4; b++) + dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, pItem->Data()[b], pItem->Data()[b]); + } +} + + +// TODO: remove these +int snapshot_data_rate[0xffff] = {0}; +int snapshot_data_updates[0xffff] = {0}; +static int snapshot_current = 0; + +static int diff_item(int *past, int *current, int *out, int size) +{ + int needed = 0; + while(size) + { + *out = *current-*past; + needed |= *out; + out++; + past++; + current++; + size--; + } + + return needed; +} + +static void undiff_item(int *past, int *diff, int *out, int size) +{ + while(size) + { + *out = *past+*diff; + + if(*diff == 0) + snapshot_data_rate[snapshot_current] += 1; + else + { + unsigned char buf[16]; + unsigned char *end = vint_pack(buf, *diff); + snapshot_data_rate[snapshot_current] += (int)(end - (unsigned char*)buf) * 8; + } + + out++; + past++; + diff++; + size--; + } +} + + +/* TODO: OPT: this should be made much faster */ +int CSnapshot::CreateDelta(CSnapshot *from, CSnapshot *to, void *dstdata) +{ + static PERFORMACE_INFO hash_scope = {"hash", 0}; + CSnapshotDelta *delta = (CSnapshotDelta *)dstdata; + int *data = (int *)delta->m_pData; + int i, itemsize, pastindex; + CSnapshotItem *pFromItem; + CSnapshotItem *pCurItem; + CSnapshotItem *pPastItem; + int count = 0; + int size_count = 0; + + delta->m_NumDeletedItems = 0; + delta->m_NumUpdateItems = 0; + delta->m_NumTempItems = 0; + + perf_start(&hash_scope); + to->GenerateHash(); + perf_end(); + + /* pack deleted stuff */ + { + static PERFORMACE_INFO scope = {"delete", 0}; + perf_start(&scope); + + for(i = 0; i < from->m_NumItems; i++) + { + pFromItem = from->GetItem(i); + if(to->GetItemIndexHashed(pFromItem->Key()) == -1) + { + /* deleted */ + delta->m_NumDeletedItems++; + *data = pFromItem->Key(); + data++; + } + } + + perf_end(); + } + + perf_start(&hash_scope); + from->GenerateHash(); + perf_end(); + + /* pack updated stuff */ + { + static PERFORMACE_INFO scope = {"update", 0}; + int pastindecies[1024]; + perf_start(&scope); + + /* fetch previous indices */ + /* we do this as a separate pass because it helps the cache */ + { + static PERFORMACE_INFO scope = {"find", 0}; + perf_start(&scope); + for(i = 0; i < to->m_NumItems; i++) + { + pCurItem = to->GetItem(i); /* O(1) .. O(n) */ + pastindecies[i] = from->GetItemIndexHashed(pCurItem->Key()); /* O(n) .. O(n^n)*/ + } + perf_end(); + } + + for(i = 0; i < to->m_NumItems; i++) + { + /* do delta */ + itemsize = to->GetItemSize(i); /* O(1) .. O(n) */ + pCurItem = to->GetItem(i); /* O(1) .. O(n) */ + pastindex = pastindecies[i]; + + if(pastindex != -1) + { + static PERFORMACE_INFO scope = {"diff", 0}; + int *item_data_dst = data+3; + perf_start(&scope); + + pPastItem = from->GetItem(pastindex); + + if(item_sizes[pCurItem->Type()]) + item_data_dst = data+2; + + if(diff_item((int*)pPastItem->Data(), (int*)pCurItem->Data(), item_data_dst, itemsize/4)) + { + + *data++ = pCurItem->Type(); + *data++ = pCurItem->ID(); + if(!item_sizes[pCurItem->Type()]) + *data++ = itemsize/4; + data += itemsize/4; + delta->m_NumUpdateItems++; + } + perf_end(); + } + else + { + static PERFORMACE_INFO scope = {"copy", 0}; + perf_start(&scope); + + *data++ = pCurItem->Type(); + *data++ = pCurItem->ID(); + if(!item_sizes[pCurItem->Type()]) + *data++ = itemsize/4; + + mem_copy(data, pCurItem->Data(), itemsize); + size_count += itemsize; + data += itemsize/4; + delta->m_NumUpdateItems++; + count++; + + perf_end(); + } + } + + perf_end(); + } + + if(0) + { + dbg_msg("snapshot", "%d %d %d", + delta->m_NumDeletedItems, + delta->m_NumUpdateItems, + delta->m_NumTempItems); + } + + /* + // TODO: pack temp stuff + + // finish + //mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int)); + //mem_copy(delta->data_start(), data, data_size); + //delta->data_size = data_size; + * */ + + if(!delta->m_NumDeletedItems && !delta->m_NumUpdateItems && !delta->m_NumTempItems) + return 0; + + return (int)((char*)data-(char*)dstdata); +} + +static int range_check(void *end, void *ptr, int size) +{ + if((const char *)ptr + size > (const char *)end) + return -1; + return 0; +} + +int CSnapshot::UnpackDelta(CSnapshot *from, CSnapshot *to, void *srcdata, int data_size) +{ + CSnapshotBuilder builder; + CSnapshotDelta *delta = (CSnapshotDelta *)srcdata; + int *data = (int *)delta->m_pData; + int *end = (int *)(((char *)srcdata + data_size)); + + CSnapshotItem *fromitem; + int i, d, keep, itemsize; + int *deleted; + int id, type, key; + int fromindex; + int *newdata; + + builder.Init(); + + /* unpack deleted stuff */ + deleted = data; + data += delta->m_NumDeletedItems; + if(data > end) + return -1; + + /* copy all non deleted stuff */ + for(i = 0; i < from->m_NumItems; i++) + { + /* dbg_assert(0, "fail!"); */ + fromitem = from->GetItem(i); + itemsize = from->GetItemSize(i); + keep = 1; + for(d = 0; d < delta->m_NumDeletedItems; d++) + { + if(deleted[d] == fromitem->Key()) + { + keep = 0; + break; + } + } + + if(keep) + { + /* keep it */ + mem_copy( + builder.NewItem(fromitem->Type(), fromitem->ID(), itemsize), + fromitem->Data(), itemsize); + } + } + + /* unpack updated stuff */ + for(i = 0; i < delta->m_NumUpdateItems; i++) + { + if(data+2 > end) + return -1; + + type = *data++; + id = *data++; + if(item_sizes[type]) + itemsize = item_sizes[type]; + else + { + if(data+1 > end) + return -2; + itemsize = (*data++) * 4; + } + snapshot_current = type; + + if(range_check(end, data, itemsize) || itemsize < 0) return -3; + + key = (type<<16)|id; + + /* create the item if needed */ + newdata = builder.GetItemData(key); + if(!newdata) + newdata = (int *)builder.NewItem(key>>16, key&0xffff, itemsize); + + /*if(range_check(end, newdata, itemsize)) return -4;*/ + + fromindex = from->GetItemIndex(key); + if(fromindex != -1) + { + /* we got an update so we need to apply the diff */ + undiff_item((int *)from->GetItem(fromindex)->Data(), data, newdata, itemsize/4); + snapshot_data_updates[snapshot_current]++; + } + else /* no previous, just copy the data */ + { + mem_copy(newdata, data, itemsize); + snapshot_data_rate[snapshot_current] += itemsize*8; + snapshot_data_updates[snapshot_current]++; + } + + data += itemsize/4; + } + + /* finish up */ + return builder.Finish(to); +} + +/* CSnapshotStorage */ + +void CSnapshotStorage::Init() +{ + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeAll() +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + mem_free(pHolder); + pHolder = pNext; + } + + /* no more snapshots in storage */ + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeUntil(int Tick) +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + if(pHolder->m_Tick >= Tick) + return; /* no more to remove */ + mem_free(pHolder); + + /* did we come to the end of the list? */ + if (!pNext) + break; + + m_pFirst = pNext; + pNext->m_pPrev = 0x0; + + pHolder = pNext; + } + + /* no more snapshots in storage */ + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt) +{ + /* allocate memory for holder + snapshot_data */ + int TotalSize = sizeof(CHolder)+DataSize; + + if(CreateAlt) + TotalSize += DataSize; + + CHolder *pHolder = (CHolder *)mem_alloc(TotalSize, 1); + + /* set data */ + pHolder->m_Tick = Tick; + pHolder->m_Tagtime = Tagtime; + pHolder->m_SnapSize = DataSize; + pHolder->m_pSnap = (CSnapshot*)(pHolder+1); + mem_copy(pHolder->m_pSnap, pData, DataSize); + + if(CreateAlt) /* create alternative if wanted */ + { + pHolder->m_pAltSnap = (CSnapshot*)(((char *)pHolder->m_pSnap) + DataSize); + mem_copy(pHolder->m_pAltSnap, pData, DataSize); + } + else + pHolder->m_pAltSnap = 0; + + + /* link */ + pHolder->m_pNext = 0; + pHolder->m_pPrev = m_pLast; + if(m_pLast) + m_pLast->m_pNext = pHolder; + else + m_pFirst = pHolder; + m_pLast = pHolder; +} + +int CSnapshotStorage::Get(int Tick, int64 *pTagtime, CSnapshot **ppData, CSnapshot **ppAltData) +{ + CHolder *pHolder = m_pFirst; + + while(pHolder) + { + if(pHolder->m_Tick == Tick) + { + if(pTagtime) + *pTagtime = pHolder->m_Tagtime; + if(ppData) + *ppData = pHolder->m_pSnap; + if(ppAltData) + *ppData = pHolder->m_pAltSnap; + return pHolder->m_SnapSize; + } + + pHolder = pHolder->m_pNext; + } + + return -1; +} + +/* CSnapshotBuilder */ + +void CSnapshotBuilder::Init() +{ + m_DataSize = 0; + m_NumItems = 0; +} + +CSnapshotItem *CSnapshotBuilder::GetItem(int Index) +{ + return (CSnapshotItem *)&(m_aData[m_aOffsets[Index]]); +} + +int *CSnapshotBuilder::GetItemData(int key) +{ + int i; + for(i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == key) + return (int *)GetItem(i)->Data(); + } + return 0; +} + +int CSnapshotBuilder::Finish(void *snapdata) +{ + /* flattern and make the snapshot */ + CSnapshot *pSnap = (CSnapshot *)snapdata; + int OffsetSize = sizeof(int)*m_NumItems; + pSnap->m_DataSize = m_DataSize; + pSnap->m_NumItems = m_NumItems; + mem_copy(pSnap->Offsets(), m_aOffsets, OffsetSize); + mem_copy(pSnap->DataStart(), m_aData, m_DataSize); + return sizeof(CSnapshot) + OffsetSize + m_DataSize; +} + +void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) +{ + CSnapshotItem *pObj = (CSnapshotItem *)(m_aData + m_DataSize); + + mem_zero(pObj, sizeof(CSnapshotItem) + Size); + pObj->m_TypeAndID = (Type<<16)|ID; + m_aOffsets[m_NumItems] = m_DataSize; + m_DataSize += sizeof(CSnapshotItem) + Size; + m_NumItems++; + + dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data"); + dbg_assert(m_NumItems < MAX_ITEMS, "too many items"); + + return pObj->Data(); +} diff --git a/src/engine/e_snapshot.h b/src/engine/e_snapshot.h index f57c95c0..60dc254c 100644 --- a/src/engine/e_snapshot.h +++ b/src/engine/e_snapshot.h @@ -4,89 +4,115 @@ #include -/* SNAPSHOT */ +/* CSnapshot */ -enum + + +class CSnapshotItem { - MAX_SNAPSHOT_SIZE=64*1024 +public: + int m_TypeAndID; + + int *Data() { return (int *)(this+1); } + int Type() { return m_TypeAndID>>16; } + int ID() { return m_TypeAndID&0xffff; } + int Key() { return m_TypeAndID; } }; -typedef struct +class CSnapshotDelta { - int type_and_id; -} SNAPSHOT_ITEM; +public: + int m_NumDeletedItems; + int m_NumUpdateItems; + int m_NumTempItems; /* needed? */ + int m_pData[1]; +}; -typedef struct +// TODO: hide a lot of these members +class CSnapshot { - int data_size; - int num_items; -} SNAPSHOT; - -int *snapitem_data(SNAPSHOT_ITEM *item); -int snapitem_type(SNAPSHOT_ITEM *item); -int snapitem_id(SNAPSHOT_ITEM *item); -int snapitem_key(SNAPSHOT_ITEM *item); - -int *snapshot_offsets(SNAPSHOT *snap); -char *snapshot_datastart(SNAPSHOT *snap); - -SNAPSHOT_ITEM *snapshot_get_item(SNAPSHOT *snap, int index); -int snapshot_get_item_datasize(SNAPSHOT *snap, int index); -int snapshot_get_item_index(SNAPSHOT *snap, int key); +public: + enum + { + MAX_SIZE=64*1024 + }; + + int m_DataSize; + int m_NumItems; + + int *Offsets() const { return (int *)(this+1); } + char *DataStart() const { return (char*)(Offsets()+m_NumItems); } + CSnapshotItem *GetItem(int Index); + int GetItemSize(int Index); + int GetItemIndex(int Key); + + int Crc(); + void DebugDump(); + + // TODO: move these + int GetItemIndexHashed(int Key); + int GenerateHash(); + + // + static CSnapshotDelta *EmptyDelta(); + static int CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pData); + static int UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pData, int DataSize); +}; -void *snapshot_empty_delta(); -int snapshot_crc(SNAPSHOT *snap); -void snapshot_debug_dump(SNAPSHOT *snap); -int snapshot_create_delta(SNAPSHOT *from, SNAPSHOT *to, void *data); -int snapshot_unpack_delta(SNAPSHOT *from, SNAPSHOT *to, void *data, int data_size); +/* CSnapshotStorage */ -/* SNAPSTORAGE */ -typedef struct SNAPSTORAGE_HOLDER_t +class CSnapshotStorage { - struct SNAPSTORAGE_HOLDER_t *prev; - struct SNAPSTORAGE_HOLDER_t *next; - - int64 tagtime; - int tick; - - int snap_size; - SNAPSHOT *snap; - SNAPSHOT *alt_snap; -} SNAPSTORAGE_HOLDER; - -typedef struct SNAPSTORAGE_t +public: + class CHolder + { + public: + CHolder *m_pPrev; + CHolder *m_pNext; + + int64 m_Tagtime; + int m_Tick; + + int m_SnapSize; + CSnapshot *m_pSnap; + CSnapshot *m_pAltSnap; + }; + + + CHolder *m_pFirst; + CHolder *m_pLast; + + void Init(); + void PurgeAll(); + void PurgeUntil(int Tick); + void Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt); + int Get(int Tick, int64 *Tagtime, CSnapshot **pData, CSnapshot **ppAltData); +}; + +class CSnapshotBuilder { - SNAPSTORAGE_HOLDER *first; - SNAPSTORAGE_HOLDER *last; -} SNAPSTORAGE; + enum + { + MAX_ITEMS = 1024*2 + }; -void snapstorage_init(SNAPSTORAGE *ss); -void snapstorage_purge_all(SNAPSTORAGE *ss); -void snapstorage_purge_until(SNAPSTORAGE *ss, int tick); -void snapstorage_add(SNAPSTORAGE *ss, int tick, int64 tagtime, int data_size, void *data, int create_alt); -int snapstorage_get(SNAPSTORAGE *ss, int tick, int64 *tagtime, SNAPSHOT **data, SNAPSHOT **alt_data); + char m_aData[CSnapshot::MAX_SIZE]; + int m_DataSize; -/* SNAPBUILD */ + int m_aOffsets[MAX_ITEMS]; + int m_NumItems; -enum -{ - SNAPBUILD_MAX_ITEMS = 1024*2 +public: + void Init(); + + void *NewItem(int Type, int ID, int Size); + + CSnapshotItem *GetItem(int Index); + int *GetItemData(int Key); + + int Finish(void *Snapdata); }; -typedef struct SNAPBUILD -{ - char data[MAX_SNAPSHOT_SIZE]; - int data_size; - - int offsets[SNAPBUILD_MAX_ITEMS]; - int num_items; -} SNAPBUILD; - -void snapbuild_init(SNAPBUILD *sb); -SNAPSHOT_ITEM *snapbuild_get_item(SNAPBUILD *sb, int index); -int *snapbuild_get_item_data(SNAPBUILD *sb, int key); -int snapbuild_finish(SNAPBUILD *sb, void *snapdata); -void *snapbuild_new_item(SNAPBUILD *sb, int type, int id, int size); #endif /* ENGINE_SNAPSHOT_H */ diff --git a/src/engine/server/es_register.c b/src/engine/server/es_register.c deleted file mode 100644 index b3ac70a6..00000000 --- a/src/engine/server/es_register.c +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -extern NETSERVER *net; - -enum -{ - REGISTERSTATE_START=0, - REGISTERSTATE_UPDATE_ADDRS, - REGISTERSTATE_QUERY_COUNT, - REGISTERSTATE_HEARTBEAT, - REGISTERSTATE_REGISTERED, - REGISTERSTATE_ERROR -}; - -static int register_state = REGISTERSTATE_START; -static int64 register_state_start = 0; -static int register_first = 1; -static int register_count = 0; - -static void register_new_state(int state) -{ - register_state = state; - register_state_start = time_get(); -} - -static void register_send_fwcheckresponse(NETADDR *addr) -{ - NETCHUNK packet; - packet.client_id = -1; - packet.address = *addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_FWRESPONSE); - packet.data = SERVERBROWSE_FWRESPONSE; - netserver_send(net, &packet); -} - -static void register_send_heartbeat(NETADDR addr) -{ - static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; - unsigned short port = config.sv_port; - NETCHUNK packet; - - mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - packet.client_id = -1; - packet.address = addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - packet.data = &data; - - /* supply the set port that the master can use if it has problems */ - if(config.sv_external_port) - port = config.sv_external_port; - data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; - data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; - netserver_send(net, &packet); -} - -static void register_send_count_request(NETADDR addr) -{ - NETCHUNK packet; - packet.client_id = -1; - packet.address = addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_GETCOUNT); - packet.data = SERVERBROWSE_GETCOUNT; - netserver_send(net, &packet); -} - -typedef struct -{ - NETADDR addr; - int count; - int valid; - int64 last_send; -} MASTERSERVER_INFO; - -static MASTERSERVER_INFO masterserver_info[MAX_MASTERSERVERS] = {{{0}}}; -static int register_registered_server = -1; - -void register_update() -{ - int64 now = time_get(); - int64 freq = time_freq(); - - if(!config.sv_register) - return; - - mastersrv_update(); - - if(register_state == REGISTERSTATE_START) - { - register_count = 0; - register_first = 1; - register_new_state(REGISTERSTATE_UPDATE_ADDRS); - mastersrv_refresh_addresses(); - dbg_msg("register", "refreshing ip addresses"); - } - else if(register_state == REGISTERSTATE_UPDATE_ADDRS) - { - register_registered_server = -1; - - if(!mastersrv_refreshing()) - { - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - NETADDR addr = mastersrv_get(i); - masterserver_info[i].addr = addr; - masterserver_info[i].count = 0; - - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - masterserver_info[i].valid = 0; - else - { - masterserver_info[i].valid = 1; - masterserver_info[i].count = -1; - masterserver_info[i].last_send = 0; - } - } - - dbg_msg("register", "fetching server counts"); - register_new_state(REGISTERSTATE_QUERY_COUNT); - } - } - else if(register_state == REGISTERSTATE_QUERY_COUNT) - { - int i; - int left = 0; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid) - continue; - - if(masterserver_info[i].count == -1) - { - left++; - if(masterserver_info[i].last_send+freq < now) - { - masterserver_info[i].last_send = now; - register_send_count_request(masterserver_info[i].addr); - } - } - } - - /* check if we are done or timed out */ - if(left == 0 || now > register_state_start+freq*3) - { - /* choose server */ - int best = -1; - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid || masterserver_info[i].count == -1) - continue; - - if(best == -1 || masterserver_info[i].count < masterserver_info[best].count) - best = i; - } - - /* server chosen */ - register_registered_server = best; - if(register_registered_server == -1) - { - dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); - register_new_state(REGISTERSTATE_ERROR); - } - else - { - dbg_msg("register", "choosen '%s' as master, sending heartbeats", mastersrv_name(register_registered_server)); - masterserver_info[register_registered_server].last_send = 0; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_HEARTBEAT) - { - /* check if we should send heartbeat */ - if(now > masterserver_info[register_registered_server].last_send+freq*15) - { - masterserver_info[register_registered_server].last_send = now; - register_send_heartbeat(masterserver_info[register_registered_server].addr); - } - - if(now > register_state_start+freq*60) - { - dbg_msg("register", "WARNING: Master server is not responding, switching master"); - register_new_state(REGISTERSTATE_START); - } - } - else if(register_state == REGISTERSTATE_REGISTERED) - { - if(register_first) - dbg_msg("register", "server registered"); - - register_first = 0; - - /* check if we should send new heartbeat again */ - if(now > register_state_start+freq) - { - if(register_count == 120) /* redo the whole process after 60 minutes to balance out the master servers */ - register_new_state(REGISTERSTATE_START); - else - { - register_count++; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_ERROR) - { - /* check for restart */ - if(now > register_state_start+freq*60) - register_new_state(REGISTERSTATE_START); - } -} - -static void register_got_count(NETCHUNK *p) -{ - unsigned char *data = (unsigned char *)p->data; - int count = (data[sizeof(SERVERBROWSE_COUNT)]<<8) | data[sizeof(SERVERBROWSE_COUNT)+1]; - int i; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(net_addr_comp(&masterserver_info[i].addr, &p->address) == 0) - { - masterserver_info[i].count = count; - break; - } - } -} - -int register_process_packet(NETCHUNK *packet) -{ - if(packet->data_size == sizeof(SERVERBROWSE_FWCHECK) && - memcmp(packet->data, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - register_send_fwcheckresponse(&packet->address); - return 1; - } - else if(packet->data_size == sizeof(SERVERBROWSE_FWOK) && - memcmp(packet->data, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) - { - if(register_first) - dbg_msg("register", "no firewall/nat problems detected"); - register_new_state(REGISTERSTATE_REGISTERED); - return 1; - } - else if(packet->data_size == sizeof(SERVERBROWSE_FWERROR) && - memcmp(packet->data, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) - { - dbg_msg("register", "ERROR: the master server reports that clients can not connect to this server."); - dbg_msg("register", "ERROR: configure your firewall/nat to let through udp on port %d.", config.sv_port); - register_new_state(REGISTERSTATE_ERROR); - return 1; - } - else if(packet->data_size == sizeof(SERVERBROWSE_COUNT)+2 && - memcmp(packet->data, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - register_got_count(packet); - return 1; - } - - return 0; -} diff --git a/src/engine/server/es_register.cpp b/src/engine/server/es_register.cpp new file mode 100644 index 00000000..0eb5dba5 --- /dev/null +++ b/src/engine/server/es_register.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include + +#include + +extern CNetServer *m_pNetServer; + +enum +{ + REGISTERSTATE_START=0, + REGISTERSTATE_UPDATE_ADDRS, + REGISTERSTATE_QUERY_COUNT, + REGISTERSTATE_HEARTBEAT, + REGISTERSTATE_REGISTERED, + REGISTERSTATE_ERROR +}; + +static int register_state = REGISTERSTATE_START; +static int64 register_state_start = 0; +static int register_first = 1; +static int register_count = 0; + +static void register_new_state(int state) +{ + register_state = state; + register_state_start = time_get(); +} + +static void register_send_fwcheckresponse(NETADDR *pAddr) +{ + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = *pAddr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); + Packet.m_pData = SERVERBROWSE_FWRESPONSE; + m_pNetServer->Send(&Packet); +} + +static void register_send_heartbeat(NETADDR addr) +{ + static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; + unsigned short port = config.sv_port; + CNetChunk Packet; + + mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); + + Packet.m_ClientID = -1; + Packet.m_Address = addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; + Packet.m_pData = &data; + + /* supply the set port that the master can use if it has problems */ + if(config.sv_external_port) + port = config.sv_external_port; + data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; + data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; + m_pNetServer->Send(&Packet); +} + +static void register_send_count_request(NETADDR Addr) +{ + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); + Packet.m_pData = SERVERBROWSE_GETCOUNT; + m_pNetServer->Send(&Packet); +} + +typedef struct +{ + NETADDR addr; + int count; + int valid; + int64 last_send; +} MASTERSERVER_INFO; + +static MASTERSERVER_INFO masterserver_info[MAX_MASTERSERVERS] = {{{0}}}; +static int register_registered_server = -1; + +void register_update() +{ + int64 now = time_get(); + int64 freq = time_freq(); + + if(!config.sv_register) + return; + + mastersrv_update(); + + if(register_state == REGISTERSTATE_START) + { + register_count = 0; + register_first = 1; + register_new_state(REGISTERSTATE_UPDATE_ADDRS); + mastersrv_refresh_addresses(); + dbg_msg("register", "refreshing ip addresses"); + } + else if(register_state == REGISTERSTATE_UPDATE_ADDRS) + { + register_registered_server = -1; + + if(!mastersrv_refreshing()) + { + int i; + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + NETADDR addr = mastersrv_get(i); + masterserver_info[i].addr = addr; + masterserver_info[i].count = 0; + + if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) + masterserver_info[i].valid = 0; + else + { + masterserver_info[i].valid = 1; + masterserver_info[i].count = -1; + masterserver_info[i].last_send = 0; + } + } + + dbg_msg("register", "fetching server counts"); + register_new_state(REGISTERSTATE_QUERY_COUNT); + } + } + else if(register_state == REGISTERSTATE_QUERY_COUNT) + { + int i; + int left = 0; + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + if(!masterserver_info[i].valid) + continue; + + if(masterserver_info[i].count == -1) + { + left++; + if(masterserver_info[i].last_send+freq < now) + { + masterserver_info[i].last_send = now; + register_send_count_request(masterserver_info[i].addr); + } + } + } + + /* check if we are done or timed out */ + if(left == 0 || now > register_state_start+freq*3) + { + /* choose server */ + int best = -1; + int i; + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + if(!masterserver_info[i].valid || masterserver_info[i].count == -1) + continue; + + if(best == -1 || masterserver_info[i].count < masterserver_info[best].count) + best = i; + } + + /* server chosen */ + register_registered_server = best; + if(register_registered_server == -1) + { + dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); + register_new_state(REGISTERSTATE_ERROR); + } + else + { + dbg_msg("register", "choosen '%s' as master, sending heartbeats", mastersrv_name(register_registered_server)); + masterserver_info[register_registered_server].last_send = 0; + register_new_state(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(register_state == REGISTERSTATE_HEARTBEAT) + { + /* check if we should send heartbeat */ + if(now > masterserver_info[register_registered_server].last_send+freq*15) + { + masterserver_info[register_registered_server].last_send = now; + register_send_heartbeat(masterserver_info[register_registered_server].addr); + } + + if(now > register_state_start+freq*60) + { + dbg_msg("register", "WARNING: Master server is not responding, switching master"); + register_new_state(REGISTERSTATE_START); + } + } + else if(register_state == REGISTERSTATE_REGISTERED) + { + if(register_first) + dbg_msg("register", "server registered"); + + register_first = 0; + + /* check if we should send new heartbeat again */ + if(now > register_state_start+freq) + { + if(register_count == 120) /* redo the whole process after 60 minutes to balance out the master servers */ + register_new_state(REGISTERSTATE_START); + else + { + register_count++; + register_new_state(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(register_state == REGISTERSTATE_ERROR) + { + /* check for restart */ + if(now > register_state_start+freq*60) + register_new_state(REGISTERSTATE_START); + } +} + +static void register_got_count(CNetChunk *pChunk) +{ + unsigned char *pData = (unsigned char *)pChunk->m_pData; + int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; + + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + if(net_addr_comp(&masterserver_info[i].addr, &pChunk->m_Address) == 0) + { + masterserver_info[i].count = Count; + break; + } + } +} + +int register_process_packet(CNetChunk *pPacket) +{ + if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) + { + register_send_fwcheckresponse(&pPacket->m_Address); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) + { + if(register_first) + dbg_msg("register", "no firewall/nat problems detected"); + register_new_state(REGISTERSTATE_REGISTERED); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) + { + dbg_msg("register", "ERROR: the master server reports that clients can not connect to this server."); + dbg_msg("register", "ERROR: configure your firewall/nat to let through udp on port %d.", config.sv_port); + register_new_state(REGISTERSTATE_ERROR); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && + memcmp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) + { + register_got_count(pPacket); + return 1; + } + + return 0; +} diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c deleted file mode 100644 index 8763351d..00000000 --- a/src/engine/server/es_server.c +++ /dev/null @@ -1,1380 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#if defined(CONF_FAMILY_WINDOWS) - #define _WIN32_WINNT 0x0500 - #include -#endif - -static SNAPBUILD builder; - -static int64 game_start_time; -static int current_tick = 0; -static int run_server = 1; - -static char browseinfo_gametype[16] = {0}; -static int browseinfo_progression = -1; - -static int64 lastheartbeat; -/*static NETADDR4 master_server;*/ - -static char current_map[64]; -static int current_map_crc; -static unsigned char *current_map_data = 0; -static int current_map_size = 0; - -void *snap_new_item(int type, int id, int size) -{ - dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); - dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); - return snapbuild_new_item(&builder, type, id, size); -} - -typedef struct -{ - short next; - short state; /* 0 = free, 1 = alloced, 2 = timed */ - int timeout; -} SNAP_ID; - -static const int MAX_IDS = 16*1024; /* should be lowered */ -static SNAP_ID snap_ids[16*1024]; -static int snap_first_free_id; -static int snap_first_timed_id; -static int snap_last_timed_id; -static int snap_id_usage; -static int snap_id_inusage; -static int snap_id_inited = 0; - -enum -{ - SRVCLIENT_STATE_EMPTY = 0, - SRVCLIENT_STATE_AUTH, - SRVCLIENT_STATE_CONNECTING, - SRVCLIENT_STATE_READY, - SRVCLIENT_STATE_INGAME, - - SRVCLIENT_SNAPRATE_INIT=0, - SRVCLIENT_SNAPRATE_FULL, - SRVCLIENT_SNAPRATE_RECOVER -}; - -typedef struct -{ - int data[MAX_INPUT_SIZE]; - int game_tick; /* the tick that was chosen for the input */ -} CLIENT_INPUT; - -/* */ -typedef struct -{ - /* connection state info */ - int state; - int latency; - int snap_rate; - - int last_acked_snapshot; - int last_input_tick; - SNAPSTORAGE snapshots; - - CLIENT_INPUT latestinput; - CLIENT_INPUT inputs[200]; /* TODO: handle input better */ - int current_input; - - char name[MAX_NAME_LENGTH]; - char clan[MAX_CLANNAME_LENGTH]; - int score; - int authed; -} CLIENT; - -static CLIENT clients[MAX_CLIENTS]; -NETSERVER *net; - -static void snap_init_id() -{ - int i; - if(snap_id_inited) - return; - - for(i = 0; i < MAX_IDS; i++) - { - snap_ids[i].next = i+1; - snap_ids[i].state = 0; - } - - snap_ids[MAX_IDS-1].next = -1; - snap_first_free_id = 0; - snap_first_timed_id = -1; - snap_last_timed_id = -1; - snap_id_usage = 0; - snap_id_inusage = 0; - - snap_id_inited = 1; -} - -static void snap_remove_first_timeout() -{ - int next_timed = snap_ids[snap_first_timed_id].next; - - /* add it to the free list */ - snap_ids[snap_first_timed_id].next = snap_first_free_id; - snap_ids[snap_first_timed_id].state = 0; - snap_first_free_id = snap_first_timed_id; - - /* remove it from the timed list */ - snap_first_timed_id = next_timed; - if(snap_first_timed_id == -1) - snap_last_timed_id = -1; - - snap_id_usage--; -} - -int snap_new_id() -{ - int id; - int64 now = time_get(); - if(!snap_id_inited) - snap_init_id(); - - /* process timed ids */ - while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout < now) - snap_remove_first_timeout(); - - id = snap_first_free_id; - dbg_assert(id != -1, "id error"); - snap_first_free_id = snap_ids[snap_first_free_id].next; - snap_ids[id].state = 1; - snap_id_usage++; - snap_id_inusage++; - return id; -} - -void snap_timeout_ids() -{ - /* process timed ids */ - while(snap_first_timed_id != -1) - snap_remove_first_timeout(); -} - -void snap_free_id(int id) -{ - dbg_assert(snap_ids[id].state == 1, "id is not alloced"); - - snap_id_inusage--; - snap_ids[id].state = 2; - snap_ids[id].timeout = time_get()+time_freq()*5; - snap_ids[id].next = -1; - - if(snap_last_timed_id != -1) - { - snap_ids[snap_last_timed_id].next = id; - snap_last_timed_id = id; - } - else - { - snap_first_timed_id = id; - snap_last_timed_id = id; - } -} - -int *server_latestinput(int client_id, int *size) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return 0; - return clients[client_id].latestinput.data; -} - -const char *server_clientname(int client_id) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return "(invalid client)"; - return clients[client_id].name; -} - -static const char *str_ltrim(const char *str) -{ - while(*str && *str <= 32) - str++; - return str; -} - -static void str_rtrim(char *str) -{ - int i = str_length(str); - while(i >= 0) - { - if(str[i] > 32) - break; - str[i] = 0; - i--; - } -} - -static int server_try_setclientname(int client_id, const char *name) -{ - int i; - char trimmed_name[64]; - - /* trim the name */ - str_copy(trimmed_name, str_ltrim(name), sizeof(trimmed_name)); - str_rtrim(trimmed_name); - dbg_msg("", "'%s' -> '%s'", name, trimmed_name); - name = trimmed_name; - - - /* check for empty names */ - if(!name[0]) - return -1; - - /* make sure that two clients doesn't have the same name */ - for(i = 0; i < MAX_CLIENTS; i++) - if(i != client_id && clients[i].state >= SRVCLIENT_STATE_READY) - { - if(strcmp(name, clients[i].name) == 0) - return -1; - } - - /* set the client name */ - str_copy(clients[client_id].name, name, MAX_NAME_LENGTH); - return 0; -} - -void server_setclientname(int client_id, const char *name) -{ - char nametry[MAX_NAME_LENGTH]; - int i; - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return; - - if(!name) - return; - - str_copy(nametry, name, MAX_NAME_LENGTH); - if(server_try_setclientname(client_id, nametry)) - { - /* auto rename */ - for(i = 1;; i++) - { - str_format(nametry, MAX_NAME_LENGTH, "(%d)%s", i, name); - if(server_try_setclientname(client_id, nametry) == 0) - break; - } - } -} - -void server_setclientscore(int client_id, int score) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return; - clients[client_id].score = score; -} - -void server_setbrowseinfo(const char *game_type, int progression) -{ - str_copy(browseinfo_gametype, game_type, sizeof(browseinfo_gametype)); - browseinfo_progression = progression; - if(browseinfo_progression > 100) - browseinfo_progression = 100; - if(browseinfo_progression < -1) - browseinfo_progression = -1; -} - -void server_kick(int client_id, const char *reason) -{ - if(client_id < 0 || client_id > MAX_CLIENTS) - return; - - if(clients[client_id].state != SRVCLIENT_STATE_EMPTY) - netserver_drop(net, client_id, reason); -} - -int server_tick() -{ - return current_tick; -} - -int64 server_tick_start_time(int tick) -{ - return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED; -} - -int server_tickspeed() -{ - return SERVER_TICK_SPEED; -} - -int server_init() -{ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - clients[i].state = SRVCLIENT_STATE_EMPTY; - clients[i].name[0] = 0; - clients[i].clan[0] = 0; - snapstorage_init(&clients[i].snapshots); - } - - current_tick = 0; - - return 0; -} - -int server_getclientinfo(int client_id, CLIENT_INFO *info) -{ - dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid"); - dbg_assert(info != 0, "info can not be null"); - - if(clients[client_id].state == SRVCLIENT_STATE_INGAME) - { - info->name = clients[client_id].name; - info->latency = clients[client_id].latency; - return 1; - } - return 0; -} - -int server_send_msg(int client_id) -{ - const MSG_INFO *info = msg_get_info(); - NETCHUNK packet; - if(!info) - return -1; - - mem_zero(&packet, sizeof(NETCHUNK)); - - packet.client_id = client_id; - packet.data = info->data; - packet.data_size = info->size; - - if(info->flags&MSGFLAG_VITAL) - packet.flags |= NETSENDFLAG_VITAL; - if(info->flags&MSGFLAG_FLUSH) - packet.flags |= NETSENDFLAG_FLUSH; - - /* write message to demo recorder */ - if(!(info->flags&MSGFLAG_NORECORD)) - demorec_record_message(info->data, info->size); - - if(!(info->flags&MSGFLAG_NOSEND)) - { - if(client_id == -1) - { - /* broadcast */ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - packet.client_id = i; - netserver_send(net, &packet); - } - } - else - netserver_send(net, &packet); - } - return 0; -} - -static void server_do_snap() -{ - int i; - - { - static PERFORMACE_INFO scope = {"presnap", 0}; - perf_start(&scope); - mods_presnap(); - perf_end(); - } - - /* create snapshot for demo recording */ - if(demorec_isrecording()) - { - char data[MAX_SNAPSHOT_SIZE]; - int snapshot_size; - - /* build snap and possibly add some messages */ - snapbuild_init(&builder); - mods_snap(-1); - snapshot_size = snapbuild_finish(&builder, data); - - /* write snapshot */ - demorec_record_snapshot(server_tick(), data, snapshot_size); - } - - /* create snapshots for all clients */ - for(i = 0; i < MAX_CLIENTS; i++) - { - /* client must be ingame to recive snapshots */ - if(clients[i].state != SRVCLIENT_STATE_INGAME) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_RECOVER && (server_tick()%50) != 0) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_INIT && (server_tick()%10) != 0) - continue; - - { - char data[MAX_SNAPSHOT_SIZE]; - char deltadata[MAX_SNAPSHOT_SIZE]; - char compdata[MAX_SNAPSHOT_SIZE]; - int snapshot_size; - int crc; - static SNAPSHOT emptysnap; - SNAPSHOT *deltashot = &emptysnap; - int deltashot_size; - int delta_tick = -1; - int deltasize; - static PERFORMACE_INFO scope = {"build", 0}; - perf_start(&scope); - - snapbuild_init(&builder); - - { - static PERFORMACE_INFO scope = {"modsnap", 0}; - perf_start(&scope); - mods_snap(i); - perf_end(); - } - - /* finish snapshot */ - snapshot_size = snapbuild_finish(&builder, data); - crc = snapshot_crc((SNAPSHOT*)data); - - /* remove old snapshos */ - /* keep 3 seconds worth of snapshots */ - snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED*3); - - /* save it the snapshot */ - snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data, 0); - - /* find snapshot that we can preform delta against */ - emptysnap.data_size = 0; - emptysnap.num_items = 0; - - { - deltashot_size = snapstorage_get(&clients[i].snapshots, clients[i].last_acked_snapshot, 0, &deltashot, 0); - if(deltashot_size >= 0) - delta_tick = clients[i].last_acked_snapshot; - else - { - /* no acked package found, force client to recover rate */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_FULL) - clients[i].snap_rate = SRVCLIENT_SNAPRATE_RECOVER; - } - } - - /* create delta */ - { - static PERFORMACE_INFO scope = {"delta", 0}; - perf_start(&scope); - deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata); - perf_end(); - } - - - if(deltasize) - { - /* compress it */ - int snapshot_size; - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets; - int n, left; - - { - static PERFORMACE_INFO scope = {"compress", 0}; - /*char buffer[512];*/ - perf_start(&scope); - snapshot_size = intpack_compress(deltadata, deltasize, compdata); - - /*str_hex(buffer, sizeof(buffer), compdata, snapshot_size); - dbg_msg("", "deltasize=%d -> %d : %s", deltasize, snapshot_size, buffer);*/ - - perf_end(); - } - - numpackets = (snapshot_size+max_size-1)/max_size; - - for(n = 0, left = snapshot_size; left; n++) - { - int chunk = left < max_size ? left : max_size; - left -= chunk; - - if(numpackets == 1) - msg_pack_start_system(NETMSG_SNAPSINGLE, MSGFLAG_FLUSH); - else - msg_pack_start_system(NETMSG_SNAP, MSGFLAG_FLUSH); - - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - - if(numpackets != 1) - { - msg_pack_int(numpackets); - msg_pack_int(n); - } - - msg_pack_int(crc); - msg_pack_int(chunk); - msg_pack_raw(&compdata[n*max_size], chunk); - msg_pack_end(); - server_send_msg(i); - } - } - else - { - msg_pack_start_system(NETMSG_SNAPEMPTY, MSGFLAG_FLUSH); - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - msg_pack_end(); - server_send_msg(i); - } - - perf_end(); - } - } - - mods_postsnap(); -} - - -static void reset_client(int cid) -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - clients[cid].inputs[i].game_tick = -1; - clients[cid].current_input = 0; - mem_zero(&clients[cid].latestinput, sizeof(clients[cid].latestinput)); - - snapstorage_purge_all(&clients[cid].snapshots); - clients[cid].last_acked_snapshot = -1; - clients[cid].last_input_tick = -1; - clients[cid].snap_rate = SRVCLIENT_SNAPRATE_INIT; - clients[cid].score = 0; - -} - -static int new_client_callback(int cid, void *user) -{ - clients[cid].state = SRVCLIENT_STATE_AUTH; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - clients[cid].authed = 0; - reset_client(cid); - return 0; -} - -static int del_client_callback(int cid, void *user) -{ - /* notify the mod about the drop */ - if(clients[cid].state >= SRVCLIENT_STATE_READY) - mods_client_drop(cid); - - clients[cid].state = SRVCLIENT_STATE_EMPTY; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - clients[cid].authed = 0; - snapstorage_purge_all(&clients[cid].snapshots); - return 0; -} - -static void server_send_map(int cid) -{ - msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(config.sv_map, 0); - msg_pack_int(current_map_crc); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line(int cid, const char *line) -{ - msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); - msg_pack_string(line, 512); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line_authed(const char *line, void *user_data) -{ - static volatile int reentry_guard = 0; - int i; - - if(reentry_guard) return; - reentry_guard++; - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY && clients[i].authed) - server_send_rcon_line(i, line); - } - - reentry_guard--; -} - -static void server_process_client_packet(NETCHUNK *packet) -{ - int cid = packet->client_id; - NETADDR addr; - - int sys; - int msg = msg_unpack_start(packet->data, packet->data_size, &sys); - - if(clients[cid].state == SRVCLIENT_STATE_AUTH) - { - if(sys && msg == NETMSG_INFO) - { - char version[64]; - const char *password; - str_copy(version, msg_unpack_string(), 64); - if(strcmp(version, mods_net_version()) != 0) - { - /* OH FUCK! wrong version, drop him */ - char reason[256]; - str_format(reason, sizeof(reason), "wrong version. server is running '%s' and client '%s'.", mods_net_version(), version); - netserver_drop(net, cid, reason); - return; - } - - str_copy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); - str_copy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); - password = msg_unpack_string(); - - if(config.password[0] != 0 && strcmp(config.password, password) != 0) - { - /* wrong password */ - netserver_drop(net, cid, "wrong password"); - return; - } - - clients[cid].state = SRVCLIENT_STATE_CONNECTING; - server_send_map(cid); - } - } - else - { - if(sys) - { - /* system message */ - if(msg == NETMSG_REQUEST_MAP_DATA) - { - int chunk = msg_unpack_int(); - int chunk_size = 1024-128; - int offset = chunk * chunk_size; - int last = 0; - - /* drop faulty map data requests */ - if(chunk < 0 || offset > current_map_size) - return; - - if(offset+chunk_size >= current_map_size) - { - chunk_size = current_map_size-offset; - if(chunk_size < 0) - chunk_size = 0; - last = 1; - } - - msg_pack_start_system(NETMSG_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(last); - msg_pack_int(current_map_size); - msg_pack_int(chunk_size); - msg_pack_raw(¤t_map_data[offset], chunk_size); - msg_pack_end(); - server_send_msg(cid); - - if(config.debug) - dbg_msg("server", "sending chunk %d with size %d", chunk, chunk_size); - } - else if(msg == NETMSG_READY) - { - if(clients[cid].state == SRVCLIENT_STATE_CONNECTING) - { - netserver_client_addr(net, cid, &addr); - - dbg_msg("server", "player is ready. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - clients[cid].state = SRVCLIENT_STATE_READY; - mods_connected(cid); - } - } - else if(msg == NETMSG_ENTERGAME) - { - if(clients[cid].state == SRVCLIENT_STATE_READY) - { - netserver_client_addr(net, cid, &addr); - - dbg_msg("server", "player has entered the game. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - clients[cid].state = SRVCLIENT_STATE_INGAME; - mods_client_enter(cid); - } - } - else if(msg == NETMSG_INPUT) - { - int tick, size, i; - CLIENT_INPUT *input; - int64 tagtime; - - clients[cid].last_acked_snapshot = msg_unpack_int(); - tick = msg_unpack_int(); - size = msg_unpack_int(); - - /* check for errors */ - if(msg_unpack_error() || size/4 > MAX_INPUT_SIZE) - return; - - if(clients[cid].last_acked_snapshot > 0) - clients[cid].snap_rate = SRVCLIENT_SNAPRATE_FULL; - - if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0, 0) >= 0) - clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq()); - - /* add message to report the input timing */ - /* skip packets that are old */ - if(tick > clients[cid].last_input_tick) - { - int time_left = ((server_tick_start_time(tick)-time_get())*1000) / time_freq(); - msg_pack_start_system(NETMSG_INPUTTIMING, 0); - msg_pack_int(tick); - msg_pack_int(time_left); - msg_pack_end(); - server_send_msg(cid); - } - - clients[cid].last_input_tick = tick; - - input = &clients[cid].inputs[clients[cid].current_input]; - - if(tick <= server_tick()) - tick = server_tick()+1; - - input->game_tick = tick; - - for(i = 0; i < size/4; i++) - input->data[i] = msg_unpack_int(); - - mem_copy(clients[cid].latestinput.data, input->data, MAX_INPUT_SIZE*sizeof(int)); - - clients[cid].current_input++; - clients[cid].current_input %= 200; - - /* call the mod with the fresh input data */ - if(clients[cid].state == SRVCLIENT_STATE_INGAME) - mods_client_direct_input(cid, clients[cid].latestinput.data); - } - else if(msg == NETMSG_RCON_CMD) - { - const char *cmd = msg_unpack_string(); - - if(msg_unpack_error() == 0 && clients[cid].authed) - { - dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); - console_execute_line(cmd); - } - } - else if(msg == NETMSG_RCON_AUTH) - { - const char *pw; - msg_unpack_string(); /* login name, not used */ - pw = msg_unpack_string(); - - if(msg_unpack_error() == 0) - { - if(config.sv_rcon_password[0] == 0) - { - server_send_rcon_line(cid, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); - } - else if(strcmp(pw, config.sv_rcon_password) == 0) - { - msg_pack_start_system(NETMSG_RCON_AUTH_STATUS, MSGFLAG_VITAL); - msg_pack_int(1); - msg_pack_end(); - server_send_msg(cid); - - clients[cid].authed = 1; - server_send_rcon_line(cid, "Authentication successful. Remote console access granted."); - dbg_msg("server", "cid=%d authed", cid); - } - else - { - server_send_rcon_line(cid, "Wrong password."); - } - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - server_send_msg(cid); - } - else - { - char hex[] = "0123456789ABCDEF"; - char buf[512]; - int b; - - for(b = 0; b < packet->data_size && b < 32; b++) - { - buf[b*3] = hex[((const unsigned char *)packet->data)[b]>>4]; - buf[b*3+1] = hex[((const unsigned char *)packet->data)[b]&0xf]; - buf[b*3+2] = ' '; - buf[b*3+3] = 0; - } - - dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); - dbg_msg("server", "%s", buf); - - } - } - else - { - /* game message */ - if(clients[cid].state >= SRVCLIENT_STATE_READY) - mods_message(msg, cid); - } - } -} - - -int server_ban_add(NETADDR addr, int seconds) -{ - return netserver_ban_add(net, addr, seconds); -} - -int server_ban_remove(NETADDR addr) -{ - return netserver_ban_remove(net, addr); -} - -static void server_send_serverinfo(NETADDR *addr, int token) -{ - NETCHUNK packet; - PACKER p; - char buf[128]; - - /* count the players */ - int player_count = 0; - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - player_count++; - } - - packer_reset(&p); - - if(token >= 0) - { - /* new token based format */ - packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); - str_format(buf, sizeof(buf), "%d", token); - packer_add_string(&p, buf, 6); - } - else - { - /* old format */ - packer_add_raw(&p, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); - } - - packer_add_string(&p, mods_version(), 32); - packer_add_string(&p, config.sv_name, 64); - packer_add_string(&p, config.sv_map, 32); - - /* gametype */ - packer_add_string(&p, browseinfo_gametype, 16); - - /* flags */ - i = 0; - if(config.password[0]) /* password set */ - i |= SRVFLAG_PASSWORD; - str_format(buf, sizeof(buf), "%d", i); - packer_add_string(&p, buf, 2); - - /* progression */ - str_format(buf, sizeof(buf), "%d", browseinfo_progression); - packer_add_string(&p, buf, 4); - - str_format(buf, sizeof(buf), "%d", player_count); packer_add_string(&p, buf, 3); /* num players */ - str_format(buf, sizeof(buf), "%d", netserver_max_clients(net)); packer_add_string(&p, buf, 3); /* max players */ - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - { - packer_add_string(&p, clients[i].name, 48); /* player name */ - str_format(buf, sizeof(buf), "%d", clients[i].score); packer_add_string(&p, buf, 6); /* player score */ - } - } - - - packet.client_id = -1; - packet.address = *addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = packer_size(&p); - packet.data = packer_data(&p); - netserver_send(net, &packet); -} - -extern int register_process_packet(NETCHUNK *packet); -extern int register_update(); - -static void server_pump_network() -{ - NETCHUNK packet; - - netserver_update(net); - - /* process packets */ - while(netserver_recv(net, &packet)) - { - if(packet.client_id == -1) - { - /* stateless */ - if(!register_process_packet(&packet)) - { - if(packet.data_size == sizeof(SERVERBROWSE_GETINFO)+1 && - memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - server_send_serverinfo(&packet.address, ((unsigned char *)packet.data)[sizeof(SERVERBROWSE_GETINFO)]); - } - - - if(packet.data_size == sizeof(SERVERBROWSE_OLD_GETINFO) && - memcmp(packet.data, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) - { - server_send_serverinfo(&packet.address, -1); - } - } - } - else - server_process_client_packet(&packet); - } -} - -static int server_load_map(const char *mapname) -{ - DATAFILE *df; - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - df = datafile_load(buf); - if(!df) - return 0; - - /* stop recording when we change map */ - demorec_record_stop(); - - /* reinit snapshot ids */ - snap_timeout_ids(); - - /* get the crc of the map */ - current_map_crc = datafile_crc(buf); - dbg_msg("server", "%s crc is %08x", buf, current_map_crc); - - str_copy(current_map, mapname, sizeof(current_map)); - map_set(df); - - /* load compelate map into memory for download */ - { - IOHANDLE file = engine_openfile(buf, IOFLAG_READ); - current_map_size = (int)io_length(file); - if(current_map_data) - mem_free(current_map_data); - current_map_data = (unsigned char *)mem_alloc(current_map_size, 1); - io_read(file, current_map_data, current_map_size); - io_close(file); - } - return 1; -} - -static int server_run() -{ - NETADDR bindaddr; - - snap_init_id(); - net_init(); - - /* */ - console_register_print_callback(server_send_rcon_line_authed, 0); - - /* load map */ - if(!server_load_map(config.sv_map)) - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - return -1; - } - - /* start server */ - /* TODO: IPv6 support */ - if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) - { - /* sweet! */ - bindaddr.port = config.sv_port; - } - else - { - mem_zero(&bindaddr, sizeof(bindaddr)); - bindaddr.port = config.sv_port; - } - - net = netserver_open(bindaddr, config.sv_max_clients, 0); - if(!net) - { - dbg_msg("server", "couldn't open socket. port might already be in use"); - return -1; - } - - netserver_set_callbacks(net, new_client_callback, del_client_callback, 0); - - dbg_msg("server", "server name is '%s'", config.sv_name); - - mods_init(); - dbg_msg("server", "version %s", mods_net_version()); - - /* start game */ - { - int64 reporttime = time_get(); - int reportinterval = 3; - - lastheartbeat = 0; - game_start_time = time_get(); - - if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); - - while(run_server) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 t = time_get(); - int new_ticks = 0; - - perf_start(&rootscope); - - /* load new map TODO: don't poll this */ - if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) - { - config.sv_map_reload = 0; - - /* load map */ - if(server_load_map(config.sv_map)) - { - int c; - - /* new map loaded */ - mods_shutdown(); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - - server_send_map(c); - reset_client(c); - clients[c].state = SRVCLIENT_STATE_CONNECTING; - } - - game_start_time = time_get(); - current_tick = 0; - mods_init(); - } - else - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - config_set_sv_map(&config, current_map); - } - } - - while(t > server_tick_start_time(current_tick+1)) - { - current_tick++; - new_ticks++; - - /* apply new input */ - { - static PERFORMACE_INFO scope = {"input", 0}; - int c, i; - - perf_start(&scope); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - for(i = 0; i < 200; i++) - { - if(clients[c].inputs[i].game_tick == server_tick()) - { - if(clients[c].state == SRVCLIENT_STATE_INGAME) - mods_client_predicted_input(c, clients[c].inputs[i].data); - break; - } - } - } - - perf_end(); - } - - /* progress game */ - { - static PERFORMACE_INFO scope = {"tick", 0}; - perf_start(&scope); - mods_tick(); - perf_end(); - } - } - - /* snap game */ - if(new_ticks) - { - if(config.sv_high_bandwidth || (current_tick%2) == 0) - { - static PERFORMACE_INFO scope = {"snap", 0}; - perf_start(&scope); - server_do_snap(); - perf_end(); - } - } - - /* master server stuff */ - register_update(); - - - { - static PERFORMACE_INFO scope = {"net", 0}; - perf_start(&scope); - server_pump_network(); - perf_end(); - } - - perf_end(); - - if(reporttime < time_get()) - { - if(config.debug) - { - /* - static NETSTATS prev_stats; - NETSTATS stats; - netserver_stats(net, &stats); - - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - - dbg_msg("server", "send=%8d recv=%8d", - (stats.send_bytes - prev_stats.send_bytes)/reportinterval, - (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); - - prev_stats = stats; - */ - } - - reporttime += time_freq()*reportinterval; - } - - /* wait for incomming data */ - net_socket_read_wait(netserver_socket(net), 5); - } - } - - mods_shutdown(); - map_unload(); - - if(current_map_data) - mem_free(current_map_data); - return 0; -} - -static void con_kick(void *result, void *user_data) -{ - server_kick(console_arg_int(result, 0), "kicked by console"); -} - -static int str_allnum(const char *str) -{ - while(*str) - { - if(!(*str >= '0' && *str <= '9')) - return 0; - str++; - } - return 1; -} - -static void con_ban(void *result, void *user_data) -{ - NETADDR addr; - char addrstr[128]; - const char *str = console_arg_string(result, 0); - int minutes = 30; - - if(console_arg_num(result) > 1) - minutes = console_arg_int(result, 1); - - if(net_addr_from_str(&addr, str) == 0) - server_ban_add(addr, minutes*60); - else if(str_allnum(str)) - { - NETADDR addr; - int cid = atoi(str); - - if(cid < 0 || cid > MAX_CLIENTS || clients[cid].state == SRVCLIENT_STATE_EMPTY) - { - dbg_msg("server", "invalid client id"); - return; - } - - netserver_client_addr(net, cid, &addr); - server_ban_add(addr, minutes*60); - } - - addr.port = 0; - net_addr_str(&addr, addrstr, sizeof(addrstr)); - - if(minutes) - dbg_msg("server", "banned %s for %d minutes", addrstr, minutes); - else - dbg_msg("server", "banned %s for life", addrstr); -} - -static void con_unban(void *result, void *user_data) -{ - NETADDR addr; - const char *str = console_arg_string(result, 0); - - if(net_addr_from_str(&addr, str) == 0) - server_ban_remove(addr); - else - dbg_msg("server", "invalid network address"); -} - -static void con_bans(void *result, void *user_data) -{ - int i; - unsigned now = time_timestamp(); - NETBANINFO info; - NETADDR addr; - int num = netserver_ban_num(net); - for(i = 0; i < num; i++) - { - unsigned t; - netserver_ban_get(net, i, &info); - addr = info.addr; - - if(info.expires == 0xffffffff) - { - dbg_msg("server", "#%d %d.%d.%d.%d for life", i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3]); - } - else - { - t = info.expires - now; - dbg_msg("server", "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], t/60, t%60); - } - } - dbg_msg("server", "%d ban(s)", num); -} - -static void con_status(void *result, void *user_data) -{ - int i; - NETADDR addr; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - netserver_client_addr(net, i, &addr); - dbg_msg("server", "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", - i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port, - clients[i].name, clients[i].score); - } - } -} - -static void con_shutdown(void *result, void *user_data) -{ - run_server = 0; -} - -static void con_record(void *result, void *user_data) -{ - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); - demorec_record_start(filename, mods_net_version(), current_map, current_map_crc, "server"); -} - -static void con_stoprecord(void *result, void *user_data) -{ - demorec_record_stop(); -} - -static void server_register_commands() -{ - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_kick, 0, ""); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_ban, 0, ""); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_unban, 0, ""); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_bans, 0, ""); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_status, 0, ""); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_shutdown, 0, ""); - - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_SERVER, con_record, 0, ""); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_stoprecord, 0, ""); -} - -int main(int argc, char **argv) -{ -#if defined(CONF_FAMILY_WINDOWS) - int i; - for(i = 1; i < argc; i++) - { - if(strcmp("-s", argv[i]) == 0 || strcmp("--silent", argv[i]) == 0) - { - ShowWindow(GetConsoleWindow(), SW_HIDE); - break; - } - } -#endif - - /* init the engine */ - dbg_msg("server", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - server_register_commands(); - mods_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* run the server */ - server_run(); - return 0; -} - diff --git a/src/engine/server/es_server.cpp b/src/engine/server/es_server.cpp new file mode 100644 index 00000000..cc0e6e0f --- /dev/null +++ b/src/engine/server/es_server.cpp @@ -0,0 +1,1969 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#if defined(CONF_FAMILY_WINDOWS) + #define _WIN32_WINNT 0x0500 + #include +#endif + + +extern int register_process_packet(CNetChunk *pPacket); +extern int register_update(); + +static const char *str_ltrim(const char *str) +{ + while(*str && *str <= 32) + str++; + return str; +} + +static void str_rtrim(char *str) +{ + int i = str_length(str); + while(i >= 0) + { + if(str[i] > 32) + break; + str[i] = 0; + i--; + } +} + + +class CSnapIDPool +{ + enum + { + MAX_IDS = 16*1024, + }; + + class CID + { + public: + short m_Next; + short m_State; /* 0 = free, 1 = alloced, 2 = timed */ + int m_Timeout; + }; + + CID m_aIDs[MAX_IDS]; + + int m_FirstFree; + int m_FirstTimed; + int m_LastTimed; + int m_Usage; + int m_InUsage; + +public: + + CSnapIDPool() + { + Reset(); + } + + void Reset() + { + for(int i = 0; i < MAX_IDS; i++) + { + m_aIDs[i].m_Next = i+1; + m_aIDs[i].m_State = 0; + } + + m_aIDs[MAX_IDS-1].m_Next = -1; + m_FirstFree = 0; + m_FirstTimed = -1; + m_LastTimed = -1; + m_Usage = 0; + m_InUsage = 0; + } + + + void RemoveFirstTimeout() + { + int NextTimed = m_aIDs[m_FirstTimed].m_Next; + + /* add it to the free list */ + m_aIDs[m_FirstTimed].m_Next = m_FirstFree; + m_aIDs[m_FirstTimed].m_State = 0; + m_FirstFree = m_FirstTimed; + + /* remove it from the timed list */ + m_FirstTimed = NextTimed; + if(m_FirstTimed == -1) + m_LastTimed = -1; + + m_Usage--; + } + + int NewID() + { + int64 now = time_get(); + + /* process timed ids */ + while(m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < now) + RemoveFirstTimeout(); + + int id = m_FirstFree; + dbg_assert(id != -1, "id error"); + m_FirstFree = m_aIDs[m_FirstFree].m_Next; + m_aIDs[id].m_State = 1; + m_Usage++; + m_InUsage++; + return id; + } + + void TimeoutIDs() + { + /* process timed ids */ + while(m_FirstTimed != -1) + RemoveFirstTimeout(); + } + + void FreeID(int id) + { + dbg_assert(m_aIDs[id].m_State == 1, "id is not alloced"); + + m_InUsage--; + m_aIDs[id].m_State = 2; + m_aIDs[id].m_Timeout = time_get()+time_freq()*5; + m_aIDs[id].m_Next = -1; + + if(m_LastTimed != -1) + { + m_aIDs[m_LastTimed].m_Next = id; + m_LastTimed = id; + } + else + { + m_FirstTimed = id; + m_LastTimed = id; + } + } +}; + +#if 0 +class IGameServer +{ +public: + /* + Function: mods_console_init + TODO + */ + virtual void ConsoleInit(); + + /* + Function: Init + Called when the server is started. + + Remarks: + It's called after the map is loaded so all map items are available. + */ + void Init() = 0; + + /* + Function: Shutdown + Called when the server quits. + + Remarks: + Should be used to clean up all resources used. + */ + void Shutdown() = 0; + + /* + Function: ClientEnter + Called when a client has joined the game. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + It's called when the client is finished loading and should enter gameplay. + */ + void ClientEnter(int cid) = 0; + + /* + Function: ClientDrop + Called when a client drops from the server. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS + */ + void ClientDrop(int cid) = 0; + + /* + Function: ClientDirectInput + Called when the server recives new input from a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + input - Pointer to the input data. + size - Size of the data. (NOT IMPLEMENTED YET) + */ + void ClientDirectInput(int cid, void *input) = 0; + + /* + Function: ClientPredictedInput + Called when the server applys the predicted input on the client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + input - Pointer to the input data. + size - Size of the data. (NOT IMPLEMENTED YET) + */ + void ClientPredictedInput(int cid, void *input) = 0; + + + /* + Function: Tick + Called with a regular interval to progress the gameplay. + + Remarks: + The SERVER_TICK_SPEED tells the number of ticks per second. + */ + void Tick() = 0; + + /* + Function: Presnap + Called before the server starts to construct snapshots for the clients. + */ + void Presnap() = 0; + + /* + Function: Snap + Called to create the snapshot for a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + The game should make a series of calls to to construct + the snapshot for the client. + */ + void Snap(int cid) = 0; + + /* + Function: PostSnap + Called after the server is done sending the snapshots. + */ + void PostSnap() = 0; + + /* + Function: ClientConnected + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + + */ + void ClientConnected(int client_id) = 0; + + + /* + Function: NetVersion + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + + */ + const char *NetVersion() = 0; + + /* + Function: Version + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + + */ + const char *Version() = 0; + + /* + Function: Message + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + + */ + void Message(int msg, int client_id) = 0; +}; + +#endif +/* +class IServer +{ +public: + BanAdd + BanRemove + TickSpeed + Tick + Kick + SetBrowseInfo + SetClientScore + SetClientName + GetClientInfo + LatestInput + ClientName + + SendMessage() + + Map + + virtual int NewSnapID() = 0; + virtual int FreeSnapID(int i) = 0; +};*/ + +class CServer +{ +public: + /* */ + class CClient + { + public: + + enum + { + STATE_EMPTY = 0, + STATE_AUTH, + STATE_CONNECTING, + STATE_READY, + STATE_INGAME, + + SNAPRATE_INIT=0, + SNAPRATE_FULL, + SNAPRATE_RECOVER + }; + + class CInput + { + public: + int m_aData[MAX_INPUT_SIZE]; + int m_GameTick; /* the tick that was chosen for the input */ + }; + + /* connection state info */ + int m_State; + int m_Latency; + int m_SnapRate; + + int m_LastAckedSnapshot; + int m_LastInputTick; + CSnapshotStorage m_Snapshots; + + CInput m_LatestInput; + CInput m_aInputs[200]; /* TODO: handle input better */ + int m_CurrentInput; + + char m_aName[MAX_NAME_LENGTH]; + char m_aClan[MAX_CLANNAME_LENGTH]; + int m_Score; + int m_Authed; + + void Reset() + { + /* reset input */ + for(int i = 0; i < 200; i++) + m_aInputs[i].m_GameTick = -1; + m_CurrentInput = 0; + mem_zero(&m_LatestInput, sizeof(m_LatestInput)); + + m_Snapshots.PurgeAll(); + m_LastAckedSnapshot = -1; + m_LastInputTick = -1; + m_SnapRate = CClient::SNAPRATE_INIT; + m_Score = 0; + } + }; + + CClient m_aClients[MAX_CLIENTS]; + + CSnapshotBuilder m_SnapshotBuilder; + CSnapIDPool m_IDPool; + CNetServer m_NetServer; + + int64 m_GameStartTime; + int m_CurrentTick; + int m_RunServer; + + char m_aBrowseinfoGametype[16]; + int m_BrowseinfoProgression; + + int64 m_Lastheartbeat; + /*static NETADDR4 master_server;*/ + + char m_aCurrentMap[64]; + int m_CurrentMapCrc; + unsigned char *m_pCurrentMapData; + int m_CurrentMapSize; + + CServer() + { + m_CurrentTick = 0; + m_RunServer = 1; + + mem_zero(m_aBrowseinfoGametype, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = -1; + + m_pCurrentMapData = 0; + m_CurrentMapSize = 0; + } + + + int TrySetClientName(int ClientID, const char *pName) + { + int i; + char aTrimmedName[64]; + + /* trim the name */ + str_copy(aTrimmedName, str_ltrim(pName), sizeof(aTrimmedName)); + str_rtrim(aTrimmedName); + dbg_msg("", "'%s' -> '%s'", pName, aTrimmedName); + pName = aTrimmedName; + + + /* check for empty names */ + if(!pName[0]) + return -1; + + /* make sure that two clients doesn't have the same name */ + for(i = 0; i < MAX_CLIENTS; i++) + if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) + { + if(strcmp(pName, m_aClients[i].m_aName) == 0) + return -1; + } + + /* set the client name */ + str_copy(m_aClients[ClientID].m_aName, pName, MAX_NAME_LENGTH); + return 0; + } + + + + void SetClientName(int ClientID, const char *pName) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + + if(!pName) + return; + + char aNameTry[MAX_NAME_LENGTH]; + str_copy(aNameTry, pName, MAX_NAME_LENGTH); + if(TrySetClientName(ClientID, aNameTry)) + { + /* auto rename */ + for(int i = 1;; i++) + { + str_format(aNameTry, MAX_NAME_LENGTH, "(%d)%s", i, pName); + if(TrySetClientName(ClientID, aNameTry) == 0) + break; + } + } + } + + void SetClientScore(int ClientID, int Score) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + m_aClients[ClientID].m_Score = Score; + } + + void SetBrowseInfo(const char *pGameType, int Progression) + { + str_copy(m_aBrowseinfoGametype, pGameType, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = Progression; + if(m_BrowseinfoProgression > 100) + m_BrowseinfoProgression = 100; + if(m_BrowseinfoProgression < -1) + m_BrowseinfoProgression = -1; + } + + void Kick(int ClientID, const char *pReason) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS) + return; + + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY) + m_NetServer.Drop(ClientID, pReason); + } + + int Tick() + { + return m_CurrentTick; + } + + int64 TickStartTime(int Tick) + { + return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; + } + + int TickSpeed() + { + return SERVER_TICK_SPEED; + } + + int Init() + { + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + m_aClients[i].m_State = CClient::STATE_EMPTY; + m_aClients[i].m_aName[0] = 0; + m_aClients[i].m_aClan[0] = 0; + m_aClients[i].m_Snapshots.Init(); + } + + m_CurrentTick = 0; + + return 0; + } + + int GetClientInfo(int ClientID, CLIENT_INFO *pInfo) + { + dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); + dbg_assert(pInfo != 0, "info can not be null"); + + if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + pInfo->name = m_aClients[ClientID].m_aName; + pInfo->latency = m_aClients[ClientID].m_Latency; + return 1; + } + return 0; + } + + int SendMsg(int ClientID) + { + const MSG_INFO *pInfo = msg_get_info(); + CNetChunk Packet; + if(!pInfo) + return -1; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = ClientID; + Packet.m_pData = pInfo->data; + Packet.m_DataSize = pInfo->size; + + if(pInfo->flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(pInfo->flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + /* write message to demo recorder */ + if(!(pInfo->flags&MSGFLAG_NORECORD)) + demorec_record_message(pInfo->data, pInfo->size); + + if(!(pInfo->flags&MSGFLAG_NOSEND)) + { + if(ClientID == -1) + { + /* broadcast */ + int i; + for(i = 0; i < MAX_CLIENTS; i++) + if(m_aClients[i].m_State == CClient::STATE_INGAME) + { + Packet.m_ClientID = i; + m_NetServer.Send(&Packet); + } + } + else + m_NetServer.Send(&Packet); + } + return 0; + } + + void DoSnapshot() + { + int i; + + { + static PERFORMACE_INFO scope = {"presnap", 0}; + perf_start(&scope); + mods_presnap(); + perf_end(); + } + + /* create snapshot for demo recording */ + if(demorec_isrecording()) + { + char data[CSnapshot::MAX_SIZE]; + int snapshot_size; + + /* build snap and possibly add some messages */ + m_SnapshotBuilder.Init(); + mods_snap(-1); + snapshot_size = m_SnapshotBuilder.Finish(data); + + /* write snapshot */ + demorec_record_snapshot(Tick(), data, snapshot_size); + } + + /* create snapshots for all clients */ + for(i = 0; i < MAX_CLIENTS; i++) + { + /* client must be ingame to recive snapshots */ + if(m_aClients[i].m_State != CClient::STATE_INGAME) + continue; + + /* this client is trying to recover, don't spam snapshots */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick()%50) != 0) + continue; + + /* this client is trying to recover, don't spam snapshots */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick()%10) != 0) + continue; + + { + char data[CSnapshot::MAX_SIZE]; + char deltadata[CSnapshot::MAX_SIZE]; + char compdata[CSnapshot::MAX_SIZE]; + int snapshot_size; + int crc; + static CSnapshot emptysnap; + CSnapshot *deltashot = &emptysnap; + int deltashot_size; + int delta_tick = -1; + int deltasize; + static PERFORMACE_INFO scope = {"build", 0}; + perf_start(&scope); + + m_SnapshotBuilder.Init(); + + { + static PERFORMACE_INFO scope = {"modsnap", 0}; + perf_start(&scope); + mods_snap(i); + perf_end(); + } + + /* finish snapshot */ + snapshot_size = m_SnapshotBuilder.Finish(data); + crc = ((CSnapshot*)data)->Crc(); + + /* remove old snapshos */ + /* keep 3 seconds worth of snapshots */ + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentTick-SERVER_TICK_SPEED*3); + + /* save it the snapshot */ + m_aClients[i].m_Snapshots.Add(m_CurrentTick, time_get(), snapshot_size, data, 0); + + /* find snapshot that we can preform delta against */ + emptysnap.m_DataSize = 0; + emptysnap.m_NumItems = 0; + + { + deltashot_size = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &deltashot, 0); + if(deltashot_size >= 0) + delta_tick = m_aClients[i].m_LastAckedSnapshot; + else + { + /* no acked package found, force client to recover rate */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) + m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER; + } + } + + /* create delta */ + { + static PERFORMACE_INFO scope = {"delta", 0}; + perf_start(&scope); + deltasize = CSnapshot::CreateDelta(deltashot, (CSnapshot*)data, deltadata); + perf_end(); + } + + + if(deltasize) + { + /* compress it */ + int snapshot_size; + const int max_size = MAX_SNAPSHOT_PACKSIZE; + int numpackets; + int n, left; + + { + static PERFORMACE_INFO scope = {"compress", 0}; + /*char buffer[512];*/ + perf_start(&scope); + snapshot_size = intpack_compress(deltadata, deltasize, compdata); + + /*str_hex(buffer, sizeof(buffer), compdata, snapshot_size); + dbg_msg("", "deltasize=%d -> %d : %s", deltasize, snapshot_size, buffer);*/ + + perf_end(); + } + + numpackets = (snapshot_size+max_size-1)/max_size; + + for(n = 0, left = snapshot_size; left; n++) + { + int chunk = left < max_size ? left : max_size; + left -= chunk; + + if(numpackets == 1) + msg_pack_start_system(NETMSG_SNAPSINGLE, MSGFLAG_FLUSH); + else + msg_pack_start_system(NETMSG_SNAP, MSGFLAG_FLUSH); + + msg_pack_int(m_CurrentTick); + msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ + + if(numpackets != 1) + { + msg_pack_int(numpackets); + msg_pack_int(n); + } + + msg_pack_int(crc); + msg_pack_int(chunk); + msg_pack_raw(&compdata[n*max_size], chunk); + msg_pack_end(); + SendMsg(i); + } + } + else + { + msg_pack_start_system(NETMSG_SNAPEMPTY, MSGFLAG_FLUSH); + msg_pack_int(m_CurrentTick); + msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ + msg_pack_end(); + SendMsg(i); + } + + perf_end(); + } + } + + mods_postsnap(); + } + + + static int NewClientCallback(int cid, void *pUser) + { + CServer *pThis = (CServer *)pUser; + pThis->m_aClients[cid].m_State = CClient::STATE_AUTH; + pThis->m_aClients[cid].m_aName[0] = 0; + pThis->m_aClients[cid].m_aClan[0] = 0; + pThis->m_aClients[cid].m_Authed = 0; + pThis->m_aClients[cid].Reset(); + return 0; + } + + static int DelClientCallback(int cid, void *pUser) + { + CServer *pThis = (CServer *)pUser; + + /* notify the mod about the drop */ + if(pThis->m_aClients[cid].m_State >= CClient::STATE_READY) + mods_client_drop(cid); + + pThis->m_aClients[cid].m_State = CClient::STATE_EMPTY; + pThis->m_aClients[cid].m_aName[0] = 0; + pThis->m_aClients[cid].m_aClan[0] = 0; + pThis->m_aClients[cid].m_Authed = 0; + pThis->m_aClients[cid].m_Snapshots.PurgeAll(); + return 0; + } + + void SendMap(int cid) + { + msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(config.sv_map, 0); + msg_pack_int(m_CurrentMapCrc); + msg_pack_end(); + server_send_msg(cid); + } + + void SendRconLine(int cid, const char *pLine) + { + msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); + msg_pack_string(pLine, 512); + msg_pack_end(); + server_send_msg(cid); + } + + static void SendRconLineAuthed(const char *pLine, void *pUser) + { + CServer *pThis = (CServer *)pUser; + static volatile int reentry_guard = 0; + int i; + + if(reentry_guard) return; + reentry_guard++; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) + pThis->SendRconLine(i, pLine); + } + + reentry_guard--; + } + + void ProcessClientPacket(CNetChunk *pPacket) + { + int cid = pPacket->m_ClientID; + NETADDR addr; + + int sys; + int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); + + if(m_aClients[cid].m_State == CClient::STATE_AUTH) + { + if(sys && msg == NETMSG_INFO) + { + char version[64]; + const char *password; + str_copy(version, msg_unpack_string(), 64); + if(strcmp(version, mods_net_version()) != 0) + { + /* OH FUCK! wrong version, drop him */ + char reason[256]; + str_format(reason, sizeof(reason), "wrong version. server is running '%s' and client '%s'.", mods_net_version(), version); + m_NetServer.Drop(cid, reason); + return; + } + + str_copy(m_aClients[cid].m_aName, msg_unpack_string(), MAX_NAME_LENGTH); + str_copy(m_aClients[cid].m_aClan, msg_unpack_string(), MAX_CLANNAME_LENGTH); + password = msg_unpack_string(); + + if(config.password[0] != 0 && strcmp(config.password, password) != 0) + { + /* wrong password */ + m_NetServer.Drop(cid, "wrong password"); + return; + } + + m_aClients[cid].m_State = CClient::STATE_CONNECTING; + SendMap(cid); + } + } + else + { + if(sys) + { + /* system message */ + if(msg == NETMSG_REQUEST_MAP_DATA) + { + int chunk = msg_unpack_int(); + int chunk_size = 1024-128; + int offset = chunk * chunk_size; + int last = 0; + + /* drop faulty map data requests */ + if(chunk < 0 || offset > m_CurrentMapSize) + return; + + if(offset+chunk_size >= m_CurrentMapSize) + { + chunk_size = m_CurrentMapSize-offset; + if(chunk_size < 0) + chunk_size = 0; + last = 1; + } + + msg_pack_start_system(NETMSG_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(last); + msg_pack_int(m_CurrentMapSize); + msg_pack_int(chunk_size); + msg_pack_raw(&m_pCurrentMapData[offset], chunk_size); + msg_pack_end(); + server_send_msg(cid); + + if(config.debug) + dbg_msg("server", "sending chunk %d with size %d", chunk, chunk_size); + } + else if(msg == NETMSG_READY) + { + if(m_aClients[cid].m_State == CClient::STATE_CONNECTING) + { + addr = m_NetServer.ClientAddr(cid); + + dbg_msg("server", "player is ready. cid=%x ip=%d.%d.%d.%d", + cid, + addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] + ); + m_aClients[cid].m_State = CClient::STATE_READY; + mods_connected(cid); + } + } + else if(msg == NETMSG_ENTERGAME) + { + if(m_aClients[cid].m_State == CClient::STATE_READY) + { + addr = m_NetServer.ClientAddr(cid); + + dbg_msg("server", "player has entered the game. cid=%x ip=%d.%d.%d.%d", + cid, + addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] + ); + m_aClients[cid].m_State = CClient::STATE_INGAME; + mods_client_enter(cid); + } + } + else if(msg == NETMSG_INPUT) + { + int tick, size, i; + CClient::CInput *pInput; + int64 tagtime; + + m_aClients[cid].m_LastAckedSnapshot = msg_unpack_int(); + tick = msg_unpack_int(); + size = msg_unpack_int(); + + /* check for errors */ + if(msg_unpack_error() || size/4 > MAX_INPUT_SIZE) + return; + + if(m_aClients[cid].m_LastAckedSnapshot > 0) + m_aClients[cid].m_SnapRate = CClient::SNAPRATE_FULL; + + if(m_aClients[cid].m_Snapshots.Get(m_aClients[cid].m_LastAckedSnapshot, &tagtime, 0, 0) >= 0) + m_aClients[cid].m_Latency = (int)(((time_get()-tagtime)*1000)/time_freq()); + + /* add message to report the input timing */ + /* skip packets that are old */ + if(tick > m_aClients[cid].m_LastInputTick) + { + int time_left = ((TickStartTime(tick)-time_get())*1000) / time_freq(); + msg_pack_start_system(NETMSG_INPUTTIMING, 0); + msg_pack_int(tick); + msg_pack_int(time_left); + msg_pack_end(); + server_send_msg(cid); + } + + m_aClients[cid].m_LastInputTick = tick; + + pInput = &m_aClients[cid].m_aInputs[m_aClients[cid].m_CurrentInput]; + + if(tick <= server_tick()) + tick = server_tick()+1; + + pInput->m_GameTick = tick; + + for(i = 0; i < size/4; i++) + pInput->m_aData[i] = msg_unpack_int(); + + mem_copy(m_aClients[cid].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); + + m_aClients[cid].m_CurrentInput++; + m_aClients[cid].m_CurrentInput %= 200; + + /* call the mod with the fresh input data */ + if(m_aClients[cid].m_State == CClient::STATE_INGAME) + mods_client_direct_input(cid, m_aClients[cid].m_LatestInput.m_aData); + } + else if(msg == NETMSG_RCON_CMD) + { + const char *cmd = msg_unpack_string(); + + if(msg_unpack_error() == 0 && m_aClients[cid].m_Authed) + { + dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); + console_execute_line(cmd); + } + } + else if(msg == NETMSG_RCON_AUTH) + { + const char *pw; + msg_unpack_string(); /* login name, not used */ + pw = msg_unpack_string(); + + if(msg_unpack_error() == 0) + { + if(config.sv_rcon_password[0] == 0) + { + SendRconLine(cid, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + } + else if(strcmp(pw, config.sv_rcon_password) == 0) + { + msg_pack_start_system(NETMSG_RCON_AUTH_STATUS, MSGFLAG_VITAL); + msg_pack_int(1); + msg_pack_end(); + server_send_msg(cid); + + m_aClients[cid].m_Authed = 1; + SendRconLine(cid, "Authentication successful. Remote console access granted."); + dbg_msg("server", "cid=%d authed", cid); + } + else + { + SendRconLine(cid, "Wrong password."); + } + } + } + else if(msg == NETMSG_PING) + { + msg_pack_start_system(NETMSG_PING_REPLY, 0); + msg_pack_end(); + server_send_msg(cid); + } + else + { + char hex[] = "0123456789ABCDEF"; + char buf[512]; + int b; + + for(b = 0; b < pPacket->m_DataSize && b < 32; b++) + { + buf[b*3] = hex[((const unsigned char *)pPacket->m_pData)[b]>>4]; + buf[b*3+1] = hex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; + buf[b*3+2] = ' '; + buf[b*3+3] = 0; + } + + dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, pPacket->m_DataSize); + dbg_msg("server", "%s", buf); + + } + } + else + { + /* game message */ + if(m_aClients[cid].m_State >= CClient::STATE_READY) + mods_message(msg, cid); + } + } + } + + void SendServerInfo(NETADDR *addr, int token) + { + CNetChunk Packet; + CPacker p; + char buf[128]; + + /* count the players */ + int player_count = 0; + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + player_count++; + } + + p.Reset(); + + if(token >= 0) + { + /* new token based format */ + p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); + str_format(buf, sizeof(buf), "%d", token); + p.AddString(buf, 6); + } + else + { + /* old format */ + p.AddRaw(SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); + } + + p.AddString(mods_version(), 32); + p.AddString(config.sv_name, 64); + p.AddString(config.sv_map, 32); + + /* gametype */ + p.AddString(m_aBrowseinfoGametype, 16); + + /* flags */ + i = 0; + if(config.password[0]) /* password set */ + i |= SRVFLAG_PASSWORD; + str_format(buf, sizeof(buf), "%d", i); + p.AddString(buf, 2); + + /* progression */ + str_format(buf, sizeof(buf), "%d", m_BrowseinfoProgression); + p.AddString(buf, 4); + + str_format(buf, sizeof(buf), "%d", player_count); p.AddString(buf, 3); /* num players */ + str_format(buf, sizeof(buf), "%d", m_NetServer.MaxClients()); p.AddString(buf, 3); /* max players */ + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + p.AddString(m_aClients[i].m_aName, 48); /* player name */ + str_format(buf, sizeof(buf), "%d", m_aClients[i].m_Score); p.AddString(buf, 6); /* player score */ + } + } + + + Packet.m_ClientID = -1; + Packet.m_Address = *addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = p.Size(); + Packet.m_pData = p.Data(); + m_NetServer.Send(&Packet); + } + + int BanAdd(NETADDR Addr, int Seconds) + { + return m_NetServer.BanAdd(Addr, Seconds); + } + + int BanRemove(NETADDR Addr) + { + return m_NetServer.BanRemove(Addr); + } + + + void PumpNetwork() + { + CNetChunk Packet; + + m_NetServer.Update(); + + /* process packets */ + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + /* stateless */ + if(!register_process_packet(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); + } + + + if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && + memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, -1); + } + } + } + else + ProcessClientPacket(&Packet); + } + } + + int LoadMap(const char *pMapName) + { + DATAFILE *df; + char buf[512]; + str_format(buf, sizeof(buf), "maps/%s.map", pMapName); + df = datafile_load(buf); + if(!df) + return 0; + + /* stop recording when we change map */ + demorec_record_stop(); + + /* reinit snapshot ids */ + m_IDPool.TimeoutIDs(); + + /* get the crc of the map */ + m_CurrentMapCrc = datafile_crc(buf); + dbg_msg("server", "%s crc is %08x", buf, m_CurrentMapCrc); + + str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); + map_set(df); + + /* load compelate map into memory for download */ + { + IOHANDLE file = engine_openfile(buf, IOFLAG_READ); + m_CurrentMapSize = (int)io_length(file); + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + m_pCurrentMapData = (unsigned char *)mem_alloc(m_CurrentMapSize, 1); + io_read(file, m_pCurrentMapData, m_CurrentMapSize); + io_close(file); + } + return 1; + } + + int Run() + { + NETADDR bindaddr; + + //snap_init_id(); + net_init(); + + /* */ + console_register_print_callback(SendRconLineAuthed, this); + + /* load map */ + if(!LoadMap(config.sv_map)) + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + return -1; + } + + /* start server */ + /* TODO: IPv6 support */ + if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) + { + /* sweet! */ + bindaddr.port = config.sv_port; + } + else + { + mem_zero(&bindaddr, sizeof(bindaddr)); + bindaddr.port = config.sv_port; + } + + + if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); + + dbg_msg("server", "server name is '%s'", config.sv_name); + + mods_init(); + dbg_msg("server", "version %s", mods_net_version()); + + /* start game */ + { + int64 reporttime = time_get(); + int reportinterval = 3; + + m_Lastheartbeat = 0; + m_GameStartTime = time_get(); + + if(config.debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(m_RunServer) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 t = time_get(); + int new_ticks = 0; + + perf_start(&rootscope); + + /* load new map TODO: don't poll this */ + if(strcmp(config.sv_map, m_aCurrentMap) != 0 || config.sv_map_reload) + { + config.sv_map_reload = 0; + + /* load map */ + if(LoadMap(config.sv_map)) + { + int c; + + /* new map loaded */ + mods_shutdown(); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + + SendMap(c); + m_aClients[c].Reset(); + m_aClients[c].m_State = CClient::STATE_CONNECTING; + } + + m_GameStartTime = time_get(); + m_CurrentTick = 0; + mods_init(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + config_set_sv_map(&config, m_aCurrentMap); + } + } + + while(t > TickStartTime(m_CurrentTick+1)) + { + m_CurrentTick++; + new_ticks++; + + /* apply new input */ + { + static PERFORMACE_INFO scope = {"input", 0}; + int c, i; + + perf_start(&scope); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(m_aClients[c].m_aInputs[i].m_GameTick == server_tick()) + { + if(m_aClients[c].m_State == CClient::STATE_INGAME) + mods_client_predicted_input(c, m_aClients[c].m_aInputs[i].m_aData); + break; + } + } + } + + perf_end(); + } + + /* progress game */ + { + static PERFORMACE_INFO scope = {"tick", 0}; + perf_start(&scope); + mods_tick(); + perf_end(); + } + } + + /* snap game */ + if(new_ticks) + { + if(config.sv_high_bandwidth || (m_CurrentTick%2) == 0) + { + static PERFORMACE_INFO scope = {"snap", 0}; + perf_start(&scope); + DoSnapshot(); + perf_end(); + } + } + + /* master server stuff */ + register_update(); + + + { + static PERFORMACE_INFO scope = {"net", 0}; + perf_start(&scope); + PumpNetwork(); + perf_end(); + } + + perf_end(); + + if(reporttime < time_get()) + { + if(config.debug) + { + /* + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + */ + } + + reporttime += time_freq()*reportinterval; + } + + /* wait for incomming data */ + net_socket_read_wait(m_NetServer.Socket(), 5); + } + } + + mods_shutdown(); + map_unload(); + + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + return 0; + } + + static void ConKick(void *pResult, void *pUser) + { + ((CServer *)pUser)->Kick(console_arg_int(pResult, 0), "kicked by console"); + } + + static int str_allnum(const char *str) + { + while(*str) + { + if(!(*str >= '0' && *str <= '9')) + return 0; + str++; + } + return 1; + } + + static void ConBan(void *pResult, void *pUser) + { + NETADDR addr; + char addrstr[128]; + const char *str = console_arg_string(pResult, 0); + int minutes = 30; + + if(console_arg_num(pResult) > 1) + minutes = console_arg_int(pResult, 1); + + if(net_addr_from_str(&addr, str) == 0) + ((CServer *)pUser)->BanAdd(addr, minutes*60); + else if(str_allnum(str)) + { + NETADDR addr; + int cid = atoi(str); + + if(cid < 0 || cid > MAX_CLIENTS || ((CServer *)pUser)->m_aClients[cid].m_State == CClient::STATE_EMPTY) + { + dbg_msg("server", "invalid client id"); + return; + } + + addr = ((CServer *)pUser)->m_NetServer.ClientAddr(cid); + ((CServer *)pUser)->BanAdd(addr, minutes*60); + } + + addr.port = 0; + net_addr_str(&addr, addrstr, sizeof(addrstr)); + + if(minutes) + dbg_msg("server", "banned %s for %d minutes", addrstr, minutes); + else + dbg_msg("server", "banned %s for life", addrstr); + } + + static void ConUnban(void *result, void *pUser) + { + NETADDR addr; + const char *str = console_arg_string(result, 0); + + if(net_addr_from_str(&addr, str) == 0) + ((CServer *)pUser)->BanRemove(addr); + else + dbg_msg("server", "invalid network address"); + } + + static void ConBans(void *pResult, void *pUser) + { + unsigned now = time_timestamp(); + + int num = ((CServer *)pUser)->m_NetServer.BanNum(); + for(int i = 0; i < num; i++) + { + CNetServer::CBanInfo Info; + ((CServer *)pUser)->m_NetServer.BanGet(i, &Info); + NETADDR Addr = Info.m_Addr; + + if(Info.m_Expires == -1) + { + dbg_msg("server", "#%d %d.%d.%d.%d for life", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + } + else + { + unsigned t = Info.m_Expires - now; + dbg_msg("server", "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], t/60, t%60); + } + } + dbg_msg("server", "%d ban(s)", num); + } + + static void ConStatus(void *pResult, void *pUser) + { + int i; + NETADDR addr; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(((CServer *)pUser)->m_aClients[i].m_State == CClient::STATE_INGAME) + { + addr = ((CServer *)pUser)->m_NetServer.ClientAddr(i); + dbg_msg("server", "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", + i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port, + ((CServer *)pUser)->m_aClients[i].m_aName, ((CServer *)pUser)->m_aClients[i].m_Score); + } + } + } + + static void ConShutdown(void *pResult, void *pUser) + { + ((CServer *)pUser)->m_RunServer = 0; + } + + static void ConRecord(void *pResult, void *pUser) + { + char filename[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(pResult, 0)); + demorec_record_start(filename, mods_net_version(), ((CServer *)pUser)->m_aCurrentMap, ((CServer *)pUser)->m_CurrentMapCrc, "server"); + } + + static void ConStopRecord(void *pResult, void *pUser) + { + demorec_record_stop(); + } + + + void RegisterCommands() + { + MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, ConKick, this, ""); + MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, ConBan, this, ""); + MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, ConUnban, this, ""); + MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, ConBans, this, ""); + MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, ConStatus, this, ""); + MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + + MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_SERVER, ConRecord, this, ""); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + } + +}; + + +// UGLY UGLY HACK for now +CServer g_Server; +CNetServer *m_pNetServer; + +int server_tick() { return g_Server.Tick(); } +int server_tickspeed() { return g_Server.TickSpeed(); } +int snap_new_id() { return g_Server.m_IDPool.NewID(); } +void snap_free_id(int id) { return g_Server.m_IDPool.FreeID(id); } +int server_send_msg(int client_id) { return g_Server.SendMsg(client_id); } +void server_setbrowseinfo(const char *game_type, int progression) { g_Server.SetBrowseInfo(game_type, progression); } +void server_setclientname(int client_id, const char *name) { g_Server.SetClientName(client_id, name); } +void server_setclientscore(int client_id, int score) { g_Server.SetClientScore(client_id, score); } + +int server_getclientinfo(int client_id, CLIENT_INFO *info) { return g_Server.GetClientInfo(client_id, info); } + +void *snap_new_item(int type, int id, int size) +{ + dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); + dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); + return g_Server.m_SnapshotBuilder.NewItem(type, id, size); +} + +int *server_latestinput(int client_id, int *size) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) + return 0; + return g_Server.m_aClients[client_id].m_LatestInput.m_aData; +} + +const char *server_clientname(int client_id) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) + return "(invalid client)"; + return g_Server.m_aClients[client_id].m_aName; +} + +#if 0 + + +static void reset_client(int cid) +{ + /* reset input */ + int i; + for(i = 0; i < 200; i++) + m_aClients[cid].m_aInputs[i].m_GameTick = -1; + m_aClients[cid].m_CurrentInput = 0; + mem_zero(&m_aClients[cid].m_Latestinput, sizeof(m_aClients[cid].m_Latestinput)); + + m_aClients[cid].m_Snapshots.PurgeAll(); + m_aClients[cid].m_LastAckedSnapshot = -1; + m_aClients[cid].m_LastInputTick = -1; + m_aClients[cid].m_SnapRate = CClient::SNAPRATE_INIT; + m_aClients[cid].m_Score = 0; + +} + + +static int new_client_callback(int cid, void *user) +{ + m_aClients[cid].state = CClient::STATE_AUTH; + m_aClients[cid].name[0] = 0; + m_aClients[cid].clan[0] = 0; + m_aClients[cid].authed = 0; + reset_client(cid); + return 0; +} + +static int del_client_callback(int cid, void *user) +{ + /* notify the mod about the drop */ + if(m_aClients[cid].state >= CClient::STATE_READY) + mods_client_drop(cid); + + m_aClients[cid].state = CClient::STATE_EMPTY; + m_aClients[cid].name[0] = 0; + m_aClients[cid].clan[0] = 0; + m_aClients[cid].authed = 0; + m_aClients[cid].snapshots.PurgeAll(); + return 0; +} +static void server_send_map(int cid) +{ + msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(config.sv_map, 0); + msg_pack_int(current_map_crc); + msg_pack_end(); + server_send_msg(cid); +} + +static void server_send_rcon_line(int cid, const char *line) +{ + msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); + msg_pack_string(line, 512); + msg_pack_end(); + server_send_msg(cid); +} + +static void server_send_rcon_line_authed(const char *line, void *user_data) +{ + static volatile int reentry_guard = 0; + int i; + + if(reentry_guard) return; + reentry_guard++; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].state != CClient::STATE_EMPTY && m_aClients[i].authed) + server_send_rcon_line(i, line); + } + + reentry_guard--; +} + +static void server_pump_network() +{ + CNetChunk Packet; + + m_NetServer.Update(); + + /* process packets */ + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + /* stateless */ + if(!register_process_packet(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + server_send_serverinfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); + } + + + if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && + memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + server_send_serverinfo(&Packet.m_Address, -1); + } + } + } + else + server_process_client_packet(&Packet); + } +} + +static int server_load_map(const char *mapname) +{ + DATAFILE *df; + char buf[512]; + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + df = datafile_load(buf); + if(!df) + return 0; + + /* stop recording when we change map */ + demorec_record_stop(); + + /* reinit snapshot ids */ + snap_timeout_ids(); + + /* get the crc of the map */ + current_map_crc = datafile_crc(buf); + dbg_msg("server", "%s crc is %08x", buf, current_map_crc); + + str_copy(current_map, mapname, sizeof(current_map)); + map_set(df); + + /* load compelate map into memory for download */ + { + IOHANDLE file = engine_openfile(buf, IOFLAG_READ); + current_map_size = (int)io_length(file); + if(current_map_data) + mem_free(current_map_data); + current_map_data = (unsigned char *)mem_alloc(current_map_size, 1); + io_read(file, current_map_data, current_map_size); + io_close(file); + } + return 1; +} + +static int server_run() +{ + NETADDR bindaddr; + + snap_init_id(); + net_init(); + + /* */ + console_register_print_callback(server_send_rcon_line_authed, 0); + + /* load map */ + if(!server_load_map(config.sv_map)) + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + return -1; + } + + /* start server */ + /* TODO: IPv6 support */ + if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) + { + /* sweet! */ + bindaddr.port = config.sv_port; + } + else + { + mem_zero(&bindaddr, sizeof(bindaddr)); + bindaddr.port = config.sv_port; + } + + + if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + m_NetServer.SetCallbacks(new_client_callback, del_client_callback, this); + + dbg_msg("server", "server name is '%s'", config.sv_name); + + mods_init(); + dbg_msg("server", "version %s", mods_net_version()); + + /* start game */ + { + int64 reporttime = time_get(); + int reportinterval = 3; + + lastheartbeat = 0; + game_start_time = time_get(); + + if(config.debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(run_server) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 t = time_get(); + int new_ticks = 0; + + perf_start(&rootscope); + + /* load new map TODO: don't poll this */ + if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) + { + config.sv_map_reload = 0; + + /* load map */ + if(server_load_map(config.sv_map)) + { + int c; + + /* new map loaded */ + mods_shutdown(); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].state == CClient::STATE_EMPTY) + continue; + + server_send_map(c); + reset_client(c); + m_aClients[c].state = CClient::STATE_CONNECTING; + } + + game_start_time = time_get(); + current_tick = 0; + mods_init(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + config_set_sv_map(&config, current_map); + } + } + + while(t > server_tick_start_time(current_tick+1)) + { + current_tick++; + new_ticks++; + + /* apply new input */ + { + static PERFORMACE_INFO scope = {"input", 0}; + int c, i; + + perf_start(&scope); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].state == CClient::STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(m_aClients[c].inputs[i].game_tick == server_tick()) + { + if(m_aClients[c].state == CClient::STATE_INGAME) + mods_client_predicted_input(c, m_aClients[c].inputs[i].data); + break; + } + } + } + + perf_end(); + } + + /* progress game */ + { + static PERFORMACE_INFO scope = {"tick", 0}; + perf_start(&scope); + mods_tick(); + perf_end(); + } + } + + /* snap game */ + if(new_ticks) + { + if(config.sv_high_bandwidth || (current_tick%2) == 0) + { + static PERFORMACE_INFO scope = {"snap", 0}; + perf_start(&scope); + server_do_snap(); + perf_end(); + } + } + + /* master server stuff */ + register_update(); + + + { + static PERFORMACE_INFO scope = {"net", 0}; + perf_start(&scope); + server_pump_network(); + perf_end(); + } + + perf_end(); + + if(reporttime < time_get()) + { + if(config.debug) + { + /* + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + */ + } + + reporttime += time_freq()*reportinterval; + } + + /* wait for incomming data */ + net_socket_read_wait(m_NetServer.Socket(), 5); + } + } + + mods_shutdown(); + map_unload(); + + if(current_map_data) + mem_free(current_map_data); + return 0; +} + + +#endif + +int main(int argc, char **argv) +{ + + m_pNetServer = &g_Server.m_NetServer; +#if defined(CONF_FAMILY_WINDOWS) + int i; + for(i = 1; i < argc; i++) + { + if(strcmp("-s", argv[i]) == 0 || strcmp("--silent", argv[i]) == 0) + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + break; + } + } +#endif + + /* init the engine */ + dbg_msg("server", "starting..."); + engine_init("Teeworlds"); + + /* register all console commands */ + + g_Server.RegisterCommands(); + mods_console_init(); + + /* parse the command line arguments */ + engine_parse_arguments(argc, argv); + + /* run the server */ + g_Server.Run(); + return 0; +} + -- cgit 1.4.1