From 620e60eebce2c1f35c5c9a82f6ca365b316587f5 Mon Sep 17 00:00:00 2001 From: Valaris Date: Sun, 29 Jan 2006 16:10:48 +0000 Subject: AS OF SVN REV. 5901, WE ARE NOW USING TRUNK. ALL UNTESTED BUGFIXES/FEATURES GO INTO TRUNK. IF YOU HAVE A WORKING AND TESTED BUGFIX PUT IT INTO STABLE AS WELL AS TRUNK. EVERYTHING ELSE GOES INTO TRUNK AND WILL BE MERGED INTO STABLE BY VALARIS AND WIZPUTER. -- VALARIS git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@5094 54d463be-8e91-2dee-dedb-b68131a5f0ec --- src/common/Makefile | 56 ++ src/common/cbasetypes.h | 253 +++++ src/common/core.c | 269 ++++++ src/common/core.h | 30 + src/common/db.c | 2344 +++++++++++++++++++++++++++++++++++++++++++++++ src/common/db.h | 734 +++++++++++++++ src/common/ers.c | 532 +++++++++++ src/common/ers.h | 193 ++++ src/common/graph.c | 318 +++++++ src/common/graph.h | 27 + src/common/grfio.c | 1146 +++++++++++++++++++++++ src/common/grfio.h | 21 + src/common/lock.c | 71 ++ src/common/lock.h | 11 + src/common/malloc.c | 715 +++++++++++++++ src/common/malloc.h | 153 ++++ src/common/mapindex.c | 130 +++ src/common/mapindex.h | 37 + src/common/mmo.h | 403 ++++++++ src/common/nullpo.c | 94 ++ src/common/nullpo.h | 237 +++++ src/common/plugin.h | 40 + src/common/plugins.c | 367 ++++++++ src/common/plugins.h | 61 ++ src/common/showmsg.c | 219 +++++ src/common/showmsg.h | 88 ++ src/common/socket.c | 1390 ++++++++++++++++++++++++++++ src/common/socket.h | 189 ++++ src/common/strlib.c | 133 +++ src/common/strlib.h | 17 + src/common/timer.c | 429 +++++++++ src/common/timer.h | 60 ++ src/common/utils.c | 384 ++++++++ src/common/utils.h | 52 ++ src/common/version.h | 30 + 35 files changed, 11233 insertions(+) create mode 100644 src/common/Makefile create mode 100644 src/common/cbasetypes.h create mode 100644 src/common/core.c create mode 100644 src/common/core.h create mode 100644 src/common/db.c create mode 100644 src/common/db.h create mode 100644 src/common/ers.c create mode 100644 src/common/ers.h create mode 100644 src/common/graph.c create mode 100644 src/common/graph.h create mode 100644 src/common/grfio.c create mode 100644 src/common/grfio.h create mode 100644 src/common/lock.c create mode 100644 src/common/lock.h create mode 100644 src/common/malloc.c create mode 100644 src/common/malloc.h create mode 100644 src/common/mapindex.c create mode 100644 src/common/mapindex.h create mode 100644 src/common/mmo.h create mode 100644 src/common/nullpo.c create mode 100644 src/common/nullpo.h create mode 100644 src/common/plugin.h create mode 100644 src/common/plugins.c create mode 100644 src/common/plugins.h create mode 100644 src/common/showmsg.c create mode 100644 src/common/showmsg.h create mode 100644 src/common/socket.c create mode 100644 src/common/socket.h create mode 100644 src/common/strlib.c create mode 100644 src/common/strlib.h create mode 100644 src/common/timer.c create mode 100644 src/common/timer.h create mode 100644 src/common/utils.c create mode 100644 src/common/utils.h create mode 100644 src/common/version.h (limited to 'src/common') diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 000000000..60b588b1e --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,56 @@ +txt sql all: obj common + +obj: + mkdir obj + +common: obj/core.o obj/socket.o obj/timer.o obj/db.o obj/plugins.o obj/lock.o \ + obj/nullpo.o obj/malloc.o obj/showmsg.o obj/strlib.o obj/utils.o \ + obj/graph.o obj/grfio.o obj/minicore.o obj/minisocket.o obj/minimalloc.o \ + obj/mapindex.o obj/unz.o obj/ers.o + + +obj/%.o: %.c + $(COMPILE.c) $(OUTPUT_OPTION) $< + +obj/mini%.o: %.c + $(COMPILE.c) -DMINICORE $(OUTPUT_OPTION) $< + +obj/unz.o: + $(MAKE) -C ../zlib + @touch $@ + + +clean: + rm -rf *.o obj + +HAVESVN = $(shell which svnversion) + +ifeq ($(findstring /,$(HAVESVN)), /) +svnversion.h: ../../Changelog-SVN.txt + @printf "#define SVNVERSION " > svnversion.h + @svnversion . >> svnversion.h +else +svnversion.h: + @printf "" > svnversion.h +endif + +obj/minicore.o: core.c core.h +obj/minisocket.o: socket.c socket.h +obj/minimalloc.o: malloc.c malloc.h + +# DO NOT DELETE + +obj/core.o: core.c core.h showmsg.h svnversion.h +obj/socket.o: socket.c socket.h mmo.h showmsg.h plugins.h +obj/timer.o: timer.c timer.h showmsg.h +obj/ers.o: ers.c ers.h cbasetypes.h +obj/db.o: db.c db.h showmsg.h ers.h +obj/lock.o: lock.c lock.h showmsg.h +obj/grfio.o: grfio.c grfio.h +obj/graph.o: graph.c graph.h +obj/nullpo.o: nullpo.c nullpo.h showmsg.h +obj/malloc.o: malloc.c malloc.h showmsg.h +obj/plugins.o: plugins.c plugins.h plugin.h +obj/showmsg.o: showmsg.c showmsg.h +obj/strlib.o: strlib.c strlib.h utils.h +obj/mapindex.o: mapindex.c mapindex.h diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h new file mode 100644 index 000000000..ec539a3db --- /dev/null +++ b/src/common/cbasetypes.h @@ -0,0 +1,253 @@ +#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 + +// __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 + +// disable attributed stuff on non-GNU +#ifndef __GNUC__ +# define __attribute__(x) +#endif + + +////////////////////////////////////////////////////////////////////////// +// useful typedefs +////////////////////////////////////////////////////////////////////////// +typedef unsigned char uchar; +typedef signed char schar; +typedef signed short sshort; +typedef unsigned short ushort; +typedef signed int sint; // don't use (only for ie. scanf) +typedef unsigned int uint; // don't use +typedef signed long slong; // don't use (only for ie. file-io) +typedef unsigned long ulong; // don't use + +typedef char* pchar; +typedef const char* cchar; +typedef unsigned char* puchar; +typedef void* ptr; +typedef int* pint; + + +////////////////////////////////////////////////////////////////////////// +// typedefs to compensate type size change from 32bit to 64bit +// MS implements LLP64 model, normal unix does LP64, +// only Silicon Graphics/Cray goes ILP64 so don't care (and don't support) +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _exact_ size. +////////////////////////////////////////////////////////////////////////// + +////////////////////////////// +#ifdef WIN32 +////////////////////////////// +typedef __int8 int8; +typedef __int16 int16; +typedef __int32 int32; + +typedef signed __int8 sint8; +typedef signed __int16 sint16; +typedef signed __int32 sint32; + +typedef unsigned __int8 uint8; +typedef unsigned __int16 uint16; +typedef unsigned __int32 uint32; +////////////////////////////// +#else // GNU +////////////////////////////// +typedef char int8; +typedef short int16; +typedef int int32; + +typedef signed char sint8; +typedef signed short sint16; +typedef signed int sint32; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +////////////////////////////// +#endif +////////////////////////////// + +#undef UINT8_MIN +#undef UINT16_MIN +#undef UINT32_MIN +#define UINT8_MIN (uint8) 0 +#define UINT16_MIN (uint16)0 +#define UINT32_MIN (uint32)0 + +#undef UINT8_MAX +#undef UINT16_MAX +#undef UINT32_MAX +#define UINT8_MAX (uint8) 0xFF +#define UINT16_MAX (uint16)0xFFFF +#define UINT32_MAX (uint32)0xFFFFFFFF + +#undef SINT8_MIN +#undef SINT16_MIN +#undef SINT32_MIN +#define SINT8_MIN (sint8) 0x80 +#define SINT16_MIN (sint16)0x8000 +#define SINT32_MIN (sint32)0x80000000 + +#undef SINT8_MAX +#undef SINT16_MAX +#undef SINT32_MAX +#define SINT8_MAX (sint8) 0x7F +#define SINT16_MAX (sint16)0x7FFF +#define SINT32_MAX (sint32)0x7FFFFFFF + + +////////////////////////////////////////////////////////////////////////// +// 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) +// size_t already defined in stdio.h +////////////////////////////// +#ifdef WIN32 // 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 +////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// portable 64-bit integers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 int64; +typedef signed __int64 sint64; +typedef unsigned __int64 uint64; +#define LLCONST(a) (a##i64) +#else +typedef long long int64; +typedef signed long long sint64; +typedef unsigned long long uint64; +#define LLCONST(a) (a##ll) +#endif + +#ifndef INT64_MIN +#define INT64_MIN (LLCONST(-9223372036854775807)-1) +#endif +#ifndef INT64_MAX +#define INT64_MAX (LLCONST(9223372036854775807)) +#endif +#ifndef UINT64_MAX +#define UINT64_MAX (LLCONST(18446744073709551615u)) +#endif + + +////////////////////////////////////////////////////////////////////////// +// some redefine of function redefines for some Compilers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +// keyword replacement in windows +#ifdef _WIN32 +#define inline __inline +#endif + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef int bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not cplusplus +////////////////////////////// + +#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))) + +////////////////////////////////////////////////////////////////////////// +// should not happen +#ifndef NULL +#define NULL (void *)0 +#endif + +////////////////////////////////////////////////////////////////////////// +// number of bits in a byte +#ifndef NBBY +#define NBBY 8 +#endif + +#endif /* _CBASETYPES_H_ */ diff --git a/src/common/core.c b/src/common/core.c new file mode 100644 index 000000000..b0b847ca2 --- /dev/null +++ b/src/common/core.c @@ -0,0 +1,269 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include + +#include "core.h" +#include "../common/db.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/graph.h" +#include "../common/grfio.h" +#include "../common/plugins.h" +#include "../common/version.h" +#include "../common/showmsg.h" + +#ifndef _WIN32 + #include "svnversion.h" +#endif + +int runflag = 1; +int arg_c = 0; +char **arg_v = NULL; + +char *SERVER_NAME = NULL; +char SERVER_TYPE = ATHENA_SERVER_NONE; +static void (*term_func)(void) = NULL; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_termfunc(void (*termfunc)(void)) +{ + term_func = termfunc; +} + +#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 : 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(0); + runflag = 0; + 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: + ShowMessage ("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); + + // Signal to create coredumps by system when necessary (crash) + compat_signal(SIGSEGV, SIG_DFL); + compat_signal(SIGFPE, SIG_DFL); + compat_signal(SIGILL, SIG_DFL); + #ifndef _WIN32 + compat_signal(SIGXFSZ, sig_proc); + compat_signal(SIGPIPE, sig_proc); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); + #endif +} +#endif + +#ifdef SVNVERSION + #define xstringify(x) stringify(x) + #define stringify(x) #x + const char *get_svn_revision(void) + { + return xstringify(SVNVERSION); + } +#else +const char* get_svn_revision(void) +{ + static char version[10]; + FILE *fp; + + if ((fp = fopen(".svn/entries", "r")) != NULL) { + char line[1024]; + int rev; + while (fgets(line,1023,fp)) + if (strstr(line,"revision=")) break; + fclose(fp); + if (sscanf(line," %*[^\"]\"%d%*[^\n]", &rev) == 1) { + sprintf(version, "%d", rev); + return version; + } + } + + // if getting revision has failed + return "Unknown"; +} +#endif + +/*====================================== + * CORE : Display title + *-------------------------------------- + */ + +static void display_title(void) +{ + ClearScreen(); // clear screen and go up/left (0, 0 position in text) + ShowMessage(""CL_WTBL" (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)"CL_CLL""CL_NORMAL"\n"); // white writing (37) on blue background (44), \033[K clean until end of file + ShowMessage(""CL_XXBL" ("CL_BT_YELLOW" (c)2005 eAthena Development Team presents "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_XXBL" ("CL_BOLD" ______ __ __ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /\\ _ \\/\\ \\__/\\ \\ v%2d.%02d.%02d "CL_XXBL")"CL_CLL""CL_NORMAL"\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" __\\ \\ \\_\\ \\ \\ ,_\\ \\ \\___ __ ___ __ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /'__`\\ \\ __ \\ \\ \\/\\ \\ _ `\\ /'__`\\/' _ `\\ /'__`\\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /\\ __/\\ \\ \\/\\ \\ \\ \\_\\ \\ \\ \\ \\/\\ __//\\ \\/\\ \\/\\ \\_\\.\\_ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\ \\____\\\\ \\_\\ \\_\\ \\__\\\\ \\_\\ \\_\\ \\____\\ \\_\\ \\_\\ \\__/.\\_\\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\/____/ \\/_/\\/_/\\/__/ \\/_/\\/_/\\/____/\\/_/\\/_/\\/__/\\/_/ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" _ _ _ _ _ _ _ _ _ _ _ _ _ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" ( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_XXBL" ("CL_BT_YELLOW" Advanced Fusion Maps (c) 2003-2005 The Fusion Project "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_WTBL" (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)"CL_CLL""CL_NORMAL"\n\n"); // reset color + + ShowInfo("SVN Revision: '"CL_WHITE"%s"CL_RESET"'.\n", get_svn_revision()); +} + +// Warning if logged in as superuser (root) +void usercheck(void){ +#ifndef _WIN32 + if ((getuid() == 0) && (getgid() == 0)) { + ShowWarning ("You are running eAthena as the root superuser.\n"); + ShowWarning ("It is unnecessary and unsafe to run eAthena with root privileges.\n"); + sleep(3); + } +#endif +} + +/*====================================== + * CORE : MAINROUTINE + *-------------------------------------- + */ +#ifndef MINICORE // minimalist Core +int main (int argc, char **argv) +{ + int next; + + // initialise program arguments + { + char *p = SERVER_NAME = argv[0]; + while ((p = strchr(p, '/')) != NULL) + SERVER_NAME = ++p; + arg_c = argc; + arg_v = argv; + } + + set_server_type(); + display_title(); + usercheck(); + + malloc_init(); /* 一番最初に実行する必要がある */ + db_init(); + signals_init(); + + timer_init(); + socket_init(); + plugins_init(); + + do_init(argc,argv); + graph_init(); + plugin_event_trigger("Athena_Init"); + + while (runflag) { + next = do_timer(gettick_nocache()); + do_sendrecv(next); +#ifndef TURBO + do_parsepacket(); +#endif + } + + plugin_event_trigger("Athena_Final"); + graph_final(); + do_final(); + + timer_final(); + plugins_final(); + socket_final(); + db_final(); + malloc_final(); + + return 0; +} +#else +int main (int argc, char **argv) +{ + // initialise program arguments + { + char *p = SERVER_NAME = argv[0]; + while ((p = strchr(p, '/')) != NULL) + SERVER_NAME = ++p; + arg_c = argc; + arg_v = argv; + } + + display_title(); + usercheck(); + do_init(argc,argv); + do_final(); + + return 0; +} +#endif + +#ifdef BCHECK +unsigned int __invalid_size_argument_for_IOC; +#endif diff --git a/src/common/core.h b/src/common/core.h new file mode 100644 index 000000000..22f625a5d --- /dev/null +++ b/src/common/core.h @@ -0,0 +1,30 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CORE_H_ +#define _CORE_H_ + +//#define SQL_DEBUG //uncomment for debug_mysql_query instead of mysql_real_query + +/* REMOVED because these type of function defines with va_args are a GCC feature and won't compile under Windows [Skotlex] +//Added here, so its avail in 'all' files .. +#define eprintf(mes, args...) \ + fprintf(stderr, "%s:%d: "mes"", __FILE__, __LINE__, args); +#define eprint(mes) \ + fprintf(stderr, "%s:%d: "mes"", __FILE__, __LINE__); +*/ + +extern int arg_c; +extern char **arg_v; + +extern int runflag; +extern char *SERVER_NAME; +extern char SERVER_TYPE; + +extern const char *get_svn_revision(void); +extern int do_init(int,char**); +extern void set_server_type(void); +extern void set_termfunc(void (*termfunc)(void)); +extern void do_final(void); + +#endif // _CORE_H_ diff --git a/src/common/db.c b/src/common/db.c new file mode 100644 index 000000000..65abecea7 --- /dev/null +++ b/src/common/db.c @@ -0,0 +1,2344 @@ +/*****************************************************************************\ + * 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. * + * * + * Properties of the RED-BLACK trees being used: * + * 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 n node in a RED-BLACK tree has the property that its * + * height is O(lg(n)). * + * Another important property is that after adding a node to a RED-BLACK * + * tree, the tree can be readjusted in O(lg(n)) time. * + * Similarly, after deleting a node from a RED-BLACK tree, the tree can be * + * readjusted in O(lg(n)) time. * + * {@link http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic18/} * + * * + * How to add new database types: * + * 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 * + * - make data an enumeration * + * - 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: * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBInterface#ensure(DBInterface,DBKey,DBCreateData,...)} and * + * {@link DBInterface#clear(DBInterface,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.h * +\*****************************************************************************/ +#include +#include +#include + +#include "db.h" +#include "../common/mmo.h" +#include "../common/utils.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.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. * + * Database - 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 Database#ht + */ +#define HASH_SIZE (256+27) + +/** + * 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 Database#ht + */ +typedef struct dbn { + // Tree structure + struct dbn *parent; + struct dbn *left; + struct dbn *right; + // Node data + DBKey key; + void *data; + // Other + enum {RED, BLACK} 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 Database#free_list + */ +struct db_free { + DBNode node; + DBNode *root; +}; + +/** + * Complete database structure. + * @param dbi 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 common\db.h#DBInterface + * @see #HASH_SIZE + * @see #DBNode + * @see #struct db_free + * @see common\db.h#DBComparator(void *,void *) + * @see common\db.h#DBHasher(void *) + * @see common\db.h#DBReleaser(void *,void *,DBRelease) + * @see common\db.h#DBOptions + * @see common\db.h#DBType + * @see #db_alloc(const char *,int,DBOptions,DBType,...) + */ +typedef struct db { + // Database interface + struct dbt dbi; + // 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 + ERInterface nodes; + DBComparator cmp; + DBHasher hash; + DBReleaser release; + DBNode ht[HASH_SIZE]; + DBType type; + DBOptions options; + unsigned int item_count; + unsigned short maxlen; + unsigned global_lock : 1; +} *Database; + +#ifdef DB_ENABLE_STATS +/** + * Structure with what is counted when the database estatistics are enabled. + * @private + * @see #DB_ENABLE_STATS + * @see #stats + */ +static struct { + // Node alloc/free + unsigned int db_node_alloc; + unsigned int db_node_free; + // Database creating/destruction counters + unsigned int db_int_alloc; + unsigned int db_uint_alloc; + unsigned int db_string_alloc; + unsigned int db_istring_alloc; + unsigned int db_int_destroy; + unsigned int db_uint_destroy; + unsigned int db_string_destroy; + unsigned int db_istring_destroy; + // Function usage counters + unsigned int db_rotate_left; + unsigned int db_rotate_right; + unsigned int db_rebalance; + unsigned int db_rebalance_erase; + unsigned int db_is_key_null; + unsigned int db_dup_key; + unsigned int db_dup_key_free; + unsigned int db_free_add; + unsigned int db_free_remove; + unsigned int db_free_lock; + unsigned int db_free_unlock; + unsigned int db_int_cmp; + unsigned int db_uint_cmp; + unsigned int db_string_cmp; + unsigned int db_istring_cmp; + unsigned int db_int_hash; + unsigned int db_uint_hash; + unsigned int db_string_hash; + unsigned int db_istring_hash; + unsigned int db_release_nothing; + unsigned int db_release_key; + unsigned int db_release_data; + unsigned int db_release_both; + unsigned int db_get; + unsigned int db_getall; + unsigned int db_vgetall; + unsigned int db_ensure; + unsigned int db_vensure; + unsigned int db_put; + unsigned int db_remove; + unsigned int db_foreach; + unsigned int db_vforeach; + unsigned int db_clear; + unsigned int db_vclear; + unsigned int db_destroy; + unsigned int db_vdestroy; + unsigned int db_size; + unsigned int db_type; + unsigned int db_options; + unsigned int db_fix_options; + unsigned int db_default_cmp; + unsigned int db_default_hash; + unsigned int db_default_release; + unsigned int db_custom_release; + unsigned int db_alloc; + unsigned int db_i2key; + unsigned int db_ui2key; + unsigned int db_str2key; + unsigned int db_init; + unsigned int 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 +}; +#endif /* 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; + +#ifdef DB_ENABLE_STATS + if (stats.db_rotate_left != (unsigned int)~0) stats.db_rotate_left++; +#endif /* DB_ENABLE_STATS */ + // 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; + +#ifdef DB_ENABLE_STATS + if (stats.db_rotate_right != (unsigned int)~0) stats.db_rotate_right++; +#endif /* DB_ENABLE_STATS */ + // 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_put(DBInterface,DBKey,void *) + */ +static void db_rebalance(DBNode node, DBNode *root) +{ + DBNode y; + +#ifdef DB_ENABLE_STATS + if (stats.db_rebalance != (unsigned int)~0) stats.db_rebalance++; +#endif /* DB_ENABLE_STATS */ + // 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(Database) + */ +static void db_rebalance_erase(DBNode node, DBNode *root) +{ + DBNode y = node; + DBNode x = NULL; + DBNode x_parent = NULL; + DBNode w; + +#ifdef DB_ENABLE_STATS + if (stats.db_rebalance_erase != (unsigned int)~0) stats.db_rebalance_erase++; +#endif /* DB_ENABLE_STATS */ + // 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 + { + int 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 considerd to be NULL. + * @param type Type of database + * @param key Key being tested + * @return not 0 if considered NULL, 0 otherwise + * @private + * @see common\db.h#DBType + * @see common\db.h#DBKey + * @see #db_get(DBInterface,DBKey) + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_remove(DBInterface,DBKey) + */ +static int db_is_key_null(DBType type, DBKey key) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_is_key_null != (unsigned int)~0) stats.db_is_key_null++; +#endif /* DB_ENABLE_STATS */ + 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(Database,DBNode,DBNode *) + * @see #db_free_remove(Database,DBNode) + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_dup_key_free(Database,DBKey) + */ +static DBKey db_dup_key(Database db, DBKey key) +{ + unsigned char *str; + +#ifdef DB_ENABLE_STATS + if (stats.db_dup_key != (unsigned int)~0) stats.db_dup_key++; +#endif /* DB_ENABLE_STATS */ + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + if (db->maxlen) { + CREATE(str, unsigned char, db->maxlen +1); + memcpy(str, key.str, db->maxlen); + str[db->maxlen] = '\0'; + key.str = str; + } else { + key.str = (unsigned char *)aStrdup((const char *)key.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(Database,DBKey) + */ +static void db_dup_key_free(Database db, DBKey key) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_dup_key_free != (unsigned int)~0) stats.db_dup_key_free++; +#endif /* DB_ENABLE_STATS */ + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + aFree(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 Database#free_list + * @see Database#free_count + * @see Database#free_max + * @see #db_remove(DBInterface,DBKey) + * @see #db_free_remove(Database,DBNode) + */ +static void db_free_add(Database db, DBNode node, DBNode *root) +{ + DBKey old_key; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_add != (unsigned int)~0) stats.db_free_add++; +#endif /* DB_ENABLE_STATS */ + 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 shure 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 Database#free_list + * @see Database#free_count + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_free_add(Database,DBNode *,DBNode) + */ +static void db_free_remove(Database db, DBNode node) +{ + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_remove != (unsigned int)~0) stats.db_free_remove++; +#endif /* DB_ENABLE_STATS */ + 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 Database#free_lock + * @see #db_unlock(Database) + */ +static void db_free_lock(Database db) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_free_lock != (unsigned int)~0) stats.db_free_lock++; +#endif /* DB_ENABLE_STATS */ + 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 Database#free_lock + * @see #db_free_dbn(DBNode) + * @see #db_lock(Database) + */ +static void db_free_unlock(Database db) +{ + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_unlock != (unsigned int)~0) stats.db_free_unlock++; +#endif /* DB_ENABLE_STATS */ + 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); +#ifdef DB_ENABLE_STATS + if (stats.db_node_free != (unsigned int)~0) stats.db_node_free++; +#endif /* DB_ENABLE_STATS */ + 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. + * maxlen 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 common\db.h#DBKey + * @see common\db.h\DBType#DB_INT + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_int_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_int_cmp != (unsigned int)~0) stats.db_int_cmp++; +#endif /* DB_ENABLE_STATS */ + 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. + * maxlen 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 common\db.h#DBKey + * @see common\db.h\DBType#DB_UINT + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_uint_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_uint_cmp != (unsigned int)~0) stats.db_uint_cmp++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBKey + * @see common\db.h\DBType#DB_STRING + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_string_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_string_cmp != (unsigned int)~0) stats.db_string_cmp++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) maxlen = (unsigned short)~0; + 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 common\db.h#DBKey + * @see common\db.h\DBType#DB_ISTRING + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_istring_cmp != (unsigned int)~0) stats.db_istring_cmp++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) maxlen = (unsigned short)~0; + 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. + * maxlen is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_INT + * @see common\db.h#DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_int_hash(DBKey key, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_int_hash != (unsigned int)~0) stats.db_int_hash++; +#endif /* DB_ENABLE_STATS */ + return (unsigned int)key.i; +} + +/** + * Default hasher for DB_UINT databases. + * Just returns the value of the key. + * maxlen is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_UINT + * @see #db_default_hash(DBType) + */ +static unsigned int db_uint_hash(DBKey key, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_uint_hash != (unsigned int)~0) stats.db_uint_hash++; +#endif /* DB_ENABLE_STATS */ + return key.ui; +} + +/** + * Default hasher for DB_STRING databases. + * If maxlen if 0, the maximum number of maxlen is used instead. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_STRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_string_hash(DBKey key, unsigned short maxlen) +{ + unsigned char *k = key.str; + unsigned int hash = 0; + unsigned short i; + +#ifdef DB_ENABLE_STATS + if (stats.db_string_hash != (unsigned int)~0) stats.db_string_hash++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) + maxlen = (unsigned short)~0; // Maximum + + for (i = 0; *k; i++) { + hash = (hash*33 + *k++)^(hash>>24); + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Default hasher for DB_ISTRING databases. + * If maxlen if 0, the maximum number of maxlen is used instead. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_ISTRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_istring_hash(DBKey key, unsigned short maxlen) +{ + unsigned char *k = key.str; + unsigned int hash = 0; + unsigned short i; + +#ifdef DB_ENABLE_STATS + if (stats.db_istring_hash != (unsigned int)~0) stats.db_istring_hash++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) + maxlen = (unsigned short)~0; // Maximum + + for (i = 0; *k; i++) { + hash = (hash*33 + LOWER(*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 common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_releaser(DBType,DBOptions) + */ +static void db_release_nothing(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_nothing != (unsigned int)~0) stats.db_release_nothing++; +#endif /* DB_ENABLE_STATS */ +} + +/** + * 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 common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_key(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_key != (unsigned int)~0) stats.db_release_key++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_KEY) aFree(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 common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_data(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_data != (unsigned int)~0) stats.db_release_data++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_DATA) aFree(data); +} + +/** + * 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 common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_both(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_both != (unsigned int)~0) stats.db_release_both++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_KEY) aFree(key.str); // needs to be a pointer + if (which&DB_RELEASE_DATA) aFree(data); +} + +/*****************************************************************************\ + * (4) Section with protected functions used in the interface of the * + * database. * + * 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. * +\*****************************************************************************/ + +/** + * Get the data of the entry identifid 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 common\db.h#DBKey + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#get(DBInterface,DBKey) + */ +static void *db_obj_get(DBInterface self, DBKey key) +{ + Database db = (Database)self; + DBNode node; + int c; + void *data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_get != (unsigned int)~0) stats.db_get++; +#endif /* DB_ENABLE_STATS */ + 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 + } + + 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) { + data = node->data; + 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 match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max 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 common\db.h#DBInterface + * @see common\db.h#DBMatcher(DBKey key, void *data, va_list args) + * @see common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + */ +static unsigned int db_obj_vgetall(DBInterface self, void **buf, unsigned int max, DBMatcher match, va_list args) +{ + Database db = (Database)self; + unsigned int i; + DBNode node; + DBNode parent; + unsigned int ret = 0; + +#ifdef DB_ENABLE_STATS + if (stats.db_vgetall != (unsigned int)~0) stats.db_vgetall++; +#endif /* DB_ENABLE_STATS */ + 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) { + parent = node->parent; + if (!(node->deleted) && match(node->key, node->data, args) == 0) { + if (buf && ret < max) + buf[ret] = node->data; + ret++; + } + 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 common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list)}. + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max 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 common\db.h#DBMatcher(DBKey key, void *data, va_list args) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see common\db.h\DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + */ +static unsigned int db_obj_getall(DBInterface self, void **buf, unsigned int max, DBMatcher match, ...) +{ + va_list args; + unsigned int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_getall != (unsigned int)~0) stats.db_getall++; +#endif /* DB_ENABLE_STATS */ + 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 + * create. + * @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 common\db.h#DBKey + * @see common\db.h#DBCreateData + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + */ +static void *db_obj_vensure(DBInterface self, DBKey key, DBCreateData create, va_list args) +{ + Database db = (Database)self; + DBNode node; + DBNode parent = NULL; + unsigned int hash; + int c = 0; + void *data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_vensure != (unsigned int)~0) stats.db_vensure++; +#endif /* DB_ENABLE_STATS */ + 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 + } + + 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) { + if (db->item_count == (unsigned int)~0) { + ShowError("db_vensure: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } +#ifdef DB_ENABLE_STATS + if (stats.db_node_alloc != (unsigned int)~0) stats.db_node_alloc++; +#endif /* DB_ENABLE_STATS */ + 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 = create(key, args); + } + data = node->data; + db_free_unlock(db); + return data; +} + +/** + * Just calls {@link common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list)}. + * 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 + * create. + * @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 common\db.h#DBKey + * @see common\db.h#DBCreateData + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see common\db.h\DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + */ +static void *db_obj_ensure(DBInterface self, DBKey key, DBCreateData create, ...) +{ + va_list args; + void *ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_ensure != (unsigned int)~0) stats.db_ensure++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // 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. + * Returns the previous data if the entry exists or 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 + * @return The previous data if the entry exists or NULL + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBInterface + * @see #db_malloc_dbn(void) + * @see common\db.h\DBInterface#put(DBInterface,DBKey,void *) + */ +static void *db_obj_put(DBInterface self, DBKey key, void *data) +{ + Database db = (Database)self; + DBNode node; + DBNode parent = NULL; + int c = 0; + unsigned int hash; + void *old_data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_put != (unsigned int)~0) stats.db_put++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // 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 NULL; // 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 NULL; // nullpo candidate + } + if (!(data || db->options&DB_OPT_ALLOW_NULL_DATA)) { + ShowError("db_put: Attempted to use non-allowed NULL data for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->item_count == (unsigned int)~0) { + ShowError("db_put: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } + // 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); + } + old_data = node->data; + break; + } + parent = node; + if (c < 0) { + node = node->left; + } else { + node = node->right; + } + } + // allocate a new node if necessary + if (node == NULL) { +#ifdef DB_ENABLE_STATS + if (stats.db_node_alloc != (unsigned int)~0) stats.db_node_alloc++; +#endif /* DB_ENABLE_STATS */ + 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_free_unlock(db); + return old_data; +} + +/** + * Remove an entry from the database. + * Returns the data of the entry. + * NOTE: The key (of the database) is released in {@link #db_free_add(Database,DBNode,DBNode *)}. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return The data of the entry or NULL if not found + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBInterface + * @see #db_free_add(Database,DBNode,DBNode *) + * @see common\db.h\DBInterface#remove(DBInterface,DBKey) + */ +static void *db_obj_remove(DBInterface self, DBKey key) +{ + Database db = (Database)self; + void *data = NULL; + DBNode node; + unsigned int hash; + int c = 0; + +#ifdef DB_ENABLE_STATS + if (stats.db_remove != (unsigned int)~0) stats.db_remove++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // 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 NULL; // 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 NULL; // 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)) { + data = node->data; + 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 data; +} + +/** + * Apply func 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 args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see common\db.h#DBInterface + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list) + */ +static int db_obj_vforeach(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + unsigned int i; + int sum = 0; + DBNode node; + DBNode parent; + +#ifdef DB_ENABLE_STATS + if (stats.db_vforeach != (unsigned int)~0) stats.db_vforeach++; +#endif /* DB_ENABLE_STATS */ + 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) { + parent = node->parent; + if (!(node->deleted)) + sum += func(node->key, node->data, args); + 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 common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list)}. + * Apply func 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 common\db.h#DBInterface + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#foreach(DBInterface,DBApply,...) + */ +static int db_obj_foreach(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_foreach != (unsigned int)~0) stats.db_foreach++; +#endif /* DB_ENABLE_STATS */ + 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 applyed 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 applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list) + */ +static int db_obj_vclear(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + int sum = 0; + unsigned int i; + DBNode node; + DBNode parent; + +#ifdef DB_ENABLE_STATS + if (stats.db_vclear != (unsigned int)~0) stats.db_vclear++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + 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) + sum += func(node->key, node->data, args); + db->release(node->key, node->data, DB_RELEASE_BOTH); + node->deleted = 1; + } +#ifdef DB_ENABLE_STATS + if (stats.db_node_free != (unsigned int)~0) stats.db_node_free++; +#endif /* DB_ENABLE_STATS */ + ers_free(db->nodes, node); + if (parent) { + if (parent->left == node) + parent->left = NULL; + else + parent->right = NULL; + } + node = parent; + } + db->ht[i] = NULL; + } + db->free_count = 0; + db->item_count = 0; + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list)}. + * Removes all entries from the database. + * Before deleting an entry, func is applyed 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 applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#clear(DBInterface,DBApply,...) + */ +static int db_obj_clear(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_clear != (unsigned int)~0) stats.db_clear++; +#endif /* DB_ENABLE_STATS */ + 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 applyed 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 applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vdestroy(DBInterface,DBApply,va_list) + */ +static int db_obj_vdestroy(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + int sum; + +#ifdef DB_ENABLE_STATS + if (stats.db_vdestroy != (unsigned int)~0) stats.db_vdestroy++; +#endif /* DB_ENABLE_STATS */ + 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->alloc_file, db->alloc_line, db->free_lock); + +#ifdef DB_ENABLE_STATS + switch (db->type) { + case DB_INT: + stats.db_int_destroy++; + break; + case DB_UINT: + stats.db_uint_destroy++; + break; + case DB_STRING: + stats.db_string_destroy++; + break; + case DB_ISTRING: + stats.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 common\db.h\DBInterface#db_vdestroy(DBInterface,DBApply,va_list)}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed 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 Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#destroy(DBInterface,DBApply,...) + */ +static int db_obj_destroy(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_destroy != (unsigned int)~0) stats.db_destroy++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBInterface + * @see Database#item_count + * @see common\db.h\DBInterface#size(DBInterface) + */ +static unsigned int db_obj_size(DBInterface self) +{ + Database db = (Database)self; + unsigned int item_count; + +#ifdef DB_ENABLE_STATS + if (stats.db_size != (unsigned int)~0) stats.db_size++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBType + * @see common\db.h#DBInterface + * @see Database#type + * @see common\db.h\DBInterface#type(DBInterface) + */ +static DBType db_obj_type(DBInterface self) +{ + Database db = (Database)self; + DBType type; + +#ifdef DB_ENABLE_STATS + if (stats.db_type != (unsigned int)~0) stats.db_type++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return -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 common\db.h#DBOptions + * @see common\db.h#DBInterface + * @see Database#options + * @see common\db.h\DBInterface#options(DBInterface) + */ +static DBOptions db_obj_options(DBInterface self) +{ + Database db = (Database)self; + DBOptions options; + +#ifdef DB_ENABLE_STATS + if (stats.db_options != (unsigned int)~0) stats.db_options++; +#endif /* DB_ENABLE_STATS */ + 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_init - Initialize the database system. * + * db_final - Finalize 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 common\db.h#DBType + * @see common\db.h#DBOptions + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + * @see common\db.h#db_fix_options(DBType,DBOptions) + */ +DBOptions db_fix_options(DBType type, DBOptions options) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_fix_options != (unsigned int)~0) stats.db_fix_options++; +#endif /* DB_ENABLE_STATS */ + switch (type) { + case DB_INT: + case DB_UINT: // Numeric database, do nothing with the keys + return 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 common\db.h#DBType + * @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) + * @see common\db.h#db_default_cmp(DBType) + */ +DBComparator db_default_cmp(DBType type) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_cmp != (unsigned int)~0) stats.db_default_cmp++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBType + * @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) + * @see common\db.h#db_default_hash(DBType) + */ +DBHasher db_default_hash(DBType type) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_hash != (unsigned int)~0) stats.db_default_hash++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBType + * @see common\db.h#DBOptions + * @see common\db.h#DBReleaser + * @see #db_release_nothing(DBKey,void *,DBRelease) + * @see #db_release_key(DBKey,void *,DBRelease) + * @see #db_release_data(DBKey,void *,DBRelease) + * @see #db_release_both(DBKey,void *,DBRelease) + * @see #db_custom_release(DBRelease) + * @see common\db.h#db_default_release(DBType,DBOptions) + */ +DBReleaser db_default_release(DBType type, DBOptions options) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_release != (unsigned int)~0) stats.db_default_release++; +#endif /* DB_ENABLE_STATS */ + 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 common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_release_nothing(DBKey,void *,DBRelease) + * @see #db_release_key(DBKey,void *,DBRelease) + * @see #db_release_data(DBKey,void *,DBRelease) + * @see #db_release_both(DBKey,void *,DBRelease) + * @see #db_default_release(DBType,DBOptions) + * @see common\db.h#db_custom_release(DBRelease) + */ +DBReleaser db_custom_release(DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_custom_release != (unsigned int)~0) stats.db_custom_release++; +#endif /* DB_ENABLE_STATS */ + 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 + * @return The interface of the database + * @public + * @see common\db.h#DBType + * @see common\db.h#DBInterface + * @see #Database + * @see #db_fix_options(DBType,DBOptions) + * @see common\db.h#db_alloc(const char *,int,DBType,unsigned short) + */ +DBInterface db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen) +{ + Database db; + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_alloc != (unsigned int)~0) stats.db_alloc++; + switch (type) { + case DB_INT: + stats.db_int_alloc++; + break; + case DB_UINT: + stats.db_uint_alloc++; + break; + case DB_STRING: + stats.db_string_alloc++; + break; + case DB_ISTRING: + stats.db_istring_alloc++; + break; + } +#endif /* DB_ENABLE_STATS */ + CREATE(db, struct db, 1); + + options = db_fix_options(type, options); + /* Interface of the database */ + db->dbi.get = db_obj_get; + db->dbi.getall = db_obj_getall; + db->dbi.vgetall = db_obj_vgetall; + db->dbi.ensure = db_obj_ensure; + db->dbi.vensure = db_obj_vensure; + db->dbi.put = db_obj_put; + db->dbi.remove = db_obj_remove; + db->dbi.foreach = db_obj_foreach; + db->dbi.vforeach = db_obj_vforeach; + db->dbi.clear = db_obj_clear; + db->dbi.vclear = db_obj_vclear; + db->dbi.destroy = db_obj_destroy; + db->dbi.vdestroy = db_obj_vdestroy; + db->dbi.size = db_obj_size; + db->dbi.type = db_obj_type; + db->dbi.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((uint32)sizeof(struct dbn)); + 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->type = type; + db->options = options; + db->item_count = 0; + db->maxlen = maxlen; + db->global_lock = 0; + + return &db->dbi; +} + +#ifdef DB_MANUAL_CAST_TO_UNION +/** + * Manual cast from 'int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + * @see common\db.h#db_i2key(int) + */ +DBKey db_i2key(int key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_i2key != (unsigned int)~0) stats.db_i2key++; +#endif /* DB_ENABLE_STATS */ + ret.i = key; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_str2key(unsigned char *) + * @see common\db.h#db_ui2key(unsigned int) + */ +DBKey db_ui2key(unsigned int key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_ui2key != (unsigned int)~0) stats.db_ui2key++; +#endif /* DB_ENABLE_STATS */ + ret.ui = key; + return ret; +} + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see common\db.h#db_str2key(unsigned char *) + */ +DBKey db_str2key(unsigned char *key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_str2key != (unsigned int)~0) stats.db_str2key++; +#endif /* DB_ENABLE_STATS */ + ret.str = key; + return ret; +} +#endif /* DB_MANUAL_CAST_TO_UNION */ + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + * @see common\db.h#db_init(void) + */ +void db_init(void) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_init != (unsigned int)~0) stats.db_init++; +#endif /* DB_ENABLE_STATS */ +} + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see common\db.h#DB_FINAL_NODE_CHECK + * @see #db_init(void) + * @see common\db.h#db_final(void) + */ +void db_final(void) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_final != (unsigned int)~0) + stats.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" + "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_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.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_init, stats.db_final); +#endif /* DB_ENABLE_STATS */ +} + diff --git a/src/common/db.h b/src/common/db.h new file mode 100644 index 000000000..dcc583bfa --- /dev/null +++ b/src/common/db.h @@ -0,0 +1,734 @@ +/*****************************************************************************\ + * 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 * + * * + * Notes on the release system: * + * 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 an enum for the data (with int, unsigned int and void *) * + * - create a custom database allocator * + * - see what functions need or should be added to the database interface * + * * + * HISTORY: * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBInterface#ensure(DBInterface,DBKey,DBCreateData,...)} and * + * {@link DBInterface#clear(DBInterface,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 + +/*****************************************************************************\ + * (1) Section with public typedefs, enums, unions, structures and defines. * + * DB_MANUAL_CAST_TO_UNION - Define when the compiler doesn't allow casting * + * to unions. * + * DBRelease - Enumeration of release options. * + * DBType - Enumeration of database types. * + * DBOptions - Bitfield enumeration of database options. * + * DBKey - Union of used key types. * + * DBApply - Format of functions applyed to the databases. * + * DBMatcher - Format of matchers used in DBInterface->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. * + * DBInterface - Structure of the interface of the database. * +\*****************************************************************************/ + +/** + * Define this to enable the functions that cast to unions. + * Required when the compiler doesn't support casting to unions. + * NOTE: It is recommened that the conditional tests to determine if this + * should be defined be located in a makefile or a header file specific for + * of compatibility and portability issues. + * @public + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + */ +//#define DB_MANUAL_CAST_TO_UNION + +/** + * 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 { + 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 { + 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 DBInterface->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 { + 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 #DBApply(DBKey,void *,va_list) + * @see #DBMatcher(DBKey,void *,va_list) + * @see #DBComparator(DBKey,DBKey,unsigned short) + * @see #DBHasher(DBKey,unsigned short) + * @see #DBReleaser(DBKey,void *,DBRelease) + * @see DBInterface#get(DBInterface,DBKey) + * @see DBInterface#put(DBInterface,DBKey,void *) + * @see DBInterface#remove(DBInterface,DBKey) + */ +typedef union { + int i; + unsigned int ui; + unsigned char *str; +} DBKey; + +/** + * Format of funtions 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 funtion + * @return Data identified by the key to be put in the database + * @public + * @see #DBKey + * @see DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + */ +typedef void *(*DBCreateData)(DBKey key, va_list args); + +/** + * Format of functions to be applyed to an unspecified quantity of entries of + * a database. + * Any function that applyes 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 funtion + * @return Value to be added up by the funtion that is applying this + * @public + * @see #DBKey + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + */ +typedef int (*DBApply)(DBKey key, void *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 #DBKey + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatcher,...) + */ +typedef int (*DBMatcher)(DBKey key, void *data, va_list args); + +/** + * Format of the comparators used internally by the database system. + * Compares key1 to key2. + * maxlen is the maximum number of character used in DB_STRING and + * DB_ISTRING databases. If 0, the maximum number of maxlen is used (64K). + * 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 #DBKey + * @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. + * maxlen is the maximum number of character used in DB_STRING and + * DB_ISTRING databases. If 0, the maximum number of maxlen is used (64K). + * @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 #DBKey + * @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 #DBKey + * @see #db_default_releaser(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +typedef void (*DBReleaser)(DBKey key, void *data, DBRelease which); + +/** + * Public interface of a database. Only contains funtions. + * All the functions take the interface as the first argument. + * @public + * @see DBInterface#get(DBInterface,DBKey) + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + * @see DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see DBInterface#put(DBInterface,DBKey,void *) + * @see DBInterface#remove(DBInterface,DBKey) + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + * @see DBInterface#destroy(DBInterface,DBApply,va_list) + * @see DBInterface#size(DBInterface) + * @see DBInterface#type(DBInterface) + * @see DBInterface#options(DBInterface) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef struct dbt { + + /** + * Get the data of the entry identifid by the key. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_get(DBInterface,DBKey) + */ + void *(*get)(struct dbt *dbi, DBKey key); + + /** + * Just calls {@link DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list)}. + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param dbi 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 #DBMatcher(DBKey key, void *data, va_list args) + * @see #DBInterface + * @see DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see common\db.c#db_getall(DBInterface,void **,unsigned int,DBMatch,...) + */ + unsigned int (*getall)(struct dbt *dbi, void **buf, unsigned int max, DBMatcher match, ...); + + /** + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param dbi 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 #DBMatcher(DBKey key, void *data, va_list args) + * @see #DBInterface + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + * @see common\db.c#db_vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + */ + unsigned int (*vgetall)(struct dbt *dbi, void **buf, unsigned int max, DBMatcher match, va_list args); + + /** + * Just calls {@link common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list)}. + * 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 + * create. + * @param dbi 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 #DBKey + * @see #DBCreateData + * @see #DBInterface + * @see DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see common\db.c#db_ensure(DBInterface,DBKey,DBCreateData,...) + */ + void *(*ensure)(struct dbt *dbi, 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 + * create. + * @param dbi 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 #DBKey + * @see #DBCreateData + * @see #DBInterface + * @see DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + * @see common\db.c#db_vensure(DBInterface,DBKey,DBCreateData,va_list) + */ + void *(*vensure)(struct dbt *dbi, DBKey key, DBCreateData create, va_list args); + + /** + * Put the data identified by the key in the database. + * Returns the previous data if the entry exists or NULL. + * NOTE: Uses the new key, the old one is released. + * @param dbi Interface of the database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @return The previous data if the entry exists or NULL + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_put(DBInterface,DBKey,void *) + */ + void *(*put)(struct dbt *dbi, DBKey key, void *data); + + /** + * Remove an entry from the database. + * Returns the data of the entry. + * NOTE: The key (of the database) is released. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @return The data of the entry or NULL if not found + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_remove(DBInterface,DBKey) + */ + void *(*remove)(struct dbt *dbi, DBKey key); + + /** + * Just calls {@link DBInterface#vforeach(DBInterface,DBApply,va_list)}. + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param dbi 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 #DBInterface + * @see #DBApply(DBKey,void *,va_list) + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see common\db.c#db_foreach(DBInterface,DBApply,...) + */ + int (*foreach)(struct dbt *dbi, DBApply func, ...); + + /** + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param dbi Interface of the database + * @param func Function to be applyed + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see common\db.c#db_vforeach(DBInterface,DBApply,va_list) + */ + int (*vforeach)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Just calls {@link DBInterface#vclear(DBInterface,DBApply,va_list)}. + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#vclear(DBInterface,DBApply,va_list) + * @see common\db.c#db_clear(DBInterface,DBApply,...) + */ + int (*clear)(struct dbt *dbi, DBApply func, ...); + + /** + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#clear(DBInterface,DBApply,...) + * @see common\db.c#vclear(DBInterface,DBApply,va_list) + */ + int (*vclear)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Just calls {@link DBInterface#vdestroy(DBInterface,DBApply,va_list)}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed 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 dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see common\db.c#db_destroy(DBInterface,DBApply,...) + */ + int (*destroy)(struct dbt *dbi, DBApply func, ...); + + /** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed 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 dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBInterface + * @see #DBApply(DBKey,void *,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + * @see common\db.c#db_vdestroy(DBInterface,DBApply,va_list) + */ + int (*vdestroy)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Return the size of the database (number of items in the database). + * @param dbi Interface of the database + * @return Size of the database + * @protected + * @see #DBInterface + * @see common\db.c#db_size(DBInterface) + */ + unsigned int (*size)(struct dbt *dbi); + + /** + * Return the type of the database. + * @param dbi Interface of the database + * @return Type of the database + * @protected + * @see #DBType + * @see #DBInterface + * @see common\db.c#db_type(DBInterface) + */ + DBType (*type)(struct dbt *dbi); + + /** + * Return the options of the database. + * @param dbi Interface of the database + * @return Options of the database + * @protected + * @see #DBOptions + * @see #DBInterface + * @see common\db.c#db_options(DBInterface) + */ + DBOptions (*options)(struct dbt *dbi); + +} *DBInterface; + +//For easy access to the common functions. +#ifdef DB_MANUAL_CAST_TO_UNION +# define i2key db_i2key +# define ui2key db_ui2key +# define str2key db_str2key +#else /* not DB_MANUAL_CAST_TO_UNION */ +# define i2key(k) ((DBKey)(int)(k)) +# define ui2key(k) ((DBKey)(unsigned int)(k)) +# define str2key(k) ((DBKey)(unsigned char *)(k)) +#endif /* DB_MANUAL_CAST_TO_UNION / not DB_MANUAL_CAST_TO_UNION */ + +#define db_get(db,k) (db)->get((db),(k)) +#define idb_get(db,k) (db)->get((db),i2key(k)) +#define uidb_get(db,k) (db)->get((db),ui2key(k)) +#define strdb_get(db,k) (db)->get((db),str2key(k)) + +#define db_put(db,k,d) (db)->put((db),(k),(d)) +#define idb_put(db,k,d) (db)->put((db),i2key(k),(d)) +#define uidb_put(db,k,d) (db)->put((db),ui2key(k),(d)) +#define strdb_put(db,k,d) (db)->put((db),str2key(k),(d)) + +#define db_remove(db,k) (db)->remove((db),(k)) +#define idb_remove(db,k) (db)->remove((db),i2key(k)) +#define uidb_remove(db,k) (db)->remove((db),ui2key(k)) +#define strdb_remove(db,k) (db)->remove((db),str2key(k)) + +//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)->ensure((db),(k),f) +#define idb_ensure(db,k,f) (db)->ensure((db),i2key(k),f) +#define uidb_ensure(db,k,f) (db)->ensure((db),ui2key(k),f) +#define strdb_ensure(db,k,f) (db)->ensure((db),str2key(k),f) + +/*****************************************************************************\ + * (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_init - Initialise the database system. * + * db_final - Finalise 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) + * @see common\db.c#db_fix_options(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 + * @see common\db.c#db_default_cmp(DBType) + */ +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 + * @see common\db.c#db_default_hash(DBType) + */ +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) + * @see common\db.c#db_default_release(DBType,DBOptions) + */ +DBReleaser db_default_release(DBType type, DBOptions options); + +/** + * Returns the releaser that behaves as which 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) + * @see common\db.c#db_custom_release(DBRelease) + */ +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 + * @return The interface of the database + * @public + * @see #DBType + * @see #DBInterface + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_fix_options(DBType,DBOptions) + * @see common\db.c#db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +DBInterface db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen); + +#ifdef DB_MANUAL_CAST_TO_UNION +/** + * Manual cast from 'int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + * @see common\db.c#db_i2key(int) + */ +DBKey db_i2key(int key); + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_str2key(unsigned char *) + * @see common\db.c#db_ui2key(unsigned int) + */ +DBKey db_ui2key(unsigned int key); + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see common\db.c#db_str2key(unsigned char *) + */ +DBKey db_str2key(unsigned char *key); +#endif /* DB_MANUAL_CAST_TO_UNION */ + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + * @see common\db.c#db_init(void) + */ +void db_init(void); + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see #db_init(void) + * @see common\db.c#db_final(void) + */ +void db_final(void); + +#endif diff --git a/src/common/ers.c b/src/common/ers.c new file mode 100644 index 000000000..b54d22977 --- /dev/null +++ b/src/common/ers.c @@ -0,0 +1,532 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + *

