diff options
Diffstat (limited to 'src/common')
35 files changed, 11233 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <signal.h>
+#include <string.h>
+
+#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. *
+ * *
+ * <B>Properties of the RED-BLACK trees being used:</B> *
+ * 1. The value of any node is greater than the value of its left child and *
+ * less than the value of its right child. *
+ * 2. Every node is colored either RED or BLACK. *
+ * 3. Every red node that is not a leaf has only black children. *
+ * 4. Every path from the root to a leaf contains the same number of black *
+ * nodes. *
+ * 5. The root node is black. *
+ * An <code>n</code> node in a RED-BLACK tree has the property that its *
+ * height is <code>O(lg(n))</code>. *
+ * Another important property is that after adding a node to a RED-BLACK *
+ * tree, the tree can be readjusted in <code>O(lg(n))</code> time. *
+ * Similarly, after deleting a node from a RED-BLACK tree, the tree can be *
+ * readjusted in <code>O(lg(n))</code> time. *
+ * {@link http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic18/} *
+ * *
+ * <B>How to add new database types:</B> *
+ * 1. Add the identifier of the new database type to the enum DBType *
+ * 2. If not already there, add the data type of the key to the union DBKey *
+ * 3. If the key can be considered NULL, update the function db_is_key_null *
+ * 4. If the key can be duplicated, update the functions db_dup_key and *
+ * db_dup_key_free *
+ * 5. Create a comparator and update the function db_default_cmp *
+ * 6. Create a hasher and update the function db_default_hash *
+ * 7. If the new database type requires or does not support some options, *
+ * update the function db_fix_options *
+ * *
+ * TODO: *
+ * - create test cases to test the database system thoroughly *
+ * - 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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.
+ * <code>maxlen</code> is ignored.
+ * @param key1 Key to be compared
+ * @param key2 Key being compared to
+ * @param maxlen Maximum length of the key to hash
+ * @return 0 if equal, negative if lower and positive if higher
+ * @see 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.
+ * <code>maxlen</code> is ignored.
+ * @param key1 Key to be compared
+ * @param key2 Key being compared to
+ * @param maxlen Maximum length of the key to hash
+ * @return 0 if equal, negative if lower and positive if higher
+ * @see 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.
+ * <code>maxlen</code> is ignored.
+ * @param key Key to be hashed
+ * @param maxlen Maximum length of the key to hash
+ * @return hash of the key
+ * @see 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.
+ * <code>maxlen</code> is ignored.
+ * @param key Key to be hashed
+ * @param maxlen Maximum length of the key to hash
+ * @return hash of the key
+ * @see 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 <code>match</code>.
+ * It puts a maximum of <code>max</code> entries into <code>buf</code>.
+ * If <code>buf</code> is NULL, it only counts the matches.
+ * Returns the number of entries that matched.
+ * NOTE: if the value returned is greater than <code>max</code>, only the
+ * first <code>max</code> entries found are put into the buffer.
+ * @param self Interface of the database
+ * @param buf Buffer to put the data of the matched entries
+ * @param max Maximum number of data entries to be put into buf
+ * @param match Function that matches the database entries
+ * @param ... Extra arguments for match
+ * @return The number of entries that matched
+ * @protected
+ * @see 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 <code>match</code>.
+ * It puts a maximum of <code>max</code> entries into <code>buf</code>.
+ * If <code>buf</code> is NULL, it only counts the matches.
+ * Returns the number of entries that matched.
+ * NOTE: if the value returned is greater than <code>max</code>, only the
+ * first <code>max</code> entries found are put into the buffer.
+ * @param self Interface of the database
+ * @param buf Buffer to put the data of the matched entries
+ * @param max Maximum number of data entries to be put into buf
+ * @param match Function that matches the database entries
+ * @param ... Extra arguments for match
+ * @return The number of entries that matched
+ * @protected
+ * @see 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
+ * <code>create</code>.
+ * @param self Interface of the database
+ * @param key Key that identifies the entry
+ * @param create Function used to create the data if the entry doesn't exist
+ * @param args Extra arguments for create
+ * @return Data of the entry
+ * @protected
+ * @see 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
+ * <code>create</code>.
+ * @param self Interface of the database
+ * @param key Key that identifies the entry
+ * @param create Function used to create the data if the entry doesn't exist
+ * @param ... Extra arguments for create
+ * @return Data of the entry
+ * @protected
+ * @see 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 <code>func</code> to every entry in the database.
+ * Returns the sum of values returned by func.
+ * @param self Interface of the database
+ * @param func Function to be applyed
+ * @param 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 <code>func</code> to every entry in the database.
+ * Returns the sum of values returned by func.
+ * @param self Interface of the database
+ * @param func Function to be applyed
+ * @param ... Extra arguments for func
+ * @return Sum of the values returned by func
+ * @protected
+ * @see 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 *
+ * *
+ * <B>Notes on the release system:</B> *
+ * Whenever an entry is removed from the database both the key and the *
+ * data are requested to be released. *
+ * At least one entry is removed when replacing an entry, removing an *
+ * entry, clearing the database or destroying the database. *
+ * What is actually released is defined by the release function, the *
+ * functions of the database only ask for the key and/or data to be *
+ * released. *
+ * *
+ * TODO: *
+ * - create 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 <stdarg.h>
+
+/*****************************************************************************\
+ * (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.
+ * <code>maxlen</code> 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.
+ * <code>maxlen</code> 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 <code>match</code>.
+ * It puts a maximum of <code>max</code> entries into <code>buf</code>.
+ * If <code>buf</code> is NULL, it only counts the matches.
+ * Returns the number of entries that matched.
+ * NOTE: if the value returned is greater than <code>max</code>, only the
+ * first <code>max</code> entries found are put into the buffer.
+ * @param 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 <code>match</code>.
+ * It puts a maximum of <code>max</code> entries into <code>buf</code>.
+ * If <code>buf</code> is NULL, it only counts the matches.
+ * Returns the number of entries that matched.
+ * NOTE: if the value returned is greater than <code>max</code>, only the
+ * first <code>max</code> entries found are put into the buffer.
+ * @param 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
+ * <code>create</code>.
+ * @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
+ * <code>create</code>.
+ * @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 <code>func</code> 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 <code>func</code> 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 <code>which</code> specifies.
+ * @param which Defines what the releaser releases
+ * @return Releaser for the specified release options
+ * @public
+ * @see #DBRelease
+ * @see #DBReleaser
+ * @see #db_default_release(DBType,DBOptions)
+ * @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 *
+ * *
+ * <H1>Entry Reusage System</H1> *
+ * *
+ * There are several root entry managers, each with a different entry size. *
+ * Each manager will keep track of how many instances have been 'created'. *
+ * They will only automatically destroy themselves after the last instance *
+ * is destroyed. *
+ * *
+ * Entries can be allocated from the managers. *
+ * If it has reusable entries (freed entry), it uses one. *
+ * So no assumption should be made about the data of the entry. *
+ * Entries should be freed in the manager they where allocated from. *
+ * Failure to do so can lead to unexpected behaviours. *
+ * *
+ * <H2>Advantages:</H2> *
+ * - The same manager is used for entries of the same size. *
+ * So entries freed in one instance of the manager can be used by other *
+ * instances of the manager. *
+ * - Much less memory allocation/deallocation - program will be faster. *
+ * - Avoids memory fragmentaion - program will run better for longer. *
+ * *
+ * <H2>Disavantages:</H2> *
+ * - Unused entries are almost inevitable - memory being wasted. *
+ * - A manager will only auto-destroy when all of its instances are *
+ * destroyed so memory will usually only be recovered near the end. *
+ * - Always wastes space for entries smaller than a pointer. *
+ * *
+ * WARNING: The system is not thread-safe at the moment. *
+ * *
+ * HISTORY: *
+ * 0.1 - Initial version *
+ * *
+ * @version 0.1 - Initial version *
+ * @author Flavio @ Amazon Project *
+ * @encoding US-ASCII *
+ * @see common#ers.h *
+\*****************************************************************************/
+#include <stdlib.h>
+
+#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 *
+ * *
+ * <H1>Entry Reusage System</H1> *
+ * *
+ * There are several root entry managers, each with a different entry size. *
+ * Each manager will keep track of how many instances have been 'created'. *
+ * They will only automatically destroy themselves after the last instance *
+ * is destroyed. *
+ * *
+ * Entries can be allocated from the managers. *
+ * If it has reusable entries (freed entry), it uses one. *
+ * So no assumption should be made about the data of the entry. *
+ * Entries should be freed in the manager they where allocated from. *
+ * Failure to do so can lead to unexpected behaviours. *
+ * *
+ * <H2>Advantages:</H2> *
+ * - The same manager is used for entries of the same size. *
+ * So entries freed in one instance of the manager can be used by other *
+ * instances of the manager. *
+ * - Much less memory allocation/deallocation - program will be faster. *
+ * - Avoids memory fragmentaion - program will run better for longer. *
+ * *
+ * <H2>Disavantages:</H2> *
+ * - Unused entries are almost inevitable - memory being wasted. *
+ * - A manager will only auto-destroy when all of its instances are *
+ * destroyed so memory will usually only be recovered near the end. *
+ * - Always wastes space for entries smaller than a pointer. *
+ * *
+ * WARNING: The system is not thread-safe at the moment. *
+ * *
+ * HISTORY: *
+ * 0.1 - Initial version *
+ * *
+ * @version 0.1 - Initial version *
+ * @author Flavio @ Amazon Project *
+ * @encoding US-ASCII *
+ * @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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+ #include <unistd.h>
+#endif
+#ifdef MINGW
+ #include <io.h>
+#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,"<h2>%s</h2>\n\n",
+ sensor[id].str);
+ p += sprintf(p,"<p><img src=\"%s.png\" width=\"%d\" height=\"%d\"></p>\n",
+ sensor[id].hash, GRP_WIDTH,GRP_HEIGHT);
+ p += sprintf(p,"<p>Max: %d, Interval: %d sec</p>\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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#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 <zlib.h>
+#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<len;len--,Src++) {
+ *Src = (*Src>>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<len;lop++,buf+=8) {
+ if(lop<20 || (type==0 && lop%cycle==0)){ // des
+ BitConvert(buf,BitSwapTable1);
+ BitConvert4(buf);
+ BitConvert(buf,BitSwapTable2);
+ } else {
+ if(cnt==7 && type==0){
+ int a;
+ BYTE tmp[8];
+ *(DWORD*)tmp = *(DWORD*)buf;
+ *(DWORD*)(tmp+4) = *(DWORD*)(buf+4);
+ cnt=0;
+ buf[0]=tmp[3];
+ buf[1]=tmp[4];
+ buf[2]=tmp[6];
+ buf[3]=tmp[0];
+ buf[4]=tmp[1];
+ buf[5]=tmp[2];
+ buf[6]=tmp[5];
+ a=tmp[7];
+ if(a==0x00) a=0x2b;
+ else if(a==0x2b) a=0x00;
+ else if(a==0x01) a=0x68;
+ else if(a==0x68) a=0x01;
+ else if(a==0x48) a=0x77;
+ else if(a==0x77) a=0x48;
+ else if(a==0x60) a=0xff;
+ else if(a==0xff) a=0x60;
+ else if(a==0x6c) a=0x80;
+ else if(a==0x80) a=0x6c;
+ else if(a==0xb9) a=0xc0;
+ else if(a==0xc0) a=0xb9;
+ else if(a==0xeb) a=0xfe;
+ else if(a==0xfe) a=0xeb;
+ buf[7]=a;
+ }
+ cnt++;
+ }
+ }
+}
+/*==========================================
+ * Grf data decode sub : zip
+ *------------------------------------------
+ */
+int decode_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 = 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<len;lop+=8) {
+ NibbleSwap(&buf[lop],8);
+ BitConvert(&buf[lop],BitSwapTable1);
+ BitConvert4(&buf[lop]);
+ BitConvert(&buf[lop],BitSwapTable2);
+ }
+ return (char*)buf;
+}
+
+/*==========================================
+ * Grfio : Entry table read
+ *------------------------------------------
+ */
+static int grfio_entryread(char *gfname,int gentry)
+{
+ FILE *fp;
+ long grf_size,list_size;
+ unsigned char grf_header[0x2e];
+ int lop,entry,entrys,ofs,grf_version;
+ char *fname;
+ unsigned char *grf_filelist;
+
+ fp = fopen(gfname, "rb");
+ if (fp == NULL) {
+ ShowWarning("GRF Data File not found: '"CL_WHITE"%s"CL_RESET"'.\n",gfname);
+ return 1; // 1:not found error
+ }
+
+ fseek(fp,0,2); // SEEK_END
+ grf_size = ftell(fp);
+ fseek(fp,0,0); // SEEK_SET
+ fread(grf_header,1,0x2e,fp);
+ if (strcmp((const char *) grf_header,"Master of Magic") ||
+ fseek(fp,getlong(grf_header+0x1e),1)) // SEEK_CUR
+ {
+ fclose(fp);
+ ShowError("%s read error\n",gfname);
+ return 2; // 2:file format error
+ }
+
+ grf_version = getlong(grf_header+0x2a) >> 8;
+
+ if (grf_version == 0x01) { //****** Grf version 01xx ******
+ list_size = grf_size - ftell(fp);
+ grf_filelist = (unsigned char *) 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+#ifndef WIN32
+#include <unistd.h>
+#else
+#include <windows.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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;i<block->unit_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;i<block->unit_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;i<BLOCK_ALLOC;i++) {
+ if(i != 0) {
+ p[i].block_prev = &p[i-1];
+ }
+ if(i != BLOCK_ALLOC -1) {
+ p[i].block_next = &p[i+1];
+ }
+ p[i].block_no = block_no + i;
+ }
+
+ /* 未使用ブロックへのポインタを更新 */
+ block_unused = &p[1];
+ p->unit_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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <showmsg.h> + +#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 <time.h>
+#include "utils.h" // _WIN32
+
+#if ! defined(Assert)
+#if defined(RELEASE)
+#define Assert(EX)
+#else
+// extern "C" {
+#include <assert.h>
+// }
+#ifndef DEFCPP
+#if defined(_WIN32) && !defined(MINGW)
+#include <crtdbg.h>
+#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 <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <unistd.h>
+#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 <windows.h>
+ #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 <dlfcn.h>
+ #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 <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#ifdef __WIN32
+#define __USE_W32_SOCKETS
+#include <windows.h>
+#include <io.h>
+typedef int socklen_t;
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#ifndef SIOCGIFCONF
+#include <sys/sockio.h> // SIOCGIFCONF on Solaris, maybe others? [Shinomori]
+#endif
+
+#endif
+
+#include <fcntl.h>
+#include <string.h>
+
+#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 <len>.w <emblem data>.?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;i<len;i++){ ShowMessage("%02x ",session[fd]->wdata[i]); } ShowMessage("\n");}
+ if(len>0){
+ if((unsigned int)len<session[fd]->wdata_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;i<fd_max;i++)
+ if(session[i] != NULL &&
+ session[i]->func_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<m;i++) {
+ mask = (mask >> 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) && (fd<FD_SETSIZE) && (NULL!=session[fd]) );
+}
+
+bool session_isActive(int fd)
+{
+ return ( session_isValid(fd) && !session[fd]->eof );
+}
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 <stdio.h>
+
+#ifdef __WIN32
+#define __USE_W32_SOCKETS
+#include <windows.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+#include <time.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <sys/types.h>
+
+#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 <windows.h>
+#else
+#include <sys/socket.h>
+#include <sys/time.h>
+#endif
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <windows.h>
+#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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+ #include <windows.h>
+ #define PATHSEP '\\'
+#else
+ #include <unistd.h>
+ #include <dirent.h>
+ #include <sys/stat.h>
+ #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<num;icnt+=16) {
+ printf("\n%p ",&buffer[icnt]);
+ for (jcnt=icnt;jcnt<icnt+16;++jcnt) {
+ if (jcnt < num) {
+ printf("%02hX ",buffer[jcnt]);
+ } else
+ printf(" ");
+ }
+
+ printf(" | ");
+
+ for (jcnt=icnt;jcnt<icnt+16;++jcnt) {
+ if (jcnt < num) {
+ if (buffer[jcnt] > 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
|