diff options
Diffstat (limited to 'src/engine/shared')
38 files changed, 6502 insertions, 0 deletions
diff --git a/src/engine/shared/compression.cpp b/src/engine/shared/compression.cpp new file mode 100644 index 00000000..63e44699 --- /dev/null +++ b/src/engine/shared/compression.cpp @@ -0,0 +1,88 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> + +#include "compression.h" + +// Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign +unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) +{ + *pDst = (i>>25)&0x40; // set sign bit if i<0 + i = i^(i>>31); // if(i<0) i = ~i + + *pDst |= i&0x3F; // pack 6bit into dst + i >>= 6; // discard 6 bits + if(i) + { + *pDst |= 0x80; // set extend bit + while(1) + { + pDst++; + *pDst = i&(0x7F); // pack 7bit + i >>= 7; // discard 7 bits + *pDst |= (i!=0)<<7; // set extend bit (may branch) + if(!i) + break; + } + } + + pDst++; + return pDst; +} + +const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut) +{ + int Sign = (*pSrc>>6)&1; + *pInOut = *pSrc&0x3F; + + do + { + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7+7); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7+7+7); + } while(0); + + pSrc++; + *pInOut ^= -Sign; // if(sign) *i = ~(*i) + return pSrc; +} + + +long CVariableInt::Decompress(const void *pSrc_, int Size, void *pDst_) +{ + const unsigned char *pSrc = (unsigned char *)pSrc_; + const unsigned char *pEnd = pSrc + Size; + int *pDst = (int *)pDst_; + while(pSrc < pEnd) + { + pSrc = CVariableInt::Unpack(pSrc, pDst); + pDst++; + } + return (long)((unsigned char *)pDst-(unsigned char *)pDst_); +} + +long CVariableInt::Compress(const void *pSrc_, int Size, void *pDst_) +{ + int *pSrc = (int *)pSrc_; + unsigned char *pDst = (unsigned char *)pDst_; + Size /= 4; + while(Size) + { + pDst = CVariableInt::Pack(pDst, *pSrc); + Size--; + pSrc++; + } + return (long)(pDst-(unsigned char *)pDst_); +} + diff --git a/src/engine/shared/compression.h b/src/engine/shared/compression.h new file mode 100644 index 00000000..9bd9e61a --- /dev/null +++ b/src/engine/shared/compression.h @@ -0,0 +1,12 @@ +#ifndef ENGINE_SHARED_COMPRESSION_H +#define ENGINE_SHARED_COMPRESSION_H +// variable int packing +class CVariableInt +{ +public: + static unsigned char *Pack(unsigned char *pDst, int i); + static const unsigned char *Unpack(const unsigned char *pSrc, int *pInOut); + static long Compress(const void *pSrc, int Size, void *pDst); + static long Decompress(const void *pSrc, int Size, void *pDst); +}; +#endif diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp new file mode 100644 index 00000000..ca12e8b7 --- /dev/null +++ b/src/engine/shared/config.cpp @@ -0,0 +1,111 @@ +#include <engine/config.h> +#include <engine/storage.h> +#include <engine/shared/config.h> + +CConfiguration g_Config; + +class CConfig : public IConfig +{ + IStorage *m_pStorage; + IOHANDLE m_ConfigFile; + + struct CCallback + { + SAVECALLBACKFUNC m_pfnFunc; + void *m_pUserData; + }; + + enum + { + MAX_CALLBACKS = 16 + }; + + CCallback m_aCallbacks[MAX_CALLBACKS]; + int m_NumCallbacks; + + void EscapeParam(char *pDst, const char *pSrc, int size) + { + for(int i = 0; *pSrc && i < size - 1; ++i) + { + if(*pSrc == '"' || *pSrc == '\\') // escape \ and " + *pDst++ = '\\'; + *pDst++ = *pSrc++; + } + *pDst = 0; + } + +public: + + CConfig() + { + m_ConfigFile = 0; + m_NumCallbacks = 0; + } + + virtual void Init() + { + m_pStorage = Kernel()->RequestInterface<IStorage>(); + Reset(); + } + + virtual void Reset() + { + #define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) g_Config.m_##Name = def; + #define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) str_copy(g_Config.m_##Name, def, len); + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR + } + + virtual void Save() + { + if(!m_pStorage) + return; + m_ConfigFile = m_pStorage->OpenFile("settings.cfg", IOFLAG_WRITE); + + if(!m_ConfigFile) + return; + + char aLineBuf[1024*2]; + char aEscapeBuf[1024*2]; + + #define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(aLineBuf, sizeof(aLineBuf), "%s %i", #ScriptName, g_Config.m_##Name); WriteLine(aLineBuf); } + #define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ EscapeParam(aEscapeBuf, g_Config.m_##Name, sizeof(aEscapeBuf)); str_format(aLineBuf, sizeof(aLineBuf), "%s \"%s\"", #ScriptName, aEscapeBuf); WriteLine(aLineBuf); } + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR + + for(int i = 0; i < m_NumCallbacks; i++) + m_aCallbacks[i].m_pfnFunc(this, m_aCallbacks[i].m_pUserData); + + io_close(m_ConfigFile); + m_ConfigFile = 0; + } + + virtual void RegisterCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData) + { + dbg_assert(m_NumCallbacks < MAX_CALLBACKS, "too many config callbacks"); + m_aCallbacks[m_NumCallbacks].m_pfnFunc = pfnFunc; + m_aCallbacks[m_NumCallbacks].m_pUserData = pUserData; + m_NumCallbacks++; + } + + virtual void WriteLine(const char *pLine) + { + if(!m_ConfigFile) + return; +#if defined(CONF_FAMILY_WINDOWS) + static const char Newline[] = "\r\n"; +#else + static const char Newline[] = "\n"; +#endif + io_write(m_ConfigFile, pLine, str_length(pLine)); + io_write(m_ConfigFile, Newline, sizeof(Newline)-1); + } +}; + +IConfig *CreateConfig() { return new CConfig; } diff --git a/src/engine/shared/config.h b/src/engine/shared/config.h new file mode 100644 index 00000000..10a54004 --- /dev/null +++ b/src/engine/shared/config.h @@ -0,0 +1,22 @@ +#ifndef ENGINE_SHARED_E_CONFIG_H +#define ENGINE_SHARED_E_CONFIG_H + +struct CConfiguration +{ + #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Save,Desc) int m_##Name; + #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Save,Desc) char m_##Name[Len]; // Flawfinder: ignore + #include "config_variables.h" + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +}; + +extern CConfiguration g_Config; + +enum +{ + CFGFLAG_SAVE=1, + CFGFLAG_CLIENT=2, + CFGFLAG_SERVER=4 +}; + +#endif diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h new file mode 100644 index 00000000..e5541911 --- /dev/null +++ b/src/engine/shared/config_variables.h @@ -0,0 +1,80 @@ +#ifndef ENGINE_SHARED_E_CONFIG_VARIABLES_H +#define ENGINE_SHARED_E_CONFIG_VARIABLES_H +#undef ENGINE_SHARED_E_CONFIG_VARIABLES_H // this file will be included several times + +// TODO: remove this +#include "././game/variables.h" + + +MACRO_CONFIG_STR(PlayerName, player_name, 24, "nameless tee", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Name of the player") +MACRO_CONFIG_STR(ClanName, clan_name, 32, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "(not used)") +MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT|CFGFLAG_SERVER, "Password to the server") +MACRO_CONFIG_STR(Logfile, logfile, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filename to log all output to") + +MACRO_CONFIG_INT(ClCpuThrottle, cl_cpu_throttle, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(ClEventthread, cl_eventthread, 0, 0, 1, CFGFLAG_CLIENT, "Enables the usage of a thread to pump the events") + +MACRO_CONFIG_INT(InpGrab, inp_grab, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use forceful input grabbing method") + +MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Server browser filtering string") + +MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out full server in browser") +MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out empty server in browser") +MACRO_CONFIG_INT(BrFilterPw, br_filter_pw, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out password protected servers in browser") +MACRO_CONFIG_INT(BrFilterPing, br_filter_ping, 999, 0, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ping to filter by in the server browser") +MACRO_CONFIG_STR(BrFilterGametype, br_filter_gametype, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Game types to filter") +MACRO_CONFIG_INT(BrFilterPure, br_filter_pure, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard servers in browser") +MACRO_CONFIG_INT(BrFilterPureMap, br_filter_pure_map, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard maps in browser") +MACRO_CONFIG_INT(BrFilterCompatversion, br_filter_compatversion, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-compatible servers in browser") + +MACRO_CONFIG_INT(BrSort, br_sort, 0, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(BrSortOrder, br_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(BrMaxRequests, br_max_requests, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser") + +MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size") +MACRO_CONFIG_INT(SndRate, snd_rate, 48000, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate") +MACRO_CONFIG_INT(SndEnable, snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound enable") +MACRO_CONFIG_INT(SndVolume, snd_volume, 100, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound volume") +MACRO_CONFIG_INT(SndDevice, snd_device, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "(deprecated) Sound device to use") + +MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(GfxScreenWidth, gfx_screen_width, 800, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution width") +MACRO_CONFIG_INT(GfxScreenHeight, gfx_screen_height, 600, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution height") +MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Fullscreen") +MACRO_CONFIG_INT(GfxAlphabits, gfx_alphabits, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Alpha bits for framebuffer (fullscreen only)") +MACRO_CONFIG_INT(GfxColorDepth, gfx_color_depth, 24, 16, 24, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Colors bits for framebuffer (fullscreen only)") +MACRO_CONFIG_INT(GfxClear, gfx_clear, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Clear screen before rendering") +MACRO_CONFIG_INT(GfxVsync, gfx_vsync, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Vertical sync") +MACRO_CONFIG_INT(GfxDisplayAllModes, gfx_display_all_modes, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(GfxTextureCompression, gfx_texture_compression, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use texture compression") +MACRO_CONFIG_INT(GfxHighDetail, gfx_high_detail, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "High detail") +MACRO_CONFIG_INT(GfxTextureQuality, gfx_texture_quality, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE|CFGFLAG_CLIENT, "FSAA Samples") +MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen refresh rate") +MACRO_CONFIG_INT(GfxFinish, gfx_finish, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity") + +MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server name") +MACRO_CONFIG_STR(SvBindaddr, sv_bindaddr, 128, "", CFGFLAG_SERVER, "Address to bind the server to") +MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server") +MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers") +MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server") +MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") +MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") +MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") +MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password") +MACRO_CONFIG_INT(SvMapReload, sv_map_reload, 0, 0, 1, CFGFLAG_SERVER, "Reload the current map") + +MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode") +MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems") +MACRO_CONFIG_INT(DbgStressNetwork, dbg_stress_network, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress network") +MACRO_CONFIG_INT(DbgPref, dbg_pref, 0, 0, 1, CFGFLAG_SERVER, "Performance outputs") +MACRO_CONFIG_INT(DbgGraphs, dbg_graphs, 0, 0, 1, CFGFLAG_CLIENT, "Performance graphs") +MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings") +MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress") +MACRO_CONFIG_INT(DbgResizable, dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing") +#endif diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp new file mode 100644 index 00000000..c545b7db --- /dev/null +++ b/src/engine/shared/console.cpp @@ -0,0 +1,489 @@ +#include <base/system.h> +#include <engine/shared/protocol.h> +#include <engine/storage.h> +#include "console.h" +#include "config.h" +#include "engine.h" +#include "linereader.h" + +const char *CConsole::CResult::GetString(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return ""; + return m_apArgs[Index]; +} + +int CConsole::CResult::GetInteger(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return 0; + return str_toint(m_apArgs[Index]); +} + +float CConsole::CResult::GetFloat(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return 0.0f; + return str_tofloat(m_apArgs[Index]); +} + +// the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces +static char *SkipBlanks(char *pStr) +{ + while(*pStr && (*pStr == ' ' || *pStr == '\t' || *pStr == '\n')) + pStr++; + return pStr; +} + +static char *SkipToBlank(char *pStr) +{ + while(*pStr && (*pStr != ' ' && *pStr != '\t' && *pStr != '\n')) + pStr++; + return pStr; +} + + +int CConsole::ParseStart(CResult *pResult, const char *pString, int Length) +{ + char *pStr; + int Len = sizeof(pResult->m_aStringStorage); + if(Length < Len) + Len = Length; + + str_copy(pResult->m_aStringStorage, pString, Length); + pStr = pResult->m_aStringStorage; + + // get command + pStr = SkipBlanks(pStr); + pResult->m_pCommand = pStr; + pStr = SkipToBlank(pStr); + + if(*pStr) + { + pStr[0] = 0; + pStr++; + } + + pResult->m_pArgsStart = pStr; + return 0; +} + +int CConsole::ParseArgs(CResult *pResult, const char *pFormat) +{ + char Command; + char *pStr; + int Optional = 0; + int Error = 0; + + pStr = pResult->m_pArgsStart; + + while(1) + { + // fetch command + Command = *pFormat; + pFormat++; + + if(!Command) + break; + + if(Command == '?') + Optional = 1; + else + { + pStr = SkipBlanks(pStr); + + if(!(*pStr)) // error, non optional command needs value + { + if(!Optional) + Error = 1; + break; + } + + // add token + if(*pStr == '"') + { + char *pDst; + pStr++; + pResult->AddArgument(pStr); + + pDst = pStr; // we might have to process escape data + while(1) + { + if(pStr[0] == '"') + break; + else if(pStr[0] == '\\') + { + if(pStr[1] == '\\') + pStr++; // skip due to escape + else if(pStr[1] == '"') + pStr++; // skip due to escape + } + else if(pStr[0] == 0) + return 1; // return error + + *pDst = *pStr; + pDst++; + pStr++; + } + + // write null termination + *pDst = 0; + + + pStr++; + } + else + { + pResult->AddArgument(pStr); + + if(Command == 'r') // rest of the string + break; + else if(Command == 'i') // validate int + pStr = SkipToBlank(pStr); + else if(Command == 'f') // validate float + pStr = SkipToBlank(pStr); + else if(Command == 's') // validate string + pStr = SkipToBlank(pStr); + + if(pStr[0] != 0) // check for end of string + { + pStr[0] = 0; + pStr++; + } + } + } + } + + return Error; +} + +void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) +{ + m_pfnPrintCallback = pfnPrintCallback; + m_pPrintCallbackUserdata = pUserData; +} + +void CConsole::Print(const char *pStr) +{ + dbg_msg("console" ,"%s", pStr); + if (m_pfnPrintCallback) + m_pfnPrintCallback(pStr, m_pPrintCallbackUserdata); +} + +void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) +{ + CResult Result; + + char aStrokeStr[2] = {'0', 0}; + if(Stroke) + aStrokeStr[0] = '1'; + + while(pStr && *pStr) + { + const char *pEnd = pStr; + const char *pNextPart = 0; + int InString = 0; + + while(*pEnd) + { + if(*pEnd == '"') + InString ^= 1; + else if(*pEnd == '\\') // escape sequences + { + if(pEnd[1] == '"') + pEnd++; + } + else if(!InString) + { + if(*pEnd == ';') // command separator + { + pNextPart = pEnd+1; + break; + } + else if(*pEnd == '#') // comment, no need to do anything more + break; + } + + pEnd++; + } + + if(ParseStart(&Result, pStr, (pEnd-pStr) + 1) != 0) + return; + + CCommand *pCommand = FindCommand(Result.m_pCommand); + + if(pCommand) + { + int IsStrokeCommand = 0; + if(Result.m_pCommand[0] == '+') + { + // insert the stroke direction token + Result.AddArgument(aStrokeStr); + IsStrokeCommand = 1; + } + + if(Stroke || IsStrokeCommand) + { + if(ParseArgs(&Result, pCommand->m_pParams)) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + Print(aBuf); + } + else + pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); + } + } + else + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand); + Print(aBuf); + } + + pStr = pNextPart; + } +} + +void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) +{ + CCommand *pCommand; + for(pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask) + { + if(str_find_nocase(pCommand->m_pName, pStr)) + pfnCallback(pCommand->m_pName, pUser); + } + } +} + +// TODO: this should regard the commands flag +CConsole::CCommand *CConsole::FindCommand(const char *pName) +{ + CCommand *pCommand; + for (pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(str_comp_nocase(pCommand->m_pName, pName) == 0) + return pCommand; + } + + return 0x0; +} + +void CConsole::ExecuteLine(const char *pStr) +{ + CConsole::ExecuteLineStroked(1, pStr); +} + + +void CConsole::ExecuteFile(const char *pFilename) +{ + // make sure that this isn't being executed already + for(CExecFile *pCur = m_pFirstExec; pCur; pCur = pCur->m_pPrev) + if(str_comp(pFilename, pCur->m_pFilename) == 0) + return; + + if(!m_pStorage) + m_pStorage = Kernel()->RequestInterface<IStorage>(); + if(!m_pStorage) + return; + + // push this one to the stack + CExecFile ThisFile; + CExecFile *pPrev = m_pFirstExec; + ThisFile.m_pFilename = pFilename; + ThisFile.m_pPrev = m_pFirstExec; + m_pFirstExec = &ThisFile; + + // exec the file + IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ); + + if(File) + { + char *pLine; + CLineReader lr; + + dbg_msg("console", "executing '%s'", pFilename); + lr.Init(File); + + while((pLine = lr.Get())) + ExecuteLine(pLine); + + io_close(File); + } + else + dbg_msg("console", "failed to open '%s'", pFilename); + + m_pFirstExec = pPrev; +} + +void CConsole::Con_Echo(IResult *pResult, void *pUserData) +{ + ((CConsole*)pUserData)->Print(pResult->GetString(0)); +} + +void CConsole::Con_Exec(IResult *pResult, void *pUserData) +{ + ((CConsole*)pUserData)->ExecuteFile(pResult->GetString(0)); +} + +struct CIntVariableData +{ + IConsole *m_pConsole; + int *m_pVariable; + int m_Min; + int m_Max; +}; + +struct CStrVariableData +{ + IConsole *m_pConsole; + char *m_pStr; + int m_MaxSize; +}; + +static void IntVariableCommand(IConsole::IResult *pResult, void *pUserData) +{ + CIntVariableData *pData = (CIntVariableData *)pUserData; + + if(pResult->NumArguments()) + { + int Val = pResult->GetInteger(0); + + // do clamping + if(pData->m_Min != pData->m_Max) + { + if (Val < pData->m_Min) + Val = pData->m_Min; + if (pData->m_Max != 0 && Val > pData->m_Max) + Val = pData->m_Max; + } + + *(pData->m_pVariable) = Val; + } + else + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "Value: %d", *(pData->m_pVariable)); + pData->m_pConsole->Print(aBuf); + } +} + +static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData) +{ + CStrVariableData *pData = (CStrVariableData *)pUserData; + + if(pResult->NumArguments()) + str_copy(pData->m_pStr, pResult->GetString(0), pData->m_MaxSize); + else + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr); + pData->m_pConsole->Print(aBuf); + } +} + +CConsole::CConsole() +{ + m_pFirstCommand = 0; + m_pFirstExec = 0; + m_pPrintCallbackUserdata = 0; + m_pfnPrintCallback = 0; + + m_pStorage = 0; + + // register some basic commands + Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text"); + Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file"); + + // TODO: this should disappear + #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \ + { \ + static CIntVariableData Data = { this, &g_Config.m_##Name, Min, Max }; \ + Register(#ScriptName, "?i", Flags, IntVariableCommand, &Data, Desc); \ + } + + #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Flags,Desc) \ + { \ + static CStrVariableData Data = { this, g_Config.m_##Name, Len }; \ + Register(#ScriptName, "?r", Flags, StrVariableCommand, &Data, Desc); \ + } + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} + +void CConsole::ParseArguments(int NumArgs, const char **ppArguments) +{ + for(int i = 0; i < NumArgs; i++) + { + // check for scripts to execute + if(ppArguments[i][0] == '-' && ppArguments[i][1] == 'f' && ppArguments[i][2] == 0 && NumArgs - i > 1) + { + ExecuteFile(ppArguments[i+1]); + i++; + } + else + { + // search arguments for overrides + ExecuteLine(ppArguments[i]); + } + } +} + +void CConsole::Register(const char *pName, const char *pParams, + int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) +{ + CCommand *pCommand = (CCommand *)mem_alloc(sizeof(CCommand), sizeof(void*)); + pCommand->m_pfnCallback = pfnFunc; + pCommand->m_pUserData = pUser; + pCommand->m_pHelp = pHelp; + pCommand->m_pName = pName; + pCommand->m_pParams = pParams; + pCommand->m_Flags = Flags; + + + pCommand->m_pNext = m_pFirstCommand; + m_pFirstCommand = pCommand; +} + +void CConsole::Con_Chain(IResult *pResult, void *pUserData) +{ + CChain *pInfo = (CChain *)pUserData; + pInfo->m_pfnChainCallback(pResult, pInfo->m_pUserData, pInfo->m_pfnCallback, pInfo->m_pCallbackUserData); +} + +void CConsole::Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) +{ + CCommand *pCommand = FindCommand(pName); + + if(!pCommand) + { + dbg_msg("console", "failed to chain '%s'", pName); + return; + } + + CChain *pChainInfo = (CChain *)mem_alloc(sizeof(CChain), sizeof(void*)); + + // store info + pChainInfo->m_pfnChainCallback = pfnChainFunc; + pChainInfo->m_pUserData = pUser; + pChainInfo->m_pfnCallback = pCommand->m_pfnCallback; + pChainInfo->m_pCallbackUserData = pCommand->m_pUserData; + + // chain + pCommand->m_pfnCallback = Con_Chain; + pCommand->m_pUserData = pChainInfo; +} + + +IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName) +{ + return FindCommand(pName); +} + + +extern IConsole *CreateConsole() { return new CConsole(); } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h new file mode 100644 index 00000000..93d23547 --- /dev/null +++ b/src/engine/shared/console.h @@ -0,0 +1,96 @@ +#ifndef ENGINE_SHARED_CONSOLE_H +#define ENGINE_SHARED_CONSOLE_H + +#include <engine/console.h> + +class CConsole : public IConsole +{ + class CCommand : public CCommandInfo + { + public: + CCommand *m_pNext; + int m_Flags; + FCommandCallback m_pfnCallback; + void *m_pUserData; + }; + + + class CChain + { + public: + FChainCommandCallback m_pfnChainCallback; + FCommandCallback m_pfnCallback; + void *m_pCallbackUserData; + void *m_pUserData; + }; + + CCommand *m_pFirstCommand; + + class CExecFile + { + public: + const char *m_pFilename; + struct CExecFile *m_pPrev; + }; + + CExecFile *m_pFirstExec; + class IStorage *m_pStorage; + + static void Con_Chain(IResult *pResult, void *pUserData); + static void Con_Echo(IResult *pResult, void *pUserData); + static void Con_Exec(IResult *pResult, void *pUserData); + + void ExecuteFileRecurse(const char *pFilename); + void ExecuteLineStroked(int Stroke, const char *pStr); + + FPrintCallback m_pfnPrintCallback; + void *m_pPrintCallbackUserdata; + + enum + { + CONSOLE_MAX_STR_LENGTH = 1024, + MAX_PARTS = (CONSOLE_MAX_STR_LENGTH+1)/2 + }; + + class CResult : public IResult + { + public: + char m_aStringStorage[CONSOLE_MAX_STR_LENGTH+1]; + char *m_pArgsStart; + + const char *m_pCommand; + const char *m_apArgs[MAX_PARTS]; + + void AddArgument(const char *pArg) + { + m_apArgs[m_NumArgs++] = pArg; + } + + virtual const char *GetString(unsigned Index); + virtual int GetInteger(unsigned Index); + virtual float GetFloat(unsigned Index); + }; + + int ParseStart(CResult *pResult, const char *pString, int Length); + int ParseArgs(CResult *pResult, const char *pFormat); + + CCommand *FindCommand(const char *pName); + +public: + CConsole(); + + virtual CCommandInfo *GetCommandInfo(const char *pName); + virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) ; + + virtual void ParseArguments(int NumArgs, const char **ppArguments); + virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp); + virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser); + + virtual void ExecuteLine(const char *pStr); + virtual void ExecuteFile(const char *pFilename); + + virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData); + virtual void Print(const char *pStr); +}; + +#endif diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp new file mode 100644 index 00000000..dcc32ef2 --- /dev/null +++ b/src/engine/shared/datafile.cpp @@ -0,0 +1,643 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/storage.h> +#include "datafile.h" +#include "engine.h" +#include <zlib.h> + +static const int DEBUG=0; + +struct CDatafileItemType +{ + int m_Type; + int m_Start; + int m_Num; +} ; + +struct CDatafileItem +{ + int m_TypeAndId; + int m_Size; +}; + +struct CDatafileHeader +{ + char m_aId[4]; + int m_Version; + int m_Size; + int m_Swaplen; + int m_NumItemTypes; + int m_NumItems; + int m_NumRawData; + int m_ItemSize; + int m_DataSize; +}; + +struct CDatafileData +{ + int m_NumItemTypes; + int m_NumItems; + int m_NumRawData; + int m_ItemSize; + int m_DataSize; + char m_aStart[4]; +}; + +struct CDatafileInfo +{ + CDatafileItemType *m_pItemTypes; + int *m_pItemOffsets; + int *m_pDataOffsets; + int *m_pDataSizes; + + char *m_pItemStart; + char *m_pDataStart; +}; + +struct CDatafile +{ + IOHANDLE m_File; + unsigned m_Crc; + CDatafileInfo m_Info; + CDatafileHeader m_Header; + int m_DataStartOffset; + char **m_ppDataPtrs; + char *m_pData; +}; + +bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename) +{ + dbg_msg("datafile", "loading. filename='%s'", pFilename); + + IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ); + if(!File) + { + dbg_msg("datafile", "could not open '%s'", pFilename); + return false; + } + + + // take the CRC of the file and store it + unsigned Crc = 0; + { + enum + { + BUFFER_SIZE = 64*1024 + }; + + unsigned char aBuffer[BUFFER_SIZE]; + + while(1) + { + unsigned Bytes = io_read(File, aBuffer, BUFFER_SIZE); + if(Bytes <= 0) + break; + Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention + } + + io_seek(File, 0, IOSEEK_START); + } + + + // TODO: change this header + CDatafileHeader Header; + io_read(File, &Header, sizeof(Header)); + if(Header.m_aId[0] != 'A' || Header.m_aId[1] != 'T' || Header.m_aId[2] != 'A' || Header.m_aId[3] != 'D') + { + if(Header.m_aId[0] != 'D' || Header.m_aId[1] != 'A' || Header.m_aId[2] != 'T' || Header.m_aId[3] != 'A') + { + dbg_msg("datafile", "wrong signature. %x %x %x %x", Header.m_aId[0], Header.m_aId[1], Header.m_aId[2], Header.m_aId[3]); + return 0; + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(&Header, sizeof(int), sizeof(Header)/sizeof(int)); +#endif + if(Header.m_Version != 3 && Header.m_Version != 4) + { + dbg_msg("datafile", "wrong version. version=%x", Header.m_Version); + return 0; + } + + // read in the rest except the data + unsigned Size = 0; + Size += Header.m_NumItemTypes*sizeof(CDatafileItemType); + Size += (Header.m_NumItems+Header.m_NumRawData)*sizeof(int); + if(Header.m_Version == 4) + Size += Header.m_NumRawData*sizeof(int); // v4 has uncompressed data sizes aswell + Size += Header.m_ItemSize; + + unsigned AllocSize = Size; + AllocSize += sizeof(CDatafile); // add space for info structure + AllocSize += Header.m_NumRawData*sizeof(void*); // add space for data pointers + + m_pDataFile = (CDatafile*)mem_alloc(AllocSize, 1); + m_pDataFile->m_Header = Header; + m_pDataFile->m_DataStartOffset = sizeof(CDatafileHeader) + Size; + m_pDataFile->m_ppDataPtrs = (char**)(m_pDataFile+1); + m_pDataFile->m_pData = (char *)(m_pDataFile+1)+Header.m_NumRawData*sizeof(char *); + m_pDataFile->m_File = File; + m_pDataFile->m_Crc = Crc; + + // clear the data pointers + mem_zero(m_pDataFile->m_ppDataPtrs, Header.m_NumRawData*sizeof(void*)); + + // read types, offsets, sizes and item data + unsigned ReadSize = io_read(File, m_pDataFile->m_pData, Size); + if(ReadSize != Size) + { + mem_free(m_pDataFile); + m_pDataFile = 0; + dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", Size, ReadSize); + return false; + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(m_pDataFile->m_pData, sizeof(int), Header.Swaplen / sizeof(int)); +#endif + + //if(DEBUG) + { + dbg_msg("datafile", "allocsize=%d", AllocSize); + dbg_msg("datafile", "readsize=%d", ReadSize); + dbg_msg("datafile", "swaplen=%d", Header.m_Swaplen); + dbg_msg("datafile", "item_size=%d", m_pDataFile->m_Header.m_ItemSize); + } + + m_pDataFile->m_Info.m_pItemTypes = (CDatafileItemType *)m_pDataFile->m_pData; + m_pDataFile->m_Info.m_pItemOffsets = (int *)&m_pDataFile->m_Info.m_pItemTypes[m_pDataFile->m_Header.m_NumItemTypes]; + m_pDataFile->m_Info.m_pDataOffsets = (int *)&m_pDataFile->m_Info.m_pItemOffsets[m_pDataFile->m_Header.m_NumItems]; + m_pDataFile->m_Info.m_pDataSizes = (int *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData]; + + if(Header.m_Version == 4) + m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataSizes[m_pDataFile->m_Header.m_NumRawData]; + else + m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData]; + m_pDataFile->m_Info.m_pDataStart = m_pDataFile->m_Info.m_pItemStart + m_pDataFile->m_Header.m_ItemSize; + + dbg_msg("datafile", "loading done. datafile='%s'", pFilename); + + if(DEBUG) + { + /* + for(int i = 0; i < m_pDataFile->data.num_raw_data; i++) + { + void *p = datafile_get_data(df, i); + dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&m_pDataFile->data)), size); + } + + for(int i = 0; i < datafile_num_items(df); i++) + { + int type, id; + void *data = datafile_get_item(df, i, &type, &id); + dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, m_pDataFile->info.item_offsets[i]); + int *idata = (int*)data; + for(int k = 0; k < 3; k++) + dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); + } + + for(int i = 0; i < m_pDataFile->data.num_m_aItemTypes; i++) + { + dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, + m_pDataFile->info.m_aItemTypes[i].type, + m_pDataFile->info.m_aItemTypes[i].start, + m_pDataFile->info.m_aItemTypes[i].num); + for(int k = 0; k < m_pDataFile->info.m_aItemTypes[i].num; k++) + { + int type, id; + datafile_get_item(df, m_pDataFile->info.m_aItemTypes[i].start+k, &type, &id); + if(type != m_pDataFile->info.m_aItemTypes[i].type) + dbg_msg("map", "\tERROR"); + } + } + */ + } + + return true; +} + +int CDataFileReader::NumData() +{ + if(!m_pDataFile) { return 0; } + return m_pDataFile->m_Header.m_NumRawData; +} + +// always returns the size in the file +int CDataFileReader::GetDataSize(int Index) +{ + if(!m_pDataFile) { return 0; } + + if(Index == m_pDataFile->m_Header.m_NumRawData-1) + return m_pDataFile->m_Header.m_DataSize-m_pDataFile->m_Info.m_pDataOffsets[Index]; + return m_pDataFile->m_Info.m_pDataOffsets[Index+1]-m_pDataFile->m_Info.m_pDataOffsets[Index]; +} + +void *CDataFileReader::GetDataImpl(int Index, int Swap) +{ + if(!m_pDataFile) { return 0; } + + // load it if needed + if(!m_pDataFile->m_ppDataPtrs[Index]) + { + // fetch the data size + int DataSize = GetDataSize(Index); + int SwapSize = DataSize; + + if(m_pDataFile->m_Header.m_Version == 4) + { + // v4 has compressed data + void *pTemp = (char *)mem_alloc(DataSize, 1); + unsigned long UncompressedSize = m_pDataFile->m_Info.m_pDataSizes[Index]; + unsigned long s; + + dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", Index, DataSize, UncompressedSize); + m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(UncompressedSize, 1); + + // read the compressed data + io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START); + io_read(m_pDataFile->m_File, pTemp, DataSize); + + // decompress the data, TODO: check for errors + s = UncompressedSize; + uncompress((Bytef*)m_pDataFile->m_ppDataPtrs[Index], &s, (Bytef*)pTemp, DataSize); // ignore_convention + SwapSize = s; + + // clean up the temporary buffers + mem_free(pTemp); + } + else + { + // load the data + dbg_msg("datafile", "loading data index=%d size=%d", Index, DataSize); + m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(DataSize, 1); + io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START); + io_read(m_pDataFile->m_File, m_pDataFile->m_ppDataPtrs[Index], DataSize); + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + if(Swap && SwapSize) + swap_endian(m_pDataFile->m_ppDataPtrs[Index], sizeof(int), SwapSize/sizeof(int)); +#endif + } + + return m_pDataFile->m_ppDataPtrs[Index]; +} + +void *CDataFileReader::GetData(int Index) +{ + return GetDataImpl(Index, 0); +} + +void *CDataFileReader::GetDataSwapped(int Index) +{ + return GetDataImpl(Index, 1); +} + +void CDataFileReader::UnloadData(int Index) +{ + if(Index < 0) + return; + + // + mem_free(m_pDataFile->m_ppDataPtrs[Index]); + m_pDataFile->m_ppDataPtrs[Index] = 0x0; +} + +int CDataFileReader::GetItemSize(int Index) +{ + if(!m_pDataFile) { return 0; } + if(Index == m_pDataFile->m_Header.m_NumItems-1) + return m_pDataFile->m_Header.m_ItemSize-m_pDataFile->m_Info.m_pItemOffsets[Index]; + return m_pDataFile->m_Info.m_pItemOffsets[Index+1]-m_pDataFile->m_Info.m_pItemOffsets[Index]; +} + +void *CDataFileReader::GetItem(int Index, int *pType, int *pId) +{ + if(!m_pDataFile) { if(pType) *pType = 0; if(pId) *pId = 0; return 0; } + + CDatafileItem *i = (CDatafileItem *)(m_pDataFile->m_Info.m_pItemStart+m_pDataFile->m_Info.m_pItemOffsets[Index]); + if(pType) + *pType = (i->m_TypeAndId>>16)&0xffff; // remove sign extention + if(pId) + *pId = i->m_TypeAndId&0xffff; + return (void *)(i+1); +} + +void CDataFileReader::GetType(int Type, int *pStart, int *pNum) +{ + *pStart = 0; + *pNum = 0; + + if(!m_pDataFile) + return; + + for(int i = 0; i < m_pDataFile->m_Header.m_NumItemTypes; i++) + { + if(m_pDataFile->m_Info.m_pItemTypes[i].m_Type == Type) + { + *pStart = m_pDataFile->m_Info.m_pItemTypes[i].m_Start; + *pNum = m_pDataFile->m_Info.m_pItemTypes[i].m_Num; + return; + } + } +} + +void *CDataFileReader::FindItem(int Type, int Id) +{ + if(!m_pDataFile) return 0; + + int Start, Num; + GetType(Type, &Start, &Num); + for(int i = 0; i < Num; i++) + { + int ItemId; + void *pItem = GetItem(Start+i,0, &ItemId); + if(Id == ItemId) + return pItem; + } + return 0; +} + +int CDataFileReader::NumItems() +{ + if(!m_pDataFile) return 0; + return m_pDataFile->m_Header.m_NumItems; +} + +bool CDataFileReader::Close() +{ + if(!m_pDataFile) + return true; + + // free the data that is loaded + int i; + for(i = 0; i < m_pDataFile->m_Header.m_NumRawData; i++) + mem_free(m_pDataFile->m_ppDataPtrs[i]); + + io_close(m_pDataFile->m_File); + mem_free(m_pDataFile); + m_pDataFile = 0; + return true; +} + +unsigned CDataFileReader::Crc() +{ + if(!m_pDataFile) return -1; + return m_pDataFile->m_Crc; +} + +bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) +{ + int i; + //DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1); + m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE); + if(!m_File) + return false; + + m_NumItems = 0; + m_NumDatas = 0; + m_NumItemTypes = 0; + mem_zero(&m_aItemTypes, sizeof(m_aItemTypes)); + + for(i = 0; i < 0xffff; i++) + { + m_aItemTypes[i].m_First = -1; + m_aItemTypes[i].m_Last = -1; + } + + return true; +} + +int CDataFileWriter::AddItem(int Type, int Id, int Size, void *pData) +{ + m_aItems[m_NumItems].m_Type = Type; + m_aItems[m_NumItems].m_Id = Id; + m_aItems[m_NumItems].m_Size = Size; + + /* + dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size); + int i; + for(i = 0; i < size/4; i++) + dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]); + */ + + // copy data + m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1); + mem_copy(m_aItems[m_NumItems].m_pData, pData, Size); + + if(!m_aItemTypes[Type].m_Num) // count item types + m_NumItemTypes++; + + // link + m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last; + m_aItems[m_NumItems].m_Next = -1; + + if(m_aItemTypes[Type].m_Last != -1) + m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems; + m_aItemTypes[Type].m_Last = m_NumItems; + + if(m_aItemTypes[Type].m_First == -1) + m_aItemTypes[Type].m_First = m_NumItems; + + m_aItemTypes[Type].m_Num++; + + m_NumItems++; + return m_NumItems-1; +} + +int CDataFileWriter::AddData(int Size, void *pData) +{ + CDataInfo *pInfo = &m_aDatas[m_NumDatas]; + unsigned long s = compressBound(Size); + void *pCompData = mem_alloc(s, 1); // temporary buffer that we use duing compression + + int Result = compress((Bytef*)pCompData, &s, (Bytef*)pData, Size); // ignore_convention + if(Result != Z_OK) + { + dbg_msg("datafile", "compression error %d", Result); + dbg_assert(0, "zlib error"); + } + + pInfo->m_UncompressedSize = Size; + pInfo->m_CompressedSize = (int)s; + pInfo->m_pCompressedData = mem_alloc(pInfo->m_CompressedSize, 1); + mem_copy(pInfo->m_pCompressedData, pCompData, pInfo->m_CompressedSize); + mem_free(pCompData); + + m_NumDatas++; + return m_NumDatas-1; +} + +int CDataFileWriter::AddDataSwapped(int Size, void *pData) +{ +#if defined(CONF_ARCH_ENDIAN_BIG) + void *pSwapped = mem_alloc(Size, 1); // temporary buffer that we use duing compression + int Index; + mem_copy(pSwapped, pData, Size); + swap_endian(&pSwapped, sizeof(int), Size/sizeof(int)); + Index = AddData(Size, Swapped); + mem_free(pSwapped); + return Index; +#else + return AddData(Size, pData); +#endif +} + + +int CDataFileWriter::Finish() +{ + int ItemSize = 0; + int TypesSize, HeaderSize, OffsetSize, FileSize, SwapSize; + int DataSize = 0; + CDatafileHeader Header; + + // we should now write this file! + if(DEBUG) + dbg_msg("datafile", "writing"); + + // calculate sizes + for(int i = 0; i < m_NumItems; i++) + { + if(DEBUG) + dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem)); + ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem); + } + + + for(int i = 0; i < m_NumDatas; i++) + DataSize += m_aDatas[i].m_CompressedSize; + + // calculate the complete size + TypesSize = m_NumItemTypes*sizeof(CDatafileItemType); + HeaderSize = sizeof(CDatafileHeader); + OffsetSize = m_NumItems*sizeof(int) + m_NumDatas*sizeof(int); + FileSize = HeaderSize + TypesSize + OffsetSize + ItemSize + DataSize; + SwapSize = FileSize - DataSize; + + (void)SwapSize; + + if(DEBUG) + dbg_msg("datafile", "num_m_aItemTypes=%d TypesSize=%d m_aItemsize=%d DataSize=%d", m_NumItemTypes, TypesSize, ItemSize, DataSize); + + // construct Header + { + Header.m_aId[0] = 'D'; + Header.m_aId[1] = 'A'; + Header.m_aId[2] = 'T'; + Header.m_aId[3] = 'A'; + Header.m_Version = 4; + Header.m_Size = FileSize - 16; + Header.m_Swaplen = SwapSize - 16; + Header.m_NumItemTypes = m_NumItemTypes; + Header.m_NumItems = m_NumItems; + Header.m_NumRawData = m_NumDatas; + Header.m_ItemSize = ItemSize; + Header.m_DataSize = DataSize; + + // TODO: apply swapping + // write Header + if(DEBUG) + dbg_msg("datafile", "HeaderSize=%d", sizeof(Header)); + io_write(m_File, &Header, sizeof(Header)); + } + + // write types + for(int i = 0, Count = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write info + CDatafileItemType Info; + Info.m_Type = i; + Info.m_Start = Count; + Info.m_Num = m_aItemTypes[i].m_Num; + if(DEBUG) + dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num); + io_write(m_File, &Info, sizeof(Info)); + Count += m_aItemTypes[i].m_Num; + } + } + + // write item offsets + for(int i = 0, Offset = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write all m_aItems in of this type + int k = m_aItemTypes[i].m_First; + while(k != -1) + { + if(DEBUG) + dbg_msg("datafile", "writing item offset num=%d offset=%d", k, Offset); + io_write(m_File, &Offset, sizeof(Offset)); + Offset += m_aItems[k].m_Size + sizeof(CDatafileItem); + + // next + k = m_aItems[k].m_Next; + } + } + } + + // write data offsets + for(int i = 0, Offset = 0; i < m_NumDatas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, Offset); + io_write(m_File, &Offset, sizeof(Offset)); + Offset += m_aDatas[i].m_CompressedSize; + } + + // write data uncompressed sizes + for(int i = 0; i < m_NumDatas; i++) + { + /* + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); + */ + io_write(m_File, &m_aDatas[i].m_UncompressedSize, sizeof(int)); + } + + // write m_aItems + for(int i = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write all m_aItems in of this type + int k = m_aItemTypes[i].m_First; + while(k != -1) + { + CDatafileItem Item; + Item.m_TypeAndId = (i<<16)|m_aItems[k].m_Id; + Item.m_Size = m_aItems[k].m_Size; + if(DEBUG) + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_Id, m_aItems[k].m_Size); + + io_write(m_File, &Item, sizeof(Item)); + io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size); + + // next + k = m_aItems[k].m_Next; + } + } + } + + // write data + for(int i = 0; i < m_NumDatas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize); + io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize); + } + + // free data + for(int i = 0; i < m_NumItems; i++) + mem_free(m_aItems[i].m_pData); + + + io_close(m_File); + + if(DEBUG) + dbg_msg("datafile", "done"); + return 0; +} diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h new file mode 100644 index 00000000..eddce611 --- /dev/null +++ b/src/engine/shared/datafile.h @@ -0,0 +1,77 @@ +#ifndef ENGINE_SHARED_DATAFILE_H +#define ENGINE_SHARED_DATAFILE_H + +// raw datafile access +class CDataFileReader +{ + class CDatafile *m_pDataFile; + void *GetDataImpl(int Index, int Swap); +public: + CDataFileReader() : m_pDataFile(0) {} + ~CDataFileReader() { Close(); } + + bool IsOpen() const { return m_pDataFile != 0; } + + bool Open(class IStorage *pStorage, const char *pFilename); + bool Close(); + + void *GetData(int Index); + void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved + int GetDataSize(int Index); + void UnloadData(int Index); + void *GetItem(int Index, int *pType, int *pId); + int GetItemSize(int Index); + void GetType(int Type, int *pStart, int *pNum); + void *FindItem(int Type, int Id); + int NumItems(); + int NumData(); + void Unload(); + + unsigned Crc(); +}; + +// write access +class CDataFileWriter +{ + struct CDataInfo + { + int m_UncompressedSize; + int m_CompressedSize; + void *m_pCompressedData; + } ; + + struct CItemInfo + { + int m_Type; + int m_Id; + int m_Size; + int m_Next; + int m_Prev; + void *m_pData; + }; + + struct CItemTypeInfo + { + int m_Num; + int m_First; + int m_Last; + }; + + IOHANDLE m_File; + int m_NumItems; + int m_NumDatas; + int m_NumItemTypes; + CItemTypeInfo m_aItemTypes[0xffff]; + CItemInfo m_aItems[1024]; + CDataInfo m_aDatas[1024]; + +public: + bool Open(class IStorage *pStorage, const char *Filename); + int AddData(int Size, void *pData); + int AddDataSwapped(int Size, void *pData); + int AddItem(int Type, int Id, int Size, void *pData); + int Finish(); +}; + + +#endif diff --git a/src/engine/shared/demorec.cpp b/src/engine/shared/demorec.cpp new file mode 100644 index 00000000..48b06e9a --- /dev/null +++ b/src/engine/shared/demorec.cpp @@ -0,0 +1,623 @@ +#include <base/system.h> +#include <engine/shared/protocol.h> +#include <engine/storage.h> +#include "demorec.h" +#include "memheap.h" +#include "snapshot.h" +#include "compression.h" +#include "network.h" +#include "engine.h" + +static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; + +CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta) +{ + m_File = 0; + m_LastTickMarker = -1; + m_pSnapshotDelta = pSnapshotDelta; +} + +//static IOHANDLE m_File = 0; + +// Record +int CDemoRecorder::Start(class IStorage *pStorage, const char *pFilename, const char *pNetVersion, const char *pMap, int Crc, const char *pType) +{ + CDemoHeader Header; + if(m_File) + return -1; + + m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE); + + if(!m_File) + { + dbg_msg("demorec/record", "Unable to open '%s' for recording", pFilename); + return -1; + } + + // write header + mem_zero(&Header, sizeof(Header)); + mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker)); + str_copy(Header.m_aNetversion, pNetVersion, sizeof(Header.m_aNetversion)); + str_copy(Header.m_aMap, pMap, sizeof(Header.m_aMap)); + str_copy(Header.m_aType, pType, sizeof(Header.m_aType)); + Header.m_aCrc[0] = (Crc>>24)&0xff; + Header.m_aCrc[1] = (Crc>>16)&0xff; + Header.m_aCrc[2] = (Crc>>8)&0xff; + Header.m_aCrc[3] = (Crc)&0xff; + io_write(m_File, &Header, sizeof(Header)); + + m_LastKeyFrame = -1; + m_LastTickMarker = -1; + + dbg_msg("demorec/record", "Recording to '%s'", pFilename); + return 0; +} + +/* + Tickmarker + 7 = Always set + 6 = Keyframe flag + 0-5 = Delta tick + + Normal + 7 = Not set + 5-6 = Type + 0-4 = Size +*/ + +enum +{ + CHUNKTYPEFLAG_TICKMARKER = 0x80, + CHUNKTICKFLAG_KEYFRAME = 0x40, // only when tickmarker is set + + CHUNKMASK_TICK = 0x3f, + CHUNKMASK_TYPE = 0x60, + CHUNKMASK_SIZE = 0x1f, + + CHUNKTYPE_SNAPSHOT = 1, + CHUNKTYPE_MESSAGE = 2, + CHUNKTYPE_DELTA = 3, + + CHUNKFLAG_BIGSIZE = 0x10 +}; + +void CDemoRecorder::WriteTickMarker(int Tick, int Keyframe) +{ + if(m_LastTickMarker == -1 || Tick-m_LastTickMarker > 63 || Keyframe) + { + unsigned char aChunk[5]; + aChunk[0] = CHUNKTYPEFLAG_TICKMARKER; + aChunk[1] = (Tick>>24)&0xff; + aChunk[2] = (Tick>>16)&0xff; + aChunk[3] = (Tick>>8)&0xff; + aChunk[4] = (Tick)&0xff; + + if(Keyframe) + aChunk[0] |= CHUNKTICKFLAG_KEYFRAME; + + io_write(m_File, aChunk, sizeof(aChunk)); + } + else + { + unsigned char aChunk[1]; + aChunk[0] = CHUNKTYPEFLAG_TICKMARKER | (Tick-m_LastTickMarker); + io_write(m_File, aChunk, sizeof(aChunk)); + } + + m_LastTickMarker = Tick; +} + +void CDemoRecorder::Write(int Type, const void *pData, int Size) +{ + char aBuffer[64*1024]; + char aBuffer2[64*1024]; + unsigned char aChunk[3]; + + if(!m_File) + return; + + /* pad the data with 0 so we get an alignment of 4, + else the compression won't work and miss some bytes */ + mem_copy(aBuffer2, pData, Size); + while(Size&3) + aBuffer2[Size++] = 0; + Size = CVariableInt::Compress(aBuffer2, Size, aBuffer); // buffer2 -> buffer + Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2)); // buffer -> buffer2 + + + aChunk[0] = ((Type&0x3)<<5); + if(Size < 30) + { + aChunk[0] |= Size; + io_write(m_File, aChunk, 1); + } + else + { + if(Size < 256) + { + aChunk[0] |= 30; + aChunk[1] = Size&0xff; + io_write(m_File, aChunk, 2); + } + else + { + aChunk[0] |= 31; + aChunk[1] = Size&0xff; + aChunk[2] = Size>>8; + io_write(m_File, aChunk, 3); + } + } + + io_write(m_File, aBuffer2, Size); +} + +void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size) +{ + if(m_LastKeyFrame == -1 || (Tick-m_LastKeyFrame) > SERVER_TICK_SPEED*5) + { + // write full tickmarker + WriteTickMarker(Tick, 1); + + // write snapshot + Write(CHUNKTYPE_SNAPSHOT, pData, Size); + + m_LastKeyFrame = Tick; + mem_copy(m_aLastSnapshotData, pData, Size); + } + else + { + // create delta, prepend tick + char aDeltaData[CSnapshot::MAX_SIZE+sizeof(int)]; + int DeltaSize; + + // write tickmarker + WriteTickMarker(Tick, 0); + + DeltaSize = m_pSnapshotDelta->CreateDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)pData, &aDeltaData); + if(DeltaSize) + { + // record delta + Write(CHUNKTYPE_DELTA, aDeltaData, DeltaSize); + mem_copy(m_aLastSnapshotData, pData, Size); + } + } +} + +void CDemoRecorder::RecordMessage(const void *pData, int Size) +{ + Write(CHUNKTYPE_MESSAGE, pData, Size); +} + +int CDemoRecorder::Stop() +{ + if(!m_File) + return -1; + + dbg_msg("demorec/record", "Stopped recording"); + io_close(m_File); + m_File = 0; + return 0; +} + + + +CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta) +{ + m_File = 0; + m_pKeyFrames = 0; + + m_pSnapshotDelta = pSnapshotDelta; + m_LastSnapshotDataSize = -1; +} + +void CDemoPlayer::SetListner(IListner *pListner) +{ + m_pListner = pListner; +} + + +int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick) +{ + unsigned char Chunk = 0; + + *pSize = 0; + *pType = 0; + + if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk)) + return -1; + + if(Chunk&CHUNKTYPEFLAG_TICKMARKER) + { + // decode tick marker + int Tickdelta = Chunk&(CHUNKMASK_TICK); + *pType = Chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); + + if(Tickdelta == 0) + { + unsigned char aTickdata[4]; + if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata)) + return -1; + *pTick = (aTickdata[0]<<24) | (aTickdata[1]<<16) | (aTickdata[2]<<8) | aTickdata[3]; + } + else + { + *pTick += Tickdelta; + } + + } + else + { + // decode normal chunk + *pType = (Chunk&CHUNKMASK_TYPE)>>5; + *pSize = Chunk&CHUNKMASK_SIZE; + + if(*pSize == 30) + { + unsigned char aSizedata[1]; + if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata)) + return -1; + *pSize = aSizedata[0]; + + } + else if(*pSize == 31) + { + unsigned char aSizedata[2]; + if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata)) + return -1; + *pSize = (aSizedata[1]<<8) | aSizedata[0]; + } + } + + return 0; +} + +void CDemoPlayer::ScanFile() +{ + long StartPos; + CHeap Heap; + CKeyFrameSearch *pFirstKey = 0; + CKeyFrameSearch *pCurrentKey = 0; + //DEMOREC_CHUNK chunk; + int ChunkSize, ChunkType, ChunkTick = 0; + int i; + + StartPos = io_tell(m_File); + m_Info.m_SeekablePoints = 0; + + while(1) + { + long CurrentPos = io_tell(m_File); + + if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick)) + break; + + // read the chunk + if(ChunkType&CHUNKTYPEFLAG_TICKMARKER) + { + if(ChunkType&CHUNKTICKFLAG_KEYFRAME) + { + CKeyFrameSearch *pKey; + + // save the position + pKey = (CKeyFrameSearch *)Heap.Allocate(sizeof(CKeyFrameSearch)); + pKey->m_Frame.m_Filepos = CurrentPos; + pKey->m_Frame.m_Tick = ChunkTick; + pKey->m_pNext = 0; + if(pCurrentKey) + pCurrentKey->m_pNext = pKey; + if(!pFirstKey) + pFirstKey = pKey; + pCurrentKey = pKey; + m_Info.m_SeekablePoints++; + } + + if(m_Info.m_Info.m_FirstTick == -1) + m_Info.m_Info.m_FirstTick = ChunkTick; + m_Info.m_Info.m_LastTick = ChunkTick; + } + else if(ChunkSize) + io_skip(m_File, ChunkSize); + + } + + // copy all the frames to an array instead for fast access + m_pKeyFrames = (CKeyFrame*)mem_alloc(m_Info.m_SeekablePoints*sizeof(CKeyFrame), 1); + for(pCurrentKey = pFirstKey, i = 0; pCurrentKey; pCurrentKey = pCurrentKey->m_pNext, i++) + m_pKeyFrames[i] = pCurrentKey->m_Frame; + + // destroy the temporary heap and seek back to the start + io_seek(m_File, StartPos, IOSEEK_START); +} + +void CDemoPlayer::DoTick() +{ + static char aCompresseddata[CSnapshot::MAX_SIZE]; + static char aDecompressed[CSnapshot::MAX_SIZE]; + static char aData[CSnapshot::MAX_SIZE]; + int ChunkType, ChunkTick, ChunkSize; + int DataSize; + int GotSnapshot = 0; + + // update ticks + m_Info.m_PreviousTick = m_Info.m_Info.m_CurrentTick; + m_Info.m_Info.m_CurrentTick = m_Info.m_NextTick; + ChunkTick = m_Info.m_Info.m_CurrentTick; + + while(1) + { + if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick)) + { + // stop on error or eof + dbg_msg("demorec", "end of file"); + Pause(); + break; + } + + // read the chunk + if(ChunkSize) + { + if(io_read(m_File, aCompresseddata, ChunkSize) != (unsigned)ChunkSize) + { + // stop on error or eof + dbg_msg("demorec", "error reading chunk"); + Stop(); + break; + } + + DataSize = CNetBase::Decompress(aCompresseddata, ChunkSize, aDecompressed, sizeof(aDecompressed)); + if(DataSize < 0) + { + // stop on error or eof + dbg_msg("demorec", "error during network decompression"); + Stop(); + break; + } + + DataSize = CVariableInt::Decompress(aDecompressed, DataSize, aData); + + if(DataSize < 0) + { + dbg_msg("demorec", "error during intpack decompression"); + Stop(); + break; + } + } + + if(ChunkType == CHUNKTYPE_DELTA) + { + // process delta snapshot + static char aNewsnap[CSnapshot::MAX_SIZE]; + + GotSnapshot = 1; + + DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)aNewsnap, aData, DataSize); + + if(DataSize >= 0) + { + if(m_pListner) + m_pListner->OnDemoPlayerSnapshot(aNewsnap, DataSize); + + m_LastSnapshotDataSize = DataSize; + mem_copy(m_aLastSnapshotData, aNewsnap, DataSize); + } + else + dbg_msg("demorec", "error duing unpacking of delta, err=%d", DataSize); + } + else if(ChunkType == CHUNKTYPE_SNAPSHOT) + { + // process full snapshot + GotSnapshot = 1; + + m_LastSnapshotDataSize = DataSize; + mem_copy(m_aLastSnapshotData, aData, DataSize); + if(m_pListner) + m_pListner->OnDemoPlayerSnapshot(aData, DataSize); + } + else + { + // if there were no snapshots in this tick, replay the last one + if(!GotSnapshot && m_pListner && m_LastSnapshotDataSize != -1) + { + GotSnapshot = 1; + m_pListner->OnDemoPlayerSnapshot(m_aLastSnapshotData, m_LastSnapshotDataSize); + } + + // check the remaining types + if(ChunkType&CHUNKTYPEFLAG_TICKMARKER) + { + m_Info.m_NextTick = ChunkTick; + break; + } + else if(ChunkType == CHUNKTYPE_MESSAGE) + { + if(m_pListner) + m_pListner->OnDemoPlayerMessage(aData, DataSize); + } + } + } +} + +void CDemoPlayer::Pause() +{ + m_Info.m_Info.m_Paused = 1; +} + +void CDemoPlayer::Unpause() +{ + if(m_Info.m_Info.m_Paused) + { + /*m_Info.start_tick = m_Info.current_tick; + m_Info.start_time = time_get();*/ + m_Info.m_Info.m_Paused = 0; + } +} + +int CDemoPlayer::Load(class IStorage *pStorage, const char *pFilename) +{ + m_File = pStorage->OpenFile(pFilename, IOFLAG_READ); + if(!m_File) + { + dbg_msg("demorec/playback", "could not open '%s'", pFilename); + return -1; + } + + // clear the playback info + mem_zero(&m_Info, sizeof(m_Info)); + m_Info.m_Info.m_FirstTick = -1; + m_Info.m_Info.m_LastTick = -1; + //m_Info.start_tick = -1; + m_Info.m_NextTick = -1; + m_Info.m_Info.m_CurrentTick = -1; + m_Info.m_PreviousTick = -1; + m_Info.m_Info.m_Speed = 1; + + m_LastSnapshotDataSize = -1; + + // read the header + io_read(m_File, &m_Info.m_Header, sizeof(m_Info.m_Header)); + if(mem_comp(m_Info.m_Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0) + { + dbg_msg("demorec/playback", "'%s' is not a demo file", pFilename); + io_close(m_File); + m_File = 0; + return -1; + } + + // scan the file for interessting points + ScanFile(); + + // ready for playback + return 0; +} + +int CDemoPlayer::NextFrame() +{ + DoTick(); + return IsPlaying(); +} + +int CDemoPlayer::Play() +{ + // fill in previous and next tick + while(m_Info.m_PreviousTick == -1 && IsPlaying()) + DoTick(); + + // set start info + /*m_Info.start_tick = m_Info.previous_tick; + m_Info.start_time = time_get();*/ + m_Info.m_CurrentTime = m_Info.m_PreviousTick*time_freq()/SERVER_TICK_SPEED; + m_Info.m_LastUpdate = time_get(); + return 0; +} + +int CDemoPlayer::SetPos(float Percent) +{ + int Keyframe; + int WantedTick; + if(!m_File) + return -1; + + // -5 because we have to have a current tick and previous tick when we do the playback + WantedTick = m_Info.m_Info.m_FirstTick + (int)((m_Info.m_Info.m_LastTick-m_Info.m_Info.m_FirstTick)*Percent) - 5; + + Keyframe = (int)(m_Info.m_SeekablePoints*Percent); + + if(Keyframe < 0 || Keyframe >= m_Info.m_SeekablePoints) + return -1; + + // get correct key frame + if(m_pKeyFrames[Keyframe].m_Tick < WantedTick) + while(Keyframe < m_Info.m_SeekablePoints-1 && m_pKeyFrames[Keyframe].m_Tick < WantedTick) + Keyframe++; + + while(Keyframe && m_pKeyFrames[Keyframe].m_Tick > WantedTick) + Keyframe--; + + // seek to the correct keyframe + io_seek(m_File, m_pKeyFrames[Keyframe].m_Filepos, IOSEEK_START); + + //m_Info.start_tick = -1; + m_Info.m_NextTick = -1; + m_Info.m_Info.m_CurrentTick = -1; + m_Info.m_PreviousTick = -1; + + // playback everything until we hit our tick + while(m_Info.m_PreviousTick < WantedTick) + DoTick(); + + Play(); + + return 0; +} + +void CDemoPlayer::SetSpeed(float Speed) +{ + m_Info.m_Info.m_Speed = Speed; +} + +int CDemoPlayer::Update() +{ + int64 Now = time_get(); + int64 Deltatime = Now-m_Info.m_LastUpdate; + m_Info.m_LastUpdate = Now; + + if(!IsPlaying()) + return 0; + + if(m_Info.m_Info.m_Paused) + { + + } + else + { + int64 Freq = time_freq(); + m_Info.m_CurrentTime += (int64)(Deltatime*(double)m_Info.m_Info.m_Speed); + + while(1) + { + int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED; + + // break if we are ready + if(CurtickStart > m_Info.m_CurrentTime) + break; + + // do one more tick + DoTick(); + + if(m_Info.m_Info.m_Paused) + return 0; + } + + // update intratick + { + int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED; + int64 PrevtickStart = (m_Info.m_PreviousTick)*Freq/SERVER_TICK_SPEED; + m_Info.m_IntraTick = (m_Info.m_CurrentTime - PrevtickStart) / (float)(CurtickStart-PrevtickStart); + m_Info.m_TickTime = (m_Info.m_CurrentTime - PrevtickStart) / (float)Freq; + } + + if(m_Info.m_Info.m_CurrentTick == m_Info.m_PreviousTick || + m_Info.m_Info.m_CurrentTick == m_Info.m_NextTick) + { + dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", + m_Info.m_PreviousTick, m_Info.m_Info.m_CurrentTick, m_Info.m_NextTick); + } + } + + return 0; +} + +int CDemoPlayer::Stop() +{ + if(!m_File) + return -1; + + dbg_msg("demorec/playback", "Stopped playback"); + io_close(m_File); + m_File = 0; + mem_free(m_pKeyFrames); + m_pKeyFrames = 0; + return 0; +} + + diff --git a/src/engine/shared/demorec.h b/src/engine/shared/demorec.h new file mode 100644 index 00000000..0936c30c --- /dev/null +++ b/src/engine/shared/demorec.h @@ -0,0 +1,117 @@ +#ifndef ENGINE_SHARED_DEMOREC_H +#define ENGINE_SHARED_DEMOREC_H + +#include <engine/demo.h> +#include "snapshot.h" + +struct CDemoHeader +{ + char m_aMarker[8]; + char m_aNetversion[64]; + char m_aMap[64]; + unsigned char m_aCrc[4]; + char m_aType[8]; +}; + +class CDemoRecorder +{ + IOHANDLE m_File; + int m_LastTickMarker; + int m_LastKeyFrame; + unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE]; + class CSnapshotDelta *m_pSnapshotDelta; + + void WriteTickMarker(int Tick, int Keyframe); + void Write(int Type, const void *pData, int Size); +public: + CDemoRecorder(class CSnapshotDelta *pSnapshotDelta); + + int Start(class IStorage *pStorage, const char *pFilename, const char *pNetversion, const char *pMap, int MapCrc, const char *pType); + int Stop(); + + void RecordSnapshot(int Tick, const void *pData, int Size); + void RecordMessage(const void *pData, int Size); + + bool IsRecording() const { return m_File != 0; } +}; + +class CDemoPlayer : public IDemoPlayer +{ +public: + class IListner + { + public: + virtual void OnDemoPlayerSnapshot(void *pData, int Size) = 0; + virtual void OnDemoPlayerMessage(void *pData, int Size) = 0; + }; + + struct CPlaybackInfo + { + CDemoHeader m_Header; + + IDemoPlayer::CInfo m_Info; + + int64 m_LastUpdate; + int64 m_CurrentTime; + + int m_SeekablePoints; + + int m_NextTick; + int m_PreviousTick; + + float m_IntraTick; + float m_TickTime; + }; + +private: + IListner *m_pListner; + + + // Playback + struct CKeyFrame + { + long m_Filepos; + int m_Tick; + }; + + struct CKeyFrameSearch + { + CKeyFrame m_Frame; + CKeyFrameSearch *m_pNext; + }; + + IOHANDLE m_File; + CKeyFrame *m_pKeyFrames; + + CPlaybackInfo m_Info; + unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE]; + int m_LastSnapshotDataSize; + class CSnapshotDelta *m_pSnapshotDelta; + + int ReadChunkHeader(int *pType, int *pSize, int *pTick); + void DoTick(); + void ScanFile(); + int NextFrame(); + +public: + + CDemoPlayer(class CSnapshotDelta *m_pSnapshotDelta); + + void SetListner(IListner *pListner); + + int Load(class IStorage *pStorage, const char *pFilename); + int Play(); + void Pause(); + void Unpause(); + int Stop(); + void SetSpeed(float Speed); + int SetPos(float Precent); + const CInfo *BaseInfo() const { return &m_Info.m_Info; } + + int Update(); + + const CPlaybackInfo *Info() const { return &m_Info; } + int IsPlaying() const { return m_File != 0; } +}; + +#endif diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp new file mode 100644 index 00000000..5cd50cf0 --- /dev/null +++ b/src/engine/shared/engine.cpp @@ -0,0 +1,76 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <base/system.h> + +#include <engine/shared/config.h> +#include <engine/shared/engine.h> +#include <engine/shared/network.h> +#include <engine/console.h> +#include "linereader.h" + +// compiled-in data-dir path +#define DATA_DIR "data" + +//static int engine_find_datadir(char *argv0); +/* +static void con_dbg_dumpmem(IConsole::IResult *result, void *user_data) +{ + mem_debug_dump(); +} + +static void con_dbg_lognetwork(IConsole::IResult *result, void *user_data) +{ + CNetBase::OpenLog("network_sent.dat", "network_recv.dat"); +}*/ + +/* +static char application_save_path[512] = {0}; +static char datadir[512] = {0}; + +const char *engine_savepath(const char *filename, char *buffer, int max) +{ + str_format(buffer, max, "%s/%s", application_save_path, filename); + return buffer; +}*/ + +void CEngine::Init(const char *pAppname) +{ + dbg_logger_stdout(); + dbg_logger_debugger(); + + // + dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); +#ifdef CONF_ARCH_ENDIAN_LITTLE + dbg_msg("engine", "arch is little endian"); +#elif defined(CONF_ARCH_ENDIAN_BIG) + dbg_msg("engine", "arch is big endian"); +#else + dbg_msg("engine", "unknown endian"); +#endif + + // init the network + net_init(); + CNetBase::Init(); + + m_HostLookupPool.Init(1); + + //MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory"); + //MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network"); + + // reset the config + //config_reset(); +} + + +static int HostLookupThread(void *pUser) +{ + CHostLookup *pLookup = (CHostLookup *)pUser; + net_host_lookup(pLookup->m_aHostname, &pLookup->m_Addr, NETTYPE_IPV4); + return 0; +} + +void CEngine::HostLookup(CHostLookup *pLookup, const char *pHostname) +{ + str_copy(pLookup->m_aHostname, pHostname, sizeof(pLookup->m_aHostname)); + m_HostLookupPool.Add(&pLookup->m_Job, HostLookupThread, pLookup); +} diff --git a/src/engine/shared/engine.h b/src/engine/shared/engine.h new file mode 100644 index 00000000..ad266ae4 --- /dev/null +++ b/src/engine/shared/engine.h @@ -0,0 +1,23 @@ +#ifndef ENGINE_SHARED_E_ENGINE_H +#define ENGINE_SHARED_E_ENGINE_H + +#include "jobs.h" + +class CHostLookup +{ +public: + CJob m_Job; + char m_aHostname[128]; + NETADDR m_Addr; +}; + +class CEngine +{ + class CJobPool m_HostLookupPool; + +public: + void Init(const char *pAppname); + void HostLookup(CHostLookup *pLookup, const char *pHostname); +}; + +#endif diff --git a/src/engine/shared/huffman.cpp b/src/engine/shared/huffman.cpp new file mode 100644 index 00000000..8b0c1cd0 --- /dev/null +++ b/src/engine/shared/huffman.cpp @@ -0,0 +1,276 @@ +#include <base/system.h> +#include "huffman.h" + +struct CHuffmanConstructNode +{ + unsigned short m_NodeId; + int m_Frequency; +}; + +void CHuffman::Setbits_r(CNode *pNode, int Bits, int Depth) +{ + if(pNode->m_aLeafs[1] != 0xffff) + Setbits_r(&m_aNodes[pNode->m_aLeafs[1]], Bits|(1<<Depth), Depth+1); + if(pNode->m_aLeafs[0] != 0xffff) + Setbits_r(&m_aNodes[pNode->m_aLeafs[0]], Bits, Depth+1); + + if(pNode->m_NumBits) + { + pNode->m_Bits = Bits; + pNode->m_NumBits = Depth; + } +} + +// TODO: this should be something faster, but it's enough for now +static void BubbleSort(CHuffmanConstructNode **ppList, int Size) +{ + int Changed = 1; + CHuffmanConstructNode *pTemp; + + while(Changed) + { + Changed = 0; + for(int i = 0; i < Size-1; i++) + { + if(ppList[i]->m_Frequency < ppList[i+1]->m_Frequency) + { + pTemp = ppList[i]; + ppList[i] = ppList[i+1]; + ppList[i+1] = pTemp; + Changed = 1; + } + } + } +} + +void CHuffman::ConstructTree(const unsigned *pFrequencies) +{ + CHuffmanConstructNode aNodesLeftStorage[HUFFMAN_MAX_SYMBOLS]; + CHuffmanConstructNode *apNodesLeft[HUFFMAN_MAX_SYMBOLS]; + int NumNodesLeft = HUFFMAN_MAX_SYMBOLS; + + // add the symbols + for(int i = 0; i < HUFFMAN_MAX_SYMBOLS; i++) + { + m_aNodes[i].m_NumBits = -1; + m_aNodes[i].m_Symbol = i; + m_aNodes[i].m_aLeafs[0] = -1; + m_aNodes[i].m_aLeafs[1] = -1; + + if(i == HUFFMAN_EOF_SYMBOL) + aNodesLeftStorage[i].m_Frequency = 1; + else + aNodesLeftStorage[i].m_Frequency = pFrequencies[i]; + aNodesLeftStorage[i].m_NodeId = i; + apNodesLeft[i] = &aNodesLeftStorage[i]; + + } + + m_NumNodes = HUFFMAN_MAX_SYMBOLS; + + // construct the table + while(NumNodesLeft > 1) + { + // we can't rely on stdlib's qsort for this, it can generate different results on different implementations + BubbleSort(apNodesLeft, NumNodesLeft); + + m_aNodes[m_NumNodes].m_NumBits = 0; + m_aNodes[m_NumNodes].m_aLeafs[0] = apNodesLeft[NumNodesLeft-1]->m_NodeId; + m_aNodes[m_NumNodes].m_aLeafs[1] = apNodesLeft[NumNodesLeft-2]->m_NodeId; + apNodesLeft[NumNodesLeft-2]->m_NodeId = m_NumNodes; + apNodesLeft[NumNodesLeft-2]->m_Frequency = apNodesLeft[NumNodesLeft-1]->m_Frequency + apNodesLeft[NumNodesLeft-2]->m_Frequency; + + m_NumNodes++; + NumNodesLeft--; + } + + // set start node + m_pStartNode = &m_aNodes[m_NumNodes-1]; + + // build symbol bits + Setbits_r(m_pStartNode, 0, 0); +} + +void CHuffman::Init(const unsigned *pFrequencies) +{ + int i; + + // make sure to cleanout every thing + mem_zero(this, sizeof(*this)); + + // construct the tree + ConstructTree(pFrequencies); + + // build decode LUT + for(i = 0; i < HUFFMAN_LUTSIZE; i++) + { + unsigned Bits = i; + int k; + CNode *pNode = m_pStartNode; + for(k = 0; k < HUFFMAN_LUTBITS; k++) + { + pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]]; + Bits >>= 1; + + if(!pNode) + break; + + if(pNode->m_NumBits) + { + m_apDecodeLut[i] = pNode; + break; + } + } + + if(k == HUFFMAN_LUTBITS) + m_apDecodeLut[i] = pNode; + } + +} + +//*************************************************************** +int CHuffman::Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize) +{ + // this macro loads a symbol for a byte into bits and bitcount +#define HUFFMAN_MACRO_LOADSYMBOL(Sym) \ + Bits |= m_aNodes[Sym].m_Bits << Bitcount; \ + Bitcount += m_aNodes[Sym].m_NumBits; + + // this macro writes the symbol stored in bits and bitcount to the dst pointer +#define HUFFMAN_MACRO_WRITE() \ + while(Bitcount >= 8) \ + { \ + *pDst++ = (unsigned char)(Bits&0xff); \ + if(pDst == pDstEnd) \ + return -1; \ + Bits >>= 8; \ + Bitcount -= 8; \ + } + + // setup buffer pointers + const unsigned char *pSrc = (const unsigned char *)pInput; + const unsigned char *pSrcEnd = pSrc + InputSize; + unsigned char *pDst = (unsigned char *)pOutput; + unsigned char *pDstEnd = pDst + OutputSize; + + // symbol variables + unsigned Bits = 0; + unsigned Bitcount = 0; + + // make sure that we have data that we want to compress + if(InputSize) + { + // {A} load the first symbol + int Symbol = *pSrc++; + + while(pSrc != pSrcEnd) + { + // {B} load the symbol + HUFFMAN_MACRO_LOADSYMBOL(Symbol) + + // {C} fetch next symbol, this is done here because it will reduce dependency in the code + Symbol = *pSrc++; + + // {B} write the symbol loaded at + HUFFMAN_MACRO_WRITE() + } + + // write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer + HUFFMAN_MACRO_LOADSYMBOL(Symbol) + HUFFMAN_MACRO_WRITE() + } + + // write EOF symbol + HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL) + HUFFMAN_MACRO_WRITE() + + // write out the last bits + *pDst++ = Bits; + + // return the size of the output + return (int)(pDst - (const unsigned char *)pOutput); + + // remove macros +#undef HUFFMAN_MACRO_LOADSYMBOL +#undef HUFFMAN_MACRO_WRITE +} + +//*************************************************************** +int CHuffman::Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize) +{ + // setup buffer pointers + unsigned char *pDst = (unsigned char *)pOutput; + unsigned char *pSrc = (unsigned char *)pInput; + unsigned char *pDstEnd = pDst + OutputSize; + unsigned char *pSrcEnd = pSrc + InputSize; + + unsigned Bits = 0; + unsigned Bitcount = 0; + + CNode *pEof = &m_aNodes[HUFFMAN_EOF_SYMBOL]; + CNode *pNode = 0; + + while(1) + { + // {A} try to load a node now, this will reduce dependency at location {D} + pNode = 0; + if(Bitcount >= HUFFMAN_LUTBITS) + pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK]; + + // {B} fill with new bits + while(Bitcount < 24 && pSrc != pSrcEnd) + { + Bits |= (*pSrc++) << Bitcount; + Bitcount += 8; + } + + // {C} load symbol now if we didn't that earlier at location {A} + if(!pNode) + pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK]; + + // {D} check if we hit a symbol already + if(pNode->m_NumBits) + { + // remove the bits for that symbol + Bits >>= pNode->m_NumBits; + Bitcount -= pNode->m_NumBits; + } + else + { + // remove the bits that the lut checked up for us + Bits >>= HUFFMAN_LUTBITS; + Bitcount -= HUFFMAN_LUTBITS; + + // walk the tree bit by bit + while(1) + { + // traverse tree + pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]]; + + // remove bit + Bitcount--; + Bits >>= 1; + + // check if we hit a symbol + if(pNode->m_NumBits) + break; + + // no more bits, decoding error + if(Bitcount == 0) + return -1; + } + } + + // check for eof + if(pNode == pEof) + break; + + // output character + if(pDst == pDstEnd) + return -1; + *pDst++ = pNode->m_Symbol; + } + + // return the size of the decompressed buffer + return (int)(pDst - (const unsigned char *)pOutput); +} diff --git a/src/engine/shared/huffman.h b/src/engine/shared/huffman.h new file mode 100644 index 00000000..5aa56c8f --- /dev/null +++ b/src/engine/shared/huffman.h @@ -0,0 +1,89 @@ +#ifndef ENGINE_SHARED_HUFFMAN_H +#define ENGINE_SHARED_HUFFMAN_H + + + +class CHuffman +{ + enum + { + HUFFMAN_EOF_SYMBOL = 256, + + HUFFMAN_MAX_SYMBOLS=HUFFMAN_EOF_SYMBOL+1, + HUFFMAN_MAX_NODES=HUFFMAN_MAX_SYMBOLS*2-1, + + HUFFMAN_LUTBITS = 10, + HUFFMAN_LUTSIZE = (1<<HUFFMAN_LUTBITS), + HUFFMAN_LUTMASK = (HUFFMAN_LUTSIZE-1) + }; + + struct CNode + { + // symbol + unsigned m_Bits; + unsigned m_NumBits; + + // don't use pointers for this. shorts are smaller so we can fit more data into the cache + unsigned short m_aLeafs[2]; + + // what the symbol represents + unsigned char m_Symbol; + }; + + CNode m_aNodes[HUFFMAN_MAX_NODES]; + CNode *m_apDecodeLut[HUFFMAN_LUTSIZE]; + CNode *m_pStartNode; + int m_NumNodes; + + void Setbits_r(CNode *pNode, int Bits, int Depth); + void ConstructTree(const unsigned *pFrequencies); + +public: + /* + Function: huffman_init + Inits the compressor/decompressor. + + Parameters: + huff - Pointer to the state to init + frequencies - A pointer to an array of 256 entries of the frequencies of the bytes + + Remarks: + - Does no allocation what so ever. + - You don't have to call any cleanup functions when you are done with it + */ + void Init(const unsigned *pFrequencies); + + /* + Function: huffman_compress + Compresses a buffer and outputs a compressed buffer. + + Parameters: + huff - Pointer to the huffman state + input - Buffer to compress + input_size - Size of the buffer to compress + output - Buffer to put the compressed data into + output_size - Size of the output buffer + + Returns: + Returns the size of the compressed data. Negative value on failure. + */ + int Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize); + + /* + Function: huffman_decompress + Decompresses a buffer + + Parameters: + huff - Pointer to the huffman state + input - Buffer to decompress + input_size - Size of the buffer to decompress + output - Buffer to put the uncompressed data into + output_size - Size of the output buffer + + Returns: + Returns the size of the uncompressed data. Negative value on failure. + */ + int Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize); + +}; +#endif // __HUFFMAN_HEADER__ diff --git a/src/engine/shared/jobs.cpp b/src/engine/shared/jobs.cpp new file mode 100644 index 00000000..83d7290b --- /dev/null +++ b/src/engine/shared/jobs.cpp @@ -0,0 +1,74 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include "jobs.h" + +CJobPool::CJobPool() +{ + // empty the pool + m_Lock = lock_create(); + m_pFirstJob = 0; + m_pLastJob = 0; +} + +void CJobPool::WorkerThread(void *pUser) +{ + CJobPool *pPool = (CJobPool *)pUser; + + while(1) + { + CJob *pJob = 0; + + // fetch job from queue + lock_wait(pPool->m_Lock); + if(pPool->m_pFirstJob) + { + pJob = pPool->m_pFirstJob; + pPool->m_pFirstJob = pPool->m_pFirstJob->m_pNext; + if(pPool->m_pFirstJob) + pPool->m_pFirstJob->m_pPrev = 0; + else + pPool->m_pLastJob = 0; + } + lock_release(pPool->m_Lock); + + // do the job if we have one + if(pJob) + { + pJob->m_Status = CJob::STATE_RUNNING; + pJob->m_Result = pJob->m_pfnFunc(pJob->m_pFuncData); + pJob->m_Status = CJob::STATE_DONE; + } + else + thread_sleep(10); + } + +} + +int CJobPool::Init(int NumThreads) +{ + // start threads + for(int i = 0; i < NumThreads; i++) + thread_create(WorkerThread, this); + return 0; +} + +int CJobPool::Add(CJob *pJob, JOBFUNC pfnFunc, void *pData) +{ + mem_zero(pJob, sizeof(CJob)); + pJob->m_pfnFunc = pfnFunc; + pJob->m_pFuncData = pData; + + lock_wait(m_Lock); + + // add job to queue + pJob->m_pPrev = m_pLastJob; + if(m_pLastJob) + m_pLastJob->m_pNext = pJob; + m_pLastJob = pJob; + if(!m_pFirstJob) + m_pFirstJob = pJob; + + lock_release(m_Lock); + return 0; +} + diff --git a/src/engine/shared/jobs.h b/src/engine/shared/jobs.h new file mode 100644 index 00000000..d04815b0 --- /dev/null +++ b/src/engine/shared/jobs.h @@ -0,0 +1,51 @@ +#ifndef ENGINE_SHARED_JOBS_H +#define ENGINE_SHARED_JOBS_H +typedef int (*JOBFUNC)(void *pData); + +class CJobPool; + +class CJob +{ + friend class CJobPool; + + CJobPool *m_pPool; + CJob *m_pPrev; + CJob *m_pNext; + + volatile int m_Status; + volatile int m_Result; + + JOBFUNC m_pfnFunc; + void *m_pFuncData; +public: + CJob() + { + m_Status = STATE_DONE; + m_pFuncData = 0; + } + + enum + { + STATE_PENDING=0, + STATE_RUNNING, + STATE_DONE + }; + + int Status() const { return m_Status; } +}; + +class CJobPool +{ + LOCK m_Lock; + CJob *m_pFirstJob; + CJob *m_pLastJob; + + static void WorkerThread(void *pUser); + +public: + CJobPool(); + + int Init(int NumThreads); + int Add(CJob *pJob, JOBFUNC pfnFunc, void *pData); +}; +#endif diff --git a/src/engine/shared/kernel.cpp b/src/engine/shared/kernel.cpp new file mode 100644 index 00000000..9f6850ba --- /dev/null +++ b/src/engine/shared/kernel.cpp @@ -0,0 +1,93 @@ +#include <base/system.h> +#include <engine/kernel.h> + +class CKernel : public IKernel +{ + enum + { + MAX_INTERFACES=32, + }; + + class CInterfaceInfo + { + public: + CInterfaceInfo() + { + m_aName[0] = 0; + m_pInterface = 0x0; + } + + char m_aName[64]; + IInterface *m_pInterface; + }; + + CInterfaceInfo m_aInterfaces[MAX_INTERFACES]; + int m_NumInterfaces; + + CInterfaceInfo *FindInterfaceInfo(const char *pName) + { + for(int i = 0; i < m_NumInterfaces; i++) + { + if(str_comp(pName, m_aInterfaces[i].m_aName) == 0) + return &m_aInterfaces[i]; + } + return 0x0; + } + +public: + + CKernel() + { + m_NumInterfaces = 0; + } + + + virtual bool RegisterInterfaceImpl(const char *pName, IInterface *pInterface) + { + // TODO: More error checks here + if(m_NumInterfaces == MAX_INTERFACES) + { + dbg_msg("kernel", "ERROR: couldn't register interface '%s'. maximum of interfaces reached", pName); + return false; + } + + if(FindInterfaceInfo(pName) != 0) + { + dbg_msg("kernel", "ERROR: couldn't register interface '%s'. interface already exists"); + return false; + } + + pInterface->m_pKernel = this; + m_aInterfaces[m_NumInterfaces].m_pInterface = pInterface; + str_copy(m_aInterfaces[m_NumInterfaces].m_aName, pName, sizeof(m_aInterfaces[m_NumInterfaces].m_aName)); + m_NumInterfaces++; + + return true; + } + + virtual bool ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) + { + if(FindInterfaceInfo(pName) == 0) + { + dbg_msg("kernel", "ERROR: couldn't reregister interface '%s'. interface doesn't exist"); + return false; + } + + pInterface->m_pKernel = this; + + return true; + } + + virtual IInterface *RequestInterfaceImpl(const char *pName) + { + CInterfaceInfo *pInfo = FindInterfaceInfo(pName); + if(!pInfo) + { + dbg_msg("kernel", "failed to find interface with the name '%s'", pName); + return 0; + } + return pInfo->m_pInterface; + } +}; + +IKernel *IKernel::Create() { return new CKernel; } diff --git a/src/engine/shared/linereader.cpp b/src/engine/shared/linereader.cpp new file mode 100644 index 00000000..b3de233b --- /dev/null +++ b/src/engine/shared/linereader.cpp @@ -0,0 +1,62 @@ +#include "linereader.h" + +void CLineReader::Init(IOHANDLE io) +{ + m_BufferMaxSize = 4*1024; + m_BufferSize = 0; + m_BufferPos = 0; + m_IO = io; +} + +char *CLineReader::Get() +{ + unsigned LineStart = m_BufferPos; + + while(1) + { + if(m_BufferPos >= m_BufferSize) + { + // fetch more + + // move the remaining part to the front + unsigned Read; + unsigned Left = m_BufferSize - LineStart; + + if(LineStart > m_BufferSize) + Left = 0; + if(Left) + mem_move(m_aBuffer, &m_aBuffer[LineStart], Left); + m_BufferPos = Left; + + // fill the buffer + Read = io_read(m_IO, &m_aBuffer[m_BufferPos], m_BufferMaxSize-m_BufferPos); + m_BufferSize = Left + Read; + LineStart = 0; + + if(!Read) + { + if(Left) + { + m_aBuffer[Left] = 0; // return the last line + m_BufferPos = Left; + m_BufferSize = Left; + return m_aBuffer; + } + else + return 0x0; // we are done! + } + } + else + { + if(m_aBuffer[m_BufferPos] == '\n' || m_aBuffer[m_BufferPos] == '\r') + { + // line found + m_aBuffer[m_BufferPos] = 0; + m_BufferPos++; + return &m_aBuffer[LineStart]; + } + else + m_BufferPos++; + } + } +} diff --git a/src/engine/shared/linereader.h b/src/engine/shared/linereader.h new file mode 100644 index 00000000..f28d42f6 --- /dev/null +++ b/src/engine/shared/linereader.h @@ -0,0 +1,17 @@ +#ifndef ENGINE_SHARED_LINEREADER_H +#define ENGINE_SHARED_LINEREADER_H +#include <base/system.h> + +// buffered stream for reading lines, should perhaps be something smaller +class CLineReader +{ + char m_aBuffer[4*1024]; + unsigned m_BufferPos; + unsigned m_BufferSize; + unsigned m_BufferMaxSize; + IOHANDLE m_IO; +public: + void Init(IOHANDLE IoHandle); + char *Get(); +}; +#endif diff --git a/src/engine/shared/map.cpp b/src/engine/shared/map.cpp new file mode 100644 index 00000000..505d18e9 --- /dev/null +++ b/src/engine/shared/map.cpp @@ -0,0 +1,45 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/map.h> +#include <engine/storage.h> +#include "datafile.h" + +class CMap : public IEngineMap +{ + CDataFileReader m_DataFile; +public: + CMap() {} + + virtual void *GetData(int Index) { return m_DataFile.GetData(Index); } + virtual void *GetDataSwapped(int Index) { return m_DataFile.GetDataSwapped(Index); } + virtual void UnloadData(int Index) { m_DataFile.UnloadData(Index); } + virtual void *GetItem(int Index, int *pType, int *pId) { return m_DataFile.GetItem(Index, pType, pId); } + virtual void GetType(int Type, int *pStart, int *pNum) { m_DataFile.GetType(Type, pStart, pNum); } + virtual void *FindItem(int Type, int Id) { return m_DataFile.FindItem(Type, Id); } + virtual int NumItems() { return m_DataFile.NumItems(); } + + virtual void Unload() + { + m_DataFile.Close(); + } + + virtual bool Load(const char *pMapName) + { + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return false; + return m_DataFile.Open(pStorage, pMapName); + } + + virtual bool IsLoaded() + { + return m_DataFile.IsOpen(); + } + + virtual unsigned Crc() + { + return m_DataFile.Crc(); + } +}; + +extern IEngineMap *CreateEngineMap() { return new CMap; } diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp new file mode 100644 index 00000000..beade5bf --- /dev/null +++ b/src/engine/shared/masterserver.cpp @@ -0,0 +1,187 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <stdio.h> + +#include <base/system.h> +#include <engine/masterserver.h> +#include <engine/storage.h> +#include "engine.h" +#include "linereader.h" + +class CMasterServer : public IEngineMasterServer +{ +public: + // master server functions + struct CMasterInfo + { + char m_aHostname[128]; + NETADDR m_Addr; + + CHostLookup m_Lookup; + } ; + + CMasterInfo m_aMasterServers[MAX_MASTERSERVERS]; + int m_NeedsUpdate; + CEngine *m_pEngine; + + CMasterServer() + { + SetDefault(); + m_NeedsUpdate = -1; + m_pEngine = 0; + } + + virtual int RefreshAddresses() + { + int i; + + if(m_NeedsUpdate != -1) + return 0; + + dbg_msg("engine/mastersrv", "refreshing master server addresses"); + + // add lookup jobs + for(i = 0; i < MAX_MASTERSERVERS; i++) + m_pEngine->HostLookup(&m_aMasterServers[i].m_Lookup, m_aMasterServers[i].m_aHostname); + + m_NeedsUpdate = 1; + return 0; + } + + virtual void Update() + { + // check if we need to update + if(m_NeedsUpdate != 1) + return; + m_NeedsUpdate = 0; + + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + if(m_aMasterServers[i].m_Lookup.m_Job.Status() != CJob::STATE_DONE) + m_NeedsUpdate = 1; + else + { + m_aMasterServers[i].m_Addr = m_aMasterServers[i].m_Lookup.m_Addr; + m_aMasterServers[i].m_Addr.port = 8300; + } + } + + if(!m_NeedsUpdate) + { + dbg_msg("engine/mastersrv", "saving addresses"); + Save(); + } + } + + virtual int IsRefreshing() + { + return m_NeedsUpdate; + } + + virtual NETADDR GetAddr(int Index) + { + return m_aMasterServers[Index].m_Addr; + } + + virtual const char *GetName(int Index) + { + return m_aMasterServers[Index].m_aHostname; + } + + virtual void DumpServers() + { + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i, + m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1], + m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]); + } + } + + virtual void Init(class CEngine *pEngine) + { + m_pEngine = pEngine; + } + + virtual void SetDefault() + { + mem_zero(m_aMasterServers, sizeof(m_aMasterServers)); + for(int i = 0; i < MAX_MASTERSERVERS; i++) + str_format(m_aMasterServers[i].m_aHostname, sizeof(m_aMasterServers[i].m_aHostname), "master%d.teeworlds.com", i+1); + } + + virtual int Load() + { + CLineReader LineReader; + IOHANDLE File; + int Count = 0; + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return -1; + + // try to open file + File = pStorage->OpenFile("masters.cfg", IOFLAG_READ); + if(!File) + return -1; + + LineReader.Init(File); + while(1) + { + CMasterInfo Info = {{0}}; + int aIp[4]; + const char *pLine = LineReader.Get(); + if(!pLine) + break; + + // parse line + if(sscanf(pLine, "%s %d.%d.%d.%d", Info.m_aHostname, &aIp[0], &aIp[1], &aIp[2], &aIp[3]) == 5) + { + Info.m_Addr.ip[0] = (unsigned char)aIp[0]; + Info.m_Addr.ip[1] = (unsigned char)aIp[1]; + Info.m_Addr.ip[2] = (unsigned char)aIp[2]; + Info.m_Addr.ip[3] = (unsigned char)aIp[3]; + Info.m_Addr.port = 8300; + if(Count != MAX_MASTERSERVERS) + { + m_aMasterServers[Count] = Info; + Count++; + } + //else + // dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", pLine, MAX_MASTERSERVERS); + } + //else + // dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", pLine); + } + + io_close(File); + return 0; + } + + virtual int Save() + { + IOHANDLE File; + + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return -1; + + // try to open file + File = pStorage->OpenFile("masters.cfg", IOFLAG_WRITE); + if(!File) + return -1; + + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "%s %d.%d.%d.%d\n", m_aMasterServers[i].m_aHostname, + m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1], + m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]); + + io_write(File, aBuf, str_length(aBuf)); + } + + io_close(File); + return 0; + } +}; + +IEngineMasterServer *CreateEngineMasterServer() { return new CMasterServer; } diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp new file mode 100644 index 00000000..6661962b --- /dev/null +++ b/src/engine/shared/memheap.cpp @@ -0,0 +1,96 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include "memheap.h" + +static const int CHUNK_SIZE = 1024*64; + +// allocates a new chunk to be used +void CHeap::NewChunk() +{ + CChunk *pChunk; + char *pMem; + + // allocate memory + pMem = (char*)mem_alloc(sizeof(CChunk)+CHUNK_SIZE, 1); + if(!pMem) + return; + + // the chunk structure is located in the begining of the chunk + // init it and return the chunk + pChunk = (CChunk*)pMem; + pChunk->m_pMemory = (char*)(pChunk+1); + pChunk->m_pCurrent = pChunk->m_pMemory; + pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE; + pChunk->m_pNext = (CChunk *)0x0; + + pChunk->m_pNext = m_pCurrent; + m_pCurrent = pChunk; +} + +//**************** +void *CHeap::AllocateFromChunk(unsigned int Size) +{ + char *pMem; + + // check if we need can fit the allocation + if(m_pCurrent->m_pCurrent + Size > m_pCurrent->m_pEnd) + return (void*)0x0; + + // get memory and move the pointer forward + pMem = m_pCurrent->m_pCurrent; + m_pCurrent->m_pCurrent += Size; + return pMem; +} + +// creates a heap +CHeap::CHeap() +{ + m_pCurrent = 0x0; + Reset(); +} + +CHeap::~CHeap() +{ + Clear(); +} + +void CHeap::Reset() +{ + Clear(); + NewChunk(); +} + +// destroys the heap +void CHeap::Clear() +{ + CChunk *pChunk = m_pCurrent; + CChunk *pNext; + + while(pChunk) + { + pNext = pChunk->m_pNext; + mem_free(pChunk); + pChunk = pNext; + } + + m_pCurrent = 0x0; +} + +// +void *CHeap::Allocate(unsigned Size) +{ + char *pMem; + + // try to allocate from current chunk + pMem = (char *)AllocateFromChunk(Size); + if(!pMem) + { + // allocate new chunk and add it to the heap + NewChunk(); + + // try to allocate again + pMem = (char *)AllocateFromChunk(Size); + } + + return pMem; +} diff --git a/src/engine/shared/memheap.h b/src/engine/shared/memheap.h new file mode 100644 index 00000000..706395f2 --- /dev/null +++ b/src/engine/shared/memheap.h @@ -0,0 +1,32 @@ +#ifndef ENGINE_SHARED_MEMHEAP_H +#define ENGINE_SHARED_MEMHEAP_H +class CHeap +{ + struct CChunk + { + char *m_pMemory; + char *m_pCurrent; + char *m_pEnd; + CChunk *m_pNext; + }; + + enum + { + // how large each chunk should be + CHUNK_SIZE = 1025*64, + }; + + CChunk *m_pCurrent; + + + void Clear(); + void NewChunk(); + void *AllocateFromChunk(unsigned int Size); + +public: + CHeap(); + ~CHeap(); + void Reset(); + void *Allocate(unsigned Size); +}; +#endif diff --git a/src/engine/shared/message.h b/src/engine/shared/message.h new file mode 100644 index 00000000..4e67a8e1 --- /dev/null +++ b/src/engine/shared/message.h @@ -0,0 +1,9 @@ +#ifndef ENGINE_SHARED_MESSAGE_H +#define ENGINE_SHARED_MESSAGE_H +class CMessage +{ +public: + virtual bool Pack(void *pData, unsigned MaxDataSize); + virtual bool Unpack(const void *pData, unsigned DataSize); +}; +#endif diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp new file mode 100644 index 00000000..0305ffff --- /dev/null +++ b/src/engine/shared/network.cpp @@ -0,0 +1,347 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> + + +#include "config.h" +#include "engine.h" +#include "network.h" +#include "huffman.h" + +void CNetRecvUnpacker::Clear() +{ + m_Valid = false; +} + +void CNetRecvUnpacker::Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID) +{ + m_Addr = *pAddr; + m_pConnection = pConnection; + m_ClientID = ClientID; + m_CurrentChunk = 0; + m_Valid = true; +} + +// TODO: rename this function +int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) +{ + CNetChunkHeader Header; + unsigned char *pEnd = m_Data.m_aChunkData + m_Data.m_DataSize; + + while(1) + { + unsigned char *pData = m_Data.m_aChunkData; + + // check for old data to unpack + if(!m_Valid || m_CurrentChunk >= m_Data.m_NumChunks) + { + Clear(); + return 0; + } + + // TODO: add checking here so we don't read too far + for(int i = 0; i < m_CurrentChunk; i++) + { + pData = Header.Unpack(pData); + pData += Header.m_Size; + } + + // unpack the header + pData = Header.Unpack(pData); + m_CurrentChunk++; + + if(pData+Header.m_Size > pEnd) + { + Clear(); + return 0; + } + + // handle sequence stuff + if(m_pConnection && (Header.m_Flags&NET_CHUNKFLAG_VITAL)) + { + if(Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE) + { + // in sequence + m_pConnection->m_Ack = (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE; + } + else + { + // old packet that we already got + if(CNetBase::IsSeqInBackroom(Header.m_Sequence, m_pConnection->m_Ack)) + continue; + + // out of sequence, request resend + if(g_Config.m_Debug) + dbg_msg("conn", "asking for resend %d %d", Header.m_Sequence, (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE); + m_pConnection->SignalResend(); + continue; // take the next chunk in the packet + } + } + + // fill in the info + pChunk->m_ClientID = m_ClientID; + pChunk->m_Address = m_Addr; + pChunk->m_Flags = 0; + pChunk->m_DataSize = Header.m_Size; + pChunk->m_pData = pData; + return 1; + } +} + +// packs the data tight and sends it +void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize) +{ + unsigned char aBuffer[NET_MAX_PACKETSIZE]; + aBuffer[0] = 0xff; + aBuffer[1] = 0xff; + aBuffer[2] = 0xff; + aBuffer[3] = 0xff; + aBuffer[4] = 0xff; + aBuffer[5] = 0xff; + mem_copy(&aBuffer[6], pData, DataSize); + net_udp_send(Socket, pAddr, aBuffer, 6+DataSize); +} + +void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket) +{ + unsigned char aBuffer[NET_MAX_PACKETSIZE]; + int CompressedSize = -1; + int FinalSize = -1; + + // log the data + if(ms_DataLogSent) + { + int Type = 1; + io_write(ms_DataLogSent, &Type, sizeof(Type)); + io_write(ms_DataLogSent, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); + io_write(ms_DataLogSent, &pPacket->m_aChunkData, pPacket->m_DataSize); + io_flush(ms_DataLogSent); + } + + // compress + CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); + + // check if the compression was enabled, successful and good enough + if(CompressedSize > 0 && CompressedSize < pPacket->m_DataSize) + { + FinalSize = CompressedSize; + pPacket->m_Flags |= NET_PACKETFLAG_COMPRESSION; + } + else + { + // use uncompressed data + FinalSize = pPacket->m_DataSize; + mem_copy(&aBuffer[3], pPacket->m_aChunkData, pPacket->m_DataSize); + pPacket->m_Flags &= ~NET_PACKETFLAG_COMPRESSION; + } + + // set header and send the packet if all things are good + if(FinalSize >= 0) + { + FinalSize += NET_PACKETHEADERSIZE; + aBuffer[0] = ((pPacket->m_Flags<<4)&0xf0)|((pPacket->m_Ack>>8)&0xf); + aBuffer[1] = pPacket->m_Ack&0xff; + aBuffer[2] = pPacket->m_NumChunks; + net_udp_send(Socket, pAddr, aBuffer, FinalSize); + + // log raw socket data + if(ms_DataLogSent) + { + int Type = 0; + io_write(ms_DataLogSent, &Type, sizeof(Type)); + io_write(ms_DataLogSent, &FinalSize, sizeof(FinalSize)); + io_write(ms_DataLogSent, aBuffer, FinalSize); + io_flush(ms_DataLogSent); + } + } +} + +// TODO: rename this function +int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket) +{ + // check the size + if(Size < NET_PACKETHEADERSIZE || Size > NET_MAX_PACKETSIZE) + { + dbg_msg("", "packet too small, %d", Size); + return -1; + } + + // log the data + if(ms_DataLogRecv) + { + int Type = 0; + io_write(ms_DataLogRecv, &Type, sizeof(Type)); + io_write(ms_DataLogRecv, &Size, sizeof(Size)); + io_write(ms_DataLogRecv, pBuffer, Size); + io_flush(ms_DataLogRecv); + } + + // read the packet + pPacket->m_Flags = pBuffer[0]>>4; + pPacket->m_Ack = ((pBuffer[0]&0xf)<<8) | pBuffer[1]; + pPacket->m_NumChunks = pBuffer[2]; + pPacket->m_DataSize = Size - NET_PACKETHEADERSIZE; + + if(pPacket->m_Flags&NET_PACKETFLAG_CONNLESS) + { + if(Size < 6) + { + dbg_msg("", "connection less packet too small, %d", Size); + return -1; + } + + pPacket->m_Flags = NET_PACKETFLAG_CONNLESS; + pPacket->m_Ack = 0; + pPacket->m_NumChunks = 0; + pPacket->m_DataSize = Size - 6; + mem_copy(pPacket->m_aChunkData, &pBuffer[6], pPacket->m_DataSize); + } + else + { + if(pPacket->m_Flags&NET_PACKETFLAG_COMPRESSION) + pPacket->m_DataSize = ms_Huffman.Decompress(&pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData)); + else + mem_copy(pPacket->m_aChunkData, &pBuffer[3], pPacket->m_DataSize); + } + + // check for errors + if(pPacket->m_DataSize < 0) + { + if(g_Config.m_Debug) + dbg_msg("network", "error during packet decoding"); + return -1; + } + + // log the data + if(ms_DataLogRecv) + { + int Type = 1; + io_write(ms_DataLogRecv, &Type, sizeof(Type)); + io_write(ms_DataLogRecv, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); + io_write(ms_DataLogRecv, pPacket->m_aChunkData, pPacket->m_DataSize); + io_flush(ms_DataLogRecv); + } + + // return success + return 0; +} + + +void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize) +{ + CNetPacketConstruct Construct; + Construct.m_Flags = NET_PACKETFLAG_CONTROL; + Construct.m_Ack = Ack; + Construct.m_NumChunks = 0; + Construct.m_DataSize = 1+ExtraSize; + Construct.m_aChunkData[0] = ControlMsg; + mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize); + + // send the control message + CNetBase::SendPacket(Socket, pAddr, &Construct); +} + + + +unsigned char *CNetChunkHeader::Pack(unsigned char *pData) +{ + pData[0] = ((m_Flags&3)<<6)|((m_Size>>4)&0x3f); + pData[1] = (m_Size&0xf); + if(m_Flags&NET_CHUNKFLAG_VITAL) + { + pData[1] |= (m_Sequence>>2)&0xf0; + pData[2] = m_Sequence&0xff; + return pData + 3; + } + return pData + 2; +} + +unsigned char *CNetChunkHeader::Unpack(unsigned char *pData) +{ + m_Flags = (pData[0]>>6)&3; + m_Size = ((pData[0]&0x3f)<<4) | (pData[1]&0xf); + m_Sequence = -1; + if(m_Flags&NET_CHUNKFLAG_VITAL) + { + m_Sequence = ((pData[1]&0xf0)<<2) | pData[2]; + return pData + 3; + } + return pData + 2; +} + + +int CNetBase::IsSeqInBackroom(int Seq, int Ack) +{ + int Bottom = (Ack-NET_MAX_SEQUENCE/2); + if(Bottom < 0) + { + if(Seq <= Ack) + return 1; + if(Seq >= (Bottom + NET_MAX_SEQUENCE)) + return 1; + } + else + { + if(Seq <= Ack && Seq >= Bottom) + return 1; + } + + return 0; +} + +IOHANDLE CNetBase::ms_DataLogSent = 0; +IOHANDLE CNetBase::ms_DataLogRecv = 0; +CHuffman CNetBase::ms_Huffman; + + +void CNetBase::OpenLog(const char *pSentLog, const char *pRecvLog) +{ + /* + if(pSentLog) + { + ms_DataLogSent = engine_openfile(pSentLog, IOFLAG_WRITE); + if(ms_DataLogSent) + dbg_msg("network", "logging sent packages to '%s'", pSentLog); + else + dbg_msg("network", "failed to open for logging '%s'", pSentLog); + } + + if(pRecvLog) + { + ms_DataLogRecv = engine_openfile(pRecvLog, IOFLAG_WRITE); + if(ms_DataLogRecv) + dbg_msg("network", "logging recv packages to '%s'", pRecvLog); + else + dbg_msg("network", "failed to open for logging '%s'", pRecvLog); + }*/ +} + +int CNetBase::Compress(const void *pData, int DataSize, void *pOutput, int OutputSize) +{ + return ms_Huffman.Compress(pData, DataSize, pOutput, OutputSize); +} + +int CNetBase::Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize) +{ + return ms_Huffman.Decompress(pData, DataSize, pOutput, OutputSize); +} + + +static const unsigned gs_aFreqTable[256+1] = { + 1<<30,4545,2657,431,1950,919,444,482,2244,617,838,542,715,1814,304,240,754,212,647,186, + 283,131,146,166,543,164,167,136,179,859,363,113,157,154,204,108,137,180,202,176, + 872,404,168,134,151,111,113,109,120,126,129,100,41,20,16,22,18,18,17,19, + 16,37,13,21,362,166,99,78,95,88,81,70,83,284,91,187,77,68,52,68, + 59,66,61,638,71,157,50,46,69,43,11,24,13,19,10,12,12,20,14,9, + 20,20,10,10,15,15,12,12,7,19,15,14,13,18,35,19,17,14,8,5, + 15,17,9,15,14,18,8,10,2173,134,157,68,188,60,170,60,194,62,175,71, + 148,67,167,78,211,67,156,69,1674,90,174,53,147,89,181,51,174,63,163,80, + 167,94,128,122,223,153,218,77,200,110,190,73,174,69,145,66,277,143,141,60, + 136,53,180,57,142,57,158,61,166,112,152,92,26,22,21,28,20,26,30,21, + 32,27,20,17,23,21,30,22,22,21,27,25,17,27,23,18,39,26,15,21, + 12,18,18,27,20,18,15,19,11,17,33,12,18,15,19,18,16,26,17,18, + 9,10,25,22,22,17,20,16,6,16,15,20,14,18,24,335,1517}; + +void CNetBase::Init() +{ + ms_Huffman.Init(gs_aFreqTable); +} diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h new file mode 100644 index 00000000..11a1b70d --- /dev/null +++ b/src/engine/shared/network.h @@ -0,0 +1,346 @@ +#ifndef ENGINE_SHARED_NETWORK_H +#define ENGINE_SHARED_NETWORK_H + +#include "ringbuffer.h" +#include "huffman.h" + +/* + +CURRENT: + packet header: 3 bytes + unsigned char flags_ack; // 4bit flags, 4bit ack + unsigned char ack; // 8 bit ack + unsigned char num_chunks; // 8 bit chunks + + (unsigned char padding[3]) // 24 bit extra incase it's a connection less packet + // this is to make sure that it's compatible with the + // old protocol + + chunk header: 2-3 bytes + unsigned char flags_size; // 2bit flags, 6 bit size + unsigned char size_seq; // 4bit size, 4bit seq + (unsigned char seq;) // 8bit seq, if vital flag is set +*/ + +enum +{ + NETFLAG_ALLOWSTATELESS=1, + NETSENDFLAG_VITAL=1, + NETSENDFLAG_CONNLESS=2, + NETSENDFLAG_FLUSH=4, + + NETSTATE_OFFLINE=0, + NETSTATE_CONNECTING, + NETSTATE_ONLINE, + + NETBANTYPE_SOFT=1, + NETBANTYPE_DROP=2 +}; + + +enum +{ + NET_VERSION = 2, + + NET_MAX_CHUNKSIZE = 1024, + NET_MAX_PAYLOAD = NET_MAX_CHUNKSIZE+16, + NET_MAX_PACKETSIZE = NET_MAX_PAYLOAD+16, + NET_MAX_CHUNKHEADERSIZE = 5, + NET_PACKETHEADERSIZE = 3, + NET_MAX_CLIENTS = 16, + NET_MAX_SEQUENCE = 1<<10, + NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1, + + NET_CONNSTATE_OFFLINE=0, + NET_CONNSTATE_CONNECT=1, + NET_CONNSTATE_PENDING=2, + NET_CONNSTATE_ONLINE=3, + NET_CONNSTATE_ERROR=4, + + NET_PACKETFLAG_CONTROL=1, + NET_PACKETFLAG_CONNLESS=2, + NET_PACKETFLAG_RESEND=4, + NET_PACKETFLAG_COMPRESSION=8, + + NET_CHUNKFLAG_VITAL=1, + NET_CHUNKFLAG_RESEND=2, + + NET_CTRLMSG_KEEPALIVE=0, + NET_CTRLMSG_CONNECT=1, + NET_CTRLMSG_CONNECTACCEPT=2, + NET_CTRLMSG_ACCEPT=3, + NET_CTRLMSG_CLOSE=4, + + NET_SERVER_MAXBANS=1024, + + NET_CONN_BUFFERSIZE=1024*16, + + NET_ENUM_TERMINATOR +}; + + +typedef int (*NETFUNC_DELCLIENT)(int ClientID, void *pUser); +typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); + +struct CNetChunk +{ + // -1 means that it's a stateless packet + // 0 on the client means the server + int m_ClientID; + NETADDR m_Address; // only used when client_id == -1 + int m_Flags; + int m_DataSize; + const void *m_pData; +}; + +class CNetChunkHeader +{ +public: + int m_Flags; + int m_Size; + int m_Sequence; + + unsigned char *Pack(unsigned char *pData); + unsigned char *Unpack(unsigned char *pData); +}; + +class CNetChunkResend +{ +public: + int m_Flags; + int m_DataSize; + unsigned char *m_pData; + + int m_Sequence; + int64 m_LastSendTime; + int64 m_FirstSendTime; +}; + +class CNetPacketConstruct +{ +public: + int m_Flags; + int m_Ack; + int m_NumChunks; + int m_DataSize; + unsigned char m_aChunkData[NET_MAX_PAYLOAD]; +}; + + +class CNetConnection +{ + // TODO: is this needed because this needs to be aware of + // the ack sequencing number and is also responible for updating + // that. this should be fixed. + friend class CNetRecvUnpacker; +private: + unsigned short m_Sequence; + unsigned short m_Ack; + unsigned m_State; + + int m_Token; + int m_RemoteClosed; + + TStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> m_Buffer; + + int64 m_LastUpdateTime; + int64 m_LastRecvTime; + int64 m_LastSendTime; + + char m_ErrorString[256]; + + CNetPacketConstruct m_Construct; + + NETADDR m_PeerAddr; + NETSOCKET m_Socket; + NETSTATS m_Stats; + + // + void Reset(); + void ResetStats(); + void SetError(const char *pString); + void AckChunks(int Ack); + + int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence); + void SendControl(int ControlMsg, const void *pExtra, int ExtraSize); + void ResendChunk(CNetChunkResend *pResend); + void Resend(); + +public: + void Init(NETSOCKET Socket); + int Connect(NETADDR *pAddr); + void Disconnect(const char *pReason); + + int Update(); + int Flush(); + + int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr); + int QueueChunk(int Flags, int DataSize, const void *pData); + + const char *ErrorString(); + void SignalResend(); + int State() const { return m_State; } + NETADDR PeerAddress() const { return m_PeerAddr; } + + void ResetErrorString() { m_ErrorString[0] = 0; } + const char *ErrorString() const { return m_ErrorString; } + + // Needed for GotProblems in NetClient + int64 LastRecvTime() const { return m_LastRecvTime; } + + int AckSequence() const { return m_Ack; } +}; + +struct CNetRecvUnpacker +{ +public: + bool m_Valid; + + NETADDR m_Addr; + CNetConnection *m_pConnection; + int m_CurrentChunk; + int m_ClientID; + CNetPacketConstruct m_Data; + unsigned char m_aBuffer[NET_MAX_PACKETSIZE]; + + CNetRecvUnpacker() { Clear(); } + void Clear(); + void Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID); + int FetchChunk(CNetChunk *pChunk); +}; + +// server side +class CNetServer +{ +public: + struct CBanInfo + { + NETADDR m_Addr; + int m_Expires; + }; + +private: + class CSlot + { + public: + CNetConnection m_Connection; + }; + + class CBan + { + public: + CBanInfo m_Info; + + // hash list + CBan *m_pHashNext; + CBan *m_pHashPrev; + + // used or free list + CBan *m_pNext; + CBan *m_pPrev; + }; + + + NETSOCKET m_Socket; + CSlot m_aSlots[NET_MAX_CLIENTS]; + int m_MaxClients; + + CBan *m_aBans[256]; + CBan m_BanPool[NET_SERVER_MAXBANS]; + CBan *m_BanPool_FirstFree; + CBan *m_BanPool_FirstUsed; + + NETFUNC_NEWCLIENT m_pfnNewClient; + NETFUNC_DELCLIENT m_pfnDelClient; + void *m_UserPtr; + + CNetRecvUnpacker m_RecvUnpacker; + + void BanRemoveByObject(CBan *pBan); + +public: + int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + + // + bool Open(NETADDR BindAddr, int MaxClients, int Flags); + int Close(); + + // + int Recv(CNetChunk *pChunk); + int Send(CNetChunk *pChunk); + int Update(); + + // + int Drop(int ClientID, const char *Reason); + + // banning + int BanAdd(NETADDR Addr, int Seconds); + int BanRemove(NETADDR Addr); + int BanNum(); // caution, slow + int BanGet(int Index, CBanInfo *pInfo); // caution, slow + + // status requests + NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } + NETSOCKET Socket() const { return m_Socket; } + int MaxClients() const { return m_MaxClients; } +}; + + + +// client side +class CNetClient +{ + NETADDR m_ServerAddr; + CNetConnection m_Connection; + CNetRecvUnpacker m_RecvUnpacker; + NETSOCKET m_Socket; +public: + // openness + bool Open(NETADDR BindAddr, int Flags); + int Close(); + + // connection state + int Disconnect(const char *Reason); + int Connect(NETADDR *Addr); + + // communication + int Recv(CNetChunk *Chunk); + int Send(CNetChunk *Chunk); + + // pumping + int Update(); + int Flush(); + + int ResetErrorString(); + + // error and state + int State(); + int GotProblems(); + const char *ErrorString(); +}; + + + +// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere +class CNetBase +{ + static IOHANDLE ms_DataLogSent; + static IOHANDLE ms_DataLogRecv; + static CHuffman ms_Huffman; +public: + static void OpenLog(const char *pSentlog, const char *pRecvlog); + static void Init(); + static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize); + static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize); + + static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize); + static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize); + static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket); + static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket); + + // The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not + static int IsSeqInBackroom(int Seq, int Ack); +}; + + +#endif diff --git a/src/engine/shared/network_client.cpp b/src/engine/shared/network_client.cpp new file mode 100644 index 00000000..f7859c0a --- /dev/null +++ b/src/engine/shared/network_client.cpp @@ -0,0 +1,139 @@ +#include <base/system.h> +#include "network.h" + +bool CNetClient::Open(NETADDR BindAddr, int Flags) +{ + // clean it + mem_zero(this, sizeof(*this)); + + // open socket + m_Socket = net_udp_create(BindAddr); + m_Connection.Init(m_Socket); + return true; +} + +int CNetClient::Close() +{ + // TODO: implement me + return 0; +} + + +int CNetClient::Disconnect(const char *pReason) +{ + dbg_msg("netclient", "disconnected. reason=\"%s\"", pReason); + m_Connection.Disconnect(pReason); + return 0; +} + +int CNetClient::Update() +{ + m_Connection.Update(); + if(m_Connection.State() == NET_CONNSTATE_ERROR) + Disconnect(m_Connection.ErrorString()); + return 0; +} + +int CNetClient::Connect(NETADDR *pAddr) +{ + m_Connection.Connect(pAddr); + return 0; +} + +int CNetClient::ResetErrorString() +{ + m_Connection.ResetErrorString(); + return 0; +} + +int CNetClient::Recv(CNetChunk *pChunk) +{ + while(1) + { + // check for a chunk + if(m_RecvUnpacker.FetchChunk(pChunk)) + return 1; + + // TODO: empty the recvinfo + NETADDR Addr; + int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); + + // no more packets for now + if(Bytes <= 0) + break; + + if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0) + { + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS) + { + pChunk->m_Flags = NETSENDFLAG_CONNLESS; + pChunk->m_ClientID = -1; + pChunk->m_Address = Addr; + pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize; + pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData; + return 1; + } + else + { + if(m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + m_RecvUnpacker.Start(&Addr, &m_Connection, 0); + } + } + } + return 0; +} + +int CNetClient::Send(CNetChunk *pChunk) +{ + if(pChunk->m_DataSize >= NET_MAX_PAYLOAD) + { + dbg_msg("netclient", "chunk payload too big. %d. dropping chunk", pChunk->m_DataSize); + return -1; + } + + if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) + { + // send connectionless packet + CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); + } + else + { + int Flags = 0; + dbg_assert(pChunk->m_ClientID == 0, "errornous client id"); + + if(pChunk->m_Flags&NETSENDFLAG_VITAL) + Flags = NET_CHUNKFLAG_VITAL; + + m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData); + + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_Connection.Flush(); + } + return 0; +} + +int CNetClient::State() +{ + if(m_Connection.State() == NET_CONNSTATE_ONLINE) + return NETSTATE_ONLINE; + if(m_Connection.State() == NET_CONNSTATE_OFFLINE) + return NETSTATE_OFFLINE; + return NETSTATE_CONNECTING; +} + +int CNetClient::Flush() +{ + return m_Connection.Flush(); +} + +int CNetClient::GotProblems() +{ + if(time_get() - m_Connection.LastRecvTime() > time_freq()) + return 1; + return 0; +} + +const char *CNetClient::ErrorString() +{ + return m_Connection.ErrorString(); +} diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp new file mode 100644 index 00000000..4ed157eb --- /dev/null +++ b/src/engine/shared/network_conn.cpp @@ -0,0 +1,354 @@ +#include <base/system.h> +#include "config.h" +#include "network.h" + +void CNetConnection::ResetStats() +{ + mem_zero(&m_Stats, sizeof(m_Stats)); +} + +void CNetConnection::Reset() +{ + m_Sequence = 0; + m_Ack = 0; + m_RemoteClosed = 0; + + m_State = NET_CONNSTATE_OFFLINE; + m_LastSendTime = 0; + m_LastRecvTime = 0; + m_LastUpdateTime = 0; + m_Token = -1; + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + + m_Buffer.Init(); + + mem_zero(&m_Construct, sizeof(m_Construct)); +} + +const char *CNetConnection::ErrorString() +{ + return m_ErrorString; +} + +void CNetConnection::SetError(const char *pString) +{ + str_copy(m_ErrorString, pString, sizeof(m_ErrorString)); +} + +void CNetConnection::Init(NETSOCKET Socket) +{ + Reset(); + ResetStats(); + + m_Socket = Socket; + mem_zero(m_ErrorString, sizeof(m_ErrorString)); +} + +void CNetConnection::AckChunks(int Ack) +{ + while(1) + { + CNetChunkResend *pResend = m_Buffer.First(); + if(!pResend) + break; + + if(CNetBase::IsSeqInBackroom(pResend->m_Sequence, Ack)) + m_Buffer.PopFirst(); + else + break; + } +} + +void CNetConnection::SignalResend() +{ + m_Construct.m_Flags |= NET_PACKETFLAG_RESEND; +} + +int CNetConnection::Flush() +{ + int NumChunks = m_Construct.m_NumChunks; + if(!NumChunks && !m_Construct.m_Flags) + return 0; + + // send of the packets + m_Construct.m_Ack = m_Ack; + CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct); + + // update send times + m_LastSendTime = time_get(); + + // clear construct so we can start building a new package + mem_zero(&m_Construct, sizeof(m_Construct)); + return NumChunks; +} + +int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) +{ + unsigned char *pChunkData; + + // check if we have space for it, if not, flush the connection + if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData)) + Flush(); + + // pack all the data + CNetChunkHeader Header; + Header.m_Flags = Flags; + Header.m_Size = DataSize; + Header.m_Sequence = Sequence; + pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize]; + pChunkData = Header.Pack(pChunkData); + mem_copy(pChunkData, pData, DataSize); + pChunkData += DataSize; + + // + m_Construct.m_NumChunks++; + m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); + + // set packet flags aswell + + if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND)) + { + // save packet if we need to resend + CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize); + if(pResend) + { + pResend->m_Sequence = Sequence; + pResend->m_Flags = Flags; + pResend->m_DataSize = DataSize; + pResend->m_pData = (unsigned char *)(pResend+1); + pResend->m_FirstSendTime = time_get(); + pResend->m_LastSendTime = pResend->m_FirstSendTime; + mem_copy(pResend->m_pData, pData, DataSize); + } + else + { + // out of buffer + Disconnect("too weak connection (out of buffer)"); + return -1; + } + } + + return 0; +} + +int CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData) +{ + if(Flags&NET_CHUNKFLAG_VITAL) + m_Sequence = (m_Sequence+1)%NET_MAX_SEQUENCE; + return QueueChunkEx(Flags, DataSize, pData, m_Sequence); +} + +void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSize) +{ + // send the control message + m_LastSendTime = time_get(); + CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize); +} + +void CNetConnection::ResendChunk(CNetChunkResend *pResend) +{ + QueueChunkEx(pResend->m_Flags|NET_CHUNKFLAG_RESEND, pResend->m_DataSize, pResend->m_pData, pResend->m_Sequence); + pResend->m_LastSendTime = time_get(); +} + +void CNetConnection::Resend() +{ + for(CNetChunkResend *pResend = m_Buffer.First(); pResend; pResend = m_Buffer.Next(pResend)) + ResendChunk(pResend); +} + +int CNetConnection::Connect(NETADDR *pAddr) +{ + if(State() != NET_CONNSTATE_OFFLINE) + return -1; + + // init connection + Reset(); + m_PeerAddr = *pAddr; + mem_zero(m_ErrorString, sizeof(m_ErrorString)); + m_State = NET_CONNSTATE_CONNECT; + SendControl(NET_CTRLMSG_CONNECT, 0, 0); + return 0; +} + +void CNetConnection::Disconnect(const char *pReason) +{ + if(State() == NET_CONNSTATE_OFFLINE) + return; + + if(m_RemoteClosed == 0) + { + if(pReason) + SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1); + else + SendControl(NET_CTRLMSG_CLOSE, 0, 0); + + m_ErrorString[0] = 0; + if(pReason) + str_copy(m_ErrorString, pReason, sizeof(m_ErrorString)); + } + + Reset(); +} + +int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) +{ + int64 Now = time_get(); + m_LastRecvTime = Now; + + // check if resend is requested + if(pPacket->m_Flags&NET_PACKETFLAG_RESEND) + Resend(); + + // + if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL) + { + int CtrlMsg = pPacket->m_aChunkData[0]; + + if(CtrlMsg == NET_CTRLMSG_CLOSE) + { + if(net_addr_comp(&m_PeerAddr, pAddr) == 0) + { + m_State = NET_CONNSTATE_ERROR; + m_RemoteClosed = 1; + + if(pPacket->m_DataSize) + { + // make sure to sanitize the error string form the other party + char Str[128]; + if(pPacket->m_DataSize < 128) + str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize); + else + str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str)); + str_sanitize_strong(Str); + + // set the error string + SetError(Str); + } + else + SetError("no reason given"); + + if(g_Config.m_Debug) + dbg_msg("conn", "closed reason='%s'", ErrorString()); + } + return 0; + } + else + { + if(State() == NET_CONNSTATE_OFFLINE) + { + if(CtrlMsg == NET_CTRLMSG_CONNECT) + { + // send response and init connection + Reset(); + m_State = NET_CONNSTATE_PENDING; + m_PeerAddr = *pAddr; + m_LastSendTime = Now; + m_LastRecvTime = Now; + m_LastUpdateTime = Now; + SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + if(g_Config.m_Debug) + dbg_msg("connection", "got connection, sending connect+accept"); + } + } + else if(State() == NET_CONNSTATE_CONNECT) + { + // connection made + if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) + { + SendControl(NET_CTRLMSG_ACCEPT, 0, 0); + m_State = NET_CONNSTATE_ONLINE; + if(g_Config.m_Debug) + dbg_msg("connection", "got connect+accept, sending accept. connection online"); + } + } + else if(State() == NET_CONNSTATE_ONLINE) + { + // connection made + /* + if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT) + { + + }*/ + } + } + } + else + { + if(State() == NET_CONNSTATE_PENDING) + { + m_State = NET_CONNSTATE_ONLINE; + if(g_Config.m_Debug) + dbg_msg("connection", "connecting online"); + } + } + + if(State() == NET_CONNSTATE_ONLINE) + { + AckChunks(pPacket->m_Ack); + } + + return 1; +} + +int CNetConnection::Update() +{ + int64 Now = time_get(); + + if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR) + return 0; + + // check for timeout + if(State() != NET_CONNSTATE_OFFLINE && + State() != NET_CONNSTATE_CONNECT && + (Now-m_LastRecvTime) > time_freq()*10) + { + m_State = NET_CONNSTATE_ERROR; + SetError("timeout"); + } + + // fix resends + if(m_Buffer.First()) + { + CNetChunkResend *pResend = m_Buffer.First(); + + // check if we have some really old stuff laying around and abort if not acked + if(Now-pResend->m_FirstSendTime > time_freq()*10) + { + m_State = NET_CONNSTATE_ERROR; + SetError("too weak connection (not acked for 10 seconds)"); + } + else + { + // resend packet if we havn't got it acked in 1 second + if(Now-pResend->m_LastSendTime > time_freq()) + ResendChunk(pResend); + } + } + + // send keep alives if nothing has happend for 250ms + if(State() == NET_CONNSTATE_ONLINE) + { + if(time_get()-m_LastSendTime > time_freq()/2) // flush connection after 500ms if needed + { + int NumFlushedChunks = Flush(); + if(NumFlushedChunks && g_Config.m_Debug) + dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks); + } + + if(time_get()-m_LastSendTime > time_freq()) + SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0); + } + else if(State() == NET_CONNSTATE_CONNECT) + { + if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect every 500ms + SendControl(NET_CTRLMSG_CONNECT, 0, 0); + } + else if(State() == NET_CONNSTATE_PENDING) + { + if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect/accept every 500ms + SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + } + + return 0; +} diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp new file mode 100644 index 00000000..32b08bf6 --- /dev/null +++ b/src/engine/shared/network_server.cpp @@ -0,0 +1,413 @@ +#include <base/system.h> +#include "network.h" + +#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \ + { if(First) First->Prev = Object; \ + Object->Prev = (struct CBan *)0; \ + Object->Next = First; \ + First = Object; } + +#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \ + { Object->Prev = After; \ + Object->Next = After->Next; \ + After->Next = Object; \ + if(Object->Next) \ + Object->Next->Prev = Object; \ + } + +#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \ + { if(Object->Next) Object->Next->Prev = Object->Prev; \ + if(Object->Prev) Object->Prev->Next = Object->Next; \ + else First = Object->Next; \ + Object->Next = 0; Object->Prev = 0; } + +#define MACRO_LIST_FIND(Start, Next, Expression) \ + { while(Start && !(Expression)) Start = Start->Next; } + +bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags) +{ + // zero out the whole structure + mem_zero(this, sizeof(*this)); + + // open socket + m_Socket = net_udp_create(BindAddr); + if(m_Socket == NETSOCKET_INVALID) + return false; + + // clamp clients + m_MaxClients = MaxClients; + if(m_MaxClients > NET_MAX_CLIENTS) + m_MaxClients = NET_MAX_CLIENTS; + if(m_MaxClients < 1) + m_MaxClients = 1; + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aSlots[i].m_Connection.Init(m_Socket); + + // setup all pointers for bans + for(int i = 1; i < NET_SERVER_MAXBANS-1; i++) + { + m_BanPool[i].m_pNext = &m_BanPool[i+1]; + m_BanPool[i].m_pPrev = &m_BanPool[i-1]; + } + + m_BanPool[0].m_pNext = &m_BanPool[1]; + m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2]; + m_BanPool_FirstFree = &m_BanPool[0]; + + return true; +} + +int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; + return 0; +} + +int CNetServer::Close() +{ + // TODO: implement me + return 0; +} + +int CNetServer::Drop(int ClientID, const char *pReason) +{ + // TODO: insert lots of checks here + NETADDR Addr = ClientAddr(ClientID); + + dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", + ClientID, + Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], + pReason + ); + + m_aSlots[ClientID].m_Connection.Disconnect(pReason); + + if(m_pfnDelClient) + m_pfnDelClient(ClientID, m_UserPtr); + + return 0; +} + +int CNetServer::BanGet(int Index, CBanInfo *pInfo) +{ + CBan *pBan; + for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--) + {} + + if(!pBan) + return 0; + *pInfo = pBan->m_Info; + return 1; +} + +int CNetServer::BanNum() +{ + int Count = 0; + CBan *pBan; + for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext) + Count++; + return Count; +} + +void CNetServer::BanRemoveByObject(CBan *pBan) +{ + int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff; + dbg_msg("netserver", "removing ban on %d.%d.%d.%d", + pBan->m_Info.m_Addr.ip[0], pBan->m_Info.m_Addr.ip[1], pBan->m_Info.m_Addr.ip[2], pBan->m_Info.m_Addr.ip[3]); + MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext); + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); +} + +int CNetServer::BanRemove(NETADDR Addr) +{ + int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + CBan *pBan = m_aBans[IpHash]; + + MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); + + if(pBan) + { + BanRemoveByObject(pBan); + return 0; + } + + return -1; +} + +int CNetServer::BanAdd(NETADDR Addr, int Seconds) +{ + int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + int Stamp = -1; + CBan *pBan; + + // remove the port + Addr.port = 0; + + if(Seconds) + Stamp = time_timestamp() + Seconds; + + // search to see if it already exists + pBan = m_aBans[IpHash]; + MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); + if(pBan) + { + // adjust the ban + pBan->m_Info.m_Expires = Stamp; + return 0; + } + + if(!m_BanPool_FirstFree) + return -1; + + // fetch and clear the new ban + pBan = m_BanPool_FirstFree; + MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); + + // setup the ban info + pBan->m_Info.m_Expires = Stamp; + pBan->m_Info.m_Addr = Addr; + + // add it to the ban hash + MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext); + + // insert it into the used list + { + if(m_BanPool_FirstUsed) + { + CBan *pInsertAfter = m_BanPool_FirstUsed; + MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires); + + if(pInsertAfter) + pInsertAfter = pInsertAfter->m_pPrev; + else + { + // add to last + pInsertAfter = m_BanPool_FirstUsed; + while(pInsertAfter->m_pNext) + pInsertAfter = pInsertAfter->m_pNext; + } + + if(pInsertAfter) + { + MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext); + } + else + { + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + } + } + else + { + MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); + } + } + + // drop banned clients + { + char Buf[128]; + NETADDR BanAddr; + + if(Seconds) + str_format(Buf, sizeof(Buf), "you have been banned for %d minutes", Seconds/60); + else + str_format(Buf, sizeof(Buf), "you have been banned for life"); + + for(int i = 0; i < MaxClients(); i++) + { + BanAddr = m_aSlots[i].m_Connection.PeerAddress(); + BanAddr.port = 0; + + if(net_addr_comp(&Addr, &BanAddr) == 0) + Drop(i, Buf); + } + } + return 0; +} + +int CNetServer::Update() +{ + int Now = time_timestamp(); + for(int i = 0; i < MaxClients(); i++) + { + m_aSlots[i].m_Connection.Update(); + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) + Drop(i, m_aSlots[i].m_Connection.ErrorString()); + } + + // remove expired bans + while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now) + { + CBan *pBan = m_BanPool_FirstUsed; + BanRemoveByObject(pBan); + } + + return 0; +} + +/* + TODO: chopp up this function into smaller working parts +*/ +int CNetServer::Recv(CNetChunk *pChunk) +{ + unsigned Now = time_timestamp(); + + while(1) + { + NETADDR Addr; + + // check for a chunk + if(m_RecvUnpacker.FetchChunk(pChunk)) + return 1; + + // TODO: empty the recvinfo + int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); + + // no more packets for now + if(Bytes <= 0) + break; + + if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0) + { + CBan *pBan = 0; + NETADDR BanAddr = Addr; + int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff; + int Found = 0; + BanAddr.port = 0; + + // search a ban + for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext) + { + if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0) + break; + } + + // check if we just should drop the packet + if(pBan) + { + // banned, reply with a message + char BanStr[128]; + if(pBan->m_Info.m_Expires) + { + int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60; + if(Mins == 1) + str_format(BanStr, sizeof(BanStr), "banned for %d minute", Mins); + else + str_format(BanStr, sizeof(BanStr), "banned for %d minutes", Mins); + } + else + str_format(BanStr, sizeof(BanStr), "banned for life"); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1); + continue; + } + + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS) + { + pChunk->m_Flags = NETSENDFLAG_CONNLESS; + pChunk->m_ClientID = -1; + pChunk->m_Address = Addr; + pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize; + pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData; + return 1; + } + else + { + // TODO: check size here + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT) + { + Found = 0; + + // check if we already got this client + for(int i = 0; i < MaxClients(); i++) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE && + net_addr_comp(&PeerAddr, &Addr) == 0) + { + Found = 1; // silent ignore.. we got this client already + break; + } + } + + // client that wants to connect + if(!Found) + { + for(int i = 0; i < MaxClients(); i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + { + Found = 1; + m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr); + if(m_pfnNewClient) + m_pfnNewClient(i, m_UserPtr); + break; + } + } + + if(!Found) + { + const char FullMsg[] = "server is full"; + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg)); + } + } + } + else + { + // normal packet, find matching slot + for(int i = 0; i < MaxClients(); i++) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(net_addr_comp(&PeerAddr, &Addr) == 0) + { + if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + { + if(m_RecvUnpacker.m_Data.m_DataSize) + m_RecvUnpacker.Start(&Addr, &m_aSlots[i].m_Connection, i); + } + } + } + } + } + } + } + return 0; +} + +int CNetServer::Send(CNetChunk *pChunk) +{ + if(pChunk->m_DataSize >= NET_MAX_PAYLOAD) + { + dbg_msg("netserver", "packet payload too big. %d. dropping packet", pChunk->m_DataSize); + return -1; + } + + if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) + { + // send connectionless packet + CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); + } + else + { + int Flags = 0; + dbg_assert(pChunk->m_ClientID >= 0, "errornous client id"); + dbg_assert(pChunk->m_ClientID < MaxClients(), "errornous client id"); + + if(pChunk->m_Flags&NETSENDFLAG_VITAL) + Flags = NET_CHUNKFLAG_VITAL; + + if(m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0) + { + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_aSlots[pChunk->m_ClientID].m_Connection.Flush(); + } + else + { + Drop(pChunk->m_ClientID, "error sending data"); + } + } + return 0; +} + diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp new file mode 100644 index 00000000..3e1d8dd6 --- /dev/null +++ b/src/engine/shared/packer.cpp @@ -0,0 +1,155 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> + +#include "packer.h" +#include "compression.h" +#include "engine.h" +#include "config.h" + +void CPacker::Reset() +{ + m_Error = 0; + m_pCurrent = m_aBuffer; + m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE; +} + +void CPacker::AddInt(int i) +{ + if(m_Error) + return; + + // make sure that we have space enough + if(m_pEnd - m_pCurrent < 6) + { + dbg_break(); + m_Error = 1; + } + else + m_pCurrent = CVariableInt::Pack(m_pCurrent, i); +} + +void CPacker::AddString(const char *pStr, int Limit) +{ + if(m_Error) + return; + + // + if(Limit > 0) + { + while(*pStr && Limit != 0) + { + *m_pCurrent++ = *pStr++; + Limit--; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + break; + } + } + *m_pCurrent++ = 0; + } + else + { + while(*pStr) + { + *m_pCurrent++ = *pStr++; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + break; + } + } + *m_pCurrent++ = 0; + } +} + +void CPacker::AddRaw(const void *pData, int Size) +{ + if(m_Error) + return; + + if(m_pCurrent+Size >= m_pEnd) + { + m_Error = 1; + return; + } + + const unsigned char *pSrc = (const unsigned char *)pData; + while(Size) + { + *m_pCurrent++ = *pSrc++; + Size--; + } +} + + +void CUnpacker::Reset(const void *pData, int Size) +{ + m_Error = 0; + m_pStart = (const unsigned char *)pData; + m_pEnd = m_pStart + Size; + m_pCurrent = m_pStart; +} + +int CUnpacker::GetInt() +{ + if(m_Error) + return 0; + + if(m_pCurrent >= m_pEnd) + { + m_Error = 1; + return 0; + } + + int i; + m_pCurrent = CVariableInt::Unpack(m_pCurrent, &i); + if(m_pCurrent > m_pEnd) + { + m_Error = 1; + return 0; + } + return i; +} + +const char *CUnpacker::GetString() +{ + if(m_Error || m_pCurrent >= m_pEnd) + return ""; + + char *pPtr = (char *)m_pCurrent; + while(*m_pCurrent) // skip the string + { + m_pCurrent++; + if(m_pCurrent == m_pEnd) + { + m_Error = 1;; + return ""; + } + } + m_pCurrent++; + + // sanitize all strings + str_sanitize(pPtr); + return pPtr; +} + +const unsigned char *CUnpacker::GetRaw(int Size) +{ + const unsigned char *pPtr = m_pCurrent; + if(m_Error) + return 0; + + // check for nasty sizes + if(Size < 0 || m_pCurrent+Size > m_pEnd) + { + m_Error = 1; + return 0; + } + + // "unpack" the data + m_pCurrent += Size; + return pPtr; +} diff --git a/src/engine/shared/packer.h b/src/engine/shared/packer.h new file mode 100644 index 00000000..7a98501a --- /dev/null +++ b/src/engine/shared/packer.h @@ -0,0 +1,42 @@ +#ifndef ENGINE_SHARED_PACKER_H +#define ENGINE_SHARED_PACKER_H + + + +class CPacker +{ + enum + { + PACKER_BUFFER_SIZE=1024*2 + }; + + unsigned char m_aBuffer[PACKER_BUFFER_SIZE]; + unsigned char *m_pCurrent; + unsigned char *m_pEnd; + int m_Error; +public: + void Reset(); + void AddInt(int i); + void AddString(const char *pStr, int Limit); + void AddRaw(const void *pData, int Size); + + int Size() const { return (int)(m_pCurrent-m_aBuffer); } + const unsigned char *Data() const { return m_aBuffer; } + bool Error() const { return m_Error; } +}; + +class CUnpacker +{ + const unsigned char *m_pStart; + const unsigned char *m_pCurrent; + const unsigned char *m_pEnd; + int m_Error; +public: + void Reset(const void *pData, int Size); + int GetInt(); + const char *GetString(); + const unsigned char *GetRaw(int Size); + bool Error() const { return m_Error; } +}; + +#endif diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h new file mode 100644 index 00000000..d09cff5a --- /dev/null +++ b/src/engine/shared/protocol.h @@ -0,0 +1,91 @@ +#ifndef ENGINE_SHARED_PROTOCOL_H +#define ENGINE_SHARED_PROTOCOL_H + +#include <base/system.h> + +/* + Connection diagram - How the initilization works. + + Client -> INFO -> Server + Contains version info, name, and some other info. + + Client <- MAP <- Server + Contains current map. + + Client -> READY -> Server + The client has loaded the map and is ready to go, + but the mod needs to send it's information aswell. + modc_connected is called on the client and + mods_connected is called on the server. + The client should call client_entergame when the + mod has done it's initilization. + + Client -> ENTERGAME -> Server + Tells the server to start sending snapshots. + client_entergame and server_client_enter is called. +*/ + + +enum +{ + NETMSG_NULL=0, + + // the first thing sent by the client + // contains the version info for the client + NETMSG_INFO=1, + + // sent by server + NETMSG_MAP_CHANGE, // sent when client should switch map + NETMSG_MAP_DATA, // map transfer, contains a chunk of the map file + NETMSG_SNAP, // normal snapshot, multiple parts + NETMSG_SNAPEMPTY, // empty snapshot + NETMSG_SNAPSINGLE, // ? + NETMSG_SNAPSMALL, // + NETMSG_INPUTTIMING, // reports how off the input was + NETMSG_RCON_AUTH_STATUS,// result of the authentication + NETMSG_RCON_LINE, // line that should be printed to the remote console + + NETMSG_AUTH_CHALLANGE, // + NETMSG_AUTH_RESULT, // + + // sent by client + NETMSG_READY, // + NETMSG_ENTERGAME, + NETMSG_INPUT, // contains the inputdata from the client + NETMSG_RCON_CMD, // + NETMSG_RCON_AUTH, // + NETMSG_REQUEST_MAP_DATA,// + + NETMSG_AUTH_START, // + NETMSG_AUTH_RESPONSE, // + + // sent by both + NETMSG_PING, + NETMSG_PING_REPLY, + NETMSG_ERROR +}; + +// this should be revised +enum +{ + SERVER_TICK_SPEED=50, + SERVER_FLAG_PASSWORD = 0x1, + + MAX_CLIENTS=16, + + MAX_INPUT_SIZE=128, + MAX_SNAPSHOT_PACKSIZE=900, + + MAX_CLANNAME_LENGTH=32, + MAX_NAME_LENGTH=24, + + + // message packing + MSGFLAG_VITAL=1, + MSGFLAG_FLUSH=2, + MSGFLAG_NORECORD=4, + MSGFLAG_RECORD=8, + MSGFLAG_NOSEND=16 +}; + +#endif diff --git a/src/engine/shared/ringbuffer.cpp b/src/engine/shared/ringbuffer.cpp new file mode 100644 index 00000000..45a845ee --- /dev/null +++ b/src/engine/shared/ringbuffer.cpp @@ -0,0 +1,192 @@ +#include <base/system.h> + +#include "ringbuffer.h" + +CRingBufferBase::CItem *CRingBufferBase::NextBlock(CItem *pItem) +{ + if(pItem->m_pNext) + return pItem->m_pNext; + return m_pFirst; +} + +CRingBufferBase::CItem *CRingBufferBase::PrevBlock(CItem *pItem) +{ + if(pItem->m_pPrev) + return pItem->m_pPrev; + return m_pLast; +} + +CRingBufferBase::CItem *CRingBufferBase::MergeBack(CItem *pItem) +{ + // make sure that this block and previous block is free + if(!pItem->m_Free || !pItem->m_pPrev || !pItem->m_pPrev->m_Free) + return pItem; + + // merge the blocks + pItem->m_pPrev->m_Size += pItem->m_Size; + pItem->m_pPrev->m_pNext = pItem->m_pNext; + + // fixup pointers + if(pItem->m_pNext) + pItem->m_pNext->m_pPrev = pItem->m_pPrev; + else + m_pLast = pItem->m_pPrev; + + if(pItem == m_pProduce) + m_pProduce = pItem->m_pPrev; + + if(pItem == m_pConsume) + m_pConsume = pItem->m_pPrev; + + // return the current block + return pItem->m_pPrev; +} + +void CRingBufferBase::Init(void *pMemory, int Size, int Flags) +{ + mem_zero(pMemory, Size); + m_Size = (Size)/sizeof(CItem)*sizeof(CItem); + m_pFirst = (CItem *)pMemory; + m_pFirst->m_Free = 1; + m_pFirst->m_Size = m_Size; + m_pLast = m_pFirst; + m_pProduce = m_pFirst; + m_pConsume = m_pFirst; + m_Flags = Flags; + +} + +void *CRingBufferBase::Allocate(int Size) +{ + int WantedSize = (Size+sizeof(CItem)+sizeof(CItem)-1)/sizeof(CItem)*sizeof(CItem); + CItem *pBlock = 0; + + // check if we even can fit this block + if(WantedSize > m_Size) + return 0; + + while(1) + { + // check for space + if(m_pProduce->m_Free) + { + if(m_pProduce->m_Size >= WantedSize) + pBlock = m_pProduce; + else + { + // wrap around to try to find a block + if(m_pFirst->m_Free && m_pFirst->m_Size >= WantedSize) + pBlock = m_pFirst; + } + } + + if(pBlock) + break; + else + { + // we have no block, check our policy and see what todo + if(m_Flags&FLAG_RECYCLE) + { + if(!PopFirst()) + return 0; + } + else + return 0; + } + } + + // okey, we have our block + + // split the block if needed + if(pBlock->m_Size > WantedSize+sizeof(CItem)) + { + CItem *pNewItem = (CItem *)((char *)pBlock + WantedSize); + pNewItem->m_pPrev = pBlock; + pNewItem->m_pNext = pBlock->m_pNext; + if(pNewItem->m_pNext) + pNewItem->m_pNext->m_pPrev = pNewItem; + pBlock->m_pNext = pNewItem; + + pNewItem->m_Free = 1; + pNewItem->m_Size = pBlock->m_Size - WantedSize; + pBlock->m_Size = WantedSize; + + if(!pNewItem->m_pNext) + m_pLast = pNewItem; + } + + + // set next block + m_pProduce = NextBlock(pBlock); + + // set as used and return the item pointer + pBlock->m_Free = 0; + return (void *)(pBlock+1); +} + +int CRingBufferBase::PopFirst() +{ + if(m_pConsume->m_Free) + return 0; + + // set the free flag + m_pConsume->m_Free = 1; + + // previous block is also free, merge them + m_pConsume = MergeBack(m_pConsume); + + // advance the consume pointer + m_pConsume = NextBlock(m_pConsume); + while(m_pConsume->m_Free && m_pConsume != m_pProduce) + { + m_pConsume = MergeBack(m_pConsume); + m_pConsume = NextBlock(m_pConsume); + } + + // in the case that we have catched up with the produce pointer + // we might stand on a free block so merge em + MergeBack(m_pConsume); + return 1; +} + + +void *CRingBufferBase::Prev(void *pCurrent) +{ + CItem *pItem = ((CItem *)pCurrent) - 1; + + while(1) + { + pItem = PrevBlock(pItem); + if(pItem == m_pProduce) + return 0; + if(!pItem->m_Free) + return pItem+1; + } +} + +void *CRingBufferBase::Next(void *pCurrent) +{ + CItem *pItem = ((CItem *)pCurrent) - 1; + + while(1) + { + pItem = NextBlock(pItem); + if(pItem == m_pProduce) + return 0; + if(!pItem->m_Free) + return pItem+1; + } +} + +void *CRingBufferBase::First() +{ + if(m_pConsume->m_Free) + return 0; + return (void *)(m_pConsume+1); +} + +void *CRingBufferBase::Last() +{ + return Prev(m_pProduce+1); +} + diff --git a/src/engine/shared/ringbuffer.h b/src/engine/shared/ringbuffer.h new file mode 100644 index 00000000..aa02b8d9 --- /dev/null +++ b/src/engine/shared/ringbuffer.h @@ -0,0 +1,66 @@ +#ifndef ENGINE_SHARED_RINGBUFFER_H +#define ENGINE_SHARED_RINGBUFFER_H + +typedef struct RINGBUFFER RINGBUFFER; + +class CRingBufferBase +{ + class CItem + { + public: + CItem *m_pPrev; + CItem *m_pNext; + int m_Free; + int m_Size; + }; + + CItem *m_pProduce; + CItem *m_pConsume; + + CItem *m_pFirst; + CItem *m_pLast; + + void *m_pMemory; + int m_Size; + int m_Flags; + + CItem *NextBlock(CItem *pItem); + CItem *PrevBlock(CItem *pItem); + CItem *MergeBack(CItem *pItem); +protected: + void *Allocate(int Size); + + void *Prev(void *pCurrent); + void *Next(void *pCurrent); + void *First(); + void *Last(); + + void Init(void *pMemory, int Size, int Flags); + int PopFirst(); +public: + enum + { + // Will start to destroy items to try to fit the next one + FLAG_RECYCLE=1 + }; +}; + +template<typename T, int TSIZE, int TFLAGS=0> +class TStaticRingBuffer : public CRingBufferBase +{ + unsigned char m_aBuffer[TSIZE]; +public: + TStaticRingBuffer() { Init(); } + + void Init() { CRingBufferBase::Init(m_aBuffer, TSIZE, TFLAGS); } + + T *Allocate(int Size) { return (T*)CRingBufferBase::Allocate(Size); } + int PopFirst() { return CRingBufferBase::PopFirst(); } + + T *Prev(T *pCurrent) { return (T*)CRingBufferBase::Prev(pCurrent); } + T *Next(T *pCurrent) { return (T*)CRingBufferBase::Next(pCurrent); } + T *First() { return (T*)CRingBufferBase::First(); } + T *Last() { return (T*)CRingBufferBase::Last(); } +}; + +#endif diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp new file mode 100644 index 00000000..d566d3a3 --- /dev/null +++ b/src/engine/shared/snapshot.cpp @@ -0,0 +1,537 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include "snapshot.h" +#include "engine.h" +#include "compression.h" + +// CSnapshot + +CSnapshotItem *CSnapshot::GetItem(int Index) +{ + return (CSnapshotItem *)(DataStart() + Offsets()[Index]); +} + +int CSnapshot::GetItemSize(int Index) +{ + if(Index == m_NumItems-1) + return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem); + return (Offsets()[Index+1] - Offsets()[Index]) - sizeof(CSnapshotItem); +} + +int CSnapshot::GetItemIndex(int Key) +{ + // TODO: OPT: this should not be a linear search. very bad + for(int i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == Key) + return i; + } + return -1; +} + +int CSnapshot::Crc() +{ + int Crc = 0; + + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + + for(int b = 0; b < Size/4; b++) + Crc += pItem->Data()[b]; + } + return Crc; +} + +void CSnapshot::DebugDump() +{ + dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems); + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID()); + for(int b = 0; b < Size/4; b++) + dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, pItem->Data()[b], pItem->Data()[b]); + } +} + + +// CSnapshotDelta + +struct CItemList +{ + int m_Num; + int m_aKeys[64]; + int m_aIndex[64]; +}; + +enum +{ + HASHLIST_SIZE = 256, +}; + +static void GenerateHash(CItemList *pHashlist, CSnapshot *pSnapshot) +{ + for(int i = 0; i < HASHLIST_SIZE; i++) + pHashlist[i].m_Num = 0; + + for(int i = 0; i < pSnapshot->NumItems(); i++) + { + int Key = pSnapshot->GetItem(i)->Key(); + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + if(pHashlist[HashID].m_Num != 64) + { + pHashlist[HashID].m_aIndex[pHashlist[HashID].m_Num] = i; + pHashlist[HashID].m_aKeys[pHashlist[HashID].m_Num] = Key; + pHashlist[HashID].m_Num++; + } + } +} + +static int GetItemIndexHashed(int Key, const CItemList *pHashlist) +{ + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + for(int i = 0; i < pHashlist[HashID].m_Num; i++) + { + if(pHashlist[HashID].m_aKeys[i] == Key) + return pHashlist[HashID].m_aIndex[i]; + } + + return -1; +} + +static int DiffItem(int *pPast, int *pCurrent, int *pOut, int Size) +{ + int Needed = 0; + while(Size) + { + *pOut = *pCurrent-*pPast; + Needed |= *pOut; + pOut++; + pPast++; + pCurrent++; + Size--; + } + + return Needed; +} + +void CSnapshotDelta::UndiffItem(int *pPast, int *pDiff, int *pOut, int Size) +{ + while(Size) + { + *pOut = *pPast+*pDiff; + + if(*pDiff == 0) + m_aSnapshotDataRate[m_SnapshotCurrent] += 1; + else + { + unsigned char aBuf[16]; + unsigned char *pEnd = CVariableInt::Pack(aBuf, *pDiff); + m_aSnapshotDataRate[m_SnapshotCurrent] += (int)(pEnd - (unsigned char*)aBuf) * 8; + } + + pOut++; + pPast++; + pDiff++; + Size--; + } +} + +CSnapshotDelta::CSnapshotDelta() +{ + mem_zero(m_aItemSizes, sizeof(m_aItemSizes)); + mem_zero(m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate)); + mem_zero(m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates)); + m_SnapshotCurrent = 0; + mem_zero(&m_Empty, sizeof(m_Empty)); +} + +void CSnapshotDelta::SetStaticsize(int ItemType, int Size) +{ + m_aItemSizes[ItemType] = Size; +} + +CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() +{ + return &m_Empty; +} + +// TODO: OPT: this should be made much faster +int CSnapshotDelta::CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pDstData) +{ + CData *pDelta = (CData *)pDstData; + int *pData = (int *)pDelta->m_pData; + int i, ItemSize, PastIndex; + CSnapshotItem *pFromItem; + CSnapshotItem *pCurItem; + CSnapshotItem *pPastItem; + int Count = 0; + int SizeCount = 0; + + pDelta->m_NumDeletedItems = 0; + pDelta->m_NumUpdateItems = 0; + pDelta->m_NumTempItems = 0; + + CItemList Hashlist[HASHLIST_SIZE]; + GenerateHash(Hashlist, pTo); + + // pack deleted stuff + for(i = 0; i < pFrom->NumItems(); i++) + { + pFromItem = pFrom->GetItem(i); + if(GetItemIndexHashed(pFromItem->Key(), Hashlist) == -1) + { + // deleted + pDelta->m_NumDeletedItems++; + *pData = pFromItem->Key(); + pData++; + } + } + + GenerateHash(Hashlist, pFrom); + int aPastIndecies[1024]; + + // fetch previous indices + // we do this as a separate pass because it helps the cache + for(i = 0; i < pTo->NumItems(); i++) + { + pCurItem = pTo->GetItem(i); // O(1) .. O(n) + aPastIndecies[i] = GetItemIndexHashed(pCurItem->Key(), Hashlist); // O(n) .. O(n^n) + } + + for(i = 0; i < pTo->NumItems(); i++) + { + // do delta + ItemSize = pTo->GetItemSize(i); // O(1) .. O(n) + pCurItem = pTo->GetItem(i); // O(1) .. O(n) + PastIndex = aPastIndecies[i]; + + if(PastIndex != -1) + { + int *pItemDataDst = pData+3; + + pPastItem = pFrom->GetItem(PastIndex); + + if(m_aItemSizes[pCurItem->Type()]) + pItemDataDst = pData+2; + + if(DiffItem((int*)pPastItem->Data(), (int*)pCurItem->Data(), pItemDataDst, ItemSize/4)) + { + + *pData++ = pCurItem->Type(); + *pData++ = pCurItem->ID(); + if(!m_aItemSizes[pCurItem->Type()]) + *pData++ = ItemSize/4; + pData += ItemSize/4; + pDelta->m_NumUpdateItems++; + } + } + else + { + *pData++ = pCurItem->Type(); + *pData++ = pCurItem->ID(); + if(!m_aItemSizes[pCurItem->Type()]) + *pData++ = ItemSize/4; + + mem_copy(pData, pCurItem->Data(), ItemSize); + SizeCount += ItemSize; + pData += ItemSize/4; + pDelta->m_NumUpdateItems++; + Count++; + } + } + + if(0) + { + dbg_msg("snapshot", "%d %d %d", + pDelta->m_NumDeletedItems, + pDelta->m_NumUpdateItems, + pDelta->m_NumTempItems); + } + + /* + // TODO: pack temp stuff + + // finish + //mem_copy(pDelta->offsets, deleted, pDelta->num_deleted_items*sizeof(int)); + //mem_copy(&(pDelta->offsets[pDelta->num_deleted_items]), update, pDelta->num_update_items*sizeof(int)); + //mem_copy(&(pDelta->offsets[pDelta->num_deleted_items+pDelta->num_update_items]), temp, pDelta->num_temp_items*sizeof(int)); + //mem_copy(pDelta->data_start(), data, data_size); + //pDelta->data_size = data_size; + * */ + + if(!pDelta->m_NumDeletedItems && !pDelta->m_NumUpdateItems && !pDelta->m_NumTempItems) + return 0; + + return (int)((char*)pData-(char*)pDstData); +} + +static int RangeCheck(void *pEnd, void *pPtr, int Size) +{ + if((const char *)pPtr + Size > (const char *)pEnd) + return -1; + return 0; +} + +int CSnapshotDelta::UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pSrcData, int DataSize) +{ + CSnapshotBuilder Builder; + CData *pDelta = (CData *)pSrcData; + int *pData = (int *)pDelta->m_pData; + int *pEnd = (int *)(((char *)pSrcData + DataSize)); + + CSnapshotItem *pFromItem; + int Keep, ItemSize; + int *pDeleted; + int Id, Type, Key; + int FromIndex; + int *pNewData; + + Builder.Init(); + + // unpack deleted stuff + pDeleted = pData; + pData += pDelta->m_NumDeletedItems; + if(pData > pEnd) + return -1; + + // copy all non deleted stuff + for(int i = 0; i < pFrom->NumItems(); i++) + { + // dbg_assert(0, "fail!"); + pFromItem = pFrom->GetItem(i); + ItemSize = pFrom->GetItemSize(i); + Keep = 1; + for(int d = 0; d < pDelta->m_NumDeletedItems; d++) + { + if(pDeleted[d] == pFromItem->Key()) + { + Keep = 0; + break; + } + } + + if(Keep) + { + // keep it + mem_copy( + Builder.NewItem(pFromItem->Type(), pFromItem->ID(), ItemSize), + pFromItem->Data(), ItemSize); + } + } + + // unpack updated stuff + for(int i = 0; i < pDelta->m_NumUpdateItems; i++) + { + if(pData+2 > pEnd) + return -1; + + Type = *pData++; + Id = *pData++; + if(m_aItemSizes[Type]) + ItemSize = m_aItemSizes[Type]; + else + { + if(pData+1 > pEnd) + return -2; + ItemSize = (*pData++) * 4; + } + m_SnapshotCurrent = Type; + + if(RangeCheck(pEnd, pData, ItemSize) || ItemSize < 0) return -3; + + Key = (Type<<16)|Id; + + // create the item if needed + pNewData = Builder.GetItemData(Key); + if(!pNewData) + pNewData = (int *)Builder.NewItem(Key>>16, Key&0xffff, ItemSize); + + //if(range_check(pEnd, pNewData, ItemSize)) return -4; + + FromIndex = pFrom->GetItemIndex(Key); + if(FromIndex != -1) + { + // we got an update so we need pTo apply the diff + UndiffItem((int *)pFrom->GetItem(FromIndex)->Data(), pData, pNewData, ItemSize/4); + m_aSnapshotDataUpdates[m_SnapshotCurrent]++; + } + else // no previous, just copy the pData + { + mem_copy(pNewData, pData, ItemSize); + m_aSnapshotDataRate[m_SnapshotCurrent] += ItemSize*8; + m_aSnapshotDataUpdates[m_SnapshotCurrent]++; + } + + pData += ItemSize/4; + } + + // finish up + return Builder.Finish(pTo); +} + + +// CSnapshotStorage + +void CSnapshotStorage::Init() +{ + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeAll() +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + mem_free(pHolder); + pHolder = pNext; + } + + // no more snapshots in storage + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeUntil(int Tick) +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + if(pHolder->m_Tick >= Tick) + return; // no more to remove + mem_free(pHolder); + + // did we come to the end of the list? + if (!pNext) + break; + + m_pFirst = pNext; + pNext->m_pPrev = 0x0; + + pHolder = pNext; + } + + // no more snapshots in storage + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt) +{ + // allocate memory for holder + snapshot_data + int TotalSize = sizeof(CHolder)+DataSize; + + if(CreateAlt) + TotalSize += DataSize; + + CHolder *pHolder = (CHolder *)mem_alloc(TotalSize, 1); + + // set data + pHolder->m_Tick = Tick; + pHolder->m_Tagtime = Tagtime; + pHolder->m_SnapSize = DataSize; + pHolder->m_pSnap = (CSnapshot*)(pHolder+1); + mem_copy(pHolder->m_pSnap, pData, DataSize); + + if(CreateAlt) // create alternative if wanted + { + pHolder->m_pAltSnap = (CSnapshot*)(((char *)pHolder->m_pSnap) + DataSize); + mem_copy(pHolder->m_pAltSnap, pData, DataSize); + } + else + pHolder->m_pAltSnap = 0; + + + // link + pHolder->m_pNext = 0; + pHolder->m_pPrev = m_pLast; + if(m_pLast) + m_pLast->m_pNext = pHolder; + else + m_pFirst = pHolder; + m_pLast = pHolder; +} + +int CSnapshotStorage::Get(int Tick, int64 *pTagtime, CSnapshot **ppData, CSnapshot **ppAltData) +{ + CHolder *pHolder = m_pFirst; + + while(pHolder) + { + if(pHolder->m_Tick == Tick) + { + if(pTagtime) + *pTagtime = pHolder->m_Tagtime; + if(ppData) + *ppData = pHolder->m_pSnap; + if(ppAltData) + *ppData = pHolder->m_pAltSnap; + return pHolder->m_SnapSize; + } + + pHolder = pHolder->m_pNext; + } + + return -1; +} + +// CSnapshotBuilder + +void CSnapshotBuilder::Init() +{ + m_DataSize = 0; + m_NumItems = 0; +} + +CSnapshotItem *CSnapshotBuilder::GetItem(int Index) +{ + return (CSnapshotItem *)&(m_aData[m_aOffsets[Index]]); +} + +int *CSnapshotBuilder::GetItemData(int Key) +{ + int i; + for(i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == Key) + return (int *)GetItem(i)->Data(); + } + return 0; +} + +int CSnapshotBuilder::Finish(void *SpnapData) +{ + // flattern and make the snapshot + CSnapshot *pSnap = (CSnapshot *)SpnapData; + int OffsetSize = sizeof(int)*m_NumItems; + pSnap->m_DataSize = m_DataSize; + pSnap->m_NumItems = m_NumItems; + mem_copy(pSnap->Offsets(), m_aOffsets, OffsetSize); + mem_copy(pSnap->DataStart(), m_aData, m_DataSize); + return sizeof(CSnapshot) + OffsetSize + m_DataSize; +} + +void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) +{ + CSnapshotItem *pObj = (CSnapshotItem *)(m_aData + m_DataSize); + + mem_zero(pObj, sizeof(CSnapshotItem) + Size); + pObj->m_TypeAndID = (Type<<16)|ID; + m_aOffsets[m_NumItems] = m_DataSize; + m_DataSize += sizeof(CSnapshotItem) + Size; + m_NumItems++; + + dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data"); + dbg_assert(m_NumItems < MAX_ITEMS, "too many items"); + + return pObj->Data(); +} diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h new file mode 100644 index 00000000..ec27d004 --- /dev/null +++ b/src/engine/shared/snapshot.h @@ -0,0 +1,136 @@ +#ifndef ENGINE_SHARED_SNAPSHOT_H +#define ENGINE_SHARED_SNAPSHOT_H + +#include <base/system.h> + +// CSnapshot + +class CSnapshotItem +{ +public: + int m_TypeAndID; + + int *Data() { return (int *)(this+1); } + int Type() { return m_TypeAndID>>16; } + int ID() { return m_TypeAndID&0xffff; } + int Key() { return m_TypeAndID; } +}; + + +class CSnapshot +{ + friend class CSnapshotBuilder; + int m_DataSize; + int m_NumItems; + + int *Offsets() const { return (int *)(this+1); } + char *DataStart() const { return (char*)(Offsets()+m_NumItems); } + +public: + enum + { + MAX_SIZE=64*1024 + }; + + void Clear() { m_DataSize = 0; m_NumItems = 0; } + int NumItems() const { return m_NumItems; } + CSnapshotItem *GetItem(int Index); + int GetItemSize(int Index); + int GetItemIndex(int Key); + + int Crc(); + void DebugDump(); +}; + + +// CSnapshotDelta + +class CSnapshotDelta +{ +public: + class CData + { + public: + int m_NumDeletedItems; + int m_NumUpdateItems; + int m_NumTempItems; // needed? + int m_pData[1]; + }; + +private: + // TODO: strange arbitrary number + short m_aItemSizes[64]; + int m_aSnapshotDataRate[0xffff]; + int m_aSnapshotDataUpdates[0xffff]; + int m_SnapshotCurrent; + CData m_Empty; + + void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size); + +public: + CSnapshotDelta(); + int GetDataRate(int Index) { return m_aSnapshotDataRate[Index]; } + int GetDataUpdates(int Index) { return m_aSnapshotDataUpdates[Index]; } + void SetStaticsize(int ItemType, int Size); + CData *EmptyDelta(); + int CreateDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData); + int UnpackDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData, int DataSize); +}; + + +// CSnapshotStorage + +class CSnapshotStorage +{ +public: + class CHolder + { + public: + CHolder *m_pPrev; + CHolder *m_pNext; + + int64 m_Tagtime; + int m_Tick; + + int m_SnapSize; + CSnapshot *m_pSnap; + CSnapshot *m_pAltSnap; + }; + + + CHolder *m_pFirst; + CHolder *m_pLast; + + void Init(); + void PurgeAll(); + void PurgeUntil(int Tick); + void Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt); + int Get(int Tick, int64 *Tagtime, CSnapshot **pData, CSnapshot **ppAltData); +}; + +class CSnapshotBuilder +{ + enum + { + MAX_ITEMS = 1024*2 + }; + + char m_aData[CSnapshot::MAX_SIZE]; + int m_DataSize; + + int m_aOffsets[MAX_ITEMS]; + int m_NumItems; + +public: + void Init(); + + void *NewItem(int Type, int ID, int Size); + + CSnapshotItem *GetItem(int Index); + int *GetItemData(int Key); + + int Finish(void *Snapdata); +}; + + +#endif // ENGINE_SNAPSHOT_H diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp new file mode 100644 index 00000000..491795ad --- /dev/null +++ b/src/engine/shared/storage.cpp @@ -0,0 +1,196 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/storage.h> +#include "engine.h" + +// compiled-in data-dir path +#define DATA_DIR "data" + +class CStorage : public IStorage +{ +public: + char m_aApplicationSavePath[512]; + char m_aDatadir[512]; + + CStorage() + { + m_aApplicationSavePath[0] = 0; + m_aDatadir[0] = 0; + } + + int Init(const char *pApplicationName, const char *pArgv0) + { + char aPath[1024] = {0}; + fs_storage_path(pApplicationName, m_aApplicationSavePath, sizeof(m_aApplicationSavePath)); + if(fs_makedir(m_aApplicationSavePath) == 0) + { + str_format(aPath, sizeof(aPath), "%s/screenshots", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/maps", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/downloadedmaps", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/demos", m_aApplicationSavePath); + fs_makedir(aPath); + } + + return FindDatadir(pArgv0); + } + + int FindDatadir(const char *pArgv0) + { + // 1) use provided data-dir override + if(m_aDatadir[0]) + { + if(fs_is_dir(m_aDatadir)) + return 0; + else + { + dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", m_aDatadir); + return -1; + } + } + + // 2) use data-dir in PWD if present + if(fs_is_dir("data/mapres")) + { + str_copy(m_aDatadir, "data", sizeof(m_aDatadir)); + return 0; + } + + // 3) use compiled-in data-dir if present + if (fs_is_dir(DATA_DIR "/mapres")) + { + str_copy(m_aDatadir, DATA_DIR, sizeof(m_aDatadir)); + return 0; + } + + // 4) check for usable path in argv[0] + { + unsigned int Pos = ~0U; + for(unsigned i = 0; pArgv0[i]; i++) + if(pArgv0[i] == '/') + Pos = i; + + if (Pos < sizeof(m_aDatadir)) + { + char aBaseDir[sizeof(m_aDatadir)]; + str_copy(aBaseDir, pArgv0, Pos); + aBaseDir[Pos] = '\0'; + str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", aBaseDir); + + if (fs_is_dir(m_aDatadir)) + return 0; + } + } + + #if defined(CONF_FAMILY_UNIX) + // 5) check for all default locations + { + const char *aDirs[] = { + "/usr/share/teeworlds/data", + "/usr/share/games/teeworlds/data", + "/usr/local/share/teeworlds/data", + "/usr/local/share/games/teeworlds/data", + "/opt/teeworlds/data" + }; + const int DirsCount = sizeof(aDirs) / sizeof(aDirs[0]); + + int i; + for (i = 0; i < DirsCount; i++) + { + if (fs_is_dir(aDirs[i])) + { + str_copy(m_aDatadir, aDirs[i], sizeof(m_aDatadir)); + return 0; + } + } + } + #endif + + // no data-dir found + dbg_msg("engine/datadir", "warning no data directory found"); + return -1; + } + + virtual void ListDirectory(int Types, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) + { + char aBuffer[1024]; + + // list current directory + if(Types&TYPE_CURRENT) + { + fs_listdir(pPath, pfnCallback, pUser); + } + + // list users directory + if(Types&TYPE_SAVE) + { + str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aApplicationSavePath, pPath); + fs_listdir(aBuffer, pfnCallback, pUser); + } + + // list datadir directory + if(Types&TYPE_DATA) + { + str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aDatadir, pPath); + fs_listdir(aBuffer, pfnCallback, pUser); + } + } + + virtual IOHANDLE OpenFile(const char *pFilename, int Flags, char *pBuffer = 0, int BufferSize = 0) + { + char aBuffer[1024]; + if(!pBuffer) + { + pBuffer = aBuffer; + BufferSize = sizeof(aBuffer); + } + + if(Flags&IOFLAG_WRITE) + { + str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename); + return io_open(pBuffer, Flags); + } + else + { + IOHANDLE Handle = 0; + + // check current directory + Handle = io_open(pFilename, Flags); + if(Handle) + return Handle; + + // check user directory + str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename); + Handle = io_open(pBuffer, Flags); + if(Handle) + return Handle; + + // check normal data directory + str_format(pBuffer, BufferSize, "%s/%s", m_aDatadir, pFilename); + Handle = io_open(pBuffer, Flags); + if(Handle) + return Handle; + } + + pBuffer[0] = 0; + return 0; + } + + static IStorage *Create(const char *pApplicationName, const char *pArgv0) + { + CStorage *p = new CStorage(); + if(p->Init(pApplicationName, pArgv0)) + { + delete p; + p = 0; + } + return p; + } +}; + +IStorage *CreateStorage(const char *pApplicationName, const char *pArgv0) { return CStorage::Create(pApplicationName, pArgv0); } |