Entry Reusage System

* + * * + * 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. * + * * + *

Advantages:

* + * - 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. * + * * + *

Disavantages:

* + * - 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 * + * @see common#ers.h * +\*****************************************************************************/ +#include + +#include "ers.h" +#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree +#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL + +#ifndef DISABLE_ERS +/*****************************************************************************\ + * (1) Private defines, structures and global variables. * + * ERS_BLOCK_ENTRIES - Number of entries in each block. * + * ERS_ROOT_SIZE - Maximum number of root entry managers. * + * ERLinkedList - Structure of a linked list of reusable entries. * + * ERSystem - Class of an entry manager. * + * ers_root - Array of root entry managers. * + * ers_num - Number of root entry managers in the array. * +\*****************************************************************************/ + +/** + * Number of entries in each block. + * @private + * @see #ers_obj_alloc_entry(ERInterface eri) + */ +#define ERS_BLOCK_ENTRIES 4096 + +/** + * Maximum number of root entry managers. + * @private + * @see #ers_root + * @see #ers_num + */ +#define ERS_ROOT_SIZE 256 + +/** + * Linked list of reusable entries. + * The minimum size of the entries is the size of this structure. + * @private + * @see ERSystem#reuse + */ +typedef struct ers_ll { + struct ers_ll *next; +} *ERLinkedList; + +/** + * Class of the object that manages entries of a certain size. + * @param eri Public interface of the object + * @param reuse Linked list of reusable data entries + * @param blocks Array with blocks of entries + * @param free Number of unused entries in the last block + * @param num Number of blocks in the array + * @param max Current maximum capacity of the array + * @param destroy Destroy lock + * @param size Size of the entries of the manager + * @private + */ +typedef struct ers { + + /** + * 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 + * @public + * @see #ERSystem + * @see common\ers.h#ERInterface + */ + struct eri eri; + + /** + * Linked list of reusable entries. + * @private + * @see #ERSystem + */ + ERLinkedList reuse; + + /** + * Array with blocks of entries. + * @private + * @see #ERSystem + */ + uint8 **blocks; + + /** + * Number of unused entries in the last block. + * @private + * @see #ERSystem + */ + uint32 free; + + /** + * Number of blocks in the array. + * @private + * @see #ERSystem + */ + uint32 num; + + /** + * Current maximum capacity of the array. + * @private + * @see #ERSystem + */ + uint32 max; + + /** + * Destroy lock. + * @private + * @see #ERSystem + */ + uint32 destroy; + + /** + * Size of the entries of the manager. + * @private + * @see #ERSystem + */ + uint32 size; + +} *ERSystem; + +/** + * Root array with entry managers. + * @private + * @static + * @see #ERS_ROOT_SIZE + * @see #ers_num + */ +static ERSystem ers_root[ERS_ROOT_SIZE]; + +/** + * Number of entry managers in the root array. + * @private + * @static + * @see #ERS_ROOT_SIZE + * @see #ers_root + */ +static uint32 ers_num = 0; + +/*****************************************************************************\ + * (2) Protected functions. * + * ers_obj_alloc_entry - Allocate an entry from the manager. * + * ers_obj_free_entry - Free an entry allocated from the manager. * + * ers_obj_entry_size - Return the size of the entries of the manager. * + * ers_obj_destroy - Destroy the instance of the manager. * +\*****************************************************************************/ + +/** + * 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 + * @protected + * @see #ERS_BLOCK_ENTRIES + * @see #ERLinkedList + * @see #ERSystem + * @see common\ers.h\ERInterface#alloc(ERInterface) + */ +static void *ers_obj_alloc_entry(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + void *ret; + + if (obj == NULL) { + ShowError("ers_obj_alloc_entry: NULL object, aborting entry allocation.\n"); + return NULL; + } + + if (obj->reuse) { // Reusable entry + ret = obj->reuse; + obj->reuse = obj->reuse->next; + } else if (obj->free) { // Unused entry + obj->free--; + ret = &obj->blocks[obj->num -1][obj->free*obj->size]; + } else { // allocate a new block + if (obj->num == obj->max) { // expand the block array + if (obj->max == UINT32_MAX) { // No more space for blocks + ShowFatalError("ers_obj_alloc_entry: maximum number of blocks reached, increase ERS_BLOCK_ENTRIES.\n" + "exiting the program...\n"); + exit(EXIT_FAILURE); + } + obj->max = (obj->max<<2) +3; // = obj->max*4 +3; - overflow won't happen + RECREATE(obj->blocks, uint8 *, obj->max); + } + CREATE(obj->blocks[obj->num], uint8, obj->size*ERS_BLOCK_ENTRIES); + obj->free = ERS_BLOCK_ENTRIES -1; + ret = &obj->blocks[obj->num][obj->free*obj->size]; + obj->num++; + } + return ret; +} + +/** + * 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 + * @protected + * @see #ERLinkedList + * @see #ERSystem + * @see ERSystem#reuse + * @see common\ers.h\ERInterface#free(ERInterface,void *) + */ +static void ers_obj_free_entry(ERInterface self, void *entry) +{ + ERSystem obj = (ERSystem)self; + ERLinkedList reuse; + + if (obj == 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 = (ERLinkedList)entry; + reuse->next = obj->reuse; + obj->reuse = reuse; +} + +/** + * 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 + * @protected + * @see #ERSystem + * @see ERSystem#size + * @see common\ers.h\ERInterface#enty_size(ERInterface) + */ +static uint32 ers_obj_entry_size(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + + if (obj == NULL) { + ShowError("ers_obj_entry_size: NULL object, returning 0.\n"); + return 0; + } + + return obj->size; +} + +/** + * 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 + * @protected + * @see #ERLinkedList + * @see #ERSystem + * @see common\ers.h\ERInterface#destroy(ERInterface) + */ +static void ers_obj_destroy(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + ERLinkedList reuse; + uint32 i, count; + + if (obj == NULL) { + ShowError("ers_obj_destroy: NULL object, aborting instance destruction.\n"); + return; + } + + obj->destroy--; + if (obj->destroy) + return; // Not last instance + + // Remove manager from root array + for (i = 0; i < ers_num; i++) { + if (ers_root[i] == obj) { + ers_num--; + if (i < ers_num) // put the last manager in the free slot + ers_root[i] = ers_root[ers_num]; + break; + } + } + reuse = obj->reuse; + count = 0; + // Check for missing/extra entries + for (i = 0; i < obj->num; i++) { + if (i == 0) { + count = ERS_BLOCK_ENTRIES -obj->free; + } else if (count > UINT32_MAX -ERS_BLOCK_ENTRIES) { + count = UINT32_MAX; + break; + } else { + count += ERS_BLOCK_ENTRIES; + } + while (reuse && count) { + count--; + reuse = reuse->next; + } + } + if (count) { // missing entries + ShowWarning("ers_obj_destroy: %u entries missing, continuing destruction.\n" + "Manager for entries of size %u.\n", + count, obj->size); + } else if (reuse) { // extra entries + while (reuse && count != UINT32_MAX) { + count++; + reuse = reuse->next; + } + ShowWarning("ers_obj_destroy: %u extra entries found, continuing destruction.\n" + "Manager for entries of size %u.\n", + count, obj->size); + } + // destroy the entry manager + if (obj->max) { + for (i = 0; i < obj->num; i++) + aFree(obj->blocks[i]); // release block of entries + aFree(obj->blocks); // release array of blocks + } + aFree(obj); // release manager +} + +/*****************************************************************************\ + * (3) Public functions. * + * ers_new - Get a new 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. * +\*****************************************************************************/ + +/** + * 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 + * @public + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ERInterface + * @see common\ers.h\ERInterface#destroy(ERInterface) + * @see common\ers.h#ers_new_(uint32) + */ +ERInterface ers_new(uint32 size) +{ + ERSystem obj; + uint32 i; + + if (size == 0) { + ShowError("ers_new: invalid size %u, aborting instance creation.\n", + size); + return NULL; + } + + if (size < sizeof(struct ers_ll)) // Minimum size + size = sizeof(struct ers_ll); + if (size%ERS_ALIGNED) // Align size + size += ERS_ALIGNED -size%ERS_ALIGNED; + + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + if (obj->size == size) { + // found a manager that handles the entry size + obj->destroy++; + return &obj->eri; + } + } + // create a new manager to handle the entry size + if (ers_num == ERS_ROOT_SIZE) { + ShowFatalError("ers_alloc: too many root objects, increase ERS_ROOT_SIZE.\n" + "exiting the program...\n"); + exit(EXIT_FAILURE); + } + obj = (ERSystem)aMalloc(sizeof(struct ers)); + // Public interface + obj->eri.alloc = ers_obj_alloc_entry; + obj->eri.free = ers_obj_free_entry; + obj->eri.entry_size = ers_obj_entry_size; + obj->eri.destroy = ers_obj_destroy; + // Block reusage system + obj->reuse = NULL; + obj->blocks = NULL; + obj->free = 0; + obj->num = 0; + obj->max = 0; + obj->destroy = 1; + // Properties + obj->size = size; + ers_root[ers_num++] = obj; + return &obj->eri; +} + +/** + * 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. + * @public + * @see #ERLinkedList + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ers_report(void) + */ +void ers_report(void) +{ + uint32 i, j, used, reusable, extra; + ERLinkedList reuse; + ERSystem obj; + + // Root system report + ShowMessage(CL_BOLD"Entry Reusage System report:\n"CL_NORMAL); + ShowMessage("root array size : %u\n", ERS_ROOT_SIZE); + ShowMessage("root entry managers : %u\n", ers_num); + ShowMessage("entries per block : %u\n", ERS_BLOCK_ENTRIES); + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + reuse = obj->reuse; + used = 0; + reusable = 0; + // Count used and reusable entries + for (j = 0; j < obj->num; j++) { + if (j == 0) { // take into acount the free entries + used = ERS_BLOCK_ENTRIES -obj->free; + } else if (reuse) { // counting reusable entries + used = ERS_BLOCK_ENTRIES; + } else { // no more reusable entries, count remaining used entries + for (; j < obj->num; j++) { + if (used > UINT32_MAX -ERS_BLOCK_ENTRIES) { // overflow + used = UINT32_MAX; + break; + } + used += ERS_BLOCK_ENTRIES; + } + break; + } + while (used && reuse) { // count reusable entries + used--; + if (reusable != UINT32_MAX) + reusable++; + reuse = reuse->next; + } + } + // Count extra reusable entries + extra = 0; + while (reuse && extra != UINT32_MAX) { + extra++; + reuse = reuse->next; + } + // Entry manager report + ShowMessage(CL_BOLD"[Entry manager #%u report]\n"CL_NORMAL, i); + ShowMessage("\tinstances : %u\n", obj->destroy); + ShowMessage("\tentry size : %u\n", obj->size); + ShowMessage("\tblock array size : %u\n", obj->max); + ShowMessage("\tallocated blocks : %u\n", obj->num); + ShowMessage("\tentries being used : %u\n", used); + ShowMessage("\tunused entries : %u\n", obj->free); + ShowMessage("\treusable entries : %u\n", reusable); + if (extra) + ShowMessage("\tWARNING - %u extra reusable entries were found.\n", extra); + } + ShowMessage("End of report\n"); +} + +/** + * 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. + * @public + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ers_force_destroy_all(void) + */ +void ers_force_destroy_all(void) +{ + uint32 i, j; + ERSystem obj; + + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + if (obj->max) { + for (j = 0; j < obj->num; j++) + aFree(obj->blocks[j]); // block of entries + aFree(obj->blocks); // array of blocks + } + aFree(obj); // entry manager object + } + ers_num = 0; +} +#endif /* not DISABLE_ERS */ + diff --git a/src/common/ers.h b/src/common/ers.h new file mode 100644 index 000000000..a512f6365 --- /dev/null +++ b/src/common/ers.h @@ -0,0 +1,193 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + *

Entry Reusage System

* + * * + * 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. * + * * + *

Advantages:

* + * - 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. * + * * + *

Disavantages:

* + * - 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 * + * @see common#ers.c * +\*****************************************************************************/ +#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. * + * ERInterface - Interface of the 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, + * @public + */ +//#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. + * @public + * @see #ers_new(uint32) + */ +#ifndef ERS_ALIGNED +# define ERS_ALIGNED 1 +#endif /* not ERS_ALIGN_ENTRY */ + +/** + * 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 + * @public + * @see #ers_new(uint32) + */ +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 + * @protected + * @see #ERInterface + * @see ERInterface#free(ERInterface,void *) + */ + 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 + * @protected + * @see #ERInterface + * @see ERInterface#alloc(ERInterface) + */ + 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 + * @protected + * @see #ERInterface + */ + uint32 (*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 + * @protected + * @see #ERInterface + * @see #ers_new(uint32) + */ + void (*destroy)(struct eri *self); + +} *ERInterface; + +#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) (uint32)0 +# define ers_destroy(obj) +// Disable the public functions +# define ers_new(size) 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 + * @public + * @see #ERS_ALIGNED + * @see #ERInterface + * @see ERInterface#destroy(ERInterface) + * @see common\ers.c#ers_new(uint32) + */ +ERInterface ers_new(uint32 size); + +/** + * 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. + * @public + * @see common\ers.c#ers_report(void) + */ +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. + * @public + * @see common\ers.c#ers_force_destroy_all(void) + */ +void ers_force_destroy_all(void); +#endif /* DISABLE_ERS / not DISABLE_ERS */ + +#endif /* _ERS_H_ */ diff --git a/src/common/graph.c b/src/common/graph.c new file mode 100644 index 000000000..a68d39ce0 --- /dev/null +++ b/src/common/graph.c @@ -0,0 +1,318 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// graph creation is enabled +#define ENABLE_GRAPH + +#ifdef ENABLE_GRAPH + +#include +#include +#include +#ifndef _WIN32 + #include +#endif +#ifdef MINGW + #include +#endif + +#include "../common/core.h" +#include "../common/timer.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "graph.h" + +struct graph { + int width; + int height; + int pallet_count; + int png_len; + int png_dirty; + unsigned char* raw_data; + unsigned char* png_data; + int * graph_value; + int graph_max; +}; + +void graph_write_dword(unsigned char* p,unsigned int v) { + p[0] = (unsigned char)((v >> 24) & 0xFF); + p[1] = (unsigned char)((v >> 16) & 0xFF); + p[2] = (unsigned char)((v >> 8) & 0xFF); + p[3] = (unsigned char)(v & 0xFF); +} + +struct graph* graph_create(unsigned int x,unsigned int y) { + struct graph *g = (struct graph*)aCalloc(sizeof(struct graph),1); + if(g == NULL) return NULL; + // 256 * 3 : パレットデータ + // x * y * 2 : イメージのバッファ + // 256 : チャンクデータなどの予備 + g->png_data = (unsigned char *) aMalloc(4 * 256 + (x + 1) * y * 2); + g->raw_data = (unsigned char *) aCalloc( (x + 1) * y , 1); + memcpy( + g->png_data, + "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x03\x00\x00\x00\xFF\xFF\xFF" + "\xFF\x00\x00\x00\x03\x50\x4C\x54\x45\xFF\xFF\xFF\xA7\xC4\x1B\xC8",0x30 + ); + graph_write_dword(g->png_data + 0x10,x); + graph_write_dword(g->png_data + 0x14,y); + graph_write_dword(g->png_data + 0x1D,grfio_crc32(g->png_data+0x0C,0x11)); + g->pallet_count = 1; + g->width = x; + g->height = y; + g->png_dirty = 1; + g->graph_value = (int *) aCalloc(x,sizeof(int)); + g->graph_max = 1; + return g; +} + +void graph_pallet(struct graph* g, int index,unsigned long c) { + if(g == NULL || c >= 256) return; + + if(g->pallet_count <= index) { + memset(g->png_data + 0x29 + 3 * g->pallet_count,0,(index - g->pallet_count) * 3); + g->pallet_count = index + 1; + } + g->png_data[0x29 + index * 3 ] = (unsigned char)((c >> 16) & 0xFF); // R + g->png_data[0x29 + index * 3 + 1] = (unsigned char)((c >> 8) & 0xFF); // G + g->png_data[0x29 + index * 3 + 2] = (unsigned char)( c & 0xFF); // B + graph_write_dword(g->png_data + 0x21,g->pallet_count * 3); + graph_write_dword( + g->png_data + 0x29 + g->pallet_count * 3, + grfio_crc32(g->png_data + 0x25,g->pallet_count * 3 + 4) + ); + g->png_dirty = 1; +} + +void graph_setpixel(struct graph* g,int x,int y,int color) { + if(g == NULL || color >= 256) { return; } + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x >= g->width) { x = g->width - 1; } + if(y >= g->height) { y = g->height - 1; } + if(color >= g->pallet_count) { graph_pallet(g,color,graph_rgb(0,0,0)); } + + g->raw_data[y * (g->width + 1) + x + 1] = (unsigned char)color; + g->png_dirty = 1; +} + +int graph_getpixel(struct graph* g,int x,int y) { + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x >= g->width) { x = g->width - 1; } + if(y >= g->height) { y = g->height - 1; } + return g->raw_data[y * (g->width + 1) + x + 1]; +} + +const unsigned char* graph_output(struct graph* g,int *len) { + unsigned long inflate_len; + unsigned char *p; + + if(g == NULL) return NULL; + if(g->png_dirty == 0) { + *len = g->png_len; + return g->png_data; + } + + p = g->png_data + 0x2D + 3 * g->pallet_count; + inflate_len = 2 * (g->width + 1) * g->height; + memcpy(p + 4,"IDAT",4); + encode_zip(p + 8,&inflate_len,g->raw_data,(g->width + 1) * g->height); + graph_write_dword(p,inflate_len); + graph_write_dword(p + 8 + inflate_len,grfio_crc32(p + 4, inflate_len + 4)); + + p += 0x0C + inflate_len; + memcpy(p,"\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82",0x0C); + p += 0x0C; + g->png_len = p - g->png_data; + g->png_dirty = 0; + *len = g->png_len; + return g->png_data; +} + +void graph_free(struct graph* g) { + if(g != NULL) { + aFree(g->png_data); + aFree(g->raw_data); + aFree(g->graph_value); + aFree(g); + } +} + +// とりあえず不効率版。後ほど書き直し予定 +void graph_square(struct graph* g,int x,int y,int xe,int ye,int color) { + int i,j; + if(g == NULL) return; + if(x < 0) { x = 0; } + if(y < 0) { y = 0; } + if(xe > g->width) { xe = g->width; } + if(ye > g->height) { ye = g->height; } + for(i = y;i < ye ; i++) { + for(j = x; j < xe ; j++) { + graph_setpixel(g,j,i,color); + } + } +} + +// とりあえず不効率版。後ほど書き直し予定 +void graph_scroll(struct graph* g,int n,int color) { + int x,y; + if(g == NULL) return; + for(y = 0; y < g->height; y++) { + for(x = 0; x < g->width - n; x++) { + graph_setpixel(g,x,y,graph_getpixel(g,x + n,y)); + } + for( ; x < g->width; x++) { + graph_setpixel(g,x,y,color); + } + } +} + +void graph_data(struct graph* g,int value) { + int i, j, start; + if(g == NULL) return; + memmove(&g->graph_value[0],&g->graph_value[1],sizeof(int) * (g->width - 1)); + g->graph_value[g->width - 1] = value; + if(value > g->graph_max) { + // 最大値を超えたので再描画 + g->graph_max = value; + graph_square(g,0,0,g->width,g->height,0); + start = 0; + } else { + // スクロールしてポイント打つ + graph_scroll(g,1,0); + start = g->width - 1; + } + for(i = start; i < g->width; i++) { + int h0 = (i == 0 ? 0 : g->graph_value[i - 1]) * g->height / g->graph_max; + int h1 = (g->graph_value[i] ) * g->height / g->graph_max; + int h2 = (h0 < h1 ? 1 : -1); + for(j = h0; j != h1; j += h2) { + graph_setpixel(g,i,g->height - 1 - j,1); + } + graph_setpixel(g,i,g->height - 1 - h1,1); + } +} + +// 上の関数群を利用して、自動的にグラフを作成するタイマー群 + +#define GRP_WIDTH 300 // グラフの幅 +#define GRP_HEIGHT 200 // グラフの高さ +#define GRP_COLOR graph_rgb(0,0,255) // グラフの色 +#define GRP_INTERVEL 60*1000 // グラフの更新間隔 + +#define GRP_PATH "httpd/" + +struct graph_sensor { + struct graph* graph; + char* str; + char hash[32]; + int scanid; + int drawid; + int interval; + unsigned int (*func)(void); +}; + +static struct graph_sensor *sensor; +static int sensor_max; + +static int graph_scan_timer(int tid,unsigned int tick,int id,int data) +{ + if(id >= 0 && id < sensor_max) + graph_data(sensor[id].graph,sensor[id].func()); + return 0; +} + +// modified by Celest -- i'm trying to separate it from httpd if possible ^^; +static int graph_draw_timer(int tid,unsigned int tick,int id,int data) +{ + char png_file[24]; + FILE *fp; + + // create/update the png file + do { + const char *png_data; + int len; + sprintf (png_file, GRP_PATH"%s.png", sensor[id].hash); + fp = fopen(png_file, "w"); + // if another png of the same hash exists + // (i.e 2nd login server with the same sensors) + // this will fail = not good >.< + if (fp == NULL) + break; + png_data = graph_output(sensor[id].graph, &len); + fwrite(png_data,1,len,fp); + fclose(fp); + } while (0); + + // create/update text snippet + do { + char buf[8192], *p; + p = buf; + sprintf (png_file, GRP_PATH"%s.graph", sensor[id].hash); + fp = fopen(png_file, "w"); + if (fp == NULL) + break; + p += sprintf(p,"

%s

\n\n", + sensor[id].str); + p += sprintf(p,"

\n", + sensor[id].hash, GRP_WIDTH,GRP_HEIGHT); + p += sprintf(p,"

Max: %d, Interval: %d sec

