mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-12 23:24:41 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
59
Engine/source/app/auth.h
Normal file
59
Engine/source/app/auth.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _AUTH_H_
|
||||
#define _AUTH_H_
|
||||
|
||||
#ifndef _EVENT_H_
|
||||
#include "platform/event.h"
|
||||
#endif
|
||||
|
||||
/// Formerly contained a certificate, showing that something was valid.
|
||||
class Auth2Certificate
|
||||
{
|
||||
U32 xxx;
|
||||
};
|
||||
|
||||
/// Formerly contained data indicating whether a user is valid.
|
||||
struct AuthInfo
|
||||
{
|
||||
enum {
|
||||
MaxNameLen = 31,
|
||||
};
|
||||
|
||||
bool valid;
|
||||
char name[MaxNameLen + 1];
|
||||
};
|
||||
|
||||
/// Formerly validated the server's authentication info.
|
||||
inline bool validateAuthenticatedServer()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Formerly validated the client's authentication info.
|
||||
inline bool validateAuthenticatedClient()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
331
Engine/source/app/badWordFilter.cpp
Normal file
331
Engine/source/app/badWordFilter.cpp
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "core/strings/stringFunctions.h"
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
#include "app/badWordFilter.h"
|
||||
#include "core/module.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
MODULE_BEGIN( BadWordFilter )
|
||||
|
||||
MODULE_INIT
|
||||
{
|
||||
BadWordFilter::create();
|
||||
}
|
||||
|
||||
MODULE_SHUTDOWN
|
||||
{
|
||||
BadWordFilter::destroy();
|
||||
}
|
||||
|
||||
MODULE_END;
|
||||
|
||||
|
||||
BadWordFilter *gBadWordFilter = NULL;
|
||||
bool BadWordFilter::filteringEnabled = true;
|
||||
|
||||
BadWordFilter::BadWordFilter()
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION( filterTables );
|
||||
|
||||
dStrcpy(defaultReplaceStr, "knqwrtlzs");
|
||||
filterTables.push_back(new FilterTable);
|
||||
curOffset = 0;
|
||||
}
|
||||
|
||||
BadWordFilter::~BadWordFilter()
|
||||
{
|
||||
for(U32 i = 0; i < filterTables.size(); i++)
|
||||
delete filterTables[i];
|
||||
}
|
||||
|
||||
void BadWordFilter::create()
|
||||
{
|
||||
Con::addVariable("pref::enableBadWordFilter", TypeBool, &filteringEnabled,
|
||||
"@brief If true, the bad word filter will be enabled.\n\n"
|
||||
"@ingroup Game");
|
||||
gBadWordFilter = new BadWordFilter;
|
||||
gBadWordFilter->addBadWord("shit");
|
||||
gBadWordFilter->addBadWord("fuck");
|
||||
gBadWordFilter->addBadWord("cock");
|
||||
gBadWordFilter->addBadWord("bitch");
|
||||
gBadWordFilter->addBadWord("cunt");
|
||||
gBadWordFilter->addBadWord("nigger");
|
||||
gBadWordFilter->addBadWord("bastard");
|
||||
gBadWordFilter->addBadWord("dick");
|
||||
gBadWordFilter->addBadWord("whore");
|
||||
gBadWordFilter->addBadWord("goddamn");
|
||||
gBadWordFilter->addBadWord("asshole");
|
||||
}
|
||||
|
||||
void BadWordFilter::destroy()
|
||||
{
|
||||
delete gBadWordFilter;
|
||||
gBadWordFilter = NULL;
|
||||
}
|
||||
|
||||
|
||||
U8 BadWordFilter::remapTable[257] = "------------------------------------------------OI---------------ABCDEFGHIJKLMNOPQRSTUVWXYZ------ABCDEFGHIJKLMNOPQRSTUVWXYZ-----C--F--TT--S-C-Z-----------S-C-ZY--CLOY-S-CA---R---UT-UP--IO-----AAAAAAACEEEEIIIIDNOOOOOXOUUUUYDBAAAAAAACEEEEIIIIDNOOOOO-OUUUUYDY";
|
||||
U8 BadWordFilter::randomJunk[MaxBadwordLength+1] = "REMsg rk34n4ksqow;xnskq;KQoaWnZa";
|
||||
|
||||
BadWordFilter::FilterTable::FilterTable()
|
||||
{
|
||||
for(U32 i = 0; i < 26; i++)
|
||||
nextState[i] = TerminateNotFound;
|
||||
}
|
||||
|
||||
bool BadWordFilter::addBadWord(const char *cword)
|
||||
{
|
||||
FilterTable *curFilterTable = filterTables[0];
|
||||
// prescan the word to see if it has any skip chars
|
||||
const U8 *word = (const U8 *) cword;
|
||||
const U8 *walk = word;
|
||||
if(dStrlen(cword) > MaxBadwordLength)
|
||||
return false;
|
||||
while(*walk)
|
||||
{
|
||||
if(remapTable[*walk] == '-')
|
||||
return false;
|
||||
walk++;
|
||||
}
|
||||
while(*word)
|
||||
{
|
||||
U8 remap = remapTable[*word] - 'A';
|
||||
U16 state = curFilterTable->nextState[remap];
|
||||
|
||||
if(state < TerminateNotFound)
|
||||
{
|
||||
// this character is already in the state table...
|
||||
curFilterTable = filterTables[state];
|
||||
}
|
||||
else if(state == TerminateFound)
|
||||
{
|
||||
// a subset of this word is already in the table...
|
||||
// exit out.
|
||||
return false;
|
||||
}
|
||||
else if(state == TerminateNotFound)
|
||||
{
|
||||
if(word[1])
|
||||
{
|
||||
curFilterTable->nextState[remap] = filterTables.size();
|
||||
filterTables.push_back(new FilterTable);
|
||||
curFilterTable = filterTables[filterTables.size() - 1];
|
||||
}
|
||||
else
|
||||
curFilterTable->nextState[remap] = TerminateFound;
|
||||
}
|
||||
word++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BadWordFilter::setDefaultReplaceStr(const char *str)
|
||||
{
|
||||
U32 len = dStrlen(str);
|
||||
if(len < 2 || len >= sizeof(defaultReplaceStr))
|
||||
return false;
|
||||
dStrcpy(defaultReplaceStr, str);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BadWordFilter::filterString(char *cstring, const char *replaceStr)
|
||||
{
|
||||
if(!replaceStr)
|
||||
replaceStr = defaultReplaceStr;
|
||||
U8 *string = (U8 *) cstring;
|
||||
U8 *starts[MaxBadwordLength];
|
||||
U8 *curStart = string;
|
||||
U32 replaceLen = dStrlen(replaceStr);
|
||||
while(*curStart)
|
||||
{
|
||||
FilterTable *curFilterTable = filterTables[0];
|
||||
S32 index = 0;
|
||||
U8 *walk = curStart;
|
||||
while(*walk)
|
||||
{
|
||||
U8 remap = remapTable[*walk];
|
||||
if(remap != '-')
|
||||
{
|
||||
starts[index++] = walk;
|
||||
U16 table = curFilterTable->nextState[remap - 'A'];
|
||||
if(table < TerminateNotFound)
|
||||
curFilterTable = filterTables[table];
|
||||
else if(table == TerminateNotFound)
|
||||
{
|
||||
curStart++;
|
||||
break;
|
||||
}
|
||||
else // terminate found
|
||||
{
|
||||
for(U32 i = 0; i < index; i++)
|
||||
{
|
||||
starts[i][0] = (U8 )replaceStr[curOffset % replaceLen];
|
||||
curOffset += randomJunk[curOffset & (MaxBadwordLength - 1)];
|
||||
}
|
||||
curStart = walk + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
walk++;
|
||||
}
|
||||
if(!*walk)
|
||||
curStart++;
|
||||
}
|
||||
}
|
||||
|
||||
bool BadWordFilter::containsBadWords(const char *cstring)
|
||||
{
|
||||
U8 *string = (U8 *) cstring;
|
||||
U8 *curStart = string;
|
||||
while(*curStart)
|
||||
{
|
||||
FilterTable *curFilterTable = filterTables[0];
|
||||
U8 *walk = curStart;
|
||||
while(*walk)
|
||||
{
|
||||
U8 remap = remapTable[*walk];
|
||||
if(remap != '-')
|
||||
{
|
||||
U16 table = curFilterTable->nextState[remap - 'A'];
|
||||
if(table < TerminateNotFound)
|
||||
curFilterTable = filterTables[table];
|
||||
else if(table == TerminateNotFound)
|
||||
{
|
||||
curStart++;
|
||||
break;
|
||||
}
|
||||
else // terminate found
|
||||
return true;
|
||||
}
|
||||
walk++;
|
||||
}
|
||||
if(!*walk)
|
||||
curStart++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DefineEngineFunction(addBadWord, bool, (const char* badWord),,
|
||||
"@brief Add a string to the bad word filter\n\n"
|
||||
|
||||
"The bad word filter is a table containing words which will not be "
|
||||
"displayed in chat windows. Instead, a designated replacement string will be displayed. "
|
||||
"There are already a number of bad words automatically defined.\n\n"
|
||||
|
||||
"@param badWord Exact text of the word to restrict.\n"
|
||||
"@return True if word was successfully added, false if the word or a subset of it already exists in the table\n"
|
||||
|
||||
"@see filterString()\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this game, \"Foobar\" is banned\n"
|
||||
"%badWord = \"Foobar\";\n\n"
|
||||
"// Returns true, word was successfully added\n"
|
||||
"addBadWord(%badWord);\n\n"
|
||||
"// Returns false, word has already been added\n"
|
||||
"addBadWord(\"Foobar\");"
|
||||
"@endtsexample\n"
|
||||
|
||||
"@ingroup Game")
|
||||
{
|
||||
return gBadWordFilter->addBadWord(badWord);
|
||||
}
|
||||
|
||||
DefineEngineFunction(filterString, const char *, (const char* baseString, const char* replacementChars), (NULL, NULL),
|
||||
"@brief Replaces the characters in a string with designated text\n\n"
|
||||
|
||||
"Uses the bad word filter to determine which characters within the string will be replaced.\n\n"
|
||||
|
||||
"@param baseString The original string to filter.\n"
|
||||
"@param replacementChars A string containing letters you wish to swap in the baseString.\n"
|
||||
"@return The new scrambled string \n"
|
||||
|
||||
"@see addBadWord()\n"
|
||||
"@see containsBadWords()\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create the base string, can come from anywhere\n"
|
||||
"%baseString = \"Foobar\";\n\n"
|
||||
"// Create a string of random letters\n"
|
||||
"%replacementChars = \"knqwrtlzs\";\n\n"
|
||||
"// Filter the string\n"
|
||||
"%newString = filterString(%baseString, %replacementChars);\n\n"
|
||||
"// Print the new string to console\n"
|
||||
"echo(%newString);"
|
||||
"@endtsexample\n"
|
||||
|
||||
"@ingroup Game")
|
||||
{
|
||||
const char *replaceStr = NULL;
|
||||
|
||||
if(replacementChars)
|
||||
replaceStr = replacementChars;
|
||||
else
|
||||
replaceStr = gBadWordFilter->getDefaultReplaceStr();
|
||||
|
||||
char *ret = Con::getReturnBuffer(dStrlen(baseString) + 1);
|
||||
dStrcpy(ret, baseString);
|
||||
gBadWordFilter->filterString(ret, replaceStr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DefineEngineFunction(containsBadWords, bool, (const char* text),,
|
||||
"@brief Checks to see if text is a bad word\n\n"
|
||||
|
||||
"The text is considered to be a bad word if it has been added to the bad word filter.\n\n"
|
||||
|
||||
"@param text Text to scan for bad words\n"
|
||||
"@return True if the text has bad word(s), false if it is clean\n"
|
||||
|
||||
"@see addBadWord()\n"
|
||||
"@see filterString()\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this game, \"Foobar\" is banned\n"
|
||||
"%badWord = \"Foobar\";\n\n"
|
||||
"// Add a banned word to the bad word filter\n"
|
||||
"addBadWord(%badWord);\n\n"
|
||||
"// Create the base string, can come from anywhere like user chat\n"
|
||||
"%userText = \"Foobar\";\n\n"
|
||||
"// Create a string of random letters\n"
|
||||
"%replacementChars = \"knqwrtlzs\";\n\n"
|
||||
"// If the text contains a bad word, filter it before printing\n"
|
||||
"// Otherwise print the original text\n"
|
||||
"if(containsBadWords(%userText))\n"
|
||||
"{\n"
|
||||
" // Filter the string\n"
|
||||
" %filteredText = filterString(%userText, %replacementChars);\n\n"
|
||||
" // Print filtered text\n"
|
||||
" echo(%filteredText);\n"
|
||||
"}\n"
|
||||
"else\n"
|
||||
" echo(%userText);\n\n"
|
||||
"@endtsexample\n"
|
||||
|
||||
"@ingroup Game")
|
||||
{
|
||||
return gBadWordFilter->containsBadWords(text);
|
||||
}
|
||||
|
||||
67
Engine/source/app/badWordFilter.h
Normal file
67
Engine/source/app/badWordFilter.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _H_BADWORDFILTER
|
||||
#define _H_BADWORDFILTER
|
||||
|
||||
#include "core/util/tVector.h"
|
||||
|
||||
class BadWordFilter
|
||||
{
|
||||
private:
|
||||
struct FilterTable
|
||||
{
|
||||
U16 nextState[26]; // only 26 alphabetical chars.
|
||||
FilterTable();
|
||||
};
|
||||
friend struct FilterTable;
|
||||
Vector<FilterTable*> filterTables;
|
||||
|
||||
enum {
|
||||
TerminateNotFound = 0xFFFE,
|
||||
TerminateFound = 0xFFFF,
|
||||
MaxBadwordLength = 32,
|
||||
};
|
||||
char defaultReplaceStr[32];
|
||||
|
||||
BadWordFilter();
|
||||
~BadWordFilter();
|
||||
U32 curOffset;
|
||||
static U8 remapTable[257];
|
||||
static U8 randomJunk[MaxBadwordLength + 1];
|
||||
static bool filteringEnabled;
|
||||
|
||||
public:
|
||||
bool addBadWord(const char *word);
|
||||
bool setDefaultReplaceStr(const char *str);
|
||||
const char* getDefaultReplaceStr(){ return defaultReplaceStr; }
|
||||
void filterString(char *string, const char *replaceStr = NULL);
|
||||
bool containsBadWords(const char *string);
|
||||
|
||||
static bool isEnabled() { return filteringEnabled; }
|
||||
static void setEnabled(bool enable) { filteringEnabled = enable; }
|
||||
static void create();
|
||||
static void destroy();
|
||||
};
|
||||
|
||||
extern BadWordFilter *gBadWordFilter;
|
||||
|
||||
#endif
|
||||
301
Engine/source/app/banList.cpp
Normal file
301
Engine/source/app/banList.cpp
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/banList.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "core/module.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
|
||||
IMPLEMENT_STATIC_CLASS( BanList,, "Functions for maintaing a list of banned users." );
|
||||
|
||||
ConsoleDoc(
|
||||
"@class BanList\n"
|
||||
"@ingroup Miscellaneous\n"
|
||||
"@brief Used for kicking and banning players from a server.\n"
|
||||
"There is only a single instance of BanList. It is very important to note that you do not ever create this object in script "
|
||||
"like you would other game play objects. You simply reference it via namespace.\n\n"
|
||||
"For this to be used effectively, make sure you are hooking up other functions to BanList. "
|
||||
"For example, functions like GameConnection::onConnectRequestRejected( %this, %msg ) and function GameConnection::onConnectRequest are excellent "
|
||||
"places to make use of the BanList. Other systems can be used in conjunction for strict control over a server\n\n"
|
||||
"@see addBadWord\n"
|
||||
"@see containsBadWords\n"
|
||||
);
|
||||
|
||||
BanList* BanList::smInstance;
|
||||
|
||||
MODULE_BEGIN( BanList )
|
||||
MODULE_INIT
|
||||
{
|
||||
new BanList;
|
||||
}
|
||||
MODULE_SHUTDOWN
|
||||
{
|
||||
delete BanList::instance();
|
||||
}
|
||||
MODULE_END;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
BanList::BanList()
|
||||
{
|
||||
AssertFatal( !smInstance, "BanList::BanList - already instantiated" );
|
||||
VECTOR_SET_ASSOCIATION( list );
|
||||
|
||||
smInstance = this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void BanList::addBan(S32 uniqueId, const char *TA, S32 banTime)
|
||||
{
|
||||
S32 curTime = Platform::getTime();
|
||||
|
||||
if(banTime != 0 && banTime < curTime)
|
||||
return;
|
||||
|
||||
// make sure this bastard isn't already banned on this server
|
||||
Vector<BanInfo>::iterator i;
|
||||
for(i = list.begin();i != list.end();i++)
|
||||
{
|
||||
if(uniqueId == i->uniqueId)
|
||||
{
|
||||
i->bannedUntil = banTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BanInfo b;
|
||||
dStrcpy(b.transportAddress, TA);
|
||||
b.uniqueId = uniqueId;
|
||||
b.bannedUntil = banTime;
|
||||
|
||||
if(!dStrnicmp(b.transportAddress, "ip:", 3))
|
||||
{
|
||||
char *c = dStrchr(b.transportAddress+3, ':');
|
||||
if(c)
|
||||
{
|
||||
*(c+1) = '*';
|
||||
*(c+2) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
list.push_back(b);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void BanList::addBanRelative(S32 uniqueId, const char *TA, S32 numSeconds)
|
||||
{
|
||||
S32 curTime = Platform::getTime();
|
||||
S32 banTime = 0;
|
||||
if(numSeconds != -1)
|
||||
banTime = curTime + numSeconds;
|
||||
|
||||
addBan(uniqueId, TA, banTime);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void BanList::removeBan(S32 uniqueId, const char *)
|
||||
{
|
||||
Vector<BanInfo>::iterator i;
|
||||
for(i = list.begin();i != list.end();i++)
|
||||
{
|
||||
if(uniqueId == i->uniqueId)
|
||||
{
|
||||
list.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool BanList::isBanned(S32 uniqueId, const char *)
|
||||
{
|
||||
S32 curTime = Platform::getTime();
|
||||
|
||||
Vector<BanInfo>::iterator i;
|
||||
for(i = list.begin();i != list.end();)
|
||||
{
|
||||
if(i->bannedUntil != 0 && i->bannedUntil < curTime)
|
||||
{
|
||||
list.erase(i);
|
||||
continue;
|
||||
}
|
||||
else if(uniqueId == i->uniqueId)
|
||||
return true;
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool BanList::isTAEq(const char *bannedTA, const char *TA)
|
||||
{
|
||||
char a, b;
|
||||
for(;;)
|
||||
{
|
||||
a = *bannedTA++;
|
||||
b = *TA++;
|
||||
if(a == '*' || (!a && b == ':')) // ignore port
|
||||
return true;
|
||||
if(dTolower(a) != dTolower(b))
|
||||
return false;
|
||||
if(!a)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void BanList::exportToFile(const char *name)
|
||||
{
|
||||
FileStream *banlist;
|
||||
|
||||
char filename[1024];
|
||||
Con::expandScriptFilename(filename, sizeof(filename), name);
|
||||
if((banlist = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL)
|
||||
return;
|
||||
|
||||
char buf[1024];
|
||||
Vector<BanInfo>::iterator i;
|
||||
for(i = list.begin(); i != list.end(); i++)
|
||||
{
|
||||
dSprintf(buf, sizeof(buf), "BanList::addAbsolute(%d, \"%s\", %d);\r\n", i->uniqueId, i->transportAddress, i->bannedUntil);
|
||||
banlist->write(dStrlen(buf), buf);
|
||||
}
|
||||
|
||||
delete banlist;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// API.
|
||||
//=============================================================================
|
||||
// MARK: ---- API ----
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DefineEngineStaticMethod( BanList, addAbsolute, void, ( S32 uniqueId, const char* transportAddress, S32 banTime ),,
|
||||
"Ban a user until a given time.\n\n"
|
||||
"@param uniqueId Unique ID of the player.\n"
|
||||
"@param transportAddress Address from which the player connected.\n"
|
||||
"@param banTime Time at which they will be allowed back in."
|
||||
"@tsexample\n"
|
||||
"// Kick someone off the server\n"
|
||||
"// %client - This is the connection to the person we are kicking\n"
|
||||
"function kick(%client)\n"
|
||||
"{\n"
|
||||
" // Let the server know what happened\n"
|
||||
" messageAll( 'MsgAdminForce', '\\c2The Admin has kicked %1.', %client.playerName);\n\n"
|
||||
" // If it is not an AI Player, execute the ban.\n"
|
||||
" if (!%client.isAIControlled())\n"
|
||||
" BanList::addAbsolute(%client.guid, %client.getAddress(), $pref::Server::KickBanTime);\n\n"
|
||||
" // Let the player know they messed up\n"
|
||||
" %client.delete(\"You have been kicked from this server\");\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
BanList::instance()->addBan( uniqueId, transportAddress, banTime );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DefineEngineStaticMethod( BanList, add, void, ( S32 uniqueId, const char* transportAddress, S32 banLength ),,
|
||||
"Ban a user for banLength seconds.\n\n"
|
||||
"@param uniqueId Unique ID of the player.\n"
|
||||
"@param transportAddress Address from which the player connected.\n"
|
||||
"@param banLength Time period over which to ban the player."
|
||||
"@tsexample\n"
|
||||
"// Kick someone off the server\n"
|
||||
"// %client - This is the connection to the person we are kicking\n"
|
||||
"function kick(%client)\n"
|
||||
"{\n"
|
||||
" // Let the server know what happened\n"
|
||||
" messageAll( 'MsgAdminForce', '\\c2The Admin has kicked %1.', %client.playerName);\n\n"
|
||||
" // If it is not an AI Player, execute the ban.\n"
|
||||
" if (!%client.isAIControlled())\n"
|
||||
" BanList::add(%client.guid, %client.getAddress(), $pref::Server::KickBanTime);\n\n"
|
||||
" // Let the player know they messed up\n"
|
||||
" %client.delete(\"You have been kicked from this server\");\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
BanList::instance()->addBanRelative( uniqueId, transportAddress, banLength );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DefineEngineStaticMethod( BanList, removeBan, void, ( S32 uniqueId, const char* transportAddress ),,
|
||||
"Unban someone.\n\n"
|
||||
"@param uniqueId Unique ID of the player.\n"
|
||||
"@param transportAddress Address from which the player connected.\n"
|
||||
"@tsexample\n"
|
||||
"BanList::removeBan(%userID, %ipAddress);\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
BanList::instance()->removeBan( uniqueId, transportAddress );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DefineEngineStaticMethod( BanList, isBanned, bool, ( S32 uniqueId, const char* transportAddress ),,
|
||||
"Is someone banned?\n\n"
|
||||
"@param uniqueId Unique ID of the player.\n"
|
||||
"@param transportAddress Address from which the player connected.\n\n"
|
||||
"@tsexample\n"
|
||||
"//-----------------------------------------------------------------------------\n"
|
||||
"// This script function is called before a client connection\n"
|
||||
"// is accepted. Returning "" will accept the connection,\n"
|
||||
"// anything else will be sent back as an error to the client.\n"
|
||||
"// All the connect args are passed also to onConnectRequest\n"
|
||||
"function GameConnection::onConnectRequest( %client, %netAddress, %name )\n"
|
||||
"{\n"
|
||||
" // Find out who is trying to connect\n"
|
||||
" echo(\"Connect request from: \" @ %netAddress);\n\n"
|
||||
" // Are they allowed in?\n"
|
||||
" if(BanList::isBanned(%client.guid, %netAddress))\n"
|
||||
" return \"CR_YOUAREBANNED\";\n\n"
|
||||
" // Is there room for an unbanned player?\n"
|
||||
" if($Server::PlayerCount >= $pref::Server::MaxPlayers)\n"
|
||||
" return \"CR_SERVERFULL\";\n"
|
||||
" return "";\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
return BanList::instance()->isBanned( uniqueId, transportAddress );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
DefineEngineStaticMethod( BanList, export, void, ( const char* filename ),,
|
||||
"Dump the banlist to a file.\n\n"
|
||||
"@param filename Path of the file to write the list to.\n\n"
|
||||
"@tsexample\n"
|
||||
"BanList::Export(\"./server/banlist.cs\");\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
BanList::instance()->exportToFile( filename );
|
||||
}
|
||||
67
Engine/source/app/banList.h
Normal file
67
Engine/source/app/banList.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _BANLIST_H_
|
||||
#define _BANLIST_H_
|
||||
|
||||
#ifndef _ENGINEAPI_H_
|
||||
#include "console/engineAPI.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
|
||||
/// Helper class to keep track of bans.
|
||||
class BanList
|
||||
{
|
||||
public:
|
||||
|
||||
DECLARE_STATIC_CLASS( BanList );
|
||||
|
||||
struct BanInfo
|
||||
{
|
||||
S32 uniqueId;
|
||||
char transportAddress[128];
|
||||
S32 bannedUntil;
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
Vector< BanInfo > list;
|
||||
|
||||
static BanList* smInstance;
|
||||
|
||||
public:
|
||||
|
||||
BanList();
|
||||
|
||||
static BanList* instance() { return smInstance; }
|
||||
|
||||
void addBan(S32 uniqueId, const char *TA, S32 banTime);
|
||||
void addBanRelative(S32 uniqueId, const char *TA, S32 numSeconds);
|
||||
void removeBan(S32 uniqueId, const char *TA);
|
||||
bool isBanned(S32 uniqueId, const char *TA);
|
||||
bool isTAEq(const char *bannedTA, const char *TA);
|
||||
void exportToFile(const char *fileName);
|
||||
};
|
||||
|
||||
#endif
|
||||
258
Engine/source/app/game.cpp
Normal file
258
Engine/source/app/game.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/platformInput.h"
|
||||
|
||||
#include "app/game.h"
|
||||
#include "math/mMath.h"
|
||||
#include "core/dnet.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "core/frameAllocator.h"
|
||||
#include "core/iTickable.h"
|
||||
#include "core/strings/findMatch.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "gui/controls/guiMLTextCtrl.h"
|
||||
#ifdef TORQUE_TGB_ONLY
|
||||
#include "T2D/oldModel/networking/t2dGameConnection.h"
|
||||
#include "T2D/oldModel/networking/t2dNetworkServerSceneProcess.h"
|
||||
#include "T2D/oldModel/networking/t2dNetworkClientSceneProcess.h"
|
||||
#else
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/gameFunctions.h"
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
#endif
|
||||
#include "platform/profiler.h"
|
||||
#include "gfx/gfxCubemap.h"
|
||||
#include "gfx/gfxTextureManager.h"
|
||||
#include "sfx/sfxSystem.h"
|
||||
|
||||
#ifdef TORQUE_PLAYER
|
||||
// See matching #ifdef in editor/editor.cpp
|
||||
bool gEditingMission = false;
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ConsoleFunctionGroupBegin( InputManagement, "Functions that let you deal with input from scripts" );
|
||||
|
||||
ConsoleFunction( deactivateDirectInput, void, 1, 1, "()"
|
||||
"@brief Disables DirectInput.\n\n"
|
||||
"Also deactivates any connected joysticks.\n\n"
|
||||
"@ingroup Input" )
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
if ( Input::isActive() )
|
||||
Input::deactivate();
|
||||
}
|
||||
|
||||
ConsoleFunction( activateDirectInput, void, 1, 1,"()"
|
||||
"@brief Activates DirectInput.\n\n"
|
||||
"Also activates any connected joysticks."
|
||||
"@ingroup Input")
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
if ( !Input::isActive() )
|
||||
Input::activate();
|
||||
}
|
||||
ConsoleFunctionGroupEnd( InputManagement );
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static const U32 MaxPlayerNameLength = 16;
|
||||
ConsoleFunction( strToPlayerName, const char*, 2, 2, "strToPlayerName( string )" )
|
||||
{
|
||||
TORQUE_UNUSED(argc);
|
||||
|
||||
const char* ptr = argv[1];
|
||||
|
||||
// Strip leading spaces and underscores:
|
||||
while ( *ptr == ' ' || *ptr == '_' )
|
||||
ptr++;
|
||||
|
||||
U32 len = dStrlen( ptr );
|
||||
if ( len )
|
||||
{
|
||||
char* ret = Con::getReturnBuffer( MaxPlayerNameLength + 1 );
|
||||
char* rptr = ret;
|
||||
ret[MaxPlayerNameLength - 1] = '\0';
|
||||
ret[MaxPlayerNameLength] = '\0';
|
||||
bool space = false;
|
||||
|
||||
U8 ch;
|
||||
while ( *ptr && dStrlen( ret ) < MaxPlayerNameLength )
|
||||
{
|
||||
ch = (U8) *ptr;
|
||||
|
||||
// Strip all illegal characters:
|
||||
if ( ch < 32 || ch == ',' || ch == '.' || ch == '\'' || ch == '`' )
|
||||
{
|
||||
ptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't allow double spaces or space-underline combinations:
|
||||
if ( ch == ' ' || ch == '_' )
|
||||
{
|
||||
if ( space )
|
||||
{
|
||||
ptr++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
space = true;
|
||||
}
|
||||
else
|
||||
space = false;
|
||||
|
||||
*rptr++ = *ptr;
|
||||
ptr++;
|
||||
}
|
||||
*rptr = '\0';
|
||||
|
||||
//finally, strip out the ML text control chars...
|
||||
return GuiMLTextCtrl::stripControlChars(ret);
|
||||
}
|
||||
|
||||
return( "" );
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin( Platform , "General platform functions.");
|
||||
|
||||
ConsoleFunction( lockMouse, void, 2, 2, "(bool isLocked)"
|
||||
"@brief Lock or unlock the mouse to the window.\n\n"
|
||||
"When true, prevents the mouse from leaving the bounds of the game window.\n\n"
|
||||
"@ingroup Input")
|
||||
{
|
||||
Platform::setWindowLocked(dAtob(argv[1]));
|
||||
}
|
||||
|
||||
|
||||
ConsoleFunction( setNetPort, bool, 2, 3, "(int port, bool bind=true)"
|
||||
"@brief Set the network port for the game to use.\n\n"
|
||||
|
||||
"@param port The port to use.\n"
|
||||
"@param bind True if bind() should be called on the port.\n"
|
||||
|
||||
"@returns True if the port was successfully opened.\n"
|
||||
|
||||
"This will trigger a windows firewall prompt. "
|
||||
"If you don't have firewall tunneling tech you can set this to false to avoid the prompt.\n\n"
|
||||
"@ingroup Networking")
|
||||
{
|
||||
bool bind = true;
|
||||
if (argc == 3)
|
||||
bind = dAtob(argv[2]);
|
||||
return Net::openPort(dAtoi(argv[1]), bind);
|
||||
}
|
||||
|
||||
ConsoleFunction( closeNetPort, void, 1, 1, "()"
|
||||
"@brief Closes the current network port\n\n"
|
||||
"@ingroup Networking")
|
||||
{
|
||||
Net::closePort();
|
||||
}
|
||||
|
||||
ConsoleFunction( saveJournal, void, 2, 2, "(string filename)"
|
||||
"Save the journal to the specified file.\n\n"
|
||||
"@ingroup Platform")
|
||||
{
|
||||
Journal::Record(argv[1]);
|
||||
}
|
||||
|
||||
ConsoleFunction( playJournal, void, 2, 3, "(string filename)"
|
||||
"@brief Begin playback of a journal from a specified field.\n\n"
|
||||
"@param filename Name and path of file journal file\n"
|
||||
"@ingroup Platform")
|
||||
{
|
||||
// CodeReview - BJG 4/24/2007 - The break flag needs to be wired back in.
|
||||
// bool jBreak = (argc > 2)? dAtob(argv[2]): false;
|
||||
Journal::Play(argv[1]);
|
||||
}
|
||||
|
||||
ConsoleFunction( getSimTime, S32, 1, 1, "()"
|
||||
"Return the current sim time in milliseconds.\n\n"
|
||||
"@brief Sim time is time since the game started.\n\n"
|
||||
"@ingroup Platform")
|
||||
{
|
||||
return Sim::getCurrentTime();
|
||||
}
|
||||
|
||||
ConsoleFunction( getRealTime, S32, 1, 1, "()"
|
||||
"@brief Return the current real time in milliseconds.\n\n"
|
||||
"Real time is platform defined; typically time since the computer booted.\n\n"
|
||||
"@ingroup Platform")
|
||||
{
|
||||
return Platform::getRealMilliseconds();
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd(Platform);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool clientProcess(U32 timeDelta)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
#ifndef TORQUE_TGB_ONLY
|
||||
ret = ClientProcessList::get()->advanceTime(timeDelta);
|
||||
#else
|
||||
ret = gt2dNetworkClientProcess.advanceTime( timeDelta );
|
||||
#endif
|
||||
|
||||
ITickable::advanceTime(timeDelta);
|
||||
|
||||
#ifndef TORQUE_TGB_ONLY
|
||||
// Determine if we're lagging
|
||||
GameConnection* connection = GameConnection::getConnectionToServer();
|
||||
if(connection)
|
||||
{
|
||||
connection->detectLag();
|
||||
}
|
||||
#else
|
||||
// Determine if we're lagging
|
||||
t2dGameConnection* connection = t2dGameConnection::getConnectionToServer();
|
||||
if(connection)
|
||||
{
|
||||
connection->detectLag();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Let SFX process.
|
||||
SFX->_update();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool serverProcess(U32 timeDelta)
|
||||
{
|
||||
bool ret = true;
|
||||
#ifndef TORQUE_TGB_ONLY
|
||||
ret = ServerProcessList::get()->advanceTime(timeDelta);
|
||||
#else
|
||||
ret = gt2dNetworkServerProcess.advanceTime( timeDelta );
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
37
Engine/source/app/game.h
Normal file
37
Engine/source/app/game.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _GAME_H_
|
||||
#define _GAME_H_
|
||||
|
||||
#ifndef _TORQUE_TYPES_H_
|
||||
#include "platform/types.h"
|
||||
#endif
|
||||
|
||||
/// Processes the next frame, including gui, rendering, and tick interpolation.
|
||||
/// This function will only have an effect when executed on the client.
|
||||
bool clientProcess(U32 timeDelta);
|
||||
|
||||
/// Processes the next cycle on the server. This function will only have an effect when executed on the server.
|
||||
bool serverProcess(U32 timeDelta);
|
||||
|
||||
#endif
|
||||
627
Engine/source/app/mainLoop.cpp
Normal file
627
Engine/source/app/mainLoop.cpp
Normal file
|
|
@ -0,0 +1,627 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/mainLoop.h"
|
||||
#include "app/game.h"
|
||||
|
||||
#include "platform/platformTimer.h"
|
||||
#include "platform/platformRedBook.h"
|
||||
#include "platform/platformVolume.h"
|
||||
#include "platform/platformMemory.h"
|
||||
#include "platform/platformTimer.h"
|
||||
#include "platform/platformNet.h"
|
||||
#include "platform/nativeDialogs/fileDialog.h"
|
||||
#include "platform/threads/thread.h"
|
||||
|
||||
#include "core/module.h"
|
||||
#include "core/threadStatic.h"
|
||||
#include "core/iTickable.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
|
||||
#include "windowManager/platformWindowMgr.h"
|
||||
|
||||
#include "core/util/journal/process.h"
|
||||
#include "util/fpsTracker.h"
|
||||
|
||||
#include "console/debugOutputConsumer.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
#include "gfx/bitmap/gBitmap.h"
|
||||
#include "gfx/gFont.h"
|
||||
#include "gfx/video/videoCapture.h"
|
||||
#include "gfx/gfxTextureManager.h"
|
||||
|
||||
#include "sim/netStringTable.h"
|
||||
#include "sim/actionMap.h"
|
||||
#include "sim/netInterface.h"
|
||||
|
||||
#include "util/sampler.h"
|
||||
#include "platform/threads/threadPool.h"
|
||||
|
||||
// For the TickMs define... fix this for T2D...
|
||||
#include "T3D/gameBase/processList.h"
|
||||
|
||||
#ifdef TORQUE_DEMO_PURCHASE
|
||||
#include "demo/pestTimer/pestTimer.h"
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
#include "platform/platformVFS.h"
|
||||
#endif
|
||||
|
||||
DITTS( F32, gTimeScale, 1.0 );
|
||||
DITTS( U32, gTimeAdvance, 0 );
|
||||
DITTS( U32, gFrameSkip, 0 );
|
||||
|
||||
extern S32 sgBackgroundProcessSleepTime;
|
||||
extern S32 sgTimeManagerProcessInterval;
|
||||
|
||||
extern FPSTracker gFPS;
|
||||
|
||||
TimeManager* tm = NULL;
|
||||
|
||||
static bool gRequiresRestart = false;
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
|
||||
/// Temporary timer used to time startup times.
|
||||
static PlatformTimer* gStartupTimer;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE )
|
||||
StringTableEntry gMiniDumpDir;
|
||||
StringTableEntry gMiniDumpExec;
|
||||
StringTableEntry gMiniDumpParams;
|
||||
StringTableEntry gMiniDumpExecDir;
|
||||
#endif
|
||||
|
||||
|
||||
namespace engineAPI
|
||||
{
|
||||
// This is the magic switch for deciding which interop the engine
|
||||
// should use. It will go away when we drop the console system
|
||||
// entirely but for now it is necessary for several behaviors that
|
||||
// differ between the interops to decide what to do.
|
||||
bool gUseConsoleInterop = true;
|
||||
|
||||
bool gIsInitialized = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The following are some tricks to make the memory leak checker run after global
|
||||
// dtors have executed by placing some code in the termination segments.
|
||||
|
||||
#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER )
|
||||
|
||||
#ifdef TORQUE_COMPILER_VISUALC
|
||||
# pragma data_seg( ".CRT$XTU" )
|
||||
|
||||
static void* sCheckMemBeforeTermination = &Memory::ensureAllFreed;
|
||||
|
||||
# pragma data_seg()
|
||||
#elif defined( TORQUE_COMPILER_GCC )
|
||||
|
||||
__attribute__ ( ( destructor ) ) static void _ensureAllFreed()
|
||||
{
|
||||
Memory::ensureAllFreed();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// Process a time event and update all sub-processes
|
||||
void processTimeEvent(S32 elapsedTime)
|
||||
{
|
||||
PROFILE_START(ProcessTimeEvent);
|
||||
|
||||
// If recording a video and not playinb back a journal, override the elapsedTime
|
||||
if (VIDCAP->isRecording() && !Journal::IsPlaying())
|
||||
elapsedTime = VIDCAP->getMsPerFrame();
|
||||
|
||||
// cap the elapsed time to one second
|
||||
// if it's more than that we're probably in a bad catch-up situation
|
||||
if(elapsedTime > 1024)
|
||||
elapsedTime = 1024;
|
||||
|
||||
U32 timeDelta;
|
||||
if(ATTS(gTimeAdvance))
|
||||
timeDelta = ATTS(gTimeAdvance);
|
||||
else
|
||||
timeDelta = (U32) (elapsedTime * ATTS(gTimeScale));
|
||||
|
||||
Platform::advanceTime(elapsedTime);
|
||||
|
||||
// Don't build up more time than a single tick... this makes the sim
|
||||
// frame rate dependent but is a useful hack for singleplayer.
|
||||
if ( ATTS(gFrameSkip) )
|
||||
if ( timeDelta > TickMs )
|
||||
timeDelta = TickMs;
|
||||
|
||||
bool tickPass;
|
||||
|
||||
PROFILE_START(ServerProcess);
|
||||
tickPass = serverProcess(timeDelta);
|
||||
PROFILE_END();
|
||||
|
||||
PROFILE_START(ServerNetProcess);
|
||||
// only send packets if a tick happened
|
||||
if(tickPass)
|
||||
GNet->processServer();
|
||||
// Used to indicate if server was just ticked.
|
||||
Con::setBoolVariable( "$pref::hasServerTicked", tickPass );
|
||||
PROFILE_END();
|
||||
|
||||
|
||||
PROFILE_START(SimAdvanceTime);
|
||||
Sim::advanceTime(timeDelta);
|
||||
PROFILE_END();
|
||||
|
||||
PROFILE_START(ClientProcess);
|
||||
tickPass = clientProcess(timeDelta);
|
||||
// Used to indicate if client was just ticked.
|
||||
Con::setBoolVariable( "$pref::hasClientTicked", tickPass );
|
||||
PROFILE_END_NAMED(ClientProcess);
|
||||
|
||||
PROFILE_START(ClientNetProcess);
|
||||
if(tickPass)
|
||||
GNet->processClient();
|
||||
PROFILE_END();
|
||||
|
||||
GNet->checkTimeouts();
|
||||
|
||||
gFPS.update();
|
||||
|
||||
// Give the texture manager a chance to cleanup any
|
||||
// textures that haven't been referenced for a bit.
|
||||
if( GFX )
|
||||
TEXMGR->cleanupCache( 5 );
|
||||
|
||||
PROFILE_END();
|
||||
|
||||
// Update the console time
|
||||
Con::setFloatVariable("Sim::Time",F32(Platform::getVirtualMilliseconds()) / 1000);
|
||||
}
|
||||
|
||||
void StandardMainLoop::init()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
gStartupTimer = PlatformTimer::create();
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_DEBUG_GUARD
|
||||
Memory::flagCurrentAllocs( Memory::FLAG_Global );
|
||||
#endif
|
||||
|
||||
Platform::setMathControlStateKnown();
|
||||
|
||||
// Asserts should be created FIRST
|
||||
PlatformAssert::create();
|
||||
|
||||
ManagedSingleton< ThreadManager >::createSingleton();
|
||||
FrameAllocator::init(TORQUE_FRAME_SIZE); // See comments in torqueConfig.h
|
||||
|
||||
// Yell if we can't initialize the network.
|
||||
if(!Net::init())
|
||||
{
|
||||
AssertISV(false, "StandardMainLoop::initCore - could not initialize networking!");
|
||||
}
|
||||
|
||||
_StringTable::create();
|
||||
|
||||
// Set up the resource manager and get some basic file types in it.
|
||||
Con::init();
|
||||
Platform::initConsole();
|
||||
NetStringTable::create();
|
||||
|
||||
// Use debug output logging on the Xbox and OSX builds
|
||||
#if defined( _XBOX ) || defined( TORQUE_OS_MAC )
|
||||
DebugOutputConsumer::init();
|
||||
#endif
|
||||
|
||||
Processor::init();
|
||||
Math::init();
|
||||
Platform::init(); // platform specific initialization
|
||||
RedBook::init();
|
||||
Platform::initConsole();
|
||||
|
||||
ThreadPool::GlobalThreadPool::createSingleton();
|
||||
|
||||
// Initialize modules.
|
||||
|
||||
ModuleManager::initializeSystem();
|
||||
|
||||
// Initialise ITickable.
|
||||
#ifdef TORQUE_TGB_ONLY
|
||||
ITickable::init( 4 );
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
// [tom, 10/28/2006] Load the VFS here so that it stays loaded
|
||||
Zip::ZipArchive *vfs = openEmbeddedVFSArchive();
|
||||
gResourceManager->addVFSRoot(vfs);
|
||||
#endif
|
||||
|
||||
Con::addVariable("timeScale", TypeF32, &ATTS(gTimeScale), "Animation time scale.\n"
|
||||
"@ingroup platform");
|
||||
Con::addVariable("timeAdvance", TypeS32, &ATTS(gTimeAdvance), "The speed at which system processing time advances.\n"
|
||||
"@ingroup platform");
|
||||
Con::addVariable("frameSkip", TypeS32, &ATTS(gFrameSkip), "Sets the number of frames to skip while rendering the scene.\n"
|
||||
"@ingroup platform");
|
||||
|
||||
Con::setVariable( "defaultGame", StringTable->insert("scripts") );
|
||||
|
||||
Con::addVariable( "_forceAllMainThread", TypeBool, &ThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n"
|
||||
"@ingroup platform" );
|
||||
|
||||
#if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE )
|
||||
Con::addVariable("MiniDump::Dir", TypeString, &gMiniDumpDir);
|
||||
Con::addVariable("MiniDump::Exec", TypeString, &gMiniDumpExec);
|
||||
Con::addVariable("MiniDump::Params", TypeString, &gMiniDumpParams);
|
||||
Con::addVariable("MiniDump::ExecDir", TypeString, &gMiniDumpExecDir);
|
||||
#endif
|
||||
|
||||
ActionMap* globalMap = new ActionMap;
|
||||
globalMap->registerObject("GlobalActionMap");
|
||||
Sim::getActiveActionMapSet()->pushObject(globalMap);
|
||||
|
||||
// Do this before we init the process so that process notifiees can get the time manager
|
||||
tm = new TimeManager;
|
||||
tm->timeEvent.notify(&::processTimeEvent);
|
||||
|
||||
Sampler::init();
|
||||
|
||||
// Hook in for UDP notification
|
||||
Net::smPacketReceive.notify(GNet, &NetInterface::processPacketReceiveEvent);
|
||||
|
||||
#ifdef TORQUE_DEMO_PURCHASE
|
||||
PestTimerinit();
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_DEBUG_GUARD
|
||||
Memory::flagCurrentAllocs( Memory::FLAG_Static );
|
||||
#endif
|
||||
}
|
||||
|
||||
void StandardMainLoop::shutdown()
|
||||
{
|
||||
delete tm;
|
||||
preShutdown();
|
||||
|
||||
// Shut down modules.
|
||||
|
||||
ModuleManager::shutdownSystem();
|
||||
|
||||
ThreadPool::GlobalThreadPool::deleteSingleton();
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
closeEmbeddedVFSArchive();
|
||||
#endif
|
||||
|
||||
RedBook::destroy();
|
||||
|
||||
Platform::shutdown();
|
||||
|
||||
#if defined( _XBOX ) || defined( TORQUE_OS_MAC )
|
||||
DebugOutputConsumer::destroy();
|
||||
#endif
|
||||
|
||||
NetStringTable::destroy();
|
||||
Con::shutdown();
|
||||
|
||||
_StringTable::destroy();
|
||||
FrameAllocator::destroy();
|
||||
Net::shutdown();
|
||||
Sampler::destroy();
|
||||
|
||||
ManagedSingleton< ThreadManager >::deleteSingleton();
|
||||
|
||||
// asserts should be destroyed LAST
|
||||
PlatformAssert::destroy();
|
||||
|
||||
#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER )
|
||||
Memory::validate();
|
||||
#endif
|
||||
}
|
||||
|
||||
void StandardMainLoop::preShutdown()
|
||||
{
|
||||
#ifdef TORQUE_TOOLS
|
||||
// Tools are given a chance to do pre-quit processing
|
||||
// - This is because for tools we like to do things such
|
||||
// as prompting to save changes before shutting down
|
||||
// and onExit is packaged which means we can't be sure
|
||||
// where in the shutdown namespace chain we are when using
|
||||
// onExit since some components of the tools may already be
|
||||
// destroyed that may be vital to saving changes to avoid
|
||||
// loss of work [1/5/2007 justind]
|
||||
if( Con::isFunction("onPreExit") )
|
||||
Con::executef( "onPreExit");
|
||||
#endif
|
||||
|
||||
//exec the script onExit() function
|
||||
if ( Con::isFunction( "onExit" ) )
|
||||
Con::executef("onExit");
|
||||
}
|
||||
|
||||
bool StandardMainLoop::handleCommandLine( S32 argc, const char **argv )
|
||||
{
|
||||
// Allow the window manager to process command line inputs; this is
|
||||
// done to let web plugin functionality happen in a fairly transparent way.
|
||||
PlatformWindowManager::get()->processCmdLineArgs(argc, argv);
|
||||
|
||||
Process::handleCommandLine( argc, argv );
|
||||
|
||||
// Set up the command line args for the console scripts...
|
||||
Con::setIntVariable("Game::argc", argc);
|
||||
U32 i;
|
||||
for (i = 0; i < argc; i++)
|
||||
Con::setVariable(avar("Game::argv%d", i), argv[i]);
|
||||
|
||||
Platform::FS::InstallFileSystems(); // install all drives for now until we have everything using the volume stuff
|
||||
Platform::FS::MountDefaults();
|
||||
|
||||
// Set our working directory.
|
||||
Torque::FS::SetCwd( "game:/" );
|
||||
|
||||
// Set our working directory.
|
||||
Platform::setCurrentDirectory( Platform::getMainDotCsDir() );
|
||||
|
||||
#ifdef TORQUE_PLAYER
|
||||
if(argc > 2 && dStricmp(argv[1], "-project") == 0)
|
||||
{
|
||||
char playerPath[1024];
|
||||
Platform::makeFullPathName(argv[2], playerPath, sizeof(playerPath));
|
||||
Platform::setCurrentDirectory(playerPath);
|
||||
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
|
||||
// Re-locate the game:/ asset mount.
|
||||
|
||||
Torque::FS::Unmount( "game" );
|
||||
Torque::FS::Mount( "game", Platform::FS::createNativeFS( playerPath ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Executes an entry script file. This is "main.cs"
|
||||
// by default, but any file name (with no whitespace
|
||||
// in it) may be run if it is specified as the first
|
||||
// command-line parameter. The script used, default
|
||||
// or otherwise, is not compiled and is loaded here
|
||||
// directly because the resource system restricts
|
||||
// access to the "root" directory.
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
Zip::ZipArchive *vfs = openEmbeddedVFSArchive();
|
||||
bool useVFS = vfs != NULL;
|
||||
#endif
|
||||
|
||||
Stream *mainCsStream = NULL;
|
||||
|
||||
// The working filestream.
|
||||
FileStream str;
|
||||
|
||||
const char *defaultScriptName = "main.cs";
|
||||
bool useDefaultScript = true;
|
||||
|
||||
// Check if any command-line parameters were passed (the first is just the app name).
|
||||
if (argc > 1)
|
||||
{
|
||||
// If so, check if the first parameter is a file to open.
|
||||
if ( (dStrcmp(argv[1], "") != 0 ) && (str.open(argv[1], Torque::FS::File::Read)) )
|
||||
{
|
||||
// If it opens, we assume it is the script to run.
|
||||
useDefaultScript = false;
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
useVFS = false;
|
||||
#endif
|
||||
mainCsStream = &str;
|
||||
}
|
||||
}
|
||||
|
||||
if (useDefaultScript)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
if(useVFS)
|
||||
success = (mainCsStream = vfs->openFile(defaultScriptName, Zip::ZipArchive::Read)) != NULL;
|
||||
else
|
||||
#endif
|
||||
success = str.open(defaultScriptName, Torque::FS::File::Read);
|
||||
|
||||
#if defined( TORQUE_DEBUG ) && defined (TORQUE_TOOLS) && !defined( _XBOX )
|
||||
if (!success)
|
||||
{
|
||||
OpenFileDialog ofd;
|
||||
FileDialogData &fdd = ofd.getData();
|
||||
fdd.mFilters = StringTable->insert("Main Entry Script (main.cs)|main.cs|");
|
||||
fdd.mTitle = StringTable->insert("Locate Game Entry Script");
|
||||
|
||||
// Get the user's selection
|
||||
if( !ofd.Execute() )
|
||||
return false;
|
||||
|
||||
// Process and update CWD so we can run the selected main.cs
|
||||
S32 pathLen = dStrlen( fdd.mFile );
|
||||
FrameTemp<char> szPathCopy( pathLen + 1);
|
||||
|
||||
dStrcpy( szPathCopy, fdd.mFile );
|
||||
//forwardslash( szPathCopy );
|
||||
|
||||
const char *path = dStrrchr(szPathCopy, '/');
|
||||
if(path)
|
||||
{
|
||||
U32 len = path - (const char*)szPathCopy;
|
||||
szPathCopy[len+1] = 0;
|
||||
|
||||
Platform::setCurrentDirectory(szPathCopy);
|
||||
|
||||
// Re-locate the game:/ asset mount.
|
||||
|
||||
Torque::FS::Unmount( "game" );
|
||||
Torque::FS::Mount( "game", Platform::FS::createNativeFS( ( const char* ) szPathCopy ) );
|
||||
|
||||
success = str.open(fdd.mFile, Torque::FS::File::Read);
|
||||
if(success)
|
||||
defaultScriptName = fdd.mFile;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( !success )
|
||||
{
|
||||
char msg[1024];
|
||||
dSprintf(msg, sizeof(msg), "Failed to open \"%s\".", defaultScriptName);
|
||||
Platform::AlertOK("Error", msg);
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
closeEmbeddedVFSArchive();
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
if(! useVFS)
|
||||
#endif
|
||||
mainCsStream = &str;
|
||||
}
|
||||
|
||||
// This should rarely happen, but lets deal with
|
||||
// it gracefully if it does.
|
||||
if ( mainCsStream == NULL )
|
||||
return false;
|
||||
|
||||
U32 size = mainCsStream->getStreamSize();
|
||||
char *script = new char[size + 1];
|
||||
mainCsStream->read(size, script);
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
if(useVFS)
|
||||
vfs->closeFile(mainCsStream);
|
||||
else
|
||||
#endif
|
||||
str.close();
|
||||
|
||||
script[size] = 0;
|
||||
|
||||
char buffer[1024], *ptr;
|
||||
Platform::makeFullPathName(useDefaultScript ? defaultScriptName : argv[1], buffer, sizeof(buffer), Platform::getCurrentDirectory());
|
||||
ptr = dStrrchr(buffer, '/');
|
||||
if(ptr != NULL)
|
||||
*ptr = 0;
|
||||
Platform::setMainDotCsDir(buffer);
|
||||
Platform::setCurrentDirectory(buffer);
|
||||
|
||||
Con::evaluate(script, false, useDefaultScript ? defaultScriptName : argv[1]);
|
||||
delete[] script;
|
||||
|
||||
#ifdef TORQUE_ENABLE_VFS
|
||||
closeEmbeddedVFSArchive();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StandardMainLoop::doMainLoop()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
if( gStartupTimer )
|
||||
{
|
||||
Con::printf( "Started up in %.2f seconds...",
|
||||
F32( gStartupTimer->getElapsedMs() ) / 1000.f );
|
||||
SAFE_DELETE( gStartupTimer );
|
||||
}
|
||||
#endif
|
||||
|
||||
bool keepRunning = true;
|
||||
// while(keepRunning)
|
||||
{
|
||||
tm->setBackgroundThreshold(mClamp(sgBackgroundProcessSleepTime, 1, 200));
|
||||
tm->setForegroundThreshold(mClamp(sgTimeManagerProcessInterval, 1, 200));
|
||||
// update foreground/background status
|
||||
if(WindowManager->getFirstWindow())
|
||||
{
|
||||
static bool lastFocus = false;
|
||||
bool newFocus = ( WindowManager->getFocusedWindow() != NULL );
|
||||
if(lastFocus != newFocus)
|
||||
{
|
||||
#ifndef TORQUE_SHIPPING
|
||||
Con::printf("Window focus status changed: focus: %d", newFocus);
|
||||
if (!newFocus)
|
||||
Con::printf(" Using background sleep time: %u", Platform::getBackgroundSleepTime());
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_OS_MAC
|
||||
if (newFocus)
|
||||
WindowManager->getFirstWindow()->show();
|
||||
|
||||
#endif
|
||||
lastFocus = newFocus;
|
||||
}
|
||||
|
||||
#ifndef TORQUE_OS_MAC
|
||||
// under the web plugin do not sleep the process when the child window loses focus as this will cripple the browser perfomance
|
||||
if (!Platform::getWebDeployment())
|
||||
tm->setBackground(!newFocus);
|
||||
else
|
||||
tm->setBackground(false);
|
||||
#else
|
||||
tm->setBackground(false);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
tm->setBackground(false);
|
||||
}
|
||||
|
||||
PROFILE_START(MainLoop);
|
||||
Sampler::beginFrame();
|
||||
|
||||
if(!Process::processEvents())
|
||||
keepRunning = false;
|
||||
|
||||
ThreadPool::processMainThreadWorkItems();
|
||||
Sampler::endFrame();
|
||||
PROFILE_END_NAMED(MainLoop);
|
||||
|
||||
#ifdef TORQUE_DEMO_PURCHASE
|
||||
CheckTimer();
|
||||
CheckBlocker();
|
||||
#endif
|
||||
}
|
||||
|
||||
return keepRunning;
|
||||
}
|
||||
|
||||
void StandardMainLoop::setRestart(bool restart )
|
||||
{
|
||||
gRequiresRestart = restart;
|
||||
}
|
||||
|
||||
bool StandardMainLoop::requiresRestart()
|
||||
{
|
||||
return gRequiresRestart;
|
||||
}
|
||||
53
Engine/source/app/mainLoop.h
Normal file
53
Engine/source/app/mainLoop.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _APP_MAINLOOP_H_
|
||||
#define _APP_MAINLOOP_H_
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
/// Support class to simplify the process of writing a main loop for Torque apps.
|
||||
class StandardMainLoop
|
||||
{
|
||||
public:
|
||||
/// Initialize core libraries and call registered init functions
|
||||
static void init();
|
||||
|
||||
/// Pass command line arguments to registered functions and main.cs
|
||||
static bool handleCommandLine(S32 argc, const char **argv);
|
||||
|
||||
/// A standard mainloop implementation.
|
||||
static bool doMainLoop();
|
||||
|
||||
/// Shut down the core libraries and call registered shutdown fucntions.
|
||||
static void shutdown();
|
||||
|
||||
static void setRestart( bool restart );
|
||||
static bool requiresRestart();
|
||||
|
||||
private:
|
||||
/// Handle "pre shutdown" tasks like notifying scripts BEFORE we delete
|
||||
/// stuff from under them.
|
||||
static void preShutdown();
|
||||
};
|
||||
|
||||
#endif
|
||||
455
Engine/source/app/net/httpObject.cpp
Normal file
455
Engine/source/app/net/httpObject.cpp
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/net/httpObject.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(HTTPObject);
|
||||
|
||||
ConsoleDocClass( HTTPObject,
|
||||
"@brief Allows communications between the game and a server using HTTP.\n\n"
|
||||
|
||||
"HTTPObject is derrived from TCPObject and makes use of the same callbacks for dealing with "
|
||||
"connections and received data. However, the way in which you use HTTPObject to connect "
|
||||
"with a server is different than TCPObject. Rather than opening a connection, sending data, "
|
||||
"waiting to receive data, and then closing the connection, you issue a get() or post() and "
|
||||
"handle the response. The connection is automatically created and destroyed for you.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this example we'll retrieve the weather in Las Vegas using\n"
|
||||
"// Google's API. The response is in XML which could be processed\n"
|
||||
"// and used by the game using SimXMLDocument, but we'll just output\n"
|
||||
"// the results to the console in this example.\n\n"
|
||||
|
||||
"// Define callbacks for our specific HTTPObject using our instance's\n"
|
||||
"// name (WeatherFeed) as the namespace.\n\n"
|
||||
|
||||
"// Handle an issue with resolving the server's name\n"
|
||||
"function WeatherFeed::onDNSFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSFailed\";\n\n"
|
||||
|
||||
" // Handle DNS failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onConnectFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"ConnectFailed\";\n\n"
|
||||
" // Handle connection failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onDNSResolved(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSResolved\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onConnected(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Connected\";\n\n"
|
||||
|
||||
" // Clear our buffer\n"
|
||||
" %this.buffer = \"\";\n"
|
||||
"}\n\n"
|
||||
|
||||
"function WeatherFeed::onDisconnect(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Disconnected\";\n\n"
|
||||
|
||||
" // Output the buffer to the console\n"
|
||||
" echo(\"Google Weather Results:\");\n"
|
||||
" echo(%this.buffer);\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Handle a line from the server\n"
|
||||
"function WeatherFeed::onLine(%this, %line)\n"
|
||||
"{\n"
|
||||
" // Store this line in out buffer\n"
|
||||
" %this.buffer = %this.buffer @ %line;\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Create the HTTPObject\n"
|
||||
"%feed = new HTTPObject(WeatherFeed);\n\n"
|
||||
|
||||
"// Define a dynamic field to store the last connection state\n"
|
||||
"%feed.lastState = \"None\";\n\n"
|
||||
|
||||
"// Send the GET command\n"
|
||||
"%feed.get(\"www.google.com:80\", \"/ig/api\", \"weather=Las-Vegas,US\");\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see TCPObject\n"
|
||||
|
||||
"@ingroup Networking\n"
|
||||
);
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
HTTPObject::HTTPObject()
|
||||
{
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
mPost = 0;
|
||||
mBufferSave = 0;
|
||||
}
|
||||
|
||||
HTTPObject::~HTTPObject()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
dFree(mPost);
|
||||
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
mPost = 0;
|
||||
dFree(mBufferSave);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
//--------------------------------------
|
||||
void HTTPObject::get(const char *host, const char *path, const char *query)
|
||||
{
|
||||
if(mHostName)
|
||||
dFree(mHostName);
|
||||
if(mPath)
|
||||
dFree(mPath);
|
||||
if(mQuery)
|
||||
dFree(mQuery);
|
||||
if(mPost)
|
||||
dFree(mPost);
|
||||
if(mBufferSave)
|
||||
dFree(mBufferSave);
|
||||
|
||||
mBufferSave = 0;
|
||||
mHostName = dStrdup(host);
|
||||
mPath = dStrdup(path);
|
||||
if(query)
|
||||
mQuery = dStrdup(query);
|
||||
else
|
||||
mQuery = NULL;
|
||||
mPost = NULL;
|
||||
|
||||
connect(host);
|
||||
}
|
||||
|
||||
void HTTPObject::post(const char *host, const char *path, const char *query, const char *post)
|
||||
{
|
||||
if(mHostName)
|
||||
dFree(mHostName);
|
||||
if(mPath)
|
||||
dFree(mPath);
|
||||
if(mQuery)
|
||||
dFree(mQuery);
|
||||
if(mPost)
|
||||
dFree(mPost);
|
||||
if(mBufferSave)
|
||||
dFree(mBufferSave);
|
||||
|
||||
mBufferSave = 0;
|
||||
mHostName = dStrdup(host);
|
||||
mPath = dStrdup(path);
|
||||
if(query && query[0])
|
||||
mQuery = dStrdup(query);
|
||||
else
|
||||
mQuery = NULL;
|
||||
mPost = dStrdup(post);
|
||||
connect(host);
|
||||
}
|
||||
|
||||
static char getHex(char c)
|
||||
{
|
||||
if(c <= 9)
|
||||
return c + '0';
|
||||
return c - 10 + 'A';
|
||||
}
|
||||
|
||||
static S32 getHexVal(char c)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
else if(c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 10;
|
||||
else if(c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void HTTPObject::expandPath(char *dest, const char *path, U32 destSize)
|
||||
{
|
||||
static bool asciiEscapeTableBuilt = false;
|
||||
static bool asciiEscapeTable[256];
|
||||
if(!asciiEscapeTableBuilt)
|
||||
{
|
||||
asciiEscapeTableBuilt = true;
|
||||
U32 i;
|
||||
for(i = 0; i <= ' '; i++)
|
||||
asciiEscapeTable[i] = true;
|
||||
for(;i <= 0x7F; i++)
|
||||
asciiEscapeTable[i] = false;
|
||||
for(;i <= 0xFF; i++)
|
||||
asciiEscapeTable[i] = true;
|
||||
asciiEscapeTable[static_cast<U32>('\"')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('_')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('\'')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('#')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('$')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('%')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('&')] = false;
|
||||
asciiEscapeTable[static_cast<U32>('+')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('-')] = true;
|
||||
asciiEscapeTable[static_cast<U32>('~')] = true;
|
||||
}
|
||||
|
||||
U32 destIndex = 0;
|
||||
U32 srcIndex = 0;
|
||||
while(path[srcIndex] && destIndex < destSize - 3)
|
||||
{
|
||||
char c = path[srcIndex++];
|
||||
if(asciiEscapeTable[static_cast<U32>(c)])
|
||||
{
|
||||
dest[destIndex++] = '%';
|
||||
dest[destIndex++] = getHex((c >> 4) & 0xF);
|
||||
dest[destIndex++] = getHex(c & 0xF);
|
||||
}
|
||||
else
|
||||
dest[destIndex++] = c;
|
||||
}
|
||||
dest[destIndex] = 0;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
void HTTPObject::onConnected()
|
||||
{
|
||||
Parent::onConnected();
|
||||
char expPath[8192];
|
||||
char buffer[8192];
|
||||
|
||||
if(mQuery)
|
||||
{
|
||||
dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery);
|
||||
expandPath(expPath, buffer, sizeof(expPath));
|
||||
}
|
||||
else
|
||||
expandPath(expPath, mPath, sizeof(expPath));
|
||||
|
||||
char *pt = dStrchr(mHostName, ':');
|
||||
if(pt)
|
||||
*pt = 0;
|
||||
dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName);
|
||||
if(pt)
|
||||
*pt = ':';
|
||||
|
||||
send((U8*)buffer, dStrlen(buffer));
|
||||
mParseState = ParsingStatusLine;
|
||||
mChunkedEncoding = false;
|
||||
}
|
||||
|
||||
void HTTPObject::onConnectFailed()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
Parent::onConnectFailed();
|
||||
}
|
||||
|
||||
|
||||
void HTTPObject::onDisconnect()
|
||||
{
|
||||
dFree(mHostName);
|
||||
dFree(mPath);
|
||||
dFree(mQuery);
|
||||
mHostName = 0;
|
||||
mPath = 0;
|
||||
mQuery = 0;
|
||||
Parent::onDisconnect();
|
||||
}
|
||||
|
||||
bool HTTPObject::processLine(U8 *line)
|
||||
{
|
||||
if(mParseState == ParsingStatusLine)
|
||||
{
|
||||
mParseState = ParsingHeader;
|
||||
}
|
||||
else if(mParseState == ParsingHeader)
|
||||
{
|
||||
if(!dStricmp((char *) line, "transfer-encoding: chunked"))
|
||||
mChunkedEncoding = true;
|
||||
if(line[0] == 0)
|
||||
{
|
||||
if(mChunkedEncoding)
|
||||
mParseState = ParsingChunkHeader;
|
||||
else
|
||||
mParseState = ProcessingBody;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(mParseState == ParsingChunkHeader)
|
||||
{
|
||||
if(line[0]) // strip off the crlf if necessary
|
||||
{
|
||||
mChunkSize = 0;
|
||||
S32 hexVal;
|
||||
while((hexVal = getHexVal(*line++)) != -1)
|
||||
{
|
||||
mChunkSize *= 16;
|
||||
mChunkSize += hexVal;
|
||||
}
|
||||
if(mBufferSave)
|
||||
{
|
||||
mBuffer = mBufferSave;
|
||||
mBufferSize = mBufferSaveSize;
|
||||
mBufferSave = 0;
|
||||
}
|
||||
if(mChunkSize)
|
||||
mParseState = ProcessingBody;
|
||||
else
|
||||
{
|
||||
mParseState = ProcessingDone;
|
||||
finishLastLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Parent::processLine((UTF8*)line);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
if(mParseState == ProcessingBody)
|
||||
{
|
||||
if(mChunkedEncoding && bufferLen >= mChunkSize)
|
||||
{
|
||||
U32 ret = onDataReceive(buffer, mChunkSize);
|
||||
mChunkSize -= ret;
|
||||
if(mChunkSize == 0)
|
||||
{
|
||||
if(mBuffer)
|
||||
{
|
||||
mBufferSaveSize = mBufferSize;
|
||||
mBufferSave = mBuffer;
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
}
|
||||
mParseState = ParsingChunkHeader;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
U32 ret = onDataReceive(buffer, bufferLen);
|
||||
mChunkSize -= ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else if(mParseState != ProcessingDone)
|
||||
{
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
return bufferLen;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
DefineEngineMethod( HTTPObject, get, void, ( const char* Address, const char* requirstURI, const char* query ), ( "" ),
|
||||
"@brief Send a GET command to a server to send or retrieve data.\n\n"
|
||||
|
||||
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
|
||||
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
|
||||
"@param query Optional. Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. "
|
||||
"If you were building the URL manually, this is the text that follows the question mark. For example: http://www.google.com/ig/api?<b>weather=Las-Vegas,US</b>\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create an HTTP object for communications\n"
|
||||
"%httpObj = new HTTPObject();\n\n"
|
||||
"// Specify a URL to transmit to\n"
|
||||
"%url = \"www.garagegames.com:80\";\n\n"
|
||||
"// Specify a URI to communicate with\n"
|
||||
"%URI = \"/index.php\";\n\n"
|
||||
"// Specify a query to send.\n"
|
||||
"%query = \"\";\n\n"
|
||||
"// Send the GET command to the server\n"
|
||||
"%httpObj.get(%url,%URI,%query);\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
if( !query || !query[ 0 ] )
|
||||
object->get(Address, requirstURI, NULL);
|
||||
else
|
||||
object->get(Address, requirstURI, query);
|
||||
}
|
||||
|
||||
DefineEngineMethod( HTTPObject, post, void, ( const char* Address, const char* requirstURI, const char* query, const char* post ),,
|
||||
"@brief Send POST command to a server to send or retrieve data.\n\n"
|
||||
|
||||
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
|
||||
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
|
||||
"@param query Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. \n"
|
||||
"@param post Submission data to be processed.\n"
|
||||
|
||||
"@note The post() method is currently non-functional.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create an HTTP object for communications\n"
|
||||
"%httpObj = new HTTPObject();\n\n"
|
||||
"// Specify a URL to transmit to\n"
|
||||
"%url = \"www.garagegames.com:80\";\n\n"
|
||||
"// Specify a URI to communicate with\n"
|
||||
"%URI = \"/index.php\";\n\n"
|
||||
"// Specify a query to send.\n"
|
||||
"%query = \"\";\n\n"
|
||||
"// Specify the submission data.\n"
|
||||
"%post = \"\";\n\n"
|
||||
"// Send the POST command to the server\n"
|
||||
"%httpObj.POST(%url,%URI,%query,%post);\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
object->post(Address, requirstURI, query, post);
|
||||
}
|
||||
81
Engine/source/app/net/httpObject.h
Normal file
81
Engine/source/app/net/httpObject.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _HTTPOBJECT_H_
|
||||
#define _HTTPOBJECT_H_
|
||||
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _TCPOBJECT_H_
|
||||
#include "app/net/tcpObject.h"
|
||||
#endif
|
||||
|
||||
class HTTPObject : public TCPObject
|
||||
{
|
||||
private:
|
||||
typedef TCPObject Parent;
|
||||
protected:
|
||||
enum ParseState {
|
||||
ParsingStatusLine,
|
||||
ParsingHeader,
|
||||
ParsingChunkHeader,
|
||||
ProcessingBody,
|
||||
ProcessingDone,
|
||||
};
|
||||
ParseState mParseState;
|
||||
U32 mTotalBytes;
|
||||
U32 mBytesRemaining;
|
||||
public:
|
||||
U32 mStatus;
|
||||
F32 mVersion;
|
||||
U32 mContentLength;
|
||||
bool mChunkedEncoding;
|
||||
U32 mChunkSize;
|
||||
const char *mContentType;
|
||||
char *mHostName;
|
||||
char *mPath;
|
||||
char *mQuery;
|
||||
char *mPost;
|
||||
U8 *mBufferSave;
|
||||
U32 mBufferSaveSize;
|
||||
public:
|
||||
static void expandPath(char *dest, const char *path, U32 destSize);
|
||||
void get(const char *hostName, const char *urlName, const char *query);
|
||||
void post(const char *host, const char *path, const char *query, const char *post);
|
||||
HTTPObject();
|
||||
~HTTPObject();
|
||||
|
||||
//static HTTPObject *find(U32 tag);
|
||||
|
||||
virtual U32 onDataReceive(U8 *buffer, U32 bufferLen);
|
||||
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
|
||||
virtual void onConnected();
|
||||
virtual void onConnectFailed();
|
||||
virtual void onDisconnect();
|
||||
bool processLine(U8 *line);
|
||||
|
||||
DECLARE_CONOBJECT(HTTPObject);
|
||||
};
|
||||
|
||||
|
||||
#endif // _H_HTTPOBJECT_
|
||||
417
Engine/source/app/net/net.cpp
Normal file
417
Engine/source/app/net/net.cpp
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "core/idGenerator.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "app/net/serverQuery.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// remote procedure call console functions
|
||||
//----------------------------------------------------------------
|
||||
|
||||
class RemoteCommandEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
enum {
|
||||
MaxRemoteCommandArgs = 20,
|
||||
CommandArgsBits = 5
|
||||
};
|
||||
|
||||
private:
|
||||
S32 mArgc;
|
||||
char *mArgv[MaxRemoteCommandArgs + 1];
|
||||
NetStringHandle mTagv[MaxRemoteCommandArgs + 1];
|
||||
static char mBuf[1024];
|
||||
public:
|
||||
RemoteCommandEvent(S32 argc=0, const char **argv=NULL, NetConnection *conn = NULL)
|
||||
{
|
||||
mArgc = argc;
|
||||
for(S32 i = 0; i < argc; i++)
|
||||
{
|
||||
if(argv[i][0] == StringTagPrefixByte)
|
||||
{
|
||||
char buffer[256];
|
||||
mTagv[i+1] = NetStringHandle(dAtoi(argv[i]+1));
|
||||
if(conn)
|
||||
{
|
||||
dSprintf(buffer + 1, sizeof(buffer) - 1, "%d", conn->getNetSendId(mTagv[i+1]));
|
||||
buffer[0] = StringTagPrefixByte;
|
||||
mArgv[i+1] = dStrdup(buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
mArgv[i+1] = dStrdup(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *getDebugName()
|
||||
{
|
||||
static char buffer[256];
|
||||
dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), mTagv[1].isValidString() ? mTagv[1].getString() : "--unknown--" );
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
~RemoteCommandEvent()
|
||||
{
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
dFree(mArgv[i+1]);
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mArgc, CommandArgsBits);
|
||||
// write it out reversed... why?
|
||||
// automatic string substitution with later arguments -
|
||||
// handled automatically by the system.
|
||||
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
conn->packString(bstream, mArgv[i+1]);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
pack(conn, bstream);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection* conn, BitStream *bstream)
|
||||
{
|
||||
|
||||
mArgc = bstream->readInt(CommandArgsBits);
|
||||
// read it out backwards
|
||||
for(S32 i = 0; i < mArgc; i++)
|
||||
{
|
||||
conn->unpackString(bstream, mBuf);
|
||||
mArgv[i+1] = dStrdup(mBuf);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *conn)
|
||||
{
|
||||
static char idBuf[10];
|
||||
|
||||
// de-tag the command name
|
||||
|
||||
for(S32 i = mArgc - 1; i >= 0; i--)
|
||||
{
|
||||
char *arg = mArgv[i+1];
|
||||
if(*arg == StringTagPrefixByte)
|
||||
{
|
||||
// it's a tag:
|
||||
U32 localTag = dAtoi(arg + 1);
|
||||
NetStringHandle tag = conn->translateRemoteStringId(localTag);
|
||||
NetStringTable::expandString( tag,
|
||||
mBuf,
|
||||
sizeof(mBuf),
|
||||
(mArgc - 1) - i,
|
||||
(const char**)(mArgv + i + 2) );
|
||||
dFree(mArgv[i+1]);
|
||||
mArgv[i+1] = dStrdup(mBuf);
|
||||
}
|
||||
}
|
||||
const char *rmtCommandName = dStrchr(mArgv[1], ' ') + 1;
|
||||
if(conn->isConnectionToServer())
|
||||
{
|
||||
dStrcpy(mBuf, "clientCmd");
|
||||
dStrcat(mBuf, rmtCommandName);
|
||||
|
||||
char *temp = mArgv[1];
|
||||
mArgv[1] = mBuf;
|
||||
|
||||
Con::execute(mArgc, (const char **) mArgv+1);
|
||||
mArgv[1] = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
dStrcpy(mBuf, "serverCmd");
|
||||
dStrcat(mBuf, rmtCommandName);
|
||||
char *temp = mArgv[1];
|
||||
|
||||
dSprintf(idBuf, sizeof(idBuf), "%d", conn->getId());
|
||||
mArgv[0] = mBuf;
|
||||
mArgv[1] = idBuf;
|
||||
|
||||
Con::execute(mArgc+1, (const char **) mArgv);
|
||||
mArgv[1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(RemoteCommandEvent);
|
||||
};
|
||||
char RemoteCommandEvent::mBuf[1024];
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(RemoteCommandEvent);
|
||||
|
||||
ConsoleDocClass( RemoteCommandEvent,
|
||||
"@brief Object used for remote procedure calls.\n\n"
|
||||
"Not intended for game development, for exposing ConsoleFunctions (such as commandToClient) only.\n\n"
|
||||
"@internal");
|
||||
|
||||
static void sendRemoteCommand(NetConnection *conn, S32 argc, const char **argv)
|
||||
{
|
||||
if(U8(argv[0][0]) != StringTagPrefixByte)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::Script, "Remote Command Error - command must be a tag.");
|
||||
return;
|
||||
}
|
||||
S32 i;
|
||||
for(i = argc - 1; i >= 0; i--)
|
||||
{
|
||||
if(argv[i][0] != 0)
|
||||
break;
|
||||
argc = i;
|
||||
}
|
||||
for(i = 0; i < argc; i++)
|
||||
conn->validateSendString(argv[i]);
|
||||
RemoteCommandEvent *cevt = new RemoteCommandEvent(argc, argv, conn);
|
||||
conn->postNetEvent(cevt);
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin( Net, "Functions for use with the network; tagged strings and remote commands.");
|
||||
|
||||
ConsoleFunction( commandToServer, void, 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1, "(string func, ...)"
|
||||
"@brief Send a command to the server.\n\n"
|
||||
|
||||
"@param func Name of the server command being called\n"
|
||||
"@param ... Various parameters being passed to server command\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create a standard function. Needs to be executed on the client, such \n"
|
||||
"// as within scripts/client/default.bind.cs\n"
|
||||
"function toggleCamera(%val)\n"
|
||||
"{\n"
|
||||
" // If key was down, call a server command named 'ToggleCamera'\n"
|
||||
" if (%val)\n"
|
||||
" commandToServer('ToggleCamera');\n"
|
||||
"}\n\n"
|
||||
"// Server command being called from above. Needs to be executed on the \n"
|
||||
"// server, such as within scripts/server/commands.cs\n"
|
||||
"function serverCmdToggleCamera(%client)\n"
|
||||
"{\n"
|
||||
" if (%client.getControlObject() == %client.player)\n"
|
||||
" {\n"
|
||||
" %client.camera.setVelocity(\"0 0 0\");\n"
|
||||
" %control = %client.camera;\n"
|
||||
" }\n"
|
||||
" else\n"
|
||||
" {\n"
|
||||
" %client.player.setVelocity(\"0 0 0\");\n"
|
||||
" %control = %client.player;\n"
|
||||
" }\n"
|
||||
" %client.setControlObject(%control);\n"
|
||||
" clientCmdSyncEditorGui();\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@ingroup Networking")
|
||||
{
|
||||
NetConnection *conn = NetConnection::getConnectionToServer();
|
||||
if(!conn)
|
||||
return;
|
||||
sendRemoteCommand(conn, argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
ConsoleFunction( commandToClient, void, 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2, "(NetConnection client, string func, ...)"
|
||||
"@brief Send a command from the server to the client\n\n"
|
||||
|
||||
"@param client The numeric ID of a client GameConnection\n"
|
||||
"@param func Name of the client function being called\n"
|
||||
"@param ... Various parameters being passed to client command\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set up the client command. Needs to be executed on the client, such as\n"
|
||||
"// within scripts/client/client.cs\n"
|
||||
"// Update the Ammo Counter with current ammo, if not any then hide the counter.\n"
|
||||
"function clientCmdSetAmmoAmountHud(%amount)\n"
|
||||
"{\n"
|
||||
" if (!%amount)\n"
|
||||
" AmmoAmount.setVisible(false);\n"
|
||||
" else\n"
|
||||
" {\n"
|
||||
" AmmoAmount.setVisible(true);\n"
|
||||
" AmmoAmount.setText(\"Ammo: \"@%amount);\n"
|
||||
" }\n"
|
||||
"}\n\n"
|
||||
"// Call it from a server function. Needs to be executed on the server, \n"
|
||||
"//such as within scripts/server/game.cs\n"
|
||||
"function GameConnection::setAmmoAmountHud(%client, %amount)\n"
|
||||
"{\n"
|
||||
" commandToClient(%client, 'SetAmmoAmountHud', %amount);\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
NetConnection *conn;
|
||||
if(!Sim::findObject(argv[1], conn))
|
||||
return;
|
||||
sendRemoteCommand(conn, argc - 2, argv + 2);
|
||||
}
|
||||
|
||||
|
||||
ConsoleFunction(removeTaggedString, void, 2, 2, "(int tag)"
|
||||
"@brief Remove a tagged string from the Net String Table\n\n"
|
||||
|
||||
"@param tag The tag associated with the string\n\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
gNetStringTable->removeString(dAtoi(argv[1]+1), true);
|
||||
}
|
||||
|
||||
ConsoleFunction( addTaggedString, const char*, 2, 2, "(string str)"
|
||||
"@brief Use the addTaggedString function to tag a new string and add it to the NetStringTable\n\n"
|
||||
|
||||
"@param str The string to be tagged and placed in the NetStringTable. Tagging ignores case, "
|
||||
"so tagging the same string (excluding case differences) will be ignored as a duplicated tag.\n\n"
|
||||
|
||||
"@return Returns a string( containing a numeric value) equivalent to the string ID for the newly tagged string"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see removeTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
NetStringHandle s(argv[1]);
|
||||
gNetStringTable->incStringRefScript(s.getIndex());
|
||||
|
||||
char *ret = Con::getReturnBuffer(10);
|
||||
ret[0] = StringTagPrefixByte;
|
||||
dSprintf(ret + 1, 9, "%d", s.getIndex());
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConsoleFunction( getTaggedString, const char*, 2, 2, "(int tag)"
|
||||
"@brief Use the getTaggedString function to convert a tag to a string.\n\n"
|
||||
|
||||
"This is not the same as detag() which can only be used within the context "
|
||||
"of a function that receives a tag. This function can be used any time and "
|
||||
"anywhere to convert a tag to a string.\n\n"
|
||||
|
||||
"@param tag A numeric tag ID.\n"
|
||||
|
||||
"@returns The string as found in the Net String table.\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see removeTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
const char *indexPtr = argv[1];
|
||||
if (*indexPtr == StringTagPrefixByte)
|
||||
indexPtr++;
|
||||
return gNetStringTable->lookupString(dAtoi(indexPtr));
|
||||
}
|
||||
|
||||
ConsoleFunction( buildTaggedString, const char*, 2, 11, "(string format, ...)"
|
||||
"@brief Build a string using the specified tagged string format.\n\n"
|
||||
|
||||
"This function takes an already tagged string (passed in as a tagged string ID) and one "
|
||||
"or more additional strings. If the tagged string contains argument tags that range from "
|
||||
"%%1 through %%9, then each additional string will be substituted into the tagged string. "
|
||||
"The final (non-tagged) combined string will be returned. The maximum length of the tagged "
|
||||
"string plus any inserted additional strings is 511 characters.\n\n"
|
||||
|
||||
"@param format A tagged string ID that contains zero or more argument tags, in the form of "
|
||||
"%%1 through %%9.\n"
|
||||
"@param ... A variable number of arguments that are insterted into the tagged string "
|
||||
"based on the argument tags within the format string.\n"
|
||||
|
||||
"@returns An ordinary string that is a combination of the original tagged string with any additional "
|
||||
"strings passed in inserted in place of each argument tag.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create a tagged string with argument tags\n"
|
||||
"%taggedStringID = addTaggedString(\"Welcome %1 to the game!\");\n\n"
|
||||
|
||||
"// Some point later, combine the tagged string with some other string\n"
|
||||
"%string = buildTaggedString(%taggedStringID, %playerName);\n"
|
||||
"echo(%string);\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
|
||||
"@see addTaggedString()\n"
|
||||
"@see getTaggedString()\n"
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
const char *indexPtr = argv[1];
|
||||
if (*indexPtr == StringTagPrefixByte)
|
||||
indexPtr++;
|
||||
const char *fmtString = gNetStringTable->lookupString(dAtoi(indexPtr));
|
||||
char *strBuffer = Con::getReturnBuffer(512);
|
||||
const char *fmtStrPtr = fmtString;
|
||||
char *strBufPtr = strBuffer;
|
||||
S32 strMaxLength = 511;
|
||||
if (!fmtString)
|
||||
goto done;
|
||||
|
||||
//build the string
|
||||
while (*fmtStrPtr)
|
||||
{
|
||||
//look for an argument tag
|
||||
if (*fmtStrPtr == '%')
|
||||
{
|
||||
if (fmtStrPtr[1] >= '1' && fmtStrPtr[1] <= '9')
|
||||
{
|
||||
S32 argIndex = S32(fmtStrPtr[1] - '0') + 1;
|
||||
if (argIndex >= argc)
|
||||
goto done;
|
||||
const char *argStr = argv[argIndex];
|
||||
if (!argStr)
|
||||
goto done;
|
||||
S32 strLength = dStrlen(argStr);
|
||||
if (strLength > strMaxLength)
|
||||
goto done;
|
||||
dStrcpy(strBufPtr, argStr);
|
||||
strBufPtr += strLength;
|
||||
strMaxLength -= strLength;
|
||||
fmtStrPtr += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//if we don't continue, just copy the character
|
||||
if (strMaxLength <= 0)
|
||||
goto done;
|
||||
*strBufPtr++ = *fmtStrPtr++;
|
||||
strMaxLength--;
|
||||
}
|
||||
|
||||
done:
|
||||
*strBufPtr = '\0';
|
||||
return strBuffer;
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd( Net );
|
||||
192
Engine/source/app/net/netTest.cpp
Normal file
192
Engine/source/app/net/netTest.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/simBase.h"
|
||||
#include "platform/event.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
class SimpleMessageEvent : public NetEvent
|
||||
{
|
||||
char *msg;
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
SimpleMessageEvent(const char *message = NULL)
|
||||
{
|
||||
if(message)
|
||||
msg = dStrdup(message);
|
||||
else
|
||||
msg = NULL;
|
||||
}
|
||||
~SimpleMessageEvent()
|
||||
{ dFree(msg); }
|
||||
|
||||
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{ bstream->writeString(msg); }
|
||||
virtual void write(NetConnection*, BitStream *bstream)
|
||||
{ bstream->writeString(msg); }
|
||||
virtual void unpack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{ char buf[256]; bstream->readString(buf); msg = dStrdup(buf); }
|
||||
virtual void process(NetConnection *)
|
||||
{ Con::printf("RMSG %d %s", mSourceId, msg); }
|
||||
|
||||
DECLARE_CONOBJECT(SimpleMessageEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent);
|
||||
|
||||
ConsoleDocClass( SimpleMessageEvent,
|
||||
"@brief A very simple example of a network event.\n\n"
|
||||
|
||||
"This object exists purely for instructional purposes. It is primarily "
|
||||
"geared toward developers that wish to understand the inner-working of "
|
||||
"Torque 3D's networking system. This is not intended for actual game "
|
||||
"development.\n\n "
|
||||
|
||||
"@see NetEvent for the inner workings of network events\n\n"
|
||||
|
||||
"@ingroup Networking\n");
|
||||
|
||||
DefineEngineStaticMethod( SimpleMessageEvent, msg, void, (NetConnection* con, const char* message),,
|
||||
"@brief Send a SimpleMessageEvent message to the specified connection.\n\n"
|
||||
|
||||
"The far end that receives the message will print the message out to the console.\n"
|
||||
|
||||
"@param con The unique ID of the connection to transmit to\n"
|
||||
"@param message The string containing the message to transmit\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Send a message to the other end of the given NetConnection\n"
|
||||
"SimpleMessageEvent::msg( %conn, \"A message from me!\");\n\n"
|
||||
|
||||
"// The far end will see the following in the console\n"
|
||||
"// (Note: The number may be something other than 1796 as it is the SimObjectID\n"
|
||||
"// of the received event)\n"
|
||||
"// \n"
|
||||
"// RMSG 1796 A message from me!\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
//NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
|
||||
|
||||
if(con)
|
||||
con->postNetEvent(new SimpleMessageEvent(message));
|
||||
}
|
||||
|
||||
//ConsoleFunction( msg, void, 3, 3, "(NetConnection id, string message)"
|
||||
// "Send a SimpleNetObject message to the specified connection.")
|
||||
//{
|
||||
// NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
|
||||
// if(con)
|
||||
// con->postNetEvent(new SimpleMessageEvent(argv[2]));
|
||||
//}
|
||||
|
||||
class SimpleNetObject : public NetObject
|
||||
{
|
||||
typedef NetObject Parent;
|
||||
public:
|
||||
char message[256];
|
||||
SimpleNetObject()
|
||||
{
|
||||
mNetFlags.set(ScopeAlways | Ghostable);
|
||||
dStrcpy(message, "Hello World!");
|
||||
}
|
||||
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
|
||||
{
|
||||
stream->writeString(message);
|
||||
return 0;
|
||||
}
|
||||
void unpackUpdate(NetConnection *conn, BitStream *stream)
|
||||
{
|
||||
stream->readString(message);
|
||||
Con::printf("Got message: %s", message);
|
||||
}
|
||||
void setMessage(const char *msg)
|
||||
{
|
||||
setMaskBits(1);
|
||||
dStrcpy(message, msg);
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(SimpleNetObject);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
|
||||
|
||||
ConsoleDocClass( SimpleNetObject,
|
||||
"@brief A very simple example of a class derived from NetObject.\n\n"
|
||||
|
||||
"This object exists purely for instructional purposes. It is primarily "
|
||||
"geared toward developers that wish to understand the inner-working of "
|
||||
"Torque 3D's networking system. This is not intended for actual game "
|
||||
"development.\n\n "
|
||||
|
||||
"@tsexample\n"
|
||||
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
|
||||
"// object so it will be immediately ghosted to all connected clients.\n"
|
||||
"$s = new SimpleNetObject();\n\n"
|
||||
|
||||
"// All connected clients will see the following in their console:\n"
|
||||
"// \n"
|
||||
"// Got message: Hello World!\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see NetObject for a full breakdown of this example object\n"
|
||||
|
||||
"@ingroup Networking\n");
|
||||
|
||||
DefineEngineMethod( SimpleNetObject, setMessage, void, (const char* msg),,
|
||||
"@brief Sets the internal message variable.\n\n"
|
||||
|
||||
"SimpleNetObject is set up to automatically transmit this new message to "
|
||||
"all connected clients. It will appear in the clients' console.\n"
|
||||
|
||||
"@param msg The new message to send\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
|
||||
"// object so it will be immediately ghosted to all connected clients.\n"
|
||||
"$s = new SimpleNetObject();\n\n"
|
||||
|
||||
"// All connected clients will see the following in their console:\n"
|
||||
"// \n"
|
||||
"// Got message: Hello World!\n\n"
|
||||
|
||||
"// Now again on the server, change the message. This will cause it to\n"
|
||||
"// be sent to all connected clients.\n"
|
||||
"$s.setMessage(\"A new message from me!\");\n\n"
|
||||
|
||||
"// All connected clients will now see in their console:\n"
|
||||
"// \n"
|
||||
"// Go message: A new message from me!\n"
|
||||
"@endtsexample\n\n"
|
||||
)
|
||||
{
|
||||
object->setMessage(msg);
|
||||
}
|
||||
|
||||
//ConsoleMethod( SimpleNetObject, setMessage, void, 3, 3, "(string msg)")
|
||||
//{
|
||||
// object->setMessage(argv[2]);
|
||||
//}
|
||||
2139
Engine/source/app/net/serverQuery.cpp
Normal file
2139
Engine/source/app/net/serverQuery.cpp
Normal file
File diff suppressed because it is too large
Load diff
128
Engine/source/app/net/serverQuery.h
Normal file
128
Engine/source/app/net/serverQuery.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SERVERQUERY_H_
|
||||
#define _SERVERQUERY_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _EVENT_H_
|
||||
#include "platform/event.h"
|
||||
#endif
|
||||
#ifndef _BITSET_H_
|
||||
#include "core/bitSet.h"
|
||||
#endif
|
||||
|
||||
#include "platform/platformNet.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Game Server Information
|
||||
|
||||
struct ServerInfo
|
||||
{
|
||||
enum StatusFlags
|
||||
{
|
||||
// Info flags (0-7):
|
||||
Status_Dedicated = BIT(0),
|
||||
Status_Passworded = BIT(1),
|
||||
Status_Linux = BIT(2),
|
||||
Status_Xenon = BIT(6),
|
||||
|
||||
// Status flags:
|
||||
Status_New = 0,
|
||||
Status_Querying = BIT(28),
|
||||
Status_Updating = BIT(29),
|
||||
Status_Responded = BIT(30),
|
||||
Status_TimedOut = BIT(31),
|
||||
};
|
||||
|
||||
U8 numPlayers;
|
||||
U8 maxPlayers;
|
||||
U8 numBots;
|
||||
char* name;
|
||||
char* gameType;
|
||||
char* missionName;
|
||||
char* missionType;
|
||||
char* statusString;
|
||||
char* infoString;
|
||||
NetAddress address;
|
||||
U32 version;
|
||||
U32 ping;
|
||||
U32 cpuSpeed;
|
||||
bool isFavorite;
|
||||
BitSet32 status;
|
||||
|
||||
ServerInfo()
|
||||
{
|
||||
numPlayers = 0;
|
||||
maxPlayers = 0;
|
||||
numBots = 0;
|
||||
name = NULL;
|
||||
gameType = NULL;
|
||||
missionType = NULL;
|
||||
missionName = NULL;
|
||||
statusString = NULL;
|
||||
infoString = NULL;
|
||||
version = 0;
|
||||
ping = 0;
|
||||
cpuSpeed = 0;
|
||||
isFavorite = false;
|
||||
status = Status_New;
|
||||
}
|
||||
~ServerInfo();
|
||||
|
||||
bool isNew() { return( status == Status_New ); }
|
||||
bool isQuerying() { return( status.test( Status_Querying ) ); }
|
||||
bool isUpdating() { return( status.test( Status_Updating ) ); }
|
||||
bool hasResponded() { return( status.test( Status_Responded ) ); }
|
||||
bool isTimedOut() { return( status.test( Status_TimedOut ) ); }
|
||||
|
||||
bool isDedicated() { return( status.test( Status_Dedicated ) ); }
|
||||
bool isPassworded() { return( status.test( Status_Passworded ) ); }
|
||||
bool isLinux() { return( status.test( Status_Linux ) ); }
|
||||
bool isXenon() { return( status.test( Status_Xenon ) ); }
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
extern Vector<ServerInfo> gServerList;
|
||||
extern bool gServerBrowserDirty;
|
||||
extern void clearServerList();
|
||||
extern void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType,
|
||||
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
|
||||
U8 filterFlags);
|
||||
extern void queryMasterGameTypes();
|
||||
extern void queryMasterServer(U8 flags, const char* gameType, const char* missionType,
|
||||
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
|
||||
U8 filterFlags, U8 buddyCount, U32* buddyList );
|
||||
extern void queryFavoriteServers( U8 flags );
|
||||
extern void querySingleServer(const NetAddress* addr, U8 flags);
|
||||
extern void startHeartbeat();
|
||||
extern void sendHeartbeat( U8 flags );
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
extern void addFakeServers( S32 howMany );
|
||||
#endif // DEBUG
|
||||
|
||||
#endif
|
||||
521
Engine/source/app/net/tcpObject.cpp
Normal file
521
Engine/source/app/net/tcpObject.cpp
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "app/net/tcpObject.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/strings/stringUnit.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
TCPObject *TCPObject::table[TCPObject::TableSize] = {0, };
|
||||
|
||||
IMPLEMENT_CONOBJECT(TCPObject);
|
||||
|
||||
ConsoleDocClass( TCPObject,
|
||||
"@brief Allows communications between the game and a server using TCP/IP protocols.\n\n"
|
||||
|
||||
"To use TCPObject you set up a connection to a server, send data to the server, and handle "
|
||||
"each line of the server's response using a callback. Once you are done communicating with "
|
||||
"the server, you disconnect.\n\n"
|
||||
|
||||
"TCPObject is intended to be used with text based protocols which means you'll need to "
|
||||
"delineate the server's response with an end-of-line character. i.e. the newline "
|
||||
"character @\\n. You may optionally include the carriage return character @\\r prior to the newline "
|
||||
"and TCPObject will strip it out before sending the line to the callback. If a newline "
|
||||
"character is not included in the server's output, the received data will not be "
|
||||
"processed until you disconnect from the server (which flushes the internal buffer).\n\n"
|
||||
|
||||
"TCPObject may also be set up to listen to a specific port, making Torque into a TCP server. "
|
||||
"When used in this manner, a callback is received when a client connection is made. Following "
|
||||
"the outside connection, text may be sent and lines are processed in the usual manner.\n\n"
|
||||
|
||||
"If you want to work with HTTP you may wish to use HTTPObject instead as it handles all of the "
|
||||
"HTTP header setup and parsing.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// In this example we'll retrieve the new forum threads RSS\n"
|
||||
"// feed from garagegames.com. As we're using TCPObject, the\n"
|
||||
"// raw text response will be received from the server, including\n"
|
||||
"// the HTTP header.\n\n"
|
||||
|
||||
"// Define callbacks for our specific TCPObject using our instance's\n"
|
||||
"// name (RSSFeed) as the namespace.\n\n"
|
||||
|
||||
"// Handle an issue with resolving the server's name\n"
|
||||
"function RSSFeed::onDNSFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSFailed\";\n\n"
|
||||
|
||||
" // Handle DNS failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onConnectFailed(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"ConnectFailed\";\n\n"
|
||||
" // Handle connection failure\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onDNSResolved(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"DNSResolved\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onConnected(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Connected\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"function RSSFeed::onDisconnect(%this)\n"
|
||||
"{\n"
|
||||
" // Store this state\n"
|
||||
" %this.lastState = \"Disconnected\";\n\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Handle a line from the server\n"
|
||||
"function RSSFeed::onLine(%this, %line)\n"
|
||||
"{\n"
|
||||
" // Print the line to the console\n"
|
||||
" echo( %line );\n"
|
||||
"}\n\n"
|
||||
|
||||
"// Create the TCPObject\n"
|
||||
"%rss = new TCPObject(RSSFeed);\n\n"
|
||||
|
||||
"// Define a dynamic field to store the last connection state\n"
|
||||
"%rss.lastState = \"None\";\n\n"
|
||||
|
||||
"// Connect to the server\n"
|
||||
"%rss.connect(\"www.garagegames.com:80\");\n\n"
|
||||
|
||||
"// Send the RSS feed request to the server. Response will be\n"
|
||||
"// handled in onLine() callback above\n"
|
||||
"%rss.send(\"GET /feeds/rss/threads HTTP/1.1\\r\\nHost: www.garagegames.com\\r\\n\\r\\n\");\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see HTTPObject\n"
|
||||
|
||||
"@ingroup Networking\n"
|
||||
);
|
||||
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnectionRequest, void, (const char* address, const char* ID), (address, ID),
|
||||
"@brief Called whenever a connection request is made.\n\n"
|
||||
"This callback is used when the TCPObject is listening to a port and a client is attempting to connect.\n"
|
||||
"@param address Server address connecting from.\n"
|
||||
"@param ID Connection ID\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onLine, void, (const char* line), (line),
|
||||
"@brief Called whenever a line of data is sent to this TCPObject.\n\n"
|
||||
"This callback is called when the received data contains a newline @\\n character, or "
|
||||
"the connection has been disconnected and the TCPObject's buffer is flushed.\n"
|
||||
"@param line Data sent from the server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDNSResolved, void, (),(),
|
||||
"Called whenever the DNS has been resolved.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDNSFailed, void, (),(),
|
||||
"Called whenever the DNS has failed to resolve.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnected, void, (),(),
|
||||
"Called whenever a connection is established with a server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onConnectFailed, void, (),(),
|
||||
"Called whenever a connection has failed to be established with a server.\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK(TCPObject, onDisconnect, void, (),(),
|
||||
"Called whenever the TCPObject disconnects from whatever it is currently connected to.\n"
|
||||
);
|
||||
|
||||
TCPObject *TCPObject::find(NetSocket tag)
|
||||
{
|
||||
for(TCPObject *walk = table[U32(tag) & TableMask]; walk; walk = walk->mNext)
|
||||
if(walk->mTag == tag)
|
||||
return walk;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TCPObject::addToTable(NetSocket newTag)
|
||||
{
|
||||
removeFromTable();
|
||||
mTag = newTag;
|
||||
mNext = table[U32(mTag) & TableMask];
|
||||
table[U32(mTag) & TableMask] = this;
|
||||
}
|
||||
|
||||
void TCPObject::removeFromTable()
|
||||
{
|
||||
for(TCPObject **walk = &table[U32(mTag) & TableMask]; *walk; walk = &((*walk)->mNext))
|
||||
{
|
||||
if(*walk == this)
|
||||
{
|
||||
*walk = mNext;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData);
|
||||
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress);
|
||||
void processConnectedNotifyEvent( NetSocket sock, U32 state );
|
||||
|
||||
S32 gTCPCount = 0;
|
||||
|
||||
TCPObject::TCPObject()
|
||||
{
|
||||
mBuffer = NULL;
|
||||
mBufferSize = 0;
|
||||
mPort = 0;
|
||||
mTag = InvalidSocket;
|
||||
mNext = NULL;
|
||||
mState = Disconnected;
|
||||
|
||||
gTCPCount++;
|
||||
|
||||
if(gTCPCount == 1)
|
||||
{
|
||||
Net::smConnectionAccept.notify(processConnectedAcceptEvent);
|
||||
Net::smConnectionReceive.notify(processConnectedReceiveEvent);
|
||||
Net::smConnectionNotify.notify(processConnectedNotifyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
TCPObject::~TCPObject()
|
||||
{
|
||||
disconnect();
|
||||
dFree(mBuffer);
|
||||
|
||||
gTCPCount--;
|
||||
|
||||
if(gTCPCount == 0)
|
||||
{
|
||||
Net::smConnectionAccept.remove(processConnectedAcceptEvent);
|
||||
Net::smConnectionReceive.remove(processConnectedReceiveEvent);
|
||||
Net::smConnectionNotify.remove(processConnectedNotifyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
bool TCPObject::processArguments(S32 argc, const char **argv)
|
||||
{
|
||||
if(argc == 0)
|
||||
return true;
|
||||
else if(argc == 1)
|
||||
{
|
||||
addToTable(U32(dAtoi(argv[0])));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TCPObject::onAdd()
|
||||
{
|
||||
if(!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
const char *name = getName();
|
||||
|
||||
if(name && name[0] && getClassRep())
|
||||
{
|
||||
Namespace *parent = getClassRep()->getNameSpace();
|
||||
Con::linkNamespaces(parent->mName, name);
|
||||
mNameSpace = Con::lookupNamespace(name);
|
||||
|
||||
}
|
||||
|
||||
Sim::getTCPGroup()->addObject(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen)
|
||||
{
|
||||
// we got a raw buffer event
|
||||
// default action is to split the buffer into lines of text
|
||||
// and call processLine on each
|
||||
// for any incomplete lines we have mBuffer
|
||||
U32 start = 0;
|
||||
parseLine(buffer, &start, bufferLen);
|
||||
return start;
|
||||
}
|
||||
|
||||
void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen)
|
||||
{
|
||||
// find the first \n in buffer
|
||||
U32 i;
|
||||
U8 *line = buffer + *start;
|
||||
|
||||
for(i = *start; i < bufferLen; i++)
|
||||
if(buffer[i] == '\n' || buffer[i] == 0)
|
||||
break;
|
||||
U32 len = i - *start;
|
||||
|
||||
if(i == bufferLen || mBuffer)
|
||||
{
|
||||
// we've hit the end with no newline
|
||||
mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1);
|
||||
dMemcpy(mBuffer + mBufferSize, line, len);
|
||||
mBufferSize += len;
|
||||
*start = i;
|
||||
|
||||
// process the line
|
||||
if(i != bufferLen)
|
||||
{
|
||||
mBuffer[mBufferSize] = 0;
|
||||
if(mBufferSize && mBuffer[mBufferSize-1] == '\r')
|
||||
mBuffer[mBufferSize - 1] = 0;
|
||||
U8 *temp = mBuffer;
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
|
||||
processLine((UTF8*)temp);
|
||||
dFree(temp);
|
||||
}
|
||||
}
|
||||
else if(i != bufferLen)
|
||||
{
|
||||
line[len] = 0;
|
||||
if(len && line[len-1] == '\r')
|
||||
line[len-1] = 0;
|
||||
processLine((UTF8*)line);
|
||||
}
|
||||
if(i != bufferLen)
|
||||
*start = i + 1;
|
||||
}
|
||||
|
||||
void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId)
|
||||
{
|
||||
char idBuf[16];
|
||||
char addrBuf[256];
|
||||
Net::addressToString(addr, addrBuf);
|
||||
dSprintf(idBuf, sizeof(idBuf), "%d", connectId);
|
||||
onConnectionRequest_callback(addrBuf,idBuf);
|
||||
}
|
||||
|
||||
bool TCPObject::processLine(UTF8 *line)
|
||||
{
|
||||
onLine_callback(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TCPObject::onDNSResolved()
|
||||
{
|
||||
mState = DNSResolved;
|
||||
onDNSResolved_callback();
|
||||
}
|
||||
|
||||
void TCPObject::onDNSFailed()
|
||||
{
|
||||
mState = Disconnected;
|
||||
onDNSFailed_callback();
|
||||
}
|
||||
|
||||
|
||||
void TCPObject::onConnected()
|
||||
{
|
||||
mState = Connected;
|
||||
onConnected_callback();
|
||||
}
|
||||
|
||||
void TCPObject::onConnectFailed()
|
||||
{
|
||||
mState = Disconnected;
|
||||
onConnectFailed_callback();
|
||||
}
|
||||
|
||||
void TCPObject::finishLastLine()
|
||||
{
|
||||
if(mBufferSize)
|
||||
{
|
||||
mBuffer[mBufferSize] = 0;
|
||||
processLine((UTF8*)mBuffer);
|
||||
dFree(mBuffer);
|
||||
mBuffer = 0;
|
||||
mBufferSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TCPObject::onDisconnect()
|
||||
{
|
||||
finishLastLine();
|
||||
mState = Disconnected;
|
||||
onDisconnect_callback();
|
||||
}
|
||||
|
||||
void TCPObject::listen(U16 port)
|
||||
{
|
||||
mState = Listening;
|
||||
U32 newTag = Net::openListenPort(port);
|
||||
addToTable(newTag);
|
||||
}
|
||||
|
||||
void TCPObject::connect(const char *address)
|
||||
{
|
||||
NetSocket newTag = Net::openConnectTo(address);
|
||||
addToTable(newTag);
|
||||
}
|
||||
|
||||
void TCPObject::disconnect()
|
||||
{
|
||||
if( mTag != InvalidSocket ) {
|
||||
Net::closeConnectTo(mTag);
|
||||
}
|
||||
removeFromTable();
|
||||
}
|
||||
|
||||
void TCPObject::send(const U8 *buffer, U32 len)
|
||||
{
|
||||
Net::sendtoSocket(mTag, buffer, S32(len));
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, send, void, (const char *data),,
|
||||
"@brief Transmits the data string to the connected computer.\n\n"
|
||||
|
||||
"This method is used to send text data to the connected computer regardless if we initiated the "
|
||||
"connection using connect(), or listening to a port using listen().\n"
|
||||
|
||||
"@param data The data string to send.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the command data\n"
|
||||
"%data = \"GET \" @ $RSSFeed::serverURL @ \" HTTP/1.0\\r\\n\";\n"
|
||||
"%data = %data @ \"Host: \" @ $RSSFeed::serverName @ \"\\r\\n\";\n"
|
||||
"%data = %data @ \"User-Agent: \" @ $RSSFeed::userAgent @ \"\\r\\n\\r\\n\"\n\n"
|
||||
|
||||
"// Send the command to the connected server.\n"
|
||||
"%thisTCPObj.send(%data);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->send( (const U8*)data, dStrlen(data) );
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, listen, void, (int port),,
|
||||
"@brief Start listening on the specified port for connections.\n\n"
|
||||
|
||||
"@param port Port for this TCPObject to start listening for connections on.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the port number list\n"
|
||||
"%portNumber = 80;\n\n"
|
||||
|
||||
"// Inform this TCPObject to start listening at the specified port.\n"
|
||||
"%thisTCPObj.send(%portNumber);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->listen(U32(port));
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, connect, void, (const char* address),,
|
||||
"@brief Connect to the given address.\n\n"
|
||||
|
||||
"@param address Server address (including port) to connect to.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Set the address.\n"
|
||||
"%address = \"www.garagegames.com:80\";\n\n"
|
||||
|
||||
"// Inform this TCPObject to connect to the specified address.\n"
|
||||
"%thisTCPObj.connect(%address);\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->connect(address);
|
||||
}
|
||||
|
||||
DefineEngineMethod(TCPObject, disconnect, void, (),,
|
||||
"@brief Disconnect from whatever this TCPObject is currently connected to, if anything.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Inform this TCPObject to disconnect from anything it is currently connected to.\n"
|
||||
"%thisTCPObj.disconnect();\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
object->disconnect();
|
||||
}
|
||||
|
||||
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData)
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(sock);
|
||||
if(!tcpo)
|
||||
{
|
||||
Con::printf("Got bad connected receive event.");
|
||||
return;
|
||||
}
|
||||
|
||||
U32 size = incomingData.size;
|
||||
U8 *buffer = (U8*)incomingData.data;
|
||||
|
||||
while(size)
|
||||
{
|
||||
U32 ret = tcpo->onReceive(buffer, size);
|
||||
AssertFatal(ret <= size, "Invalid return size");
|
||||
size -= ret;
|
||||
buffer += ret;
|
||||
}
|
||||
}
|
||||
|
||||
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress)
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(listeningPort);
|
||||
if(!tcpo)
|
||||
return;
|
||||
|
||||
tcpo->onConnectionRequest(&originatingAddress, newConnection);
|
||||
}
|
||||
|
||||
void processConnectedNotifyEvent( NetSocket sock, U32 state )
|
||||
{
|
||||
TCPObject *tcpo = TCPObject::find(sock);
|
||||
if(!tcpo)
|
||||
return;
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case Net::DNSResolved:
|
||||
tcpo->onDNSResolved();
|
||||
break;
|
||||
case Net::DNSFailed:
|
||||
tcpo->onDNSFailed();
|
||||
break;
|
||||
case Net::Connected:
|
||||
tcpo->onConnected();
|
||||
break;
|
||||
case Net::ConnectFailed:
|
||||
tcpo->onConnectFailed();
|
||||
break;
|
||||
case Net::Disconnected:
|
||||
tcpo->onDisconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
96
Engine/source/app/net/tcpObject.h
Normal file
96
Engine/source/app/net/tcpObject.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TCPOBJECT_H_
|
||||
#define _TCPOBJECT_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
#include "platform/platformNet.h"
|
||||
|
||||
class TCPObject : public SimObject
|
||||
{
|
||||
public:
|
||||
enum State {Disconnected, DNSResolved, Connected, Listening };
|
||||
|
||||
DECLARE_CALLBACK(void, onConnectionRequest, (const char* address, const char* ID));
|
||||
DECLARE_CALLBACK(void, onLine, (const char* line));
|
||||
DECLARE_CALLBACK(void, onDNSResolved,());
|
||||
DECLARE_CALLBACK(void, onDNSFailed, ());
|
||||
DECLARE_CALLBACK(void, onConnected, ());
|
||||
DECLARE_CALLBACK(void, onConnectFailed, ());
|
||||
DECLARE_CALLBACK(void, onDisconnect, ());
|
||||
|
||||
private:
|
||||
NetSocket mTag;
|
||||
TCPObject *mNext;
|
||||
enum { TableSize = 256, TableMask = 0xFF };
|
||||
static TCPObject *table[TableSize];
|
||||
State mState;
|
||||
|
||||
protected:
|
||||
typedef SimObject Parent;
|
||||
U8 *mBuffer;
|
||||
U32 mBufferSize;
|
||||
U16 mPort;
|
||||
|
||||
public:
|
||||
TCPObject();
|
||||
virtual ~TCPObject();
|
||||
|
||||
void parseLine(U8 *buffer, U32 *start, U32 bufferLen);
|
||||
void finishLastLine();
|
||||
|
||||
static TCPObject *find(NetSocket tag);
|
||||
|
||||
// onReceive gets called continuously until all bytes are processed
|
||||
// return # of bytes processed each time.
|
||||
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
|
||||
virtual bool processLine(UTF8 *line); // process a complete line of text... default action is to call into script
|
||||
virtual void onDNSResolved();
|
||||
virtual void onDNSFailed();
|
||||
virtual void onConnected();
|
||||
virtual void onConnectFailed();
|
||||
virtual void onConnectionRequest(const NetAddress *addr, U32 connectId);
|
||||
virtual void onDisconnect();
|
||||
void connect(const char *address);
|
||||
void listen(U16 port);
|
||||
void disconnect();
|
||||
State getState() { return mState; }
|
||||
|
||||
bool processArguments(S32 argc, const char **argv);
|
||||
void send(const U8 *buffer, U32 bufferLen);
|
||||
void addToTable(NetSocket newTag);
|
||||
void removeFromTable();
|
||||
|
||||
void setPort(U16 port) { mPort = port; }
|
||||
|
||||
bool onAdd();
|
||||
|
||||
DECLARE_CONOBJECT(TCPObject);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // _H_TCPOBJECT_
|
||||
132
Engine/source/app/version.cpp
Normal file
132
Engine/source/app/version.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "app/version.h"
|
||||
#include "console/console.h"
|
||||
|
||||
static const U32 csgVersionNumber = TORQUE_GAME_ENGINE;
|
||||
|
||||
U32 getVersionNumber()
|
||||
{
|
||||
return csgVersionNumber;
|
||||
}
|
||||
|
||||
const char* getVersionString()
|
||||
{
|
||||
return TORQUE_GAME_ENGINE_VERSION_STRING;
|
||||
}
|
||||
|
||||
/// TGE 0001
|
||||
/// TGEA 0002
|
||||
/// TGB 0003
|
||||
/// TGEA 360 0004
|
||||
/// TGE WII 0005
|
||||
/// Torque 3D 0006
|
||||
|
||||
const char* getEngineProductString()
|
||||
{
|
||||
#ifndef TORQUE_ENGINE_PRODUCT
|
||||
return "Torque Engine";
|
||||
#else
|
||||
switch (TORQUE_ENGINE_PRODUCT)
|
||||
{
|
||||
case 0001:
|
||||
return "Torque Game Engine";
|
||||
case 0002:
|
||||
return "Torque Game Engine Advanced";
|
||||
case 0003:
|
||||
return "Torque 2D";
|
||||
case 0004:
|
||||
return "Torque 360";
|
||||
case 0005:
|
||||
return "Torque for Wii";
|
||||
case 0006:
|
||||
return "Torque 3D";
|
||||
|
||||
default:
|
||||
return "Torque Engine";
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* getCompileTimeString()
|
||||
{
|
||||
return __DATE__ " at " __TIME__;
|
||||
}
|
||||
//----------------------------------------------------------------
|
||||
|
||||
ConsoleFunctionGroupBegin( CompileInformation, "Functions to get version information about the current executable." );
|
||||
|
||||
ConsoleFunction( getVersionNumber, S32, 1, 1, "Get the version of the build, as a string.\n\n"
|
||||
"@ingroup Debugging")
|
||||
{
|
||||
return getVersionNumber();
|
||||
}
|
||||
|
||||
ConsoleFunction( getVersionString, const char*, 1, 1, "Get the version of the build, as a string.\n\n"
|
||||
"@ingroup Debugging")
|
||||
{
|
||||
return getVersionString();
|
||||
}
|
||||
|
||||
ConsoleFunction( getEngineName, const char*, 1, 1, "Get the name of the engine product that this is running from, as a string.\n\n"
|
||||
"@ingroup Debugging")
|
||||
{
|
||||
return getEngineProductString();
|
||||
}
|
||||
|
||||
ConsoleFunction( getCompileTimeString, const char*, 1, 1, "Get the time of compilation.\n\n"
|
||||
"@ingroup Debugging")
|
||||
{
|
||||
return getCompileTimeString();
|
||||
}
|
||||
|
||||
ConsoleFunction( getBuildString, const char*, 1, 1, "Get the type of build, \"Debug\" or \"Release\".\n\n"
|
||||
"@ingroup Debugging")
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
return "Debug";
|
||||
#else
|
||||
return "Release";
|
||||
#endif
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd( CompileInformation );
|
||||
|
||||
ConsoleFunction(isDemo, bool, 1, 1, "")
|
||||
{
|
||||
#ifdef TORQUE_DEMO
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
ConsoleFunction(isWebDemo, bool, 1, 1, "")
|
||||
{
|
||||
#ifdef TORQUE_DEMO
|
||||
return Platform::getWebDeployment();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
45
Engine/source/app/version.h
Normal file
45
Engine/source/app/version.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _VERSION_H_
|
||||
#define _VERSION_H_
|
||||
|
||||
/// This is our global version number for the engine source code that
|
||||
/// we are using. See <game>/source/torqueConfig.h for the game's source
|
||||
/// code version, the game name, and which type of game it is (TGB, TGE, TGEA, etc.).
|
||||
///
|
||||
/// Version number is major * 1000 + minor * 100 + revision * 10.
|
||||
#define TORQUE_GAME_ENGINE 1100
|
||||
|
||||
/// Human readable engine version string.
|
||||
#define TORQUE_GAME_ENGINE_VERSION_STRING "2011"
|
||||
|
||||
/// Gets the specified version number. The version number is specified as a global in version.cc
|
||||
U32 getVersionNumber();
|
||||
/// Gets the version number in string form
|
||||
const char* getVersionString();
|
||||
/// Gets the engine product name in string form
|
||||
const char* getEngineProductString();
|
||||
/// Gets the compile date and time
|
||||
const char* getCompileTimeString();
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue