diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-27 00:47:07 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-27 00:47:07 +0000 |
| commit | bdcc0b09ddc630c166f0dfebcf376765fb1f9801 (patch) | |
| tree | 90fd22f041b6e4519f3459fcad983930afc2a647 /src/engine | |
| parent | 4a128a9609cc5fc61077b3cd73ffa831551b7dc2 (diff) | |
| download | zcatch-bdcc0b09ddc630c166f0dfebcf376765fb1f9801.tar.gz zcatch-bdcc0b09ddc630c166f0dfebcf376765fb1f9801.zip | |
major update. added png support. fixed abit with the network
Diffstat (limited to 'src/engine')
| -rw-r--r-- | src/engine/client/client.cpp | 15 | ||||
| -rw-r--r-- | src/engine/client/gfx.cpp | 91 | ||||
| -rw-r--r-- | src/engine/client/pnglite/pnglite.c | 612 | ||||
| -rw-r--r-- | src/engine/client/pnglite/pnglite.h | 212 | ||||
| -rw-r--r-- | src/engine/interface.h | 15 | ||||
| -rw-r--r-- | src/engine/packet.h | 186 | ||||
| -rw-r--r-- | src/engine/server/server.cpp | 15 | ||||
| -rw-r--r-- | src/engine/versions.h | 3 |
8 files changed, 1111 insertions, 38 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 4aceaf0f..dd52608c 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -6,6 +6,7 @@ #include <string.h> #include <stdarg.h> +#include <stdlib.h> #include <math.h> #include <engine/interface.h> @@ -331,7 +332,7 @@ public: */ packet p(NETMSG_CLIENT_CONNECT); - p.write_str(TEEWARS_NETVERSION); // payload + p.write_str(TEEWARS_VERSION); // payload p.write_str(name); p.write_str("no clan"); p.write_str("password"); @@ -388,7 +389,7 @@ public: bool load_data() { - debug_font = gfx_load_texture_tga("data/debug_font.tga"); + debug_font = gfx_load_texture("data/debug_font.png"); return true; } @@ -420,8 +421,8 @@ public: if (!inited) { - tee_texture = gfx_load_texture_tga("data/gui_tee.tga"); - connecting_texture = gfx_load_texture_tga("data/gui/connecting.tga"); + tee_texture = gfx_load_texture("data/gui_tee.png"); + connecting_texture = gfx_load_texture("data/gui/connecting.png"); inited = true; } @@ -565,7 +566,11 @@ public: void process_packet(packet *p) { - if(p->msg() == NETMSG_SERVER_ACCEPT) + if(p->version() != TEEWARS_NETVERSION) + { + error("wrong version"); + } + else if(p->msg() == NETMSG_SERVER_ACCEPT) { const char *map; map = p->read_str(); diff --git a/src/engine/client/gfx.cpp b/src/engine/client/gfx.cpp index a824feac..71b5de7d 100644 --- a/src/engine/client/gfx.cpp +++ b/src/engine/client/gfx.cpp @@ -4,6 +4,11 @@ #include <engine/interface.h> +#include "pnglite/pnglite.h" + +#include <string.h> + + using namespace baselib; static opengl::context context; @@ -150,7 +155,7 @@ bool gfx_init(bool fullscreen) textures[MAX_TEXTURES-1].next = -1; // create null texture, will get id=0 - gfx_load_texture_raw(4,4,null_texture_data); + gfx_load_texture_raw(4,4,IMG_RGBA,null_texture_data); return true; } @@ -175,7 +180,7 @@ void gfx_blend_additive() glBlendFunc(GL_SRC_ALPHA, GL_ONE); } -int gfx_load_texture_raw(int w, int h, const void *data) +int gfx_load_texture_raw(int w, int h, int format, const void *data) { // grab texture int tex = first_free_texture; @@ -185,20 +190,51 @@ int gfx_load_texture_raw(int w, int h, const void *data) // set data and return // TODO: should be RGBA, not BGRA dbg_msg("gfx", "%d = %dx%d", tex, w, h); - textures[tex].tex.data2d(w, h, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, data); + if(format == IMG_RGB) + textures[tex].tex.data2d(w, h, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, data); + else if(format == IMG_RGBA) + textures[tex].tex.data2d(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, data); + else if(format == IMG_BGR) + textures[tex].tex.data2d(w, h, GL_RGB, GL_BGR, GL_UNSIGNED_BYTE, data); + else if(format == IMG_BGRA) + textures[tex].tex.data2d(w, h, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, data); return tex; } // simple uncompressed RGBA loaders -int gfx_load_texture_tga(const char *filename) +int gfx_load_texture(const char *filename) { - image_info img; - - if(gfx_load_tga(&img, filename)) + int l = strlen(filename); + if(l < 3) + return 0; + + if( (filename[l-3] == 't' || filename[l-3] == 'T') && + (filename[l-2] == 'g' || filename[l-2] == 'G') && + (filename[l-1] == 'a' || filename[l-1] == 'A')) + { + image_info img; + if(gfx_load_tga(&img, filename)) + { + int id = gfx_load_texture_raw(img.width, img.height, img.format, img.data); + mem_free(img.data); + return id; + } + return 0; + } + + if( (filename[l-3] == 'p' || filename[l-3] == 'P') && + (filename[l-2] == 'n' || filename[l-2] == 'N') && + (filename[l-1] == 'g' || filename[l-1] == 'G')) { - int id = gfx_load_texture_raw(img.width, img.height, img.data); - mem_free(img.data); - return id; + image_info img; + if(gfx_load_png(&img, filename)) + { + int id = gfx_load_texture_raw(img.width, img.height, img.format, img.data); + mem_free(img.data); + return id; + } + + return 0; } return 0; @@ -240,6 +276,7 @@ int gfx_load_tga(image_info *img, const char *filename) // read data int data_size = img->width*img->height*4; img->data = mem_alloc(data_size, 1); + img->format = IMG_BGRA; if (flipy) { @@ -255,6 +292,40 @@ int gfx_load_tga(image_info *img, const char *filename) return 1; } + + +int gfx_load_png(image_info *img, const char *filename) +{ + // open file for reading + png_init(0,0); + + png_t png; + if(png_open_file(&png, filename) != PNG_NO_ERROR) + { + dbg_msg("game/png", "failed to open file. filename='%s'", filename); + 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'", filename); + png_close_file(&png); + } + + unsigned char *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 (g_pVertices) diff --git a/src/engine/client/pnglite/pnglite.c b/src/engine/client/pnglite/pnglite.c new file mode 100644 index 00000000..c0e96ec6 --- /dev/null +++ b/src/engine/client/pnglite/pnglite.c @@ -0,0 +1,612 @@ +/* pnglite.c - pnglite library + For conditions of distribution and use, see copyright notice in pnglite.h +*/ + +#include <zlib.h> +#include "pnglite.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +#define DO_CRC_CHECKS 1 + +static png_alloc_t png_alloc; +static png_free_t png_free; + +static size_t file_read(png_t* png, void* out, size_t size, size_t numel) +{ + size_t result; + if(png->read_fun) + { + result = png->read_fun(out, size, numel, png->user_pointer); + } + else + { + if(!out) + { + result = fseek(png->user_pointer, (long)(size*numel), SEEK_CUR); + } + else + { + result = fread(out, size, numel, png->user_pointer); + } + } + + return result; +} + +static unsigned file_read_ul(png_t* png) +{ + unsigned result; + unsigned char buf[4]; + + if(file_read(png, buf, 1, 4) != 4) + return PNG_FILE_ERROR; + + result = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; + + return result; +} + +static unsigned get_ul(unsigned char* buf) +{ + unsigned result; + unsigned char foo[4]; + + memcpy(foo, buf, 4); + + result = (foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3]; + + return result; +} + +int png_init(png_alloc_t pngalloc, png_free_t pngfree) +{ + if(pngalloc) + png_alloc = pngalloc; + else + png_alloc = &malloc; + + if(pngfree) + png_free = pngfree; + else + png_free = &free; + + return PNG_NO_ERROR; +} + +static int png_get_bpp(png_t* png) +{ + int bpp; + + switch(png->color_type) + { + case PNG_GREYSCALE: + bpp = 1; break; + case PNG_TRUECOLOR: + bpp = 3; break; + case PNG_INDEXED: + bpp = 1; break; + case PNG_GREYSCALE_ALPHA: + bpp = 2; break; + case PNG_TRUECOLOR_ALPHA: + bpp = 4; break; + default: + return PNG_FILE_ERROR; + } + + bpp *= png->depth/8; + + return bpp; +} + +static int png_read_ihdr(png_t* png) +{ + unsigned length; +#if DO_CRC_CHECKS + unsigned orig_crc; + unsigned calc_crc; +#endif + unsigned char ihdr[13+4]; // length should be 13, make room for type (IHDR) + + length = file_read_ul(png); + + if(length != 13) + { + printf("%d\n", length); + return PNG_CRC_ERROR; + } + + if(file_read(png, ihdr, 1, 13+4) != 13+4) + return PNG_EOF_ERROR; +#if DO_CRC_CHECKS + orig_crc = file_read_ul(png); + + calc_crc = crc32(0L, Z_NULL, 0); + calc_crc = crc32(calc_crc, ihdr, 13+4); + + if(orig_crc != calc_crc) + return PNG_CRC_ERROR; +#else + file_read_ul(png); +#endif + + png->width = get_ul(ihdr+4); + png->height = get_ul(ihdr+8); + png->depth = ihdr[12]; + png->color_type = ihdr[13]; + png->compression_method = ihdr[14]; + png->filter_method = ihdr[15]; + png->interlace_method = ihdr[16]; + + if(png->color_type == PNG_INDEXED) + return PNG_NOT_SUPPORTED; + + if(png->depth != 8 && png->depth != 16) + return PNG_NOT_SUPPORTED; + + if(png->interlace_method) + return PNG_NOT_SUPPORTED; + + return PNG_NO_ERROR; +} + +void png_print_info(png_t* png) +{ + printf("PNG INFO:\n"); + printf("\twidth:\t\t%d\n", png->width); + printf("\theight:\t\t%d\n", png->height); + printf("\tdepth:\t\t%d\n", png->depth); + printf("\tcolor:\t\t"); + + switch(png->color_type) + { + case PNG_GREYSCALE: printf("greyscale\n"); break; + case PNG_TRUECOLOR: printf("truecolor\n"); break; + case PNG_INDEXED: printf("palette\n"); break; + case PNG_GREYSCALE_ALPHA: printf("greyscale with alpha\n"); break; + case PNG_TRUECOLOR_ALPHA: printf("truecolor with alpha\n"); break; + default: printf("unknown, this is not good\n"); break; + } + + printf("\tcompression:\t%s\n", png->compression_method?"unknown, this is not good":"inflate/deflate"); + printf("\tfilter:\t\t%s\n", png->filter_method?"unknown, this is not good":"adaptive"); + printf("\tinterlace:\t%s\n", png->interlace_method?"interlace":"no interlace"); +} + + +int png_open(png_t* png, png_read_callback_t read_fun, void* user_pointer) +{ + char header[8]; + int result; + + png->read_fun = read_fun; + png->user_pointer = user_pointer; + + if(!read_fun && !user_pointer) + return PNG_WRONG_ARGUMENTS; + + if(file_read(png, header, 1, 8) != 8) + return PNG_EOF_ERROR; + + if(memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) != 0) + return PNG_HEADER_ERROR; + + result = png_read_ihdr(png); + + png->bpp = png_get_bpp(png); + + return result; +} + +int png_open_file(png_t *png, const char* filename) +{ + FILE* fp = fopen(filename, "rb"); + + if(!fp) + return PNG_FILE_ERROR; + + return png_open(png, 0, fp); +} + +int png_close_file(png_t* png) +{ + fclose(png->user_pointer); + + return PNG_NO_ERROR; +} + +static int png_init_inflate(png_t* png) +{ + z_stream *stream; + png->zs = png_alloc(sizeof(z_stream)); + + stream = png->zs; + + if(!stream) + return PNG_MEMORY_ERROR; + + memset(stream, 0, sizeof(z_stream)); + + if(inflateInit(stream) != Z_OK) + return PNG_ZLIB_ERROR; + + stream->next_out = png->png_data; + stream->avail_out = png->png_datalen; + + return PNG_NO_ERROR; +} + +static int png_end_inflate(png_t* png) +{ + z_stream *stream = png->zs; + + if(!stream) + return PNG_MEMORY_ERROR; + + if(inflateEnd(stream) != Z_OK) + { + printf("ZLIB says: %s\n", stream->msg); + return PNG_ZLIB_ERROR; + } + + png_free(png->zs); + + return PNG_NO_ERROR; +} + +static int png_inflate(png_t* png, unsigned char* data, int len) +{ + int result; + z_stream *stream = png->zs; + + if(!stream) + return PNG_MEMORY_ERROR; + + stream->next_in = data; + stream->avail_in = len; + + result = inflate(stream, Z_SYNC_FLUSH); + + if(result != Z_STREAM_END && result != Z_OK) + { + printf("%s\n", stream->msg); + return PNG_ZLIB_ERROR; + } + + if(stream->avail_in != 0) + return PNG_ZLIB_ERROR; + + return PNG_NO_ERROR; +} + +static int png_read_idat(png_t* png, unsigned char* output, unsigned out_len, unsigned firstlen) +{ + unsigned type; + char *chunk; + int result; + unsigned length = firstlen; + unsigned old_len = length; + +#if DO_CRC_CHECKS + unsigned orig_crc; + unsigned calc_crc; +#endif + + chunk = png_alloc(firstlen); + + result = png_init_inflate(png); + + if(result != PNG_NO_ERROR) + { + png_end_inflate(png); + png_free(chunk); + return result; + } + + do + { + if(file_read(png, chunk, 1, length) != length) + { + png_end_inflate(png); + png_free(chunk); + return PNG_FILE_ERROR; + } + +#if DO_CRC_CHECKS + calc_crc = crc32(0L, Z_NULL, 0); + calc_crc = crc32(calc_crc, "IDAT", 4); + calc_crc = crc32(calc_crc, chunk, length); + + orig_crc = file_read_ul(png); + + if(orig_crc != calc_crc) + { + result = PNG_CRC_ERROR; + break; + } +#else + file_read_ul(png); +#endif + + result = png_inflate(png, chunk, length); + + if(result != PNG_NO_ERROR) break; + + length = file_read_ul(png); + + if(length > old_len) + { + png_free(chunk); + chunk = png_alloc(length); + old_len = length; + } + + if(file_read(png, &type, 1, 4) != 4) + { + result = PNG_FILE_ERROR; + break; + } + + }while(type == *(int*)"IDAT"); + + if(type == *(int*)"IEND") + result = PNG_DONE; + + png_free(chunk); + png_end_inflate(png); + + return result; +} + +static int png_process_chunk(png_t* png) +{ + int result = PNG_NO_ERROR, idat_len = 0; + unsigned type; + unsigned length; + + length = file_read_ul(png); + + if(file_read(png, &type, 1, 4) != 4) + return PNG_FILE_ERROR; + + if(type == *(int*)"IDAT") // if we found an idat, all other idats should be followed with no other chunks in between + { + png->png_datalen = png->width * png->height * png->bpp + png->height; + png->png_data = png_alloc(png->png_datalen); + + if(!png->png_data) + return PNG_MEMORY_ERROR; + + return png_read_idat(png, png->png_data, png->png_datalen, length); + } + else if(type == *(int*)"IEND") + { + return PNG_DONE; + } + else + { + file_read(png, 0, 1, length + 4); // unknown chunk + } + + return result; +} + +static void png_filter_sub(png_t* png, int stride, unsigned char* in, unsigned char* out, int len) +{ + int i; + unsigned char a = 0; + + for(i = 0; i < len; i++) + { + if(i >= stride) + a = out[i - stride]; + + out[i] = in[i] + a; + } +} + +static void png_filter_up(png_t* png, int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) +{ + int i; + + if(prev_line) + { + for(i = 0; i < len; i++) + out[i] = in[i] + prev_line[i]; + } + else + memcpy(out, in, len); +} + +static void png_filter_average(png_t* png, int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) +{ + int i; + unsigned char a = 0; + unsigned char b = 0; + unsigned int sum = 0; + + for(i = 0; i < len; i++) + { + if(prev_line) + b = prev_line[i]; + + if(i >= stride) + a = out[i - stride]; + + sum = a; + sum += b; + + out[i] = in[i] + sum/2; + } +} + +static unsigned char png_paeth(unsigned char a, unsigned char b, unsigned char c) +{ + int p = (int)a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + + int pr; + + if(pa <= pb && pa <= pc) + pr = a; + else if(pb <= pc) + pr = b; + else + pr = c; + + return pr; +} + +static void png_filter_paeth(png_t* png, int stride, unsigned char* in, unsigned char* out, unsigned char* prev_line, int len) +{ + int i; + unsigned char a; + unsigned char b; + unsigned char c; + + for(i = 0; i < len; i++) + { + if(prev_line && i >= stride) + { + a = out[i - stride]; + b = prev_line[i]; + c = prev_line[i - stride]; + } + else + { + if(prev_line) + b = prev_line[i]; + else + b = 0; + + if(i >= stride) + a = out[i - stride]; + else + a = 0; + + c = 0; + } + + out[i] = in[i] + png_paeth(a, b, c); + } +} + +static int png_unfilter(png_t* png, unsigned char* data) +{ + unsigned i; + unsigned pos = 0; + unsigned outpos = 0; + unsigned char *filtered = png->png_data; + + int stride = png->bpp; + + while(pos < png->png_datalen) + { + unsigned char filter = filtered[pos]; + + pos++; + + if(png->depth == 16) + { + for(i = 0; i < png->width * stride; i+=2) + { + *(short*)(filtered+pos+i) = (filtered[pos+i] << 8) | filtered[pos+i+1]; + } + } + + switch(filter) + { + case 0: // none + memcpy(data+outpos, filtered+pos, png->width * stride); + break; + case 1: // sub + png_filter_sub(png, stride, filtered+pos, data+outpos, png->width * stride); + break; + case 2: // up + if(outpos) + png_filter_up(png, stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); + else + png_filter_up(png, stride, filtered+pos, data+outpos, 0, png->width*stride); + break; + case 3: // average + if(outpos) + png_filter_average(png, stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); + else + png_filter_average(png, stride, filtered+pos, data+outpos, 0, png->width*stride); + break; + case 4: // paeth + if(outpos) + png_filter_paeth(png, stride, filtered+pos, data+outpos, data + outpos - (png->width*stride), png->width*stride); + else + png_filter_paeth(png, stride, filtered+pos, data+outpos, 0, png->width*stride); + break; + default: + return PNG_UNKNOWN_FILTER; + } + + outpos += png->width * stride; + pos += png->width * stride; + } + + return PNG_NO_ERROR; +} + +int png_get_data(png_t* png, unsigned char* data) +{ + int result = PNG_NO_ERROR; + + while(result == PNG_NO_ERROR) + { + result = png_process_chunk(png); + } + + if(result != PNG_DONE) + { + png_free(png->png_data); + return result; + } + + result = png_unfilter(png, data); + + png_free(png->png_data); + + return result; +} + +char* png_error_string(int error) +{ + switch(error) + { + case PNG_NO_ERROR: + return "No error"; + case PNG_FILE_ERROR: + return "Unknown file error."; + case PNG_HEADER_ERROR: + return "No PNG header found. Are you sure this is a PNG?"; + case PNG_IO_ERROR: + return "Failure while reading file."; + case PNG_EOF_ERROR: + return "Reached end of file."; + case PNG_CRC_ERROR: + return "CRC or chunk length error."; + case PNG_MEMORY_ERROR: + return "Could not allocate memory."; + case PNG_ZLIB_ERROR: + return "zlib reported an error."; + case PNG_UNKNOWN_FILTER: + return "Unknown filter method used in scanline."; + case PNG_DONE: + return "PNG done"; + case PNG_NOT_SUPPORTED: + return "The PNG is unsupported by pnglite, too bad for you!"; + case PNG_WRONG_ARGUMENTS: + return "Wrong combination of arguments passed to png_open. You must use either a read_function or supply a file pointer to use."; + default: + return "Unknown error."; + }; +} + diff --git a/src/engine/client/pnglite/pnglite.h b/src/engine/client/pnglite/pnglite.h new file mode 100644 index 00000000..012a38d1 --- /dev/null +++ b/src/engine/client/pnglite/pnglite.h @@ -0,0 +1,212 @@ +/* pnglite.h - Interface for pnglite library + Copyright (c) 2007 Daniel Karling + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + Daniel Karling + daniel.karling@gmail.com + */ + + +#ifndef _PNGLITE_H_ +#define _PNGLITE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Enumerations for pnglite. + Negative numbers are error codes and 0 and up are okay responses. +*/ + +enum +{ + PNG_DONE = 1, + PNG_NO_ERROR = 0, + PNG_FILE_ERROR = -1, + PNG_HEADER_ERROR = -2, + PNG_IO_ERROR = -3, + PNG_EOF_ERROR = -4, + PNG_CRC_ERROR = -5, + PNG_MEMORY_ERROR = -6, + PNG_ZLIB_ERROR = -7, + PNG_UNKNOWN_FILTER = -8, + PNG_NOT_SUPPORTED = -9, + PNG_WRONG_ARGUMENTS = -10 +}; + +/* + The five different kinds of color storage in PNG files. +*/ + +enum +{ + PNG_GREYSCALE = 0, + PNG_TRUECOLOR = 2, + PNG_INDEXED = 3, + PNG_GREYSCALE_ALPHA = 4, + PNG_TRUECOLOR_ALPHA = 6 +}; + +/* + Typedefs for callbacks. +*/ + +typedef unsigned (*png_read_callback_t)(void* output, unsigned size, unsigned numel, void* user_pointer); +typedef void (*png_free_t)(void* p); +typedef void * (*png_alloc_t)(unsigned s); + +typedef struct +{ + void* zs; // pointer to z_stream + png_read_callback_t read_fun; + void* user_pointer; + + unsigned char* png_data; + unsigned png_datalen; + + unsigned width; + unsigned height; + unsigned char depth; + unsigned char color_type; + unsigned char compression_method; + unsigned char filter_method; + unsigned char interlace_method; + unsigned char bpp; +}png_t; + +/* + Function: png_init + + This function initializes pnglite. The parameters can be used to set your own memory allocation routines following these formats: + + > void* (*custom_alloc)(size_t s) + > void (*custom_free)(void* p) + Parameters: + pngalloc - Pointer to custom allocation routine. If 0 is passed, malloc from libc will be used. + pngfree - Pointer to custom free routine. If 0 is passed, free from libc will be used. + + Returns: + Always returns PNG_NO_ERROR. +*/ + +int png_init(png_alloc_t pngalloc, png_free_t pngfree); + +/* + Function: png_open_file + + This function is used to open a png file with the internal file IO system. This function should be used instead of + png_open if no custom read function is used. + + Parameters: + png - Empty png_t struct. + filename - Filename of the file to be opened. + + Returns: + PNG_NO_ERROR on success, otherwise an error code. +*/ + +int png_open_file(png_t *png, const char* filename); + +/* + Function: png_open + + This function reads a png from the specified callback. The callback should be of the format: + + > size_t (*png_read_callback_t)(void* output, size_t size, size_t numel, void* user_pointer). + + The callback will be called each time pnglite needs more data. The callback should read as much data as requested, + or return 0. This should always be possible if the PNG is sane. If the output-buffer is a null-pointer the callback + should only skip ahead the specified number of elements. If the callback is a null-pointer the user_pointer will be + treated as a file pointer (use png_open_file instead). + + Parameters: + png - png_t struct + read_fun - Callback function for reading. + user_pointer - User pointer to be passed to read_fun. + + Returns: + PNG_NO_ERROR on success, otherwise an error code. +*/ + +int png_open(png_t* png, png_read_callback_t read_fun, void* user_pointer); + +/* + Function: png_print_info + + This function prints some info about the opened png file to stdout. + + Parameters: + png - png struct to get info from. +*/ + +void png_print_info(png_t* png); + +/* + Function: png_error_string + + This function translates an error code to a human readable string. + + Parameters: + error - Error code. + + Returns: + Pointer to string. +*/ + +char* png_error_string(int error); + +/* + Function: png_get_data + + This function decodes the opened png file and stores the result in data. data should be big enough to hold the decoded png. Required size will be: + + > width*height*(bytes per pixel) + + Parameters: + data - Where to store result. + + Returns: + PNG_NO_ERROR on success, otherwise an error code. +*/ + +int png_get_data(png_t* png, unsigned char* data); + +/* + Function: png_close_file + + Closes an open png file pointer. Should only be used when the png has been opened with png_open_file. + + Parameters: + png - png to close. + + Returns: + PNG_NO_ERROR +*/ + +int png_close_file(png_t* png); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/engine/interface.h b/src/engine/interface.h index 95ea0252..200e7322 100644 --- a/src/engine/interface.h +++ b/src/engine/interface.h @@ -15,6 +15,11 @@ enum SERVER_CLIENT_TIMEOUT=5, SNAP_CURRENT=0, SNAP_PREV=1, + + IMG_RGB=0, + IMG_RGBA, + IMG_BGR, + IMG_BGRA, }; struct snap_item @@ -33,10 +38,12 @@ public: struct image_info { int width, height; + int format; void *data; }; int gfx_load_tga(image_info *img, const char *filename); +int gfx_load_png(image_info *img, const char *filename); /* @@ -50,8 +57,8 @@ void gfx_swap(); // NOT EXPOSED // textures /* - Function: gfx_load_texture_tga - Loads a TGA from file. + Function: gfx_load_texture + Loads a texture from a file. TGA and PNG supported. Arguments: filename - Null terminated string to the file to load. @@ -62,7 +69,7 @@ void gfx_swap(); // NOT EXPOSED See Also: <gfx_unload_texture> */ -int gfx_load_texture_tga(const char *filename); +int gfx_load_texture(const char *filename); /* Function: gfx_load_texture_raw @@ -83,7 +90,7 @@ int gfx_load_texture_tga(const char *filename); See Also: <gfx_unload_texture> */ -int gfx_load_texture_raw(int w, int h, const void *data); +int gfx_load_texture_raw(int w, int h, int format, const void *data); /* Function: gfx_texture_set diff --git a/src/engine/packet.h b/src/engine/packet.h index 6dc99043..fd93d744 100644 --- a/src/engine/packet.h +++ b/src/engine/packet.h @@ -1,9 +1,14 @@ #include <baselib/stream/file.h> #include <baselib/network.h> +#include "versions.h" + +#define MACRO_MAKEINT(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d) + // TODO: this is not KISS class packet { + friend class connection; protected: enum { @@ -13,6 +18,11 @@ protected: // packet data struct header { + unsigned id; + unsigned version; + unsigned size_and_flags; + unsigned crc; + unsigned msg; unsigned ack; unsigned seq; @@ -60,10 +70,20 @@ protected: } public: + + enum + { + FLAG_VITAL=1, + FLAG_RESEND=2 + }; + packet(unsigned msg=0) { current = packet_data; current += sizeof(header); + + ((header*)packet_data)->id = MACRO_MAKEINT('K','M','A',1); + ((header*)packet_data)->version = TEEWARS_NETVERSION; ((header*)packet_data)->msg = msg; } @@ -128,9 +148,11 @@ public: // TODO: impelement this bool is_good() const { return true; } + unsigned version() const { return ((header*)packet_data)->version; } unsigned msg() const { return ((header*)packet_data)->msg; } unsigned seq() const { return ((header*)packet_data)->seq; } unsigned ack() const { return ((header*)packet_data)->ack; } + unsigned flags() const { return (((header*)packet_data)->size_and_flags) & 0xffff; } // access functions to get the size and data int size() const { return (int)(current-(unsigned char*)packet_data); } @@ -138,6 +160,89 @@ public: void *data() { return packet_data; } }; +// TODO: remove all the allocations from this class +class ring_buffer +{ + struct item + { + item *next; + item *prev; + int size; + }; + + item *first; + item *last; + + unsigned buffer_size; +public: + ring_buffer() + { + first = 0; + last = 0; + buffer_size = 0; + } + + ~ring_buffer() + { + reset(); + } + + void reset() + { + // clear all + while(peek_data()) + next(); + } + + void *alloc(int size) + { + item *i = (item*)mem_alloc(sizeof(item)+size, 1); + i->size = size; + + i->prev = last; + i->next = 0; + if(last) + last->next = i; + else + first = i; + last = i; + + buffer_size += size; + return (void*)(i+1); + } + + unsigned peek_size() + { + if(!first) + return 0; + return first->size; + } + + void *peek_data() + { + if(!first) + return 0; + return (void*)(first+1); + } + + void next() + { + if(first) + { + item *next = first->next; + buffer_size += first->size; + mem_free(first); + first = next; + if(first) + first->prev = 0; + else + last = 0; + } + } + + unsigned size() { return buffer_size; } +}; + // class connection { @@ -145,11 +250,52 @@ class connection baselib::netaddr4 addr; unsigned seq; unsigned ack; - unsigned last_ack; unsigned counter_sent_bytes; unsigned counter_recv_bytes; + int needs_resend; + + /* + struct resend_packet + { + resend_packet *next; + unsigned seq; + unsigned msg; + unsigned size; + char data[1]; + }; + + resend_packet *first_resend; + resend_packet *last_resend; + */ + + ring_buffer resend_buffer; + + void save_for_resend(packet *p) + { + /* + packet *n = (packet *)resend_buffer.alloc(p->size()); + mem_copy(n->data(), p->data(), p->size()); + n->current = (unsigned char*)n->data() + p->size(); + */ + } + + void remove_resends(unsigned ack) + { + /* + while(1) + { + packet *p = (packet *)resend_buffer.peek_data(); + if(!p) + break; + + if(p->seq() > ack) + break; + resend_buffer.next(); + }*/ + } + public: void counter_reset() { @@ -165,24 +311,28 @@ public: void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr) { + resend_buffer.reset(); + this->addr = *addr; this->socket = socket; - last_ack = 0; ack = 0; seq = 0; + needs_resend = 0; counter_reset(); } void send(packet *p) { - if(p->msg()&(31<<1)) - { - // vital packet + if(p->flags()&packet::FLAG_VITAL) seq++; - // TODO: save packet, we might need to retransmit - } p->set_header(ack, seq); + + if(p->flags()&packet::FLAG_VITAL) + save_for_resend(p); + + // TODO: request resend if needed, use needs_resend variable + socket->send(&address(), p->data(), p->size()); counter_sent_bytes += p->size(); } @@ -191,7 +341,7 @@ public: { counter_recv_bytes += p->size(); - if(p->msg()&(31<<1)) + if(p->flags()&packet::FLAG_VITAL) { if(p->seq() == ack+1) { @@ -202,8 +352,8 @@ public: } else if(p->seq() > ack) { - // TODO: request resend // packet loss + needs_resend = 1; dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack); return p; } @@ -214,9 +364,14 @@ public: } } - if(last_ack != p->ack()) + // remove resends + remove_resends(p->ack()); + + // handle resends + if(p->flags()&packet::FLAG_RESEND) { - // TODO: remove acked packets + // peer as requested a resend of all non acked packages. + } return p; @@ -233,7 +388,6 @@ public: enum { - NETMSG_VITAL=0x80000000, NETMSG_CONTEXT_CONNECT=0x00010000, NETMSG_CONTEXT_GAME=0x00020000, NETMSG_CONTEXT_GLOBAL=0x00040000, @@ -253,7 +407,7 @@ enum // str32 mapname - NETMSG_CLIENT_DONE=NETMSG_VITAL|NETMSG_CONTEXT_CONNECT|3, + NETMSG_CLIENT_DONE=NETMSG_CONTEXT_CONNECT|3, // nothing // game phase @@ -266,10 +420,10 @@ enum NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these // int input[MAX_INPUTS] - NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2, - NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2, + NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|2, + NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|2, - NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|NETMSG_VITAL|3, // check if client is alive + NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|3, // check if client is alive NETMSG_CLIENT_ERROR=0x0fffffff, // str128 reason diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index d9151a43..c272ec91 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1,5 +1,16 @@ #include <stdio.h> #include <string.h> #include <baselib/system.h> #include <engine/interface.h> //#include "socket.h" #include <engine/packet.h> #include <engine/snapshot.h> #include <engine/lzw.h> #include <engine/versions.h> namespace baselib {} using namespace baselib; int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) { if( a->ip[0] != b->ip[0] || a->ip[1] != b->ip[1] || a->ip[2] != b->ip[2] || a->ip[3] != b->ip[3] || a->port != b->port ) return 1; return 0; } // --- string handling (MOVE THESE!!) --- void snap_encode_string(const char *src, int *dst, int length, int max_length) { const unsigned char *p = (const unsigned char *)src; // handle whole int for(int i = 0; i < length/4; i++) { *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); p += 4; dst++; } // take care of the left overs int left = length%4; if(left) { unsigned last = 0; switch(left) { case 3: last |= p[2]<<8; case 2: last |= p[1]<<16; case 1: last |= p[0]<<24; } *dst = last; } } class snapshot_builder { public: static const int MAX_ITEMS = 512; //static const int MAX_DATA_SIZE=1*1024; char data[MAX_SNAPSHOT_SIZE]; int data_size; int offsets[MAX_ITEMS]; int num_items; int top_size; int top_items; int snapnum; snapshot_builder() { top_size = 0; top_items = 0; snapnum = 0; } void start() { data_size = 0; num_items = 0; } int finish(void *snapdata) { snapnum++; // collect some data /* int change = 0; if(data_size > top_size) { change++; top_size = data_size; } if(num_items > top_items) { change++; top_items = num_items; } if(change) { dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size); }*/ // flattern and make the snapshot snapshot *snap = (snapshot *)snapdata; snap->num_items = num_items; int offset_size = sizeof(int)*num_items; mem_copy(snap->offsets, offsets, offset_size); mem_copy(snap->data_start(), data, data_size); return sizeof(int) + offset_size + data_size; } void *new_item(int type, int id, int size) { snapshot::item *obj = (snapshot::item *)(data+data_size); obj->type_and_id = (type<<16)|id; offsets[num_items] = data_size; data_size += sizeof(int) + size; num_items++; dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); dbg_assert(num_items < MAX_ITEMS, "too many items"); return &obj->data; } }; static snapshot_builder builder; 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 builder.new_item(type, id, size); } // class client { public: enum { STATE_EMPTY = 0, STATE_CONNECTING = 1, STATE_INGAME = 2, }; // connection state info int state; // (ticks) if lastactivity > 5 seconds kick him int64 lastactivity; connection conn; char name[MAX_NAME_LENGTH]; char clan[MAX_CLANNAME_LENGTH]; /* client() { state = STATE_EMPTY; name[0] = 0; clan[0] = 0; } ~client() { dbg_assert(state == STATE_EMPTY, "client destoyed while in use"); }*/ bool is_empty() const { return state == STATE_EMPTY; } bool is_ingame() const { return state == STATE_INGAME; } const netaddr4 &address() const { return conn.address(); } }; static client clients[MAX_CLIENTS]; static int current_tick = 0; static int send_heartbeats = 1; int server_tick() { return current_tick; } int server_tickspeed() { return 50; } int server_init() { for(int i = 0; i < MAX_CLIENTS; i++) { clients[i].state = client::STATE_EMPTY; clients[i].name[0] = 0; clients[i].clan[0] = 0; clients[i].lastactivity = 0; } 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].is_ingame()) { info->name = clients[client_id].name; info->latency = 0; return 1; } return 0; } // class server { public: socket_udp4 game_socket; - const char *map_name; const char *server_name; int64 lasttick; int64 lastheartbeat; netaddr4 master_server; int biggest_snapshot; bool run(const char *servername, const char *mapname) { biggest_snapshot = 0; net_init(); // For Windows compatibility. map_name = mapname; server_name = servername; // load map if(!map_load(mapname)) { dbg_msg("server", "failed to load map. mapname='%s'"); return false; } // start server if(!game_socket.open(8303)) { dbg_msg("network/server", "couldn't open socket"); return false; } for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0) { // TODO: fix me //master_server = netaddr4(0, 0, 0, 0, 0); } mods_init(); int64 time_per_tick = time_freq()/SERVER_TICK_SPEED; int64 time_per_heartbeat = time_freq() * 30; int64 starttime = time_get(); //int64 lasttick = starttime; lasttick = starttime; lastheartbeat = 0; int64 reporttime = time_get(); int64 reportinterval = time_freq()*3; int64 simulationtime = 0; int64 snaptime = 0; int64 networktime = 0; while(1) { int64 t = time_get(); if(t-lasttick > time_per_tick) { { int64 start = time_get(); tick(); simulationtime += time_get()-start; } { int64 start = time_get(); snap(); snaptime += time_get()-start; } // Check for client timeouts for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].state != client::STATE_EMPTY) { // check last activity time if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT) client_timeout(i); } } lasttick += time_per_tick; } if(send_heartbeats) { if (t > lastheartbeat+time_per_heartbeat) { if (master_server.port != 0) { int players = 0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) players++; // TODO: fix me netaddr4 me(127, 0, 0, 0, 8303); send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); } lastheartbeat = t+time_per_heartbeat; } } { int64 start = time_get(); pump_network(); networktime += time_get()-start; } if(reporttime < time_get()) { int64 totaltime = simulationtime+snaptime+networktime; dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%", simulationtime/(float)reportinterval*1000, snaptime/(float)reportinterval*1000, networktime/(float)reportinterval*1000, totaltime/(float)reportinterval*1000, (simulationtime+snaptime+networktime)/(float)reportinterval*100.0f); unsigned sent_total=0, recv_total=0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) { unsigned s,r; clients[i].conn.counter_get(&s,&r); clients[i].conn.counter_reset(); sent_total += s; recv_total += r; } dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d", biggest_snapshot, sent_total/3, recv_total/3); simulationtime = 0; snaptime = 0; networktime = 0; reporttime += reportinterval; } thread_sleep(1); } mods_shutdown(); map_unload(); } void tick() { current_tick++; mods_tick(); } void snap() { mods_presnap(); for(int i = 0; i < MAX_CLIENTS; i++) { if(clients[i].is_ingame()) { char data[MAX_SNAPSHOT_SIZE]; char compdata[MAX_SNAPSHOT_SIZE]; builder.start(); mods_snap(i); // finish snapshot int snapshot_size = builder.finish(data); // compress it int compsize = lzw_compress(data, snapshot_size, compdata); snapshot_size = compsize; if(snapshot_size > biggest_snapshot) biggest_snapshot = snapshot_size; const int max_size = MAX_SNAPSHOT_PACKSIZE; int numpackets = (snapshot_size+max_size-1)/max_size; for(int n = 0, left = snapshot_size; left; n++) { int chunk = left < max_size ? left : max_size; left -= chunk; packet p(NETMSG_SERVER_SNAP); p.write_int(numpackets); p.write_int(n); p.write_int(chunk); p.write_raw(&compdata[n*max_size], chunk); clients[i].conn.send(&p); } } } mods_postsnap(); } void send_accept(client *client, const char *map) { packet p(NETMSG_SERVER_ACCEPT); p.write_str(map); client->conn.send(&p); } void drop(int cid, const char *reason) { if(clients[cid].state == client::STATE_EMPTY) return; clients[cid].state = client::STATE_EMPTY; mods_client_drop(cid); dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name); } int find_client(const netaddr4 *addr) { // fetch client for(int i = 0; i < MAX_CLIENTS; i++) { if(!clients[i].is_empty() && clients[i].address() == *addr) return i; } return -1; } void client_process_packet(int cid, packet *p) { clients[cid].lastactivity = lasttick; if(p->msg() == NETMSG_CLIENT_DONE) { dbg_msg("game", "player as entered the game. cid=%x", cid); clients[cid].state = client::STATE_INGAME; mods_client_enter(cid); } else if(p->msg() == NETMSG_CLIENT_INPUT) { int input[MAX_INPUT_SIZE]; int size = p->read_int(); for(int i = 0; i < size/4; i++) input[i] = p->read_int(); if(p->is_good()) { //dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]); mods_client_input(cid, input); } } else if(p->msg() == NETMSG_CLIENT_ERROR) { const char *reason = p->read_str(); if(p->is_good()) dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason); else dbg_msg("network/server", "client error. cid=%x", cid); drop(cid, "client error"); } else { dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg()); drop(cid, "invalid message"); } } void process_packet(packet *p, netaddr4 *from) { if(p->msg() == NETMSG_CLIENT_CONNECT) { // we got no state for this client yet const char *version; const char *name; const char *clan; const char *password; const char *skin; version = p->read_str(); name = p->read_str(); clan = p->read_str(); password = p->read_str(); skin = p->read_str(); if(p->is_good()) { // check version if(strcmp(version, TEEWARS_NETVERSION) != 0) { dbg_msg("network/server", "wrong version connecting '%s'", version); - // TODO: send error return; } // look for empty slot, linear search int id = -1; for(int i = 0; i < MAX_CLIENTS; i++) if(clients[i].is_empty()) { id = i; break; } if(id != -1) { // slot found // TODO: perform correct copy here mem_copy(clients[id].name, name, MAX_NAME_LENGTH); mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH); clients[id].state = client::STATE_CONNECTING; clients[id].conn.init(&game_socket, from); clients[id].lastactivity = lasttick; clients[id].name[MAX_NAME_LENGTH-1] = 0; clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0; dbg_msg("network/server", "client connected. '%s' on slot %d", name, id); // TODO: return success send_accept(&clients[id], map_name); } else { // no slot found // TODO: send error dbg_msg("network/server", "client connected but server is full"); for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); } } } else { int cid = find_client(from); if(cid >= 0) { if(clients[cid].conn.feed(p)) { // packet is ok unsigned msg = p->msg(); // client found, check state if(((msg>>16)&0xff)&clients[cid].state) { // state is ok client_process_packet(cid, p); } else { // invalid state, disconnect the client drop(cid, "invalid message at this state"); } } else { drop(cid, "connection error"); } } else dbg_msg("network/server", "packet from strange address."); } } void client_timeout(int clientId) { drop(clientId, "client timedout"); } void pump_network() { while(1) { packet p; netaddr4 from; //int bytes = net_udp4_recv( int bytes = game_socket.recv(&from, p.data(), p.max_size()); //int bytes = game_socket.recv(&from, p.data(), p.max_size()); if(bytes <= 0) break; process_packet(&p, &from); } // TODO: check for client timeouts } char *write_int(char *buffer, int integer) { *buffer++ = integer >> 24; *buffer++ = integer >> 16; *buffer++ = integer >> 8; *buffer++ = integer; return buffer; } char *write_netaddr4(char *buffer, NETADDR4 *address) { *buffer++ = address->ip[0]; *buffer++ = address->ip[1]; *buffer++ = address->ip[2]; *buffer++ = address->ip[3]; return write_int(buffer, address->port); } void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name) { char buffer[216] = {0}; char *d = buffer; d = write_int(d, 'TWHB'); d = write_int(d, version); d = write_netaddr4(d, address); d = write_int(d,players); d = write_int(d, max_players); int len = strlen(name); if (len > 128) len = 128; memcpy(d, name, len); d += 128; len = strlen(map_name); if (len > 64) len = 64; memcpy(d, map_name, len); d += 64; + const char *map_name; const char *server_name; int64 lasttick; int64 lastheartbeat; netaddr4 master_server; int biggest_snapshot; bool run(const char *servername, const char *mapname) { biggest_snapshot = 0; net_init(); // For Windows compatibility. map_name = mapname; server_name = servername; // load map if(!map_load(mapname)) { dbg_msg("server", "failed to load map. mapname='%s'"); return false; } // start server if(!game_socket.open(8303)) { dbg_msg("network/server", "couldn't open socket"); return false; } for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0) { // TODO: fix me //master_server = netaddr4(0, 0, 0, 0, 0); } mods_init(); int64 time_per_tick = time_freq()/SERVER_TICK_SPEED; int64 time_per_heartbeat = time_freq() * 30; int64 starttime = time_get(); //int64 lasttick = starttime; lasttick = starttime; lastheartbeat = 0; int64 reporttime = time_get(); int64 reportinterval = time_freq()*3; int64 simulationtime = 0; int64 snaptime = 0; int64 networktime = 0; while(1) { int64 t = time_get(); if(t-lasttick > time_per_tick) { { int64 start = time_get(); tick(); simulationtime += time_get()-start; } { int64 start = time_get(); snap(); snaptime += time_get()-start; } // Check for client timeouts for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].state != client::STATE_EMPTY) { // check last activity time if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT) client_timeout(i); } } lasttick += time_per_tick; } if(send_heartbeats) { if (t > lastheartbeat+time_per_heartbeat) { if (master_server.port != 0) { int players = 0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) players++; // TODO: fix me netaddr4 me(127, 0, 0, 0, 8303); send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); } lastheartbeat = t+time_per_heartbeat; } } { int64 start = time_get(); pump_network(); networktime += time_get()-start; } if(reporttime < time_get()) { int64 totaltime = simulationtime+snaptime+networktime; dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%", simulationtime/(float)reportinterval*1000, snaptime/(float)reportinterval*1000, networktime/(float)reportinterval*1000, totaltime/(float)reportinterval*1000, (simulationtime+snaptime+networktime)/(float)reportinterval*100.0f); unsigned sent_total=0, recv_total=0; for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) { unsigned s,r; clients[i].conn.counter_get(&s,&r); clients[i].conn.counter_reset(); sent_total += s; recv_total += r; } dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d", biggest_snapshot, sent_total/3, recv_total/3); simulationtime = 0; snaptime = 0; networktime = 0; reporttime += reportinterval; } thread_sleep(1); } mods_shutdown(); map_unload(); } void tick() { current_tick++; mods_tick(); } void snap() { mods_presnap(); for(int i = 0; i < MAX_CLIENTS; i++) { if(clients[i].is_ingame()) { char data[MAX_SNAPSHOT_SIZE]; char compdata[MAX_SNAPSHOT_SIZE]; builder.start(); mods_snap(i); // finish snapshot int snapshot_size = builder.finish(data); // compress it int compsize = lzw_compress(data, snapshot_size, compdata); snapshot_size = compsize; if(snapshot_size > biggest_snapshot) biggest_snapshot = snapshot_size; const int max_size = MAX_SNAPSHOT_PACKSIZE; int numpackets = (snapshot_size+max_size-1)/max_size; for(int n = 0, left = snapshot_size; left; n++) { int chunk = left < max_size ? left : max_size; left -= chunk; packet p(NETMSG_SERVER_SNAP); p.write_int(numpackets); p.write_int(n); p.write_int(chunk); p.write_raw(&compdata[n*max_size], chunk); clients[i].conn.send(&p); } } } mods_postsnap(); } void send_accept(client *client, const char *map) { packet p(NETMSG_SERVER_ACCEPT); p.write_str(map); client->conn.send(&p); } void drop(int cid, const char *reason) { if(clients[cid].state == client::STATE_EMPTY) return; clients[cid].state = client::STATE_EMPTY; mods_client_drop(cid); dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name); } int find_client(const netaddr4 *addr) { // fetch client for(int i = 0; i < MAX_CLIENTS; i++) { if(!clients[i].is_empty() && clients[i].address() == *addr) return i; } return -1; } void client_process_packet(int cid, packet *p) { clients[cid].lastactivity = lasttick; if(p->msg() == NETMSG_CLIENT_DONE) { dbg_msg("game", "player as entered the game. cid=%x", cid); clients[cid].state = client::STATE_INGAME; mods_client_enter(cid); } else if(p->msg() == NETMSG_CLIENT_INPUT) { int input[MAX_INPUT_SIZE]; int size = p->read_int(); for(int i = 0; i < size/4; i++) input[i] = p->read_int(); if(p->is_good()) { //dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]); mods_client_input(cid, input); } } else if(p->msg() == NETMSG_CLIENT_ERROR) { const char *reason = p->read_str(); if(p->is_good()) dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason); else dbg_msg("network/server", "client error. cid=%x", cid); drop(cid, "client error"); } else { dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg()); drop(cid, "invalid message"); } } void process_packet(packet *p, netaddr4 *from) { + // do version check + if(p->version() != TEEWARS_NETVERSION) + { + // send an empty packet back. + // this will allow the client to check the version + packet p; + game_socket.send(from, p.data(), p.size()); + return; + } + if(p->msg() == NETMSG_CLIENT_CONNECT) { // we got no state for this client yet const char *version; const char *name; const char *clan; const char *password; const char *skin; version = p->read_str(); name = p->read_str(); clan = p->read_str(); password = p->read_str(); skin = p->read_str(); if(p->is_good()) { + /* // check version if(strcmp(version, TEEWARS_NETVERSION) != 0) { dbg_msg("network/server", "wrong version connecting '%s'", version); + // TODO: send error return; }*/ // look for empty slot, linear search int id = -1; for(int i = 0; i < MAX_CLIENTS; i++) if(clients[i].is_empty()) { id = i; break; } if(id != -1) { // slot found // TODO: perform correct copy here mem_copy(clients[id].name, name, MAX_NAME_LENGTH); mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH); clients[id].state = client::STATE_CONNECTING; clients[id].conn.init(&game_socket, from); clients[id].lastactivity = lasttick; clients[id].name[MAX_NAME_LENGTH-1] = 0; clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0; dbg_msg("network/server", "client connected. '%s' on slot %d", name, id); // TODO: return success send_accept(&clients[id], map_name); } else { // no slot found // TODO: send error dbg_msg("network/server", "client connected but server is full"); for(int i = 0; i < MAX_CLIENTS; i++) dbg_msg("network/server", "\t%d: %d", i, clients[i].state); } } } else { int cid = find_client(from); if(cid >= 0) { if(clients[cid].conn.feed(p)) { // packet is ok unsigned msg = p->msg(); // client found, check state if(((msg>>16)&0xff)&clients[cid].state) { // state is ok client_process_packet(cid, p); } else { // invalid state, disconnect the client drop(cid, "invalid message at this state"); } } else { drop(cid, "connection error"); } } else dbg_msg("network/server", "packet from strange address."); } } void client_timeout(int clientId) { drop(clientId, "client timedout"); } void pump_network() { while(1) { packet p; netaddr4 from; //int bytes = net_udp4_recv( int bytes = game_socket.recv(&from, p.data(), p.max_size()); //int bytes = game_socket.recv(&from, p.data(), p.max_size()); if(bytes <= 0) break; process_packet(&p, &from); } // TODO: check for client timeouts } char *write_int(char *buffer, int integer) { *buffer++ = integer >> 24; *buffer++ = integer >> 16; *buffer++ = integer >> 8; *buffer++ = integer; return buffer; } char *write_netaddr4(char *buffer, NETADDR4 *address) { *buffer++ = address->ip[0]; *buffer++ = address->ip[1]; *buffer++ = address->ip[2]; *buffer++ = address->ip[3]; return write_int(buffer, address->port); } void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name) { char buffer[216] = {0}; char *d = buffer; d = write_int(d, 'TWHB'); d = write_int(d, version); d = write_netaddr4(d, address); d = write_int(d,players); d = write_int(d, max_players); int len = strlen(name); if (len > 128) len = 128; memcpy(d, name, len); d += 128; len = strlen(map_name); if (len > 64) len = 64; memcpy(d, map_name, len); d += 64; game_socket.send(&master_server, buffer, sizeof(buffer)); } }; int main(int argc, char **argv) { dbg_msg("server", "starting..."); const char *mapname = "data/demo.map"; const char *servername = 0; // parse arguments for(int i = 1; i < argc; i++) { if(argv[i][0] == '-' && argv[i][1] == 'm' && argv[i][2] == 0 && argc - i > 1) { // -m map i++; mapname = argv[i]; } else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1) { // -n server name i++; servername = argv[i]; } else if(argv[i][0] == '-' && argv[i][1] == 'p' && argv[i][2] == 0) { // -p (private server) send_heartbeats = 0; } } if(!mapname) { dbg_msg("server", "no map given (-m MAPNAME)"); return 0; } if(!servername) { dbg_msg("server", "no server name given (-n \"server name\")"); return 0; } server_init(); server s; s.run(servername, mapname); return 0; } \ No newline at end of file diff --git a/src/engine/versions.h b/src/engine/versions.h index d70ee721..f5fceb48 100644 --- a/src/engine/versions.h +++ b/src/engine/versions.h @@ -1,2 +1,3 @@ -#define TEEWARS_NETVERSION "dev v2" +#define TEEWARS_NETVERSION 0xffffffff +//#define TEEWARS_NETVERSION_STRING "dev v2" #define TEEWARS_VERSION "0.2.1-dev" |