\n\n", + sensor[id].graph->graph_max, sensor[id].interval / 1000); + fprintf(fp, buf); + fclose(fp); + } while (0); + + return 0; +} + +void graph_add_sensor(const char* string, int interval, unsigned int (*callback_func)(void)) +{ + int draw_interval = interval * 2; + struct graph *g = graph_create(GRP_WIDTH,GRP_HEIGHT); + graph_pallet(g,1,GRP_COLOR); + + sensor = (struct graph_sensor *) aRealloc(sensor, sizeof(struct graph_sensor) * (sensor_max + 1)); + sensor[sensor_max].graph = g; + sensor[sensor_max].str = aStrdup(string); + // create crc32 hash of the sensor's name + sprintf (sensor[sensor_max].hash, "%lu%c", grfio_crc32(string,strlen(string)), 'a' + SERVER_TYPE); + sensor[sensor_max].func = callback_func; + sensor[sensor_max].scanid = add_timer_interval(gettick() + 500, graph_scan_timer, sensor_max, 0, interval); + sensor[sensor_max].drawid = add_timer_interval(gettick() + 1000, graph_draw_timer, sensor_max, 0, draw_interval < 60000 ? 60000 : draw_interval); + sensor[sensor_max].interval = interval; + sensor_max++; + +} + +void graph_final (void) +{ + int i; + for(i = 0; i < sensor_max; i++) { + char png_file[24]; + // remove the png and snippet file + sprintf (png_file, GRP_PATH"%s.png", sensor[i].hash); + unlink (png_file); + sprintf (png_file, GRP_PATH"%s.graph", sensor[i].hash); + unlink (png_file); + graph_free(sensor[i].graph); + aFree(sensor[i].str); + //delete_timer(sensor[i].scanid,graph_scan_timer); + //delete_timer(sensor[i].drawid,graph_draw_timer); + } + aFree(sensor); + sensor_max = 0; +} + +void graph_init (void) +{ + graph_add_sensor ("Memory Usage", 1000, malloc_usage); + add_timer_func_list(graph_scan_timer, "graph_scan_timer"); + add_timer_func_list(graph_draw_timer, "graph_draw_timer"); +} + +#else +void graph_init (void) {} +void graph_final (void) {} +#endif diff --git a/src/common/graph.h b/src/common/graph.h new file mode 100644 index 000000000..6c80dd41c --- /dev/null +++ b/src/common/graph.h @@ -0,0 +1,27 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GRAPH_H_ +#define _GRAPH_H_ + +void graph_init (void); +void graph_final (void); + +struct graph* graph_create(unsigned int x,unsigned int y); +void graph_pallet(struct graph* g, int index,unsigned long c); +const unsigned char* graph_output(struct graph* g,int *len); +void graph_setpixel(struct graph* g,int x,int y,int color); +void graph_scroll(struct graph* g,int n,int color); +void graph_square(struct graph* g,int x,int y,int xe,int ye,int color); + +// athenaの状態を調査するセンサーを追加する。 +// string : センサーの名称(Login Users など) +// inetrval : センサーの値を所得する間隔(msec) +// callback_func : センサーの値を返す関数( unsigned int login_users(void); など) + +void graph_add_sensor(const char* string, int interval, unsigned int (*callback_func)(void)); + +#define graph_rgb(r,g,b) (((r) << 16) | ((g) << 8) | (b)) + +#endif + diff --git a/src/common/grfio.c b/src/common/grfio.c new file mode 100644 index 000000000..a3907a7f2 --- /dev/null +++ b/src/common/grfio.c @@ -0,0 +1,1146 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +/********************************************************************* + * + * Ragnarok Online Emulator : grfio.c -- grf file I/O Module + *-------------------------------------------------------------------- + * special need library : zlib + ********************************************************************* + * $Id: grfio.c,v 1.2 2004/09/29 17:31:49 kalaspuff Exp $ + * + * 2002/12/18... the original edition + * 2003/01/23 ... Code correction + * 2003/02/01 ... An addition and decryption processing are improved for LocalFile and two or more GRF(s) check processing. + * 2003/02/02 ... Even if there is no grf it does not stop -- as -- correction + * 2003/02/02... grf reading specification can be added later -- as -- correction (grfio_add function addition) + * 2003/02 / 03... at the time of grfio_resourcecheck processing the entry addition processing method -- correction + * 2003/02/05... change of the processing in grfio_init + * 2003/02/23... a local file check -- GRFIO_LOCAL -- switch (Defoe -- Function Off) + * 2003/10/21 ... The data of alpha client was read. + * 2003/11/10 ... Ready new grf format. + * 2003/11/11 ... version check fix & bug fix + */ + +#include +#include +#include +#include +#include + +#include "grfio.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../zlib/unzip.h" + +#define CHUNK 16384 + +#ifdef __WIN32 + #include "../zlib/zlib.h" + #include "../zlib/iowin32.h" +#else + #include +#endif + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +//static char data_file[1024] = ""; // "data.grf"; +//static char sdata_file[1024] = ""; // "sdata.grf"; +//static char adata_file[1024] = ""; // "adata.grf"; +static char data_dir[1024] = ""; // "../"; + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct { + int srclen; // compressed size + int srclen_aligned; // + int declen; // original size + int srcpos; + short next; + char cycle; + char type; + char fn[128-4*5]; // file name + char gentry; // read grf file select +} FILELIST; +//gentry ... 0 : It acquires from a local file. +// It acquires from the resource file of 1>=:gentry_table[gentry-1]. +// 1<=: Check a local file. +// If it is, after re-setting to 0, it acquires from a local file. +// If there is nothing, mark reversal will be carried out, and it will re-set, and will acquire from a resource file as well as 1>=. + +//Since char defines *FILELIST.gentry, the maximum which can be added by grfio_add becomes by 127 pieces. + +#define GENTRY_LIMIT 127 +#define FILELIST_LIMIT 65536 // temporary maximum, and a theory top maximum are 2G. + +static FILELIST *filelist = NULL; +static int filelist_entrys = 0; +static int filelist_maxentry = 0; + +static char **gentry_table = NULL; +static int gentry_entrys = 0; +static int gentry_maxentry = 0; + +#define RESNAME_LIMIT 1024 +#define RESNAME_ADDS 16 + +typedef struct resname_entry { + char src[64]; + char dst[64]; +} Resname; +static struct resname_entry *localresname = NULL; +static int resname_entrys = 0; +static int resname_maxentrys = 0; + +//---------------------------- +// file list hash table +//---------------------------- +static int filelist_hash[256]; + +//---------------------------- +// grf decode data table +//---------------------------- +static unsigned char BitMaskTable[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static char BitSwapTable1[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 +}; +static char BitSwapTable2[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 +}; +static char BitSwapTable3[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 +}; + +static unsigned char NibbleData[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, + } +}; +/*----------------- + * long data get + */ +static unsigned int getlong(unsigned char *p) +{ +// return *p+p[1]*256+(p[2]+p[3]*256)*65536; + return p[0] + | p[1] << 0x08 + | p[2] << 0x10 + | p[3] << 0x18; // Shinomori +} + +/*========================================== + * Grf data decode : Subs + *------------------------------------------ + */ +static void NibbleSwap(BYTE *Src, int len) +{ + for(;0>4) | (*Src<<4); + } +} + +static void BitConvert(BYTE *Src,char *BitSwapTable) +{ + int lop,prm; + BYTE tmp[8]; +// *(DWORD*)tmp=*(DWORD*)(tmp+4)=0; + memset(tmp,0,8); + for(lop=0;lop!=64;lop++) { + prm = BitSwapTable[lop]-1; + if (Src[(prm >> 3) & 7] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) & 7] |= BitMaskTable[lop & 7]; + } + } +// *(DWORD*)Src = *(DWORD*)tmp; +// *(DWORD*)(Src+4) = *(DWORD*)(tmp+4); + memcpy(Src,tmp,8); +} + +static void BitConvert4(BYTE *Src) +{ + int lop,prm; + BYTE tmp[8]; + tmp[0] = ((Src[7]<<5) | (Src[4]>>3)) & 0x3f; // ..0 vutsr + tmp[1] = ((Src[4]<<1) | (Src[5]>>7)) & 0x3f; // ..srqpo n + tmp[2] = ((Src[4]<<5) | (Src[5]>>3)) & 0x3f; // ..o nmlkj + tmp[3] = ((Src[5]<<1) | (Src[6]>>7)) & 0x3f; // ..kjihg f + tmp[4] = ((Src[5]<<5) | (Src[6]>>3)) & 0x3f; // ..g fedcb + tmp[5] = ((Src[6]<<1) | (Src[7]>>7)) & 0x3f; // ..cba98 7 + tmp[6] = ((Src[6]<<5) | (Src[7]>>3)) & 0x3f; // ..8 76543 + tmp[7] = ((Src[7]<<1) | (Src[4]>>7)) & 0x3f; // ..43210 v + + for(lop=0;lop!=4;lop++) { + tmp[lop] = (NibbleData[lop][tmp[lop*2]] & 0xf0) + | (NibbleData[lop][tmp[lop*2+1]] & 0x0f); + } + + *(DWORD*)(tmp+4)=0; + for(lop=0;lop!=32;lop++) { + prm = BitSwapTable3[lop]-1; + if (tmp[prm >> 3] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) + 4] |= BitMaskTable[lop & 7]; + } + } +// *(DWORD*)Src ^= *(DWORD*)(tmp+4); + Src[0] ^= tmp[4]; + Src[1] ^= tmp[5]; + Src[2] ^= tmp[6]; + Src[3] ^= tmp[7]; +} + +static void decode_des_etc(BYTE *buf,int len,int type,int cycle) +{ + int lop,cnt=0; + if(cycle<3) cycle=3; + else if(cycle<5) cycle++; + else if(cycle<7) cycle+=9; + else cycle+=15; + + for(lop=0;lop*8 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = (Bytef*) dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +int encode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen) { + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = (Bytef*) dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = deflateInit(&stream,Z_DEFAULT_COMPRESSION); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/*========================================== +* Decompress from file source to file dest until stream ends or EOF. +* inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be +* allocated for processing, Z_DATA_ERROR if the deflate data is +* invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and +* the version of the library linked do not match, or Z_ERRNO if there +* is an error reading or writing the files. +* +* Version 1.2 9 November 2004 Mark Adler +*------------------------------------------ +*/ +int decode_file (FILE *source, FILE *dest) +{ + int err; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + err = inflateInit(&strm); + if (err != Z_OK) return 0; //return err; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + inflateEnd(&strm); + return 0; + } + if (strm.avail_in == 0) + break; + strm.next_in = in; + + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + err = inflate(&strm, Z_NO_FLUSH); + Assert(err != Z_STREAM_ERROR); /* state not clobbered */ + switch (err) { + case Z_NEED_DICT: + err = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + //return err; + return 0; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + inflateEnd(&strm); + //return Z_ERRNO; + return 0; + } + } while (strm.avail_out == 0); + Assert(strm.avail_in == 0); /* all input will be used */ + + /* done when inflate() says it's done */ + } while (err != Z_STREAM_END); + + /* clean up and return */ + inflateEnd(&strm); + return err == Z_STREAM_END ? 1 : 0; +} + +/* =================================== +* Unzips a file. 1: success, 0: error +* Adapted from miniunz.c [Celest] +* Version 1.01b, May 30th, 2004 +* Copyright (C) 1998-2004 Gilles Vollant +* ------------------------------------- +*/ +int deflate_file (const char *source, const char *filename) +{ +#ifdef _WIN32 + zlib_filefunc_def ffunc; +#endif + unzFile uf = NULL; + int err = UNZ_OK; + uInt size_buf = 8192; + FILE *fout = NULL; + void *buf; + +#ifdef _WIN32 + fill_win32_filefunc(&ffunc); + uf = unzOpen2(source, &ffunc); +#else + uf = unzOpen(source); +#endif + + if (uf == NULL) { + //printf("Cannot open %s\n", source); + return 0; + } + //printf("%s opened\n", source); + + if (unzLocateFile(uf, filename, 0) != UNZ_OK) { + //printf("file %s not found in the zipfile\n", filename); + return 0; + } + + err = unzOpenCurrentFilePassword(uf, NULL); + //if (err != UNZ_OK) + // printf("error %d with zipfile in unzOpenCurrentFilePassword\n", err); + + fout = fopen(filename,"wb"); + if (fout == NULL) { + //printf("error opening %s\n", filename); + return 0; + } + + buf = (void *)aMalloc(size_buf); + do { + err = unzReadCurrentFile(uf, buf, size_buf); + if (err < 0) { + //printf("error %d with zipfile in unzReadCurrentFile\n", err); + break; + } + if (err > 0 && + fwrite(buf, err, 1, fout)!=1) + { + //printf("error in writing extracted file\n"); + err = UNZ_ERRNO; + break; + } + } while (err > 0); + + if (fout) fclose(fout); + + if (err == UNZ_OK) { + err = unzCloseCurrentFile (uf); + //if (err != UNZ_OK) + // printf("error %d with zipfile in unzCloseCurrentFile\n", err); + aFree(buf); + return (err == UNZ_OK); + } + + unzCloseCurrentFile(uf); /* don't lose the error */ + + return 0; +} + +unsigned long grfio_crc32 (const char *buf, unsigned int len) +{ + return crc32(crc32(0L, Z_NULL, 0), buf, len); +} + +/*********************************************************** + *** File List Sobroutines *** + ***********************************************************/ + +/*========================================== + * File List : Hash make + *------------------------------------------ + */ +static int filehash(unsigned char *fname) +{ + unsigned int hash=0; + while(*fname) { + hash = ((hash<<1)+(hash>>7)*9+tolower(*fname)); + fname++; + } + return hash & 255; +} + +/*========================================== + * File List : Hash initalize + *------------------------------------------ + */ +static void hashinit(void) +{ + int lop; + for (lop = 0; lop < 256; lop++) + filelist_hash[lop] = -1; +} + +/*========================================== + * File List : File find + *------------------------------------------ + */ +FILELIST *filelist_find(char *fname) +{ + int hash; + + if (!filelist) + return NULL; + + for (hash = filelist_hash[filehash((unsigned char *) fname)]; hash >= 0; hash = filelist[hash].next) { + if(strcmpi(filelist[hash].fn, fname) == 0) + break; + } + + return (hash >= 0) ? &filelist[hash] : NULL; +} + +/*========================================== + * File List : Filelist add + *------------------------------------------ + */ +#define FILELIST_ADDS 1024 // number increment of file lists ` + +static FILELIST* filelist_add(FILELIST *entry) +{ + int hash; + + if (filelist_entrys >= FILELIST_LIMIT) { + ShowFatalError("filelist limit : filelist_add\n"); + exit(1); + } + + 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((unsigned char *) entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys - 1]; +} + +static FILELIST* filelist_modify(FILELIST *entry) +{ + FILELIST *fentry; + if ((fentry = filelist_find(entry->fn)) != NULL) { + int tmp = fentry->next; + memcpy(fentry, entry, sizeof(FILELIST)); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +/*========================================== + * File List : filelist size adjust + *------------------------------------------ + */ +static void filelist_adjust(void) +{ + if (filelist != NULL) { + if (filelist_maxentry > filelist_entrys) { + filelist = (FILELIST *)aRealloc( + filelist, filelist_entrys * sizeof(FILELIST)); + filelist_maxentry = filelist_entrys; + } + } +} + +/*********************************************************** + *** Grfio Sobroutines *** + ***********************************************************/ +/*========================================== + * Grfio : Local Resnametable replace + *------------------------------------------ + */ +static void grfio_resnametable(char *src, char *dest) +{ + int lop; + if (localresname == NULL || + sscanf(src, "%*5s%s", dest) < 1) + { + // if not found copy the unresolved name into buffer + strcpy(dest, src); + return; + } + + for (lop = 0; lop < resname_entrys; lop++) { + if (strcmpi(localresname[lop].src, dest) == 0) { + sprintf(dest, "data\\%s", localresname[lop].dst); + return; + } + } + + return; +} + +/*========================================== + * Grfio : Local Resnametable Initialize + *------------------------------------------ + */ +static void grfio_resnameinit (void) +{ + FILE *fp; + char *p; + // max length per entry is 34 in resnametable + char w1[64], w2[64], restable[256], line[256]; + + sprintf(restable, "%sdata\\resnametable.txt", data_dir); + for (p = &restable[0]; *p != 0; p++) + if (*p == '\\') *p = '/'; + + fp = fopen(restable,"rb"); + if (fp == NULL) { + //ShowError("%s not found (grfio_resnameinit)\n", restable); + return; + } + + while (fgets(line, sizeof(line) - 1, fp)){ + if (sscanf(line, "%[^#]#%[^#]#", w1, w2) != 2) + continue; + // only save up necessary resource files + if (strstr(w1, ".gat") == NULL && + strstr(w1, ".txt") == NULL) + continue; + if (resname_entrys >= RESNAME_LIMIT) + break; + if (resname_entrys >= resname_maxentrys) { + resname_maxentrys += RESNAME_ADDS; + localresname = (Resname*) aRealloc (localresname, resname_maxentrys * sizeof(Resname)); + memset(localresname + (resname_maxentrys - RESNAME_ADDS), '\0', sizeof(Resname) * RESNAME_ADDS); + } + strcpy(localresname[resname_entrys].src, w1); + strcpy(localresname[resname_entrys].dst, w2); + resname_entrys++; + } + fclose(fp); + + // free up unused sections + if (resname_maxentrys > resname_entrys) { + localresname = (Resname*) aRealloc (localresname, resname_entrys * sizeof(Resname)); + resname_maxentrys = resname_entrys; + } +} + +/*========================================== + * Grfio : Resource file size get + *------------------------------------------ + */ +int grfio_size(char *fname) +{ + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry == NULL || entry->gentry < 0) { // LocalFileCheck + char lfname[256], rname[256], *p; + FILELIST lentry; + struct stat st; + + grfio_resnametable(fname, rname); + sprintf(lfname, "%s%s", data_dir, rname); + + for (p = &lfname[0]; *p != 0; p++) + if (*p=='\\') *p = '/'; // * At the time of Unix + + if (stat(lfname, &st) == 0) { + strncpy(lentry.fn, fname, sizeof(lentry.fn) - 1); + lentry.declen = st.st_size; + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else if (entry == NULL) { + ShowError("%s not found (grfio_size)\n", fname); + //exit(1); + return -1; + } + } + return entry->declen; +} + +/*========================================== + * Grfio : Resource file read & size get + *------------------------------------------ + */ +void* grfio_reads(char *fname, int *size) +{ + FILE *in; + FILELIST *entry; + unsigned char *buf2 = NULL; + + entry = filelist_find(fname); + + if (entry == NULL || entry->gentry <= 0) { // LocalFileCheck + char lfname[256], rname[256], *p; + FILELIST lentry; + + // resolve filename into rname + grfio_resnametable(fname, rname); + sprintf(lfname, "%s%s", data_dir, rname); + + for (p = &lfname[0]; *p != 0; p++) + if (*p == '\\') *p = '/'; // * At the time of Unix + + in = fopen(lfname, "rb"); + if (in != NULL) { + if (entry != NULL && entry->gentry == 0) { + lentry.declen = entry->declen; + } else { + fseek(in,0,2); // SEEK_END + lentry.declen = ftell(in); + } + fseek(in,0,0); // SEEK_SET + buf2 = (unsigned char *)aCallocA(lentry.declen + 1024, 1); + fread(buf2, 1, lentry.declen, in); + fclose(in); + strncpy(lentry.fn, fname, sizeof(lentry.fn) - 1); + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else { + if (entry != NULL && entry->gentry < 0) { + entry->gentry = -entry->gentry; // local file checked + } else { + ShowError("%s not found (grfio_reads - local file %s)\n", fname, lfname); + return NULL; + } + } + } + if (entry != NULL && entry->gentry > 0) { // Archive[GRF] File Read + char *gfname = gentry_table[entry->gentry - 1]; + in = fopen(gfname, "rb"); + if(in != NULL) { + unsigned char *buf = (unsigned char *)aCallocA(entry->srclen_aligned + 1024, 1); + fseek(in, entry->srcpos, 0); + fread(buf, 1, entry->srclen_aligned, in); + fclose(in); + buf2 = (unsigned char *)aCallocA(entry->declen + 1024, 1); + if (entry->type == 1 || entry->type == 3 || entry->type == 5) { + uLongf len; + if (entry->cycle >= 0) + decode_des_etc(buf, entry->srclen_aligned, entry->cycle == 0, entry->cycle); + len = entry->declen; + decode_zip(buf2, &len, buf, entry->srclen); + if (len != entry->declen) { + ShowError("decode_zip size miss match err: %d != %d\n", (int)len, entry->declen); + aFree(buf); + aFree(buf2); + return NULL; + } + } else { + memcpy(buf2, buf, entry->declen); + } + aFree(buf); + } else { + ShowError("%s not found (grfio_reads - grf file %s)\n", fname, gfname); + return NULL; + } + } + if (size != NULL && entry != NULL) + *size = entry->declen; + + return buf2; +} + +/*========================================== + * Grfio : Resource file read + *------------------------------------------ + */ +void* grfio_read(char *fname) +{ + return grfio_reads(fname, NULL); +} + +/*========================================== + * Resource filename decode + *------------------------------------------ + */ +static char * decode_filename(unsigned char *buf,int len) +{ + int lop; + for(lop=0;lop> 8; + + if (grf_version == 0x01) { //****** Grf version 01xx ****** + list_size = grf_size - ftell(fp); + grf_filelist = (unsigned char *) aCallocA(list_size, 1); + /*if (grf_filelist == NULL){ + fclose(fp); + ShowError("out of memory : grf_filelist\n"); + return 3; // 3:memory alloc error + }*/ + fread(grf_filelist,1,list_size,fp); + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for (entry = 0,ofs = 0; entry < entrys; entry++) { + int ofs2, srclen, srccount, type; + char *period_ptr; + FILELIST aentry; + + ofs2 = ofs+getlong(grf_filelist+ofs)+4; + type = grf_filelist[ofs2+12]; + if (type != 0) { // Directory Index ... skip + fname = decode_filename(grf_filelist+ofs+6, grf_filelist[ofs]-6); + if (strlen(fname) > sizeof(aentry.fn) - 1) { + ShowFatalError("file name too long : %s\n",fname); + aFree(grf_filelist); + exit(1); + } + srclen = 0; + if ((period_ptr = strrchr(fname, '.')) != NULL) { + for(lop = 0; lop < 4; lop++) { + if (strcmpi(period_ptr, ".gnd\0.gat\0.act\0.str"+lop*5) == 0) + break; + } + srclen = getlong(grf_filelist+ofs2) - getlong(grf_filelist+ofs2+8) - 715; + if(lop == 4) { + for(lop = 10, srccount = 1; srclen >= lop; lop = lop * 10, srccount++); + } else { + srccount = 0; + } + } else { + srccount = 0; + } + + 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.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn, fname,sizeof(aentry.fn)-1); +#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; + + fread(eheader,1,8,fp); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if ((long)rSize > grf_size-ftell(fp)) { // Warning fix [Lance] + fclose(fp); + ShowError("Illegal data format : grf compress entry size\n"); + return 4; + } + + rBuf = (unsigned char *)aCallocA(rSize , 1); // Get a Read Size + /*if (rBuf==NULL) { + fclose(fp); + ShowError("out of memory : grf compress entry table buffer\n"); + return 3; + }*/ + grf_filelist = (unsigned char *)aCallocA(eSize , 1); // Get a Extend Size + /*if (grf_filelist==NULL) { + aFree(rBuf); + fclose(fp); + ShowError("out of memory : grf extract entry table buffer\n"); + return 3; + }*/ + fread(rBuf,1,rSize,fp); + fclose(fp); + decode_zip(grf_filelist, &eSize, rBuf, rSize); // Decode function + list_size = eSize; + aFree(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for(entry = 0, ofs = 0; entry < entrys; entry++){ + int ofs2, srclen, srccount, type; + FILELIST aentry; + + fname = (char*)(grf_filelist+ofs); + if (strlen(fname) > sizeof(aentry.fn)-1) { + ShowFatalError("grf : file name too long : %s\n",fname); + aFree(grf_filelist); + exit(1); + } + //ofs2 = ofs+strlen((char*)(grf_filelist+ofs))+1; + ofs2 = ofs + strlen(fname)+1; + type = grf_filelist[ofs2+12]; + if (type == 1 || type == 3 || type == 5) { + srclen = getlong(grf_filelist+ofs2); + if (grf_filelist[ofs2+12] == 3) { + for (lop = 10, srccount = 1; srclen >= lop; lop = lop * 10, srccount++); + } else if (grf_filelist[ofs2+12] == 5) { + srccount = 0; + } else { // if (grf_filelist[ofs2+12]==1) { + srccount = -1; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#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("not support grf versions : %04x\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_adjust(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + +/*========================================== + * Grfio : Resource file check + *------------------------------------------ + */ +static void grfio_resourcecheck(void) +{ + int size; + char *buf, *ptr; + char w1[256], w2[256], src[256], dst[256]; + FILELIST *entry; + + buf = (char *)grfio_reads("data\\resnametable.txt", &size); + if (buf == NULL) + return; + buf[size] = 0; + + for (ptr = buf; ptr - buf < size;) { + if (sscanf(ptr,"%[^#]#%[^#]#",w1,w2) == 2) { + if (strstr(w2, "bmp")) { + sprintf(src, "data\\texture\\%s", w1); + sprintf(dst, "data\\texture\\%s", w2); + } else { + sprintf(src, "data\\%s", w1); + sprintf(dst, "data\\%s", w2); + } + entry = filelist_find(dst); + if (entry != NULL) { + FILELIST fentry; + memcpy(&fentry, entry, sizeof(FILELIST)); + strncpy(fentry.fn, src, sizeof(fentry.fn) - 1); + filelist_modify(&fentry); + } else { + //ShowError("file not found in data.grf : %s < %s\n",dst,src); + } + } + ptr = strchr(ptr,'\n'); // Next line + if (!ptr) break; + ptr++; + } + aFree(buf); + filelist_adjust(); // Unnecessary area release of filelist +} + +/*========================================== + * Grfio : Resource add + *------------------------------------------ + */ +#define GENTRY_ADDS 16 // The number increment of gentry_table entries + +int grfio_add(char *fname) +{ + int len,result; + char *buf; + + if (gentry_entrys >= GENTRY_LIMIT) { + ShowFatalError("gentrys limit : grfio_add\n"); + exit(1); + } + + if (gentry_entrys >= gentry_maxentry) { + 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); + } + len = strlen( fname ); + buf = (char*)aCallocA(len + 1, 1); + strcpy(buf, fname); + gentry_table[gentry_entrys++] = buf; + + result = grfio_entryread(fname, gentry_entrys - 1); + if (result == 0) + // Resource check + grfio_resourcecheck(); + + return result; +} + +/*========================================== + * Grfio : Finalize + *------------------------------------------ + */ +void grfio_final(void) +{ + if (filelist != NULL) + aFree(filelist); + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table != NULL) { + int lop; + for (lop = 0; lop < gentry_entrys; lop++) { + if (gentry_table[lop] != NULL) + aFree(gentry_table[lop]); + } + aFree(gentry_table); + } + gentry_table = NULL; + gentry_entrys = gentry_maxentry = 0; + + if (localresname) aFree(localresname); +} + +/*========================================== + * Grfio : Initialize + *------------------------------------------ + */ +void grfio_init(char *fname) +{ + FILE *data_conf; + char line[1024], w1[1024], w2[1024]; + int result = 0; + + hashinit(); // hash table initialization + + data_conf = fopen(fname, "r"); + // It will read, if there is grf-files.txt. + if (data_conf) { + while(fgets(line, sizeof(line) - 1, data_conf)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + // Entry table reading + if(strcmp(w1, "grf") == 0 || + strcmp(w1, "data") == 0 || // Primary data file + strcmp(w1, "sdata") == 0 || // Sakray data file + strcmp(w1, "adata") == 0) // Alpha version data file + // increment if successfully loaded + result += (grfio_add(w2) == 0); + else if(strcmp(w1,"data_dir") == 0) // Data directory + strcpy(data_dir, w2); + } + + fclose(data_conf); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", fname); + } // end of reading grf-files.txt + + if (result == 0) { + ShowInfo("No grf's loaded.. using default data directory\n"); + //exit(1); // It ends, if a resource cannot read one. + } + + // initialise Resnametable + grfio_resnameinit(); + + return; +} diff --git a/src/common/grfio.h b/src/common/grfio.h new file mode 100644 index 000000000..a7faafc1c --- /dev/null +++ b/src/common/grfio.h @@ -0,0 +1,21 @@ +// 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(char*); // GRFIO Initialize +void grfio_final(void); // GRFIO Finalize +int grfio_add(char*); // GRFIO Resource file add +void* grfio_read(char*); // GRFIO data file read +void* grfio_reads(char*,int*); // GRFIO data file read & size get +int grfio_size(char*); // GRFIO data file size get +unsigned long grfio_crc32(const char *buf, unsigned int len); + +int decode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen); +int encode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen); +int decode_file (FILE *source, FILE *dest); + +int deflate_file (const char *source, const char *filename); + +#endif // _GRFIO_H_ diff --git a/src/common/lock.c b/src/common/lock.c new file mode 100644 index 000000000..c7bf623e5 --- /dev/null +++ b/src/common/lock.c @@ -0,0 +1,71 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#define F_OK 0x0 +#define R_OK 0x4 +#endif +#include "lock.h" +#include "showmsg.h" + +#ifndef _WIN32 + #define exists(filename) (!access(filename, F_OK)) +#else +// could be speed up maybe? +int exists(char *file) { + FILE *fp; + if ((fp = fopen(file,"r")) && fclose(fp) == 0) return 1; + return 0; +} +#endif + +// 書き込みファイルの保護処理 +// (書き込みが終わるまで、旧ファイルを保管しておく) + +// 新しいファイルの書き込み開始 +FILE* lock_fopen (const char* filename, int *info) { + char newfile[512]; + FILE *fp; + int no = 0; + + // 安全なファイル名を得る(手抜き) + do { + sprintf(newfile, "%s_%04d.tmp", filename, ++no); + } while((fp = fopen(newfile,"r")) && (fclose(fp), no < 9999)); + *info = no; + return fopen(newfile,"w"); +} + +// 旧ファイルを削除&新ファイルをリネーム +int lock_fclose (FILE *fp, const char* filename, int *info) { + int ret = 1; + char newfile[512]; + char oldfile[512]; + if (fp != NULL) { + ret = fclose(fp); + sprintf(newfile, "%s_%04d.tmp", filename, *info); + sprintf(oldfile, "%s.bak", filename); // old backup file + + if (exists(oldfile)) remove(oldfile); // remove backup file if it already exists + rename (filename, oldfile); // backup our older data instead of deleting it + + // このタイミングで落ちると最悪。 + if ((ret = rename(newfile,filename)) != 0) { // rename our temporary file to its correct name +#if defined(__NETBSD__) || defined(_WIN32) || defined(sun) || defined (_sun) || defined (__sun__) + ShowError("%s - '"CL_WHITE"%s"CL_RESET"'\n", strerror(errno), newfile); +#else + char ebuf[255]; + ShowError("%s - '"CL_WHITE"%s"CL_RESET"'\n", strerror_r(errno, ebuf, sizeof(ebuf)), newfile); +#endif + } + } + + return ret; +} + diff --git a/src/common/lock.h b/src/common/lock.h new file mode 100644 index 000000000..5c846eb73 --- /dev/null +++ b/src/common/lock.h @@ -0,0 +1,11 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LOCK_H_ +#define _LOCK_H_ + +FILE* lock_fopen(const char* filename,int *info); +int lock_fclose(FILE *fp,const char* filename,int *info); + +#endif + diff --git a/src/common/malloc.c b/src/common/malloc.c new file mode 100644 index 000000000..1e3af2d40 --- /dev/null +++ b/src/common/malloc.c @@ -0,0 +1,715 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include "malloc.h" +#include "../common/core.h" +#include "../common/showmsg.h" + +#ifdef MINICORE + #undef LOG_MEMMGR +#endif + +void* aMalloc_ (size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = MALLOC(size); +#else + void *ret = mwMalloc(size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: malloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + } + + return ret; +} +void* aMallocA_ (size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = MALLOCA(size); +#else + void *ret = mwMalloc(size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: malloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + } + + return ret; +} +void* aCalloc_ (size_t num, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = CALLOC(num, size); +#else + void *ret = mwCalloc(num, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: calloc error out of memory!\n", file, line, func); + exit(1); + } + return ret; +} +void* aCallocA_ (size_t num, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = CALLOCA(num, size); +#else + void *ret = mwCalloc(num, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: calloc error out of memory!\n",file,line,func); + exit(1); + } + return ret; +} +void* aRealloc_ (void *p, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = REALLOC(p, size); +#else + void *ret = mwRealloc(p, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: realloc %p %d\n",file,line,func,p,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: realloc error out of memory!\n",file,line,func); + exit(1); + } + return ret; +} +char* aStrdup_ (const char *p, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + char *ret = STRDUP(p); +#else + char *ret = mwStrdup(p, file, line); +#endif + // ShowMessage("%s:%d: in func %s: strdup %p\n",file,line,func,p); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: strdup error out of memory!\n", file, line, func); + exit(1); + } + return ret; +} +void aFree_ (void *p, const char *file, int line, const char *func) +{ + // ShowMessage("%s:%d: in func %s: free %p\n",file,line,func,p); + if (p) + #ifndef MEMWATCH + FREE(p); + #else + mwFree(p, file, line); + #endif + + p = NULL; +} + +#ifdef GCOLLECT + +void* _bcallocA(size_t size, size_t cnt) +{ + void *ret = MALLOCA(size * cnt); + if (ret) memset(ret, 0, size * cnt); + return ret; +} +void* _bcalloc(size_t size, size_t cnt) +{ + void *ret = MALLOC(size * cnt); + if (ret) memset(ret, 0, size * cnt); + return ret; +} +char* _bstrdup(const char *chr) +{ + int len = strlen(chr); + char *ret = (char*)MALLOC(len + 1); + if (ret) memcpy(ret, chr, len + 1); + return ret; +} + +#endif + +#ifdef USE_MEMMGR + +/* USE_MEMMGR */ + +/* + * メモリマネージャ + * malloc , free の処理を効率的に出来るようにしたもの。 + * 複雑な処理を行っているので、若干重くなるかもしれません。 + * + * データ構造など(説明下手ですいません^^; ) + * ・メモリを複数の「ブロック」に分けて、さらにブロックを複数の「ユニット」 + * に分けています。ユニットのサイズは、1ブロックの容量を複数個に均等配分 + * したものです。たとえば、1ユニット32KBの場合、ブロック1つは32Byteのユ + * ニットが、1024個集まって出来ていたり、64Byteのユニットが 512個集まって + * 出来ていたりします。(padding,unit_head を除く) + * + * ・ブロック同士はリンクリスト(block_prev,block_next) でつながり、同じサイ + * ズを持つブロック同士もリンクリスト(samesize_prev,samesize_nect) でつな + * がっています。それにより、不要となったメモリの再利用が効率的に行えます。 + */ + +/* ブロックに入るデータ量 */ +#define BLOCK_DATA_SIZE 80*1024 + +/* 一度に確保するブロックの数。 */ +#define BLOCK_ALLOC 32 + +/* ブロックのアライメント */ +#define BLOCK_ALIGNMENT 64 + +/* ブロック */ +struct block { + int block_no; /* ブロック番号 */ + struct block* block_prev; /* 前に確保した領域 */ + struct block* block_next; /* 次に確保した領域 */ + int samesize_no; /* 同じサイズの番号 */ + struct block* samesize_prev; /* 同じサイズの前の領域 */ + struct block* samesize_next; /* 同じサイズの次の領域 */ + size_t unit_size; /* ユニットのバイト数 0=未使用 */ + size_t unit_hash; /* ユニットのハッシュ */ + int unit_count; /* ユニットの数 */ + int unit_used; /* 使用済みユニット */ + char data[BLOCK_DATA_SIZE]; +}; + +struct unit_head { + struct block* block; + size_t size; + const char* file; + int line; + unsigned int checksum; +}; + +struct chunk { + char *block; + struct chunk *next; +}; + +static struct block* block_first = NULL; +static struct block* block_last = NULL; +static struct block* block_unused = NULL; + +/* ユニットへのハッシュ。80KB/64Byte = 1280個 */ +static struct block* unit_first[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 最初 */ +static struct block* unit_unfill[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 埋まってない */ +static struct block* unit_last[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 最後 */ + +/* メモリを使い回せない領域用のデータ */ +struct unit_head_large { + 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 chunk *chunk_first = NULL; + +static struct block* block_malloc(void); +static void block_free(struct block* p); +static void memmgr_info(void); +static unsigned int memmgr_usage_bytes = 0; + +void* _mmalloc(size_t size, const char *file, int line, const char *func ) { + int i; + struct block *block; + size_t size_hash; + + if (((long) size) < 0) { + printf("_mmalloc: %d\n", size); + return 0; + } + + size_hash = (size+BLOCK_ALIGNMENT-1) / BLOCK_ALIGNMENT; + if(size == 0) { + return NULL; + } + memmgr_usage_bytes += size; + + /* ブロック長を超える領域の確保には、malloc() を用いる */ + /* その際、unit_head.block に NULL を代入して区別する */ + if(size_hash * BLOCK_ALIGNMENT > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { +#ifdef MEMWATCH + struct unit_head_large* p = (struct unit_head_large*)mwMalloc(sizeof(struct unit_head_large) + size,file,line); +#else + struct unit_head_large* p = (struct unit_head_large*) MALLOC (sizeof(struct unit_head_large) + size); +#endif + if(p != NULL) { + p->unit_head.block = NULL; + p->unit_head.size = size; + 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; + *(int*)((char*)p + sizeof(struct unit_head_large) - sizeof(int) + size) = 0xdeadbeaf; + return (char *)p + sizeof(struct unit_head_large) - sizeof(int); + } else { + ShowFatalError("Memory manager::memmgr_alloc failed.\n"); + exit(1); + } + } + + /* 同一サイズのブロックが確保されていない時、新たに確保する */ + if(unit_unfill[size_hash] == NULL) { + block = block_malloc(); + if(unit_first[size_hash] == NULL) { + /* 初回確保 */ + unit_first[size_hash] = block; + unit_last[size_hash] = block; + block->samesize_no = 0; + block->samesize_prev = NULL; + block->samesize_next = NULL; + } else { + /* 連結作業 */ + unit_last[size_hash]->samesize_next = block; + block->samesize_no = unit_last[size_hash]->samesize_no + 1; + block->samesize_prev = unit_last[size_hash]; + block->samesize_next = NULL; + unit_last[size_hash] = block; + } + unit_unfill[size_hash] = block; + block->unit_size = size_hash * BLOCK_ALIGNMENT + sizeof(struct unit_head); + block->unit_count = (int)(BLOCK_DATA_SIZE / block->unit_size); + block->unit_used = 0; + block->unit_hash = size_hash; + /* 未使用Flagを立てる */ + for(i=0;iunit_count;i++) { + ((struct unit_head*)(&block->data[block->unit_size * i]))->block = NULL; + } + } + /* ユニット使用個数加算 */ + block = unit_unfill[size_hash]; + block->unit_used++; + + /* ユニット内を全て使い果たした */ + if(block->unit_count == block->unit_used) { + do { + unit_unfill[size_hash] = unit_unfill[size_hash]->samesize_next; + } while( + unit_unfill[size_hash] != NULL && + unit_unfill[size_hash]->unit_count == unit_unfill[size_hash]->unit_used + ); + } + + /* ブロックの中の空きユニット捜索 */ + for(i=0;iunit_count;i++) { + struct unit_head *head = (struct unit_head*)(&block->data[block->unit_size * i]); + if(head->block == NULL) { + head->block = block; + head->size = size; + head->line = line; + head->file = file; + *(int*)((char*)head + sizeof(struct unit_head) - sizeof(int) + size) = 0xdeadbeaf; + return (char *)head + sizeof(struct unit_head) - sizeof(int); + } + } + // ここに来てはいけない。 + ShowFatalError("Memory manager::memmgr_malloc() serious error.\n"); + memmgr_info(); + exit(1); + return NULL; +}; + +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(int)))->size; + if(old_size > size) { + // サイズ縮小 -> そのまま返す(手抜き) + return memblock; + } else { + // サイズ拡大 + 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; + size_t size_hash; + + if (ptr == NULL) + return; + + head = (struct unit_head *)((char *)ptr - sizeof(struct unit_head) + sizeof(int)); + size_hash = (head->size+BLOCK_ALIGNMENT-1) / BLOCK_ALIGNMENT; + + if(head->block == NULL) { + if(size_hash * BLOCK_ALIGNMENT > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { + /* malloc() で直に確保された領域 */ + struct unit_head_large *head_large = (struct unit_head_large *)((char *)ptr - sizeof(struct unit_head_large) + sizeof(int)); + if( + *(int*)((char*)head_large + sizeof(struct unit_head_large) - sizeof(int) + head->size) + != 0xdeadbeaf) + { + ShowError("Memory manager: args of aFree is overflowed pointer %s line %d\n", file, line); + } + 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; + } + head->block = NULL; + memmgr_usage_bytes -= head->size; + FREE (head_large); + } else { + ShowError("Memory manager: args of aFree is freed pointer %s line %d\n", file, line); + } + ptr = NULL; + return; + } else { + /* ユニット解放 */ + struct block *block = head->block; + if((unsigned long)block % sizeof(struct block) != 0) { + ShowError("Memory manager: args of aFree is not valid pointer %s line %d\n", file, line); + } else if(*(int*)((char*)head + sizeof(struct unit_head) - sizeof(int) + head->size) != 0xdeadbeaf) { + ShowError("Memory manager: args of aFree is overflowed pointer %s line %d\n", file, line); + } else { + head->block = NULL; + memmgr_usage_bytes -= head->size; + if(--block->unit_used == 0) { + /* ブロックの解放 */ + if(unit_unfill[block->unit_hash] == block) { + /* 空きユニットに指定されている */ + do { + unit_unfill[block->unit_hash] = unit_unfill[block->unit_hash]->samesize_next; + } while( + unit_unfill[block->unit_hash] != NULL && + unit_unfill[block->unit_hash]->unit_count == unit_unfill[block->unit_hash]->unit_used + ); + } + if(block->samesize_prev == NULL && block->samesize_next == NULL) { + /* 独立ブロックの解放 */ + unit_first[block->unit_hash] = NULL; + unit_last[block->unit_hash] = NULL; + unit_unfill[block->unit_hash] = NULL; + } else if(block->samesize_prev == NULL) { + /* 先頭ブロックの解放 */ + unit_first[block->unit_hash] = block->samesize_next; + (block->samesize_next)->samesize_prev = NULL; + } else if(block->samesize_next == NULL) { + /* 末端ブロックの解放 */ + unit_last[block->unit_hash] = block->samesize_prev; + (block->samesize_prev)->samesize_next = NULL; + } else { + /* 中間ブロックの解放 */ + (block->samesize_next)->samesize_prev = block->samesize_prev; + (block->samesize_prev)->samesize_next = block->samesize_next; + } + block_free(block); + } else { + /* 空きユニットの再設定 */ + if( + unit_unfill[block->unit_hash] == NULL || + unit_unfill[block->unit_hash]->samesize_no > block->samesize_no + ) { + unit_unfill[block->unit_hash] = block; + } + } + ptr = NULL; + } + } +} + +/* 現在の状況を表示する */ +static void memmgr_info(void) { + int i; + struct block *p; + ShowInfo("** Memory Manager Information **\n"); + if(block_first == NULL) { + ShowMessage("Uninitialized.\n"); + return; + } + ShowMessage( + "Blocks: %04u , BlockSize: %06u Byte , Used: %08uKB\n", + block_last->block_no+1,sizeof(struct block), + (block_last->block_no+1) * sizeof(struct block) / 1024 + ); + p = block_first; + for(i=0;i<=block_last->block_no;i++) { + ShowMessage(" Block #%04u : ",p->block_no); + if(p->unit_size == 0) { + ShowMessage("unused.\n"); + } else { + ShowMessage( + "size: %05u byte. used: %04u/%04u prev:", + p->unit_size - sizeof(struct unit_head),p->unit_used,p->unit_count + ); + if(p->samesize_prev == NULL) { + ShowMessage("NULL"); + } else { + ShowMessage("%04u",(p->samesize_prev)->block_no); + } + ShowMessage(" next:"); + if(p->samesize_next == NULL) { + ShowMessage("NULL"); + } else { + ShowMessage("%04u",(p->samesize_next)->block_no); + } + ShowMessage("\n"); + } + p = p->block_next; + } +} + +/* ブロックを確保する */ +static struct block* block_malloc(void) { + if(block_unused != NULL) { + /* ブロック用の領域は確保済み */ + struct block* ret = block_unused; + do { + block_unused = block_unused->block_next; + } while(block_unused != NULL && block_unused->unit_size != 0); + return ret; + } else { + /* ブロック用の領域を新たに確保する */ + int i; + int block_no; + struct block* p; + struct chunk* chunk; + char *pb = (char *) CALLOC (sizeof(struct block),BLOCK_ALLOC + 1); + if(pb == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(1); + } + + // store original block address in chunk + chunk = (struct chunk *) MALLOC (sizeof(struct chunk)); + if (chunk == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(1); + } + chunk->block = pb; + chunk->next = (chunk_first) ? chunk_first : NULL; + chunk_first = chunk; + + // ブロックのポインタの先頭をsizeof(block) アライメントに揃える + // このアドレスをfree() することはないので、直接ポインタを変更している。 + pb += sizeof(struct block) - ((unsigned long)pb % sizeof(struct block)); + p = (struct block*)pb; + if(block_first == NULL) { + /* 初回確保 */ + block_no = 0; + block_first = p; + } else { + block_no = block_last->block_no + 1; + block_last->block_next = p; + p->block_prev = block_last; + } + block_last = &p[BLOCK_ALLOC - 1]; + /* ブロックを連結させる */ + for(i=0;iunit_size = 1; + return p; + } +} + +static void block_free(struct block* p) { + /* free() せずに、未使用フラグを付けるだけ */ + p->unit_size = 0; + /* 未使用ポインターを更新する */ + if(block_unused == NULL) { + block_unused = p; + } else if(block_unused->block_no > p->block_no) { + block_unused = p; + } +} + +unsigned int 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) { + log_fp = fopen(memmer_logfile,"w"); + if (!log_fp) log_fp = stdout; + fprintf(log_fp, "Memory manager: Memory leaks found.\n"); + } + fprintf(log_fp, buf); + return; +} +#endif + +static void memmgr_final (void) +{ + struct block *block = block_first; + struct chunk *chunk = chunk_first, *chunk2; + struct unit_head_large *large = unit_head_large_first, *large2; + int i; + +#ifdef LOG_MEMMGR + int count = 0; + char buf[128]; +#endif + + while (block) { + if (block->unit_size) { + for (i = 0; i < block->unit_count; i++) { + struct unit_head *head = (struct unit_head*)(&block->data[block->unit_size * i]); + if(head->block != NULL) + { + #ifdef LOG_MEMMGR + sprintf (buf, + "%04d : %s line %d size %d\n", ++count, + head->file, head->line, head->size); + memmgr_log (buf); + #endif + // get block pointer and free it [celest] + _mfree ((char *)head + sizeof(struct unit_head) - sizeof(int), ALC_MARK); + } + } + } + //if (block->block_no >= block2->block_no + BLOCK_ALLOC - 1) { + // reached a new block array + //block = block->block_next; + + /* Okay wise guys... this is how block2 was allocated: [Skotlex] + struct block* p; + char *pb = (char *) CALLOC (sizeof(struct block),BLOCK_ALLOC + 1); + pb += sizeof(struct block) - ((unsigned long)pb % sizeof(struct block)); + p = (struct block*)pb; + + The reason we get an invalid pointer is that we allocated pb, not p. + So how do you get pb when you only have p? + The answer is, you can't, because the original pointer was lost when + memory-aligning the block. So we either forget this FREE or use a + self-reference... + Since we are already quitting, it might be ok to just not free the block + as it is. + */ + // didn't realise that before o.o -- block chunks are now freed below [celest] + // FREE(block2); + //block2 = block; + //continue; + //} + block = block->block_next; + } + + // free the allocated block chunks + chunk = chunk_first; + while (chunk) { + chunk2 = chunk->next; + FREE(chunk->block); + FREE(chunk); + chunk = chunk2; + } + + while(large) { + large2 = large->next; + #ifdef LOG_MEMMGR + sprintf (buf, + "%04d : %s line %d size %d\n", ++count, + large->unit_head.file, large->unit_head.line, large->unit_head.size); + memmgr_log (buf); + #endif + FREE (large); + 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 + return; +} + +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); + #endif + return; +} +#endif + + +/*====================================== + * Initialise + *-------------------------------------- + */ + +unsigned int malloc_usage (void) +{ +#ifdef USE_MEMMGR + return memmgr_usage (); +#else + return 0; +#endif +} + +void malloc_final (void) +{ +#ifdef USE_MEMMGR + memmgr_final (); +#endif + return; +} + +void malloc_init (void) +{ +#ifdef USE_MEMMGR + memmgr_init (); +#endif + return; +} diff --git a/src/common/malloc.h b/src/common/malloc.h new file mode 100644 index 000000000..e9dbb9d44 --- /dev/null +++ b/src/common/malloc.h @@ -0,0 +1,153 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif +#define ALC_MARK __FILE__, __LINE__, __func__ + +////////////////////////////////////////////////////////////////////// +// Whether to use Athena's built-in Memory Manager (enabled by default) +// To disable just comment the following line +#if !defined(DMALLOC) && !defined(BCHECK) + #define USE_MEMMGR +#endif +// Whether to enable Memory Manager's logging +#define LOG_MEMMGR + +#ifdef USE_MEMMGR + +# define aMalloc(n) _mmalloc(n,ALC_MARK) +# define aMallocA(n) _mmalloc(n,ALC_MARK) +# define aCalloc(m,n) _mcalloc(m,n,ALC_MARK) +# define aCallocA(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, const char *, int, const char *); + void* _mcalloc (size_t, size_t, const char *, int, const char *); + void* _mrealloc (void *, size_t, const char *, int, const char *); + char* _mstrdup (const char *, const char *, int, const char *); + void _mfree (void *, const char *, int, const char *); + +#else + +# define aMalloc(n) aMalloc_(n,ALC_MARK) +# define aMallocA(n) aMallocA_(n,ALC_MARK) +# define aCalloc(m,n) aCalloc_(m,n,ALC_MARK) +# define aCallocA(m,n) aCallocA_(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, const char *, int, const char *); + void* aMallocA_ (size_t, const char *, int, const char *); + void* aCalloc_ (size_t, size_t, const char *, int, const char *); + void* aCallocA_ (size_t, size_t, const char *, int, const char *); + void* aRealloc_ (void *, size_t, const char *, int, const char *); + char* aStrdup_ (const char *, const char *, int, const char *); + void aFree_ (void *, const char *, int, const char *); + +#endif + +////////////// Memory Managers ////////////////// + +#ifdef MEMWATCH + +# include "memwatch.h" +# define MALLOC(n) mwMalloc(n,__FILE__, __LINE__) +# define MALLOCA(n) mwMalloc(n,__FILE__, __LINE__) +# define CALLOC(m,n) mwCalloc(m,n,__FILE__, __LINE__) +# define CALLOCA(m,n) mwCalloc(m,n,__FILE__, __LINE__) +# define REALLOC(p,n) mwRealloc(p,n,__FILE__, __LINE__) +# define STRDUP(p) mwStrdup(p,__FILE__, __LINE__) +# define FREE(p) mwFree(p,__FILE__, __LINE__) + +#elif defined(DMALLOC) + +# include "dmalloc.h" +# define MALLOC(n) dmalloc_malloc(__FILE__, __LINE__, (n), DMALLOC_FUNC_MALLOC, 0, 0) +# define MALLOCA(n) dmalloc_malloc(__FILE__, __LINE__, (n), DMALLOC_FUNC_MALLOC, 0, 0) +# define CALLOC(m,n) dmalloc_malloc(__FILE__, __LINE__, (m)*(n), DMALLOC_FUNC_CALLOC, 0, 0) +# define CALLOCA(m,n) dmalloc_malloc(__FILE__, __LINE__, (m)*(n), DMALLOC_FUNC_CALLOC, 0, 0) +# define REALLOC(p,n) dmalloc_realloc(__FILE__, __LINE__, (p), (n), DMALLOC_FUNC_REALLOC, 0) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#elif defined(GCOLLECT) + +# include "gc.h" +# define MALLOC(n) GC_MALLOC(n) +# define MALLOCA(n) GC_MALLOC_ATOMIC(n) +# define CALLOC(m,n) _bcalloc(m,n) +# define CALLOCA(m,n) _bcallocA(m,n) +# define REALLOC(p,n) GC_REALLOC(p,n) +# define STRDUP(p) _bstrdup(p) +# define FREE(p) GC_FREE(p) + + void * _bcalloc(size_t, size_t); + void * _bcallocA(size_t, size_t); + char * _bstrdup(const char *); + +#elif defined(BCHECK) + +# define MALLOC(n) malloc(n) +# define MALLOCA(n) malloc(n) +# define CALLOC(m,n) calloc(m,n) +# define CALLOCA(m,n) calloc(m,n) +# define REALLOC(p,n) realloc(p,n) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#else + +# define MALLOC(n) malloc(n) +# define MALLOCA(n) malloc(n) +# define CALLOC(m,n) calloc(m,n) +# define CALLOCA(m,n) calloc(m,n) +# define REALLOC(p,n) realloc(p,n) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#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 CREATE_A(result, type, number) (result) = (type *) aCallocA ((number), sizeof(type)); + +#define RECREATE(result, type, number) (result) = (type *) aRealloc ((result), sizeof(type) * (number)); + +//////////////////////////////////////////////// + +unsigned int malloc_usage (void); +void malloc_init (void); +void malloc_final (void); + +#endif diff --git a/src/common/mapindex.c b/src/common/mapindex.c new file mode 100644 index 000000000..e600b5ad7 --- /dev/null +++ b/src/common/mapindex.c @@ -0,0 +1,130 @@ +#include "mmo.h" +#include +#include +#include +#include + +#define MAX_MAPINDEX 2000 + +//Leave an extra char of space to hold the terminator, in case for the strncpy(mapindex_id2name()) calls. +struct { + char name[MAP_NAME_LENGTH+1]; //Stores map name + int length; //Stores string length WITHOUT the extension for quick lookup. +} indexes[MAX_MAPINDEX]; + +static unsigned short max_index = 0; + +char mapindex_cfgfile[80] = "db/map_index.txt"; + +unsigned short mapindex_name2id(char* name) { + //TODO: Perhaps use a db to speed this up? [Skotlex] + int i; + int length = strlen(name); + char *ext = strstr(name, "."); + if (ext) + length = ext-name; //Base map-name length without the extension. + for (i = 1; i < max_index; i++) + { + if (indexes[i].length == length && strncmp(indexes[i].name,name,length)==0) + return i; + } +#ifdef MAPINDEX_AUTOADD + if (i < MAX_MAPINDEX) { + char map_name[MAP_NAME_LENGTH+5]; + length = strlen(name); + if (length > MAP_NAME_LENGTH) + return; + memcpy(map_name, name, length+1); + if ((ext = strstr(map_name, ".")) != NULL) { + length = ext-map_name; + sprintf(ext, ".gat"); + } else { //No extension? + length = strlen(map_name); + strcat(map_name, ".gat"); + } + if (length > MAP_NAME_LENGTH - 4) + return 0; //Can't be added. + strncpy(indexes[i].name, map_name, MAP_NAME_LENGTH); + indexes[i].length = strlen(map_name); + ShowDebug("mapindex_name2id: Added map \"%s\" to position %d\n", indexes[i], i); + return i; + } +#endif + ShowDebug("mapindex_name2id: Map \"%s\" not found in index list!\n", name); + return 0; +} + +char* mapindex_id2name(unsigned short id) { + if (id > MAX_MAPINDEX || !indexes[id].length) { + ShowDebug("mapindex_id2name: Requested name for non-existant map index [%d] in cache.\n", id); + return indexes[0].name; //Theorically this should never happen, hence we return this string to prevent null pointer crashes. + } + return indexes[id].name; +} + +void mapindex_init(void) { + FILE *fp; + char line[1024]; + char *ext; + int last_index = -1; + int index, length; + 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(1); //Server can't really run without this file. + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + switch (sscanf(line,"%1000s\t%d",map_name,&index)) { + case 1: //Map with no ID given, auto-assign + index = last_index+1; + case 2: //Map with ID given + if (index < 0 || index >= MAX_MAPINDEX) { + ShowError("(mapindex_init) Map index (%d) for \"%s\" out of range (max is %d)\n", index, map_name, MAX_MAPINDEX); + continue; + } + length = strlen(map_name); + if (length > MAP_NAME_LENGTH) { + ShowError("(mapindex_init) Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + continue; + } + if ((ext = strstr(map_name, ".gat")) != NULL) { //Gat map + length = ext-map_name; + } else if ((ext = strstr(map_name, ".afm")) != NULL || (ext = strstr(map_name, ".af2")) != NULL) { //afm map + length = ext-map_name; + sprintf(ext, ".gat"); //Change the extension to gat + } else if ((ext = strstr(map_name, ".")) != NULL) { //Generic extension? + length = ext-map_name; + sprintf(ext, ".gat"); + } else { //No extension? + length = strlen(map_name); + strcat(map_name, ".gat"); + } + if (length > MAP_NAME_LENGTH - 4) { + ShowError("(mapindex_init) Adjusted Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + continue; + } + + if (indexes[index].length) + ShowWarning("(mapindex_init) Overriding index %d: map \"%s\" -> \"%s\"\n", indexes[index].name, map_name); + + strncpy(indexes[index].name, map_name, MAP_NAME_LENGTH); + indexes[index].length = length; + if (max_index <= index) + max_index = index+1; + break; + default: + continue; + } + last_index = index; + } +} + +void mapindex_final(void) { +} + diff --git a/src/common/mapindex.h b/src/common/mapindex.h new file mode 100644 index 000000000..7e2bbe289 --- /dev/null +++ b/src/common/mapindex.h @@ -0,0 +1,37 @@ +#ifndef _MAX_INDEX_H +#define _MAX_INDEX_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]; + +//whether to enable auto-adding of maps during run. Not so secure as the map indexes will vary! +#define MAPINDEX_AUTOADD + +//Some definitions for the mayor city maps. +#define MAP_PRONTERA "prontera.gat" +#define MAP_GEFFEN "geffen.gat" +#define MAP_MORROC "morocc.gat" +#define MAP_ALBERTA "alberta.gat" +#define MAP_PAYON "payon.gat" +#define MAP_IZLUDE "izlude.gat" +#define MAP_ALDEBARAN "aldebaran.gat" +#define MAP_LUTIE "xmas.gat" +#define MAP_COMODO "comodo.gat" +#define MAP_YUNO "yuno.gat" +#define MAP_AMATSU "amatsu.gat" +#define MAP_GONRYUN "gonryun.gat" +#define MAP_UMBALA "umbala.gat" +#define MAP_NIFLHEIM "niflheim.gat" +#define MAP_LOUYANG "louyang.gat" +#define MAP_JAWAII "jawaii.gat" +#define MAP_AYOTHAYA "ayothaya.gat" +#define MAP_EINBROCH "einbroch.gat" +#define MAP_LIGHTHALZEN "lighthalzen.gat" +#define MAP_EINBECH "einbech.gat" +#define MAP_HUGEL "hugel.gat" +#define MAP_JAIL "sec_pri.gat" +unsigned short mapindex_name2id(char*); +const char* mapindex_id2name(unsigned short); +void mapindex_init(void); +void mapindex_final(void); + +#endif diff --git a/src/common/mmo.h b/src/common/mmo.h new file mode 100644 index 000000000..d0d4685e2 --- /dev/null +++ b/src/common/mmo.h @@ -0,0 +1,403 @@ +// 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 +#include "utils.h" // _WIN32 + +#if ! defined(Assert) +#if defined(RELEASE) +#define Assert(EX) +#else +// extern "C" { +#include +// } +#ifndef DEFCPP +#if defined(_WIN32) && !defined(MINGW) +#include +#endif +#endif +#define Assert(EX) assert(EX) +#endif +#endif /* ! defined(Assert) */ + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define RET RETCODE + +#define FIFOSIZE_SERVERLINK 256*1024 + +// set to 0 to not check IP of player between each server. +// set to another value if you want to check (1) +#define CMP_AUTHFIFO_IP 1 + +#define CMP_AUTHFIFO_LOGIN2 1 + +//Remove/Comment this line to disable sc_data saving. [Skotlex] +#define ENABLE_SC_SAVING + +#define MAX_MAP_PER_SERVER 1024 +#define MAX_INVENTORY 100 +//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 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 +#define MAX_FAME 1000000000 +#define MAX_CART 100 +#define MAX_SKILL 1100 // Bumped to 1100 for new quest skills, will need to further increase one day... [DracoRPG] +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 32 +#define ACCOUNT_REG2_NUM 16 +//Should hold the max of GLOBAL/ACCOUNT/ACCOUNT2 (needed for some arrays that hold all three) +#define MAX_REG_NUM 96 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 300 +#define MAX_GUILD_STORAGE 1000 +#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_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 15 // increased max guild skills because of new skills [Sara-chan] +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 +#define MAX_GUARDIANS 8 //Local max per castle. [Skotlex] + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#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 24 +//For item names, which tend to have much longer names. +#define ITEM_NAME_LENGTH 24 +//For Map Names, which the client considers to be 16 in length +#define MAP_NAME_LENGTH 16 + +#define MAX_FRIENDS 40 +#define MAX_MEMOPOINTS 10 + +//These max values can be exceeded and the char/map servers will update them with no problems +//These are just meant to minimize the updating needed between char/map servers as players login. +//Room for initial 10K accounts +#define DEFAULT_MAX_ACCOUNT_ID 2010000 +//Room for initial 100k characters +#define DEFAULT_MAX_CHAR_ID 250000 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[MAX_SLOTS]; +}; + +struct point{ + unsigned short map; + short x,y; +}; + +struct skill { + unsigned short id,lv,flag; +}; + +struct global_reg { + char str[32]; + char value[256]; // [zBuffer] +}; + +//For saving status changes across sessions. [Skotlex] +struct status_change_data { + unsigned short type; //SC_type + int val1, val2, val3, val4, tick; //Remaining duration. +}; + +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 friend { + int account_id; + int char_id; + char name[NAME_LENGTH]; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + int father; + int mother; + int child; + + int base_exp,job_exp,zeny; + + short class_; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,manner; + unsigned char karma; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + int fame; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[NAME_LENGTH]; + unsigned int base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point,save_point,memo_point[MAX_MEMOPOINTS]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + + struct friend friends[MAX_FRIENDS]; //New friend system [Skotlex] +}; + +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 storage { + int dirty; + int account_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_STORAGE]; +}; + +struct guild_storage { + int dirty; + int guild_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + int char_id; + char name[NAME_LENGTH]; + struct map_session_data *sd; + unsigned short map; + unsigned short lv; + unsigned leader : 1, + online : 1; +}; + +struct party { + int party_id; + char name[NAME_LENGTH]; + unsigned exp : 1, + item : 2; //&1: Party-Share (round-robin), &2: pickup style: shared. + short itemc; //For item sharing through round-robin, holds last item receiver. + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class_,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[NAME_LENGTH]; + struct map_session_data *sd; +}; + +struct guild_position { + char name[NAME_LENGTH]; + int mode; + int exp_mode; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[NAME_LENGTH]; +}; + +struct guild_explusion { + char name[NAME_LENGTH]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point; +#ifdef TXT_ONLY + //FIXME: Gotta remove this variable completely, but doing so screws up the format of the txt save file... + int castle_id; +#endif + char name[NAME_LENGTH],master[NAME_LENGTH]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +#ifndef TXT_ONLY + unsigned char save_flag; +#endif +}; + +struct guild_castle { + int castle_id; + char map_name[MAP_NAME_LENGTH]; + 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 hp; + int id; + } guardian[MAX_GUARDIANS]; //New simplified structure. [Skotlex] +}; +struct square { + int val1[5]; + int val2[5]; +}; + +struct fame_list { + int id; + int fame; + char name[NAME_LENGTH]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV, // ギルドのLv + GBI_SKILLPOINT, // ギルドのスキルポイント + GBI_SKILLLV, // ギルドスキルLv +}; + +enum { + 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_GUARDIANRESEARCH=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, +}; + +#ifndef __WIN32 + #ifndef strcmpi + #define strcmpi strcasecmp + #endif + #ifndef stricmp + #define stricmp strcasecmp + #endif + #ifndef strncmpi + #define strncmpi strncasecmp + #endif + #ifndef strnicmp + #define strnicmp strncasecmp + #endif +#else + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #ifndef strncmpi + #define strncmpi strnicmp + #endif +#endif + +#endif // _MMO_H_ diff --git a/src/common/nullpo.c b/src/common/nullpo.c new file mode 100644 index 000000000..8508f1333 --- /dev/null +++ b/src/common/nullpo.c @@ -0,0 +1,94 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include "nullpo.h" +#include "../common/showmsg.h" +// #include "logs.h" // 布石してみる + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullチェック 及び 情報出力 + *-------------------------------------- + */ +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情報出力(外部呼出し向けラッパ) + *-------------------------------------- + */ +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情報出力(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); + + // 最後に改行したか確認 + if (fmt[strlen(fmt)-1] != '\n') + ShowMessage("\n"); + } + } + ShowMessage("--- end nullpo info ----------------------------------------\n"); + + // ここらでnullpoログをファイルに書き出せたら + // まとめて提出できるなと思っていたり。 +} diff --git a/src/common/nullpo.h b/src/common/nullpo.h new file mode 100644 index 000000000..66d984224 --- /dev/null +++ b/src/common/nullpo.h @@ -0,0 +1,237 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#define NULLPO_CHECK 1 + // 全体のスイッチを宣言しているヘッダがあれば + // そこに移動していただけると + +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif + +#ifdef _WIN32 +#define __attribute__(x) /* nothing */ +#endif + + +#define NLP_MARK __FILE__, __LINE__, __func__ + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullチェック 及び 情報出力後 return + *・展開するとifとかreturn等が出るので + * 一行単体で使ってください。 + *・nullpo_ret(x = func()); + * のような使用法も想定しています。 + *-------------------------------------- + * nullpo_ret(t) + * 戻り値 0固定 + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retv(t) + * 戻り値 なし + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retr(ret, t) + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 0 + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 なし + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * 詳細情報出力用 + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ + +#if 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;} + +// 可変引数マクロに関する条件コンパイル +#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用 */ +#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 + +/* その他の場合・・・ orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// 良い方法が思いつかなかったので・・・苦肉の策です。 +// 一応ワーニングは出ないはず + +#define nullpo_ret(t) if((t)){;} +#define nullpo_retv(t) if((t)){;} +#define nullpo_retr(ret, t) if((t)){;} +#define nullpo_retb(t) if((t)){;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) if((t)){;} +#define nullpo_retv_f(t, fmt, ...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, ...) if((t)){;} +#define nullpo_retb_f(t, fmt, ...) if((t)){;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) if((t)){;} +#define nullpo_retv_f(t, fmt, args...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, args...) if((t)){;} +#define nullpo_retb_f(t, fmt, args...) if((t)){;} + +#else +/* その他の場合・・・ orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullチェック 及び 情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullチェック 及び 詳細な情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + * [返り値] + * 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情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpo詳細情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif diff --git a/src/common/plugin.h b/src/common/plugin.h new file mode 100644 index 000000000..2ccefb6bd --- /dev/null +++ b/src/common/plugin.h @@ -0,0 +1,40 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +////// Plugin functions /////////////// + +#define PLUGIN_VERSION "1.02" + +typedef struct _Plugin_Info { + char *name; + char type; + char *version; + char *req_version; + char *description; +} Plugin_Info; + +typedef struct _Plugin_Event_Table { + char *func_name; + char *event_name; +} Plugin_Event_Table; + +////// Plugin Export functions ///////////// + +#define PLUGIN_ALL 0 +#define PLUGIN_LOGIN 1 +#define PLUGIN_CHAR 2 +#define PLUGIN_MAP 8 +#define PLUGIN_CORE 16 + +#define IMPORT_SYMBOL(s,n) (s) = plugin_call_table[n] + +////// Global Plugin variables ///////////// + +#define PLUGIN_INFO struct _Plugin_Info plugin_info +#define PLUGIN_EVENTS_TABLE struct _Plugin_Event_Table plugin_event_table[] +void **plugin_call_table; + +#endif // _PLUGIN_H_ diff --git a/src/common/plugins.c b/src/common/plugins.c new file mode 100644 index 000000000..fbadca065 --- /dev/null +++ b/src/common/plugins.c @@ -0,0 +1,367 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "plugin.h" +#include "plugins.h" +#include "../common/mmo.h" +#include "../common/core.h" +#include "../common/timer.h" +#include "../common/utils.h" +#include "../common/socket.h" +#include "../common/malloc.h" +#include "../common/version.h" +#include "../common/showmsg.h" + +////////////////////////////////////////////// + +typedef struct _Plugin_Event { + void (*func)(void); + struct _Plugin_Event *next; +} Plugin_Event; + +typedef struct _Plugin_Event_List { + char *name; + struct _Plugin_Event_List *next; + struct _Plugin_Event *events; +} Plugin_Event_List; + +static int auto_search = 1; +static int load_priority = 0; +Plugin_Event_List *event_head = NULL; +Plugin *plugin_head = NULL; + +Plugin_Info default_info = { "Unknown", PLUGIN_ALL, "0", PLUGIN_VERSION, "Unknown" }; + +static size_t call_table_size = 0; +static size_t max_call_table = 0; + +////// Plugin Events Functions ////////////////// + +int register_plugin_func (char *name) +{ + Plugin_Event_List *evl; + if (name) { + evl = (Plugin_Event_List *) aMalloc(sizeof(Plugin_Event_List)); + evl->name = (char *) aMalloc (strlen(name) + 1); + + evl->next = event_head; + strcpy(evl->name, name); + evl->events = NULL; + event_head = evl; + } + return 0; +} + +Plugin_Event_List *search_plugin_func (char *name) +{ + Plugin_Event_List *evl = event_head; + while (evl) { + if (strcmpi(evl->name, name) == 0) + return evl; + evl = evl->next; + } + return NULL; +} + +int register_plugin_event (void (*func)(void), char* name) +{ + Plugin_Event_List *evl = search_plugin_func(name); + if (!evl) { + // register event if it doesn't exist already + register_plugin_func(name); + // relocate the new event list + evl = search_plugin_func(name); + } + if (evl) { + Plugin_Event *ev; + + ev = (Plugin_Event *) aMalloc(sizeof(Plugin_Event)); + ev->func = func; + ev->next = NULL; + + if (evl->events == NULL) + evl->events = ev; + else { + Plugin_Event *ev2 = evl->events; + while (ev2) { + if (ev2->next == NULL) { + ev2->next = ev; + break; + } + ev2 = ev2->next; + } + } + } + return 0; +} + +int plugin_event_trigger (char *name) +{ + int c = 0; + Plugin_Event_List *evl = search_plugin_func(name); + if (evl) { + Plugin_Event *ev = evl->events; + while (ev) { + ev->func(); + ev = ev->next; + c++; + } + } + return c; +} + +////// Plugins Call Table Functions ///////// + +int export_symbol (void *var, int offset) +{ + //printf ("0x%x\n", var); + + // add to the end of the list + if (offset < 0) + offset = call_table_size; + + // realloc if not large enough + if ((size_t)offset >= max_call_table) { + max_call_table = 1 + offset; + plugin_call_table = (void**)aRealloc(plugin_call_table, max_call_table*sizeof(void*)); + + // clear the new alloced block + memset(plugin_call_table + call_table_size, 0, (max_call_table-call_table_size)*sizeof(void*)); + } + + // the new table size is delimited by the new element at the end + if ((size_t)offset >= call_table_size) + call_table_size = offset+1; + + // put the pointer at the selected place + plugin_call_table[offset] = var; + return 0; +} + +////// Plugins Core ///////////////////////// + +Plugin *plugin_open (const char *filename) +{ + Plugin *plugin; + Plugin_Info *info; + Plugin_Event_Table *events; + void **procs; + int init_flag = 1; + + //printf ("loading %s\n", filename); + + // Check if the plugin has been loaded before + plugin = plugin_head; + while (plugin) { + // returns handle to the already loaded plugin + if (plugin->state && strcmpi(plugin->filename, filename) == 0) { + //printf ("not loaded (duplicate) : %s\n", filename); + return plugin; + } + plugin = plugin->next; + } + + plugin = (Plugin *)aCalloc(1, sizeof(Plugin)); + plugin->state = -1; // not loaded + + plugin->dll = DLL_OPEN(filename); + if (!plugin->dll) { + //printf ("not loaded (invalid file) : %s\n", filename); + plugin_unload(plugin); + return NULL; + } + + // Retrieve plugin information + plugin->state = 0; // initialising + DLL_SYM (info, plugin->dll, "plugin_info"); + // For high priority plugins (those that are explicitly loaded from the conf file) + // we'll ignore them even (could be a 3rd party dll file) + if ((!info && load_priority == 0) || + (info && ((atof(info->req_version) < atof(PLUGIN_VERSION)) || // plugin is based on older code + (info->type != PLUGIN_ALL && info->type != PLUGIN_CORE && info->type != SERVER_TYPE) || // plugin is not for this server + (info->type == PLUGIN_CORE && SERVER_TYPE != PLUGIN_LOGIN && SERVER_TYPE != PLUGIN_CHAR && SERVER_TYPE != PLUGIN_MAP)))) + { + //printf ("not loaded (incompatible) : %s\n", filename); + plugin_unload(plugin); + return NULL; + } + plugin->info = (info) ? info : &default_info; + + plugin->filename = (char *) aMalloc (strlen(filename) + 1); + strcpy(plugin->filename, filename); + + // Initialise plugin call table (For exporting procedures) + DLL_SYM (procs, plugin->dll, "plugin_call_table"); + if (procs) *procs = plugin_call_table; + + // Register plugin events + DLL_SYM (events, plugin->dll, "plugin_event_table"); + if (events) { + int i = 0; + while (events[i].func_name) { + if (strcmpi(events[i].event_name, "Plugin_Test") == 0) { + int (*test_func)(void); + DLL_SYM (test_func, plugin->dll, events[i].func_name); + if (test_func && test_func() == 0) { + // plugin has failed test, disabling + //printf ("disabled (failed test) : %s\n", filename); + init_flag = 0; + } + } else { + void (*func)(void); + DLL_SYM (func, plugin->dll, events[i].func_name); + if (func) register_plugin_event (func, events[i].event_name); + } + i++; + } + } + + plugin->next = plugin_head; + plugin_head = plugin; + + plugin->state = init_flag; // fully loaded + ShowStatus ("Done loading plugin '"CL_WHITE"%s"CL_RESET"'\n", (info) ? plugin->info->name : filename); + + return plugin; +} + +void plugin_load (const char *filename) +{ + plugin_open(filename); +} + +void plugin_unload (Plugin *plugin) +{ + if (plugin == NULL) + return; + if (plugin->filename) aFree(plugin->filename); + if (plugin->dll) DLL_CLOSE(plugin->dll); + aFree(plugin); +} + +#ifdef _WIN32 +char *DLL_ERROR(void) +{ + static char dllbuf[80]; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dw, 0, dllbuf, 80, NULL); + return dllbuf; +} +#endif + +////// Initialize/Finalize //////////////////// + +int plugins_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, 1020, fp)) { + if(line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "auto_search") == 0) { + if(strcmpi(w2, "yes")==0) + auto_search = 1; + else if(strcmpi(w2, "no")==0) + auto_search = 0; + else auto_search = atoi(w2); + } else if (strcmpi(w1, "plugin") == 0) { + char filename[128]; + sprintf (filename, "plugins/%s%s", w2, DLL_EXT); + plugin_load(filename); + } else if (strcmpi(w1, "import") == 0) + plugins_config_read(w2); + } + fclose(fp); + return 0; +} + +void plugins_init (void) +{ + char *PLUGIN_CONF_FILENAME = "conf/plugin_athena.conf"; + register_plugin_func("Plugin_Init"); + register_plugin_func("Plugin_Final"); + register_plugin_func("Athena_Init"); + register_plugin_func("Athena_Final"); + + // networking + export_symbol (func_parse_table, 18); + export_symbol (RFIFOSKIP, 17); + export_symbol (WFIFOSET, 16); + export_symbol (delete_session, 15); + export_symbol (session, 14); + export_symbol (&fd_max, 13); + export_symbol (addr_, 12); + // timers + export_symbol (get_uptime, 11); + export_symbol (delete_timer, 10); + export_symbol (add_timer_func_list, 9); + export_symbol (add_timer_interval, 8); + export_symbol (add_timer, 7); + export_symbol ((void *)get_svn_revision, 6); + export_symbol (gettick, 5); + // core + export_symbol (&runflag, 4); + export_symbol (arg_v, 3); + export_symbol (&arg_c, 2); + export_symbol (SERVER_NAME, 1); + export_symbol (&SERVER_TYPE, 0); + + load_priority = 1; + plugins_config_read (PLUGIN_CONF_FILENAME); + load_priority = 0; + + if (auto_search) + findfile("plugins", DLL_EXT, plugin_load); + + plugin_event_trigger("Plugin_Init"); + + return; +} + +void plugins_final (void) +{ + Plugin *plugin = plugin_head, *plugin2; + Plugin_Event_List *evl = event_head, *evl2; + Plugin_Event *ev, *ev2; + + plugin_event_trigger("Plugin_Final"); + + while (plugin) { + plugin2 = plugin->next; + plugin_unload(plugin); + plugin = plugin2; + } + + while (evl) { + ev = evl->events; + while (ev) { + ev2 = ev->next; + aFree(ev); + ev = ev2; + } + evl2 = evl->next; + aFree(evl->name); + aFree(evl); + evl = evl2; + } + + aFree(plugin_call_table); + + return; +} diff --git a/src/common/plugins.h b/src/common/plugins.h new file mode 100644 index 000000000..d642b5965 --- /dev/null +++ b/src/common/plugins.h @@ -0,0 +1,61 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PLUGINS_H_ +#define _PLUGINS_H_ + +////// Dynamic Link Library functions /////////////// + +#ifdef _WIN32 + + #include + #define DLL_OPEN(x) LoadLibrary(x) + #define DLL_SYM(x,y,z) (FARPROC)(x) = GetProcAddress(y,z) + #define DLL_CLOSE(x) FreeLibrary(x) + #define DLL_EXT ".dll" + #define DLL HINSTANCE + char *DLL_ERROR(void); + +#else + + #include + #define DLL_OPEN(x) dlopen(x,RTLD_NOW) + #define DLL_SYM(x,y,z) (x) = (void *)dlsym(y,z) + #define DLL_CLOSE(x) dlclose(x) + #define DLL_ERROR dlerror + + #ifdef CYGWIN + #define DLL_EXT ".dll" + #else + #define DLL_EXT ".so" + #endif + #define DLL void * + +#endif + +////// Plugin Definitions /////////////////// + +typedef struct _Plugin { + DLL dll; + char state; + char *filename; + struct _Plugin_Info *info; + struct _Plugin *next; +} Plugin; + +///////////////////////////////////////////// + +int register_plugin_func (char *); +int register_plugin_event (void (*)(void), char *); +int plugin_event_trigger (char *); + +int export_symbol (void *, int); +#define EXPORT_SYMBOL(s) export_symbol((s), -1); + +Plugin *plugin_open (const char *); +void plugin_load (const char *); +void plugin_unload (Plugin *); +void plugins_init (void); +void plugins_final (void); + +#endif // _PLUGINS_H_ diff --git a/src/common/showmsg.c b/src/common/showmsg.c new file mode 100644 index 000000000..7d940b189 --- /dev/null +++ b/src/common/showmsg.c @@ -0,0 +1,219 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include +#include "showmsg.h" + +#ifdef _WIN32 + #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 + #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 + +int msg_silent; //Specifies how silent the console is. +char tmp_output[1024] = {"\0"}; +char timestamp_format[20] = ""; //For displaying Timestamps +// by MC Cameri +int _vShowMessage(enum msg_type flag, const char *string, va_list ap) +{ + // _ShowMessage MUST be used instead of printf as of 10/24/2004. + // Return: 0 = Successful, 1 = Failed. +// int ret = 0; + char prefix[100]; +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + FILE *fp; +#endif + + if (!string || strlen(string) <= 0) { + ShowError("Empty string passed to _vShowMessage().\n"); + return 1; + } + + if (timestamp_format) + { //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_DEBUG && !SHOW_DEBUG_MSG) || + (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) + ) ; //Do not print it. + else { + if (flag == MSG_ERROR || flag == MSG_FATALERROR || flag == MSG_SQL) + { //Send Errors to StdErr [Skotlex] + fprintf (stderr, "%s ", prefix); + vfprintf (stderr, string, ap); + fflush (stderr); + } else { + if (flag != MSG_NONE) + printf ("%s ", prefix); + vprintf (string, ap); + fflush (stdout); + } + } + +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + if(strlen(DEBUGLOGPATH) > 0) { + fp=fopen(DEBUGLOGPATH,"a"); + if (fp == NULL) { + printf(CL_RED"[ERROR]"CL_RESET": Could not open '"CL_WHITE"%s"CL_RESET"', access denied.\n",DEBUGLOGPATH); + fflush(stdout); + return 0; + } + fprintf(fp,"%s ", prefix); + vfprintf(fp,string,ap); + fclose(fp); + } else { + printf(CL_RED"[ERROR]"CL_RESET": DEBUGLOGPATH not defined!\n"); + } +#endif + + va_end(ap); +/* + if ((core_config.debug_output_level > -1) && (flag >= core_config.debug_output_level)) { + FILE *fp; + fp=fopen(OUTPUT_MESSAGES_LOG,"a"); + if (fp == NULL) { + ShowError("Could not open '"CL_WHITE"%s"CL_RESET"', file not found.\n",OUTPUT_MESSAGES_LOG); + fflush(stdout); + return; + } + StripColor(output); + strcpy(output,"\r"); + fwrite(output,strlen(output),1,fp); + fclose(fp); + } +*/ + 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, ...) +{ + va_list ap; + + va_start(ap, string); + return _vShowMessage(flag, string, ap); +} + +// direct printf replacement +int ShowMessage(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_NONE, string, ap); +} +int ShowStatus(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_STATUS, string, ap); +} +int ShowSQL(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_SQL, string, ap); +} +int ShowInfo(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_INFORMATION, string, ap); +} +int ShowNotice(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_NOTICE, string, ap); +} +int ShowWarning(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_WARNING, string, ap); +} +int ShowDebug(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_DEBUG, string, ap); +} +int ShowError(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_ERROR, string, ap); +} +int ShowFatalError(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_FATALERROR, string, ap); +} diff --git a/src/common/showmsg.h b/src/common/showmsg.h new file mode 100644 index 000000000..af851de40 --- /dev/null +++ b/src/common/showmsg.h @@ -0,0 +1,88 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SHOWMSG_H_ +#define _SHOWMSG_H_ + +#define SHOW_DEBUG_MSG 1 + +// 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 + +#ifdef _WIN32 + #define CL_RESET "" + #define CL_CLS "" + #define CL_CLL "" + #define CL_BOLD "" + #define CL_NORMAL CL_RESET + #define CL_NONE CL_RESET + #define CL_WHITE "" + #define CL_GRAY "" + #define CL_RED "" + #define CL_GREEN "" + #define CL_YELLOW "" + #define CL_BLUE "" + #define CL_MAGENTA "" + #define CL_CYAN "" + #define CL_BT_YELLOW "" + #define CL_WTBL "" + #define CL_XXBL "" + #define CL_PASS "" +#else + #define CL_RESET "\033[0;0m" + #define CL_CLS "\033[2J" + #define CL_CLL "\033[K" + + // font settings + #define CL_BOLD "\033[1m" + #define CL_NORMAL CL_RESET + #define CL_NONE CL_RESET + + #define CL_WHITE "\033[1;29m" + #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" + + #define CL_BT_YELLOW "\033[1;33m" + #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 +#endif + +extern int msg_silent; //Specifies how silent the console is. [Skotlex] +extern char timestamp_format[20]; //For displaying Timestamps [Skotlex] +extern char tmp_output[1024]; + +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 int ShowMessage(const char *, ...); +extern int ShowStatus(const char *, ...); +extern int ShowSQL(const char *, ...); +extern int ShowInfo(const char *, ...); +extern int ShowNotice(const char *, ...); +extern int ShowWarning(const char *, ...); +extern int ShowDebug(const char *, ...); +extern int ShowError(const char *, ...); +extern int ShowFatalError(const char *, ...); + +#endif diff --git a/src/common/socket.c b/src/common/socket.c new file mode 100644 index 000000000..2558e83ac --- /dev/null +++ b/src/common/socket.c @@ -0,0 +1,1390 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +#include +#include +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#include +#include + +#ifndef SIOCGIFCONF +#include // SIOCGIFCONF on Solaris, maybe others? [Shinomori] +#endif + +#endif + +#include +#include + +#include "../common/socket.h" +#include "../common/mmo.h" // [Valaris] thanks to fov +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +fd_set readfds; +int fd_max; +time_t last_tick; +time_t stall_time = 60; +int ip_rules = 1; + +// reuse port +#ifndef SO_REUSEPORT + #define SO_REUSEPORT 15 +#endif + +// values derived from freya +// a player that send more than 2k is probably a hacker without be parsed +// biggest known packet: S 0153 .w .?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes) +size_t rfifo_size = (16*1024); +size_t wfifo_size = (16*1024); + +#ifndef TCP_FRAME_LEN +#define TCP_FRAME_LEN 1053 +#endif + +#define CONVIP(ip) ip&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,ip>>24 + +struct socket_data *session[FD_SETSIZE]; + +static int null_parse(int fd); +static int (*default_func_parse)(int) = null_parse; + +static int null_console_parse(char *buf); +static int (*default_console_parse)(char*) = null_console_parse; +#ifndef MINICORE +static int connect_check(unsigned int ip); +#else + #define connect_check(n) 1 +#endif + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_defaultparse(int (*defaultparse)(int)) +{ + default_func_parse = defaultparse; +} + +void set_nonblocking(int fd, int yes) { + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); +} + +static void setsocketopts(int fd) +{ + int yes = 1; // reuse fix + size_t buff; + size_t buff_size = sizeof (buff); + + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); +#ifdef SO_REUSEPORT + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); +#endif + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); + +#ifdef __WIN32 +{ //set SO_LINGER option (from Freya) + //(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/closesocket_2.asp) + struct linger opt; + opt.l_onoff = 1; + opt.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&opt, sizeof(opt))) + ShowWarning("setsocketopts: Unable to set SO_LINGER mode for connection %d!\n",fd); +} +#endif + + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&wfifo_size , sizeof(wfifo_size )); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&buff, &buff_size) == 0) + { + if (buff < wfifo_size) //We are not going to complain if we get more, aight? [Skotlex] + ShowError("setsocketopts: Requested send buffer size failed (requested %d bytes buffer, received a buffer of size %d)\n", wfifo_size, buff); + } + else + perror("setsocketopts: getsockopt wfifo"); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &rfifo_size , sizeof(rfifo_size )); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &buff, &buff_size) == 0) + { + if (buff < rfifo_size) + ShowError("setsocketopts: Requested receive buffer size failed (requested %d bytes buffer, received a buffer of size %d)\n", rfifo_size, buff); + } + else + perror("setsocketopts: getsockopt rfifo"); +} + +/*====================================== + * CORE : Socket Sub Function + *-------------------------------------- + */ +static void set_eof(int fd) +{ //Marks a connection eof and invokes the parse_function to disconnect it right away. [Skotlex] + if (session_isActive(fd)) + session[fd]->eof=1; +} + +static int recv_to_fifo(int fd) +{ + int len; + + if( (fd<0) || (fd>=FD_SETSIZE) || (NULL==session[fd]) ) + return -1; + + if(session[fd]->eof) + return -1; + +#ifdef __WIN32 + len=recv(fd,(char *)session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd), 0); + if (len == SOCKET_ERROR) { + if (WSAGetLastError() == WSAECONNABORTED) { + ShowWarning("recv_to_fifo: Software caused connection abort on session #%d\n", fd); + FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it. +// exit(1); //Windows can't really recover from this one. [Skotlex] + } + if (WSAGetLastError() != WSAEWOULDBLOCK) { +// ShowDebug("recv_to_fifo: error %d, ending connection #%d\n", WSAGetLastError(), fd); + set_eof(fd); + } + return 0; + } +#else + len=read(fd,session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd)); + if (len == -1) + { + if (errno == ECONNABORTED) + { + ShowFatalError("recv_to_fifo: Network broken (Software caused connection abort on session #%d)\n", fd); +// exit(1); //Temporal debug, maybe this can be fixed. + } + if (errno != EAGAIN) { //Connection error. +// perror("closing session: recv_to_fifo"); + set_eof(fd); + } + return 0; + } +#endif + if (len <= 0) { //Normal connection end. + set_eof(fd); + return 0; + } + + session[fd]->rdata_size+=len; + session[fd]->rdata_tick = last_tick; + return 0; +} + +static int send_from_fifo(int fd) +{ + int len; + if( !session_isValid(fd) ) + return -1; + +// if (s->eof) // if we close connection, we can not send last information (you're been disconnected, etc...) [Yor] +// return -1; +/* + // clear write buffer if not connected <- I really like more the idea of sending the last information. [Skotlex] + if( session[fd]->eof ) + { + session[fd]->wdata_size = 0; + return -1; + } +*/ + + if (session[fd]->wdata_size == 0) + return 0; + +#ifdef __WIN32 + len=send(fd, (const char *)session[fd]->wdata,session[fd]->wdata_size, 0); + if (len == SOCKET_ERROR) { + if (WSAGetLastError() == WSAECONNABORTED) { + ShowWarning("send_from_fifo: Software caused connection abort on session #%d\n", fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it. + } + if (WSAGetLastError() != WSAEWOULDBLOCK) { +// ShowDebug("send_from_fifo: error %d, ending connection #%d\n", WSAGetLastError(), fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } +#else + len=write(fd,session[fd]->wdata,session[fd]->wdata_size); + if (len == -1) + { + if (errno == ECONNABORTED) + { + ShowWarning("send_from_fifo: Network broken (Software caused connection abort on session #%d)\n", fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + if (errno != EAGAIN) { +// perror("closing session: send_from_fifo"); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } +#endif + + //{ int i; ShowMessage("send %d : ",fd); for(i=0;iwdata[i]); } ShowMessage("\n");} + if(len>0){ + if((unsigned int)lenwdata_size){ + memmove(session[fd]->wdata,session[fd]->wdata+len,session[fd]->wdata_size-len); + session[fd]->wdata_size-=len; + } else { + session[fd]->wdata_size=0; + } + } + return 0; +} + +void flush_fifo(int fd) +{ + if(session[fd] != NULL && session[fd]->func_send == send_from_fifo) + { + set_nonblocking(fd, 1); + send_from_fifo(fd); + set_nonblocking(fd, 0); + } +} + +void flush_fifos(void) +{ + int i; + for(i=1;ifunc_send == send_from_fifo) + send_from_fifo(i); +} + +static int null_parse(int fd) +{ + ShowMessage("null_parse : %d\n",fd); + session[fd]->rdata_pos = session[fd]->rdata_size; //RFIFOSKIP(fd, RFIFOREST(fd)); simplify calculation + return 0; +} + +/*====================================== + * CORE : Socket Function + *-------------------------------------- + */ + +static int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; +#ifdef __WIN32 + int len; +#else + socklen_t len; +#endif + //ShowMessage("connect_client : %d\n",listen_fd); + + len=sizeof(client_address); + + fd = accept(listen_fd,(struct sockaddr*)&client_address,&len); +#ifdef __WIN32 + if (fd == SOCKET_ERROR || fd == INVALID_SOCKET || fd < 0) { + ShowError("accept failed (code %d)!\n", fd, WSAGetLastError()); + return -1; + } +#else + if(fd==-1) { + perror("accept"); + return -1; + } +#endif + + if(fd_max<=fd) fd_max=fd+1; + + setsocketopts(fd); + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("connect_client (set nonblock)"); +#endif + + if (ip_rules && !connect_check(*(unsigned int*)(&client_address.sin_addr))) { + do_close(fd); + return -1; + } else + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE_A(session[fd]->rdata, unsigned char, rfifo_size); + CREATE_A(session[fd]->wdata, unsigned char, wfifo_size); + + session[fd]->max_rdata = (int)rfifo_size; + session[fd]->max_wdata = (int)wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + if(!session[listen_fd]->func_parse) + session[fd]->func_parse = default_func_parse; + else + session[fd]->func_parse = session[listen_fd]->func_parse; + session[fd]->client_addr = client_address; + session[fd]->rdata_tick = last_tick; + session[fd]->type = SESSION_UNKNOWN; // undefined type + + //ShowMessage("new_session : %d %d\n",fd,session[fd]->eof); + return fd; +} + +int make_listen_port(int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = socket( AF_INET, SOCK_STREAM, 0 ); +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if (fd == -1) { + perror("make_listen_port:socket()"); + exit(1); + } +#endif + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_listen_port (set nonblock)"); +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl( INADDR_ANY ); + server_address.sin_port = htons((unsigned short)port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("bind failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result == -1 ) { + perror("bind"); + exit(1); + } +#endif + result = listen( fd, 5 ); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("listen failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result != 0 ) { /* error */ + perror("listen"); + exit(1); + } +#endif + if ( fd < 0 || fd > FD_SETSIZE ) + { //Crazy error that can happen in Windows? (info from Freya) + ShowFatalError("listen() returned invalid fd %d!\n",fd); + exit(1); + } + + if(fd_max<=fd) fd_max=fd+1; + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + return fd; +} + +int make_listen_bind(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = (int)socket( AF_INET, SOCK_STREAM, 0 ); + +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if (fd == -1) { + perror("make_listen_port:socket()"); + exit(1); + } +#endif + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_listen_bind (set nonblock)"); +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons((unsigned short)port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("bind failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result == -1 ) { + perror("bind"); + exit(1); + } +#endif + result = listen( fd, 5 ); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("listen failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result != 0) { /* error */ + perror("listen"); + exit(1); + } +#endif + if ( fd < 0 || fd > FD_SETSIZE ) + { //Crazy error that can happen in Windows? (info from Freya) + ShowFatalError("listen() returned invalid fd %d!\n",fd); + exit(1); + } + + if(fd_max<=fd) fd_max=fd+1; + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + ShowStatus("Open listen port on %d.%d.%d.%d:%i\n", + (ip)&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,(ip>>24)&0xFF,port); + + return fd; +} + +// Console Reciever [Wizputer] +int console_recieve(int i) { + int n; + char *buf; + + CREATE_A(buf, char, 64); + memset(buf,0,sizeof(64)); + + n = read(0, buf , 64); + if ( n < 0 ) + ShowError("Console input read error\n"); + else + { + ShowNotice ("Sorry, the console is currently non-functional.\n"); +// session[0]->func_console(buf); + } + + aFree(buf); + return 0; +} + +void set_defaultconsoleparse(int (*defaultparse)(char*)) +{ + default_console_parse = defaultparse; +} + +static int null_console_parse(char *buf) +{ + ShowMessage("null_console_parse : %s\n",buf); + return 0; +} + +// function parse table +// To-do: -- use dynamic arrays +// -- add a register_parse_func(); +struct func_parse_table func_parse_table[SESSION_MAX]; + +int default_func_check (struct socket_data *sd) { return 1; } + +void func_parse_check (struct socket_data *sd) +{ + int i; + for (i = SESSION_HTTP; i < SESSION_MAX; i++) { + if (func_parse_table[i].func && + func_parse_table[i].check && + func_parse_table[i].check(sd) != 0) + { + sd->type = i; + sd->func_parse = func_parse_table[i].func; + return; + } + } + + // undefined -- treat as raw socket (using default parse) + sd->type = SESSION_RAW; +} + +// Console Input [Wizputer] +int start_console(void) { + + //Until a better plan is came up with... can't be using session[0] anymore! [Skotlex] + ShowNotice("The console is currently nonfunctional.\n"); + return 0; + + FD_SET(0,&readfds); + + if (!session[0]) { // dummy socket already uses fd 0 + CREATE(session[0], struct socket_data, 1); + } + memset(session[0],0,sizeof(*session[0])); + + session[0]->func_recv = console_recieve; + session[0]->func_console = default_console_parse; + + return 0; +} + +int make_connection(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = (int)socket( AF_INET, SOCK_STREAM, 0 ); + +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + return -1; + } +#else + if (fd == -1) { + perror("make_connection:socket()"); + return -1; + } +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons((unsigned short)port); + + ShowStatus("Connecting to %d.%d.%d.%d:%i\n", + (ip)&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,(ip>>24)&0xFF,port); + + result = connect(fd, (struct sockaddr *)(&server_address), sizeof(struct sockaddr_in)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("connect failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + do_close(fd); + return -1; + } +#else + if (result < 0) { //This is only used when the map/char server try to connect to each other, so it can be handled. [Skotlex] + perror("make_connection"); + do_close(fd); + return -1; + } +#endif +//Now the socket can be made non-blocking. [Skotlex] +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_connection (set nonblock)"); +#endif + + if (fd_max <= fd) + fd_max = fd + 1; + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE_A(session[fd]->rdata, unsigned char, rfifo_size); + CREATE_A(session[fd]->wdata, unsigned char, wfifo_size); + + session[fd]->max_rdata = (int)rfifo_size; + session[fd]->max_wdata = (int)wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + session[fd]->rdata_tick = last_tick; + + return fd; +} + +int delete_session(int fd) +{ + if (fd <= 0 || fd >= FD_SETSIZE) + return -1; + FD_CLR(fd, &readfds); + if (session[fd]){ + if (session[fd]->rdata) + aFree(session[fd]->rdata); + if (session[fd]->wdata) + aFree(session[fd]->wdata); + if (session[fd]->session_data) + aFree(session[fd]->session_data); + aFree(session[fd]); + session[fd] = NULL; + } + //ShowMessage("delete_session:%d\n",fd); + return 0; +} + +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 + (int)addition > session[fd]->max_wdata ) + { // grow rule; grow in multiples of wfifo_size + newsize = wfifo_size; + while( session[fd]->wdata_size + addition > newsize ) newsize += newsize; + } + else if( session[fd]->max_wdata>=FIFOSIZE_SERVERLINK) { + //Inter-server adjust. [Skotlex] + if ((session[fd]->wdata_size+(int)addition)*4 < session[fd]->max_wdata) + newsize = session[fd]->max_wdata/2; + else + return 0; //No change + } else if( session[fd]->max_wdata>(int)wfifo_size && + (session[fd]->wdata_size+(int)addition)*4 < session[fd]->max_wdata ) + { // shrink rule, shrink by 2 when only a quater of the fifo is used, don't shrink below 4*addition + newsize = session[fd]->max_wdata/2; + } + else // no change + return 0; + + RECREATE(session[fd]->wdata, unsigned char, newsize); + session[fd]->max_wdata = (int)newsize; + + return 0; +} + +int WFIFOSET(int fd,int 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 + unsigned char *sin_addr = (unsigned char *)&s->client_addr.sin_addr; + ShowFatalError("socket: Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %d byteson a %d/%d bytes buffer.\n", fd, + sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3], len, s->wdata_size, s->max_wdata); + ShowDebug("Likely command that caused it: 0x%x\n", WFIFOW(fd,0)); + // no other chance, make a better fifo model + exit(1); + } + + s->wdata_size += len; + // always keep a wfifo_size reserve in the buffer + // For inter-server connections, let the reserve be 1/8th of the link size. + newreserve = s->wdata_size + (s->max_wdata>=FIFOSIZE_SERVERLINK?FIFOSIZE_SERVERLINK<<3:wfifo_size); + + if (s->wdata_size > (TCP_FRAME_LEN)) + send_from_fifo(fd); + + // realloc after sending + // readfifo does not need to be realloced at all + // Even the inter-server buffer may need reallocating! [Skotlex] + realloc_writefifo(fd, newreserve); + + return 0; +} + +int do_sendrecv(int next) +{ + fd_set rfd,wfd,efd; //Added the Error Set so that such sockets can be made eof. They are the same as the rfd for now. [Skotlex] + struct timeval timeout; + int ret,i; + + last_tick = time(0); + + //memcpy(&rfd, &readfds, sizeof(rfd)); + //memcpy(&efd, &readfds, sizeof(efd)); + FD_ZERO(&wfd); + + for (i = 1; i < fd_max; i++){ //Session 0 is never a valid session, so it's best to skip it. [Skotlex] + if(!session[i]) { + if (FD_ISSET(i, &readfds)){ + ShowDebug("force clear fds %d\n", i); + FD_CLR(i, &readfds); + //FD_CLR(i, &rfd); + //FD_CLR(i, &efd); + } + continue; + } + if(session[i]->wdata_size) + FD_SET(i, &wfd); + } + + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + memcpy(&rfd, &readfds, sizeof(rfd)); + memcpy(&efd, &readfds, sizeof(efd)); + ret = select(fd_max, &rfd, &wfd, &efd, &timeout); + +#ifdef __WIN32 + if (ret == SOCKET_ERROR) { + if (WSAGetLastError() == WSAEWOULDBLOCK) + return 0; //Eh... try again later? + ShowError("do_sendrecv: select error (code %d)\n", WSAGetLastError()); +#else + if (ret < 0) { + perror("do_sendrecv"); + if (errno == 11) //Isn't there a constantI can use instead of this hardcoded value? This should be "resource temporarily unavailable": ie: try again. + return 0; +#endif + + //if error, remove invalid connections + //Individual socket handling code shamelessly assimilated from Freya :3 + // an error give invalid values in fd_set structures -> init them again + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_ZERO(&efd); + for(i = 1; i < fd_max; i++) { //Session 0 is not parsed, it's a 'vacuum' for disconnected sessions. [Skotlex] + if (!session[i]) { +#ifdef __WIN32 + //Debug to locate runaway sockets in Windows [Skotlex] + if (FD_ISSET(i, &readfds)) { + FD_CLR(i, &readfds); + ShowDebug("Socket %d was set (read fifos) without a session, removed.\n", i); + } +#endif + continue; + } + if (FD_ISSET(i, &readfds)){ + FD_SET(i, &rfd); + FD_SET(i, &efd); + } + if (session[i]->wdata_size) + FD_SET(i, &wfd); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(i + 1, &rfd, &wfd, &efd, &timeout) >= 0 && !FD_ISSET(i, &efd)) { + if (FD_ISSET(i, &wfd)) { + if (session[i]->func_send) + session[i]->func_send(i); + FD_CLR(i, &wfd); + } + if (FD_ISSET(i, &rfd)) { + if (session[i]->func_recv) + session[i]->func_recv(i); + FD_CLR(i, &rfd); + } + FD_CLR(i, &efd); + } else { + ShowDebug("do_sendrecv: Session #%d caused error in select(), disconnecting.\n", i); + set_eof(i); // set eof + // an error gives invalid values in fd_set structures -> init them again + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_ZERO(&efd); + } + } + return 0; + }else if(ret > 0) { + for (i = 1; i < fd_max; i++){ + if(!session[i]) + continue; + + if(FD_ISSET(i,&efd)){ + //ShowMessage("error:%d\n",i); + ShowDebug("do_sendrecv: Connection error on Session %d.\n", i); + set_eof(i); + continue; + } + + if (FD_ISSET(i, &wfd)) { + //ShowMessage("write:%d\n",i); + if(session[i]->func_send) + session[i]->func_send(i); + } + + if(FD_ISSET(i,&rfd)){ + //ShowMessage("read:%d\n",i); + if(session[i]->func_recv) + session[i]->func_recv(i); + } + + + if(session[i] && session[i]->eof) //The session check is for when the connection ended in func_parse + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + if (session[i]->func_parse) + session[i]->func_parse(i); //This should close the session inmediately. + } + } // for (i = 0 + } + return 0; +} + +int do_parsepacket(void) +{ + int i; + struct socket_data *sd; + for(i = 1; i < fd_max; i++){ + sd = session[i]; + if(!sd) + continue; + if ((sd->rdata_tick != 0) && DIFF_TICK(last_tick,sd->rdata_tick) > stall_time) { + ShowInfo ("Session #%d timed out\n", i); + sd->eof = 1; + } + if(sd->rdata_size == 0 && sd->eof == 0) + continue; + if(sd->func_parse){ + if(sd->type == SESSION_UNKNOWN) + func_parse_check(sd); + if(sd->type != SESSION_UNKNOWN) + sd->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) { + session[i]->eof = 1; + continue; + } + } + RFIFOHEAD(i); + RFIFOFLUSH(i); + } + return 0; +} + +/* DDoS 攻撃対策 */ +#ifndef MINICORE +enum { + ACO_DENY_ALLOW=0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, +}; + +struct _access_control { + unsigned int ip; + unsigned int mask; +}; + +static struct _access_control *access_allow; +static struct _access_control *access_deny; +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 = 3000; +static int ddos_autoreset = 600*1000; + +struct _connect_history { + struct _connect_history *next; + struct _connect_history *prev; + int status; + int count; + unsigned int ip; + unsigned int tick; +}; +static struct _connect_history *connect_history[0x10000]; +static int connect_check_(unsigned int ip); + +// 接続できるかどうかの確認 +// false : 接続OK +// true : 接続NG +static int connect_check(unsigned int ip) { + int result = connect_check_(ip); + if(access_debug) { + ShowMessage("connect_check: Connection from %d.%d.%d.%d %s\n", + CONVIP(ip),result ? "allowed." : "denied!"); + } + return result; +} + +static int connect_check_(unsigned int ip) { + struct _connect_history *hist = connect_history[ip & 0xFFFF]; + struct _connect_history *hist_new; + int i,is_allowip = 0,is_denyip = 0,connect_ok = 0; + + // allow , deny リストに入っているか確認 + for(i = 0;i < access_allownum; i++) { + if((ip & access_allow[i].mask) == (access_allow[i].ip & access_allow[i].mask)) { + if(access_debug) { + ShowMessage("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; + } + } + for(i = 0;i < access_denynum; i++) { + if((ip & access_deny[i].mask) == (access_deny[i].ip & access_deny[i].mask)) { + if(access_debug) { + ShowMessage("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; + } + } + // コネクト出来るかどうか確認 + // connect_ok + // 0 : 無条件に拒否 + // 1 : 田代砲チェックの結果次第 + // 2 : 無条件に許可 + switch(access_order) { + case ACO_DENY_ALLOW: + default: + if(is_allowip) { + connect_ok = 2; + } else if(is_denyip) { + connect_ok = 0; + } else { + connect_ok = 1; + } + break; + case ACO_ALLOW_DENY: + if(is_denyip) { + connect_ok = 0; + } else if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 1; + } + break; + case ACO_MUTUAL_FAILTURE: + if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 0; + } + break; + } + + // 接続履歴を調べる + while(hist) { + if(ip == hist->ip) { + // 同じIP発見 + if(hist->status) { + // ban フラグが立ってる + return (connect_ok == 2 ? 1 : 0); + } else if(DIFF_TICK(gettick(),hist->tick) < ddos_interval) { + // ddos_interval秒以内にリクエスト有り + hist->tick = gettick(); + if(hist->count++ >= ddos_count) { + // ddos 攻撃を検出 + hist->status = 1; + ShowWarning("connect_check: DDOS Attack detected from %d.%d.%d.%d!\n", + CONVIP(ip)); + return (connect_ok == 2 ? 1 : 0); + } else { + return connect_ok; + } + } else { + // ddos_interval秒以内にリクエスト無いのでタイマークリア + hist->tick = gettick(); + hist->count = 0; + return connect_ok; + } + } + hist = hist->next; + } + // IPリストに無いので新規作成 + hist_new = (struct _connect_history *) aCalloc(1,sizeof(struct _connect_history)); + hist_new->ip = ip; + hist_new->tick = gettick(); + if(connect_history[ip & 0xFFFF] != NULL) { + hist = connect_history[ip & 0xFFFF]; + hist->prev = hist_new; + hist_new->next = hist; + } + connect_history[ip & 0xFFFF] = hist_new; + return connect_ok; +} + +static int connect_check_clear(int tid,unsigned int tick,int id,int data) { + int i; + int clear = 0; + int list = 0; + struct _connect_history *hist , *hist2; + for(i = 0;i < 0x10000 ; i++) { + hist = connect_history[i]; + while(hist) { + if ((DIFF_TICK(tick,hist->tick) > ddos_interval * 3 && !hist->status) || + (DIFF_TICK(tick,hist->tick) > ddos_autoreset && hist->status)) { + // clear data + hist2 = hist->next; + if(hist->prev) { + hist->prev->next = hist->next; + } else { + connect_history[i] = hist->next; + } + if(hist->next) { + hist->next->prev = hist->prev; + } + aFree(hist); + hist = hist2; + clear++; + } else { + hist = hist->next; + } + list++; + } + } + if(access_debug) { + ShowMessage("connect_check_clear: Cleared %d of %d from IP list.\n", clear, list); + } + return list; +} + +// IPマスクチェック +int access_ipmask(const char *str,struct _access_control* acc) +{ + unsigned int mask=0,i=0,m,ip, a0,a1,a2,a3; + if( !strcmp(str,"all") ) { + ip = 0; + mask = 0; + } else { + if( sscanf(str,"%d.%d.%d.%d%n",&a0,&a1,&a2,&a3,&i)!=4 || i==0) { + ShowError("access_ipmask: Unknown format %s!\n",str); + return 0; + } + ip = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + + if(sscanf(str+i,"/%d.%d.%d.%d",&a0,&a1,&a2,&a3)==4 ){ + mask = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + } else if(sscanf(str+i,"/%d",&m) == 1) { + for(i=0;i> 1) | 0x80000000; + } + mask = ntohl(mask); + } else { + mask = 0xFFFFFFFF; + } + } + if(access_debug) { + ShowMessage("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) { + int i; + 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,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"stall_time")==0){ + stall_time = atoi(w2); + #ifndef MINICORE + } else if(strcmpi(w1,"enable_ip_rules")==0){ + if(strcmpi(w2,"yes")==0) + ip_rules = 1; + else if(strcmpi(w2,"no")==0) + ip_rules = 0; + else ip_rules = atoi(w2); + } else if(strcmpi(w1,"order")==0){ + access_order=atoi(w2); + if(strcmpi(w2,"deny,allow")==0) access_order=ACO_DENY_ALLOW; + if(strcmpi(w2,"allow,deny")==0) access_order=ACO_ALLOW_DENY; + if(strcmpi(w2,"mutual-failure")==0) access_order=ACO_MUTUAL_FAILTURE; + } else if(strcmpi(w1,"allow")==0){ + access_allow = (struct _access_control *) aRealloc(access_allow,(access_allownum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_allow[access_allownum])) { + access_allownum++; + } + } else if(strcmpi(w1,"deny")==0){ + access_deny = (struct _access_control *) aRealloc(access_deny,(access_denynum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_deny[access_denynum])) { + access_denynum++; + } + } 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")){ + if(strcmpi(w2,"yes")==0) + access_debug = 1; + else if(strcmpi(w2,"no")==0) + access_debug = 0; + else access_debug = atoi(w2); + #endif + } else if (strcmpi(w1, "import") == 0) + socket_config_read(w2); + } + fclose(fp); + return 0; +} + +int RFIFOSKIP(int fd,int len) +{ + struct socket_data *s; + + if ( !session_isActive(fd) ) //Nullpo error here[Kevin] + return 0; + + s = session[fd]; + + if (s->rdata_size-s->rdata_pos-len<0) { + //fprintf(stderr,"too many skip\n"); + //exit(1); + //better than a COMPLETE program abort // TEST! :) + ShowError("too many skip (%d) now skipped: %d (FD: %d)\n", len, RFIFOREST(fd), fd); + len = RFIFOREST(fd); + } + s->rdata_pos = s->rdata_pos+len; + return 0; +} + + +unsigned int addr_[16]; // ip addresses of local host (host byte order) +unsigned int naddr_ = 0; // # of ip addresses + +void socket_final (void) +{ + int i; +#ifndef MINICORE + struct _connect_history *hist , *hist2; + for(i = 0; i < 0x10000; i++) { + hist = connect_history[i]; + while(hist) { + hist2 = hist->next; + aFree(hist); + hist = hist2; + } + } + if (access_allow) + aFree(access_allow); + if (access_deny) + aFree(access_deny); +#endif + + for (i = 1; i < fd_max; i++) { + if(session[i]) + delete_session(i); + } + + // session[0] のダミーデータを削除 + aFree(session[0]->rdata); + aFree(session[0]->wdata); + aFree(session[0]); +} + +//Closes a socket. +//Needed to simplify shutdown code as well as manage the subtle differences in socket management from Windows and *nix. +void do_close(int fd) +{ +//We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. +#ifdef __WIN32 +// shutdown(fd, SD_BOTH); //FIXME: Shutdown requires winsock2.h! What would be the proper shutting down method for winsock1? + if (session[fd] && session[fd]->func_send == send_from_fifo) + session[fd]->func_send(fd); //Flush the data as it is gonna be closed down, but it may not succeed as it is a nonblocking socket! [Skotlex] + closesocket(fd); +#else + if (close(fd)) + perror("do_close: close"); +#endif + if (session[fd]) + delete_session(fd); +} + +void socket_init (void) +{ + char *SOCKET_CONF_FILENAME = "conf/packet_athena.conf"; +#ifdef __WIN32 + char** a; + unsigned int i; + char fullhost[255]; + struct hostent* hent; + + /* Start up the windows networking */ + WORD version_wanted = MAKEWORD(1, 1); //Demand at least WinSocket version 1.1 (from Freya) + WSADATA wsaData; + + if ( WSAStartup(version_wanted, &wsaData) != 0 ) { + ShowFatalError("SYSERR: WinSock not available!\n"); + exit(1); + } + + if(gethostname(fullhost, sizeof(fullhost)) == SOCKET_ERROR) { + ShowError("Ugg.. no hostname defined!\n"); + return; + } + + // 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. + hent = gethostbyname(fullhost); + if (hent == NULL) { + ShowError("Cannot resolve our own hostname to a IP address"); + return; + } + + a = hent->h_addr_list; + for(i = 0; a[i] != 0 && i < 16; ++i) { + unsigned long addr1 = ntohl(*(unsigned long*) a[i]); + addr_[i] = addr1; + } + naddr_ = i; +#else + int pos; + int fdes = socket(AF_INET, SOCK_STREAM, 0); + char buf[16 * sizeof(struct ifreq)]; + struct ifconf ic; + + // 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(ioctl(fdes, SIOCGIFCONF, &ic) == -1) { + ShowError("SIOCGIFCONF failed!\n"); + return; + } + + for(pos = 0; pos < ic.ifc_len;) + { + struct ifreq * ir = (struct ifreq *) (ic.ifc_buf + pos); + + struct sockaddr_in * a = (struct sockaddr_in *) &(ir->ifr_addr); + + if(a->sin_family == AF_INET) { + u_long ad = ntohl(a->sin_addr.s_addr); + if(ad != INADDR_LOOPBACK) { + addr_[naddr_ ++] = ad; + if(naddr_ == 16) + break; + } + } + + #if defined(_AIX) || defined(__APPLE__) + pos += ir->ifr_addr.sa_len; // For when we port athena to run on Mac's :) + pos += sizeof(ir->ifr_name); + #else + pos += sizeof(struct ifreq); + #endif + } +#endif + + FD_ZERO(&readfds); + + socket_config_read(SOCKET_CONF_FILENAME); + + // initialise last send-receive tick + last_tick = time(0); + + // session[0] Was for the console (whatever that was?), but 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], struct socket_data, 1); + CREATE_A(session[0]->rdata, unsigned char, 2*rfifo_size); + CREATE_A(session[0]->wdata, unsigned char, 2*wfifo_size); + session[0]->max_rdata = (int)2*rfifo_size; + session[0]->max_wdata = (int)2*wfifo_size; + + memset (func_parse_table, 0, sizeof(func_parse_table)); + func_parse_table[SESSION_RAW].check = default_func_check; + func_parse_table[SESSION_RAW].func = default_func_parse; + +#ifndef MINICORE + // とりあえず5分ごとに不要なデータを削除する + add_timer_func_list(connect_check_clear, "connect_check_clear"); + add_timer_interval(gettick()+1000,connect_check_clear,0,0,300*1000); +#endif +} + + +bool session_isValid(int fd) +{ //End of Exam has pointed out that fd==0 is actually an unconnected session! [Skotlex] + //But this is not so true, it is used... for... something. The console uses it, would this not cause problems? [Skotlex] + return ( (fd>0) && (fdeof ); +} diff --git a/src/common/socket.h b/src/common/socket.h new file mode 100644 index 000000000..ba27e34a8 --- /dev/null +++ b/src/common/socket.h @@ -0,0 +1,189 @@ +// 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 + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +#include +#else +#include +#include +#include +#endif +#include +#include "malloc.h" + +extern time_t last_tick; +extern time_t stall_time; + +// define declaration + +#define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) +#ifdef TURBO +#define RFIFOHEAD(fd) char *rbPtr = session[fd]->rdata+session[fd]->rdata_pos +#define RFIFOP(fd,pos) (&rbPtr[pos]) +#else +//Make it a comment so it does not disrupts the rest of code. +#define RFIFOHEAD(fd) // +#define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) +#endif +// use function instead of macro. +#define RFIFOB(fd,pos) (*(unsigned char*)RFIFOP(fd,pos)) +#define RFIFOW(fd,pos) (*(unsigned short*)RFIFOP(fd,pos)) +#define RFIFOL(fd,pos) (*(unsigned long*)RFIFOP(fd,pos)) +#define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) +//#define RFIFOSKIP(fd,len) ((session[fd]->rdata_size-session[fd]->rdata_pos-(len)<0) ? (fprintf(stderr,"too many skip\n"),exit(1)) : (session[fd]->rdata_pos+=(len))) + +#define RBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define RBUFB(p,pos) (*(unsigned char*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(unsigned short*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(unsigned long*)RBUFP((p),(pos))) + +#define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) +#ifdef TURBO +#define WFIFOHEAD(fd, x) char *wbPtr = session[fd]->wdata+session[fd]->wdata_size; +#define WFIFOP(fd,pos) (&wbPtr[pos]) +#else +#define WFIFOHEAD(fd, x) ; +#define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) +#endif +#define WFIFOB(fd,pos) (*(unsigned char*)WFIFOP(fd,pos)) +#define WFIFOW(fd,pos) (*(unsigned short*)WFIFOP(fd,pos)) +#define WFIFOL(fd,pos) (*(unsigned long*)WFIFOP(fd,pos)) +// use function instead of macro. +//#define WFIFOSET(fd,len) (session[fd]->wdata_size = (session[fd]->wdata_size + (len) + 2048 < session[fd]->max_wdata) ? session[fd]->wdata_size + len : session[fd]->wdata_size) +#define WBUFP(p,pos) (((unsigned char*)(p)) + (pos)) +#define WBUFB(p,pos) (*(unsigned char*)((p) + (pos))) +#define WBUFW(p,pos) (*(unsigned short*)((p) + (pos))) +#define WBUFL(p,pos) (*(unsigned long*)((p) + (pos))) + +//FD_SETSIZE must be modified on the project files/Makefile, since a change here won't affect +// dependant windows libraries. +/* +#ifdef __WIN32 +//The default FD_SETSIZE is kinda small for windows systems. + #ifdef FD_SETSIZE + #undef FD_SETSIZE + #endif +#define FD_SETSIZE 4096 +#endif +*/ +#ifdef __INTERIX +#define FD_SETSIZE 4096 +#endif // __INTERIX + +/* Removed Cygwin FD_SETSIZE declarations, now are directly passed on to the compiler through Makefile [Valaris] */ + +// Session type +enum SessionType { + SESSION_UNKNOWN = -1, + SESSION_RAW = 0, + SESSION_HTTP = 1, +//----------------- + SESSION_MAX = 2 +}; + +// Struct declaration + +struct socket_data{ + unsigned char eof; + unsigned char *rdata, *wdata; + unsigned int max_rdata, max_wdata; + unsigned int rdata_size, wdata_size; + int rdata_pos; + time_t rdata_tick; + struct sockaddr_in client_addr; + int (*func_recv)(int); + int (*func_send)(int); + int (*func_parse)(int); + int (*func_console)(char*); + void* session_data; + void* session_data2; + enum SessionType type; +}; + +// Parse functions table +struct func_parse_table { + int (*func)(int); + int (*check)(struct socket_data *); +}; +extern struct func_parse_table func_parse_table[SESSION_MAX]; + + +// Data prototype declaration + +extern struct socket_data *session[FD_SETSIZE]; + +extern int fd_max; + + + + + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef int bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not cplusplus +////////////////////////////// + + + +////////////////////////////////// +// some checking on sockets +extern bool session_isValid(int fd); +extern bool session_isActive(int fd); +////////////////////////////////// + + + + + + + + + + +// Function prototype declaration + +int make_listen_port(int); +int make_listen_bind(long,int); +int make_connection(long,int); +int delete_session(int); +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,int len); +int RFIFOSKIP(int fd,int len); + +int do_sendrecv(int next); +int do_parsepacket(void); +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, int yes); + +int start_console(void); + +void set_defaultparse(int (*defaultparse)(int)); +void set_defaultconsoleparse(int (*defaultparse)(char*)); + +extern unsigned int addr_[16]; // ip addresses of local host (host byte order) +extern unsigned int naddr_; // # of ip addresses + + +#endif // _SOCKET_H_ diff --git a/src/common/strlib.c b/src/common/strlib.c new file mode 100644 index 000000000..12c34556f --- /dev/null +++ b/src/common/strlib.c @@ -0,0 +1,133 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "strlib.h" +#include "utils.h" +#include "malloc.h" + +//----------------------------------------------- +// string lib. +char* jstrescape (char* pt) { + //copy from here + char *ptr; + int i =0, j=0; + + //copy string to temporary + CREATE_A(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[0]; +} + +char* jstrescapecpy (char* pt,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; + + 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]; +} +int jmemescapecpy (char* pt,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 remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//Trims a string, also removes illegal characters such as \t and reduces continous spaces to a single one. by [Foruken] +char *trim(char *str, const char *delim) +{ + char *strp = strtok(str,delim); + char buf[1024]; + char *bufp = buf; + memset(buf,0,sizeof buf); + + while(strp) { + strcpy(bufp, strp); + bufp = bufp + strlen(strp); + strp = strtok(NULL, delim); + if (strp) { + strcpy(bufp," "); + bufp++; + } + } + strcpy(str,buf); + return str; +} diff --git a/src/common/strlib.h b/src/common/strlib.h new file mode 100644 index 000000000..f4ee7074b --- /dev/null +++ b/src/common/strlib.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +char* jstrescape (char* pt); +char* jstrescapecpy (char* pt,char* spt); +int jmemescapecpy (char* pt,char* spt, int size); + +// custom functions +int remove_control_chars(unsigned char *); +char *trim(char *str, const char *delim); +#endif diff --git a/src/common/timer.c b/src/common/timer.c new file mode 100644 index 000000000..7b7ac5e2c --- /dev/null +++ b/src/common/timer.c @@ -0,0 +1,429 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +// Well, this won't last another 30++ years (where conversion will truncate). +//#define _USE_32BIT_TIME_T // use 32 bit time variables on 64bit windows +#include +#else +#include +#include +#endif + + +#include +#include +#include +#include + +#include "timer.h" +#include "malloc.h" +#include "showmsg.h" + +// タイマー間隔の最小値。モンスターの大量召還時、多数のクライアント接続時に +// サーバーが反応しなくなる場合は、TIMER_MIN_INTERVAL を増やしてください。 + +// If the server shows no reaction when processing thousands of monsters +// or connected by many clients, please increase TIMER_MIN_INTERVAL. + +#define TIMER_MIN_INTERVAL 50 + +static struct TimerData* timer_data = NULL; +static int timer_data_max = 0; +static int timer_data_num = 0; + +static int* free_timer_list = NULL; +static int free_timer_list_max = 0; +static int free_timer_list_pos = 0; + +static int timer_heap_num = 0; +static int timer_heap_max = 0; +static int* timer_heap = NULL; + +static int fix_heap_flag =0; //Flag for fixing the stack only once per tick loop. May not be the best way, but it's all I can think of currently :X [Skotlex] + +// for debug +struct timer_func_list { + int (*func)(int,unsigned int,int,int); + struct timer_func_list* next; + char* name; +}; +static struct timer_func_list* tfl_root; + +time_t start_time; + +#ifdef __WIN32 +/* Modified struct timezone to void - we pass NULL anyway */ +void gettimeofday (struct timeval *t, void *dummy) +{ + DWORD millisec = GetTickCount(); + + t->tv_sec = (int) (millisec / 1000); + t->tv_usec = (millisec % 1000) * 1000; +} +#endif + +// +int add_timer_func_list(int (*func)(int,unsigned int,int,int), char* name) +{ + struct timer_func_list* tfl; + + if (name) { + tfl = (struct timer_func_list*) aCalloc (sizeof(struct timer_func_list), 1); + tfl->name = (char *) aMalloc (strlen(name) + 1); + + tfl->next = tfl_root; + tfl->func = func; + strcpy(tfl->name, name); + tfl_root = tfl; + } + return 0; +} + +char* search_timer_func_list(int (*func)(int,unsigned int,int,int)) +{ + struct timer_func_list* tfl = tfl_root; + while (tfl) { + if (func == tfl->func) + return tfl->name; + tfl = tfl->next; + } + + return "unknown timer function"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ +static unsigned int gettick_cache; +static int gettick_count; + +unsigned int gettick_nocache(void) +{ + struct timeval tval; + + gettimeofday(&tval, NULL); + gettick_count = 256; + + return gettick_cache = tval.tv_sec * 1000 + tval.tv_usec / 1000; +} + +unsigned int gettick(void) +{ + gettick_count--; + if (gettick_count < 0) + return gettick_nocache(); + + return gettick_cache; +} + +/*====================================== + * CORE : Timer Heap + *-------------------------------------- + */ +static void push_timer_heap(int index) +{ + int i, j; + int min, max, pivot; // for sorting + + // check number of element + if (timer_heap_num >= timer_heap_max) { + if (timer_heap_max == 0) { + timer_heap_max = 256; + timer_heap = (int *) aCalloc( sizeof(int) , 256); + } else { + timer_heap_max += 256; + timer_heap = (int *) aRealloc( timer_heap, sizeof(int) * timer_heap_max); + memset(timer_heap + (timer_heap_max - 256), 0, sizeof(int) * 256); + } + } + + // do a sorting from higher to lower + j = timer_data[index].tick; // speed up + // with less than 4 values, it's speeder to use simple loop + if (timer_heap_num < 4) { + for(i = timer_heap_num; i > 0; i--) +// if (j < timer_data[timer_heap[i - 1]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[i - 1]].tick) < 0) + break; + else + timer_heap[i] = timer_heap[i - 1]; + timer_heap[i] = index; + // searching by dichotomie + } else { + // if lower actual item is higher than new +// if (j < timer_data[timer_heap[timer_heap_num - 1]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[timer_heap_num - 1]].tick) < 0) + timer_heap[timer_heap_num] = index; + else { + // searching position + min = 0; + max = timer_heap_num - 1; + while (min < max) { + pivot = (min + max) / 2; +// if (j < timer_data[timer_heap[pivot]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[pivot]].tick) < 0) + min = pivot + 1; + else + max = pivot; + } + // move elements - do loop if there are a little number of elements to move + if (timer_heap_num - min < 5) { + for(i = timer_heap_num; i > min; i--) + timer_heap[i] = timer_heap[i - 1]; + // move elements - else use memmove (speeder for a lot of elements) + } else + memmove(&timer_heap[min + 1], &timer_heap[min], sizeof(int) * (timer_heap_num - min)); + // save new element + timer_heap[min] = index; + } + } + + timer_heap_num++; +} + +/*========================== + * Timer Management + *-------------------------- + */ + +int acquire_timer (void) +{ + int i; + + if (free_timer_list_pos) { + do { + i = free_timer_list[--free_timer_list_pos]; + } while(i >= timer_data_num && free_timer_list_pos > 0); + } else + i = timer_data_num; + + if (i >= timer_data_num) + for (i = timer_data_num; i < timer_data_max && timer_data[i].type; i++); + if (i >= timer_data_num && i >= timer_data_max) { + if (timer_data_max == 0) { + timer_data_max = 256; + timer_data = (struct TimerData*) aCalloc( sizeof(struct TimerData) , timer_data_max); + } else { + timer_data_max += 256; + timer_data = (struct TimerData *) aRealloc( timer_data, sizeof(struct TimerData) * timer_data_max); + memset(timer_data + (timer_data_max - 256), 0, sizeof(struct TimerData) * 256); + } + } + + return i; +} + +int add_timer(unsigned int tick,int (*func)(int,unsigned int,int,int), int id, int data) +{ + int 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); + + if (tid >= timer_data_num) + timer_data_num = tid + 1; + + return tid; +} + +int add_timer_interval(unsigned int tick, int (*func)(int,unsigned int,int,int), int id, int data, int interval) +{ + int 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); + + if (tid >= timer_data_num) + timer_data_num = tid + 1; + + return tid; +} + +int delete_timer(int id, int (*func)(int,unsigned int,int,int)) +{ + if (id <= 0 || id >= timer_data_num) { + ShowError("delete_timer error : no such timer %d\n", id); + return -1; + } + if (timer_data[id].func != func) { + ShowError("delete_timer error : function mismatch %08x(%s) != %08x(%s)\n", + (int)timer_data[id].func, search_timer_func_list(timer_data[id].func), + (int)func, search_timer_func_list(func)); + return -2; + } + // そのうち消えるにまかせる + timer_data[id].func = NULL; + timer_data[id].type = TIMER_ONCE_AUTODEL; + + return 0; +} + +int addtick_timer(int tid, unsigned int tick) +{ + return timer_data[tid].tick += tick; +} + +//Sets the tick at which the timer triggers directly (meant as a replacement of delete_timer + add_timer) [Skotlex] +//FIXME: DON'T use this function yet, it is not correctly reorganizing the timer stack causing unexpected problems later on! +int settick_timer(int tid, unsigned int tick) +{ + int i,j; + if (timer_data[tid].tick == tick) + return tick; + + //FIXME: This search is not all that effective... there doesn't seems to be a better way to locate an element in the heap. + for(i = timer_heap_num-1; i >= 0 && timer_heap[i] != tid; i--); + + if (i < 0) + return -1; //Sort of impossible, isn't it? + if (DIFF_TICK(timer_data[tid].tick, tick) > 0) + { //Timer is accelerated, shift timer near the end of the heap. + if (i == timer_heap_num-1) //Nothing to shift. + j = timer_heap_num-1; + else { + for (j = i+1; j < timer_heap_num && DIFF_TICK(timer_data[j].tick, tick) > 0; j++); + j--; + memmove(&timer_heap[i], &timer_heap[i+1], (j-i)*sizeof(int)); + } + } else { //Timer is delayed, shift timer near the beginning of the heap. + if (i == 0) //Nothing to shift. + j = 0; + else { + for (j = i-1; j >= 0 && DIFF_TICK(timer_data[j].tick, tick) < 0; j--); + j++; + memmove(&timer_heap[j+1], &timer_heap[j], (i-j)*sizeof(int)); + } + } + timer_heap[j] = tid; + timer_data[tid].tick = tick; + return tick; +} + +struct TimerData* get_timer(int tid) +{ + return &timer_data[tid]; +} + +//Correcting the heap when the tick overflows is an idea taken from jA to +//prevent timer problems. Thanks to [End of Exam] for providing the required data. [Skotlex] +//This funtion will rearrange the heap and assign new tick values. +static void fix_timer_heap(unsigned int tick) +{ + if (timer_heap_num >= 0 && tick < 0x00010000 && timer_data[timer_heap[0]].tick > 0xf0000000) + { //The last timer is way too far into the future, and the current tick is too close to 0, overflow was very likely + //(not perfect, but will work as long as the timer is not expected to happen 50 or so days into the future) + int i; + int *tmp_heap; + for (i=0; i < timer_heap_num && timer_data[timer_heap[i]].tick > 0xf0000000; i++) + { //All functions with high tick value should had been executed already... + timer_data[timer_heap[i]].tick = 0; + } + //Move elements to readjust the heap. + tmp_heap = aCalloc(sizeof(int), i); + memmove(&tmp_heap[0], &timer_heap[0], i*sizeof(int)); + memmove(&timer_heap[0], &timer_heap[i], (timer_heap_num-i)*sizeof(int)); + memmove(&timer_heap[timer_heap_num-i], &tmp_heap[0], i*sizeof(int)); + aFree(tmp_heap); + } +} + +int do_timer(unsigned int tick) +{ + int i, nextmin = 1000; + + if (tick < 0x010000 && fix_heap_flag) + { + fix_timer_heap(tick); + fix_heap_flag = 0; + } + + while(timer_heap_num) { + i = timer_heap[timer_heap_num - 1]; // next shorter element + if ((nextmin = DIFF_TICK(timer_data[i].tick, tick)) > 0) + break; + if (timer_heap_num > 0) // suppress the actual element from the table + timer_heap_num--; + timer_data[i].type |= TIMER_REMOVE_HEAP; + if (timer_data[i].func) { + if (nextmin < -1000) { + // 1秒以上の大幅な遅延が発生しているので、 + // timer処理タイミングを現在値とする事で + // 呼び出し時タイミング(引数のtick)相対で処理してる + // timer関数の次回処理タイミングを遅らせる + timer_data[i].func(i, tick, timer_data[i].id, timer_data[i].data); + } else { + timer_data[i].func(i, timer_data[i].tick, timer_data[i].id, timer_data[i].data); + } + } + if (timer_data[i].type & TIMER_REMOVE_HEAP) { + switch(timer_data[i].type & ~TIMER_REMOVE_HEAP) { + case TIMER_ONCE_AUTODEL: + timer_data[i].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + free_timer_list = (int *) aRealloc(free_timer_list, sizeof(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++] = i; + break; + case TIMER_INTERVAL: + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + timer_data[i].tick = tick + timer_data[i].interval; + } else { + timer_data[i].tick += timer_data[i].interval; + } + timer_data[i].type &= ~TIMER_REMOVE_HEAP; + push_timer_heap(i); + break; + } + } + } + + if (nextmin < TIMER_MIN_INTERVAL) + nextmin = TIMER_MIN_INTERVAL; + + if ((unsigned int)(tick + nextmin) < tick) //Tick will loop, rearrange the heap on the next iteration. + fix_heap_flag = 1; + return nextmin; +} + +unsigned long get_uptime (void) +{ + return (unsigned long) difftime (time(NULL), start_time); +} + +void timer_init(void) +{ + time(&start_time); +} + +void timer_final(void) +{ + struct timer_func_list* tfl = tfl_root, *tfl2; + + while (tfl) { + tfl2 = tfl->next; // copy next pointer + aFree(tfl->name); // free structures + aFree(tfl); + tfl = tfl2; // use copied pointer for next cycle + } + + if (timer_data) aFree(timer_data); + if (timer_heap) aFree(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..aafefd1e2 --- /dev/null +++ b/src/common/timer.h @@ -0,0 +1,60 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#ifdef __WIN32 +/* We need winsock lib to have timeval struct - windows is weirdo */ +#define __USE_W32_SOCKETS +#include +#endif + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +#ifdef __WIN32 +void gettimeofday(struct timeval *t, void *dummy); +#endif + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +int settick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +unsigned long get_uptime(void); + +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..57dc1f480 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,384 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include + +#ifdef WIN32 + #include + #define PATHSEP '\\' +#else + #include + #include + #include + #define PATHSEP '/' +#endif + +#include "utils.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +void dump(unsigned char *buffer, int num) +{ + int icnt,jcnt; + + printf(" Hex ASCII\n"); + printf(" ----------------------------------------------- ----------------"); + + for (icnt=0;icnt 31 && buffer[jcnt] < 127) + printf("%c",buffer[jcnt]); + else + printf("."); + } else + printf(" "); + } + } + printf("\n"); +} + +//NOTE: There is no need to use this function as the standard sqrt is plenty fast as it is. [Skotlex] +int newt_sqrt(int input) +{ + int new_value, value = input/2, count = 0; + if (!value) //Division by zero fix, pointed out by Shinomori. [Skotlex] + return input; + do + { + new_value = (value + input/value)>>1; + if (abs(value - new_value) <= 1) + return new_value; + value = new_value; + } + while (count++ < 25); + return new_value; +} + +#if defined(_WIN32) && !defined(MINGW) +char *rindex(char *str, char c) +{ + char *sptr; + + sptr = str; + while(*sptr) + ++sptr; + if (c == '\0') + return(sptr); + while(str != sptr) + if (*sptr-- == c) + return(++sptr); + return(NULL); +} + +int strcasecmp(const char *arg1, const char *arg2) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + ShowError("strcasecmp: received a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; arg1[i] || arg2[i]; i++) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +int strncasecmp(const char *arg1, const char *arg2, int n) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + ShowError("strncasecmp(): received a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; (arg1[i] || arg2[i]) && (n > 0); i++, n--) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +void str_upper(char *name) +{ + + int len = (int)strlen(name); + while (len--) { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +void str_lower(char *name) +{ + int len = (int)strlen(name); + + while (len--) { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} + +#endif + +// Allocate a StringBuf [MouseJstr] +struct StringBuf * StringBuf_Malloc() +{ + struct StringBuf * ret = (struct StringBuf *) aMallocA(sizeof(struct StringBuf)); + StringBuf_Init(ret); + return ret; +} + +// Initialize a previously allocated StringBuf [MouseJstr] +void StringBuf_Init(struct StringBuf * sbuf) { + sbuf->max_ = 1024; + sbuf->ptr_ = sbuf->buf_ = (char *) aMallocA(sbuf->max_ + 1); +} + +// printf into a StringBuf, moving the pointer [MouseJstr] +int StringBuf_Printf(struct StringBuf *sbuf,const char *fmt,...) +{ + va_list ap; + int n, size, off; + + while (1) { + /* Try to print in the allocated space. */ + va_start(ap, fmt); + size = sbuf->max_ - (sbuf->ptr_ - sbuf->buf_); + n = vsnprintf (sbuf->ptr_, size, fmt, ap); + va_end(ap); + /* If that worked, return the length. */ + if (n > -1 && n < size) { + sbuf->ptr_ += n; + return (int)(sbuf->ptr_ - sbuf->buf_); + } + /* Else try again with more space. */ + sbuf->max_ *= 2; // twice the old size + off = (int)(sbuf->ptr_ - sbuf->buf_); + sbuf->buf_ = (char *) aRealloc(sbuf->buf_, sbuf->max_ + 1); + sbuf->ptr_ = sbuf->buf_ + off; + } +} + +// Append buf2 onto the end of buf1 [MouseJstr] +int StringBuf_Append(struct StringBuf *buf1,const struct StringBuf *buf2) +{ + int buf1_avail = buf1->max_ - (buf1->ptr_ - buf1->buf_); + int size2 = (int)(buf2->ptr_ - buf2->buf_); + + if (size2 >= buf1_avail) { + int off = (int)(buf1->ptr_ - buf1->buf_); + buf1->max_ += size2; + buf1->buf_ = (char *) aRealloc(buf1->buf_, buf1->max_ + 1); + buf1->ptr_ = buf1->buf_ + off; + } + + memcpy(buf1->ptr_, buf2->buf_, size2); + buf1->ptr_ += size2; + return (int)(buf1->ptr_ - buf1->buf_); +} + +// Destroy a StringBuf [MouseJstr] +void StringBuf_Destroy(struct StringBuf *sbuf) +{ + aFree(sbuf->buf_); + sbuf->ptr_ = sbuf->buf_ = 0; +} + +// Free a StringBuf returned by StringBuf_Malloc [MouseJstr] +void StringBuf_Free(struct StringBuf *sbuf) +{ + StringBuf_Destroy(sbuf); + aFree(sbuf); +} + +// Return the built string from the StringBuf [MouseJstr] +char * StringBuf_Value(struct StringBuf *sbuf) +{ + *sbuf->ptr_ = '\0'; + return sbuf->buf_; +} + +#ifdef WIN32 + +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_DATA 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 = FindFirstFile(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 (FindNextFile(hFind, &FindFileData) != 0); + FindClose(hFind); + } + return; +} +#else + +#define MAX_DIR_PATH 2048 + +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 +} +#endif + +unsigned char GetByte(unsigned long val, size_t num) +{ + switch(num) + { + case 0: + return (unsigned char)((val & 0x000000FF) ); + case 1: + return (unsigned char)((val & 0x0000FF00)>>0x08); + case 2: + return (unsigned char)((val & 0x00FF0000)>>0x10); + case 3: + return (unsigned char)((val & 0xFF000000)>>0x18); + default: + return 0; //better throw something here + } +} +unsigned short GetWord(unsigned long val, size_t num) +{ + switch(num) + { + case 0: + return (unsigned short)((val & 0x0000FFFF) ); + case 1: + return (unsigned short)((val & 0xFFFF0000)>>0x10); + default: + return 0; //better throw something here + } +} +unsigned short MakeWord(unsigned char byte0, unsigned char byte1) +{ + return byte0 | (byte1<<0x08); +} +unsigned long MakeDWord(unsigned short word0, unsigned short word1) +{ + return ((unsigned long)word0) + | ((unsigned long)word1<<0x10); +} + diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 000000000..9d2febe1b --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,52 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef COMMON_UTILS_H +#define COMMON_UTILS_H + + +#ifndef NULL +#define NULL (void *)0 +#endif + +#define LOWER(c) (((c)>='A' && (c) <= 'Z') ? ((c)+('a'-'A')) : (c)) +#define UPPER(c) (((c)>='a' && (c) <= 'z') ? ((c)+('A'-'a')) : (c) ) + +/* strcasecmp -> stricmp -> str_cmp */ +#if defined(_WIN32) && !defined(MINGW) + int strcasecmp(const char *arg1, const char *arg2); + int strncasecmp(const char *arg1, const char *arg2, int n); + void str_upper(char *name); + void str_lower(char *name); + char *rindex(char *str, char c); +#endif + +void dump(unsigned char *buffer, int num); +int newt_sqrt(int value); //Newton aproximation for getting a fast sqrt. + +struct StringBuf { + char *buf_; + char *ptr_; + unsigned int max_; +}; + +struct StringBuf * StringBuf_Malloc(void); +void StringBuf_Init(struct StringBuf *); +int StringBuf_Printf(struct StringBuf *,const char *,...); +int StringBuf_Append(struct StringBuf *,const struct StringBuf *); +char * StringBuf_Value(struct StringBuf *); +void StringBuf_Destroy(struct StringBuf *); +void StringBuf_Free(struct StringBuf *); + +void findfile(const char *p, const char *pat, void (func)(const char*)); + +////////////////////////////////////////////////////////////////////////// +// byte word dword access [Shinomori] +////////////////////////////////////////////////////////////////////////// + +extern unsigned char GetByte(unsigned long val, size_t num); +extern unsigned short GetWord(unsigned long val, size_t num); +extern unsigned short MakeWord(unsigned char byte0, unsigned char byte1); +extern unsigned long MakeDWord(unsigned short word0, unsigned short word1); + +#endif diff --git a/src/common/version.h b/src/common/version.h new file mode 100644 index 000000000..1c7961ee1 --- /dev/null +++ b/src/common/version.h @@ -0,0 +1,30 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define ATHENA_MAJOR_VERSION 1 // Major Version +#define ATHENA_MINOR_VERSION 0 // Minor Version +#define ATHENA_REVISION 0 // Revision + +#define ATHENA_RELEASE_FLAG 1 // 1=Develop,0=Stable +#define ATHENA_OFFICIAL_FLAG 1 // 1=Mod,0=Official + +#define ATHENA_SERVER_NONE 0 // not defined +#define ATHENA_SERVER_LOGIN 1 // login server +#define ATHENA_SERVER_CHAR 2 // char server +#define ATHENA_SERVER_INTER 4 // inter server +#define ATHENA_SERVER_MAP 8 // map server + +// ATHENA_MOD_VERSIONはパッチ番号です。 +// これは無理に変えなくても気が向いたら変える程度の扱いで。 +// (毎回アップロードの度に変更するのも面倒と思われるし、そもそも +//  この項目を参照する人がいるかどうかで疑問だから。) +// その程度の扱いなので、サーバーに問い合わせる側も、あくまで目安程度の扱いで +// あんまり信用しないこと。 +// 鯖snapshotの時や、大きな変更があった場合は設定してほしいです。 +// C言語の仕様上、最初に0を付けると8進数になるので間違えないで下さい。 +#define ATHENA_MOD_VERSION 1249 // mod version (patch No.) + +#endif -- cgit v1.2.3-70-g09d2