diff options
Diffstat (limited to 'src/common')
55 files changed, 19854 insertions, 0 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 000000000..e8c6c0a70 --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,163 @@ + +# +# Create svnversion.h +# +message( STATUS "Creating svnversion.h" ) +if( SVNVERSION ) + file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h + "#ifndef SVNVERSION\n#define SVNVERSION ${SVNVERSION}\n#endif\n" ) +else() + file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h "" ) +endif() +set( GLOBAL_INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "" ) +set( SVNVERSION ${SVNVERSION} + CACHE STRING "SVN version of the source code" ) +if( INSTALL_COMPONENT_DEVELOPMENT ) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h + DESTINATION "src/common" + COMPONENT Development_base ) +endif( INSTALL_COMPONENT_DEVELOPMENT ) +message( STATUS "Creating svnversion.h - done" ) + + +##################################################################### +# setup +# +set( COMMON_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + CACHE PATH "common source directory" ) +mark_as_advanced( COMMON_SOURCE_DIR ) + +set( COMMON_ALL_HEADERS + "${CMAKE_CURRENT_BINARY_DIR}/svnversion.h" + "${COMMON_SOURCE_DIR}/cbasetypes.h" + "${COMMON_SOURCE_DIR}/mmo.h" + ) + +set( COMMON_MINI_HEADERS + ${COMMON_ALL_HEADERS} + "${COMMON_SOURCE_DIR}/core.h" + "${COMMON_SOURCE_DIR}/malloc.h" + "${COMMON_SOURCE_DIR}/showmsg.h" + "${COMMON_SOURCE_DIR}/strlib.h" + ${LIBCONFIG_HEADERS} # needed by showmsg.h + CACHE INTERNAL "" ) +set( COMMON_MINI_SOURCES + "${COMMON_SOURCE_DIR}/core.c" + "${COMMON_SOURCE_DIR}/malloc.c" + "${COMMON_SOURCE_DIR}/showmsg.c" + "${COMMON_SOURCE_DIR}/strlib.c" + ${LIBCONFIG_SOURCES} # needed by showmsg.c + CACHE INTERNAL "" ) +set( COMMON_MINI_INCLUDE_DIRS ${LIBCONFIG_INCLUDE_DIRS} CACHE INTERNAL "" ) +set( COMMON_MINI_DEFINITIONS "-DMINICORE ${LIBCONFIG_DEFINITIONS}" CACHE INTERNAL "" ) + + +# +# common_base +# +if( WITH_ZLIB ) +message( STATUS "Creating target common_base" ) +set( COMMON_BASE_HEADERS + ${COMMON_ALL_HEADERS} + "${COMMON_SOURCE_DIR}/conf.h" + "${COMMON_SOURCE_DIR}/core.h" + "${COMMON_SOURCE_DIR}/db.h" + "${COMMON_SOURCE_DIR}/des.h" + "${COMMON_SOURCE_DIR}/ers.h" + "${COMMON_SOURCE_DIR}/grfio.h" + "${COMMON_SOURCE_DIR}/malloc.h" + "${COMMON_SOURCE_DIR}/mapindex.h" + "${COMMON_SOURCE_DIR}/md5calc.h" + "${COMMON_SOURCE_DIR}/nullpo.h" + "${COMMON_SOURCE_DIR}/random.h" + "${COMMON_SOURCE_DIR}/showmsg.h" + "${COMMON_SOURCE_DIR}/socket.h" + "${COMMON_SOURCE_DIR}/strlib.h" + "${COMMON_SOURCE_DIR}/timer.h" + "${COMMON_SOURCE_DIR}/utils.h" + "${COMMON_SOURCE_DIR}/atomic.h" + "${COMMON_SOURCE_DIR}/spinlock.h" + "${COMMON_SOURCE_DIR}/thread.h" + "${COMMON_SOURCE_DIR}/mutex.h" + "${COMMON_SOURCE_DIR}/raconf.h" + "${COMMON_SOURCE_DIR}/mempool.h" + ${LIBCONFIG_HEADERS} # needed by conf.h/showmsg.h + CACHE INTERNAL "common_base headers" ) +set( COMMON_BASE_SOURCES + "${COMMON_SOURCE_DIR}/conf.c" + "${COMMON_SOURCE_DIR}/core.c" + "${COMMON_SOURCE_DIR}/db.c" + "${COMMON_SOURCE_DIR}/des.c" + "${COMMON_SOURCE_DIR}/ers.c" + "${COMMON_SOURCE_DIR}/grfio.c" + "${COMMON_SOURCE_DIR}/malloc.c" + "${COMMON_SOURCE_DIR}/mapindex.c" + "${COMMON_SOURCE_DIR}/md5calc.c" + "${COMMON_SOURCE_DIR}/nullpo.c" + "${COMMON_SOURCE_DIR}/random.c" + "${COMMON_SOURCE_DIR}/showmsg.c" + "${COMMON_SOURCE_DIR}/socket.c" + "${COMMON_SOURCE_DIR}/strlib.c" + "${COMMON_SOURCE_DIR}/timer.c" + "${COMMON_SOURCE_DIR}/utils.c" + "${COMMON_SOURCE_DIR}/thread.c" + "${COMMON_SOURCE_DIR}/mutex.c" + "${COMMON_SOURCE_DIR}/mempool.c" + "${COMMON_SOURCE_DIR}/raconf.c" + ${LIBCONFIG_SOURCES} # needed by conf.c/showmsg.c + CACHE INTERNAL "common_base sources" ) +set( COMMON_BASE_INCLUDE_DIRS + ${LIBCONFIG_INCLUDE_DIRS} + CACHE INTERNAL "common_base include dirs" ) +set( COMMON_BASE_DEFINITIONS + ${LIBCONFIG_DEFINITIONS} + CACHE INTERNAL "common_base definitions" ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ${ZLIB_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${MT19937AR_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS}" ) +set( SOURCE_FILES ${MT19937AR_HEADERS} ${MT19937AR_SOURCES} ${COMMON_BASE_HEADERS} ${COMMON_BASE_SOURCES} ) +source_group( mt19937ar FILES ${MT19937AR_HEADERS} ${MT19937AR_SOURCES} ) +source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_BASE_SOURCES} ) +add_library( common_base ${SOURCE_FILES} ) +target_link_libraries( common_base ${LIBRARIES} ) +set_target_properties( common_base PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +include_directories( ${INCLUDE_DIRS} ) +set( HAVE_common_base ON CACHE INTERNAL "" ) +set( TARGET_LIST ${TARGET_LIST} common_base CACHE INTERNAL "" ) +message( STATUS "Creating target common_base - done" ) +else() +message( STATUS "Skipping target common_base (requires ZLIB)" ) +unset( HAVE_common_base CACHE ) +endif() + + +# +# common_sql +# +if( HAVE_common_base AND WITH_MYSQL ) +message( STATUS "Creating target common_sql" ) +set( COMMON_SQL_HEADERS + ${COMMON_ALL_HEADERS} + "${CMAKE_CURRENT_SOURCE_DIR}/sql.h" + CACHE INTERNAL "common_sql headers" ) +set( COMMON_SQL_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/sql.c" + CACHE INTERNAL "common_sql sources" ) +set( DEPENDENCIES common_base ) +set( LIBRARIES ${GLOBAL_LIBRARIES} ${MYSQL_LIBRARIES} ) +set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${MYSQL_INCLUDE_DIRS} ) +set( DEFINITIONS "${GLOBAL_DEFINITIONS}" ) +set( SOURCE_FILES ${COMMON_SQL_HEADERS} ${COMMON_SQL_SOURCES} ) +source_group( common FILES ${COMMON_SQL_HEADERS} ${COMMON_SQL_SOURCES} ) +add_library( common_sql ${SOURCE_FILES} ) +add_dependencies( common_sql ${DEPENDENCIES} ) +target_link_libraries( common_sql ${LIBRARIES} ${DEPENDENCIES} ) +set_target_properties( common_sql PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) +include_directories( ${INCLUDE_DIRS} ) +set( HAVE_common_sql ON CACHE INTERNAL "" ) +set( TARGET_LIST ${TARGET_LIST} common_sql CACHE INTERNAL "" ) +message( STATUS "Creating target common_sql - done" ) +else() +message( STATUS "Skipping target common_sql (requires common_base and MYSQL)" ) +unset( HAVE_common_sql CACHE ) +endif() diff --git a/src/common/Makefile.in b/src/common/Makefile.in new file mode 100644 index 000000000..c24499c02 --- /dev/null +++ b/src/common/Makefile.in @@ -0,0 +1,97 @@ + +COMMON_OBJ = obj_all/core.o obj_all/socket.o obj_all/timer.o obj_all/db.o \ + obj_all/nullpo.o obj_all/malloc.o obj_all/showmsg.o obj_all/strlib.o obj_all/utils.o \ + obj_all/grfio.o obj_all/mapindex.o obj_all/ers.o obj_all/md5calc.o \ + obj_all/minicore.o obj_all/minisocket.o obj_all/minimalloc.o obj_all/random.o obj_all/des.o \ + obj_all/conf.o obj_all/thread.o obj_all/mutex.o obj_all/raconf.o obj_all/mempool.o + +COMMON_H = $(shell ls ../common/*.h) + +COMMON_SQL_OBJ = obj_sql/sql.o +COMMON_SQL_H = sql.h + +MT19937AR_OBJ = ../../3rdparty/mt19937ar/mt19937ar.o +MT19937AR_H = ../../3rdparty/mt19937ar/mt19937ar.h +MT19937AR_INCLUDE = -I../../3rdparty/mt19937ar + +LIBCONFIG_OBJ = ../../3rdparty/libconfig/libconfig.o ../../3rdparty/libconfig/grammar.o \ + ../../3rdparty/libconfig/scanctx.o ../../3rdparty/libconfig/scanner.o ../../3rdparty/libconfig/strbuf.o +LIBCONFIG_H = ../../3rdparty/libconfig/libconfig.h ../../3rdparty/libconfig/grammar.h \ + ../../3rdparty/libconfig/parsectx.h ../../3rdparty/libconfig/scanctx.h ../../3rdparty/libconfig/scanner.h \ + ../../3rdparty/libconfig/strbuf.h ../../3rdparty/libconfig/wincompat.h +LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig + +HAVE_MYSQL=@HAVE_MYSQL@ +ifeq ($(HAVE_MYSQL),yes) + ALL_DEPENDS=sql + SQL_DEPENDS=common common_sql +else + SQL_DEPENDS=needs_mysql +endif + +@SET_MAKE@ + +##################################################################### +.PHONY : all sql clean help + +all: $(ALL_DEPENDS) + +sql: $(SQL_DEPENDS) + +clean: + @echo " CLEAN common" + @rm -rf *.o obj_all obj_sql + +help: + @echo "possible targets are 'sql' 'all' 'clean' 'help'" + @echo "'sql' - builds object files used in sql servers" + @echo "'all' - builds all above targets" + @echo "'clean' - cleans builds and objects" + @echo "'help' - outputs this message" + +##################################################################### + +needs_mysql: + @echo "MySQL not found or disabled by the configure script" + @exit 1 + +obj_all: + @echo " MKDIR obj_all" + @-mkdir obj_all + +obj_sql: + @echo " MKDIR obj_sql" + @-mkdir obj_sql + +obj_all/common.a: $(COMMON_OBJ) + @echo " AR $@" + @@AR@ rcs obj_all/common.a $(COMMON_OBJ) + +obj_sql/common_sql.a: $(COMMON_SQL_OBJ) + @echo " AR $@" + @@AR@ rcs obj_sql/common_sql.a $(COMMON_SQL_OBJ) + + +common: obj_all $(COMMON_OBJ) $(MT19937AR_OBJ) $(LIBCONFIG_OBJ) obj_all/common.a + +common_sql: obj_sql $(COMMON_SQL_OBJ) obj_sql/common_sql.a + +obj_all/%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +obj_all/mini%.o: %.c $(COMMON_H) $(MT19937AR_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(MT19937AR_INCLUDE) $(LIBCONFIG_INCLUDE) -DMINICORE @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + +obj_sql/%.o: %.c $(COMMON_H) $(COMMON_SQL_H) $(LIBCONFIG_H) + @echo " CC $<" + @@CC@ @CFLAGS@ $(LIBCONFIG_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< + + +# missing object files +MT19937AR_OBJ: + @$(MAKE) -C ../../3rdparty/mt19937ar + +LIBCONFIG_OBJ: + @$(MAKE) -C ../../3rdparty/libconfig diff --git a/src/common/atomic.h b/src/common/atomic.h new file mode 100644 index 000000000..b1a4bda92 --- /dev/null +++ b/src/common/atomic.h @@ -0,0 +1,142 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_ATOMIC_H_ +#define _rA_ATOMIC_H_ + +// Atomic Operations +// (Interlocked CompareExchange, Add .. and so on ..) +// +// Implementation varies / depends on: +// - Architecture +// - Compiler +// - Operating System +// +// our Abstraction is fully API-Compatible to Microsofts implementation @ NT5.0+ +// +#include "../common/cbasetypes.h" + +#if defined(_MSC_VER) +#include "../common/winapi.h" + +#if !defined(_M_X64) +// When compiling for windows 32bit, the 8byte interlocked operations are not provided by microsoft +// (because they need at least i586 so its not generic enough.. ... ) +forceinline int64 InterlockedCompareExchange64(volatile int64 *dest, int64 exch, int64 _cmp){ + _asm{ + lea esi,_cmp; + lea edi,exch; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,dest; + + lock CMPXCHG8B [esi]; + } +} + + +forceinline volatile int64 InterlockedIncrement64(volatile int64 *addend){ + __int64 old; + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old+1), old) != old); + + return (old + 1); +} + + + +forceinline volatile int64 InterlockedDecrement64(volatile int64 *addend){ + __int64 old; + + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old-1), old) != old); + + return (old - 1); +} + +forceinline volatile int64 InterlockedExchangeAdd64(volatile int64 *addend, int64 increment){ + __int64 old; + + do{ + old = *addend; + }while(InterlockedCompareExchange64(addend, (old + increment), old) != old); + + return old; +} + +forceinline volatile int64 InterlockedExchange64(volatile int64 *target, int64 val){ + __int64 old; + do{ + old = *target; + }while(InterlockedCompareExchange64(target, val, old) != old); + + return old; +} + +#endif //endif 32bit windows + +#elif defined(__GNUC__) + +#if !defined(__x86_64__) && !defined(__i386__) +#error Your Target Platfrom is not supported +#endif + +static forceinline int64 InterlockedExchangeAdd64(volatile int64 *addend, int64 increment){ + return __sync_fetch_and_add(addend, increment); +}//end: InterlockedExchangeAdd64() + + +static forceinline int32 InterlockedExchangeAdd(volatile int32 *addend, int32 increment){ + return __sync_fetch_and_add(addend, increment); +}//end: InterlockedExchangeAdd() + + +static forceinline int64 InterlockedIncrement64(volatile int64 *addend){ + return __sync_add_and_fetch(addend, 1); +}//end: InterlockedIncrement64() + + +static forceinline int32 InterlockedIncrement(volatile int32 *addend){ + return __sync_add_and_fetch(addend, 1); +}//end: InterlockedIncrement() + + +static forceinline int64 InterlockedDecrement64(volatile int64 *addend){ + return __sync_sub_and_fetch(addend, 1); +}//end: InterlockedDecrement64() + + +static forceinline int32 InterlockedDecrement(volatile int32 *addend){ + return __sync_sub_and_fetch(addend, 1); +}//end: InterlockedDecrement() + + +static forceinline int64 InterlockedCompareExchange64(volatile int64 *dest, int64 exch, int64 cmp){ + return __sync_val_compare_and_swap(dest, cmp, exch); +}//end: InterlockedCompareExchange64() + + +static forceinline int32 InterlockedCompareExchange(volatile int32 *dest, int32 exch, int32 cmp){ + return __sync_val_compare_and_swap(dest, cmp, exch); +}//end: InterlockedCompareExchnage() + + +static forceinline int64 InterlockedExchange64(volatile int64 *target, int64 val){ + return __sync_lock_test_and_set(target, val); +}//end: InterlockedExchange64() + + +static forceinline int32 InterlockedExchange(volatile int32 *target, int32 val){ + return __sync_lock_test_and_set(target, val); +}//end: InterlockedExchange() + + +#endif //endif compiler decission + + +#endif diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h new file mode 100644 index 000000000..731a8b578 --- /dev/null +++ b/src/common/cbasetypes.h @@ -0,0 +1,404 @@ +#ifndef _CBASETYPES_H_ +#define _CBASETYPES_H_ + +/* +--------+-----------+--------+---------+ + * | ILP32 | LP64 | ILP64 | (LL)P64 | + * +------------+--------+-----------+--------+---------+ + * | ints | 32-bit | 32-bit | 64-bit | 32-bit | + * | longs | 32-bit | 64-bit | 64-bit | 32-bit | + * | long-longs | 64-bit | 64-bit | 64-bit | 64-bit | + * | pointers | 32-bit | 64-bit | 64-bit | 64-bit | + * +------------+--------+-----------+--------+---------+ + * | where | -- | Tiger | Alpha | Windows | + * | used | | Unix | Cray | | + * | | | Sun & SGI | | | + * +------------+--------+-----------+--------+---------+ + * Taken from http://developer.apple.com/macosx/64bit.html + */ + +////////////////////////////////////////////////////////////////////////// +// basic include for all basics +// introduces types and global functions +////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// setting some defines on platforms +////////////////////////////////////////////////////////////////////////// +#if (defined(__WIN32__) || defined(__WIN32) || defined(_WIN32) || defined(_WIN64) || defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(WIN32) +#define WIN32 +#endif + +#if defined(__MINGW32__) && !defined(MINGW) +#define MINGW +#endif + +#if (defined(__CYGWIN__) || defined(__CYGWIN32__)) && !defined(CYGWIN) +#define CYGWIN +#endif + +// __APPLE__ is the only predefined macro on MacOS X +#if defined(__APPLE__) +#define __DARWIN__ +#endif + +// 64bit OS +#if defined(_M_IA64) || defined(_M_X64) || defined(_WIN64) || defined(_LP64) || defined(_ILP64) || defined(__LP64__) || defined(__ppc64__) +#define __64BIT__ +#endif + +#if defined(_ILP64) +#error "this specific 64bit architecture is not supported" +#endif + +// debug mode +#if defined(_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif + +// debug function name +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif + + +// disable attributed stuff on non-GNU +#if !defined(__GNUC__) && !defined(MINGW) +# define __attribute__(x) +#endif + +////////////////////////////////////////////////////////////////////////// +// portable printf/scanf format macros and integer definitions +// NOTE: Visual C++ uses <inttypes.h> and <stdint.h> provided in /3rdparty +////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include <inttypes.h> +#include <stdint.h> +#include <limits.h> + +// temporary fix for bugreport:4961 (unintended conversion from signed to unsigned) +// (-20 >= UCHAR_MAX) returns true +// (-20 >= USHRT_MAX) returns true +#if defined(__FreeBSD__) && defined(__x86_64) +#undef UCHAR_MAX +#define UCHAR_MAX (unsigned char)0xff +#undef USHRT_MAX +#define USHRT_MAX (unsigned short)0xffff +#endif + +// ILP64 isn't supported, so always 32 bits? +#ifndef UINT_MAX +#define UINT_MAX 0xffffffff +#endif + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _exact_ size. +////////////////////////////////////////////////////////////////////////// + +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +typedef int8_t sint8; +typedef int16_t sint16; +typedef int32_t sint32; +typedef int64_t sint64; + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +#undef UINT8_MIN +#undef UINT16_MIN +#undef UINT32_MIN +#undef UINT64_MIN +#define UINT8_MIN ((uint8) UINT8_C(0x00)) +#define UINT16_MIN ((uint16)UINT16_C(0x0000)) +#define UINT32_MIN ((uint32)UINT32_C(0x00000000)) +#define UINT64_MIN ((uint64)UINT64_C(0x0000000000000000)) + +#undef UINT8_MAX +#undef UINT16_MAX +#undef UINT32_MAX +#undef UINT64_MAX +#define UINT8_MAX ((uint8) UINT8_C(0xFF)) +#define UINT16_MAX ((uint16)UINT16_C(0xFFFF)) +#define UINT32_MAX ((uint32)UINT32_C(0xFFFFFFFF)) +#define UINT64_MAX ((uint64)UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#undef SINT8_MIN +#undef SINT16_MIN +#undef SINT32_MIN +#undef SINT64_MIN +#define SINT8_MIN ((sint8) INT8_C(0x80)) +#define SINT16_MIN ((sint16)INT16_C(0x8000)) +#define SINT32_MIN ((sint32)INT32_C(0x80000000)) +#define SINT64_MIN ((sint32)INT64_C(0x8000000000000000)) + +#undef SINT8_MAX +#undef SINT16_MAX +#undef SINT32_MAX +#undef SINT64_MAX +#define SINT8_MAX ((sint8) INT8_C(0x7F)) +#define SINT16_MAX ((sint16)INT16_C(0x7FFF)) +#define SINT32_MAX ((sint32)INT32_C(0x7FFFFFFF)) +#define SINT64_MAX ((sint64)INT64_C(0x7FFFFFFFFFFFFFFF)) + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _minimum_ size. +// These could be larger than you expect, +// they are designed for speed. +////////////////////////////////////////////////////////////////////////// +typedef long int ppint; +typedef long int ppint8; +typedef long int ppint16; +typedef long int ppint32; + +typedef unsigned long int ppuint; +typedef unsigned long int ppuint8; +typedef unsigned long int ppuint16; +typedef unsigned long int ppuint32; + + +////////////////////////////////////////////////////////////////////////// +// integer with exact processor width (and best speed) +////////////////////////////// +#include <stddef.h> // size_t + +#if defined(WIN32) && !defined(MINGW) // does not have a signed size_t +////////////////////////////// +#if defined(_WIN64) // naive 64bit windows platform +typedef __int64 ssize_t; +#else +typedef int ssize_t; +#endif +////////////////////////////// +#endif +////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// pointer sized integers +////////////////////////////////////////////////////////////////////////// +typedef intptr_t intptr; +typedef uintptr_t uintptr; + + +////////////////////////////////////////////////////////////////////////// +// Add a 'sysint' Type which has the width of the platform we're compiled for. +////////////////////////////////////////////////////////////////////////// +#if defined(__GNUC__) + #if defined(__x86_64__) + typedef int64 sysint; + typedef uint64 usysint; + #else + typedef int32 sysint; + typedef uint32 usysint; + #endif +#elif defined(_MSC_VER) + #if defined(_M_X64) + typedef int64 sysint; + typedef uint64 usysint; + #else + typedef int32 sysint; + typedef uint32 usysint; + #endif +#else + #error Compiler / Platform is unsupported. +#endif + + +////////////////////////////////////////////////////////////////////////// +// some redefine of function redefines for some Compilers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define strncmpi strnicmp +#define snprintf _snprintf +#if defined(_MSC_VER) && _MSC_VER < 1400 +#define vsnprintf _vsnprintf +#endif +#else +#define strcmpi strcasecmp +#define stricmp strcasecmp +#define strncmpi strncasecmp +#define strnicmp strncasecmp +#endif +#if defined(_MSC_VER) && _MSC_VER > 1200 +#define strtoull _strtoui64 +#endif + +// keyword replacement +#ifdef _MSC_VER +// For MSVC (windows) +#define inline __inline +#define forceinline __forceinline +#define ra_align(n) __declspec(align(n)) +#else +// For GCC +#define forceinline __attribute__((always_inline)) inline +#define ra_align(n) __attribute__(( aligned(n) )) +#endif + + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef char bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not __cplusplus +////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// macro tools + +#ifdef swap // just to be sure +#undef swap +#endif +// hmm only ints? +//#define swap(a,b) { int temp=a; a=b; b=temp;} +// if using macros then something that is type independent +//#define swap(a,b) ((a == b) || ((a ^= b), (b ^= a), (a ^= b))) +// Avoid "value computed is not used" warning and generates the same assembly code +#define swap(a,b) if (a != b) ((a ^= b), (b ^= a), (a ^= b)) + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +////////////////////////////////////////////////////////////////////////// +// should not happen +#ifndef NULL +#define NULL (void *)0 +#endif + +////////////////////////////////////////////////////////////////////////// +// number of bits in a byte +#ifndef NBBY +#define NBBY 8 +#endif + +////////////////////////////////////////////////////////////////////////// +// path separator + +#if defined(WIN32) +#define PATHSEP '\\' +#define PATHSEP_STR "\\" +#elif defined(__APPLE__) +// FIXME Mac OS X is unix based, is this still correct? +#define PATHSEP ':' +#define PATHSEP_STR ":" +#else +#define PATHSEP '/' +#define PATHSEP_STR "/" +#endif + +////////////////////////////////////////////////////////////////////////// +// Assert + +#if ! defined(Assert) +#if defined(RELEASE) +#define Assert(EX) +#else +// extern "C" { +#include <assert.h> +// } +#if !defined(DEFCPP) && defined(WIN32) && !defined(MINGW) +#include <crtdbg.h> +#endif +#define Assert(EX) assert(EX) +#endif +#endif /* ! defined(Assert) */ + +////////////////////////////////////////////////////////////////////////// +// Has to be unsigned to avoid problems in some systems +// Problems arise when these functions expect an argument in the range [0,256[ and are fed a signed char. +#include <ctype.h> +#define ISALNUM(c) (isalnum((unsigned char)(c))) +#define ISALPHA(c) (isalpha((unsigned char)(c))) +#define ISCNTRL(c) (iscntrl((unsigned char)(c))) +#define ISDIGIT(c) (isdigit((unsigned char)(c))) +#define ISGRAPH(c) (isgraph((unsigned char)(c))) +#define ISLOWER(c) (islower((unsigned char)(c))) +#define ISPRINT(c) (isprint((unsigned char)(c))) +#define ISPUNCT(c) (ispunct((unsigned char)(c))) +#define ISSPACE(c) (isspace((unsigned char)(c))) +#define ISUPPER(c) (isupper((unsigned char)(c))) +#define ISXDIGIT(c) (isxdigit((unsigned char)(c))) +#define TOASCII(c) (toascii((unsigned char)(c))) +#define TOLOWER(c) (tolower((unsigned char)(c))) +#define TOUPPER(c) (toupper((unsigned char)(c))) + +////////////////////////////////////////////////////////////////////////// +// length of a static array +#define ARRAYLENGTH(A) ( sizeof(A)/sizeof((A)[0]) ) + +////////////////////////////////////////////////////////////////////////// +// Make sure va_copy exists +#include <stdarg.h> // va_list, va_copy(?) +#if !defined(va_copy) +#if defined(__va_copy) +#define va_copy __va_copy +#else +#define va_copy(dst, src) ((void) memcpy(&(dst), &(src), sizeof(va_list))) +#endif +#endif + + +////////////////////////////////////////////////////////////////////////// +// Use the preprocessor to 'stringify' stuff (concert to a string). +// example: +// #define TESTE blabla +// QUOTE(TESTE) -> "TESTE" +// EXPAND_AND_QUOTE(TESTE) -> "blabla" +#define QUOTE(x) #x +#define EXPAND_AND_QUOTE(x) QUOTE(x) + + +////////////////////////////////////////////////////////////////////////// +// Set a pointer variable to a pointer value. +#ifdef __cplusplus +template <typename T1, typename T2> +void SET_POINTER(T1*&var, T2* p) +{ + var = static_cast<T1*>(p); +} +template <typename T1, typename T2> +void SET_FUNCPOINTER(T1& var, T2 p) +{ + char ASSERT_POINTERSIZE[sizeof(T1) == sizeof(void*) && sizeof(T2) == sizeof(void*)?1:-1];// 1 if true, -1 if false + union{ T1 out; T2 in; } tmp;// /!\ WARNING casting a pointer to a function pointer is against the C++ standard + tmp.in = p; + var = tmp.out; +} +#else +#define SET_POINTER(var,p) (var) = (p) +#define SET_FUNCPOINTER(var,p) (var) = (p) +#endif + + +#endif /* _CBASETYPES_H_ */ diff --git a/src/common/conf.c b/src/common/conf.c new file mode 100644 index 000000000..3057bd4dc --- /dev/null +++ b/src/common/conf.c @@ -0,0 +1,109 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "conf.h" +#include "libconfig.h" + +#include "../common/showmsg.h" // ShowError + +int conf_read_file(config_t *config, const char *config_filename) +{ + config_init(config); + if (!config_read_file(config, config_filename)) { + ShowError("%s:%d - %s\n", config_error_file(config), + config_error_line(config), config_error_text(config)); + config_destroy(config); + return 1; + } + return 0; +} + +// +// Functions to copy settings from libconfig/contrib +// +static void config_setting_copy_simple(config_setting_t *parent, const config_setting_t *src); +static void config_setting_copy_elem(config_setting_t *parent, const config_setting_t *src); +static void config_setting_copy_aggregate(config_setting_t *parent, const config_setting_t *src); +int config_setting_copy(config_setting_t *parent, const config_setting_t *src); + +void config_setting_copy_simple(config_setting_t *parent, const config_setting_t *src) +{ + if (config_setting_is_aggregate(src)) { + config_setting_copy_aggregate(parent, src); + } + else { + config_setting_t *set = config_setting_add(parent, config_setting_name(src), config_setting_type(src)); + + if (set == NULL) + return; + + if (CONFIG_TYPE_INT == config_setting_type(src)) { + config_setting_set_int(set, config_setting_get_int(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_INT64 == config_setting_type(src)) { + config_setting_set_int64(set, config_setting_get_int64(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_FLOAT == config_setting_type(src)) { + config_setting_set_float(set, config_setting_get_float(src)); + } else if (CONFIG_TYPE_STRING == config_setting_type(src)) { + config_setting_set_string(set, config_setting_get_string(src)); + } else if (CONFIG_TYPE_BOOL == config_setting_type(src)) { + config_setting_set_bool(set, config_setting_get_bool(src)); + } + } +} + +void config_setting_copy_elem(config_setting_t *parent, const config_setting_t *src) +{ + config_setting_t *set = NULL; + + if (config_setting_is_aggregate(src)) + config_setting_copy_aggregate(parent, src); + else if (CONFIG_TYPE_INT == config_setting_type(src)) { + set = config_setting_set_int_elem(parent, -1, config_setting_get_int(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_INT64 == config_setting_type(src)) { + set = config_setting_set_int64_elem(parent, -1, config_setting_get_int64(src)); + config_setting_set_format(set, src->format); + } else if (CONFIG_TYPE_FLOAT == config_setting_type(src)) { + config_setting_set_float_elem(parent, -1, config_setting_get_float(src)); + } else if (CONFIG_TYPE_STRING == config_setting_type(src)) { + config_setting_set_string_elem(parent, -1, config_setting_get_string(src)); + } else if (CONFIG_TYPE_BOOL == config_setting_type(src)) { + config_setting_set_bool_elem(parent, -1, config_setting_get_bool(src)); + } +} + +void config_setting_copy_aggregate(config_setting_t *parent, const config_setting_t *src) +{ + config_setting_t *newAgg; + int i, n; + + newAgg = config_setting_add(parent, config_setting_name(src), config_setting_type(src)); + + if (newAgg == NULL) + return; + + n = config_setting_length(src); + + for (i = 0; i < n; i++) { + if (config_setting_is_group(src)) { + config_setting_copy_simple(newAgg, config_setting_get_elem(src, i)); + } else { + config_setting_copy_elem(newAgg, config_setting_get_elem(src, i)); + } + } +} + +int config_setting_copy(config_setting_t *parent, const config_setting_t *src) +{ + if (!config_setting_is_group(parent) && !config_setting_is_list(parent)) + return CONFIG_FALSE; + + if (config_setting_is_aggregate(src)) { + config_setting_copy_aggregate(parent, src); + } else { + config_setting_copy_simple(parent, src); + } + return CONFIG_TRUE; +} diff --git a/src/common/conf.h b/src/common/conf.h new file mode 100644 index 000000000..666853ba6 --- /dev/null +++ b/src/common/conf.h @@ -0,0 +1,13 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CONF_H_ +#define _CONF_H_ + +#include "../common/cbasetypes.h" +#include "libconfig.h" + +int conf_read_file(config_t *config, const char *config_filename); +int config_setting_copy(config_setting_t *parent, const config_setting_t *src); + +#endif // _CONF_H_ diff --git a/src/common/core.c b/src/common/core.c new file mode 100644 index 000000000..e1f99885b --- /dev/null +++ b/src/common/core.c @@ -0,0 +1,355 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "core.h" +#ifndef MINICORE +#include "../common/db.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/thread.h" +#include "../common/mempool.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#ifndef _WIN32 +#include <unistd.h> +#else +#include "../common/winapi.h" // Console close event handling +#endif + + +/// Called when a terminate signal is received. +void (*shutdown_callback)(void) = NULL; + +#if defined(BUILDBOT) + int buildbotflag = 0; +#endif + +int runflag = CORE_ST_RUN; +int arg_c = 0; +char **arg_v = NULL; + +char *SERVER_NAME = NULL; +char SERVER_TYPE = ATHENA_SERVER_NONE; + +#ifndef MINICORE // minimalist Core +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +#ifdef WIN32 // windows don't have SIGPIPE +#define SIGPIPE SIGINT +#endif + +#ifndef POSIX +#define compat_signal(signo, func) signal(signo, func) +#else +sigfunc *compat_signal(int signo, sigfunc *func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_INTERRUPT + sact.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + + if (sigaction(signo, &sact, &oact) < 0) + return (SIG_ERR); + + return (oact.sa_handler); +} +#endif + +/*====================================== + * CORE : Console events for Windows + *--------------------------------------*/ +#ifdef _WIN32 +static BOOL WINAPI console_handler(DWORD c_event) +{ + switch(c_event) + { + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + if( shutdown_callback != NULL ) + shutdown_callback(); + else + runflag = CORE_ST_STOP;// auto-shutdown + break; + default: + return FALSE; + } + return TRUE; +} + +static void cevents_init() +{ + if (SetConsoleCtrlHandler(console_handler,TRUE)==FALSE) + ShowWarning ("Unable to install the console handler!\n"); +} +#endif + +/*====================================== + * CORE : Signal Sub Function + *--------------------------------------*/ +static void sig_proc(int sn) +{ + static int is_called = 0; + + switch (sn) { + case SIGINT: + case SIGTERM: + if (++is_called > 3) + exit(EXIT_SUCCESS); + if( shutdown_callback != NULL ) + shutdown_callback(); + else + runflag = CORE_ST_STOP;// auto-shutdown + break; + case SIGSEGV: + case SIGFPE: + do_abort(); + // Pass the signal to the system's default handler + compat_signal(sn, SIG_DFL); + raise(sn); + break; +#ifndef _WIN32 + case SIGXFSZ: + // ignore and allow it to set errno to EFBIG + ShowWarning ("Max file size reached!\n"); + //run_flag = 0; // should we quit? + break; + case SIGPIPE: + //ShowInfo ("Broken pipe found... closing socket\n"); // set to eof in socket.c + break; // does nothing here +#endif + } +} + +void signals_init (void) +{ + compat_signal(SIGTERM, sig_proc); + compat_signal(SIGINT, sig_proc); +#ifndef _DEBUG // need unhandled exceptions to debug on Windows + compat_signal(SIGSEGV, sig_proc); + compat_signal(SIGFPE, sig_proc); +#endif +#ifndef _WIN32 + compat_signal(SIGILL, SIG_DFL); + compat_signal(SIGXFSZ, sig_proc); + compat_signal(SIGPIPE, sig_proc); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); +#endif +} +#endif + +#ifdef SVNVERSION + const char *get_svn_revision(void) + { + return EXPAND_AND_QUOTE(SVNVERSION); + } +#else// not SVNVERSION +const char* get_svn_revision(void) +{ + static char svn_version_buffer[16] = ""; + FILE *fp; + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + + // subversion 1.7 uses a sqlite3 database + // FIXME this is hackish at best... + // - ignores database file structure + // - assumes the data in NODES.dav_cache column ends with "!svn/ver/<revision>/<path>)" + // - since it's a cache column, the data might not even exist + if( (fp = fopen(".svn"PATHSEP_STR"wc.db", "rb")) != NULL || (fp = fopen(".."PATHSEP_STR".svn"PATHSEP_STR"wc.db", "rb")) != NULL ) + { + #ifndef SVNNODEPATH + //not sure how to handle branches, so i'll leave this overridable define until a better solution comes up + #define SVNNODEPATH trunk + #endif + const char* prefix = "!svn/ver/"; + const char* postfix = "/"EXPAND_AND_QUOTE(SVNNODEPATH)")"; // there should exist only 1 entry like this + size_t prefix_len = strlen(prefix); + size_t postfix_len = strlen(postfix); + size_t i,j,len; + char* buffer; + + // read file to buffer + fseek(fp, 0, SEEK_END); + len = ftell(fp); + buffer = (char*)aMalloc(len + 1); + fseek(fp, 0, SEEK_SET); + len = fread(buffer, 1, len, fp); + buffer[len] = '\0'; + fclose(fp); + + // parse buffer + for( i = prefix_len + 1; i + postfix_len <= len; ++i ) + { + if( buffer[i] != postfix[0] || memcmp(buffer + i, postfix, postfix_len) != 0 ) + continue; // postfix missmatch + for( j = i; j > 0; --j ) + {// skip digits + if( !ISDIGIT(buffer[j - 1]) ) + break; + } + if( memcmp(buffer + j - prefix_len, prefix, prefix_len) != 0 ) + continue; // prefix missmatch + // done + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(buffer + j)); + break; + } + aFree(buffer); + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + } + + // subversion 1.6 and older? + if ((fp = fopen(".svn/entries", "r")) != NULL) + { + char line[1024]; + int rev; + // Check the version + if (fgets(line, sizeof(line), fp)) + { + if(!ISDIGIT(line[0])) + { + // XML File format + while (fgets(line,sizeof(line),fp)) + if (strstr(line,"revision=")) break; + if (sscanf(line," %*[^\"]\"%d%*[^\n]", &rev) == 1) { + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", rev); + } + } + else + { + // Bin File format + if ( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get bin name\n"); } // Get the name + if ( fgets(line, sizeof(line), fp) == NULL ) { printf("Can't get entries kind\n"); } // Get the entries kind + if(fgets(line, sizeof(line), fp)) // Get the rev numver + { + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "%d", atoi(line)); + } + } + } + fclose(fp); + + if( svn_version_buffer[0] != '\0' ) + return svn_version_buffer; + } + + // fallback + snprintf(svn_version_buffer, sizeof(svn_version_buffer), "Unknown"); + return svn_version_buffer; +} +#endif + +/*====================================== + * CORE : Display title + * ASCII By CalciumKid 1/12/2011 + *--------------------------------------*/ +static void display_title(void) { + //ClearScreen(); // clear screen and go up/left (0, 0 position in text) + + ShowMessage("\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BT_WHITE" rAthena Development Team presents "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" ___ __ __ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" _____/ | / /_/ /_ ___ ____ ____ _ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" / ___/ /| |/ __/ __ \\/ _ \\/ __ \\/ __ `/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" / / / ___ / /_/ / / / __/ / / / /_/ / "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" /_/ /_/ |_\\__/_/ /_/\\___/_/ /_/\\__,_/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_GREEN" http://rathena.org/board/ "CL_PASS""CL_CLL""CL_NORMAL"\n"); + ShowMessage(""CL_PASS" "CL_BOLD" "CL_PASS""CL_CLL""CL_NORMAL"\n"); + + ShowInfo("SVN Revision: '"CL_WHITE"%s"CL_RESET"'.\n", get_svn_revision()); +} + +// Warning if executed as superuser (root) +void usercheck(void) +{ +#ifndef _WIN32 + if (geteuid() == 0) { + ShowWarning ("You are running rAthena with root privileges, it is not necessary.\n"); + } +#endif +} + +/*====================================== + * CORE : MAINROUTINE + *--------------------------------------*/ +int main (int argc, char **argv) +{ + {// initialize program arguments + char *p1 = SERVER_NAME = argv[0]; + char *p2 = p1; + while ((p1 = strchr(p2, '/')) != NULL || (p1 = strchr(p2, '\\')) != NULL) + { + SERVER_NAME = ++p1; + p2 = p1; + } + arg_c = argc; + arg_v = argv; + } + + malloc_init();// needed for Show* in display_title() [FlavioJS] + +#ifdef MINICORE // minimalist Core + display_title(); + usercheck(); + do_init(argc,argv); + do_final(); +#else// not MINICORE + set_server_type(); + display_title(); + usercheck(); + + rathread_init(); + mempool_init(); + db_init(); + signals_init(); + +#ifdef _WIN32 + cevents_init(); +#endif + + timer_init(); + socket_init(); + + do_init(argc,argv); + + {// Main runtime cycle + int next; + while (runflag != CORE_ST_STOP) { + next = do_timer(gettick_nocache()); + do_sockets(next); + } + } + + do_final(); + + timer_final(); + socket_final(); + db_final(); + mempool_final(); + rathread_final(); +#endif + + malloc_final(); + + return 0; +} diff --git a/src/common/core.h b/src/common/core.h new file mode 100644 index 000000000..d48962c94 --- /dev/null +++ b/src/common/core.h @@ -0,0 +1,47 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CORE_H_ +#define _CORE_H_ + +extern int arg_c; +extern char **arg_v; + +#if defined(BUILDBOT) + extern int buildbotflag; +#endif + +/// @see E_CORE_ST +extern int runflag; +extern char *SERVER_NAME; + +enum { + ATHENA_SERVER_NONE = 0, // not defined + ATHENA_SERVER_LOGIN = 1, // login server + ATHENA_SERVER_CHAR = 2, // char server + ATHENA_SERVER_INTER = 4, // inter server + ATHENA_SERVER_MAP = 8, // map server +}; + +extern char SERVER_TYPE; + +extern int parse_console(const char* buf); +extern const char *get_svn_revision(void); +extern int do_init(int,char**); +extern void set_server_type(void); +extern void do_abort(void); +extern void do_final(void); + +/// The main loop continues until runflag is CORE_ST_STOP +enum E_CORE_ST +{ + CORE_ST_STOP = 0, + CORE_ST_RUN, + CORE_ST_LAST +}; + +/// Called when a terminate signal is received. (Ctrl+C pressed) +/// If NULL, runflag is set to CORE_ST_STOP instead. +extern void (*shutdown_callback)(void); + +#endif /* _CORE_H_ */ diff --git a/src/common/db.c b/src/common/db.c new file mode 100644 index 000000000..204c6d2ea --- /dev/null +++ b/src/common/db.c @@ -0,0 +1,2822 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL + * For more information, see LICENCE in the main folder + * + * This file is separated in five sections: + * (1) Private typedefs, enums, structures, defines and gblobal variables + * (2) Private functions + * (3) Protected functions used internally + * (4) Protected functions used in the interface of the database + * (5) Public functions + * + * The databases are structured as a hashtable of RED-BLACK trees. + * + * <B>Properties of the RED-BLACK trees being used:</B> + * 1. The value of any node is greater than the value of its left child and + * less than the value of its right child. + * 2. Every node is colored either RED or BLACK. + * 3. Every red node that is not a leaf has only black children. + * 4. Every path from the root to a leaf contains the same number of black + * nodes. + * 5. The root node is black. + * An <code>n</code> node in a RED-BLACK tree has the property that its + * height is <code>O(lg(n))</code>. + * Another important property is that after adding a node to a RED-BLACK + * tree, the tree can be readjusted in <code>O(lg(n))</code> time. + * Similarly, after deleting a node from a RED-BLACK tree, the tree can be + * readjusted in <code>O(lg(n))</code> time. + * {@link http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic18/} + * + * <B>How to add new database types:</B> + * 1. Add the identifier of the new database type to the enum DBType + * 2. If not already there, add the data type of the key to the union DBKey + * 3. If the key can be considered NULL, update the function db_is_key_null + * 4. If the key can be duplicated, update the functions db_dup_key and + * db_dup_key_free + * 5. Create a comparator and update the function db_default_cmp + * 6. Create a hasher and update the function db_default_hash + * 7. If the new database type requires or does not support some options, + * update the function db_fix_options + * + * TODO: + * - create test cases to test the database system thoroughly + * - finish this header describing the database system + * - create custom database allocator + * - make the system thread friendly + * - change the structure of the database to T-Trees + * - create a db that organizes itself by splaying + * + * HISTORY: + * 2012/03/09 - Added enum for data types (int, uint, void*) + * 2008/02/19 - Fixed db_obj_get not handling deleted entries correctly. + * 2007/11/09 - Added an iterator to the database. + * 2006/12/21 - Added 1-node cache to the database. + * 2.1 (Athena build #???#) - Portability fix + * - Fixed the portability of casting to union and added the functions + * ensure and clear to the database. + * 2.0 (Athena build 4859) - Transition version + * - Almost everything recoded with a strategy similar to objects, + * database structure is maintained. + * 1.0 (up to Athena build 4706) + * - Previous database system. + * + * @version 2006/12/21 + * @author Athena Dev team + * @encoding US-ASCII + * @see #db.h +\*****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "db.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.h" +#include "../common/strlib.h" + +/*****************************************************************************\ + * (1) Private typedefs, enums, structures, defines and global variables of * + * the database system. * + * DB_ENABLE_STATS - Define to enable database statistics. * + * HASH_SIZE - Define with the size of the hashtable. * + * DBNColor - Enumeration of colors of the nodes. * + * DBNode - Structure of a node in RED-BLACK trees. * + * struct db_free - Structure that holds a deleted node to be freed. * + * DBMap_impl - Struture of the database. * + * stats - Statistics about the database system. * +\*****************************************************************************/ + +/** + * If defined statistics about database nodes, database creating/destruction + * and function usage are keept and displayed when finalizing the database + * system. + * WARNING: This adds overhead to every database operation (not shure how much). + * @private + * @see #DBStats + * @see #stats + * @see #db_final(void) + */ +//#define DB_ENABLE_STATS + +/** + * Size of the hashtable in the database. + * @private + * @see DBMap_impl#ht + */ +#define HASH_SIZE (256+27) + +/** + * The color of individual nodes. + * @private + * @see struct dbn + */ +typedef enum node_color { + RED, + BLACK +} node_color; + +/** + * A node in a RED-BLACK tree of the database. + * @param parent Parent node + * @param left Left child node + * @param right Right child node + * @param key Key of this database entry + * @param data Data of this database entry + * @param deleted If the node is deleted + * @param color Color of the node + * @private + * @see DBMap_impl#ht + */ +typedef struct dbn { + // Tree structure + struct dbn *parent; + struct dbn *left; + struct dbn *right; + // Node data + DBKey key; + DBData data; + // Other + node_color color; + unsigned deleted : 1; +} *DBNode; + +/** + * Structure that holds a deleted node. + * @param node Deleted node + * @param root Address to the root of the tree + * @private + * @see DBMap_impl#free_list + */ +struct db_free { + DBNode node; + DBNode *root; +}; + +/** + * Complete database structure. + * @param vtable Interface of the database + * @param alloc_file File where the database was allocated + * @param alloc_line Line in the file where the database was allocated + * @param free_list Array of deleted nodes to be freed + * @param free_count Number of deleted nodes in free_list + * @param free_max Current maximum capacity of free_list + * @param free_lock Lock for freeing the nodes + * @param nodes Manager of reusable tree nodes + * @param cmp Comparator of the database + * @param hash Hasher of the database + * @param release Releaser of the database + * @param ht Hashtable of RED-BLACK trees + * @param type Type of the database + * @param options Options of the database + * @param item_count Number of items in the database + * @param maxlen Maximum length of strings in DB_STRING and DB_ISTRING databases + * @param global_lock Global lock of the database + * @private + * @see #db_alloc(const char*,int,DBType,DBOptions,unsigned short) + */ +typedef struct DBMap_impl { + // Database interface + struct DBMap vtable; + // File and line of allocation + const char *alloc_file; + int alloc_line; + // Lock system + struct db_free *free_list; + unsigned int free_count; + unsigned int free_max; + unsigned int free_lock; + // Other + ERS nodes; + DBComparator cmp; + DBHasher hash; + DBReleaser release; + DBNode ht[HASH_SIZE]; + DBNode cache; + DBType type; + DBOptions options; + uint32 item_count; + unsigned short maxlen; + unsigned global_lock : 1; +} DBMap_impl; + +/** + * Complete iterator structure. + * @param vtable Interface of the iterator + * @param db Parent database + * @param ht_index Current index of the hashtable + * @param node Current node + * @private + * @see #DBIterator + * @see #DBMap_impl + * @see #DBNode + */ +typedef struct DBIterator_impl { + // Iterator interface + struct DBIterator vtable; + DBMap_impl* db; + int ht_index; + DBNode node; +} DBIterator_impl; + +#if defined(DB_ENABLE_STATS) +/** + * Structure with what is counted when the database statistics are enabled. + * @private + * @see #DB_ENABLE_STATS + * @see #stats + */ +static struct db_stats { + // Node alloc/free + uint32 db_node_alloc; + uint32 db_node_free; + // Database creating/destruction counters + uint32 db_int_alloc; + uint32 db_uint_alloc; + uint32 db_string_alloc; + uint32 db_istring_alloc; + uint32 db_int_destroy; + uint32 db_uint_destroy; + uint32 db_string_destroy; + uint32 db_istring_destroy; + // Function usage counters + uint32 db_rotate_left; + uint32 db_rotate_right; + uint32 db_rebalance; + uint32 db_rebalance_erase; + uint32 db_is_key_null; + uint32 db_dup_key; + uint32 db_dup_key_free; + uint32 db_free_add; + uint32 db_free_remove; + uint32 db_free_lock; + uint32 db_free_unlock; + uint32 db_int_cmp; + uint32 db_uint_cmp; + uint32 db_string_cmp; + uint32 db_istring_cmp; + uint32 db_int_hash; + uint32 db_uint_hash; + uint32 db_string_hash; + uint32 db_istring_hash; + uint32 db_release_nothing; + uint32 db_release_key; + uint32 db_release_data; + uint32 db_release_both; + uint32 dbit_first; + uint32 dbit_last; + uint32 dbit_next; + uint32 dbit_prev; + uint32 dbit_exists; + uint32 dbit_remove; + uint32 dbit_destroy; + uint32 db_iterator; + uint32 db_exists; + uint32 db_get; + uint32 db_getall; + uint32 db_vgetall; + uint32 db_ensure; + uint32 db_vensure; + uint32 db_put; + uint32 db_remove; + uint32 db_foreach; + uint32 db_vforeach; + uint32 db_clear; + uint32 db_vclear; + uint32 db_destroy; + uint32 db_vdestroy; + uint32 db_size; + uint32 db_type; + uint32 db_options; + uint32 db_fix_options; + uint32 db_default_cmp; + uint32 db_default_hash; + uint32 db_default_release; + uint32 db_custom_release; + uint32 db_alloc; + uint32 db_i2key; + uint32 db_ui2key; + uint32 db_str2key; + uint32 db_i2data; + uint32 db_ui2data; + uint32 db_ptr2data; + uint32 db_data2i; + uint32 db_data2ui; + uint32 db_data2ptr; + uint32 db_init; + uint32 db_final; +} stats = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; +#define DB_COUNTSTAT(token) if (stats. ## token != UINT32_MAX) ++stats. ## token +#else /* !defined(DB_ENABLE_STATS) */ +#define DB_COUNTSTAT(token) +#endif /* !defined(DB_ENABLE_STATS) */ + +/*****************************************************************************\ + * (2) Section of private functions used by the database system. * + * db_rotate_left - Rotate a tree node to the left. * + * db_rotate_right - Rotate a tree node to the right. * + * db_rebalance - Rebalance the tree. * + * db_rebalance_erase - Rebalance the tree after a BLACK node was erased. * + * db_is_key_null - Returns not 0 if the key is considered NULL. * + * db_dup_key - Duplicate a key for internal use. * + * db_dup_key_free - Free the duplicated key. * + * db_free_add - Add a node to the free_list of a database. * + * db_free_remove - Remove a node from the free_list of a database. * + * db_free_lock - Increment the free_lock of a database. * + * db_free_unlock - Decrement the free_lock of a database. * + * If it was the last lock, frees the nodes in free_list. * + * NOTE: Keeps the database trees balanced. * +\*****************************************************************************/ + +/** + * Rotate a node to the left. + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_left(DBNode node, DBNode *root) +{ + DBNode y = node->right; + + DB_COUNTSTAT(db_rotate_left); + // put the left of y at the right of node + node->right = y->left; + if (y->left) + y->left->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->left) { + node->parent->left = y; // node was at the left + } else { + node->parent->right = y; // node was at the right + } + // put node at the left of y + y->left = node; + node->parent = y; +} + +/** + * Rotate a node to the right + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_right(DBNode node, DBNode *root) +{ + DBNode y = node->left; + + DB_COUNTSTAT(db_rotate_right); + // put the right of y at the left of node + node->left = y->right; + if (y->right != 0) + y->right->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->right) { + node->parent->right = y; // node was at the right + } else { + node->parent->left = y; // node was at the left + } + // put node at the right of y + y->right = node; + node->parent = y; +} + +/** + * Rebalance the RED-BLACK tree. + * Called when the node and it's parent are both RED. + * @param node Node to be rebalanced + * @param root Pointer to the root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_obj_put(DBMap*,DBKey,DBData) + */ +static void db_rebalance(DBNode node, DBNode *root) +{ + DBNode y; + + DB_COUNTSTAT(db_rebalance); + // Restore the RED-BLACK properties + node->color = RED; + while (node != *root && node->parent->color == RED) { + if (node->parent == node->parent->parent->left) { + // If node's parent is a left, y is node's right 'uncle' + y = node->parent->parent->right; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->right) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_left(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_right(node->parent->parent, root); + } + } else { + // If node's parent is a right, y is node's left 'uncle' + y = node->parent->parent->left; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->left) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_right(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_left(node->parent->parent, root); + } + } + } + (*root)->color = BLACK; // the root can and should always be black +} + +/** + * Erase a node from the RED-BLACK tree, keeping the tree balanced. + * @param node Node to be erased from the tree + * @param root Root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_free_unlock(DBMap_impl*) + */ +static void db_rebalance_erase(DBNode node, DBNode *root) +{ + DBNode y = node; + DBNode x = NULL; + DBNode x_parent = NULL; + DBNode w; + + DB_COUNTSTAT(db_rebalance_erase); + // Select where to change the tree + if (y->left == NULL) { // no left + x = y->right; + } else if (y->right == NULL) { // no right + x = y->left; + } else { // both exist, go to the leftmost node of the right sub-tree + y = y->right; + while (y->left != NULL) + y = y->left; + x = y->right; + } + + // Remove the node from the tree + if (y != node) { // both childs existed + // put the left of 'node' in the left of 'y' + node->left->parent = y; + y->left = node->left; + + // 'y' is not the direct child of 'node' + if (y != node->right) { + // put 'x' in the old position of 'y' + x_parent = y->parent; + if (x) x->parent = y->parent; + y->parent->left = x; + // put the right of 'node' in 'y' + y->right = node->right; + node->right->parent = y; + // 'y' is a direct child of 'node' + } else { + x_parent = y; + } + + // link 'y' and the parent of 'node' + if (*root == node) { + *root = y; // 'node' was the root + } else if (node->parent->left == node) { + node->parent->left = y; // 'node' was at the left + } else { + node->parent->right = y; // 'node' was at the right + } + y->parent = node->parent; + // switch colors + { + node_color tmp = y->color; + y->color = node->color; + node->color = tmp; + } + y = node; + } else { // one child did not exist + // put x in node's position + x_parent = y->parent; + if (x) x->parent = y->parent; + // link x and node's parent + if (*root == node) { + *root = x; // node was the root + } else if (node->parent->left == node) { + node->parent->left = x; // node was at the left + } else { + node->parent->right = x; // node was at the right + } + } + + // Restore the RED-BLACK properties + if (y->color != RED) { + while (x != *root && (x == NULL || x->color == BLACK)) { + if (x == x_parent->left) { + w = x_parent->right; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_left(x_parent, root); + w = x_parent->right; + } + if ((w->left == NULL || w->left->color == BLACK) && + (w->right == NULL || w->right->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->right == NULL || w->right->color == BLACK) { + if (w->left) w->left->color = BLACK; + w->color = RED; + db_rotate_right(w, root); + w = x_parent->right; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->right) w->right->color = BLACK; + db_rotate_left(x_parent, root); + break; + } + } else { + w = x_parent->left; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_right(x_parent, root); + w = x_parent->left; + } + if ((w->right == NULL || w->right->color == BLACK) && + (w->left == NULL || w->left->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->left == NULL || w->left->color == BLACK) { + if (w->right) w->right->color = BLACK; + w->color = RED; + db_rotate_left(w, root); + w = x_parent->left; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->left) w->left->color = BLACK; + db_rotate_right(x_parent, root); + break; + } + } + } + if (x) x->color = BLACK; + } +} + +/** + * Returns not 0 if the key is considered to be NULL. + * @param type Type of database + * @param key Key being tested + * @return not 0 if considered NULL, 0 otherwise + * @private + * @see #db_obj_get(DBMap*,DBKey) + * @see #db_obj_put(DBMap*,DBKey,DBData) + * @see #db_obj_remove(DBMap*,DBKey) + */ +static int db_is_key_null(DBType type, DBKey key) +{ + DB_COUNTSTAT(db_is_key_null); + switch (type) { + case DB_STRING: + case DB_ISTRING: + return (key.str == NULL); + + default: // Not a pointer + return 0; + } +} + +/** + * Duplicate the key used in the database. + * @param db Database the key is being used in + * @param key Key to be duplicated + * @param Duplicated key + * @private + * @see #db_free_add(DBMap_impl*,DBNode,DBNode *) + * @see #db_free_remove(DBMap_impl*,DBNode) + * @see #db_obj_put(DBMap*,DBKey,void *) + * @see #db_dup_key_free(DBMap_impl*,DBKey) + */ +static DBKey db_dup_key(DBMap_impl* db, DBKey key) +{ + char *str; + size_t len; + + DB_COUNTSTAT(db_dup_key); + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + len = strnlen(key.str, db->maxlen); + str = (char*)aMalloc(len + 1); + memcpy(str, key.str, len); + str[len] = '\0'; + key.str = str; + return key; + + default: + return key; + } +} + +/** + * Free a key duplicated by db_dup_key. + * @param db Database the key is being used in + * @param key Key to be freed + * @private + * @see #db_dup_key(DBMap_impl*,DBKey) + */ +static void db_dup_key_free(DBMap_impl* db, DBKey key) +{ + DB_COUNTSTAT(db_dup_key_free); + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + aFree((char*)key.str); + return; + + default: + return; + } +} + +/** + * Add a node to the free_list of the database. + * Marks the node as deleted. + * If the key isn't duplicated, the key is duplicated and released. + * @param db Target database + * @param root Root of the tree from the node + * @param node Target node + * @private + * @see #struct db_free + * @see DBMap_impl#free_list + * @see DBMap_impl#free_count + * @see DBMap_impl#free_max + * @see #db_obj_remove(DBMap*,DBKey) + * @see #db_free_remove(DBMap_impl*,DBNode) + */ +static void db_free_add(DBMap_impl* db, DBNode node, DBNode *root) +{ + DBKey old_key; + + DB_COUNTSTAT(db_free_add); + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_add: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + if (!(db->options&DB_OPT_DUP_KEY)) { // Make sure we have a key until the node is freed + old_key = node->key; + node->key = db_dup_key(db, node->key); + db->release(old_key, node->data, DB_RELEASE_KEY); + } + if (db->free_count == db->free_max) { // No more space, expand free_list + db->free_max = (db->free_max<<2) +3; // = db->free_max*4 +3 + if (db->free_max <= db->free_count) { + if (db->free_count == (unsigned int)~0) { + ShowFatalError("db_free_add: free_count overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_max = (unsigned int)~0; + } + RECREATE(db->free_list, struct db_free, db->free_max); + } + node->deleted = 1; + db->free_list[db->free_count].node = node; + db->free_list[db->free_count].root = root; + db->free_count++; + db->item_count--; +} + +/** + * Remove a node from the free_list of the database. + * Marks the node as not deleted. + * NOTE: Frees the duplicated key of the node. + * @param db Target database + * @param node Node being removed from free_list + * @private + * @see #struct db_free + * @see DBMap_impl#free_list + * @see DBMap_impl#free_count + * @see #db_obj_put(DBMap*,DBKey,DBData) + * @see #db_free_add(DBMap_impl*,DBNode*,DBNode) + */ +static void db_free_remove(DBMap_impl* db, DBNode node) +{ + unsigned int i; + + DB_COUNTSTAT(db_free_remove); + for (i = 0; i < db->free_count; i++) { + if (db->free_list[i].node == node) { + if (i < db->free_count -1) // copy the last item to where the removed one was + memcpy(&db->free_list[i], &db->free_list[db->free_count -1], sizeof(struct db_free)); + db_dup_key_free(db, node->key); + break; + } + } + node->deleted = 0; + if (i == db->free_count) { + ShowWarning("db_free_remove: node was not found - database allocated at %s:%d\n", db->alloc_file, db->alloc_line); + } else { + db->free_count--; + } + db->item_count++; +} + +/** + * Increment the free_lock of the database. + * @param db Target database + * @private + * @see DBMap_impl#free_lock + * @see #db_unlock(DBMap_impl*) + */ +static void db_free_lock(DBMap_impl* db) +{ + DB_COUNTSTAT(db_free_lock); + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_lock: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_lock++; +} + +/** + * Decrement the free_lock of the database. + * If it was the last lock, frees the nodes of the database. + * Keeps the tree balanced. + * NOTE: Frees the duplicated keys of the nodes + * @param db Target database + * @private + * @see DBMap_impl#free_lock + * @see #db_free_dbn(DBNode) + * @see #db_lock(DBMap_impl*) + */ +static void db_free_unlock(DBMap_impl* db) +{ + unsigned int i; + + DB_COUNTSTAT(db_free_unlock); + if (db->free_lock == 0) { + ShowWarning("db_free_unlock: free_lock was already 0\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + } else { + db->free_lock--; + } + if (db->free_lock) + return; // Not last lock + + for (i = 0; i < db->free_count ; i++) { + db_rebalance_erase(db->free_list[i].node, db->free_list[i].root); + db_dup_key_free(db, db->free_list[i].node->key); + DB_COUNTSTAT(db_node_free); + ers_free(db->nodes, db->free_list[i].node); + } + db->free_count = 0; +} + +/*****************************************************************************\ + * (3) Section of protected functions used internally. * + * NOTE: the protected functions used in the database interface are in the * + * next section. * + * db_int_cmp - Default comparator for DB_INT databases. * + * db_uint_cmp - Default comparator for DB_UINT databases. * + * db_string_cmp - Default comparator for DB_STRING databases. * + * db_istring_cmp - Default comparator for DB_ISTRING databases. * + * db_int_hash - Default hasher for DB_INT databases. * + * db_uint_hash - Default hasher for DB_UINT databases. * + * db_string_hash - Default hasher for DB_STRING databases. * + * db_istring_hash - Default hasher for DB_ISTRING databases. * + * db_release_nothing - Releaser that releases nothing. * + * db_release_key - Releaser that only releases the key. * + * db_release_data - Releaser that only releases the data. * + * db_release_both - Releaser that releases key and data. * +\*****************************************************************************/ + +/** + * Default comparator for DB_INT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * <code>maxlen</code> is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_INT + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_int_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_int_cmp); + if (key1.i < key2.i) return -1; + if (key1.i > key2.i) return 1; + return 0; +} + +/** + * Default comparator for DB_UINT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * <code>maxlen</code> is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_UINT + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_uint_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_uint_cmp); + if (key1.ui < key2.ui) return -1; + if (key1.ui > key2.ui) return 1; + return 0; +} + +/** + * Default comparator for DB_STRING databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_STRING + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_string_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + DB_COUNTSTAT(db_string_cmp); + return strncmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default comparator for DB_ISTRING databases. + * Compares key1 to key2 case insensitively. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see DBType#DB_ISTRING + * @see #DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ + DB_COUNTSTAT(db_istring_cmp); + return strncasecmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default hasher for DB_INT databases. + * Returns the value of the key as an unsigned int. + * <code>maxlen</code> is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_INT + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_int_hash(DBKey key, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_int_hash); + return (unsigned int)key.i; +} + +/** + * Default hasher for DB_UINT databases. + * Just returns the value of the key. + * <code>maxlen</code> is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_UINT + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_uint_hash(DBKey key, unsigned short maxlen) +{ + (void)maxlen;//not used + DB_COUNTSTAT(db_uint_hash); + return key.ui; +} + +/** + * Default hasher for DB_STRING databases. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_STRING + * @see #DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_string_hash(DBKey key, unsigned short maxlen) +{ + const char *k = key.str; + unsigned int hash = 0; + unsigned short i; + + DB_COUNTSTAT(db_string_hash); + + for (i = 0; *k; ++i) { + hash = (hash*33 + ((unsigned char)*k))^(hash>>24); + k++; + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Default hasher for DB_ISTRING databases. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see DBType#DB_ISTRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_istring_hash(DBKey key, unsigned short maxlen) +{ + const char *k = key.str; + unsigned int hash = 0; + unsigned short i; + + DB_COUNTSTAT(db_istring_hash); + + for (i = 0; *k; i++) { + hash = (hash*33 + ((unsigned char)TOLOWER(*k)))^(hash>>24); + k++; + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Releaser that releases nothing. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBReleaser + * @see #db_default_releaser(DBType,DBOptions) + */ +static void db_release_nothing(DBKey key, DBData data, DBRelease which) +{ + (void)key;(void)data;(void)which;//not used + DB_COUNTSTAT(db_release_nothing); +} + +/** + * Releaser that only releases the key. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_key(DBKey key, DBData data, DBRelease which) +{ + (void)data;//not used + DB_COUNTSTAT(db_release_key); + if (which&DB_RELEASE_KEY) aFree((char*)key.str); // needs to be a pointer +} + +/** + * Releaser that only releases the data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBData + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_data(DBKey key, DBData data, DBRelease which) +{ + (void)key;//not used + DB_COUNTSTAT(db_release_data); + if (which&DB_RELEASE_DATA && data.type == DB_DATA_PTR) aFree(data.u.ptr); +} + +/** + * Releaser that releases both key and data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see #DBKey + * @see #DBData + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_both(DBKey key, DBData data, DBRelease which) +{ + DB_COUNTSTAT(db_release_both); + if (which&DB_RELEASE_KEY) aFree((char*)key.str); // needs to be a pointer + if (which&DB_RELEASE_DATA && data.type == DB_DATA_PTR) aFree(data.u.ptr); +} + +/*****************************************************************************\ + * (4) Section with protected functions used in the interface of the * + * database and interface of the iterator. * + * dbit_obj_first - Fetches the first entry from the database. * + * dbit_obj_last - Fetches the last entry from the database. * + * dbit_obj_next - Fetches the next entry from the database. * + * dbit_obj_prev - Fetches the previous entry from the database. * + * dbit_obj_exists - Returns true if the current entry exists. * + * dbit_obj_remove - Remove the current entry from the database. * + * dbit_obj_destroy - Destroys the iterator, unlocking the database and * + * freeing used memory. * + * db_obj_iterator - Return a new database iterator. * + * db_obj_exists - Checks if an entry exists. * + * db_obj_get - Get the data identified by the key. * + * db_obj_vgetall - Get the data of the matched entries. * + * db_obj_getall - Get the data of the matched entries. * + * db_obj_vensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_ensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_put - Put data identified by the key in the database. * + * db_obj_remove - Remove an entry from the database. * + * db_obj_vforeach - Apply a function to every entry in the database. * + * db_obj_foreach - Apply a function to every entry in the database. * + * db_obj_vclear - Remove all entries from the database. * + * db_obj_clear - Remove all entries from the database. * + * db_obj_vdestroy - Destroy the database, freeing all the used memory. * + * db_obj_destroy - Destroy the database, freeing all the used memory. * + * db_obj_size - Return the size of the database. * + * db_obj_type - Return the type of the database. * + * db_obj_options - Return the options of the database. * +\*****************************************************************************/ + +/** + * Fetches the first entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#first + */ +DBData* dbit_obj_first(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_first); + // position before the first entry + it->ht_index = -1; + it->node = NULL; + // get next entry + return self->next(self, out_key); +} + +/** + * Fetches the last entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#last + */ +DBData* dbit_obj_last(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_last); + // position after the last entry + it->ht_index = HASH_SIZE; + it->node = NULL; + // get previous entry + return self->prev(self, out_key); +} + +/** + * Fetches the next entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#next + */ +DBData* dbit_obj_next(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + DBNode parent; + struct dbn fake; + + DB_COUNTSTAT(dbit_next); + if( it->ht_index < 0 ) + {// get first node + it->ht_index = 0; + it->node = NULL; + } + node = it->node; + memset(&fake, 0, sizeof(fake)); + for( ; it->ht_index < HASH_SIZE; ++(it->ht_index) ) + { + // Iterate in the order: left tree, current node, right tree + if( node == NULL ) + {// prepare initial node of this hash + node = it->db->ht[it->ht_index]; + if( node == NULL ) + continue;// next hash + fake.right = node; + node = &fake; + } + + while( node ) + {// next node + if( node->right ) + {// continue in the right subtree + node = node->right; + while( node->left ) + node = node->left;// get leftmost node + } + else + {// continue to the next parent (recursive) + parent = node->parent; + while( parent ) + { + if( parent->right != node ) + break; + node = parent; + parent = node->parent; + } + if( parent == NULL ) + {// next hash + node = NULL; + break; + } + node = parent; + } + + if( !node->deleted ) + {// found next entry + it->node = node; + if( out_key ) + memcpy(out_key, &node->key, sizeof(DBKey)); + return &node->data; + } + } + } + it->node = NULL; + return NULL;// not found +} + +/** + * Fetches the previous entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + * @see DBIterator#prev + */ +DBData* dbit_obj_prev(DBIterator* self, DBKey* out_key) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + DBNode parent; + struct dbn fake; + + DB_COUNTSTAT(dbit_prev); + if( it->ht_index >= HASH_SIZE ) + {// get last node + it->ht_index = HASH_SIZE-1; + it->node = NULL; + } + node = it->node; + memset(&fake, 0, sizeof(fake)); + for( ; it->ht_index >= 0; --(it->ht_index) ) + { + // Iterate in the order: right tree, current node, left tree + if( node == NULL ) + {// prepare initial node of this hash + node = it->db->ht[it->ht_index]; + if( node == NULL ) + continue;// next hash + fake.left = node; + node = &fake; + } + + + while( node ) + {// next node + if( node->left ) + {// continue in the left subtree + node = node->left; + while( node->right ) + node = node->right;// get rightmost node + } + else + {// continue to the next parent (recursive) + parent = node->parent; + while( parent ) + { + if( parent->left != node ) + break; + node = parent; + parent = node->parent; + } + if( parent == NULL ) + {// next hash + node = NULL; + break; + } + node = parent; + } + + if( !node->deleted ) + {// found previous entry + it->node = node; + if( out_key ) + memcpy(out_key, &node->key, sizeof(DBKey)); + return &node->data; + } + } + } + it->node = NULL; + return NULL;// not found +} + +/** + * Returns true if the fetched entry exists. + * The databases entries might have NULL data, so use this to to test if + * the iterator is done. + * @param self Iterator + * @return true if the entry exists + * @protected + * @see DBIterator#exists + */ +bool dbit_obj_exists(DBIterator* self) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_exists); + return (it->node && !it->node->deleted); +} + +/** + * Removes the current entry from the database. + * NOTE: {@link DBIterator#exists} will return false until another entry + * is fetched + * Puts data of the removed entry in out_data, if out_data is not NULL. + * @param self Iterator + * @param out_data Data of the removed entry. + * @return 1 if entry was removed, 0 otherwise + * @protected + * @see DBMap#remove + * @see DBIterator#remove + */ +int dbit_obj_remove(DBIterator* self, DBData *out_data) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + DBNode node; + int retval = 0; + + DB_COUNTSTAT(dbit_remove); + node = it->node; + if( node && !node->deleted ) + { + DBMap_impl* db = it->db; + if( db->cache == node ) + db->cache = NULL; + if( out_data ) + memcpy(out_data, &node->data, sizeof(DBData)); + retval = 1; + db->release(node->key, node->data, DB_RELEASE_DATA); + db_free_add(db, node, &db->ht[it->ht_index]); + } + return retval; +} + +/** + * Destroys this iterator and unlocks the database. + * @param self Iterator + * @protected + */ +void dbit_obj_destroy(DBIterator* self) +{ + DBIterator_impl* it = (DBIterator_impl*)self; + + DB_COUNTSTAT(dbit_destroy); + // unlock the database + db_free_unlock(it->db); + // free iterator + aFree(self); +} + +/** + * Returns a new iterator for this database. + * The iterator keeps the database locked until it is destroyed. + * The database will keep functioning normally but will only free internal + * memory when unlocked, so destroy the iterator as soon as possible. + * @param self Database + * @return New iterator + * @protected + */ +static DBIterator* db_obj_iterator(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBIterator_impl* it; + + DB_COUNTSTAT(db_iterator); + CREATE(it, struct DBIterator_impl, 1); + /* Interface of the iterator **/ + it->vtable.first = dbit_obj_first; + it->vtable.last = dbit_obj_last; + it->vtable.next = dbit_obj_next; + it->vtable.prev = dbit_obj_prev; + it->vtable.exists = dbit_obj_exists; + it->vtable.remove = dbit_obj_remove; + it->vtable.destroy = dbit_obj_destroy; + /* Initial state (before the first entry) */ + it->db = db; + it->ht_index = -1; + it->node = NULL; + /* Lock the database */ + db_free_lock(db); + return &it->vtable; +} + +/** + * Returns true if the entry exists. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return true is the entry exists + * @protected + * @see DBMap#exists + */ +static bool db_obj_exists(DBMap* self, DBKey key) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + int c; + bool found = false; + + DB_COUNTSTAT(db_exists); + if (db == NULL) return false; // nullpo candidate + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + return false; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) { +#if defined(DEBUG) + if (db->cache->deleted) { + ShowDebug("db_exists: Cache contains a deleted node. Please report this!!!\n"); + return false; + } +#endif + return true; // cache hit + } + + db_free_lock(db); + node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + db->cache = node; + found = true; + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return found; +} + +/** + * Get the data of the entry identified by the key. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + * @see DBMap#get + */ +static DBData* db_obj_get(DBMap* self, DBKey key) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + int c; + DBData *data = NULL; + + DB_COUNTSTAT(db_get); + if (db == NULL) return NULL; // nullpo candidate + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_get: Attempted to retrieve non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) { +#if defined(DEBUG) + if (db->cache->deleted) { + ShowDebug("db_get: Cache contains a deleted node. Please report this!!!\n"); + return NULL; + } +#endif + return &db->cache->data; // cache hit + } + + db_free_lock(db); + node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + data = &node->data; + db->cache = node; + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return data; +} + +/** + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall + */ +static unsigned int db_obj_vgetall(DBMap* self, DBData **buf, unsigned int max, DBMatcher match, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int i; + DBNode node; + DBNode parent; + unsigned int ret = 0; + + DB_COUNTSTAT(db_vgetall); + if (db == NULL) return 0; // nullpo candidate + if (match == NULL) return 0; // nullpo candidate + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Match in the order: current node, left tree, right tree + node = db->ht[i]; + while (node) { + + if (!(node->deleted)) { + va_list argscopy; + va_copy(argscopy, args); + if (match(node->key, node->data, argscopy) == 0) { + if (buf && ret < max) + buf[ret] = &node->data; + ret++; + } + va_end(argscopy); + } + + if (node->left) { + node = node->left; + continue; + } + + if (node->right) { + node = node->right; + continue; + } + + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + + } + } + db_free_unlock(db); + return ret; +} + +/** + * Just calls {@link DBMap#vgetall}. + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall + * @see DBMap#getall + */ +static unsigned int db_obj_getall(DBMap* self, DBData **buf, unsigned int max, DBMatcher match, ...) +{ + va_list args; + unsigned int ret; + + DB_COUNTSTAT(db_getall); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, match); + ret = self->vgetall(self, buf, max, match, args); + va_end(args); + return ret; +} + +/** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure + */ +static DBData* db_obj_vensure(DBMap* self, DBKey key, DBCreateData create, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + DBNode parent = NULL; + unsigned int hash; + int c = 0; + DBData *data = NULL; + + DB_COUNTSTAT(db_vensure); + if (db == NULL) return NULL; // nullpo candidate + if (create == NULL) { + ShowError("db_ensure: Create function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_ensure: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) + return &db->cache->data; // cache hit + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + node = db->ht[hash]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + break; + } + parent = node; + if (c < 0) + node = node->left; + else + node = node->right; + } + // Create node if necessary + if (node == NULL) { + va_list argscopy; + if (db->item_count == UINT32_MAX) { + ShowError("db_vensure: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } + DB_COUNTSTAT(db_node_alloc); + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, *data, DB_RELEASE_KEY); + } else { + node->key = key; + } + va_copy(argscopy, args); + node->data = create(key, argscopy); + va_end(argscopy); + } + data = &node->data; + db->cache = node; + db_free_unlock(db); + return data; +} + +/** + * Just calls {@link DBMap#vensure}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure + * @see DBMap#ensure + */ +static DBData* db_obj_ensure(DBMap* self, DBKey key, DBCreateData create, ...) +{ + va_list args; + DBData *ret = NULL; + + DB_COUNTSTAT(db_ensure); + if (self == NULL) return NULL; // nullpo candidate + + va_start(args, create); + ret = self->vensure(self, key, create, args); + va_end(args); + return ret; +} + +/** + * Put the data identified by the key in the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: Uses the new key, the old one is released. + * @param self Interface of the database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + * @see #db_malloc_dbn(void) + * @see DBMap#put + */ +static int db_obj_put(DBMap* self, DBKey key, DBData data, DBData *out_data) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + DBNode parent = NULL; + int c = 0, retval = 0; + unsigned int hash; + + DB_COUNTSTAT(db_put); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_put: Database is being destroyed, aborting entry insertion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_put: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_DATA) && (data.type == DB_DATA_PTR && data.u.ptr == NULL)) { + ShowError("db_put: Attempted to use non-allowed NULL data for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + if (db->item_count == UINT32_MAX) { + ShowError("db_put: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return 0; + } + // search for an equal node + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for (node = db->ht[hash]; node; ) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { // equal entry, replace + if (node->deleted) { + db_free_remove(db, node); + } else { + db->release(node->key, node->data, DB_RELEASE_BOTH); + if (out_data) + memcpy(out_data, &node->data, sizeof(*out_data)); + retval = 1; + } + break; + } + parent = node; + if (c < 0) { + node = node->left; + } else { + node = node->right; + } + } + // allocate a new node if necessary + if (node == NULL) { + DB_COUNTSTAT(db_node_alloc); + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, data, DB_RELEASE_KEY); + } else { + node->key = key; + } + node->data = data; + db->cache = node; + db_free_unlock(db); + return retval; +} + +/** + * Remove an entry from the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: The key (of the database) is released in {@link #db_free_add(DBMap_impl*,DBNode,DBNode *)}. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + * @see #db_free_add(DBMap_impl*,DBNode,DBNode *) + * @see DBMap#remove + */ +static int db_obj_remove(DBMap* self, DBKey key, DBData *out_data) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBNode node; + unsigned int hash; + int c = 0, retval = 0; + + DB_COUNTSTAT(db_remove); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_remove: Database is being destroyed. Aborting entry deletion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_remove: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for(node = db->ht[hash]; node; ){ + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + if (db->cache == node) + db->cache = NULL; + if (out_data) + memcpy(out_data, &node->data, sizeof(*out_data)); + retval = 1; + db->release(node->key, node->data, DB_RELEASE_DATA); + db_free_add(db, node, &db->ht[hash]); + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return retval; +} + +/** + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applied + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach + */ +static int db_obj_vforeach(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int i; + int sum = 0; + DBNode node; + DBNode parent; + + DB_COUNTSTAT(db_vforeach); + if (db == NULL) return 0; // nullpo candidate + if (func == NULL) { + ShowError("db_foreach: Passed function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Apply func in the order: current node, left node, right node + node = db->ht[i]; + while (node) { + if (!(node->deleted)) { + va_list argscopy; + va_copy(argscopy, args); + sum += func(node->key, &node->data, argscopy); + va_end(argscopy); + } + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + } + } + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link DBMap#vforeach}. + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applyed + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach + * @see DBMap#foreach + */ +static int db_obj_foreach(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_foreach); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vforeach(self, func, args); + va_end(args); + return ret; +} + +/** + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear + */ +static int db_obj_vclear(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + int sum = 0; + unsigned int i; + DBNode node; + DBNode parent; + + DB_COUNTSTAT(db_vclear); + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + db->cache = NULL; + for (i = 0; i < HASH_SIZE; i++) { + // Apply the func and delete in the order: left tree, right tree, current node + node = db->ht[i]; + db->ht[i] = NULL; + while (node) { + parent = node->parent; + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + if (node->deleted) { + db_dup_key_free(db, node->key); + } else { + if (func) + { + va_list argscopy; + va_copy(argscopy, args); + sum += func(node->key, &node->data, argscopy); + va_end(argscopy); + } + db->release(node->key, node->data, DB_RELEASE_BOTH); + node->deleted = 1; + } + DB_COUNTSTAT(db_node_free); + if (parent) { + if (parent->left == node) + parent->left = NULL; + else + parent->right = NULL; + } + ers_free(db->nodes, node); + node = parent; + } + db->ht[i] = NULL; + } + db->free_count = 0; + db->item_count = 0; + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link DBMap#vclear}. + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear + * @see DBMap#clear + */ +static int db_obj_clear(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_clear); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vclear(self, func, args); + va_end(args); + return ret; +} + +/** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy + */ +static int db_obj_vdestroy(DBMap* self, DBApply func, va_list args) +{ + DBMap_impl* db = (DBMap_impl*)self; + int sum; + + DB_COUNTSTAT(db_vdestroy); + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_vdestroy: Database is already locked for destruction. Aborting second database destruction.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; + } + if (db->free_lock) + ShowWarning("db_vdestroy: Database is still in use, %u lock(s) left. Continuing database destruction.\n" + "Database allocated at %s:%d\n", + db->free_lock, db->alloc_file, db->alloc_line); + +#ifdef DB_ENABLE_STATS + switch (db->type) { + case DB_INT: DB_COUNTSTAT(db_int_destroy); break; + case DB_UINT: DB_COUNTSTAT(db_uint_destroy); break; + case DB_STRING: DB_COUNTSTAT(db_string_destroy); break; + case DB_ISTRING: DB_COUNTSTAT(db_istring_destroy); break; + } +#endif /* DB_ENABLE_STATS */ + db_free_lock(db); + db->global_lock = 1; + sum = self->vclear(self, func, args); + aFree(db->free_list); + db->free_list = NULL; + db->free_max = 0; + ers_destroy(db->nodes); + db_free_unlock(db); + aFree(db); + return sum; +} + +/** + * Just calls {@link DBMap#db_vdestroy}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy + * @see DBMap#destroy + */ +static int db_obj_destroy(DBMap* self, DBApply func, ...) +{ + va_list args; + int ret; + + DB_COUNTSTAT(db_destroy); + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vdestroy(self, func, args); + va_end(args); + return ret; +} + +/** + * Return the size of the database (number of items in the database). + * @param self Interface of the database + * @return Size of the database + * @protected + * @see DBMap_impl#item_count + * @see DBMap#size + */ +static unsigned int db_obj_size(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + unsigned int item_count; + + DB_COUNTSTAT(db_size); + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + item_count = db->item_count; + db_free_unlock(db); + + return item_count; +} + +/** + * Return the type of database. + * @param self Interface of the database + * @return Type of the database + * @protected + * @see DBMap_impl#type + * @see DBMap#type + */ +static DBType db_obj_type(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBType type; + + DB_COUNTSTAT(db_type); + if (db == NULL) return (DBType)-1; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + type = db->type; + db_free_unlock(db); + + return type; +} + +/** + * Return the options of the database. + * @param self Interface of the database + * @return Options of the database + * @protected + * @see DBMap_impl#options + * @see DBMap#options + */ +static DBOptions db_obj_options(DBMap* self) +{ + DBMap_impl* db = (DBMap_impl*)self; + DBOptions options; + + DB_COUNTSTAT(db_options); + if (db == NULL) return DB_OPT_BASE; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + options = db->options; + db_free_unlock(db); + + return options; +} + +/*****************************************************************************\ + * (5) Section with public functions. + * db_fix_options - Apply database type restrictions to the options. + * db_default_cmp - Get the default comparator for a type of database. + * db_default_hash - Get the default hasher for a type of database. + * db_default_release - Get the default releaser for a type of database with the specified options. + * db_custom_release - Get a releaser that behaves a certains way. + * db_alloc - Allocate a new database. + * db_i2key - Manual cast from 'int' to 'DBKey'. + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. + * db_i2data - Manual cast from 'int' to 'DBData'. + * db_ui2data - Manual cast from 'unsigned int' to 'DBData'. + * db_ptr2data - Manual cast from 'void*' to 'DBData'. + * db_data2i - Gets 'int' value from 'DBData'. + * db_data2ui - Gets 'unsigned int' value from 'DBData'. + * db_data2ptr - Gets 'void*' value from 'DBData'. + * db_init - Initializes the database system. + * db_final - Finalizes the database system. +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +DBOptions db_fix_options(DBType type, DBOptions options) +{ + DB_COUNTSTAT(db_fix_options); + switch (type) { + case DB_INT: + case DB_UINT: // Numeric database, do nothing with the keys + return (DBOptions)(options&~(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)); + + default: + ShowError("db_fix_options: Unknown database type %u with options %x\n", type, options); + case DB_STRING: + case DB_ISTRING: // String databases, no fix required + return options; + } +} + +/** + * Returns the default comparator for the specified type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see #db_int_cmp(DBKey,DBKey,unsigned short) + * @see #db_uint_cmp(DBKey,DBKey,unsigned short) + * @see #db_string_cmp(DBKey,DBKey,unsigned short) + * @see #db_istring_cmp(DBKey,DBKey,unsigned short) + */ +DBComparator db_default_cmp(DBType type) +{ + DB_COUNTSTAT(db_default_cmp); + switch (type) { + case DB_INT: return &db_int_cmp; + case DB_UINT: return &db_uint_cmp; + case DB_STRING: return &db_string_cmp; + case DB_ISTRING: return &db_istring_cmp; + default: + ShowError("db_default_cmp: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see #db_int_hash(DBKey,unsigned short) + * @see #db_uint_hash(DBKey,unsigned short) + * @see #db_string_hash(DBKey,unsigned short) + * @see #db_istring_hash(DBKey,unsigned short) + */ +DBHasher db_default_hash(DBType type) +{ + DB_COUNTSTAT(db_default_hash); + switch (type) { + case DB_INT: return &db_int_hash; + case DB_UINT: return &db_uint_hash; + case DB_STRING: return &db_string_hash; + case DB_ISTRING: return &db_istring_hash; + default: + ShowError("db_default_hash: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed with {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser. + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the specified options + * @public + * @see #db_release_nothing(DBKey,DBData,DBRelease) + * @see #db_release_key(DBKey,DBData,DBRelease) + * @see #db_release_data(DBKey,DBData,DBRelease) + * @see #db_release_both(DBKey,DBData,DBRelease) + * @see #db_custom_release(DBRelease) + */ +DBReleaser db_default_release(DBType type, DBOptions options) +{ + DB_COUNTSTAT(db_default_release); + options = db_fix_options(type, options); + if (options&DB_OPT_RELEASE_DATA) { // Release data, what about the key? + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return &db_release_both; // Release both key and data + return &db_release_data; // Only release data + } + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return &db_release_key; // Only release key + return &db_release_nothing; // Release nothing +} + +/** + * Returns the releaser that releases the specified release options. + * @param which Options that specified what the releaser releases + * @return Releaser for the specified release options + * @public + * @see #db_release_nothing(DBKey,DBData,DBRelease) + * @see #db_release_key(DBKey,DBData,DBRelease) + * @see #db_release_data(DBKey,DBData,DBRelease) + * @see #db_release_both(DBKey,DBData,DBRelease) + * @see #db_default_release(DBType,DBOptions) + */ +DBReleaser db_custom_release(DBRelease which) +{ + DB_COUNTSTAT(db_custom_release); + switch (which) { + case DB_RELEASE_NOTHING: return &db_release_nothing; + case DB_RELEASE_KEY: return &db_release_key; + case DB_RELEASE_DATA: return &db_release_data; + case DB_RELEASE_BOTH: return &db_release_both; + default: + ShowError("db_custom_release: Unknown release options %u\n", which); + return NULL; + } +} + +/** + * Allocate a new database of the specified type. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases. If 0, the maximum number of maxlen is used (64K). + * @return The interface of the database + * @public + * @see #DBMap_impl + * @see #db_fix_options(DBType,DBOptions) + */ +DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen) +{ + DBMap_impl* db; + unsigned int i; + +#ifdef DB_ENABLE_STATS + DB_COUNTSTAT(db_alloc); + switch (type) { + case DB_INT: DB_COUNTSTAT(db_int_alloc); break; + case DB_UINT: DB_COUNTSTAT(db_uint_alloc); break; + case DB_STRING: DB_COUNTSTAT(db_string_alloc); break; + case DB_ISTRING: DB_COUNTSTAT(db_istring_alloc); break; + } +#endif /* DB_ENABLE_STATS */ + CREATE(db, struct DBMap_impl, 1); + + options = db_fix_options(type, options); + /* Interface of the database */ + db->vtable.iterator = db_obj_iterator; + db->vtable.exists = db_obj_exists; + db->vtable.get = db_obj_get; + db->vtable.getall = db_obj_getall; + db->vtable.vgetall = db_obj_vgetall; + db->vtable.ensure = db_obj_ensure; + db->vtable.vensure = db_obj_vensure; + db->vtable.put = db_obj_put; + db->vtable.remove = db_obj_remove; + db->vtable.foreach = db_obj_foreach; + db->vtable.vforeach = db_obj_vforeach; + db->vtable.clear = db_obj_clear; + db->vtable.vclear = db_obj_vclear; + db->vtable.destroy = db_obj_destroy; + db->vtable.vdestroy = db_obj_vdestroy; + db->vtable.size = db_obj_size; + db->vtable.type = db_obj_type; + db->vtable.options = db_obj_options; + /* File and line of allocation */ + db->alloc_file = file; + db->alloc_line = line; + /* Lock system */ + db->free_list = NULL; + db->free_count = 0; + db->free_max = 0; + db->free_lock = 0; + /* Other */ + db->nodes = ers_new(sizeof(struct dbn),"db.c::db_alloc",ERS_OPT_NONE); + db->cmp = db_default_cmp(type); + db->hash = db_default_hash(type); + db->release = db_default_release(type, options); + for (i = 0; i < HASH_SIZE; i++) + db->ht[i] = NULL; + db->cache = NULL; + db->type = type; + db->options = options; + db->item_count = 0; + db->maxlen = maxlen; + db->global_lock = 0; + + if( db->maxlen == 0 && (type == DB_STRING || type == DB_ISTRING) ) + db->maxlen = UINT16_MAX; + + return &db->vtable; +} + +/** + * Manual cast from 'int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_i2key(int key) +{ + DBKey ret; + + DB_COUNTSTAT(db_i2key); + ret.i = key; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_ui2key(unsigned int key) +{ + DBKey ret; + + DB_COUNTSTAT(db_ui2key); + ret.ui = key; + return ret; +} + +/** + * Manual cast from 'const char *' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_str2key(const char *key) +{ + DBKey ret; + + DB_COUNTSTAT(db_str2key); + ret.str = key; + return ret; +} + +/** + * Manual cast from 'int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_i2data(int data) +{ + DBData ret; + + DB_COUNTSTAT(db_i2data); + ret.type = DB_DATA_INT; + ret.u.i = data; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ui2data(unsigned int data) +{ + DBData ret; + + DB_COUNTSTAT(db_ui2data); + ret.type = DB_DATA_UINT; + ret.u.ui = data; + return ret; +} + +/** + * Manual cast from 'void *' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ptr2data(void *data) +{ + DBData ret; + + DB_COUNTSTAT(db_ptr2data); + ret.type = DB_DATA_PTR; + ret.u.ptr = data; + return ret; +} + +/** + * Gets int type data from struct DBData. + * If data is not int type, returns 0. + * @param data Data + * @return Integer value of the data. + * @public + */ +int db_data2i(DBData *data) +{ + DB_COUNTSTAT(db_data2i); + if (data && DB_DATA_INT == data->type) + return data->u.i; + return 0; +} + +/** + * Gets unsigned int type data from struct DBData. + * If data is not unsigned int type, returns 0. + * @param data Data + * @return Unsigned int value of the data. + * @public + */ +unsigned int db_data2ui(DBData *data) +{ + DB_COUNTSTAT(db_data2ui); + if (data && DB_DATA_UINT == data->type) + return data->u.ui; + return 0; +} + +/** + * Gets void* type data from struct DBData. + * If data is not void* type, returns NULL. + * @param data Data + * @return Void* value of the data. + * @public + */ +void* db_data2ptr(DBData *data) +{ + DB_COUNTSTAT(db_data2ptr); + if (data && DB_DATA_PTR == data->type) + return data->u.ptr; + return NULL; +} + +/** + * Initializes the database system. + * @public + * @see #db_final(void) + */ +void db_init(void) +{ + DB_COUNTSTAT(db_init); +} + +/** + * Finalizes the database system. + * @public + * @see #db_init(void) + */ +void db_final(void) +{ +#ifdef DB_ENABLE_STATS + DB_COUNTSTAT(db_final); + ShowInfo(CL_WHITE"Database nodes"CL_RESET":\n" + "allocated %u, freed %u\n", + stats.db_node_alloc, stats.db_node_free); + ShowInfo(CL_WHITE"Database types"CL_RESET":\n" + "DB_INT : allocated %10u, destroyed %10u\n" + "DB_UINT : allocated %10u, destroyed %10u\n" + "DB_STRING : allocated %10u, destroyed %10u\n" + "DB_ISTRING : allocated %10u, destroyed %10u\n", + stats.db_int_alloc, stats.db_int_destroy, + stats.db_uint_alloc, stats.db_uint_destroy, + stats.db_string_alloc, stats.db_string_destroy, + stats.db_istring_alloc, stats.db_istring_destroy); + ShowInfo(CL_WHITE"Database function counters"CL_RESET":\n" + "db_rotate_left %10u, db_rotate_right %10u,\n" + "db_rebalance %10u, db_rebalance_erase %10u,\n" + "db_is_key_null %10u,\n" + "db_dup_key %10u, db_dup_key_free %10u,\n" + "db_free_add %10u, db_free_remove %10u,\n" + "db_free_lock %10u, db_free_unlock %10u,\n" + "db_int_cmp %10u, db_uint_cmp %10u,\n" + "db_string_cmp %10u, db_istring_cmp %10u,\n" + "db_int_hash %10u, db_uint_hash %10u,\n" + "db_string_hash %10u, db_istring_hash %10u,\n" + "db_release_nothing %10u, db_release_key %10u,\n" + "db_release_data %10u, db_release_both %10u,\n" + "dbit_first %10u, dbit_last %10u,\n" + "dbit_next %10u, dbit_prev %10u,\n" + "dbit_exists %10u, dbit_remove %10u,\n" + "dbit_destroy %10u, db_iterator %10u,\n" + "db_exits %10u, db_get %10u,\n" + "db_getall %10u, db_vgetall %10u,\n" + "db_ensure %10u, db_vensure %10u,\n" + "db_put %10u, db_remove %10u,\n" + "db_foreach %10u, db_vforeach %10u,\n" + "db_clear %10u, db_vclear %10u,\n" + "db_destroy %10u, db_vdestroy %10u,\n" + "db_size %10u, db_type %10u,\n" + "db_options %10u, db_fix_options %10u,\n" + "db_default_cmp %10u, db_default_hash %10u,\n" + "db_default_release %10u, db_custom_release %10u,\n" + "db_alloc %10u, db_i2key %10u,\n" + "db_ui2key %10u, db_str2key %10u,\n" + "db_i2data %10u, db_ui2data %10u,\n" + "db_ptr2data %10u, db_data2i %10u,\n" + "db_data2ui %10u, db_data2ptr %10u,\n" + "db_init %10u, db_final %10u\n", + stats.db_rotate_left, stats.db_rotate_right, + stats.db_rebalance, stats.db_rebalance_erase, + stats.db_is_key_null, + stats.db_dup_key, stats.db_dup_key_free, + stats.db_free_add, stats.db_free_remove, + stats.db_free_lock, stats.db_free_unlock, + stats.db_int_cmp, stats.db_uint_cmp, + stats.db_string_cmp, stats.db_istring_cmp, + stats.db_int_hash, stats.db_uint_hash, + stats.db_string_hash, stats.db_istring_hash, + stats.db_release_nothing, stats.db_release_key, + stats.db_release_data, stats.db_release_both, + stats.dbit_first, stats.dbit_last, + stats.dbit_next, stats.dbit_prev, + stats.dbit_exists, stats.dbit_remove, + stats.dbit_destroy, stats.db_iterator, + stats.db_exists, stats.db_get, + stats.db_getall, stats.db_vgetall, + stats.db_ensure, stats.db_vensure, + stats.db_put, stats.db_remove, + stats.db_foreach, stats.db_vforeach, + stats.db_clear, stats.db_vclear, + stats.db_destroy, stats.db_vdestroy, + stats.db_size, stats.db_type, + stats.db_options, stats.db_fix_options, + stats.db_default_cmp, stats.db_default_hash, + stats.db_default_release, stats.db_custom_release, + stats.db_alloc, stats.db_i2key, + stats.db_ui2key, stats.db_str2key, + stats.db_i2data, stats.db_ui2data, + stats.db_ptr2data, stats.db_data2i, + stats.db_data2ui, stats.db_data2ptr, + stats.db_init, stats.db_final); +#endif /* DB_ENABLE_STATS */ +} + +// Link DB System - jAthena +void linkdb_insert( struct linkdb_node** head, void *key, void* data) +{ + struct linkdb_node *node; + if( head == NULL ) return ; + node = (struct linkdb_node*)aMalloc( sizeof(struct linkdb_node) ); + if( *head == NULL ) { + // first node + *head = node; + node->prev = NULL; + node->next = NULL; + } else { + // link nodes + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + node->key = key; + node->data = data; +} + +void linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ... ) +{ + struct linkdb_node *node; + if( head == NULL ) return; + node = *head; + while ( node ) { + va_list args; + va_start(args, func); + func( node->key, node->data, args ); + va_end(args); + node = node->next; + } +} + +void* linkdb_search( struct linkdb_node** head, void *key) +{ + int n = 0; + struct linkdb_node *node; + if( head == NULL ) return NULL; + node = *head; + while( node ) { + if( node->key == key ) { + if( node->prev && n > 5 ) { + //Moving the head in order to improve processing efficiency + if(node->prev) node->prev->next = node->next; + if(node->next) node->next->prev = node->prev; + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + return node->data; + } + node = node->next; + n++; + } + return NULL; +} + +void* linkdb_erase( struct linkdb_node** head, void *key) +{ + struct linkdb_node *node; + if( head == NULL ) return NULL; + node = *head; + while( node ) { + if( node->key == key ) { + void *data = node->data; + if( node->prev == NULL ) + *head = node->next; + else + node->prev->next = node->next; + if( node->next ) + node->next->prev = node->prev; + aFree( node ); + return data; + } + node = node->next; + } + return NULL; +} + +void linkdb_replace( struct linkdb_node** head, void *key, void *data ) +{ + int n = 0; + struct linkdb_node *node; + if( head == NULL ) return ; + node = *head; + while( node ) { + if( node->key == key ) { + if( node->prev && n > 5 ) { + //Moving the head in order to improve processing efficiency + if(node->prev) node->prev->next = node->next; + if(node->next) node->next->prev = node->prev; + node->next = *head; + node->prev = (*head)->prev; + (*head)->prev = node; + (*head) = node; + } + node->data = data; + return ; + } + node = node->next; + n++; + } + //Insert because it can not find + linkdb_insert( head, key, data ); +} + +void linkdb_final( struct linkdb_node** head ) +{ + struct linkdb_node *node, *node2; + if( head == NULL ) return ; + node = *head; + while( node ) { + node2 = node->next; + aFree( node ); + node = node2; + } + *head = NULL; +} diff --git a/src/common/db.h b/src/common/db.h new file mode 100644 index 000000000..4fe6a93d6 --- /dev/null +++ b/src/common/db.h @@ -0,0 +1,1492 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * This file is separated in two sections: * + * (1) public typedefs, enums, unions, structures and defines * + * (2) public functions * + * * + * <B>Notes on the release system:</B> * + * Whenever an entry is removed from the database both the key and the * + * data are requested to be released. * + * At least one entry is removed when replacing an entry, removing an * + * entry, clearing the database or destroying the database. * + * What is actually released is defined by the release function, the * + * functions of the database only ask for the key and/or data to be * + * released. * + * * + * TODO: * + * - create a custom database allocator * + * - see what functions need or should be added to the database interface * + * * + * HISTORY: * + * 2012/03/09 - Added enum for data types (int, uint, void*) * + * 2007/11/09 - Added an iterator to the database. * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBMap#ensure(DBMap,DBKey,DBCreateData,...)} and * + * {@link DBMap#clear(DBMap,DBApply,...)}. * + * 2.0 (Athena build 4859) - Transition version * + * - Almost everything recoded with a strategy similar to objects, * + * database structure is maintained. * + * 1.0 (up to Athena build 4706) * + * - Previous database system. * + * * + * @version 2.1 (Athena build #???#) - Portability fix * + * @author (Athena build 4859) Flavio @ Amazon Project * + * @author (up to Athena build 4706) Athena Dev Teams * + * @encoding US-ASCII * + * @see common#db.c * +\*****************************************************************************/ +#ifndef _DB_H_ +#define _DB_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h> + +/*****************************************************************************\ + * (1) Section with public typedefs, enums, unions, structures and defines. * + * DBRelease - Enumeration of release options. * + * DBType - Enumeration of database types. * + * DBOptions - Bitfield enumeration of database options. * + * DBKey - Union of used key types. * + * DBDataType - Enumeration of data types. * + * DBData - Struct for used data types. * + * DBApply - Format of functions applied to the databases. * + * DBMatcher - Format of matchers used in DBMap::getall. * + * DBComparator - Format of the comparators used by the databases. * + * DBHasher - Format of the hashers used by the databases. * + * DBReleaser - Format of the releasers used by the databases. * + * DBIterator - Database iterator. * + * DBMap - Database interface. * +\*****************************************************************************/ + +/** + * Bitfield with what should be released by the releaser function (if the + * function supports it). + * @public + * @see #DBReleaser + * @see #db_custom_release(DBRelease) + */ +typedef enum DBRelease { + DB_RELEASE_NOTHING = 0, + DB_RELEASE_KEY = 1, + DB_RELEASE_DATA = 2, + DB_RELEASE_BOTH = 3 +} DBRelease; + +/** + * Supported types of database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_INT Uses int's for keys + * @param DB_UINT Uses unsigned int's for keys + * @param DB_STRING Uses strings for keys. + * @param DB_ISTRING Uses case insensitive strings for keys. + * @public + * @see #DBOptions + * @see #DBKey + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum DBType { + DB_INT, + DB_UINT, + DB_STRING, + DB_ISTRING +} DBType; + +/** + * Bitfield of options that define the behaviour of the database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_OPT_BASE Base options: does not duplicate keys, releases nothing + * and does not allow NULL keys or NULL data. + * @param DB_OPT_DUP_KEY Duplicates the keys internally. If DB_OPT_RELEASE_KEY + * is defined, the real key is freed as soon as the entry is added. + * @param DB_OPT_RELEASE_KEY Releases the key. + * @param DB_OPT_RELEASE_DATA Releases the data whenever an entry is removed + * from the database. + * WARNING: for funtions that return the data (like DBMap::remove), + * a dangling pointer will be returned. + * @param DB_OPT_RELEASE_BOTH Releases both key and data. + * @param DB_OPT_ALLOW_NULL_KEY Allow NULL keys in the database. + * @param DB_OPT_ALLOW_NULL_DATA Allow NULL data in the database. + * @public + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum DBOptions { + DB_OPT_BASE = 0, + DB_OPT_DUP_KEY = 1, + DB_OPT_RELEASE_KEY = 2, + DB_OPT_RELEASE_DATA = 4, + DB_OPT_RELEASE_BOTH = 6, + DB_OPT_ALLOW_NULL_KEY = 8, + DB_OPT_ALLOW_NULL_DATA = 16, +} DBOptions; + +/** + * Union of key types used by the database. + * @param i Type of key for DB_INT databases + * @param ui Type of key for DB_UINT databases + * @param str Type of key for DB_STRING and DB_ISTRING databases + * @public + * @see #DBType + * @see DBMap#get + * @see DBMap#put + * @see DBMap#remove + */ +typedef union DBKey { + int i; + unsigned int ui; + const char *str; +} DBKey; + +/** + * Supported types of database data. + * @param DB_DATA_INT Uses ints for data. + * @param DB_DATA_UINT Uses unsigned ints for data. + * @param DB_DATA_PTR Uses void pointers for data. + * @public + * @see #DBData + */ +typedef enum DBDataType { + DB_DATA_INT, + DB_DATA_UINT, + DB_DATA_PTR +} DBDataType; + +/** + * Struct for data types used by the database. + * @param type Type of data + * @param u Union of available data types + * @param u.i Data of int type + * @param u.ui Data of unsigned int type + * @param u.ptr Data of void* type + * @public + */ +typedef struct DBData { + DBDataType type; + union { + int i; + unsigned int ui; + void *ptr; + } u; +} DBData; + +/** + * Format of functions that create the data for the key when the entry doesn't + * exist in the database yet. + * @param key Key of the database entry + * @param args Extra arguments of the function + * @return Data identified by the key to be put in the database + * @public + * @see DBMap#vensure + * @see DBMap#ensure + */ +typedef DBData (*DBCreateData)(DBKey key, va_list args); + +/** + * Format of functions to be applied to an unspecified quantity of entries of + * a database. + * Any function that applies this function to the database will return the sum + * of values returned by this function. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the function + * @return Value to be added up by the function that is applying this + * @public + * @see DBMap#vforeach + * @see DBMap#foreach + * @see DBMap#vdestroy + * @see DBMap#destroy + */ +typedef int (*DBApply)(DBKey key, DBData *data, va_list args); + +/** + * Format of functions that match database entries. + * The purpose of the match depends on the function that is calling the matcher. + * Returns 0 if it is a match, another number otherwise. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the function + * @return 0 if a match, another number otherwise + * @public + * @see DBMap#getall + */ +typedef int (*DBMatcher)(DBKey key, DBData data, va_list args); + +/** + * Format of the comparators used internally by the database system. + * Compares key1 to key2. + * Returns 0 is equal, negative if lower and positive is higher. + * @param key1 Key being compared + * @param key2 Key we are comparing to + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return 0 if equal, negative if lower and positive if higher + * @public + * @see #db_default_cmp(DBType) + */ +typedef int (*DBComparator)(DBKey key1, DBKey key2, unsigned short maxlen); + +/** + * Format of the hashers used internally by the database system. + * Creates the hash of the key. + * @param key Key being hashed + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return Hash of the key + * @public + * @see #db_default_hash(DBType) + */ +typedef unsigned int (*DBHasher)(DBKey key, unsigned short maxlen); + +/** + * Format of the releaser used by the database system. + * Releases nothing, the key, the data or both. + * All standard releasers use aFree to release. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @public + * @see #DBRelease + * @see #db_default_releaser(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +typedef void (*DBReleaser)(DBKey key, DBData data, DBRelease which); + + + +typedef struct DBIterator DBIterator; +typedef struct DBMap DBMap; + + + +/** + * Database iterator. + * Supports forward iteration, backward iteration and removing entries from the database. + * The iterator is initially positioned before the first entry of the database. + * While the iterator exists the database is locked internally, so invoke + * {@link DBIterator#destroy} as soon as possible. + * @public + * @see #DBMap + */ +struct DBIterator +{ + + /** + * Fetches the first entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*first)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the last entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*last)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the next entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*next)(DBIterator* self, DBKey* out_key); + + /** + * Fetches the previous entry in the database. + * Returns the data of the entry. + * Puts the key in out_key, if out_key is not NULL. + * @param self Iterator + * @param out_key Key of the entry + * @return Data of the entry + * @protected + */ + DBData* (*prev)(DBIterator* self, DBKey* out_key); + + /** + * Returns true if the fetched entry exists. + * The databases entries might have NULL data, so use this to to test if + * the iterator is done. + * @param self Iterator + * @return true is the entry exists + * @protected + */ + bool (*exists)(DBIterator* self); + + /** + * Removes the current entry from the database. + * NOTE: {@link DBIterator#exists} will return false until another entry + * is fetched + * Puts data of the removed entry in out_data, if out_data is not NULL. + * @param self Iterator + * @param out_data Data of the removed entry. + * @return 1 if entry was removed, 0 otherwise + * @protected + * @see DBMap#remove + */ + int (*remove)(DBIterator* self, DBData *out_data); + + /** + * Destroys this iterator and unlocks the database. + * @param self Iterator + * @protected + */ + void (*destroy)(DBIterator* self); + +}; + +/** + * Public interface of a database. Only contains funtions. + * All the functions take the interface as the first argument. + * @public + * @see #db_alloc(const char*,int,DBType,DBOptions,unsigned short) + */ +struct DBMap { + + /** + * Returns a new iterator for this database. + * The iterator keeps the database locked until it is destroyed. + * The database will keep functioning normally but will only free internal + * memory when unlocked, so destroy the iterator as soon as possible. + * @param self Database + * @return New iterator + * @protected + */ + DBIterator* (*iterator)(DBMap* self); + + /** + * Returns true if the entry exists. + * @param self Database + * @param key Key that identifies the entry + * @return true is the entry exists + * @protected + */ + bool (*exists)(DBMap* self, DBKey key); + + /** + * Get the data of the entry identified by the key. + * @param self Database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + */ + DBData* (*get)(DBMap* self, DBKey key); + + /** + * Just calls {@link DBMap#vgetall}. + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#vgetall(DBMap*,void **,unsigned int,DBMatcher,va_list) + */ + unsigned int (*getall)(DBMap* self, DBData** buf, unsigned int max, DBMatcher match, ...); + + /** + * Get the data of the entries matched by <code>match</code>. + * It puts a maximum of <code>max</code> entries into <code>buf</code>. + * If <code>buf</code> is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than <code>max</code>, only the + * first <code>max</code> entries found are put into the buffer. + * @param self Database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see DBMap#getall(DBMap*,void **,unsigned int,DBMatcher,...) + */ + unsigned int (*vgetall)(DBMap* self, DBData** buf, unsigned int max, DBMatcher match, va_list args); + + /** + * Just calls {@link DBMap#vensure}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#vensure(DBMap*,DBKey,DBCreateData,va_list) + */ + DBData* (*ensure)(DBMap* self, DBKey key, DBCreateData create, ...); + + /** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * <code>create</code>. + * @param self Database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see DBMap#ensure(DBMap*,DBKey,DBCreateData,...) + */ + DBData* (*vensure)(DBMap* self, DBKey key, DBCreateData create, va_list args); + + /** + * Put the data identified by the key in the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: Uses the new key, the old one is released. + * @param self Database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + */ + int (*put)(DBMap* self, DBKey key, DBData data, DBData *out_data); + + /** + * Remove an entry from the database. + * Puts the previous data in out_data, if out_data is not NULL. + * NOTE: The key (of the database) is released. + * @param self Database + * @param key Key that identifies the entry + * @param out_data Previous data if the entry exists + * @return 1 if if the entry already exists, 0 otherwise + * @protected + */ + int (*remove)(DBMap* self, DBKey key, DBData *out_data); + + /** + * Just calls {@link DBMap#vforeach}. + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Database + * @param func Function to be applied + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#vforeach(DBMap*,DBApply,va_list) + */ + int (*foreach)(DBMap* self, DBApply func, ...); + + /** + * Apply <code>func</code> to every entry in the database. + * Returns the sum of values returned by func. + * @param self Database + * @param func Function to be applied + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see DBMap#foreach(DBMap*,DBApply,...) + */ + int (*vforeach)(DBMap* self, DBApply func, va_list args); + + /** + * Just calls {@link DBMap#vclear}. + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vclear(DBMap*,DBApply,va_list) + */ + int (*clear)(DBMap* self, DBApply func, ...); + + /** + * Removes all entries from the database. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#clear(DBMap*,DBApply,...) + */ + int (*vclear)(DBMap* self, DBApply func, va_list args); + + /** + * Just calls {@link DBMap#vdestroy}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#vdestroy(DBMap*,DBApply,va_list) + */ + int (*destroy)(DBMap* self, DBApply func, ...); + + /** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applied to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Database + * @param func Function to be applied to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see DBMap#destroy(DBMap*,DBApply,...) + */ + int (*vdestroy)(DBMap* self, DBApply func, va_list args); + + /** + * Return the size of the database (number of items in the database). + * @param self Database + * @return Size of the database + * @protected + */ + unsigned int (*size)(DBMap* self); + + /** + * Return the type of the database. + * @param self Database + * @return Type of the database + * @protected + */ + DBType (*type)(DBMap* self); + + /** + * Return the options of the database. + * @param self Database + * @return Options of the database + * @protected + */ + DBOptions (*options)(DBMap* self); + +}; + +// For easy access to the common functions. + +#define db_exists(db,k) ( (db)->exists((db),(k)) ) +#define idb_exists(db,k) ( (db)->exists((db),db_i2key(k)) ) +#define uidb_exists(db,k) ( (db)->exists((db),db_ui2key(k)) ) +#define strdb_exists(db,k) ( (db)->exists((db),db_str2key(k)) ) + +// Get pointer-type data from DBMaps of various key types +#define db_get(db,k) ( db_data2ptr((db)->get((db),(k))) ) +#define idb_get(db,k) ( db_data2ptr((db)->get((db),db_i2key(k))) ) +#define uidb_get(db,k) ( db_data2ptr((db)->get((db),db_ui2key(k))) ) +#define strdb_get(db,k) ( db_data2ptr((db)->get((db),db_str2key(k))) ) + +// Get int-type data from DBMaps of various key types +#define db_iget(db,k) ( db_data2i((db)->get((db),(k))) ) +#define idb_iget(db,k) ( db_data2i((db)->get((db),db_i2key(k))) ) +#define uidb_iget(db,k) ( db_data2i((db)->get((db),db_ui2key(k))) ) +#define strdb_iget(db,k) ( db_data2i((db)->get((db),db_str2key(k))) ) + +// Get uint-type data from DBMaps of various key types +#define db_uiget(db,k) ( db_data2ui((db)->get((db),(k))) ) +#define idb_uiget(db,k) ( db_data2ui((db)->get((db),db_i2key(k))) ) +#define uidb_uiget(db,k) ( db_data2ui((db)->get((db),db_ui2key(k))) ) +#define strdb_uiget(db,k) ( db_data2ui((db)->get((db),db_str2key(k))) ) + +// Put pointer-type data into DBMaps of various key types +#define db_put(db,k,d) ( (db)->put((db),(k),db_ptr2data(d),NULL) ) +#define idb_put(db,k,d) ( (db)->put((db),db_i2key(k),db_ptr2data(d),NULL) ) +#define uidb_put(db,k,d) ( (db)->put((db),db_ui2key(k),db_ptr2data(d),NULL) ) +#define strdb_put(db,k,d) ( (db)->put((db),db_str2key(k),db_ptr2data(d),NULL) ) + +// Put int-type data into DBMaps of various key types +#define db_iput(db,k,d) ( (db)->put((db),(k),db_i2data(d),NULL) ) +#define idb_iput(db,k,d) ( (db)->put((db),db_i2key(k),db_i2data(d),NULL) ) +#define uidb_iput(db,k,d) ( (db)->put((db),db_ui2key(k),db_i2data(d),NULL) ) +#define strdb_iput(db,k,d) ( (db)->put((db),db_str2key(k),db_i2data(d),NULL) ) + +// Put uint-type data into DBMaps of various key types +#define db_uiput(db,k,d) ( (db)->put((db),(k),db_ui2data(d),NULL) ) +#define idb_uiput(db,k,d) ( (db)->put((db),db_i2key(k),db_ui2data(d),NULL) ) +#define uidb_uiput(db,k,d) ( (db)->put((db),db_ui2key(k),db_ui2data(d),NULL) ) +#define strdb_uiput(db,k,d) ( (db)->put((db),db_str2key(k),db_ui2data(d),NULL) ) + +// Remove entry from DBMaps of various key types +#define db_remove(db,k) ( (db)->remove((db),(k),NULL) ) +#define idb_remove(db,k) ( (db)->remove((db),db_i2key(k),NULL) ) +#define uidb_remove(db,k) ( (db)->remove((db),db_ui2key(k),NULL) ) +#define strdb_remove(db,k) ( (db)->remove((db),db_str2key(k),NULL) ) + +//These are discarding the possible vargs you could send to the function, so those +//that require vargs must not use these defines. +#define db_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),(k),(f))) ) +#define idb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_i2key(k),(f))) ) +#define uidb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_ui2key(k),(f))) ) +#define strdb_ensure(db,k,f) ( db_data2ptr((db)->ensure((db),db_str2key(k),(f))) ) + +// Database creation and destruction macros +#define idb_alloc(opt) db_alloc(__FILE__,__LINE__,DB_INT,(opt),sizeof(int)) +#define uidb_alloc(opt) db_alloc(__FILE__,__LINE__,DB_UINT,(opt),sizeof(unsigned int)) +#define strdb_alloc(opt,maxlen) db_alloc(__FILE__,__LINE__,DB_STRING,(opt),(maxlen)) +#define stridb_alloc(opt,maxlen) db_alloc(__FILE__,__LINE__,DB_ISTRING,(opt),(maxlen)) +#define db_destroy(db) ( (db)->destroy((db),NULL) ) +// Other macros +#define db_clear(db) ( (db)->clear(db,NULL) ) +#define db_size(db) ( (db)->size(db) ) +#define db_iterator(db) ( (db)->iterator(db) ) +#define dbi_first(dbi) ( db_data2ptr((dbi)->first(dbi,NULL)) ) +#define dbi_last(dbi) ( db_data2ptr((dbi)->last(dbi,NULL)) ) +#define dbi_next(dbi) ( db_data2ptr((dbi)->next(dbi,NULL)) ) +#define dbi_prev(dbi) ( db_data2ptr((dbi)->prev(dbi,NULL)) ) +#define dbi_remove(dbi) ( (dbi)->remove(dbi,NULL) ) +#define dbi_exists(dbi) ( (dbi)->exists(dbi) ) +#define dbi_destroy(dbi) ( (dbi)->destroy(dbi) ) + +/*****************************************************************************\ + * (2) Section with public functions. * + * db_fix_options - Fix the options for a type of database. * + * db_default_cmp - Get the default comparator for a type of database. * + * db_default_hash - Get the default hasher for a type of database. * + * db_default_release - Get the default releaser for a type of database * + * with the fixed options. * + * db_custom_release - Get the releaser that behaves as specified. * + * db_alloc - Allocate a new database. * + * db_i2key - Manual cast from 'int' to 'DBKey'. * + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. * + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. * + * db_i2data - Manual cast from 'int' to 'DBData'. * + * db_ui2data - Manual cast from 'unsigned int' to 'DBData'. * + * db_ptr2data - Manual cast from 'void*' to 'DBData'. * + * db_data2i - Gets 'int' value from 'DBData'. * + * db_data2ui - Gets 'unsigned int' value from 'DBData'. * + * db_data2ptr - Gets 'void*' value from 'DBData'. * + * db_init - Initializes the database system. * + * db_final - Finalizes the database system. * +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see #DBType + * @see #DBOptions + * @see #db_default_release(DBType,DBOptions) + */ +DBOptions db_fix_options(DBType type, DBOptions options); + +/** + * Returns the default comparator for the type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBComparator + */ +DBComparator db_default_cmp(DBType type); + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBHasher + */ +DBHasher db_default_hash(DBType type); + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the fixed options + * @public + * @see #DBType + * @see #DBOptions + * @see #DBReleaser + * @see #db_fix_options(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +DBReleaser db_default_release(DBType type, DBOptions options); + +/** + * Returns the releaser that behaves as <code>which</code> specifies. + * @param which Defines what the releaser releases + * @return Releaser for the specified release options + * @public + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +DBReleaser db_custom_release(DBRelease which); + +/** + * Allocate a new database of the specified type. + * It uses the default comparator, hasher and releaser of the specified + * database type and fixed options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases. If 0, the maximum number of maxlen is used (64K). + * @return The interface of the database + * @public + * @see #DBType + * @see #DBMap + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_fix_options(DBType,DBOptions) + */ +DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen); + +/** + * Manual cast from 'int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_i2key(int key); + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_ui2key(unsigned int key); + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + */ +DBKey db_str2key(const char *key); + +/** + * Manual cast from 'int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_i2data(int data); + +/** + * Manual cast from 'unsigned int' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ui2data(unsigned int data); + +/** + * Manual cast from 'void *' to the struct DBData. + * @param data Data to be casted + * @return The data as a DBData struct + * @public + */ +DBData db_ptr2data(void *data); + +/** + * Gets int type data from struct DBData. + * If data is not int type, returns 0. + * @param data Data + * @return Integer value of the data. + * @public + */ +int db_data2i(DBData *data); + +/** + * Gets unsigned int type data from struct DBData. + * If data is not unsigned int type, returns 0. + * @param data Data + * @return Unsigned int value of the data. + * @public + */ +unsigned int db_data2ui(DBData *data); + +/** + * Gets void* type data from struct DBData. + * If data is not void* type, returns NULL. + * @param data Data + * @return Void* value of the data. + * @public + */ +void* db_data2ptr(DBData *data); + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + */ +void db_init(void); + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see #db_init(void) + */ +void db_final(void); + +// Link DB System - From jAthena +struct linkdb_node { + struct linkdb_node *next; + struct linkdb_node *prev; + void *key; + void *data; +}; + +typedef void (*LinkDBFunc)(void* key, void* data, va_list args); + +void linkdb_insert ( struct linkdb_node** head, void *key, void* data); // d•¡‚ðl—¶‚µ‚È‚¢ +void linkdb_replace( struct linkdb_node** head, void *key, void* data); // d•¡‚ðl—¶‚·‚é +void* linkdb_search ( struct linkdb_node** head, void *key); +void* linkdb_erase ( struct linkdb_node** head, void *key); +void linkdb_final ( struct linkdb_node** head ); +void linkdb_foreach( struct linkdb_node** head, LinkDBFunc func, ... ); + + + +/// Finds an entry in an array. +/// ex: ARR_FIND(0, size, i, list[i] == target); +/// +/// @param __start Starting index (ex: 0) +/// @param __end End index (ex: size of the array) +/// @param __var Index variable +/// @param __cmp Expression that returns true when the target entry is found +#define ARR_FIND(__start, __end, __var, __cmp) \ + do{ \ + for( (__var) = (__start); (__var) < (__end); ++(__var) ) \ + if( __cmp ) \ + break; \ + }while(0) + + + +/// Moves an entry of the array. +/// Use ARR_MOVERIGHT/ARR_MOVELEFT if __from and __to are direct numbers. +/// ex: ARR_MOVE(i, 0, list, int);// move index i to index 0 +/// +/// +/// @param __from Initial index of the entry +/// @param __to Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVE(__from, __to, __arr, __type) \ + do{ \ + if( (__from) != (__to) ) \ + { \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + if( (__from) < (__to) ) \ + memmove((__arr)+(__from), (__arr)+(__from)+1, ((__to)-(__from))*sizeof(__type)); \ + else if( (__from) > (__to) ) \ + memmove((__arr)+(__to)+1, (__arr)+(__to), ((__from)-(__to))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + } \ + }while(0) + + + +/// Moves an entry of the array to the right. +/// ex: ARR_MOVERIGHT(1, 4, list, int);// move index 1 to index 4 +/// +/// @param __from Initial index of the entry +/// @param __to Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVERIGHT(__from, __to, __arr, __type) \ + do{ \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + memmove((__arr)+(__from), (__arr)+(__from)+1, ((__to)-(__from))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + }while(0) + + + +/// Moves an entry of the array to the left. +/// ex: ARR_MOVELEFT(3, 0, list, int);// move index 3 to index 0 +/// +/// @param __from Initial index of the entry +/// @param __end Target index of the entry +/// @param __arr Array +/// @param __type Type of entry +#define ARR_MOVELEFT(__from, __to, __arr, __type) \ + do{ \ + __type __backup__; \ + memmove(&__backup__, (__arr)+(__from), sizeof(__type)); \ + memmove((__arr)+(__to)+1, (__arr)+(__to), ((__from)-(__to))*sizeof(__type)); \ + memmove((__arr)+(__to), &__backup__, sizeof(__type)); \ + }while(0) + + + +///////////////////////////////////////////////////////////////////// +// Vector library based on defines. (dynamic array) +// uses aMalloc, aRealloc, aFree + + + +/// Declares an anonymous vector struct. +/// +/// @param __type Type of data +#define VECTOR_DECL(__type) \ + struct { \ + size_t _max_; \ + size_t _len_; \ + __type* _data_; \ + } + + + +/// Declares a named vector struct. +/// +/// @param __name Structure name +/// @param __type Type of data +#define VECTOR_STRUCT_DECL(__name,__type) \ + struct __name { \ + size_t _max_; \ + size_t _len_; \ + __type* _data_; \ + } + + + +/// Declares and initializes an anonymous vector variable. +/// +/// @param __type Type of data +/// @param __var Variable name +#define VECTOR_VAR(__type,__var) \ + VECTOR_DECL(__type) __var = {0,0,NULL} + + + +/// Declares and initializes a named vector variable. +/// +/// @param __name Structure name +/// @param __var Variable name +#define VECTOR_STRUCT_VAR(__name,__var) \ + struct __name __var = {0,0,NULL} + + + +/// Initializes a vector. +/// +/// @param __vec Vector +#define VECTOR_INIT(__vec) \ + memset(&(__vec), 0, sizeof(__vec)) + + + +/// Returns the internal array of values. +/// +/// @param __vec Vector +/// @return Array of values +#define VECTOR_DATA(__vec) \ + ( (__vec)._data_ ) + + + +/// Returns the length of the vector. +/// +/// @param __vec Vector +/// @return Length +#define VECTOR_LENGTH(__vec) \ + ( (__vec)._len_ ) + + + +/// Returns the capacity of the vector. +/// +/// @param __vec Vector +/// @return Capacity +#define VECTOR_CAPACITY(__vec) \ + ( (__vec)._max_ ) + + + +/// Returns the value at the target index. +/// Assumes the index exists. +/// +/// @param __vec Vector +/// @param __idx Index +/// @return Value +#define VECTOR_INDEX(__vec,__idx) \ + ( VECTOR_DATA(__vec)[__idx] ) + + + +/// Returns the first value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return First value +#define VECTOR_FIRST(__vec) \ + ( VECTOR_INDEX(__vec,0) ) + + + +/// Returns the last value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return Last value +#define VECTOR_LAST(__vec) \ + ( VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)-1) ) + + + +/// Resizes the vector. +/// Excess values are discarded, new positions are zeroed. +/// +/// @param __vec Vector +/// @param __n Size +#define VECTOR_RESIZE(__vec,__n) \ + do{ \ + if( (__n) > VECTOR_CAPACITY(__vec) ) \ + { /* increase size */ \ + if( VECTOR_CAPACITY(__vec) == 0 ) SET_POINTER(VECTOR_DATA(__vec), aMalloc((__n)*sizeof(VECTOR_FIRST(__vec)))); /* allocate new */ \ + else SET_POINTER(VECTOR_DATA(__vec), aRealloc(VECTOR_DATA(__vec),(__n)*sizeof(VECTOR_FIRST(__vec)))); /* reallocate */ \ + memset(VECTOR_DATA(__vec)+VECTOR_LENGTH(__vec), 0, (VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec))*sizeof(VECTOR_FIRST(__vec))); /* clear new data */ \ + VECTOR_CAPACITY(__vec) = (__n); /* update capacity */ \ + } \ + else if( (__n) == 0 && VECTOR_CAPACITY(__vec) ) \ + { /* clear vector */ \ + aFree(VECTOR_DATA(__vec)); VECTOR_DATA(__vec) = NULL; /* free data */ \ + VECTOR_CAPACITY(__vec) = 0; /* clear capacity */ \ + VECTOR_LENGTH(__vec) = 0; /* clear length */ \ + } \ + else if( (__n) < VECTOR_CAPACITY(__vec) ) \ + { /* reduce size */ \ + SET_POINTER(VECTOR_DATA(__vec), aRealloc(VECTOR_DATA(__vec),(__n)*sizeof(VECTOR_FIRST(__vec)))); /* reallocate */ \ + VECTOR_CAPACITY(__vec) = (__n); /* update capacity */ \ + if( VECTOR_LENGTH(__vec) > (__n) ) VECTOR_LENGTH(__vec) = (__n); /* update length */ \ + } \ + }while(0) + + + +/// Ensures that the array has the target number of empty positions. +/// Increases the capacity in multiples of __step. +/// +/// @param __vec Vector +/// @param __n Empty positions +/// @param __step Increase +#define VECTOR_ENSURE(__vec,__n,__step) \ + do{ \ + size_t _empty_ = VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec); \ + while( (__n) > _empty_ ) _empty_ += (__step); \ + if( _empty_ != VECTOR_CAPACITY(__vec)-VECTOR_LENGTH(__vec) ) VECTOR_RESIZE(__vec,_empty_+VECTOR_LENGTH(__vec)); \ + }while(0) + + + +/// Inserts a zeroed value in the target index. +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +#define VECTOR_INSERTZEROED(__vec,__idx) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+1),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + memset(&VECTOR_INDEX(__vec,__idx), 0, sizeof(VECTOR_INDEX(__vec,__idx))); /* set zeroed value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the target index. (using the '=' operator) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __val Value +#define VECTOR_INSERT(__vec,__idx,__val) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+1),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + VECTOR_INDEX(__vec,__idx) = (__val); /* set value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the target index. (using memcpy) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __val Value +#define VECTOR_INSERTCOPY(__vec,__idx,__val) \ + VECTOR_INSERTARRAY(__vec,__idx,&(__val),1) + + + +/// Inserts the values of the array in the target index. (using memcpy) +/// Assumes the index is valid and there is enough capacity. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __pval Array of values +/// @param __n Number of values +#define VECTOR_INSERTARRAY(__vec,__idx,__pval,__n) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,(__idx)+(__n)),&VECTOR_INDEX(__vec,__idx),(VECTOR_LENGTH(__vec)-(__idx))*sizeof(VECTOR_FIRST(__vec))); \ + memcpy(&VECTOR_INDEX(__vec,__idx), (__pval), (__n)*sizeof(VECTOR_FIRST(__vec))); /* set values */ \ + VECTOR_LENGTH(__vec) += (__n); /* increase length */ \ + }while(0) + + + +/// Inserts a zeroed value in the end of the vector. +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +#define VECTOR_PUSHZEROED(__vec) \ + do{ \ + memset(&VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)), 0, sizeof(VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)))); /* set zeroed value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + +/// Inserts a value in the end of the vector. (using the '=' operator) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __val Value +#define VECTOR_PUSH(__vec,__val) \ + do{ \ + VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)) = (__val); /* set value */ \ + ++VECTOR_LENGTH(__vec); /* increase length */ \ + }while(0) + + + +/// Inserts a value in the end of the vector. (using memcpy) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __val Value +#define VECTOR_PUSHCOPY(__vec,__val) \ + VECTOR_PUSHARRAY(__vec,&(__val),1) + + + +/// Inserts the values of the array in the end of the vector. (using memcpy) +/// Assumes there is enough capacity. +/// +/// @param __vec Vector +/// @param __pval Array of values +/// @param __n Number of values +#define VECTOR_PUSHARRAY(__vec,__pval,__n) \ + do{ \ + memcpy(&VECTOR_INDEX(__vec,VECTOR_LENGTH(__vec)), (__pval), (__n)*sizeof(VECTOR_FIRST(__vec))); /* set values */ \ + VECTOR_LENGTH(__vec) += (__n); /* increase length */ \ + }while(0) + + + +/// Removes and returns the last value of the vector. +/// Assumes the array is not empty. +/// +/// @param __vec Vector +/// @return Removed value +#define VECTOR_POP(__vec) \ + ( VECTOR_INDEX(__vec,--VECTOR_LENGTH(__vec)) ) + + + +/// Removes the last N values of the vector and returns the value of the last pop. +/// Assumes there are enough values. +/// +/// @param __vec Vector +/// @param __n Number of pops +/// @return Last removed value +#define VECTOR_POPN(__vec,__n) \ + ( VECTOR_INDEX(__vec,(VECTOR_LENGTH(__vec)-=(__n))) ) + + + +/// Removes the target index from the vector. +/// Assumes the index is valid and there are enough values. +/// +/// @param __vec Vector +/// @param __idx Index +#define VECTOR_ERASE(__vec,__idx) \ + VECTOR_ERASEN(__vec,__idx,1) + + + +/// Removes N values from the target index of the vector. +/// Assumes the index is valid and there are enough values. +/// +/// @param __vec Vector +/// @param __idx Index +/// @param __n Number of values +#define VECTOR_ERASEN(__vec,__idx,__n) \ + do{ \ + if( (__idx) < VECTOR_LENGTH(__vec)-(__n) ) /* move data */ \ + memmove(&VECTOR_INDEX(__vec,__idx),&VECTOR_INDEX(__vec,(__idx)+(__n)),(VECTOR_LENGTH(__vec)-((__idx)+(__n)))*sizeof(VECTOR_FIRST(__vec))); \ + VECTOR_LENGTH(__vec) -= (__n); /* decrease length */ \ + }while(0) + + + +/// Clears the vector, freeing allocated data. +/// +/// @param __vec Vector +#define VECTOR_CLEAR(__vec) \ + do{ \ + if( VECTOR_CAPACITY(__vec) ) \ + { \ + aFree(VECTOR_DATA(__vec)); VECTOR_DATA(__vec) = NULL; /* clear allocated array */ \ + VECTOR_CAPACITY(__vec) = 0; /* clear capacity */ \ + VECTOR_LENGTH(__vec) = 0; /* clear length */ \ + } \ + }while(0) + + + +///////////////////////////////////////////////////////////////////// +// Binary heap library based on defines. (uses the vector defines above) +// uses aMalloc, aRealloc, aFree + + + +/// Declares an anonymous binary heap struct. +/// +/// @param __type Type of data +#define BHEAP_DECL(__type) VECTOR_DECL(__type) + + + +/// Declares a named binary heap struct. +/// +/// @param __name Structure name +/// @param __type Type of data +#define BHEAP_STRUCT_DECL(__name,__type) VECTOR_STRUCT_DECL(__name,__type) + + + +/// Declares and initializes an anonymous binary heap variable. +/// +/// @param __type Type of data +/// @param __var Variable name +#define BHEAP_VAR(__type,__var) VECTOR_VAR(__type,__var) + + + +/// Declares and initializes a named binary heap variable. +/// +/// @param __name Structure name +/// @param __var Variable name +#define BHEAP_STRUCT_VAR(__name,__var) VECTOR_STRUCT_VAR(__name,__var) + + + +/// Initializes a heap. +/// +/// @param __heap Binary heap +#define BHEAP_INIT(__heap) VECTOR_INIT(__heap) + + + +/// Returns the internal array of values. +/// +/// @param __heap Binary heap +/// @return Array of values +#define BHEAP_DATA(__heap) VECTOR_DATA(__heap) + + + +/// Returns the length of the heap. +/// +/// @param __heap Binary heap +/// @return Length +#define BHEAP_LENGTH(__heap) VECTOR_LENGTH(__heap) + + + +/// Returns the capacity of the heap. +/// +/// @param __heap Binary heap +/// @return Capacity +#define BHEAP_CAPACITY(__heap) VECTOR_CAPACITY(__heap) + + + +/// Ensures that the heap has the target number of empty positions. +/// Increases the capacity in multiples of __step. +/// +/// @param __heap Binary heap +/// @param __n Empty positions +/// @param __step Increase +#define BHEAP_ENSURE(__heap,__n,__step) VECTOR_ENSURE(__heap,__n,__step) + + + +/// Returns the top value of the heap. +/// Assumes the heap is not empty. +/// +/// @param __heap Binary heap +/// @return Value at the top +#define BHEAP_PEEK(__heap) VECTOR_INDEX(__heap,0) + + + +/// Inserts a value in the heap. (using the '=' operator) +/// Assumes there is enough capacity. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __val Value +/// @param __topcmp Comparator +#define BHEAP_PUSH(__heap,__val,__topcmp) \ + do{ \ + size_t _i_ = VECTOR_LENGTH(__heap); \ + VECTOR_PUSH(__heap,__val); /* insert at end */ \ + while( _i_ ) \ + { /* restore heap property in parents */ \ + size_t _parent_ = (_i_-1)/2; \ + if( __topcmp(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)) < 0 ) \ + break; /* done */ \ + swap(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)); \ + _i_ = _parent_; \ + } \ + }while(0) + + + +/// Removes the top value of the heap. (using the '=' operator) +/// Assumes the heap is not empty. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __topcmp Comparator +#define BHEAP_POP(__heap,__topcmp) BHEAP_POPINDEX(__heap,0,__topcmp) + + + +/// Removes the target value of the heap. (using the '=' operator) +/// Assumes the index exists. +/// +/// The comparator takes two values as arguments, returns: +/// - negative if the first value is on the top +/// - positive if the second value is on the top +/// - 0 if they are equal +/// +/// @param __heap Binary heap +/// @param __idx Index +/// @param __topcmp Comparator +#define BHEAP_POPINDEX(__heap,__idx,__topcmp) \ + do{ \ + size_t _i_ = __idx; \ + VECTOR_INDEX(__heap,__idx) = VECTOR_POP(__heap); /* put last at index */ \ + while( _i_ ) \ + { /* restore heap property in parents */ \ + size_t _parent_ = (_i_-1)/2; \ + if( __topcmp(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)) < 0 ) \ + break; /* done */ \ + swap(VECTOR_INDEX(__heap,_parent_),VECTOR_INDEX(__heap,_i_)); \ + _i_ = _parent_; \ + } \ + while( _i_ < VECTOR_LENGTH(__heap) ) \ + { /* restore heap property in childs */ \ + size_t _lchild_ = _i_*2 + 1; \ + size_t _rchild_ = _i_*2 + 2; \ + if( (_lchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_lchild_)) <= 0) && \ + (_rchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_rchild_)) <= 0) ) \ + break; /* done */ \ + else if( _rchild_ >= VECTOR_LENGTH(__heap) || __topcmp(VECTOR_INDEX(__heap,_lchild_),VECTOR_INDEX(__heap,_rchild_)) <= 0 ) \ + { /* left child */ \ + swap(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_lchild_)); \ + _i_ = _lchild_; \ + } \ + else \ + { /* right child */ \ + swap(VECTOR_INDEX(__heap,_i_),VECTOR_INDEX(__heap,_rchild_)); \ + _i_ = _rchild_; \ + } \ + } \ + }while(0) + + + +/// Clears the binary heap, freeing allocated data. +/// +/// @param __heap Binary heap +#define BHEAP_CLEAR(__heap) VECTOR_CLEAR(__heap) + + + +/// Generic comparator for a min-heap. (minimum value at top) +/// Returns -1 if v1 is smaller, 1 if v2 is smaller, 0 if equal. +/// +/// @param v1 First value +/// @param v2 Second value +/// @return negative if v1 is top, positive if v2 is top, 0 if equal +#define BHEAP_MINTOPCMP(v1,v2) ( v1 == v2 ? 0 : v1 < v2 ? -1 : 1 ) + + + +/// Generic comparator for a max-heap. (maximum value at top) +/// Returns -1 if v1 is bigger, 1 if v2 is bigger, 0 if equal. +/// +/// @param v1 First value +/// @param v2 Second value +/// @return negative if v1 is top, positive if v2 is top, 0 if equal +#define BHEAP_MAXTOPCMP(v1,v2) ( v1 == v2 ? 0 : v1 > v2 ? -1 : 1 ) + + + +#endif /* _DB_H_ */ diff --git a/src/common/des.c b/src/common/des.c new file mode 100644 index 000000000..917fc33e0 --- /dev/null +++ b/src/common/des.c @@ -0,0 +1,218 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#include "../common/cbasetypes.h" +#include "../common/des.h" + + +/// DES (Data Encryption Standard) algorithm, modified version. +/// @see http://www.eathena.ws/board/index.php?autocom=bugtracker&showbug=5099. +/// @see http://en.wikipedia.org/wiki/Data_Encryption_Standard +/// @see http://en.wikipedia.org/wiki/DES_supplementary_material + + +/// Bitmask for accessing individual bits of a byte. +static const uint8_t mask[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + + +/// Initial permutation (IP). +static void IP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t ip_table[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(ip_table); ++i ) + { + uint8_t j = ip_table[i] - 1; + if( src->b[(j >> 3) & 7] & mask[j & 7] ) + tmp .b[(i >> 3) & 7] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Final permutation (IP^-1). +static void FP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t fp_table[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(fp_table); ++i ) + { + uint8_t j = fp_table[i] - 1; + if( src->b[(j >> 3) & 7] & mask[j & 7] ) + tmp .b[(i >> 3) & 7] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Expansion (E). +/// Expands upper four 8-bits (32b) into eight 6-bits (48b). +static void E(BIT64* src) +{ + BIT64 tmp = {{0}}; + +if( false ) +{// original + static const uint8_t expand_table[48] = { + 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(expand_table); ++i ) + { + uint8_t j = expand_table[i] - 1; + if( src->b[j / 8 + 4] & mask[j % 8] ) + tmp .b[i / 6 + 0] |= mask[i % 6]; + } +} +else +{// optimized + tmp.b[0] = ((src->b[7]<<5) | (src->b[4]>>3)) & 0x3f; // ..0 vutsr + tmp.b[1] = ((src->b[4]<<1) | (src->b[5]>>7)) & 0x3f; // ..srqpo n + tmp.b[2] = ((src->b[4]<<5) | (src->b[5]>>3)) & 0x3f; // ..o nmlkj + tmp.b[3] = ((src->b[5]<<1) | (src->b[6]>>7)) & 0x3f; // ..kjihg f + tmp.b[4] = ((src->b[5]<<5) | (src->b[6]>>3)) & 0x3f; // ..g fedcb + tmp.b[5] = ((src->b[6]<<1) | (src->b[7]>>7)) & 0x3f; // ..cba98 7 + tmp.b[6] = ((src->b[6]<<5) | (src->b[7]>>3)) & 0x3f; // ..8 76543 + tmp.b[7] = ((src->b[7]<<1) | (src->b[4]>>7)) & 0x3f; // ..43210 v +} + + *src = tmp; +} + + +/// Transposition (P-BOX). +static void TP(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t tp_table[32] = { + 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25, + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(tp_table); ++i ) + { + uint8_t j = tp_table[i] - 1; + if( src->b[(j >> 3) + 0] & mask[j & 7] ) + tmp .b[(i >> 3) + 4] |= mask[i & 7]; + } + + *src = tmp; +} + + +/// Substitution boxes (S-boxes). +/// NOTE: This implementation was optimized to process two nibbles in one step (twice as fast). +static void SBOX(BIT64* src) +{ + BIT64 tmp = {{0}}; + + static const uint8_t s_table[4][64] = { + { + 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e, + 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85, + 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72, + 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9, + },{ + 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3, + 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19, + 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78, + 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce, + },{ + 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15, + 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68, + 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda, + 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d, + },{ + 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4, + 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62, + 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d, + 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb, + } + }; + + size_t i; + for( i = 0; i < ARRAYLENGTH(s_table); ++i ) + { + tmp.b[i] = (s_table[i][src->b[i*2+0]] & 0xf0) + | (s_table[i][src->b[i*2+1]] & 0x0f); + } + + *src = tmp; +} + + +/// DES round function. +/// XORs src[0..3] with TP(SBOX(E(src[4..7]))). +static void RoundFunction(BIT64* src) +{ + BIT64 tmp = *src; + E(&tmp); + SBOX(&tmp); + TP(&tmp); + + src->b[0] ^= tmp.b[4]; + src->b[1] ^= tmp.b[5]; + src->b[2] ^= tmp.b[6]; + src->b[3] ^= tmp.b[7]; +} + + +void des_decrypt_block(BIT64* block) +{ + IP(block); + RoundFunction(block); + FP(block); +} + + +void des_decrypt(unsigned char* data, size_t size) +{ + BIT64* p = (BIT64*)data; + size_t i; + + for( i = 0; i*8 < size; i += 8 ) + des_decrypt_block(p); +} diff --git a/src/common/des.h b/src/common/des.h new file mode 100644 index 000000000..e42136436 --- /dev/null +++ b/src/common/des.h @@ -0,0 +1,15 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +#ifndef _DES_H_ +#define _DES_H_ + + +/// One 64-bit block. +typedef struct BIT64 { uint8_t b[8]; } BIT64; + + +void des_decrypt_block(BIT64* block); +void des_decrypt(unsigned char* data, size_t size); + + +#endif // _DES_H_ diff --git a/src/common/ers.c b/src/common/ers.c new file mode 100644 index 000000000..b94b0888d --- /dev/null +++ b/src/common/ers.c @@ -0,0 +1,292 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * <H1>Entry Reusage System</H1> * + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + * <H2>Advantages:</H2> * + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + * <H2>Disavantages:</H2> * + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * 1.0 - ERS Rework * + * * + * @version 1.0 - ERS Rework * + * @author GreenBox @ rAthena Project * + * @encoding US-ASCII * + * @see common#ers.h * +\*****************************************************************************/ +#include <stdlib.h> + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree +#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL +#include "ers.h" + +#ifndef DISABLE_ERS + +#define ERS_ROOT_SIZE 256 +#define ERS_BLOCK_ENTRIES 4096 + +struct ers_list +{ + struct ers_list *Next; +}; + +typedef struct ers_cache +{ + // Allocated object size, including ers_list size + unsigned int ObjectSize; + + // Number of ers_instances referencing this + int ReferenceCount; + + // Reuse linked list + struct ers_list *ReuseList; + + // Memory blocks array + unsigned char **Blocks; + + // Max number of blocks + unsigned int Max; + + // Free objects count + unsigned int Free; + + // Used objects count + unsigned int Used; + + // Linked list + struct ers_cache *Next, *Prev; +} ers_cache_t; + +typedef struct +{ + // Interface to ERS + struct eri VTable; + + // Name, used for debbuging purpouses + char *Name; + + // Misc options + enum ERSOptions Options; + + // Our cache + ers_cache_t *Cache; + + // Count of objects in use, used for detecting memory leaks + unsigned int Count; +} ers_instance_t; + + +// Array containing a pointer for all ers_cache structures +static ers_cache_t *CacheList; + +static ers_cache_t *ers_find_cache(unsigned int size) +{ + ers_cache_t *cache; + + for (cache = CacheList; cache; cache = cache->Next) + if (cache->ObjectSize == size) + return cache; + + CREATE(cache, ers_cache_t, 1); + cache->ObjectSize = size; + cache->ReferenceCount = 0; + cache->ReuseList = NULL; + cache->Blocks = NULL; + cache->Free = 0; + cache->Used = 0; + cache->Max = 0; + + if (CacheList == NULL) + { + CacheList = cache; + } + else + { + cache->Next = CacheList; + cache->Next->Prev = cache; + CacheList = cache; + CacheList->Prev = NULL; + } + + return cache; +} + +static void ers_free_cache(ers_cache_t *cache, bool remove) +{ + unsigned int i; + + for (i = 0; i < cache->Used; i++) + aFree(cache->Blocks[i]); + + if (cache->Next) + cache->Next->Prev = cache->Prev; + + if (cache->Prev) + cache->Prev->Next = cache->Next; + else + CacheList = cache->Next; + + aFree(cache->Blocks); + aFree(cache); +} + +static void *ers_obj_alloc_entry(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + void *ret; + + if (instance == NULL) + { + ShowError("ers_obj_alloc_entry: NULL object, aborting entry freeing.\n"); + return NULL; + } + + if (instance->Cache->ReuseList != NULL) + { + ret = (void *)((unsigned char *)instance->Cache->ReuseList + sizeof(struct ers_list)); + instance->Cache->ReuseList = instance->Cache->ReuseList->Next; + } + else if (instance->Cache->Free > 0) + { + instance->Cache->Free--; + ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)]; + } + else + { + if (instance->Cache->Used == instance->Cache->Max) + { + instance->Cache->Max = (instance->Cache->Max * 4) + 3; + RECREATE(instance->Cache->Blocks, unsigned char *, instance->Cache->Max); + } + + CREATE(instance->Cache->Blocks[instance->Cache->Used], unsigned char, instance->Cache->ObjectSize * ERS_BLOCK_ENTRIES); + instance->Cache->Used++; + + instance->Cache->Free = ERS_BLOCK_ENTRIES -1; + ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)]; + } + + instance->Count++; + + return ret; +} + +static void ers_obj_free_entry(ERS self, void *entry) +{ + ers_instance_t *instance = (ers_instance_t *)self; + struct ers_list *reuse = (struct ers_list *)((unsigned char *)entry - sizeof(struct ers_list)); + + if (instance == NULL) + { + ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n"); + return; + } + else if (entry == NULL) + { + ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n"); + return; + } + + reuse->Next = instance->Cache->ReuseList; + instance->Cache->ReuseList = reuse; + instance->Count--; +} + +static size_t ers_obj_entry_size(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + + if (instance == NULL) + { + ShowError("ers_obj_entry_size: NULL object, aborting entry freeing.\n"); + return 0; + } + + return instance->Cache->ObjectSize; +} + +static void ers_obj_destroy(ERS self) +{ + ers_instance_t *instance = (ers_instance_t *)self; + + if (instance == NULL) + { + ShowError("ers_obj_destroy: NULL object, aborting entry freeing.\n"); + return; + } + + if (instance->Count > 0) + if (!(instance->Options & ERS_OPT_CLEAR)) + ShowWarning("Memory leak detected at ERS '%s', %d objects not freed.\n", instance->Name, instance->Count); + + if (--instance->Cache->ReferenceCount <= 0) + ers_free_cache(instance->Cache, true); + + aFree(instance); +} + +ERS ers_new(uint32 size, char *name, enum ERSOptions options) +{ + ers_instance_t *instance; + CREATE(instance, ers_instance_t, 1); + + size += sizeof(struct ers_list); + if (size % ERS_ALIGNED) + size += ERS_ALIGNED - size % ERS_ALIGNED; + + instance->VTable.alloc = ers_obj_alloc_entry; + instance->VTable.free = ers_obj_free_entry; + instance->VTable.entry_size = ers_obj_entry_size; + instance->VTable.destroy = ers_obj_destroy; + + instance->Name = name; + instance->Options = options; + + instance->Cache = ers_find_cache(size); + instance->Cache->ReferenceCount++; + + instance->Count = 0; + + return &instance->VTable; +} + +void ers_report(void) +{ + // FIXME: Someone use this? Is it really needed? +} + +void ers_force_destroy_all(void) +{ + ers_cache_t *cache; + + for (cache = CacheList; cache; cache = cache->Next) + ers_free_cache(cache, false); +} + +#endif diff --git a/src/common/ers.h b/src/common/ers.h new file mode 100644 index 000000000..dc66af5ef --- /dev/null +++ b/src/common/ers.h @@ -0,0 +1,172 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * <H1>Entry Reusage System</H1> * + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + * <H2>Advantages:</H2> * + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + * <H2>Disavantages:</H2> * + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * * + * @version 0.1 - Initial version * + * @author Flavio @ Amazon Project * + * @encoding US-ASCII * +\*****************************************************************************/ +#ifndef _ERS_H_ +#define _ERS_H_ + +#include "../common/cbasetypes.h" + +/*****************************************************************************\ + * (1) All public parts of the Entry Reusage System. * + * DISABLE_ERS - Define to disable this system. * + * ERS_ALIGNED - Alignment of the entries in the blocks. * + * ERS - Entry manager. * + * ers_new - Allocate an instance of an entry manager. * + * ers_report - Print a report about the current state. * + * ers_force_destroy_all - Force the destruction of all the managers. * +\*****************************************************************************/ + +/** + * Define this to disable the Entry Reusage System. + * All code except the typedef of ERInterface will be disabled. + * To allow a smooth transition, + */ +//#define DISABLE_ERS + +/** + * Entries are aligned to ERS_ALIGNED bytes in the blocks of entries. + * By default it aligns to one byte, using the "natural order" of the entries. + * This should NEVER be set to zero or less. + * If greater than one, some memory can be wasted. This should never be needed + * but is here just in case some aligment issues arise. + */ +#ifndef ERS_ALIGNED +# define ERS_ALIGNED 1 +#endif /* not ERS_ALIGN_ENTRY */ + +enum ERSOptions { + ERS_OPT_NONE = 0, + ERS_OPT_CLEAR = 1,/* silently clears any entries left in the manager upon destruction */ +}; + +/** + * Public interface of the entry manager. + * @param alloc Allocate an entry from this manager + * @param free Free an entry allocated from this manager + * @param entry_size Return the size of the entries of this manager + * @param destroy Destroy this instance of the manager + */ +typedef struct eri { + + /** + * Allocate an entry from this entry manager. + * If there are reusable entries available, it reuses one instead. + * @param self Interface of the entry manager + * @return An entry + */ + void *(*alloc)(struct eri *self); + + /** + * Free an entry allocated from this manager. + * WARNING: Does not check if the entry was allocated by this manager. + * Freeing such an entry can lead to unexpected behaviour. + * @param self Interface of the entry manager + * @param entry Entry to be freed + */ + void (*free)(struct eri *self, void *entry); + + /** + * Return the size of the entries allocated from this manager. + * @param self Interface of the entry manager + * @return Size of the entries of this manager in bytes + */ + size_t (*entry_size)(struct eri *self); + + /** + * Destroy this instance of the manager. + * The manager is actually only destroyed when all the instances are destroyed. + * When destroying the manager a warning is shown if the manager has + * missing/extra entries. + * @param self Interface of the entry manager + */ + void (*destroy)(struct eri *self); + +} *ERS; + +#ifdef DISABLE_ERS +// Use memory manager to allocate/free and disable other interface functions +# define ers_alloc(obj,type) (type *)aMalloc(sizeof(type)) +# define ers_free(obj,entry) aFree(entry) +# define ers_entry_size(obj) (size_t)0 +# define ers_destroy(obj) +// Disable the public functions +# define ers_new(size,name,options) NULL +# define ers_report() +# define ers_force_destroy_all() +#else /* not DISABLE_ERS */ +// These defines should be used to allow the code to keep working whenever +// the system is disabled +# define ers_alloc(obj,type) (type *)(obj)->alloc(obj) +# define ers_free(obj,entry) (obj)->free((obj),(entry)) +# define ers_entry_size(obj) (obj)->entry_size(obj) +# define ers_destroy(obj) (obj)->destroy(obj) + +/** + * Get a new instance of the manager that handles the specified entry size. + * Size has to greater than 0. + * If the specified size is smaller than a pointer, the size of a pointer is + * used instead. + * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of + * ERS_ALIGNED that is greater or equal to size is what's actually used. + * @param The requested size of the entry in bytes + * @return Interface of the object + */ +ERS ers_new(uint32 size, char *name, enum ERSOptions options); + +/** + * Print a report about the current state of the Entry Reusage System. + * Shows information about the global system and each entry manager. + * The number of entries are checked and a warning is shown if extra reusable + * entries are found. + * The extra entries are included in the count of reusable entries. + */ +void ers_report(void); + +/** + * Forcibly destroy all the entry managers, checking for nothing. + * The system is left as if no instances or entries had ever been allocated. + * All previous entries and instances of the managers become invalid. + * The use of this is NOT recommended. + * It should only be used in extreme situations to make shure all the memory + * allocated by this system is released. + */ +void ers_force_destroy_all(void); +#endif /* DISABLE_ERS / not DISABLE_ERS */ + +#endif /* _ERS_H_ */ diff --git a/src/common/evdp.h b/src/common/evdp.h new file mode 100644 index 000000000..bc3454686 --- /dev/null +++ b/src/common/evdp.h @@ -0,0 +1,168 @@ +#ifndef _rA_EVDP_H_ +#define _rA_EVDP_H_ + +#include "../common/cbasetypes.h" + +typedef struct EVDP_DATA EVDP_DATA; + + +//#idef EVDP_EPOLL +#include <sys/epoll.h> +struct EVDP_DATA{ + struct epoll_event ev_data; + bool ev_added; +}; +//#endif + + +enum EVDP_EVENTFLAGS{ + EVDP_EVENT_IN = 1, // Incomming data + EVDP_EVENT_OUT = 2, // Connection accepts writing. + EVDP_EVENT_HUP = 4 // Connection Closed. +}; + +typedef struct EVDP_EVENT{ + int32 events; // due to performance reasons, this should be the first member. + int32 fd; // Connection Identifier +} EVDP_EVENT; + + + +/** + * Network Event Dispatcher Initialization / Finalization routines + */ +void evdp_init(); +void evdp_final(); + + +/** + * Will Wait for events. + * + * @param *out_ev pointer to array in size at least of max_events. + * @param max_events max no of events to report with this call (coalesc) + * @param timeout_ticks max time to wait in ticks (milliseconds) + * + * @Note: + * The function will block until an event has occured on one of the monitored connections + * or the timeout of timeout_ticks has passed by. + * Upon successfull call (changed connections) this function will write the connection + * Identifier & event to the out_fds array. + * + * @return 0 -> Timeout, > 0 no of changed connections. + */ +int32 evdp_wait(EVDP_EVENT *out_fds, int32 max_events, int32 timeout_ticks); + + +/** + * Applys the given mask on the given connection. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * @param mask new event mask we're monitoring for. + */ +//void evdp_apply(int32 fd, EVDP_DATA *ep, int32 mask); + + +/** + * Adds a connection (listner) to the event notification system. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * + * @note: + * Listener type sockets are edge triggered, (see epoll manual for more information) + * - This basicaly means that youll receive one event, adn you have to accept until accept returns an error (nothing to accept) + * + * MONITORS by default: IN + * + * @return success indicator. + */ +bool evdp_addlistener(int32 fd, EVDP_DATA *ep); + +/** + * Adds a connection (client connectioN) to the event notification system + * + * @param fd connection identifier + * @param *ep event data pointr for the connection + * + * @note: + * + * MONITORS by default: IN, HUP + * + * @return success indicator. + */ +bool evdp_addclient(int32 fd, EVDP_DATA *ep); + +/** + * Adds a connection (pending / outgoing connection!) to the event notification system. + * + * @param fd connection identifier + * @param *ep event data pointer for the conneciton. + * + * @note: + * Outgoing connection type sockets are getting monitored for connection established + * successfull + * - if the connection has been established - we're generitng a writable notification .. (send) + * this is typical for BSD / posix conform network stacks. + * - Additinionally its edge triggered. + * + * @see evdp_outgoingconnection_established + * + * + * @return success indicator + */ +bool evdp_addconnecting(int32 fd, EVDP_DATA *ep); + +/** + * Adds an outgoing connection to the normal event notification system after it has been successfully established. + * + * @param fd connection identifier + * @param *ep event data pointer for the conneciton. + + * @note + * after this call, its handled like a normal "client" connection (incomming) + * + * @rturn success indicator + */ +bool evdp_outgoingconnection_established(int32 fd, EVDP_DATA *ep); + +/** + * Marks a connection to be monitored for writable. + * + * @param fd connection identifier + * @param *ep event data pointer for the connection + * + * @note: + * the connection must be already added (as client or listener) + * + * + * @return sucess indicator + */ +bool evdp_writable_add(int32 fd, EVDP_DATA *ep); + +/** + * Removes the connection from writable notification monitoring + * + * @param fd connection identifier + * @param *ep event data pointr for the connection + * + */ +void evdp_writable_remove(int32 fd, EVDP_DATA *ep); + +/** + * Removes an connectio from the event notification system. + * + * @param fd connection iditentfir + * @param *ep event data pointer for th connection + * + * + * @note: + * this will also clear the given EVENT_DATA block + * so the connection slot is in an "initial" blank status / ready to get reused. + * + */ +void evdp_remove(int32 fd, EVDP_DATA *ep); + + + +#endif diff --git a/src/common/evdp_epoll.c b/src/common/evdp_epoll.c new file mode 100644 index 000000000..0357dfc66 --- /dev/null +++ b/src/common/evdp_epoll.c @@ -0,0 +1,232 @@ +// +// Event Dispatcher Abstraction for EPOLL +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/epoll.h> +#include <sys/fcntl.h> +#include <sys/socket.h> + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/evdp.h" + + +#define EPOLL_MAX_PER_CYCLE 10 // Max Events to coalesc. per cycle. + + +static int epoll_fd = -1; + + +void evdp_init(){ + + epoll_fd = epoll_create( EPOLL_MAX_PER_CYCLE ); + if(epoll_fd == -1){ + ShowFatalError("evdp [EPOLL]: Cannot create event dispatcher (errno: %u / %s)\n", errno, strerror(errno) ); + exit(1); + } + +}//end: evdp_init() + + +void evdp_final(){ + + if(epoll_fd != -1){ + close(epoll_fd); + epoll_fd = -1; + } + +}//end: evdp_final() + + +int32 evdp_wait(EVDP_EVENT *out_fds, int32 max_events, int32 timeout_ticks){ + struct epoll_event l_events[EPOLL_MAX_PER_CYCLE]; + register struct epoll_event *ev; + register int nfds, n; + + if(max_events > EPOLL_MAX_PER_CYCLE) + max_events = EPOLL_MAX_PER_CYCLE; + + nfds = epoll_wait( epoll_fd, l_events, max_events, timeout_ticks); + if(nfds == -1){ + // @TODO: check if core is in shutdown mode. if - ignroe error. + + ShowFatalError("evdp [EPOLL]: epoll_wait returned bad / unexpected status (errno: %u / %s)\n", errno, strerror(errno)); + exit(1); //.. + } + + // Loop thru all events and copy it to the local ra evdp_event.. struct. + for(n = 0; n < nfds; n++){ + ev = &l_events[n]; + + out_fds->fd = ev->data.fd; + out_fds->events = 0; // clear + + if(ev->events & EPOLLHUP) + out_fds->events |= EVDP_EVENT_HUP; + + if(ev->events & EPOLLIN) + out_fds->events |= EVDP_EVENT_IN; + + if(ev->events & EPOLLOUT) + out_fds->events |= EVDP_EVENT_OUT; + + out_fds++; + } + + return nfds; // 0 on timeout or > 0 .. +}//end: evdp_wait() + + +void evdp_remove(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added == true){ + + if( epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_remove - epoll_ctl (EPOLL_CTL_DEL) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + } + + ep->ev_data.events = 0; // clear struct. + ep->ev_data.data.fd = -1; // .. clear struct .. + + ep->ev_added = false; // not added! + } + + +}//end: evdp_remove() + + +bool evdp_addlistener(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLET|EPOLLIN; + ep->ev_data.data.fd = fd; + + // No check here for 'added ?' + // listeners cannot be added twice. + // + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0 ){ + ShowError("evdp [EPOLL]: evdp_addlistener - epoll_ctl (EPOLL_CTL_ADD) faield! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + return false; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addlistener() + + +bool evdp_addclient(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLIN | EPOLLHUP; + ep->ev_data.data.fd = fd; + + // No check for "added?" here, + // this function only gets called upon accpept. + // + + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_addclient - epoll_ctl (EPOLL_CTL_ADD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + return false; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addclient() + + +bool evdp_addconnecting(int32 fd, EVDP_DATA *ep){ + + ep->ev_data.events = EPOLLET | EPOLLOUT | EPOLLHUP; + ep->ev_data.data.fd = fd; + + if( epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_addconnecting - epoll_ctl (EPOLL_CTL_ADD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events = 0; + ep->ev_data.data.fd = -1; + } + + ep->ev_added = true; + + return true; +}//end: evdp_addconnecting() + + +bool evdp_outgoingconnection_established(int32 fd, EVDP_DATA *ep){ + int32 saved_mask; + + if(ep->ev_added != true){ + // ! + ShowError("evdp [EPOLL]: evdp_outgoingconnection_established fd #%u is not added to event dispatcher! invalid call.\n", fd); + return false; + } + + saved_mask = ep->ev_data.events; + + ep->ev_data.events = EPOLLIN | EPOLLHUP; + + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0){ + ep->ev_data.events = saved_mask; // restore old mask. + ShowError("evdp [EPOLL]: evdp_outgoingconnection_established - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + return false; + } + + return true; +}//end: evdp_outgoingconnection_established() + + +bool evdp_writable_add(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added != true){ + ShowError("evdp [EPOLL]: evdp_writable_add - tried to add not added fd #%u\n",fd); + return false; + } + + if(! (ep->ev_data.events & EPOLLOUT) ){ // + + ep->ev_data.events |= EPOLLOUT; + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0 ){ + ShowError("evdp [EPOLL]: evdp_writable_add - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno: %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events &= ~EPOLLOUT; // remove from local flagmask due to failed syscall. + return false; + } + } + + return true; +}//end: evdp_writable_add() + + +void evdp_writable_remove(int32 fd, EVDP_DATA *ep){ + + if(ep->ev_added != true){ + ShowError("evdp [EPOLL]: evdp_writable_remove - tried to remove not added fd #%u\n", fd); + return; + } + + if( ep->ev_data.events & EPOLLOUT ){ + + ep->ev_data.events &= ~EPOLLOUT; + if( epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ep->ev_data) != 0){ + ShowError("evdp [EPOLL]: evdp_writable_remove - epoll_ctl (EPOLL_CTL_MOD) failed! fd #%u (errno %u / %s)\n", fd, errno, strerror(errno)); + ep->ev_data.events |= EPOLLOUT; // add back to local flagmask because of failed syscall. + return; + } + } + + return; +}//end: evdp_writable_remove() diff --git a/src/common/grfio.c b/src/common/grfio.c new file mode 100644 index 000000000..8f430cfb9 --- /dev/null +++ b/src/common/grfio.c @@ -0,0 +1,818 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/des.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/utils.h" +#include "grfio.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <zlib.h> + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct _FILELIST { + int srclen; // compressed size + int srclen_aligned; + int declen; // original size + int srcpos; // position of entry in grf + int next; // index of next filelist entry with same hash (-1: end of entry chain) + char type; + char fn[128-4*5]; // file name + char* fnd; // if the file was cloned, contains name of original file + char gentry; // read grf file select +} FILELIST; + +#define FILELIST_TYPE_FILE 0x01 // entry is a file +#define FILELIST_TYPE_ENCRYPT_HEADER 0x04 // encryption mode 1 (header DES only) +#define FILELIST_TYPE_ENCRYPT_MIXED 0x02 // encryption mode 0 (header DES + periodic DES/shuffle) + +//gentry ... > 0 : data read from a grf file (gentry_table[gentry-1]) +//gentry ... 0 : data read from a local file (data directory) +//gentry ... < 0 : entry "-(gentry)" is marked for a local file check +// - if local file exists, gentry will be set to 0 (thus using the local file) +// - if local file doesn't exist, sign is inverted (thus using the original file inside a grf) +// (NOTE: this case is only used once (during startup) and only if GRFIO_LOCAL is enabled) +// (NOTE: probably meant to be used to override grf contents by files in the data directory) +//#define GRFIO_LOCAL + + +// stores info about every loaded file +FILELIST* filelist = NULL; +int filelist_entrys = 0; +int filelist_maxentry = 0; + +// stores grf file names +char** gentry_table = NULL; +int gentry_entrys = 0; +int gentry_maxentry = 0; + +// the path to the data directory +char data_dir[1024] = ""; + + +// little endian char array to uint conversion +static unsigned int getlong(unsigned char* p) +{ + return (p[0] << 0 | p[1] << 8 | p[2] << 16 | p[3] << 24); +} + + +static void NibbleSwap(unsigned char* src, int len) +{ + while( len > 0 ) + { + *src = (*src >> 4) | (*src << 4); + ++src; + --len; + } +} + + +/// Substitutes some specific values for others, leaves rest intact. Obfuscation. +/// NOTE: Operation is symmetric (calling it twice gives back the original input). +static uint8_t grf_substitution(uint8_t in) +{ + uint8_t out; + + switch( in ) + { + case 0x00: out = 0x2B; break; + case 0x2B: out = 0x00; break; + case 0x6C: out = 0x80; break; + case 0x01: out = 0x68; break; + case 0x68: out = 0x01; break; + case 0x48: out = 0x77; break; + case 0x60: out = 0xFF; break; + case 0x77: out = 0x48; break; + case 0xB9: out = 0xC0; break; + case 0xC0: out = 0xB9; break; + case 0xFE: out = 0xEB; break; + case 0xEB: out = 0xFE; break; + case 0x80: out = 0x6C; break; + case 0xFF: out = 0x60; break; + default: out = in; break; + } + + return out; +} + +/* this is not used anywhere, is it ok to delete? */ +//static void grf_shuffle_enc(BIT64* src) { +// BIT64 out; +// +// out.b[0] = src->b[3]; +// out.b[1] = src->b[4]; +// out.b[2] = src->b[5]; +// out.b[3] = src->b[0]; +// out.b[4] = src->b[1]; +// out.b[5] = src->b[6]; +// out.b[6] = src->b[2]; +// out.b[7] = grf_substitution(src->b[7]); +// +// *src = out; +//} + + +static void grf_shuffle_dec(BIT64* src) +{ + BIT64 out; + + out.b[0] = src->b[3]; + out.b[1] = src->b[4]; + out.b[2] = src->b[6]; + out.b[3] = src->b[0]; + out.b[4] = src->b[1]; + out.b[5] = src->b[2]; + out.b[6] = src->b[5]; + out.b[7] = grf_substitution(src->b[7]); + + *src = out; +} + + +static void grf_decode_header(unsigned char* buf, size_t len) +{ + BIT64* p = (BIT64*)buf; + size_t nblocks = len / sizeof(BIT64); + size_t i; + + // first 20 blocks are all des-encrypted + for( i = 0; i < 20 && i < nblocks; ++i ) + des_decrypt_block(&p[i]); + + // the rest is plaintext, done. +} + + +static void grf_decode_full(unsigned char* buf, size_t len, int cycle) +{ + BIT64* p = (BIT64*)buf; + size_t nblocks = len / sizeof(BIT64); + int dcycle, scycle; + size_t i, j; + + // first 20 blocks are all des-encrypted + for( i = 0; i < 20 && i < nblocks; ++i ) + des_decrypt_block(&p[i]); + + // after that only one of every 'dcycle' blocks is des-encrypted + dcycle = cycle; + + // and one of every 'scycle' plaintext blocks is shuffled (starting from the 0th but skipping the 0th) + scycle = 7; + + // so decrypt/de-shuffle periodically + j = -1; // 0, adjusted to fit the ++j step + for( i = 20; i < nblocks; ++i ) + { + if( i % dcycle == 0 ) + {// decrypt block + des_decrypt_block(&p[i]); + continue; + } + + ++j; + if( j % scycle == 0 && j != 0 ) + {// de-shuffle block + grf_shuffle_dec(&p[i]); + continue; + } + + // plaintext, do nothing. + } +} + + +/// Decodes grf data. +/// @param buf data to decode (in-place) +/// @param len length of the data +/// @param entry_type flags associated with the data +/// @param entry_len true (unaligned) length of the data +static void grf_decode(unsigned char* buf, size_t len, char entry_type, int entry_len) +{ + if( entry_type & FILELIST_TYPE_ENCRYPT_MIXED ) + {// fully encrypted + int digits; + int cycle; + int i; + + // compute number of digits of the entry length + digits = 1; + for( i = 10; i <= entry_len; i *= 10 ) + ++digits; + + // choose size of gap between two encrypted blocks + // digits: 0 1 2 3 4 5 6 7 8 9 ... + // cycle: 1 1 1 4 5 14 15 22 23 24 ... + cycle = ( digits < 3 ) ? 1 + : ( digits < 5 ) ? digits + 1 + : ( digits < 7 ) ? digits + 9 + : digits + 15; + + grf_decode_full(buf, len, cycle); + } + else + if( entry_type & FILELIST_TYPE_ENCRYPT_HEADER ) + {// header encrypted + grf_decode_header(buf, len); + } + else + {// plaintext + ; + } +} + + +/****************************************************** + *** Zlib Subroutines *** + ******************************************************/ + +/// zlib crc32 +unsigned long grfio_crc32(const unsigned char* buf, unsigned int len) +{ + return crc32(crc32(0L, Z_NULL, 0), buf, len); +} + + +/// zlib uncompress +int decode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen) +{ + return uncompress((Bytef*)dest, destLen, (const Bytef*)source, sourceLen); +} + + +/// zlib compress +int encode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen) +{ + return compress((Bytef*)dest, destLen, (const Bytef*)source, sourceLen); +} + + +/*********************************************************** + *** File List Subroutines *** + ***********************************************************/ +// file list hash table +int filelist_hash[256]; + +// initializes the table that holds the first elements of all hash chains +static void hashinit(void) +{ + int i; + for (i = 0; i < 256; i++) + filelist_hash[i] = -1; +} + +// hashes a filename string into a number from {0..255} +static int filehash(const char* fname) +{ + unsigned int hash = 0; + while(*fname) { + hash = (hash<<1) + (hash>>7)*9 + TOLOWER(*fname); + fname++; + } + return hash & 255; +} + +// finds a FILELIST entry with the specified file name +static FILELIST* filelist_find(const char* fname) +{ + int hash, index; + + if (!filelist) + return NULL; + + hash = filelist_hash[filehash(fname)]; + for (index = hash; index != -1; index = filelist[index].next) + if(!strcmpi(filelist[index].fn, fname)) + break; + + return (index >= 0) ? &filelist[index] : NULL; +} + +// returns the original file name +char* grfio_find_file(const char* fname) +{ + FILELIST *filelist = filelist_find(fname); + if (!filelist) return NULL; + return (!filelist->fnd ? filelist->fn : filelist->fnd); +} + +// adds a FILELIST entry into the list of loaded files +static FILELIST* filelist_add(FILELIST* entry) +{ + int hash; + + #define FILELIST_ADDS 1024 // number increment of file lists ` + + if (filelist_entrys >= filelist_maxentry) { + filelist = (FILELIST *)aRealloc(filelist, (filelist_maxentry + FILELIST_ADDS) * sizeof(FILELIST)); + memset(filelist + filelist_maxentry, '\0', FILELIST_ADDS * sizeof(FILELIST)); + filelist_maxentry += FILELIST_ADDS; + } + + memcpy (&filelist[filelist_entrys], entry, sizeof(FILELIST)); + + hash = filehash(entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys - 1]; +} + +// adds a new FILELIST entry or overwrites an existing one +static FILELIST* filelist_modify(FILELIST* entry) +{ + FILELIST* fentry = filelist_find(entry->fn); + if (fentry != NULL) { + int tmp = fentry->next; + memcpy(fentry, entry, sizeof(FILELIST)); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +// shrinks the file list array if too long +static void filelist_compact(void) +{ + if (filelist == NULL) + return; + + if (filelist_entrys < filelist_maxentry) { + filelist = (FILELIST *)aRealloc(filelist, filelist_entrys * sizeof(FILELIST)); + filelist_maxentry = filelist_entrys; + } +} + + +/*********************************************************** + *** Grfio Subroutines *** + ***********************************************************/ + + +/// Combines are resource path with the data folder location to create local resource path. +static void grfio_localpath_create(char* buffer, size_t size, const char* filename) +{ + unsigned int i; + size_t len; + + len = strlen(data_dir); + + if( data_dir[0] == '\0' || data_dir[len-1] == '/' || data_dir[len-1] == '\\' ) + { + safesnprintf(buffer, size, "%s%s", data_dir, filename); + } + else + { + safesnprintf(buffer, size, "%s/%s", data_dir, filename); + } + + // normalize path + for( i = 0; buffer[i] != '\0'; ++i ) + if( buffer[i] == '\\' ) + buffer[i] = '/'; +} + + +/// Reads a file into a newly allocated buffer (from grf or data directory). +void* grfio_reads(const char* fname, int* size) +{ + unsigned char* buf2 = NULL; + + FILELIST* entry = filelist_find(fname); + if( entry == NULL || entry->gentry <= 0 ) {// LocalFileCheck + char lfname[256]; + int declen; + FILE* in; + grfio_localpath_create(lfname, sizeof(lfname), ( entry && entry->fnd ) ? entry->fnd : fname); + + in = fopen(lfname, "rb"); + if( in != NULL ) { + fseek(in,0,SEEK_END); + declen = ftell(in); + fseek(in,0,SEEK_SET); + buf2 = (unsigned char *)aMalloc(declen+1); // +1 for resnametable zero-termination + if(fread(buf2, 1, declen, in) != declen) ShowError("An error occured in fread grfio_reads, fname=%s \n",fname); + fclose(in); + + if( size ) + *size = declen; + } else { + if (entry != NULL && entry->gentry < 0) { + entry->gentry = -entry->gentry; // local file checked + } else { + ShowError("grfio_reads: %s not found (local file: %s)\n", fname, lfname); + return NULL; + } + } + } + + if( entry != NULL && entry->gentry > 0 ) {// Archive[GRF] File Read + char* grfname = gentry_table[entry->gentry - 1]; + FILE* in = fopen(grfname, "rb"); + if( in != NULL ) { + int fsize = entry->srclen_aligned; + unsigned char *buf = (unsigned char *)aMalloc(fsize); + fseek(in, entry->srcpos, 0); + if(fread(buf, 1, fsize, in) != fsize) ShowError("An error occured in fread in grfio_reads, grfname=%s\n",grfname); + fclose(in); + + buf2 = (unsigned char *)aMalloc(entry->declen+1); // +1 for resnametable zero-termination + if( entry->type & FILELIST_TYPE_FILE ) + {// file + uLongf len; + grf_decode(buf, fsize, entry->type, entry->srclen); + len = entry->declen; + decode_zip(buf2, &len, buf, entry->srclen); + if (len != (uLong)entry->declen) { + ShowError("decode_zip size mismatch err: %d != %d\n", (int)len, entry->declen); + aFree(buf); + aFree(buf2); + return NULL; + } + } else {// directory? + memcpy(buf2, buf, entry->declen); + } + + if( size ) + *size = entry->declen; + + aFree(buf); + } else { + ShowError("grfio_reads: %s not found (GRF file: %s)\n", fname, grfname); + return NULL; + } + } + + return buf2; +} + + +/// Decodes encrypted filename from a version 01xx grf index. +static char* decode_filename(unsigned char* buf, int len) +{ + int lop; + for(lop=0;lop<len;lop+=8) { + NibbleSwap(&buf[lop],8); + des_decrypt(&buf[lop],8); + } + return (char*)buf; +} + + +/// Compares file extension against known large file types. +/// @return true if the file should undergo full mode 0 decryption, and true otherwise. +static bool isFullEncrypt(const char* fname) +{ + static const char extensions[4][5] = { ".gnd", ".gat", ".act", ".str" }; + size_t i; + + const char* ext = strrchr(fname, '.'); + if( ext != NULL ) + for( i = 0; i < ARRAYLENGTH(extensions); ++i ) + if( strcmpi(ext, extensions[i]) == 0 ) + return false; + + return true; +} + + +/// Loads all entries in the specified grf file into the filelist. +/// @param gentry index of the grf file name in the gentry_table +static int grfio_entryread(const char* grfname, int gentry) +{ + long grf_size,list_size; + unsigned char grf_header[0x2e]; + int entry,entrys,ofs,grf_version; + unsigned char *grf_filelist; + + FILE* fp = fopen(grfname, "rb"); + if( fp == NULL ) { + ShowWarning("GRF data file not found: '%s'\n",grfname); + return 1; // 1:not found error + } else + ShowInfo("GRF data file found: '%s'\n",grfname); + + fseek(fp,0,SEEK_END); + grf_size = ftell(fp); + fseek(fp,0,SEEK_SET); + + if(fread(grf_header,1,0x2e,fp) != 0x2e) { ShowError("Couldn't read all grf_header element of %s \n", grfname); } + if( strcmp((const char*)grf_header,"Master of Magic") != 0 || fseek(fp,getlong(grf_header+0x1e),SEEK_CUR) != 0 ) { + fclose(fp); + ShowError("GRF %s read error\n", grfname); + return 2; // 2:file format error + } + + grf_version = getlong(grf_header+0x2a) >> 8; + + if( grf_version == 0x01 ) {// ****** Grf version 01xx ****** + list_size = grf_size - ftell(fp); + grf_filelist = (unsigned char *) aMalloc(list_size); + if(fread(grf_filelist,1,list_size,fp) != list_size) { ShowError("Couldn't read all grf_filelist element of %s \n", grfname); } + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for( entry = 0, ofs = 0; entry < entrys; ++entry ) { + FILELIST aentry; + + int ofs2 = ofs+getlong(grf_filelist+ofs)+4; + unsigned char type = grf_filelist[ofs2+12]; + if( type & FILELIST_TYPE_FILE ) { + char* fname = decode_filename(grf_filelist+ofs+6, grf_filelist[ofs]-6); + int srclen = getlong(grf_filelist+ofs2+0) - getlong(grf_filelist+ofs2+8) - 715; + + if( strlen(fname) > sizeof(aentry.fn) - 1 ) { + ShowFatalError("GRF file name %s is too long\n", fname); + aFree(grf_filelist); + exit(EXIT_FAILURE); + } + + type |= ( isFullEncrypt(fname) ) ? FILELIST_TYPE_ENCRYPT_MIXED : FILELIST_TYPE_ENCRYPT_HEADER; + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4)-37579; + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.type = type; + safestrncpy(aentry.fn, fname, sizeof(aentry.fn)); + aentry.fnd = NULL; +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + + ofs = ofs2 + 17; + } + + aFree(grf_filelist); + } else if( grf_version == 0x02 ) {// ****** Grf version 02xx ****** + unsigned char eheader[8]; + unsigned char *rBuf; + uLongf rSize, eSize; + + if(fread(eheader,1,8,fp) != 8) ShowError("An error occured in fread while reading eheader buffer\n"); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if( (long)rSize > grf_size-ftell(fp) ) { + fclose(fp); + ShowError("Illegal data format: GRF compress entry size\n"); + return 4; + } + + rBuf = (unsigned char *)aMalloc(rSize); // Get a Read Size + grf_filelist = (unsigned char *)aMalloc(eSize); // Get a Extend Size + if(fread(rBuf,1,rSize,fp) != rSize) ShowError("An error occured in fread \n"); + fclose(fp); + decode_zip(grf_filelist, &eSize, rBuf, rSize); // Decode function + aFree(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for( entry = 0, ofs = 0; entry < entrys; ++entry ) { + FILELIST aentry; + + char* fname = (char*)(grf_filelist+ofs); + int ofs2 = ofs + (int)strlen(fname)+1; + int type = grf_filelist[ofs2+12]; + + if( strlen(fname) > sizeof(aentry.fn)-1 ) { + ShowFatalError("GRF file name %s is too long\n", fname); + aFree(grf_filelist); + exit(EXIT_FAILURE); + } + + if( type & FILELIST_TYPE_FILE ) {// file + aentry.srclen = getlong(grf_filelist+ofs2+0); + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.type = type; + safestrncpy(aentry.fn, fname, sizeof(aentry.fn)); + aentry.fnd = NULL; +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + + ofs = ofs2 + 17; + } + + aFree(grf_filelist); + } else {// ****** Grf Other version ****** + fclose(fp); + ShowError("GRF version %04x not supported\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_compact(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + + +static bool grfio_parse_restable_row(const char* row) +{ + char w1[256], w2[256]; + char src[256], dst[256]; + char local[256]; + FILELIST* entry; + + if( sscanf(row, "%[^#\r\n]#%[^#\r\n]#", w1, w2) != 2 ) + return false; + + if( strstr(w2, ".gat") == NULL && strstr(w2, ".rsw") == NULL ) + return false; // we only need the maps' GAT and RSW files + + sprintf(src, "data\\%s", w1); + sprintf(dst, "data\\%s", w2); + + entry = filelist_find(dst); + if( entry != NULL ) + {// alias for GRF resource + FILELIST fentry; + memcpy(&fentry, entry, sizeof(FILELIST)); + safestrncpy(fentry.fn, src, sizeof(fentry.fn)); + fentry.fnd = aStrdup(dst); + filelist_modify(&fentry); + return true; + } + + grfio_localpath_create(local, sizeof(local), dst); + if( exists(local) ) + {// alias for local resource + FILELIST fentry; + memset(&fentry, 0, sizeof(fentry)); + safestrncpy(fentry.fn, src, sizeof(fentry.fn)); + fentry.fnd = aStrdup(dst); + filelist_modify(&fentry); + return true; + } + + return false; +} + + +/// Grfio Resource file check. +static void grfio_resourcecheck(void) +{ + char restable[256]; + char *ptr, *buf; + int size; + FILE* fp; + int i = 0; + + // read resnametable from data directory and return if successful + grfio_localpath_create(restable, sizeof(restable), "data\\resnametable.txt"); + + fp = fopen(restable, "rb"); + if( fp != NULL ) + { + char line[256]; + while( fgets(line, sizeof(line), fp) ) + { + if( grfio_parse_restable_row(line) ) + ++i; + } + + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, "resnametable.txt"); + return; // we're done here! + } + + // read resnametable from loaded GRF's, only if it cannot be loaded from the data directory + buf = (char *)grfio_reads("data\\resnametable.txt", &size); + if( buf != NULL ) + { + buf[size] = '\0'; + + ptr = buf; + while( ptr - buf < size ) + { + if( grfio_parse_restable_row(ptr) ) + ++i; + + ptr = strchr(ptr, '\n'); + if( ptr == NULL ) break; + ptr++; + } + + aFree(buf); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, "data\\resnametable.txt"); + return; + } +} + + +/// Reads a grf file and adds it to the list. +static int grfio_add(const char* fname) +{ + if( gentry_entrys >= gentry_maxentry ) + { + #define GENTRY_ADDS 4 // The number increment of gentry_table entries + gentry_maxentry += GENTRY_ADDS; + gentry_table = (char**)aRealloc(gentry_table, gentry_maxentry * sizeof(char*)); + memset(gentry_table + (gentry_maxentry - GENTRY_ADDS), 0, sizeof(char*) * GENTRY_ADDS); + } + + gentry_table[gentry_entrys++] = aStrdup(fname); + + return grfio_entryread(fname, gentry_entrys - 1); +} + + +/// Finalizes grfio. +void grfio_final(void) +{ + if (filelist != NULL) { + int i; + for (i = 0; i < filelist_entrys; i++) + if (filelist[i].fnd != NULL) + aFree(filelist[i].fnd); + + aFree(filelist); + filelist = NULL; + } + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table != NULL) { + int i; + for (i = 0; i < gentry_entrys; i++) + if (gentry_table[i] != NULL) + aFree(gentry_table[i]); + + aFree(gentry_table); + gentry_table = NULL; + } + gentry_entrys = gentry_maxentry = 0; +} + + +/// Initializes grfio. +void grfio_init(const char* fname) +{ + FILE* data_conf; + int grf_num = 0; + + hashinit(); // hash table initialization + + data_conf = fopen(fname, "r"); + if( data_conf != NULL ) + { + char line[1024]; + while( fgets(line, sizeof(line), data_conf) ) + { + char w1[1024], w2[1024]; + + if( line[0] == '/' && line[1] == '/' ) + continue; // skip comments + + if( sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2 ) + continue; // skip unrecognized lines + + // Entry table reading + if( strcmp(w1, "grf") == 0 ) // GRF file + { + if( grfio_add(w2) == 0 ) + ++grf_num; + } + else if( strcmp(w1,"data_dir") == 0 ) // Data directory + { + safestrncpy(data_dir, w2, sizeof(data_dir)); + } + } + + fclose(data_conf); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", fname); + } + + if( grf_num == 0 ) + ShowInfo("No GRF loaded, using default data directory\n"); + + // Unneccessary area release of filelist + filelist_compact(); + + // Resource check + grfio_resourcecheck(); +} diff --git a/src/common/grfio.h b/src/common/grfio.h new file mode 100644 index 000000000..c5a56a14e --- /dev/null +++ b/src/common/grfio.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GRFIO_H_ +#define _GRFIO_H_ + +void grfio_init(const char* fname); +void grfio_final(void); +void* grfio_reads(const char* fname, int* size); +char* grfio_find_file(const char* fname); +#define grfio_read(fn) grfio_reads(fn, NULL) + +unsigned long grfio_crc32(const unsigned char *buf, unsigned int len); +int decode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen); +int encode_zip(void* dest, unsigned long* destLen, const void* source, unsigned long sourceLen); + +#endif /* _GRFIO_H_ */ diff --git a/src/common/malloc.c b/src/common/malloc.c new file mode 100644 index 000000000..9976a28d5 --- /dev/null +++ b/src/common/malloc.c @@ -0,0 +1,718 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/malloc.h" +#include "../common/core.h" +#include "../common/showmsg.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +////////////// Memory Libraries ////////////////// + +#if defined(MEMWATCH) + +# include <string.h> +# include "memwatch.h" +# define MALLOC(n,file,line,func) mwMalloc((n),(file),(line)) +# define CALLOC(m,n,file,line,func) mwCalloc((m),(n),(file),(line)) +# define REALLOC(p,n,file,line,func) mwRealloc((p),(n),(file),(line)) +# define STRDUP(p,file,line,func) mwStrdup((p),(file),(line)) +# define FREE(p,file,line,func) mwFree((p),(file),(line)) +# define MEMORY_USAGE() 0 +# define MEMORY_VERIFY(ptr) mwIsSafeAddr(ptr, 1) +# define MEMORY_CHECK() CHECK() + +#elif defined(DMALLOC) + +# include <string.h> +# include <stdlib.h> +# include "dmalloc.h" +# define MALLOC(n,file,line,func) dmalloc_malloc((file),(line),(n),DMALLOC_FUNC_MALLOC,0,0) +# define CALLOC(m,n,file,line,func) dmalloc_malloc((file),(line),(m)*(n),DMALLOC_FUNC_CALLOC,0,0) +# define REALLOC(p,n,file,line,func) dmalloc_realloc((file),(line),(p),(n),DMALLOC_FUNC_REALLOC,0) +# define STRDUP(p,file,line,func) strdup(p) +# define FREE(p,file,line,func) free(p) +# define MEMORY_USAGE() dmalloc_memory_allocated() +# define MEMORY_VERIFY(ptr) (dmalloc_verify(ptr) == DMALLOC_VERIFY_NOERROR) +# define MEMORY_CHECK() dmalloc_log_stats(); dmalloc_log_unfreed() + +#elif defined(GCOLLECT) + +# include "gc.h" +# ifdef GC_ADD_CALLER +# define RETURN_ADDR 0, +# else +# define RETURN_ADDR +# endif +# define MALLOC(n,file,line,func) GC_debug_malloc((n), RETURN_ADDR (file),(line)) +# define CALLOC(m,n,file,line,func) GC_debug_malloc((m)*(n), RETURN_ADDR (file),(line)) +# define REALLOC(p,n,file,line,func) GC_debug_realloc((p),(n), RETURN_ADDR (file),(line)) +# define STRDUP(p,file,line,func) GC_debug_strdup((p), RETURN_ADDR (file),(line)) +# define FREE(p,file,line,func) GC_debug_free(p) +# define MEMORY_USAGE() GC_get_heap_size() +# define MEMORY_VERIFY(ptr) (GC_base(ptr) != NULL) +# define MEMORY_CHECK() GC_gcollect() + +#else + +# define MALLOC(n,file,line,func) malloc(n) +# define CALLOC(m,n,file,line,func) calloc((m),(n)) +# define REALLOC(p,n,file,line,func) realloc((p),(n)) +# define STRDUP(p,file,line,func) strdup(p) +# define FREE(p,file,line,func) free(p) +# define MEMORY_USAGE() 0 +# define MEMORY_VERIFY(ptr) true +# define MEMORY_CHECK() + +#endif + +void* aMalloc_(size_t size, const char *file, int line, const char *func) +{ + void *ret = MALLOC(size, file, line, func); + // ShowMessage("%s:%d: in func %s: aMalloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aMalloc error out of memory!\n",file,line,func); + exit(EXIT_FAILURE); + } + + return ret; +} +void* aCalloc_(size_t num, size_t size, const char *file, int line, const char *func) +{ + void *ret = CALLOC(num, size, file, line, func); + // ShowMessage("%s:%d: in func %s: aCalloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aCalloc error out of memory!\n", file, line, func); + exit(EXIT_FAILURE); + } + return ret; +} +void* aRealloc_(void *p, size_t size, const char *file, int line, const char *func) +{ + void *ret = REALLOC(p, size, file, line, func); + // ShowMessage("%s:%d: in func %s: aRealloc %p %d\n",file,line,func,p,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aRealloc error out of memory!\n",file,line,func); + exit(EXIT_FAILURE); + } + return ret; +} +char* aStrdup_(const char *p, const char *file, int line, const char *func) +{ + char *ret = STRDUP(p, file, line, func); + // ShowMessage("%s:%d: in func %s: aStrdup %p\n",file,line,func,p); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: aStrdup error out of memory!\n", file, line, func); + exit(EXIT_FAILURE); + } + return ret; +} +void aFree_(void *p, const char *file, int line, const char *func) +{ + // ShowMessage("%s:%d: in func %s: aFree %p\n",file,line,func,p); + if (p) + FREE(p, file, line, func); + + p = NULL; +} + + +#ifdef USE_MEMMGR + +#if defined(DEBUG) +#define DEBUG_MEMMGR +#endif + +/* USE_MEMMGR */ + +/* + * ƒƒ‚ƒŠƒ}ƒl[ƒWƒƒ + * malloc , free ‚̈—‚ðŒø—¦“I‚Éo—ˆ‚é‚悤‚É‚µ‚½‚à‚ÌB + * •¡ŽG‚Ȉ—‚ðs‚Á‚Ä‚¢‚é‚Ì‚ÅAŽáŠ±d‚‚È‚é‚©‚à‚µ‚ê‚Ü‚¹‚ñB + * + * ƒf[ƒ^\‘¢‚È‚Çià–¾‰ºŽè‚Å‚·‚¢‚Ü‚¹‚ñ^^; j + * Eƒƒ‚ƒŠ‚ð•¡”‚ÌuƒuƒƒbƒNv‚É•ª‚¯‚ÄA‚³‚ç‚ɃuƒƒbƒN‚ð•¡”‚Ìuƒ†ƒjƒbƒgv + * ‚É•ª‚¯‚Ä‚¢‚Ü‚·Bƒ†ƒjƒbƒg‚̃TƒCƒY‚ÍA‚PƒuƒƒbƒN‚Ì—e—Ê‚ð•¡”ŒÂ‚É‹Ï“™”z•ª + * ‚µ‚½‚à‚Ì‚Å‚·B‚½‚Æ‚¦‚ÎA‚Pƒ†ƒjƒbƒg32KB‚Ìê‡AƒuƒƒbƒN‚P‚‚Í32Byte‚̃† + * ƒjƒbƒg‚ªA1024ŒÂW‚Ü‚Á‚Äo—ˆ‚Ä‚¢‚½‚èA64Byte‚̃†ƒjƒbƒg‚ª 512ŒÂW‚Ü‚Á‚Ä + * o—ˆ‚Ä‚¢‚½‚肵‚Ü‚·Bipadding,unit_head ‚ðœ‚j + * + * EƒuƒƒbƒN“¯Žm‚̓Šƒ“ƒNƒŠƒXƒg(block_prev,block_next) ‚ł‚Ȃª‚èA“¯‚¶ƒTƒC + * ƒY‚ðŽ‚ƒuƒƒbƒN“¯Žm‚àƒŠƒ“ƒNƒŠƒXƒg(hash_prev,hash_nect) ‚Å‚Â‚È + * ‚ª‚Á‚Ä‚¢‚Ü‚·B‚»‚ê‚É‚æ‚èA•s—v‚Æ‚È‚Á‚½ƒƒ‚ƒŠ‚ÌÄ—˜—p‚ªŒø—¦“I‚És‚¦‚Ü‚·B + */ + +/* ƒuƒƒbƒN‚̃Aƒ‰ƒCƒƒ“ƒg */ +#define BLOCK_ALIGNMENT1 16 +#define BLOCK_ALIGNMENT2 64 + +/* ƒuƒƒbƒN‚É“ü‚éƒf[ƒ^—Ê */ +#define BLOCK_DATA_COUNT1 128 +#define BLOCK_DATA_COUNT2 608 + +/* ƒuƒƒbƒN‚Ì‘å‚«‚³: 16*128 + 64*576 = 40KB */ +#define BLOCK_DATA_SIZE1 ( BLOCK_ALIGNMENT1 * BLOCK_DATA_COUNT1 ) +#define BLOCK_DATA_SIZE2 ( BLOCK_ALIGNMENT2 * BLOCK_DATA_COUNT2 ) +#define BLOCK_DATA_SIZE ( BLOCK_DATA_SIZE1 + BLOCK_DATA_SIZE2 ) + +/* ˆê“x‚ÉŠm•Û‚·‚éƒuƒƒbƒN‚Ì”B */ +#define BLOCK_ALLOC 104 + +/* ƒuƒƒbƒN */ +struct block { + struct block* block_next; /* ŽŸ‚ÉŠm•Û‚µ‚½—̈æ */ + struct block* unfill_prev; /* ŽŸ‚Ì–„‚Ü‚Á‚Ä‚¢‚È‚¢—̈æ */ + struct block* unfill_next; /* ŽŸ‚Ì–„‚Ü‚Á‚Ä‚¢‚È‚¢—̈æ */ + unsigned short unit_size; /* ƒ†ƒjƒbƒg‚Ì‘å‚«‚³ */ + unsigned short unit_hash; /* ƒ†ƒjƒbƒg‚̃nƒbƒVƒ… */ + unsigned short unit_count; /* ƒ†ƒjƒbƒg‚̌” */ + unsigned short unit_used; /* Žg—pƒ†ƒjƒbƒg” */ + unsigned short unit_unfill; /* –¢Žg—pƒ†ƒjƒbƒg‚ÌêŠ */ + unsigned short unit_maxused; /* Žg—pƒ†ƒjƒbƒg‚ÌÅ‘å’l */ + char data[ BLOCK_DATA_SIZE ]; +}; + +struct unit_head { + struct block *block; + const char* file; + unsigned short line; + unsigned short size; + long checksum; +}; + +static struct block* hash_unfill[BLOCK_DATA_COUNT1 + BLOCK_DATA_COUNT2 + 1]; +static struct block* block_first, *block_last, block_head; + +/* ƒƒ‚ƒŠ‚ðŽg‚¢‰ñ‚¹‚È‚¢—̈æ—p‚̃f[ƒ^ */ +struct unit_head_large { + size_t size; + struct unit_head_large* prev; + struct unit_head_large* next; + struct unit_head unit_head; +}; + +static struct unit_head_large *unit_head_large_first = NULL; + +static struct block* block_malloc(unsigned short hash); +static void block_free(struct block* p); +static size_t memmgr_usage_bytes; + +#define block2unit(p, n) ((struct unit_head*)(&(p)->data[ p->unit_size * (n) ])) +#define memmgr_assert(v) do { if(!(v)) { ShowError("Memory manager: assertion '" #v "' failed!\n"); } } while(0) + +static unsigned short size2hash( size_t size ) +{ + if( size <= BLOCK_DATA_SIZE1 ) { + return (unsigned short)(size + BLOCK_ALIGNMENT1 - 1) / BLOCK_ALIGNMENT1; + } else if( size <= BLOCK_DATA_SIZE ){ + return (unsigned short)(size - BLOCK_DATA_SIZE1 + BLOCK_ALIGNMENT2 - 1) / BLOCK_ALIGNMENT2 + + BLOCK_DATA_COUNT1; + } else { + return 0xffff; // ƒuƒƒbƒN’·‚ð’´‚¦‚éê‡‚Í hash ‚É‚µ‚È‚¢ + } +} + +static size_t hash2size( unsigned short hash ) +{ + if( hash <= BLOCK_DATA_COUNT1) { + return hash * BLOCK_ALIGNMENT1; + } else { + return (hash - BLOCK_DATA_COUNT1) * BLOCK_ALIGNMENT2 + BLOCK_DATA_SIZE1; + } +} + +void* _mmalloc(size_t size, const char *file, int line, const char *func ) +{ + struct block *block; + short size_hash = size2hash( size ); + struct unit_head *head; + + if (((long) size) < 0) { + ShowError("_mmalloc: %d\n", size); + return NULL; + } + + if(size == 0) { + return NULL; + } + memmgr_usage_bytes += size; + + /* ƒuƒƒbƒN’·‚ð’´‚¦‚é—̈æ‚ÌŠm•Û‚É‚ÍAmalloc() ‚ð—p‚¢‚é */ + /* ‚»‚ÌÛAunit_head.block ‚É NULL ‚ð‘ã“ü‚µ‚Ä‹æ•Ê‚·‚é */ + if(hash2size(size_hash) > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { + struct unit_head_large* p = (struct unit_head_large*)MALLOC(sizeof(struct unit_head_large)+size,file,line,func); + if(p != NULL) { + p->size = size; + p->unit_head.block = NULL; + p->unit_head.size = 0; + p->unit_head.file = file; + p->unit_head.line = line; + p->prev = NULL; + if (unit_head_large_first == NULL) + p->next = NULL; + else { + unit_head_large_first->prev = p; + p->next = unit_head_large_first; + } + unit_head_large_first = p; + *(long*)((char*)p + sizeof(struct unit_head_large) - sizeof(long) + size) = 0xdeadbeaf; + return (char *)p + sizeof(struct unit_head_large) - sizeof(long); + } else { + ShowFatalError("Memory manager::memmgr_alloc failed (allocating %d+%d bytes at %s:%d).\n", sizeof(struct unit_head_large), size, file, line); + exit(EXIT_FAILURE); + } + } + + /* “¯ˆêƒTƒCƒY‚̃uƒƒbƒN‚ªŠm•Û‚³‚ê‚Ä‚¢‚È‚¢ŽžAV‚½‚ÉŠm•Û‚·‚é */ + if(hash_unfill[size_hash]) { + block = hash_unfill[size_hash]; + } else { + block = block_malloc(size_hash); + } + + if( block->unit_unfill == 0xFFFF ) { + // freeςݗ̈悪Žc‚Á‚Ä‚¢‚È‚¢ + memmgr_assert(block->unit_used < block->unit_count); + memmgr_assert(block->unit_used == block->unit_maxused); + head = block2unit(block, block->unit_maxused); + block->unit_used++; + block->unit_maxused++; + } else { + head = block2unit(block, block->unit_unfill); + block->unit_unfill = head->size; + block->unit_used++; + } + + if( block->unit_unfill == 0xFFFF && block->unit_maxused >= block->unit_count) { + // ƒ†ƒjƒbƒg‚ðŽg‚¢‰Ê‚½‚µ‚½‚Ì‚ÅAunfillƒŠƒXƒg‚©‚çíœ + if( block->unfill_prev == &block_head) { + hash_unfill[ size_hash ] = block->unfill_next; + } else { + block->unfill_prev->unfill_next = block->unfill_next; + } + if( block->unfill_next ) { + block->unfill_next->unfill_prev = block->unfill_prev; + } + block->unfill_prev = NULL; + } + +#ifdef DEBUG_MEMMGR + { + size_t i, sz = hash2size( size_hash ); + for( i=0; i<sz; i++ ) + { + if( ((unsigned char*)head)[ sizeof(struct unit_head) - sizeof(long) + i] != 0xfd ) + { + if( head->line != 0xfdfd ) + { + ShowError("Memory manager: freed-data is changed. (freed in %s line %d)\n", head->file,head->line); + } + else + { + ShowError("Memory manager: not-allocated-data is changed.\n"); + } + break; + } + } + memset( (char *)head + sizeof(struct unit_head) - sizeof(long), 0xcd, sz ); + } +#endif + + head->block = block; + head->file = file; + head->line = line; + head->size = (unsigned short)size; + *(long*)((char*)head + sizeof(struct unit_head) - sizeof(long) + size) = 0xdeadbeaf; + return (char *)head + sizeof(struct unit_head) - sizeof(long); +} + +void* _mcalloc(size_t num, size_t size, const char *file, int line, const char *func ) +{ + void *p = _mmalloc(num * size,file,line,func); + memset(p,0,num * size); + return p; +} + +void* _mrealloc(void *memblock, size_t size, const char *file, int line, const char *func ) +{ + size_t old_size; + if(memblock == NULL) { + return _mmalloc(size,file,line,func); + } + + old_size = ((struct unit_head *)((char *)memblock - sizeof(struct unit_head) + sizeof(long)))->size; + if( old_size == 0 ) { + old_size = ((struct unit_head_large *)((char *)memblock - sizeof(struct unit_head_large) + sizeof(long)))->size; + } + if(old_size > size) { + // ƒTƒCƒYk¬ -> ‚»‚Ì‚Ü‚Ü•Ô‚·iŽè”²‚«j + return memblock; + } else { + // ƒTƒCƒYŠg‘å + void *p = _mmalloc(size,file,line,func); + if(p != NULL) { + memcpy(p,memblock,old_size); + } + _mfree(memblock,file,line,func); + return p; + } +} + +char* _mstrdup(const char *p, const char *file, int line, const char *func ) +{ + if(p == NULL) { + return NULL; + } else { + size_t len = strlen(p); + char *string = (char *)_mmalloc(len + 1,file,line,func); + memcpy(string,p,len+1); + return string; + } +} + +void _mfree(void *ptr, const char *file, int line, const char *func ) +{ + struct unit_head *head; + + if (ptr == NULL) + return; + + head = (struct unit_head *)((char *)ptr - sizeof(struct unit_head) + sizeof(long)); + if(head->size == 0) { + /* malloc() ‚Å’¼‚ÉŠm•Û‚³‚ꂽ—̈æ */ + struct unit_head_large *head_large = (struct unit_head_large *)((char *)ptr - sizeof(struct unit_head_large) + sizeof(long)); + if( + *(long*)((char*)head_large + sizeof(struct unit_head_large) - sizeof(long) + head_large->size) + != 0xdeadbeaf) + { + ShowError("Memory manager: args of aFree 0x%p is overflowed pointer %s line %d\n", ptr, file, line); + } else { + head->size = 0xFFFF; + if(head_large->prev) { + head_large->prev->next = head_large->next; + } else { + unit_head_large_first = head_large->next; + } + if(head_large->next) { + head_large->next->prev = head_large->prev; + } + memmgr_usage_bytes -= head_large->size; +#ifdef DEBUG_MEMMGR + // set freed memory to 0xfd + memset(ptr, 0xfd, head_large->size); +#endif + FREE(head_large,file,line,func); + } + } else { + /* ƒ†ƒjƒbƒg‰ð•ú */ + struct block *block = head->block; + if( (char*)head - (char*)block > sizeof(struct block) ) { + ShowError("Memory manager: args of aFree 0x%p is invalid pointer %s line %d\n", ptr, file, line); + } else if(head->block == NULL) { + ShowError("Memory manager: args of aFree 0x%p is freed pointer %s:%d@%s\n", ptr, file, line, func); + } else if(*(long*)((char*)head + sizeof(struct unit_head) - sizeof(long) + head->size) != 0xdeadbeaf) { + ShowError("Memory manager: args of aFree 0x%p is overflowed pointer %s line %d\n", ptr, file, line); + } else { + memmgr_usage_bytes -= head->size; + head->block = NULL; +#ifdef DEBUG_MEMMGR + memset(ptr, 0xfd, block->unit_size - sizeof(struct unit_head) + sizeof(long) ); + head->file = file; + head->line = line; +#endif + memmgr_assert( block->unit_used > 0 ); + if(--block->unit_used == 0) { + /* ƒuƒƒbƒN‚̉ð•ú */ + block_free(block); + } else { + if( block->unfill_prev == NULL) { + // unfill ƒŠƒXƒg‚ɒljÁ + if( hash_unfill[ block->unit_hash ] ) { + hash_unfill[ block->unit_hash ]->unfill_prev = block; + } + block->unfill_prev = &block_head; + block->unfill_next = hash_unfill[ block->unit_hash ]; + hash_unfill[ block->unit_hash ] = block; + } + head->size = block->unit_unfill; + block->unit_unfill = (unsigned short)(((uintptr_t)head - (uintptr_t)block->data) / block->unit_size); + } + } + } +} + +/* ƒuƒƒbƒN‚ðŠm•Û‚·‚é */ +static struct block* block_malloc(unsigned short hash) +{ + int i; + struct block *p; + if(hash_unfill[0] != NULL) { + /* ƒuƒƒbƒN—p‚̗̈æ‚ÍŠm•ÛÏ‚Ý */ + p = hash_unfill[0]; + hash_unfill[0] = hash_unfill[0]->unfill_next; + } else { + /* ƒuƒƒbƒN—p‚̗̈æ‚ðV‚½‚ÉŠm•Û‚·‚é */ + p = (struct block*)MALLOC(sizeof(struct block) * (BLOCK_ALLOC), __FILE__, __LINE__, __func__ ); + if(p == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(EXIT_FAILURE); + } + + if(block_first == NULL) { + /* ‰‰ñŠm•Û */ + block_first = p; + } else { + block_last->block_next = p; + } + block_last = &p[BLOCK_ALLOC - 1]; + block_last->block_next = NULL; + /* ƒuƒƒbƒN‚ð˜AŒ‹‚³‚¹‚é */ + for(i=0;i<BLOCK_ALLOC;i++) { + if(i != 0) { + // p[0] ‚Í‚±‚ê‚©‚çŽg‚¤‚̂ŃŠƒ“ƒN‚ɂ͉Á‚¦‚È‚¢ + p[i].unfill_next = hash_unfill[0]; + hash_unfill[0] = &p[i]; + p[i].unfill_prev = NULL; + p[i].unit_used = 0; + } + if(i != BLOCK_ALLOC -1) { + p[i].block_next = &p[i+1]; + } + } + } + + // unfill ‚ɒljÁ + memmgr_assert(hash_unfill[ hash ] == NULL); + hash_unfill[ hash ] = p; + p->unfill_prev = &block_head; + p->unfill_next = NULL; + p->unit_size = (unsigned short)(hash2size( hash ) + sizeof(struct unit_head)); + p->unit_hash = hash; + p->unit_count = BLOCK_DATA_SIZE / p->unit_size; + p->unit_used = 0; + p->unit_unfill = 0xFFFF; + p->unit_maxused = 0; +#ifdef DEBUG_MEMMGR + memset( p->data, 0xfd, sizeof(p->data) ); +#endif + return p; +} + +static void block_free(struct block* p) +{ + if( p->unfill_prev ) { + if( p->unfill_prev == &block_head) { + hash_unfill[ p->unit_hash ] = p->unfill_next; + } else { + p->unfill_prev->unfill_next = p->unfill_next; + } + if( p->unfill_next ) { + p->unfill_next->unfill_prev = p->unfill_prev; + } + p->unfill_prev = NULL; + } + + p->unfill_next = hash_unfill[0]; + hash_unfill[0] = p; +} + +size_t memmgr_usage (void) +{ + return memmgr_usage_bytes / 1024; +} + +#ifdef LOG_MEMMGR +static char memmer_logfile[128]; +static FILE *log_fp; + +static void memmgr_log (char *buf) +{ + if( !log_fp ) + { + time_t raw; + struct tm* t; + + log_fp = fopen(memmer_logfile,"at"); + if (!log_fp) log_fp = stdout; + + time(&raw); + t = localtime(&raw); + fprintf(log_fp, "\nMemory manager: Memory leaks found at %d/%02d/%02d %02dh%02dm%02ds (Revision %s).\n", + (t->tm_year+1900), (t->tm_mon+1), t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, get_svn_revision()); + } + fprintf(log_fp, "%s", buf); + return; +} +#endif /* LOG_MEMMGR */ + +/// Returns true if the memory location is active. +/// Active means it is allocated and points to a usable part. +/// +/// @param ptr Pointer to the memory +/// @return true if the memory is active +bool memmgr_verify(void* ptr) +{ + struct block* block = block_first; + struct unit_head_large* large = unit_head_large_first; + + if( ptr == NULL ) + return false;// never valid + + // search small blocks + while( block ) + { + if( (char*)ptr >= (char*)block && (char*)ptr < ((char*)block) + sizeof(struct block) ) + {// found memory block + if( block->unit_used && (char*)ptr >= block->data ) + {// memory block is being used and ptr points to a sub-unit + size_t i = (size_t)((char*)ptr - block->data)/block->unit_size; + struct unit_head* head = block2unit(block, i); + if( i < block->unit_maxused && head->block != NULL ) + {// memory unit is allocated, check if ptr points to the usable part + return ( (char*)ptr >= ((char*)head) + sizeof(struct unit_head) - sizeof(long) + && (char*)ptr < ((char*)head) + sizeof(struct unit_head) - sizeof(long) + head->size ); + } + } + return false; + } + block = block->block_next; + } + + // search large blocks + while( large ) + { + if( (char*)ptr >= (char*)large && (char*)ptr < ((char*)large) + large->size ) + {// found memory block, check if ptr points to the usable part + return ( (char*)ptr >= ((char*)large) + sizeof(struct unit_head_large) - sizeof(long) + && (char*)ptr < ((char*)large) + sizeof(struct unit_head_large) - sizeof(long) + large->size ); + } + large = large->next; + } + return false; +} + +static void memmgr_final (void) +{ + struct block *block = block_first; + struct unit_head_large *large = unit_head_large_first; + +#ifdef LOG_MEMMGR + int count = 0; +#endif /* LOG_MEMMGR */ + + while (block) { + if (block->unit_used) { + int i; + for (i = 0; i < block->unit_maxused; i++) { + struct unit_head *head = block2unit(block, i); + if(head->block != NULL) { + char* ptr = (char *)head + sizeof(struct unit_head) - sizeof(long); +#ifdef LOG_MEMMGR + char buf[1024]; + sprintf (buf, + "%04d : %s line %d size %lu address 0x%p\n", ++count, + head->file, head->line, (unsigned long)head->size, ptr); + memmgr_log (buf); +#endif /* LOG_MEMMGR */ + // get block pointer and free it [celest] + _mfree(ptr, ALC_MARK); + } + } + } + block = block->block_next; + } + + while(large) { + struct unit_head_large *large2; +#ifdef LOG_MEMMGR + char buf[1024]; + sprintf (buf, + "%04d : %s line %d size %lu address 0x%p\n", ++count, + large->unit_head.file, large->unit_head.line, (unsigned long)large->size, &large->unit_head.checksum); + memmgr_log (buf); +#endif /* LOG_MEMMGR */ + large2 = large->next; + FREE(large,file,line,func); + large = large2; + } +#ifdef LOG_MEMMGR + if(count == 0) { + ShowInfo("Memory manager: No memory leaks found.\n"); + } else { + ShowWarning("Memory manager: Memory leaks found and fixed.\n"); + fclose(log_fp); + } +#endif /* LOG_MEMMGR */ +} + +static void memmgr_init (void) +{ +#ifdef LOG_MEMMGR + sprintf(memmer_logfile, "log/%s.leaks", SERVER_NAME); + ShowStatus("Memory manager initialised: "CL_WHITE"%s"CL_RESET"\n", memmer_logfile); + memset(hash_unfill, 0, sizeof(hash_unfill)); +#endif /* LOG_MEMMGR */ +} +#endif /* USE_MEMMGR */ + + +/*====================================== + * Initialise + *-------------------------------------- + */ + + +/// Tests the memory for errors and memory leaks. +void malloc_memory_check(void) +{ + MEMORY_CHECK(); +} + + +/// Returns true if a pointer is valid. +/// The check is best-effort, false positives are possible. +bool malloc_verify_ptr(void* ptr) +{ +#ifdef USE_MEMMGR + return memmgr_verify(ptr) && MEMORY_VERIFY(ptr); +#else + return MEMORY_VERIFY(ptr); +#endif +} + + +size_t malloc_usage (void) +{ +#ifdef USE_MEMMGR + return memmgr_usage (); +#else + return MEMORY_USAGE(); +#endif +} + +void malloc_final (void) +{ +#ifdef USE_MEMMGR + memmgr_final (); +#endif + MEMORY_CHECK(); +} + +void malloc_init (void) +{ +#if defined(DMALLOC) && defined(CYGWIN) + // http://dmalloc.com/docs/latest/online/dmalloc_19.html + dmalloc_debug_setup(getenv("DMALLOC_OPTIONS")); +#endif +#ifdef GCOLLECT + // don't garbage collect, only report inaccessible memory that was not deallocated + GC_find_leak = 1; + GC_INIT(); +#endif +#ifdef USE_MEMMGR + memmgr_init (); +#endif +} diff --git a/src/common/malloc.h b/src/common/malloc.h new file mode 100644 index 000000000..6b4e8e5c4 --- /dev/null +++ b/src/common/malloc.h @@ -0,0 +1,92 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#include "../common/cbasetypes.h" + +#define ALC_MARK __FILE__, __LINE__, __func__ + + +// default use of the built-in memory manager +#if !defined(NO_MEMMGR) && !defined(USE_MEMMGR) +#if defined(MEMWATCH) || defined(DMALLOC) || defined(GCOLLECT) +// disable built-in memory manager when using another memory library +#define NO_MEMMGR +#else +// use built-in memory manager by default +#define USE_MEMMGR +#endif +#endif + + +////////////////////////////////////////////////////////////////////// +// Athena's built-in Memory Manager +#ifdef USE_MEMMGR + +// Enable memory manager logging by default +#define LOG_MEMMGR + +// no logging for minicore +#if defined(MINICORE) && defined(LOG_MEMMGR) +#undef LOG_MEMMGR +#endif + +# define aMalloc(n) _mmalloc(n,ALC_MARK) +# define aCalloc(m,n) _mcalloc(m,n,ALC_MARK) +# define aRealloc(p,n) _mrealloc(p,n,ALC_MARK) +# define aStrdup(p) _mstrdup(p,ALC_MARK) +# define aFree(p) _mfree(p,ALC_MARK) + + void* _mmalloc (size_t size, const char *file, int line, const char *func); + void* _mcalloc (size_t num, size_t size, const char *file, int line, const char *func); + void* _mrealloc (void *p, size_t size, const char *file, int line, const char *func); + char* _mstrdup (const char *p, const char *file, int line, const char *func); + void _mfree (void *p, const char *file, int line, const char *func); + +#else + +# define aMalloc(n) aMalloc_((n),ALC_MARK) +# define aCalloc(m,n) aCalloc_((m),(n),ALC_MARK) +# define aRealloc(p,n) aRealloc_(p,n,ALC_MARK) +# define aStrdup(p) aStrdup_(p,ALC_MARK) +# define aFree(p) aFree_(p,ALC_MARK) + + void* aMalloc_ (size_t size, const char *file, int line, const char *func); + void* aCalloc_ (size_t num, size_t size, const char *file, int line, const char *func); + void* aRealloc_ (void *p, size_t size, const char *file, int line, const char *func); + char* aStrdup_ (const char *p, const char *file, int line, const char *func); + void aFree_ (void *p, const char *file, int line, const char *func); + +#endif + +/////////////// Buffer Creation ///////////////// +// Full credit for this goes to Shinomori [Ajarn] + +#ifdef __GNUC__ // GCC has variable length arrays + + #define CREATE_BUFFER(name, type, size) type name[size] + #define DELETE_BUFFER(name) + +#else // others don't, so we emulate them + + #define CREATE_BUFFER(name, type, size) type *name = (type *) aCalloc (size, sizeof(type)) + #define DELETE_BUFFER(name) aFree(name) + +#endif + +////////////// Others ////////////////////////// +// should be merged with any of above later +#define CREATE(result, type, number) (result) = (type *) aCalloc ((number), sizeof(type)) +#define RECREATE(result, type, number) (result) = (type *) aRealloc ((result), sizeof(type) * (number)) + +//////////////////////////////////////////////// + +void malloc_memory_check(void); +bool malloc_verify_ptr(void* ptr); +size_t malloc_usage (void); +void malloc_init (void); +void malloc_final (void); + +#endif /* _MALLOC_H_ */ diff --git a/src/common/mapindex.c b/src/common/mapindex.c new file mode 100644 index 000000000..d46047833 --- /dev/null +++ b/src/common/mapindex.c @@ -0,0 +1,185 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../common/strlib.h" +#include "mapindex.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +struct _indexes { + char name[MAP_NAME_LENGTH]; //Stores map name +} indexes[MAX_MAPINDEX]; + +int max_index = 0; + +char mapindex_cfgfile[80] = "db/map_index.txt"; + +#define mapindex_exists(id) (indexes[id].name[0] != '\0') + +/// Retrieves the map name from 'string' (removing .gat extension if present). +/// Result gets placed either into 'buf' or in a static local buffer. +const char* mapindex_getmapname(const char* string, char* output) +{ + static char buf[MAP_NAME_LENGTH]; + char* dest = (output != NULL) ? output : buf; + + size_t len = strnlen(string, MAP_NAME_LENGTH_EXT); + if (len == MAP_NAME_LENGTH_EXT) { + ShowWarning("(mapindex_normalize_name) Map name '%*s' is too long!\n", 2*MAP_NAME_LENGTH_EXT, string); + len--; + } + if (len >= 4 && stricmp(&string[len-4], ".gat") == 0) + len -= 4; // strip .gat extension + + len = min(len, MAP_NAME_LENGTH-1); + strncpy(dest, string, len+1); + memset(&dest[len], '\0', MAP_NAME_LENGTH-len); + + return dest; +} + +/// Retrieves the map name from 'string' (adding .gat extension if not already present). +/// Result gets placed either into 'buf' or in a static local buffer. +const char* mapindex_getmapname_ext(const char* string, char* output) +{ + static char buf[MAP_NAME_LENGTH_EXT]; + char* dest = (output != NULL) ? output : buf; + + size_t len; + + strcpy(buf,string); + sscanf(string,"%*[^#]%*[#]%s",buf); + + len = safestrnlen(buf, MAP_NAME_LENGTH); + + if (len == MAP_NAME_LENGTH) { + ShowWarning("(mapindex_normalize_name) Map name '%*s' is too long!\n", 2*MAP_NAME_LENGTH, buf); + len--; + } + strncpy(dest, buf, len+1); + + if (len < 4 || stricmp(&dest[len-4], ".gat") != 0) { + strcpy(&dest[len], ".gat"); + len += 4; // add .gat extension + } + + memset(&dest[len], '\0', MAP_NAME_LENGTH_EXT-len); + + return dest; +} + +/// Adds a map to the specified index +/// Returns 1 if successful, 0 oherwise +int mapindex_addmap(int index, const char* name) +{ + char map_name[MAP_NAME_LENGTH]; + + if (index == -1){ + for (index = 1; index < max_index; index++) + { + //if (strcmp(indexes[index].name,"#CLEARED#")==0) + if (indexes[index].name[0] == '\0') + break; + } + } + + if (index < 0 || index >= MAX_MAPINDEX) { + ShowError("(mapindex_add) Map index (%d) for \"%s\" out of range (max is %d)\n", index, name, MAX_MAPINDEX); + return 0; + } + + mapindex_getmapname(name, map_name); + + if (map_name[0] == '\0') { + ShowError("(mapindex_add) Cannot add maps with no name.\n"); + return 0; + } + + if (strlen(map_name) >= MAP_NAME_LENGTH) { + ShowError("(mapindex_add) Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + return 0; + } + + if (mapindex_exists(index)) + ShowWarning("(mapindex_add) Overriding index %d: map \"%s\" -> \"%s\"\n", index, indexes[index].name, map_name); + + safestrncpy(indexes[index].name, map_name, MAP_NAME_LENGTH); + if (max_index <= index) + max_index = index+1; + + return index; +} + +unsigned short mapindex_name2id(const char* name) +{ + //TODO: Perhaps use a db to speed this up? [Skotlex] + int i; + + char map_name[MAP_NAME_LENGTH]; + mapindex_getmapname(name, map_name); + + for (i = 1; i < max_index; i++) + { + if (strcmpi(indexes[i].name,map_name)==0) + return i; + } + ShowDebug("mapindex_name2id: Map \"%s\" not found in index list!\n", map_name); + return 0; +} + +const char* mapindex_id2name(unsigned short id) +{ + if (id > MAX_MAPINDEX || !mapindex_exists(id)) { + ShowDebug("mapindex_id2name: Requested name for non-existant map index [%d] in cache.\n", id); + return indexes[0].name; // dummy empty string so that the callee doesn't crash + } + return indexes[id].name; +} + +void mapindex_init(void) +{ + FILE *fp; + char line[1024]; + int last_index = -1; + int index; + char map_name[1024]; + + memset (&indexes, 0, sizeof (indexes)); + fp=fopen(mapindex_cfgfile,"r"); + if(fp==NULL){ + ShowFatalError("Unable to read mapindex config file %s!\n", mapindex_cfgfile); + exit(EXIT_FAILURE); //Server can't really run without this file. + } + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + + switch (sscanf(line, "%1023s\t%d", map_name, &index)) + { + case 1: //Map with no ID given, auto-assign + index = last_index+1; + case 2: //Map with ID given + mapindex_addmap(index,map_name); + break; + default: + continue; + } + last_index = index; + } + fclose(fp); +} + +int mapindex_removemap(int index){ + indexes[index].name[0] = '\0'; + return 0; +} + +void mapindex_final(void) +{ +} diff --git a/src/common/mapindex.h b/src/common/mapindex.h new file mode 100644 index 000000000..75cb254c0 --- /dev/null +++ b/src/common/mapindex.h @@ -0,0 +1,60 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MAPINDEX_H_ +#define _MAPINDEX_H_ + +//File in charge of assigning a numberic ID to each map in existance for space saving when passing map info between servers. +extern char mapindex_cfgfile[80]; + +#define MAX_MAPINDEX 2000 + +//Some definitions for the mayor city maps. +#define MAP_PRONTERA "prontera" +#define MAP_GEFFEN "geffen" +#define MAP_MORROC "morocc" +#define MAP_ALBERTA "alberta" +#define MAP_PAYON "payon" +#define MAP_IZLUDE "izlude" +#define MAP_ALDEBARAN "aldebaran" +#define MAP_LUTIE "xmas" +#define MAP_COMODO "comodo" +#define MAP_YUNO "yuno" +#define MAP_AMATSU "amatsu" +#define MAP_GONRYUN "gonryun" +#define MAP_UMBALA "umbala" +#define MAP_NIFLHEIM "niflheim" +#define MAP_LOUYANG "louyang" +#define MAP_JAWAII "jawaii" +#define MAP_AYOTHAYA "ayothaya" +#define MAP_EINBROCH "einbroch" +#define MAP_LIGHTHALZEN "lighthalzen" +#define MAP_EINBECH "einbech" +#define MAP_HUGEL "hugel" +#define MAP_RACHEL "rachel" +#define MAP_VEINS "veins" +#define MAP_JAIL "sec_pri" +#define MAP_NOVICE "new_1-1" +#define MAP_MOSCOVIA "moscovia" +#define MAP_MIDCAMP "mid_camp" +#define MAP_MANUK "manuk" +#define MAP_SPLENDIDE "splendide" +#define MAP_BRASILIS "brasilis" +#define MAP_DICASTES "dicastes01" +#define MAP_MORA "mora" +#define MAP_DEWATA "dewata" +#define MAP_MALANGDO "malangdo" +#define MAP_MALAYA "malaya" +#define MAP_ECLAGE "eclage" + +const char* mapindex_getmapname(const char* string, char* output); +const char* mapindex_getmapname_ext(const char* string, char* output); +unsigned short mapindex_name2id(const char*); +const char* mapindex_id2name(unsigned short); +void mapindex_init(void); +void mapindex_final(void); + +int mapindex_addmap(int index, const char* name); +int mapindex_removemap(int index); + +#endif /* _MAPINDEX_H_ */ diff --git a/src/common/md5calc.c b/src/common/md5calc.c new file mode 100644 index 000000000..05fde42cc --- /dev/null +++ b/src/common/md5calc.c @@ -0,0 +1,240 @@ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "../common/random.h" +#include "md5calc.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// String Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +static void MD5_String2binary(const char * string, unsigned char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = (unsigned int)strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_Binary(const char * string, unsigned char * output) +{ + MD5_String2binary(string,output); +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + +/** output is a sequence of non-zero characters to be used as password salt. */ +void MD5_Salt(unsigned int len, char * output) +{ + unsigned int i; + for( i = 0; i < len; ++i ) + output[i] = (char)(1 + rnd() % 255); + +} diff --git a/src/common/md5calc.h b/src/common/md5calc.h new file mode 100644 index 000000000..323affa2c --- /dev/null +++ b/src/common/md5calc.h @@ -0,0 +1,8 @@ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_Binary(const char * string, unsigned char * output); +void MD5_Salt(unsigned int len, char * output); + +#endif /* _MD5CALC_H_ */ diff --git a/src/common/mempool.c b/src/common/mempool.c new file mode 100644 index 000000000..5eccbf178 --- /dev/null +++ b/src/common/mempool.c @@ -0,0 +1,568 @@ + +// +// Memory Pool Implementation (Threadsafe) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef WIN32 +#include "../common/winapi.h" +#else +#include <unistd.h> +#endif + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/mempool.h" +#include "../common/atomic.h" +#include "../common/spinlock.h" +#include "../common/thread.h" +#include "../common/malloc.h" +#include "../common/mutex.h" + +#define ALIGN16 ra_align(16) +#define ALIGN_TO(x, a) (x + ( a - ( x % a) ) ) +#define ALIGN_TO_16(x) ALIGN_TO(x, 16) + +#undef MEMPOOL_DEBUG +#define MEMPOOLASSERT + + +#define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) ) +#define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) ) +struct ra_align(16) node{ + void *next; + void *segment; +#ifdef MEMPOOLASSERT + bool used; + uint64 magic; + #define NODE_MAGIC 0xBEEF00EAEACAFE07ll +#endif +}; + + +// The Pointer to this struct is the base address of the segment itself. +struct pool_segment{ + mempool pool; // pool, this segment belongs to + struct pool_segment *next; + int64 num_nodes_total; + int64 num_bytes; +}; + + +struct mempool{ + // Settings + char *name; + uint64 elem_size; + uint64 elem_realloc_step; + int64 elem_realloc_thresh; + + // Callbacks that get called for every node that gets allocated + // Example usage: initialization of mutex/lock for each node. + memPoolOnNodeAllocationProc onalloc; + memPoolOnNodeDeallocationProc ondealloc; + + // Locks + SPIN_LOCK segmentLock; + SPIN_LOCK nodeLock; + + + // Internal + struct pool_segment *segments; + struct node *free_list; + + volatile int64 num_nodes_total; + volatile int64 num_nodes_free; + + volatile int64 num_segments; + volatile int64 num_bytes_total; + + volatile int64 peak_nodes_used; // Peak Node Usage + volatile int64 num_realloc_events; // Number of reallocations done. (allocate additional nodes) + + // list (used for global management such as allocator..) + struct mempool *next; +} ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken .. + + +/// +// Implementation: +// +static void segment_allocate_add(mempool p, uint64 count); + +static SPIN_LOCK l_mempoolListLock; +static mempool l_mempoolList = NULL; +static rAthread l_async_thread = NULL; +static ramutex l_async_lock = NULL; +static racond l_async_cond = NULL; +static volatile int32 l_async_terminate = 0; + +static void *mempool_async_allocator(void *x){ + mempool p; + + + while(1){ + if(l_async_terminate > 0) + break; + + EnterSpinLock(&l_mempoolListLock); + + for(p = l_mempoolList; p != NULL; p = p->next){ + + if(p->num_nodes_free < p->elem_realloc_thresh){ + // add new segment. + segment_allocate_add(p, p->elem_realloc_step); + // increase stats counter + InterlockedIncrement64(&p->num_realloc_events); + } + + } + + LeaveSpinLock(&l_mempoolListLock); + + ramutex_lock( l_async_lock ); + racond_wait( l_async_cond, l_async_lock, -1 ); + ramutex_unlock( l_async_lock ); + } + + + return NULL; +}//end: mempool_async_allocator() + + +void mempool_init(){ + + if( rand()%2 + 1 ) + return; + + if(sizeof(struct node)%16 != 0 ){ + ShowFatalError("mempool_init: struct node alignment failure. %u != multiple of 16\n", sizeof(struct node)); + exit(EXIT_FAILURE); + } + + // Global List start + InitializeSpinLock(&l_mempoolListLock); + l_mempoolList = NULL; + + // Initialize mutex + stuff needed for async allocator worker. + l_async_terminate = 0; + l_async_lock = ramutex_create(); + l_async_cond = racond_create(); + + l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 1024*1024, RAT_PRIO_NORMAL); + if(l_async_thread == NULL){ + ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n"); + exit(EXIT_FAILURE); + } + +}//end: mempool_init() + + +void mempool_final(){ + mempool p, pn; + + if( rand()%2 + 1 ) + return; + + ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n"); + + // Terminate worker / wait until its terminated. + InterlockedIncrement(&l_async_terminate); + racond_signal(l_async_cond); + rathread_wait(l_async_thread, NULL); + + // Destroy cond var and mutex. + racond_destroy( l_async_cond ); + ramutex_destroy( l_async_lock ); + + // Free remaining mempools + // ((bugged code! this should halppen, every mempool should + // be freed by the subsystem that has allocated it.) + // + EnterSpinLock(&l_mempoolListLock); + p = l_mempoolList; + while(1){ + if(p == NULL) + break; + + pn = p->next; + + ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name); + mempool_destroy(p); + + p = pn; + } + LeaveSpinLock(&l_mempoolListLock); + +}//end: mempool_final() + + +static void segment_allocate_add(mempool p, uint64 count){ + + // Required Memory: + // sz( segment ) + // count * sz( real_node_size ) + // + // where real node size is: + // ALIGN_TO_16( sz( node ) ) + p->elem_size + // so the nodes usable address is nodebase + ALIGN_TO_16(sz(node)) + // + size_t total_sz; + struct pool_segment *seg = NULL; + struct node *nodeList = NULL; + struct node *node = NULL; + char *ptr = NULL; + uint64 i; + + total_sz = ALIGN_TO_16( sizeof(struct pool_segment) ) + + ( (size_t)count * (sizeof(struct node) + (size_t)p->elem_size) ) ; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f); +#endif + + // allocate! (spin forever until weve got the memory.) + i=0; + while(1){ + ptr = (char*)aMalloc(total_sz); + if(ptr != NULL) break; + + i++; // increase failcount. + if(!(i & 7)){ + ShowWarning("Mempool [%s] Segment AllocateAdd => System seems to be Out of Memory (%0.2f MiB). Try #%u\n", (float)total_sz/1024.f/1024.f, i); +#ifdef WIN32 + Sleep(1000); +#else + sleep(1); +#endif + }else{ + rathread_yield(); /// allow/force vuln. ctxswitch + } + }//endwhile: allocation spinloop. + + // Clear Memory. + memset(ptr, 0x00, total_sz); + + // Initialize segment struct. + seg = (struct pool_segment*)ptr; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + + seg->pool = p; + seg->num_nodes_total = count; + seg->num_bytes = total_sz; + + + // Initialze nodes! + nodeList = NULL; + for(i = 0; i < count; i++){ + node = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; + + node->segment = seg; +#ifdef MEMPOOLASSERT + node->used = false; + node->magic = NODE_MAGIC; +#endif + + if(p->onalloc != NULL) p->onalloc( NODE_TO_DATA(node) ); + + node->next = nodeList; + nodeList = node; + } + + + + // Link in Segment. + EnterSpinLock(&p->segmentLock); + seg->next = p->segments; + p->segments = seg; + LeaveSpinLock(&p->segmentLock); + + // Link in Nodes + EnterSpinLock(&p->nodeLock); + nodeList->next = p->free_list; + p->free_list = nodeList; + LeaveSpinLock(&p->nodeLock); + + + // Increase Stats: + InterlockedExchangeAdd64(&p->num_nodes_total, count); + InterlockedExchangeAdd64(&p->num_nodes_free, count); + InterlockedIncrement64(&p->num_segments); + InterlockedExchangeAdd64(&p->num_bytes_total, total_sz); + +}//end: segment_allocate_add() + + +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc){ + //.. + uint64 realloc_thresh; + mempool pool; + pool = (mempool)aCalloc( 1, sizeof(struct mempool) ); + + if(pool == NULL){ + ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool) ); + exit(EXIT_FAILURE); + } + + // Check minimum initial count / realloc count requirements. + if(initial_count < 50) + initial_count = 50; + if(realloc_count < 50) + realloc_count = 50; + + // Set Reallocation threshold to 5% of realloc_count, at least 10. + realloc_thresh = (realloc_count/100)*5; // + if(realloc_thresh < 10) + realloc_thresh = 10; + + // Initialize members.. + pool->name = aStrdup(name); + pool->elem_size = ALIGN_TO_16(elem_size); + pool->elem_realloc_step = realloc_count; + pool->elem_realloc_thresh = realloc_thresh; + pool->onalloc = onNodeAlloc; + pool->ondealloc = onNodeDealloc; + + InitializeSpinLock(&pool->segmentLock); + InitializeSpinLock(&pool->nodeLock); + + // Initial Statistic values: + pool->num_nodes_total = 0; + pool->num_nodes_free = 0; + pool->num_segments = 0; + pool->num_bytes_total = 0; + pool->peak_nodes_used = 0; + pool->num_realloc_events = 0; + + // +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Init (ElemSize: %u, Initial Count: %u, Realloc Count: %u)\n", pool->name, pool->elem_size, initial_count, pool->elem_realloc_step); +#endif + + // Allocate first segment directly :) + segment_allocate_add(pool, initial_count); + + + // Add Pool to the global pool list + EnterSpinLock(&l_mempoolListLock); + pool->next = l_mempoolList; + l_mempoolList = pool; + LeaveSpinLock(&l_mempoolListLock); + + + return pool; +}//end: mempool_create() + + +void mempool_destroy(mempool p){ + struct pool_segment *seg, *segnext; + struct node *niter; + mempool piter, pprev; + char *ptr; + int64 i; + +#ifdef MEMPOOL_DEBUG + ShowDebug("Mempool [%s] Destroy\n", p->name); +#endif + + // Unlink from global list. + EnterSpinLock(&l_mempoolListLock); + piter = l_mempoolList; + pprev = l_mempoolList; + while(1){ + if(piter == NULL) + break; + + + if(piter == p){ + // unlink from list, + // + if(pprev == l_mempoolList){ + // this (p) is list begin. so set next as head. + l_mempoolList = p->next; + }else{ + // replace prevs next wuth our next. + pprev->next = p->next; + } + break; + } + + pprev = piter; + piter = piter->next; + } + + p->next = NULL; + LeaveSpinLock(&l_mempoolListLock); + + + // Get both locks. + EnterSpinLock(&p->segmentLock); + EnterSpinLock(&p->nodeLock); + + + if(p->num_nodes_free != p->num_nodes_total) + ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free) ); + + // Free All Segments (this will also free all nodes) + // The segment pointer is the base pointer to the whole segment. + seg = p->segments; + while(1){ + if(seg == NULL) + break; + + segnext = seg->next; + + // .. + if(p->ondealloc != NULL){ + // walk over the segment, and call dealloc callback! + ptr = (char*)seg; + ptr += ALIGN_TO_16(sizeof(struct pool_segment)); + for(i = 0; i < seg->num_nodes_total; i++){ + niter = (struct node*)ptr; + ptr += sizeof(struct node); + ptr += p->elem_size; +#ifdef MEMPOOLASSERT + if(niter->magic != NODE_MAGIC){ + ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter); + continue; + } +#endif + + p->ondealloc( NODE_TO_DATA(niter) ); + + + } + }//endif: ondealloc callback? + + // simple .. + aFree(seg); + + seg = segnext; + } + + // Clear node ptr + p->free_list = NULL; + InterlockedExchange64(&p->num_nodes_free, 0); + InterlockedExchange64(&p->num_nodes_total, 0); + InterlockedExchange64(&p->num_segments, 0); + InterlockedExchange64(&p->num_bytes_total, 0); + + LeaveSpinLock(&p->nodeLock); + LeaveSpinLock(&p->segmentLock); + + // Free pool itself :D + aFree(p->name); + aFree(p); + +}//end: mempool_destroy() + + +void *mempool_node_get(mempool p){ + struct node *node; + int64 num_used; + + if(p->num_nodes_free < p->elem_realloc_thresh) + racond_signal(l_async_cond); + + while(1){ + + EnterSpinLock(&p->nodeLock); + + node = p->free_list; + if(node != NULL) + p->free_list = node->next; + + LeaveSpinLock(&p->nodeLock); + + if(node != NULL) + break; + + rathread_yield(); + } + + InterlockedDecrement64(&p->num_nodes_free); + + // Update peak value + num_used = (p->num_nodes_total - p->num_nodes_free); + if(num_used > p->peak_nodes_used){ + InterlockedExchange64(&p->peak_nodes_used, num_used); + } + +#ifdef MEMPOOLASSERT + node->used = true; +#endif + + return NODE_TO_DATA(node); +}//end: mempool_node_get() + + +void mempool_node_put(mempool p, void *data){ + struct node *node; + + node = DATA_TO_NODE(data); +#ifdef MEMPOOLASSERT + if(node->magic != NODE_MAGIC){ + ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name, data); + return; // lost, + } + + { + struct pool_segment *node_seg = node->segment; + if(node_seg->pool != p){ + ShowError("Mempool [%s] node_put faild, given node (data address %p) doesnt belongs to this pool. ( Node Origin is [%s] )\n", p->name, data, node_seg->pool); + return; + } + } + + // reset used flag. + node->used = false; +#endif + + // + EnterSpinLock(&p->nodeLock); + node->next = p->free_list; + p->free_list = node; + LeaveSpinLock(&p->nodeLock); + + InterlockedIncrement64(&p->num_nodes_free); + +}//end: mempool_node_put() + + +mempool_stats mempool_get_stats(mempool pool){ + mempool_stats stats; + + // initialize all with zeros + memset(&stats, 0x00, sizeof(mempool_stats)); + + stats.num_nodes_total = pool->num_nodes_total; + stats.num_nodes_free = pool->num_nodes_free; + stats.num_nodes_used = (stats.num_nodes_total - stats.num_nodes_free); + stats.num_segments = pool->num_segments; + stats.num_realloc_events= pool->num_realloc_events; + stats.peak_nodes_used = pool->peak_nodes_used; + stats.num_bytes_total = pool->num_bytes_total; + + // Pushing such a large block over the stack as return value isnt nice + // but lazy :) and should be okay in this case (Stats / Debug..) + // if you dont like it - feel free and refactor it. + return stats; +}//end: mempool_get_stats() + diff --git a/src/common/mempool.h b/src/common/mempool.h new file mode 100644 index 000000000..aeaebe7fe --- /dev/null +++ b/src/common/mempool.h @@ -0,0 +1,100 @@ +#ifndef _rA_MEMPOOL_H_ +#define _rA_MEMPOOL_H_ + +#include "../common/cbasetypes.h" + +typedef struct mempool *mempool; + +typedef void (*memPoolOnNodeAllocationProc)(void *ptr); +typedef void (*memPoolOnNodeDeallocationProc)(void *ptr); + +typedef struct mempool_stats{ + int64 num_nodes_total; + int64 num_nodes_free; + int64 num_nodes_used; + + int64 num_segments; + int64 num_realloc_events; + + int64 peak_nodes_used; + + int64 num_bytes_total; +} mempool_stats; + + +// +void mempool_init(); +void mempool_final(); + + +/** + * Creates a new Mempool + * + * @param name - Name of the pool (used for debug / error messages) + * @param elem_size - size of each element + * @param initial_count - preallocation count + * @param realloc_count - #no of nodes being allocated when pool is running empty. + * @param onNodeAlloc - Node Allocation callback (see @note!) + * @param onNodeDealloc - Node Deallocation callback (see @note!) + * + * @note: + * The onNode(De)alloc callbacks are only called once during segment allocation + * (pool initialization / rallocation ) + * you can use this callbacks for example to initlaize a mutex or somethingelse + * you definitly need during runtime + * + * @return not NULL + */ +mempool mempool_create(const char *name, + uint64 elem_size, + uint64 initial_count, + uint64 realloc_count, + + memPoolOnNodeAllocationProc onNodeAlloc, + memPoolOnNodeDeallocationProc onNodeDealloc); + + +/** + * Destroys a Mempool + * + * @param pool - the mempool to destroy + * + * @note: + * Everything gets deallocated, regardless if everything was freed properly! + * So you have to ensure that all references are cleared properly! + */ +void mempool_destroy(mempool pool); + + +/** + * Gets a new / empty node from the given mempool. + * + * @param pool - the pool to get an empty node from. + * + * @return Address of empty Node + */ +void *mempool_node_get(mempool pool); + + +/** + * Returns the given node to the given mempool + * + * @param pool - the pool to put the node, to + * @param node - the node to return + */ +void mempool_node_put(mempool pool, void *node); + + +/** + * Returns Statistics for the given mempool + * + * @param pool - the pool to get thats for + * + * @note: i dont like pushing masses of values over the stack, too - but its lazy and okay for stats. (blacksirius) + * + * @return stats struct + */ +mempool_stats mempool_get_stats(mempool pool); + + +#endif diff --git a/src/common/mmo.h b/src/common/mmo.h new file mode 100644 index 000000000..493f87691 --- /dev/null +++ b/src/common/mmo.h @@ -0,0 +1,750 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include "cbasetypes.h" +#include <time.h> + +// server->client protocol version +// 0 - pre-? +// 1 - ? - 0x196 +// 2 - ? - 0x78, 0x79 +// 3 - ? - 0x1c8, 0x1c9, 0x1de +// 4 - ? - 0x1d7, 0x1d8, 0x1d9, 0x1da +// 5 - 2003-12-18aSakexe+ - 0x1ee, 0x1ef, 0x1f0, ?0x1c4, 0x1c5? +// 6 - 2004-03-02aSakexe+ - 0x1f4, 0x1f5 +// 7 - 2005-04-11aSakexe+ - 0x229, 0x22a, 0x22b, 0x22c +// 20061023 - 2006-10-23aSakexe+ - 0x6b, 0x6d +// 20070521 - 2007-05-21aSakexe+ - 0x283 +// 20070821 - 2007-08-21aSakexe+ - 0x2c5 +// 20070918 - 2007-09-18aSakexe+ - 0x2d7, 0x2d9, 0x2da +// 20071106 - 2007-11-06aSakexe+ - 0x78, 0x7c, 0x22c +// 20080102 - 2008-01-02aSakexe+ - 0x2ec, 0x2ed , 0x2ee +// 20081126 - 2008-11-26aSakexe+ - 0x1a2 +// 20090408 - 2009-04-08aSakexe+ - 0x44a (dont use as it overlaps with RE client packets) +// 20080827 - 2008-08-27aRagexeRE+ - First RE Client +// 20081217 - 2008-12-17aRagexeRE+ - 0x6d (Note: This one still use old Char Info Packet Structure) +// 20081218 - 2008-12-17bRagexeRE+ - 0x6d (Note: From this one client use new Char Info Packet Structure) +// 20090603 - 2009-06-03aRagexeRE+ - 0x7d7, 0x7d8, 0x7d9, 0x7da +// 20090617 - 2009-06-17aRagexeRE+ - 0x7d9 +// 20090922 - 2009-09-22aRagexeRE+ - 0x7e5, 0x7e7, 0x7e8, 0x7e9 +// 20091103 - 2009-11-03aRagexeRE+ - 0x7f7, 0x7f8, 0x7f9 +// 20100105 - 2010-01-05aRagexeRE+ - 0x133, 0x800, 0x801 +// 20100126 - 2010-01-26aRagexeRE+ - 0x80e +// 20100223 - 2010-02-23aRagexeRE+ - 0x80f +// 20100413 - 2010-04-13aRagexeRE+ - 0x6b +// 20100629 - 2010-06-29aRagexeRE+ - 0x2d0, 0xaa, 0x2d1, 0x2d2 +// 20100721 - 2010-07-21aRagexeRE+ - 0x6b, 0x6d +// 20100727 - 2010-07-27aRagexeRE+ - 0x6b, 0x6d +// 20100803 - 2010-08-03aRagexeRE+ - 0x6b, 0x6d, 0x827, 0x828, 0x829, 0x82a, 0x82b, 0x82c, 0x842, 0x843 +// 20101124 - 2010-11-24aRagexeRE+ - 0x856, 0x857, 0x858 +// 20110111 - 2011-01-11aRagexeRE+ - 0x6b, 0x6d +// 20110928 - 2011-09-28aRagexeRE+ - 0x6b, 0x6d +// 20111025 - 2011-10-25aRagexeRE+ - 0x6b, 0x6d +// 20120307 - 2012-03-07aRagexeRE+ - 0x970 + +#ifndef PACKETVER + #define PACKETVER 20120410 + //#define PACKETVER 20111116 +#endif + +//Remove/Comment this line to disable sc_data saving. [Skotlex] +#define ENABLE_SC_SAVING +//Remove/Comment this line to disable server-side hot-key saving support [Skotlex] +//Note that newer clients no longer save hotkeys in the registry! +#define HOTKEY_SAVING + +#if PACKETVER < 20090603 + // (27 = 9 skills x 3 bars) (0x02b9,191) + #define MAX_HOTKEYS 27 +#elif PACKETVER < 20090617 + // (36 = 9 skills x 4 bars) (0x07d9,254) + #define MAX_HOTKEYS 36 +#else + // (38 = 9 skills x 4 bars & 2 Quickslots)(0x07d9,268) + #define MAX_HOTKEYS 38 +#endif + +#define MAX_MAP_PER_SERVER 1500 // Increased to allow creation of Instance Maps +#define MAX_INVENTORY 100 +//Max number of characters per account. Note that changing this setting alone is not enough if the client is not hexed to support more characters as well. +#define MAX_CHARS 9 +//Number of slots carded equipment can have. Never set to less than 4 as they are also used to keep the data of forged items/equipment. [Skotlex] +//Note: The client seems unable to receive data for more than 4 slots due to all related packets having a fixed size. +#define MAX_SLOTS 4 +//Max amount of a single stacked item +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 +#define MAX_FAME 1000000000 +#define MAX_CART 100 +#define MAX_SKILL 3100 +#define GLOBAL_REG_NUM 256 // max permanent character variables per char +#define ACCOUNT_REG_NUM 64 // max permanent local account variables per account +#define ACCOUNT_REG2_NUM 16 // max permanent global account variables per account +//Should hold the max of GLOBAL/ACCOUNT/ACCOUNT2 (needed for some arrays that hold all three) +#define MAX_REG_NUM 256 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 600 +#define MAX_GUILD_STORAGE 600 +#define MAX_PARTY 12 +#define MAX_GUILD 16+10*6 // increased max guild members +6 per 1 extension levels [Lupus] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPULSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 15 // increased max guild skills because of new skills [Sara-chan] +#define MAX_GUILDLEVEL 50 +#define MAX_GUARDIANS 8 //Local max per castle. [Skotlex] +#define MAX_QUEST_DB 2200 //Max quests that the server will load +#define MAX_QUEST_OBJECTIVES 3 //Max quest objectives for a quest + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +//For character names, title names, guilds, maps, etc. +//Includes null-terminator as it is the length of the array. +#define NAME_LENGTH (23 + 1) +//For item names, which tend to have much longer names. +#define ITEM_NAME_LENGTH 50 +//For Map Names, which the client considers to be 16 in length including the .gat extension +#define MAP_NAME_LENGTH (11 + 1) +#define MAP_NAME_LENGTH_EXT (MAP_NAME_LENGTH + 4) + +#define MAX_FRIENDS 40 +#define MAX_MEMOPOINTS 3 + +//Size of the fame list arrays. +#define MAX_FAME_LIST 10 + +//Limits to avoid ID collision with other game objects +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 +#define START_CHAR_NUM 150000 + +//Guilds +#define MAX_GUILDMES1 60 +#define MAX_GUILDMES2 120 + +//Base Homun skill. +#define HM_SKILLBASE 8001 +#define MAX_HOMUNSKILL 43 +#define MAX_HOMUNCULUS_CLASS 52 //[orn], Increased to 60 from 16 to allow new Homun-S. +#define HM_CLASS_BASE 6001 +#define HM_CLASS_MAX (HM_CLASS_BASE+MAX_HOMUNCULUS_CLASS-1) + +//Mail System +#define MAIL_MAX_INBOX 30 +#define MAIL_TITLE_LENGTH 40 +#define MAIL_BODY_LENGTH 200 + +//Mercenary System +#define MC_SKILLBASE 8201 +#define MAX_MERCSKILL 40 +#define MAX_MERCENARY_CLASS 44 + +//Elemental System +#define MAX_ELEMENTALSKILL 42 +#define EL_SKILLBASE 8401 +#define MAX_ELESKILLTREE 3 +#define MAX_ELEMENTAL_CLASS 12 +#define EL_CLASS_BASE 2114 +#define EL_CLASS_MAX (EL_CLASS_BASE+MAX_ELEMENTAL_CLASS-1) + +enum item_types { + IT_HEALING = 0, + IT_UNKNOWN, //1 + IT_USABLE, //2 + IT_ETC, //3 + IT_WEAPON, //4 + IT_ARMOR, //5 + IT_CARD, //6 + IT_PETEGG, //7 + IT_PETARMOR,//8 + IT_UNKNOWN2,//9 + IT_AMMO, //10 + IT_DELAYCONSUME,//11 + IT_CASH = 18, + IT_MAX +}; + + +//Questlog system [Kevin] [Inkfish] +typedef enum quest_state { Q_INACTIVE, Q_ACTIVE, Q_COMPLETE } quest_state; + +struct quest { + int quest_id; + unsigned int time; + int count[MAX_QUEST_OBJECTIVES]; + quest_state state; +}; + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; // location(s) where item is equipped (using enum equip_pos for bitmasking) + char identify; + char refine; + char attribute; + short card[MAX_SLOTS]; + unsigned int expire_time; + char favorite; + uint64 unique_id; +}; + +struct point { + unsigned short map; + short x,y; +}; + +enum e_skill_flag +{ + SKILL_FLAG_PERMANENT, + SKILL_FLAG_TEMPORARY, + SKILL_FLAG_PLAGIARIZED, + SKILL_FLAG_REPLACED_LV_0, // temporary skill overshadowing permanent skill of level 'N - SKILL_FLAG_REPLACED_LV_0', + SKILL_FLAG_PERM_GRANTED, // permanent, granted through someway e.g. script + //... +}; + +struct s_skill { + unsigned short id; + unsigned char lv; + unsigned char flag; // see enum e_skill_flag +}; + +struct global_reg { + char str[32]; + char value[256]; +}; + +//Holds array of global registries, used by the char server and converter. +struct accreg { + int account_id, char_id; + int reg_num; + struct global_reg reg[MAX_REG_NUM]; +}; + +//For saving status changes across sessions. [Skotlex] +struct status_change_data { + unsigned short type; //SC_type + long val1, val2, val3, val4, tick; //Remaining duration. +}; + +struct storage_data { + int storage_amount; + struct item items[MAX_STORAGE]; +}; + +struct guild_storage { + int dirty; + int guild_id; + short storage_status; + short storage_amount; + struct item items[MAX_GUILD_STORAGE]; + unsigned short lock; +}; + +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class_; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[NAME_LENGTH]; + char rename_flag; + char incuvate; +}; + +struct s_homunculus { //[orn] + char name[NAME_LENGTH]; + int hom_id; + int char_id; + short class_; + short prev_class; + int hp,max_hp,sp,max_sp; + unsigned int intimacy; //[orn] + short hunger; + struct s_skill hskill[MAX_HOMUNSKILL]; //albator + short skillpts; + short level; + unsigned int exp; + short rename_flag; + short vaporize; //albator + int str ; + int agi ; + int vit ; + int int_ ; + int dex ; + int luk ; + + char spiritball; //for homun S [lighta] +}; + +struct s_mercenary { + int mercenary_id; + int char_id; + short class_; + int hp, sp; + unsigned int kill_count; + unsigned int life_time; +}; + +struct s_elemental { + int elemental_id; + int char_id; + short class_; + int mode; + int hp, sp, max_hp, max_sp, matk, atk, atk2; + short hit, flee, amotion, def, mdef; + int life_time; +}; + +struct s_friend { + int account_id; + int char_id; + char name[NAME_LENGTH]; +}; + +#ifdef HOTKEY_SAVING +struct hotkey { + unsigned int id; + unsigned short lv; + unsigned char type; // 0: item, 1: skill +}; +#endif + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + int father; + int mother; + int child; + + unsigned int base_exp,job_exp; + int zeny; + + short class_; + unsigned int status_point,skill_point; + int hp,max_hp,sp,max_sp; + unsigned int option; + short manner; + unsigned char karma; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id,hom_id,mer_id,ele_id; + int fame; + + // Mercenary Guilds Rank + int arch_faith, arch_calls; + int spear_faith, spear_calls; + int sword_faith, sword_calls; + + short weapon; // enum weapon_type + short shield; // view-id + short head_top,head_mid,head_bottom; + short robe; + + char name[NAME_LENGTH]; + unsigned int base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char slot,sex; + + uint32 mapip; + uint16 mapport; + + struct point last_point,save_point,memo_point[MAX_MEMOPOINTS]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct storage_data storage; + struct s_skill skill[MAX_SKILL]; + + struct s_friend friends[MAX_FRIENDS]; //New friend system [Skotlex] +#ifdef HOTKEY_SAVING + struct hotkey hotkeys[MAX_HOTKEYS]; +#endif + bool show_equip; + short rename; + + time_t delete_date; +}; + +typedef enum mail_status { + MAIL_NEW, + MAIL_UNREAD, + MAIL_READ, +} mail_status; + +struct mail_message { + int id; + int send_id; + char send_name[NAME_LENGTH]; + int dest_id; + char dest_name[NAME_LENGTH]; + char title[MAIL_TITLE_LENGTH]; + char body[MAIL_BODY_LENGTH]; + + mail_status status; + time_t timestamp; // marks when the message was sent + + int zeny; + struct item item; +}; + +struct mail_data { + short amount; + bool full; + short unchecked, unread; + struct mail_message msg[MAIL_MAX_INBOX]; +}; + +struct auction_data { + unsigned int auction_id; + int seller_id; + char seller_name[NAME_LENGTH]; + int buyer_id; + char buyer_name[NAME_LENGTH]; + + struct item item; + // This data is required for searching, as itemdb is not read by char server + char item_name[ITEM_NAME_LENGTH]; + short type; + + unsigned short hours; + int price, buynow; + time_t timestamp; // auction's end time + int auction_end_timer; +}; + +struct registry { + int global_num; + struct global_reg global[GLOBAL_REG_NUM]; + int account_num; + struct global_reg account[ACCOUNT_REG_NUM]; + int account2_num; + struct global_reg account2[ACCOUNT_REG2_NUM]; +}; + +struct party_member { + int account_id; + int char_id; + char name[NAME_LENGTH]; + unsigned short class_; + unsigned short map; + unsigned short lv; + unsigned leader : 1, + online : 1; +}; + +struct party { + int party_id; + char name[NAME_LENGTH]; + unsigned char count; //Count of online characters. + unsigned exp : 1, + item : 2; //&1: Party-Share (round-robin), &2: pickup style: shared. + struct party_member member[MAX_PARTY]; +}; + +struct map_session_data; +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class_,lv; + uint64 exp; + int exp_payper; + short online,position; + char name[NAME_LENGTH]; + struct map_session_data *sd; + unsigned char modified; +}; + +struct guild_position { + char name[NAME_LENGTH]; + int mode; + int exp_mode; + unsigned char modified; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[NAME_LENGTH]; +}; + +struct guild_expulsion { + char name[NAME_LENGTH]; + char mes[40]; + int account_id; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + uint64 exp; + unsigned int next_exp; + int skill_point; + char name[NAME_LENGTH],master[NAME_LENGTH]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[MAX_GUILDMES1],mes2[MAX_GUILDMES2]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_expulsion expulsion[MAX_GUILDEXPULSION]; + struct guild_skill skill[MAX_GUILDSKILL]; + + unsigned short save_flag; // for TXT saving +}; + +struct guild_castle { + int castle_id; + int mapindex; + char castle_name[NAME_LENGTH]; + char castle_event[NAME_LENGTH]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + struct { + unsigned visible : 1; + int id; // object id + } guardian[MAX_GUARDIANS]; + int* temp_guardians; // ids of temporary guardians (mobs) + int temp_guardians_max; +}; + +struct fame_list { + int id; + int fame; + char name[NAME_LENGTH]; +}; + +enum { //Change Guild Infos + GBI_EXP =1, // Guild Experience (EXP) + GBI_GUILDLV, // Guild level + GBI_SKILLPOINT, // Guild skillpoints + GBI_SKILLLV, // Guild skill_lv ?? seem unused +}; + +enum { //Change Member Infos + GMI_POSITION =0, + GMI_EXP, + GMI_HAIR, + GMI_HAIR_COLOR, + GMI_GENDER, + GMI_CLASS, + GMI_LEVEL, +}; + +enum { + GD_SKILLBASE=10000, + GD_APPROVAL=10000, + GD_KAFRACONTRACT=10001, + GD_GUARDRESEARCH=10002, + GD_GUARDUP=10003, + GD_EXTENSION=10004, + GD_GLORYGUILD=10005, + GD_LEADERSHIP=10006, + GD_GLORYWOUNDS=10007, + GD_SOULCOLD=10008, + GD_HAWKEYES=10009, + GD_BATTLEORDER=10010, + GD_REGENERATION=10011, + GD_RESTORE=10012, + GD_EMERGENCYCALL=10013, + GD_DEVELOPMENT=10014, + GD_MAX, +}; + + +//These mark the ID of the jobs, as expected by the client. [Skotlex] +enum { + JOB_NOVICE, + JOB_SWORDMAN, + JOB_MAGE, + JOB_ARCHER, + JOB_ACOLYTE, + JOB_MERCHANT, + JOB_THIEF, + JOB_KNIGHT, + JOB_PRIEST, + JOB_WIZARD, + JOB_BLACKSMITH, + JOB_HUNTER, + JOB_ASSASSIN, + JOB_KNIGHT2, + JOB_CRUSADER, + JOB_MONK, + JOB_SAGE, + JOB_ROGUE, + JOB_ALCHEMIST, + JOB_BARD, + JOB_DANCER, + JOB_CRUSADER2, + JOB_WEDDING, + JOB_SUPER_NOVICE, + JOB_GUNSLINGER, + JOB_NINJA, + JOB_XMAS, + JOB_SUMMER, + JOB_MAX_BASIC, + + JOB_NOVICE_HIGH = 4001, + JOB_SWORDMAN_HIGH, + JOB_MAGE_HIGH, + JOB_ARCHER_HIGH, + JOB_ACOLYTE_HIGH, + JOB_MERCHANT_HIGH, + JOB_THIEF_HIGH, + JOB_LORD_KNIGHT, + JOB_HIGH_PRIEST, + JOB_HIGH_WIZARD, + JOB_WHITESMITH, + JOB_SNIPER, + JOB_ASSASSIN_CROSS, + JOB_LORD_KNIGHT2, + JOB_PALADIN, + JOB_CHAMPION, + JOB_PROFESSOR, + JOB_STALKER, + JOB_CREATOR, + JOB_CLOWN, + JOB_GYPSY, + JOB_PALADIN2, + + JOB_BABY, + JOB_BABY_SWORDMAN, + JOB_BABY_MAGE, + JOB_BABY_ARCHER, + JOB_BABY_ACOLYTE, + JOB_BABY_MERCHANT, + JOB_BABY_THIEF, + JOB_BABY_KNIGHT, + JOB_BABY_PRIEST, + JOB_BABY_WIZARD, + JOB_BABY_BLACKSMITH, + JOB_BABY_HUNTER, + JOB_BABY_ASSASSIN, + JOB_BABY_KNIGHT2, + JOB_BABY_CRUSADER, + JOB_BABY_MONK, + JOB_BABY_SAGE, + JOB_BABY_ROGUE, + JOB_BABY_ALCHEMIST, + JOB_BABY_BARD, + JOB_BABY_DANCER, + JOB_BABY_CRUSADER2, + JOB_SUPER_BABY, + + JOB_TAEKWON, + JOB_STAR_GLADIATOR, + JOB_STAR_GLADIATOR2, + JOB_SOUL_LINKER, + + JOB_GANGSI, + JOB_DEATH_KNIGHT, + JOB_DARK_COLLECTOR, + + JOB_RUNE_KNIGHT = 4054, + JOB_WARLOCK, + JOB_RANGER, + JOB_ARCH_BISHOP, + JOB_MECHANIC, + JOB_GUILLOTINE_CROSS, + + JOB_RUNE_KNIGHT_T, + JOB_WARLOCK_T, + JOB_RANGER_T, + JOB_ARCH_BISHOP_T, + JOB_MECHANIC_T, + JOB_GUILLOTINE_CROSS_T, + + JOB_ROYAL_GUARD, + JOB_SORCERER, + JOB_MINSTREL, + JOB_WANDERER, + JOB_SURA, + JOB_GENETIC, + JOB_SHADOW_CHASER, + + JOB_ROYAL_GUARD_T, + JOB_SORCERER_T, + JOB_MINSTREL_T, + JOB_WANDERER_T, + JOB_SURA_T, + JOB_GENETIC_T, + JOB_SHADOW_CHASER_T, + + JOB_RUNE_KNIGHT2, + JOB_RUNE_KNIGHT_T2, + JOB_ROYAL_GUARD2, + JOB_ROYAL_GUARD_T2, + JOB_RANGER2, + JOB_RANGER_T2, + JOB_MECHANIC2, + JOB_MECHANIC_T2, + + JOB_BABY_RUNE = 4096, + JOB_BABY_WARLOCK, + JOB_BABY_RANGER, + JOB_BABY_BISHOP, + JOB_BABY_MECHANIC, + JOB_BABY_CROSS, + + JOB_BABY_GUARD, + JOB_BABY_SORCERER, + JOB_BABY_MINSTREL, + JOB_BABY_WANDERER, + JOB_BABY_SURA, + JOB_BABY_GENETIC, + JOB_BABY_CHASER, + + JOB_BABY_RUNE2, + JOB_BABY_GUARD2, + JOB_BABY_RANGER2, + JOB_BABY_MECHANIC2, + + JOB_SUPER_NOVICE_E = 4190, + JOB_SUPER_BABY_E, + + JOB_KAGEROU = 4211, + JOB_OBORO, + + JOB_MAX, +}; + +enum { + SEX_FEMALE = 0, + SEX_MALE, + SEX_SERVER +}; + +// sanity checks... +#if MAX_ZENY > INT_MAX +#error MAX_ZENY is too big +#endif + +#endif /* _MMO_H_ */ diff --git a/src/common/mutex.c b/src/common/mutex.c new file mode 100644 index 000000000..6b4f55119 --- /dev/null +++ b/src/common/mutex.c @@ -0,0 +1,247 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifdef WIN32 +#include "../common/winapi.h" +#else +#include <pthread.h> +#include <time.h> +#include <sys/time.h> +#endif + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/mutex.h" + +struct ramutex{ +#ifdef WIN32 + CRITICAL_SECTION hMutex; +#else + pthread_mutex_t hMutex; +#endif +}; + + +struct racond{ +#ifdef WIN32 + HANDLE events[2]; + ra_align(8) volatile LONG nWaiters; + CRITICAL_SECTION waiters_lock; + +#define EVENT_COND_SIGNAL 0 +#define EVENT_COND_BROADCAST 1 + +#else + pthread_cond_t hCond; +#endif +}; + + +//////////////////// +// Mutex +// +// Implementation: +// + + +ramutex ramutex_create(){ + struct ramutex *m; + + m = (struct ramutex*)aMalloc( sizeof(struct ramutex) ); + if(m == NULL){ + ShowFatalError("ramutex_create: OOM while allocating %u bytes.\n", sizeof(struct ramutex)); + return NULL; + } + +#ifdef WIN32 + InitializeCriticalSection(&m->hMutex); +#else + pthread_mutex_init(&m->hMutex, NULL); +#endif + + return m; +}//end: ramutex_create() + + +void ramutex_destroy( ramutex m ){ + +#ifdef WIN32 + DeleteCriticalSection(&m->hMutex); +#else + pthread_mutex_destroy(&m->hMutex); +#endif + + aFree(m); + +}//end: ramutex_destroy() + + +void ramutex_lock( ramutex m ){ + +#ifdef WIN32 + EnterCriticalSection(&m->hMutex); +#else + pthread_mutex_lock(&m->hMutex); +#endif +}//end: ramutex_lock + + +bool ramutex_trylock( ramutex m ){ +#ifdef WIN32 + if(TryEnterCriticalSection(&m->hMutex) == TRUE) + return true; + + return false; +#else + if(pthread_mutex_trylock(&m->hMutex) == 0) + return true; + + return false; +#endif +}//end: ramutex_trylock() + + +void ramutex_unlock( ramutex m ){ +#ifdef WIN32 + LeaveCriticalSection(&m->hMutex); +#else + pthread_mutex_unlock(&m->hMutex); +#endif + +}//end: ramutex_unlock() + + + +/////////////// +// Condition Variables +// +// Implementation: +// + +racond racond_create(){ + struct racond *c; + + c = (struct racond*)aMalloc( sizeof(struct racond) ); + if(c == NULL){ + ShowFatalError("racond_create: OOM while allocating %u bytes\n", sizeof(struct racond)); + return NULL; + } + +#ifdef WIN32 + c->nWaiters = 0; + c->events[ EVENT_COND_SIGNAL ] = CreateEvent( NULL, FALSE, FALSE, NULL ); + c->events[ EVENT_COND_BROADCAST ] = CreateEvent( NULL, TRUE, FALSE, NULL ); + InitializeCriticalSection( &c->waiters_lock ); +#else + pthread_cond_init(&c->hCond, NULL); +#endif + + return c; +}//end: racond_create() + + +void racond_destroy( racond c ){ +#ifdef WIN32 + CloseHandle( c->events[ EVENT_COND_SIGNAL ] ); + CloseHandle( c->events[ EVENT_COND_BROADCAST ] ); + DeleteCriticalSection( &c->waiters_lock ); +#else + pthread_cond_destroy(&c->hCond); +#endif + + aFree(c); +}//end: racond_destroy() + + +void racond_wait( racond c, ramutex m, sysint timeout_ticks){ +#ifdef WIN32 + register DWORD ms; + int result; + bool is_last = false; + + + EnterCriticalSection(&c->waiters_lock); + c->nWaiters++; + LeaveCriticalSection(&c->waiters_lock); + + if(timeout_ticks < 0) + ms = INFINITE; + else + ms = (timeout_ticks > MAXDWORD) ? (MAXDWORD - 1) : (DWORD)timeout_ticks; + + + // we can release the mutex (m) here, cause win's + // manual reset events maintain state when used with + // SetEvent() + ramutex_unlock(m); + + result = WaitForMultipleObjects(2, c->events, FALSE, ms); + + + EnterCriticalSection(&c->waiters_lock); + c->nWaiters--; + if( (result == WAIT_OBJECT_0 + EVENT_COND_BROADCAST) && (c->nWaiters == 0) ) + is_last = true; // Broadcast called! + LeaveCriticalSection(&c->waiters_lock); + + + + // we are the last waiter that has to be notified, or to stop waiting + // so we have to do a manual reset + if(is_last == true) + ResetEvent( c->events[EVENT_COND_BROADCAST] ); + + + ramutex_lock(m); + +#else + if(timeout_ticks < 0){ + pthread_cond_wait( &c->hCond, &m->hMutex ); + }else{ + struct timespec wtime; + int64 exact_timeout = gettick() + timeout_ticks; + + wtime.tv_sec = exact_timeout/1000; + wtime.tv_nsec = (exact_timeout%1000)*1000000; + + pthread_cond_timedwait( &c->hCond, &m->hMutex, &wtime); + } + +#endif +}//end: racond_wait() + + +void racond_signal( racond c ){ +#ifdef WIN32 +// bool has_waiters = false; +// EnterCriticalSection(&c->waiters_lock); +// if(c->nWaiters > 0) +// has_waiters = true; +// LeaveCriticalSection(&c->waiters_lock); + +// if(has_waiters == true) + SetEvent( c->events[ EVENT_COND_SIGNAL ] ); +#else + pthread_cond_signal(&c->hCond); +#endif +}//end: racond_signal() + + +void racond_broadcast( racond c ){ +#ifdef WIN32 +// bool has_waiters = false; +// EnterCriticalSection(&c->waiters_lock); +// if(c->nWaiters > 0) +// has_waiters = true; +// LeaveCriticalSection(&c->waiters_lock); + +// if(has_waiters == true) + SetEvent( c->events[ EVENT_COND_BROADCAST ] ); +#else + pthread_cond_broadcast(&c->hCond); +#endif +}//end: racond_broadcast() + + diff --git a/src/common/mutex.h b/src/common/mutex.h new file mode 100644 index 000000000..1999627cd --- /dev/null +++ b/src/common/mutex.h @@ -0,0 +1,92 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_MUTEX_H_ +#define _rA_MUTEX_H_ + + +typedef struct ramutex *ramutex; // Mutex +typedef struct racond *racond; // Condition Var + +/** + * Creates a Mutex + * + * @return not NULL + */ +ramutex ramutex_create(); + +/** + * Destroys a Mutex + * + * @param m - the mutex to destroy + */ +void ramutex_destroy( ramutex m ); + +/** + * Gets a lock + * + * @param m - the mutex to lock + */ +void ramutex_lock( ramutex m); + +/** + * Trys to get the Lock + * + * @param m - the mutex try to lock + * + * @return boolean (true = got the lock) + */ +bool ramutex_trylock( ramutex m ); + +/** + * Unlocks a mutex + * + * @param m - the mutex to unlock + */ +void ramutex_unlock( ramutex m); + + +/** + * Creates a Condition variable + * + * @return not NULL + */ +racond racond_create(); + +/** + * Destroy a Condition variable + * + * @param c - the condition varaible to destroy + */ +void racond_destroy( racond c ); + +/** + * Waits Until state is signalled + * + * @param c - the condition var to wait for signalled state + * @param m - the mutex used for syncronization + * @param timeout_ticks - timeout in ticks ( -1 = INFINITE ) + */ +void racond_wait( racond c, ramutex m, sysint timeout_ticks); + +/** + * Sets the given condition var to signalled state + * + * @param c - condition var to set in signalled state. + * + * @note: + * Only one waiter gets notified. + */ +void racond_signal( racond c ); + +/** + * Sets notifys all waiting threads thats signalled. + * @param c - condition var to set in signalled state + * + * @note: + * All Waiters getting notified. + */ +void racond_broadcast( racond c ); + + +#endif diff --git a/src/common/netbuffer.c b/src/common/netbuffer.c new file mode 100644 index 000000000..57742d612 --- /dev/null +++ b/src/common/netbuffer.c @@ -0,0 +1,221 @@ + +// +// Network Buffer Subsystem (iobuffer) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "../common/cbasetypes.h" +#include "../common/atomic.h" +#include "../common/mempool.h" +#include "../common/showmsg.h" +#include "../common/raconf.h" +#include "../common/thread.h" +#include "../common/malloc.h" +#include "../common/core.h" + +#include "../common/netbuffer.h" + + +// +// Buffers are available in the following sizes: +// 48, 192, 2048, 8192 +// 65536 (inter server connects may use it for charstatus struct..) +// + + +/// +// Implementation: +// +static volatile int32 l_nEmergencyAllocations = 0; // stats. +static sysint l_nPools = 0; +static sysint *l_poolElemSize = NULL; +static mempool *l_pool = NULL; + + +void netbuffer_init(){ + char localsection[32]; + raconf conf; + sysint i; + + // Initialize Statistic counters: + l_nEmergencyAllocations = 0; + + // Set localsection name according to running serverype. + switch(SERVER_TYPE){ + case ATHENA_SERVER_LOGIN: strcpy(localsection, "login-netbuffer"); break; + case ATHENA_SERVER_CHAR: strcpy(localsection, "char-netbuffer"); break; + case ATHENA_SERVER_INTER: strcpy(localsection, "inter-netbuffer"); break; + case ATHENA_SERVER_MAP: strcpy(localsection, "map-netbuffer"); break; + default: strcpy(localsection, "unsupported_type"); break; + } + + + conf = raconf_parse("conf/network.conf"); + if(conf == NULL){ + ShowFatalError("Failed to Parse required Configuration (conf/network.conf)"); + exit(EXIT_FAILURE); + } + + // Get Values from config file + l_nPools = (sysint)raconf_getintEx(conf, localsection, "netbuffer", "num", 0); + if(l_nPools == 0){ + ShowFatalError("Netbuffer (network.conf) failure - requires at least 1 Pool.\n"); + exit(EXIT_FAILURE); + } + + // Allocate arrays. + l_poolElemSize = (sysint*)aCalloc( l_nPools, sizeof(sysint) ); + l_pool = (mempool*)aCalloc( l_nPools, sizeof(mempool) ); + + + for(i = 0; i < l_nPools; i++){ + int64 num_prealloc, num_realloc; + char key[32]; + + sprintf(key, "pool_%u_size", (uint32)i+1); + l_poolElemSize[i] = (sysint)raconf_getintEx(conf, localsection, "netbuffer", key, 4096); + if(l_poolElemSize[i] < 32){ + ShowWarning("Netbuffer (network.conf) failure - minimum allowed buffer size is 32 byte) - fixed.\n"); + l_poolElemSize[i] = 32; + } + + sprintf(key, "pool_%u_prealloc", (uint32)i+1); + num_prealloc = raconf_getintEx(conf, localsection, "netbuffer", key, 150); + + sprintf(key, "pool_%u_realloc_step", (uint32)i+1); + num_realloc = raconf_getintEx(conf, localsection, "netbuffer", key, 100); + + // Create Pool! + sprintf(key, "Netbuffer %u", (uint32)l_poolElemSize[i]); // name. + + // Info + ShowInfo("NetBuffer: Creating Pool %u (Prealloc: %u, Realloc Step: %u) - %0.2f MiB\n", l_poolElemSize[i], num_prealloc, num_realloc, (float)((sizeof(struct netbuf) + l_poolElemSize[i] - 32)* num_prealloc)/1024.0f/1024.0f); + + // + // Size Calculation: + // struct netbuf + requested buffer size - 32 (because the struct already contains 32 byte buffer space at the end of struct) + l_pool[i] = mempool_create(key, (sizeof(struct netbuf) + l_poolElemSize[i] - 32), num_prealloc, num_realloc, NULL, NULL); + if(l_pool[i] == NULL){ + ShowFatalError("Netbuffer: cannot create Pool for %u byte buffers.\n", l_poolElemSize[i]); + // @leak: clean everything :D + exit(EXIT_FAILURE); + } + + }// + + + raconf_destroy(conf); + +}//end: netbuffer_init() + + +void netbuffer_final(){ + sysint i; + + if(l_nPools > 0){ + /// .. finalize mempools + for(i = 0; i < l_nPools; i++){ + mempool_stats stats = mempool_get_stats(l_pool[i]); + + ShowInfo("Netbuffer: Freeing Pool %u (Peak Usage: %u, Realloc Events: %u)\n", l_poolElemSize[i], stats.peak_nodes_used, stats.num_realloc_events); + + mempool_destroy(l_pool[i]); + } + + if(l_nEmergencyAllocations > 0){ + ShowWarning("Netbuffer: did %u Emergency Allocations, please tune your network.conf!\n", l_nEmergencyAllocations); + l_nEmergencyAllocations = 0; + } + + aFree(l_poolElemSize); l_poolElemSize = NULL; + aFree(l_pool); l_pool = NULL; + l_nPools = 0; + } + + +}//end: netbuffer_final() + + +netbuf netbuffer_get( sysint sz ){ + sysint i; + netbuf nb = NULL; + + // Search an appropriate pool + for(i = 0; i < l_nPools; i++){ + if(sz <= l_poolElemSize[i]){ + // match + + nb = (netbuf)mempool_node_get(l_pool[i]); + nb->pool = i; + + break; + } + } + + // No Bufferpool found that mets there quirements?.. (thats bad..) + if(nb == NULL){ + ShowWarning("Netbuffer: get(%u): => no appropriate pool found - emergency allocation required.\n", sz); + ShowWarning("Please reconfigure your network.conf!"); + + InterlockedIncrement(&l_nEmergencyAllocations); + + // .. better to check (netbuf struct provides 32 byte bufferspace itself. + if(sz < 32) sz = 32; + + // allocate memory using malloc .. + while(1){ + nb = (netbuf) aMalloc( (sizeof(struct netbuf) + sz - 32) ); + if(nb != NULL){ + memset(nb, 0x00, (sizeof(struct netbuf) + sz - 32) ); // zero memory! (to enforce commit @ os.) + nb->pool = -1; // emergency alloc. + break; + } + + rathread_yield(); + }// spin allocation. + + } + + + nb->refcnt = 1; // Initial refcount is 1 + + return nb; +}//end: netbuffer_get() + + +void netbuffer_put( netbuf nb ){ + + // Decrement reference counter, if > 0 do nothing :) + if( InterlockedDecrement(&nb->refcnt) > 0 ) + return; + + // Is this buffer an emergency allocated buffer? + if(nb->pool == -1){ + aFree(nb); + return; + } + + + // Otherwise its a normal mempool based buffer + // return it to the according mempool: + mempool_node_put( l_pool[nb->pool], nb); + + +}//end: netbuffer_put() + + +void netbuffer_incref( netbuf nb ){ + + InterlockedIncrement(&nb->refcnt); + +}//end: netbuf_incref() diff --git a/src/common/netbuffer.h b/src/common/netbuffer.h new file mode 100644 index 000000000..844241226 --- /dev/null +++ b/src/common/netbuffer.h @@ -0,0 +1,83 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_NETBUFFER_H_ +#define _rA_NETBUFFER_H_ + +#include "../common/cbasetypes.h" + +typedef struct netbuf{ + sysint pool; // The pool ID this buffer belongs to, + // is set to -1 if its an emergency allocated buffer + + struct netbuf *next; // Used by Network system. + + volatile int32 refcnt; // Internal Refcount, it gets lowered every call to netbuffer_put, + // if its getting zero, the buffer will returned back to the pool + // and can be reused. + + int32 dataPos; // Current Offset + // Used only for Reading (recv job) + // write cases are using the sessions local datapos member due to + // shared write buffer support. + + int32 dataLen; // read buffer case: + // The length expected to read to. + // when this->dataPos == dateLen, read job has been completed. + // write buffer case: + // The lngth of data in te buffer + // when s->dataPos == dataLen, write job has been completed + // + // Note: + // leftBytes = (dateLen - dataPos) + // + // Due to shared buffer support + // dataPos gets not used in write case (each connection has its local offset) + // + + // The Bufferspace itself. + char buf[32]; +} *netbuf; + + +void netbuffer_init(); +void netbuffer_final(); + +/** + * Gets a netbuffer that has atleast (sz) byes space. + * + * @note: The netbuffer system guarantees that youll always recevie a buffer. + * no check for null is required! + * + * @param sz - minimum size needed. + * + * @return pointer to netbuf struct + */ +netbuf netbuffer_get( sysint sz ); + + +/** + * Returns the given netbuffer (decreases refcount, if its 0 - the buffer will get returned to the pool) + * + * @param buf - the buffer to return + */ +void netbuffer_put( netbuf buf ); + + +/** + * Increases the Refcount on the given buffer + * (used for areasends .. etc) + * + */ +void netbuffer_incref( netbuf buf ); + + +// Some Useful macros +#define NBUFP(netbuf,pos) (((uint8*)(netbuf->buf)) + (pos)) +#define NBUFB(netbuf,pos) (*(uint8*)((netbuf->buf) + (pos))) +#define NBUFW(netbuf,pos) (*(uint16*)((netbuf->buf) + (pos))) +#define NBUFL(netbuf,pos) (*(uint32*)((netbuf->buf) + (pos))) + + + +#endif diff --git a/src/common/network.c b/src/common/network.c new file mode 100644 index 000000000..1f1621363 --- /dev/null +++ b/src/common/network.c @@ -0,0 +1,1061 @@ +// +// Network Subsystem (previously known as socket system) +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// +//#ifdef HAVE_ACCETP4 +#define _GNU_SOURCE +//#endif + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/timer.h" +#include "../common/evdp.h" +#include "../common/netbuffer.h" + +#include "../common/network.h" + +#define ENABLE_IPV6 +#define HAVE_ACCEPT4 +#define EVENTS_PER_CYCLE 10 +#define PARANOID_CHECKS + +// Local Vars (settings..) +static int l_ListenBacklog = 64; + +// +// Global Session Array (previously exported as session[] +// +SESSION g_Session[MAXCONN]; + + +// +static bool onSend(int32 fd); + + +#define _network_free_netbuf_async( buf ) add_timer( 0, _network_async_free_netbuf_proc, 0, (intptr_t) buf) +static int _network_async_free_netbuf_proc(int tid, unsigned int tick, int id, intptr_t data){ + // netbuf is in data + netbuffer_put( (netbuf)data ); + + return 0; +}//end: _network_async_free_netbuf_proc() + + + +void network_init(){ + SESSION *s; + int32 i; + + memset(g_Session, 0x00, (sizeof(SESSION) * MAXCONN) ); + + for(i = 0; i < MAXCONN; i++){ + s = &g_Session[i]; + + s->type = NST_FREE; + s->disconnect_in_progress = false; + + } + + // Initialize the correspondig event dispatcher + evdp_init(); + + // + add_timer_func_list(_network_async_free_netbuf_proc, "_network_async_free_netbuf_proc"); + +}//end: network_init() + + +void network_final(){ + + // @TODO: + // .. disconnect and cleanup everything! + + evdp_final(); + +}//end: network_final() + + +void network_do(){ + struct EVDP_EVENT l_events[EVENTS_PER_CYCLE]; + register struct EVDP_EVENT *ev; + register int n, nfds; + register SESSION *s; + + nfds = evdp_wait( l_events, EVENTS_PER_CYCLE, 1000); // @TODO: timer_getnext() + + for(n = 0; n < nfds; n++){ + ev = &l_events[n]; + s = &g_Session[ ev->fd ]; + + if(ev->events & EVDP_EVENT_HUP){ + network_disconnect( ev->fd ); + continue; // no further event processing. + }// endif vent is HUP (disconnect) + + + if(ev->events & EVDP_EVENT_IN){ + + if(s->onRecv != NULL){ + if( false == s->onRecv(ev->fd) ){ + network_disconnect(ev->fd); + continue; // .. + } + }else{ + ShowError("network_do: fd #%u has no onRecv proc set. - disconnecting\n", ev->fd); + network_disconnect(ev->fd); + continue; + } + + }// endif event is IN (recv) + + + if(ev->events & EVDP_EVENT_OUT){ + if(s->onSend != NULL){ + if( false == s->onSend(ev->fd) ){ + network_disconnect(ev->fd); + continue; + } + }else{ + ShowError("network_do: fd #%u has no onSend proc set. - disconnecting\n", ev->fd); + network_disconnect(ev->fd); + continue; + } + }// endif event is OUT (send) + + }//endfor + +}//end: network_do() + + +static bool _setnonblock(int32 fd){ + int flags = fcntl(fd, F_GETFL, 0); + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) + return false; + + return true; +}//end: _setnonblock() + + +static bool _network_accept(int32 fd){ + SESSION *listener = &g_Session[fd]; + SESSION *s; + union{ + struct sockaddr_in v4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 v6; +#endif + } _addr; + int newfd; + socklen_t addrlen; + struct sockaddr *addr; + + // Accept until OS returns - nothing to accept anymore + // - this is required due to our EVDP abstraction. (which handles on listening sockets similar to epoll's EPOLLET flag.) + while(1){ +#ifdef ENABLE_IPV6 + if(listener->v6 == true){ + addrlen = sizeof(_addr.v6); + addr = (struct sockaddr*)&_addr.v6; + }else{ +#endif + addrlen = sizeof(_addr.v4); + addr = (struct sockaddr*)&_addr.v4; +#ifdef ENABLE_IPV6 + } +#endif + +#ifdef HAVE_ACCEPT4 + newfd = accept4(fd, addr, &addrlen, SOCK_NONBLOCK); +#else + newfd = accept(fd, addr, &addrlen); +#endif + + if(newfd == -1){ + if(errno == EAGAIN || errno == EWOULDBLOCK) + break; // this is fully valid & whished., se explaination on top of while(1) + + // Otherwis .. we have serious problems :( seems tahat our listner has gone away.. + // @TODO handle this .. + ShowError("_network_accept: accept() returned error. closing listener. (errno: %u / %s)\n", errno, strerror(errno)); + + return false; // will call disconnect after return. + //break; + } + +#ifndef HAVE_ACCEPT4 // no accept4 means, we have to set nonblock by ourself. .. + if(_setnonblock(newfd) == false){ + ShowError("_network_accept: failed to set newly accepted connection nonblocking (errno: %u / %s). - disconnecting.\n", errno, strerror(errno)); + close(newfd); + continue; + } +#endif + + // Check connection limits. + if(newfd >= MAXCONN){ + ShowError("_network_accept: failed to accept connection - MAXCONN (%u) exceeded.\n", MAXCONN); + close(newfd); + continue; // we have to loop over the events (and disconnect them too ..) but otherwise we would leak event notifications. + } + + + // Create new Session. + s = &g_Session[newfd]; + s->type = NST_CLIENT; + + // The new connection inherits listenr's handlers. + s->onDisconnect = listener->onDisconnect; + s->onConnect = listener->onConnect; // maybe useless but .. fear the future .. :~ + + // Register the new connection @ EVDP + if( evdp_addclient(newfd, &s->evdp_data) == false){ + ShowError("_network_accept: failed to accept connection - event subsystem returned an error.\n"); + close(newfd); + s->type = NST_FREE; + } + + // Call the onConnect handler on the listener. + if( listener->onConnect(newfd) == false ){ + // Resfused by onConnect handler.. + evdp_remove(newfd, &s->evdp_data); + + close(newfd); + s->type = NST_FREE; + + s->data = NULL; // be on the safe side ~ ! + continue; + } + + + } + + return true; +}//end: _network_accept() + + +void network_disconnect(int32 fd){ + SESSION *s = &g_Session[fd]; + netbuf b, bn; + + // Prevent recursive calls + // by wrong implemented on disconnect handlers.. and such.. + if(s->disconnect_in_progress == true) + return; + + s->disconnect_in_progress = true; + + + // Disconnect Todo: + // - Call onDisconnect Handler + // - Release all Assigned buffers. + // - remove from event system (notifications) + // - cleanup session structure + // - close connection. + // + + if(s->onDisconnect != NULL && + s->type != NST_LISTENER){ + + s->onDisconnect( fd ); + } + + // Read Buffer + if(s->read.buf != NULL){ + netbuffer_put(s->read.buf); + s->read.buf = NULL; + } + + // Write Buffer(s) + b = s->write.buf; + while(1){ + if(b == NULL) break; + + bn = b->next; + + netbuffer_put(b); + + b = bn; + } + s->write.buf = NULL; + s->write.buf_last = NULL; + + s->write.n_outstanding = 0; + s->write.max_outstanding = 0; + + + // Remove from event system. + evdp_remove(fd, &s->evdp_data); + + // Cleanup Session Structure. + s->type = NST_FREE; + s->data = NULL; // no application level data assigned + s->disconnect_in_progress = false; + + + // Close connection + close(fd); + +}//end: network_disconnect() + + +int32 network_addlistener(bool v6, const char *addr, uint16 port){ + SESSION *s; + int optval, fd; + +#if !defined(ENABLE_IPV6) + if(v6 == true){ + ShowError("network_addlistener(%c, '%s', %u): this release has no IPV6 support.\n", (v6==true?'t':'f'), addr, port); + return -1; + } +#endif + + +#ifdef ENABLE_IPV6 + if(v6 == true) + fd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + fd = socket(AF_INET, SOCK_STREAM, 0); + + // Error? + if(fd == -1){ + ShowError("network_addlistener(%c, '%s', %u): socket() failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + return -1; + } + + // Too many connections? + if(fd >= MAXCONN){ + ShowError("network_addlistener(%c, '%s', %u): cannot create listener, exceeds more than supported connections (%u).\n", (v6==true?'t':'f'), addr, port, MAXCONN); + close(fd); + return -1; + } + + + s = &g_Session[fd]; + if(s->type != NST_FREE){ // additional checks.. :) + ShowError("network_addlistener(%c, '%s', %u): failed, got fd #%u which is already in use in local session table?!\n", (v6==true?'t':'f'), addr, port, fd); + close(fd); + return -1; + } + + + // Fill ip addr structs +#ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&s->addr.v6, 0x00, sizeof(s->addr.v6)); + s->addr.v6.sin6_family = AF_INET6; + s->addr.v6.sin6_port = htons(port); + if(inet_pton(AF_INET6, addr, &s->addr.v6.sin6_addr) != 1){ + ShowError("network_addlistener(%c, '%s', %u): failed to parse the given IPV6 address.\n", (v6==true?'t':'f'), addr, port); + close(fd); + return -1; + } + + }else{ +#endif + memset(&s->addr.v4, 0x00, sizeof(s->addr.v4)); + s->addr.v4.sin_family = AF_INET; + s->addr.v4.sin_port = htons(port); + s->addr.v4.sin_addr.s_addr = inet_addr(addr); +#ifdef ENABLE_IPV6 + } +#endif + + + // if OS has support for SO_REUSEADDR, apply the flag + // so the address could be used when there're still time_wait sockets outstanding from previous application run. +#ifdef SO_REUSEADDR + optval=1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); +#endif + + // Bind +#ifdef ENABLE_IPV6 + if(v6 == true){ + if( bind(fd, (struct sockaddr*)&s->addr.v6, sizeof(s->addr.v6)) == -1) { + ShowError("network_addlistener(%c, '%s', %u): bind failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + }else{ +#endif + if( bind(fd, (struct sockaddr*)&s->addr.v4, sizeof(s->addr.v4)) == -1) { + ShowError("network_addlistener(%c, '%s', %u): bind failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } +#ifdef ENABLE_IPV6 + } +#endif + + if( listen(fd, l_ListenBacklog) == -1){ + ShowError("network_addlistener(%c, '%s', %u): listen failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Set to nonblock! + if(_setnonblock(fd) == false){ + ShowError("network_addlistener(%c, '%s', %u): cannot set to nonblock (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Rgister @ evdp. + if( evdp_addlistener(fd, &s->evdp_data) != true){ + ShowError("network_addlistener(%c, '%s', %u): eventdispatcher subsystem returned an error.\n", (v6==true?'t':'f'), addr, port); + close(fd); + return -1; + } + + + // Apply flags on Session array for this conneciton. + if(v6 == true) s->v6 = true; + else s->v6 = false; + + s->type = NST_LISTENER; + s->onRecv = _network_accept; + + ShowStatus("Added Listener on '%s':%u\n", addr, port, (v6==true ? "(ipv6)":"(ipv4)") ); + + return fd; +}//end: network_addlistener() + + +static bool _network_connect_establishedHandler(int32 fd){ + register SESSION *s = &g_Session[fd]; + int val; + socklen_t val_len; + + if(s->type == NST_FREE) + return true; // due to multiple non coalesced event notifications + // this can happen .. when a previous handled event has already disconnected the connection + // within the same cycle.. + + val = -1; + val_len = sizeof(val); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &val_len); + + if(val != 0){ + // :( .. cleanup session.. + s->type = NST_FREE; + s->onSend = NULL; + s->onConnect = NULL; + s->onDisconnect = NULL; + + evdp_remove(fd, &s->evdp_data); + close(fd); + + return true; // we CANT return false, + // becuase the normal disconnect procedure would execute the ondisconnect handler, which we dont want .. in this case. + }else{ + // ok + if(s->onConnect(fd) == false) { + // onConnect handler has refused the connection .. + // cleanup .. and ok + s->type = NST_FREE; + s->onSend = NULL; + s->onConnect = NULL; + s->onDisconnect = NULL; + + evdp_remove(fd, &s->evdp_data); + close(fd); + + return true; // we dnot want the ondisconnect handler to be executed, so its okay to handle this by ourself. + } + + // connection established ! + // + if( evdp_outgoingconnection_established(fd, &s->evdp_data) == false ){ + return false; // we want the normal disconnect procedure.. with call to ondisconnect handler. + } + + s->onSend = NULL; + + ShowStatus("#%u connection successfull!\n", fd); + } + + return true; +}//end: _network_connect_establishedHandler() + + +int32 network_connect(bool v6, + const char *addr, + uint16 port, + const char *from_addr, + uint16 from_port, + bool (*onConnectionEstablishedHandler)(int32 fd), + void (*onConnectionLooseHandler)(int32 fd) +){ + register SESSION *s; + int32 fd, optval, ret; + struct sockaddr_in ip4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 ip6; +#endif + +#ifdef ENABLE_IPV6 + if(v6 == true) + fd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + fd = socket(AF_INET, SOCK_STREAM, 0); + +#ifndef ENABLE_IPV6 + // check.. + if(v6 == true){ + ShowError("network_connect(%c, '%s', %u...): tried to create an ipv6 connection, IPV6 is not supported in this release.\n", (v6==true?'t':'f'), addr, port); + return -1; + } +#endif + + // check connection limits. + if(fd >= MAXCONN){ + ShowError("network_connect(%c, '%s', %u...): cannot create new connection, exceeeds more than supported connections (%u)\n", (v6==true?'t':'f'), addr, port ); + close(fd); + return -1; + } + + + // Originating IP/Port pair given ? + if(from_addr != NULL && *from_addr != 0){ + //.. + #ifdef SO_REUSEADDR + optval=1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + #endif + + #ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&ip6, 0x00, sizeof(ip6)); + ip6.sin6_family = AF_INET6; + ip6.sin6_port = htons(from_port); + + if(inet_pton(AF_INET6, from_addr, &ip6.sin6_addr) != 1){ + ShowError("network_connect(%c, '%s', %u...): cannot parse originating (from) IPV6 address (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + ret = bind(fd, (struct sockaddr*)&ip6, sizeof(ip6)); + }else{ + #endif + memset(&ip4, 0x00, sizeof(ip4)); + + ip4.sin_family = AF_INET; + ip4.sin_port = htons(from_port); + ip4.sin_addr.s_addr = inet_addr(from_addr); + ret = bind(fd, (struct sockaddr*)&ip4, sizeof(ip4)); + #ifdef ENABLE_IPV6 + } + #endif + + } + + + // Set non block + if(_setnonblock(fd) == false){ + ShowError("network_connect(%c, '%s', %u...): cannot set socket to nonblocking (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + + // Create ip addr block to connect to .. +#ifdef ENABLE_IPV6 + if(v6 == true){ + memset(&ip6, 0x00, sizeof(ip6)); + ip6.sin6_family = AF_INET6; + ip6.sin6_port = htons(port); + + if(inet_pton(AF_INET6, addr, &ip6.sin6_addr) != 1){ + ShowError("network_connect(%c, '%s', %u...): cannot parse destination IPV6 address (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + close(fd); + return -1; + } + + }else{ +#endif + memset(&ip4, 0x00, sizeof(ip4)); + + ip4.sin_family = AF_INET; + ip4.sin_port = htons(port); + ip4.sin_addr.s_addr = inet_addr(addr); +#ifdef ENABLE_IPV6 + } +#endif + + + // Assign Session.. + s = &g_Session[fd]; + s->type = NST_OUTGOING; + s->v6 = v6; + s->onConnect = onConnectionEstablishedHandler; + s->onDisconnect = onConnectionLooseHandler; + s->onRecv = NULL; + s->onSend = _network_connect_establishedHandler; +#ifdef ENABLE_IPV6 + if(v6 == true) + memcpy(&s->addr.v6, &ip6, sizeof(ip6)); + else +#endif + memcpy(&s->addr.v4, &ip4, sizeof(ip4)); + + // Register @ EVDP. as outgoing (see doc of the function) + if(evdp_addconnecting(fd, &s->evdp_data) == false){ + ShowError("network_connect(%c, '%s', %u...): eventdispatcher subsystem returned an error.\n", (v6==true?'t':'f'), addr, port); + + // cleanup session x.x.. + s->type = NST_FREE; + s->onConnect = NULL; + s->onDisconnect = NULL; + s->onSend = NULL; + + // close, return error code. + close(fd); + return -1; + } + + +#ifdef ENABLE_IPV6 + if(v6 == true) + ret = connect(fd, (struct sockaddr*)&ip6, sizeof(ip6)); + else +#endif + ret = connect(fd, (struct sockaddr*)&ip4, sizeof(ip4)); + + + // + if(ret != 0 && errno != EINPROGRESS){ + ShowWarning("network_connect(%c, '%s', %u...): connection failed (errno: %u / %s)\n", (v6==true?'t':'f'), addr, port, errno, strerror(errno)); + + // Cleanup session .. + s->type = NST_FREE; + s->onConnect = NULL; + s->onDisconnect = NULL; + s->onSend = NULL; + + // .. remove from evdp and close fd. + evdp_remove(fd, &s->evdp_data); + close(fd); + return -1; + } + + + // ! The Info Message :~D + ShowStatus("network_connect fd#%u (%s:%u) in progress.. \n", fd, addr, port); + +return fd; +}//end: network_connect() + + +static bool _onSend(int32 fd){ + register SESSION *s = &g_Session[fd]; + register netbuf buf, buf_next; + register uint32 szNeeded; + register int wLen; + + if(s->type == NST_FREE) + return true; // Possible due to multipl non coalsced event notifications + // so onSend gets called after disconnect caused by an previous vent. + // we can ignore the call to onSend, then. + + buf = s->write.buf; + while(1){ + if(buf == NULL) + break; + + buf_next = buf->next; + + + szNeeded = (buf->dataLen - s->write.dataPos); // using th session-local .dataPos member, due to shared write buffer support. + + // try to write. + wLen = write(fd, &buf->buf[s->write.dataPos], szNeeded); + if(wLen == 0){ + return false; // eof. + }else if(wLen == -1){ + if(errno == EAGAIN || errno == EWOULDBLOCK) + return true; // dont disconnect / try again later. + + // all other errors. . + return false; + } + + // Wrote data.. => + szNeeded -= wLen; + if(szNeeded > 0){ + // still data left .. + // + s->write.dataPos += wLen; // fix offset. + return true; + }else{ + // this buffer has been written successfully + // could be returned to pool. + netbuffer_put(buf); + s->write.n_outstanding--; // When threadsafe -> Interlocked here. + s->write.dataPos = 0; + } + + + buf = buf_next; + } + + // okay, + // reaching this part means: + // while interrupted by break - + // which means all buffers are written, nothing left + // + + s->write.buf_last = NULL; + s->write.buf = NULL; + s->write.n_outstanding = 0; + s->write.dataPos = 0; + + // Remove from event dispatcher (write notification) + // + evdp_writable_remove(fd, &s->evdp_data); + + return true; +}//end: _onSend() + + +static bool _onRORecv(int32 fd){ + register SESSION *s = &g_Session[fd]; + register uint32 szNeeded; + register char *p; + register int rLen; + + if(s->type == NST_FREE) + return true; // Possible due to multiple non coalesced events by evdp. + // simply ignore this call returning positive result. + + // Initialize p and szNeeded depending on change + // + switch(s->read.state){ + case NRS_WAITOP: + szNeeded = s->read.head_left; + p = ((char*)&s->read.head[0]) + (2-szNeeded); + break; + + case NRS_WAITLEN: + szNeeded = s->read.head_left; + p = ((char*)&s->read.head[1]) + (2-szNeeded); + break; + + case NRS_WAITDATA:{ + register netbuf buf = s->read.buf; + + szNeeded = (buf->dataLen - buf->dataPos); + p = (char*)&buf->buf[ buf->dataPos ]; + } + break; + + default: + // .. the impossible gets possible .. + ShowError("_onRORecv: fd #%u has unknown read.state (%d) - disconnecting\n", fd, s->read.state); + return false; + break; + } + + + // + + rLen = read(fd, p, szNeeded); + if(rLen == 0){ + // eof.. + return false; + }else if(rLen == -1){ + + if(errno == EAGAIN || errno == EWOULDBLOCK){ + // try again later .. (this case shouldnt happen, because we're event trigered.. but .. sometimes it happens :) + return true; + } + + // an additional interesting case would be + // EINTR, this 'could' be handled .. but: + // posix says that its possible that data gets currupted during irq + // or data gor read and not reported.., so we'd have a data loss.. + // (which shouldnt happen with stream based protocols such as tcp) + // its better to disonnect the client in that case. + + return false; + } + + // + // Got Data: + // next action also depends on current state .. + // + szNeeded -= rLen; + switch(s->read.state){ + case NRS_WAITOP: + + if(szNeeded > 0){ + // still data missing .. + s->read.head_left = szNeeded; + return true; // wait for completion. + }else{ + // complete .. + // next state depends on packet type. + + s->read.head[1] = ((uint16*)s->netparser_data)[ s->read.head[0] ]; // store lenght of packet by opcode head[0] to head[1] + + if(s->read.head[1] == ROPACKET_UNKNOWN){ + // unknown packet - disconnect + ShowWarning("_onRORecv: fd #%u got unlnown packet 0x%04x - disconnecting.\n", fd, s->read.head[0]); + return false; + } + else if(s->read.head[1] == ROPACKET_DYNLEN){ + // dynamic length + // next state: requrie len. + s->read.state = NRS_WAITLEN; + s->read.head_left = 2; + return true; // + } + else if(s->read.head[1] == 2){ + // packet has no data (only opcode) + register netbuf buf = netbuffer_get(2); // :D whoohoo its giant! + + NBUFW(buf, 0) = s->read.head[0]; // store opcode @ packet begin. + buf->dataPos = 2; + buf->dataLen = 2; + buf->next = NULL; + + // Back to initial state -> Need opcode. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // Call completion routine here. + s->onPacketComplete(fd, s->read.head[0], 2, buf); + + return true; // done :) + } + else{ + // paket needs .. data .. + register netbuf buf = netbuffer_get( s->read.head[1] ); + + NBUFW(buf, 0) = s->read.head[0]; // store opcode @ packet begin. + buf->dataPos = 2; + buf->dataLen = s->read.head[1]; + buf->next = NULL; + + // attach buffer. + s->read.buf = buf; + + // set state: + s->read.state = NRS_WAITDATA; + + return true; + } + + }//endif: szNeeded > 0 (opcode read completed?) + + break; + + + case NRS_WAITLEN: + + if(szNeeded > 0){ + // incomplete .. + s->read.head_left = szNeeded; + return true; + }else{ + + if(s->read.head[1] == 4){ + // packet has no data (only opcode + length) + register netbuf buf = netbuffer_get( 4 ); + + NBUFL(buf, 0) = *((uint32*)&s->read.head[0]); // copy Opcode + length to netbuffer using MOVL + buf->dataPos = 4; + buf->dataLen = 4; + buf->next = NULL; + + // set initial state (need opcode) + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // call completion routine. + s->onPacketComplete(fd, s->read.head[0], 4, buf); + + return true; + } + else if(s->read.head[1] < 4){ + // invalid header. + ShowWarning("_onRORecv: fd #%u invalid header - got packet 0x%04x, reported length < 4 - INVALID - disconnecting\n", fd, s->read.head[0]); + return false; + } + else{ + // Data needed + // next state -> waitdata! + register netbuf buf = netbuffer_get( s->read.head[1] ); + + NBUFL(buf, 0) = *((uint32*)&s->read.head[0]); // copy Opcode + length to netbuffer using MOVL + buf->dataPos = 4; + buf->dataLen = s->read.head[1]; + buf->next = NULL; + + // attach to session: + s->read.buf = buf; + s->read.state = NRS_WAITDATA; + + return true; + } + + }//endif: szNeeded > 0 (length read complete?) + + break; + + + case NRS_WAITDATA: + + if(szNeeded == 0){ + // Packet finished! + // compltion. + register netbuf buf = s->read.buf; + + // set initial state. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + s->read.buf = NULL; + + // Call completion routine. + s->onPacketComplete(fd, NBUFW(buf, 0), buf->dataLen, buf); + + return true; + }else{ + // still data needed + s->read.buf->dataPos += rLen; + + return true; + } + break; + + + // + default: + ShowError("_onRORecv: fd #%u has unknown read.state (%d) [2] - disconnecting\n", fd, s->read.state); + return false; + break; + } + + + return false; +}//end: _onRORecv() + + +void network_send(int32 fd, netbuf buf){ + register SESSION *s = &g_Session[fd]; + +#ifdef PARANOID_CHECKS + if(fd >= MAXCONN){ + ShowError("network_send: tried to attach buffer to connection idientifer #%u which is out of bounds.\n", fd); + _network_free_netbuf_async(buf); + return; + } +#endif + + + if(s->type == NST_FREE) + return; + + // Check Max Outstanding buffers limit. + if( (s->write.max_outstanding > 0) && + (s->write.n_outstanding >= s->write.max_outstanding) ){ + + ShowWarning("network_send: fd #%u max Outstanding buffers exceeded. - disconnecting.\n", fd); + network_disconnect(fd); + // + _network_free_netbuf_async(buf); + return; + } + + + // Attach to the end: + buf->next = NULL; + if(s->write.buf_last != NULL){ + s->write.buf_last->next = buf; + s->write.buf_last = buf; + + }else{ + // currently no buffer attached. + s->write.buf = s->write.buf_last = buf; + + // register @ evdp for writable notification. + evdp_writable_add(fd, &s->evdp_data); // + } + + + // + s->write.n_outstanding++; + +}//end: network_send() + + +void network_parser_set_ro(int32 fd, + int16 *packetlentable, + void (*onPacketCompleteProc)(int32 fd, uint16 op, uint16 len, netbuf buf) + ){ + register SESSION *s = &g_Session[fd]; + register netbuf b, nb; // used for potential free attached buffers. + + if(s->type == NST_FREE) + return; + + s->onPacketComplete = onPacketCompleteProc; + + s->onRecv = _onRORecv; // .. + s->onSend = _onSend; // Using the normal generic netbuf based send function. + + s->netparser_data = packetlentable; + + // Initial State -> Need Packet OPCode. + s->read.state = NRS_WAITOP; + s->read.head_left = 2; + + + // Detach (if..) all buffers. + if(s->read.buf != NULL){ + _network_free_netbuf_async(s->read.buf); // + s->read.buf = NULL; + } + + if(s->write.buf != NULL){ + b = s->write.buf; + while(1){ + nb = b->next; + + _network_free_netbuf_async(b); + + b = nb; + } + + s->write.buf = NULL; + s->write.buf_last = NULL; + s->write.n_outstanding = 0; + } + + // not changing any limits on outstanding .. + // + +}//end: network_parser_set_ro() diff --git a/src/common/network.h b/src/common/network.h new file mode 100644 index 000000000..d7b463a2f --- /dev/null +++ b/src/common/network.h @@ -0,0 +1,189 @@ +#ifndef _rA_NETWORK_H_ +#define _rA_NETWORK_H_ + +#include <netinet/in.h> +#include "../common/cbasetypes.h" +#include "../common/netbuffer.h" +#include "../common/evdp.h" + +#ifndef MAXCONN +#define MAXCONN 16384 +#endif + + +typedef struct SESSION{ + EVDP_DATA evdp_data; // Must be always the frist member! (some evdp's may rely on this fact) + + // Connection Type + enum{ NST_FREE=0, NST_LISTENER = 1, NST_CLIENT=2, NST_OUTGOING=3} type; + + // Flags / Settings. + bool v6; // is v6? + bool disconnect_in_progress; // To prevent stack overflows / recursive calls. + + + union{ // union to save memory. + struct sockaddr_in v4; + struct sockaddr_in6 v6; + }addr; + + + // "lowlevel" Handlers + // (Implemented by the protocol specific parser) + // + bool (*onRecv)(int32 fd); // return false = disconnect + bool (*onSend)(int32 fd); // return false = disconnect + + // Event Handlers for LISTENER type sockets + // + // onConnect gets Called when a connection has been + // successfully accepted. + // Session entry is available in this Handler! + // A returncode of false will reejct the connection (disconnect) + // Note: When rejecting a connection in onConnect by returning false + // The onDisconnect handler wont get called! + // Note: the onConnect Handler is also responsible for setting + // the appropriate netparser (which implements onRecv/onSend..) [protocol specific] + // + // onDisconnect gets called when a connection gets disconnected + // (by peer as well as by core) + // + bool (*onConnect)(int32 fd); // return false = disconnect (wont accept) + void (*onDisconnect)(int32 fd); + + + // + // Parser specific data + // + void *netparser_data; // incase of RO Packet Parser, pointer to packet len table (uint16array) + void (*onPacketComplete)(int32 fd, uint16 op, uint16 len, netbuf buf); + + + // + // Buffers + // + struct{ + enum NETREADSTATE { NRS_WAITOP = 0, NRS_WAITLEN = 1, NRS_WAITDATA = 2} state; + + uint32 head_left; + uint16 head[2]; + + netbuf buf; + } read; + + struct{ + uint32 max_outstanding; + uint32 n_outstanding; + + uint32 dataPos; + + netbuf buf, buf_last; + } write; + + // Application Level data Pointer + // (required for backward compatibility with previous athena socket system.) + void *data; + +} SESSION; + + +/** + * Subsystem Initialization / Finalization. + * + */ +void network_init(); +void network_final(); + + +/** + * Will do the net work :) .. + */ +void network_do(); + + +/** + * Adds a new listner. + * + * @param v6 v6 listner? + * @param *addr the address to listen on. + * @param port port to listen on + * + * @return -1 on error otherwise the identifier of the new listener. + */ +int32 network_addlistener(bool v6, const char *addr, uint16 port); + + +/** + * Tries to establish an outgoing connection. + * + * @param v6 operate with IPv6 addresses? + * @param addr the address to connect to + * @param port the port to connect to + * @param from_addr the address to connect from (local source / optional if auto -> NULL) + * @param from_port the port to connect from (local source / optional if auto -> 0) + * @param onConnectionEstablishedHandler callback that gets called when the connection is established. + * @param onConnectionLooseHandler callback that gets called when the connection gets disconnected (or the connection couldnt be established) + * + * @return -1 on error otherwise the identifier of the new connection + */ +int32 network_connect(bool v6, + const char *addr, + uint16 port, + const char *from_addr, + uint16 from_port, + bool (*onConnectionEstablishedHandler)(int32 fd), + void (*onConnectionLooseHandler)(int32 fd) +); + + + +/** + * Disconnects the given connection + * + * @param fd connection identifier. + * + * @Note: + * - onDisconnect callback gets called! + * - cleares (returns) all assigned buffers + * + */ +void network_disconnect(int32 fd); + + +/** + * Attach's a netbuffer at the end of sending queue to the given connection + * + * @param fd connection identifier + * @param buf netbuffer to attach. + */ +void network_send(int32 fd, netbuf buf); + + +/** + * Sets the parser to RO Protocol like Packet Parser. + * + * @param fd connection identifier + * @param *packetlentable pointer to array of uint16 in size of UINT16_MAX, + * @param onComplteProc callback for packet completion. + * + * @note: + * PacketLen Table Fromat: + * each element's offsets represents th ro opcode. + * value is length. + * a length of 0 means the packet is dynamic. + * a length of UINT16_MAX means the packet is unknown. + * + * Static Packets must contain their hader in len so (0x64 == 55 ..) + * + */ +void network_parser_set_ro(int32 fd, + int16 *packetlentable, + void (*onPacketCompleteProc)(int32 fd, uint16 op, uint16 len, netbuf buf) + ); +#define ROPACKET_UNKNOWN UINT16_MAX +#define ROPACKET_DYNLEN 0 + + + + +#endif diff --git a/src/common/nullpo.c b/src/common/nullpo.c new file mode 100644 index 000000000..4383109a7 --- /dev/null +++ b/src/common/nullpo.c @@ -0,0 +1,91 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "nullpo.h" +#include "../common/showmsg.h" +// #include "logs.h" // •z΂µ‚Ä‚Ý‚é + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullƒ`ƒFƒbƒN ‹y‚Ñ î•ño—Í + *--------------------------------------*/ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) +{ + va_list ap; + + if (target != NULL) + return 0; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); + return 1; +} + +int nullpo_chk(const char *file, int line, const char *func, const void *target) +{ + if (target != NULL) + return 0; + + nullpo_info_core(file, line, func, NULL, NULL); + return 1; +} + + +/*====================================== + * nullpoî•ño—Í(ŠO•”ŒÄo‚µŒü‚¯ƒ‰ƒbƒp) + *--------------------------------------*/ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); +} + +void nullpo_info(const char *file, int line, const char *func) +{ + nullpo_info_core(file, line, func, NULL, NULL); +} + + +/*====================================== + * nullpoî•ño—Í(Main) + *--------------------------------------*/ +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap) +{ + if (file == NULL) + file = "??"; + + func = + func == NULL ? "unknown": + func[0] == '\0' ? "unknown": + func; + + ShowMessage("--- nullpo info --------------------------------------------\n"); + ShowMessage("%s:%d: in func `%s'\n", file, line, func); + if (fmt != NULL) + { + if (fmt[0] != '\0') + { + vprintf(fmt, ap); + + // ÅŒã‚ɉüs‚µ‚½‚©Šm”F + if (fmt[strlen(fmt)-1] != '\n') + ShowMessage("\n"); + } + } + ShowMessage("--- end nullpo info ----------------------------------------\n"); + + // ‚±‚±‚ç‚ÅnullpoƒƒO‚ðƒtƒ@ƒCƒ‹‚É‘‚«o‚¹‚½‚ç + // ‚Ü‚Æ‚ß‚Ä’ño‚Å‚«‚é‚È‚ÆŽv‚Á‚Ä‚¢‚½‚èB +} diff --git a/src/common/nullpo.h b/src/common/nullpo.h new file mode 100644 index 000000000..8ee86a782 --- /dev/null +++ b/src/common/nullpo.h @@ -0,0 +1,225 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#include "../common/cbasetypes.h" + +#define NLP_MARK __FILE__, __LINE__, __func__ + +// enabled by default on debug builds +#if defined(DEBUG) && !defined(NULLPO_CHECK) +#define NULLPO_CHECK +#endif + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullƒ`ƒFƒbƒN ‹y‚Ñ î•ño—ÍŒã return + *E“WŠJ‚·‚é‚Æif‚Æ‚©return“™‚ªo‚é‚Ì‚Å + * ˆês’P‘Ì‚ÅŽg‚Á‚Ä‚‚¾‚³‚¢B + *Enullpo_ret(x = func()); + * ‚̂悤‚ÈŽg—p–@‚à‘z’肵‚Ä‚¢‚Ü‚·B + *-------------------------------------- + * nullpo_ret(t) + * –ß‚è’l 0ŒÅ’è + * [ˆø”] + * t ƒ`ƒFƒbƒN‘ÎÛ + *-------------------------------------- + * nullpo_retv(t) + * –ß‚è’l ‚È‚µ + * [ˆø”] + * t ƒ`ƒFƒbƒN‘ÎÛ + *-------------------------------------- + * nullpo_retr(ret, t) + * –ß‚è’l Žw’è + * [ˆø”] + * ret return(ret); + * t ƒ`ƒFƒbƒN‘ÎÛ + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * Ú×î•ño—Í—p + * –ß‚è’l 0 + * [ˆø”] + * t ƒ`ƒFƒbƒN‘ÎÛ + * fmt ... vprintf‚É“n‚³‚ê‚é + * ”õl‚âŠÖŒW•Ï”‚Ì‘‚«o‚µ‚È‚Ç‚É + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * Ú×î•ño—Í—p + * –ß‚è’l ‚È‚µ + * [ˆø”] + * t ƒ`ƒFƒbƒN‘ÎÛ + * fmt ... vprintf‚É“n‚³‚ê‚é + * ”õl‚âŠÖŒW•Ï”‚Ì‘‚«o‚µ‚È‚Ç‚É + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * Ú×î•ño—Í—p + * –ß‚è’l Žw’è + * [ˆø”] + * ret return(ret); + * t ƒ`ƒFƒbƒN‘ÎÛ + * fmt ... vprintf‚É“n‚³‚ê‚é + * ”õl‚âŠÖŒW•Ï”‚Ì‘‚«o‚µ‚È‚Ç‚É + *-------------------------------------- + */ + +#if defined(NULLPO_CHECK) + +#define nullpo_ret(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(0);} + +#define nullpo_retv(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return;} + +#define nullpo_retr(ret, t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(ret);} + +#define nullpo_retb(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {break;} + +// ‰Â•Ïˆø”ƒ}ƒNƒ‚ÉŠÖ‚·‚éðŒƒRƒ“ƒpƒCƒ‹ +#if __STDC_VERSION__ >= 199901L +/* C99‚ɑΉž */ +#define nullpo_ret_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(0);} + +#define nullpo_retv_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return;} + +#define nullpo_retr_f(ret, t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(ret);} + +#define nullpo_retb_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {break;} + +#elif __GNUC__ >= 2 +/* GCC—p */ +#define nullpo_ret_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(0);} + +#define nullpo_retv_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return;} + +#define nullpo_retr_f(ret, t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(ret);} + +#define nullpo_retb_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {break;} + +#else + +/* ‚»‚Ì‘¼‚Ìê‡EEE orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// —Ç‚¢•û–@‚ªŽv‚¢‚‚©‚È‚©‚Á‚½‚Ì‚ÅEEE‹ê“÷‚Ìô‚Å‚·B +// ˆê‰žƒ[ƒjƒ“ƒO‚Ío‚È‚¢‚Í‚¸ + +#define nullpo_ret(t) (void)(t) +#define nullpo_retv(t) (void)(t) +#define nullpo_retr(ret, t) (void)(t) +#define nullpo_retb(t) (void)(t) + +// ‰Â•Ïˆø”ƒ}ƒNƒ‚ÉŠÖ‚·‚éðŒƒRƒ“ƒpƒCƒ‹ +#if __STDC_VERSION__ >= 199901L +/* C99‚ɑΉž */ +#define nullpo_ret_f(t, fmt, ...) (void)(t) +#define nullpo_retv_f(t, fmt, ...) (void)(t) +#define nullpo_retr_f(ret, t, fmt, ...) (void)(t) +#define nullpo_retb_f(t, fmt, ...) (void)(t) + +#elif __GNUC__ >= 2 +/* GCC—p */ +#define nullpo_ret_f(t, fmt, args...) (void)(t) +#define nullpo_retv_f(t, fmt, args...) (void)(t) +#define nullpo_retr_f(ret, t, fmt, args...) (void)(t) +#define nullpo_retb_f(t, fmt, args...) (void)(t) + +#else +/* ‚»‚Ì‘¼‚Ìê‡EEE orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullƒ`ƒFƒbƒN ‹y‚Ñ î•ño—Í + * [ˆø”] + * file __FILE__ + * line __LINE__ + * func __func__ (ŠÖ”–¼) + * ‚±‚ê‚ç‚É‚Í NLP_MARK ‚ðŽg‚¤‚Æ‚æ‚¢ + * target ƒ`ƒFƒbƒN‘ÎÛ + * [•Ô‚è’l] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullƒ`ƒFƒbƒN ‹y‚Ñ ÚׂÈî•ño—Í + * [ˆø”] + * file __FILE__ + * line __LINE__ + * func __func__ (ŠÖ”–¼) + * ‚±‚ê‚ç‚É‚Í NLP_MARK ‚ðŽg‚¤‚Æ‚æ‚¢ + * target ƒ`ƒFƒbƒN‘ÎÛ + * fmt ... vprintf‚É“n‚³‚ê‚é + * ”õl‚âŠÖŒW•Ï”‚Ì‘‚«o‚µ‚È‚Ç‚É + * [•Ô‚è’l] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) + __attribute__((format(printf,5,6))); + + +/*====================================== + * nullpo_info + * nullpoî•ño—Í + * [ˆø”] + * file __FILE__ + * line __LINE__ + * func __func__ (ŠÖ”–¼) + * ‚±‚ê‚ç‚É‚Í NLP_MARK ‚ðŽg‚¤‚Æ‚æ‚¢ + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpoÚ×î•ño—Í + * [ˆø”] + * file __FILE__ + * line __LINE__ + * func __func__ (ŠÖ”–¼) + * ‚±‚ê‚ç‚É‚Í NLP_MARK ‚ðŽg‚¤‚Æ‚æ‚¢ + * fmt ... vprintf‚É“n‚³‚ê‚é + * ”õl‚âŠÖŒW•Ï”‚Ì‘‚«o‚µ‚È‚Ç‚É + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif /* _NULLPO_H_ */ diff --git a/src/common/raconf.c b/src/common/raconf.c new file mode 100644 index 000000000..2703560ff --- /dev/null +++ b/src/common/raconf.c @@ -0,0 +1,584 @@ +// +// Athena style config parser +// (would be better to have "one" implementation instead of .. 4 :) +// +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) RAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/cbasetypes.h" +#include "../common/showmsg.h" +#include "../common/db.h" +#include "../common/malloc.h" + +#include "../common/raconf.h" + +#define SECTION_LEN 32 +#define VARNAME_LEN 64 + +struct raconf { + DBMap *db; +}; + + +struct conf_value{ + int64 intval; + bool bval; + double floatval; + size_t strval_len; // not includung \0 + char strval[16]; +}; + + + +static struct conf_value *makeValue(const char *key, char *val, size_t val_len){ + struct conf_value *v; + char *p; + size_t sz; + + sz = sizeof(struct conf_value); + if(val_len >= sizeof(v->strval)) + sz += (val_len - sizeof(v->strval) + 1); + + v = (struct conf_value*)aCalloc(1, sizeof(struct conf_value)); + if(v == NULL){ + ShowFatalError("raconf: makeValue => Out of Memory while allocating new node.\n"); + return NULL; + } + + memcpy(v->strval, val, val_len); + v->strval[val_len+1] = '\0'; + v->strval_len = val_len; + + + // Parse boolean value: + if((val_len == 4) && (strncmpi("true", val, 4) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("yes", val, 3) == 0)) + v->bval = true; + else if((val_len == 3) && (strncmpi("oui", val, 3) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("si", val, 2) == 0)) + v->bval = true; + else if((val_len == 2) && (strncmpi("ja", val, 2) == 0)) + v->bval = true; + else if((val_len == 1) && (*val == '1')) + v->bval = true; + else if((val_len == 5) && (strncmpi("false", val, 5) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 3) && (strncmpi("non", val, 3) == 0)) + v->bval = false; + else if((val_len == 2) && (strncmpi("no", val, 2) == 0)) + v->bval = false; + else if((val_len == 4) && (strncmpi("nein", val, 4) == 0)) + v->bval = false; + else if((val_len == 1) && (*val == '0')) + v->bval = false; + else + v->bval = false; // assume false. + + // Parse number + // Supported formats: + // prefix: 0x hex . + // postix: h for hex + // b for bin (dual) + if( (val_len >= 1 && (val[val_len] == 'h')) || (val_len >= 2 && (val[0] == '0' && val[1] == 'x')) ){//HEX! + if(val[val_len] == 'h'){ + val[val_len]= '\0'; + v->intval = strtoull(val, NULL, 16); + val[val_len] = 'h'; + }else + v->intval = strtoull(&val[2], NULL, 16); + }else if( val_len >= 1 && (val[val_len] == 'b') ){ //BIN + val[val_len] = '\0'; + v->intval = strtoull(val, NULL, 2); + val[val_len] = 'b'; + }else if( *val >='0' && *val <= '9'){ // begins with normal digit, so assume its dec. + // is it float? + bool is_float = false; + + for(p = val; *p != '\0'; p++){ + if(*p == '.'){ + v->floatval = strtod(val, NULL); + v->intval = (int64) v->floatval; + is_float = true; + break; + } + } + + if(is_float == false){ + v->intval = strtoull(val, NULL, 10); + v->floatval = (double) v->intval; + } + }else{ + // Everything else: lets use boolean for fallback + if(v->bval == true) + v->intval = 1; + else + v->intval = 0; + } + + return v; +}//end: makeValue() + + +static bool configParse(raconf inst, const char *fileName){ + FILE *fp; + char line[4096]; + char currentSection[SECTION_LEN]; + char *p; + char c; + int linecnt; + size_t linelen; + size_t currentSection_len; + + fp = fopen(fileName, "r"); + if(fp == NULL){ + ShowError("configParse: cannot open '%s' for reading.\n", fileName); + return false; + } + + + // Start with empty section: + currentSection[0] = '\0'; + currentSection_len = 0; + + // + linecnt = 0; + while(1){ + linecnt++; + + if(fgets(line, sizeof(line), fp) != line) + break; + + linelen = strlen(line); + p = line; + + // Skip whitespaces from beginning (space and tab) + _line_begin_skip_whities: + c = *p; + if(c == ' ' || c == '\t'){ + p++; + linelen--; + goto _line_begin_skip_whities; + } + + // Remove linebreaks as (cr or lf) and whitespaces from line end! + _line_end_skip_whities_and_breaks: + c = p[linelen-1]; + if(c == '\r' || c == '\n' || c == ' ' || c == '\t'){ + p[--linelen] = '\0'; + goto _line_end_skip_whities_and_breaks; + } + + // Empty line? + // or line starts with comment (commented out)? + if(linelen == 0 || (p[0] == '/' && p[1] == '/') || p[0] == ';') + continue; + + // Variable names can contain: + // A-Za-z-_.0-9 + // + // Sections start with [ .. ] (INI Style) + // + c = *p; + + // check what we have.. :) + if(c == '['){ // got section! + // Got Section! + // Search for ] + char *start = (p+1); + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Section name in %s:%u (expected ']')\n", fileName, linecnt); + fclose(fp); + return false; + }else if(c == ']'){ // closing backet (section name termination) + if( (p - start + 1) > (sizeof(currentSection) ) ){ + ShowError("Syntax Error: Section name in %s:%u is too large (max Supported length: %u chars)\n", fileName, linecnt, sizeof(currentSection)-1); + fclose(fp); + return false; + } + + // Set section! + *p = '\0'; // add termination here. + memcpy(currentSection, start, (p-start)+1 ); // we'll copy \0, too! (we replaced the ] backet with \0.) + currentSection_len = (p-start); + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == ' ') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. (allowed char / specifier) + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Section name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse section name + + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // Got variable! + // Search for '=' or ':' wich termiantes the name + char *start = p; + char *valuestart = NULL; + size_t start_len; + + while(1){ + ++p; + c = *p; + + if(c == '\0'){ + ShowError("Syntax Error: unterminated Variable name in %s:%u\n", fileName, linecnt); + fclose(fp); + return false; + }else if( (c == '=') || (c == ':') ){ + // got name termination + + *p = '\0'; // Terminate it so (start) will hold the pointer to the name. + + break; + + }else if( (c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ){ + // skip .. allowed char + continue; + }else{ + ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Variable name.\n", c, fileName, linecnt, (p-line)); + fclose(fp); + return false; + } + + }//endwhile: parse var name + + start_len = (p-start); + if(start_len >= VARNAME_LEN){ + ShowError("%s:%u Variable length exceeds limit of %u Characters.\n", fileName, linecnt, VARNAME_LEN-1); + fclose(fp); + return false; + }else if(start_len == 0){ + ShowError("%s:%u Empty Variable name is not allowed.\n", fileName, linecnt); + fclose(fp); + return false; + } + + + valuestart = (p+1); + + + // Skip whitespace from begin of value (tab and space) + _skip_value_begin_whities: + c = *valuestart; + if(c == ' ' || c == '\t'){ + valuestart++; + goto _skip_value_begin_whities; + } + + // Scan for value termination, + // wich can be \0 or comment start (// or ; (INI) ) + // + p = valuestart; + while(1){ + c = *p; + if(c == '\0'){ + // Terminated by line end. + break; + }else if(c == '/' && p[1] == '/'){ + // terminated by c++ style comment. + *p = '\0'; + break; + }else if(c == ';'){ + // terminated by ini style comment. + *p = '\0'; + break; + } + + p++; + }//endwhile: search var value end. + + + // Strip whitespaces from end of value. + if(valuestart != p){ // not empty! + p--; + _strip_value_end_whities: + c = *p; + if(c == ' ' || c == '\t'){ + *p = '\0'; + p--; + goto _strip_value_end_whities; + } + p++; + } + + + // Buildin Hook: + if( stricmp(start, "import") == 0){ + if( configParse(inst, valuestart) != true){ + ShowError("%s:%u - Import of '%s' failed!\n", fileName, linecnt, valuestart); + } + }else{ + // put it to db. + struct conf_value *v, *o; + char key[ (SECTION_LEN+VARNAME_LEN+1+1) ]; //+1 for delimiter, +1 for termination. + size_t section_len; + + if(*currentSection == '\0'){ // empty / none + strncpy(key, "<unnamed>",9); + section_len = 9; + }else{ + strncpy(key, currentSection, currentSection_len); + section_len = currentSection_len; + } + + key[section_len] = '.'; // Delim + + strncpy(&key[section_len+1], start, start_len); + + key[section_len + start_len + 1] = '\0'; + + + v = makeValue(key, valuestart, (p-valuestart) ); + + // Try to get the old one before + o = strdb_get(inst->db, key); + if(o != NULL){ + strdb_remove(inst->db, key); + aFree(o); // + } + + strdb_put( inst->db, key, v); + } + + + }else{ + ShowError("Syntax Error: unexpected Character '%c' in %s:%u (offset %u)\n", c, fileName, linecnt, (p-line) ); + fclose(fp); + return false; + } + + + + } + + + + fclose(fp); + return true; +}//end: configParse() + + +#define MAKEKEY(dest, section, key) { size_t section_len, key_len; \ + if(section == NULL || *section == '\0'){ \ + strncpy(dest, "<unnamed>", 9); \ + section_len = 9; \ + }else{ \ + section_len = strlen(section); \ + strncpy(dest, section, section_len); \ + } \ + \ + dest[section_len] = '.'; \ + \ + key_len = strlen(key); \ + strncpy(&dest[section_len+1], key, key_len); \ + dest[section_len + key_len + 1] = '\0'; \ + } + + +raconf raconf_parse(const char *file_name){ + struct raconf *rc; + + rc = aCalloc(1, sizeof(struct raconf) ); + if(rc == NULL){ + ShowFatalError("raconf_parse: failed to allocate memory for new handle\n"); + return NULL; + } + + rc->db = strdb_alloc(DB_OPT_BASE | DB_OPT_DUP_KEY, 98); + // + + if(configParse(rc, file_name) != true){ + ShowError("Failed to Parse Configuration file '%s'\n", file_name); + } + + return rc; +}//end: raconf_parse() + + +void raconf_destroy(raconf rc){ + DBIterator *iter; + struct conf_value *v; + + // Clear all entrys in db. + iter = db_iterator(rc->db); + for( v = (struct conf_value*)dbi_first(iter); dbi_exists(iter); v = (struct conf_value*)dbi_next(iter) ){ + aFree(v); + } + dbi_destroy(iter); + + db_destroy(rc->db); + + aFree(rc); + +}//end: raconf_destroy() + +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->bval; +}//end: raconf_getbool() + + +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return (float)v->floatval; +}//end: raconf_getfloat() + + +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->intval; + +}//end: raconf_getint() + + +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + + v = strdb_get(rc->db, keystr); + if(v == NULL) + return _default; + else + return v->strval; +}//end: raconf_getstr() + + +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->bval; + } + + }else{ + return v->bval; + } +}//end: raconf_getboolEx() + + +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return (float)v->floatval; + } + + }else{ + return (float)v->floatval; + } + +}//end: raconf_getfloatEx() + + +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->intval; + } + + }else{ + return v->intval; + } + +}//end: raconf_getintEx() + + +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default){ + char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1]; + struct conf_value *v; + + MAKEKEY(keystr, section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + + MAKEKEY(keystr, fallback_section, key); + v = strdb_get(rc->db, keystr); + if(v == NULL){ + return _default; + }else{ + return v->strval; + } + + }else{ + return v->strval; + } + +}//end: raconf_getstrEx() diff --git a/src/common/raconf.h b/src/common/raconf.h new file mode 100644 index 000000000..68a2b51b2 --- /dev/null +++ b/src/common/raconf.h @@ -0,0 +1,59 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _rA_CONF_H_ +#define _rA_CONF_H_ + +#include "../common/cbasetypes.h" + +// rAthena generic configuration file parser +// +// Config file Syntax is athena style +// extended with ini style support (including sections) +// +// Comments are started with // or ; (ini style) +// + +typedef struct raconf *raconf; + + +/** + * Parses a rAthna Configuration file + * + * @param file_name path to the file to parse + * + * @returns not NULL incase of success + */ +raconf raconf_parse(const char *file_name); + + +/** + * Frees a Handle received from raconf_parse + * + * @param rc - the handle to free + */ +void raconf_destroy(raconf rc); + + +/** + * Gets the value for Section / Key pair, if key not exists returns _default! + * + */ +bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default); +float raconf_getfloat(raconf rc,const char *section, const char *key, float _default); +int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default); +const char* raconf_getstr(raconf rc, const char *section, const char *key, const char *_default); + +/** + * Gets the value for Section / Key pair, but has fallback section option if not found in section, + * if not found in both - default gets returned. + * + */ +bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default); +float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default); +int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default); +const char* raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default); + + + +#endif diff --git a/src/common/random.c b/src/common/random.c new file mode 100644 index 000000000..5c048c7eb --- /dev/null +++ b/src/common/random.c @@ -0,0 +1,83 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/showmsg.h" +#include "../common/timer.h" // gettick +#include "random.h" +#if defined(WIN32) + #include "../common/winapi.h" +#elif defined(HAVE_GETPID) || defined(HAVE_GETTID) + #include <sys/types.h> + #include <unistd.h> +#endif +#include <time.h> // time +#include <mt19937ar.h> // init_genrand, genrand_int32, genrand_res53 + + +/// Initializes the random number generator with an appropriate seed. +void rnd_init(void) +{ + uint32 seed = gettick(); + seed += (uint32)time(NULL); +#if defined(WIN32) + seed += GetCurrentProcessId(); + seed += GetCurrentThreadId(); +#else +#if defined(HAVE_GETPID) + seed += (uint32)getpid(); +#endif // HAVE_GETPID +#if defined(HAVE_GETTID) + seed += (uint32)gettid(); +#endif // HAVE_GETTID +#endif + init_genrand(seed); +} + + +/// Initializes the random number generator. +void rnd_seed(uint32 seed) +{ + init_genrand(seed); +} + + +/// Generates a random number in the interval [0, SINT32_MAX] +int32 rnd(void) +{ + return (int32)genrand_int31(); +} + + +/// Generates a random number in the interval [0, dice_faces) +/// NOTE: interval is open ended, so dice_faces is excluded (unless it's 0) +uint32 rnd_roll(uint32 dice_faces) +{ + return (uint32)(rnd_uniform()*dice_faces); +} + + +/// Generates a random number in the interval [min, max] +/// Returns min if range is invalid. +int32 rnd_value(int32 min, int32 max) +{ + if( min >= max ) + return min; + return min + (int32)(rnd_uniform()*(max-min+1)); +} + + +/// Generates a random number in the interval [0.0, 1.0) +/// NOTE: interval is open ended, so 1.0 is excluded +double rnd_uniform(void) +{ + return ((uint32)genrand_int32())*(1.0/4294967296.0);// divided by 2^32 +} + + +/// Generates a random number in the interval [0.0, 1.0) with 53-bit resolution +/// NOTE: interval is open ended, so 1.0 is excluded +/// NOTE: 53 bits is the maximum precision of a double +double rnd_uniform53(void) +{ + return genrand_res53(); +} diff --git a/src/common/random.h b/src/common/random.h new file mode 100644 index 000000000..43dfd36c0 --- /dev/null +++ b/src/common/random.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _RANDOM_H_ +#define _RANDOM_H_ + +#include "../common/cbasetypes.h" + +void rnd_init(void); +void rnd_seed(uint32); + +int32 rnd(void);// [0, SINT32_MAX] +uint32 rnd_roll(uint32 dice_faces);// [0, dice_faces) +int32 rnd_value(int32 min, int32 max);// [min, max] +double rnd_uniform(void);// [0.0, 1.0) +double rnd_uniform53(void);// [0.0, 1.0) + +#endif /* _RANDOM_H_ */ diff --git a/src/common/showmsg.c b/src/common/showmsg.c new file mode 100644 index 000000000..609ae3c50 --- /dev/null +++ b/src/common/showmsg.c @@ -0,0 +1,892 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/strlib.h" // StringBuf +#include "showmsg.h" +#include "core.h" //[Ind] - For SERVER_TYPE + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <stdlib.h> // atexit + +#include "libconfig.h" + +#ifdef WIN32 + #include "../common/winapi.h" + + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log\\map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log\\char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log\\login-server.log" + #endif + #endif + #endif +#else + #include <unistd.h> + + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log/map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log/char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log/login-server.log" + #endif + #endif + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/// behavioral parameter. +/// when redirecting output: +/// if true prints escape sequences +/// if false removes the escape sequences +int stdout_with_ansisequence = 0; + +int msg_silent = 0; //Specifies how silent the console is. + +int console_msg_log = 0;//[Ind] msg error logging + +/////////////////////////////////////////////////////////////////////////////// +/// static/dynamic buffer for the messages + +#define SBUF_SIZE 2054 // never put less that what's required for the debug message + +#define NEWBUF(buf) \ + struct { \ + char s_[SBUF_SIZE]; \ + StringBuf *d_; \ + char *v_; \ + int l_; \ + } buf ={"",NULL,NULL,0}; \ +//define NEWBUF + +#define BUFVPRINTF(buf,fmt,args) \ + buf.l_ = vsnprintf(buf.s_, SBUF_SIZE, fmt, args); \ + if( buf.l_ >= 0 && buf.l_ < SBUF_SIZE ) \ + {/* static buffer */ \ + buf.v_ = buf.s_; \ + } \ + else \ + {/* dynamic buffer */ \ + buf.d_ = StringBuf_Malloc(); \ + buf.l_ = StringBuf_Vprintf(buf.d_, fmt, args); \ + buf.v_ = StringBuf_Value(buf.d_); \ + ShowDebug("showmsg: dynamic buffer used, increase the static buffer size to %d or more.\n", buf.l_+1);\ + } \ +//define BUFVPRINTF + +#define BUFVAL(buf) buf.v_ +#define BUFLEN(buf) buf.l_ + +#define FREEBUF(buf) \ + if( buf.d_ ) \ + { \ + StringBuf_Free(buf.d_); \ + buf.d_ = NULL; \ + } \ + buf.v_ = NULL; \ +//define FREEBUF + +/////////////////////////////////////////////////////////////////////////////// +#ifdef _WIN32 +// XXX adapted from eApp (comments are left untouched) [flaviojs] + +/////////////////////////////////////////////////////////////////////////////// +// ansi compatible printf with control sequence parser for windows +// fast hack, handle with care, not everything implemented +// +// \033[#;...;#m - Set Graphics Rendition (SGR) +// +// printf("\x1b[1;31;40m"); // Bright red on black +// printf("\x1b[3;33;45m"); // Blinking yellow on magenta (blink not implemented) +// printf("\x1b[1;30;47m"); // Bright black (grey) on dim white +// +// Style Foreground Background +// 1st Digit 2nd Digit 3rd Digit RGB +// 0 - Reset 30 - Black 40 - Black 000 +// 1 - FG Bright 31 - Red 41 - Red 100 +// 2 - Unknown 32 - Green 42 - Green 010 +// 3 - Blink 33 - Yellow 43 - Yellow 110 +// 4 - Underline 34 - Blue 44 - Blue 001 +// 5 - BG Bright 35 - Magenta 45 - Magenta 101 +// 6 - Unknown 36 - Cyan 46 - Cyan 011 +// 7 - Reverse 37 - White 47 - White 111 +// 8 - Concealed (invisible) +// +// \033[#A - Cursor Up (CUU) +// Moves the cursor up by the specified number of lines without changing columns. +// If the cursor is already on the top line, this sequence is ignored. \e[A is equivalent to \e[1A. +// +// \033[#B - Cursor Down (CUD) +// Moves the cursor down by the specified number of lines without changing columns. +// If the cursor is already on the bottom line, this sequence is ignored. \e[B is equivalent to \e[1B. +// +// \033[#C - Cursor Forward (CUF) +// Moves the cursor forward by the specified number of columns without changing lines. +// If the cursor is already in the rightmost column, this sequence is ignored. \e[C is equivalent to \e[1C. +// +// \033[#D - Cursor Backward (CUB) +// Moves the cursor back by the specified number of columns without changing lines. +// If the cursor is already in the leftmost column, this sequence is ignored. \e[D is equivalent to \e[1D. +// +// \033[#E - Cursor Next Line (CNL) +// Moves the cursor down the indicated # of rows, to column 1. \e[E is equivalent to \e[1E. +// +// \033[#F - Cursor Preceding Line (CPL) +// Moves the cursor up the indicated # of rows, to column 1. \e[F is equivalent to \e[1F. +// +// \033[#G - Cursor Horizontal Absolute (CHA) +// Moves the cursor to indicated column in current row. \e[G is equivalent to \e[1G. +// +// \033[#;#H - Cursor Position (CUP) +// Moves the cursor to the specified position. The first # specifies the line number, +// the second # specifies the column. If you do not specify a position, the cursor moves to the home position: +// the upper-left corner of the screen (line 1, column 1). +// +// \033[#;#f - Horizontal & Vertical Position +// (same as \033[#;#H) +// +// \033[s - Save Cursor Position (SCP) +// The current cursor position is saved. +// +// \033[u - Restore cursor position (RCP) +// Restores the cursor position saved with the (SCP) sequence \033[s. +// (addition, restore to 0,0 if nothinh was saved before) +// + +// \033[#J - Erase Display (ED) +// Clears the screen and moves to the home position +// \033[0J - Clears the screen from cursor to end of display. The cursor position is unchanged. (default) +// \033[1J - Clears the screen from start to cursor. The cursor position is unchanged. +// \033[2J - Clears the screen and moves the cursor to the home position (line 1, column 1). +// +// \033[#K - Erase Line (EL) +// Clears the current line from the cursor position +// \033[0K - Clears all characters from the cursor position to the end of the line (including the character at the cursor position). The cursor position is unchanged. (default) +// \033[1K - Clears all characters from start of line to the cursor position. (including the character at the cursor position). The cursor position is unchanged. +// \033[2K - Clears all characters of the whole line. The cursor position is unchanged. + + +/* +not implemented + +\033[#L +IL: Insert Lines: The cursor line and all lines below it move down # lines, leaving blank space. The cursor position is unchanged. The bottommost # lines are lost. \e[L is equivalent to \e[1L. +\033[#M +DL: Delete Line: The block of # lines at and below the cursor are deleted; all lines below them move up # lines to fill in the gap, leaving # blank lines at the bottom of the screen. The cursor position is unchanged. \e[M is equivalent to \e[1M. +\033[#\@ +ICH: Insert CHaracter: The cursor character and all characters to the right of it move right # columns, leaving behind blank space. The cursor position is unchanged. The rightmost # characters on the line are lost. \e[\@ is equivalent to \e[1\@. +\033[#P +DCH: Delete CHaracter: The block of # characters at and to the right of the cursor are deleted; all characters to the right of it move left # columns, leaving behind blank space. The cursor position is unchanged. \e[P is equivalent to \e[1P. + +Escape sequences for Select Character Set +*/ + +#define is_console(handle) (FILE_TYPE_CHAR==GetFileType(handle)) + +/////////////////////////////////////////////////////////////////////////////// +int VFPRINTF(HANDLE handle, const char *fmt, va_list argptr) +{ + ///////////////////////////////////////////////////////////////// + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + static COORD saveposition = {0,0}; + */ + + ///////////////////////////////////////////////////////////////// + DWORD written; + char *p, *q; + NEWBUF(tempbuf); // temporary buffer + + if(!fmt || !*fmt) + return 0; + + // Print everything to the buffer + BUFVPRINTF(tempbuf,fmt,argptr); + + if( !is_console(handle) && stdout_with_ansisequence ) + { + WriteFile(handle, BUFVAL(tempbuf), BUFLEN(tempbuf), &written, 0); + return 0; + } + + // start with processing + p = BUFVAL(tempbuf); + while ((q = strchr(p, 0x1b)) != NULL) + { // find the escape character + if( 0==WriteConsole(handle, p, (DWORD)(q-p), &written, 0) ) // write up to the escape + WriteFile(handle, p, (DWORD)(q-p), &written, 0); + + if( q[1]!='[' ) + { // write the escape char (whatever purpose it has) + if(0==WriteConsole(handle, q, 1, &written, 0) ) + WriteFile(handle,q, 1, &written, 0); + p=q+1; //and start searching again + } + else + { // from here, we will skip the '\033[' + // we break at the first unprocessible position + // assuming regular text is starting there + uint8 numbers[16], numpoint=0; + CONSOLE_SCREEN_BUFFER_INFO info; + + // initialize + GetConsoleScreenBufferInfo(handle, &info); + memset(numbers,0,sizeof(numbers)); + + // skip escape and bracket + q=q+2; + for(;;) + { + if( ISDIGIT(*q) ) + { // add number to number array, only accept 2digits, shift out the rest + // so // \033[123456789m will become \033[89m + numbers[numpoint] = (numbers[numpoint]<<4) | (*q-'0'); + ++q; + // and next character + continue; + } + else if( *q == ';' ) + { // delimiter + if(numpoint<sizeof(numbers)/sizeof(*numbers)) + { // go to next array position + numpoint++; + } + else + { // array is full, so we 'forget' the first value + memmove(numbers,numbers+1,sizeof(numbers)/sizeof(*numbers)-1); + numbers[sizeof(numbers)/sizeof(*numbers)-1]=0; + } + ++q; + // and next number + continue; + } + else if( *q == 'm' ) + { // \033[#;...;#m - Set Graphics Rendition (SGR) + uint8 i; + for(i=0; i<= numpoint; ++i) + { + if( 0x00 == (0xF0 & numbers[i]) ) + { // upper nibble 0 + if( 0 == numbers[i] ) + { // reset + info.wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } + else if( 1==numbers[i] ) + { // set foreground intensity + info.wAttributes |= FOREGROUND_INTENSITY; + } + else if( 5==numbers[i] ) + { // set background intensity + info.wAttributes |= BACKGROUND_INTENSITY; + } + else if( 7==numbers[i] ) + { // reverse colors (just xor them) + info.wAttributes ^= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + } + //case '2': // not existing + //case '3': // blinking (not implemented) + //case '4': // unterline (not implemented) + //case '6': // not existing + //case '8': // concealed (not implemented) + //case '9': // not existing + } + else if( 0x20 == (0xF0 & numbers[i]) ) + { // off + + if( 1==numbers[i] ) + { // set foreground intensity off + info.wAttributes &= ~FOREGROUND_INTENSITY; + } + else if( 5==numbers[i] ) + { // set background intensity off + info.wAttributes &= ~BACKGROUND_INTENSITY; + } + else if( 7==numbers[i] ) + { // reverse colors (just xor them) + info.wAttributes ^= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + } + } + else if( 0x30 == (0xF0 & numbers[i]) ) + { // foreground + uint8 num = numbers[i]&0x0F; + if(num==9) info.wAttributes |= FOREGROUND_INTENSITY; + if(num>7) num=7; // set white for 37, 38 and 39 + info.wAttributes &= ~(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); + if( (num & 0x01)>0 ) // lowest bit set = red + info.wAttributes |= FOREGROUND_RED; + if( (num & 0x02)>0 ) // second bit set = green + info.wAttributes |= FOREGROUND_GREEN; + if( (num & 0x04)>0 ) // third bit set = blue + info.wAttributes |= FOREGROUND_BLUE; + } + else if( 0x40 == (0xF0 & numbers[i]) ) + { // background + uint8 num = numbers[i]&0x0F; + if(num==9) info.wAttributes |= BACKGROUND_INTENSITY; + if(num>7) num=7; // set white for 47, 48 and 49 + info.wAttributes &= ~(BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE); + if( (num & 0x01)>0 ) // lowest bit set = red + info.wAttributes |= BACKGROUND_RED; + if( (num & 0x02)>0 ) // second bit set = green + info.wAttributes |= BACKGROUND_GREEN; + if( (num & 0x04)>0 ) // third bit set = blue + info.wAttributes |= BACKGROUND_BLUE; + } + } + // set the attributes + SetConsoleTextAttribute(handle, info.wAttributes); + } + else if( *q=='J' ) + { // \033[#J - Erase Display (ED) + // \033[0J - Clears the screen from cursor to end of display. The cursor position is unchanged. + // \033[1J - Clears the screen from start to cursor. The cursor position is unchanged. + // \033[2J - Clears the screen and moves the cursor to the home position (line 1, column 1). + uint8 num = (numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F); + int cnt; + DWORD tmp; + COORD origin = {0,0}; + if(num==1) + { // chars from start up to and including cursor + cnt = info.dwSize.X * info.dwCursorPosition.Y + info.dwCursorPosition.X + 1; + } + else if(num==2) + { // Number of chars on screen. + cnt = info.dwSize.X * info.dwSize.Y; + SetConsoleCursorPosition(handle, origin); + } + else// 0 and default + { // number of chars from cursor to end + origin = info.dwCursorPosition; + cnt = info.dwSize.X * (info.dwSize.Y - info.dwCursorPosition.Y) - info.dwCursorPosition.X; + } + FillConsoleOutputAttribute(handle, info.wAttributes, cnt, origin, &tmp); + FillConsoleOutputCharacter(handle, ' ', cnt, origin, &tmp); + } + else if( *q=='K' ) + { // \033[K : clear line from actual position to end of the line + // \033[0K - Clears all characters from the cursor position to the end of the line. + // \033[1K - Clears all characters from start of line to the cursor position. + // \033[2K - Clears all characters of the whole line. + + uint8 num = (numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F); + COORD origin = {0,info.dwCursorPosition.Y}; //warning C4204 + SHORT cnt; + DWORD tmp; + if(num==1) + { + cnt = info.dwCursorPosition.X + 1; + } + else if(num==2) + { + cnt = info.dwSize.X; + } + else// 0 and default + { + origin = info.dwCursorPosition; + cnt = info.dwSize.X - info.dwCursorPosition.X; // how many spaces until line is full + } + FillConsoleOutputAttribute(handle, info.wAttributes, cnt, origin, &tmp); + FillConsoleOutputCharacter(handle, ' ', cnt, origin, &tmp); + } + else if( *q == 'H' || *q == 'f' ) + { // \033[#;#H - Cursor Position (CUP) + // \033[#;#f - Horizontal & Vertical Position + // The first # specifies the line number, the second # specifies the column. + // The default for both is 1 + info.dwCursorPosition.X = (numbers[numpoint])?(numbers[numpoint]>>4)*10+((numbers[numpoint]&0x0F)-1):0; + info.dwCursorPosition.Y = (numpoint && numbers[numpoint-1])?(numbers[numpoint-1]>>4)*10+((numbers[numpoint-1]&0x0F)-1):0; + + if( info.dwCursorPosition.X >= info.dwSize.X ) info.dwCursorPosition.Y = info.dwSize.X-1; + if( info.dwCursorPosition.Y >= info.dwSize.Y ) info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q=='s' ) + { // \033[s - Save Cursor Position (SCP) + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(handle, &info); + saveposition = info.dwCursorPosition; + */ + } + else if( *q=='u' ) + { // \033[u - Restore cursor position (RCP) + /* XXX Two streams are being used. Disabled to avoid inconsistency [flaviojs] + SetConsoleCursorPosition(handle, saveposition); + */ + } + else if( *q == 'A' ) + { // \033[#A - Cursor Up (CUU) + // Moves the cursor UP # number of lines + info.dwCursorPosition.Y -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.Y < 0 ) + info.dwCursorPosition.Y = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'B' ) + { // \033[#B - Cursor Down (CUD) + // Moves the cursor DOWN # number of lines + info.dwCursorPosition.Y += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.Y >= info.dwSize.Y ) + info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'C' ) + { // \033[#C - Cursor Forward (CUF) + // Moves the cursor RIGHT # number of columns + info.dwCursorPosition.X += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.X >= info.dwSize.X ) + info.dwCursorPosition.X = info.dwSize.X-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'D' ) + { // \033[#D - Cursor Backward (CUB) + // Moves the cursor LEFT # number of columns + info.dwCursorPosition.X -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + + if( info.dwCursorPosition.X < 0 ) + info.dwCursorPosition.X = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'E' ) + { // \033[#E - Cursor Next Line (CNL) + // Moves the cursor down the indicated # of rows, to column 1 + info.dwCursorPosition.Y += (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + info.dwCursorPosition.X = 0; + + if( info.dwCursorPosition.Y >= info.dwSize.Y ) + info.dwCursorPosition.Y = info.dwSize.Y-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'F' ) + { // \033[#F - Cursor Preceding Line (CPL) + // Moves the cursor up the indicated # of rows, to column 1. + info.dwCursorPosition.Y -= (numbers[numpoint])?(numbers[numpoint]>>4)*10+(numbers[numpoint]&0x0F):1; + info.dwCursorPosition.X = 0; + + if( info.dwCursorPosition.Y < 0 ) + info.dwCursorPosition.Y = 0; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'G' ) + { // \033[#G - Cursor Horizontal Absolute (CHA) + // Moves the cursor to indicated column in current row. + info.dwCursorPosition.X = (numbers[numpoint])?(numbers[numpoint]>>4)*10+((numbers[numpoint]&0x0F)-1):0; + + if( info.dwCursorPosition.X >= info.dwSize.X ) + info.dwCursorPosition.X = info.dwSize.X-1; + SetConsoleCursorPosition(handle, info.dwCursorPosition); + } + else if( *q == 'L' || *q == 'M' || *q == '@' || *q == 'P') + { // not implemented, just skip + } + else + { // no number nor valid sequencer + // something is fishy, we break and give the current char free + --q; + } + // skip the sequencer and search again + p = q+1; + break; + }// end while + } + } + if (*p) // write the rest of the buffer + if( 0==WriteConsole(handle, p, (DWORD)strlen(p), &written, 0) ) + WriteFile(handle, p, (DWORD)strlen(p), &written, 0); + FREEBUF(tempbuf); + return 0; +} + +int FPRINTF(HANDLE handle, const char *fmt, ...) +{ + int ret; + va_list argptr; + va_start(argptr, fmt); + ret = VFPRINTF(handle,fmt,argptr); + va_end(argptr); + return ret; +} + +#define FFLUSH(handle) + +#define STDOUT GetStdHandle(STD_OUTPUT_HANDLE) +#define STDERR GetStdHandle(STD_ERROR_HANDLE) + +#else // not _WIN32 + + +#define is_console(file) (0!=isatty(fileno(file))) + +//vprintf_without_ansiformats +int VFPRINTF(FILE *file, const char *fmt, va_list argptr) +{ + char *p, *q; + NEWBUF(tempbuf); // temporary buffer + + if(!fmt || !*fmt) + return 0; + + if( is_console(file) || stdout_with_ansisequence ) + { + vfprintf(file, fmt, argptr); + return 0; + } + + // Print everything to the buffer + BUFVPRINTF(tempbuf,fmt,argptr); + + // start with processing + p = BUFVAL(tempbuf); + while ((q = strchr(p, 0x1b)) != NULL) + { // find the escape character + fprintf(file, "%.*s", (int)(q-p), p); // write up to the escape + if( q[1]!='[' ) + { // write the escape char (whatever purpose it has) + fprintf(file, "%.*s", 1, q); + p=q+1; //and start searching again + } + else + { // from here, we will skip the '\033[' + // we break at the first unprocessible position + // assuming regular text is starting there + + // skip escape and bracket + q=q+2; + while(1) + { + if( ISDIGIT(*q) ) + { + ++q; + // and next character + continue; + } + else if( *q == ';' ) + { // delimiter + ++q; + // and next number + continue; + } + else if( *q == 'm' ) + { // \033[#;...;#m - Set Graphics Rendition (SGR) + // set the attributes + } + else if( *q=='J' ) + { // \033[#J - Erase Display (ED) + } + else if( *q=='K' ) + { // \033[K : clear line from actual position to end of the line + } + else if( *q == 'H' || *q == 'f' ) + { // \033[#;#H - Cursor Position (CUP) + // \033[#;#f - Horizontal & Vertical Position + } + else if( *q=='s' ) + { // \033[s - Save Cursor Position (SCP) + } + else if( *q=='u' ) + { // \033[u - Restore cursor position (RCP) + } + else if( *q == 'A' ) + { // \033[#A - Cursor Up (CUU) + // Moves the cursor UP # number of lines + } + else if( *q == 'B' ) + { // \033[#B - Cursor Down (CUD) + // Moves the cursor DOWN # number of lines + } + else if( *q == 'C' ) + { // \033[#C - Cursor Forward (CUF) + // Moves the cursor RIGHT # number of columns + } + else if( *q == 'D' ) + { // \033[#D - Cursor Backward (CUB) + // Moves the cursor LEFT # number of columns + } + else if( *q == 'E' ) + { // \033[#E - Cursor Next Line (CNL) + // Moves the cursor down the indicated # of rows, to column 1 + } + else if( *q == 'F' ) + { // \033[#F - Cursor Preceding Line (CPL) + // Moves the cursor up the indicated # of rows, to column 1. + } + else if( *q == 'G' ) + { // \033[#G - Cursor Horizontal Absolute (CHA) + // Moves the cursor to indicated column in current row. + } + else if( *q == 'L' || *q == 'M' || *q == '@' || *q == 'P') + { // not implemented, just skip + } + else + { // no number nor valid sequencer + // something is fishy, we break and give the current char free + --q; + } + // skip the sequencer and search again + p = q+1; + break; + }// end while + } + } + if (*p) // write the rest of the buffer + fprintf(file, "%s", p); + FREEBUF(tempbuf); + return 0; +} +int FPRINTF(FILE *file, const char *fmt, ...) +{ + int ret; + va_list argptr; + va_start(argptr, fmt); + ret = VFPRINTF(file,fmt,argptr); + va_end(argptr); + return ret; +} + +#define FFLUSH fflush + +#define STDOUT stdout +#define STDERR stderr + +#endif// not _WIN32 + + + + + + + + + + +char timestamp_format[20] = ""; //For displaying Timestamps + +int _vShowMessage(enum msg_type flag, const char *string, va_list ap) +{ + va_list apcopy; + char prefix[100]; +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + FILE *fp; +#endif + + if (!string || *string == '\0') { + ShowError("Empty string passed to _vShowMessage().\n"); + return 1; + } + /** + * For the buildbot, these result in a EXIT_FAILURE from core.c when done reading the params. + **/ +#if defined(BUILDBOT) + if( flag == MSG_WARNING || + flag == MSG_ERROR || + flag == MSG_SQL ) { + buildbotflag = 1; + } +#endif + if( + ( flag == MSG_WARNING && console_msg_log&1 ) || + ( ( flag == MSG_ERROR || flag == MSG_SQL ) && console_msg_log&2 ) || + ( flag == MSG_DEBUG && console_msg_log&4 ) ) {//[Ind] + FILE *log = NULL; + if( (log = fopen(SERVER_TYPE == ATHENA_SERVER_MAP ? "./log/map-msg_log.log" : "./log/unknown.log","a+")) ) { + char timestring[255]; + time_t curtime; + time(&curtime); + strftime(timestring, 254, "%m/%d/%Y %H:%M:%S", localtime(&curtime)); + fprintf(log,"(%s) [ %s ] : ", + timestring, + flag == MSG_WARNING ? "Warning" : + flag == MSG_ERROR ? "Error" : + flag == MSG_SQL ? "SQL Error" : + flag == MSG_DEBUG ? "Debug" : + "Unknown"); + va_copy(apcopy, ap); + vfprintf(log,string,apcopy); + va_end(apcopy); + fclose(log); + } + } + if( + (flag == MSG_INFORMATION && msg_silent&1) || + (flag == MSG_STATUS && msg_silent&2) || + (flag == MSG_NOTICE && msg_silent&4) || + (flag == MSG_WARNING && msg_silent&8) || + (flag == MSG_ERROR && msg_silent&16) || + (flag == MSG_SQL && msg_silent&16) || + (flag == MSG_DEBUG && msg_silent&32) + ) + return 0; //Do not print it. + + if (timestamp_format[0] && flag != MSG_NONE) + { //Display time format. [Skotlex] + time_t t = time(NULL); + strftime(prefix, 80, timestamp_format, localtime(&t)); + } else prefix[0]='\0'; + + switch (flag) { + case MSG_NONE: // direct printf replacement + break; + case MSG_STATUS: //Bright Green (To inform about good things) + strcat(prefix,CL_GREEN"[Status]"CL_RESET":"); + break; + case MSG_SQL: //Bright Violet (For dumping out anything related with SQL) <- Actually, this is mostly used for SQL errors with the database, as successes can as well just be anything else... [Skotlex] + strcat(prefix,CL_MAGENTA"[SQL]"CL_RESET":"); + break; + case MSG_INFORMATION: //Bright White (Variable information) + strcat(prefix,CL_WHITE"[Info]"CL_RESET":"); + break; + case MSG_NOTICE: //Bright White (Less than a warning) + strcat(prefix,CL_WHITE"[Notice]"CL_RESET":"); + break; + case MSG_WARNING: //Bright Yellow + strcat(prefix,CL_YELLOW"[Warning]"CL_RESET":"); + break; + case MSG_DEBUG: //Bright Cyan, important stuff! + strcat(prefix,CL_CYAN"[Debug]"CL_RESET":"); + break; + case MSG_ERROR: //Bright Red (Regular errors) + strcat(prefix,CL_RED"[Error]"CL_RESET":"); + break; + case MSG_FATALERROR: //Bright Red (Fatal errors, abort(); if possible) + strcat(prefix,CL_RED"[Fatal Error]"CL_RESET":"); + break; + default: + ShowError("In function _vShowMessage() -> Invalid flag passed.\n"); + return 1; + } + + if (flag == MSG_ERROR || flag == MSG_FATALERROR || flag == MSG_SQL) + { //Send Errors to StdErr [Skotlex] + FPRINTF(STDERR, "%s ", prefix); + va_copy(apcopy, ap); + VFPRINTF(STDERR, string, apcopy); + va_end(apcopy); + FFLUSH(STDERR); + } else { + if (flag != MSG_NONE) + FPRINTF(STDOUT, "%s ", prefix); + va_copy(apcopy, ap); + VFPRINTF(STDOUT, string, apcopy); + va_end(apcopy); + FFLUSH(STDOUT); + } + +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + if(strlen(DEBUGLOGPATH) > 0) { + fp=fopen(DEBUGLOGPATH,"a"); + if (fp == NULL) { + FPRINTF(STDERR, CL_RED"[ERROR]"CL_RESET": Could not open '"CL_WHITE"%s"CL_RESET"', access denied.\n", DEBUGLOGPATH); + FFLUSH(STDERR); + } else { + fprintf(fp,"%s ", prefix); + va_copy(apcopy, ap); + vfprintf(fp,string,apcopy); + va_end(apcopy); + fclose(fp); + } + } else { + FPRINTF(STDERR, CL_RED"[ERROR]"CL_RESET": DEBUGLOGPATH not defined!\n"); + FFLUSH(STDERR); + } +#endif + + return 0; +} + +void ClearScreen(void) +{ +#ifndef _WIN32 + ShowMessage(CL_CLS); // to prevent empty string passed messages +#endif +} +int _ShowMessage(enum msg_type flag, const char *string, ...) +{ + int ret; + va_list ap; + va_start(ap, string); + ret = _vShowMessage(flag, string, ap); + va_end(ap); + return ret; +} + +// direct printf replacement +void ShowMessage(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_NONE, string, ap); + va_end(ap); +} +void ShowStatus(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_STATUS, string, ap); + va_end(ap); +} +void ShowSQL(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_SQL, string, ap); + va_end(ap); +} +void ShowInfo(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_INFORMATION, string, ap); + va_end(ap); +} +void ShowNotice(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_NOTICE, string, ap); + va_end(ap); +} +void ShowWarning(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_WARNING, string, ap); + va_end(ap); +} +void ShowConfigWarning(config_setting_t *config, const char *string, ...) +{ + StringBuf buf; + va_list ap; + StringBuf_Init(&buf); + StringBuf_AppendStr(&buf, string); + StringBuf_Printf(&buf, " (%s:%d)\n", config_setting_source_file(config), config_setting_source_line(config)); + va_start(ap, string); + _vShowMessage(MSG_WARNING, StringBuf_Value(&buf), ap); + va_end(ap); + StringBuf_Destroy(&buf); +} +void ShowDebug(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_DEBUG, string, ap); + va_end(ap); +} +void ShowError(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_ERROR, string, ap); + va_end(ap); +} +void ShowFatalError(const char *string, ...) { + va_list ap; + va_start(ap, string); + _vShowMessage(MSG_FATALERROR, string, ap); + va_end(ap); +} diff --git a/src/common/showmsg.h b/src/common/showmsg.h new file mode 100644 index 000000000..0d6cb5c9f --- /dev/null +++ b/src/common/showmsg.h @@ -0,0 +1,99 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SHOWMSG_H_ +#define _SHOWMSG_H_ + +#include "libconfig.h" + +// for help with the console colors look here: +// http://www.edoceo.com/liberum/?doc=printf-with-color +// some code explanation (used here): +// \033[2J : clear screen and go up/left (0, 0 position) +// \033[K : clear line from actual position to end of the line +// \033[0m : reset color parameter +// \033[1m : use bold for font + +#define CL_RESET "\033[0m" +#define CL_CLS "\033[2J" +#define CL_CLL "\033[K" + +// font settings +#define CL_BOLD "\033[1m" +#define CL_NORM CL_RESET +#define CL_NORMAL CL_RESET +#define CL_NONE CL_RESET +// foreground color and bold font (bright color on windows) +#define CL_WHITE "\033[1;37m" +#define CL_GRAY "\033[1;30m" +#define CL_RED "\033[1;31m" +#define CL_GREEN "\033[1;32m" +#define CL_YELLOW "\033[1;33m" +#define CL_BLUE "\033[1;34m" +#define CL_MAGENTA "\033[1;35m" +#define CL_CYAN "\033[1;36m" + +// background color +#define CL_BG_BLACK "\033[40m" +#define CL_BG_RED "\033[41m" +#define CL_BG_GREEN "\033[42m" +#define CL_BG_YELLOW "\033[43m" +#define CL_BG_BLUE "\033[44m" +#define CL_BG_MAGENTA "\033[45m" +#define CL_BG_CYAN "\033[46m" +#define CL_BG_WHITE "\033[47m" +// foreground color and normal font (normal color on windows) +#define CL_LT_BLACK "\033[0;30m" +#define CL_LT_RED "\033[0;31m" +#define CL_LT_GREEN "\033[0;32m" +#define CL_LT_YELLOW "\033[0;33m" +#define CL_LT_BLUE "\033[0;34m" +#define CL_LT_MAGENTA "\033[0;35m" +#define CL_LT_CYAN "\033[0;36m" +#define CL_LT_WHITE "\033[0;37m" +// foreground color and bold font (bright color on windows) +#define CL_BT_BLACK "\033[1;30m" +#define CL_BT_RED "\033[1;31m" +#define CL_BT_GREEN "\033[1;32m" +#define CL_BT_YELLOW "\033[1;33m" +#define CL_BT_BLUE "\033[1;34m" +#define CL_BT_MAGENTA "\033[1;35m" +#define CL_BT_CYAN "\033[1;36m" +#define CL_BT_WHITE "\033[1;37m" + +#define CL_WTBL "\033[37;44m" // white on blue +#define CL_XXBL "\033[0;44m" // default on blue +#define CL_PASS "\033[0;32;42m" // green on green + +#define CL_SPACE " " // space aquivalent of the print messages + +extern int stdout_with_ansisequence; //If the color ansi sequences are to be used. [flaviojs] +extern int msg_silent; //Specifies how silent the console is. [Skotlex] +extern int console_msg_log; //Specifies what error messages to log. [Ind] +extern char timestamp_format[20]; //For displaying Timestamps [Skotlex] + +enum msg_type { + MSG_NONE, + MSG_STATUS, + MSG_SQL, + MSG_INFORMATION, + MSG_NOTICE, + MSG_WARNING, + MSG_DEBUG, + MSG_ERROR, + MSG_FATALERROR +}; + +extern void ClearScreen(void); +extern void ShowMessage(const char *, ...); +extern void ShowStatus(const char *, ...); +extern void ShowSQL(const char *, ...); +extern void ShowInfo(const char *, ...); +extern void ShowNotice(const char *, ...); +extern void ShowWarning(const char *, ...); +extern void ShowDebug(const char *, ...); +extern void ShowError(const char *, ...); +extern void ShowFatalError(const char *, ...); +extern void ShowConfigWarning(config_setting_t *config, const char *string, ...); + +#endif /* _SHOWMSG_H_ */ diff --git a/src/common/socket.c b/src/common/socket.c new file mode 100644 index 000000000..d24a9c1d8 --- /dev/null +++ b/src/common/socket.c @@ -0,0 +1,1456 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "socket.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#ifdef WIN32 + #include "../common/winapi.h" +#else + #include <errno.h> + #include <sys/socket.h> + #include <netinet/in.h> + #include <netinet/tcp.h> + #include <net/if.h> + #include <unistd.h> + #include <sys/time.h> + #include <sys/ioctl.h> + #include <netdb.h> + #include <arpa/inet.h> + + #ifndef SIOCGIFCONF + #include <sys/sockio.h> // SIOCGIFCONF on Solaris, maybe others? [Shinomori] + #endif + #ifndef FIONBIO + #include <sys/filio.h> // FIONBIO on Solaris [FlavioJS] + #endif + + #ifdef HAVE_SETRLIMIT + #include <sys/resource.h> + #endif +#endif + +///////////////////////////////////////////////////////////////////// +#if defined(WIN32) +///////////////////////////////////////////////////////////////////// +// windows portability layer + +typedef int socklen_t; + +#define sErrno WSAGetLastError() +#define S_ENOTSOCK WSAENOTSOCK +#define S_EWOULDBLOCK WSAEWOULDBLOCK +#define S_EINTR WSAEINTR +#define S_ECONNABORTED WSAECONNABORTED + +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND +#define SHUT_RDWR SD_BOTH + +// global array of sockets (emulating linux) +// fd is the position in the array +static SOCKET sock_arr[FD_SETSIZE]; +static int sock_arr_len = 0; + +/// Returns the socket associated with the target fd. +/// +/// @param fd Target fd. +/// @return Socket +#define fd2sock(fd) sock_arr[fd] + +/// Returns the first fd associated with the socket. +/// Returns -1 if the socket is not found. +/// +/// @param s Socket +/// @return Fd or -1 +int sock2fd(SOCKET s) +{ + int fd; + + // search for the socket + for( fd = 1; fd < sock_arr_len; ++fd ) + if( sock_arr[fd] == s ) + break;// found the socket + if( fd == sock_arr_len ) + return -1;// not found + return fd; +} + + +/// Inserts the socket into the global array of sockets. +/// Returns a new fd associated with the socket. +/// If there are too many sockets it closes the socket, sets an error and +// returns -1 instead. +/// Since fd 0 is reserved, it returns values in the range [1,FD_SETSIZE[. +/// +/// @param s Socket +/// @return New fd or -1 +int sock2newfd(SOCKET s) +{ + int fd; + + // find an empty position + for( fd = 1; fd < sock_arr_len; ++fd ) + if( sock_arr[fd] == INVALID_SOCKET ) + break;// empty position + if( fd == ARRAYLENGTH(sock_arr) ) + {// too many sockets + closesocket(s); + WSASetLastError(WSAEMFILE); + return -1; + } + sock_arr[fd] = s; + if( sock_arr_len <= fd ) + sock_arr_len = fd+1; + return fd; +} + +int sAccept(int fd, struct sockaddr* addr, int* addrlen) +{ + SOCKET s; + + // accept connection + s = accept(fd2sock(fd), addr, addrlen); + if( s == INVALID_SOCKET ) + return -1;// error + return sock2newfd(s); +} + +int sClose(int fd) +{ + int ret = closesocket(fd2sock(fd)); + fd2sock(fd) = INVALID_SOCKET; + return ret; +} + +int sSocket(int af, int type, int protocol) +{ + SOCKET s; + + // create socket + s = socket(af,type,protocol); + if( s == INVALID_SOCKET ) + return -1;// error + return sock2newfd(s); +} + +char* sErr(int code) +{ + static char sbuf[512]; + // strerror does not handle socket codes + if( FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + code, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPTSTR)&sbuf, sizeof(sbuf), NULL) == 0 ) + snprintf(sbuf, sizeof(sbuf), "unknown error"); + return sbuf; +} + +#define sBind(fd,name,namelen) bind(fd2sock(fd),name,namelen) +#define sConnect(fd,name,namelen) connect(fd2sock(fd),name,namelen) +#define sIoctl(fd,cmd,argp) ioctlsocket(fd2sock(fd),cmd,argp) +#define sListen(fd,backlog) listen(fd2sock(fd),backlog) +#define sRecv(fd,buf,len,flags) recv(fd2sock(fd),buf,len,flags) +#define sSelect select +#define sSend(fd,buf,len,flags) send(fd2sock(fd),buf,len,flags) +#define sSetsockopt(fd,level,optname,optval,optlen) setsockopt(fd2sock(fd),level,optname,optval,optlen) +#define sShutdown(fd,how) shutdown(fd2sock(fd),how) +#define sFD_SET(fd,set) FD_SET(fd2sock(fd),set) +#define sFD_CLR(fd,set) FD_CLR(fd2sock(fd),set) +#define sFD_ISSET(fd,set) FD_ISSET(fd2sock(fd),set) +#define sFD_ZERO FD_ZERO + +///////////////////////////////////////////////////////////////////// +#else +///////////////////////////////////////////////////////////////////// +// nix portability layer + +#define SOCKET_ERROR (-1) + +#define sErrno errno +#define S_ENOTSOCK EBADF +#define S_EWOULDBLOCK EAGAIN +#define S_EINTR EINTR +#define S_ECONNABORTED ECONNABORTED + +#define sAccept accept +#define sClose close +#define sSocket socket +#define sErr strerror + +#define sBind bind +#define sConnect connect +#define sIoctl ioctl +#define sListen listen +#define sRecv recv +#define sSelect select +#define sSend send +#define sSetsockopt setsockopt +#define sShutdown shutdown +#define sFD_SET FD_SET +#define sFD_CLR FD_CLR +#define sFD_ISSET FD_ISSET +#define sFD_ZERO FD_ZERO + +///////////////////////////////////////////////////////////////////// +#endif +///////////////////////////////////////////////////////////////////// + +#ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 +#endif + +fd_set readfds; +int fd_max; +time_t last_tick; +time_t stall_time = 60; + +uint32 addr_[16]; // ip addresses of local host (host byte order) +int naddr_ = 0; // # of ip addresses + +// Maximum packet size in bytes, which the client is able to handle. +// Larger packets cause a buffer overflow and stack corruption. +static size_t socket_max_client_packet = 24576; + +// initial recv buffer size (this will also be the max. size) +// biggest known packet: S 0153 <len>.w <emblem data>.?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes) +#define RFIFO_SIZE (2*1024) +// initial send buffer size (will be resized as needed) +#define WFIFO_SIZE (16*1024) + +// Maximum size of pending data in the write fifo. (for non-server connections) +// The connection is closed if it goes over the limit. +#define WFIFO_MAX (1*1024*1024) + +struct socket_data* session[FD_SETSIZE]; + +#ifdef SEND_SHORTLIST +int send_shortlist_array[FD_SETSIZE];// we only support FD_SETSIZE sockets, limit the array to that +int send_shortlist_count = 0;// how many fd's are in the shortlist +uint32 send_shortlist_set[(FD_SETSIZE+31)/32];// to know if specific fd's are already in the shortlist +#endif + +static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse); + +#ifndef MINICORE + int ip_rules = 1; + static int connect_check(uint32 ip); +#endif + +const char* error_msg(void) +{ + static char buf[512]; + int code = sErrno; + snprintf(buf, sizeof(buf), "error %d: %s", code, sErr(code)); + return buf; +} + +/*====================================== + * CORE : Default processing functions + *--------------------------------------*/ +int null_recv(int fd) { return 0; } +int null_send(int fd) { return 0; } +int null_parse(int fd) { return 0; } + +ParseFunc default_func_parse = null_parse; + +void set_defaultparse(ParseFunc defaultparse) +{ + default_func_parse = defaultparse; +} + + +/*====================================== + * CORE : Socket options + *--------------------------------------*/ +void set_nonblocking(int fd, unsigned long yes) +{ + // FIONBIO Use with a nonzero argp parameter to enable the nonblocking mode of socket s. + // The argp parameter is zero if nonblocking is to be disabled. + if( sIoctl(fd, FIONBIO, &yes) != 0 ) + ShowError("set_nonblocking: Failed to set socket #%d to non-blocking mode (%s) - Please report this!!!\n", fd, error_msg()); +} + +void setsocketopts(int fd) +{ + int yes = 1; // reuse fix +#if !defined(WIN32) + // set SO_REAUSEADDR to true, unix only. on windows this option causes + // the previous owner of the socket to give up, which is not desirable + // in most cases, neither compatible with unix. + sSetsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof(yes)); +#ifdef SO_REUSEPORT + sSetsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof(yes)); +#endif +#endif + + // Set the socket into no-delay mode; otherwise packets get delayed for up to 200ms, likely creating server-side lag. + // The RO protocol is mainly single-packet request/response, plus the FIFO model already does packet grouping anyway. + sSetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&yes, sizeof(yes)); + + // force the socket into no-wait, graceful-close mode (should be the default, but better make sure) + //(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/closesocket_2.asp) + { + struct linger opt; + opt.l_onoff = 0; // SO_DONTLINGER + opt.l_linger = 0; // Do not care + if( sSetsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&opt, sizeof(opt)) ) + ShowWarning("setsocketopts: Unable to set SO_LINGER mode for connection #%d!\n", fd); + } +} + +/*====================================== + * CORE : Socket Sub Function + *--------------------------------------*/ +void set_eof(int fd) +{ + if( session_isActive(fd) ) + { +#ifdef SEND_SHORTLIST + // Add this socket to the shortlist for eof handling. + send_shortlist_add_fd(fd); +#endif + session[fd]->flag.eof = 1; + } +} + +int recv_to_fifo(int fd) +{ + int len; + + if( !session_isActive(fd) ) + return -1; + + len = sRecv(fd, (char *) session[fd]->rdata + session[fd]->rdata_size, (int)RFIFOSPACE(fd), 0); + + if( len == SOCKET_ERROR ) + {//An exception has occured + if( sErrno != S_EWOULDBLOCK ) { + //ShowDebug("recv_to_fifo: %s, closing connection #%d\n", error_msg(), fd); + set_eof(fd); + } + return 0; + } + + if( len == 0 ) + {//Normal connection end. + set_eof(fd); + return 0; + } + + session[fd]->rdata_size += len; + session[fd]->rdata_tick = last_tick; + return 0; +} + +int send_from_fifo(int fd) +{ + int len; + + if( !session_isValid(fd) ) + return -1; + + if( session[fd]->wdata_size == 0 ) + return 0; // nothing to send + + len = sSend(fd, (const char *) session[fd]->wdata, (int)session[fd]->wdata_size, MSG_NOSIGNAL); + + if( len == SOCKET_ERROR ) + {//An exception has occured + if( sErrno != S_EWOULDBLOCK ) { + //ShowDebug("send_from_fifo: %s, ending connection #%d\n", error_msg(), fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } + + if( len > 0 ) + { + // some data could not be transferred? + // shift unsent data to the beginning of the queue + if( (size_t)len < session[fd]->wdata_size ) + memmove(session[fd]->wdata, session[fd]->wdata + len, session[fd]->wdata_size - len); + + session[fd]->wdata_size -= len; + } + + return 0; +} + +/// Best effort - there's no warranty that the data will be sent. +void flush_fifo(int fd) +{ + if(session[fd] != NULL) + session[fd]->func_send(fd); +} + +void flush_fifos(void) +{ + int i; + for(i = 1; i < fd_max; i++) + flush_fifo(i); +} + +/*====================================== + * CORE : Connection functions + *--------------------------------------*/ +int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; + socklen_t len; + + len = sizeof(client_address); + + fd = sAccept(listen_fd, (struct sockaddr*)&client_address, &len); + if ( fd == -1 ) { + ShowError("connect_client: accept failed (%s)!\n", error_msg()); + return -1; + } + if( fd == 0 ) + {// reserved + ShowError("connect_client: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("connect_client: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + set_nonblocking(fd, 1); + +#ifndef MINICORE + if( ip_rules && !connect_check(ntohl(client_address.sin_addr.s_addr)) ) { + do_close(fd); + return -1; + } +#endif + + if( fd_max <= fd ) fd_max = fd + 1; + sFD_SET(fd,&readfds); + + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); + session[fd]->client_addr = ntohl(client_address.sin_addr.s_addr); + + return fd; +} + +int make_listen_bind(uint32 ip, uint16 port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + if( fd == -1 ) + { + ShowError("make_listen_bind: socket creation failed (%s)!\n", error_msg()); + exit(EXIT_FAILURE); + } + if( fd == 0 ) + {// reserved + ShowError("make_listen_bind: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("make_listen_bind: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + set_nonblocking(fd, 1); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl(ip); + server_address.sin_port = htons(port); + + result = sBind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); + if( result == SOCKET_ERROR ) { + ShowError("make_listen_bind: bind failed (socket #%d, %s)!\n", fd, error_msg()); + exit(EXIT_FAILURE); + } + result = sListen(fd,5); + if( result == SOCKET_ERROR ) { + ShowError("make_listen_bind: listen failed (socket #%d, %s)!\n", fd, error_msg()); + exit(EXIT_FAILURE); + } + + if(fd_max <= fd) fd_max = fd + 1; + sFD_SET(fd, &readfds); + + create_session(fd, connect_client, null_send, null_parse); + session[fd]->client_addr = 0; // just listens + session[fd]->rdata_tick = 0; // disable timeouts on this socket + + return fd; +} + +int make_connection(uint32 ip, uint16 port, bool silent) { + struct sockaddr_in remote_address; + int fd; + int result; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + if (fd == -1) { + ShowError("make_connection: socket creation failed (%s)!\n", error_msg()); + return -1; + } + if( fd == 0 ) + {// reserved + ShowError("make_connection: Socket #0 is reserved - Please report this!!!\n"); + sClose(fd); + return -1; + } + if( fd >= FD_SETSIZE ) + {// socket number too big + ShowError("make_connection: New socket #%d is greater than can we handle! Increase the value of FD_SETSIZE (currently %d) for your OS to fix this!\n", fd, FD_SETSIZE); + sClose(fd); + return -1; + } + + setsocketopts(fd); + + remote_address.sin_family = AF_INET; + remote_address.sin_addr.s_addr = htonl(ip); + remote_address.sin_port = htons(port); + + if( !silent ) + ShowStatus("Connecting to %d.%d.%d.%d:%i\n", CONVIP(ip), port); + + result = sConnect(fd, (struct sockaddr *)(&remote_address), sizeof(struct sockaddr_in)); + if( result == SOCKET_ERROR ) { + if( !silent ) + ShowError("make_connection: connect failed (socket #%d, %s)!\n", fd, error_msg()); + do_close(fd); + return -1; + } + //Now the socket can be made non-blocking. [Skotlex] + set_nonblocking(fd, 1); + + if (fd_max <= fd) fd_max = fd + 1; + sFD_SET(fd,&readfds); + + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); + session[fd]->client_addr = ntohl(remote_address.sin_addr.s_addr); + + return fd; +} + +static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse) +{ + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, unsigned char, RFIFO_SIZE); + CREATE(session[fd]->wdata, unsigned char, WFIFO_SIZE); + session[fd]->max_rdata = RFIFO_SIZE; + session[fd]->max_wdata = WFIFO_SIZE; + session[fd]->func_recv = func_recv; + session[fd]->func_send = func_send; + session[fd]->func_parse = func_parse; + session[fd]->rdata_tick = last_tick; + return 0; +} + +static void delete_session(int fd) +{ + if( session_isValid(fd) ) + { + aFree(session[fd]->rdata); + aFree(session[fd]->wdata); + aFree(session[fd]->session_data); + aFree(session[fd]); + session[fd] = NULL; + } +} + +int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size) +{ + if( !session_isValid(fd) ) + return 0; + + if( session[fd]->max_rdata != rfifo_size && session[fd]->rdata_size < rfifo_size) { + RECREATE(session[fd]->rdata, unsigned char, rfifo_size); + session[fd]->max_rdata = rfifo_size; + } + + if( session[fd]->max_wdata != wfifo_size && session[fd]->wdata_size < wfifo_size) { + RECREATE(session[fd]->wdata, unsigned char, wfifo_size); + session[fd]->max_wdata = wfifo_size; + } + return 0; +} + +int realloc_writefifo(int fd, size_t addition) +{ + size_t newsize; + + if( !session_isValid(fd) ) // might not happen + return 0; + + if( session[fd]->wdata_size + addition > session[fd]->max_wdata ) + { // grow rule; grow in multiples of WFIFO_SIZE + newsize = WFIFO_SIZE; + while( session[fd]->wdata_size + addition > newsize ) newsize += WFIFO_SIZE; + } + else + if( session[fd]->max_wdata >= (size_t)2*(session[fd]->flag.server?FIFOSIZE_SERVERLINK:WFIFO_SIZE) + && (session[fd]->wdata_size+addition)*4 < session[fd]->max_wdata ) + { // shrink rule, shrink by 2 when only a quarter of the fifo is used, don't shrink below nominal size. + newsize = session[fd]->max_wdata / 2; + } + else // no change + return 0; + + RECREATE(session[fd]->wdata, unsigned char, newsize); + session[fd]->max_wdata = newsize; + + return 0; +} + +/// advance the RFIFO cursor (marking 'len' bytes as processed) +int RFIFOSKIP(int fd, size_t len) +{ + struct socket_data *s; + + if ( !session_isActive(fd) ) + return 0; + + s = session[fd]; + + if ( s->rdata_size < s->rdata_pos + len ) { + ShowError("RFIFOSKIP: skipped past end of read buffer! Adjusting from %d to %d (session #%d)\n", len, RFIFOREST(fd), fd); + len = RFIFOREST(fd); + } + + s->rdata_pos = s->rdata_pos + len; + return 0; +} + +/// advance the WFIFO cursor (marking 'len' bytes for sending) +int WFIFOSET(int fd, size_t len) +{ + size_t newreserve; + struct socket_data* s = session[fd]; + + if( !session_isValid(fd) || s->wdata == NULL ) + return 0; + + // we have written len bytes to the buffer already before calling WFIFOSET + if(s->wdata_size+len > s->max_wdata) + { // actually there was a buffer overflow already + uint32 ip = s->client_addr; + ShowFatalError("WFIFOSET: Write Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %u bytes on a %u/%u bytes buffer.\n", fd, CONVIP(ip), (unsigned int)len, (unsigned int)s->wdata_size, (unsigned int)s->max_wdata); + ShowDebug("Likely command that caused it: 0x%x\n", (*(uint16*)(s->wdata + s->wdata_size))); + // no other chance, make a better fifo model + exit(EXIT_FAILURE); + } + + if( len > 0xFFFF ) + { + // dynamic packets allow up to UINT16_MAX bytes (<packet_id>.W <packet_len>.W ...) + // all known fixed-size packets are within this limit, so use the same limit + ShowFatalError("WFIFOSET: Packet 0x%x is too big. (len=%u, max=%u)\n", (*(uint16*)(s->wdata + s->wdata_size)), (unsigned int)len, 0xFFFF); + exit(EXIT_FAILURE); + } + else if( len == 0 ) + { + // abuses the fact, that the code that did WFIFOHEAD(fd,0), already wrote + // the packet type into memory, even if it could have overwritten vital data + // this can happen when a new packet was added on map-server, but packet len table was not updated + ShowWarning("WFIFOSET: Attempted to send zero-length packet, most likely 0x%04x (please report this).\n", WFIFOW(fd,0)); + return 0; + } + + if( !s->flag.server ) { + + if( len > socket_max_client_packet ) {// see declaration of socket_max_client_packet for details + ShowError("WFIFOSET: Dropped too large client packet 0x%04x (length=%u, max=%u).\n", WFIFOW(fd,0), len, socket_max_client_packet); + return 0; + } + + if( s->wdata_size+len > WFIFO_MAX ) {// reached maximum write fifo size + ShowError("WFIFOSET: Maximum write buffer size for client connection %d exceeded, most likely caused by packet 0x%04x (len=%u, ip=%lu.%lu.%lu.%lu).\n", fd, WFIFOW(fd,0), len, CONVIP(s->client_addr)); + set_eof(fd); + return 0; + } + + } + s->wdata_size += len; + //If the interserver has 200% of its normal size full, flush the data. + if( s->flag.server && s->wdata_size >= 2*FIFOSIZE_SERVERLINK ) + flush_fifo(fd); + + // always keep a WFIFO_SIZE reserve in the buffer + // For inter-server connections, let the reserve be 1/4th of the link size. + newreserve = s->flag.server ? FIFOSIZE_SERVERLINK / 4 : WFIFO_SIZE; + + // readjust the buffer to include the chosen reserve + realloc_writefifo(fd, newreserve); + +#ifdef SEND_SHORTLIST + send_shortlist_add_fd(fd); +#endif + + return 0; +} + +int do_sockets(int next) +{ + fd_set rfd; + struct timeval timeout; + int ret,i; + + // PRESEND Timers are executed before do_sendrecv and can send packets and/or set sessions to eof. + // Send remaining data and process client-side disconnects here. +#ifdef SEND_SHORTLIST + send_shortlist_do_sends(); +#else + for (i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if(session[i]->wdata_size) + session[i]->func_send(i); + } +#endif + + // can timeout until the next tick + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + + memcpy(&rfd, &readfds, sizeof(rfd)); + ret = sSelect(fd_max, &rfd, NULL, NULL, &timeout); + + if( ret == SOCKET_ERROR ) + { + if( sErrno != S_EINTR ) + { + ShowFatalError("do_sockets: select() failed, %s!\n", error_msg()); + exit(EXIT_FAILURE); + } + return 0; // interrupted by a signal, just loop and try again + } + + last_tick = time(NULL); + +#if defined(WIN32) + // on windows, enumerating all members of the fd_set is way faster if we access the internals + for( i = 0; i < (int)rfd.fd_count; ++i ) + { + int fd = sock2fd(rfd.fd_array[i]); + if( session[fd] ) + session[fd]->func_recv(fd); + } +#else + // otherwise assume that the fd_set is a bit-array and enumerate it in a standard way + for( i = 1; ret && i < fd_max; ++i ) + { + if(sFD_ISSET(i,&rfd) && session[i]) + { + session[i]->func_recv(i); + --ret; + } + } +#endif + + // POSTSEND Send remaining data and handle eof sessions. +#ifdef SEND_SHORTLIST + send_shortlist_do_sends(); +#else + for (i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if(session[i]->wdata_size) + session[i]->func_send(i); + + if(session[i]->flag.eof) //func_send can't free a session, this is safe. + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + session[i]->func_parse(i); //This should close the session immediately. + } + } +#endif + + // parse input data on each socket + for(i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + if (session[i]->rdata_tick && DIFF_TICK(last_tick, session[i]->rdata_tick) > stall_time) { + if( session[i]->flag.server ) {/* server is special */ + if( session[i]->flag.ping != 2 )/* only update if necessary otherwise it'd resend the ping unnecessarily */ + session[i]->flag.ping = 1; + } else { + ShowInfo("Session #%d timed out\n", i); + set_eof(i); + } + } + + session[i]->func_parse(i); + + if(!session[i]) + continue; + + // after parse, check client's RFIFO size to know if there is an invalid packet (too big and not parsed) + if (session[i]->rdata_size == RFIFO_SIZE && session[i]->max_rdata == RFIFO_SIZE) { + set_eof(i); + continue; + } + RFIFOFLUSH(i); + } + + return 0; +} + +////////////////////////////// +#ifndef MINICORE +////////////////////////////// +// IP rules and DDoS protection + +typedef struct _connect_history { + struct _connect_history* next; + uint32 ip; + uint32 tick; + int count; + unsigned ddos : 1; +} ConnectHistory; + +typedef struct _access_control { + uint32 ip; + uint32 mask; +} AccessControl; + +enum _aco { + ACO_DENY_ALLOW, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILURE +}; + +static AccessControl* access_allow = NULL; +static AccessControl* access_deny = NULL; +static int access_order = ACO_DENY_ALLOW; +static int access_allownum = 0; +static int access_denynum = 0; +static int access_debug = 0; +static int ddos_count = 10; +static int ddos_interval = 3*1000; +static int ddos_autoreset = 10*60*1000; +/// Connection history, an array of linked lists. +/// The array's index for any ip is ip&0xFFFF +static ConnectHistory* connect_history[0x10000]; + +static int connect_check_(uint32 ip); + +/// Verifies if the IP can connect. (with debug info) +/// @see connect_check_() +static int connect_check(uint32 ip) +{ + int result = connect_check_(ip); + if( access_debug ) { + ShowInfo("connect_check: Connection from %d.%d.%d.%d %s\n", CONVIP(ip),result ? "allowed." : "denied!"); + } + return result; +} + +/// Verifies if the IP can connect. +/// 0 : Connection Rejected +/// 1 or 2 : Connection Accepted +static int connect_check_(uint32 ip) +{ + ConnectHistory* hist = connect_history[ip&0xFFFF]; + int i; + int is_allowip = 0; + int is_denyip = 0; + int connect_ok = 0; + + // Search the allow list + for( i=0; i < access_allownum; ++i ){ + if( (ip & access_allow[i].mask) == (access_allow[i].ip & access_allow[i].mask) ){ + if( access_debug ){ + ShowInfo("connect_check: Found match from allow list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_allow[i].ip), + CONVIP(access_allow[i].mask)); + } + is_allowip = 1; + break; + } + } + // Search the deny list + for( i=0; i < access_denynum; ++i ){ + if( (ip & access_deny[i].mask) == (access_deny[i].ip & access_deny[i].mask) ){ + if( access_debug ){ + ShowInfo("connect_check: Found match from deny list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_deny[i].ip), + CONVIP(access_deny[i].mask)); + } + is_denyip = 1; + break; + } + } + // Decide connection status + // 0 : Reject + // 1 : Accept + // 2 : Unconditional Accept (accepts even if flagged as DDoS) + switch(access_order) { + case ACO_DENY_ALLOW: + default: + if( is_denyip ) + connect_ok = 0; // Reject + else if( is_allowip ) + connect_ok = 2; // Unconditional Accept + else + connect_ok = 1; // Accept + break; + case ACO_ALLOW_DENY: + if( is_allowip ) + connect_ok = 2; // Unconditional Accept + else if( is_denyip ) + connect_ok = 0; // Reject + else + connect_ok = 1; // Accept + break; + case ACO_MUTUAL_FAILURE: + if( is_allowip && !is_denyip ) + connect_ok = 2; // Unconditional Accept + else + connect_ok = 0; // Reject + break; + } + + // Inspect connection history + while( hist ) { + if( ip == hist->ip ) + {// IP found + if( hist->ddos ) + {// flagged as DDoS + return (connect_ok == 2 ? 1 : 0); + } else if( DIFF_TICK(gettick(),hist->tick) < ddos_interval ) + {// connection within ddos_interval + hist->tick = gettick(); + if( hist->count++ >= ddos_count ) + {// DDoS attack detected + hist->ddos = 1; + ShowWarning("connect_check: DDoS Attack detected from %d.%d.%d.%d!\n", CONVIP(ip)); + return (connect_ok == 2 ? 1 : 0); + } + return connect_ok; + } else + {// not within ddos_interval, clear data + hist->tick = gettick(); + hist->count = 0; + return connect_ok; + } + } + hist = hist->next; + } + // IP not found, add to history + CREATE(hist, ConnectHistory, 1); + memset(hist, 0, sizeof(ConnectHistory)); + hist->ip = ip; + hist->tick = gettick(); + hist->next = connect_history[ip&0xFFFF]; + connect_history[ip&0xFFFF] = hist; + return connect_ok; +} + +/// Timer function. +/// Deletes old connection history records. +static int connect_check_clear(int tid, unsigned int tick, int id, intptr_t data) +{ + int i; + int clear = 0; + int list = 0; + ConnectHistory root; + ConnectHistory* prev_hist; + ConnectHistory* hist; + + for( i=0; i < 0x10000 ; ++i ){ + prev_hist = &root; + root.next = hist = connect_history[i]; + while( hist ){ + if( (!hist->ddos && DIFF_TICK(tick,hist->tick) > ddos_interval*3) || + (hist->ddos && DIFF_TICK(tick,hist->tick) > ddos_autoreset) ) + {// Remove connection history + prev_hist->next = hist->next; + aFree(hist); + hist = prev_hist->next; + clear++; + } else { + prev_hist = hist; + hist = hist->next; + } + list++; + } + connect_history[i] = root.next; + } + if( access_debug ){ + ShowInfo("connect_check_clear: Cleared %d of %d from IP list.\n", clear, list); + } + return list; +} + +/// Parses the ip address and mask and puts it into acc. +/// Returns 1 is successful, 0 otherwise. +int access_ipmask(const char* str, AccessControl* acc) +{ + uint32 ip; + uint32 mask; + unsigned int a[4]; + unsigned int m[4]; + int n; + + if( strcmp(str,"all") == 0 ) { + ip = 0; + mask = 0; + } else { + if( ((n=sscanf(str,"%u.%u.%u.%u/%u.%u.%u.%u",a,a+1,a+2,a+3,m,m+1,m+2,m+3)) != 8 && // not an ip + standard mask + (n=sscanf(str,"%u.%u.%u.%u/%u",a,a+1,a+2,a+3,m)) != 5 && // not an ip + bit mask + (n=sscanf(str,"%u.%u.%u.%u",a,a+1,a+2,a+3)) != 4 ) || // not an ip + a[0] > 255 || a[1] > 255 || a[2] > 255 || a[3] > 255 || // invalid ip + (n == 8 && (m[0] > 255 || m[1] > 255 || m[2] > 255 || m[3] > 255)) || // invalid standard mask + (n == 5 && m[0] > 32) ){ // invalid bit mask + return 0; + } + ip = MAKEIP(a[0],a[1],a[2],a[3]); + if( n == 8 ) + {// standard mask + mask = MAKEIP(m[0],m[1],m[2],m[3]); + } else if( n == 5 ) + {// bit mask + mask = 0; + while( m[0] ){ + mask = (mask >> 1) | 0x80000000; + --m[0]; + } + } else + {// just this ip + mask = 0xFFFFFFFF; + } + } + if( access_debug ){ + ShowInfo("access_ipmask: Loaded IP:%d.%d.%d.%d mask:%d.%d.%d.%d\n", CONVIP(ip), CONVIP(mask)); + } + acc->ip = ip; + acc->mask = mask; + return 1; +} +////////////////////////////// +#endif +////////////////////////////// + +int socket_config_read(const char* cfgName) +{ + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if(fp == NULL) { + ShowError("File not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, sizeof(line), fp)) + { + if(line[0] == '/' && line[1] == '/') + continue; + if(sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (!strcmpi(w1, "stall_time")) { + stall_time = atoi(w2); + if( stall_time < 3 ) + stall_time = 3;/* a minimum is required to refrain it from killing itself */ + } +#ifndef MINICORE + else if (!strcmpi(w1, "enable_ip_rules")) { + ip_rules = config_switch(w2); + } else if (!strcmpi(w1, "order")) { + if (!strcmpi(w2, "deny,allow")) + access_order = ACO_DENY_ALLOW; + else if (!strcmpi(w2, "allow,deny")) + access_order = ACO_ALLOW_DENY; + else if (!strcmpi(w2, "mutual-failure")) + access_order = ACO_MUTUAL_FAILURE; + } else if (!strcmpi(w1, "allow")) { + RECREATE(access_allow, AccessControl, access_allownum+1); + if (access_ipmask(w2, &access_allow[access_allownum])) + ++access_allownum; + else + ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); + } else if (!strcmpi(w1, "deny")) { + RECREATE(access_deny, AccessControl, access_denynum+1); + if (access_ipmask(w2, &access_deny[access_denynum])) + ++access_denynum; + else + ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); + } + else if (!strcmpi(w1,"ddos_interval")) + ddos_interval = atoi(w2); + else if (!strcmpi(w1,"ddos_count")) + ddos_count = atoi(w2); + else if (!strcmpi(w1,"ddos_autoreset")) + ddos_autoreset = atoi(w2); + else if (!strcmpi(w1,"debug")) + access_debug = config_switch(w2); + else if (!strcmpi(w1,"socket_max_client_packet")) + socket_max_client_packet = strtoul(w2, NULL, 0); +#endif + else if (!strcmpi(w1, "import")) + socket_config_read(w2); + else + ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); + } + + fclose(fp); + return 0; +} + + +void socket_final(void) +{ + int i; +#ifndef MINICORE + ConnectHistory* hist; + ConnectHistory* next_hist; + + for( i=0; i < 0x10000; ++i ){ + hist = connect_history[i]; + while( hist ){ + next_hist = hist->next; + aFree(hist); + hist = next_hist; + } + } + if( access_allow ) + aFree(access_allow); + if( access_deny ) + aFree(access_deny); +#endif + + for( i = 1; i < fd_max; i++ ) + if(session[i]) + do_close(i); + + // session[0] ‚̃_ƒ~[ƒf[ƒ^‚ðíœ + aFree(session[0]->rdata); + aFree(session[0]->wdata); + aFree(session[0]); +} + +/// Closes a socket. +void do_close(int fd) +{ + if( fd <= 0 ||fd >= FD_SETSIZE ) + return;// invalid + + flush_fifo(fd); // Try to send what's left (although it might not succeed since it's a nonblocking socket) + sFD_CLR(fd, &readfds);// this needs to be done before closing the socket + sShutdown(fd, SHUT_RDWR); // Disallow further reads/writes + sClose(fd); // We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. + if (session[fd]) delete_session(fd); +} + +/// Retrieve local ips in host byte order. +/// Uses loopback is no address is found. +int socket_getips(uint32* ips, int max) +{ + int num = 0; + + if( ips == NULL || max <= 0 ) + return 0; + +#ifdef WIN32 + { + char fullhost[255]; + u_long** a; + struct hostent* hent; + + // XXX This should look up the local IP addresses in the registry + // instead of calling gethostbyname. However, the way IP addresses + // are stored in the registry is annoyingly complex, so I'll leave + // this as T.B.D. [Meruru] + if( gethostname(fullhost, sizeof(fullhost)) == SOCKET_ERROR ) + { + ShowError("socket_getips: No hostname defined!\n"); + return 0; + } + else + { + hent = gethostbyname(fullhost); + if( hent == NULL ){ + ShowError("socket_getips: Cannot resolve our own hostname to an IP address\n"); + return 0; + } + a = (u_long**)hent->h_addr_list; + for( ; a[num] != NULL && num < max; ++num) + ips[num] = (uint32)ntohl(*a[num]); + } + } +#else // not WIN32 + { + int pos; + int fd; + char buf[2*16*sizeof(struct ifreq)]; + struct ifconf ic; + struct ifreq* ir; + struct sockaddr_in* a; + u_long ad; + + fd = sSocket(AF_INET, SOCK_STREAM, 0); + + memset(buf, 0x00, sizeof(buf)); + + // The ioctl call will fail with Invalid Argument if there are more + // interfaces than will fit in the buffer + ic.ifc_len = sizeof(buf); + ic.ifc_buf = buf; + if( sIoctl(fd, SIOCGIFCONF, &ic) == -1 ) + { + ShowError("socket_getips: SIOCGIFCONF failed!\n"); + return 0; + } + else + { + for( pos=0; pos < ic.ifc_len && num < max; ) + { + ir = (struct ifreq*)(buf+pos); + a = (struct sockaddr_in*) &(ir->ifr_addr); + if( a->sin_family == AF_INET ){ + ad = ntohl(a->sin_addr.s_addr); + if( ad != INADDR_LOOPBACK && ad != INADDR_ANY ) + ips[num++] = (uint32)ad; + } + #if (defined(BSD) && BSD >= 199103) || defined(_AIX) || defined(__APPLE__) + pos += ir->ifr_addr.sa_len + sizeof(ir->ifr_name); + #else// not AIX or APPLE + pos += sizeof(struct ifreq); + #endif//not AIX or APPLE + } + } + sClose(fd); + } +#endif // not W32 + + // Use loopback if no ips are found + if( num == 0 ) + ips[num++] = (uint32)INADDR_LOOPBACK; + + return num; +} + +void socket_init(void) +{ + char *SOCKET_CONF_FILENAME = "conf/packet_athena.conf"; + unsigned int rlim_cur = FD_SETSIZE; + +#ifdef WIN32 + {// Start up windows networking + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 0); + if( WSAStartup(wVersionRequested, &wsaData) != 0 ) + { + ShowError("socket_init: WinSock not available!\n"); + return; + } + if( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0 ) + { + ShowError("socket_init: WinSock version mismatch (2.0 or compatible required)!\n"); + return; + } + } +#elif defined(HAVE_SETRLIMIT) && !defined(CYGWIN) + // NOTE: getrlimit and setrlimit have bogus behaviour in cygwin. + // "Number of fds is virtually unlimited in cygwin" (sys/param.h) + {// set socket limit to FD_SETSIZE + struct rlimit rlp; + if( 0 == getrlimit(RLIMIT_NOFILE, &rlp) ) + { + rlp.rlim_cur = FD_SETSIZE; + if( 0 != setrlimit(RLIMIT_NOFILE, &rlp) ) + {// failed, try setting the maximum too (permission to change system limits is required) + rlp.rlim_max = FD_SETSIZE; + if( 0 != setrlimit(RLIMIT_NOFILE, &rlp) ) + {// failed + const char *errmsg = error_msg(); + int rlim_ori; + // set to maximum allowed + getrlimit(RLIMIT_NOFILE, &rlp); + rlim_ori = (int)rlp.rlim_cur; + rlp.rlim_cur = rlp.rlim_max; + setrlimit(RLIMIT_NOFILE, &rlp); + // report limit + getrlimit(RLIMIT_NOFILE, &rlp); + rlim_cur = rlp.rlim_cur; + ShowWarning("socket_init: failed to set socket limit to %d, setting to maximum allowed (original limit=%d, current limit=%d, maximum allowed=%d, %s).\n", FD_SETSIZE, rlim_ori, (int)rlp.rlim_cur, (int)rlp.rlim_max, errmsg); + } + } + } + } +#endif + + // Get initial local ips + naddr_ = socket_getips(addr_,16); + + sFD_ZERO(&readfds); +#if defined(SEND_SHORTLIST) + memset(send_shortlist_set, 0, sizeof(send_shortlist_set)); +#endif + + socket_config_read(SOCKET_CONF_FILENAME); + + // initialise last send-receive tick + last_tick = time(NULL); + + // session[0] is now currently used for disconnected sessions of the map server, and as such, + // should hold enough buffer (it is a vacuum so to speak) as it is never flushed. [Skotlex] + create_session(0, null_recv, null_send, null_parse); + +#ifndef MINICORE + // Delete old connection history every 5 minutes + memset(connect_history, 0, sizeof(connect_history)); + add_timer_func_list(connect_check_clear, "connect_check_clear"); + add_timer_interval(gettick()+1000, connect_check_clear, 0, 0, 5*60*1000); +#endif + + ShowInfo("Server supports up to '"CL_WHITE"%u"CL_RESET"' concurrent connections.\n", rlim_cur); +} + + +bool session_isValid(int fd) +{ + return ( fd > 0 && fd < FD_SETSIZE && session[fd] != NULL ); +} + +bool session_isActive(int fd) +{ + return ( session_isValid(fd) && !session[fd]->flag.eof ); +} + +// Resolves hostname into a numeric ip. +uint32 host2ip(const char* hostname) +{ + struct hostent* h = gethostbyname(hostname); + return (h != NULL) ? ntohl(*(uint32*)h->h_addr) : 0; +} + +// Converts a numeric ip into a dot-formatted string. +// Result is placed either into a user-provided buffer or a static system buffer. +const char* ip2str(uint32 ip, char ip_str[16]) +{ + struct in_addr addr; + addr.s_addr = htonl(ip); + return (ip_str == NULL) ? inet_ntoa(addr) : strncpy(ip_str, inet_ntoa(addr), 16); +} + +// Converts a dot-formatted ip string into a numeric ip. +uint32 str2ip(const char* ip_str) +{ + return ntohl(inet_addr(ip_str)); +} + +// Reorders bytes from network to little endian (Windows). +// Neccessary for sending port numbers to the RO client until Gravity notices that they forgot ntohs() calls. +uint16 ntows(uint16 netshort) +{ + return ((netshort & 0xFF) << 8) | ((netshort & 0xFF00) >> 8); +} + +#ifdef SEND_SHORTLIST +// Add a fd to the shortlist so that it'll be recognized as a fd that needs +// sending or eof handling. +void send_shortlist_add_fd(int fd) +{ + int i; + int bit; + + if( !session_isValid(fd) ) + return;// out of range + + i = fd/32; + bit = fd%32; + + if( (send_shortlist_set[i]>>bit)&1 ) + return;// already in the list + + if( send_shortlist_count >= ARRAYLENGTH(send_shortlist_array) ) + { + ShowDebug("send_shortlist_add_fd: shortlist is full, ignoring... (fd=%d shortlist.count=%d shortlist.length=%d)\n", fd, send_shortlist_count, ARRAYLENGTH(send_shortlist_array)); + return; + } + + // set the bit + send_shortlist_set[i] |= 1<<bit; + // Add to the end of the shortlist array. + send_shortlist_array[send_shortlist_count++] = fd; +} + +// Do pending network sends and eof handling from the shortlist. +void send_shortlist_do_sends() +{ + int i; + + for( i = send_shortlist_count-1; i >= 0; --i ) + { + int fd = send_shortlist_array[i]; + int idx = fd/32; + int bit = fd%32; + + // Remove fd from shortlist, move the last fd to the current position + --send_shortlist_count; + send_shortlist_array[i] = send_shortlist_array[send_shortlist_count]; + send_shortlist_array[send_shortlist_count] = 0; + + if( fd <= 0 || fd >= FD_SETSIZE ) + { + ShowDebug("send_shortlist_do_sends: fd is out of range, corrupted memory? (fd=%d)\n", fd); + continue; + } + if( ((send_shortlist_set[idx]>>bit)&1) == 0 ) + { + ShowDebug("send_shortlist_do_sends: fd is not set, why is it in the shortlist? (fd=%d)\n", fd); + continue; + } + send_shortlist_set[idx]&=~(1<<bit);// unset fd + // If this session still exists, perform send operations on it and + // check for the eof state. + if( session[fd] ) + { + // Send data + if( session[fd]->wdata_size ) + session[fd]->func_send(fd); + + // If it's been marked as eof, call the parse func on it so that + // the socket will be immediately closed. + if( session[fd]->flag.eof ) + session[fd]->func_parse(fd); + + // If the session still exists, is not eof and has things left to + // be sent from it we'll re-add it to the shortlist. + if( session[fd] && !session[fd]->flag.eof && session[fd]->wdata_size ) + send_shortlist_add_fd(fd); + } + } +} +#endif diff --git a/src/common/socket.h b/src/common/socket.h new file mode 100644 index 000000000..7c0e02f5d --- /dev/null +++ b/src/common/socket.h @@ -0,0 +1,163 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include "../common/cbasetypes.h" + +#ifdef WIN32 + #include "../common/winapi.h" + typedef long in_addr_t; +#else + #include <sys/types.h> + #include <sys/socket.h> + #include <netinet/in.h> +#endif + +#include <time.h> + +#define FIFOSIZE_SERVERLINK 256*1024 + +// socket I/O macros +#define RFIFOHEAD(fd) +#define WFIFOHEAD(fd, size) do{ if((fd) && session[fd]->wdata_size + (size) > session[fd]->max_wdata ) realloc_writefifo(fd, size); }while(0) +#define RFIFOP(fd,pos) (session[fd]->rdata + session[fd]->rdata_pos + (pos)) +#define WFIFOP(fd,pos) (session[fd]->wdata + session[fd]->wdata_size + (pos)) + +#define RFIFOB(fd,pos) (*(uint8*)RFIFOP(fd,pos)) +#define WFIFOB(fd,pos) (*(uint8*)WFIFOP(fd,pos)) +#define RFIFOW(fd,pos) (*(uint16*)RFIFOP(fd,pos)) +#define WFIFOW(fd,pos) (*(uint16*)WFIFOP(fd,pos)) +#define RFIFOL(fd,pos) (*(uint32*)RFIFOP(fd,pos)) +#define WFIFOL(fd,pos) (*(uint32*)WFIFOP(fd,pos)) +#define RFIFOQ(fd,pos) (*(uint64*)RFIFOP(fd,pos)) +#define WFIFOQ(fd,pos) (*(uint64*)WFIFOP(fd,pos)) +#define RFIFOSPACE(fd) (session[fd]->max_rdata - session[fd]->rdata_size) +#define WFIFOSPACE(fd) (session[fd]->max_wdata - session[fd]->wdata_size) + +#define RFIFOREST(fd) (session[fd]->flag.eof ? 0 : session[fd]->rdata_size - session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) \ + do { \ + if(session[fd]->rdata_size == session[fd]->rdata_pos){ \ + session[fd]->rdata_size = session[fd]->rdata_pos = 0; \ + } else { \ + session[fd]->rdata_size -= session[fd]->rdata_pos; \ + memmove(session[fd]->rdata, session[fd]->rdata+session[fd]->rdata_pos, session[fd]->rdata_size); \ + session[fd]->rdata_pos = 0; \ + } \ + } while(0) + +// buffer I/O macros +#define RBUFP(p,pos) (((uint8*)(p)) + (pos)) +#define RBUFB(p,pos) (*(uint8*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(uint16*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(uint32*)RBUFP((p),(pos))) +#define RBUFQ(p,pos) (*(uint64*)RBUFP((p),(pos))) + +#define WBUFP(p,pos) (((uint8*)(p)) + (pos)) +#define WBUFB(p,pos) (*(uint8*)WBUFP((p),(pos))) +#define WBUFW(p,pos) (*(uint16*)WBUFP((p),(pos))) +#define WBUFL(p,pos) (*(uint32*)WBUFP((p),(pos))) +#define WBUFQ(p,pos) (*(uint64*)WBUFP((p),(pos))) + +#define TOB(n) ((uint8)((n)&UINT8_MAX)) +#define TOW(n) ((uint16)((n)&UINT16_MAX)) +#define TOL(n) ((uint32)((n)&UINT32_MAX)) + + +// Struct declaration +typedef int (*RecvFunc)(int fd); +typedef int (*SendFunc)(int fd); +typedef int (*ParseFunc)(int fd); + +struct socket_data +{ + struct { + unsigned char eof : 1; + unsigned char server : 1; + unsigned char ping : 2; + } flag; + + uint32 client_addr; // remote client address + + uint8 *rdata, *wdata; + size_t max_rdata, max_wdata; + size_t rdata_size, wdata_size; + size_t rdata_pos; + time_t rdata_tick; // time of last recv (for detecting timeouts); zero when timeout is disabled + + RecvFunc func_recv; + SendFunc func_send; + ParseFunc func_parse; + + void* session_data; // stores application-specific data related to the session +}; + + +// Data prototype declaration + +extern struct socket_data* session[FD_SETSIZE]; + +extern int fd_max; + +extern time_t last_tick; +extern time_t stall_time; + +////////////////////////////////// +// some checking on sockets +extern bool session_isValid(int fd); +extern bool session_isActive(int fd); +////////////////////////////////// + +// Function prototype declaration + +int make_listen_bind(uint32 ip, uint16 port); +int make_connection(uint32 ip, uint16 port, bool silent); +int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size); +int realloc_writefifo(int fd, size_t addition); +int WFIFOSET(int fd, size_t len); +int RFIFOSKIP(int fd, size_t len); + +int do_sockets(int next); +void do_close(int fd); +void socket_init(void); +void socket_final(void); + +extern void flush_fifo(int fd); +extern void flush_fifos(void); +extern void set_nonblocking(int fd, unsigned long yes); + +void set_defaultparse(ParseFunc defaultparse); + +// hostname/ip conversion functions +uint32 host2ip(const char* hostname); +const char* ip2str(uint32 ip, char ip_str[16]); +uint32 str2ip(const char* ip_str); +#define CONVIP(ip) ((ip)>>24)&0xFF,((ip)>>16)&0xFF,((ip)>>8)&0xFF,((ip)>>0)&0xFF +#define MAKEIP(a,b,c,d) (uint32)( ( ( (a)&0xFF ) << 24 ) | ( ( (b)&0xFF ) << 16 ) | ( ( (c)&0xFF ) << 8 ) | ( ( (d)&0xFF ) << 0 ) ) +uint16 ntows(uint16 netshort); + +int socket_getips(uint32* ips, int max); + +extern uint32 addr_[16]; // ip addresses of local host (host byte order) +extern int naddr_; // # of ip addresses + +void set_eof(int fd); + +/// Use a shortlist of sockets instead of iterating all sessions for sockets +/// that have data to send or need eof handling. +/// Adapted to use a static array instead of a linked list. +/// +/// @author Buuyo-tama +#define SEND_SHORTLIST + +#ifdef SEND_SHORTLIST +// Add a fd to the shortlist so that it'll be recognized as a fd that needs +// sending done on it. +void send_shortlist_add_fd(int fd); +// Do pending network sends (and eof handling) from the shortlist. +void send_shortlist_do_sends(); +#endif + +#endif /* _SOCKET_H_ */ diff --git a/src/common/spinlock.h b/src/common/spinlock.h new file mode 100644 index 000000000..3419bfdd5 --- /dev/null +++ b/src/common/spinlock.h @@ -0,0 +1,104 @@ +#pragma once +#ifndef _rA_SPINLOCK_H_ +#define _rA_SPINLOCK_H_ + +// +// CAS based Spinlock Implementation +// +// CamelCase names are choosen to be consistent with microsofts winapi +// which implements CriticalSection by this naming... +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder +// +// + +#ifdef WIN32 +#include "../common/winapi.h" +#endif + +#include "../common/cbasetypes.h" +#include "../common/atomic.h" +#include "../common/thread.h" + +#ifdef WIN32 + +typedef struct __declspec( align(64) ) SPIN_LOCK{ + volatile LONG lock; + volatile LONG nest; + volatile LONG sync_lock; +} SPIN_LOCK, *PSPIN_LOCK; +#else +typedef struct SPIN_LOCK{ + volatile int32 lock; + volatile int32 nest; // nesting level. + + volatile int32 sync_lock; +} __attribute__((aligned(64))) SPIN_LOCK, *PSPIN_LOCK; +#endif + + + +static forceinline void InitializeSpinLock(PSPIN_LOCK lck){ + lck->lock = 0; + lck->nest = 0; + lck->sync_lock = 0; +} + +static forceinline void FinalizeSpinLock(PSPIN_LOCK lck){ + return; +} + + +#define getsynclock(l) { while(1){ if(InterlockedCompareExchange(l, 1, 0) == 0) break; rathread_yield(); } } +#define dropsynclock(l) { InterlockedExchange(l, 0); } + +static forceinline void EnterSpinLock(PSPIN_LOCK lck){ + int tid = rathread_get_tid(); + + // Get Sync Lock && Check if the requester thread already owns the lock. + // if it owns, increase nesting level + getsynclock(&lck->sync_lock); + if(InterlockedCompareExchange(&lck->lock, tid, tid) == tid){ + InterlockedIncrement(&lck->nest); + dropsynclock(&lck->sync_lock); + return; // Got Lock + } + // drop sync lock + dropsynclock(&lck->sync_lock); + + + // Spin until we've got it ! + while(1){ + + if(InterlockedCompareExchange(&lck->lock, tid, 0) == 0){ + + InterlockedIncrement(&lck->nest); + return; // Got Lock + } + + rathread_yield(); // Force ctxswitch to another thread. + } + +} + + +static forceinline void LeaveSpinLock(PSPIN_LOCK lck){ + int tid = rathread_get_tid(); + + getsynclock(&lck->sync_lock); + + if(InterlockedCompareExchange(&lck->lock, tid, tid) == tid){ // this thread owns the lock. + if(InterlockedDecrement(&lck->nest) == 0) + InterlockedExchange(&lck->lock, 0); // Unlock! + } + + dropsynclock(&lck->sync_lock); +} + + + + +#endif diff --git a/src/common/sql.c b/src/common/sql.c new file mode 100644 index 000000000..800aa89b0 --- /dev/null +++ b/src/common/sql.c @@ -0,0 +1,948 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/strlib.h" +#include "../common/timer.h" +#include "sql.h" + +#ifdef WIN32 +#include "../common/winapi.h" +#endif +#include <mysql.h> +#include <string.h>// strlen/strnlen/memcpy/memset +#include <stdlib.h>// strtoul + + + +/// Sql handle +struct Sql +{ + StringBuf buf; + MYSQL handle; + MYSQL_RES* result; + MYSQL_ROW row; + unsigned long* lengths; + int keepalive; +}; + + + +// Column length receiver. +// Takes care of the possible size missmatch between uint32 and unsigned long. +struct s_column_length +{ + uint32* out_length; + unsigned long length; +}; +typedef struct s_column_length s_column_length; + + + +/// Sql statement +struct SqlStmt +{ + StringBuf buf; + MYSQL_STMT* stmt; + MYSQL_BIND* params; + MYSQL_BIND* columns; + s_column_length* column_lengths; + size_t max_params; + size_t max_columns; + bool bind_params; + bool bind_columns; +}; + + + +/////////////////////////////////////////////////////////////////////////////// +// Sql Handle +/////////////////////////////////////////////////////////////////////////////// + + + +/// Allocates and initializes a new Sql handle. +Sql* Sql_Malloc(void) +{ + Sql* self; + + CREATE(self, Sql, 1); + mysql_init(&self->handle); + StringBuf_Init(&self->buf); + self->lengths = NULL; + self->result = NULL; + self->keepalive = INVALID_TIMER; + + return self; +} + + + +static int Sql_P_Keepalive(Sql* self); + +/// Establishes a connection. +int Sql_Connect(Sql* self, const char* user, const char* passwd, const char* host, uint16 port, const char* db) +{ + if( self == NULL ) + return SQL_ERROR; + + StringBuf_Clear(&self->buf); + if( !mysql_real_connect(&self->handle, host, user, passwd, db, (unsigned int)port, NULL/*unix_socket*/, 0/*clientflag*/) ) + { + ShowSQL("%s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + + self->keepalive = Sql_P_Keepalive(self); + if( self->keepalive == INVALID_TIMER ) + { + ShowSQL("Failed to establish keepalive for DB connection!\n"); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + + +/// Retrieves the timeout of the connection. +int Sql_GetTimeout(Sql* self, uint32* out_timeout) +{ + if( self && out_timeout && SQL_SUCCESS == Sql_Query(self, "SHOW VARIABLES LIKE 'wait_timeout'") ) + { + char* data; + size_t len; + if( SQL_SUCCESS == Sql_NextRow(self) && + SQL_SUCCESS == Sql_GetData(self, 1, &data, &len) ) + { + *out_timeout = (uint32)strtoul(data, NULL, 10); + Sql_FreeResult(self); + return SQL_SUCCESS; + } + Sql_FreeResult(self); + } + return SQL_ERROR; +} + + + +/// Retrieves the name of the columns of a table into out_buf, with the separator after each name. +int Sql_GetColumnNames(Sql* self, const char* table, char* out_buf, size_t buf_len, char sep) +{ + char* data; + size_t len; + size_t off = 0; + + if( self == NULL || SQL_ERROR == Sql_Query(self, "EXPLAIN `%s`", table) ) + return SQL_ERROR; + + out_buf[off] = '\0'; + while( SQL_SUCCESS == Sql_NextRow(self) && SQL_SUCCESS == Sql_GetData(self, 0, &data, &len) ) + { + len = strnlen(data, len); + if( off + len + 2 > buf_len ) + { + ShowDebug("Sql_GetColumns: output buffer is too small\n"); + *out_buf = '\0'; + return SQL_ERROR; + } + memcpy(out_buf+off, data, len); + off += len; + out_buf[off++] = sep; + } + out_buf[off] = '\0'; + Sql_FreeResult(self); + return SQL_SUCCESS; +} + + + +/// Changes the encoding of the connection. +int Sql_SetEncoding(Sql* self, const char* encoding) +{ + if( self && mysql_set_character_set(&self->handle, encoding) == 0 ) + return SQL_SUCCESS; + return SQL_ERROR; +} + + + +/// Pings the connection. +int Sql_Ping(Sql* self) +{ + if( self && mysql_ping(&self->handle) == 0 ) + return SQL_SUCCESS; + return SQL_ERROR; +} + + + +/// Wrapper function for Sql_Ping. +/// +/// @private +static int Sql_P_KeepaliveTimer(int tid, unsigned int tick, int id, intptr_t data) +{ + Sql* self = (Sql*)data; + ShowInfo("Pinging SQL server to keep connection alive...\n"); + Sql_Ping(self); + return 0; +} + + + +/// Establishes keepalive (periodic ping) on the connection. +/// +/// @return the keepalive timer id, or INVALID_TIMER +/// @private +static int Sql_P_Keepalive(Sql* self) +{ + uint32 timeout, ping_interval; + + // set a default value first + timeout = 28800; // 8 hours + + // request the timeout value from the mysql server + Sql_GetTimeout(self, &timeout); + + if( timeout < 60 ) + timeout = 60; + + // establish keepalive + ping_interval = timeout - 30; // 30-second reserve + //add_timer_func_list(Sql_P_KeepaliveTimer, "Sql_P_KeepaliveTimer"); + return add_timer_interval(gettick() + ping_interval*1000, Sql_P_KeepaliveTimer, 0, (intptr_t)self, ping_interval*1000); +} + + + +/// Escapes a string. +size_t Sql_EscapeString(Sql* self, char *out_to, const char *from) +{ + if( self ) + return (size_t)mysql_real_escape_string(&self->handle, out_to, from, (unsigned long)strlen(from)); + else + return (size_t)mysql_escape_string(out_to, from, (unsigned long)strlen(from)); +} + + + +/// Escapes a string. +size_t Sql_EscapeStringLen(Sql* self, char *out_to, const char *from, size_t from_len) +{ + if( self ) + return (size_t)mysql_real_escape_string(&self->handle, out_to, from, (unsigned long)from_len); + else + return (size_t)mysql_escape_string(out_to, from, (unsigned long)from_len); +} + + + +/// Executes a query. +int Sql_Query(Sql* self, const char* query, ...) +{ + int res; + va_list args; + + va_start(args, query); + res = Sql_QueryV(self, query, args); + va_end(args); + + return res; +} + + + +/// Executes a query. +int Sql_QueryV(Sql* self, const char* query, va_list args) +{ + if( self == NULL ) + return SQL_ERROR; + + Sql_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_Vprintf(&self->buf, query, args); + if( mysql_real_query(&self->handle, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + self->result = mysql_store_result(&self->handle); + if( mysql_errno(&self->handle) != 0 ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + return SQL_SUCCESS; +} + + + +/// Executes a query. +int Sql_QueryStr(Sql* self, const char* query) +{ + if( self == NULL ) + return SQL_ERROR; + + Sql_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_AppendStr(&self->buf, query); + if( mysql_real_query(&self->handle, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + self->result = mysql_store_result(&self->handle); + if( mysql_errno(&self->handle) != 0 ) + { + ShowSQL("DB error - %s\n", mysql_error(&self->handle)); + return SQL_ERROR; + } + return SQL_SUCCESS; +} + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE query. +uint64 Sql_LastInsertId(Sql* self) +{ + if( self ) + return (uint64)mysql_insert_id(&self->handle); + else + return 0; +} + + + +/// Returns the number of columns in each row of the result. +uint32 Sql_NumColumns(Sql* self) +{ + if( self && self->result ) + return (uint32)mysql_num_fields(self->result); + return 0; +} + + + +/// Returns the number of rows in the result. +uint64 Sql_NumRows(Sql* self) +{ + if( self && self->result ) + return (uint64)mysql_num_rows(self->result); + return 0; +} + + + +/// Fetches the next row. +int Sql_NextRow(Sql* self) +{ + if( self && self->result ) + { + self->row = mysql_fetch_row(self->result); + if( self->row ) + { + self->lengths = mysql_fetch_lengths(self->result); + return SQL_SUCCESS; + } + self->lengths = NULL; + if( mysql_errno(&self->handle) == 0 ) + return SQL_NO_DATA; + } + return SQL_ERROR; +} + + + +/// Gets the data of a column. +int Sql_GetData(Sql* self, size_t col, char** out_buf, size_t* out_len) +{ + if( self && self->row ) + { + if( col < Sql_NumColumns(self) ) + { + if( out_buf ) *out_buf = self->row[col]; + if( out_len ) *out_len = (size_t)self->lengths[col]; + } + else + {// out of range - ignore + if( out_buf ) *out_buf = NULL; + if( out_len ) *out_len = 0; + } + return SQL_SUCCESS; + } + return SQL_ERROR; +} + + + +/// Frees the result of the query. +void Sql_FreeResult(Sql* self) +{ + if( self && self->result ) + { + mysql_free_result(self->result); + self->result = NULL; + self->row = NULL; + self->lengths = NULL; + } +} + + + +/// Shows debug information (last query). +void Sql_ShowDebug_(Sql* self, const char* debug_file, const unsigned long debug_line) +{ + if( self == NULL ) + ShowDebug("at %s:%lu - self is NULL\n", debug_file, debug_line); + else if( StringBuf_Length(&self->buf) > 0 ) + ShowDebug("at %s:%lu - %s\n", debug_file, debug_line, StringBuf_Value(&self->buf)); + else + ShowDebug("at %s:%lu\n", debug_file, debug_line); +} + + + +/// Frees a Sql handle returned by Sql_Malloc. +void Sql_Free(Sql* self) +{ + if( self ) + { + Sql_FreeResult(self); + StringBuf_Destroy(&self->buf); + if( self->keepalive != INVALID_TIMER ) delete_timer(self->keepalive, Sql_P_KeepaliveTimer); + aFree(self); + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// Prepared Statements +/////////////////////////////////////////////////////////////////////////////// + + + +/// Returns the mysql integer type for the target size. +/// +/// @private +static enum enum_field_types Sql_P_SizeToMysqlIntType(int sz) +{ + switch( sz ) + { + case 1: return MYSQL_TYPE_TINY; + case 2: return MYSQL_TYPE_SHORT; + case 4: return MYSQL_TYPE_LONG; + case 8: return MYSQL_TYPE_LONGLONG; + default: + ShowDebug("SizeToMysqlIntType: unsupported size (%d)\n", sz); + return MYSQL_TYPE_NULL; + } +} + + + +/// Binds a parameter/result. +/// +/// @private +static int Sql_P_BindSqlDataType(MYSQL_BIND* bind, enum SqlDataType buffer_type, void* buffer, size_t buffer_len, unsigned long* out_length, int8* out_is_null) +{ + memset(bind, 0, sizeof(MYSQL_BIND)); + switch( buffer_type ) + { + case SQLDT_NULL: bind->buffer_type = MYSQL_TYPE_NULL; + buffer_len = 0;// FIXME length = ? [FlavioJS] + break; + // fixed size + case SQLDT_UINT8: bind->is_unsigned = 1; + case SQLDT_INT8: bind->buffer_type = MYSQL_TYPE_TINY; + buffer_len = 1; + break; + case SQLDT_UINT16: bind->is_unsigned = 1; + case SQLDT_INT16: bind->buffer_type = MYSQL_TYPE_SHORT; + buffer_len = 2; + break; + case SQLDT_UINT32: bind->is_unsigned = 1; + case SQLDT_INT32: bind->buffer_type = MYSQL_TYPE_LONG; + buffer_len = 4; + break; + case SQLDT_UINT64: bind->is_unsigned = 1; + case SQLDT_INT64: bind->buffer_type = MYSQL_TYPE_LONGLONG; + buffer_len = 8; + break; + // platform dependent size + case SQLDT_UCHAR: bind->is_unsigned = 1; + case SQLDT_CHAR: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(char)); + buffer_len = sizeof(char); + break; + case SQLDT_USHORT: bind->is_unsigned = 1; + case SQLDT_SHORT: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(short)); + buffer_len = sizeof(short); + break; + case SQLDT_UINT: bind->is_unsigned = 1; + case SQLDT_INT: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(int)); + buffer_len = sizeof(int); + break; + case SQLDT_ULONG: bind->is_unsigned = 1; + case SQLDT_LONG: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(long)); + buffer_len = sizeof(long); + break; + case SQLDT_ULONGLONG: bind->is_unsigned = 1; + case SQLDT_LONGLONG: bind->buffer_type = Sql_P_SizeToMysqlIntType(sizeof(int64)); + buffer_len = sizeof(int64); + break; + // floating point + case SQLDT_FLOAT: bind->buffer_type = MYSQL_TYPE_FLOAT; + buffer_len = 4; + break; + case SQLDT_DOUBLE: bind->buffer_type = MYSQL_TYPE_DOUBLE; + buffer_len = 8; + break; + // other + case SQLDT_STRING: + case SQLDT_ENUM: bind->buffer_type = MYSQL_TYPE_STRING; + break; + case SQLDT_BLOB: bind->buffer_type = MYSQL_TYPE_BLOB; + break; + default: + ShowDebug("Sql_P_BindSqlDataType: unsupported buffer type (%d)\n", buffer_type); + return SQL_ERROR; + } + bind->buffer = buffer; + bind->buffer_length = (unsigned long)buffer_len; + bind->length = out_length; + bind->is_null = (my_bool*)out_is_null; + return SQL_SUCCESS; +} + + + +/// Prints debug information about a field (type and length). +/// +/// @private +static void Sql_P_ShowDebugMysqlFieldInfo(const char* prefix, enum enum_field_types type, int is_unsigned, unsigned long length, const char* length_postfix) +{ + const char* sign = (is_unsigned ? "UNSIGNED " : ""); + const char* type_string; + switch( type ) + { + default: + ShowDebug("%stype=%s%u, length=%d\n", prefix, sign, type, length); + return; +#define SHOW_DEBUG_OF(x) case x: type_string = #x; break + SHOW_DEBUG_OF(MYSQL_TYPE_TINY); + SHOW_DEBUG_OF(MYSQL_TYPE_SHORT); + SHOW_DEBUG_OF(MYSQL_TYPE_LONG); + SHOW_DEBUG_OF(MYSQL_TYPE_INT24); + SHOW_DEBUG_OF(MYSQL_TYPE_LONGLONG); + SHOW_DEBUG_OF(MYSQL_TYPE_DECIMAL); + SHOW_DEBUG_OF(MYSQL_TYPE_FLOAT); + SHOW_DEBUG_OF(MYSQL_TYPE_DOUBLE); + SHOW_DEBUG_OF(MYSQL_TYPE_TIMESTAMP); + SHOW_DEBUG_OF(MYSQL_TYPE_DATE); + SHOW_DEBUG_OF(MYSQL_TYPE_TIME); + SHOW_DEBUG_OF(MYSQL_TYPE_DATETIME); + SHOW_DEBUG_OF(MYSQL_TYPE_YEAR); + SHOW_DEBUG_OF(MYSQL_TYPE_STRING); + SHOW_DEBUG_OF(MYSQL_TYPE_VAR_STRING); + SHOW_DEBUG_OF(MYSQL_TYPE_BLOB); + SHOW_DEBUG_OF(MYSQL_TYPE_SET); + SHOW_DEBUG_OF(MYSQL_TYPE_ENUM); + SHOW_DEBUG_OF(MYSQL_TYPE_NULL); +#undef SHOW_DEBUG_TYPE_OF + } + ShowDebug("%stype=%s%s, length=%d%s\n", prefix, sign, type_string, length, length_postfix); +} + + + +/// Reports debug information about a truncated column. +/// +/// @private +static void SqlStmt_P_ShowDebugTruncatedColumn(SqlStmt* self, size_t i) +{ + MYSQL_RES* meta; + MYSQL_FIELD* field; + MYSQL_BIND* column; + + meta = mysql_stmt_result_metadata(self->stmt); + field = mysql_fetch_field_direct(meta, (unsigned int)i); + ShowSQL("DB error - data of field '%s' was truncated.\n", field->name); + ShowDebug("column - %lu\n", (unsigned long)i); + Sql_P_ShowDebugMysqlFieldInfo("data - ", field->type, field->flags&UNSIGNED_FLAG, self->column_lengths[i].length, ""); + column = &self->columns[i]; + if( column->buffer_type == MYSQL_TYPE_STRING ) + Sql_P_ShowDebugMysqlFieldInfo("buffer - ", column->buffer_type, column->is_unsigned, column->buffer_length, "+1(nul-terminator)"); + else + Sql_P_ShowDebugMysqlFieldInfo("buffer - ", column->buffer_type, column->is_unsigned, column->buffer_length, ""); + mysql_free_result(meta); +} + + + +/// Allocates and initializes a new SqlStmt handle. +SqlStmt* SqlStmt_Malloc(Sql* sql) +{ + SqlStmt* self; + MYSQL_STMT* stmt; + + if( sql == NULL ) + return NULL; + + stmt = mysql_stmt_init(&sql->handle); + if( stmt == NULL ) + { + ShowSQL("DB error - %s\n", mysql_error(&sql->handle)); + return NULL; + } + CREATE(self, SqlStmt, 1); + StringBuf_Init(&self->buf); + self->stmt = stmt; + self->params = NULL; + self->columns = NULL; + self->column_lengths = NULL; + self->max_params = 0; + self->max_columns = 0; + self->bind_params = false; + self->bind_columns = false; + + return self; +} + + + +/// Prepares the statement. +int SqlStmt_Prepare(SqlStmt* self, const char* query, ...) +{ + int res; + va_list args; + + va_start(args, query); + res = SqlStmt_PrepareV(self, query, args); + va_end(args); + + return res; +} + + + +/// Prepares the statement. +int SqlStmt_PrepareV(SqlStmt* self, const char* query, va_list args) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_Vprintf(&self->buf, query, args); + if( mysql_stmt_prepare(self->stmt, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_params = false; + + return SQL_SUCCESS; +} + + + +/// Prepares the statement. +int SqlStmt_PrepareStr(SqlStmt* self, const char* query) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + StringBuf_Clear(&self->buf); + StringBuf_AppendStr(&self->buf, query); + if( mysql_stmt_prepare(self->stmt, StringBuf_Value(&self->buf), (unsigned long)StringBuf_Length(&self->buf)) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_params = false; + + return SQL_SUCCESS; +} + + + +/// Returns the number of parameters in the prepared statement. +size_t SqlStmt_NumParams(SqlStmt* self) +{ + if( self ) + return (size_t)mysql_stmt_param_count(self->stmt); + else + return 0; +} + + + +/// Binds a parameter to a buffer. +int SqlStmt_BindParam(SqlStmt* self, size_t idx, enum SqlDataType buffer_type, void* buffer, size_t buffer_len) +{ + if( self == NULL ) + return SQL_ERROR; + + if( !self->bind_params ) + {// initialize the bindings + size_t i; + size_t count; + + count = SqlStmt_NumParams(self); + if( self->max_params < count ) + { + self->max_params = count; + RECREATE(self->params, MYSQL_BIND, count); + } + memset(self->params, 0, count*sizeof(MYSQL_BIND)); + for( i = 0; i < count; ++i ) + self->params[i].buffer_type = MYSQL_TYPE_NULL; + self->bind_params = true; + } + if( idx < self->max_params ) + return Sql_P_BindSqlDataType(self->params+idx, buffer_type, buffer, buffer_len, NULL, NULL); + else + return SQL_SUCCESS;// out of range - ignore +} + + + +/// Executes the prepared statement. +int SqlStmt_Execute(SqlStmt* self) +{ + if( self == NULL ) + return SQL_ERROR; + + SqlStmt_FreeResult(self); + if( (self->bind_params && mysql_stmt_bind_param(self->stmt, self->params)) || + mysql_stmt_execute(self->stmt) ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + self->bind_columns = false; + if( mysql_stmt_store_result(self->stmt) )// store all the data + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE statement. +uint64 SqlStmt_LastInsertId(SqlStmt* self) +{ + if( self ) + return (uint64)mysql_stmt_insert_id(self->stmt); + else + return 0; +} + + + +/// Returns the number of columns in each row of the result. +size_t SqlStmt_NumColumns(SqlStmt* self) +{ + if( self ) + return (size_t)mysql_stmt_field_count(self->stmt); + else + return 0; +} + + + +/// Binds the result of a column to a buffer. +int SqlStmt_BindColumn(SqlStmt* self, size_t idx, enum SqlDataType buffer_type, void* buffer, size_t buffer_len, uint32* out_length, int8* out_is_null) +{ + if( self == NULL ) + return SQL_ERROR; + + if( buffer_type == SQLDT_STRING || buffer_type == SQLDT_ENUM ) + { + if( buffer_len < 1 ) + { + ShowDebug("SqlStmt_BindColumn: buffer_len(%d) is too small, no room for the nul-terminator\n", buffer_len); + return SQL_ERROR; + } + --buffer_len;// nul-terminator + } + if( !self->bind_columns ) + {// initialize the bindings + size_t i; + size_t cols; + + cols = SqlStmt_NumColumns(self); + if( self->max_columns < cols ) + { + self->max_columns = cols; + RECREATE(self->columns, MYSQL_BIND, cols); + RECREATE(self->column_lengths, s_column_length, cols); + } + memset(self->columns, 0, cols*sizeof(MYSQL_BIND)); + memset(self->column_lengths, 0, cols*sizeof(s_column_length)); + for( i = 0; i < cols; ++i ) + self->columns[i].buffer_type = MYSQL_TYPE_NULL; + self->bind_columns = true; + } + if( idx < self->max_columns ) + { + self->column_lengths[idx].out_length = out_length; + return Sql_P_BindSqlDataType(self->columns+idx, buffer_type, buffer, buffer_len, &self->column_lengths[idx].length, out_is_null); + } + else + { + return SQL_SUCCESS;// out of range - ignore + } +} + + + +/// Returns the number of rows in the result. +uint64 SqlStmt_NumRows(SqlStmt* self) +{ + if( self ) + return (uint64)mysql_stmt_num_rows(self->stmt); + else + return 0; +} + + + +/// Fetches the next row. +int SqlStmt_NextRow(SqlStmt* self) +{ + int err; + size_t i; + size_t cols; + MYSQL_BIND* column; + unsigned long length; + + if( self == NULL ) + return SQL_ERROR; + + // bind columns + if( self->bind_columns && mysql_stmt_bind_result(self->stmt, self->columns) ) + err = 1;// error binding columns + else + err = mysql_stmt_fetch(self->stmt);// fetch row + + // check for errors + if( err == MYSQL_NO_DATA ) + return SQL_NO_DATA; +#if defined(MYSQL_DATA_TRUNCATED) + // MySQL 5.0/5.1 defines and returns MYSQL_DATA_TRUNCATED [FlavioJS] + if( err == MYSQL_DATA_TRUNCATED ) + { + my_bool truncated; + + if( !self->bind_columns ) + { + ShowSQL("DB error - data truncated (unknown source, columns are not bound)\n"); + return SQL_ERROR; + } + + // find truncated column + cols = SqlStmt_NumColumns(self); + for( i = 0; i < cols; ++i ) + { + column = &self->columns[i]; + column->error = &truncated; + mysql_stmt_fetch_column(self->stmt, column, (unsigned int)i, 0); + column->error = NULL; + if( truncated ) + {// report truncated column + SqlStmt_P_ShowDebugTruncatedColumn(self, i); + return SQL_ERROR; + } + } + ShowSQL("DB error - data truncated (unknown source)\n"); + return SQL_ERROR; + } +#endif + if( err ) + { + ShowSQL("DB error - %s\n", mysql_stmt_error(self->stmt)); + return SQL_ERROR; + } + + // propagate column lengths and clear unused parts of string/enum/blob buffers + cols = SqlStmt_NumColumns(self); + for( i = 0; i < cols; ++i ) + { + length = self->column_lengths[i].length; + column = &self->columns[i]; +#if !defined(MYSQL_DATA_TRUNCATED) + // MySQL 4.1/(below?) returns success even if data is truncated, so we test truncation manually [FlavioJS] + if( column->buffer_length < length ) + {// report truncated column + if( column->buffer_type == MYSQL_TYPE_STRING || column->buffer_type == MYSQL_TYPE_BLOB ) + {// string/enum/blob column + SqlStmt_P_ShowDebugTruncatedColumn(self, i); + return SQL_ERROR; + } + // FIXME numeric types and null [FlavioJS] + } +#endif + if( self->column_lengths[i].out_length ) + *self->column_lengths[i].out_length = (uint32)length; + if( column->buffer_type == MYSQL_TYPE_STRING ) + {// clear unused part of the string/enum buffer (and nul-terminate) + memset((char*)column->buffer + length, 0, column->buffer_length - length + 1); + } + else if( column->buffer_type == MYSQL_TYPE_BLOB && length < column->buffer_length ) + {// clear unused part of the blob buffer + memset((char*)column->buffer + length, 0, column->buffer_length - length); + } + } + + return SQL_SUCCESS; +} + + + +/// Frees the result of the statement execution. +void SqlStmt_FreeResult(SqlStmt* self) +{ + if( self ) + mysql_stmt_free_result(self->stmt); +} + + + +/// Shows debug information (with statement). +void SqlStmt_ShowDebug_(SqlStmt* self, const char* debug_file, const unsigned long debug_line) +{ + if( self == NULL ) + ShowDebug("at %s:%lu - self is NULL\n", debug_file, debug_line); + else if( StringBuf_Length(&self->buf) > 0 ) + ShowDebug("at %s:%lu - %s\n", debug_file, debug_line, StringBuf_Value(&self->buf)); + else + ShowDebug("at %s:%lu\n", debug_file, debug_line); +} + + + +/// Frees a SqlStmt returned by SqlStmt_Malloc. +void SqlStmt_Free(SqlStmt* self) +{ + if( self ) + { + SqlStmt_FreeResult(self); + StringBuf_Destroy(&self->buf); + mysql_stmt_close(self->stmt); + if( self->params ) + aFree(self->params); + if( self->columns ) + { + aFree(self->columns); + aFree(self->column_lengths); + } + aFree(self); + } +} diff --git a/src/common/sql.h b/src/common/sql.h new file mode 100644 index 000000000..898e2c778 --- /dev/null +++ b/src/common/sql.h @@ -0,0 +1,344 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _COMMON_SQL_H_ +#define _COMMON_SQL_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h>// va_list + + + +// Return codes +#define SQL_ERROR -1 +#define SQL_SUCCESS 0 +#define SQL_NO_DATA 100 + +// macro definition to determine whether the mySQL engine is running on InnoDB (rather than MyISAM) +// uncomment this line if the your mySQL tables have been changed to run on InnoDB +// this macro will adjust how logs are recorded in the database to accommodate the change +//#define SQL_INNODB + +/// Data type identifier. +/// String, enum and blob data types need the buffer length specified. +enum SqlDataType +{ + SQLDT_NULL, + // fixed size + SQLDT_INT8, + SQLDT_INT16, + SQLDT_INT32, + SQLDT_INT64, + SQLDT_UINT8, + SQLDT_UINT16, + SQLDT_UINT32, + SQLDT_UINT64, + // platform dependent size + SQLDT_CHAR, + SQLDT_SHORT, + SQLDT_INT, + SQLDT_LONG, + SQLDT_LONGLONG, + SQLDT_UCHAR, + SQLDT_USHORT, + SQLDT_UINT, + SQLDT_ULONG, + SQLDT_ULONGLONG, + // floating point + SQLDT_FLOAT, + SQLDT_DOUBLE, + // other + SQLDT_STRING, + SQLDT_ENUM, + // Note: An ENUM is a string with restricted values. When an invalid value + // is inserted, it is saved as an empty string (numerical value 0). + SQLDT_BLOB, + SQLDT_LASTID +}; + +struct Sql;// Sql handle (private access) +struct SqlStmt;// Sql statement (private access) + +typedef enum SqlDataType SqlDataType; +typedef struct Sql Sql; +typedef struct SqlStmt SqlStmt; + + +/// Allocates and initializes a new Sql handle. +struct Sql* Sql_Malloc(void); + + + +/// Establishes a connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Connect(Sql* self, const char* user, const char* passwd, const char* host, uint16 port, const char* db); + + + + +/// Retrieves the timeout of the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetTimeout(Sql* self, uint32* out_timeout); + + + + +/// Retrieves the name of the columns of a table into out_buf, with the separator after each name. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetColumnNames(Sql* self, const char* table, char* out_buf, size_t buf_len, char sep); + + + + +/// Changes the encoding of the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_SetEncoding(Sql* self, const char* encoding); + + + +/// Pings the connection. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Ping(Sql* self); + + + +/// Escapes a string. +/// The output buffer must be at least strlen(from)*2+1 in size. +/// +/// @return The size of the escaped string +size_t Sql_EscapeString(Sql* self, char* out_to, const char* from); + + + +/// Escapes a string. +/// The output buffer must be at least from_len*2+1 in size. +/// +/// @return The size of the escaped string +size_t Sql_EscapeStringLen(Sql* self, char* out_to, const char* from, size_t from_len); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is constructed as if it was sprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_Query(Sql* self, const char* query, ...); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is constructed as if it was svprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_QueryV(Sql* self, const char* query, va_list args); + + + +/// Executes a query. +/// Any previous result is freed. +/// The query is used directly. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_QueryStr(Sql* self, const char* query); + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE query. +/// +/// @return Value of the auto-increment column +uint64 Sql_LastInsertId(Sql* self); + + + +/// Returns the number of columns in each row of the result. +/// +/// @return Number of columns +uint32 Sql_NumColumns(Sql* self); + + + +/// Returns the number of rows in the result. +/// +/// @return Number of rows +uint64 Sql_NumRows(Sql* self); + + + +/// Fetches the next row. +/// The data of the previous row is no longer valid. +/// +/// @return SQL_SUCCESS, SQL_ERROR or SQL_NO_DATA +int Sql_NextRow(Sql* self); + + + +/// Gets the data of a column. +/// The data remains valid until the next row is fetched or the result is freed. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int Sql_GetData(Sql* self, size_t col, char** out_buf, size_t* out_len); + + + +/// Frees the result of the query. +void Sql_FreeResult(Sql* self); + + + +#if defined(SQL_REMOVE_SHOWDEBUG) +#define Sql_ShowDebug(self) (void)0 +#else +#define Sql_ShowDebug(self) Sql_ShowDebug_(self, __FILE__, __LINE__) +#endif +/// Shows debug information (last query). +void Sql_ShowDebug_(Sql* self, const char* debug_file, const unsigned long debug_line); + + + +/// Frees a Sql handle returned by Sql_Malloc. +void Sql_Free(Sql* self); + + + +/////////////////////////////////////////////////////////////////////////////// +// Prepared Statements +/////////////////////////////////////////////////////////////////////////////// +// Parameters are placed in the statement by embedding question mark ('?') +// characters into the query at the appropriate positions. +// The markers are legal only in places where they represent data. +// The markers cannot be inside quotes. Quotes will be added automatically +// when they are required. +// +// example queries with parameters: +// 1) SELECT col FROM table WHERE id=? +// 2) INSERT INTO table(col1,col2) VALUES(?,?) + + + +/// Allocates and initializes a new SqlStmt handle. +/// It uses the connection of the parent Sql handle. +/// Queries in Sql and SqlStmt are independent and don't affect each other. +/// +/// @return SqlStmt handle or NULL if an error occured +struct SqlStmt* SqlStmt_Malloc(Sql* sql); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is constructed as if it was sprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_Prepare(SqlStmt* self, const char* query, ...); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is constructed as if it was svprintf. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_PrepareV(SqlStmt* self, const char* query, va_list args); + + + +/// Prepares the statement. +/// Any previous result is freed and all parameter bindings are removed. +/// The query is used directly. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_PrepareStr(SqlStmt* self, const char* query); + + + +/// Returns the number of parameters in the prepared statement. +/// +/// @return Number or paramenters +size_t SqlStmt_NumParams(SqlStmt* self); + + + +/// Binds a parameter to a buffer. +/// The buffer data will be used when the statement is executed. +/// All parameters should have bindings. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_BindParam(SqlStmt* self, size_t idx, SqlDataType buffer_type, void* buffer, size_t buffer_len); + + + +/// Executes the prepared statement. +/// Any previous result is freed and all column bindings are removed. +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_Execute(SqlStmt* self); + + + +/// Returns the number of the AUTO_INCREMENT column of the last INSERT/UPDATE statement. +/// +/// @return Value of the auto-increment column +uint64 SqlStmt_LastInsertId(SqlStmt* self); + + + +/// Returns the number of columns in each row of the result. +/// +/// @return Number of columns +size_t SqlStmt_NumColumns(SqlStmt* self); + + + +/// Binds the result of a column to a buffer. +/// The buffer will be filled with data when the next row is fetched. +/// For string/enum buffer types there has to be enough space for the data +/// and the nul-terminator (an extra byte). +/// +/// @return SQL_SUCCESS or SQL_ERROR +int SqlStmt_BindColumn(SqlStmt* self, size_t idx, SqlDataType buffer_type, void* buffer, size_t buffer_len, uint32* out_length, int8* out_is_null); + + + +/// Returns the number of rows in the result. +/// +/// @return Number of rows +uint64 SqlStmt_NumRows(SqlStmt* self); + + + +/// Fetches the next row. +/// All column bindings will be filled with data. +/// +/// @return SQL_SUCCESS, SQL_ERROR or SQL_NO_DATA +int SqlStmt_NextRow(SqlStmt* self); + + + +/// Frees the result of the statement execution. +void SqlStmt_FreeResult(SqlStmt* self); + + + +#if defined(SQL_REMOVE_SHOWDEBUG) +#define SqlStmt_ShowDebug(self) (void)0 +#else +#define SqlStmt_ShowDebug(self) SqlStmt_ShowDebug_(self, __FILE__, __LINE__) +#endif +/// Shows debug information (with statement). +void SqlStmt_ShowDebug_(SqlStmt* self, const char* debug_file, const unsigned long debug_line); + + + +/// Frees a SqlStmt returned by SqlStmt_Malloc. +void SqlStmt_Free(SqlStmt* self); + + + +#endif /* _COMMON_SQL_H_ */ diff --git a/src/common/strlib.c b/src/common/strlib.c new file mode 100644 index 000000000..dfacbf136 --- /dev/null +++ b/src/common/strlib.c @@ -0,0 +1,1167 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "strlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + + +#define J_MAX_MALLOC_SIZE 65535 + +// escapes a string in-place (' -> \' , \ -> \\ , % -> _) +char* jstrescape (char* pt) +{ + //copy from here + char *ptr; + int i = 0, j = 0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy(ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + aFree(ptr); + return pt; +} + +// escapes a string into a provided buffer +char* jstrescapecpy (char* pt, const char* spt) +{ + //copy from here + //WARNING: Target string pt should be able to hold strlen(spt)*2, as each time + //a escape character is found, the target's final length increases! [Skotlex] + int i =0, j=0; + + if (!spt) { //Return an empty string [Skotlex] + pt[0] = '\0'; + return &pt[0]; + } + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return &pt[0]; +} + +// escapes exactly 'size' bytes of a string into a provided buffer +int jmemescapecpy (char* pt, const char* spt, int size) +{ + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} + +// Function to suppress control characters in a string. +int remove_control_chars(char* str) +{ + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (ISCNTRL(str[i])) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +// Removes characters identified by ISSPACE from the start and end of the string +// NOTE: make sure the string is not const!! +char* trim(char* str) +{ + size_t start; + size_t end; + + if( str == NULL ) + return str; + + // get start position + for( start = 0; str[start] && ISSPACE(str[start]); ++start ) + ; + // get end position + for( end = strlen(str); start < end && str[end-1] && ISSPACE(str[end-1]); --end ) + ; + // trim + if( start == end ) + *str = '\0';// empty string + else + {// move string with nul terminator + str[end] = '\0'; + memmove(str,str+start,end-start+1); + } + return str; +} + +// Converts one or more consecutive occurences of the delimiters into a single space +// and removes such occurences from the beginning and end of string +// NOTE: make sure the string is not const!! +char* normalize_name(char* str,const char* delims) +{ + char* in = str; + char* out = str; + int put_space = 0; + + if( str == NULL || delims == NULL ) + return str; + + // trim start of string + while( *in && strchr(delims,*in) ) + ++in; + while( *in ) + { + if( put_space ) + {// replace trim characters with a single space + *out = ' '; + ++out; + } + // copy non trim characters + while( *in && !strchr(delims,*in) ) + { + *out = *in; + ++out; + ++in; + } + // skip trim characters + while( *in && strchr(delims,*in) ) + ++in; + put_space = 1; + } + *out = '\0'; + return str; +} + +//stristr: Case insensitive version of strstr, code taken from +//http://www.daniweb.com/code/snippet313.html, Dave Sinkula +// +const char* stristr(const char* haystack, const char* needle) +{ + if ( !*needle ) + { + return haystack; + } + for ( ; *haystack; ++haystack ) + { + if ( TOUPPER(*haystack) == TOUPPER(*needle) ) + { + // matched starting char -- loop through remaining chars + const char *h, *n; + for ( h = haystack, n = needle; *h && *n; ++h, ++n ) + { + if ( TOUPPER(*h) != TOUPPER(*n) ) + { + break; + } + } + if ( !*n ) // matched all of 'needle' to null termination + { + return haystack; // return the start of the match + } + } + } + return 0; +} + +#ifdef __WIN32 +char* _strtok_r(char *s1, const char *s2, char **lasts) +{ + char *ret; + + if (s1 == NULL) + s1 = *lasts; + while(*s1 && strchr(s2, *s1)) + ++s1; + if(*s1 == '\0') + return NULL; + ret = s1; + while(*s1 && !strchr(s2, *s1)) + ++s1; + if(*s1) + *s1++ = '\0'; + *lasts = s1; + return ret; +} +#endif + +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) +/* Find the length of STRING, but scan at most MAXLEN characters. + If no '\0' terminator is found in that many characters, return MAXLEN. */ +size_t strnlen (const char* string, size_t maxlen) +{ + const char* end = (const char*)memchr(string, '\0', maxlen); + return end ? (size_t) (end - string) : maxlen; +} +#endif + +#if defined(WIN32) && defined(_MSC_VER) && _MSC_VER <= 1200 +uint64 strtoull(const char* str, char** endptr, int base) +{ + uint64 result; + int count; + int n; + + if( base == 0 ) + { + if( str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + base = 16; + else + if( str[0] == '0' ) + base = 8; + else + base = 10; + } + + if( base == 8 ) + count = sscanf(str, "%I64o%n", &result, &n); + else + if( base == 10 ) + count = sscanf(str, "%I64u%n", &result, &n); + else + if( base == 16 ) + count = sscanf(str, "%I64x%n", &result, &n); + else + count = 0; // fail + + if( count < 1 ) + { + errno = EINVAL; + result = 0; + n = 0; + } + + if( endptr ) + *endptr = (char*)str + n; + + return result; +} +#endif + +//---------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//---------------------------------------------------- +int e_mail_check(char* email) +{ + char ch; + char* last_arobas; + size_t len = strlen(email); + + // athena limits + if (len < 3 || len > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[len-1] == '@') + return 0; + + if (email[len-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) + if (strchr(last_arobas, ch) != NULL) + return 0; + + if (strchr(last_arobas, ' ') != NULL || strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//-------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, français, deutsch, español +//-------------------------------------------------- +int config_switch(const char* str) +{ + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return (int)strtol(str, NULL, 0); +} + +/// strncpy that always nul-terminates the string +char* safestrncpy(char* dst, const char* src, size_t n) +{ + if( n > 0 ) + { + char* d = dst; + const char* s = src; + d[--n] = '\0';/* nul-terminate string */ + for( ; n > 0; --n ) + { + if( (*d++ = *s++) == '\0' ) + {/* nul-pad remaining bytes */ + while( --n > 0 ) + *d++ = '\0'; + break; + } + } + } + return dst; +} + +/// doesn't crash on null pointer +size_t safestrnlen(const char* string, size_t maxlen) +{ + return ( string != NULL ) ? strnlen(string, maxlen) : 0; +} + +/// Works like snprintf, but always nul-terminates the buffer. +/// Returns the size of the string (without nul-terminator) +/// or -1 if the buffer is too small. +/// +/// @param buf Target buffer +/// @param sz Size of the buffer (including nul-terminator) +/// @param fmt Format string +/// @param ... Format arguments +/// @return The size of the string or -1 if the buffer is too small +int safesnprintf(char* buf, size_t sz, const char* fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap,fmt); + ret = vsnprintf(buf, sz, fmt, ap); + va_end(ap); + if( ret < 0 || (size_t)ret >= sz ) + {// overflow + buf[sz-1] = '\0';// always nul-terminate + return -1; + } + return ret; +} + +/// Returns the line of the target position in the string. +/// Lines start at 1. +int strline(const char* str, size_t pos) +{ + const char* target; + int line; + + if( str == NULL || pos == 0 ) + return 1; + + target = str+pos; + for( line = 1; ; ++line ) + { + str = strchr(str, '\n'); + if( str == NULL || target <= str ) + break;// found target line + ++str;// skip newline + } + return line; +} + +/// Produces the hexadecimal representation of the given input. +/// The output buffer must be at least count*2+1 in size. +/// Returns true on success, false on failure. +/// +/// @param output Output string +/// @param input Binary input buffer +/// @param count Number of bytes to convert +bool bin2hex(char* output, unsigned char* input, size_t count) +{ + char toHex[] = "0123456789abcdef"; + size_t i; + + for( i = 0; i < count; ++i ) + { + *output++ = toHex[(*input & 0xF0) >> 4]; + *output++ = toHex[(*input & 0x0F) >> 0]; + ++input; + } + *output = '\0'; + return true; +} + + + +///////////////////////////////////////////////////////////////////// +/// Parses a single field in a delim-separated string. +/// The delimiter after the field is skipped. +/// +/// @param sv Parse state +/// @return 1 if a field was parsed, 0 if already done, -1 on error. +int sv_parse_next(struct s_svstate* sv) +{ + enum { + START_OF_FIELD, + PARSING_FIELD, + PARSING_C_ESCAPE, + END_OF_FIELD, + TERMINATE, + END + } state; + const char* str; + int len; + enum e_svopt opt; + char delim; + int i; + + if( sv == NULL ) + return -1;// error + + str = sv->str; + len = sv->len; + opt = sv->opt; + delim = sv->delim; + + // check opt + if( delim == '\n' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_LF)) ) + { + ShowError("sv_parse_next: delimiter '\\n' is not compatible with options SV_TERMINATE_LF or SV_TERMINATE_CRLF.\n"); + return -1;// error + } + if( delim == '\r' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_CR)) ) + { + ShowError("sv_parse_next: delimiter '\\r' is not compatible with options SV_TERMINATE_CR or SV_TERMINATE_CRLF.\n"); + return -1;// error + } + + if( sv->done || str == NULL ) + { + sv->done = true; + return 0;// nothing to parse + } + +#define IS_END() ( i >= len ) +#define IS_DELIM() ( str[i] == delim ) +#define IS_TERMINATOR() ( \ + ((opt&SV_TERMINATE_LF) && str[i] == '\n') || \ + ((opt&SV_TERMINATE_CR) && str[i] == '\r') || \ + ((opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n') ) +#define IS_C_ESCAPE() ( (opt&SV_ESCAPE_C) && str[i] == '\\' ) +#define SET_FIELD_START() sv->start = i +#define SET_FIELD_END() sv->end = i + + i = sv->off; + state = START_OF_FIELD; + while( state != END ) + { + switch( state ) + { + case START_OF_FIELD:// record start of field and start parsing it + SET_FIELD_START(); + state = PARSING_FIELD; + break; + + case PARSING_FIELD:// skip field character + if( IS_END() || IS_DELIM() || IS_TERMINATOR() ) + state = END_OF_FIELD; + else if( IS_C_ESCAPE() ) + state = PARSING_C_ESCAPE; + else + ++i;// normal character + break; + + case PARSING_C_ESCAPE:// skip escape sequence (validates it too) + { + ++i;// '\\' + if( IS_END() ) + { + ShowError("sv_parse_next: empty escape sequence\n"); + return -1; + } + if( str[i] == 'x' ) + {// hex escape + ++i;// 'x' + if( IS_END() || !ISXDIGIT(str[i]) ) + { + ShowError("sv_parse_next: \\x with no following hex digits\n"); + return -1; + } + do{ + ++i;// hex digit + }while( !IS_END() && ISXDIGIT(str[i])); + } + else if( str[i] == '0' || str[i] == '1' || str[i] == '2' ) + {// octal escape + ++i;// octal digit + if( !IS_END() && str[i] >= '0' && str[i] <= '7' ) + ++i;// octal digit + if( !IS_END() && str[i] >= '0' && str[i] <= '7' ) + ++i;// octal digit + } + else if( strchr(SV_ESCAPE_C_SUPPORTED, str[i]) ) + {// supported escape character + ++i; + } + else + { + ShowError("sv_parse_next: unknown escape sequence \\%c\n", str[i]); + return -1; + } + state = PARSING_FIELD; + break; + } + + case END_OF_FIELD:// record end of field and stop + SET_FIELD_END(); + state = END; + if( IS_END() ) + ;// nothing else + else if( IS_DELIM() ) + ++i;// delim + else if( IS_TERMINATOR() ) + state = TERMINATE; + break; + + case TERMINATE: +#if 0 + // skip line terminator + if( (opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n' ) + i += 2;// CRLF + else + ++i;// CR or LF +#endif + sv->done = true; + state = END; + break; + } + } + if( IS_END() ) + sv->done = true; + sv->off = i; + +#undef IS_END +#undef IS_DELIM +#undef IS_TERMINATOR +#undef IS_C_ESCAPE +#undef SET_FIELD_START +#undef SET_FIELD_END + + return 1; +} + + +/// Parses a delim-separated string. +/// Starts parsing at startoff and fills the pos array with position pairs. +/// out_pos[0] and out_pos[1] are the start and end of line. +/// Other position pairs are the start and end of fields. +/// Returns the number of fields found or -1 if an error occurs. +/// +/// out_pos can be NULL. +/// If a line terminator is found, the end position is placed there. +/// out_pos[2] and out_pos[3] for the first field, out_pos[4] and out_pos[5] +/// for the seconds field and so on. +/// Unfilled positions are set to -1. +/// +/// @param str String to parse +/// @param len Length of the string +/// @param startoff Where to start parsing +/// @param delim Field delimiter +/// @param out_pos Array of resulting positions +/// @param npos Size of the pos array +/// @param opt Options that determine the parsing behaviour +/// @return Number of fields found in the string or -1 if an error occured +int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt) +{ + struct s_svstate sv; + int count; + + // initialize + if( out_pos == NULL ) npos = 0; + for( count = 0; count < npos; ++count ) + out_pos[count] = -1; + sv.str = str; + sv.len = len; + sv.off = startoff; + sv.opt = opt; + sv.delim = delim; + sv.done = false; + + // parse + count = 0; + if( npos > 0 ) out_pos[0] = startoff; + while( !sv.done ) + { + ++count; + if( sv_parse_next(&sv) <= 0 ) + return -1;// error + if( npos > count*2 ) out_pos[count*2] = sv.start; + if( npos > count*2+1 ) out_pos[count*2+1] = sv.end; + } + if( npos > 1 ) out_pos[1] = sv.off; + return count; +} + +/// Splits a delim-separated string. +/// WARNING: this function modifies the input string +/// Starts splitting at startoff and fills the out_fields array. +/// out_fields[0] is the start of the next line. +/// Other entries are the start of fields (nul-teminated). +/// Returns the number of fields found or -1 if an error occurs. +/// +/// out_fields can be NULL. +/// Fields that don't fit in out_fields are not nul-terminated. +/// Extra entries in out_fields are filled with the end of the last field (empty string). +/// +/// @param str String to parse +/// @param len Length of the string +/// @param startoff Where to start parsing +/// @param delim Field delimiter +/// @param out_fields Array of resulting fields +/// @param nfields Size of the field array +/// @param opt Options that determine the parsing behaviour +/// @return Number of fields found in the string or -1 if an error occured +int sv_split(char* str, int len, int startoff, char delim, char** out_fields, int nfields, enum e_svopt opt) +{ + int pos[1024]; + int i; + int done; + char* end; + int ret = sv_parse(str, len, startoff, delim, pos, ARRAYLENGTH(pos), opt); + + if( ret == -1 || out_fields == NULL || nfields <= 0 ) + return ret; // nothing to do + + // next line + end = str + pos[1]; + if( end[0] == '\0' ) + { + *out_fields = end; + } + else if( (opt&SV_TERMINATE_LF) && end[0] == '\n' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = '\0'; + *out_fields = end + 1; + } + else if( (opt&SV_TERMINATE_CRLF) && end[0] == '\r' && end[1] == '\n' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = end[1] = '\0'; + *out_fields = end + 2; + } + else if( (opt&SV_TERMINATE_CR) && end[0] == '\r' ) + { + if( !(opt&SV_KEEP_TERMINATOR) ) + end[0] = '\0'; + *out_fields = end + 1; + } + else + { + ShowError("sv_split: unknown line delimiter 0x02%x.\n", (unsigned char)end[0]); + return -1;// error + } + ++out_fields; + --nfields; + + // fields + i = 2; + done = 0; + while( done < ret && nfields > 0 ) + { + if( i < ARRAYLENGTH(pos) ) + {// split field + *out_fields = str + pos[i]; + end = str + pos[i+1]; + *end = '\0'; + // next field + i += 2; + ++done; + ++out_fields; + --nfields; + } + else + {// get more fields + sv_parse(str, len, pos[i-1] + 1, delim, pos, ARRAYLENGTH(pos), opt); + i = 2; + } + } + // remaining fields + for( i = 0; i < nfields; ++i ) + out_fields[i] = end; + return ret; +} + +/// Escapes src to out_dest according to the format of the C compiler. +/// Returns the length of the escaped string. +/// out_dest should be len*4+1 in size. +/// +/// @param out_dest Destination buffer +/// @param src Source string +/// @param len Length of the source string +/// @param escapes Extra characters to be escaped +/// @return Length of the escaped string +size_t sv_escape_c(char* out_dest, const char* src, size_t len, const char* escapes) +{ + size_t i; + size_t j; + + if( out_dest == NULL ) + return 0;// nothing to do + if( src == NULL ) + {// nothing to escape + *out_dest = 0; + return 0; + } + if( escapes == NULL ) + escapes = ""; + + for( i = 0, j = 0; i < len; ++i ) + { + switch( src[i] ) + { + case '\0':// octal 0 + out_dest[j++] = '\\'; + out_dest[j++] = '0'; + out_dest[j++] = '0'; + out_dest[j++] = '0'; + break; + case '\r':// carriage return + out_dest[j++] = '\\'; + out_dest[j++] = 'r'; + break; + case '\n':// line feed + out_dest[j++] = '\\'; + out_dest[j++] = 'n'; + break; + case '\\':// escape character + out_dest[j++] = '\\'; + out_dest[j++] = '\\'; + break; + default: + if( strchr(escapes,src[i]) ) + {// escape + out_dest[j++] = '\\'; + switch( src[i] ) + { + case '\a': out_dest[j++] = 'a'; break; + case '\b': out_dest[j++] = 'b'; break; + case '\t': out_dest[j++] = 't'; break; + case '\v': out_dest[j++] = 'v'; break; + case '\f': out_dest[j++] = 'f'; break; + case '\?': out_dest[j++] = '?'; break; + default:// to octal + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0700)>>6)); + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0070)>>3)); + out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0007) )); + break; + } + } + else + out_dest[j++] = src[i]; + break; + } + } + out_dest[j] = 0; + return j; +} + +/// Unescapes src to out_dest according to the format of the C compiler. +/// Returns the length of the unescaped string. +/// out_dest should be len+1 in size and can be the same buffer as src. +/// +/// @param out_dest Destination buffer +/// @param src Source string +/// @param len Length of the source string +/// @return Length of the escaped string +size_t sv_unescape_c(char* out_dest, const char* src, size_t len) +{ + static unsigned char low2hex[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x0? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x1? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x2? + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,// 0x3? + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x4? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x5? + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x6? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xA? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xB? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xC? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xD? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xE? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF? + }; + size_t i; + size_t j; + + for( i = 0, j = 0; i < len; ) + { + if( src[i] == '\\' ) + { + ++i;// '\\' + if( i >= len ) + ShowWarning("sv_unescape_c: empty escape sequence\n"); + else if( src[i] == 'x' ) + {// hex escape sequence + unsigned char c = 0; + unsigned char inrange = 1; + + ++i;// 'x' + if( i >= len || !ISXDIGIT(src[i]) ) + { + ShowWarning("sv_unescape_c: \\x with no following hex digits\n"); + continue; + } + do{ + if( c > 0x0F && inrange ) + { + ShowWarning("sv_unescape_c: hex escape sequence out of range\n"); + inrange = 0; + } + c = (c<<4)|low2hex[(unsigned char)src[i]];// hex digit + ++i; + }while( i < len && ISXDIGIT(src[i]) ); + out_dest[j++] = (char)c; + } + else if( src[i] == '0' || src[i] == '1' || src[i] == '2' || src[i] == '3' ) + {// octal escape sequence (255=0377) + unsigned char c = src[i]-'0'; + ++i;// '0', '1', '2' or '3' + if( i < len && src[i] >= '0' && src[i] <= '7' ) + { + c = (c<<3)|(src[i]-'0'); + ++i;// octal digit + } + if( i < len && src[i] >= '0' && src[i] <= '7' ) + { + c = (c<<3)|(src[i]-'0'); + ++i;// octal digit + } + out_dest[j++] = (char)c; + } + else + {// other escape sequence + if( strchr(SV_ESCAPE_C_SUPPORTED, src[i]) == NULL ) + ShowWarning("sv_unescape_c: unknown escape sequence \\%c\n", src[i]); + switch( src[i] ) + { + case 'a': out_dest[j++] = '\a'; break; + case 'b': out_dest[j++] = '\b'; break; + case 't': out_dest[j++] = '\t'; break; + case 'n': out_dest[j++] = '\n'; break; + case 'v': out_dest[j++] = '\v'; break; + case 'f': out_dest[j++] = '\f'; break; + case 'r': out_dest[j++] = '\r'; break; + case '?': out_dest[j++] = '\?'; break; + default: out_dest[j++] = src[i]; break; + } + ++i;// escaped character + } + } + else + out_dest[j++] = src[i++];// normal character + } + out_dest[j] = 0; + return j; +} + +/// Skips a C escape sequence (starting with '\\'). +const char* skip_escaped_c(const char* p) +{ + if( p && *p == '\\' ) + { + ++p; + switch( *p ) + { + case 'x':// hexadecimal + ++p; + while( ISXDIGIT(*p) ) + ++p; + break; + case '0': + case '1': + case '2': + case '3':// octal + ++p; + if( *p >= '0' && *p <= '7' ) + ++p; + if( *p >= '0' && *p <= '7' ) + ++p; + break; + default: + if( *p && strchr(SV_ESCAPE_C_SUPPORTED, *p) ) + ++p; + } + } + return p; +} + + +/// Opens and parses a file containing delim-separated columns, feeding them to the specified callback function row by row. +/// Tracks the progress of the operation (current line number, number of successfully processed rows). +/// Returns 'true' if it was able to process the specified file, or 'false' if it could not be read. +/// +/// @param directory Directory +/// @param filename File to process +/// @param delim Field delimiter +/// @param mincols Minimum number of columns of a valid row +/// @param maxcols Maximum number of columns of a valid row +/// @param parseproc User-supplied row processing function +/// @return true on success, false if file could not be opened +bool sv_readdb(const char* directory, const char* filename, char delim, int mincols, int maxcols, int maxrows, bool (*parseproc)(char* fields[], int columns, int current)) +{ + FILE* fp; + int lines = 0; + int entries = 0; + char** fields; // buffer for fields ([0] is reserved) + int columns, fields_length; + char path[1024], line[1024]; + char* match; + + snprintf(path, sizeof(path), "%s/%s", directory, filename); + + // open file + fp = fopen(path, "r"); + if( fp == NULL ) + { + ShowError("sv_readdb: can't read %s\n", path); + return false; + } + + // allocate enough memory for the maximum requested amount of columns plus the reserved one + fields_length = maxcols+1; + fields = (char**)aMalloc(fields_length*sizeof(char*)); + + // process rows one by one + while( fgets(line, sizeof(line), fp) ) + { + lines++; + + if( ( match = strstr(line, "//") ) != NULL ) + {// strip comments + match[0] = 0; + } + + //TODO: strip trailing whitespace + if( line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + + columns = sv_split(line, strlen(line), 0, delim, fields, fields_length, (e_svopt)(SV_TERMINATE_LF|SV_TERMINATE_CRLF)); + + if( columns < mincols ) + { + ShowError("sv_readdb: Insufficient columns in line %d of \"%s\" (found %d, need at least %d).\n", lines, path, columns, mincols); + continue; // not enough columns + } + if( columns > maxcols ) + { + ShowError("sv_readdb: Too many columns in line %d of \"%s\" (found %d, maximum is %d).\n", lines, path, columns, maxcols ); + continue; // too many columns + } + if( entries == maxrows ) + { + ShowError("sv_readdb: Reached the maximum allowed number of entries (%d) when parsing file \"%s\".\n", maxrows, path); + break; + } + + // parse this row + if( !parseproc(fields+1, columns, entries) ) + { + ShowError("sv_readdb: Could not process contents of line %d of \"%s\".\n", lines, path); + continue; // invalid row contents + } + + // success! + entries++; + } + + aFree(fields); + fclose(fp); + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, path); + + return true; +} + + +///////////////////////////////////////////////////////////////////// +// StringBuf - dynamic string +// +// @author MouseJstr (original) + +/// Allocates a StringBuf +StringBuf* StringBuf_Malloc() +{ + StringBuf* self; + CREATE(self, StringBuf, 1); + StringBuf_Init(self); + return self; +} + +/// Initializes a previously allocated StringBuf +void StringBuf_Init(StringBuf* self) +{ + self->max_ = 1024; + self->ptr_ = self->buf_ = (char*)aMalloc(self->max_ + 1); +} + +/// Appends the result of printf to the StringBuf +int StringBuf_Printf(StringBuf* self, const char* fmt, ...) +{ + int len; + va_list ap; + + va_start(ap, fmt); + len = StringBuf_Vprintf(self, fmt, ap); + va_end(ap); + + return len; +} + +/// Appends the result of vprintf to the StringBuf +int StringBuf_Vprintf(StringBuf* self, const char* fmt, va_list ap) +{ + int n, size, off; + + for(;;) + { + va_list apcopy; + /* Try to print in the allocated space. */ + size = self->max_ - (self->ptr_ - self->buf_); + va_copy(apcopy, ap); + n = vsnprintf(self->ptr_, size, fmt, apcopy); + va_end(apcopy); + /* If that worked, return the length. */ + if( n > -1 && n < size ) + { + self->ptr_ += n; + return (int)(self->ptr_ - self->buf_); + } + /* Else try again with more space. */ + self->max_ *= 2; // twice the old size + off = (int)(self->ptr_ - self->buf_); + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } +} + +/// Appends the contents of another StringBuf to the StringBuf +int StringBuf_Append(StringBuf* self, const StringBuf* sbuf) +{ + int available = self->max_ - (self->ptr_ - self->buf_); + int needed = (int)(sbuf->ptr_ - sbuf->buf_); + + if( needed >= available ) + { + int off = (int)(self->ptr_ - self->buf_); + self->max_ += needed; + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } + + memcpy(self->ptr_, sbuf->buf_, needed); + self->ptr_ += needed; + return (int)(self->ptr_ - self->buf_); +} + +// Appends str to the StringBuf +int StringBuf_AppendStr(StringBuf* self, const char* str) +{ + int available = self->max_ - (self->ptr_ - self->buf_); + int needed = (int)strlen(str); + + if( needed >= available ) + {// not enough space, expand the buffer (minimum expansion = 1024) + int off = (int)(self->ptr_ - self->buf_); + self->max_ += max(needed, 1024); + self->buf_ = (char*)aRealloc(self->buf_, self->max_ + 1); + self->ptr_ = self->buf_ + off; + } + + memcpy(self->ptr_, str, needed); + self->ptr_ += needed; + return (int)(self->ptr_ - self->buf_); +} + +// Returns the length of the data in the Stringbuf +int StringBuf_Length(StringBuf* self) +{ + return (int)(self->ptr_ - self->buf_); +} + +/// Returns the data in the StringBuf +char* StringBuf_Value(StringBuf* self) +{ + *self->ptr_ = '\0'; + return self->buf_; +} + +/// Clears the contents of the StringBuf +void StringBuf_Clear(StringBuf* self) +{ + self->ptr_ = self->buf_; +} + +/// Destroys the StringBuf +void StringBuf_Destroy(StringBuf* self) +{ + aFree(self->buf_); + self->ptr_ = self->buf_ = 0; + self->max_ = 0; +} + +// Frees a StringBuf returned by StringBuf_Malloc +void StringBuf_Free(StringBuf* self) +{ + StringBuf_Destroy(self); + aFree(self); +} diff --git a/src/common/strlib.h b/src/common/strlib.h new file mode 100644 index 000000000..bbc2c6105 --- /dev/null +++ b/src/common/strlib.h @@ -0,0 +1,155 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _STRLIB_H_ +#define _STRLIB_H_ + +#include "../common/cbasetypes.h" +#include <stdarg.h> + +#define __USE_GNU // required to enable strnlen on some platforms +#include <string.h> +#undef __USE_GNU + +char* jstrescape (char* pt); +char* jstrescapecpy (char* pt, const char* spt); +int jmemescapecpy (char* pt, const char* spt, int size); + +int remove_control_chars(char* str); +char* trim(char* str); +char* normalize_name(char* str,const char* delims); +const char *stristr(const char *haystack, const char *needle); + +#ifdef WIN32 +#define HAVE_STRTOK_R +#define strtok_r(s,delim,save_ptr) _strtok_r((s),(delim),(save_ptr)) +char* _strtok_r(char* s1, const char* s2, char** lasts); +#endif + +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) +size_t strnlen (const char* string, size_t maxlen); +#endif + +#if defined(WIN32) && defined(_MSC_VER) && _MSC_VER <= 1200 +uint64 strtoull(const char* str, char** endptr, int base); +#endif + +int e_mail_check(char* email); +int config_switch(const char* str); + +/// strncpy that always nul-terminates the string +char* safestrncpy(char* dst, const char* src, size_t n); + +/// doesn't crash on null pointer +size_t safestrnlen(const char* string, size_t maxlen); + +/// Works like snprintf, but always nul-terminates the buffer. +/// Returns the size of the string (without nul-terminator) +/// or -1 if the buffer is too small. +int safesnprintf(char* buf, size_t sz, const char* fmt, ...); + +/// Returns the line of the target position in the string. +/// Lines start at 1. +int strline(const char* str, size_t pos); + +/// Produces the hexadecimal representation of the given input. +/// The output buffer must be at least count*2+1 in size. +/// Returns true on success, false on failure. +bool bin2hex(char* output, unsigned char* input, size_t count); + + +/// Bitfield determining the behaviour of sv_parse and sv_split. +typedef enum e_svopt +{ + // default: no escapes and no line terminator + SV_NOESCAPE_NOTERMINATE = 0, + // Escapes according to the C compiler. + SV_ESCAPE_C = 1, + // Line terminators + SV_TERMINATE_LF = 2, + SV_TERMINATE_CRLF = 4, + SV_TERMINATE_CR = 8, + // If sv_split keeps the end of line terminator, instead of replacing with '\0' + SV_KEEP_TERMINATOR = 16 +} e_svopt; + +/// Other escape sequences supported by the C compiler. +#define SV_ESCAPE_C_SUPPORTED "abtnvfr\?\"'\\" + +/// Parse state. +/// The field is [start,end[ +struct s_svstate +{ + const char* str; //< string to parse + int len; //< string length + int off; //< current offset in the string + int start; //< where the field starts + int end; //< where the field ends + enum e_svopt opt; //< parse options + char delim; //< field delimiter + bool done; //< if all the text has been parsed +}; + +/// Parses a single field in a delim-separated string. +/// The delimiter after the field is skipped. +/// +/// @param sv Parse state +/// @return 1 if a field was parsed, 0 if done, -1 on error. +int sv_parse_next(struct s_svstate* sv); + +/// Parses a delim-separated string. +/// Starts parsing at startoff and fills the pos array with position pairs. +/// out_pos[0] and out_pos[1] are the start and end of line. +/// Other position pairs are the start and end of fields. +/// Returns the number of fields found or -1 if an error occurs. +int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt); + +/// Splits a delim-separated string. +/// WARNING: this function modifies the input string +/// Starts splitting at startoff and fills the out_fields array. +/// out_fields[0] is the start of the next line. +/// Other entries are the start of fields (nul-teminated). +/// Returns the number of fields found or -1 if an error occurs. +int sv_split(char* str, int len, int startoff, char delim, char** out_fields, int nfields, enum e_svopt opt); + +/// Escapes src to out_dest according to the format of the C compiler. +/// Returns the length of the escaped string. +/// out_dest should be len*4+1 in size. +size_t sv_escape_c(char* out_dest, const char* src, size_t len, const char* escapes); + +/// Unescapes src to out_dest according to the format of the C compiler. +/// Returns the length of the unescaped string. +/// out_dest should be len+1 in size and can be the same buffer as src. +size_t sv_unescape_c(char* out_dest, const char* src, size_t len); + +/// Skips a C escape sequence (starting with '\\'). +const char* skip_escaped_c(const char* p); + +/// Opens and parses a file containing delim-separated columns, feeding them to the specified callback function row by row. +/// Tracks the progress of the operation (current line number, number of successfully processed rows). +/// Returns 'true' if it was able to process the specified file, or 'false' if it could not be read. +bool sv_readdb(const char* directory, const char* filename, char delim, int mincols, int maxcols, int maxrows, bool (*parseproc)(char* fields[], int columns, int current)); + + +/// StringBuf - dynamic string +struct StringBuf +{ + char *buf_; + char *ptr_; + unsigned int max_; +}; +typedef struct StringBuf StringBuf; + +StringBuf* StringBuf_Malloc(void); +void StringBuf_Init(StringBuf* self); +int StringBuf_Printf(StringBuf* self, const char* fmt, ...); +int StringBuf_Vprintf(StringBuf* self, const char* fmt, va_list args); +int StringBuf_Append(StringBuf* self, const StringBuf *sbuf); +int StringBuf_AppendStr(StringBuf* self, const char* str); +int StringBuf_Length(StringBuf* self); +char* StringBuf_Value(StringBuf* self); +void StringBuf_Clear(StringBuf* self); +void StringBuf_Destroy(StringBuf* self); +void StringBuf_Free(StringBuf* self); + +#endif /* _STRLIB_H_ */ diff --git a/src/common/thread.c b/src/common/thread.c new file mode 100644 index 000000000..315b310b2 --- /dev/null +++ b/src/common/thread.c @@ -0,0 +1,317 @@ +// +// Basic Threading abstraction (for pthread / win32 based systems) +// +// Author: Florian Wilkemeyer <fw@f-ws.de> +// +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifdef WIN32 +#include "../common/winapi.h" +#define getpagesize() 4096 // @TODO: implement this properly (GetSystemInfo .. dwPageSize..). (Atm as on all supported win platforms its 4k its static.) +#define __thread __declspec( thread ) +#else +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> +#include <sched.h> +#endif + +#include "cbasetypes.h" +#include "malloc.h" +#include "showmsg.h" +#include "thread.h" + +// When Compiling using MSC (on win32..) we know we have support in any case! +#ifdef _MSC_VER +#define HAS_TLS +#endif + + +#define RA_THREADS_MAX 64 + +struct rAthread { + unsigned int myID; + + RATHREAD_PRIO prio; + rAthreadProc proc; + void *param; + + #ifdef WIN32 + HANDLE hThread; + #else + pthread_t hThread; + #endif +}; + + +#ifdef HAS_TLS +__thread int g_rathread_ID = -1; +#endif + + +/// +/// Subystem Code +/// +static struct rAthread l_threads[RA_THREADS_MAX]; + +void rathread_init(){ + register unsigned int i; + memset(&l_threads, 0x00, RA_THREADS_MAX * sizeof(struct rAthread) ); + + for(i = 0; i < RA_THREADS_MAX; i++){ + l_threads[i].myID = i; + } + + // now lets init thread id 0, which represnts the main thread +#ifdef HAS_TLS + g_rathread_ID = 0; +#endif + l_threads[0].prio = RAT_PRIO_NORMAL; + l_threads[0].proc = (rAthreadProc)0xDEADCAFE; + +}//end: rathread_init() + + + +void rathread_final(){ + register unsigned int i; + + // Unterminated Threads Left? + // Should'nt happen .. + // Kill 'em all! + // + for(i = 1; i < RA_THREADS_MAX; i++){ + if(l_threads[i].proc != NULL){ + ShowWarning("rAthread_final: unterminated Thread (tid %u entryPoint %p) - forcing to terminate (kill)\n", i, l_threads[i].proc); + rathread_destroy(&l_threads[i]); + } + } + + +}//end: rathread_final() + + + +// gets called whenever a thread terminated .. +static void rat_thread_terminated( rAthread handle ){ + + int id_backup = handle->myID; + + // Simply set all members to 0 (except the id) + memset(handle, 0x00, sizeof(struct rAthread)); + + handle->myID = id_backup; // done ;) + +}//end: rat_thread_terminated() + +#ifdef WIN32 +DWORD WINAPI _raThreadMainRedirector(LPVOID p){ +#else +static void *_raThreadMainRedirector( void *p ){ + sigset_t set; // on Posix Thread platforms +#endif + void *ret; + + // Update myID @ TLS to right id. +#ifdef HAS_TLS + g_rathread_ID = ((rAthread)p)->myID; +#endif + +#ifndef WIN32 + // When using posix threads + // the threads inherits the Signal mask from the thread which's spawned + // this thread + // so we've to block everything we dont care about. + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + sigaddset(&set, SIGPIPE); + + pthread_sigmask(SIG_BLOCK, &set, NULL); + +#endif + + + ret = ((rAthread)p)->proc( ((rAthread)p)->param ) ; + +#ifdef WIN32 + CloseHandle( ((rAthread)p)->hThread ); +#endif + + rat_thread_terminated( (rAthread)p ); +#ifdef WIN32 + return (DWORD)ret; +#else + return ret; +#endif +}//end: _raThreadMainRedirector() + + + + + +/// +/// API Level +/// +rAthread rathread_create( rAthreadProc entryPoint, void *param ){ + return rathread_createEx( entryPoint, param, (1<<23) /*8MB*/, RAT_PRIO_NORMAL ); +}//end: rathread_create() + + +rAthread rathread_createEx( rAthreadProc entryPoint, void *param, size_t szStack, RATHREAD_PRIO prio ){ +#ifndef WIN32 + pthread_attr_t attr; +#endif + size_t tmp; + unsigned int i; + rAthread handle = NULL; + + + // given stacksize aligned to systems pagesize? + tmp = szStack % getpagesize(); + if(tmp != 0) + szStack += tmp; + + + // Get a free Thread Slot. + for(i = 0; i < RA_THREADS_MAX; i++){ + if(l_threads[i].proc == NULL){ + handle = &l_threads[i]; + break; + } + } + + if(handle == NULL){ + ShowError("rAthread: cannot create new thread (entryPoint: %p) - no free thread slot found!", entryPoint); + return NULL; + } + + + + handle->proc = entryPoint; + handle->param = param; + +#ifdef WIN32 + handle->hThread = CreateThread(NULL, szStack, _raThreadMainRedirector, (void*)handle, 0, NULL); +#else + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, szStack); + + if(pthread_create(&handle->hThread, &attr, _raThreadMainRedirector, (void*)handle) != 0){ + handle->proc = NULL; + handle->param = NULL; + return NULL; + } + pthread_attr_destroy(&attr); +#endif + + rathread_prio_set( handle, prio ); + + return handle; +}//end: rathread_createEx + + +void rathread_destroy ( rAthread handle ){ +#ifdef WIN32 + if( TerminateThread(handle->hThread, 0) != FALSE){ + CloseHandle(handle->hThread); + rat_thread_terminated(handle); + } +#else + if( pthread_cancel( handle->hThread ) == 0){ + + // We have to join it, otherwise pthread wont re-cycle its internal ressources assoc. with this thread. + // + pthread_join( handle->hThread, NULL ); + + // Tell our manager to release ressources ;) + rat_thread_terminated(handle); + } +#endif +}//end: rathread_destroy() + +rAthread rathread_self( ){ +#ifdef HAS_TLS + rAthread handle = &l_threads[g_rathread_ID]; + + if(handle->proc != NULL) // entry point set, so its used! + return handle; +#else + // .. so no tls means we have to search the thread by its api-handle .. + int i; + + #ifdef WIN32 + HANDLE hSelf; + hSelf = GetCurrent = GetCurrentThread(); + #else + pthread_t hSelf; + hSelf = pthread_self(); + #endif + + for(i = 0; i < RA_THREADS_MAX; i++){ + if(l_threads[i].hThread == hSelf && l_threads[i].proc != NULL) + return &l_threads[i]; + } + +#endif + + return NULL; +}//end: rathread_self() + + +int rathread_get_tid(){ + +#ifdef HAS_TLS + return g_rathread_ID; +#else + // todo + #ifdef WIN32 + return (int)GetCurrentThreadId(); + #else + return (intptr_t)pthread_self(); + #endif + +#endif + +}//end: rathread_get_tid() + + +bool rathread_wait( rAthread handle, void* *out_exitCode ){ + + // Hint: + // no thread data cleanup routine call here! + // its managed by the callProxy itself.. + // +#ifdef WIN32 + WaitForSingleObject(handle->hThread, INFINITE); + return true; +#else + if(pthread_join(handle->hThread, out_exitCode) == 0) + return true; + return false; +#endif + +}//end: rathread_wait() + + +void rathread_prio_set( rAthread handle, RATHREAD_PRIO prio ){ + handle->prio = RAT_PRIO_NORMAL; + //@TODO +}//end: rathread_prio_set() + + +RATHREAD_PRIO rathread_prio_get( rAthread handle){ + return handle->prio; +}//end: rathread_prio_get() + + +void rathread_yield(){ +#ifdef WIN32 + SwitchToThread(); +#else + sched_yield(); +#endif +}//end: rathread_yield() diff --git a/src/common/thread.h b/src/common/thread.h new file mode 100644 index 000000000..a5a66e954 --- /dev/null +++ b/src/common/thread.h @@ -0,0 +1,119 @@ +// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#pragma once +#ifndef _rA_THREAD_H_ +#define _rA_THREAD_H_ + +#include "../common/cbasetypes.h" + +typedef struct rAthread *rAthread; +typedef void* (*rAthreadProc)(void*); + +typedef enum RATHREAD_PRIO { + RAT_PRIO_LOW = 0, + RAT_PRIO_NORMAL, + RAT_PRIO_HIGH +} RATHREAD_PRIO; + + +/** + * Creates a new Thread + * + * @param entyPoint - entryProc, + * @param param - general purpose parameter, would be given as parameter to the thread's entrypoint. + * + * @return not NULL if success + */ +rAthread rathread_create( rAthreadProc entryPoint, void *param ); + + +/** + * Creates a new Thread (with more creation options) + * + * @param entyPoint - entryProc, + * @param param - general purpose parameter, would be given as parameter to the thread's entrypoint + * @param szStack - stack Size in bytes + * @param prio - Priority of the Thread @ OS Scheduler.. + * + * @return not NULL if success + */ +rAthread rathread_createEx( rAthreadProc entryPoint, void *param, size_t szStack, RATHREAD_PRIO prio ); + + +/** + * Destroys the given Thread immediatly + * + * @note The Handle gets invalid after call! dont use it afterwards. + * + * @param handle - thread to destroy. + */ +void rathread_destroy ( rAthread handle ); + + +/** + * Returns the thread handle of the thread calling this function + * + * @note this wont work @ programms main thread + * @note the underlying implementation might not perform very well, cache the value received! + * + * @return not NULL if success + */ +rAthread rathread_self( ); + + +/** + * Returns own thrad id (TID) + * + * @note this is an unique identifier for the calling thread, and + * depends on platfrom / compiler, and may not be the systems Thread ID! + * + * @return -1 when fails, otherwise >= 0 + */ +int rathread_get_tid(); + + +/** + * Waits for the given thread to terminate + * + * @param handle - thread to wait (join) for + * @param out_Exitcode - [OPTIONAL] - if given => Exitcode (value) of the given thread - if it's terminated + * + * @return true - if the given thread has been terminated. + */ +bool rathread_wait( rAthread handle, void* *out_exitCode ); + + +/** + * Sets the given PRIORITY @ OS Task Scheduler + * + * @param handle - thread to set prio for + * @param rio - the priority (RAT_PRIO_LOW ... ) + */ +void rathread_prio_set( rAthread handle, RATHREAD_PRIO prio ); + + +/** + * Gets the current Prio of the given trhead + * + * @param handle - the thread to get the prio for. + */ +RATHREAD_PRIO rathread_prio_get( rAthread handle); + + +/** + * Tells the OS scheduler to yield the execution of the calling thread + * + * @note: this will not "pause" the thread, + * it just allows the OS to spent the remaining time + * of the slice to another thread. + */ +void rathread_yield(); + + + +void rathread_init(); +void rathread_final(); + + +#endif diff --git a/src/common/timer.c b/src/common/timer.c new file mode 100644 index 000000000..c239a9d70 --- /dev/null +++ b/src/common/timer.c @@ -0,0 +1,432 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/utils.h" +#include "timer.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef WIN32 +#include "../common/winapi.h" // GetTickCount() +#else +#include <unistd.h> +#include <sys/time.h> // struct timeval, gettimeofday() +#endif + +// If the server can't handle processing thousands of monsters +// or many connected clients, please increase TIMER_MIN_INTERVAL. +#define TIMER_MIN_INTERVAL 50 +#define TIMER_MAX_INTERVAL 1000 + +// timers (array) +static struct TimerData* timer_data = NULL; +static int timer_data_max = 0; +static int timer_data_num = 0; + +// free timers (array) +static int* free_timer_list = NULL; +static int free_timer_list_max = 0; +static int free_timer_list_pos = 0; + + +/// Comparator for the timer heap. (minimum tick at top) +/// Returns negative if tid1's tick is smaller, positive if tid2's tick is smaller, 0 if equal. +/// +/// @param tid1 First timer +/// @param tid2 Second timer +/// @return negative if tid1 is top, positive if tid2 is top, 0 if equal +#define DIFFTICK_MINTOPCMP(tid1,tid2) DIFF_TICK(timer_data[tid1].tick,timer_data[tid2].tick) + +// timer heap (binary heap of tid's) +static BHEAP_VAR(int, timer_heap); + + +// server startup time +time_t start_time; + + +/*---------------------------- + * Timer debugging + *----------------------------*/ +struct timer_func_list { + struct timer_func_list* next; + TimerFunc func; + char* name; +} *tfl_root = NULL; + +/// Sets the name of a timer function. +int add_timer_func_list(TimerFunc func, char* name) +{ + struct timer_func_list* tfl; + + if (name) { + for( tfl=tfl_root; tfl != NULL; tfl=tfl->next ) + {// check suspicious cases + if( func == tfl->func ) + ShowWarning("add_timer_func_list: duplicating function %p(%s) as %s.\n",tfl->func,tfl->name,name); + else if( strcmp(name,tfl->name) == 0 ) + ShowWarning("add_timer_func_list: function %p has the same name as %p(%s)\n",func,tfl->func,tfl->name); + } + CREATE(tfl,struct timer_func_list,1); + tfl->next = tfl_root; + tfl->func = func; + tfl->name = aStrdup(name); + tfl_root = tfl; + } + return 0; +} + +/// Returns the name of the timer function. +char* search_timer_func_list(TimerFunc func) +{ + struct timer_func_list* tfl; + + for( tfl=tfl_root; tfl != NULL; tfl=tfl->next ) + if (func == tfl->func) + return tfl->name; + + return "unknown timer function"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ + +#if defined(ENABLE_RDTSC) +static uint64 RDTSC_BEGINTICK = 0, RDTSC_CLOCK = 0; + +static __inline uint64 _rdtsc(){ + register union{ + uint64 qw; + uint32 dw[2]; + } t; + + asm volatile("rdtsc":"=a"(t.dw[0]), "=d"(t.dw[1]) ); + + return t.qw; +} + +static void rdtsc_calibrate(){ + uint64 t1, t2; + int32 i; + + ShowStatus("Calibrating Timer Source, please wait... "); + + RDTSC_CLOCK = 0; + + for(i = 0; i < 5; i++){ + t1 = _rdtsc(); + usleep(1000000); //1000 MS + t2 = _rdtsc(); + RDTSC_CLOCK += (t2 - t1) / 1000; + } + RDTSC_CLOCK /= 5; + + RDTSC_BEGINTICK = _rdtsc(); + + ShowMessage(" done. (Frequency: %u Mhz)\n", (uint32)(RDTSC_CLOCK/1000) ); +} + +#endif + +/// platform-abstracted tick retrieval +static unsigned int tick(void) +{ +#if defined(WIN32) + return GetTickCount(); +#elif defined(ENABLE_RDTSC) + // + return (unsigned int)((_rdtsc() - RDTSC_BEGINTICK) / RDTSC_CLOCK); + // +#elif defined(HAVE_MONOTONIC_CLOCK) + struct timespec tval; + clock_gettime(CLOCK_MONOTONIC, &tval); + return tval.tv_sec * 1000 + tval.tv_nsec / 1000000; +#else + struct timeval tval; + gettimeofday(&tval, NULL); + return tval.tv_sec * 1000 + tval.tv_usec / 1000; +#endif +} + +////////////////////////////////////////////////////////////////////////// +#if defined(TICK_CACHE) && TICK_CACHE > 1 +////////////////////////////////////////////////////////////////////////// +// tick is cached for TICK_CACHE calls +static unsigned int gettick_cache; +static int gettick_count = 1; + +unsigned int gettick_nocache(void) +{ + gettick_count = TICK_CACHE; + gettick_cache = tick(); + return gettick_cache; +} + +unsigned int gettick(void) +{ + return ( --gettick_count == 0 ) ? gettick_nocache() : gettick_cache; +} +////////////////////////////// +#else +////////////////////////////// +// tick doesn't get cached +unsigned int gettick_nocache(void) +{ + return tick(); +} + +unsigned int gettick(void) +{ + return tick(); +} +////////////////////////////////////////////////////////////////////////// +#endif +////////////////////////////////////////////////////////////////////////// + +/*====================================== + * CORE : Timer Heap + *--------------------------------------*/ + +/// Adds a timer to the timer_heap +static void push_timer_heap(int tid) +{ + BHEAP_ENSURE(timer_heap, 1, 256); + BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP); +} + +/*========================== + * Timer Management + *--------------------------*/ + +/// Returns a free timer id. +static int acquire_timer(void) +{ + int tid; + + // select a free timer + if (free_timer_list_pos) { + do { + tid = free_timer_list[--free_timer_list_pos]; + } while(tid >= timer_data_num && free_timer_list_pos > 0); + } else + tid = timer_data_num; + + // check available space + if( tid >= timer_data_num ) + for (tid = timer_data_num; tid < timer_data_max && timer_data[tid].type; tid++); + if (tid >= timer_data_num && tid >= timer_data_max) + {// expand timer array + timer_data_max += 256; + if( timer_data ) + RECREATE(timer_data, struct TimerData, timer_data_max); + else + CREATE(timer_data, struct TimerData, timer_data_max); + memset(timer_data + (timer_data_max - 256), 0, sizeof(struct TimerData)*256); + } + + if( tid >= timer_data_num ) + timer_data_num = tid + 1; + + return tid; +} + +/// Starts a new timer that is deleted once it expires (single-use). +/// Returns the timer's id. +int add_timer(unsigned int tick, TimerFunc func, int id, intptr_t data) +{ + int tid; + + tid = acquire_timer(); + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_ONCE_AUTODEL; + timer_data[tid].interval = 1000; + push_timer_heap(tid); + + return tid; +} + +/// Starts a new timer that automatically restarts itself (infinite loop until manually removed). +/// Returns the timer's id, or INVALID_TIMER if it fails. +int add_timer_interval(unsigned int tick, TimerFunc func, int id, intptr_t data, int interval) +{ + int tid; + + if( interval < 1 ) + { + ShowError("add_timer_interval: invalid interval (tick=%u %p[%s] id=%d data=%d diff_tick=%d)\n", tick, func, search_timer_func_list(func), id, data, DIFF_TICK(tick, gettick())); + return INVALID_TIMER; + } + + tid = acquire_timer(); + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_INTERVAL; + timer_data[tid].interval = interval; + push_timer_heap(tid); + + return tid; +} + +/// Retrieves internal timer data +const struct TimerData* get_timer(int tid) +{ + return ( tid >= 0 && tid < timer_data_num ) ? &timer_data[tid] : NULL; +} + +/// Marks a timer specified by 'id' for immediate deletion once it expires. +/// Param 'func' is used for debug/verification purposes. +/// Returns 0 on success, < 0 on failure. +int delete_timer(int tid, TimerFunc func) +{ + if( tid < 0 || tid >= timer_data_num ) + { + ShowError("delete_timer error : no such timer %d (%p(%s))\n", tid, func, search_timer_func_list(func)); + return -1; + } + if( timer_data[tid].func != func ) + { + ShowError("delete_timer error : function mismatch %p(%s) != %p(%s)\n", timer_data[tid].func, search_timer_func_list(timer_data[tid].func), func, search_timer_func_list(func)); + return -2; + } + + timer_data[tid].func = NULL; + timer_data[tid].type = TIMER_ONCE_AUTODEL; + + return 0; +} + +/// Adjusts a timer's expiration time. +/// Returns the new tick value, or -1 if it fails. +int addtick_timer(int tid, unsigned int tick) +{ + return settick_timer(tid, timer_data[tid].tick+tick); +} + +/// Modifies a timer's expiration time (an alternative to deleting a timer and starting a new one). +/// Returns the new tick value, or -1 if it fails. +int settick_timer(int tid, unsigned int tick) +{ + size_t i; + + // search timer position + ARR_FIND(0, BHEAP_LENGTH(timer_heap), i, BHEAP_DATA(timer_heap)[i] == tid); + if( i == BHEAP_LENGTH(timer_heap) ) + { + ShowError("settick_timer: no such timer %d (%p(%s))\n", tid, timer_data[tid].func, search_timer_func_list(timer_data[tid].func)); + return -1; + } + + if( (int)tick == -1 ) + tick = 0;// add 1ms to avoid the error value -1 + + if( timer_data[tid].tick == tick ) + return (int)tick;// nothing to do, already in propper position + + // pop and push adjusted timer + BHEAP_POPINDEX(timer_heap, i, DIFFTICK_MINTOPCMP); + timer_data[tid].tick = tick; + BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP); + return (int)tick; +} + +/// Executes all expired timers. +/// Returns the value of the smallest non-expired timer (or 1 second if there aren't any). +int do_timer(unsigned int tick) +{ + int diff = TIMER_MAX_INTERVAL; // return value + + // process all timers one by one + while( BHEAP_LENGTH(timer_heap) ) + { + int tid = BHEAP_PEEK(timer_heap);// top element in heap (smallest tick) + + diff = DIFF_TICK(timer_data[tid].tick, tick); + if( diff > 0 ) + break; // no more expired timers to process + + // remove timer + BHEAP_POP(timer_heap, DIFFTICK_MINTOPCMP); + timer_data[tid].type |= TIMER_REMOVE_HEAP; + + if( timer_data[tid].func ) + { + if( diff < -1000 ) + // timer was delayed for more than 1 second, use current tick instead + timer_data[tid].func(tid, tick, timer_data[tid].id, timer_data[tid].data); + else + timer_data[tid].func(tid, timer_data[tid].tick, timer_data[tid].id, timer_data[tid].data); + } + + // in the case the function didn't change anything... + if( timer_data[tid].type & TIMER_REMOVE_HEAP ) + { + timer_data[tid].type &= ~TIMER_REMOVE_HEAP; + + switch( timer_data[tid].type ) + { + default: + case TIMER_ONCE_AUTODEL: + timer_data[tid].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + RECREATE(free_timer_list,int,free_timer_list_max); + memset(free_timer_list + (free_timer_list_max - 256), 0, 256 * sizeof(int)); + } + free_timer_list[free_timer_list_pos++] = tid; + break; + case TIMER_INTERVAL: + if( DIFF_TICK(timer_data[tid].tick, tick) < -1000 ) + timer_data[tid].tick = tick + timer_data[tid].interval; + else + timer_data[tid].tick += timer_data[tid].interval; + push_timer_heap(tid); + break; + } + } + } + + return cap_value(diff, TIMER_MIN_INTERVAL, TIMER_MAX_INTERVAL); +} + +unsigned long get_uptime(void) +{ + return (unsigned long)difftime(time(NULL), start_time); +} + +void timer_init(void) +{ +#if defined(ENABLE_RDTSC) + rdtsc_calibrate(); +#endif + + time(&start_time); +} + +void timer_final(void) +{ + struct timer_func_list *tfl; + struct timer_func_list *next; + + for( tfl=tfl_root; tfl != NULL; tfl = next ) { + next = tfl->next; // copy next pointer + aFree(tfl->name); // free structures + aFree(tfl); + } + + if (timer_data) aFree(timer_data); + BHEAP_CLEAR(timer_heap); + if (free_timer_list) aFree(free_timer_list); +} diff --git a/src/common/timer.h b/src/common/timer.h new file mode 100644 index 000000000..d45c73d12 --- /dev/null +++ b/src/common/timer.h @@ -0,0 +1,57 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#include "../common/cbasetypes.h" + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +#define INVALID_TIMER -1 + +// timer flags +enum { + TIMER_ONCE_AUTODEL = 0x01, + TIMER_INTERVAL = 0x02, + TIMER_REMOVE_HEAP = 0x10, +}; + +// Struct declaration + +typedef int (*TimerFunc)(int tid, unsigned int tick, int id, intptr_t data); + +struct TimerData { + unsigned int tick; + TimerFunc func; + int type; + int interval; + int heap_pos; + + // general-purpose storage + int id; + intptr_t data; +}; + +// Function prototype declaration + +unsigned int gettick(void); +unsigned int gettick_nocache(void); + +int add_timer(unsigned int tick, TimerFunc func, int id, intptr_t data); +int add_timer_interval(unsigned int tick, TimerFunc func, int id, intptr_t data, int interval); +const struct TimerData* get_timer(int tid); +int delete_timer(int tid, TimerFunc func); + +int addtick_timer(int tid, unsigned int tick); +int settick_timer(int tid, unsigned int tick); + +int add_timer_func_list(TimerFunc func, char* name); + +unsigned long get_uptime(void); + +int do_timer(unsigned int tick); +void timer_init(void); +void timer_final(void); + +#endif /* _TIMER_H_ */ diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 000000000..296df7e70 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,283 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "socket.h" +#include "utils.h" + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> // floor() + +#ifdef WIN32 + #include "../common/winapi.h" + #ifndef F_OK + #define F_OK 0x0 + #endif /* F_OK */ +#else + #include <unistd.h> + #include <dirent.h> + #include <sys/stat.h> +#endif + + +/// Dumps given buffer into file pointed to by a handle. +void WriteDump(FILE* fp, const void* buffer, size_t length) +{ + size_t i; + char hex[48+1], ascii[16+1]; + + fprintf(fp, "--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; + + for( i = 0; i < length; i++ ) + { + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); + + if( (i%16) == 15 ) + { + fprintf(fp, "%03X %s %s\n", (unsigned int)(i/16), hex, ascii); + } + } + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + fprintf(fp, "%03X %-48s %-16s\n", (unsigned int)(i/16), hex, ascii); + } +} + + +/// Dumps given buffer on the console. +void ShowDump(const void* buffer, size_t length) +{ + size_t i; + char hex[48+1], ascii[16+1]; + + ShowDebug("--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; + + for( i = 0; i < length; i++ ) + { + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); + + if( (i%16) == 15 ) + { + ShowDebug("%03X %s %s\n", i/16, hex, ascii); + } + } + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + ShowDebug("%03X %-48s %-16s\n", i/16, hex, ascii); + } +} + + +#ifdef WIN32 + +static char* checkpath(char *path, const char *srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='/') { + *p++ = '\\'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + WIN32_FIND_DATAA FindFileData; + HANDLE hFind; + char tmppath[MAX_PATH+1]; + + const char *path = (p ==NULL)? "." : p; + const char *pattern = (pat==NULL)? "" : pat; + + checkpath(tmppath,path); + if( PATHSEP != tmppath[strlen(tmppath)-1]) + strcat(tmppath, "\\*"); + else + strcat(tmppath, "*"); + + hFind = FindFirstFileA(tmppath, &FindFileData); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(FindFileData.cFileName, ".") == 0) + continue; + if (strcmp(FindFileData.cFileName, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path,PATHSEP,FindFileData.cFileName); + + if (FindFileData.cFileName && strstr(FindFileData.cFileName, pattern)) { + func( tmppath ); + } + + + if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + findfile(tmppath, pat, func); + } + }while (FindNextFileA(hFind, &FindFileData) != 0); + FindClose(hFind); + } + return; +} +#else + +#define MAX_DIR_PATH 2048 + +static char* checkpath(char *path, const char*srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='\\') { + *p++ = '/'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + DIR* dir; // pointer to the scanned directory. + struct dirent* entry; // pointer to one directory entry. + struct stat dir_stat; // used by stat(). + char tmppath[MAX_DIR_PATH+1]; + char path[MAX_DIR_PATH+1]= "."; + const char *pattern = (pat==NULL)? "" : pat; + if(p!=NULL) strcpy(path,p); + + // open the directory for reading + dir = opendir( checkpath(path, path) ); + if (!dir) { + ShowError("Cannot read directory '%s'\n", path); + return; + } + + // scan the directory, traversing each sub-directory + // matching the pattern for each file name. + while ((entry = readdir(dir))) { + // skip the "." and ".." entries. + if (strcmp(entry->d_name, ".") == 0) + continue; + if (strcmp(entry->d_name, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path, PATHSEP, entry->d_name); + + // check if the pattern matchs. + if (entry->d_name && strstr(entry->d_name, pattern)) { + func( tmppath ); + } + // check if it is a directory. + if (stat(tmppath, &dir_stat) == -1) { + ShowError("stat error %s\n': ", tmppath); + continue; + } + // is this a directory? + if (S_ISDIR(dir_stat.st_mode)) { + // decent recursivly + findfile(tmppath, pat, func); + } + }//end while + + closedir(dir); +} +#endif + +bool exists(const char* filename) +{ + return !access(filename, F_OK); +} + +uint8 GetByte(uint32 val, int idx) +{ + switch( idx ) + { + case 0: return (uint8)( (val & 0x000000FF) ); + case 1: return (uint8)( (val & 0x0000FF00) >> 0x08 ); + case 2: return (uint8)( (val & 0x00FF0000) >> 0x10 ); + case 3: return (uint8)( (val & 0xFF000000) >> 0x18 ); + default: +#if defined(DEBUG) + ShowDebug("GetByte: invalid index (idx=%d)\n", idx); +#endif + return 0; + } +} + +uint16 GetWord(uint32 val, int idx) +{ + switch( idx ) + { + case 0: return (uint16)( (val & 0x0000FFFF) ); + case 1: return (uint16)( (val & 0xFFFF0000) >> 0x10 ); + default: +#if defined(DEBUG) + ShowDebug("GetWord: invalid index (idx=%d)\n", idx); +#endif + return 0; + } +} +uint16 MakeWord(uint8 byte0, uint8 byte1) +{ + return byte0 | (byte1 << 0x08); +} + +uint32 MakeDWord(uint16 word0, uint16 word1) +{ + return + ( (uint32)(word0 ) )| + ( (uint32)(word1 << 0x10) ); +} + + +/// calculates the value of A / B, in percent (rounded down) +unsigned int get_percentage(const unsigned int A, const unsigned int B) +{ + double result; + + if( B == 0 ) + { + ShowError("get_percentage(): divison by zero! (A=%u,B=%u)\n", A, B); + return ~0U; + } + + result = 100 * ((double)A / (double)B); + + if( result > UINT_MAX ) + { + ShowError("get_percentage(): result percentage too high! (A=%u,B=%u,result=%g)\n", A, B, result); + return UINT_MAX; + } + + return (unsigned int)floor(result); +} diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 000000000..8e39f2655 --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,32 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include "../common/cbasetypes.h" +#include <stdio.h> // FILE* + +// generate a hex dump of the first 'length' bytes of 'buffer' +void WriteDump(FILE* fp, const void* buffer, size_t length); +void ShowDump(const void* buffer, size_t length); + +void findfile(const char *p, const char *pat, void (func)(const char*)); +bool exists(const char* filename); + +//Caps values to min/max +#define cap_value(a, min, max) ((a >= max) ? max : (a <= min) ? min : a) + +/// calculates the value of A / B, in percent (rounded down) +unsigned int get_percentage(const unsigned int A, const unsigned int B); + +////////////////////////////////////////////////////////////////////////// +// byte word dword access [Shinomori] +////////////////////////////////////////////////////////////////////////// + +extern uint8 GetByte(uint32 val, int idx); +extern uint16 GetWord(uint32 val, int idx); +extern uint16 MakeWord(uint8 byte0, uint8 byte1); +extern uint32 MakeDWord(uint16 word0, uint16 word1); + +#endif /* _UTILS_H_ */ diff --git a/src/common/winapi.h b/src/common/winapi.h new file mode 100644 index 000000000..7ce555049 --- /dev/null +++ b/src/common/winapi.h @@ -0,0 +1,36 @@ +#pragma once + + +#define STRICT +#define NTDDI_VERSION NTDDI_WIN2K +#define _WIN32_WINNT 0x0500 +#define WINVER 0x0500 +#define _WIN32_IE 0x0600 +#define WIN32_LEAN_AND_MEAN +#define NOCOMM +#define NOKANJI +#define NOHELP +#define NOMCX +#define NOCLIPBOARD +#define NOCOLOR +#define NONLS +#define NOMEMMGR +#define NOMETAFILE +#define NOOPENFILE +#define NOSERVICE +#define NOSOUND +#define NOTEXTMETRIC + + +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS + +#include <io.h> +#include <Windows.h> +#include <WinSock2.h> +#include <In6addr.h> +#include <Ws2tcpip.h> +#include <Mswsock.h> +#include <MMSystem.h> + + |