diff --git a/StormLib/stormlib/SCommon.cpp b/StormLib/stormlib/SCommon.cpp index 3195acb..17132a5 100644 --- a/StormLib/stormlib/SCommon.cpp +++ b/StormLib/stormlib/SCommon.cpp @@ -373,7 +373,6 @@ DWORD DecryptName1(const char * szFileName) DWORD dwSeed1 = 0x7FED7FED; DWORD dwSeed2 = 0xEEEEEEEE; DWORD ch; - while(*pbKey != 0) { @@ -381,8 +380,7 @@ DWORD DecryptName1(const char * szFileName) dwSeed1 = StormBuffer[0x100 + ch] ^ (dwSeed1 + dwSeed2); dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; - } - + } return dwSeed1; } @@ -445,7 +443,7 @@ TMPQHash * GetHashEntry(TMPQArchive * ha, const char * szFileName) dwIndex = DecryptHashIndex(ha, szFileName); dwName1 = DecryptName1(szFileName); dwName2 = DecryptName2(szFileName); - pHash = pHash0 = ha->pHashTable + dwIndex; + pHash = pHash0 = ha->pHashTable + dwIndex; // Look for hash index while(pHash->dwBlockIndex != HASH_ENTRY_FREE) diff --git a/StormLib/stormlib/SFileOpenFileEx.cpp b/StormLib/stormlib/SFileOpenFileEx.cpp index 43ffdc9..0e0affc 100644 --- a/StormLib/stormlib/SFileOpenFileEx.cpp +++ b/StormLib/stormlib/SFileOpenFileEx.cpp @@ -281,7 +281,7 @@ BOOL WINAPI SFileOpenFileEx(HANDLE hMPQ, const char * szFileName, DWORD dwSearch if(dwSearchScope != SFILE_OPEN_BY_INDEX && (szFileName == NULL || *szFileName == 0)) nError = ERROR_INVALID_PARAMETER; } - + // Prepare the file opening if(nError == ERROR_SUCCESS) { @@ -310,10 +310,10 @@ BOOL WINAPI SFileOpenFileEx(HANDLE hMPQ, const char * szFileName, DWORD dwSearch nHandleSize = sizeof(TMPQFile) + strlen(szFileName); if((pHash = GetHashEntryEx(ha, szFileName, lcLocale)) != NULL) - { + { dwHashIndex = (DWORD)(pHash - ha->pHashTable); dwBlockIndex = pHash->dwBlockIndex; - } + } } } diff --git a/StormLib/stormlib/SFileReadFile.cpp b/StormLib/stormlib/SFileReadFile.cpp index 5fd3cb6..3553ff9 100644 --- a/StormLib/stormlib/SFileReadFile.cpp +++ b/StormLib/stormlib/SFileReadFile.cpp @@ -214,14 +214,14 @@ static DWORD WINAPI ReadMPQBlocks(TMPQFile * hf, DWORD dwBlockPos, BYTE * buffer if(blockSize < (DWORD)outLength) { // Is the file compressed with PKWARE Data Compression Library ? - if(hf->pBlock->dwFlags & MPQ_FILE_IMPLODE) - Decompress_pklib((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize); + if(hf->pBlock->dwFlags & MPQ_FILE_IMPLODE) + Decompress_pklib((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize); // Is it a file compressed by Blizzard's multiple compression ? // Note that Storm.dll v 1.0.9 distributed with Warcraft III // passes the full path name of the opened archive as the new last parameter if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS) - { + { if(!SCompDecompress((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize)) { dwBytesRead = 0; diff --git a/default-legacy.cfg b/default-legacy.cfg new file mode 100644 index 0000000..9a1ba30 --- /dev/null +++ b/default-legacy.cfg @@ -0,0 +1,510 @@ +######################## +# CURSES CONFIGURATION # +######################## + +### the term buffer size / window size + +term_width = 135 +term_height = 52 + +### enable curses user interface + +curses_enabled = 1 + +### enable split view in realm windows + +curses_splitview = 0 + +### 0: horizontal lists, anything else: vertical lists + +curses_listtype = 0 + +##################### +# BOT CONFIGURATION # +##################### + +### the log file + +bot_log = ghost.log + +### the log method +### set this to 1 to leave the log unlocked while GHost++ is running (may be slower, particularly on Windows) +### set this to 2 to lock the log while GHost++ is running (may be faster, particularly on Windows) +### note: if the log is locked you will not be able to edit/move/delete it while GHost++ is running + +bot_logmethod = 1 + +### the language file + +bot_language = language.cfg + +### the path to your local Warcraft III directory +### this path must contain war3.exe, storm.dll, and game.dll +### if this path contains War3Patch.mpq the bot will attempt to extract "Scripts\common.j" and "Scripts\blizzard.j" on startup and write them to bot_mapcfgpath (which is defined later in this file) +### common.j and blizzard.j are only required for automatically calculating map_crc, you do not need them if your map config files already contain map_crc + +bot_war3path = C:\Program Files\Warcraft III + +### whether to act as Warcraft III: The Frozen Throne or not +### set this to 0 to act as Warcraft III: Reign of Chaos (you WILL NOT need to enter a TFT cd key to login to battle.net) +### set this to 1 to act as Warcraft III: The Frozen Throne (you WILL need to enter a TFT cd key to login to battle.net) + +bot_tft = 1 + +### the address GHost++ will bind to when hosting games (leave it blank to bind to all available addresses) +### if you don't know what this is just leave it blank + +bot_bindaddress = + +### the port GHost++ will host games on (this must be different from your admingame_port) + +bot_hostport = 6112 + +### whether to allow GProxy++ reliable reconnects or not +### you should ensure that bot_synclimit is set to a reasonable value if you choose to allow GProxy++ reliable reconnects +### a reasonable value is 5000 divided by bot_latency, e.g. if bot_latency is 100 use a value of 50 for bot_synclimit + +bot_reconnect = 0 + +### the port GHost++ will listen for GProxy++ reliable reconnects on + +bot_reconnectport = 6114 + +### the maximum number of minutes to wait for a GProxy++ client to reconnect to the game +### if you set this to 0 or 1 GHost++ will wait for up to 1 minute +### if you set this to 10 or more GHost++ will only wait for up to 10 minutes +### due to the way GProxy++ works, increasing this value increases bandwidth requirements and CPU requirements on the players' computers +### players can always vote to drop a player who is lagging after waiting 45 seconds regardless of this value + +bot_reconnectwaittime = 3 + +### maximum number of games to host at once + +bot_maxgames = 5 + +### command trigger for ingame only (battle.net command triggers are defined later) + +bot_commandtrigger = ! + +### the path to the directory where you keep your map config files +### this directory can also contain common.j and blizzard.j (extracted from War3Patch.mpq) +### common.j and blizzard.j are only required for automatically calculating map_crc, you do not need them if your map config files already contain map_crc + +bot_mapcfgpath = mapcfgs + +### the path to the directory where you keep your savegame files + +bot_savegamepath = savegames + +### the path to the directory where you keep your map files +### GHost++ doesn't require map files but if it has access to them it can send them to players and automatically calculate most map config values +### GHost++ will search [bot_mappath + map_localpath] for the map file (map_localpath is set in each map's config file) + +bot_mappath = maps + +### whether to save replays or not + +bot_savereplays = 0 + +### the path to the directory where you want GHost++ to save replays + +bot_replaypath = replays + +### the Warcraft 3 version to save replays as + +replay_war3version = 24 + +### the Warcraft 3 build number to save replays as (this is specific to each Warcraft 3 version) +### patch 1.23: war3version 23, buildnumber 6058 +### patch 1.24: war3version 24, buildnumber 6059 +### patch 1.24b: war3version 24, buildnumber 6059 + +replay_buildnumber = 6059 + +### the bot's virtual host name as it appears in the game lobby +### colour codes are defined by the sequence "|cFF" followed by a six character hexadecimal colour in RRGGBB format (e.g. 0000FF for pure blue) +### the virtual host name cannot be longer than 15 characters including the colour code, if you try to go over this limit GHost++ will use the default virtual host name + +bot_virtualhostname = |cFF4080C0GHost + +### whether to hide each player's IP address from other players or not + +bot_hideipaddresses = 0 + +### whether to check for multiple IP address usage or not + +bot_checkmultipleipusage = 1 + +### whether to do automatic spoof checks or not +### you can always manually spoof check by whispering the bot (and in fact this is required before running admin commands) +### set to 0 to disable automatic spoof checks +### set to 1 to enable automatic spoof checks on all players +### set to 2 to enable automatic spoof checks on potential admins only + +bot_spoofchecks = 2 + +### whether to require spoof checks or not +### this controls whether the bot will require players to spoof check before starting the game +### it does NOT control whether the bot will require players to spoof check before running admin commands - spoof checks are ALWAYS required for admin status +### if you require spoof checks, players will be kicked from the lobby if they haven't spoof checked within 20 seconds of joining (autohosted games only) + +bot_requirespoofchecks = 0 + +### whether to consider admins and root admins as reserved players or not +### reserved players are allowed to join full games because another player will be kicked to allow them to join + +bot_reserveadmins = 1 + +### whether to display game refresh messages by default +### this can always be changed for a particular game with the !refresh command + +bot_refreshmessages = 0 + +### whether to automatically lock games when the owner joins + +bot_autolock = 0 + +### whether to automatically save games when a player disconnects +### this can always be changed for a particular game with the !autosave command + +bot_autosave = 0 + +### whether to allow map downloads or not +### set to 0 to disable map downloads +### set to 1 to enable map downloads +### set to 2 to enable conditional map downloads (an admin must start each map download with the !download or !dl command) + +bot_allowdownloads = 1 + +### whether to ping players during map downloads or not +### GHost++ will always stop pinging any players who are downloading the map +### this config value determines whether GHost++ should stop pinging *all* players when at least one player is downloading the map + +bot_pingduringdownloads = 0 + +### the maximum number of players allowed to download the map at the same time + +bot_maxdownloaders = 3 + +### the maximum combined download speed of all players downloading the map (in KB/sec) + +bot_maxdownloadspeed = 100 + +### use LC style pings (divide actual pings by two) + +bot_lcpings = 1 + +### auto kick players with ping higher than this + +bot_autokickping = 400 + +### the ban method +### if bot_banmethod = 1, GHost++ will automatically reject players using a banned name +### if bot_banmethod = 2, GHost++ will automatically reject players using a banned IP address +### if bot_banmethod = 3, GHost++ will automatically reject players using a banned name or IP address +### if bot_banmethod is anything else GHost++ will print a message when a banned player joins but will not automatically reject them + +bot_banmethod = 1 + +### the IP blacklist file + +bot_ipblacklistfile = ipblacklist.txt + +### automatically close the game lobby if a reserved player (or admin) doesn't join it for this many minutes +### games which are set to automatically start when enough players join are exempt from this limit (e.g. autohosted games) + +bot_lobbytimelimit = 10 + +### the game latency +### this can always be changed for a particular game with the !latency command (which enforces a minimum of 20 and a maximum of 500) + +bot_latency = 100 + +### the maximum number of packets a player is allowed to get out of sync by before starting the lag screen +### before version 8.0 GHost++ did not have a lag screen which is the same as setting this to a very high number +### this can always be changed for a particular game with the !synclimit command (which enforces a minimum of 10 and a maximum of 10000) + +bot_synclimit = 50 + +### whether votekicks are allowed or not + +bot_votekickallowed = 1 + +### the percentage of players required to vote yes for a votekick to pass +### the player starting the votekick is assumed to have voted yes and the player the votekick is started against is assumed to have voted no +### the formula for calculating the number of votes needed is votes_needed = ceil( ( num_players - 1 ) * bot_votekickpercentage / 100 ) +### this means it will round UP the number of votes required +### if you set it to 100 it will require 2/3, 3/4, 4/5, 5/6, 6/7, 7/8, 8/9, 9/10, 10/11, and 11/12 votes to pass +### if you set it to 90 it will require 2/3, 3/4, 4/5, 5/6, 6/7, 7/8, 8/9, 9/10, 9/11, and 10/12 votes to pass +### if you set it to 80 it will require 2/3, 3/4, 4/5, 4/6, 5/7, 6/8, 7/9, 8/10, 8/11, and 9/12 votes to pass +### if you set it to 70 it will require 2/3, 3/4, 3/5, 4/6, 5/7, 5/8, 6/9, 7/10, 7/11, and 8/12 votes to pass +### if you set it to 60 it will require 2/3, 2/4, 3/5, 3/6, 4/7, 5/8, 5/9, 6/10, 6/11, and 7/12 votes to pass + +bot_votekickpercentage = 100 + +### the default map config (the ".cfg" will be added automatically if you leave it out) + +bot_defaultmap = wormwar + +### the MOTD file +### the first 8 lines of this file will be displayed when a player joins the game +### if this file doesn't exist a default MOTD will be used + +bot_motdfile = motd.txt + +### the gameloaded file +### the first 8 lines of this file will be displayed when the game finished loading (after the player loading times are displayed) + +bot_gameloadedfile = gameloaded.txt + +### the gameover file +### the first 8 lines of this file will be displayed when the game is over +### this only works when using a stats class - note: at the time of this writing the only stats class is for DotA maps + +bot_gameoverfile = gameover.txt + +### whether to send "local admin messages" or not +### these messages are battle.net chat messages, whispers, and emotes which the bot receives and passes on to the "local admin" +### the local admin is the game owner if they are playing from a LAN or the same computer as the bot +### this is useful when you are using the admin game to play with one set of CD keys and you want messages sent to the bot to be relayed to you +### you can enable or disable this for a particular game with the !messages command + +bot_localadminmessages = 1 + +### the "TCP no delay" flag +### this controls whether or not your operating system should use the "no delay" algorithm on game sockets +### the algorithm is designed to reduce latency by sending data in small packets as soon as possible rather than waiting to send a single larger packet +### enabling this algorithm requires additional bandwidth because it is a less efficient way of sending data +### however, it may reduce game latencies in some cases + +tcp_nodelay = 0 + +### the matchmaking method +### this controls how the bot matches players when they join the game when using !autohostmm +### set it to 0 to disable matchmaking (first come first served, even if their scores are very different) +### set it to 1 to use the "furthest score" method (the player with the furthest score from the average is kicked to make room for another player) +### set it to 2 to use the "lowest score" method (the player with the lowest score is kicked to make room for another player) + +bot_matchmakingmethod = 1 + +############################ +# ADMIN GAME CONFIGURATION # +############################ + +### whether to create the admin game or not (see readme.txt for more information) + +admingame_create = 0 + +### the port GHost++ will host the admin game on (this must be different from your bot_hostport) + +admingame_port = 6113 + +### the admin game password + +admingame_password = + +### the default map config to use in the admin game +### if this value is blank the bot will use a hardcoded map instead +### it's recommended that you use the hardcoded map instead of specifying a different one +### this value exists because the hardcoded map is specific to Warcraft 3 versions and you may wish to use a previous or newer version +### the ".cfg" will be added automatically if you leave it out + +admingame_map = + +##################### +# LAN CONFIGURATION # +##################### + +### the Warcraft 3 version to use when broadcasting LAN games + +lan_war3version = 24 + +### the UDP broadcast target +### if this value is blank the bot will try to broadcast LAN games on the default interface which is chosen by your operating system +### sometimes your operating system will choose the wrong interface when more than one exists +### therefore you can use this value to force the bot to use a specific interface +### for example you may set it to "192.168.1.255" to broadcast LAN games to the 192.168.1.x subnet + +udp_broadcasttarget = + +### the UDP "don't route" flag + +udp_dontroute = 0 + +########################## +# AUTOHOST CONFIGURATION # +########################## + +### this section of the config file is for enabling autohost when the bot starts up without having to issue a command +### you can activate the autohost feature without changing anything here by using the !autohost command + +autohost_maxgames = 0 +autohost_startplayers = 0 +autohost_gamename = +autohost_owner = + +########################## +# DATABASE CONFIGURATION # +########################## + +### database type +### use "sqlite3" for a local SQLite database +### use "mysql" for any MySQL database + +db_type = sqlite3 + +### sqlite3 database configuration +### this is only used if your database type is SQLite + +db_sqlite3_file = ghost.dbs + +### mysql database configuration +### this is only used if your database type is MySQL + +db_mysql_server = localhost +db_mysql_database = ghost +db_mysql_user = YOUR_USERNAME +db_mysql_password = YOUR_PASSWORD +db_mysql_port = 0 + +### the bot ID is included each time the bot adds data to the MySQL database +### it is used to identify where each row of data came from when you configure multiple bots to connect to the same MySQL database +### GHost++ does not use the bot ID number itself, it's just to help you keep track of the data in your database + +db_mysql_botid = 1 + +############################ +# BATTLE.NET CONFIGURATION # +############################ + +### which battle.net server to connect to +### 1.) useast.battle.net +### 2.) uswest.battle.net +### 3.) asia.battle.net +### 4.) europe.battle.net +### note that each banned player is tied to the realm it was created on and the realm is case sensitive +### so if you change your realm from useast.battle.net to USEAST.BATTLE.NET it'll still connect but anyone previously banned will not be counted as banned until you change it back + +bnet_server = useast.battle.net + +### the server alias +### this name will be used to identify the battle.net server in the GHost++ console +### if you leave it blank it will use a short name such as "USEast" for official battle.net servers or it will use the actual server address + +bnet_serveralias = USEast + +### your Warcraft III: Reign of Chaos CD key +### you cannot use the same CD key here that you yourself use to login to battle.net if you plan to login at the same time as your bot + +bnet_cdkeyroc = FFFFFFFFFFFFFFFFFFFFFFFFFF + +### your Warcraft III: The Frozen Throne CD key +### you cannot use the same CD key here that you yourself use to login to battle.net if you plan to login at the same time as your bot + +bnet_cdkeytft = FFFFFFFFFFFFFFFFFFFFFFFFFF + +### the locale specifies the area of the world you are from +### battle.net uses this to group players together, showing them games hosted by players and bots mostly from their own area +### it's important to set this to the correct value to increase the effectiveness of the game refresher +### if you are using Windows you can set this to "system" to use the locale of your system +### otherwise you can use the list at the following URL to get the correct value for your area: +### http://msdn.microsoft.com/en-us/library/0h88fahh%28VS.85%29.aspx +### put the "decimal value" here, e.g. 1033 is the default for "English - United States" +### note: you cannot use a value of "system" on Linux, doing so will use a default value of 1033 instead + +bnet_locale = system + +### your battle.net username +### you cannot use the same username here that you yourself use to login to battle.net if you plan to login at the same time as your bot + +bnet_username = + +### your battle.net password + +bnet_password = + +### the first channel to join upon entering battle.net + +bnet_firstchannel = The Void + +### the root admins on this battle.net server only +### seperate each name with a space, e.g. bnet_rootadmin = Varlock Kilranin Instinct121 + +bnet_rootadmin = + +### command trigger for this battle.net server only + +bnet_commandtrigger = ! + +### whether to automatically add your friends list to each game's reserved list + +bnet_holdfriends = 1 + +### whether to automatically add your clan members list to each game's reserved list + +bnet_holdclan = 1 + +### whether to allow anonymous users (non admins) to use public commands such as !stats and !statsdota on this battle.net connection +### if you are having trouble with spammers causing your bot to flood the server you should disable this + +bnet_publiccommands = 1 + +### BNLS server information for Warden handling (see readme.txt for more information) +### you will need to use a valid BNLS server here if you are connecting to an official battle.net realm or you will be disconnected every two minutes + +bnet_bnlsserver = localhost +bnet_bnlsport = 9367 +bnet_bnlswardencookie = 1 + +### you will need to edit this section of the config file if you're connecting to a PVPGN server +### your PVPGN server operator will tell you what to put here + +bnet_custom_war3version = 24 +bnet_custom_exeversion = +bnet_custom_exeversionhash = +bnet_custom_passwordhashtype = +bnet_custom_pvpgnrealmname = PvPGN Realm + +### +### example configuration for connecting to a second official battle.net server +### + +# bnet2_server = uswest.battle.net +# bnet2_serveralias = USWest +# bnet2_cdkeyroc = FFFFFFFFFFFFFFFFFFFFFFFFFF +# bnet2_cdkeytft = FFFFFFFFFFFFFFFFFFFFFFFFFF +# bnet2_locale = system +# bnet2_username = +# bnet2_password = +# bnet2_firstchannel = The Void +# bnet2_rootadmin = +# bnet2_commandtrigger = ! +# bnet2_holdfriends = 1 +# bnet2_holdclan = 1 +# bnet2_publiccommands = 1 +# bnet2_bnlsserver = localhost +# bnet2_bnlsport = 9367 +# bnet2_bnlswardencookie = 2 + +### +### example configuration for connecting to a third PVPGN battle.net server +### + +# bnet3_server = server.eurobattle.net +# bnet3_serveralias = EuroBattle +# bnet3_cdkeyroc = FFFFFFFFFFFFFFFFFFFFFFFFFF +# bnet3_cdkeytft = FFFFFFFFFFFFFFFFFFFFFFFFFF +# bnet3_locale = system +# bnet3_username = +# bnet3_password = +# bnet3_firstchannel = The Void +# bnet3_rootadmin = +# bnet3_commandtrigger = ! +# bnet3_holdfriends = 1 +# bnet3_holdclan = 1 +# bnet3_publiccommands = 1 +# bnet3_custom_war3version = 24 +# bnet3_custom_exeversion = 184 0 22 1 +# bnet3_custom_exeversionhash = 219 152 153 144 +# bnet3_custom_passwordhashtype = pvpgn +# bnet3_custom_pvpgnrealmname = PvPGN Realm diff --git a/default.cfg b/default.cfg index ee5e945..59bb620 100644 --- a/default.cfg +++ b/default.cfg @@ -1,3 +1,41 @@ +#################### +# UI CONFIGURATION # +#################### + +### enable curses user interface + +ui_enabled = 1 + +### the user interface size +### size should be reasonable: aspect ratio larger than 2:5 and height between 11 and 85. +### good sizes: 120x25, 125x45, 135x43, 150x50 +### it might crash with some values +### configure the size here instead of using command /resize in the UI +### size is limited by display resolution, in Windows xp when using 1024x768 you shouldn't use sizes larger than 125x45 or it crashes + +ui_width = 125 +ui_height = 45 + +### split games-tab +### splitsid: server id. (bnet_ = 1, bnet2_ = 2, bnet3_ = 3, etc...) +### spliton: enable split-view + +ui_splitsid = 1 +ui_spliton = 0 + +### game-tab: show general info always = 0 +### show general info in a tab = 1 + +ui_gameinfotab = 0 + +### debugging +### adds debug-tab, writes debugging info to ui_log.txt +### it is recommended to not use this because of the log's filesize and possible performance loss +### use this only if you encounter a crash and plan to reproduce it etc.. + +ui_debugon = 0 + + ##################### # BOT CONFIGURATION # ##################### diff --git a/ghost-legacy.exe b/ghost-legacy.exe new file mode 100644 index 0000000..3ecaf61 Binary files /dev/null and b/ghost-legacy.exe differ diff --git a/ghost-legacy/Makefile b/ghost-legacy/Makefile new file mode 100644 index 0000000..987f491 --- /dev/null +++ b/ghost-legacy/Makefile @@ -0,0 +1,85 @@ +SHELL = /bin/sh +SYSTEM = $(shell uname) +C++ = g++ +CC = gcc +DFLAGS = -DGHOST_MYSQL +OFLAGS = -O3 +LFLAGS = -L. -L../bncsutil/src/bncsutil/ -L../StormLib/stormlib/ -lbncsutil -lpthread -ldl -lz -lStorm -lmysqlclient_r -lboost_date_time-mt -lboost_thread-mt -lboost_system-mt -lboost_filesystem-mt -lcurses +CFLAGS = + +ifeq ($(SYSTEM),Darwin) +DFLAGS += -D__APPLE__ +OFLAGS += -flat_namespace +else +LFLAGS += -lrt +endif + +ifeq ($(SYSTEM),FreeBSD) +DFLAGS += -D__FREEBSD__ +endif + +ifeq ($(SYSTEM),SunOS) +DFLAGS += -D__SOLARIS__ +LFLAGS += -lresolv -lsocket -lnsl +endif + +CFLAGS += $(OFLAGS) $(DFLAGS) -I. -I../bncsutil/src/ -I../StormLib/ + +ifeq ($(SYSTEM),Darwin) +CFLAGS += -I../mysql/include/ +endif + +OBJS = bncsutilinterface.o bnet.o bnetprotocol.o bnlsclient.o bnlsprotocol.o commandpacket.o config.o crc32.o csvparser.o game.o game_admin.o game_base.o gameplayer.o gameprotocol.o gameslot.o ghost.o ghostdb.o ghostdbmysql.o ghostdbsqlite.o gpsprotocol.o language.o map.o packed.o replay.o savegame.o sha1.o socket.o stats.o statsdota.o statsw3mmd.o util.o userinterface.o +COBJS = sqlite3.o +PROGS = ./ghost++ + +all: $(OBJS) $(COBJS) $(PROGS) + +./ghost++: $(OBJS) $(COBJS) + $(C++) -o ./ghost++ $(OBJS) $(COBJS) $(LFLAGS) + +clean: + rm -f $(OBJS) $(COBJS) $(PROGS) + +$(OBJS): %.o: %.cpp + $(C++) -o $@ $(CFLAGS) -c $< + +$(COBJS): %.o: %.c + $(CC) -o $@ $(CFLAGS) -c $< + +./ghost++: $(OBJS) $(COBJS) + +all: $(PROGS) + +bncsutilinterface.o: ghost.h includes.h util.h bncsutilinterface.h +bnet.o: ghost.h includes.h util.h config.h language.h socket.h commandpacket.h ghostdb.h bncsutilinterface.h bnlsclient.h bnetprotocol.h bnet.h map.h packed.h savegame.h replay.h gameprotocol.h game_base.h +bnetprotocol.o: ghost.h includes.h util.h bnetprotocol.h +bnlsclient.o: ghost.h includes.h util.h socket.h commandpacket.h bnlsprotocol.h bnlsclient.h +bnlsprotocol.o: ghost.h includes.h util.h bnlsprotocol.h +commandpacket.o: ghost.h includes.h commandpacket.h +config.o: ghost.h includes.h config.h +crc32.o: ghost.h includes.h crc32.h +csvparser.o: csvparser.h +game.o: ghost.h includes.h util.h config.h language.h socket.h ghostdb.h bnet.h map.h packed.h savegame.h gameplayer.h gameprotocol.h game_base.h game.h stats.h statsdota.h statsw3mmd.h +game_admin.o: ghost.h includes.h util.h config.h language.h socket.h ghostdb.h bnet.h map.h packed.h savegame.h replay.h gameplayer.h gameprotocol.h game_base.h game_admin.h +game_base.o: ghost.h includes.h util.h config.h language.h socket.h ghostdb.h bnet.h map.h packed.h savegame.h replay.h gameplayer.h gameprotocol.h game_base.h next_combination.h +gameplayer.o: ghost.h includes.h util.h language.h socket.h commandpacket.h bnet.h map.h gameplayer.h gameprotocol.h gpsprotocol.h game_base.h +gameprotocol.o: ghost.h includes.h util.h crc32.h gameplayer.h gameprotocol.h game_base.h +gameslot.o: ghost.h includes.h gameslot.h +ghost.o: ghost.h includes.h util.h crc32.h sha1.h csvparser.h config.h language.h socket.h ghostdb.h ghostdbsqlite.h ghostdbmysql.h bnet.h map.h packed.h savegame.h gameplayer.h gameprotocol.h gpsprotocol.h game_base.h game.h game_admin.h userinterface.h +ghostdb.o: ghost.h includes.h util.h config.h ghostdb.h +ghostdbmysql.o: ghost.h includes.h util.h config.h ghostdb.h ghostdbmysql.h +ghostdbsqlite.o: ghost.h includes.h util.h config.h ghostdb.h ghostdbsqlite.h +gpsprotocol.o: ghost.h util.h gpsprotocol.h +language.o: ghost.h includes.h config.h language.h +map.o: ghost.h includes.h util.h crc32.h sha1.h config.h map.h +packed.o: ghost.h includes.h util.h crc32.h packed.h +replay.o: ghost.h includes.h util.h packed.h replay.h gameprotocol.h +savegame.o: ghost.h includes.h util.h packed.h savegame.h +sha1.o: sha1.h +socket.o: ghost.h includes.h util.h socket.h +stats.o: ghost.h includes.h stats.h +statsdota.o: ghost.h includes.h util.h ghostdb.h gameplayer.h gameprotocol.h game_base.h stats.h statsdota.h +statsw3mmd.o: ghost.h includes.h util.h ghostdb.h gameprotocol.h game_base.h stats.h statsw3mmd.h +util.o: ghost.h includes.h util.h +userinterface.h: includes.h diff --git a/ghost-legacy/StormLibRAS.lib b/ghost-legacy/StormLibRAS.lib new file mode 100644 index 0000000..82d9913 Binary files /dev/null and b/ghost-legacy/StormLibRAS.lib differ diff --git a/ghost-legacy/bncsutilinterface.cpp b/ghost-legacy/bncsutilinterface.cpp new file mode 100644 index 0000000..637bc8d --- /dev/null +++ b/ghost-legacy/bncsutilinterface.cpp @@ -0,0 +1,161 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "bncsutilinterface.h" + +#include + +// +// CBNCSUtilInterface +// + +CBNCSUtilInterface :: CBNCSUtilInterface( string userName, string userPassword ) +{ + // m_nls = (void *)nls_init( userName.c_str( ), userPassword.c_str( ) ); + m_NLS = new NLS( userName, userPassword ); +} + +CBNCSUtilInterface :: ~CBNCSUtilInterface( ) +{ + // nls_free( (nls_t *)m_nls ); + delete (NLS *)m_NLS; +} + +void CBNCSUtilInterface :: Reset( string userName, string userPassword ) +{ + // nls_free( (nls_t *)m_nls ); + // m_nls = (void *)nls_init( userName.c_str( ), userPassword.c_str( ) ); + delete (NLS *)m_NLS; + m_NLS = new NLS( userName, userPassword ); +} + +bool CBNCSUtilInterface :: HELP_SID_AUTH_CHECK( bool TFT, string war3Path, string keyROC, string keyTFT, string valueStringFormula, string mpqFileName, BYTEARRAY clientToken, BYTEARRAY serverToken ) +{ + // set m_EXEVersion, m_EXEVersionHash, m_EXEInfo, m_InfoROC, m_InfoTFT + + string FileWar3EXE = war3Path + "war3.exe"; + string FileStormDLL = war3Path + "Storm.dll"; + + if( !UTIL_FileExists( FileStormDLL ) ) + FileStormDLL = war3Path + "storm.dll"; + + string FileGameDLL = war3Path + "game.dll"; + bool ExistsWar3EXE = UTIL_FileExists( FileWar3EXE ); + bool ExistsStormDLL = UTIL_FileExists( FileStormDLL ); + bool ExistsGameDLL = UTIL_FileExists( FileGameDLL ); + + if( ExistsWar3EXE && ExistsStormDLL && ExistsGameDLL ) + { + // todotodo: check getExeInfo return value to ensure 1024 bytes was enough + + char buf[1024]; + uint32_t EXEVersion; + getExeInfo( FileWar3EXE.c_str( ), (char *)&buf, 1024, (uint32_t *)&EXEVersion, BNCSUTIL_PLATFORM_X86 ); + m_EXEInfo = buf; + m_EXEVersion = UTIL_CreateByteArray( EXEVersion, false ); + uint32_t EXEVersionHash; + checkRevisionFlat( valueStringFormula.c_str( ), FileWar3EXE.c_str( ), FileStormDLL.c_str( ), FileGameDLL.c_str( ), extractMPQNumber( mpqFileName.c_str( ) ), (unsigned long *)&EXEVersionHash ); + m_EXEVersionHash = UTIL_CreateByteArray( EXEVersionHash, false ); + m_KeyInfoROC = CreateKeyInfo( keyROC, UTIL_ByteArrayToUInt32( clientToken, false ), UTIL_ByteArrayToUInt32( serverToken, false ) ); + + if( TFT ) + m_KeyInfoTFT = CreateKeyInfo( keyTFT, UTIL_ByteArrayToUInt32( clientToken, false ), UTIL_ByteArrayToUInt32( serverToken, false ) ); + + if( m_KeyInfoROC.size( ) == 36 && ( !TFT || m_KeyInfoTFT.size( ) == 36 ) ) + return true; + else + { + if( m_KeyInfoROC.size( ) != 36 ) + CONSOLE_Print( "[BNCSUI] unable to create ROC key info - invalid ROC key" ); + + if( TFT && m_KeyInfoTFT.size( ) != 36 ) + CONSOLE_Print( "[BNCSUI] unable to create TFT key info - invalid TFT key" ); + } + } + else + { + if( !ExistsWar3EXE ) + CONSOLE_Print( "[BNCSUI] unable to open [" + FileWar3EXE + "]" ); + + if( !ExistsStormDLL ) + CONSOLE_Print( "[BNCSUI] unable to open [" + FileStormDLL + "]" ); + + if( !ExistsGameDLL ) + CONSOLE_Print( "[BNCSUI] unable to open [" + FileGameDLL + "]" ); + } + + return false; +} + +bool CBNCSUtilInterface :: HELP_SID_AUTH_ACCOUNTLOGON( ) +{ + // set m_ClientKey + + char buf[32]; + // nls_get_A( (nls_t *)m_nls, buf ); + ( (NLS *)m_NLS )->getPublicKey( buf ); + m_ClientKey = UTIL_CreateByteArray( (unsigned char *)buf, 32 ); + return true; +} + +bool CBNCSUtilInterface :: HELP_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY salt, BYTEARRAY serverKey ) +{ + // set m_M1 + + char buf[20]; + // nls_get_M1( (nls_t *)m_nls, buf, string( serverKey.begin( ), serverKey.end( ) ).c_str( ), string( salt.begin( ), salt.end( ) ).c_str( ) ); + ( (NLS *)m_NLS )->getClientSessionKey( buf, string( salt.begin( ), salt.end( ) ).c_str( ), string( serverKey.begin( ), serverKey.end( ) ).c_str( ) ); + m_M1 = UTIL_CreateByteArray( (unsigned char *)buf, 20 ); + return true; +} + +bool CBNCSUtilInterface :: HELP_PvPGNPasswordHash( string userPassword ) +{ + // set m_PvPGNPasswordHash + + char buf[20]; + hashPassword( userPassword.c_str( ), buf ); + m_PvPGNPasswordHash = UTIL_CreateByteArray( (unsigned char *)buf, 20 ); + return true; +} + +BYTEARRAY CBNCSUtilInterface :: CreateKeyInfo( string key, uint32_t clientToken, uint32_t serverToken ) +{ + unsigned char Zeros[] = { 0, 0, 0, 0 }; + BYTEARRAY KeyInfo; + CDKeyDecoder Decoder( key.c_str( ), key.size( ) ); + + if( Decoder.isKeyValid( ) ) + { + UTIL_AppendByteArray( KeyInfo, UTIL_CreateByteArray( (uint32_t)key.size( ), false ) ); + UTIL_AppendByteArray( KeyInfo, UTIL_CreateByteArray( Decoder.getProduct( ), false ) ); + UTIL_AppendByteArray( KeyInfo, UTIL_CreateByteArray( Decoder.getVal1( ), false ) ); + UTIL_AppendByteArray( KeyInfo, UTIL_CreateByteArray( Zeros, 4 ) ); + size_t Length = Decoder.calculateHash( clientToken, serverToken ); + char *buf = new char[Length]; + Length = Decoder.getHash( buf ); + UTIL_AppendByteArray( KeyInfo, UTIL_CreateByteArray( (unsigned char *)buf, Length ) ); + delete [] buf; + } + + return KeyInfo; +} diff --git a/ghost-legacy/bncsutilinterface.h b/ghost-legacy/bncsutilinterface.h new file mode 100644 index 0000000..76b5858 --- /dev/null +++ b/ghost-legacy/bncsutilinterface.h @@ -0,0 +1,68 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef BNCSUTIL_INTERFACE_H +#define BNCSUTIL_INTERFACE_H + +// +// CBNCSUtilInterface +// + +class CBNCSUtilInterface +{ +private: + void *m_NLS; + BYTEARRAY m_EXEVersion; // set in HELP_SID_AUTH_CHECK + BYTEARRAY m_EXEVersionHash; // set in HELP_SID_AUTH_CHECK + string m_EXEInfo; // set in HELP_SID_AUTH_CHECK + BYTEARRAY m_KeyInfoROC; // set in HELP_SID_AUTH_CHECK + BYTEARRAY m_KeyInfoTFT; // set in HELP_SID_AUTH_CHECK + BYTEARRAY m_ClientKey; // set in HELP_SID_AUTH_ACCOUNTLOGON + BYTEARRAY m_M1; // set in HELP_SID_AUTH_ACCOUNTLOGONPROOF + BYTEARRAY m_PvPGNPasswordHash; // set in HELP_PvPGNPasswordHash + +public: + CBNCSUtilInterface( string userName, string userPassword ); + ~CBNCSUtilInterface( ); + + BYTEARRAY GetEXEVersion( ) { return m_EXEVersion; } + BYTEARRAY GetEXEVersionHash( ) { return m_EXEVersionHash; } + string GetEXEInfo( ) { return m_EXEInfo; } + BYTEARRAY GetKeyInfoROC( ) { return m_KeyInfoROC; } + BYTEARRAY GetKeyInfoTFT( ) { return m_KeyInfoTFT; } + BYTEARRAY GetClientKey( ) { return m_ClientKey; } + BYTEARRAY GetM1( ) { return m_M1; } + BYTEARRAY GetPvPGNPasswordHash( ) { return m_PvPGNPasswordHash; } + + void SetEXEVersion( BYTEARRAY &nEXEVersion ) { m_EXEVersion = nEXEVersion; } + void SetEXEVersionHash( BYTEARRAY &nEXEVersionHash ) { m_EXEVersionHash = nEXEVersionHash; } + + void Reset( string userName, string userPassword ); + + bool HELP_SID_AUTH_CHECK( bool TFT, string war3Path, string keyROC, string keyTFT, string valueStringFormula, string mpqFileName, BYTEARRAY clientToken, BYTEARRAY serverToken ); + bool HELP_SID_AUTH_ACCOUNTLOGON( ); + bool HELP_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY salt, BYTEARRAY serverKey ); + bool HELP_PvPGNPasswordHash( string userPassword ); + +private: + BYTEARRAY CreateKeyInfo( string key, uint32_t clientToken, uint32_t serverToken ); +}; + +#endif diff --git a/ghost-legacy/bnet.cpp b/ghost-legacy/bnet.cpp new file mode 100644 index 0000000..89efcc3 --- /dev/null +++ b/ghost-legacy/bnet.cpp @@ -0,0 +1,2675 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "commandpacket.h" +#include "ghostdb.h" +#include "bncsutilinterface.h" +#include "bnlsclient.h" +#include "bnetprotocol.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "replay.h" +#include "gameprotocol.h" +#include "game_base.h" + +#include + +using namespace boost :: filesystem; + +// +// CBNET +// + +CBNET :: CBNET( CGHost *nGHost, string nServer, string nServerAlias, string nBNLSServer, uint16_t nBNLSPort, uint32_t nBNLSWardenCookie, string nCDKeyROC, string nCDKeyTFT, string nCountryAbbrev, string nCountry, uint32_t nLocaleID, string nUserName, string nUserPassword, string nFirstChannel, string nRootAdmin, char nCommandTrigger, bool nHoldFriends, bool nHoldClan, bool nPublicCommands, unsigned char nWar3Version, BYTEARRAY nEXEVersion, BYTEARRAY nEXEVersionHash, string nPasswordHashType, string nPVPGNRealmName, uint32_t nMaxMessageLength, uint32_t nHostCounterID ) +{ + // todotodo: append path seperator to Warcraft3Path if needed + + m_GHost = nGHost; + m_Socket = new CTCPClient( ); + m_Protocol = new CBNETProtocol( ); + m_BNLSClient = NULL; + m_BNCSUtil = new CBNCSUtilInterface( nUserName, nUserPassword ); + m_CallableAdminList = m_GHost->m_DB->ThreadedAdminList( nServer ); + m_CallableBanList = m_GHost->m_DB->ThreadedBanList( nServer ); + m_Exiting = false; + m_Server = nServer; + string LowerServer = m_Server; + transform( LowerServer.begin( ), LowerServer.end( ), LowerServer.begin( ), (int(*)(int))tolower ); + + if( !nServerAlias.empty( ) ) + m_ServerAlias = nServerAlias; + else if( LowerServer == "useast.battle.net" ) + m_ServerAlias = "USEast"; + else if( LowerServer == "uswest.battle.net" ) + m_ServerAlias = "USWest"; + else if( LowerServer == "asia.battle.net" ) + m_ServerAlias = "Asia"; + else if( LowerServer == "europe.battle.net" ) + m_ServerAlias = "Europe"; + else + m_ServerAlias = m_Server; + + if( nPasswordHashType == "pvpgn" && !nBNLSServer.empty( ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] pvpgn connection found with a configured BNLS server, ignoring BNLS server" ); + nBNLSServer.clear( ); + nBNLSPort = 0; + nBNLSWardenCookie = 0; + } + + m_BNLSServer = nBNLSServer; + m_BNLSPort = nBNLSPort; + m_BNLSWardenCookie = nBNLSWardenCookie; + m_CDKeyROC = nCDKeyROC; + m_CDKeyTFT = nCDKeyTFT; + + // remove dashes from CD keys and convert to uppercase + + m_CDKeyROC.erase( remove( m_CDKeyROC.begin( ), m_CDKeyROC.end( ), '-' ), m_CDKeyROC.end( ) ); + m_CDKeyTFT.erase( remove( m_CDKeyTFT.begin( ), m_CDKeyTFT.end( ), '-' ), m_CDKeyTFT.end( ) ); + transform( m_CDKeyROC.begin( ), m_CDKeyROC.end( ), m_CDKeyROC.begin( ), (int(*)(int))toupper ); + transform( m_CDKeyTFT.begin( ), m_CDKeyTFT.end( ), m_CDKeyTFT.begin( ), (int(*)(int))toupper ); + + if( m_CDKeyROC.size( ) != 26 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] warning - your ROC CD key is not 26 characters long and is probably invalid" ); + + if( m_GHost->m_TFT && m_CDKeyTFT.size( ) != 26 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] warning - your TFT CD key is not 26 characters long and is probably invalid" ); + + m_CountryAbbrev = nCountryAbbrev; + m_Country = nCountry; + m_LocaleID = nLocaleID; + m_UserName = nUserName; + m_UserPassword = nUserPassword; + m_FirstChannel = nFirstChannel; + m_RootAdmin = nRootAdmin; + transform( m_RootAdmin.begin( ), m_RootAdmin.end( ), m_RootAdmin.begin( ), (int(*)(int))tolower ); + m_CommandTrigger = nCommandTrigger; + m_War3Version = nWar3Version; + m_EXEVersion = nEXEVersion; + m_EXEVersionHash = nEXEVersionHash; + m_PasswordHashType = nPasswordHashType; + m_PVPGNRealmName = nPVPGNRealmName; + m_MaxMessageLength = nMaxMessageLength; + m_HostCounterID = nHostCounterID; + m_LastDisconnectedTime = 0; + m_LastConnectionAttemptTime = 0; + m_LastNullTime = 0; + m_LastOutPacketTicks = 0; + m_LastOutPacketSize = 0; + m_LastAdminRefreshTime = GetTime( ); + m_LastBanRefreshTime = GetTime( ); + m_FirstConnect = true; + m_WaitingToConnect = true; + m_LoggedIn = false; + m_InChat = false; + m_HoldFriends = nHoldFriends; + m_HoldClan = nHoldClan; + m_PublicCommands = nPublicCommands; +} + +CBNET :: ~CBNET( ) +{ + delete m_Socket; + delete m_Protocol; + delete m_BNLSClient; + + while( !m_Packets.empty( ) ) + { + delete m_Packets.front( ); + m_Packets.pop( ); + } + + delete m_BNCSUtil; + + for( vector :: iterator i = m_Friends.begin( ); i != m_Friends.end( ); i++ ) + delete *i; + + for( vector :: iterator i = m_Clans.begin( ); i != m_Clans.end( ); i++ ) + delete *i; + + for( vector :: iterator i = m_PairedAdminCounts.begin( ); i != m_PairedAdminCounts.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedAdminAdds.begin( ); i != m_PairedAdminAdds.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedAdminRemoves.begin( ); i != m_PairedAdminRemoves.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedBanCounts.begin( ); i != m_PairedBanCounts.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedBanRemoves.begin( ); i != m_PairedBanRemoves.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedGPSChecks.begin( ); i != m_PairedGPSChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedDPSChecks.begin( ); i != m_PairedDPSChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + if( m_CallableAdminList ) + m_GHost->m_Callables.push_back( m_CallableAdminList ); + + if( m_CallableBanList ) + m_GHost->m_Callables.push_back( m_CallableBanList ); + + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ); i++ ) + delete *i; +} + +BYTEARRAY CBNET :: GetUniqueName( ) +{ + return m_Protocol->GetUniqueName( ); +} + +vector > CBNET :: GetFriends( ) +{ + vector > result; + + for( vector :: iterator i = m_Friends.begin( ); i != m_Friends.end( ); i++ ) + result.push_back(pair((*i)->GetAccount( ), (*i)->GetArea( ) ) ); + + return result; +} + +vector > CBNET :: GetClan( ) +{ + vector > result; + + int k = 0; + for( vector :: iterator i = m_Clans.begin( ); i != m_Clans.end( ) && k < 50; i++, k++ ) + result.push_back(pair((*i)->GetName( ), (*i)->GetRawStatus( ) ) ); + + return result; +} + +vector > CBNET :: GetBans( ) +{ + vector > result; + + int k = 0; + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ) && k < 100; i++, k++ ) + result.push_back(pair((*i)->GetName( ), 0 ) ); + + return result; +} + +vector > CBNET :: GetAdmins( ) +{ + vector > result; + + vector roots = UTIL_Tokenize( m_RootAdmin, ' ' ); + + for( vector :: iterator i = roots.begin( ); i != roots.end( ); i++ ) + result.push_back(pair( *i, 2 ) ); + + for( vector :: iterator i = m_Admins.begin( ); i != m_Admins.end( ); i++ ) + result.push_back(pair( *i, 0 ) ); + + return result; +} + +unsigned int CBNET :: SetFD( void *fd, void *send_fd, int *nfds ) +{ + unsigned int NumFDs = 0; + + if( !m_Socket->HasError( ) && m_Socket->GetConnected( ) ) + { + m_Socket->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds ); + NumFDs++; + + if( m_BNLSClient ) + NumFDs += m_BNLSClient->SetFD( fd, send_fd, nfds ); + } + + return NumFDs; +} + +bool CBNET :: Update( void *fd, void *send_fd ) +{ + // + // update callables + // + + for( vector :: iterator i = m_PairedAdminCounts.begin( ); i != m_PairedAdminCounts.end( ); ) + { + if( i->second->GetReady( ) ) + { + uint32_t Count = i->second->GetResult( ); + + if( Count == 0 ) + QueueChatCommand( m_GHost->m_Language->ThereAreNoAdmins( m_Server ), i->first, !i->first.empty( ) ); + else if( Count == 1 ) + QueueChatCommand( m_GHost->m_Language->ThereIsAdmin( m_Server ), i->first, !i->first.empty( ) ); + else + QueueChatCommand( m_GHost->m_Language->ThereAreAdmins( m_Server, UTIL_ToString( Count ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminCounts.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedAdminAdds.begin( ); i != m_PairedAdminAdds.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + AddAdmin( i->second->GetUser( ) ); + QueueChatCommand( m_GHost->m_Language->AddedUserToAdminDatabase( m_Server, i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + } + else + QueueChatCommand( m_GHost->m_Language->ErrorAddingUserToAdminDatabase( m_Server, i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminAdds.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedAdminRemoves.begin( ); i != m_PairedAdminRemoves.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + RemoveAdmin( i->second->GetUser( ) ); + QueueChatCommand( m_GHost->m_Language->DeletedUserFromAdminDatabase( m_Server, i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + } + else + QueueChatCommand( m_GHost->m_Language->ErrorDeletingUserFromAdminDatabase( m_Server, i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminRemoves.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedBanCounts.begin( ); i != m_PairedBanCounts.end( ); ) + { + if( i->second->GetReady( ) ) + { + uint32_t Count = i->second->GetResult( ); + + if( Count == 0 ) + QueueChatCommand( m_GHost->m_Language->ThereAreNoBannedUsers( m_Server ), i->first, !i->first.empty( ) ); + else if( Count == 1 ) + QueueChatCommand( m_GHost->m_Language->ThereIsBannedUser( m_Server ), i->first, !i->first.empty( ) ); + else + QueueChatCommand( m_GHost->m_Language->ThereAreBannedUsers( m_Server, UTIL_ToString( Count ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanCounts.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + AddBan( i->second->GetUser( ), i->second->GetIP( ), i->second->GetGameName( ), i->second->GetAdmin( ), i->second->GetReason( ) ); + QueueChatCommand( m_GHost->m_Language->BannedUser( i->second->GetServer( ), i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + } + else + QueueChatCommand( m_GHost->m_Language->ErrorBanningUser( i->second->GetServer( ), i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanAdds.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedBanRemoves.begin( ); i != m_PairedBanRemoves.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + RemoveBan( i->second->GetUser( ) ); + QueueChatCommand( m_GHost->m_Language->UnbannedUser( i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + } + else + QueueChatCommand( m_GHost->m_Language->ErrorUnbanningUser( i->second->GetUser( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanRemoves.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedGPSChecks.begin( ); i != m_PairedGPSChecks.end( ); ) + { + if( i->second->GetReady( ) ) + { + CDBGamePlayerSummary *GamePlayerSummary = i->second->GetResult( ); + + if( GamePlayerSummary ) + QueueChatCommand( m_GHost->m_Language->HasPlayedGamesWithThisBot( i->second->GetName( ), GamePlayerSummary->GetFirstGameDateTime( ), GamePlayerSummary->GetLastGameDateTime( ), UTIL_ToString( GamePlayerSummary->GetTotalGames( ) ), UTIL_ToString( (float)GamePlayerSummary->GetAvgLoadingTime( ) / 1000, 2 ), UTIL_ToString( GamePlayerSummary->GetAvgLeftPercent( ) ) ), i->first, !i->first.empty( ) ); + else + QueueChatCommand( m_GHost->m_Language->HasntPlayedGamesWithThisBot( i->second->GetName( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedGPSChecks.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedDPSChecks.begin( ); i != m_PairedDPSChecks.end( ); ) + { + if( i->second->GetReady( ) ) + { + CDBDotAPlayerSummary *DotAPlayerSummary = i->second->GetResult( ); + + if( DotAPlayerSummary ) + { + string Summary = m_GHost->m_Language->HasPlayedDotAGamesWithThisBot( i->second->GetName( ), + UTIL_ToString( DotAPlayerSummary->GetTotalGames( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalWins( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalLosses( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalDeaths( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCreepKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCreepDenies( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalAssists( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalNeutralKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalTowerKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalRaxKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCourierKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetAvgKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgDeaths( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCreepKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCreepDenies( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgAssists( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgNeutralKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgTowerKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgRaxKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCourierKills( ), 2 ) ); + + QueueChatCommand( Summary, i->first, !i->first.empty( ) ); + } + else + QueueChatCommand( m_GHost->m_Language->HasntPlayedDotAGamesWithThisBot( i->second->GetName( ) ), i->first, !i->first.empty( ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedDPSChecks.erase( i ); + } + else + i++; + } + + // refresh the admin list every 5 minutes + + if( !m_CallableAdminList && GetTime( ) - m_LastAdminRefreshTime >= 300 ) + m_CallableAdminList = m_GHost->m_DB->ThreadedAdminList( m_Server ); + + if( m_CallableAdminList && m_CallableAdminList->GetReady( ) ) + { + // CONSOLE_Print( "[BNET: " + m_ServerAlias + "] refreshed admin list (" + UTIL_ToString( m_Admins.size( ) ) + " -> " + UTIL_ToString( m_CallableAdminList->GetResult( ).size( ) ) + " admins)" ); + m_Admins = m_CallableAdminList->GetResult( ); + m_GHost->m_DB->RecoverCallable( m_CallableAdminList ); + delete m_CallableAdminList; + m_CallableAdminList = NULL; + m_LastAdminRefreshTime = GetTime( ); + } + + // refresh the ban list every 60 minutes + + if( !m_CallableBanList && GetTime( ) - m_LastBanRefreshTime >= 3600 ) + m_CallableBanList = m_GHost->m_DB->ThreadedBanList( m_Server ); + + if( m_CallableBanList && m_CallableBanList->GetReady( ) ) + { + // CONSOLE_Print( "[BNET: " + m_ServerAlias + "] refreshed ban list (" + UTIL_ToString( m_Bans.size( ) ) + " -> " + UTIL_ToString( m_CallableBanList->GetResult( ).size( ) ) + " bans)" ); + + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ); i++ ) + delete *i; + + m_Bans = m_CallableBanList->GetResult( ); + m_GHost->m_DB->RecoverCallable( m_CallableBanList ); + delete m_CallableBanList; + m_CallableBanList = NULL; + m_LastBanRefreshTime = GetTime( ); + } + + // we return at the end of each if statement so we don't have to deal with errors related to the order of the if statements + // that means it might take a few ms longer to complete a task involving multiple steps (in this case, reconnecting) due to blocking or sleeping + // but it's not a big deal at all, maybe 100ms in the worst possible case (based on a 50ms blocking time) + + if( m_Socket->HasError( ) ) + { + // the socket has an error + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] disconnected from battle.net due to socket error" ); + + if( m_Socket->GetError( ) == ECONNRESET && GetTime( ) - m_LastConnectionAttemptTime <= 15 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] warning - you are probably temporarily IP banned from battle.net" ); + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] waiting 90 seconds to reconnect" ); + m_GHost->EventBNETDisconnected( this ); + delete m_BNLSClient; + m_BNLSClient = NULL; + m_BNCSUtil->Reset( m_UserName, m_UserPassword ); + m_Socket->Reset( ); + m_LastDisconnectedTime = GetTime( ); + m_LoggedIn = false; + m_InChat = false; + m_WaitingToConnect = true; + return m_Exiting; + } + + if( !m_Socket->GetConnecting( ) && !m_Socket->GetConnected( ) && !m_WaitingToConnect ) + { + // the socket was disconnected + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] disconnected from battle.net" ); + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] waiting 90 seconds to reconnect" ); + m_GHost->EventBNETDisconnected( this ); + delete m_BNLSClient; + m_BNLSClient = NULL; + m_BNCSUtil->Reset( m_UserName, m_UserPassword ); + m_Socket->Reset( ); + m_LastDisconnectedTime = GetTime( ); + m_LoggedIn = false; + m_InChat = false; + m_WaitingToConnect = true; + return m_Exiting; + } + + if( m_Socket->GetConnected( ) ) + { + // the socket is connected and everything appears to be working properly + + m_Socket->DoRecv( (fd_set *)fd ); + ExtractPackets( ); + ProcessPackets( ); + + // update the BNLS client + + if( m_BNLSClient ) + { + if( m_BNLSClient->Update( fd, send_fd ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] deleting BNLS client" ); + delete m_BNLSClient; + m_BNLSClient = NULL; + } + else + { + BYTEARRAY WardenResponse = m_BNLSClient->GetWardenResponse( ); + + if( !WardenResponse.empty( ) ) + m_Socket->PutBytes( m_Protocol->SEND_SID_WARDEN( WardenResponse ) ); + } + } + + // check if at least one packet is waiting to be sent and if we've waited long enough to prevent flooding + // this formula has changed many times but currently we wait 1 second if the last packet was "small", 3.5 seconds if it was "medium", and 4 seconds if it was "big" + + uint32_t WaitTicks = 0; + + if( m_LastOutPacketSize < 10 ) + WaitTicks = 1000; + else if( m_LastOutPacketSize < 100 ) + WaitTicks = 3500; + else + WaitTicks = 4000; + + if( !m_OutPackets.empty( ) && GetTicks( ) - m_LastOutPacketTicks >= WaitTicks ) + { + if( m_OutPackets.size( ) > 7 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] packet queue warning - there are " + UTIL_ToString( m_OutPackets.size( ) ) + " packets waiting to be sent" ); + + m_Socket->PutBytes( m_OutPackets.front( ) ); + m_LastOutPacketSize = m_OutPackets.front( ).size( ); + m_OutPackets.pop( ); + m_LastOutPacketTicks = GetTicks( ); + } + + // send a null packet every 60 seconds to detect disconnects + + if( GetTime( ) - m_LastNullTime >= 60 && GetTicks( ) - m_LastOutPacketTicks >= 60000 ) + { + m_Socket->PutBytes( m_Protocol->SEND_SID_NULL( ) ); + m_LastNullTime = GetTime( ); + } + + m_Socket->DoSend( (fd_set *)send_fd ); + return m_Exiting; + } + + if( m_Socket->GetConnecting( ) ) + { + // we are currently attempting to connect to battle.net + + if( m_Socket->CheckConnect( ) ) + { + // the connection attempt completed + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] connected" ); + m_GHost->EventBNETConnected( this ); + m_Socket->PutBytes( m_Protocol->SEND_PROTOCOL_INITIALIZE_SELECTOR( ) ); + m_Socket->PutBytes( m_Protocol->SEND_SID_AUTH_INFO( m_War3Version, m_GHost->m_TFT, m_LocaleID, m_CountryAbbrev, m_Country ) ); + m_Socket->DoSend( (fd_set *)send_fd ); + m_LastNullTime = GetTime( ); + m_LastOutPacketTicks = GetTicks( ); + + while( !m_OutPackets.empty( ) ) + m_OutPackets.pop( ); + + return m_Exiting; + } + else if( GetTime( ) - m_LastConnectionAttemptTime >= 15 ) + { + // the connection attempt timed out (15 seconds) + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] connect timed out" ); + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] waiting 90 seconds to reconnect" ); + m_GHost->EventBNETConnectTimedOut( this ); + m_Socket->Reset( ); + m_LastDisconnectedTime = GetTime( ); + m_WaitingToConnect = true; + return m_Exiting; + } + } + + if( !m_Socket->GetConnecting( ) && !m_Socket->GetConnected( ) && ( m_FirstConnect || GetTime( ) - m_LastDisconnectedTime >= 90 ) ) + { + // attempt to connect to battle.net + + m_FirstConnect = false; + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] connecting to server [" + m_Server + "] on port 6112" ); + m_GHost->EventBNETConnecting( this ); + + if( !m_GHost->m_BindAddress.empty( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] attempting to bind to address [" + m_GHost->m_BindAddress + "]" ); + + if( m_ServerIP.empty( ) ) + { + m_Socket->Connect( m_GHost->m_BindAddress, m_Server, 6112 ); + + if( !m_Socket->HasError( ) ) + { + m_ServerIP = m_Socket->GetIPString( ); + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] resolved and cached server IP address " + m_ServerIP ); + } + } + else + { + // use cached server IP address since resolving takes time and is blocking + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] using cached server IP address " + m_ServerIP ); + m_Socket->Connect( m_GHost->m_BindAddress, m_ServerIP, 6112 ); + } + + m_WaitingToConnect = false; + m_LastConnectionAttemptTime = GetTime( ); + return m_Exiting; + } + + return m_Exiting; +} + +void CBNET :: ExtractPackets( ) +{ + // extract as many packets as possible from the socket's receive buffer and put them in the m_Packets queue + + string *RecvBuffer = m_Socket->GetBytes( ); + BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); + + // a packet is at least 4 bytes so loop as long as the buffer contains 4 bytes + + while( Bytes.size( ) >= 4 ) + { + // byte 0 is always 255 + + if( Bytes[0] == BNET_HEADER_CONSTANT ) + { + // bytes 2 and 3 contain the length of the packet + + uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); + + if( Length >= 4 ) + { + if( Bytes.size( ) >= Length ) + { + m_Packets.push( new CCommandPacket( BNET_HEADER_CONSTANT, Bytes[1], BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); + *RecvBuffer = RecvBuffer->substr( Length ); + Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); + } + else + return; + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error - received invalid packet from battle.net (bad length), disconnecting" ); + m_Socket->Disconnect( ); + return; + } + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error - received invalid packet from battle.net (bad header constant), disconnecting" ); + m_Socket->Disconnect( ); + return; + } + } +} + +void CBNET :: ProcessPackets( ) +{ + CIncomingGameHost *GameHost = NULL; + CIncomingChatEvent *ChatEvent = NULL; + BYTEARRAY WardenData; + vector Friends; + vector Clans; + + // process all the received packets in the m_Packets queue + // this normally means sending some kind of response + + while( !m_Packets.empty( ) ) + { + CCommandPacket *Packet = m_Packets.front( ); + m_Packets.pop( ); + + if( Packet->GetPacketType( ) == BNET_HEADER_CONSTANT ) + { + switch( Packet->GetID( ) ) + { + case CBNETProtocol :: SID_NULL: + // warning: we do not respond to NULL packets with a NULL packet of our own + // this is because PVPGN servers are programmed to respond to NULL packets so it will create a vicious cycle of useless traffic + // official battle.net servers do not respond to NULL packets + + m_Protocol->RECEIVE_SID_NULL( Packet->GetData( ) ); + break; + + case CBNETProtocol :: SID_GETADVLISTEX: + GameHost = m_Protocol->RECEIVE_SID_GETADVLISTEX( Packet->GetData( ) ); + + if( GameHost ) + CONSOLE_Print( "[BNET] joining game [" + GameHost->GetGameName( ) + "]", GetRealmId( ), false ); + + delete GameHost; + GameHost = NULL; + break; + + case CBNETProtocol :: SID_ENTERCHAT: + if( m_Protocol->RECEIVE_SID_ENTERCHAT( Packet->GetData( ) ) ) + { + CONSOLE_Print( "[BNET] joining channel [" + m_FirstChannel + "]", GetRealmId( ), false ); + m_InChat = true; + m_Socket->PutBytes( m_Protocol->SEND_SID_JOINCHANNEL( m_FirstChannel ) ); + } + + break; + + case CBNETProtocol :: SID_CHATEVENT: + ChatEvent = m_Protocol->RECEIVE_SID_CHATEVENT( Packet->GetData( ) ); + + if( ChatEvent ) + ProcessChatEvent( ChatEvent ); + + delete ChatEvent; + ChatEvent = NULL; + break; + + case CBNETProtocol :: SID_CHECKAD: + m_Protocol->RECEIVE_SID_CHECKAD( Packet->GetData( ) ); + break; + + case CBNETProtocol :: SID_STARTADVEX3: + if( m_Protocol->RECEIVE_SID_STARTADVEX3( Packet->GetData( ) ) ) + { + m_InChat = false; + m_GHost->EventBNETGameRefreshed( this ); + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] startadvex3 failed" ); + m_GHost->EventBNETGameRefreshFailed( this ); + } + + break; + + case CBNETProtocol :: SID_PING: + m_Socket->PutBytes( m_Protocol->SEND_SID_PING( m_Protocol->RECEIVE_SID_PING( Packet->GetData( ) ) ) ); + break; + + case CBNETProtocol :: SID_AUTH_INFO: + if( m_Protocol->RECEIVE_SID_AUTH_INFO( Packet->GetData( ) ) ) + { + if( m_BNCSUtil->HELP_SID_AUTH_CHECK( m_GHost->m_TFT, m_GHost->m_Warcraft3Path, m_CDKeyROC, m_CDKeyTFT, m_Protocol->GetValueStringFormulaString( ), m_Protocol->GetIX86VerFileNameString( ), m_Protocol->GetClientToken( ), m_Protocol->GetServerToken( ) ) ) + { + // override the exe information generated by bncsutil if specified in the config file + // apparently this is useful for pvpgn users + + if( m_EXEVersion.size( ) == 4 ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] using custom exe version bnet_custom_exeversion = " + UTIL_ToString( m_EXEVersion[0] ) + " " + UTIL_ToString( m_EXEVersion[1] ) + " " + UTIL_ToString( m_EXEVersion[2] ) + " " + UTIL_ToString( m_EXEVersion[3] ) ); + m_BNCSUtil->SetEXEVersion( m_EXEVersion ); + } + + if( m_EXEVersionHash.size( ) == 4 ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] using custom exe version hash bnet_custom_exeversionhash = " + UTIL_ToString( m_EXEVersionHash[0] ) + " " + UTIL_ToString( m_EXEVersionHash[1] ) + " " + UTIL_ToString( m_EXEVersionHash[2] ) + " " + UTIL_ToString( m_EXEVersionHash[3] ) ); + m_BNCSUtil->SetEXEVersionHash( m_EXEVersionHash ); + } + + if( m_GHost->m_TFT ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] attempting to auth as Warcraft III: The Frozen Throne" ); + else + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] attempting to auth as Warcraft III: Reign of Chaos" ); + + m_Socket->PutBytes( m_Protocol->SEND_SID_AUTH_CHECK( m_GHost->m_TFT, m_Protocol->GetClientToken( ), m_BNCSUtil->GetEXEVersion( ), m_BNCSUtil->GetEXEVersionHash( ), m_BNCSUtil->GetKeyInfoROC( ), m_BNCSUtil->GetKeyInfoTFT( ), m_BNCSUtil->GetEXEInfo( ), "GHost" ) ); + + // the Warden seed is the first 4 bytes of the ROC key hash + // initialize the Warden handler + + if( !m_BNLSServer.empty( ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] creating BNLS client" ); + delete m_BNLSClient; + m_BNLSClient = new CBNLSClient( m_BNLSServer, m_BNLSPort, m_BNLSWardenCookie ); + m_BNLSClient->QueueWardenSeed( UTIL_ByteArrayToUInt32( m_BNCSUtil->GetKeyInfoROC( ), false, 16 ) ); + } + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - bncsutil key hash failed (check your Warcraft 3 path and cd keys), disconnecting" ); + m_Socket->Disconnect( ); + delete Packet; + return; + } + } + + break; + + case CBNETProtocol :: SID_AUTH_CHECK: + if( m_Protocol->RECEIVE_SID_AUTH_CHECK( Packet->GetData( ) ) ) + { + // cd keys accepted + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] cd keys accepted" ); + m_BNCSUtil->HELP_SID_AUTH_ACCOUNTLOGON( ); + m_Socket->PutBytes( m_Protocol->SEND_SID_AUTH_ACCOUNTLOGON( m_BNCSUtil->GetClientKey( ), m_UserName ) ); + } + else + { + // cd keys not accepted + + switch( UTIL_ByteArrayToUInt32( m_Protocol->GetKeyState( ), false ) ) + { + case CBNETProtocol :: KR_ROC_KEY_IN_USE: + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - ROC CD key in use by user [" + m_Protocol->GetKeyStateDescription( ) + "], disconnecting" ); + break; + case CBNETProtocol :: KR_TFT_KEY_IN_USE: + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - TFT CD key in use by user [" + m_Protocol->GetKeyStateDescription( ) + "], disconnecting" ); + break; + case CBNETProtocol :: KR_OLD_GAME_VERSION: + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - game version is too old, disconnecting" ); + break; + case CBNETProtocol :: KR_INVALID_VERSION: + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - game version is invalid, disconnecting" ); + break; + default: + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - cd keys not accepted, disconnecting" ); + break; + } + + m_Socket->Disconnect( ); + delete Packet; + return; + } + + break; + + case CBNETProtocol :: SID_AUTH_ACCOUNTLOGON: + if( m_Protocol->RECEIVE_SID_AUTH_ACCOUNTLOGON( Packet->GetData( ) ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] username [" + m_UserName + "] accepted" ); + + if( m_PasswordHashType == "pvpgn" ) + { + // pvpgn logon + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] using pvpgn logon type (for pvpgn servers only)" ); + m_BNCSUtil->HELP_PvPGNPasswordHash( m_UserPassword ); + m_Socket->PutBytes( m_Protocol->SEND_SID_AUTH_ACCOUNTLOGONPROOF( m_BNCSUtil->GetPvPGNPasswordHash( ) ) ); + } + else + { + // battle.net logon + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] using battle.net logon type (for official battle.net servers only)" ); + m_BNCSUtil->HELP_SID_AUTH_ACCOUNTLOGONPROOF( m_Protocol->GetSalt( ), m_Protocol->GetServerPublicKey( ) ); + m_Socket->PutBytes( m_Protocol->SEND_SID_AUTH_ACCOUNTLOGONPROOF( m_BNCSUtil->GetM1( ) ) ); + } + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - invalid username, disconnecting" ); + m_Socket->Disconnect( ); + delete Packet; + return; + } + + break; + + case CBNETProtocol :: SID_AUTH_ACCOUNTLOGONPROOF: + if( m_Protocol->RECEIVE_SID_AUTH_ACCOUNTLOGONPROOF( Packet->GetData( ) ) ) + { + // logon successful + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon successful" ); + m_LoggedIn = true; + m_GHost->EventBNETLoggedIn( this ); + m_Socket->PutBytes( m_Protocol->SEND_SID_NETGAMEPORT( m_GHost->m_HostPort ) ); + m_Socket->PutBytes( m_Protocol->SEND_SID_ENTERCHAT( ) ); + m_Socket->PutBytes( m_Protocol->SEND_SID_FRIENDSLIST( ) ); + m_Socket->PutBytes( m_Protocol->SEND_SID_CLANMEMBERLIST( ) ); + } + else + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] logon failed - invalid password, disconnecting" ); + + // try to figure out if the user might be using the wrong logon type since too many people are confused by this + + string Server = m_Server; + transform( Server.begin( ), Server.end( ), Server.begin( ), (int(*)(int))tolower ); + + if( m_PasswordHashType == "pvpgn" && ( Server == "useast.battle.net" || Server == "uswest.battle.net" || Server == "asia.battle.net" || Server == "europe.battle.net" ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] it looks like you're trying to connect to a battle.net server using a pvpgn logon type, check your config file's \"battle.net custom data\" section" ); + else if( m_PasswordHashType != "pvpgn" && ( Server != "useast.battle.net" && Server != "uswest.battle.net" && Server != "asia.battle.net" && Server != "europe.battle.net" ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] it looks like you're trying to connect to a pvpgn server using a battle.net logon type, check your config file's \"battle.net custom data\" section" ); + + m_Socket->Disconnect( ); + delete Packet; + return; + } + + break; + + case CBNETProtocol :: SID_WARDEN: + WardenData = m_Protocol->RECEIVE_SID_WARDEN( Packet->GetData( ) ); + + if( m_BNLSClient ) + m_BNLSClient->QueueWardenRaw( WardenData ); + else + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] warning - received warden packet but no BNLS server is available, you will be kicked from battle.net soon" ); + + break; + + case CBNETProtocol :: SID_FRIENDSLIST: + Friends = m_Protocol->RECEIVE_SID_FRIENDSLIST( Packet->GetData( ) ); + + for( vector :: iterator i = m_Friends.begin( ); i != m_Friends.end( ); i++ ) + delete *i; + + m_Friends = Friends; + break; + + case CBNETProtocol :: SID_CLANMEMBERLIST: + vector Clans = m_Protocol->RECEIVE_SID_CLANMEMBERLIST( Packet->GetData( ) ); + + for( vector :: iterator i = m_Clans.begin( ); i != m_Clans.end( ); i++ ) + delete *i; + + m_Clans = Clans; + break; + } + } + + delete Packet; + } +} + +void CBNET :: ProcessChatEvent( CIncomingChatEvent *chatEvent ) +{ + CBNETProtocol :: IncomingChatEvent Event = chatEvent->GetChatEvent( ); + uint32_t UserFlags = chatEvent->GetUserFlags( ); + bool Whisper = ( Event == CBNETProtocol :: EID_WHISPER ); + string User = chatEvent->GetUser( ); + string Message = chatEvent->GetMessage( ); + + if( Event == CBNETProtocol :: EID_SHOWUSER ) + CONSOLE_AddChannelUser( User, GetRealmId( ), UserFlags ); + else if( Event == CBNETProtocol :: EID_USERFLAGS ) + CONSOLE_UpdateChannelUser( User, GetRealmId( ), UserFlags ); + else if( Event == CBNETProtocol :: EID_JOIN ) + CONSOLE_AddChannelUser( User, GetRealmId( ), UserFlags ); + else if( Event == CBNETProtocol :: EID_LEAVE ) + CONSOLE_RemoveChannelUser( User, GetRealmId( )); + else if( Event == CBNETProtocol :: EID_WHISPER ) + { + m_ReplyTarget = User; + + CONSOLE_Print( "[WHISPER] <" + User + "> " + Message, GetRealmId( ), false ); + m_GHost->EventBNETWhisper( this, User, Message ); + } + else if( Event == CBNETProtocol :: EID_TALK ) + { + CONSOLE_Print( "<" + User + "> " + Message, GetRealmId( ), false ); + m_GHost->EventBNETChat( this, User, Message ); + } + else if( Event == CBNETProtocol :: EID_BROADCAST ) + CONSOLE_Print( "[BROADCAST] " + Message, GetRealmId( ), false ); + else if( Event == CBNETProtocol :: EID_CHANNEL ) + { + CONSOLE_Print( "[BNET] joined channel [" + Message + "]", GetRealmId( ), false ); + m_CurrentChannel = Message; + CONSOLE_ChangeChannel( Message, GetRealmId( ) ); + CONSOLE_RemoveChannelUsers( GetRealmId( ) ); + CONSOLE_AddChannelUser( m_UserName, GetRealmId( ), UserFlags ); + } + else if( Event == CBNETProtocol :: EID_CHANNELFULL ) + CONSOLE_Print( "[BNET] channel is full", GetRealmId( ), false ); + else if( Event == CBNETProtocol :: EID_CHANNELDOESNOTEXIST ) + CONSOLE_Print( "[BNET] channel does not exist", GetRealmId( ), false ); + else if( Event == CBNETProtocol :: EID_CHANNELRESTRICTED ) + CONSOLE_Print( "[BNET] channel restricted", GetRealmId( ), false ); + else if( Event == CBNETProtocol :: EID_ERROR ) + CONSOLE_Print( "[ERROR] " + Message, GetRealmId( ), false ); + else if( Event == CBNETProtocol :: EID_EMOTE ) + CONSOLE_Print( "[EMOTE] <" + User + "> " + Message, GetRealmId( ), false ); + + if( Event == CBNETProtocol :: EID_WHISPER || Event == CBNETProtocol :: EID_TALK || Event == 29 ) + { + // handle spoof checking for current game + // this case covers whispers - we assume that anyone who sends a whisper to the bot with message "spoofcheck" should be considered spoof checked + // note that this means you can whisper "spoofcheck" even in a public game to manually spoofcheck if the /whois fails + + if( Event == CBNETProtocol :: EID_WHISPER && m_GHost->m_CurrentGame ) + { + if( Message == "s" || Message == "sc" || Message == "spoof" || Message == "check" || Message == "spoofcheck" ) + m_GHost->m_CurrentGame->AddToSpoofed( m_Server, User, true ); + else if( Message.find( m_GHost->m_CurrentGame->GetGameName( ) ) != string :: npos ) + { + // look for messages like "entered a Warcraft III The Frozen Throne game called XYZ" + // we don't look for the English part of the text anymore because we want this to work with multiple languages + // it's a pretty safe bet that anyone whispering the bot with a message containing the game name is a valid spoofcheck + + if( m_PasswordHashType == "pvpgn" && User == m_PVPGNRealmName ) + { + // the equivalent pvpgn message is: [PvPGN Realm] Your friend abc has entered a Warcraft III Frozen Throne game named "xyz". + + vector Tokens = UTIL_Tokenize( Message, ' ' ); + + if( Tokens.size( ) >= 3 ) + m_GHost->m_CurrentGame->AddToSpoofed( m_Server, Tokens[2], false ); + } + else + m_GHost->m_CurrentGame->AddToSpoofed( m_Server, User, false ); + } + } + + // handle bot commands + + if( Message == "?trigger" && ( IsAdmin( User ) || IsRootAdmin( User ) || ( m_PublicCommands && m_OutPackets.size( ) <= 3 ) ) ) + QueueChatCommand( m_GHost->m_Language->CommandTrigger( string( 1, m_CommandTrigger ) ), User, Whisper ); + else if( !Message.empty( ) && Message[0] == m_CommandTrigger ) + { + // extract the command trigger, the command, and the payload + // e.g. "!say hello world" -> command: "say", payload: "hello world" + + string Command; + string Payload; + string :: size_type PayloadStart = Message.find( " " ); + + if( PayloadStart != string :: npos ) + { + Command = Message.substr( 1, PayloadStart - 1 ); + Payload = Message.substr( PayloadStart + 1 ); + } + else + Command = Message.substr( 1 ); + + transform( Command.begin( ), Command.end( ), Command.begin( ), (int(*)(int))tolower ); + + if( IsAdmin( User ) || IsRootAdmin( User ) ) + { + if( User == "" ) + User = m_UserName; + + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] admin [" + User + "] sent command [" + Message + "]" ); + + /***************** + * ADMIN COMMANDS * + ******************/ + + // + // !ADDADMIN + // + + if( Command == "addadmin" && !Payload.empty( ) ) + { + if( IsRootAdmin( User ) ) + { + if( IsAdmin( Payload ) ) + QueueChatCommand( m_GHost->m_Language->UserIsAlreadyAnAdmin( m_Server, Payload ), User, Whisper ); + else + m_PairedAdminAdds.push_back( PairedAdminAdd( Whisper ? User : string( ), m_GHost->m_DB->ThreadedAdminAdd( m_Server, Payload ) ) ); + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !ADDBAN + // !BAN + // + + if( ( Command == "addban" || Command == "ban" ) && !Payload.empty( ) ) + { + // extract the victim and the reason + // e.g. "Varlock leaver after dying" -> victim: "Varlock", reason: "leaver after dying" + + string Victim; + string Reason; + stringstream SS; + SS << Payload; + SS >> Victim; + + if( !SS.eof( ) ) + { + getline( SS, Reason ); + string :: size_type Start = Reason.find_first_not_of( " " ); + + if( Start != string :: npos ) + Reason = Reason.substr( Start ); + } + + if( IsBannedName( Victim ) ) + QueueChatCommand( m_GHost->m_Language->UserIsAlreadyBanned( m_Server, Victim ), User, Whisper ); + else + m_PairedBanAdds.push_back( PairedBanAdd( Whisper ? User : string( ), m_GHost->m_DB->ThreadedBanAdd( m_Server, Victim, string( ), string( ), User, Reason ) ) ); + } + + // + // !ANNOUNCE + // + + if( Command == "announce" && m_GHost->m_CurrentGame && !m_GHost->m_CurrentGame->GetCountDownStarted( ) ) + { + if( Payload.empty( ) || Payload == "off" ) + { + QueueChatCommand( m_GHost->m_Language->AnnounceMessageDisabled( ), User, Whisper ); + m_GHost->m_CurrentGame->SetAnnounce( 0, string( ) ); + } + else + { + // extract the interval and the message + // e.g. "30 hello everyone" -> interval: "30", message: "hello everyone" + + uint32_t Interval; + string Message; + stringstream SS; + SS << Payload; + SS >> Interval; + + if( SS.fail( ) || Interval == 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #1 to announce command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] missing input #2 to announce command" ); + else + { + getline( SS, Message ); + string :: size_type Start = Message.find_first_not_of( " " ); + + if( Start != string :: npos ) + Message = Message.substr( Start ); + + QueueChatCommand( m_GHost->m_Language->AnnounceMessageEnabled( ), User, Whisper ); + m_GHost->m_CurrentGame->SetAnnounce( Interval, Message ); + } + } + } + } + + // + // !AUTOHOST + // + + if( Command == "autohost" ) + { + if( IsRootAdmin( User ) ) + { + if( Payload.empty( ) || Payload == "off" ) + { + QueueChatCommand( m_GHost->m_Language->AutoHostDisabled( ), User, Whisper ); + m_GHost->m_AutoHostGameName.clear( ); + m_GHost->m_AutoHostOwner.clear( ); + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = 0; + m_GHost->m_AutoHostAutoStartPlayers = 0; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + else + { + // extract the maximum games, auto start players, and the game name + // e.g. "5 10 BattleShips Pro" -> maximum games: "5", auto start players: "10", game name: "BattleShips Pro" + + uint32_t MaximumGames; + uint32_t AutoStartPlayers; + string GameName; + stringstream SS; + SS << Payload; + SS >> MaximumGames; + + if( SS.fail( ) || MaximumGames == 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #1 to autohost command" ); + else + { + SS >> AutoStartPlayers; + + if( SS.fail( ) || AutoStartPlayers == 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #2 to autohost command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] missing input #3 to autohost command" ); + else + { + getline( SS, GameName ); + string :: size_type Start = GameName.find_first_not_of( " " ); + + if( Start != string :: npos ) + GameName = GameName.substr( Start ); + + QueueChatCommand( m_GHost->m_Language->AutoHostEnabled( ), User, Whisper ); + delete m_GHost->m_AutoHostMap; + m_GHost->m_AutoHostMap = new CMap( *m_GHost->m_Map ); + m_GHost->m_AutoHostGameName = GameName; + m_GHost->m_AutoHostOwner = User; + m_GHost->m_AutoHostServer = m_Server; + m_GHost->m_AutoHostMaximumGames = MaximumGames; + m_GHost->m_AutoHostAutoStartPlayers = AutoStartPlayers; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + } + } + } + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !AUTOHOSTMM + // + + if( Command == "autohostmm" ) + { + if( IsRootAdmin( User ) ) + { + if( Payload.empty( ) || Payload == "off" ) + { + QueueChatCommand( m_GHost->m_Language->AutoHostDisabled( ), User, Whisper ); + m_GHost->m_AutoHostGameName.clear( ); + m_GHost->m_AutoHostOwner.clear( ); + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = 0; + m_GHost->m_AutoHostAutoStartPlayers = 0; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + else + { + // extract the maximum games, auto start players, minimum score, maximum score, and the game name + // e.g. "5 10 800 1200 BattleShips Pro" -> maximum games: "5", auto start players: "10", minimum score: "800", maximum score: "1200", game name: "BattleShips Pro" + + uint32_t MaximumGames; + uint32_t AutoStartPlayers; + double MinimumScore; + double MaximumScore; + string GameName; + stringstream SS; + SS << Payload; + SS >> MaximumGames; + + if( SS.fail( ) || MaximumGames == 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #1 to autohostmm command" ); + else + { + SS >> AutoStartPlayers; + + if( SS.fail( ) || AutoStartPlayers == 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #2 to autohostmm command" ); + else + { + SS >> MinimumScore; + + if( SS.fail( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #3 to autohostmm command" ); + else + { + SS >> MaximumScore; + + if( SS.fail( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #4 to autohostmm command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] missing input #5 to autohostmm command" ); + else + { + getline( SS, GameName ); + string :: size_type Start = GameName.find_first_not_of( " " ); + + if( Start != string :: npos ) + GameName = GameName.substr( Start ); + + QueueChatCommand( m_GHost->m_Language->AutoHostEnabled( ), User, Whisper ); + delete m_GHost->m_AutoHostMap; + m_GHost->m_AutoHostMap = new CMap( *m_GHost->m_Map ); + m_GHost->m_AutoHostGameName = GameName; + m_GHost->m_AutoHostOwner = User; + m_GHost->m_AutoHostServer = m_Server; + m_GHost->m_AutoHostMaximumGames = MaximumGames; + m_GHost->m_AutoHostAutoStartPlayers = AutoStartPlayers; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = true; + m_GHost->m_AutoHostMinimumScore = MinimumScore; + m_GHost->m_AutoHostMaximumScore = MaximumScore; + } + } + } + } + } + } + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !AUTOSTART + // + + if( Command == "autostart" && m_GHost->m_CurrentGame && !m_GHost->m_CurrentGame->GetCountDownStarted( ) ) + { + if( Payload.empty( ) || Payload == "off" ) + { + QueueChatCommand( m_GHost->m_Language->AutoStartDisabled( ), User, Whisper ); + m_GHost->m_CurrentGame->SetAutoStartPlayers( 0 ); + } + else + { + uint32_t AutoStartPlayers = UTIL_ToUInt32( Payload ); + + if( AutoStartPlayers != 0 ) + { + QueueChatCommand( m_GHost->m_Language->AutoStartEnabled( UTIL_ToString( AutoStartPlayers ) ), User, Whisper ); + m_GHost->m_CurrentGame->SetAutoStartPlayers( AutoStartPlayers ); + } + } + } + + // + // !CHANNEL (change channel) + // + + if( Command == "channel" && !Payload.empty( ) ) + QueueChatCommand( "/join " + Payload ); + + // + // !CHECKADMIN + // + + if( Command == "checkadmin" && !Payload.empty( ) ) + { + if( IsRootAdmin( User ) ) + { + if( IsAdmin( Payload ) ) + QueueChatCommand( m_GHost->m_Language->UserIsAnAdmin( m_Server, Payload ), User, Whisper ); + else + QueueChatCommand( m_GHost->m_Language->UserIsNotAnAdmin( m_Server, Payload ), User, Whisper ); + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !CHECKBAN + // + + if( Command == "checkban" && !Payload.empty( ) ) + { + CDBBan *Ban = IsBannedName( Payload ); + + if( Ban ) + QueueChatCommand( m_GHost->m_Language->UserWasBannedOnByBecause( m_Server, Payload, Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ), User, Whisper ); + else + QueueChatCommand( m_GHost->m_Language->UserIsNotBanned( m_Server, Payload ), User, Whisper ); + } + + // + // !CLOSE (close slot) + // + + if( Command == "close" && !Payload.empty( ) && m_GHost->m_CurrentGame ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + { + // close as many slots as specified, e.g. "5 10" closes slots 5 and 10 + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + uint32_t SID; + SS >> SID; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input to close command" ); + break; + } + else + m_GHost->m_CurrentGame->CloseSlot( (unsigned char)( SID - 1 ), true ); + } + } + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !CLOSEALL + // + + if( Command == "closeall" && m_GHost->m_CurrentGame ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + m_GHost->m_CurrentGame->CloseAllSlots( ); + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !COUNTADMINS + // + + if( Command == "countadmins" ) + { + if( IsRootAdmin( User ) ) + m_PairedAdminCounts.push_back( PairedAdminCount( Whisper ? User : string( ), m_GHost->m_DB->ThreadedAdminCount( m_Server ) ) ); + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !COUNTBANS + // + + if( Command == "countbans" ) + m_PairedBanCounts.push_back( PairedBanCount( Whisper ? User : string( ), m_GHost->m_DB->ThreadedBanCount( m_Server ) ) ); + + // + // !DBSTATUS + // + + if( Command == "dbstatus" ) + QueueChatCommand( m_GHost->m_DB->GetStatus( ), User, Whisper ); + + // + // !DELADMIN + // + + if( Command == "deladmin" && !Payload.empty( ) ) + { + if( IsRootAdmin( User ) ) + { + if( !IsAdmin( Payload ) ) + QueueChatCommand( m_GHost->m_Language->UserIsNotAnAdmin( m_Server, Payload ), User, Whisper ); + else + m_PairedAdminRemoves.push_back( PairedAdminRemove( Whisper ? User : string( ), m_GHost->m_DB->ThreadedAdminRemove( m_Server, Payload ) ) ); + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !DELBAN + // !UNBAN + // + + if( ( Command == "delban" || Command == "unban" ) && !Payload.empty( ) ) + m_PairedBanRemoves.push_back( PairedBanRemove( Whisper ? User : string( ), m_GHost->m_DB->ThreadedBanRemove( Payload ) ) ); + + // + // !DISABLE + // + + if( Command == "disable" ) + { + if( IsRootAdmin( User ) ) + { + QueueChatCommand( m_GHost->m_Language->BotDisabled( ), User, Whisper ); + m_GHost->m_Enabled = false; + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !DOWNLOADS + // + + if( Command == "downloads" && !Payload.empty( ) ) + { + uint32_t Downloads = UTIL_ToUInt32( Payload ); + + if( Downloads == 0 ) + { + QueueChatCommand( m_GHost->m_Language->MapDownloadsDisabled( ), User, Whisper ); + m_GHost->m_AllowDownloads = 0; + } + else if( Downloads == 1 ) + { + QueueChatCommand( m_GHost->m_Language->MapDownloadsEnabled( ), User, Whisper ); + m_GHost->m_AllowDownloads = 1; + } + else if( Downloads == 2 ) + { + QueueChatCommand( m_GHost->m_Language->MapDownloadsConditional( ), User, Whisper ); + m_GHost->m_AllowDownloads = 2; + } + } + + // + // !ENABLE + // + + if( Command == "enable" ) + { + if( IsRootAdmin( User ) ) + { + QueueChatCommand( m_GHost->m_Language->BotEnabled( ), User, Whisper ); + m_GHost->m_Enabled = true; + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !END + // + + if( Command == "end" && !Payload.empty( ) ) + { + // todotodo: what if a game ends just as you're typing this command and the numbering changes? + + uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; + + if( GameNumber < m_GHost->m_Games.size( ) ) + { + // if the game owner is still in the game only allow the root admin to end the game + + if( m_GHost->m_Games[GameNumber]->GetPlayerFromName( m_GHost->m_Games[GameNumber]->GetOwnerName( ), false ) && !IsRootAdmin( User ) ) + QueueChatCommand( m_GHost->m_Language->CantEndGameOwnerIsStillPlaying( m_GHost->m_Games[GameNumber]->GetOwnerName( ) ), User, Whisper ); + else + { + QueueChatCommand( m_GHost->m_Language->EndingGame( m_GHost->m_Games[GameNumber]->GetDescription( ) ), User, Whisper ); + CONSOLE_Print( "[GAME: " + m_GHost->m_Games[GameNumber]->GetGameName( ) + "] is over (admin ended game)" ); + m_GHost->m_Games[GameNumber]->StopPlayers( "was disconnected (admin ended game)" ); + } + } + else + QueueChatCommand( m_GHost->m_Language->GameNumberDoesntExist( Payload ), User, Whisper ); + } + + // + // !ENFORCESG + // + + if( Command == "enforcesg" && !Payload.empty( ) ) + { + // only load files in the current directory just to be safe + + if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) + QueueChatCommand( m_GHost->m_Language->UnableToLoadReplaysOutside( ), User, Whisper ); + else + { + string File = m_GHost->m_ReplayPath + Payload + ".w3g"; + + if( UTIL_FileExists( File ) ) + { + QueueChatCommand( m_GHost->m_Language->LoadingReplay( File ), User, Whisper ); + CReplay *Replay = new CReplay( ); + Replay->Load( File, false ); + Replay->ParseReplay( false ); + m_GHost->m_EnforcePlayers = Replay->GetPlayers( ); + delete Replay; + } + else + QueueChatCommand( m_GHost->m_Language->UnableToLoadReplayDoesntExist( File ), User, Whisper ); + } + } + + // + // !EXIT + // !QUIT + // + + if( Command == "exit" || Command == "quit" ) + { + if( IsRootAdmin( User ) ) + { + if( Payload == "nice" ) + m_GHost->m_ExitingNice = true; + else if( Payload == "force" ) + m_Exiting = true; + else + { + if( m_GHost->m_CurrentGame || !m_GHost->m_Games.empty( ) ) + QueueChatCommand( m_GHost->m_Language->AtLeastOneGameActiveUseForceToShutdown( ), User, Whisper ); + else + m_Exiting = true; + } + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !GETCLAN + // + + if( Command == "getclan" ) + { + SendGetClanList( ); + QueueChatCommand( m_GHost->m_Language->UpdatingClanList( ), User, Whisper ); + } + + // + // !GETFRIENDS + // + + if( Command == "getfriends" ) + { + SendGetFriendsList( ); + QueueChatCommand( m_GHost->m_Language->UpdatingFriendsList( ), User, Whisper ); + } + + // + // !GETGAME + // + + if( Command == "getgame" && !Payload.empty( ) ) + { + uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; + + if( GameNumber < m_GHost->m_Games.size( ) ) + QueueChatCommand( m_GHost->m_Language->GameNumberIs( Payload, m_GHost->m_Games[GameNumber]->GetDescription( ) ), User, Whisper ); + else + QueueChatCommand( m_GHost->m_Language->GameNumberDoesntExist( Payload ), User, Whisper ); + } + + // + // !GETGAMES + // + + if( Command == "getgames" ) + { + if( m_GHost->m_CurrentGame ) + QueueChatCommand( m_GHost->m_Language->GameIsInTheLobby( m_GHost->m_CurrentGame->GetDescription( ), UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ), User, Whisper ); + else + QueueChatCommand( m_GHost->m_Language->ThereIsNoGameInTheLobby( UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ), User, Whisper ); + } + + // + // !HOLD (hold a slot for someone) + // + + if( Command == "hold" && !Payload.empty( ) && m_GHost->m_CurrentGame ) + { + // hold as many players as specified, e.g. "Varlock Kilranin" holds players "Varlock" and "Kilranin" + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + string HoldName; + SS >> HoldName; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input to hold command" ); + break; + } + else + { + QueueChatCommand( m_GHost->m_Language->AddedPlayerToTheHoldList( HoldName ), User, Whisper ); + m_GHost->m_CurrentGame->AddToReserved( HoldName ); + } + } + } + + // + // !HOSTSG + // + + if( Command == "hostsg" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, true, Payload, User, User, m_Server, Whisper ); + + // + // !LOAD (load config file) + // + + if( Command == "load" ) + { + if( Payload.empty( ) ) + QueueChatCommand( m_GHost->m_Language->CurrentlyLoadedMapCFGIs( m_GHost->m_Map->GetCFGFile( ) ), User, Whisper ); + else + { + string FoundMapConfigs; + + try + { + path MapCFGPath( m_GHost->m_MapCFGPath ); + string Pattern = Payload; + transform( Pattern.begin( ), Pattern.end( ), Pattern.begin( ), (int(*)(int))tolower ); + + if( !exists( MapCFGPath ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error listing map configs - map config path doesn't exist" ); + QueueChatCommand( m_GHost->m_Language->ErrorListingMapConfigs( ), User, Whisper ); + } + else + { + directory_iterator EndIterator; + path LastMatch; + uint32_t Matches = 0; + + for( directory_iterator i( MapCFGPath ); i != EndIterator; i++ ) + { + string FileName = i->filename( ); + string Stem = i->path( ).stem( ); + transform( FileName.begin( ), FileName.end( ), FileName.begin( ), (int(*)(int))tolower ); + transform( Stem.begin( ), Stem.end( ), Stem.begin( ), (int(*)(int))tolower ); + + if( !is_directory( i->status( ) ) && i->path( ).extension( ) == ".cfg" && FileName.find( Pattern ) != string :: npos ) + { + LastMatch = i->path( ); + Matches++; + + if( FoundMapConfigs.empty( ) ) + FoundMapConfigs = i->filename( ); + else + FoundMapConfigs += ", " + i->filename( ); + + // if the pattern matches the filename exactly, with or without extension, stop any further matching + + if( FileName == Pattern || Stem == Pattern ) + { + Matches = 1; + break; + } + } + } + + if( Matches == 0 ) + QueueChatCommand( m_GHost->m_Language->NoMapConfigsFound( ), User, Whisper ); + else if( Matches == 1 ) + { + string File = LastMatch.filename( ); + QueueChatCommand( m_GHost->m_Language->LoadingConfigFile( m_GHost->m_MapCFGPath + File ), User, Whisper ); + CConfig MapCFG; + MapCFG.Read( LastMatch.string( ) ); + m_GHost->m_Map->Load( &MapCFG, m_GHost->m_MapCFGPath + File ); + } + else + QueueChatCommand( m_GHost->m_Language->FoundMapConfigs( FoundMapConfigs ), User, Whisper ); + } + } + catch( const exception &ex ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error listing map configs - caught exception [" + ex.what( ) + "]" ); + QueueChatCommand( m_GHost->m_Language->ErrorListingMapConfigs( ), User, Whisper ); + } + } + } + + // + // !LOADSG + // + + if( Command == "loadsg" && !Payload.empty( ) ) + { + // only load files in the current directory just to be safe + + if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) + QueueChatCommand( m_GHost->m_Language->UnableToLoadSaveGamesOutside( ), User, Whisper ); + else + { + string File = m_GHost->m_SaveGamePath + Payload + ".w3z"; + string FileNoPath = Payload + ".w3z"; + + if( UTIL_FileExists( File ) ) + { + if( m_GHost->m_CurrentGame ) + QueueChatCommand( m_GHost->m_Language->UnableToLoadSaveGameGameInLobby( ), User, Whisper ); + else + { + QueueChatCommand( m_GHost->m_Language->LoadingSaveGame( File ), User, Whisper ); + m_GHost->m_SaveGame->Load( File, false ); + m_GHost->m_SaveGame->ParseSaveGame( ); + m_GHost->m_SaveGame->SetFileName( File ); + m_GHost->m_SaveGame->SetFileNameNoPath( FileNoPath ); + } + } + else + QueueChatCommand( m_GHost->m_Language->UnableToLoadSaveGameDoesntExist( File ), User, Whisper ); + } + } + + // + // !MAP (load map file) + // + + if( Command == "map" ) + { + if( Payload.empty( ) ) + QueueChatCommand( m_GHost->m_Language->CurrentlyLoadedMapCFGIs( m_GHost->m_Map->GetCFGFile( ) ), User, Whisper ); + else + { + string FoundMaps; + + try + { + path MapPath( m_GHost->m_MapPath ); + string Pattern = Payload; + transform( Pattern.begin( ), Pattern.end( ), Pattern.begin( ), (int(*)(int))tolower ); + + if( !exists( MapPath ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error listing maps - map path doesn't exist" ); + QueueChatCommand( m_GHost->m_Language->ErrorListingMaps( ), User, Whisper ); + } + else + { + directory_iterator EndIterator; + path LastMatch; + uint32_t Matches = 0; + + for( directory_iterator i( MapPath ); i != EndIterator; i++ ) + { + string FileName = i->filename( ); + string Stem = i->path( ).stem( ); + transform( FileName.begin( ), FileName.end( ), FileName.begin( ), (int(*)(int))tolower ); + transform( Stem.begin( ), Stem.end( ), Stem.begin( ), (int(*)(int))tolower ); + + if( !is_directory( i->status( ) ) && FileName.find( Pattern ) != string :: npos ) + { + LastMatch = i->path( ); + Matches++; + + if( FoundMaps.empty( ) ) + FoundMaps = i->filename( ); + else + FoundMaps += ", " + i->filename( ); + + // if the pattern matches the filename exactly, with or without extension, stop any further matching + + if( FileName == Pattern || Stem == Pattern ) + { + Matches = 1; + break; + } + } + } + + if( Matches == 0 ) + QueueChatCommand( m_GHost->m_Language->NoMapsFound( ), User, Whisper ); + else if( Matches == 1 ) + { + string File = LastMatch.filename( ); + QueueChatCommand( m_GHost->m_Language->LoadingConfigFile( File ), User, Whisper ); + + // hackhack: create a config file in memory with the required information to load the map + + CConfig MapCFG; + MapCFG.Set( "map_path", "Maps\\Download\\" + File ); + MapCFG.Set( "map_localpath", File ); + m_GHost->m_Map->Load( &MapCFG, File ); + } + else + QueueChatCommand( m_GHost->m_Language->FoundMaps( FoundMaps ), User, Whisper ); + } + } + catch( const exception &ex ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] error listing maps - caught exception [" + ex.what( ) + "]" ); + QueueChatCommand( m_GHost->m_Language->ErrorListingMaps( ), User, Whisper ); + } + } + } + + // + // !OPEN (open slot) + // + + if( Command == "open" && !Payload.empty( ) && m_GHost->m_CurrentGame ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + { + // open as many slots as specified, e.g. "5 10" opens slots 5 and 10 + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + uint32_t SID; + SS >> SID; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input to open command" ); + break; + } + else + m_GHost->m_CurrentGame->OpenSlot( (unsigned char)( SID - 1 ), true ); + } + } + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !OPENALL + // + + if( Command == "openall" && m_GHost->m_CurrentGame ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + m_GHost->m_CurrentGame->OpenAllSlots( ); + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !PRIV (host private game) + // + + if( Command == "priv" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, false, Payload, User, User, m_Server, Whisper ); + + // + // !PRIVBY (host private game by other player) + // + + if( Command == "privby" && !Payload.empty( ) ) + { + // extract the owner and the game name + // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" + + string Owner; + string GameName; + string :: size_type GameNameStart = Payload.find( " " ); + + if( GameNameStart != string :: npos ) + { + Owner = Payload.substr( 0, GameNameStart ); + GameName = Payload.substr( GameNameStart + 1 ); + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, false, GameName, Owner, User, m_Server, Whisper ); + } + } + + // + // !PUB (host public game) + // + + if( Command == "pub" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PUBLIC, false, Payload, User, User, m_Server, Whisper ); + + // + // !PUBBY (host public game by other player) + // + + if( Command == "pubby" && !Payload.empty( ) ) + { + // extract the owner and the game name + // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" + + string Owner; + string GameName; + string :: size_type GameNameStart = Payload.find( " " ); + + if( GameNameStart != string :: npos ) + { + Owner = Payload.substr( 0, GameNameStart ); + GameName = Payload.substr( GameNameStart + 1 ); + m_GHost->CreateGame( m_GHost->m_Map, GAME_PUBLIC, false, GameName, Owner, User, m_Server, Whisper ); + } + } + + // + // !RELOAD + // + + if( Command == "reload" ) + { + if( IsRootAdmin( User ) ) + { + QueueChatCommand( m_GHost->m_Language->ReloadingConfigurationFiles( ), User, Whisper ); + m_GHost->ReloadConfigs( ); + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !SAY + // + + if( Command == "say" && !Payload.empty( ) ) + QueueChatCommand( Payload ); + + // + // !SAYGAME + // + + if( Command == "saygame" && !Payload.empty( ) ) + { + if( IsRootAdmin( User ) ) + { + // extract the game number and the message + // e.g. "3 hello everyone" -> game number: "3", message: "hello everyone" + + uint32_t GameNumber; + string Message; + stringstream SS; + SS << Payload; + SS >> GameNumber; + + if( SS.fail( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #1 to saygame command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] missing input #2 to saygame command" ); + else + { + getline( SS, Message ); + string :: size_type Start = Message.find_first_not_of( " " ); + + if( Start != string :: npos ) + Message = Message.substr( Start ); + + if( GameNumber - 1 < m_GHost->m_Games.size( ) ) + m_GHost->m_Games[GameNumber - 1]->SendAllChat( "ADMIN: " + Message ); + else + QueueChatCommand( m_GHost->m_Language->GameNumberDoesntExist( UTIL_ToString( GameNumber ) ), User, Whisper ); + } + } + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !SAYGAMES + // + + if( Command == "saygames" && !Payload.empty( ) ) + { + if( IsRootAdmin( User ) ) + { + if( m_GHost->m_CurrentGame ) + m_GHost->m_CurrentGame->SendAllChat( Payload ); + + for( vector :: iterator i = m_GHost->m_Games.begin( ); i != m_GHost->m_Games.end( ); i++ ) + (*i)->SendAllChat( "ADMIN: " + Payload ); + } + else + QueueChatCommand( m_GHost->m_Language->YouDontHaveAccessToThatCommand( ), User, Whisper ); + } + + // + // !SP + // + + if( Command == "sp" && m_GHost->m_CurrentGame && !m_GHost->m_CurrentGame->GetCountDownStarted( ) ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + { + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) ); + m_GHost->m_CurrentGame->ShuffleSlots( ); + } + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !START + // + + if( Command == "start" && m_GHost->m_CurrentGame && !m_GHost->m_CurrentGame->GetCountDownStarted( ) && m_GHost->m_CurrentGame->GetNumHumanPlayers( ) > 0 ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + { + // if the player sent "!start force" skip the checks and start the countdown + // otherwise check that the game is ready to start + + if( Payload == "force" ) + m_GHost->m_CurrentGame->StartCountDown( true ); + else + m_GHost->m_CurrentGame->StartCountDown( false ); + } + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !SWAP (swap slots) + // + + if( Command == "swap" && !Payload.empty( ) && m_GHost->m_CurrentGame ) + { + if( !m_GHost->m_CurrentGame->GetLocked( ) ) + { + uint32_t SID1; + uint32_t SID2; + stringstream SS; + SS << Payload; + SS >> SID1; + + if( SS.fail( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #1 to swap command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] missing input #2 to swap command" ); + else + { + SS >> SID2; + + if( SS.fail( ) ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] bad input #2 to swap command" ); + else + m_GHost->m_CurrentGame->SwapSlots( (unsigned char)( SID1 - 1 ), (unsigned char)( SID2 - 1 ) ); + } + } + } + else + QueueChatCommand( m_GHost->m_Language->TheGameIsLockedBNET( ), User, Whisper ); + } + + // + // !UNHOST + // + + if( Command == "unhost" ) + { + if( m_GHost->m_CurrentGame ) + { + if( m_GHost->m_CurrentGame->GetCountDownStarted( ) ) + QueueChatCommand( m_GHost->m_Language->UnableToUnhostGameCountdownStarted( m_GHost->m_CurrentGame->GetDescription( ) ), User, Whisper ); + + // if the game owner is still in the game only allow the root admin to unhost the game + + else if( m_GHost->m_CurrentGame->GetPlayerFromName( m_GHost->m_CurrentGame->GetOwnerName( ), false ) && !IsRootAdmin( User ) ) + QueueChatCommand( m_GHost->m_Language->CantUnhostGameOwnerIsPresent( m_GHost->m_CurrentGame->GetOwnerName( ) ), User, Whisper ); + else + { + QueueChatCommand( m_GHost->m_Language->UnhostingGame( m_GHost->m_CurrentGame->GetDescription( ) ), User, Whisper ); + m_GHost->m_CurrentGame->SetExiting( true ); + } + } + else + QueueChatCommand( m_GHost->m_Language->UnableToUnhostGameNoGameInLobby( ), User, Whisper ); + } + + // + // !WARDENSTATUS + // + + if( Command == "wardenstatus" ) + { + if( m_BNLSClient ) + QueueChatCommand( "WARDEN STATUS --- " + UTIL_ToString( m_BNLSClient->GetTotalWardenIn( ) ) + " requests received, " + UTIL_ToString( m_BNLSClient->GetTotalWardenOut( ) ) + " responses sent.", User, Whisper ); + else + QueueChatCommand( "WARDEN STATUS --- Not connected to BNLS server.", User, Whisper ); + } + } + else + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] non-admin [" + User + "] sent command [" + Message + "]" ); + + /********************* + * NON ADMIN COMMANDS * + *********************/ + + // don't respond to non admins if there are more than 3 messages already in the queue + // this prevents malicious users from filling up the bot's chat queue and crippling the bot + // in some cases the queue may be full of legitimate messages but we don't really care if the bot ignores one of these commands once in awhile + // e.g. when several users join a game at the same time and cause multiple /whois messages to be queued at once + + if( IsAdmin( User ) || IsRootAdmin( User ) || ( m_PublicCommands && m_OutPackets.size( ) <= 3 ) ) + { + // + // !STATS + // + + if( Command == "stats" ) + { + string StatsUser = User; + + if( !Payload.empty( ) ) + StatsUser = Payload; + + // check for potential abuse + + if( !StatsUser.empty( ) && StatsUser.size( ) < 16 && StatsUser[0] != '/' ) + m_PairedGPSChecks.push_back( PairedGPSCheck( Whisper ? User : string( ), m_GHost->m_DB->ThreadedGamePlayerSummaryCheck( StatsUser ) ) ); + } + + // + // !STATSDOTA + // + + if( Command == "statsdota" ) + { + string StatsUser = User; + + if( !Payload.empty( ) ) + StatsUser = Payload; + + // check for potential abuse + + if( !StatsUser.empty( ) && StatsUser.size( ) < 16 && StatsUser[0] != '/' ) + m_PairedDPSChecks.push_back( PairedDPSCheck( Whisper ? User : string( ), m_GHost->m_DB->ThreadedDotAPlayerSummaryCheck( StatsUser ) ) ); + } + + // + // !VERSION + // + + if( Command == "version" ) + { + if( IsAdmin( User ) || IsRootAdmin( User ) ) + QueueChatCommand( m_GHost->m_Language->VersionAdmin( m_GHost->m_Version ), User, Whisper ); + else + QueueChatCommand( m_GHost->m_Language->VersionNotAdmin( m_GHost->m_Version ), User, Whisper ); + } + } + } + } + else if( Event == CBNETProtocol :: EID_INFO ) + { + CONSOLE_Print( "[INFO] " + Message, GetRealmId( ), false ); + + // extract the first word which we hope is the username + // this is not necessarily true though since info messages also include channel MOTD's and such + + string UserName; + string :: size_type Split = Message.find( " " ); + + if( Split != string :: npos ) + UserName = Message.substr( 0, Split ); + else + UserName = Message.substr( 0 ); + + // handle spoof checking for current game + // this case covers whois results which are used when hosting a public game (we send out a "/whois [player]" for each player) + // at all times you can still /w the bot with "spoofcheck" to manually spoof check + + if( m_GHost->m_CurrentGame && m_GHost->m_CurrentGame->GetPlayerFromName( UserName, true ) ) + { + if( Message.find( "is away" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofPossibleIsAway( UserName ) ); + else if( Message.find( "is unavailable" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofPossibleIsUnavailable( UserName ) ); + else if( Message.find( "is refusing messages" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofPossibleIsRefusingMessages( UserName ) ); + else if( Message.find( "is using Warcraft III The Frozen Throne in the channel" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofDetectedIsNotInGame( UserName ) ); + else if( Message.find( "is using Warcraft III The Frozen Throne in channel" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofDetectedIsNotInGame( UserName ) ); + else if( Message.find( "is using Warcraft III The Frozen Throne in a private channel" ) != string :: npos ) + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofDetectedIsInPrivateChannel( UserName ) ); + + if( Message.find( "is using Warcraft III The Frozen Throne in game" ) != string :: npos || Message.find( "is using Warcraft III Frozen Throne and is currently in game" ) != string :: npos ) + { + // check both the current game name and the last game name against the /whois response + // this is because when the game is rehosted, players who joined recently will be in the previous game according to battle.net + // note: if the game is rehosted more than once it is possible (but unlikely) for a false positive because only two game names are checked + + if( Message.find( m_GHost->m_CurrentGame->GetGameName( ) ) != string :: npos || Message.find( m_GHost->m_CurrentGame->GetLastGameName( ) ) != string :: npos ) + m_GHost->m_CurrentGame->AddToSpoofed( m_Server, UserName, false ); + else + m_GHost->m_CurrentGame->SendAllChat( m_GHost->m_Language->SpoofDetectedIsInAnotherGame( UserName ) ); + } + } + } +} + +void CBNET :: SendJoinChannel( string channel ) +{ + if( m_LoggedIn && m_InChat ) + m_Socket->PutBytes( m_Protocol->SEND_SID_JOINCHANNEL( channel ) ); +} + +void CBNET :: SendGetFriendsList( ) +{ + if( m_LoggedIn ) + m_Socket->PutBytes( m_Protocol->SEND_SID_FRIENDSLIST( ) ); +} + +void CBNET :: SendGetClanList( ) +{ + if( m_LoggedIn ) + m_Socket->PutBytes( m_Protocol->SEND_SID_CLANMEMBERLIST( ) ); +} + +void CBNET :: QueueEnterChat( ) +{ + if( m_LoggedIn ) + m_OutPackets.push( m_Protocol->SEND_SID_ENTERCHAT( ) ); +} + +void CBNET :: QueueChatCommand( string chatCommand, bool hidden ) +{ + if( chatCommand.empty( ) ) + return; + + if( m_LoggedIn ) + { + if( m_PasswordHashType == "pvpgn" && chatCommand.size( ) > m_MaxMessageLength ) + chatCommand = chatCommand.substr( 0, m_MaxMessageLength ); + + if( chatCommand.size( ) > 255 ) + chatCommand = chatCommand.substr( 0, 255 ); + + if( m_OutPackets.size( ) > 10 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] attempted to queue chat command [" + chatCommand + "] but there are too many (" + UTIL_ToString( m_OutPackets.size( ) ) + ") packets queued, discarding" ); + else + { + bool whisper = false; + int r = 0; + + if ( chatCommand.size( ) > 3 ) + { + if ( chatCommand.substr( 0, 3 ) == "/w " ) { whisper = true; r = 3; } + else if ( chatCommand.substr( 0, 9 ) == "/whisper " ) { whisper = true; r = 9; } + else if ( chatCommand.substr( 0, 5 ) == "/f m " ) { whisper = true; r = 5; } + else if ( chatCommand.substr( 0, 7 ) == "/f msg " ) { whisper = true; r = 7; } + else if ( chatCommand.substr( 0, 11 ) == "/friends m " ) { whisper = true; r = 11; } + else if ( chatCommand.substr( 0, 13 ) == "/friends msg " ) { whisper = true; r = 13; } + } + + if ( whisper ) + { + if ( r == 3 || r == 9 ) + { + int nameEndpos = chatCommand.find_first_of( " ", r ); + if ( nameEndpos != -1 ) + { + string target = chatCommand.substr( r, nameEndpos - r ); + CONSOLE_Print( "[WHISPERED: " + target + "] " + chatCommand.substr( nameEndpos + 1, chatCommand.length( ) ) , GetRealmId( ), false ); + } + } + else CONSOLE_Print( "[WHISPERED: Friends] " + chatCommand.substr( r, chatCommand.length( ) ) , GetRealmId( ), false ); + } + else if (!hidden) CONSOLE_Print( "<" + m_UserName + "> " + chatCommand, GetRealmId( ), false ); + + m_OutPackets.push( m_Protocol->SEND_SID_CHATCOMMAND( chatCommand ) ); + } + } +} + +void CBNET :: QueueChatCommand( string chatCommand, string user, bool whisper ) +{ + if( chatCommand.empty( ) ) + return; + + // if whisper is true send the chat command as a whisper to user, otherwise just queue the chat command + + if( whisper ) + QueueChatCommand( "/w " + user + " " + chatCommand ); + else + QueueChatCommand( chatCommand ); +} + +void CBNET :: QueueGameCreate( unsigned char state, string gameName, string hostName, CMap *map, CSaveGame *savegame, uint32_t hostCounter ) +{ + if( m_LoggedIn && map ) + { + if( !m_CurrentChannel.empty( ) ) + m_FirstChannel = m_CurrentChannel; + + m_InChat = false; + + // a game creation message is just a game refresh message with upTime = 0 + + QueueGameRefresh( state, gameName, hostName, map, savegame, 0, hostCounter ); + } +} + +void CBNET :: QueueGameRefresh( unsigned char state, string gameName, string hostName, CMap *map, CSaveGame *saveGame, uint32_t upTime, uint32_t hostCounter ) +{ + if( hostName.empty( ) ) + { + BYTEARRAY UniqueName = m_Protocol->GetUniqueName( ); + hostName = string( UniqueName.begin( ), UniqueName.end( ) ); + } + + if( m_LoggedIn && map ) + { + // construct a fixed host counter which will be used to identify players from this realm + // the fixed host counter's 4 most significant bits will contain a 4 bit ID (0-15) + // the rest of the fixed host counter will contain the 28 least significant bits of the actual host counter + // since we're destroying 4 bits of information here the actual host counter should not be greater than 2^28 which is a reasonable assumption + // when a player joins a game we can obtain the ID from the received host counter + // note: LAN broadcasts use an ID of 0, battle.net refreshes use an ID of 1-10, the rest are unused + + uint32_t FixedHostCounter = ( hostCounter & 0x0FFFFFFF ) | ( m_HostCounterID << 28 ); + + if( saveGame ) + { + uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME; + + // the state should always be private when creating a saved game + + if( state == GAME_PRIVATE ) + MapGameType |= MAPGAMETYPE_PRIVATEGAME; + + // use an invalid map width/height to indicate reconnectable games + + BYTEARRAY MapWidth; + MapWidth.push_back( 192 ); + MapWidth.push_back( 7 ); + BYTEARRAY MapHeight; + MapHeight.push_back( 192 ); + MapHeight.push_back( 7 ); + + if( m_GHost->m_Reconnect ) + m_OutPackets.push( m_Protocol->SEND_SID_STARTADVEX3( state, UTIL_CreateByteArray( MapGameType, false ), map->GetMapGameFlags( ), MapWidth, MapHeight, gameName, hostName, upTime, "Save\\Multiplayer\\" + saveGame->GetFileNameNoPath( ), saveGame->GetMagicNumber( ), map->GetMapSHA1( ), FixedHostCounter ) ); + else + m_OutPackets.push( m_Protocol->SEND_SID_STARTADVEX3( state, UTIL_CreateByteArray( MapGameType, false ), map->GetMapGameFlags( ), UTIL_CreateByteArray( (uint16_t)0, false ), UTIL_CreateByteArray( (uint16_t)0, false ), gameName, hostName, upTime, "Save\\Multiplayer\\" + saveGame->GetFileNameNoPath( ), saveGame->GetMagicNumber( ), map->GetMapSHA1( ), FixedHostCounter ) ); + } + else + { + uint32_t MapGameType = map->GetMapGameType( ); + MapGameType |= MAPGAMETYPE_UNKNOWN0; + + if( state == GAME_PRIVATE ) + MapGameType |= MAPGAMETYPE_PRIVATEGAME; + + // use an invalid map width/height to indicate reconnectable games + + BYTEARRAY MapWidth; + MapWidth.push_back( 192 ); + MapWidth.push_back( 7 ); + BYTEARRAY MapHeight; + MapHeight.push_back( 192 ); + MapHeight.push_back( 7 ); + + if( m_GHost->m_Reconnect ) + m_OutPackets.push( m_Protocol->SEND_SID_STARTADVEX3( state, UTIL_CreateByteArray( MapGameType, false ), map->GetMapGameFlags( ), MapWidth, MapHeight, gameName, hostName, upTime, map->GetMapPath( ), map->GetMapCRC( ), map->GetMapSHA1( ), FixedHostCounter ) ); + else + m_OutPackets.push( m_Protocol->SEND_SID_STARTADVEX3( state, UTIL_CreateByteArray( MapGameType, false ), map->GetMapGameFlags( ), map->GetMapWidth( ), map->GetMapHeight( ), gameName, hostName, upTime, map->GetMapPath( ), map->GetMapCRC( ), map->GetMapSHA1( ), FixedHostCounter ) ); + } + } +} + +void CBNET :: QueueGameUncreate( ) +{ + if( m_LoggedIn ) + m_OutPackets.push( m_Protocol->SEND_SID_STOPADV( ) ); +} + +void CBNET :: UnqueuePackets( unsigned char type ) +{ + queue Packets; + uint32_t Unqueued = 0; + + while( !m_OutPackets.empty( ) ) + { + // todotodo: it's very inefficient to have to copy all these packets while searching the queue + + BYTEARRAY Packet = m_OutPackets.front( ); + m_OutPackets.pop( ); + + if( Packet.size( ) >= 2 && Packet[1] == type ) + Unqueued++; + else + Packets.push( Packet ); + } + + m_OutPackets = Packets; + + if( Unqueued > 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] unqueued " + UTIL_ToString( Unqueued ) + " packets of type " + UTIL_ToString( type ) ); +} + +void CBNET :: UnqueueChatCommand( string chatCommand ) +{ + // hackhack: this is ugly code + // generate the packet that would be sent for this chat command + // then search the queue for that exact packet + + BYTEARRAY PacketToUnqueue = m_Protocol->SEND_SID_CHATCOMMAND( chatCommand ); + queue Packets; + uint32_t Unqueued = 0; + + while( !m_OutPackets.empty( ) ) + { + // todotodo: it's very inefficient to have to copy all these packets while searching the queue + + BYTEARRAY Packet = m_OutPackets.front( ); + m_OutPackets.pop( ); + + if( Packet == PacketToUnqueue ) + Unqueued++; + else + Packets.push( Packet ); + } + + m_OutPackets = Packets; + + if( Unqueued > 0 ) + CONSOLE_Print( "[BNET: " + m_ServerAlias + "] unqueued " + UTIL_ToString( Unqueued ) + " chat command packets" ); +} + +void CBNET :: UnqueueGameRefreshes( ) +{ + UnqueuePackets( CBNETProtocol :: SID_STARTADVEX3 ); +} + +void CBNET :: RequestListUpdates( ) +{ + SendGetFriendsList( ); + SendGetClanList( ); +} + +bool CBNET :: IsAdmin( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + for( vector :: iterator i = m_Admins.begin( ); i != m_Admins.end( ); i++ ) + { + if( *i == name ) + return true; + } + + return false; +} + +bool CBNET :: IsRootAdmin( string name ) +{ + if( name == "" ) + return true; + + // m_RootAdmin was already transformed to lower case in the constructor + + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + // updated to permit multiple root admins seperated by a space, e.g. "Varlock Kilranin Instinct121" + // note: this function gets called frequently so it would be better to parse the root admins just once and store them in a list somewhere + // however, it's hardly worth optimizing at this point since the code's already written + + stringstream SS; + string s; + SS << m_RootAdmin; + + while( !SS.eof( ) ) + { + SS >> s; + + if( name == s ) + return true; + } + + return false; +} + +CDBBan *CBNET :: IsBannedName( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + // todotodo: optimize this - maybe use a map? + + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ); i++ ) + { + if( (*i)->GetName( ) == name ) + return *i; + } + + return NULL; +} + +CDBBan *CBNET :: IsBannedIP( string ip ) +{ + // todotodo: optimize this - maybe use a map? + + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ); i++ ) + { + if( (*i)->GetIP( ) == ip ) + return *i; + } + + return NULL; +} + +void CBNET :: AddAdmin( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + m_Admins.push_back( name ); +} + +void CBNET :: AddBan( string name, string ip, string gamename, string admin, string reason ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + m_Bans.push_back( new CDBBan( m_Server, name, ip, "N/A", gamename, admin, reason ) ); +} + +void CBNET :: RemoveAdmin( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + for( vector :: iterator i = m_Admins.begin( ); i != m_Admins.end( ); ) + { + if( *i == name ) + i = m_Admins.erase( i ); + else + i++; + } +} + +void CBNET :: RemoveBan( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + for( vector :: iterator i = m_Bans.begin( ); i != m_Bans.end( ); ) + { + if( (*i)->GetName( ) == name ) + i = m_Bans.erase( i ); + else + i++; + } +} + +void CBNET :: HoldFriends( CBaseGame *game ) +{ + if( game ) + { + for( vector :: iterator i = m_Friends.begin( ); i != m_Friends.end( ); i++ ) + game->AddToReserved( (*i)->GetAccount( ) ); + } +} + +void CBNET :: HoldClan( CBaseGame *game ) +{ + if( game ) + { + for( vector :: iterator i = m_Clans.begin( ); i != m_Clans.end( ); i++ ) + game->AddToReserved( (*i)->GetName( ) ); + } +} + +void CBNET :: HiddenGhostCommand( string Message ) +{ + // "" ok or not? revert to m_UserName? + CIncomingChatEvent temp( CBNETProtocol::IncomingChatEvent(29), 0, 0, "", Message ); + ProcessChatEvent( &temp ); +} diff --git a/ghost-legacy/bnet.h b/ghost-legacy/bnet.h new file mode 100644 index 0000000..5ffc127 --- /dev/null +++ b/ghost-legacy/bnet.h @@ -0,0 +1,202 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef BNET_H +#define BNET_H + +// +// CBNET +// + +class CTCPClient; +class CCommandPacket; +class CBNCSUtilInterface; +class CBNETProtocol; +class CBNLSClient; +class CIncomingFriendList; +class CIncomingClanList; +class CIncomingChatEvent; +class CCallableAdminCount; +class CCallableAdminAdd; +class CCallableAdminRemove; +class CCallableAdminList; +class CCallableBanCount; +class CCallableBanAdd; +class CCallableBanRemove; +class CCallableBanList; +class CCallableGamePlayerSummaryCheck; +class CCallableDotAPlayerSummaryCheck; +class CDBBan; + +typedef pair PairedAdminCount; +typedef pair PairedAdminAdd; +typedef pair PairedAdminRemove; +typedef pair PairedBanCount; +typedef pair PairedBanAdd; +typedef pair PairedBanRemove; +typedef pair PairedGPSCheck; +typedef pair PairedDPSCheck; + +class CBNET +{ +public: + CGHost *m_GHost; + +private: + CTCPClient *m_Socket; // the connection to battle.net + CBNETProtocol *m_Protocol; // battle.net protocol + CBNLSClient *m_BNLSClient; // the BNLS client (for external warden handling) + queue m_Packets; // queue of incoming packets + CBNCSUtilInterface *m_BNCSUtil; // the interface to the bncsutil library (used for logging into battle.net) + queue m_OutPackets; // queue of outgoing packets to be sent (to prevent getting kicked for flooding) + vector m_Friends; // vector of friends + vector m_Clans; // vector of clan members + vector m_PairedAdminCounts; // vector of paired threaded database admin counts in progress + vector m_PairedAdminAdds; // vector of paired threaded database admin adds in progress + vector m_PairedAdminRemoves; // vector of paired threaded database admin removes in progress + vector m_PairedBanCounts; // vector of paired threaded database ban counts in progress + vector m_PairedBanAdds; // vector of paired threaded database ban adds in progress + vector m_PairedBanRemoves; // vector of paired threaded database ban removes in progress + vector m_PairedGPSChecks; // vector of paired threaded database game player summary checks in progress + vector m_PairedDPSChecks; // vector of paired threaded database DotA player summary checks in progress + CCallableAdminList *m_CallableAdminList; // threaded database admin list in progress + CCallableBanList *m_CallableBanList; // threaded database ban list in progress + vector m_Admins; // vector of cached admins + vector m_Bans; // vector of cached bans + bool m_Exiting; // set to true and this class will be deleted next update + string m_Server; // battle.net server to connect to + string m_ServerIP; // battle.net server to connect to (the IP address so we don't have to resolve it every time we connect) + string m_ServerAlias; // battle.net server alias (short name, e.g. "USEast") + string m_BNLSServer; // BNLS server to connect to (for warden handling) + uint16_t m_BNLSPort; // BNLS port + uint32_t m_BNLSWardenCookie; // BNLS warden cookie + string m_CDKeyROC; // ROC CD key + string m_CDKeyTFT; // TFT CD key + string m_CountryAbbrev; // country abbreviation + string m_Country; // country + uint32_t m_LocaleID; // see: http://msdn.microsoft.com/en-us/library/0h88fahh%28VS.85%29.aspx + string m_UserName; // battle.net username + string m_UserPassword; // battle.net password + string m_FirstChannel; // the first chat channel to join upon entering chat (note: we hijack this to store the last channel when entering a game) + string m_CurrentChannel; // the current chat channel + string m_RootAdmin; // the root admin + char m_CommandTrigger; // the character prefix to identify commands + unsigned char m_War3Version; // custom warcraft 3 version for PvPGN users + BYTEARRAY m_EXEVersion; // custom exe version for PvPGN users + BYTEARRAY m_EXEVersionHash; // custom exe version hash for PvPGN users + string m_PasswordHashType; // password hash type for PvPGN users + string m_PVPGNRealmName; // realm name for PvPGN users (for mutual friend spoofchecks) + uint32_t m_MaxMessageLength; // maximum message length for PvPGN users + uint32_t m_HostCounterID; // the host counter ID to identify players from this realm + uint32_t m_LastDisconnectedTime; // GetTime when we were last disconnected from battle.net + uint32_t m_LastConnectionAttemptTime; // GetTime when we last attempted to connect to battle.net + uint32_t m_LastNullTime; // GetTime when the last null packet was sent for detecting disconnects + uint32_t m_LastOutPacketTicks; // GetTicks when the last packet was sent for the m_OutPackets queue + uint32_t m_LastOutPacketSize; + uint32_t m_LastAdminRefreshTime; // GetTime when the admin list was last refreshed from the database + uint32_t m_LastBanRefreshTime; // GetTime when the ban list was last refreshed from the database + bool m_FirstConnect; // if we haven't tried to connect to battle.net yet + bool m_WaitingToConnect; // if we're waiting to reconnect to battle.net after being disconnected + bool m_LoggedIn; // if we've logged into battle.net or not + bool m_InChat; // if we've entered chat or not (but we're not necessarily in a chat channel yet) + bool m_HoldFriends; // whether to auto hold friends when creating a game or not + bool m_HoldClan; // whether to auto hold clan members when creating a game or not + bool m_PublicCommands; // whether to allow public commands or not + + string m_ReplyTarget; + +public: + CBNET( CGHost *nGHost, string nServer, string nServerAlias, string nBNLSServer, uint16_t nBNLSPort, uint32_t nBNLSWardenCookie, string nCDKeyROC, string nCDKeyTFT, string nCountryAbbrev, string nCountry, uint32_t nLocaleID, string nUserName, string nUserPassword, string nFirstChannel, string nRootAdmin, char nCommandTrigger, bool nHoldFriends, bool nHoldClan, bool nPublicCommands, unsigned char nWar3Version, BYTEARRAY nEXEVersion, BYTEARRAY nEXEVersionHash, string nPasswordHashType, string nPVPGNRealmName, uint32_t nMaxMessageLength, uint32_t nHostCounterID ); + ~CBNET( ); + + bool GetExiting( ) { return m_Exiting; } + string GetServer( ) { return m_Server; } + string GetServerAlias( ) { return m_ServerAlias; } + string GetCDKeyROC( ) { return m_CDKeyROC; } + string GetCDKeyTFT( ) { return m_CDKeyTFT; } + string GetUserName( ) { return m_UserName; } + string GetUserPassword( ) { return m_UserPassword; } + string GetFirstChannel( ) { return m_FirstChannel; } + string GetCurrentChannel( ) { return m_CurrentChannel; } + string GetRootAdmin( ) { return m_RootAdmin; } + char GetCommandTrigger( ) { return m_CommandTrigger; } + BYTEARRAY GetEXEVersion( ) { return m_EXEVersion; } + BYTEARRAY GetEXEVersionHash( ) { return m_EXEVersionHash; } + string GetPasswordHashType( ) { return m_PasswordHashType; } + string GetPVPGNRealmName( ) { return m_PVPGNRealmName; } + uint32_t GetHostCounterID( ) { return m_HostCounterID; } + bool GetLoggedIn( ) { return m_LoggedIn; } + bool GetInChat( ) { return m_InChat; } + bool GetHoldFriends( ) { return m_HoldFriends; } + bool GetHoldClan( ) { return m_HoldClan; } + bool GetPublicCommands( ) { return m_PublicCommands; } + uint32_t GetOutPacketsQueued( ) { return m_OutPackets.size( ); } + BYTEARRAY GetUniqueName( ); + + vector > GetFriends( ); + vector > GetClan( ); + vector > GetBans( ); + vector > GetAdmins( ); + string GetReplyTarget( ) { return m_ReplyTarget; } + uint32_t GetRealmId( ) { return m_HostCounterID - 1; } + + // processing functions + + unsigned int SetFD( void *fd, void *send_fd, int *nfds ); + bool Update( void *fd, void *send_fd ); + void ExtractPackets( ); + void ProcessPackets( ); + void ProcessChatEvent( CIncomingChatEvent *chatEvent ); + + // functions to send packets to battle.net + + void SendJoinChannel( string channel ); + void SendGetFriendsList( ); + void SendGetClanList( ); + void QueueEnterChat( ); + void QueueChatCommand( string chatCommand, bool hidden = false ); + void QueueChatCommand( string chatCommand, string user, bool whisper ); + void QueueGameCreate( unsigned char state, string gameName, string hostName, CMap *map, CSaveGame *saveGame, uint32_t hostCounter ); + void QueueGameRefresh( unsigned char state, string gameName, string hostName, CMap *map, CSaveGame *saveGame, uint32_t upTime, uint32_t hostCounter ); + void QueueGameUncreate( ); + + void UnqueuePackets( unsigned char type ); + void UnqueueChatCommand( string chatCommand ); + void UnqueueGameRefreshes( ); + + void RequestListUpdates( ); + + // other functions + + bool IsAdmin( string name ); + bool IsRootAdmin( string name ); + CDBBan *IsBannedName( string name ); + CDBBan *IsBannedIP( string ip ); + void AddAdmin( string name ); + void AddBan( string name, string ip, string gamename, string admin, string reason ); + void RemoveAdmin( string name ); + void RemoveBan( string name ); + void HoldFriends( CBaseGame *game ); + void HoldClan( CBaseGame *game ); + + void HiddenGhostCommand( string Message ); +}; + +#endif diff --git a/ghost-legacy/bnetprotocol.cpp b/ghost-legacy/bnetprotocol.cpp new file mode 100644 index 0000000..eeadfae --- /dev/null +++ b/ghost-legacy/bnetprotocol.cpp @@ -0,0 +1,1147 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "bnetprotocol.h" + +CBNETProtocol :: CBNETProtocol( ) +{ + unsigned char ClientToken[] = { 220, 1, 203, 7 }; + m_ClientToken = UTIL_CreateByteArray( ClientToken, 4 ); +} + +CBNETProtocol :: ~CBNETProtocol( ) +{ + +} + +/////////////////////// +// RECEIVE FUNCTIONS // +/////////////////////// + +bool CBNETProtocol :: RECEIVE_SID_NULL( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_NULL" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + + return ValidateLength( data ); +} + +CIncomingGameHost *CBNETProtocol :: RECEIVE_SID_GETADVLISTEX( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_GETADVLISTEX" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> GamesFound + // if( GamesFound > 0 ) + // 10 bytes -> ??? + // 2 bytes -> Port + // 4 bytes -> IP + // null term string -> GameName + // 2 bytes -> ??? + // 8 bytes -> HostCounter + + if( ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY GamesFound = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + if( UTIL_ByteArrayToUInt32( GamesFound, false ) > 0 && data.size( ) >= 25 ) + { + BYTEARRAY Port = BYTEARRAY( data.begin( ) + 18, data.begin( ) + 20 ); + BYTEARRAY IP = BYTEARRAY( data.begin( ) + 20, data.begin( ) + 24 ); + BYTEARRAY GameName = UTIL_ExtractCString( data, 24 ); + + if( data.size( ) >= GameName.size( ) + 35 ) + { + BYTEARRAY HostCounter; + HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 27, true ) ); + HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 29, true ) ); + HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 31, true ) ); + HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 33, true ) ); + return new CIncomingGameHost( IP, + UTIL_ByteArrayToUInt16( Port, false ), + string( GameName.begin( ), GameName.end( ) ), + HostCounter ); + } + } + } + + return NULL; +} + +bool CBNETProtocol :: RECEIVE_SID_ENTERCHAT( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_ENTERCHAT" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // null terminated string -> UniqueName + + if( ValidateLength( data ) && data.size( ) >= 5 ) + { + m_UniqueName = UTIL_ExtractCString( data, 4 ); + return true; + } + + return false; +} + +CIncomingChatEvent *CBNETProtocol :: RECEIVE_SID_CHATEVENT( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_CHATEVENT" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> EventID + // 4 bytes -> UserFlags + // 4 bytes -> Ping + // 12 bytes -> ??? + // null terminated string -> User + // null terminated string -> Message + + if( ValidateLength( data ) && data.size( ) >= 29 ) + { + BYTEARRAY EventID = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + BYTEARRAY UserFlags = BYTEARRAY( data.begin( ) + 8, data.begin( ) + 12 ); + BYTEARRAY Ping = BYTEARRAY( data.begin( ) + 12, data.begin( ) + 16 ); + BYTEARRAY User = UTIL_ExtractCString( data, 28 ); + BYTEARRAY Message = UTIL_ExtractCString( data, User.size( ) + 29 ); + + switch( UTIL_ByteArrayToUInt32( EventID, false ) ) + { + case CBNETProtocol :: EID_SHOWUSER: + case CBNETProtocol :: EID_JOIN: + case CBNETProtocol :: EID_LEAVE: + case CBNETProtocol :: EID_WHISPER: + case CBNETProtocol :: EID_TALK: + case CBNETProtocol :: EID_BROADCAST: + case CBNETProtocol :: EID_CHANNEL: + case CBNETProtocol :: EID_USERFLAGS: + case CBNETProtocol :: EID_WHISPERSENT: + case CBNETProtocol :: EID_CHANNELFULL: + case CBNETProtocol :: EID_CHANNELDOESNOTEXIST: + case CBNETProtocol :: EID_CHANNELRESTRICTED: + case CBNETProtocol :: EID_INFO: + case CBNETProtocol :: EID_ERROR: + case CBNETProtocol :: EID_EMOTE: + return new CIncomingChatEvent( (CBNETProtocol :: IncomingChatEvent)UTIL_ByteArrayToUInt32( EventID, false ), + UTIL_ByteArrayToUInt32( UserFlags, false ), + UTIL_ByteArrayToUInt32( Ping, false ), + string( User.begin( ), User.end( ) ), + string( Message.begin( ), Message.end( ) ) ); + } + + } + + return NULL; +} + +bool CBNETProtocol :: RECEIVE_SID_CHECKAD( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_CHECKAD" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + + return ValidateLength( data ); +} + +bool CBNETProtocol :: RECEIVE_SID_STARTADVEX3( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_STARTADVEX3" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Status + + if( ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY Status = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + if( UTIL_ByteArrayToUInt32( Status, false ) == 0 ) + return true; + } + + return false; +} + +BYTEARRAY CBNETProtocol :: RECEIVE_SID_PING( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_PING" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Ping + + if( ValidateLength( data ) && data.size( ) >= 8 ) + return BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + return BYTEARRAY( ); +} + +bool CBNETProtocol :: RECEIVE_SID_LOGONRESPONSE( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_LOGONRESPONSE" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Status + + if( ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY Status = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + if( UTIL_ByteArrayToUInt32( Status, false ) == 1 ) + return true; + } + + return false; +} + +bool CBNETProtocol :: RECEIVE_SID_AUTH_INFO( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_AUTH_INFO" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> LogonType + // 4 bytes -> ServerToken + // 4 bytes -> ??? + // 8 bytes -> MPQFileTime + // null terminated string -> IX86VerFileName + // null terminated string -> ValueStringFormula + + if( ValidateLength( data ) && data.size( ) >= 25 ) + { + m_LogonType = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + m_ServerToken = BYTEARRAY( data.begin( ) + 8, data.begin( ) + 12 ); + m_MPQFileTime = BYTEARRAY( data.begin( ) + 16, data.begin( ) + 24 ); + m_IX86VerFileName = UTIL_ExtractCString( data, 24 ); + m_ValueStringFormula = UTIL_ExtractCString( data, m_IX86VerFileName.size( ) + 25 ); + return true; + } + + return false; +} + +bool CBNETProtocol :: RECEIVE_SID_AUTH_CHECK( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_AUTH_CHECK" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> KeyState + // null terminated string -> KeyStateDescription + + if( ValidateLength( data ) && data.size( ) >= 9 ) + { + m_KeyState = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + m_KeyStateDescription = UTIL_ExtractCString( data, 8 ); + + if( UTIL_ByteArrayToUInt32( m_KeyState, false ) == KR_GOOD ) + return true; + } + + return false; +} + +bool CBNETProtocol :: RECEIVE_SID_AUTH_ACCOUNTLOGON( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_AUTH_ACCOUNTLOGON" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Status + // if( Status == 0 ) + // 32 bytes -> Salt + // 32 bytes -> ServerPublicKey + + if( ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY status = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + if( UTIL_ByteArrayToUInt32( status, false ) == 0 && data.size( ) >= 72 ) + { + m_Salt = BYTEARRAY( data.begin( ) + 8, data.begin( ) + 40 ); + m_ServerPublicKey = BYTEARRAY( data.begin( ) + 40, data.begin( ) + 72 ); + return true; + } + } + + return false; +} + +bool CBNETProtocol :: RECEIVE_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_AUTH_ACCOUNTLOGONPROOF" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Status + + if( ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY Status = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + + if( UTIL_ByteArrayToUInt32( Status, false ) == 0 ) + return true; + } + + return false; +} + +BYTEARRAY CBNETProtocol :: RECEIVE_SID_WARDEN( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_WARDEN" ); + // DEBUG_PRINT( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // n bytes -> Data + + if( ValidateLength( data ) && data.size( ) >= 4 ) + return BYTEARRAY( data.begin( ) + 4, data.end( ) ); + + return BYTEARRAY( ); +} + +vector CBNETProtocol :: RECEIVE_SID_FRIENDSLIST( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_FRIENDSLIST" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 1 byte -> Total + // for( 1 .. Total ) + // null term string -> Account + // 1 byte -> Status + // 1 byte -> Area + // 4 bytes -> ??? + // null term string -> Location + + vector Friends; + + if( ValidateLength( data ) && data.size( ) >= 5 ) + { + unsigned int i = 5; + unsigned char Total = data[4]; + + while( Total > 0 ) + { + Total--; + + if( data.size( ) < i + 1 ) + break; + + BYTEARRAY Account = UTIL_ExtractCString( data, i ); + i += Account.size( ) + 1; + + if( data.size( ) < i + 7 ) + break; + + unsigned char Status = data[i]; + unsigned char Area = data[i + 1]; + i += 6; + BYTEARRAY Location = UTIL_ExtractCString( data, i ); + i += Location.size( ) + 1; + Friends.push_back( new CIncomingFriendList( string( Account.begin( ), Account.end( ) ), + Status, + Area, + string( Location.begin( ), Location.end( ) ) ) ); + } + } + + return Friends; +} + +vector CBNETProtocol :: RECEIVE_SID_CLANMEMBERLIST( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_CLANMEMBERLIST" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> ??? + // 1 byte -> Total + // for( 1 .. Total ) + // null term string -> Name + // 1 byte -> Rank + // 1 byte -> Status + // null term string -> Location + + vector ClanList; + + if( ValidateLength( data ) && data.size( ) >= 9 ) + { + unsigned int i = 9; + unsigned char Total = data[8]; + + while( Total > 0 ) + { + Total--; + + if( data.size( ) < i + 1 ) + break; + + BYTEARRAY Name = UTIL_ExtractCString( data, i ); + i += Name.size( ) + 1; + + if( data.size( ) < i + 3 ) + break; + + unsigned char Rank = data[i]; + unsigned char Status = data[i + 1]; + i += 2; + + // in the original VB source the location string is read but discarded, so that's what I do here + + BYTEARRAY Location = UTIL_ExtractCString( data, i ); + i += Location.size( ) + 1; + ClanList.push_back( new CIncomingClanList( string( Name.begin( ), Name.end( ) ), + Rank, + Status ) ); + } + } + + return ClanList; +} + +CIncomingClanList *CBNETProtocol :: RECEIVE_SID_CLANMEMBERSTATUSCHANGE( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED SID_CLANMEMBERSTATUSCHANGE" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // null terminated string -> Name + // 1 byte -> Rank + // 1 byte -> Status + // null terminated string -> Location + + if( ValidateLength( data ) && data.size( ) >= 5 ) + { + BYTEARRAY Name = UTIL_ExtractCString( data, 4 ); + + if( data.size( ) >= Name.size( ) + 7 ) + { + unsigned char Rank = data[Name.size( ) + 5]; + unsigned char Status = data[Name.size( ) + 6]; + + // in the original VB source the location string is read but discarded, so that's what I do here + + BYTEARRAY Location = UTIL_ExtractCString( data, Name.size( ) + 7 ); + return new CIncomingClanList( string( Name.begin( ), Name.end( ) ), + Rank, + Status ); + } + } + + return NULL; +} + +//////////////////// +// SEND FUNCTIONS // +//////////////////// + +BYTEARRAY CBNETProtocol :: SEND_PROTOCOL_INITIALIZE_SELECTOR( ) +{ + BYTEARRAY packet; + packet.push_back( 1 ); + // DEBUG_Print( "SENT PROTOCOL_INITIALIZE_SELECTOR" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_NULL( ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_NULL ); // SID_NULL + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + AssignLength( packet ); + // DEBUG_Print( "SENT SID_NULL" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_STOPADV( ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_STOPADV ); // SID_STOPADV + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + AssignLength( packet ); + // DEBUG_Print( "SENT SID_STOPADV" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_GETADVLISTEX( string gameName ) +{ + unsigned char MapFilter1[] = { 255, 3, 0, 0 }; + unsigned char MapFilter2[] = { 255, 3, 0, 0 }; + unsigned char MapFilter3[] = { 0, 0, 0, 0 }; + unsigned char NumGames[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_GETADVLISTEX ); // SID_GETADVLISTEX + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, MapFilter1, 4 ); // Map Filter + UTIL_AppendByteArray( packet, MapFilter2, 4 ); // Map Filter + UTIL_AppendByteArray( packet, MapFilter3, 4 ); // Map Filter + UTIL_AppendByteArray( packet, NumGames, 4 ); // maximum number of games to list + UTIL_AppendByteArrayFast( packet, gameName ); // Game Name + packet.push_back( 0 ); // Game Password is NULL + packet.push_back( 0 ); // Game Stats is NULL + AssignLength( packet ); + // DEBUG_Print( "SENT SID_GETADVLISTEX" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_ENTERCHAT( ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_ENTERCHAT ); // SID_ENTERCHAT + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // Account Name is NULL on Warcraft III/The Frozen Throne + packet.push_back( 0 ); // Stat String is NULL on CDKEY'd products + AssignLength( packet ); + // DEBUG_Print( "SENT SID_ENTERCHAT" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_JOINCHANNEL( string channel ) +{ + unsigned char NoCreateJoin[] = { 2, 0, 0, 0 }; + unsigned char FirstJoin[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_JOINCHANNEL ); // SID_JOINCHANNEL + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + + if( channel.size( ) > 0 ) + UTIL_AppendByteArray( packet, NoCreateJoin, 4 ); // flags for no create join + else + UTIL_AppendByteArray( packet, FirstJoin, 4 ); // flags for first join + + UTIL_AppendByteArrayFast( packet, channel ); + AssignLength( packet ); + // DEBUG_Print( "SENT SID_JOINCHANNEL" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_CHATCOMMAND( string command ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_CHATCOMMAND ); // SID_CHATCOMMAND + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, command ); // Message + AssignLength( packet ); + // DEBUG_Print( "SENT SID_CHATCOMMAND" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_CHECKAD( ) +{ + unsigned char Zeros[] = { 0, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_CHECKAD ); // SID_CHECKAD + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + AssignLength( packet ); + // DEBUG_Print( "SENT SID_CHECKAD" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_STARTADVEX3( unsigned char state, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, BYTEARRAY mapSHA1, uint32_t hostCounter ) +{ + // todotodo: sort out how GameType works, the documentation is horrendous + +/* + +Game type tag: (read W3GS_GAMEINFO for this field) + 0x00000001 - Custom + 0x00000009 - Blizzard/Ladder +Map author: (mask 0x00006000) can be combined +*0x00002000 - Blizzard + 0x00004000 - Custom +Battle type: (mask 0x00018000) cant be combined + 0x00000000 - Battle +*0x00010000 - Scenario +Map size: (mask 0x000E0000) can be combined with 2 nearest values + 0x00020000 - Small + 0x00040000 - Medium +*0x00080000 - Huge +Observers: (mask 0x00700000) cant be combined + 0x00100000 - Allowed observers + 0x00200000 - Observers on defeat +*0x00400000 - No observers +Flags: + 0x00000800 - Private game flag (not used in game list) + +*/ + + unsigned char Unknown[] = { 255, 3, 0, 0 }; + unsigned char CustomGame[] = { 0, 0, 0, 0 }; + + string HostCounterString = UTIL_ToHexString( hostCounter ); + + if( HostCounterString.size( ) < 8 ) + HostCounterString.insert( 0, 8 - HostCounterString.size( ), '0' ); + + HostCounterString = string( HostCounterString.rbegin( ), HostCounterString.rend( ) ); + + BYTEARRAY packet; + + // make the stat string + + BYTEARRAY StatString; + UTIL_AppendByteArrayFast( StatString, mapFlags ); + StatString.push_back( 0 ); + UTIL_AppendByteArrayFast( StatString, mapWidth ); + UTIL_AppendByteArrayFast( StatString, mapHeight ); + UTIL_AppendByteArrayFast( StatString, mapCRC ); + UTIL_AppendByteArrayFast( StatString, mapPath ); + UTIL_AppendByteArrayFast( StatString, hostName ); + StatString.push_back( 0 ); + UTIL_AppendByteArrayFast( StatString, mapSHA1 ); + StatString = UTIL_EncodeStatString( StatString ); + + if( mapGameType.size( ) == 4 && mapFlags.size( ) == 4 && mapWidth.size( ) == 2 && mapHeight.size( ) == 2 && !gameName.empty( ) && !hostName.empty( ) && !mapPath.empty( ) && mapCRC.size( ) == 4 && mapSHA1.size( ) == 20 && StatString.size( ) < 128 && HostCounterString.size( ) == 8 ) + { + // make the rest of the packet + + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_STARTADVEX3 ); // SID_STARTADVEX3 + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( state ); // State (16 = public, 17 = private, 18 = close) + packet.push_back( 0 ); // State continued... + packet.push_back( 0 ); // State continued... + packet.push_back( 0 ); // State continued... + UTIL_AppendByteArray( packet, upTime, false ); // time since creation + UTIL_AppendByteArrayFast( packet, mapGameType ); // Game Type, Parameter + UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? + UTIL_AppendByteArray( packet, CustomGame, 4 ); // Custom Game + UTIL_AppendByteArrayFast( packet, gameName ); // Game Name + packet.push_back( 0 ); // Game Password is NULL + packet.push_back( 98 ); // Slots Free (ascii 98 = char 'b' = 11 slots free) - note: do not reduce this as this is the # of PID's Warcraft III will allocate + UTIL_AppendByteArrayFast( packet, HostCounterString, false ); // Host Counter + UTIL_AppendByteArrayFast( packet, StatString ); // Stat String + packet.push_back( 0 ); // Stat String null terminator (the stat string is encoded to remove all even numbers i.e. zeros) + AssignLength( packet ); + } + else + CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_STARTADVEX3" ); + + // DEBUG_Print( "SENT SID_STARTADVEX3" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_NOTIFYJOIN( string gameName ) +{ + unsigned char ProductID[] = { 0, 0, 0, 0 }; + unsigned char ProductVersion[] = { 14, 0, 0, 0 }; // Warcraft III is 14 + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_NOTIFYJOIN ); // SID_NOTIFYJOIN + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, ProductID, 4 ); // Product ID + UTIL_AppendByteArray( packet, ProductVersion, 4 ); // Product Version + UTIL_AppendByteArrayFast( packet, gameName ); // Game Name + packet.push_back( 0 ); // Game Password is NULL + AssignLength( packet ); + // DEBUG_Print( "SENT SID_NOTIFYJOIN" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_PING( BYTEARRAY pingValue ) +{ + BYTEARRAY packet; + + if( pingValue.size( ) == 4 ) + { + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_PING ); // SID_PING + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, pingValue ); // Ping Value + AssignLength( packet ); + } + else + CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_PING" ); + + // DEBUG_Print( "SENT SID_PING" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_LOGONRESPONSE( BYTEARRAY clientToken, BYTEARRAY serverToken, BYTEARRAY passwordHash, string accountName ) +{ + // todotodo: check that the passed BYTEARRAY sizes are correct (don't know what they should be right now so I can't do this today) + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_LOGONRESPONSE ); // SID_LOGONRESPONSE + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, clientToken ); // Client Token + UTIL_AppendByteArrayFast( packet, serverToken ); // Server Token + UTIL_AppendByteArrayFast( packet, passwordHash ); // Password Hash + UTIL_AppendByteArrayFast( packet, accountName ); // Account Name + AssignLength( packet ); + // DEBUG_Print( "SENT SID_LOGONRESPONSE" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_NETGAMEPORT( uint16_t serverPort ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_NETGAMEPORT ); // SID_NETGAMEPORT + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, serverPort, false ); // local game server port + AssignLength( packet ); + // DEBUG_Print( "SENT SID_NETGAMEPORT" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_INFO( unsigned char ver, bool TFT, uint32_t localeID, string countryAbbrev, string country ) +{ + unsigned char ProtocolID[] = { 0, 0, 0, 0 }; + unsigned char PlatformID[] = { 54, 56, 88, 73 }; // "IX86" + unsigned char ProductID_ROC[] = { 51, 82, 65, 87 }; // "WAR3" + unsigned char ProductID_TFT[] = { 80, 88, 51, 87 }; // "W3XP" + unsigned char Version[] = { ver, 0, 0, 0 }; + unsigned char Language[] = { 83, 85, 110, 101 }; // "enUS" + unsigned char LocalIP[] = { 127, 0, 0, 1 }; + unsigned char TimeZoneBias[] = { 44, 1, 0, 0 }; // 300 minutes (GMT -0500) + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_AUTH_INFO ); // SID_AUTH_INFO + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, ProtocolID, 4 ); // Protocol ID + UTIL_AppendByteArray( packet, PlatformID, 4 ); // Platform ID + + if( TFT ) + UTIL_AppendByteArray( packet, ProductID_TFT, 4 ); // Product ID (TFT) + else + UTIL_AppendByteArray( packet, ProductID_ROC, 4 ); // Product ID (ROC) + + UTIL_AppendByteArray( packet, Version, 4 ); // Version + UTIL_AppendByteArray( packet, Language, 4 ); // Language (hardcoded as enUS to ensure battle.net sends the bot messages in English) + UTIL_AppendByteArray( packet, LocalIP, 4 ); // Local IP for NAT compatibility + UTIL_AppendByteArray( packet, TimeZoneBias, 4 ); // Time Zone Bias + UTIL_AppendByteArray( packet, localeID, false ); // Locale ID + UTIL_AppendByteArray( packet, localeID, false ); // Language ID (copying the locale ID should be sufficient since we don't care about sublanguages) + UTIL_AppendByteArrayFast( packet, countryAbbrev ); // Country Abbreviation + UTIL_AppendByteArrayFast( packet, country ); // Country + AssignLength( packet ); + // DEBUG_Print( "SENT SID_AUTH_INFO" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_CHECK( bool TFT, BYTEARRAY clientToken, BYTEARRAY exeVersion, BYTEARRAY exeVersionHash, BYTEARRAY keyInfoROC, BYTEARRAY keyInfoTFT, string exeInfo, string keyOwnerName ) +{ + uint32_t NumKeys = 0; + + if( TFT ) + NumKeys = 2; + else + NumKeys = 1; + + BYTEARRAY packet; + + if( clientToken.size( ) == 4 && exeVersion.size( ) == 4 && exeVersionHash.size( ) == 4 && keyInfoROC.size( ) == 36 && ( !TFT || keyInfoTFT.size( ) == 36 ) ) + { + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_AUTH_CHECK ); // SID_AUTH_CHECK + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, clientToken ); // Client Token + UTIL_AppendByteArrayFast( packet, exeVersion ); // EXE Version + UTIL_AppendByteArrayFast( packet, exeVersionHash ); // EXE Version Hash + UTIL_AppendByteArray( packet, NumKeys, false ); // number of keys in this packet + UTIL_AppendByteArray( packet, (uint32_t)0, false ); // boolean Using Spawn (32 bit) + UTIL_AppendByteArrayFast( packet, keyInfoROC ); // ROC Key Info + + if( TFT ) + UTIL_AppendByteArrayFast( packet, keyInfoTFT ); // TFT Key Info + + UTIL_AppendByteArrayFast( packet, exeInfo ); // EXE Info + UTIL_AppendByteArrayFast( packet, keyOwnerName ); // CD Key Owner Name + AssignLength( packet ); + } + else + CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_CHECK" ); + + // DEBUG_Print( "SENT SID_AUTH_CHECK" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGON( BYTEARRAY clientPublicKey, string accountName ) +{ + BYTEARRAY packet; + + if( clientPublicKey.size( ) == 32 ) + { + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_AUTH_ACCOUNTLOGON ); // SID_AUTH_ACCOUNTLOGON + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, clientPublicKey ); // Client Key + UTIL_AppendByteArrayFast( packet, accountName ); // Account Name + AssignLength( packet ); + } + else + CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON" ); + + // DEBUG_Print( "SENT SID_AUTH_ACCOUNTLOGON" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY clientPasswordProof ) +{ + BYTEARRAY packet; + + if( clientPasswordProof.size( ) == 20 ) + { + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_AUTH_ACCOUNTLOGONPROOF ); // SID_AUTH_ACCOUNTLOGONPROOF + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, clientPasswordProof ); // Client Password Proof + AssignLength( packet ); + } + else + CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON" ); + + // DEBUG_Print( "SENT SID_AUTH_ACCOUNTLOGONPROOF" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_WARDEN( BYTEARRAY wardenResponse ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_WARDEN ); // SID_WARDEN + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArrayFast( packet, wardenResponse ); // warden response + AssignLength( packet ); + // DEBUG_Print( "SENT SID_WARDEN" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_FRIENDSLIST( ) +{ + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_FRIENDSLIST ); // SID_FRIENDSLIST + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + AssignLength( packet ); + // DEBUG_Print( "SENT SID_FRIENDSLIST" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CBNETProtocol :: SEND_SID_CLANMEMBERLIST( ) +{ + unsigned char Cookie[] = { 0, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant + packet.push_back( SID_CLANMEMBERLIST ); // SID_CLANMEMBERLIST + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, Cookie, 4 ); // cookie + AssignLength( packet ); + // DEBUG_Print( "SENT SID_CLANMEMBERLIST" ); + // DEBUG_Print( packet ); + return packet; +} + +///////////////////// +// OTHER FUNCTIONS // +///////////////////// + +bool CBNETProtocol :: AssignLength( BYTEARRAY &content ) +{ + // insert the actual length of the content array into bytes 3 and 4 (indices 2 and 3) + + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes = UTIL_CreateByteArray( (uint16_t)content.size( ), false ); + content[2] = LengthBytes[0]; + content[3] = LengthBytes[1]; + return true; + } + + return false; +} + +bool CBNETProtocol :: ValidateLength( BYTEARRAY &content ) +{ + // verify that bytes 3 and 4 (indices 2 and 3) of the content array describe the length + + uint16_t Length; + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes.push_back( content[2] ); + LengthBytes.push_back( content[3] ); + Length = UTIL_ByteArrayToUInt16( LengthBytes, false ); + + if( Length == content.size( ) ) + return true; + } + + return false; +} + +// +// CIncomingGameHost +// + +CIncomingGameHost :: CIncomingGameHost( BYTEARRAY &nIP, uint16_t nPort, string nGameName, BYTEARRAY &nHostCounter ) +{ + m_IP = nIP; + m_Port = nPort; + m_GameName = nGameName; + m_HostCounter = nHostCounter; +} + +CIncomingGameHost :: ~CIncomingGameHost( ) +{ + +} + +string CIncomingGameHost :: GetIPString( ) +{ + string Result; + + if( m_IP.size( ) >= 4 ) + { + for( unsigned int i = 0; i < 4; i++ ) + { + Result += UTIL_ToString( (unsigned int)m_IP[i] ); + + if( i < 3 ) + Result += "."; + } + } + + return Result; +} + +// +// CIncomingChatEvent +// + +CIncomingChatEvent :: CIncomingChatEvent( CBNETProtocol :: IncomingChatEvent nChatEvent, uint32_t nUserFlags, uint32_t nPing, string nUser, string nMessage ) +{ + m_ChatEvent = nChatEvent; + m_UserFlags = nUserFlags; + m_Ping = nPing; + m_User = nUser; + m_Message = nMessage; +} + +CIncomingChatEvent :: ~CIncomingChatEvent( ) +{ + +} + +// +// CIncomingFriendList +// + +CIncomingFriendList :: CIncomingFriendList( string nAccount, unsigned char nStatus, unsigned char nArea, string nLocation ) +{ + m_Account = nAccount; + m_Status = nStatus; + m_Area = nArea; + m_Location = nLocation; +} + +CIncomingFriendList :: ~CIncomingFriendList( ) +{ + +} + +string CIncomingFriendList :: GetDescription( ) +{ + string Description; + Description += GetAccount( ) + "\n"; + Description += ExtractStatus( GetStatus( ) ) + "\n"; + Description += ExtractArea( GetArea( ) ) + "\n"; + Description += ExtractLocation( GetLocation( ) ) + "\n\n"; + return Description; +} + +string CIncomingFriendList :: ExtractStatus( unsigned char status ) +{ + string Result; + + if( status & 1 ) + Result += ""; + + if( status & 2 ) + Result += ""; + + if( status & 4 ) + Result += ""; + + if( Result.empty( ) ) + Result = ""; + + return Result; +} + +string CIncomingFriendList :: ExtractArea( unsigned char area ) +{ + switch( area ) + { + case 0: return ""; + case 1: return ""; + case 2: return ""; + case 3: return ""; + case 4: return ""; + case 5: return ""; + } + + return ""; +} + +string CIncomingFriendList :: ExtractLocation( string location ) +{ + string Result; + + if( location.substr( 0, 4 ) == "PX3W" ) + Result = location.substr( 4 ); + + if( Result.empty( ) ) + Result = "."; + + return Result; +} + +// +// CIncomingClanList +// + +CIncomingClanList :: CIncomingClanList( string nName, unsigned char nRank, unsigned char nStatus ) +{ + m_Name = nName; + m_Rank = nRank; + m_Status = nStatus; +} + +CIncomingClanList :: ~CIncomingClanList( ) +{ + +} + +string CIncomingClanList :: GetRank( ) +{ + switch( m_Rank ) + { + case 0: return "Recruit"; + case 1: return "Peon"; + case 2: return "Grunt"; + case 3: return "Shaman"; + case 4: return "Chieftain"; + } + + return "Rank Unknown"; +} + +string CIncomingClanList :: GetStatus( ) +{ + if( m_Status == 0 ) + return "Offline"; + else + return "Online"; +} + +string CIncomingClanList :: GetDescription( ) +{ + string Description; + Description += GetName( ) + "\n"; + Description += GetStatus( ) + "\n"; + Description += GetRank( ) + "\n\n"; + return Description; +} diff --git a/ghost-legacy/bnetprotocol.h b/ghost-legacy/bnetprotocol.h new file mode 100644 index 0000000..92e9078 --- /dev/null +++ b/ghost-legacy/bnetprotocol.h @@ -0,0 +1,268 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef BNETPROTOCOL_H +#define BNETPROTOCOL_H + +// +// CBNETProtocol +// + +#define BNET_HEADER_CONSTANT 255 + +class CIncomingGameHost; +class CIncomingChatEvent; +class CIncomingFriendList; +class CIncomingClanList; + +class CBNETProtocol +{ +public: + enum Protocol { + SID_NULL = 0, // 0x0 + SID_STOPADV = 2, // 0x2 + SID_GETADVLISTEX = 9, // 0x9 + SID_ENTERCHAT = 10, // 0xA + SID_JOINCHANNEL = 12, // 0xC + SID_CHATCOMMAND = 14, // 0xE + SID_CHATEVENT = 15, // 0xF + SID_CHECKAD = 21, // 0x15 + SID_STARTADVEX3 = 28, // 0x1C + SID_DISPLAYAD = 33, // 0x21 + SID_NOTIFYJOIN = 34, // 0x22 + SID_PING = 37, // 0x25 + SID_LOGONRESPONSE = 41, // 0x29 + SID_NETGAMEPORT = 69, // 0x45 + SID_AUTH_INFO = 80, // 0x50 + SID_AUTH_CHECK = 81, // 0x51 + SID_AUTH_ACCOUNTLOGON = 83, // 0x53 + SID_AUTH_ACCOUNTLOGONPROOF = 84, // 0x54 + SID_WARDEN = 94, // 0x5E + SID_FRIENDSLIST = 101, // 0x65 + SID_FRIENDSUPDATE = 102, // 0x66 + SID_CLANMEMBERLIST = 125, // 0x7D + SID_CLANMEMBERSTATUSCHANGE = 127 // 0x7F + }; + + enum KeyResult { + KR_GOOD = 0, + KR_OLD_GAME_VERSION = 256, + KR_INVALID_VERSION = 257, + KR_ROC_KEY_IN_USE = 513, + KR_TFT_KEY_IN_USE = 529 + }; + + enum IncomingChatEvent { + EID_SHOWUSER = 1, // received when you join a channel (includes users in the channel and their information) + EID_JOIN = 2, // received when someone joins the channel you're currently in + EID_LEAVE = 3, // received when someone leaves the channel you're currently in + EID_WHISPER = 4, // received a whisper message + EID_TALK = 5, // received when someone talks in the channel you're currently in + EID_BROADCAST = 6, // server broadcast + EID_CHANNEL = 7, // received when you join a channel (includes the channel's name, flags) + EID_USERFLAGS = 9, // user flags updates + EID_WHISPERSENT = 10, // sent a whisper message + EID_CHANNELFULL = 13, // channel is full + EID_CHANNELDOESNOTEXIST = 14, // channel does not exist + EID_CHANNELRESTRICTED = 15, // channel is restricted + EID_INFO = 18, // broadcast/information message + EID_ERROR = 19, // error message + EID_EMOTE = 23 // emote + }; + +private: + BYTEARRAY m_ClientToken; // set in constructor + BYTEARRAY m_LogonType; // set in RECEIVE_SID_AUTH_INFO + BYTEARRAY m_ServerToken; // set in RECEIVE_SID_AUTH_INFO + BYTEARRAY m_MPQFileTime; // set in RECEIVE_SID_AUTH_INFO + BYTEARRAY m_IX86VerFileName; // set in RECEIVE_SID_AUTH_INFO + BYTEARRAY m_ValueStringFormula; // set in RECEIVE_SID_AUTH_INFO + BYTEARRAY m_KeyState; // set in RECEIVE_SID_AUTH_CHECK + BYTEARRAY m_KeyStateDescription; // set in RECEIVE_SID_AUTH_CHECK + BYTEARRAY m_Salt; // set in RECEIVE_SID_AUTH_ACCOUNTLOGON + BYTEARRAY m_ServerPublicKey; // set in RECEIVE_SID_AUTH_ACCOUNTLOGON + BYTEARRAY m_UniqueName; // set in RECEIVE_SID_ENTERCHAT + +public: + CBNETProtocol( ); + ~CBNETProtocol( ); + + BYTEARRAY GetClientToken( ) { return m_ClientToken; } + BYTEARRAY GetLogonType( ) { return m_LogonType; } + BYTEARRAY GetServerToken( ) { return m_ServerToken; } + BYTEARRAY GetMPQFileTime( ) { return m_MPQFileTime; } + BYTEARRAY GetIX86VerFileName( ) { return m_IX86VerFileName; } + string GetIX86VerFileNameString( ) { return string( m_IX86VerFileName.begin( ), m_IX86VerFileName.end( ) ); } + BYTEARRAY GetValueStringFormula( ) { return m_ValueStringFormula; } + string GetValueStringFormulaString( ) { return string( m_ValueStringFormula.begin( ), m_ValueStringFormula.end( ) ); } + BYTEARRAY GetKeyState( ) { return m_KeyState; } + string GetKeyStateDescription( ) { return string( m_KeyStateDescription.begin( ), m_KeyStateDescription.end( ) ); } + BYTEARRAY GetSalt( ) { return m_Salt; } + BYTEARRAY GetServerPublicKey( ) { return m_ServerPublicKey; } + BYTEARRAY GetUniqueName( ) { return m_UniqueName; } + + // receive functions + + bool RECEIVE_SID_NULL( BYTEARRAY data ); + CIncomingGameHost *RECEIVE_SID_GETADVLISTEX( BYTEARRAY data ); + bool RECEIVE_SID_ENTERCHAT( BYTEARRAY data ); + CIncomingChatEvent *RECEIVE_SID_CHATEVENT( BYTEARRAY data ); + bool RECEIVE_SID_CHECKAD( BYTEARRAY data ); + bool RECEIVE_SID_STARTADVEX3( BYTEARRAY data ); + BYTEARRAY RECEIVE_SID_PING( BYTEARRAY data ); + bool RECEIVE_SID_LOGONRESPONSE( BYTEARRAY data ); + bool RECEIVE_SID_AUTH_INFO( BYTEARRAY data ); + bool RECEIVE_SID_AUTH_CHECK( BYTEARRAY data ); + bool RECEIVE_SID_AUTH_ACCOUNTLOGON( BYTEARRAY data ); + bool RECEIVE_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY data ); + BYTEARRAY RECEIVE_SID_WARDEN( BYTEARRAY data ); + vector RECEIVE_SID_FRIENDSLIST( BYTEARRAY data ); + vector RECEIVE_SID_CLANMEMBERLIST( BYTEARRAY data ); + CIncomingClanList *RECEIVE_SID_CLANMEMBERSTATUSCHANGE( BYTEARRAY data ); + + // send functions + + BYTEARRAY SEND_PROTOCOL_INITIALIZE_SELECTOR( ); + BYTEARRAY SEND_SID_NULL( ); + BYTEARRAY SEND_SID_STOPADV( ); + BYTEARRAY SEND_SID_GETADVLISTEX( string gameName ); + BYTEARRAY SEND_SID_ENTERCHAT( ); + BYTEARRAY SEND_SID_JOINCHANNEL( string channel ); + BYTEARRAY SEND_SID_CHATCOMMAND( string command ); + BYTEARRAY SEND_SID_CHECKAD( ); + BYTEARRAY SEND_SID_STARTADVEX3( unsigned char state, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, BYTEARRAY mapSHA1, uint32_t hostCounter ); + BYTEARRAY SEND_SID_NOTIFYJOIN( string gameName ); + BYTEARRAY SEND_SID_PING( BYTEARRAY pingValue ); + BYTEARRAY SEND_SID_LOGONRESPONSE( BYTEARRAY clientToken, BYTEARRAY serverToken, BYTEARRAY passwordHash, string accountName ); + BYTEARRAY SEND_SID_NETGAMEPORT( uint16_t serverPort ); + BYTEARRAY SEND_SID_AUTH_INFO( unsigned char ver, bool TFT, uint32_t localeID, string countryAbbrev, string country ); + BYTEARRAY SEND_SID_AUTH_CHECK( bool TFT, BYTEARRAY clientToken, BYTEARRAY exeVersion, BYTEARRAY exeVersionHash, BYTEARRAY keyInfoROC, BYTEARRAY keyInfoTFT, string exeInfo, string keyOwnerName ); + BYTEARRAY SEND_SID_AUTH_ACCOUNTLOGON( BYTEARRAY clientPublicKey, string accountName ); + BYTEARRAY SEND_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY clientPasswordProof ); + BYTEARRAY SEND_SID_WARDEN( BYTEARRAY wardenResponse ); + BYTEARRAY SEND_SID_FRIENDSLIST( ); + BYTEARRAY SEND_SID_CLANMEMBERLIST( ); + + // other functions + +private: + bool AssignLength( BYTEARRAY &content ); + bool ValidateLength( BYTEARRAY &content ); +}; + +// +// CIncomingGameHost +// + +class CIncomingGameHost +{ +private: + BYTEARRAY m_IP; + uint16_t m_Port; + string m_GameName; + BYTEARRAY m_HostCounter; + +public: + CIncomingGameHost( BYTEARRAY &nIP, uint16_t nPort, string nGameName, BYTEARRAY &nHostCounter ); + ~CIncomingGameHost( ); + + BYTEARRAY GetIP( ) { return m_IP; } + string GetIPString( ); + uint16_t GetPort( ) { return m_Port; } + string GetGameName( ) { return m_GameName; } + BYTEARRAY GetHostCounter( ) { return m_HostCounter; } +}; + +// +// CIncomingChatEvent +// + +class CIncomingChatEvent +{ +private: + CBNETProtocol :: IncomingChatEvent m_ChatEvent; + uint32_t m_UserFlags; + uint32_t m_Ping; + string m_User; + string m_Message; + +public: + CIncomingChatEvent( CBNETProtocol :: IncomingChatEvent nChatEvent, uint32_t nUserFlags, uint32_t nPing, string nUser, string nMessage ); + ~CIncomingChatEvent( ); + + CBNETProtocol :: IncomingChatEvent GetChatEvent( ) { return m_ChatEvent; } + uint32_t GetUserFlags( ) { return m_UserFlags; } + uint32_t GetPing( ) { return m_Ping; } + string GetUser( ) { return m_User; } + string GetMessage( ) { return m_Message; } +}; + +// +// CIncomingFriendList +// + +class CIncomingFriendList +{ +private: + string m_Account; + unsigned char m_Status; + unsigned char m_Area; + string m_Location; + +public: + CIncomingFriendList( string nAccount, unsigned char nStatus, unsigned char nArea, string nLocation ); + ~CIncomingFriendList( ); + + string GetAccount( ) { return m_Account; } + unsigned char GetStatus( ) { return m_Status; } + unsigned char GetArea( ) { return m_Area; } + string GetLocation( ) { return m_Location; } + string GetDescription( ); + +private: + string ExtractStatus( unsigned char status ); + string ExtractArea( unsigned char area ); + string ExtractLocation( string location ); +}; + +// +// CIncomingClanList +// + +class CIncomingClanList +{ +private: + string m_Name; + unsigned char m_Rank; + unsigned char m_Status; + +public: + CIncomingClanList( string nName, unsigned char nRank, unsigned char nStatus ); + ~CIncomingClanList( ); + + string GetName( ) { return m_Name; } + string GetRank( ); + string GetStatus( ); + string GetDescription( ); + + unsigned char GetRawStatus( ) { return m_Status; } +}; + +#endif diff --git a/ghost-legacy/bnlsclient.cpp b/ghost-legacy/bnlsclient.cpp new file mode 100644 index 0000000..bce9bee --- /dev/null +++ b/ghost-legacy/bnlsclient.cpp @@ -0,0 +1,193 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "socket.h" +#include "commandpacket.h" +#include "bnlsprotocol.h" +#include "bnlsclient.h" + +// +// CBNLSClient +// + +CBNLSClient :: CBNLSClient( string nServer, uint16_t nPort, uint32_t nWardenCookie ) +{ + m_Socket = new CTCPClient( ); + m_Protocol = new CBNLSProtocol( ); + m_WasConnected = false; + m_Server = nServer; + m_Port = nPort; + m_LastNullTime = 0; + m_WardenCookie = nWardenCookie; + m_TotalWardenIn = 0; + m_TotalWardenOut = 0; +} + +CBNLSClient :: ~CBNLSClient( ) +{ + delete m_Socket; + delete m_Protocol; + + while( !m_Packets.empty( ) ) + { + delete m_Packets.front( ); + m_Packets.pop( ); + } +} + +BYTEARRAY CBNLSClient :: GetWardenResponse( ) +{ + BYTEARRAY WardenResponse; + + if( !m_WardenResponses.empty( ) ) + { + WardenResponse = m_WardenResponses.front( ); + m_WardenResponses.pop( ); + m_TotalWardenOut++; + } + + return WardenResponse; +} + +unsigned int CBNLSClient :: SetFD( void *fd, void *send_fd, int *nfds ) +{ + if( !m_Socket->HasError( ) && m_Socket->GetConnected( ) ) + { + m_Socket->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds ); + return 1; + } + + return 0; +} + +bool CBNLSClient :: Update( void *fd, void *send_fd ) +{ + if( m_Socket->HasError( ) ) + { + CONSOLE_Print( "[BNLSC: " + m_Server + ":" + UTIL_ToString( m_Port ) + ":C" + UTIL_ToString( m_WardenCookie ) + "] disconnected from BNLS server due to socket error" ); + return true; + } + + if( !m_Socket->GetConnecting( ) && !m_Socket->GetConnected( ) && m_WasConnected ) + { + CONSOLE_Print( "[BNLSC: " + m_Server + ":" + UTIL_ToString( m_Port ) + ":C" + UTIL_ToString( m_WardenCookie ) + "] disconnected from BNLS server" ); + return true; + } + + if( m_Socket->GetConnected( ) ) + { + m_Socket->DoRecv( (fd_set *)fd ); + ExtractPackets( ); + ProcessPackets( ); + + if( GetTime( ) - m_LastNullTime >= 50 ) + { + m_Socket->PutBytes( m_Protocol->SEND_BNLS_NULL( ) ); + m_LastNullTime = GetTime( ); + } + + while( !m_OutPackets.empty( ) ) + { + m_Socket->PutBytes( m_OutPackets.front( ) ); + m_OutPackets.pop( ); + } + + m_Socket->DoSend( (fd_set *)send_fd ); + return false; + } + + if( m_Socket->GetConnecting( ) && m_Socket->CheckConnect( ) ) + { + CONSOLE_Print( "[BNLSC: " + m_Server + ":" + UTIL_ToString( m_Port ) + ":C" + UTIL_ToString( m_WardenCookie ) + "] connected" ); + m_WasConnected = true; + m_LastNullTime = GetTime( ); + return false; + } + + if( !m_Socket->GetConnecting( ) && !m_Socket->GetConnected( ) && !m_WasConnected ) + { + CONSOLE_Print( "[BNLSC: " + m_Server + ":" + UTIL_ToString( m_Port ) + ":C" + UTIL_ToString( m_WardenCookie ) + "] connecting to server [" + m_Server + "] on port " + UTIL_ToString( m_Port ) ); + m_Socket->Connect( string( ), m_Server, m_Port ); + return false; + } + + return false; +} + +void CBNLSClient :: ExtractPackets( ) +{ + string *RecvBuffer = m_Socket->GetBytes( ); + BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); + + while( Bytes.size( ) >= 3 ) + { + uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false ); + + if( Length >= 3 ) + { + if( Bytes.size( ) >= Length ) + { + m_Packets.push( new CCommandPacket( 0, Bytes[2], BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); + *RecvBuffer = RecvBuffer->substr( Length ); + Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); + } + else + return; + } + else + { + CONSOLE_Print( "[BNLSC: " + m_Server + ":" + UTIL_ToString( m_Port ) + ":C" + UTIL_ToString( m_WardenCookie ) + "] error - received invalid packet from BNLS server (bad length), disconnecting" ); + m_Socket->Disconnect( ); + return; + } + } +} + +void CBNLSClient :: ProcessPackets( ) +{ + while( !m_Packets.empty( ) ) + { + CCommandPacket *Packet = m_Packets.front( ); + m_Packets.pop( ); + + if( Packet->GetID( ) == CBNLSProtocol :: BNLS_WARDEN ) + { + BYTEARRAY WardenResponse = m_Protocol->RECEIVE_BNLS_WARDEN( Packet->GetData( ) ); + + if( !WardenResponse.empty( ) ) + m_WardenResponses.push( WardenResponse ); + } + + delete Packet; + } +} + +void CBNLSClient :: QueueWardenSeed( uint32_t seed ) +{ + m_OutPackets.push( m_Protocol->SEND_BNLS_WARDEN_SEED( m_WardenCookie, seed ) ); +} + +void CBNLSClient :: QueueWardenRaw( BYTEARRAY wardenRaw ) +{ + m_OutPackets.push( m_Protocol->SEND_BNLS_WARDEN_RAW( m_WardenCookie, wardenRaw ) ); + m_TotalWardenIn++; +} diff --git a/ghost-legacy/bnlsclient.h b/ghost-legacy/bnlsclient.h new file mode 100644 index 0000000..0562c37 --- /dev/null +++ b/ghost-legacy/bnlsclient.h @@ -0,0 +1,69 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef BNLSCLIENT_H +#define BNLSCLIENT_H + +// +// CBNLSClient +// + +class CTCPClient; +class CBNLSProtocol; +class CCommandPacket; + +class CBNLSClient +{ +private: + CTCPClient *m_Socket; // the connection to the BNLS server + CBNLSProtocol *m_Protocol; // battle.net protocol + queue m_Packets; // queue of incoming packets + bool m_WasConnected; + string m_Server; + uint16_t m_Port; + uint32_t m_LastNullTime; + uint32_t m_WardenCookie; // the warden cookie + queue m_OutPackets; // queue of outgoing packets to be sent + queue m_WardenResponses; // the warden responses to be sent to battle.net + uint32_t m_TotalWardenIn; + uint32_t m_TotalWardenOut; + +public: + CBNLSClient( string nServer, uint16_t nPort, uint32_t nWardenCookie ); + ~CBNLSClient( ); + + BYTEARRAY GetWardenResponse( ); + uint32_t GetTotalWardenIn( ) { return m_TotalWardenIn; } + uint32_t GetTotalWardenOut( ) { return m_TotalWardenOut; } + + // processing functions + + unsigned int SetFD( void *fd, void *send_fd, int *nfds ); + bool Update( void *fd, void *send_fd ); + void ExtractPackets( ); + void ProcessPackets( ); + + // other functions + + void QueueWardenSeed( uint32_t seed ); + void QueueWardenRaw( BYTEARRAY wardenRaw ); +}; + +#endif diff --git a/ghost-legacy/bnlsprotocol.cpp b/ghost-legacy/bnlsprotocol.cpp new file mode 100644 index 0000000..8929ef9 --- /dev/null +++ b/ghost-legacy/bnlsprotocol.cpp @@ -0,0 +1,157 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "bnlsprotocol.h" + +CBNLSProtocol :: CBNLSProtocol( ) +{ + +} + +CBNLSProtocol :: ~CBNLSProtocol( ) +{ + +} + +/////////////////////// +// RECEIVE FUNCTIONS // +/////////////////////// + +BYTEARRAY CBNLSProtocol :: RECEIVE_BNLS_WARDEN( BYTEARRAY data ) +{ + // 2 bytes -> Length + // 1 byte -> ID + // (BYTE) -> Usage + // (DWORD) -> Cookie + // (BYTE) -> Result + // (WORD) -> Length of data + // (VOID) -> Data + + if( ValidateLength( data ) && data.size( ) >= 11 ) + { + unsigned char Usage = data[3]; + uint32_t Cookie = UTIL_ByteArrayToUInt32( data, false, 4 ); + unsigned char Result = data[8]; + uint16_t Length = UTIL_ByteArrayToUInt16( data, false, 10 ); + + if( Result == 0x00 ) + return BYTEARRAY( data.begin( ) + 11, data.end( ) ); + else + CONSOLE_Print( "[BNLSPROTO] received error code " + UTIL_ToString( data[8] ) ); + } + + return BYTEARRAY( ); +} + +//////////////////// +// SEND FUNCTIONS // +//////////////////// + +BYTEARRAY CBNLSProtocol :: SEND_BNLS_NULL( ) +{ + BYTEARRAY packet; + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( BNLS_NULL ); // BNLS_NULL + AssignLength( packet ); + return packet; +} + +BYTEARRAY CBNLSProtocol :: SEND_BNLS_WARDEN_SEED( uint32_t cookie, uint32_t seed ) +{ + unsigned char Client[] = { 80, 88, 51, 87 }; // "W3XP" + + BYTEARRAY packet; + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( BNLS_WARDEN ); // BNLS_WARDEN + packet.push_back( 0 ); // BNLS_WARDEN_SEED + UTIL_AppendByteArray( packet, cookie, false ); // cookie + UTIL_AppendByteArray( packet, Client, 4 ); // Client + UTIL_AppendByteArray( packet, (uint16_t)4, false ); // length of seed + UTIL_AppendByteArray( packet, seed, false ); // seed + packet.push_back( 0 ); // username is blank + UTIL_AppendByteArray( packet, (uint16_t)0, false ); // password length + // password + AssignLength( packet ); + return packet; +} + +BYTEARRAY CBNLSProtocol :: SEND_BNLS_WARDEN_RAW( uint32_t cookie, BYTEARRAY raw ) +{ + BYTEARRAY packet; + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( BNLS_WARDEN ); // BNLS_WARDEN + packet.push_back( 1 ); // BNLS_WARDEN_RAW + UTIL_AppendByteArray( packet, cookie, false ); // cookie + UTIL_AppendByteArray( packet, (uint16_t)raw.size( ), false ); // raw length + UTIL_AppendByteArray( packet, raw ); // raw + AssignLength( packet ); + return packet; +} + +BYTEARRAY CBNLSProtocol :: SEND_BNLS_WARDEN_RUNMODULE( uint32_t cookie ) +{ + return BYTEARRAY( ); +} + +///////////////////// +// OTHER FUNCTIONS // +///////////////////// + +bool CBNLSProtocol :: AssignLength( BYTEARRAY &content ) +{ + // insert the actual length of the content array into bytes 1 and 2 (indices 0 and 1) + + BYTEARRAY LengthBytes; + + if( content.size( ) >= 2 && content.size( ) <= 65535 ) + { + LengthBytes = UTIL_CreateByteArray( (uint16_t)content.size( ), false ); + content[0] = LengthBytes[0]; + content[1] = LengthBytes[1]; + return true; + } + + return false; +} + +bool CBNLSProtocol :: ValidateLength( BYTEARRAY &content ) +{ + // verify that bytes 1 and 2 (indices 0 and 1) of the content array describe the length + + uint16_t Length; + BYTEARRAY LengthBytes; + + if( content.size( ) >= 2 && content.size( ) <= 65535 ) + { + LengthBytes.push_back( content[0] ); + LengthBytes.push_back( content[1] ); + Length = UTIL_ByteArrayToUInt16( LengthBytes, false ); + + if( Length == content.size( ) ) + return true; + } + + return false; +} diff --git a/ghost-legacy/bnlsprotocol.h b/ghost-legacy/bnlsprotocol.h new file mode 100644 index 0000000..d0f9f10 --- /dev/null +++ b/ghost-legacy/bnlsprotocol.h @@ -0,0 +1,84 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef BNLSPROTOCOL_H +#define BNLSPROTOCOL_H + +// +// CBNLSProtocol +// + +class CBNLSProtocol +{ +public: + enum Protocol { + BNLS_NULL = 0x00, + BNLS_CDKEY = 0x01, + BNLS_LOGONCHALLENGE = 0x02, + BNLS_LOGONPROOF = 0x03, + BNLS_CREATEACCOUNT = 0x04, + BNLS_CHANGECHALLENGE = 0x05, + BNLS_CHANGEPROOF = 0x06, + BNLS_UPGRADECHALLENGE = 0x07, + BNLS_UPGRADEPROOF = 0x08, + BNLS_VERSIONCHECK = 0x09, + BNLS_CONFIRMLOGON = 0x0a, + BNLS_HASHDATA = 0x0b, + BNLS_CDKEY_EX = 0x0c, + BNLS_CHOOSENLSREVISION = 0x0d, + BNLS_AUTHORIZE = 0x0e, + BNLS_AUTHORIZEPROOF = 0x0f, + BNLS_REQUESTVERSIONBYTE = 0x10, + BNLS_VERIFYSERVER = 0x11, + BNLS_RESERVESERVERSLOTS = 0x12, + BNLS_SERVERLOGONCHALLENGE = 0x13, + BNLS_SERVERLOGONPROOF = 0x14, + BNLS_RESERVED0 = 0x15, + BNLS_RESERVED1 = 0x16, + BNLS_RESERVED2 = 0x17, + BNLS_VERSIONCHECKEX = 0x18, + BNLS_RESERVED3 = 0x19, + BNLS_VERSIONCHECKEX2 = 0x1a, + BNLS_WARDEN = 0x7d + }; + +public: + CBNLSProtocol( ); + ~CBNLSProtocol( ); + + // receive functions + + BYTEARRAY RECEIVE_BNLS_WARDEN( BYTEARRAY data ); + + // send functions + + BYTEARRAY SEND_BNLS_NULL( ); + BYTEARRAY SEND_BNLS_WARDEN_SEED( uint32_t cookie, uint32_t seed ); + BYTEARRAY SEND_BNLS_WARDEN_RAW( uint32_t cookie, BYTEARRAY raw ); + BYTEARRAY SEND_BNLS_WARDEN_RUNMODULE( uint32_t cookie ); + + // other functions + +private: + bool AssignLength( BYTEARRAY &content ); + bool ValidateLength( BYTEARRAY &content ); +}; + +#endif diff --git a/ghost-legacy/commandpacket.cpp b/ghost-legacy/commandpacket.cpp new file mode 100644 index 0000000..9c22f74 --- /dev/null +++ b/ghost-legacy/commandpacket.cpp @@ -0,0 +1,38 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "commandpacket.h" + +// +// CCommandPacket +// + +CCommandPacket :: CCommandPacket( unsigned char nPacketType, int nID, BYTEARRAY nData ) +{ + m_PacketType = nPacketType; + m_ID = nID; + m_Data = nData; +} + +CCommandPacket :: ~CCommandPacket( ) +{ + +} diff --git a/ghost-legacy/commandpacket.h b/ghost-legacy/commandpacket.h new file mode 100644 index 0000000..dc75488 --- /dev/null +++ b/ghost-legacy/commandpacket.h @@ -0,0 +1,44 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef COMMANDPACKET_H +#define COMMANDPACKET_H + +// +// CCommandPacket +// + +class CCommandPacket +{ +private: + unsigned char m_PacketType; + int m_ID; + BYTEARRAY m_Data; + +public: + CCommandPacket( unsigned char nPacketType, int nID, BYTEARRAY nData ); + ~CCommandPacket( ); + + unsigned char GetPacketType( ) { return m_PacketType; } + int GetID( ) { return m_ID; } + BYTEARRAY GetData( ) { return m_Data; } +}; + +#endif diff --git a/ghost-legacy/config.cpp b/ghost-legacy/config.cpp new file mode 100644 index 0000000..2a1276c --- /dev/null +++ b/ghost-legacy/config.cpp @@ -0,0 +1,108 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "config.h" + +#include + +// +// CConfig +// + +CConfig :: CConfig( ) +{ + +} + +CConfig :: ~CConfig( ) +{ + +} + +void CConfig :: Read( string file ) +{ + ifstream in; + in.open( file.c_str( ) ); + + if( in.fail( ) ) + CONSOLE_Print( "[CONFIG] warning - unable to read file [" + file + "]" ); + else + { + CONSOLE_Print( "[CONFIG] loading file [" + file + "]" ); + string Line; + + while( !in.eof( ) ) + { + getline( in, Line ); + + // ignore blank lines and comments + + if( Line.empty( ) || Line[0] == '#' ) + continue; + + // remove newlines and partial newlines to help fix issues with Windows formatted config files on Linux systems + + Line.erase( remove( Line.begin( ), Line.end( ), '\r' ), Line.end( ) ); + Line.erase( remove( Line.begin( ), Line.end( ), '\n' ), Line.end( ) ); + + string :: size_type Split = Line.find( "=" ); + + if( Split == string :: npos ) + continue; + + string :: size_type KeyStart = Line.find_first_not_of( " " ); + string :: size_type KeyEnd = Line.find( " ", KeyStart ); + string :: size_type ValueStart = Line.find_first_not_of( " ", Split + 1 ); + string :: size_type ValueEnd = Line.size( ); + + if( ValueStart != string :: npos ) + m_CFG[Line.substr( KeyStart, KeyEnd - KeyStart )] = Line.substr( ValueStart, ValueEnd - ValueStart ); + } + + in.close( ); + } +} + +bool CConfig :: Exists( string key ) +{ + return m_CFG.find( key ) != m_CFG.end( ); +} + +int CConfig :: GetInt( string key, int x ) +{ + if( m_CFG.find( key ) == m_CFG.end( ) ) + return x; + else + return atoi( m_CFG[key].c_str( ) ); +} + +string CConfig :: GetString( string key, string x ) +{ + if( m_CFG.find( key ) == m_CFG.end( ) ) + return x; + else + return m_CFG[key]; +} + +void CConfig :: Set( string key, string x ) +{ + m_CFG[key] = x; +} diff --git a/ghost-legacy/config.h b/ghost-legacy/config.h new file mode 100644 index 0000000..ccd3b7f --- /dev/null +++ b/ghost-legacy/config.h @@ -0,0 +1,44 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef CONFIG_H +#define CONFIG_H + +// +// CConfig +// + +class CConfig +{ +private: + map m_CFG; + +public: + CConfig( ); + ~CConfig( ); + + void Read( string file ); + bool Exists( string key ); + int GetInt( string key, int x ); + string GetString( string key, string x ); + void Set( string key, string x ); +}; + +#endif diff --git a/ghost-legacy/crc32.cpp b/ghost-legacy/crc32.cpp new file mode 100644 index 0000000..510757c --- /dev/null +++ b/ghost-legacy/crc32.cpp @@ -0,0 +1,43 @@ +#include "ghost.h" +#include "crc32.h" + +void CCRC32 :: Initialize( ) +{ + for( int iCodes = 0; iCodes <= 0xFF; iCodes++ ) + { + ulTable[iCodes] = Reflect( iCodes, 8 ) << 24; + + for( int iPos = 0; iPos < 8; iPos++ ) + ulTable[iCodes] = ( ulTable[iCodes] << 1 ) ^ ( ulTable[iCodes] & (1 << 31) ? CRC32_POLYNOMIAL : 0 ); + + ulTable[iCodes] = Reflect( ulTable[iCodes], 32 ); + } +} + +uint32_t CCRC32 :: Reflect( uint32_t ulReflect, char cChar ) +{ + uint32_t ulValue = 0; + + for( int iPos = 1; iPos < ( cChar + 1 ); iPos++ ) + { + if( ulReflect & 1 ) + ulValue |= 1 << ( cChar - iPos ); + + ulReflect >>= 1; + } + + return ulValue; +} + +uint32_t CCRC32 :: FullCRC( unsigned char *sData, uint32_t ulLength ) +{ + uint32_t ulCRC = 0xFFFFFFFF; + PartialCRC( &ulCRC, sData, ulLength ); + return ulCRC ^ 0xFFFFFFFF; +} + +void CCRC32 :: PartialCRC( uint32_t *ulInCRC, unsigned char *sData, uint32_t ulLength ) +{ + while( ulLength-- ) + *ulInCRC = ( *ulInCRC >> 8 ) ^ ulTable[( *ulInCRC & 0xFF ) ^ *sData++]; +} diff --git a/ghost-legacy/crc32.h b/ghost-legacy/crc32.h new file mode 100644 index 0000000..00f63ae --- /dev/null +++ b/ghost-legacy/crc32.h @@ -0,0 +1,18 @@ +#ifndef CRC32_H +#define CRC32_H + +#define CRC32_POLYNOMIAL 0x04c11db7 + +class CCRC32 +{ +public: + void Initialize( ); + uint32_t FullCRC( unsigned char *sData, uint32_t ulLength ); + void PartialCRC( uint32_t *ulInCRC, unsigned char *sData, uint32_t ulLength ); + +private: + uint32_t Reflect( uint32_t ulReflect, char cChar ); + uint32_t ulTable[256]; +}; + +#endif diff --git a/ghost-legacy/csvparser.cpp b/ghost-legacy/csvparser.cpp new file mode 100644 index 0000000..47a2be0 --- /dev/null +++ b/ghost-legacy/csvparser.cpp @@ -0,0 +1,122 @@ +/* +Copyright (c) 2001, Mayukh Bose +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Mayukh Bose nor the names of other +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include "csvparser.h" +using namespace std; + + +CSVParser::CSVParser() +{ + m_sData = ""; + m_nPos = 0; +} + +void CSVParser::SkipSpaces(void) +{ + while (m_nPos < m_sData.length() && m_sData[m_nPos] == ' ') + m_nPos++; +} + +const CSVParser & CSVParser::operator <<(const string & sIn) +{ + this->m_sData = sIn; + this->m_nPos = 0; + return *this; +} + +const CSVParser & CSVParser::operator <<(const char *sIn) +{ + this->m_sData = sIn; + this->m_nPos = 0; + return *this; +} + +CSVParser & CSVParser::operator >>(int & nOut) +{ + string sTmp = ""; + SkipSpaces(); + while (m_nPos < m_sData.length() && m_sData[m_nPos] != ',') + sTmp += m_sData[m_nPos++]; + + m_nPos++; // skip past comma + nOut = atoi(sTmp.c_str()); + return *this; +} + +CSVParser & CSVParser::operator >>(double & nOut) +{ + string sTmp = ""; + SkipSpaces(); + while (m_nPos < m_sData.length() && m_sData[m_nPos] != ',') + sTmp += m_sData[m_nPos++]; + + m_nPos++; // skip past comma + nOut = atof(sTmp.c_str()); + return *this; +} + +CSVParser & CSVParser::operator >>(string & sOut) +{ + bool bQuotes = false; + sOut = ""; + SkipSpaces(); + + // Jump past first " if necessary + if (m_nPos < m_sData.length() && m_sData[m_nPos] == '"') { + bQuotes = true; + m_nPos++; + } + + while (m_nPos < m_sData.length()) { + if (!bQuotes && m_sData[m_nPos] == ',') + break; + if (bQuotes && m_sData[m_nPos] == '"') { + if (m_nPos + 1 >= m_sData.length() - 1) + break; + if (m_sData[m_nPos+1] == ',') + break; + } + sOut += m_sData[m_nPos++]; + } + + // Jump past last " if necessary + if (bQuotes && m_nPos < m_sData.length() && m_sData[m_nPos] == '"') + m_nPos++; + + // Jump past , if necessary + if (m_nPos < m_sData.length() && m_sData[m_nPos] == ',') + m_nPos++; + + return *this; +} diff --git a/ghost-legacy/csvparser.h b/ghost-legacy/csvparser.h new file mode 100644 index 0000000..5c414a9 --- /dev/null +++ b/ghost-legacy/csvparser.h @@ -0,0 +1,53 @@ +#ifndef __CSVPARSE_H_2001_06_07__ +#define __CSVPARSE_H_2001_06_07__ + +/* +Copyright (c) 2001, Mayukh Bose +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Mayukh Bose nor the names of other +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +using namespace std; + +class CSVParser { + private: + string m_sData; + string::size_type m_nPos; + void SkipSpaces(void); + public: + CSVParser(); + const CSVParser & operator << (const string &sIn); + const CSVParser & operator << (const char *sIn); + CSVParser & operator >> (int &nOut); + CSVParser & operator >> (double &nOut); + CSVParser & operator >> (string &sOut); +}; + +#endif diff --git a/ghost-legacy/game.cpp b/ghost-legacy/game.cpp new file mode 100644 index 0000000..f0c500e --- /dev/null +++ b/ghost-legacy/game.cpp @@ -0,0 +1,1827 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "ghostdb.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "game_base.h" +#include "game.h" +#include "stats.h" +#include "statsdota.h" +#include "statsw3mmd.h" + +#include +#include +#include + +// +// sorting classes +// + +class CGamePlayerSortAscByPing +{ +public: + bool operator( ) ( CGamePlayer *Player1, CGamePlayer *Player2 ) const + { + return Player1->GetPing( false ) < Player2->GetPing( false ); + } +}; + +class CGamePlayerSortDescByPing +{ +public: + bool operator( ) ( CGamePlayer *Player1, CGamePlayer *Player2 ) const + { + return Player1->GetPing( false ) > Player2->GetPing( false ); + } +}; + +// +// CGame +// + +CGame :: CGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ) : CBaseGame( nGHost, nMap, nSaveGame, nHostPort, nGameState, nGameName, nOwnerName, nCreatorName, nCreatorServer ) +{ + m_DBBanLast = NULL; + m_DBGame = new CDBGame( 0, string( ), m_Map->GetMapPath( ), string( ), string( ), string( ), 0 ); + + if( m_Map->GetMapType( ) == "w3mmd" ) + m_Stats = new CStatsW3MMD( this, m_Map->GetMapStatsW3MMDCategory( ) ); + else if( m_Map->GetMapType( ) == "dota" ) + m_Stats = new CStatsDOTA( this ); + else + m_Stats = NULL; + + m_CallableGameAdd = NULL; +} + +CGame :: ~CGame( ) +{ + if( m_CallableGameAdd && m_CallableGameAdd->GetReady( ) ) + { + if( m_CallableGameAdd->GetResult( ) > 0 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] saving player/stats data to database" ); + + // store the CDBGamePlayers in the database + + for( vector :: iterator i = m_DBGamePlayers.begin( ); i != m_DBGamePlayers.end( ); i++ ) + m_GHost->m_Callables.push_back( m_GHost->m_DB->ThreadedGamePlayerAdd( m_CallableGameAdd->GetResult( ), (*i)->GetName( ), (*i)->GetIP( ), (*i)->GetSpoofed( ), (*i)->GetSpoofedRealm( ), (*i)->GetReserved( ), (*i)->GetLoadingTime( ), (*i)->GetLeft( ), (*i)->GetLeftReason( ), (*i)->GetTeam( ), (*i)->GetColour( ) ) ); + + // store the stats in the database + + if( m_Stats ) + m_Stats->Save( m_GHost, m_GHost->m_DB, m_CallableGameAdd->GetResult( ) ); + } + else + CONSOLE_Print( "[GAME: " + m_GameName + "] unable to save player/stats data to database" ); + + m_GHost->m_DB->RecoverCallable( m_CallableGameAdd ); + delete m_CallableGameAdd; + m_CallableGameAdd = NULL; + } + + for( vector :: iterator i = m_PairedBanChecks.begin( ); i != m_PairedBanChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedGPSChecks.begin( ); i != m_PairedGPSChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedDPSChecks.begin( ); i != m_PairedDPSChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) + delete *i; + + delete m_DBGame; + + for( vector :: iterator i = m_DBGamePlayers.begin( ); i != m_DBGamePlayers.end( ); i++ ) + delete *i; + + delete m_Stats; + + // it's a "bad thing" if m_CallableGameAdd is non NULL here + // it means the game is being deleted after m_CallableGameAdd was created (the first step to saving the game data) but before the associated thread terminated + // rather than failing horribly we choose to allow the thread to complete in the orphaned callables list but step 2 will never be completed + // so this will create a game entry in the database without any gameplayers and/or DotA stats + + if( m_CallableGameAdd ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] game is being deleted before all game data was saved, game data has been lost" ); + m_GHost->m_Callables.push_back( m_CallableGameAdd ); + } +} + +bool CGame :: Update( void *fd, void *send_fd ) +{ + // update callables + + for( vector :: iterator i = m_PairedBanChecks.begin( ); i != m_PairedBanChecks.end( ); ) + { + if( i->second->GetReady( ) ) + { + CDBBan *Ban = i->second->GetResult( ); + + if( Ban ) + SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( i->second->GetServer( ), i->second->GetUser( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + else + SendAllChat( m_GHost->m_Language->UserIsNotBanned( i->second->GetServer( ), i->second->GetUser( ) ) ); + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanChecks.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + for( vector :: iterator j = m_GHost->m_BNETs.begin( ); j != m_GHost->m_BNETs.end( ); j++ ) + { + if( (*j)->GetServer( ) == i->second->GetServer( ) ) + (*j)->AddBan( i->second->GetUser( ), i->second->GetIP( ), i->second->GetGameName( ), i->second->GetAdmin( ), i->second->GetReason( ) ); + } + + SendAllChat( m_GHost->m_Language->PlayerWasBannedByPlayer( i->second->GetServer( ), i->second->GetUser( ), i->first ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanAdds.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedGPSChecks.begin( ); i != m_PairedGPSChecks.end( ); ) + { + if( i->second->GetReady( ) ) + { + CDBGamePlayerSummary *GamePlayerSummary = i->second->GetResult( ); + + if( GamePlayerSummary ) + { + if( i->first.empty( ) ) + SendAllChat( m_GHost->m_Language->HasPlayedGamesWithThisBot( i->second->GetName( ), GamePlayerSummary->GetFirstGameDateTime( ), GamePlayerSummary->GetLastGameDateTime( ), UTIL_ToString( GamePlayerSummary->GetTotalGames( ) ), UTIL_ToString( (float)GamePlayerSummary->GetAvgLoadingTime( ) / 1000, 2 ), UTIL_ToString( GamePlayerSummary->GetAvgLeftPercent( ) ) ) ); + else + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + SendChat( Player, m_GHost->m_Language->HasPlayedGamesWithThisBot( i->second->GetName( ), GamePlayerSummary->GetFirstGameDateTime( ), GamePlayerSummary->GetLastGameDateTime( ), UTIL_ToString( GamePlayerSummary->GetTotalGames( ) ), UTIL_ToString( (float)GamePlayerSummary->GetAvgLoadingTime( ) / 1000, 2 ), UTIL_ToString( GamePlayerSummary->GetAvgLeftPercent( ) ) ) ); + } + } + else + { + if( i->first.empty( ) ) + SendAllChat( m_GHost->m_Language->HasntPlayedGamesWithThisBot( i->second->GetName( ) ) ); + else + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + SendChat( Player, m_GHost->m_Language->HasntPlayedGamesWithThisBot( i->second->GetName( ) ) ); + } + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedGPSChecks.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedDPSChecks.begin( ); i != m_PairedDPSChecks.end( ); ) + { + if( i->second->GetReady( ) ) + { + CDBDotAPlayerSummary *DotAPlayerSummary = i->second->GetResult( ); + + if( DotAPlayerSummary ) + { + string Summary = m_GHost->m_Language->HasPlayedDotAGamesWithThisBot( i->second->GetName( ), + UTIL_ToString( DotAPlayerSummary->GetTotalGames( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalWins( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalLosses( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalDeaths( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCreepKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCreepDenies( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalAssists( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalNeutralKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalTowerKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalRaxKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetTotalCourierKills( ) ), + UTIL_ToString( DotAPlayerSummary->GetAvgKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgDeaths( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCreepKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCreepDenies( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgAssists( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgNeutralKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgTowerKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgRaxKills( ), 2 ), + UTIL_ToString( DotAPlayerSummary->GetAvgCourierKills( ), 2 ) ); + + if( i->first.empty( ) ) + SendAllChat( Summary ); + else + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + SendChat( Player, Summary ); + } + } + else + { + if( i->first.empty( ) ) + SendAllChat( m_GHost->m_Language->HasntPlayedDotAGamesWithThisBot( i->second->GetName( ) ) ); + else + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + SendChat( Player, m_GHost->m_Language->HasntPlayedDotAGamesWithThisBot( i->second->GetName( ) ) ); + } + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedDPSChecks.erase( i ); + } + else + i++; + } + + return CBaseGame :: Update( fd, send_fd ); +} + +void CGame :: EventPlayerDeleted( CGamePlayer *player ) +{ + CBaseGame :: EventPlayerDeleted( player ); + + // record everything we need to know about the player for storing in the database later + // since we haven't stored the game yet (it's not over yet!) we can't link the gameplayer to the game + // see the destructor for where these CDBGamePlayers are stored in the database + // we could have inserted an incomplete record on creation and updated it later but this makes for a cleaner interface + + if( m_GameLoading || m_GameLoaded ) + { + // todotodo: since we store players that crash during loading it's possible that the stats classes could have no information on them + // that could result in a DBGamePlayer without a corresponding DBDotAPlayer - just be aware of the possibility + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + unsigned char Team = 255; + unsigned char Colour = 255; + + if( SID < m_Slots.size( ) ) + { + Team = m_Slots[SID].GetTeam( ); + Colour = m_Slots[SID].GetColour( ); + } + + m_DBGamePlayers.push_back( new CDBGamePlayer( 0, 0, player->GetName( ), player->GetExternalIPString( ), player->GetSpoofed( ) ? 1 : 0, player->GetSpoofedRealm( ), player->GetReserved( ) ? 1 : 0, player->GetFinishedLoading( ) ? player->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks : 0, m_GameTicks / 1000, player->GetLeftReason( ), Team, Colour ) ); + + // also keep track of the last player to leave for the !banlast command + + for( vector :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) + { + if( (*i)->GetName( ) == player->GetName( ) ) + m_DBBanLast = *i; + } + } +} + +void CGame :: EventPlayerAction( CGamePlayer *player, CIncomingAction *action ) +{ + CBaseGame :: EventPlayerAction( player, action ); + + // give the stats class a chance to process the action + + if( m_Stats && m_Stats->ProcessAction( action ) && m_GameOverTime == 0 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] gameover timer started (stats class reported game over)" ); + SendEndMessage( ); + m_GameOverTime = GetTime( ); + } +} + +bool CGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ) +{ + bool HideCommand = CBaseGame :: EventPlayerBotCommand( player, command, payload ); + + // todotodo: don't be lazy + + string User = player->GetName( ); + string Command = command; + string Payload = payload; + + bool AdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == player->GetSpoofedRealm( ) && (*i)->IsAdmin( User ) ) + { + AdminCheck = true; + break; + } + } + + bool RootAdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == player->GetSpoofedRealm( ) && (*i)->IsRootAdmin( User ) ) + { + RootAdminCheck = true; + break; + } + } + + if( player->GetSpoofed( ) && ( AdminCheck || RootAdminCheck || IsOwner( User ) ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] admin [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); + + if( !m_Locked || RootAdminCheck || IsOwner( User ) ) + { + /***************** + * ADMIN COMMANDS * + ******************/ + + // + // !ABORT (abort countdown) + // !A + // + + // we use "!a" as an alias for abort because you don't have much time to abort the countdown so it's useful for the abort command to be easy to type + + if( ( Command == "abort" || Command == "a" ) && m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); + m_CountDownStarted = false; + } + + // + // !ADDBAN + // !BAN + // + + if( ( Command == "addban" || Command == "ban" ) && !Payload.empty( ) && !m_GHost->m_BNETs.empty( ) ) + { + // extract the victim and the reason + // e.g. "Varlock leaver after dying" -> victim: "Varlock", reason: "leaver after dying" + + string Victim; + string Reason; + stringstream SS; + SS << Payload; + SS >> Victim; + + if( !SS.eof( ) ) + { + getline( SS, Reason ); + string :: size_type Start = Reason.find_first_not_of( " " ); + + if( Start != string :: npos ) + Reason = Reason.substr( Start ); + } + + if( m_GameLoaded ) + { + string VictimLower = Victim; + transform( VictimLower.begin( ), VictimLower.end( ), VictimLower.begin( ), (int(*)(int))tolower ); + uint32_t Matches = 0; + CDBBan *LastMatch = NULL; + + // try to match each player with the passed string (e.g. "Varlock" would be matched with "lock") + // we use the m_DBBans vector for this in case the player already left and thus isn't in the m_Players vector anymore + + for( vector :: iterator i = m_DBBans.begin( ); i != m_DBBans.end( ); i++ ) + { + string TestName = (*i)->GetName( ); + transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); + + if( TestName.find( VictimLower ) != string :: npos ) + { + Matches++; + LastMatch = *i; + + // if the name matches exactly stop any further matching + + if( TestName == VictimLower ) + { + Matches = 1; + break; + } + } + } + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToBanNoMatchesFound( Victim ) ); + else if( Matches == 1 ) + m_PairedBanAdds.push_back( PairedBanAdd( User, m_GHost->m_DB->ThreadedBanAdd( LastMatch->GetServer( ), LastMatch->GetName( ), LastMatch->GetIP( ), m_GameName, User, Reason ) ) ); + else + SendAllChat( m_GHost->m_Language->UnableToBanFoundMoreThanOneMatch( Victim ) ); + } + else + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Victim, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToBanNoMatchesFound( Victim ) ); + else if( Matches == 1 ) + m_PairedBanAdds.push_back( PairedBanAdd( User, m_GHost->m_DB->ThreadedBanAdd( LastMatch->GetJoinedRealm( ), LastMatch->GetName( ), LastMatch->GetExternalIPString( ), m_GameName, User, Reason ) ) ); + else + SendAllChat( m_GHost->m_Language->UnableToBanFoundMoreThanOneMatch( Victim ) ); + } + } + + // + // !ANNOUNCE + // + + if( Command == "announce" && !m_CountDownStarted ) + { + if( Payload.empty( ) || Payload == "off" ) + { + SendAllChat( m_GHost->m_Language->AnnounceMessageDisabled( ) ); + SetAnnounce( 0, string( ) ); + } + else + { + // extract the interval and the message + // e.g. "30 hello everyone" -> interval: "30", message: "hello everyone" + + uint32_t Interval; + string Message; + stringstream SS; + SS << Payload; + SS >> Interval; + + if( SS.fail( ) || Interval == 0 ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to announce command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to announce command" ); + else + { + getline( SS, Message ); + string :: size_type Start = Message.find_first_not_of( " " ); + + if( Start != string :: npos ) + Message = Message.substr( Start ); + + SendAllChat( m_GHost->m_Language->AnnounceMessageEnabled( ) ); + SetAnnounce( Interval, Message ); + } + } + } + } + + // + // !AUTOSAVE + // + + if( Command == "autosave" ) + { + if( Payload == "on" ) + { + SendAllChat( m_GHost->m_Language->AutoSaveEnabled( ) ); + m_AutoSave = true; + } + else if( Payload == "off" ) + { + SendAllChat( m_GHost->m_Language->AutoSaveDisabled( ) ); + m_AutoSave = false; + } + } + + // + // !AUTOSTART + // + + if( Command == "autostart" && !m_CountDownStarted ) + { + if( Payload.empty( ) || Payload == "off" ) + { + SendAllChat( m_GHost->m_Language->AutoStartDisabled( ) ); + m_AutoStartPlayers = 0; + } + else + { + uint32_t AutoStartPlayers = UTIL_ToUInt32( Payload ); + + if( AutoStartPlayers != 0 ) + { + SendAllChat( m_GHost->m_Language->AutoStartEnabled( UTIL_ToString( AutoStartPlayers ) ) ); + m_AutoStartPlayers = AutoStartPlayers; + } + } + } + + // + // !BANLAST + // + + if( Command == "banlast" && m_GameLoaded && !m_GHost->m_BNETs.empty( ) && m_DBBanLast ) + m_PairedBanAdds.push_back( PairedBanAdd( User, m_GHost->m_DB->ThreadedBanAdd( m_DBBanLast->GetServer( ), m_DBBanLast->GetName( ), m_DBBanLast->GetIP( ), m_GameName, User, Payload ) ) ); + + // + // !CHECK + // + + if( Command == "check" ) + { + if( !Payload.empty( ) ) + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToCheckPlayerNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + bool LastMatchAdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == LastMatch->GetSpoofedRealm( ) && (*i)->IsAdmin( LastMatch->GetName( ) ) ) + { + LastMatchAdminCheck = true; + break; + } + } + + bool LastMatchRootAdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == LastMatch->GetSpoofedRealm( ) && (*i)->IsRootAdmin( LastMatch->GetName( ) ) ) + { + LastMatchRootAdminCheck = true; + break; + } + } + + SendAllChat( m_GHost->m_Language->CheckedPlayer( LastMatch->GetName( ), LastMatch->GetNumPings( ) > 0 ? UTIL_ToString( LastMatch->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( LastMatch->GetExternalIP( ), true ) ), LastMatchAdminCheck || LastMatchRootAdminCheck ? "Yes" : "No", IsOwner( LastMatch->GetName( ) ) ? "Yes" : "No", LastMatch->GetSpoofed( ) ? "Yes" : "No", LastMatch->GetSpoofedRealm( ).empty( ) ? "N/A" : LastMatch->GetSpoofedRealm( ), LastMatch->GetReserved( ) ? "Yes" : "No" ) ); + } + else + SendAllChat( m_GHost->m_Language->UnableToCheckPlayerFoundMoreThanOneMatch( Payload ) ); + } + else + SendAllChat( m_GHost->m_Language->CheckedPlayer( User, player->GetNumPings( ) > 0 ? UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( player->GetExternalIP( ), true ) ), AdminCheck || RootAdminCheck ? "Yes" : "No", IsOwner( User ) ? "Yes" : "No", player->GetSpoofed( ) ? "Yes" : "No", player->GetSpoofedRealm( ).empty( ) ? "N/A" : player->GetSpoofedRealm( ), player->GetReserved( ) ? "Yes" : "No" ) ); + } + + // + // !CHECKBAN + // + + if( Command == "checkban" && !Payload.empty( ) && !m_GHost->m_BNETs.empty( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + m_PairedBanChecks.push_back( PairedBanCheck( User, m_GHost->m_DB->ThreadedBanCheck( (*i)->GetServer( ), Payload, string( ) ) ) ); + } + + // + // !CLEARHCL + // + + if( Command == "clearhcl" && !m_CountDownStarted ) + { + m_HCLCommandString.clear( ); + SendAllChat( m_GHost->m_Language->ClearingHCL( ) ); + } + + // + // !CLOSE (close slot) + // + + if( Command == "close" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) + { + // close as many slots as specified, e.g. "5 10" closes slots 5 and 10 + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + uint32_t SID; + SS >> SID; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to close command" ); + break; + } + else + CloseSlot( (unsigned char)( SID - 1 ), true ); + } + } + + // + // !CLOSEALL + // + + if( Command == "closeall" && !m_GameLoading && !m_GameLoaded ) + CloseAllSlots( ); + + // + // !COMP (computer slot) + // + + if( Command == "comp" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) + { + // extract the slot and the skill + // e.g. "1 2" -> slot: "1", skill: "2" + + uint32_t Slot; + uint32_t Skill = 1; + stringstream SS; + SS << Payload; + SS >> Slot; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comp command" ); + else + { + if( !SS.eof( ) ) + SS >> Skill; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to comp command" ); + else + ComputerSlot( (unsigned char)( Slot - 1 ), (unsigned char)Skill, true ); + } + } + + // + // !COMPCOLOUR (computer colour change) + // + + if( Command == "compcolour" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) + { + // extract the slot and the colour + // e.g. "1 2" -> slot: "1", colour: "2" + + uint32_t Slot; + uint32_t Colour; + stringstream SS; + SS << Payload; + SS >> Slot; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to compcolour command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to compcolour command" ); + else + { + SS >> Colour; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to compcolour command" ); + else + { + unsigned char SID = (unsigned char)( Slot - 1 ); + + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) && Colour < 12 && SID < m_Slots.size( ) ) + { + if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) + ColourSlot( SID, Colour ); + } + } + } + } + } + + // + // !COMPHANDICAP (computer handicap change) + // + + if( Command == "comphandicap" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) + { + // extract the slot and the handicap + // e.g. "1 50" -> slot: "1", handicap: "50" + + uint32_t Slot; + uint32_t Handicap; + stringstream SS; + SS << Payload; + SS >> Slot; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comphandicap command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to comphandicap command" ); + else + { + SS >> Handicap; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to comphandicap command" ); + else + { + unsigned char SID = (unsigned char)( Slot - 1 ); + + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) && ( Handicap == 50 || Handicap == 60 || Handicap == 70 || Handicap == 80 || Handicap == 90 || Handicap == 100 ) && SID < m_Slots.size( ) ) + { + if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) + { + m_Slots[SID].SetHandicap( (unsigned char)Handicap ); + SendAllSlotInfo( ); + } + } + } + } + } + } + + // + // !COMPRACE (computer race change) + // + + if( Command == "comprace" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) + { + // extract the slot and the race + // e.g. "1 human" -> slot: "1", race: "human" + + uint32_t Slot; + string Race; + stringstream SS; + SS << Payload; + SS >> Slot; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to comprace command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to comprace command" ); + else + { + getline( SS, Race ); + string :: size_type Start = Race.find_first_not_of( " " ); + + if( Start != string :: npos ) + Race = Race.substr( Start ); + + transform( Race.begin( ), Race.end( ), Race.begin( ), (int(*)(int))tolower ); + unsigned char SID = (unsigned char)( Slot - 1 ); + + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) && !( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES ) && SID < m_Slots.size( ) ) + { + if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) + { + if( Race == "human" ) + { + m_Slots[SID].SetRace( SLOTRACE_HUMAN | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } + else if( Race == "orc" ) + { + m_Slots[SID].SetRace( SLOTRACE_ORC | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } + else if( Race == "night elf" ) + { + m_Slots[SID].SetRace( SLOTRACE_NIGHTELF | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } + else if( Race == "undead" ) + { + m_Slots[SID].SetRace( SLOTRACE_UNDEAD | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } + else if( Race == "random" ) + { + m_Slots[SID].SetRace( SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } + else + CONSOLE_Print( "[GAME: " + m_GameName + "] unknown race [" + Race + "] sent to comprace command" ); + } + } + } + } + } + + // + // !COMPTEAM (computer team change) + // + + if( Command == "compteam" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded && !m_SaveGame ) + { + // extract the slot and the team + // e.g. "1 2" -> slot: "1", team: "2" + + uint32_t Slot; + uint32_t Team; + stringstream SS; + SS << Payload; + SS >> Slot; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to compteam command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to compteam command" ); + else + { + SS >> Team; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to compteam command" ); + else + { + unsigned char SID = (unsigned char)( Slot - 1 ); + + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) && Team < 12 && SID < m_Slots.size( ) ) + { + if( m_Slots[SID].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[SID].GetComputer( ) == 1 ) + { + m_Slots[SID].SetTeam( (unsigned char)( Team - 1 ) ); + SendAllSlotInfo( ); + } + } + } + } + } + } + + // + // !DBSTATUS + // + + if( Command == "dbstatus" ) + SendAllChat( m_GHost->m_DB->GetStatus( ) ); + + // + // !DOWNLOAD + // !DL + // + + if( ( Command == "download" || Command == "dl" ) && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToStartDownloadNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + if( !LastMatch->GetDownloadStarted( ) && !LastMatch->GetDownloadFinished( ) ) + { + unsigned char SID = GetSIDFromPID( LastMatch->GetPID( ) ); + + if( SID < m_Slots.size( ) && m_Slots[SID].GetDownloadStatus( ) != 100 ) + { + // inform the client that we are willing to send the map + + CONSOLE_Print( "[GAME: " + m_GameName + "] map download started for player [" + LastMatch->GetName( ) + "]" ); + Send( LastMatch, m_Protocol->SEND_W3GS_STARTDOWNLOAD( GetHostPID( ) ) ); + LastMatch->SetDownloadAllowed( true ); + LastMatch->SetDownloadStarted( true ); + LastMatch->SetStartedDownloadingTicks( GetTicks( ) ); + } + } + } + else + SendAllChat( m_GHost->m_Language->UnableToStartDownloadFoundMoreThanOneMatch( Payload ) ); + } + + // + // !DROP + // + + if( Command == "drop" && m_GameLoaded ) + StopLaggers( "lagged out (dropped by admin)" ); + + // + // !END + // + + if( Command == "end" && m_GameLoaded ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] is over (admin ended game)" ); + StopPlayers( "was disconnected (admin ended game)" ); + } + + // + // !FAKEPLAYER + // + + if( Command == "fakeplayer" && !m_CountDownStarted ) + { + if( m_FakePlayerPID == 255 ) + CreateFakePlayer( ); + else + DeleteFakePlayer( ); + } + + // + // !FPPAUSE + // + + if( Command == "fppause" && m_FakePlayerPID != 255 && m_GameLoaded ) + { + BYTEARRAY CRC; + BYTEARRAY Action; + Action.push_back( 1 ); + m_Actions.push( new CIncomingAction( m_FakePlayerPID, CRC, Action ) ); + } + + // + // !FPRESUME + // + + if( Command == "fpresume" && m_FakePlayerPID != 255 && m_GameLoaded ) + { + BYTEARRAY CRC; + BYTEARRAY Action; + Action.push_back( 2 ); + m_Actions.push( new CIncomingAction( m_FakePlayerPID, CRC, Action ) ); + } + + // + // !FROM + // + + if( Command == "from" ) + { + string Froms; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + // we reverse the byte order on the IP because it's stored in network byte order + + Froms += (*i)->GetNameTerminated( ); + Froms += ": ("; + Froms += m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( (*i)->GetExternalIP( ), true ) ); + Froms += ")"; + + if( i != m_Players.end( ) - 1 ) + Froms += ", "; + + if( ( m_GameLoading || m_GameLoaded ) && Froms.size( ) > 100 ) + { + // cut the text into multiple lines ingame + + SendAllChat( Froms ); + Froms.clear( ); + } + } + + if( !Froms.empty( ) ) + SendAllChat( Froms ); + } + + // + // !HCL + // + + if( Command == "hcl" && !m_CountDownStarted ) + { + if( !Payload.empty( ) ) + { + if( Payload.size( ) <= m_Slots.size( ) ) + { + string HCLChars = "abcdefghijklmnopqrstuvwxyz0123456789 -=,."; + + if( Payload.find_first_not_of( HCLChars ) == string :: npos ) + { + m_HCLCommandString = Payload; + SendAllChat( m_GHost->m_Language->SettingHCL( m_HCLCommandString ) ); + } + else + SendAllChat( m_GHost->m_Language->UnableToSetHCLInvalid( ) ); + } + else + SendAllChat( m_GHost->m_Language->UnableToSetHCLTooLong( ) ); + } + else + SendAllChat( m_GHost->m_Language->TheHCLIs( m_HCLCommandString ) ); + } + + // + // !HOLD (hold a slot for someone) + // + + if( Command == "hold" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) + { + // hold as many players as specified, e.g. "Varlock Kilranin" holds players "Varlock" and "Kilranin" + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + string HoldName; + SS >> HoldName; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to hold command" ); + break; + } + else + { + SendAllChat( m_GHost->m_Language->AddedPlayerToTheHoldList( HoldName ) ); + AddToReserved( HoldName ); + } + } + } + + // + // !KICK (kick a player) + // + + if( Command == "kick" && !Payload.empty( ) ) + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToKickNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + LastMatch->SetDeleteMe( true ); + LastMatch->SetLeftReason( m_GHost->m_Language->WasKickedByPlayer( User ) ); + + if( !m_GameLoading && !m_GameLoaded ) + LastMatch->SetLeftCode( PLAYERLEAVE_LOBBY ); + else + LastMatch->SetLeftCode( PLAYERLEAVE_LOST ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( LastMatch->GetPID( ) ), false ); + } + else + SendAllChat( m_GHost->m_Language->UnableToKickFoundMoreThanOneMatch( Payload ) ); + } + + // + // !LATENCY (set game latency) + // + + if( Command == "latency" ) + { + if( Payload.empty( ) ) + SendAllChat( m_GHost->m_Language->LatencyIs( UTIL_ToString( m_Latency ) ) ); + else + { + m_Latency = UTIL_ToUInt32( Payload ); + + if( m_Latency <= 20 ) + { + m_Latency = 20; + SendAllChat( m_GHost->m_Language->SettingLatencyToMinimum( "20" ) ); + } + else if( m_Latency >= 500 ) + { + m_Latency = 500; + SendAllChat( m_GHost->m_Language->SettingLatencyToMaximum( "500" ) ); + } + else + SendAllChat( m_GHost->m_Language->SettingLatencyTo( UTIL_ToString( m_Latency ) ) ); + } + } + + // + // !LOCK + // + + if( Command == "lock" && ( RootAdminCheck || IsOwner( User ) ) ) + { + SendAllChat( m_GHost->m_Language->GameLocked( ) ); + m_Locked = true; + } + + // + // !MESSAGES + // + + if( Command == "messages" ) + { + if( Payload == "on" ) + { + SendAllChat( m_GHost->m_Language->LocalAdminMessagesEnabled( ) ); + m_LocalAdminMessages = true; + } + else if( Payload == "off" ) + { + SendAllChat( m_GHost->m_Language->LocalAdminMessagesDisabled( ) ); + m_LocalAdminMessages = false; + } + } + + // + // !MUTE + // + + if( Command == "mute" ) + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToMuteNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + SendAllChat( m_GHost->m_Language->MutedPlayer( LastMatch->GetName( ), User ) ); + LastMatch->SetMuted( true ); + } + else + SendAllChat( m_GHost->m_Language->UnableToMuteFoundMoreThanOneMatch( Payload ) ); + } + + // + // !MUTEALL + // + + if( Command == "muteall" && m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->GlobalChatMuted( ) ); + m_MuteAll = true; + } + + // + // !OPEN (open slot) + // + + if( Command == "open" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) + { + // open as many slots as specified, e.g. "5 10" opens slots 5 and 10 + + stringstream SS; + SS << Payload; + + while( !SS.eof( ) ) + { + uint32_t SID; + SS >> SID; + + if( SS.fail( ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input to open command" ); + break; + } + else + OpenSlot( (unsigned char)( SID - 1 ), true ); + } + } + + // + // !OPENALL + // + + if( Command == "openall" && !m_GameLoading && !m_GameLoaded ) + OpenAllSlots( ); + + // + // !OWNER (set game owner) + // + + if( Command == "owner" ) + { + if( RootAdminCheck || IsOwner( User ) || !GetPlayerFromName( m_OwnerName, false ) ) + { + if( !Payload.empty( ) ) + { + SendAllChat( m_GHost->m_Language->SettingGameOwnerTo( Payload ) ); + m_OwnerName = Payload; + } + else + { + SendAllChat( m_GHost->m_Language->SettingGameOwnerTo( User ) ); + m_OwnerName = User; + } + } + else + SendAllChat( m_GHost->m_Language->UnableToSetGameOwner( m_OwnerName ) ); + } + + // + // !PING + // + + if( Command == "ping" ) + { + // kick players with ping higher than payload if payload isn't empty + // we only do this if the game hasn't started since we don't want to kick players from a game in progress + + uint32_t Kicked = 0; + uint32_t KickPing = 0; + + if( !m_GameLoading && !m_GameLoaded && !Payload.empty( ) ) + KickPing = UTIL_ToUInt32( Payload ); + + // copy the m_Players vector so we can sort by descending ping so it's easier to find players with high pings + + vector SortedPlayers = m_Players; + sort( SortedPlayers.begin( ), SortedPlayers.end( ), CGamePlayerSortDescByPing( ) ); + string Pings; + + for( vector :: iterator i = SortedPlayers.begin( ); i != SortedPlayers.end( ); i++ ) + { + Pings += (*i)->GetNameTerminated( ); + Pings += ": "; + + if( (*i)->GetNumPings( ) > 0 ) + { + Pings += UTIL_ToString( (*i)->GetPing( m_GHost->m_LCPings ) ); + + if( !m_GameLoading && !m_GameLoaded && !(*i)->GetReserved( ) && KickPing > 0 && (*i)->GetPing( m_GHost->m_LCPings ) > KickPing ) + { + (*i)->SetDeleteMe( true ); + (*i)->SetLeftReason( "was kicked for excessive ping " + UTIL_ToString( (*i)->GetPing( m_GHost->m_LCPings ) ) + " > " + UTIL_ToString( KickPing ) ); + (*i)->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( (*i)->GetPID( ) ), false ); + Kicked++; + } + + Pings += "ms"; + } + else + Pings += "N/A"; + + if( i != SortedPlayers.end( ) - 1 ) + Pings += ", "; + + if( ( m_GameLoading || m_GameLoaded ) && Pings.size( ) > 100 ) + { + // cut the text into multiple lines ingame + + SendAllChat( Pings ); + Pings.clear( ); + } + } + + if( !Pings.empty( ) ) + SendAllChat( Pings ); + + if( Kicked > 0 ) + SendAllChat( m_GHost->m_Language->KickingPlayersWithPingsGreaterThan( UTIL_ToString( Kicked ), UTIL_ToString( KickPing ) ) ); + } + + // + // !PRIV (rehost as private game) + // + + if( Command == "priv" && !Payload.empty( ) && !m_CountDownStarted && !m_SaveGame ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] trying to rehost as private game [" + Payload + "]" ); + SendAllChat( m_GHost->m_Language->TryingToRehostAsPrivateGame( Payload ) ); + m_GameState = GAME_PRIVATE; + m_LastGameName = m_GameName; + m_GameName = Payload; + m_HostCounter = m_GHost->m_HostCounter++; + m_RefreshError = false; + m_RefreshRehosted = true; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + // unqueue any existing game refreshes because we're going to assume the next successful game refresh indicates that the rehost worked + // this ignores the fact that it's possible a game refresh was just sent and no response has been received yet + // we assume this won't happen very often since the only downside is a potential false positive + + (*i)->UnqueueGameRefreshes( ); + (*i)->QueueGameUncreate( ); + (*i)->QueueEnterChat( ); + + // we need to send the game creation message now because private games are not refreshed + + (*i)->QueueGameCreate( m_GameState, m_GameName, string( ), m_Map, NULL, m_HostCounter ); + + if( (*i)->GetPasswordHashType( ) != "pvpgn" ) + (*i)->QueueEnterChat( ); + } + + m_CreationTime = GetTime( ); + m_LastRefreshTime = GetTime( ); + } + + // + // !PUB (rehost as public game) + // + + if( Command == "pub" && !Payload.empty( ) && !m_CountDownStarted && !m_SaveGame ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] trying to rehost as public game [" + Payload + "]" ); + SendAllChat( m_GHost->m_Language->TryingToRehostAsPublicGame( Payload ) ); + m_GameState = GAME_PUBLIC; + m_LastGameName = m_GameName; + m_GameName = Payload; + m_HostCounter = m_GHost->m_HostCounter++; + m_RefreshError = false; + m_RefreshRehosted = true; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + // unqueue any existing game refreshes because we're going to assume the next successful game refresh indicates that the rehost worked + // this ignores the fact that it's possible a game refresh was just sent and no response has been received yet + // we assume this won't happen very often since the only downside is a potential false positive + + (*i)->UnqueueGameRefreshes( ); + (*i)->QueueGameUncreate( ); + (*i)->QueueEnterChat( ); + + // the game creation message will be sent on the next refresh + } + + m_CreationTime = GetTime( ); + m_LastRefreshTime = GetTime( ); + } + + // + // !REFRESH (turn on or off refresh messages) + // + + if( Command == "refresh" && !m_CountDownStarted ) + { + if( Payload == "on" ) + { + SendAllChat( m_GHost->m_Language->RefreshMessagesEnabled( ) ); + m_RefreshMessages = true; + } + else if( Payload == "off" ) + { + SendAllChat( m_GHost->m_Language->RefreshMessagesDisabled( ) ); + m_RefreshMessages = false; + } + } + + // + // !SAY + // + + if( Command == "say" && !Payload.empty( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + (*i)->QueueChatCommand( Payload ); + + HideCommand = true; + } + + // + // !SENDLAN + // + + if( Command == "sendlan" && !Payload.empty( ) && !m_CountDownStarted ) + { + // extract the ip and the port + // e.g. "1.2.3.4 6112" -> ip: "1.2.3.4", port: "6112" + + string IP; + uint32_t Port = 6112; + stringstream SS; + SS << Payload; + SS >> IP; + + if( !SS.eof( ) ) + SS >> Port; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad inputs to sendlan command" ); + else + { + // we send 12 for SlotsTotal because this determines how many PID's Warcraft 3 allocates + // we need to make sure Warcraft 3 allocates at least SlotsTotal + 1 but at most 12 PID's + // this is because we need an extra PID for the virtual host player (but we always delete the virtual host player when the 12th person joins) + // however, we can't send 13 for SlotsTotal because this causes Warcraft 3 to crash when sharing control of units + // nor can we send SlotsTotal because then Warcraft 3 crashes when playing maps with less than 12 PID's (because of the virtual host player taking an extra PID) + // we also send 12 for SlotsOpen because Warcraft 3 assumes there's always at least one player in the game (the host) + // so if we try to send accurate numbers it'll always be off by one and results in Warcraft 3 assuming the game is full when it still needs one more player + // the easiest solution is to simply send 12 for both so the game will always show up as (1/12) players + + if( m_SaveGame ) + { + // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect) + + uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME; + BYTEARRAY MapWidth; + MapWidth.push_back( 0 ); + MapWidth.push_back( 0 ); + BYTEARRAY MapHeight; + MapHeight.push_back( 0 ); + MapHeight.push_back( 0 ); + m_GHost->m_UDPSocket->SendTo( IP, Port, m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, "Save\\Multiplayer\\" + m_SaveGame->GetFileNameNoPath( ), m_SaveGame->GetMagicNumber( ), 12, 12, m_HostPort, m_HostCounter ) ); + } + else + { + // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect) + // note: we do not use m_Map->GetMapGameType because none of the filters are set when broadcasting to LAN (also as you might expect) + + uint32_t MapGameType = MAPGAMETYPE_UNKNOWN0; + m_GHost->m_UDPSocket->SendTo( IP, Port, m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), m_Map->GetMapWidth( ), m_Map->GetMapHeight( ), m_GameName, "Varlock", GetTime( ) - m_CreationTime, m_Map->GetMapPath( ), m_Map->GetMapCRC( ), 12, 12, m_HostPort, m_HostCounter ) ); + } + } + } + + // + // !SP + // + + if( Command == "sp" && !m_CountDownStarted ) + { + SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) ); + ShuffleSlots( ); + } + + // + // !START + // + + if( Command == "start" && !m_CountDownStarted ) + { + // if the player sent "!start force" skip the checks and start the countdown + // otherwise check that the game is ready to start + + if( Payload == "force" ) + StartCountDown( true ); + else + { + if( GetTicks( ) - m_LastPlayerLeaveTicks >= 2000 ) + StartCountDown( false ); + else + SendAllChat( m_GHost->m_Language->CountDownAbortedSomeoneLeftRecently( ) ); + } + } + + // + // !SWAP (swap slots) + // + + if( Command == "swap" && !Payload.empty( ) && !m_GameLoading && !m_GameLoaded ) + { + uint32_t SID1; + uint32_t SID2; + stringstream SS; + SS << Payload; + SS >> SID1; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #1 to swap command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] missing input #2 to swap command" ); + else + { + SS >> SID2; + + if( SS.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] bad input #2 to swap command" ); + else + SwapSlots( (unsigned char)( SID1 - 1 ), (unsigned char)( SID2 - 1 ) ); + } + } + } + + // + // !SYNCLIMIT + // + + if( Command == "synclimit" ) + { + if( Payload.empty( ) ) + SendAllChat( m_GHost->m_Language->SyncLimitIs( UTIL_ToString( m_SyncLimit ) ) ); + else + { + m_SyncLimit = UTIL_ToUInt32( Payload ); + + if( m_SyncLimit <= 10 ) + { + m_SyncLimit = 10; + SendAllChat( m_GHost->m_Language->SettingSyncLimitToMinimum( "10" ) ); + } + else if( m_SyncLimit >= 10000 ) + { + m_SyncLimit = 10000; + SendAllChat( m_GHost->m_Language->SettingSyncLimitToMaximum( "10000" ) ); + } + else + SendAllChat( m_GHost->m_Language->SettingSyncLimitTo( UTIL_ToString( m_SyncLimit ) ) ); + } + } + + // + // !UNHOST + // + + if( Command == "unhost" && !m_CountDownStarted ) + m_Exiting = true; + + // + // !UNLOCK + // + + if( Command == "unlock" && ( RootAdminCheck || IsOwner( User ) ) ) + { + SendAllChat( m_GHost->m_Language->GameUnlocked( ) ); + m_Locked = false; + } + + // + // !UNMUTE + // + + if( Command == "unmute" ) + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendAllChat( m_GHost->m_Language->UnableToMuteNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + SendAllChat( m_GHost->m_Language->UnmutedPlayer( LastMatch->GetName( ), User ) ); + LastMatch->SetMuted( false ); + } + else + SendAllChat( m_GHost->m_Language->UnableToMuteFoundMoreThanOneMatch( Payload ) ); + } + + // + // !UNMUTEALL + // + + if( Command == "unmuteall" && m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->GlobalChatUnmuted( ) ); + m_MuteAll = false; + } + + // + // !VIRTUALHOST + // + + if( Command == "virtualhost" && !Payload.empty( ) && Payload.size( ) <= 15 && !m_CountDownStarted ) + { + DeleteVirtualHost( ); + m_VirtualHostName = Payload; + } + + // + // !VOTECANCEL + // + + if( Command == "votecancel" && !m_KickVotePlayer.empty( ) ) + { + SendAllChat( m_GHost->m_Language->VoteKickCancelled( m_KickVotePlayer ) ); + m_KickVotePlayer.clear( ); + m_StartedKickVoteTime = 0; + } + + // + // !W + // + + if( Command == "w" && !Payload.empty( ) ) + { + // extract the name and the message + // e.g. "Varlock hello there!" -> name: "Varlock", message: "hello there!" + + string Name; + string Message; + string :: size_type MessageStart = Payload.find( " " ); + + if( MessageStart != string :: npos ) + { + Name = Payload.substr( 0, MessageStart ); + Message = Payload.substr( MessageStart + 1 ); + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + (*i)->QueueChatCommand( Message, Name, true ); + } + + HideCommand = true; + } + } + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] admin command ignored, the game is locked" ); + SendChat( player, m_GHost->m_Language->TheGameIsLocked( ) ); + } + } + else + { + if( !player->GetSpoofed( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] non-spoofchecked user [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); + else + CONSOLE_Print( "[GAME: " + m_GameName + "] non-admin [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); + } + + /********************* + * NON ADMIN COMMANDS * + *********************/ + + // + // !CHECKME + // + + if( Command == "checkme" ) + SendChat( player, m_GHost->m_Language->CheckedPlayer( User, player->GetNumPings( ) > 0 ? UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) + "ms" : "N/A", m_GHost->m_DBLocal->FromCheck( UTIL_ByteArrayToUInt32( player->GetExternalIP( ), true ) ), AdminCheck || RootAdminCheck ? "Yes" : "No", IsOwner( User ) ? "Yes" : "No", player->GetSpoofed( ) ? "Yes" : "No", player->GetSpoofedRealm( ).empty( ) ? "N/A" : player->GetSpoofedRealm( ), player->GetReserved( ) ? "Yes" : "No" ) ); + + // + // !STATS + // + + if( Command == "stats" && GetTime( ) - player->GetStatsSentTime( ) >= 5 ) + { + string StatsUser = User; + + if( !Payload.empty( ) ) + StatsUser = Payload; + + if( player->GetSpoofed( ) && ( AdminCheck || RootAdminCheck || IsOwner( User ) ) ) + m_PairedGPSChecks.push_back( PairedGPSCheck( string( ), m_GHost->m_DB->ThreadedGamePlayerSummaryCheck( StatsUser ) ) ); + else + m_PairedGPSChecks.push_back( PairedGPSCheck( User, m_GHost->m_DB->ThreadedGamePlayerSummaryCheck( StatsUser ) ) ); + + player->SetStatsSentTime( GetTime( ) ); + } + + // + // !STATSDOTA + // + + if( Command == "statsdota" && GetTime( ) - player->GetStatsDotASentTime( ) >= 5 ) + { + string StatsUser = User; + + if( !Payload.empty( ) ) + StatsUser = Payload; + + if( player->GetSpoofed( ) && ( AdminCheck || RootAdminCheck || IsOwner( User ) ) ) + m_PairedDPSChecks.push_back( PairedDPSCheck( string( ), m_GHost->m_DB->ThreadedDotAPlayerSummaryCheck( StatsUser ) ) ); + else + m_PairedDPSChecks.push_back( PairedDPSCheck( User, m_GHost->m_DB->ThreadedDotAPlayerSummaryCheck( StatsUser ) ) ); + + player->SetStatsDotASentTime( GetTime( ) ); + } + + // + // !VERSION + // + + if( Command == "version" ) + { + if( player->GetSpoofed( ) && ( AdminCheck || RootAdminCheck || IsOwner( User ) ) ) + SendChat( player, m_GHost->m_Language->VersionAdmin( m_GHost->m_Version ) ); + else + SendChat( player, m_GHost->m_Language->VersionNotAdmin( m_GHost->m_Version ) ); + } + + // + // !VOTEKICK + // + + if( Command == "votekick" && m_GHost->m_VoteKickAllowed && !Payload.empty( ) ) + { + if( !m_KickVotePlayer.empty( ) ) + SendChat( player, m_GHost->m_Language->UnableToVoteKickAlreadyInProgress( ) ); + else if( m_Players.size( ) == 2 ) + SendChat( player, m_GHost->m_Language->UnableToVoteKickNotEnoughPlayers( ) ); + else + { + CGamePlayer *LastMatch = NULL; + uint32_t Matches = GetPlayerFromNamePartial( Payload, &LastMatch ); + + if( Matches == 0 ) + SendChat( player, m_GHost->m_Language->UnableToVoteKickNoMatchesFound( Payload ) ); + else if( Matches == 1 ) + { + if( LastMatch->GetReserved( ) ) + SendChat( player, m_GHost->m_Language->UnableToVoteKickPlayerIsReserved( LastMatch->GetName( ) ) ); + else + { + m_KickVotePlayer = LastMatch->GetName( ); + m_StartedKickVoteTime = GetTime( ); + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + (*i)->SetKickVote( false ); + + player->SetKickVote( true ); + CONSOLE_Print( "[GAME: " + m_GameName + "] votekick against player [" + m_KickVotePlayer + "] started by player [" + User + "]" ); + SendAllChat( m_GHost->m_Language->StartedVoteKick( LastMatch->GetName( ), User, UTIL_ToString( (uint32_t)ceil( ( GetNumHumanPlayers( ) - 1 ) * (float)m_GHost->m_VoteKickPercentage / 100 ) - 1 ) ) ); + SendAllChat( m_GHost->m_Language->TypeYesToVote( string( 1, m_GHost->m_CommandTrigger ) ) ); + } + } + else + SendChat( player, m_GHost->m_Language->UnableToVoteKickFoundMoreThanOneMatch( Payload ) ); + } + } + + // + // !YES + // + + if( Command == "yes" && !m_KickVotePlayer.empty( ) && player->GetName( ) != m_KickVotePlayer && !player->GetKickVote( ) ) + { + player->SetKickVote( true ); + uint32_t VotesNeeded = (uint32_t)ceil( ( GetNumHumanPlayers( ) - 1 ) * (float)m_GHost->m_VoteKickPercentage / 100 ); + uint32_t Votes = 0; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetKickVote( ) ) + Votes++; + } + + if( Votes >= VotesNeeded ) + { + CGamePlayer *Victim = GetPlayerFromName( m_KickVotePlayer, true ); + + if( Victim ) + { + Victim->SetDeleteMe( true ); + Victim->SetLeftReason( m_GHost->m_Language->WasKickedByVote( ) ); + + if( !m_GameLoading && !m_GameLoaded ) + Victim->SetLeftCode( PLAYERLEAVE_LOBBY ); + else + Victim->SetLeftCode( PLAYERLEAVE_LOST ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( Victim->GetPID( ) ), false ); + + CONSOLE_Print( "[GAME: " + m_GameName + "] votekick against player [" + m_KickVotePlayer + "] passed with " + UTIL_ToString( Votes ) + "/" + UTIL_ToString( GetNumHumanPlayers( ) ) + " votes" ); + SendAllChat( m_GHost->m_Language->VoteKickPassed( m_KickVotePlayer ) ); + } + else + SendAllChat( m_GHost->m_Language->ErrorVoteKickingPlayer( m_KickVotePlayer ) ); + + m_KickVotePlayer.clear( ); + m_StartedKickVoteTime = 0; + } + else + SendAllChat( m_GHost->m_Language->VoteKickAcceptedNeedMoreVotes( m_KickVotePlayer, User, UTIL_ToString( VotesNeeded - Votes ) ) ); + } + + return HideCommand; +} + +void CGame :: EventGameStarted( ) +{ + CBaseGame :: EventGameStarted( ); + + // record everything we need to ban each player in case we decide to do so later + // this is because when a player leaves the game an admin might want to ban that player + // but since the player has already left the game we don't have access to their information anymore + // so we create a "potential ban" for each player and only store it in the database if requested to by an admin + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + m_DBBans.push_back( new CDBBan( (*i)->GetJoinedRealm( ), (*i)->GetName( ), (*i)->GetExternalIPString( ), string( ), string( ), string( ), string( ) ) ); +} + +bool CGame :: IsGameDataSaved( ) +{ + return m_CallableGameAdd && m_CallableGameAdd->GetReady( ); +} + +void CGame :: SaveGameData( ) +{ + CONSOLE_Print( "[GAME: " + m_GameName + "] saving game data to database" ); + m_CallableGameAdd = m_GHost->m_DB->ThreadedGameAdd( m_GHost->m_BNETs.size( ) == 1 ? m_GHost->m_BNETs[0]->GetServer( ) : string( ), m_DBGame->GetMap( ), m_GameName, m_OwnerName, m_GameTicks / 1000, m_GameState, m_CreatorName, m_CreatorServer ); +} diff --git a/ghost-legacy/game.h b/ghost-legacy/game.h new file mode 100644 index 0000000..a3b0904 --- /dev/null +++ b/ghost-legacy/game.h @@ -0,0 +1,70 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAME_H +#define GAME_H + +// +// CGame +// + +class CDBBan; +class CDBGame; +class CDBGamePlayer; +class CStats; +class CCallableBanCheck; +class CCallableBanAdd; +class CCallableGameAdd; +class CCallableGamePlayerSummaryCheck; +class CCallableDotAPlayerSummaryCheck; + +typedef pair PairedBanCheck; +typedef pair PairedBanAdd; +typedef pair PairedGPSCheck; +typedef pair PairedDPSCheck; + +class CGame : public CBaseGame +{ +protected: + CDBBan *m_DBBanLast; // last ban for the !banlast command - this is a pointer to one of the items in m_DBBans + vector m_DBBans; // vector of potential ban data for the database (see the Update function for more info, it's not as straightforward as you might think) + CDBGame *m_DBGame; // potential game data for the database + vector m_DBGamePlayers; // vector of potential gameplayer data for the database + CStats *m_Stats; // class to keep track of game stats such as kills/deaths/assists in dota + CCallableGameAdd *m_CallableGameAdd; // threaded database game addition in progress + vector m_PairedBanChecks; // vector of paired threaded database ban checks in progress + vector m_PairedBanAdds; // vector of paired threaded database ban adds in progress + vector m_PairedGPSChecks; // vector of paired threaded database game player summary checks in progress + vector m_PairedDPSChecks; // vector of paired threaded database DotA player summary checks in progress + +public: + CGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ); + virtual ~CGame( ); + + virtual bool Update( void *fd, void *send_fd ); + virtual void EventPlayerDeleted( CGamePlayer *player ); + virtual void EventPlayerAction( CGamePlayer *player, CIncomingAction *action ); + virtual bool EventPlayerBotCommand( CGamePlayer *player, string command, string payload ); + virtual void EventGameStarted( ); + virtual bool IsGameDataSaved( ); + virtual void SaveGameData( ); +}; + +#endif diff --git a/ghost-legacy/game_admin.cpp b/ghost-legacy/game_admin.cpp new file mode 100644 index 0000000..547711e --- /dev/null +++ b/ghost-legacy/game_admin.cpp @@ -0,0 +1,1305 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "ghostdb.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "replay.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "game_base.h" +#include "game_admin.h" + +#include + +#include + +using namespace boost :: filesystem; + +// +// CAdminGame +// + +CAdminGame :: CAdminGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nPassword ) : CBaseGame( nGHost, nMap, nSaveGame, nHostPort, nGameState, nGameName, string( ), string( ), string( ) ) +{ + m_VirtualHostName = "|cFFC04040Admin"; + m_MuteLobby = true; + m_Password = nPassword; +} + +CAdminGame :: ~CAdminGame( ) +{ + for( vector :: iterator i = m_PairedAdminCounts.begin( ); i != m_PairedAdminCounts.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedAdminAdds.begin( ); i != m_PairedAdminAdds.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedAdminRemoves.begin( ); i != m_PairedAdminRemoves.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + for( vector :: iterator i = m_PairedBanCounts.begin( ); i != m_PairedBanCounts.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + /* + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); + + */ + + for( vector :: iterator i = m_PairedBanRemoves.begin( ); i != m_PairedBanRemoves.end( ); i++ ) + m_GHost->m_Callables.push_back( i->second ); +} + +bool CAdminGame :: Update( void *fd, void *send_fd ) +{ + // + // update callables + // + + for( vector :: iterator i = m_PairedAdminCounts.begin( ); i != m_PairedAdminCounts.end( ); ) + { + if( i->second->GetReady( ) ) + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + uint32_t Count = i->second->GetResult( ); + + if( Count == 0 ) + SendChat( Player, m_GHost->m_Language->ThereAreNoAdmins( i->second->GetServer( ) ) ); + else if( Count == 1 ) + SendChat( Player, m_GHost->m_Language->ThereIsAdmin( i->second->GetServer( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ThereAreAdmins( i->second->GetServer( ), UTIL_ToString( Count ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminCounts.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedAdminAdds.begin( ); i != m_PairedAdminAdds.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + for( vector :: iterator j = m_GHost->m_BNETs.begin( ); j != m_GHost->m_BNETs.end( ); j++ ) + { + if( (*j)->GetServer( ) == i->second->GetServer( ) ) + (*j)->AddAdmin( i->second->GetUser( ) ); + } + } + + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + if( i->second->GetResult( ) ) + SendChat( Player, m_GHost->m_Language->AddedUserToAdminDatabase( i->second->GetServer( ), i->second->GetUser( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ErrorAddingUserToAdminDatabase( i->second->GetServer( ), i->second->GetUser( ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminAdds.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedAdminRemoves.begin( ); i != m_PairedAdminRemoves.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + for( vector :: iterator j = m_GHost->m_BNETs.begin( ); j != m_GHost->m_BNETs.end( ); j++ ) + { + if( (*j)->GetServer( ) == i->second->GetServer( ) ) + (*j)->RemoveAdmin( i->second->GetUser( ) ); + } + } + + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + if( i->second->GetResult( ) ) + SendChat( Player, m_GHost->m_Language->DeletedUserFromAdminDatabase( i->second->GetServer( ), i->second->GetUser( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ErrorDeletingUserFromAdminDatabase( i->second->GetServer( ), i->second->GetUser( ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedAdminRemoves.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_PairedBanCounts.begin( ); i != m_PairedBanCounts.end( ); ) + { + if( i->second->GetReady( ) ) + { + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + uint32_t Count = i->second->GetResult( ); + + if( Count == 0 ) + SendChat( Player, m_GHost->m_Language->ThereAreNoBannedUsers( i->second->GetServer( ) ) ); + else if( Count == 1 ) + SendChat( Player, m_GHost->m_Language->ThereIsBannedUser( i->second->GetServer( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ThereAreBannedUsers( i->second->GetServer( ), UTIL_ToString( Count ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanCounts.erase( i ); + } + else + i++; + } + + /* + + for( vector :: iterator i = m_PairedBanAdds.begin( ); i != m_PairedBanAdds.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + for( vector :: iterator j = m_GHost->m_BNETs.begin( ); j != m_GHost->m_BNETs.end( ); j++ ) + { + if( (*j)->GetServer( ) == i->second->GetServer( ) ) + (*j)->AddBan( i->second->GetUser( ), i->second->GetIP( ), i->second->GetGameName( ), i->second->GetAdmin( ), i->second->GetReason( ) ); + } + } + + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + if( i->second->GetResult( ) ) + SendChat( Player, m_GHost->m_Language->BannedUser( i->second->GetServer( ), i->second->GetUser( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ErrorBanningUser( i->second->GetServer( ), i->second->GetUser( ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanAdds.erase( i ); + } + else + i++; + } + + */ + + for( vector :: iterator i = m_PairedBanRemoves.begin( ); i != m_PairedBanRemoves.end( ); ) + { + if( i->second->GetReady( ) ) + { + if( i->second->GetResult( ) ) + { + for( vector :: iterator j = m_GHost->m_BNETs.begin( ); j != m_GHost->m_BNETs.end( ); j++ ) + { + if( (*j)->GetServer( ) == i->second->GetServer( ) ) + (*j)->RemoveBan( i->second->GetUser( ) ); + } + } + + CGamePlayer *Player = GetPlayerFromName( i->first, true ); + + if( Player ) + { + if( i->second->GetResult( ) ) + SendChat( Player, m_GHost->m_Language->UnbannedUser( i->second->GetUser( ) ) ); + else + SendChat( Player, m_GHost->m_Language->ErrorUnbanningUser( i->second->GetUser( ) ) ); + } + + m_GHost->m_DB->RecoverCallable( i->second ); + delete i->second; + i = m_PairedBanRemoves.erase( i ); + } + else + i++; + } + + // reset the last reserved seen timer since the admin game should never be considered abandoned + + m_LastReservedSeen = GetTime( ); + return CBaseGame :: Update( fd, send_fd ); +} + +void CAdminGame :: SendAdminChat( string message ) +{ + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetLoggedIn( ) ) + SendChat( *i, message ); + } +} + +void CAdminGame :: SendWelcomeMessage( CGamePlayer *player ) +{ + SendChat( player, "GHost++ Admin Game http://www.codelain.com/" ); + SendChat( player, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" ); + SendChat( player, "Commands: addadmin, autohost, autohostmm, checkadmin" ); + SendChat( player, "Commands: checkban, countadmins, countbans, deladmin" ); + SendChat( player, "Commands: delban, disable, downloads, enable, end, enforcesg" ); + SendChat( player, "Commands: exit, getgame, getgames, hostsg, load, loadsg" ); + SendChat( player, "Commands: map, password, priv, privby, pub, pubby, quit" ); + SendChat( player, "Commands: reload, say, saygame, saygames, unban, unhost, w" ); +} + +void CAdminGame :: EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ) +{ + uint32_t Time = GetTime( ); + + for( vector :: iterator i = m_TempBans.begin( ); i != m_TempBans.end( ); ) + { + // remove old tempbans (after 5 seconds) + + if( Time - (*i).second >= 5 ) + i = m_TempBans.erase( i ); + else + { + if( (*i).first == potential->GetExternalIPString( ) ) + { + // tempbanned, goodbye + + potential->GetSocket( )->PutBytes( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_WRONGPASSWORD ) ); + potential->SetDeleteMe( true ); + CONSOLE_Print( "[ADMINGAME] player [" + joinPlayer->GetName( ) + "] at ip [" + (*i).first + "] is trying to join the game but is tempbanned" ); + return; + } + + i++; + } + } + + CBaseGame :: EventPlayerJoined( potential, joinPlayer ); +} + +bool CAdminGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ) +{ + CBaseGame :: EventPlayerBotCommand( player, command, payload ); + + // todotodo: don't be lazy + + string User = player->GetName( ); + string Command = command; + string Payload = payload; + + if( player->GetLoggedIn( ) ) + { + CONSOLE_Print( "[ADMINGAME] admin [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); + + /***************** + * ADMIN COMMANDS * + ******************/ + + // + // !ADDADMIN + // + + if( Command == "addadmin" && !Payload.empty( ) ) + { + // extract the name and the server + // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" + + string Name; + string Server; + stringstream SS; + SS << Payload; + SS >> Name; + + if( SS.eof( ) ) + { + if( m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + else + CONSOLE_Print( "[ADMINGAME] missing input #2 to addadmin command" ); + } + else + SS >> Server; + + if( !Server.empty( ) ) + { + string Servers; + bool FoundServer = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( Servers.empty( ) ) + Servers = (*i)->GetServer( ); + else + Servers += ", " + (*i)->GetServer( ); + + if( (*i)->GetServer( ) == Server ) + { + FoundServer = true; + + if( (*i)->IsAdmin( Name ) ) + SendChat( player, m_GHost->m_Language->UserIsAlreadyAnAdmin( Server, Name ) ); + else + m_PairedAdminAdds.push_back( PairedAdminAdd( player->GetName( ), m_GHost->m_DB->ThreadedAdminAdd( Server, Name ) ) ); + + break; + } + } + + if( !FoundServer ) + SendChat( player, m_GHost->m_Language->ValidServers( Servers ) ); + } + } + + // + // !AUTOHOST + // + + if( Command == "autohost" ) + { + if( Payload.empty( ) || Payload == "off" ) + { + SendChat( player, m_GHost->m_Language->AutoHostDisabled( ) ); + m_GHost->m_AutoHostGameName.clear( ); + m_GHost->m_AutoHostOwner.clear( ); + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = 0; + m_GHost->m_AutoHostAutoStartPlayers = 0; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + else + { + // extract the maximum games, auto start players, and the game name + // e.g. "5 10 BattleShips Pro" -> maximum games: "5", auto start players: "10", game name: "BattleShips Pro" + + uint32_t MaximumGames; + uint32_t AutoStartPlayers; + string GameName; + stringstream SS; + SS << Payload; + SS >> MaximumGames; + + if( SS.fail( ) || MaximumGames == 0 ) + CONSOLE_Print( "[ADMINGAME] bad input #1 to autohost command" ); + else + { + SS >> AutoStartPlayers; + + if( SS.fail( ) || AutoStartPlayers == 0 ) + CONSOLE_Print( "[ADMINGAME] bad input #2 to autohost command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[ADMINGAME] missing input #3 to autohost command" ); + else + { + getline( SS, GameName ); + string :: size_type Start = GameName.find_first_not_of( " " ); + + if( Start != string :: npos ) + GameName = GameName.substr( Start ); + + SendChat( player, m_GHost->m_Language->AutoHostEnabled( ) ); + delete m_GHost->m_AutoHostMap; + m_GHost->m_AutoHostMap = new CMap( *m_GHost->m_Map ); + m_GHost->m_AutoHostGameName = GameName; + m_GHost->m_AutoHostOwner = User; + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = MaximumGames; + m_GHost->m_AutoHostAutoStartPlayers = AutoStartPlayers; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + } + } + } + } + + // + // !AUTOHOSTMM + // + + if( Command == "autohostmm" ) + { + if( Payload.empty( ) || Payload == "off" ) + { + SendChat( player, m_GHost->m_Language->AutoHostDisabled( ) ); + m_GHost->m_AutoHostGameName.clear( ); + m_GHost->m_AutoHostOwner.clear( ); + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = 0; + m_GHost->m_AutoHostAutoStartPlayers = 0; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = false; + m_GHost->m_AutoHostMinimumScore = 0.0; + m_GHost->m_AutoHostMaximumScore = 0.0; + } + else + { + // extract the maximum games, auto start players, and the game name + // e.g. "5 10 800 1200 BattleShips Pro" -> maximum games: "5", auto start players: "10", minimum score: "800", maximum score: "1200", game name: "BattleShips Pro" + + uint32_t MaximumGames; + uint32_t AutoStartPlayers; + double MinimumScore; + double MaximumScore; + string GameName; + stringstream SS; + SS << Payload; + SS >> MaximumGames; + + if( SS.fail( ) || MaximumGames == 0 ) + CONSOLE_Print( "[ADMINGAME] bad input #1 to autohostmm command" ); + else + { + SS >> AutoStartPlayers; + + if( SS.fail( ) || AutoStartPlayers == 0 ) + CONSOLE_Print( "[ADMINGAME] bad input #2 to autohostmm command" ); + else + { + SS >> MinimumScore; + + if( SS.fail( ) ) + CONSOLE_Print( "[ADMINGAME] bad input #3 to autohostmm command" ); + else + { + SS >> MaximumScore; + + if( SS.fail( ) ) + CONSOLE_Print( "[ADMINGAME] bad input #4 to autohostmm command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[ADMINGAME] missing input #5 to autohostmm command" ); + else + { + getline( SS, GameName ); + string :: size_type Start = GameName.find_first_not_of( " " ); + + if( Start != string :: npos ) + GameName = GameName.substr( Start ); + + SendChat( player, m_GHost->m_Language->AutoHostEnabled( ) ); + delete m_GHost->m_AutoHostMap; + m_GHost->m_AutoHostMap = new CMap( *m_GHost->m_Map ); + m_GHost->m_AutoHostGameName = GameName; + m_GHost->m_AutoHostOwner = User; + m_GHost->m_AutoHostServer.clear( ); + m_GHost->m_AutoHostMaximumGames = MaximumGames; + m_GHost->m_AutoHostAutoStartPlayers = AutoStartPlayers; + m_GHost->m_LastAutoHostTime = GetTime( ); + m_GHost->m_AutoHostMatchMaking = true; + m_GHost->m_AutoHostMinimumScore = MinimumScore; + m_GHost->m_AutoHostMaximumScore = MaximumScore; + } + } + } + } + } + } + } + + // + // !CHECKADMIN + // + + if( Command == "checkadmin" && !Payload.empty( ) ) + { + // extract the name and the server + // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" + + string Name; + string Server; + stringstream SS; + SS << Payload; + SS >> Name; + + if( SS.eof( ) ) + { + if( m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + else + CONSOLE_Print( "[ADMINGAME] missing input #2 to checkadmin command" ); + } + else + SS >> Server; + + if( !Server.empty( ) ) + { + string Servers; + bool FoundServer = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( Servers.empty( ) ) + Servers = (*i)->GetServer( ); + else + Servers += ", " + (*i)->GetServer( ); + + if( (*i)->GetServer( ) == Server ) + { + FoundServer = true; + + if( (*i)->IsAdmin( Name ) ) + SendChat( player, m_GHost->m_Language->UserIsAnAdmin( Server, Name ) ); + else + SendChat( player, m_GHost->m_Language->UserIsNotAnAdmin( Server, Name ) ); + + break; + } + } + + if( !FoundServer ) + SendChat( player, m_GHost->m_Language->ValidServers( Servers ) ); + } + } + + // + // !CHECKBAN + // + + if( Command == "checkban" && !Payload.empty( ) ) + { + // extract the name and the server + // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" + + string Name; + string Server; + stringstream SS; + SS << Payload; + SS >> Name; + + if( SS.eof( ) ) + { + if( m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + else + CONSOLE_Print( "[ADMINGAME] missing input #2 to checkban command" ); + } + else + SS >> Server; + + if( !Server.empty( ) ) + { + string Servers; + bool FoundServer = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( Servers.empty( ) ) + Servers = (*i)->GetServer( ); + else + Servers += ", " + (*i)->GetServer( ); + + if( (*i)->GetServer( ) == Server ) + { + FoundServer = true; + CDBBan *Ban = (*i)->IsBannedName( Name ); + + if( Ban ) + SendChat( player, m_GHost->m_Language->UserWasBannedOnByBecause( Server, Name, Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + else + SendChat( player, m_GHost->m_Language->UserIsNotBanned( Server, Name ) ); + + break; + } + } + + if( !FoundServer ) + SendChat( player, m_GHost->m_Language->ValidServers( Servers ) ); + } + } + + // + // !COUNTADMINS + // + + if( Command == "countadmins" ) + { + string Server = Payload; + + if( Server.empty( ) && m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + + if( !Server.empty( ) ) + m_PairedAdminCounts.push_back( PairedAdminCount( player->GetName( ), m_GHost->m_DB->ThreadedAdminCount( Server ) ) ); + } + + // + // !COUNTBANS + // + + if( Command == "countbans" ) + { + string Server = Payload; + + if( Server.empty( ) && m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + + if( !Server.empty( ) ) + m_PairedBanCounts.push_back( PairedBanCount( player->GetName( ), m_GHost->m_DB->ThreadedBanCount( Server ) ) ); + } + + // + // !DELADMIN + // + + if( Command == "deladmin" && !Payload.empty( ) ) + { + // extract the name and the server + // e.g. "Varlock useast.battle.net" -> name: "Varlock", server: "useast.battle.net" + + string Name; + string Server; + stringstream SS; + SS << Payload; + SS >> Name; + + if( SS.eof( ) ) + { + if( m_GHost->m_BNETs.size( ) == 1 ) + Server = m_GHost->m_BNETs[0]->GetServer( ); + else + CONSOLE_Print( "[ADMINGAME] missing input #2 to deladmin command" ); + } + else + SS >> Server; + + if( !Server.empty( ) ) + { + string Servers; + bool FoundServer = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( Servers.empty( ) ) + Servers = (*i)->GetServer( ); + else + Servers += ", " + (*i)->GetServer( ); + + if( (*i)->GetServer( ) == Server ) + { + FoundServer = true; + + if( !(*i)->IsAdmin( Name ) ) + SendChat( player, m_GHost->m_Language->UserIsNotAnAdmin( Server, Name ) ); + else + m_PairedAdminRemoves.push_back( PairedAdminRemove( player->GetName( ), m_GHost->m_DB->ThreadedAdminRemove( Server, Name ) ) ); + + break; + } + } + + if( !FoundServer ) + SendChat( player, m_GHost->m_Language->ValidServers( Servers ) ); + } + } + + // + // !DELBAN + // !UNBAN + // + + if( ( Command == "delban" || Command == "unban" ) && !Payload.empty( ) ) + m_PairedBanRemoves.push_back( PairedBanRemove( player->GetName( ), m_GHost->m_DB->ThreadedBanRemove( Payload ) ) ); + + // + // !DISABLE + // + + if( Command == "disable" ) + { + SendChat( player, m_GHost->m_Language->BotDisabled( ) ); + m_GHost->m_Enabled = false; + } + + // + // !DOWNLOADS + // + + if( Command == "downloads" && !Payload.empty( ) ) + { + uint32_t Downloads = UTIL_ToUInt32( Payload ); + + if( Downloads == 0 ) + { + SendChat( player, m_GHost->m_Language->MapDownloadsDisabled( ) ); + m_GHost->m_AllowDownloads = 0; + } + else if( Downloads == 1 ) + { + SendChat( player, m_GHost->m_Language->MapDownloadsEnabled( ) ); + m_GHost->m_AllowDownloads = 1; + } + else if( Downloads == 2 ) + { + SendChat( player, m_GHost->m_Language->MapDownloadsConditional( ) ); + m_GHost->m_AllowDownloads = 2; + } + } + + // + // !ENABLE + // + + if( Command == "enable" ) + { + SendChat( player, m_GHost->m_Language->BotEnabled( ) ); + m_GHost->m_Enabled = true; + } + + // + // !END + // + + if( Command == "end" && !Payload.empty( ) ) + { + // todotodo: what if a game ends just as you're typing this command and the numbering changes? + + uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; + + if( GameNumber < m_GHost->m_Games.size( ) ) + { + SendChat( player, m_GHost->m_Language->EndingGame( m_GHost->m_Games[GameNumber]->GetDescription( ) ) ); + CONSOLE_Print( "[GAME: " + m_GHost->m_Games[GameNumber]->GetGameName( ) + "] is over (admin ended game)" ); + m_GHost->m_Games[GameNumber]->StopPlayers( "was disconnected (admin ended game)" ); + } + else + SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( Payload ) ); + } + + // + // !ENFORCESG + // + + if( Command == "enforcesg" && !Payload.empty( ) ) + { + // only load files in the current directory just to be safe + + if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) + SendChat( player, m_GHost->m_Language->UnableToLoadReplaysOutside( ) ); + else + { + string File = m_GHost->m_ReplayPath + Payload + ".w3g"; + + if( UTIL_FileExists( File ) ) + { + SendChat( player, m_GHost->m_Language->LoadingReplay( File ) ); + CReplay *Replay = new CReplay( ); + Replay->Load( File, false ); + Replay->ParseReplay( false ); + m_GHost->m_EnforcePlayers = Replay->GetPlayers( ); + delete Replay; + } + else + SendChat( player, m_GHost->m_Language->UnableToLoadReplayDoesntExist( File ) ); + } + } + + // + // !EXIT + // !QUIT + // + + if( Command == "exit" || Command == "quit" ) + { + if( Payload == "nice" ) + m_GHost->m_ExitingNice = true; + else if( Payload == "force" ) + m_Exiting = true; + else + { + if( m_GHost->m_CurrentGame || !m_GHost->m_Games.empty( ) ) + SendChat( player, m_GHost->m_Language->AtLeastOneGameActiveUseForceToShutdown( ) ); + else + m_Exiting = true; + } + } + + // + // !GETGAME + // + + if( Command == "getgame" && !Payload.empty( ) ) + { + uint32_t GameNumber = UTIL_ToUInt32( Payload ) - 1; + + if( GameNumber < m_GHost->m_Games.size( ) ) + SendChat( player, m_GHost->m_Language->GameNumberIs( Payload, m_GHost->m_Games[GameNumber]->GetDescription( ) ) ); + else + SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( Payload ) ); + } + + // + // !GETGAMES + // + + if( Command == "getgames" ) + { + if( m_GHost->m_CurrentGame ) + SendChat( player, m_GHost->m_Language->GameIsInTheLobby( m_GHost->m_CurrentGame->GetDescription( ), UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ) ); + else + SendChat( player, m_GHost->m_Language->ThereIsNoGameInTheLobby( UTIL_ToString( m_GHost->m_Games.size( ) ), UTIL_ToString( m_GHost->m_MaxGames ) ) ); + } + + // + // !HOSTSG + // + + if( Command == "hostsg" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, true, Payload, User, User, string( ), false ); + + // + // !LOAD (load config file) + // + + if( Command == "load" ) + { + if( Payload.empty( ) ) + SendChat( player, m_GHost->m_Language->CurrentlyLoadedMapCFGIs( m_GHost->m_Map->GetCFGFile( ) ) ); + else + { + string FoundMapConfigs; + + try + { + path MapCFGPath( m_GHost->m_MapCFGPath ); + string Pattern = Payload; + transform( Pattern.begin( ), Pattern.end( ), Pattern.begin( ), (int(*)(int))tolower ); + + if( !exists( MapCFGPath ) ) + { + CONSOLE_Print( "[ADMINGAME] error listing map configs - map config path doesn't exist" ); + SendChat( player, m_GHost->m_Language->ErrorListingMapConfigs( ) ); + } + else + { + directory_iterator EndIterator; + path LastMatch; + uint32_t Matches = 0; + + for( directory_iterator i( MapCFGPath ); i != EndIterator; i++ ) + { + string FileName = i->filename( ); + string Stem = i->path( ).stem( ); + transform( FileName.begin( ), FileName.end( ), FileName.begin( ), (int(*)(int))tolower ); + transform( Stem.begin( ), Stem.end( ), Stem.begin( ), (int(*)(int))tolower ); + + if( !is_directory( i->status( ) ) && i->path( ).extension( ) == ".cfg" && FileName.find( Pattern ) != string :: npos ) + { + LastMatch = i->path( ); + Matches++; + + if( FoundMapConfigs.empty( ) ) + FoundMapConfigs = i->filename( ); + else + FoundMapConfigs += ", " + i->filename( ); + + // if the pattern matches the filename exactly, with or without extension, stop any further matching + + if( FileName == Pattern || Stem == Pattern ) + { + Matches = 1; + break; + } + } + } + + if( Matches == 0 ) + SendChat( player, m_GHost->m_Language->NoMapConfigsFound( ) ); + else if( Matches == 1 ) + { + string File = LastMatch.filename( ); + SendChat( player, m_GHost->m_Language->LoadingConfigFile( m_GHost->m_MapCFGPath + File ) ); + CConfig MapCFG; + MapCFG.Read( LastMatch.string( ) ); + m_GHost->m_Map->Load( &MapCFG, m_GHost->m_MapCFGPath + File ); + } + else + SendChat( player, m_GHost->m_Language->FoundMapConfigs( FoundMapConfigs ) ); + } + } + catch( const exception &ex ) + { + CONSOLE_Print( string( "[ADMINGAME] error listing map configs - caught exception [" ) + ex.what( ) + "]" ); + SendChat( player, m_GHost->m_Language->ErrorListingMapConfigs( ) ); + } + } + } + + // + // !LOADSG + // + + if( Command == "loadsg" && !Payload.empty( ) ) + { + // only load files in the current directory just to be safe + + if( Payload.find( "/" ) != string :: npos || Payload.find( "\\" ) != string :: npos ) + SendChat( player, m_GHost->m_Language->UnableToLoadSaveGamesOutside( ) ); + else + { + string File = m_GHost->m_SaveGamePath + Payload + ".w3z"; + string FileNoPath = Payload + ".w3z"; + + if( UTIL_FileExists( File ) ) + { + if( m_GHost->m_CurrentGame ) + SendChat( player, m_GHost->m_Language->UnableToLoadSaveGameGameInLobby( ) ); + else + { + SendChat( player, m_GHost->m_Language->LoadingSaveGame( File ) ); + m_GHost->m_SaveGame->Load( File, false ); + m_GHost->m_SaveGame->ParseSaveGame( ); + m_GHost->m_SaveGame->SetFileName( File ); + m_GHost->m_SaveGame->SetFileNameNoPath( FileNoPath ); + } + } + else + SendChat( player, m_GHost->m_Language->UnableToLoadSaveGameDoesntExist( File ) ); + } + } + + // + // !MAP (load map file) + // + + if( Command == "map" ) + { + if( Payload.empty( ) ) + SendChat( player, m_GHost->m_Language->CurrentlyLoadedMapCFGIs( m_GHost->m_Map->GetCFGFile( ) ) ); + else + { + string FoundMaps; + + try + { + path MapPath( m_GHost->m_MapPath ); + string Pattern = Payload; + transform( Pattern.begin( ), Pattern.end( ), Pattern.begin( ), (int(*)(int))tolower ); + + if( !exists( MapPath ) ) + { + CONSOLE_Print( "[ADMINGAME] error listing maps - map path doesn't exist" ); + SendChat( player, m_GHost->m_Language->ErrorListingMaps( ) ); + } + else + { + directory_iterator EndIterator; + path LastMatch; + uint32_t Matches = 0; + + for( directory_iterator i( MapPath ); i != EndIterator; i++ ) + { + string FileName = i->filename( ); + string Stem = i->path( ).stem( ); + transform( FileName.begin( ), FileName.end( ), FileName.begin( ), (int(*)(int))tolower ); + transform( Stem.begin( ), Stem.end( ), Stem.begin( ), (int(*)(int))tolower ); + + if( !is_directory( i->status( ) ) && FileName.find( Pattern ) != string :: npos ) + { + LastMatch = i->path( ); + Matches++; + + if( FoundMaps.empty( ) ) + FoundMaps = i->filename( ); + else + FoundMaps += ", " + i->filename( ); + + // if the pattern matches the filename exactly, with or without extension, stop any further matching + + if( FileName == Pattern || Stem == Pattern ) + { + Matches = 1; + break; + } + } + } + + if( Matches == 0 ) + SendChat( player, m_GHost->m_Language->NoMapsFound( ) ); + else if( Matches == 1 ) + { + string File = LastMatch.filename( ); + SendChat( player, m_GHost->m_Language->LoadingConfigFile( File ) ); + + // hackhack: create a config file in memory with the required information to load the map + + CConfig MapCFG; + MapCFG.Set( "map_path", "Maps\\Download\\" + File ); + MapCFG.Set( "map_localpath", File ); + m_GHost->m_Map->Load( &MapCFG, File ); + } + else + SendChat( player, m_GHost->m_Language->FoundMaps( FoundMaps ) ); + } + } + catch( const exception &ex ) + { + CONSOLE_Print( string( "[ADMINGAME] error listing maps - caught exception [" ) + ex.what( ) + "]" ); + SendChat( player, m_GHost->m_Language->ErrorListingMaps( ) ); + } + } + } + + // + // !PRIV (host private game) + // + + if( Command == "priv" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, false, Payload, User, User, string( ), false ); + + // + // !PRIVBY (host private game by other player) + // + + if( Command == "privby" && !Payload.empty( ) ) + { + // extract the owner and the game name + // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" + + string Owner; + string GameName; + string :: size_type GameNameStart = Payload.find( " " ); + + if( GameNameStart != string :: npos ) + { + Owner = Payload.substr( 0, GameNameStart ); + GameName = Payload.substr( GameNameStart + 1 ); + m_GHost->CreateGame( m_GHost->m_Map, GAME_PRIVATE, false, GameName, Owner, User, string( ), false ); + } + } + + // + // !PUB (host public game) + // + + if( Command == "pub" && !Payload.empty( ) ) + m_GHost->CreateGame( m_GHost->m_Map, GAME_PUBLIC, false, Payload, User, User, string( ), false ); + + // + // !PUBBY (host public game by other player) + // + + if( Command == "pubby" && !Payload.empty( ) ) + { + // extract the owner and the game name + // e.g. "Varlock dota 6.54b arem ~~~" -> owner: "Varlock", game name: "dota 6.54b arem ~~~" + + string Owner; + string GameName; + string :: size_type GameNameStart = Payload.find( " " ); + + if( GameNameStart != string :: npos ) + { + Owner = Payload.substr( 0, GameNameStart ); + GameName = Payload.substr( GameNameStart + 1 ); + m_GHost->CreateGame( m_GHost->m_Map, GAME_PUBLIC, false, GameName, Owner, User, string( ), false ); + } + } + + // + // !RELOAD + // + + if( Command == "reload" ) + { + SendChat( player, m_GHost->m_Language->ReloadingConfigurationFiles( ) ); + m_GHost->ReloadConfigs( ); + } + + // + // !SAY + // + + if( Command == "say" && !Payload.empty( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + (*i)->QueueChatCommand( Payload ); + } + + // + // !SAYGAME + // + + if( Command == "saygame" && !Payload.empty( ) ) + { + // extract the game number and the message + // e.g. "3 hello everyone" -> game number: "3", message: "hello everyone" + + uint32_t GameNumber; + string Message; + stringstream SS; + SS << Payload; + SS >> GameNumber; + + if( SS.fail( ) ) + CONSOLE_Print( "[ADMINGAME] bad input #1 to saygame command" ); + else + { + if( SS.eof( ) ) + CONSOLE_Print( "[ADMINGAME] missing input #2 to saygame command" ); + else + { + getline( SS, Message ); + string :: size_type Start = Message.find_first_not_of( " " ); + + if( Start != string :: npos ) + Message = Message.substr( Start ); + + if( GameNumber - 1 < m_GHost->m_Games.size( ) ) + m_GHost->m_Games[GameNumber - 1]->SendAllChat( "ADMIN: " + Message ); + else + SendChat( player, m_GHost->m_Language->GameNumberDoesntExist( UTIL_ToString( GameNumber ) ) ); + } + } + } + + // + // !SAYGAMES + // + + if( Command == "saygames" && !Payload.empty( ) ) + { + if( m_GHost->m_CurrentGame ) + m_GHost->m_CurrentGame->SendAllChat( Payload ); + + for( vector :: iterator i = m_GHost->m_Games.begin( ); i != m_GHost->m_Games.end( ); i++ ) + (*i)->SendAllChat( "ADMIN: " + Payload ); + } + + // + // !UNHOST + // + + if( Command == "unhost" ) + { + if( m_GHost->m_CurrentGame ) + { + if( m_GHost->m_CurrentGame->GetCountDownStarted( ) ) + SendChat( player, m_GHost->m_Language->UnableToUnhostGameCountdownStarted( m_GHost->m_CurrentGame->GetDescription( ) ) ); + else + { + SendChat( player, m_GHost->m_Language->UnhostingGame( m_GHost->m_CurrentGame->GetDescription( ) ) ); + m_GHost->m_CurrentGame->SetExiting( true ); + } + } + else + SendChat( player, m_GHost->m_Language->UnableToUnhostGameNoGameInLobby( ) ); + } + + // + // !W + // + + if( Command == "w" && !Payload.empty( ) ) + { + // extract the name and the message + // e.g. "Varlock hello there!" -> name: "Varlock", message: "hello there!" + + string Name; + string Message; + string :: size_type MessageStart = Payload.find( " " ); + + if( MessageStart != string :: npos ) + { + Name = Payload.substr( 0, MessageStart ); + Message = Payload.substr( MessageStart + 1 ); + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + (*i)->QueueChatCommand( Message, Name, true ); + } + } + } + else + CONSOLE_Print( "[ADMINGAME] user [" + User + "] sent command [" + Command + "] with payload [" + Payload + "]" ); + + /********************* + * NON ADMIN COMMANDS * + *********************/ + + // + // !PASSWORD + // + + if( Command == "password" && !player->GetLoggedIn( ) ) + { + if( !m_Password.empty( ) && Payload == m_Password ) + { + CONSOLE_Print( "[ADMINGAME] user [" + User + "] logged in" ); + SendChat( player, m_GHost->m_Language->AdminLoggedIn( ) ); + player->SetLoggedIn( true ); + } + else + { + uint32_t LoginAttempts = player->GetLoginAttempts( ) + 1; + player->SetLoginAttempts( LoginAttempts ); + CONSOLE_Print( "[ADMINGAME] user [" + User + "] login attempt failed" ); + SendChat( player, m_GHost->m_Language->AdminInvalidPassword( UTIL_ToString( LoginAttempts ) ) ); + + if( LoginAttempts >= 1 ) + { + player->SetDeleteMe( true ); + player->SetLeftReason( "was kicked for too many failed login attempts" ); + player->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); + + // tempban for 5 seconds to prevent bruteforcing + + m_TempBans.push_back( TempBan( player->GetExternalIPString( ), GetTime( ) ) ); + } + } + } + + // always hide chat commands from other players in the admin game + // note: this is actually redundant because we've already set m_MuteLobby = true so this has no effect + // if you actually wanted to relay chat commands you would have to set m_MuteLobby = false AND return false here + + return true; +} diff --git a/ghost-legacy/game_admin.h b/ghost-legacy/game_admin.h new file mode 100644 index 0000000..f59aa96 --- /dev/null +++ b/ghost-legacy/game_admin.h @@ -0,0 +1,67 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAME_ADMIN_H +#define GAME_ADMIN_H + +// +// CAdminGame +// + +class CCallableAdminCount; +class CCallableAdminAdd; +class CCallableAdminRemove; +class CCallableBanCount; +// class CCallableBanAdd; +class CCallableBanRemove; + +typedef pair PairedAdminCount; +typedef pair PairedAdminAdd; +typedef pair PairedAdminRemove; +typedef pair PairedBanCount; +// typedef pair PairedBanAdd; +typedef pair PairedBanRemove; + +typedef pair TempBan; + +class CAdminGame : public CBaseGame +{ +protected: + string m_Password; + vector m_TempBans; + vector m_PairedAdminCounts; // vector of paired threaded database admin counts in progress + vector m_PairedAdminAdds; // vector of paired threaded database admin adds in progress + vector m_PairedAdminRemoves; // vector of paired threaded database admin removes in progress + vector m_PairedBanCounts; // vector of paired threaded database ban counts in progress + // vector m_PairedBanAdds; // vector of paired threaded database ban adds in progress + vector m_PairedBanRemoves; // vector of paired threaded database ban removes in progress + +public: + CAdminGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nPassword ); + virtual ~CAdminGame( ); + + virtual bool Update( void *fd, void *send_fd ); + virtual void SendAdminChat( string message ); + virtual void SendWelcomeMessage( CGamePlayer *player ); + virtual void EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ); + virtual bool EventPlayerBotCommand( CGamePlayer *player, string command, string payload ); +}; + +#endif diff --git a/ghost-legacy/game_base.cpp b/ghost-legacy/game_base.cpp new file mode 100644 index 0000000..7e09da6 --- /dev/null +++ b/ghost-legacy/game_base.cpp @@ -0,0 +1,4622 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "ghostdb.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "replay.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "game_base.h" + +#include +#include +#include + +#include "next_combination.h" + +// +// CBaseGame +// + +CBaseGame :: CBaseGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ) +{ + m_GHost = nGHost; + m_Socket = new CTCPServer( ); + m_Protocol = new CGameProtocol( m_GHost ); + m_Map = new CMap( *nMap ); + m_SaveGame = nSaveGame; + + if( m_GHost->m_SaveReplays && !m_SaveGame ) + m_Replay = new CReplay( ); + else + m_Replay = NULL; + + m_Exiting = false; + m_Saving = false; + m_HostPort = nHostPort; + m_GameState = nGameState; + m_VirtualHostPID = 255; + m_FakePlayerPID = 255; + + // wait time of 1 minute = 0 empty actions required + // wait time of 2 minutes = 1 empty action required + // etc... + + if( m_GHost->m_ReconnectWaitTime == 0 ) + m_GProxyEmptyActions = 0; + else + { + m_GProxyEmptyActions = m_GHost->m_ReconnectWaitTime - 1; + + // clamp to 9 empty actions (10 minutes) + + if( m_GProxyEmptyActions > 9 ) + m_GProxyEmptyActions = 9; + } + + m_GameName = nGameName; + m_LastGameName = nGameName; + m_VirtualHostName = m_GHost->m_VirtualHostName; + m_OwnerName = nOwnerName; + m_CreatorName = nCreatorName; + m_CreatorServer = nCreatorServer; + m_HCLCommandString = m_Map->GetMapDefaultHCL( ); + m_RandomSeed = GetTicks( ); + m_HostCounter = m_GHost->m_HostCounter++; + m_Latency = m_GHost->m_Latency; + m_SyncLimit = m_GHost->m_SyncLimit; + m_SyncCounter = 0; + m_GameTicks = 0; + m_CreationTime = GetTime( ); + m_LastPingTime = GetTime( ); + m_LastRefreshTime = GetTime( ); + m_LastDownloadTicks = GetTime( ); + m_DownloadCounter = 0; + m_LastDownloadCounterResetTicks = GetTicks( ); + m_LastAnnounceTime = 0; + m_AnnounceInterval = 0; + m_LastAutoStartTime = GetTime( ); + m_AutoStartPlayers = 0; + m_LastCountDownTicks = 0; + m_CountDownCounter = 0; + m_StartedLoadingTicks = 0; + m_StartPlayers = 0; + m_LastLagScreenResetTime = 0; + m_LastActionSentTicks = 0; + m_LastActionLateBy = 0; + m_StartedLaggingTime = 0; + m_LastLagScreenTime = 0; + m_LastReservedSeen = GetTime( ); + m_StartedKickVoteTime = 0; + m_GameOverTime = 0; + m_LastPlayerLeaveTicks = 0; + m_MinimumScore = 0.0; + m_MaximumScore = 0.0; + m_SlotInfoChanged = false; + m_Locked = false; + m_RefreshMessages = m_GHost->m_RefreshMessages; + m_RefreshError = false; + m_RefreshRehosted = false; + m_MuteAll = false; + m_MuteLobby = false; + m_CountDownStarted = false; + m_GameLoading = false; + m_GameLoaded = false; + m_LoadInGame = m_Map->GetMapLoadInGame( ); + m_Lagging = false; + m_AutoSave = m_GHost->m_AutoSave; + m_MatchMaking = false; + m_LocalAdminMessages = m_GHost->m_LocalAdminMessages; + + if( m_SaveGame ) + { + m_EnforceSlots = m_SaveGame->GetSlots( ); + m_Slots = m_SaveGame->GetSlots( ); + + // the savegame slots contain player entries + // we really just want the open/closed/computer entries + // so open all the player slots + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 ) + { + (*i).SetPID( 0 ); + (*i).SetDownloadStatus( 255 ); + (*i).SetSlotStatus( SLOTSTATUS_OPEN ); + } + } + } + else + m_Slots = m_Map->GetSlots( ); + + if( !m_GHost->m_IPBlackListFile.empty( ) ) + { + ifstream in; + in.open( m_GHost->m_IPBlackListFile.c_str( ) ); + + if( in.fail( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] error loading IP blacklist file [" + m_GHost->m_IPBlackListFile + "]" ); + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] loading IP blacklist file [" + m_GHost->m_IPBlackListFile + "]" ); + string Line; + + while( !in.eof( ) ) + { + getline( in, Line ); + + // ignore blank lines and comments + + if( Line.empty( ) || Line[0] == '#' ) + continue; + + // remove newlines and partial newlines to help fix issues with Windows formatted files on Linux systems + + Line.erase( remove( Line.begin( ), Line.end( ), ' ' ), Line.end( ) ); + Line.erase( remove( Line.begin( ), Line.end( ), '\r' ), Line.end( ) ); + Line.erase( remove( Line.begin( ), Line.end( ), '\n' ), Line.end( ) ); + + // ignore lines that don't look like IP addresses + + if( Line.find_first_not_of( "1234567890." ) != string :: npos ) + continue; + + m_IPBlackList.insert( Line ); + } + + in.close( ); + + CONSOLE_Print( "[GAME: " + m_GameName + "] loaded " + UTIL_ToString( m_IPBlackList.size( ) ) + " lines from IP blacklist file" ); + } + } + + // start listening for connections + + if( !m_GHost->m_BindAddress.empty( ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] attempting to bind to address [" + m_GHost->m_BindAddress + "]" ); + else + CONSOLE_Print( "[GAME: " + m_GameName + "] attempting to bind to all available addresses" ); + + if( m_Socket->Listen( m_GHost->m_BindAddress, m_HostPort ) ) + CONSOLE_Print( "[GAME: " + m_GameName + "] listening on port " + UTIL_ToString( m_HostPort ) ); + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] error listening on port " + UTIL_ToString( m_HostPort ) ); + m_Exiting = true; + } +} + +CBaseGame :: ~CBaseGame( ) +{ + // save replay + // todotodo: put this in a thread + + if( m_Replay && ( m_GameLoading || m_GameLoaded ) ) + { + time_t Now = time( NULL ); + char Time[17]; + memset( Time, 0, sizeof( char ) * 17 ); + strftime( Time, sizeof( char ) * 17, "%Y-%m-%d %H-%M", localtime( &Now ) ); + string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ); + string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 ); + + if( MinString.size( ) == 1 ) + MinString.insert( 0, "0" ); + + if( SecString.size( ) == 1 ) + SecString.insert( 0, "0" ); + + m_Replay->BuildReplay( m_GameName, m_StatString, m_GHost->m_ReplayWar3Version, m_GHost->m_ReplayBuildNumber ); + m_Replay->Save( m_GHost->m_TFT, m_GHost->m_ReplayPath + UTIL_FileSafeName( "GHost++ " + string( Time ) + " " + m_GameName + " (" + MinString + "m" + SecString + "s).w3g" ) ); + } + + delete m_Socket; + delete m_Protocol; + delete m_Map; + delete m_Replay; + + for( vector :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) + delete *i; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + delete *i; + + for( vector :: iterator i = m_ScoreChecks.begin( ); i != m_ScoreChecks.end( ); i++ ) + m_GHost->m_Callables.push_back( *i ); + + while( !m_Actions.empty( ) ) + { + delete m_Actions.front( ); + m_Actions.pop( ); + } +} + +uint32_t CBaseGame :: GetNextTimedActionTicks( ) +{ + // return the number of ticks (ms) until the next "timed action", which for our purposes is the next game update + // the main GHost++ loop will make sure the next loop update happens at or before this value + // note: there's no reason this function couldn't take into account the game's other timers too but they're far less critical + // warning: this function must take into account when actions are not being sent (e.g. during loading or lagging) + + if( !m_GameLoaded || m_Lagging ) + return 50; + + uint32_t TicksSinceLastUpdate = GetTicks( ) - m_LastActionSentTicks; + + if( TicksSinceLastUpdate > m_Latency - m_LastActionLateBy ) + return 0; + else + return m_Latency - m_LastActionLateBy - TicksSinceLastUpdate; +} + +uint32_t CBaseGame :: GetSlotsOccupied( ) +{ + uint32_t NumSlotsOccupied = 0; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED ) + NumSlotsOccupied++; + } + + return NumSlotsOccupied; +} + +uint32_t CBaseGame :: GetSlotsOpen( ) +{ + uint32_t NumSlotsOpen = 0; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN ) + NumSlotsOpen++; + } + + return NumSlotsOpen; +} + +uint32_t CBaseGame :: GetNumPlayers( ) +{ + uint32_t NumPlayers = GetNumHumanPlayers( ); + + if( m_FakePlayerPID != 255 ) + NumPlayers++; + + return NumPlayers; +} + +uint32_t CBaseGame :: GetNumHumanPlayers( ) +{ + uint32_t NumHumanPlayers = 0; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + NumHumanPlayers++; + } + + return NumHumanPlayers; +} + +string CBaseGame :: GetDescription( ) +{ + string Description = m_GameName + " : " + m_OwnerName + " : " + UTIL_ToString( GetNumHumanPlayers( ) ) + "/" + UTIL_ToString( m_GameLoading || m_GameLoaded ? m_StartPlayers : m_Slots.size( ) ); + + if( m_GameLoading || m_GameLoaded ) + Description += " : " + UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ) + "m"; + else + Description += " : " + UTIL_ToString( ( GetTime( ) - m_CreationTime ) / 60 ) + "m"; + + return Description; +} + +void CBaseGame :: SetAnnounce( uint32_t interval, string message ) +{ + m_AnnounceInterval = interval; + m_AnnounceMessage = message; + m_LastAnnounceTime = GetTime( ); +} + +unsigned int CBaseGame :: SetFD( void *fd, void *send_fd, int *nfds ) +{ + unsigned int NumFDs = 0; + + if( m_Socket ) + { + m_Socket->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds ); + NumFDs++; + } + + for( vector :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) + { + if( (*i)->GetSocket( ) ) + { + (*i)->GetSocket( )->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds ); + NumFDs++; + } + } + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetSocket( ) ) + { + (*i)->GetSocket( )->SetFD( (fd_set *)fd, (fd_set *)send_fd, nfds ); + NumFDs++; + } + } + + return NumFDs; +} + +bool CBaseGame :: Update( void *fd, void *send_fd ) +{ + // update callables + + for( vector :: iterator i = m_ScoreChecks.begin( ); i != m_ScoreChecks.end( ); ) + { + if( (*i)->GetReady( ) ) + { + double Score = (*i)->GetResult( ); + + for( vector :: iterator j = m_Potentials.begin( ); j != m_Potentials.end( ); j++ ) + { + if( (*j)->GetJoinPlayer( ) && (*j)->GetJoinPlayer( )->GetName( ) == (*i)->GetName( ) ) + EventPlayerJoinedWithScore( *j, (*j)->GetJoinPlayer( ), Score ); + } + + m_GHost->m_DB->RecoverCallable( *i ); + delete *i; + i = m_ScoreChecks.erase( i ); + } + else + i++; + } + + // update players + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); ) + { + if( (*i)->Update( fd ) ) + { + EventPlayerDeleted( *i ); + delete *i; + i = m_Players.erase( i ); + } + else + i++; + } + + for( vector :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); ) + { + if( (*i)->Update( fd ) ) + { + // flush the socket (e.g. in case a rejection message is queued) + + if( (*i)->GetSocket( ) ) + (*i)->GetSocket( )->DoSend( (fd_set *)send_fd ); + + delete *i; + i = m_Potentials.erase( i ); + } + else + i++; + } + + // create the virtual host player + + if( !m_GameLoading && !m_GameLoaded && GetNumPlayers( ) < 12 ) + CreateVirtualHost( ); + + // unlock the game + + if( m_Locked && !GetPlayerFromName( m_OwnerName, false ) ) + { + SendAllChat( m_GHost->m_Language->GameUnlocked( ) ); + m_Locked = false; + } + + // ping every 5 seconds + // changed this to ping during game loading as well to hopefully fix some problems with people disconnecting during loading + // changed this to ping during the game as well + + if( GetTime( ) - m_LastPingTime >= 5 ) + { + // note: we must send pings to players who are downloading the map because Warcraft III disconnects from the lobby if it doesn't receive a ping every ~90 seconds + // so if the player takes longer than 90 seconds to download the map they would be disconnected unless we keep sending pings + // todotodo: ignore pings received from players who have recently finished downloading the map + + SendAll( m_Protocol->SEND_W3GS_PING_FROM_HOST( ) ); + + // we also broadcast the game to the local network every 5 seconds so we hijack this timer for our nefarious purposes + // however we only want to broadcast if the countdown hasn't started + // see the !sendlan code later in this file for some more information about how this works + // todotodo: should we send a game cancel message somewhere? we'll need to implement a host counter for it to work + + if( !m_CountDownStarted ) + { + // construct a fixed host counter which will be used to identify players from this "realm" (i.e. LAN) + // the fixed host counter's 4 most significant bits will contain a 4 bit ID (0-15) + // the rest of the fixed host counter will contain the 28 least significant bits of the actual host counter + // since we're destroying 4 bits of information here the actual host counter should not be greater than 2^28 which is a reasonable assumption + // when a player joins a game we can obtain the ID from the received host counter + // note: LAN broadcasts use an ID of 0, battle.net refreshes use an ID of 1-10, the rest are unused + + uint32_t FixedHostCounter = m_HostCounter & 0x0FFFFFFF; + + if( m_SaveGame ) + { + // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect) + + uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME; + BYTEARRAY MapWidth; + MapWidth.push_back( 0 ); + MapWidth.push_back( 0 ); + BYTEARRAY MapHeight; + MapHeight.push_back( 0 ); + MapHeight.push_back( 0 ); + m_GHost->m_UDPSocket->Broadcast( 6112, m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), MapWidth, MapHeight, m_GameName, "Varlock", GetTime( ) - m_CreationTime, "Save\\Multiplayer\\" + m_SaveGame->GetFileNameNoPath( ), m_SaveGame->GetMagicNumber( ), 12, 12, m_HostPort, FixedHostCounter ) ); + } + else + { + // note: the PrivateGame flag is not set when broadcasting to LAN (as you might expect) + // note: we do not use m_Map->GetMapGameType because none of the filters are set when broadcasting to LAN (also as you might expect) + + uint32_t MapGameType = MAPGAMETYPE_UNKNOWN0; + m_GHost->m_UDPSocket->Broadcast( 6112, m_Protocol->SEND_W3GS_GAMEINFO( m_GHost->m_TFT, m_GHost->m_LANWar3Version, UTIL_CreateByteArray( MapGameType, false ), m_Map->GetMapGameFlags( ), m_Map->GetMapWidth( ), m_Map->GetMapHeight( ), m_GameName, "Varlock", GetTime( ) - m_CreationTime, m_Map->GetMapPath( ), m_Map->GetMapCRC( ), 12, 12, m_HostPort, FixedHostCounter ) ); + } + } + + m_LastPingTime = GetTime( ); + } + + // auto rehost if there was a refresh error in autohosted games + + if( m_RefreshError && !m_CountDownStarted && m_GameState == GAME_PUBLIC && !m_GHost->m_AutoHostGameName.empty( ) && m_GHost->m_AutoHostMaximumGames != 0 && m_GHost->m_AutoHostAutoStartPlayers != 0 && m_AutoStartPlayers != 0 ) + { + // there's a slim chance that this isn't actually an autohosted game since there is no explicit autohost flag + // however, if autohosting is enabled and this game is public and this game is set to autostart, it's probably autohosted + // so rehost it using the current autohost game name + + string GameName = m_GHost->m_AutoHostGameName + " #" + UTIL_ToString( m_GHost->m_HostCounter ); + CONSOLE_Print( "[GAME: " + m_GameName + "] automatically trying to rehost as public game [" + GameName + "] due to refresh failure" ); + m_LastGameName = m_GameName; + m_GameName = GameName; + m_HostCounter = m_GHost->m_HostCounter++; + m_RefreshError = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + (*i)->QueueGameUncreate( ); + (*i)->QueueEnterChat( ); + + // the game creation message will be sent on the next refresh + } + + m_CreationTime = GetTime( ); + m_LastRefreshTime = GetTime( ); + } + + // refresh every 3 seconds + + if( !m_RefreshError && !m_CountDownStarted && m_GameState == GAME_PUBLIC && GetSlotsOpen( ) > 0 && GetTime( ) - m_LastRefreshTime >= 3 ) + { + // send a game refresh packet to each battle.net connection + + bool Refreshed = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + // don't queue a game refresh message if the queue contains more than 1 packet because they're very low priority + + if( (*i)->GetOutPacketsQueued( ) <= 1 ) + { + (*i)->QueueGameRefresh( m_GameState, m_GameName, string( ), m_Map, m_SaveGame, GetTime( ) - m_CreationTime, m_HostCounter ); + Refreshed = true; + } + } + + // only print the "game refreshed" message if we actually refreshed on at least one battle.net server + + if( m_RefreshMessages && Refreshed ) + SendAllChat( m_GHost->m_Language->GameRefreshed( ) ); + + m_LastRefreshTime = GetTime( ); + } + + // send more map data + + if( !m_GameLoading && !m_GameLoaded && GetTicks( ) - m_LastDownloadCounterResetTicks >= 1000 ) + { + // hackhack: another timer hijack is in progress here + // since the download counter is reset once per second it's a great place to update the slot info if necessary + + if( m_SlotInfoChanged ) + SendAllSlotInfo( ); + + m_DownloadCounter = 0; + m_LastDownloadCounterResetTicks = GetTicks( ); + } + + if( !m_GameLoading && !m_GameLoaded && GetTicks( ) - m_LastDownloadTicks >= 100 ) + { + uint32_t Downloaders = 0; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetDownloadStarted( ) && !(*i)->GetDownloadFinished( ) ) + { + Downloaders++; + + if( m_GHost->m_MaxDownloaders > 0 && Downloaders > m_GHost->m_MaxDownloaders ) + break; + + // send up to 100 pieces of the map at once so that the download goes faster + // if we wait for each MAPPART packet to be acknowledged by the client it'll take a long time to download + // this is because we would have to wait the round trip time (the ping time) between sending every 1442 bytes of map data + // doing it this way allows us to send at least 140 KB in each round trip interval which is much more reasonable + // the theoretical throughput is [140 KB * 1000 / ping] in KB/sec so someone with 100 ping (round trip ping, not LC ping) could download at 1400 KB/sec + // note: this creates a queue of map data which clogs up the connection when the client is on a slower connection (e.g. dialup) + // in this case any changes to the lobby are delayed by the amount of time it takes to send the queued data (i.e. 140 KB, which could be 30 seconds or more) + // for example, players joining and leaving, slot changes, chat messages would all appear to happen much later for the low bandwidth player + // note: the throughput is also limited by the number of times this code is executed each second + // e.g. if we send the maximum amount (140 KB) 10 times per second the theoretical throughput is 1400 KB/sec + // therefore the maximum throughput is 1400 KB/sec regardless of ping and this value slowly diminishes as the player's ping increases + // in addition to this, the throughput is limited by the configuration value bot_maxdownloadspeed + // in summary: the actual throughput is MIN( 140 * 1000 / ping, 1400, bot_maxdownloadspeed ) in KB/sec assuming only one player is downloading the map + + uint32_t MapSize = UTIL_ByteArrayToUInt32( m_Map->GetMapSize( ), false ); + + while( (*i)->GetLastMapPartSent( ) < (*i)->GetLastMapPartAcked( ) + 1442 * 100 && (*i)->GetLastMapPartSent( ) < MapSize ) + { + if( (*i)->GetLastMapPartSent( ) == 0 ) + { + // overwrite the "started download ticks" since this is the first time we've sent any map data to the player + // prior to this we've only determined if the player needs to download the map but it's possible we could have delayed sending any data due to download limits + + (*i)->SetStartedDownloadingTicks( GetTicks( ) ); + } + + // limit the download speed if we're sending too much data + // the download counter is the # of map bytes downloaded in the last second (it's reset once per second) + + if( m_GHost->m_MaxDownloadSpeed > 0 && m_DownloadCounter > m_GHost->m_MaxDownloadSpeed * 1024 ) + break; + + Send( *i, m_Protocol->SEND_W3GS_MAPPART( GetHostPID( ), (*i)->GetPID( ), (*i)->GetLastMapPartSent( ), m_Map->GetMapData( ) ) ); + (*i)->SetLastMapPartSent( (*i)->GetLastMapPartSent( ) + 1442 ); + m_DownloadCounter += 1442; + } + } + } + + m_LastDownloadTicks = GetTicks( ); + } + + // announce every m_AnnounceInterval seconds + + if( !m_AnnounceMessage.empty( ) && !m_CountDownStarted && GetTime( ) - m_LastAnnounceTime >= m_AnnounceInterval ) + { + SendAllChat( m_AnnounceMessage ); + m_LastAnnounceTime = GetTime( ); + } + + // kick players who don't spoof check within 20 seconds when spoof checks are required and the game is autohosted + + if( !m_CountDownStarted && m_GHost->m_RequireSpoofChecks && m_GameState == GAME_PUBLIC && !m_GHost->m_AutoHostGameName.empty( ) && m_GHost->m_AutoHostMaximumGames != 0 && m_GHost->m_AutoHostAutoStartPlayers != 0 && m_AutoStartPlayers != 0 ) + { + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetSpoofed( ) && GetTime( ) - (*i)->GetJoinTime( ) >= 20 ) + { + (*i)->SetDeleteMe( true ); + (*i)->SetLeftReason( m_GHost->m_Language->WasKickedForNotSpoofChecking( ) ); + (*i)->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( (*i)->GetPID( ) ), false ); + } + } + } + + // try to auto start every 10 seconds + + if( !m_CountDownStarted && m_AutoStartPlayers != 0 && GetTime( ) - m_LastAutoStartTime >= 10 ) + { + StartCountDownAuto( m_GHost->m_RequireSpoofChecks ); + m_LastAutoStartTime = GetTime( ); + } + + // countdown every 500 ms + + if( m_CountDownStarted && GetTicks( ) - m_LastCountDownTicks >= 500 ) + { + if( m_CountDownCounter > 0 ) + { + // we use a countdown counter rather than a "finish countdown time" here because it might alternately round up or down the count + // this sometimes resulted in a countdown of e.g. "6 5 3 2 1" during my testing which looks pretty dumb + // doing it this way ensures it's always "5 4 3 2 1" but each interval might not be *exactly* the same length + + SendAllChat( UTIL_ToString( m_CountDownCounter ) + ". . ." ); + m_CountDownCounter--; + } + else if( !m_GameLoading && !m_GameLoaded ) + EventGameStarted( ); + + m_LastCountDownTicks = GetTicks( ); + } + + // check if the lobby is "abandoned" and needs to be closed since it will never start + + if( !m_GameLoading && !m_GameLoaded && m_AutoStartPlayers == 0 && m_GHost->m_LobbyTimeLimit > 0 ) + { + // check if there's a player with reserved status in the game + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetReserved( ) ) + m_LastReservedSeen = GetTime( ); + } + + // check if we've hit the time limit + + if( GetTime( ) - m_LastReservedSeen >= m_GHost->m_LobbyTimeLimit * 60 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] is over (lobby time limit hit)" ); + return true; + } + } + + // check if the game is loaded + + if( m_GameLoading ) + { + bool FinishedLoading = true; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + FinishedLoading = (*i)->GetFinishedLoading( ); + + if( !FinishedLoading ) + break; + } + + if( FinishedLoading ) + { + m_LastActionSentTicks = GetTicks( ); + m_GameLoading = false; + m_GameLoaded = true; + EventGameLoaded( ); + } + else + { + // reset the "lag" screen (the load-in-game screen) every 30 seconds + + if( m_LoadInGame && GetTime( ) - m_LastLagScreenResetTime >= 30 ) + { + bool UsingGProxy = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetGProxy( ) ) + UsingGProxy = true; + } + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetFinishedLoading( ) ) + { + // stop the lag screen + + for( vector :: iterator j = m_Players.begin( ); j != m_Players.end( ); j++ ) + { + if( !(*j)->GetFinishedLoading( ) ) + Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( *j, true ) ); + } + + // send an empty update + // this resets the lag screen timer but creates a rather annoying problem + // in order to prevent a desync we must make sure every player receives the exact same "desyncable game data" (updates and player leaves) in the exact same order + // unfortunately we cannot send updates to players who are still loading the map, so we buffer the updates to those players (see the else clause a few lines down for the code) + // in addition to this we must ensure any player leave messages are sent in the exact same position relative to these updates so those must be buffered too + + if( UsingGProxy && !(*i)->GetGProxy( ) ) + { + // we must send empty actions to non-GProxy++ players + // GProxy++ will insert these itself so we don't need to send them to GProxy++ players + // empty actions are used to extend the time a player can use when reconnecting + + for( unsigned char j = 0; j < m_GProxyEmptyActions; j++ ) + Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + } + + Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + + // start the lag screen + + Send( *i, m_Protocol->SEND_W3GS_START_LAG( m_Players, true ) ); + } + else + { + // buffer the empty update since the player is still loading the map + + if( UsingGProxy && !(*i)->GetGProxy( ) ) + { + // we must send empty actions to non-GProxy++ players + // GProxy++ will insert these itself so we don't need to send them to GProxy++ players + // empty actions are used to extend the time a player can use when reconnecting + + for( unsigned char j = 0; j < m_GProxyEmptyActions; j++ ) + (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + } + + (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + } + } + + // add actions to replay + + if( m_Replay ) + { + if( UsingGProxy ) + { + for( unsigned char i = 0; i < m_GProxyEmptyActions; i++ ) + m_Replay->AddTimeSlot( 0, queue( ) ); + } + + m_Replay->AddTimeSlot( 0, queue( ) ); + } + + // Warcraft III doesn't seem to respond to empty actions + + /* if( UsingGProxy ) + m_SyncCounter += m_GProxyEmptyActions; + + m_SyncCounter++; */ + m_LastLagScreenResetTime = GetTime( ); + } + } + } + + // keep track of the largest sync counter (the number of keepalive packets received by each player) + // if anyone falls behind by more than m_SyncLimit keepalives we start the lag screen + + if( m_GameLoaded ) + { + // check if anyone has started lagging + // we consider a player to have started lagging if they're more than m_SyncLimit keepalives behind + + if( !m_Lagging ) + { + string LaggingString; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( m_SyncCounter - (*i)->GetSyncCounter( ) > m_SyncLimit ) + { + (*i)->SetLagging( true ); + (*i)->SetStartedLaggingTicks( GetTicks( ) ); + m_Lagging = true; + m_StartedLaggingTime = GetTime( ); + + if( LaggingString.empty( ) ) + LaggingString = (*i)->GetName( ); + else + LaggingString += ", " + (*i)->GetName( ); + } + } + + if( m_Lagging ) + { + // start the lag screen + + CONSOLE_Print( "[GAME: " + m_GameName + "] started lagging on [" + LaggingString + "]" ); + SendAll( m_Protocol->SEND_W3GS_START_LAG( m_Players ) ); + + // reset everyone's drop vote + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + (*i)->SetDropVote( false ); + + m_LastLagScreenResetTime = GetTime( ); + } + } + + if( m_Lagging ) + { + bool UsingGProxy = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetGProxy( ) ) + UsingGProxy = true; + } + + uint32_t WaitTime = 60; + + if( UsingGProxy ) + WaitTime = ( m_GProxyEmptyActions + 1 ) * 60; + + if( GetTime( ) - m_StartedLaggingTime >= WaitTime ) + StopLaggers( m_GHost->m_Language->WasAutomaticallyDroppedAfterSeconds( UTIL_ToString( WaitTime ) ) ); + + // we cannot allow the lag screen to stay up for more than ~65 seconds because Warcraft III disconnects if it doesn't receive an action packet at least this often + // one (easy) solution is to simply drop all the laggers if they lag for more than 60 seconds + // another solution is to reset the lag screen the same way we reset it when using load-in-game + // this is required in order to give GProxy++ clients more time to reconnect + + if( GetTime( ) - m_LastLagScreenResetTime >= 60 ) + { + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + // stop the lag screen + + for( vector :: iterator j = m_Players.begin( ); j != m_Players.end( ); j++ ) + { + if( (*j)->GetLagging( ) ) + Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( *j ) ); + } + + // send an empty update + // this resets the lag screen timer + + if( UsingGProxy && !(*i)->GetGProxy( ) ) + { + // we must send additional empty actions to non-GProxy++ players + // GProxy++ will insert these itself so we don't need to send them to GProxy++ players + // empty actions are used to extend the time a player can use when reconnecting + + for( unsigned char j = 0; j < m_GProxyEmptyActions; j++ ) + Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + } + + Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + + // start the lag screen + + Send( *i, m_Protocol->SEND_W3GS_START_LAG( m_Players ) ); + } + + // add actions to replay + + if( m_Replay ) + { + if( UsingGProxy ) + { + for( unsigned char i = 0; i < m_GProxyEmptyActions; i++ ) + m_Replay->AddTimeSlot( 0, queue( ) ); + } + + m_Replay->AddTimeSlot( 0, queue( ) ); + } + + // Warcraft III doesn't seem to respond to empty actions + + /* if( UsingGProxy ) + m_SyncCounter += m_GProxyEmptyActions; + + m_SyncCounter++; */ + m_LastLagScreenResetTime = GetTime( ); + } + + // check if anyone has stopped lagging normally + // we consider a player to have stopped lagging if they're less than half m_SyncLimit keepalives behind + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetLagging( ) && m_SyncCounter - (*i)->GetSyncCounter( ) < m_SyncLimit / 2 ) + { + // stop the lag screen for this player + + CONSOLE_Print( "[GAME: " + m_GameName + "] stopped lagging on [" + (*i)->GetName( ) + "]" ); + SendAll( m_Protocol->SEND_W3GS_STOP_LAG( *i ) ); + (*i)->SetLagging( false ); + (*i)->SetStartedLaggingTicks( 0 ); + } + } + + // check if everyone has stopped lagging + + bool Lagging = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetLagging( ) ) + Lagging = true; + } + + m_Lagging = Lagging; + + // reset m_LastActionSentTicks because we want the game to stop running while the lag screen is up + + m_LastActionSentTicks = GetTicks( ); + + // keep track of the last lag screen time so we can avoid timing out players + + m_LastLagScreenTime = GetTime( ); + } + } + + // send actions every m_Latency milliseconds + // actions are at the heart of every Warcraft 3 game but luckily we don't need to know their contents to relay them + // we queue player actions in EventPlayerAction then just resend them in batches to all players here + + if( m_GameLoaded && !m_Lagging && GetTicks( ) - m_LastActionSentTicks >= m_Latency - m_LastActionLateBy ) + SendAllActions( ); + + // expire the votekick + + if( !m_KickVotePlayer.empty( ) && GetTime( ) - m_StartedKickVoteTime >= 60 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] votekick against player [" + m_KickVotePlayer + "] expired" ); + SendAllChat( m_GHost->m_Language->VoteKickExpired( m_KickVotePlayer ) ); + m_KickVotePlayer.clear( ); + m_StartedKickVoteTime = 0; + } + + // start the gameover timer if there's only one player left + + if( m_Players.size( ) == 1 && m_FakePlayerPID == 255 && m_GameOverTime == 0 && ( m_GameLoading || m_GameLoaded ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] gameover timer started (one player left)" ); + m_GameOverTime = GetTime( ); + } + + // finish the gameover timer + + if( m_GameOverTime != 0 && GetTime( ) - m_GameOverTime >= 60 ) + { + bool AlreadyStopped = true; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetDeleteMe( ) ) + { + AlreadyStopped = false; + break; + } + } + + if( !AlreadyStopped ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] is over (gameover timer finished)" ); + StopPlayers( "was disconnected (gameover timer finished)" ); + } + } + + // end the game if there aren't any players left + + if( m_Players.empty( ) && ( m_GameLoading || m_GameLoaded ) ) + { + if( !m_Saving ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] is over (no players left)" ); + SaveGameData( ); + m_Saving = true; + } + else if( IsGameDataSaved( ) ) + return true; + } + + // accept new connections + + if( m_Socket ) + { + CTCPSocket *NewSocket = m_Socket->Accept( (fd_set *)fd ); + + if( NewSocket ) + { + // check the IP blacklist + + if( m_IPBlackList.find( NewSocket->GetIPString( ) ) == m_IPBlackList.end( ) ) + { + if( m_GHost->m_TCPNoDelay ) + NewSocket->SetNoDelay( true ); + + m_Potentials.push_back( new CPotentialPlayer( m_Protocol, this, NewSocket ) ); + } + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] rejected connection from [" + NewSocket->GetIPString( ) + "] due to blacklist" ); + delete NewSocket; + } + } + + if( m_Socket->HasError( ) ) + return true; + } + + return m_Exiting; +} + +void CBaseGame :: UpdatePost( void *send_fd ) +{ + // we need to manually call DoSend on each player now because CGamePlayer :: Update doesn't do it + // this is in case player 2 generates a packet for player 1 during the update but it doesn't get sent because player 1 already finished updating + // in reality since we're queueing actions it might not make a big difference but oh well + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetSocket( ) ) + (*i)->GetSocket( )->DoSend( (fd_set *)send_fd ); + } + + for( vector :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) + { + if( (*i)->GetSocket( ) ) + (*i)->GetSocket( )->DoSend( (fd_set *)send_fd ); + } +} + +void CBaseGame :: Send( CGamePlayer *player, BYTEARRAY data ) +{ + if( player ) + player->Send( data ); +} + +void CBaseGame :: Send( unsigned char PID, BYTEARRAY data ) +{ + Send( GetPlayerFromPID( PID ), data ); +} + +void CBaseGame :: Send( BYTEARRAY PIDs, BYTEARRAY data ) +{ + for( unsigned int i = 0; i < PIDs.size( ); i++ ) + Send( PIDs[i], data ); +} + +void CBaseGame :: SendAll( BYTEARRAY data ) +{ + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + (*i)->Send( data ); +} + +void CBaseGame :: SendChat( unsigned char fromPID, CGamePlayer *player, string message ) +{ + // send a private message to one player - it'll be marked [Private] in Warcraft 3 + + if( player ) + { + if( !m_GameLoading && !m_GameLoaded ) + { + if( message.size( ) > 254 ) + message = message.substr( 0, 254 ); + + Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 16, BYTEARRAY( ), message ) ); + } + else + { + unsigned char ExtraFlags[] = { 3, 0, 0, 0 }; + + // based on my limited testing it seems that the extra flags' first byte contains 3 plus the recipient's colour to denote a private message + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( SID < m_Slots.size( ) ) + ExtraFlags[0] = 3 + m_Slots[SID].GetColour( ); + + if( message.size( ) > 127 ) + message = message.substr( 0, 127 ); + + Send( player, m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, UTIL_CreateByteArray( player->GetPID( ) ), 32, UTIL_CreateByteArray( ExtraFlags, 4 ), message ) ); + } + } +} + +void CBaseGame :: SendChat( unsigned char fromPID, unsigned char toPID, string message ) +{ + SendChat( fromPID, GetPlayerFromPID( toPID ), message ); +} + +void CBaseGame :: SendChat( CGamePlayer *player, string message ) +{ + SendChat( GetHostPID( ), player, message ); +} + +void CBaseGame :: SendChat( unsigned char toPID, string message ) +{ + SendChat( GetHostPID( ), toPID, message ); +} + +void CBaseGame :: SendAllChat( unsigned char fromPID, string message ) +{ + // send a public message to all players - it'll be marked [All] in Warcraft 3 + + if( GetNumHumanPlayers( ) > 0 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] [Local]: " + message ); + + if( !m_GameLoading && !m_GameLoaded ) + { + if( message.size( ) > 254 ) + message = message.substr( 0, 254 ); + + SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 16, BYTEARRAY( ), message ) ); + } + else + { + if( message.size( ) > 127 ) + message = message.substr( 0, 127 ); + + SendAll( m_Protocol->SEND_W3GS_CHAT_FROM_HOST( fromPID, GetPIDs( ), 32, UTIL_CreateByteArray( (uint32_t)0, false ), message ) ); + + if( m_Replay ) + m_Replay->AddChatMessage( fromPID, 32, 0, message ); + } + } +} + +void CBaseGame :: SendAllChat( string message ) +{ + SendAllChat( GetHostPID( ), message ); +} + +void CBaseGame :: SendLocalAdminChat( string message ) +{ + if( !m_LocalAdminMessages ) + return; + + // send a message to LAN/local players who are admins + // at the time of this writing it is only possible for the game owner to meet this criteria because being an admin requires spoof checking + // this is mainly used for relaying battle.net whispers, chat messages, and emotes to these players + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetSpoofed( ) && IsOwner( (*i)->GetName( ) ) && ( UTIL_IsLanIP( (*i)->GetExternalIP( ) ) || UTIL_IsLocalIP( (*i)->GetExternalIP( ), m_GHost->m_LocalAddresses ) ) ) + { + if( m_VirtualHostPID != 255 ) + SendChat( m_VirtualHostPID, *i, message ); + else + { + // make the chat message originate from the recipient since it's not going to be logged to the replay + + SendChat( (*i)->GetPID( ), *i, message ); + } + } + } +} + +void CBaseGame :: SendAllSlotInfo( ) +{ + if( !m_GameLoading && !m_GameLoaded ) + { + SendAll( m_Protocol->SEND_W3GS_SLOTINFO( m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) ); + m_SlotInfoChanged = false; + } +} + +void CBaseGame :: SendVirtualHostPlayerInfo( CGamePlayer *player ) +{ + if( m_VirtualHostPID == 255 ) + return; + + BYTEARRAY IP; + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + Send( player, m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) ); +} + +void CBaseGame :: SendFakePlayerInfo( CGamePlayer *player ) +{ + if( m_FakePlayerPID == 255 ) + return; + + BYTEARRAY IP; + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + Send( player, m_Protocol->SEND_W3GS_PLAYERINFO( m_FakePlayerPID, "FakePlayer", IP, IP ) ); +} + +void CBaseGame :: SendAllActions( ) +{ + bool UsingGProxy = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetGProxy( ) ) + UsingGProxy = true; + } + + m_GameTicks += m_Latency; + + if( UsingGProxy ) + { + // we must send empty actions to non-GProxy++ players + // GProxy++ will insert these itself so we don't need to send them to GProxy++ players + // empty actions are used to extend the time a player can use when reconnecting + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetGProxy( ) ) + { + for( unsigned char j = 0; j < m_GProxyEmptyActions; j++ ) + Send( *i, m_Protocol->SEND_W3GS_INCOMING_ACTION( queue( ), 0 ) ); + } + } + + if( m_Replay ) + { + for( unsigned char i = 0; i < m_GProxyEmptyActions; i++ ) + m_Replay->AddTimeSlot( 0, queue( ) ); + } + } + + // Warcraft III doesn't seem to respond to empty actions + + /* if( UsingGProxy ) + m_SyncCounter += m_GProxyEmptyActions; */ + + m_SyncCounter++; + + // we aren't allowed to send more than 1460 bytes in a single packet but it's possible we might have more than that many bytes waiting in the queue + + if( !m_Actions.empty( ) ) + { + // we use a "sub actions queue" which we keep adding actions to until we reach the size limit + // start by adding one action to the sub actions queue + + queue SubActions; + CIncomingAction *Action = m_Actions.front( ); + m_Actions.pop( ); + SubActions.push( Action ); + uint32_t SubActionsLength = Action->GetLength( ); + + while( !m_Actions.empty( ) ) + { + Action = m_Actions.front( ); + m_Actions.pop( ); + + // check if adding the next action to the sub actions queue would put us over the limit (1452 because the INCOMING_ACTION and INCOMING_ACTION2 packets use an extra 8 bytes) + + if( SubActionsLength + Action->GetLength( ) > 1452 ) + { + // we'd be over the limit if we added the next action to the sub actions queue + // so send everything already in the queue and then clear it out + // the W3GS_INCOMING_ACTION2 packet handles the overflow but it must be sent *before* the corresponding W3GS_INCOMING_ACTION packet + + SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION2( SubActions ) ); + + if( m_Replay ) + m_Replay->AddTimeSlot2( SubActions ); + + while( !SubActions.empty( ) ) + { + delete SubActions.front( ); + SubActions.pop( ); + } + + SubActionsLength = 0; + } + + SubActions.push( Action ); + SubActionsLength += Action->GetLength( ); + } + + SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( SubActions, m_Latency ) ); + + if( m_Replay ) + m_Replay->AddTimeSlot( m_Latency, SubActions ); + + while( !SubActions.empty( ) ) + { + delete SubActions.front( ); + SubActions.pop( ); + } + } + else + { + SendAll( m_Protocol->SEND_W3GS_INCOMING_ACTION( m_Actions, m_Latency ) ); + + if( m_Replay ) + m_Replay->AddTimeSlot( m_Latency, m_Actions ); + } + + uint32_t ActualSendInterval = GetTicks( ) - m_LastActionSentTicks; + uint32_t ExpectedSendInterval = m_Latency - m_LastActionLateBy; + m_LastActionLateBy = ActualSendInterval - ExpectedSendInterval; + + if( m_LastActionLateBy > m_Latency ) + { + // something is going terribly wrong - GHost++ is probably starved of resources + // print a message because even though this will take more resources it should provide some information to the administrator for future reference + // other solutions - dynamically modify the latency, request higher priority, terminate other games, ??? + + CONSOLE_Print( "[GAME: " + m_GameName + "] warning - the latency is " + UTIL_ToString( m_Latency ) + "ms but the last update was late by " + UTIL_ToString( m_LastActionLateBy ) + "ms" ); + m_LastActionLateBy = m_Latency; + } + + m_LastActionSentTicks = GetTicks( ); +} + +void CBaseGame :: SendWelcomeMessage( CGamePlayer *player ) +{ + // read from motd.txt if available (thanks to zeeg for this addition) + + ifstream in; + in.open( m_GHost->m_MOTDFile.c_str( ) ); + + if( in.fail( ) ) + { + // default welcome message + + if( m_HCLCommandString.empty( ) ) + SendChat( player, " " ); + + SendChat( player, " " ); + SendChat( player, " " ); + SendChat( player, " " ); + SendChat( player, "GHost++ http://www.codelain.com/" ); + SendChat( player, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" ); + SendChat( player, " Game Name: " + m_GameName ); + + if( !m_HCLCommandString.empty( ) ) + SendChat( player, " HCL Command String: " + m_HCLCommandString ); + } + else + { + // custom welcome message + // don't print more than 8 lines + + uint32_t Count = 0; + string Line; + + while( !in.eof( ) && Count < 8 ) + { + getline( in, Line ); + + if( Line.empty( ) ) + { + if( !in.eof( ) ) + SendChat( player, " " ); + } + else + SendChat( player, Line ); + + Count++; + } + + in.close( ); + } +} + +void CBaseGame :: SendEndMessage( ) +{ + // read from gameover.txt if available + + ifstream in; + in.open( m_GHost->m_GameOverFile.c_str( ) ); + + if( !in.fail( ) ) + { + // don't print more than 8 lines + + uint32_t Count = 0; + string Line; + + while( !in.eof( ) && Count < 8 ) + { + getline( in, Line ); + + if( Line.empty( ) ) + { + if( !in.eof( ) ) + SendAllChat( " " ); + } + else + SendAllChat( Line ); + + Count++; + } + + in.close( ); + } +} + +void CBaseGame :: EventPlayerDeleted( CGamePlayer *player ) +{ + CONSOLE_Print( "[GAME: " + m_GameName + "] deleting player [" + player->GetName( ) + "]: " + player->GetLeftReason( ) ); + + // remove any queued spoofcheck messages for this player + + if( player->GetWhoisSent( ) && !player->GetJoinedRealm( ).empty( ) && player->GetSpoofedRealm( ).empty( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == player->GetJoinedRealm( ) ) + { + // hackhack: there must be a better way to do this + + if( (*i)->GetPasswordHashType( ) == "pvpgn" ) + (*i)->UnqueueChatCommand( "/whereis " + player->GetName( ) ); + else + (*i)->UnqueueChatCommand( "/whois " + player->GetName( ) ); + + (*i)->UnqueueChatCommand( "/w " + player->GetName( ) + " " + m_GHost->m_Language->SpoofCheckByReplying( ) ); + } + } + } + + m_LastPlayerLeaveTicks = GetTicks( ); + + // in some cases we're forced to send the left message early so don't send it again + + if( player->GetLeftMessageSent( ) ) + return; + + if( m_GameLoaded ) + SendAllChat( player->GetName( ) + " " + player->GetLeftReason( ) + "." ); + + if( player->GetLagging( ) ) + SendAll( m_Protocol->SEND_W3GS_STOP_LAG( player ) ); + + // autosave + + if( m_GameLoaded && player->GetLeftCode( ) == PLAYERLEAVE_DISCONNECT && m_AutoSave ) + { + string SaveGameName = UTIL_FileSafeName( "GHost++ AutoSave " + m_GameName + " (" + player->GetName( ) + ").w3z" ); + CONSOLE_Print( "[GAME: " + m_GameName + "] auto saving [" + SaveGameName + "] before player drop, shortened send interval = " + UTIL_ToString( GetTicks( ) - m_LastActionSentTicks ) ); + BYTEARRAY CRC; + BYTEARRAY Action; + Action.push_back( 6 ); + UTIL_AppendByteArray( Action, SaveGameName ); + m_Actions.push( new CIncomingAction( player->GetPID( ), CRC, Action ) ); + + // todotodo: with the new latency system there needs to be a way to send a 0-time action + + SendAllActions( ); + } + + if( m_GameLoading && m_LoadInGame ) + { + // we must buffer player leave messages when using "load in game" to prevent desyncs + // this ensures the player leave messages are correctly interleaved with the empty updates sent to each player + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetFinishedLoading( ) ) + { + if( !player->GetFinishedLoading( ) ) + Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( player ) ); + + Send( *i, m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) ); + } + else + (*i)->AddLoadInGameData( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) ); + } + } + else + { + // tell everyone about the player leaving + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( player->GetPID( ), player->GetLeftCode( ) ) ); + } + + // set the replay's host PID and name to the last player to leave the game + // this will get overwritten as each player leaves the game so it will eventually be set to the last player + + if( m_Replay && ( m_GameLoading || m_GameLoaded ) ) + { + m_Replay->SetHostPID( player->GetPID( ) ); + m_Replay->SetHostName( player->GetName( ) ); + + // add leave message to replay + + if( m_GameLoading && !m_LoadInGame ) + m_Replay->AddLeaveGameDuringLoading( 1, player->GetPID( ), player->GetLeftCode( ) ); + else + m_Replay->AddLeaveGame( 1, player->GetPID( ), player->GetLeftCode( ) ); + } + + // abort the countdown if there was one in progress + + if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); + m_CountDownStarted = false; + } + + // abort the votekick + + if( !m_KickVotePlayer.empty( ) ) + SendAllChat( m_GHost->m_Language->VoteKickCancelled( m_KickVotePlayer ) ); + + m_KickVotePlayer.clear( ); + m_StartedKickVoteTime = 0; +} + +void CBaseGame :: EventPlayerDisconnectTimedOut( CGamePlayer *player ) +{ + if( player->GetGProxy( ) && m_GameLoaded ) + { + if( !player->GetGProxyDisconnectNoticeSent( ) ) + { + SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionTimedOutGProxy( ) + "." ); + player->SetGProxyDisconnectNoticeSent( true ); + } + + if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 ) + { + uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime ); + + if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 ) + TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60; + + SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) ); + player->SetLastGProxyWaitNoticeSentTime( GetTime( ) ); + } + + return; + } + + // not only do we not do any timeouts if the game is lagging, we allow for an additional grace period of 10 seconds + // this is because Warcraft 3 stops sending packets during the lag screen + // so when the lag screen finishes we would immediately disconnect everyone if we didn't give them some extra time + + if( GetTime( ) - m_LastLagScreenTime >= 10 ) + { + player->SetDeleteMe( true ); + player->SetLeftReason( m_GHost->m_Language->HasLostConnectionTimedOut( ) ); + player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); + } +} + +void CBaseGame :: EventPlayerDisconnectPlayerError( CGamePlayer *player ) +{ + // at the time of this comment there's only one player error and that's when we receive a bad packet from the player + // since TCP has checks and balances for data corruption the chances of this are pretty slim + + player->SetDeleteMe( true ); + player->SetLeftReason( m_GHost->m_Language->HasLostConnectionPlayerError( player->GetErrorString( ) ) ); + player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); +} + +void CBaseGame :: EventPlayerDisconnectSocketError( CGamePlayer *player ) +{ + if( player->GetGProxy( ) && m_GameLoaded ) + { + if( !player->GetGProxyDisconnectNoticeSent( ) ) + { + SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionSocketErrorGProxy( player->GetSocket( )->GetErrorString( ) ) + "." ); + player->SetGProxyDisconnectNoticeSent( true ); + } + + if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 ) + { + uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime ); + + if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 ) + TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60; + + SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) ); + player->SetLastGProxyWaitNoticeSentTime( GetTime( ) ); + } + + return; + } + + player->SetDeleteMe( true ); + player->SetLeftReason( m_GHost->m_Language->HasLostConnectionSocketError( player->GetSocket( )->GetErrorString( ) ) ); + player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); +} + +void CBaseGame :: EventPlayerDisconnectConnectionClosed( CGamePlayer *player ) +{ + if( player->GetGProxy( ) && m_GameLoaded ) + { + if( !player->GetGProxyDisconnectNoticeSent( ) ) + { + SendAllChat( player->GetName( ) + " " + m_GHost->m_Language->HasLostConnectionClosedByRemoteHostGProxy( ) + "." ); + player->SetGProxyDisconnectNoticeSent( true ); + } + + if( GetTime( ) - player->GetLastGProxyWaitNoticeSentTime( ) >= 20 ) + { + uint32_t TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60 - ( GetTime( ) - m_StartedLaggingTime ); + + if( TimeRemaining > ( (uint32_t)m_GProxyEmptyActions + 1 ) * 60 ) + TimeRemaining = ( m_GProxyEmptyActions + 1 ) * 60; + + SendAllChat( player->GetPID( ), m_GHost->m_Language->WaitForReconnectSecondsRemain( UTIL_ToString( TimeRemaining ) ) ); + player->SetLastGProxyWaitNoticeSentTime( GetTime( ) ); + } + + return; + } + + player->SetDeleteMe( true ); + player->SetLeftReason( m_GHost->m_Language->HasLostConnectionClosedByRemoteHost( ) ); + player->SetLeftCode( PLAYERLEAVE_DISCONNECT ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); +} + +void CBaseGame :: EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ) +{ + // check if the new player's name is empty or too long + + if( joinPlayer->GetName( ).empty( ) || joinPlayer->GetName( ).size( ) > 15 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with an invalid name of length " + UTIL_ToString( joinPlayer->GetName( ).size( ) ) ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // check if the new player's name is the same as the virtual host name + + if( joinPlayer->GetName( ) == m_VirtualHostName ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with the virtual host name" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // check if the new player's name is already taken + + if( GetPlayerFromName( joinPlayer->GetName( ), false ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but that name is already taken" ); + // SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButTaken( joinPlayer->GetName( ) ) ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // identify their joined realm + // this is only possible because when we send a game refresh via LAN or battle.net we encode an ID value in the 4 most significant bits of the host counter + // the client sends the host counter when it joins so we can extract the ID value here + // note: this is not a replacement for spoof checking since it doesn't verify the player's name and it can be spoofed anyway + + uint32_t HostCounterID = joinPlayer->GetHostCounter( ) >> 28; + string JoinedRealm; + + // we use an ID value of 0 to denote joining via LAN + + if( HostCounterID != 0 ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetHostCounterID( ) == HostCounterID ) + JoinedRealm = (*i)->GetServer( ); + } + } + + // check if the new player's name is banned but only if bot_banmethod is not 0 + // this is because if bot_banmethod is 0 and we announce the ban here it's possible for the player to be rejected later because the game is full + // this would allow the player to spam the chat by attempting to join the game multiple times in a row + + if( m_GHost->m_BanMethod != 0 ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == JoinedRealm ) + { + CDBBan *Ban = (*i)->IsBannedName( joinPlayer->GetName( ) ); + + if( Ban ) + { + if( m_GHost->m_BanMethod == 1 || m_GHost->m_BanMethod == 3 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but is banned by name" ); + + if( m_IgnoredNames.find( joinPlayer->GetName( ) ) == m_IgnoredNames.end( ) ) + { + SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButBannedByName( joinPlayer->GetName( ) ) ); + SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + m_IgnoredNames.insert( joinPlayer->GetName( ) ); + } + + // let banned players "join" the game with an arbitrary PID then immediately close the connection + // this causes them to be kicked back to the chat channel on battle.net + + vector Slots = m_Map->GetSlots( ); + potential->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( 1, potential->GetSocket( )->GetPort( ), potential->GetExternalIP( ), Slots, 0, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) ); + potential->SetDeleteMe( true ); + return; + } + + break; + } + } + + CDBBan *Ban = (*i)->IsBannedIP( potential->GetExternalIPString( ) ); + + if( Ban ) + { + if( m_GHost->m_BanMethod == 2 || m_GHost->m_BanMethod == 3 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but is banned by IP address" ); + + if( m_IgnoredNames.find( joinPlayer->GetName( ) ) == m_IgnoredNames.end( ) ) + { + SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButBannedByIP( joinPlayer->GetName( ), potential->GetExternalIPString( ), Ban->GetName( ) ) ); + SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + m_IgnoredNames.insert( joinPlayer->GetName( ) ); + } + + // let banned players "join" the game with an arbitrary PID then immediately close the connection + // this causes them to be kicked back to the chat channel on battle.net + + vector Slots = m_Map->GetSlots( ); + potential->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( 1, potential->GetSocket( )->GetPort( ), potential->GetExternalIP( ), Slots, 0, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) ); + potential->SetDeleteMe( true ); + return; + } + + break; + } + } + } + + if( m_MatchMaking && m_AutoStartPlayers != 0 && !m_Map->GetMapMatchMakingCategory( ).empty( ) && m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + { + // matchmaking is enabled + // start a database query to determine the player's score + // when the query is complete we will call EventPlayerJoinedWithScore + + m_ScoreChecks.push_back( m_GHost->m_DB->ThreadedScoreCheck( m_Map->GetMapMatchMakingCategory( ), joinPlayer->GetName( ), JoinedRealm ) ); + return; + } + + // check if the player is an admin or root admin on any connected realm for determining reserved status + // we can't just use the spoof checked realm like in EventPlayerBotCommand because the player hasn't spoof checked yet + + bool AnyAdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->IsAdmin( joinPlayer->GetName( ) ) || (*i)->IsRootAdmin( joinPlayer->GetName( ) ) ) + { + AnyAdminCheck = true; + break; + } + } + + bool Reserved = IsReserved( joinPlayer->GetName( ) ) || ( m_GHost->m_ReserveAdmins && AnyAdminCheck ) || IsOwner( joinPlayer->GetName( ) ); + + // try to find a slot + + unsigned char SID = 255; + unsigned char EnforcePID = 255; + unsigned char EnforceSID = 0; + CGameSlot EnforceSlot( 255, 0, 0, 0, 0, 0, 0 ); + + if( m_SaveGame ) + { + // in a saved game we enforce the player layout and the slot layout + // unfortunately we don't know how to extract the player layout from the saved game so we use the data from a replay instead + // the !enforcesg command defines the player layout by parsing a replay + + for( vector :: iterator i = m_EnforcePlayers.begin( ); i != m_EnforcePlayers.end( ); i++ ) + { + if( (*i).second == joinPlayer->GetName( ) ) + EnforcePID = (*i).first; + } + + for( vector :: iterator i = m_EnforceSlots.begin( ); i != m_EnforceSlots.end( ); i++ ) + { + if( (*i).GetPID( ) == EnforcePID ) + { + EnforceSlot = *i; + break; + } + + EnforceSID++; + } + + if( EnforcePID == 255 || EnforceSlot.GetPID( ) == 255 || EnforceSID >= m_Slots.size( ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but isn't in the enforced list" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + SID = EnforceSID; + } + else + { + // try to find an empty slot + + SID = GetEmptySlot( false ); + + if( SID == 255 && Reserved ) + { + // a reserved player is trying to join the game but it's full, try to find a reserved slot + + SID = GetEmptySlot( true ); + + if( SID != 255 ) + { + CGamePlayer *KickedPlayer = GetPlayerFromSID( SID ); + + if( KickedPlayer ) + { + KickedPlayer->SetDeleteMe( true ); + KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForReservedPlayer( joinPlayer->GetName( ) ) ); + KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); + + // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message + // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) ); + KickedPlayer->SetLeftMessageSent( true ); + } + } + } + + if( SID == 255 && IsOwner( joinPlayer->GetName( ) ) ) + { + // the owner player is trying to join the game but it's full and we couldn't even find a reserved slot, kick the player in the lowest numbered slot + // updated this to try to find a player slot so that we don't end up kicking a computer + + SID = 0; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetComputer( ) == 0 ) + { + SID = i; + break; + } + } + + CGamePlayer *KickedPlayer = GetPlayerFromSID( SID ); + + if( KickedPlayer ) + { + KickedPlayer->SetDeleteMe( true ); + KickedPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForOwnerPlayer( joinPlayer->GetName( ) ) ); + KickedPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); + + // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message + // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( KickedPlayer->GetPID( ), KickedPlayer->GetLeftCode( ) ) ); + KickedPlayer->SetLeftMessageSent( true ); + } + } + } + + if( SID >= m_Slots.size( ) ) + { + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // check if the new player's name is banned but only if bot_banmethod is 0 + // this is because if bot_banmethod is 0 we need to wait to announce the ban until now because they could have been rejected because the game was full + // this would have allowed the player to spam the chat by attempting to join the game multiple times in a row + + if( m_GHost->m_BanMethod == 0 ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == JoinedRealm ) + { + CDBBan *Ban = (*i)->IsBannedName( joinPlayer->GetName( ) ); + + if( Ban ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is using a banned name" ); + SendAllChat( m_GHost->m_Language->HasBannedName( joinPlayer->GetName( ) ) ); + SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + break; + } + } + + CDBBan *Ban = (*i)->IsBannedIP( potential->GetExternalIPString( ) ); + + if( Ban ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is using a banned IP address" ); + SendAllChat( m_GHost->m_Language->HasBannedIP( joinPlayer->GetName( ), potential->GetExternalIPString( ), Ban->GetName( ) ) ); + SendAllChat( m_GHost->m_Language->UserWasBannedOnByBecause( Ban->GetServer( ), Ban->GetName( ), Ban->GetDate( ), Ban->GetAdmin( ), Ban->GetReason( ) ) ); + break; + } + } + } + + // we have a slot for the new player + // make room for them by deleting the virtual host player if we have to + + if( GetNumPlayers( ) >= 11 || EnforcePID == m_VirtualHostPID ) + DeleteVirtualHost( ); + + // turning the CPotentialPlayer into a CGamePlayer is a bit of a pain because we have to be careful not to close the socket + // this problem is solved by setting the socket to NULL before deletion and handling the NULL case in the destructor + // we also have to be careful to not modify the m_Potentials vector since we're currently looping through it + + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] joined the game" ); + CGamePlayer *Player = new CGamePlayer( potential, m_SaveGame ? EnforcePID : GetNewPID( ), JoinedRealm, joinPlayer->GetName( ), joinPlayer->GetInternalIP( ), Reserved ); + + // consider LAN players to have already spoof checked since they can't + // since so many people have trouble with this feature we now use the JoinedRealm to determine LAN status + + if( JoinedRealm.empty( ) ) + Player->SetSpoofed( true ); + + Player->SetWhoisShouldBeSent( m_GHost->m_SpoofChecks == 1 || ( m_GHost->m_SpoofChecks == 2 && AnyAdminCheck ) ); + m_Players.push_back( Player ); + potential->SetSocket( NULL ); + potential->SetDeleteMe( true ); + + if( m_SaveGame ) + m_Slots[SID] = EnforceSlot; + else + { + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) ); + else + { + if( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES ) + m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, 12, 12, SLOTRACE_RANDOM ); + else + m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, 12, 12, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ); + + // try to pick a team and colour + // make sure there aren't too many other players already + + unsigned char NumOtherPlayers = 0; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 ) + NumOtherPlayers++; + } + + if( NumOtherPlayers < m_Map->GetMapNumPlayers( ) ) + { + if( SID < m_Map->GetMapNumPlayers( ) ) + m_Slots[SID].SetTeam( SID ); + else + m_Slots[SID].SetTeam( 0 ); + + m_Slots[SID].SetColour( GetNewColour( ) ); + } + } + } + + // send slot info to the new player + // the SLOTINFOJOIN packet also tells the client their assigned PID and that the join was successful + + Player->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( Player->GetPID( ), Player->GetSocket( )->GetPort( ), Player->GetExternalIP( ), m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) ); + + // send virtual host info and fake player info (if present) to the new player + + SendVirtualHostPlayerInfo( Player ); + SendFakePlayerInfo( Player ); + + BYTEARRAY BlankIP; + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && *i != Player ) + { + // send info about the new player to every other player + + if( (*i)->GetSocket( ) ) + { + if( m_GHost->m_HideIPAddresses ) + (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), BlankIP, BlankIP ) ); + else + (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), Player->GetExternalIP( ), Player->GetInternalIP( ) ) ); + } + + // send info about every other player to the new player + + if( m_GHost->m_HideIPAddresses ) + Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), BlankIP, BlankIP ) ); + else + Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), (*i)->GetExternalIP( ), (*i)->GetInternalIP( ) ) ); + } + } + + // send a map check packet to the new player + + Player->Send( m_Protocol->SEND_W3GS_MAPCHECK( m_Map->GetMapPath( ), m_Map->GetMapSize( ), m_Map->GetMapInfo( ), m_Map->GetMapCRC( ), m_Map->GetMapSHA1( ) ) ); + + // send slot info to everyone, so the new player gets this info twice but everyone else still needs to know the new slot layout + + SendAllSlotInfo( ); + + // send a welcome message + + SendWelcomeMessage( Player ); + + // if spoof checks are required and we won't automatically spoof check this player then tell them how to spoof check + // e.g. if automatic spoof checks are disabled, or if automatic spoof checks are done on admins only and this player isn't an admin + + if( m_GHost->m_RequireSpoofChecks && !Player->GetWhoisShouldBeSent( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + // note: the following (commented out) line of code will crash because calling GetUniqueName( ) twice will result in two different return values + // and unfortunately iterators are not valid if compared against different containers + // this comment shall serve as warning to not make this mistake again since it has now been made twice before in GHost++ + // string( (*i)->GetUniqueName( ).begin( ), (*i)->GetUniqueName( ).end( ) ) + + BYTEARRAY UniqueName = (*i)->GetUniqueName( ); + + if( (*i)->GetServer( ) == JoinedRealm ) + SendChat( Player, m_GHost->m_Language->SpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) ); + } + } + + // check for multiple IP usage + + if( m_GHost->m_CheckMultipleIPUsage ) + { + string Others; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( Player != *i && Player->GetExternalIPString( ) == (*i)->GetExternalIPString( ) ) + { + if( Others.empty( ) ) + Others = (*i)->GetName( ); + else + Others += ", " + (*i)->GetName( ); + } + } + + if( !Others.empty( ) ) + SendAllChat( m_GHost->m_Language->MultipleIPAddressUsageDetected( joinPlayer->GetName( ), Others ) ); + } + + // abort the countdown if there was one in progress + + if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); + m_CountDownStarted = false; + } + + // auto lock the game + + if( m_GHost->m_AutoLock && !m_Locked && IsOwner( joinPlayer->GetName( ) ) ) + { + SendAllChat( m_GHost->m_Language->GameLocked( ) ); + m_Locked = true; + } +} + +void CBaseGame :: EventPlayerJoinedWithScore( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer, double score ) +{ + // this function is only called when matchmaking is enabled + // EventPlayerJoined will be called first in all cases + // if matchmaking is enabled EventPlayerJoined will start a database query to retrieve the player's score and keep the connection open while we wait + // when the database query is complete EventPlayerJoinedWithScore will be called + + // check if the new player's name is the same as the virtual host name + + if( joinPlayer->GetName( ) == m_VirtualHostName ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game with the virtual host name" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // check if the new player's name is already taken + + if( GetPlayerFromName( joinPlayer->GetName( ), false ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but that name is already taken" ); + // SendAllChat( m_GHost->m_Language->TryingToJoinTheGameButTaken( joinPlayer->GetName( ) ) ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // check if the new player's score is within the limits + + if( score > -99999.0 && ( score < m_MinimumScore || score > m_MaximumScore ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has a rating [" + UTIL_ToString( score, 2 ) + "] outside the limits [" + UTIL_ToString( m_MinimumScore, 2 ) + "] to [" + UTIL_ToString( m_MaximumScore, 2 ) + "]" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // try to find an empty slot + + unsigned char SID = GetEmptySlot( false ); + + // check if the player is an admin or root admin on any connected realm for determining reserved status + // we can't just use the spoof checked realm like in EventPlayerBotCommand because the player hasn't spoof checked yet + + bool AnyAdminCheck = false; + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->IsAdmin( joinPlayer->GetName( ) ) || (*i)->IsRootAdmin( joinPlayer->GetName( ) ) ) + { + AnyAdminCheck = true; + break; + } + } + + if( SID == 255 ) + { + // no empty slot found, time to do some matchmaking! + // note: the database code uses a score of -100000 to denote "no score" + + if( m_GHost->m_MatchMakingMethod == 0 ) + { + // method 0: don't do any matchmaking + // that was easy! + } + else if( m_GHost->m_MatchMakingMethod == 1 ) + { + // method 1: furthest score method + // calculate the average score of all players in the game + // then kick the player with the score furthest from that average (or a player without a score) + // this ensures that the players' scores will tend to converge as players join the game + + double AverageScore = 0.0; + uint32_t PlayersScored = 0; + + if( score > -99999.0 ) + { + AverageScore = score; + PlayersScored = 1; + } + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetScore( ) > -99999.0 ) + { + AverageScore += (*i)->GetScore( ); + PlayersScored++; + } + } + + if( PlayersScored > 0 ) + AverageScore /= PlayersScored; + + // calculate the furthest player from the average + + CGamePlayer *FurthestPlayer = NULL; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !FurthestPlayer || (*i)->GetScore( ) < -99999.0 || abs( (*i)->GetScore( ) - AverageScore ) > abs( FurthestPlayer->GetScore( ) - AverageScore ) ) + FurthestPlayer = *i; + } + + if( !FurthestPlayer ) + { + // this should be impossible + + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but no furthest player was found (this should be impossible)" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // kick the new player if they have the furthest score + + if( score < -99999.0 || abs( score - AverageScore ) > abs( FurthestPlayer->GetScore( ) - AverageScore ) ) + { + if( score < -99999.0 ) + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the furthest rating [N/A] from the average [" + UTIL_ToString( AverageScore, 2 ) + "]" ); + else + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the furthest rating [" + UTIL_ToString( score, 2 ) + "] from the average [" + UTIL_ToString( AverageScore, 2 ) + "]" ); + + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // kick the furthest player + + SID = GetSIDFromPID( FurthestPlayer->GetPID( ) ); + FurthestPlayer->SetDeleteMe( true ); + + if( FurthestPlayer->GetScore( ) < -99999.0 ) + FurthestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingFurthestScore( "N/A", UTIL_ToString( AverageScore, 2 ) ) ); + else + FurthestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingFurthestScore( UTIL_ToString( FurthestPlayer->GetScore( ), 2 ), UTIL_ToString( AverageScore, 2 ) ) ); + + FurthestPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); + + // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message + // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( FurthestPlayer->GetPID( ), FurthestPlayer->GetLeftCode( ) ) ); + FurthestPlayer->SetLeftMessageSent( true ); + + if( FurthestPlayer->GetScore( ) < -99999.0 ) + SendAllChat( m_GHost->m_Language->PlayerWasKickedForFurthestScore( FurthestPlayer->GetName( ), "N/A", UTIL_ToString( AverageScore, 2 ) ) ); + else + SendAllChat( m_GHost->m_Language->PlayerWasKickedForFurthestScore( FurthestPlayer->GetName( ), UTIL_ToString( FurthestPlayer->GetScore( ), 2 ), UTIL_ToString( AverageScore, 2 ) ) ); + } + else if( m_GHost->m_MatchMakingMethod == 2 ) + { + // method 2: lowest score method + // kick the player with the lowest score (or a player without a score) + + CGamePlayer *LowestPlayer = NULL; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !LowestPlayer || (*i)->GetScore( ) < -99999.0 || (*i)->GetScore( ) < LowestPlayer->GetScore( ) ) + LowestPlayer = *i; + } + + if( !LowestPlayer ) + { + // this should be impossible + + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but no lowest player was found (this should be impossible)" ); + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // kick the new player if they have the lowest score + + if( score < -99999.0 || score < LowestPlayer->GetScore( ) ) + { + if( score < -99999.0 ) + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the lowest rating [N/A]" ); + else + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] is trying to join the game but has the lowest rating [" + UTIL_ToString( score, 2 ) + "]" ); + + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // kick the lowest player + + SID = GetSIDFromPID( LowestPlayer->GetPID( ) ); + LowestPlayer->SetDeleteMe( true ); + + if( LowestPlayer->GetScore( ) < -99999.0 ) + LowestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingLowestScore( "N/A" ) ); + else + LowestPlayer->SetLeftReason( m_GHost->m_Language->WasKickedForHavingLowestScore( UTIL_ToString( LowestPlayer->GetScore( ), 2 ) ) ); + + LowestPlayer->SetLeftCode( PLAYERLEAVE_LOBBY ); + + // send a playerleave message immediately since it won't normally get sent until the player is deleted which is after we send a playerjoin message + // we don't need to call OpenSlot here because we're about to overwrite the slot data anyway + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( LowestPlayer->GetPID( ), LowestPlayer->GetLeftCode( ) ) ); + LowestPlayer->SetLeftMessageSent( true ); + + if( LowestPlayer->GetScore( ) < -99999.0 ) + SendAllChat( m_GHost->m_Language->PlayerWasKickedForLowestScore( LowestPlayer->GetName( ), "N/A" ) ); + else + SendAllChat( m_GHost->m_Language->PlayerWasKickedForLowestScore( LowestPlayer->GetName( ), UTIL_ToString( LowestPlayer->GetScore( ), 2 ) ) ); + } + } + + if( SID >= m_Slots.size( ) ) + { + potential->Send( m_Protocol->SEND_W3GS_REJECTJOIN( REJECTJOIN_FULL ) ); + potential->SetDeleteMe( true ); + return; + } + + // we have a slot for the new player + // make room for them by deleting the virtual host player if we have to + + if( GetNumPlayers( ) >= 11 ) + DeleteVirtualHost( ); + + // identify their joined realm + // this is only possible because when we send a game refresh via LAN or battle.net we encode an ID value in the 4 most significant bits of the host counter + // the client sends the host counter when it joins so we can extract the ID value here + // note: this is not a replacement for spoof checking since it doesn't verify the player's name and it can be spoofed anyway + + uint32_t HostCounterID = joinPlayer->GetHostCounter( ) >> 28; + string JoinedRealm; + + // we use an ID value of 0 to denote joining via LAN + + if( HostCounterID != 0 ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetHostCounterID( ) == HostCounterID ) + JoinedRealm = (*i)->GetServer( ); + } + } + + // turning the CPotentialPlayer into a CGamePlayer is a bit of a pain because we have to be careful not to close the socket + // this problem is solved by setting the socket to NULL before deletion and handling the NULL case in the destructor + // we also have to be careful to not modify the m_Potentials vector since we're currently looping through it + + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + joinPlayer->GetName( ) + "|" + potential->GetExternalIPString( ) + "] joined the game" ); + CGamePlayer *Player = new CGamePlayer( potential, GetNewPID( ), JoinedRealm, joinPlayer->GetName( ), joinPlayer->GetInternalIP( ), false ); + + // consider LAN players to have already spoof checked since they can't + // since so many people have trouble with this feature we now use the JoinedRealm to determine LAN status + + if( JoinedRealm.empty( ) ) + Player->SetSpoofed( true ); + + Player->SetWhoisShouldBeSent( m_GHost->m_SpoofChecks == 1 || ( m_GHost->m_SpoofChecks == 2 && AnyAdminCheck ) ); + Player->SetScore( score ); + m_Players.push_back( Player ); + potential->SetSocket( NULL ); + potential->SetDeleteMe( true ); + m_Slots[SID] = CGameSlot( Player->GetPID( ), 255, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) ); + + // send slot info to the new player + // the SLOTINFOJOIN packet also tells the client their assigned PID and that the join was successful + + Player->Send( m_Protocol->SEND_W3GS_SLOTINFOJOIN( Player->GetPID( ), Player->GetSocket( )->GetPort( ), Player->GetExternalIP( ), m_Slots, m_RandomSeed, m_Map->GetMapLayoutStyle( ), m_Map->GetMapNumPlayers( ) ) ); + + // send virtual host info and fake player info (if present) to the new player + + SendVirtualHostPlayerInfo( Player ); + SendFakePlayerInfo( Player ); + + BYTEARRAY BlankIP; + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + BlankIP.push_back( 0 ); + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && *i != Player ) + { + // send info about the new player to every other player + + if( (*i)->GetSocket( ) ) + { + if( m_GHost->m_HideIPAddresses ) + (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), BlankIP, BlankIP ) ); + else + (*i)->Send( m_Protocol->SEND_W3GS_PLAYERINFO( Player->GetPID( ), Player->GetName( ), Player->GetExternalIP( ), Player->GetInternalIP( ) ) ); + } + + // send info about every other player to the new player + + if( m_GHost->m_HideIPAddresses ) + Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), BlankIP, BlankIP ) ); + else + Player->Send( m_Protocol->SEND_W3GS_PLAYERINFO( (*i)->GetPID( ), (*i)->GetName( ), (*i)->GetExternalIP( ), (*i)->GetInternalIP( ) ) ); + } + } + + // send a map check packet to the new player + + Player->Send( m_Protocol->SEND_W3GS_MAPCHECK( m_Map->GetMapPath( ), m_Map->GetMapSize( ), m_Map->GetMapInfo( ), m_Map->GetMapCRC( ), m_Map->GetMapSHA1( ) ) ); + + // send slot info to everyone, so the new player gets this info twice but everyone else still needs to know the new slot layout + + SendAllSlotInfo( ); + + // send a welcome message + + SendWelcomeMessage( Player ); + + // if spoof checks are required and we won't automatically spoof check this player then tell them how to spoof check + // e.g. if automatic spoof checks are disabled, or if automatic spoof checks are done on admins only and this player isn't an admin + + if( m_GHost->m_RequireSpoofChecks && !Player->GetWhoisShouldBeSent( ) ) + { + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + // note: the following (commented out) line of code will crash because calling GetUniqueName( ) twice will result in two different return values + // and unfortunately iterators are not valid if compared against different containers + // this comment shall serve as warning to not make this mistake again since it has now been made twice before in GHost++ + // string( (*i)->GetUniqueName( ).begin( ), (*i)->GetUniqueName( ).end( ) ) + + BYTEARRAY UniqueName = (*i)->GetUniqueName( ); + + if( (*i)->GetServer( ) == JoinedRealm ) + SendChat( Player, m_GHost->m_Language->SpoofCheckByWhispering( string( UniqueName.begin( ), UniqueName.end( ) ) ) ); + } + } + + if( score < -99999.0 ) + SendAllChat( m_GHost->m_Language->PlayerHasScore( joinPlayer->GetName( ), "N/A" ) ); + else + SendAllChat( m_GHost->m_Language->PlayerHasScore( joinPlayer->GetName( ), UTIL_ToString( score, 2 ) ) ); + + uint32_t PlayersScored = 0; + uint32_t PlayersNotScored = 0; + double AverageScore = 0.0; + double MinScore = 0.0; + double MaxScore = 0.0; + bool Found = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + { + if( (*i)->GetScore( ) < -99999.0 ) + PlayersNotScored++; + else + { + PlayersScored++; + AverageScore += (*i)->GetScore( ); + + if( !Found || (*i)->GetScore( ) < MinScore ) + MinScore = (*i)->GetScore( ); + + if( !Found || (*i)->GetScore( ) > MaxScore ) + MaxScore = (*i)->GetScore( ); + + Found = true; + } + } + } + + double Spread = MaxScore - MinScore; + SendAllChat( m_GHost->m_Language->RatedPlayersSpread( UTIL_ToString( PlayersScored ), UTIL_ToString( PlayersScored + PlayersNotScored ), UTIL_ToString( (uint32_t)Spread ) ) ); + + // check for multiple IP usage + + if( m_GHost->m_CheckMultipleIPUsage ) + { + string Others; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( Player != *i && Player->GetExternalIPString( ) == (*i)->GetExternalIPString( ) ) + { + if( Others.empty( ) ) + Others = (*i)->GetName( ); + else + Others += ", " + (*i)->GetName( ); + } + } + + if( !Others.empty( ) ) + SendAllChat( m_GHost->m_Language->MultipleIPAddressUsageDetected( joinPlayer->GetName( ), Others ) ); + } + + // abort the countdown if there was one in progress + + if( m_CountDownStarted && !m_GameLoading && !m_GameLoaded ) + { + SendAllChat( m_GHost->m_Language->CountDownAborted( ) ); + m_CountDownStarted = false; + } + + // auto lock the game + + if( m_GHost->m_AutoLock && !m_Locked && IsOwner( joinPlayer->GetName( ) ) ) + { + SendAllChat( m_GHost->m_Language->GameLocked( ) ); + m_Locked = true; + } + + // balance the slots + + if( m_AutoStartPlayers != 0 && GetNumHumanPlayers( ) == m_AutoStartPlayers ) + BalanceSlots( ); +} + +void CBaseGame :: EventPlayerLeft( CGamePlayer *player, uint32_t reason ) +{ + // this function is only called when a player leave packet is received, not when there's a socket error, kick, etc... + + player->SetDeleteMe( true ); + + if( reason == PLAYERLEAVE_GPROXY ) + player->SetLeftReason( m_GHost->m_Language->WasUnrecoverablyDroppedFromGProxy( ) ); + else + player->SetLeftReason( m_GHost->m_Language->HasLeftVoluntarily( ) ); + + player->SetLeftCode( PLAYERLEAVE_LOST ); + + if( !m_GameLoading && !m_GameLoaded ) + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); +} + +void CBaseGame :: EventPlayerLoaded( CGamePlayer *player ) +{ + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] finished loading in " + UTIL_ToString( (float)( player->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) + " seconds" ); + + if( m_LoadInGame ) + { + // send any buffered data to the player now + // see the Update function for more information about why we do this + // this includes player loaded messages, game updates, and player leave messages + + queue *LoadInGameData = player->GetLoadInGameData( ); + + while( !LoadInGameData->empty( ) ) + { + Send( player, LoadInGameData->front( ) ); + LoadInGameData->pop( ); + } + + // start the lag screen for the new player + + bool FinishedLoading = true; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + FinishedLoading = (*i)->GetFinishedLoading( ); + + if( !FinishedLoading ) + break; + } + + if( !FinishedLoading ) + Send( player, m_Protocol->SEND_W3GS_START_LAG( m_Players, true ) ); + + // remove the new player from previously loaded players' lag screens + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( *i != player && (*i)->GetFinishedLoading( ) ) + Send( *i, m_Protocol->SEND_W3GS_STOP_LAG( player ) ); + } + + // send a chat message to previously loaded players + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( *i != player && (*i)->GetFinishedLoading( ) ) + SendChat( *i, m_GHost->m_Language->PlayerFinishedLoading( player->GetName( ) ) ); + } + + if( !FinishedLoading ) + SendChat( player, m_GHost->m_Language->PleaseWaitPlayersStillLoading( ) ); + } + else + SendAll( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( player->GetPID( ) ) ); +} + +void CBaseGame :: EventPlayerAction( CGamePlayer *player, CIncomingAction *action ) +{ + m_Actions.push( action ); + + // check for players saving the game and notify everyone + + if( !action->GetAction( )->empty( ) && (*action->GetAction( ))[0] == 6 ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] is saving the game" ); + SendAllChat( m_GHost->m_Language->PlayerIsSavingTheGame( player->GetName( ) ) ); + } +} + +void CBaseGame :: EventPlayerKeepAlive( CGamePlayer *player, uint32_t checkSum ) +{ + // check for desyncs + // however, it's possible that not every player has sent a checksum for this frame yet + // first we verify that we have enough checksums to work with otherwise we won't know exactly who desynced + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetDeleteMe( ) && (*i)->GetCheckSums( )->empty( ) ) + return; + } + + // now we check for desyncs since we know that every player has at least one checksum waiting + + bool FoundPlayer = false; + uint32_t FirstCheckSum = 0; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetDeleteMe( ) ) + { + FoundPlayer = true; + FirstCheckSum = (*i)->GetCheckSums( )->front( ); + break; + } + } + + if( !FoundPlayer ) + return; + + bool AddToReplay = true; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetDeleteMe( ) && (*i)->GetCheckSums( )->front( ) != FirstCheckSum ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] desync detected" ); + SendAllChat( m_GHost->m_Language->DesyncDetected( ) ); + + // try to figure out who desynced + // this is complicated by the fact that we don't know what the correct game state is so we let the players vote + // put the players into bins based on their game state + + map > Bins; + + for( vector :: iterator j = m_Players.begin( ); j != m_Players.end( ); j++ ) + { + if( !(*j)->GetDeleteMe( ) ) + Bins[(*j)->GetCheckSums( )->front( )].push_back( (*j)->GetPID( ) ); + } + + uint32_t StateNumber = 1; + map > :: iterator LargestBin = Bins.begin( ); + bool Tied = false; + + for( map > :: iterator j = Bins.begin( ); j != Bins.end( ); j++ ) + { + if( (*j).second.size( ) > (*LargestBin).second.size( ) ) + { + LargestBin = j; + Tied = false; + } + else if( j != LargestBin && (*j).second.size( ) == (*LargestBin).second.size( ) ) + Tied = true; + + string Players; + + for( vector :: iterator k = (*j).second.begin( ); k != (*j).second.end( ); k++ ) + { + CGamePlayer *Player = GetPlayerFromPID( *k ); + + if( Player ) + { + if( Players.empty( ) ) + Players = Player->GetName( ); + else + Players += ", " + Player->GetName( ); + } + } + + SendAllChat( m_GHost->m_Language->PlayersInGameState( UTIL_ToString( StateNumber ), Players ) ); + StateNumber++; + } + + FirstCheckSum = (*LargestBin).first; + + if( Tied ) + { + // there is a tie, which is unfortunate + // the most common way for this to happen is with a desync in a 1v1 situation + // this is not really unsolvable since the game shouldn't continue anyway so we just kick both players + // in a 2v2 or higher the chance of this happening is very slim + // however, we still kick every player because it's not fair to pick one or another group + // todotodo: it would be possible to split the game at this point and create a "new" game for each game state + + CONSOLE_Print( "[GAME: " + m_GameName + "] can't kick desynced players because there is a tie, kicking all players instead" ); + StopPlayers( m_GHost->m_Language->WasDroppedDesync( ) ); + AddToReplay = false; + } + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] kicking desynced players" ); + + for( map > :: iterator j = Bins.begin( ); j != Bins.end( ); j++ ) + { + // kick players who are NOT in the largest bin + // examples: suppose there are 10 players + // the most common case will be 9v1 (e.g. one player desynced and the others were unaffected) and this will kick the single outlier + // another (very unlikely) possibility is 8v1v1 or 8v2 and this will kick both of the outliers, regardless of whether their game states match + + if( (*j).first != (*LargestBin).first ) + { + for( vector :: iterator k = (*j).second.begin( ); k != (*j).second.end( ); k++ ) + { + CGamePlayer *Player = GetPlayerFromPID( *k ); + + if( Player ) + { + Player->SetDeleteMe( true ); + Player->SetLeftReason( m_GHost->m_Language->WasDroppedDesync( ) ); + Player->SetLeftCode( PLAYERLEAVE_LOST ); + } + } + } + } + } + + // don't continue looking for desyncs, we already found one! + + break; + } + } + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetDeleteMe( ) ) + (*i)->GetCheckSums( )->pop( ); + } + + // add checksum to replay + + /* if( m_Replay && AddToReplay ) + m_Replay->AddCheckSum( FirstCheckSum ); */ +} + +void CBaseGame :: EventPlayerChatToHost( CGamePlayer *player, CIncomingChatPlayer *chatPlayer ) +{ + if( chatPlayer->GetFromPID( ) == player->GetPID( ) ) + { + if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGE || chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGEEXTRA ) + { + // relay the chat message to other players + + bool Relay = !player->GetMuted( ); + BYTEARRAY ExtraFlags = chatPlayer->GetExtraFlags( ); + + // calculate timestamp + + string MinString = UTIL_ToString( ( m_GameTicks / 1000 ) / 60 ); + string SecString = UTIL_ToString( ( m_GameTicks / 1000 ) % 60 ); + + if( MinString.size( ) == 1 ) + MinString.insert( 0, "0" ); + + if( SecString.size( ) == 1 ) + SecString.insert( 0, "0" ); + + if( !ExtraFlags.empty( ) ) + { + if( ExtraFlags[0] == 0 ) + { + // this is an ingame [All] message, print it to the console + + CONSOLE_Print( "[GAME: " + m_GameName + "] (" + MinString + ":" + SecString + ") [All] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) ); + + // don't relay ingame messages targeted for all players if we're currently muting all + // note that commands will still be processed even when muting all because we only stop relaying the messages, the rest of the function is unaffected + + if( m_MuteAll ) + Relay = false; + } + else if( ExtraFlags[0] == 2 ) + { + // this is an ingame [Obs/Ref] message, print it to the console + + CONSOLE_Print( "[GAME: " + m_GameName + "] (" + MinString + ":" + SecString + ") [Obs/Ref] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) ); + } + + if( Relay ) + { + // add chat message to replay + // this includes allied chat and private chat from both teams as long as it was relayed + + if( m_Replay ) + m_Replay->AddChatMessage( chatPlayer->GetFromPID( ), chatPlayer->GetFlag( ), UTIL_ByteArrayToUInt32( chatPlayer->GetExtraFlags( ), false ), chatPlayer->GetMessage( ) ); + } + } + else + { + // this is a lobby message, print it to the console + + CONSOLE_Print( "[GAME: " + m_GameName + "] [Lobby] [" + player->GetName( ) + "]: " + chatPlayer->GetMessage( ) ); + + if( m_MuteLobby ) + Relay = false; + } + + // handle bot commands + + string Message = chatPlayer->GetMessage( ); + + if( Message == "?trigger" ) + SendChat( player, m_GHost->m_Language->CommandTrigger( string( 1, m_GHost->m_CommandTrigger ) ) ); + else if( !Message.empty( ) && Message[0] == m_GHost->m_CommandTrigger ) + { + // extract the command trigger, the command, and the payload + // e.g. "!say hello world" -> command: "say", payload: "hello world" + + string Command; + string Payload; + string :: size_type PayloadStart = Message.find( " " ); + + if( PayloadStart != string :: npos ) + { + Command = Message.substr( 1, PayloadStart - 1 ); + Payload = Message.substr( PayloadStart + 1 ); + } + else + Command = Message.substr( 1 ); + + transform( Command.begin( ), Command.end( ), Command.begin( ), (int(*)(int))tolower ); + + // don't allow EventPlayerBotCommand to veto a previous instruction to set Relay to false + // so if Relay is already false (e.g. because the player is muted) then it cannot be forced back to true here + + if( EventPlayerBotCommand( player, Command, Payload ) ) + Relay = false; + } + + if( Relay ) + Send( chatPlayer->GetToPIDs( ), m_Protocol->SEND_W3GS_CHAT_FROM_HOST( chatPlayer->GetFromPID( ), chatPlayer->GetToPIDs( ), chatPlayer->GetFlag( ), chatPlayer->GetExtraFlags( ), chatPlayer->GetMessage( ) ) ); + } + else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_TEAMCHANGE && !m_CountDownStarted ) + EventPlayerChangeTeam( player, chatPlayer->GetByte( ) ); + else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_COLOURCHANGE && !m_CountDownStarted ) + EventPlayerChangeColour( player, chatPlayer->GetByte( ) ); + else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_RACECHANGE && !m_CountDownStarted ) + EventPlayerChangeRace( player, chatPlayer->GetByte( ) ); + else if( chatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_HANDICAPCHANGE && !m_CountDownStarted ) + EventPlayerChangeHandicap( player, chatPlayer->GetByte( ) ); + } +} + +bool CBaseGame :: EventPlayerBotCommand( CGamePlayer *player, string command, string payload ) +{ + // return true if the command itself should be hidden from other players + + return false; +} + +void CBaseGame :: EventPlayerChangeTeam( CGamePlayer *player, unsigned char team ) +{ + // player is requesting a team change + + if( m_SaveGame ) + return; + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + { + unsigned char oldSID = GetSIDFromPID( player->GetPID( ) ); + unsigned char newSID = GetEmptySlot( team, player->GetPID( ) ); + SwapSlots( oldSID, newSID ); + } + else + { + if( team > 12 ) + return; + + if( team == 12 ) + { + if( m_Map->GetMapObservers( ) != MAPOBS_ALLOWED && m_Map->GetMapObservers( ) != MAPOBS_REFEREES ) + return; + } + else + { + if( team >= m_Map->GetMapNumPlayers( ) ) + return; + + // make sure there aren't too many other players already + + unsigned char NumOtherPlayers = 0; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && m_Slots[i].GetTeam( ) != 12 && m_Slots[i].GetPID( ) != player->GetPID( ) ) + NumOtherPlayers++; + } + + if( NumOtherPlayers >= m_Map->GetMapNumPlayers( ) ) + return; + } + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( SID < m_Slots.size( ) ) + { + m_Slots[SID].SetTeam( team ); + + if( team == 12 ) + { + // if they're joining the observer team give them the observer colour + + m_Slots[SID].SetColour( 12 ); + } + else if( m_Slots[SID].GetColour( ) == 12 ) + { + // if they're joining a regular team give them an unused colour + + m_Slots[SID].SetColour( GetNewColour( ) ); + } + + SendAllSlotInfo( ); + } + } +} + +void CBaseGame :: EventPlayerChangeColour( CGamePlayer *player, unsigned char colour ) +{ + // player is requesting a colour change + + if( m_SaveGame ) + return; + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + return; + + if( colour > 11 ) + return; + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( SID < m_Slots.size( ) ) + { + // make sure the player isn't an observer + + if( m_Slots[SID].GetTeam( ) == 12 ) + return; + + ColourSlot( SID, colour ); + } +} + +void CBaseGame :: EventPlayerChangeRace( CGamePlayer *player, unsigned char race ) +{ + // player is requesting a race change + + if( m_SaveGame ) + return; + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + return; + + if( m_Map->GetMapFlags( ) & MAPFLAG_RANDOMRACES ) + return; + + if( race != SLOTRACE_HUMAN && race != SLOTRACE_ORC && race != SLOTRACE_NIGHTELF && race != SLOTRACE_UNDEAD && race != SLOTRACE_RANDOM ) + return; + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( SID < m_Slots.size( ) ) + { + m_Slots[SID].SetRace( race | SLOTRACE_SELECTABLE ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: EventPlayerChangeHandicap( CGamePlayer *player, unsigned char handicap ) +{ + // player is requesting a handicap change + + if( m_SaveGame ) + return; + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + return; + + if( handicap != 50 && handicap != 60 && handicap != 70 && handicap != 80 && handicap != 90 && handicap != 100 ) + return; + + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( SID < m_Slots.size( ) ) + { + m_Slots[SID].SetHandicap( handicap ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: EventPlayerDropRequest( CGamePlayer *player ) +{ + // todotodo: check that we've waited the full 45 seconds + + if( m_Lagging ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] player [" + player->GetName( ) + "] voted to drop laggers" ); + SendAllChat( m_GHost->m_Language->PlayerVotedToDropLaggers( player->GetName( ) ) ); + + // check if at least half the players voted to drop + + uint32_t Votes = 0; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetDropVote( ) ) + Votes++; + } + + if( (float)Votes / m_Players.size( ) > 0.49 ) + StopLaggers( m_GHost->m_Language->LaggedOutDroppedByVote( ) ); + } +} + +void CBaseGame :: EventPlayerMapSize( CGamePlayer *player, CIncomingMapSize *mapSize ) +{ + if( m_GameLoading || m_GameLoaded ) + return; + + // todotodo: the variable names here are confusing due to extremely poor design on my part + + uint32_t MapSize = UTIL_ByteArrayToUInt32( m_Map->GetMapSize( ), false ); + + if( mapSize->GetSizeFlag( ) != 1 || mapSize->GetMapSize( ) != MapSize ) + { + // the player doesn't have the map + + if( m_GHost->m_AllowDownloads != 0 ) + { + string *MapData = m_Map->GetMapData( ); + + if( !MapData->empty( ) ) + { + if( m_GHost->m_AllowDownloads == 1 || ( m_GHost->m_AllowDownloads == 2 && player->GetDownloadAllowed( ) ) ) + { + if( !player->GetDownloadStarted( ) && mapSize->GetSizeFlag( ) == 1 ) + { + // inform the client that we are willing to send the map + + CONSOLE_Print( "[GAME: " + m_GameName + "] map download started for player [" + player->GetName( ) + "]" ); + Send( player, m_Protocol->SEND_W3GS_STARTDOWNLOAD( GetHostPID( ) ) ); + player->SetDownloadStarted( true ); + player->SetStartedDownloadingTicks( GetTicks( ) ); + } + else + player->SetLastMapPartAcked( mapSize->GetMapSize( ) ); + } + } + else + { + player->SetDeleteMe( true ); + player->SetLeftReason( "doesn't have the map and there is no local copy of the map to send" ); + player->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); + } + } + else + { + player->SetDeleteMe( true ); + player->SetLeftReason( "doesn't have the map and map downloads are disabled" ); + player->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); + } + } + else + { + if( player->GetDownloadStarted( ) ) + { + // calculate download rate + + float Seconds = (float)( GetTicks( ) - player->GetStartedDownloadingTicks( ) ) / 1000; + float Rate = (float)MapSize / 1024 / Seconds; + CONSOLE_Print( "[GAME: " + m_GameName + "] map download finished for player [" + player->GetName( ) + "] in " + UTIL_ToString( Seconds, 1 ) + " seconds" ); + SendAllChat( m_GHost->m_Language->PlayerDownloadedTheMap( player->GetName( ), UTIL_ToString( Seconds, 1 ), UTIL_ToString( Rate, 1 ) ) ); + player->SetDownloadFinished( true ); + player->SetFinishedDownloadingTime( GetTime( ) ); + + // add to database + + m_GHost->m_Callables.push_back( m_GHost->m_DB->ThreadedDownloadAdd( m_Map->GetMapPath( ), MapSize, player->GetName( ), player->GetExternalIPString( ), player->GetSpoofed( ) ? 1 : 0, player->GetSpoofedRealm( ), GetTicks( ) - player->GetStartedDownloadingTicks( ) ) ); + } + } + + unsigned char NewDownloadStatus = (unsigned char)( (float)mapSize->GetMapSize( ) / MapSize * 100 ); + unsigned char SID = GetSIDFromPID( player->GetPID( ) ); + + if( NewDownloadStatus > 100 ) + NewDownloadStatus = 100; + + if( SID < m_Slots.size( ) ) + { + // only send the slot info if the download status changed + + if( m_Slots[SID].GetDownloadStatus( ) != NewDownloadStatus ) + { + m_Slots[SID].SetDownloadStatus( NewDownloadStatus ); + + // we don't actually send the new slot info here + // this is an optimization because it's possible for a player to download a map very quickly + // if we send a new slot update for every percentage change in their download status it adds up to a lot of data + // instead, we mark the slot info as "out of date" and update it only once in awhile (once per second when this comment was made) + + m_SlotInfoChanged = true; + } + } +} + +void CBaseGame :: EventPlayerPongToHost( CGamePlayer *player, uint32_t pong ) +{ + // autokick players with excessive pings but only if they're not reserved and we've received at least 3 pings from them + // also don't kick anyone if the game is loading or loaded - this could happen because we send pings during loading but we stop sending them after the game is loaded + // see the Update function for where we send pings + + if( !m_GameLoading && !m_GameLoaded && !player->GetDeleteMe( ) && !player->GetReserved( ) && player->GetNumPings( ) >= 3 && player->GetPing( m_GHost->m_LCPings ) > m_GHost->m_AutoKickPing ) + { + // send a chat message because we don't normally do so when a player leaves the lobby + + SendAllChat( m_GHost->m_Language->AutokickingPlayerForExcessivePing( player->GetName( ), UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) ) ); + player->SetDeleteMe( true ); + player->SetLeftReason( "was autokicked for excessive ping of " + UTIL_ToString( player->GetPing( m_GHost->m_LCPings ) ) ); + player->SetLeftCode( PLAYERLEAVE_LOBBY ); + OpenSlot( GetSIDFromPID( player->GetPID( ) ), false ); + } +} + +void CBaseGame :: EventGameRefreshed( string server ) +{ + if( m_RefreshRehosted ) + { + // we're not actually guaranteed this refresh was for the rehosted game and not the previous one + // but since we unqueue game refreshes when rehosting, the only way this can happen is due to network delay + // it's a risk we're willing to take but can result in a false positive here + + SendAllChat( m_GHost->m_Language->RehostWasSuccessful( ) ); + m_RefreshRehosted = false; + } +} + +void CBaseGame :: EventGameStarted( ) +{ + CONSOLE_Print( "[GAME: " + m_GameName + "] started loading with " + UTIL_ToString( GetNumHumanPlayers( ) ) + " players" ); + + // encode the HCL command string in the slot handicaps + // here's how it works: + // the user inputs a command string to be sent to the map + // it is almost impossible to send a message from the bot to the map so we encode the command string in the slot handicaps + // this works because there are only 6 valid handicaps but Warcraft III allows the bot to set up to 256 handicaps + // we encode the original (unmodified) handicaps in the new handicaps and use the remaining space to store a short message + // only occupied slots deliver their handicaps to the map and we can send one character (from a list) per handicap + // when the map finishes loading, assuming it's designed to use the HCL system, it checks if anyone has an invalid handicap + // if so, it decodes the message from the handicaps and restores the original handicaps using the encoded values + // the meaning of the message is specific to each map and the bot doesn't need to understand it + // e.g. you could send game modes, # of rounds, level to start on, anything you want as long as it fits in the limited space available + // note: if you attempt to use the HCL system on a map that does not support HCL the bot will drastically modify the handicaps + // since the map won't automatically restore the original handicaps in this case your game will be ruined + + if( !m_HCLCommandString.empty( ) ) + { + if( m_HCLCommandString.size( ) <= GetSlotsOccupied( ) ) + { + string HCLChars = "abcdefghijklmnopqrstuvwxyz0123456789 -=,."; + + if( m_HCLCommandString.find_first_not_of( HCLChars ) == string :: npos ) + { + unsigned char EncodingMap[256]; + unsigned char j = 0; + + for( uint32_t i = 0; i < 256; i++ ) + { + // the following 7 handicap values are forbidden + + if( j == 0 || j == 50 || j == 60 || j == 70 || j == 80 || j == 90 || j == 100 ) + j++; + + EncodingMap[i] = j++; + } + + unsigned char CurrentSlot = 0; + + for( string :: iterator si = m_HCLCommandString.begin( ); si != m_HCLCommandString.end( ); si++ ) + { + while( m_Slots[CurrentSlot].GetSlotStatus( ) != SLOTSTATUS_OCCUPIED ) + CurrentSlot++; + + unsigned char HandicapIndex = ( m_Slots[CurrentSlot].GetHandicap( ) - 50 ) / 10; + unsigned char CharIndex = HCLChars.find( *si ); + m_Slots[CurrentSlot++].SetHandicap( EncodingMap[HandicapIndex + CharIndex * 6] ); + } + + SendAllSlotInfo( ); + CONSOLE_Print( "[GAME: " + m_GameName + "] successfully encoded HCL command string [" + m_HCLCommandString + "]" ); + } + else + CONSOLE_Print( "[GAME: " + m_GameName + "] encoding HCL command string [" + m_HCLCommandString + "] failed because it contains invalid characters" ); + } + else + CONSOLE_Print( "[GAME: " + m_GameName + "] encoding HCL command string [" + m_HCLCommandString + "] failed because there aren't enough occupied slots" ); + } + + // send a final slot info update if necessary + // this typically won't happen because we prevent the !start command from completing while someone is downloading the map + // however, if someone uses !start force while a player is downloading the map this could trigger + // this is because we only permit slot info updates to be flagged when it's just a change in download status, all others are sent immediately + // it might not be necessary but let's clean up the mess anyway + + if( m_SlotInfoChanged ) + SendAllSlotInfo( ); + + m_StartedLoadingTicks = GetTicks( ); + m_LastLagScreenResetTime = GetTime( ); + m_GameLoading = true; + + // since we use a fake countdown to deal with leavers during countdown the COUNTDOWN_START and COUNTDOWN_END packets are sent in quick succession + // send a start countdown packet + + SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_START( ) ); + + // remove the virtual host player + + DeleteVirtualHost( ); + + // send an end countdown packet + + SendAll( m_Protocol->SEND_W3GS_COUNTDOWN_END( ) ); + + // send a game loaded packet for the fake player (if present) + + if( m_FakePlayerPID != 255 ) + SendAll( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( m_FakePlayerPID ) ); + + // record the starting number of players + + m_StartPlayers = GetNumHumanPlayers( ); + + // close the listening socket + + delete m_Socket; + m_Socket = NULL; + + // delete any potential players that are still hanging around + + for( vector :: iterator i = m_Potentials.begin( ); i != m_Potentials.end( ); i++ ) + delete *i; + + m_Potentials.clear( ); + + // set initial values for replay + + if( m_Replay ) + { + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + m_Replay->AddPlayer( (*i)->GetPID( ), (*i)->GetName( ) ); + + if( m_FakePlayerPID != 255 ) + m_Replay->AddPlayer( m_FakePlayerPID, "FakePlayer" ); + + m_Replay->SetSlots( m_Slots ); + m_Replay->SetRandomSeed( m_RandomSeed ); + m_Replay->SetSelectMode( m_Map->GetMapLayoutStyle( ) ); + m_Replay->SetStartSpotCount( m_Map->GetMapNumPlayers( ) ); + + if( m_SaveGame ) + { + uint32_t MapGameType = MAPGAMETYPE_SAVEDGAME; + + if( m_GameState == GAME_PRIVATE ) + MapGameType |= MAPGAMETYPE_PRIVATEGAME; + + m_Replay->SetMapGameType( MapGameType ); + } + else + { + uint32_t MapGameType = m_Map->GetMapGameType( ); + MapGameType |= MAPGAMETYPE_UNKNOWN0; + + if( m_GameState == GAME_PRIVATE ) + MapGameType |= MAPGAMETYPE_PRIVATEGAME; + + m_Replay->SetMapGameType( MapGameType ); + } + + if( !m_Players.empty( ) ) + { + // this might not be necessary since we're going to overwrite the replay's host PID and name everytime a player leaves + + m_Replay->SetHostPID( m_Players[0]->GetPID( ) ); + m_Replay->SetHostName( m_Players[0]->GetName( ) ); + } + } + + // build a stat string for use when saving the replay + // we have to build this now because the map data is going to be deleted + + BYTEARRAY StatString; + UTIL_AppendByteArray( StatString, m_Map->GetMapGameFlags( ) ); + StatString.push_back( 0 ); + UTIL_AppendByteArray( StatString, m_Map->GetMapWidth( ) ); + UTIL_AppendByteArray( StatString, m_Map->GetMapHeight( ) ); + UTIL_AppendByteArray( StatString, m_Map->GetMapCRC( ) ); + UTIL_AppendByteArray( StatString, m_Map->GetMapPath( ) ); + UTIL_AppendByteArray( StatString, "GHost++" ); + StatString.push_back( 0 ); + UTIL_AppendByteArray( StatString, m_Map->GetMapSHA1( ) ); // note: in replays generated by Warcraft III it stores 20 zeros for the SHA1 instead of the real thing + StatString = UTIL_EncodeStatString( StatString ); + m_StatString = string( StatString.begin( ), StatString.end( ) ); + + // delete the map data + + delete m_Map; + m_Map = NULL; + + if( m_LoadInGame ) + { + // buffer all the player loaded messages + // this ensures that every player receives the same set of player loaded messages in the same order, even if someone leaves during loading + // if someone leaves during loading we buffer the leave message to ensure it gets sent in the correct position but the player loaded message wouldn't get sent if we didn't buffer it now + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + for( vector :: iterator j = m_Players.begin( ); j != m_Players.end( ); j++ ) + (*j)->AddLoadInGameData( m_Protocol->SEND_W3GS_GAMELOADED_OTHERS( (*i)->GetPID( ) ) ); + } + } + + // move the game to the games in progress vector + + m_GHost->m_CurrentGame = NULL; + m_GHost->m_Games.push_back( this ); + + // and finally reenter battle.net chat + + for( vector :: iterator i = m_GHost->m_BNETs.begin( ); i != m_GHost->m_BNETs.end( ); i++ ) + { + (*i)->QueueGameUncreate( ); + (*i)->QueueEnterChat( ); + } +} + +void CBaseGame :: EventGameLoaded( ) +{ + CONSOLE_Print( "[GAME: " + m_GameName + "] finished loading with " + UTIL_ToString( GetNumHumanPlayers( ) ) + " players" ); + + // send shortest, longest, and personal load times to each player + + CGamePlayer *Shortest = NULL; + CGamePlayer *Longest = NULL; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !Shortest || (*i)->GetFinishedLoadingTicks( ) < Shortest->GetFinishedLoadingTicks( ) ) + Shortest = *i; + + if( !Longest || (*i)->GetFinishedLoadingTicks( ) > Longest->GetFinishedLoadingTicks( ) ) + Longest = *i; + } + + if( Shortest && Longest ) + { + SendAllChat( m_GHost->m_Language->ShortestLoadByPlayer( Shortest->GetName( ), UTIL_ToString( (float)( Shortest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) ); + SendAllChat( m_GHost->m_Language->LongestLoadByPlayer( Longest->GetName( ), UTIL_ToString( (float)( Longest->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) ); + } + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + SendChat( *i, m_GHost->m_Language->YourLoadingTimeWas( UTIL_ToString( (float)( (*i)->GetFinishedLoadingTicks( ) - m_StartedLoadingTicks ) / 1000, 2 ) ) ); + + // read from gameloaded.txt if available + + ifstream in; + in.open( m_GHost->m_GameLoadedFile.c_str( ) ); + + if( !in.fail( ) ) + { + // don't print more than 8 lines + + uint32_t Count = 0; + string Line; + + while( !in.eof( ) && Count < 8 ) + { + getline( in, Line ); + + if( Line.empty( ) ) + { + if( !in.eof( ) ) + SendAllChat( " " ); + } + else + SendAllChat( Line ); + + Count++; + } + + in.close( ); + } +} + +unsigned char CBaseGame :: GetSIDFromPID( unsigned char PID ) +{ + if( m_Slots.size( ) > 255 ) + return 255; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetPID( ) == PID ) + return i; + } + + return 255; +} + +CGamePlayer *CBaseGame :: GetPlayerFromPID( unsigned char PID ) +{ + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == PID ) + return *i; + } + + return NULL; +} + +CGamePlayer *CBaseGame :: GetPlayerFromSID( unsigned char SID ) +{ + if( SID < m_Slots.size( ) ) + return GetPlayerFromPID( m_Slots[SID].GetPID( ) ); + + return NULL; +} + +CGamePlayer *CBaseGame :: GetPlayerFromName( string name, bool sensitive ) +{ + if( !sensitive ) + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + { + string TestName = (*i)->GetName( ); + + if( !sensitive ) + transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); + + if( TestName == name ) + return *i; + } + } + + return NULL; +} + +uint32_t CBaseGame :: GetPlayerFromNamePartial( string name, CGamePlayer **player ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t Matches = 0; + *player = NULL; + + // try to match each player with the passed string (e.g. "Varlock" would be matched with "lock") + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + { + string TestName = (*i)->GetName( ); + transform( TestName.begin( ), TestName.end( ), TestName.begin( ), (int(*)(int))tolower ); + + if( TestName.find( name ) != string :: npos ) + { + Matches++; + *player = *i; + + // if the name matches exactly stop any further matching + + if( TestName == name ) + { + Matches = 1; + break; + } + } + } + } + + return Matches; +} + +CGamePlayer *CBaseGame :: GetPlayerFromColour( unsigned char colour ) +{ + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetColour( ) == colour ) + return GetPlayerFromSID( i ); + } + + return NULL; +} + +unsigned char CBaseGame :: GetNewPID( ) +{ + // find an unused PID for a new player to use + + for( unsigned char TestPID = 1; TestPID < 255; TestPID++ ) + { + if( TestPID == m_VirtualHostPID || TestPID == m_FakePlayerPID ) + continue; + + bool InUse = false; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) == TestPID ) + { + InUse = true; + break; + } + } + + if( !InUse ) + return TestPID; + } + + // this should never happen + + return 255; +} + +unsigned char CBaseGame :: GetNewColour( ) +{ + // find an unused colour for a player to use + + for( unsigned char TestColour = 0; TestColour < 12; TestColour++ ) + { + bool InUse = false; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetColour( ) == TestColour ) + { + InUse = true; + break; + } + } + + if( !InUse ) + return TestColour; + } + + // this should never happen + + return 12; +} + +BYTEARRAY CBaseGame :: GetPIDs( ) +{ + BYTEARRAY result; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + result.push_back( (*i)->GetPID( ) ); + } + + return result; +} + +BYTEARRAY CBaseGame :: GetPIDs( unsigned char excludePID ) +{ + BYTEARRAY result; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && (*i)->GetPID( ) != excludePID ) + result.push_back( (*i)->GetPID( ) ); + } + + return result; +} + +unsigned char CBaseGame :: GetHostPID( ) +{ + // return the player to be considered the host (it can be any player) - mainly used for sending text messages from the bot + // try to find the virtual host player first + + if( m_VirtualHostPID != 255 ) + return m_VirtualHostPID; + + // try to find the fakeplayer next + + if( m_FakePlayerPID != 255 ) + return m_FakePlayerPID; + + // try to find the owner player next + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) && IsOwner( (*i)->GetName( ) ) ) + return (*i)->GetPID( ); + } + + // okay then, just use the first available player + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetLeftMessageSent( ) ) + return (*i)->GetPID( ); + } + + return 255; +} + +unsigned char CBaseGame :: GetEmptySlot( bool reserved ) +{ + if( m_Slots.size( ) > 255 ) + return 255; + + if( m_SaveGame ) + { + // unfortunately we don't know which slot each player was assigned in the savegame + // but we do know which slots were occupied and which weren't so let's at least force players to use previously occupied slots + + vector SaveGameSlots = m_SaveGame->GetSlots( ); + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) + return i; + } + + // don't bother with reserved slots in savegames + } + else + { + // look for an empty slot for a new player to occupy + // if reserved is true then we're willing to use closed or occupied slots as long as it wouldn't displace a player with a reserved slot + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN ) + return i; + } + + if( reserved ) + { + // no empty slots, but since player is reserved give them a closed slot + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_CLOSED ) + return i; + } + + // no closed slots either, give them an occupied slot but not one occupied by another reserved player + // first look for a player who is downloading the map and has the least amount downloaded so far + + unsigned char LeastDownloaded = 100; + unsigned char LeastSID = 255; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + CGamePlayer *Player = GetPlayerFromSID( i ); + + if( Player && !Player->GetReserved( ) && m_Slots[i].GetDownloadStatus( ) < LeastDownloaded ) + { + LeastDownloaded = m_Slots[i].GetDownloadStatus( ); + LeastSID = i; + } + } + + if( LeastSID != 255 ) + return LeastSID; + + // nobody who isn't reserved is downloading the map, just choose the first player who isn't reserved + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + CGamePlayer *Player = GetPlayerFromSID( i ); + + if( Player && !Player->GetReserved( ) ) + return i; + } + } + } + + return 255; +} + +unsigned char CBaseGame :: GetEmptySlot( unsigned char team, unsigned char PID ) +{ + if( m_Slots.size( ) > 255 ) + return 255; + + // find an empty slot based on player's current slot + + unsigned char StartSlot = GetSIDFromPID( PID ); + + if( StartSlot < m_Slots.size( ) ) + { + if( m_Slots[StartSlot].GetTeam( ) != team ) + { + // player is trying to move to another team so start looking from the first slot on that team + // we actually just start looking from the very first slot since the next few loops will check the team for us + + StartSlot = 0; + } + + if( m_SaveGame ) + { + vector SaveGameSlots = m_SaveGame->GetSlots( ); + + for( unsigned char i = StartSlot; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) + return i; + } + + for( unsigned char i = 0; i < StartSlot; i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team && SaveGameSlots[i].GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && SaveGameSlots[i].GetComputer( ) == 0 ) + return i; + } + } + else + { + // find an empty slot on the correct team starting from StartSlot + + for( unsigned char i = StartSlot; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team ) + return i; + } + + // didn't find an empty slot, but we could have missed one with SID < StartSlot + // e.g. in the DotA case where I am in slot 4 (yellow), slot 5 (orange) is occupied, and slot 1 (blue) is open and I am trying to move to another slot + + for( unsigned char i = 0; i < StartSlot; i++ ) + { + if( m_Slots[i].GetSlotStatus( ) == SLOTSTATUS_OPEN && m_Slots[i].GetTeam( ) == team ) + return i; + } + } + } + + return 255; +} + +void CBaseGame :: SwapSlots( unsigned char SID1, unsigned char SID2 ) +{ + if( SID1 < m_Slots.size( ) && SID2 < m_Slots.size( ) && SID1 != SID2 ) + { + CGameSlot Slot1 = m_Slots[SID1]; + CGameSlot Slot2 = m_Slots[SID2]; + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + { + // don't swap the team, colour, or race + + m_Slots[SID1] = CGameSlot( Slot2.GetPID( ), Slot2.GetDownloadStatus( ), Slot2.GetSlotStatus( ), Slot2.GetComputer( ), Slot1.GetTeam( ), Slot1.GetColour( ), Slot1.GetRace( ), Slot2.GetComputerType( ), Slot2.GetHandicap( ) ); + m_Slots[SID2] = CGameSlot( Slot1.GetPID( ), Slot1.GetDownloadStatus( ), Slot1.GetSlotStatus( ), Slot1.GetComputer( ), Slot2.GetTeam( ), Slot2.GetColour( ), Slot2.GetRace( ), Slot1.GetComputerType( ), Slot1.GetHandicap( ) ); + } + else + { + // swap everything + + m_Slots[SID1] = Slot2; + m_Slots[SID2] = Slot1; + } + + SendAllSlotInfo( ); + } +} + +void CBaseGame :: OpenSlot( unsigned char SID, bool kick ) +{ + if( SID < m_Slots.size( ) ) + { + if( kick ) + { + CGamePlayer *Player = GetPlayerFromSID( SID ); + + if( Player ) + { + Player->SetDeleteMe( true ); + Player->SetLeftReason( "was kicked when opening a slot" ); + Player->SetLeftCode( PLAYERLEAVE_LOBBY ); + } + } + + CGameSlot Slot = m_Slots[SID]; + m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: CloseSlot( unsigned char SID, bool kick ) +{ + if( SID < m_Slots.size( ) ) + { + if( kick ) + { + CGamePlayer *Player = GetPlayerFromSID( SID ); + + if( Player ) + { + Player->SetDeleteMe( true ); + Player->SetLeftReason( "was kicked when closing a slot" ); + Player->SetLeftCode( PLAYERLEAVE_LOBBY ); + } + } + + CGameSlot Slot = m_Slots[SID]; + m_Slots[SID] = CGameSlot( 0, 255, SLOTSTATUS_CLOSED, 0, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ) ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: ComputerSlot( unsigned char SID, unsigned char skill, bool kick ) +{ + if( SID < m_Slots.size( ) && skill < 3 ) + { + if( kick ) + { + CGamePlayer *Player = GetPlayerFromSID( SID ); + + if( Player ) + { + Player->SetDeleteMe( true ); + Player->SetLeftReason( "was kicked when creating a computer in a slot" ); + Player->SetLeftCode( PLAYERLEAVE_LOBBY ); + } + } + + CGameSlot Slot = m_Slots[SID]; + m_Slots[SID] = CGameSlot( 0, 100, SLOTSTATUS_OCCUPIED, 1, Slot.GetTeam( ), Slot.GetColour( ), Slot.GetRace( ), skill ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: ColourSlot( unsigned char SID, unsigned char colour ) +{ + if( SID < m_Slots.size( ) && colour < 12 ) + { + // make sure the requested colour isn't already taken + + bool Taken = false; + unsigned char TakenSID = 0; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetColour( ) == colour ) + { + TakenSID = i; + Taken = true; + } + } + + if( Taken && m_Slots[TakenSID].GetSlotStatus( ) != SLOTSTATUS_OCCUPIED ) + { + // the requested colour is currently "taken" by an unused (open or closed) slot + // but we allow the colour to persist within a slot so if we only update the existing player's colour the unused slot will have the same colour + // this isn't really a problem except that if someone then joins the game they'll receive the unused slot's colour resulting in a duplicate + // one way to solve this (which we do here) is to swap the player's current colour into the unused slot + + m_Slots[TakenSID].SetColour( m_Slots[SID].GetColour( ) ); + m_Slots[SID].SetColour( colour ); + SendAllSlotInfo( ); + } + else if( !Taken ) + { + // the requested colour isn't used by ANY slot + + m_Slots[SID].SetColour( colour ); + SendAllSlotInfo( ); + } + } +} + +void CBaseGame :: OpenAllSlots( ) +{ + bool Changed = false; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_CLOSED ) + { + (*i).SetSlotStatus( SLOTSTATUS_OPEN ); + Changed = true; + } + } + + if( Changed ) + SendAllSlotInfo( ); +} + +void CBaseGame :: CloseAllSlots( ) +{ + bool Changed = false; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OPEN ) + { + (*i).SetSlotStatus( SLOTSTATUS_CLOSED ); + Changed = true; + } + } + + if( Changed ) + SendAllSlotInfo( ); +} + +void CBaseGame :: ShuffleSlots( ) +{ + // we only want to shuffle the player slots + // that means we need to prevent this function from shuffling the open/closed/computer slots too + // so we start by copying the player slots to a temporary vector + + vector PlayerSlots; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetTeam( ) != 12 ) + PlayerSlots.push_back( *i ); + } + + // now we shuffle PlayerSlots + + if( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) + { + // rather than rolling our own probably broken shuffle algorithm we use random_shuffle because it's guaranteed to do it properly + // so in order to let random_shuffle do all the work we need a vector to operate on + // unfortunately we can't just use PlayerSlots because the team/colour/race shouldn't be modified + // so make a vector we can use + + vector SIDs; + + for( unsigned char i = 0; i < PlayerSlots.size( ); i++ ) + SIDs.push_back( i ); + + random_shuffle( SIDs.begin( ), SIDs.end( ) ); + + // now put the PlayerSlots vector in the same order as the SIDs vector + + vector Slots; + + // as usual don't modify the team/colour/race + + for( unsigned char i = 0; i < SIDs.size( ); i++ ) + Slots.push_back( CGameSlot( PlayerSlots[SIDs[i]].GetPID( ), PlayerSlots[SIDs[i]].GetDownloadStatus( ), PlayerSlots[SIDs[i]].GetSlotStatus( ), PlayerSlots[SIDs[i]].GetComputer( ), PlayerSlots[i].GetTeam( ), PlayerSlots[i].GetColour( ), PlayerSlots[i].GetRace( ) ) ); + + PlayerSlots = Slots; + } + else + { + // regular game + // it's easy when we're allowed to swap the team/colour/race! + + random_shuffle( PlayerSlots.begin( ), PlayerSlots.end( ) ); + } + + // now we put m_Slots back together again + + vector :: iterator CurrentPlayer = PlayerSlots.begin( ); + vector Slots; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetTeam( ) != 12 ) + { + Slots.push_back( *CurrentPlayer ); + CurrentPlayer++; + } + else + Slots.push_back( *i ); + } + + m_Slots = Slots; + + // and finally tell everyone about the new slot configuration + + SendAllSlotInfo( ); +} + +vector CBaseGame :: BalanceSlotsRecursive( vector PlayerIDs, unsigned char *TeamSizes, double *PlayerScores, unsigned char StartTeam ) +{ + // take a brute force approach to finding the best balance by iterating through every possible combination of players + // 1.) since the number of teams is arbitrary this algorithm must be recursive + // 2.) on the first recursion step every possible combination of players into two "teams" is checked, where the first team is the correct size and the second team contains everyone else + // 3.) on the next recursion step every possible combination of the remaining players into two more "teams" is checked, continuing until all the actual teams are accounted for + // 4.) for every possible combination, check the largest difference in total scores between any two actual teams + // 5.) minimize this value by choosing the combination of players with the smallest difference + + vector BestOrdering = PlayerIDs; + double BestDifference = -1.0; + + for( unsigned char i = StartTeam; i < 12; i++ ) + { + if( TeamSizes[i] > 0 ) + { + unsigned char Mid = TeamSizes[i]; + + // the base case where only one actual team worth of players was passed to this function is handled by the behaviour of next_combination + // in this case PlayerIDs.begin( ) + Mid will actually be equal to PlayerIDs.end( ) and next_combination will return false + + while( next_combination( PlayerIDs.begin( ), PlayerIDs.begin( ) + Mid, PlayerIDs.end( ) ) ) + { + // we're splitting the players into every possible combination of two "teams" based on the midpoint Mid + // the first (left) team contains the correct number of players but the second (right) "team" might or might not + // for example, it could contain one, two, or more actual teams worth of players + // so recurse using the second "team" as the full set of players to perform the balancing on + + vector BestSubOrdering = BalanceSlotsRecursive( vector( PlayerIDs.begin( ) + Mid, PlayerIDs.end( ) ), TeamSizes, PlayerScores, i + 1 ); + + // BestSubOrdering now contains the best ordering of all the remaining players (the "right team") given this particular combination of players into two "teams" + // in order to calculate the largest difference in total scores we need to recombine the subordering with the first team + + vector TestOrdering = vector( PlayerIDs.begin( ), PlayerIDs.begin( ) + Mid ); + TestOrdering.insert( TestOrdering.end( ), BestSubOrdering.begin( ), BestSubOrdering.end( ) ); + + // now calculate the team scores for all the teams that we know about (e.g. on subsequent recursion steps this will NOT be every possible team) + + vector :: iterator CurrentPID = TestOrdering.begin( ); + double TeamScores[12]; + + for( unsigned char j = StartTeam; j < 12; j++ ) + { + TeamScores[j] = 0.0; + + for( unsigned char k = 0; k < TeamSizes[j]; k++ ) + { + TeamScores[j] += PlayerScores[*CurrentPID]; + CurrentPID++; + } + } + + // find the largest difference in total scores between any two teams + + double LargestDifference = 0.0; + + for( unsigned char j = StartTeam; j < 12; j++ ) + { + if( TeamSizes[j] > 0 ) + { + for( unsigned char k = j + 1; k < 12; k++ ) + { + if( TeamSizes[k] > 0 ) + { + double Difference = abs( TeamScores[j] - TeamScores[k] ); + + if( Difference > LargestDifference ) + LargestDifference = Difference; + } + } + } + } + + // and minimize it + + if( BestDifference < 0.0 || LargestDifference < BestDifference ) + { + BestOrdering = TestOrdering; + BestDifference = LargestDifference; + } + } + } + } + + return BestOrdering; +} + +void CBaseGame :: BalanceSlots( ) +{ + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) ) + { + CONSOLE_Print( "[GAME: " + m_GameName + "] error balancing slots - can't balance slots without fixed player settings" ); + return; + } + + // setup the necessary variables for the balancing algorithm + // use an array of 13 elements for 12 players because GHost++ allocates PID's from 1-12 (i.e. excluding 0) and we use the PID to index the array + + vector PlayerIDs; + unsigned char TeamSizes[12]; + double PlayerScores[13]; + memset( TeamSizes, 0, sizeof( unsigned char ) * 12 ); + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + unsigned char PID = (*i)->GetPID( ); + + if( PID < 13 ) + { + unsigned char SID = GetSIDFromPID( PID ); + + if( SID < m_Slots.size( ) ) + { + unsigned char Team = m_Slots[SID].GetTeam( ); + + if( Team < 12 ) + { + // we are forced to use a default score because there's no way to balance the teams otherwise + + double Score = (*i)->GetScore( ); + + if( Score < -99999.0 ) + Score = m_Map->GetMapDefaultPlayerScore( ); + + PlayerIDs.push_back( PID ); + TeamSizes[Team]++; + PlayerScores[PID] = Score; + } + } + } + } + + sort( PlayerIDs.begin( ), PlayerIDs.end( ) ); + + // balancing the teams is a variation of the bin packing problem which is NP + // we can have up to 12 players and/or teams so the scope of the problem is sometimes small enough to process quickly + // let's try to figure out roughly how much work this is going to take + // examples: + // 2 teams of 4 = 70 ~ 5ms *** ok + // 2 teams of 5 = 252 ~ 5ms *** ok + // 2 teams of 6 = 924 ~ 20ms *** ok + // 3 teams of 2 = 90 ~ 5ms *** ok + // 3 teams of 3 = 1680 ~ 25ms *** ok + // 3 teams of 4 = 34650 ~ 250ms *** will cause a lag spike + // 4 teams of 2 = 2520 ~ 30ms *** ok + // 4 teams of 3 = 369600 ~ 3500ms *** unacceptable + + uint32_t AlgorithmCost = 0; + uint32_t PlayersLeft = PlayerIDs.size( ); + + for( unsigned char i = 0; i < 12; i++ ) + { + if( TeamSizes[i] > 0 ) + { + if( AlgorithmCost == 0 ) + AlgorithmCost = nCr( PlayersLeft, TeamSizes[i] ); + else + AlgorithmCost *= nCr( PlayersLeft, TeamSizes[i] ); + + PlayersLeft -= TeamSizes[i]; + } + } + + if( AlgorithmCost > 40000 ) + { + // the cost is too high, don't run the algorithm + // a possible alternative: stop after enough iterations and/or time has passed + + CONSOLE_Print( "[GAME: " + m_GameName + "] shuffling slots instead of balancing - the algorithm is too slow (with a cost of " + UTIL_ToString( AlgorithmCost ) + ") for this team configuration" ); + SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) ); + ShuffleSlots( ); + return; + } + + uint32_t StartTicks = GetTicks( ); + vector BestOrdering = BalanceSlotsRecursive( PlayerIDs, TeamSizes, PlayerScores, 0 ); + uint32_t EndTicks = GetTicks( ); + + // the BestOrdering assumes the teams are in slot order although this may not be the case + // so put the players on the correct teams regardless of slot order + + vector :: iterator CurrentPID = BestOrdering.begin( ); + + for( unsigned char i = 0; i < 12; i++ ) + { + unsigned char CurrentSlot = 0; + + for( unsigned char j = 0; j < TeamSizes[i]; j++ ) + { + while( CurrentSlot < m_Slots.size( ) && m_Slots[CurrentSlot].GetTeam( ) != i ) + CurrentSlot++; + + // put the CurrentPID player on team i by swapping them into CurrentSlot + + unsigned char SID1 = CurrentSlot; + unsigned char SID2 = GetSIDFromPID( *CurrentPID ); + + if( SID1 < m_Slots.size( ) && SID2 < m_Slots.size( ) ) + { + CGameSlot Slot1 = m_Slots[SID1]; + CGameSlot Slot2 = m_Slots[SID2]; + m_Slots[SID1] = CGameSlot( Slot2.GetPID( ), Slot2.GetDownloadStatus( ), Slot2.GetSlotStatus( ), Slot2.GetComputer( ), Slot1.GetTeam( ), Slot1.GetColour( ), Slot1.GetRace( ) ); + m_Slots[SID2] = CGameSlot( Slot1.GetPID( ), Slot1.GetDownloadStatus( ), Slot1.GetSlotStatus( ), Slot1.GetComputer( ), Slot2.GetTeam( ), Slot2.GetColour( ), Slot2.GetRace( ) ); + } + else + { + CONSOLE_Print( "[GAME: " + m_GameName + "] shuffling slots instead of balancing - the balancing algorithm tried to do an invalid swap (this shouldn't happen)" ); + SendAllChat( m_GHost->m_Language->ShufflingPlayers( ) ); + ShuffleSlots( ); + return; + } + + CurrentPID++; + CurrentSlot++; + } + } + + CONSOLE_Print( "[GAME: " + m_GameName + "] balancing slots completed in " + UTIL_ToString( EndTicks - StartTicks ) + "ms (with a cost of " + UTIL_ToString( AlgorithmCost ) + ")" ); + SendAllChat( m_GHost->m_Language->BalancingSlotsCompleted( ) ); + SendAllSlotInfo( ); + + for( unsigned char i = 0; i < 12; i++ ) + { + bool TeamHasPlayers = false; + double TeamScore = 0.0; + + for( vector :: iterator j = m_Players.begin( ); j != m_Players.end( ); j++ ) + { + unsigned char SID = GetSIDFromPID( (*j)->GetPID( ) ); + + if( SID < m_Slots.size( ) && m_Slots[SID].GetTeam( ) == i ) + { + TeamHasPlayers = true; + double Score = (*j)->GetScore( ); + + if( Score < -99999.0 ) + Score = m_Map->GetMapDefaultPlayerScore( ); + + TeamScore += Score; + } + } + + if( TeamHasPlayers ) + SendAllChat( m_GHost->m_Language->TeamCombinedScore( UTIL_ToString( i + 1 ), UTIL_ToString( TeamScore, 2 ) ) ); + } +} + +void CBaseGame :: AddToSpoofed( string server, string name, bool sendMessage ) +{ + CGamePlayer *Player = GetPlayerFromName( name, true ); + + if( Player ) + { + Player->SetSpoofedRealm( server ); + Player->SetSpoofed( true ); + + if( sendMessage ) + SendAllChat( m_GHost->m_Language->SpoofCheckAcceptedFor( server, name ) ); + } +} + +void CBaseGame :: AddToReserved( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + // check that the user is not already reserved + + for( vector :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); i++ ) + { + if( *i == name ) + return; + } + + m_Reserved.push_back( name ); + + // upgrade the user if they're already in the game + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + string NameLower = (*i)->GetName( ); + transform( NameLower.begin( ), NameLower.end( ), NameLower.begin( ), (int(*)(int))tolower ); + + if( NameLower == name ) + (*i)->SetReserved( true ); + } +} + +bool CBaseGame :: IsOwner( string name ) +{ + string OwnerLower = m_OwnerName; + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + transform( OwnerLower.begin( ), OwnerLower.end( ), OwnerLower.begin( ), (int(*)(int))tolower ); + return name == OwnerLower; +} + +bool CBaseGame :: IsReserved( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + + for( vector :: iterator i = m_Reserved.begin( ); i != m_Reserved.end( ); i++ ) + { + if( *i == name ) + return true; + } + + return false; +} + +bool CBaseGame :: IsDownloading( ) +{ + // returns true if at least one player is downloading the map + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetDownloadStarted( ) && !(*i)->GetDownloadFinished( ) ) + return true; + } + + return false; +} + +bool CBaseGame :: IsGameDataSaved( ) +{ + return true; +} + +void CBaseGame :: SaveGameData( ) +{ + +} + +void CBaseGame :: StartCountDown( bool force ) +{ + if( !m_CountDownStarted ) + { + if( force ) + { + m_CountDownStarted = true; + m_CountDownCounter = 5; + } + else + { + // check if the HCL command string is short enough + + if( m_HCLCommandString.size( ) > GetSlotsOccupied( ) ) + { + SendAllChat( m_GHost->m_Language->TheHCLIsTooLongUseForceToStart( ) ); + return; + } + + // check if everyone has the map + + string StillDownloading; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 ) + { + CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) ); + + if( Player ) + { + if( StillDownloading.empty( ) ) + StillDownloading = Player->GetName( ); + else + StillDownloading += ", " + Player->GetName( ); + } + } + } + + if( !StillDownloading.empty( ) ) + SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) ); + + // check if everyone is spoof checked + + string NotSpoofChecked; + + if( m_GHost->m_RequireSpoofChecks ) + { + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetSpoofed( ) ) + { + if( NotSpoofChecked.empty( ) ) + NotSpoofChecked = (*i)->GetName( ); + else + NotSpoofChecked += ", " + (*i)->GetName( ); + } + } + + if( !NotSpoofChecked.empty( ) ) + SendAllChat( m_GHost->m_Language->PlayersNotYetSpoofChecked( NotSpoofChecked ) ); + } + + // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now + // see function EventPlayerPongToHost for the autokicker code + + string NotPinged; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetReserved( ) && (*i)->GetNumPings( ) < 3 ) + { + if( NotPinged.empty( ) ) + NotPinged = (*i)->GetName( ); + else + NotPinged += ", " + (*i)->GetName( ); + } + } + + if( !NotPinged.empty( ) ) + SendAllChat( m_GHost->m_Language->PlayersNotYetPinged( NotPinged ) ); + + // if no problems found start the game + + if( StillDownloading.empty( ) && NotSpoofChecked.empty( ) && NotPinged.empty( ) ) + { + m_CountDownStarted = true; + m_CountDownCounter = 5; + } + } + } +} + +void CBaseGame :: StartCountDownAuto( bool requireSpoofChecks ) +{ + if( !m_CountDownStarted ) + { + // check if enough players are present + + if( GetNumHumanPlayers( ) < m_AutoStartPlayers ) + { + SendAllChat( m_GHost->m_Language->WaitingForPlayersBeforeAutoStart( UTIL_ToString( m_AutoStartPlayers ), UTIL_ToString( m_AutoStartPlayers - GetNumHumanPlayers( ) ) ) ); + return; + } + + // check if everyone has the map + + string StillDownloading; + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + { + if( (*i).GetSlotStatus( ) == SLOTSTATUS_OCCUPIED && (*i).GetComputer( ) == 0 && (*i).GetDownloadStatus( ) != 100 ) + { + CGamePlayer *Player = GetPlayerFromPID( (*i).GetPID( ) ); + + if( Player ) + { + if( StillDownloading.empty( ) ) + StillDownloading = Player->GetName( ); + else + StillDownloading += ", " + Player->GetName( ); + } + } + } + + if( !StillDownloading.empty( ) ) + { + SendAllChat( m_GHost->m_Language->PlayersStillDownloading( StillDownloading ) ); + return; + } + + // check if everyone is spoof checked + + string NotSpoofChecked; + + if( requireSpoofChecks ) + { + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetSpoofed( ) ) + { + if( NotSpoofChecked.empty( ) ) + NotSpoofChecked = (*i)->GetName( ); + else + NotSpoofChecked += ", " + (*i)->GetName( ); + } + } + + if( !NotSpoofChecked.empty( ) ) + SendAllChat( m_GHost->m_Language->PlayersNotYetSpoofChecked( NotSpoofChecked ) ); + } + + // check if everyone has been pinged enough (3 times) that the autokicker would have kicked them by now + // see function EventPlayerPongToHost for the autokicker code + + string NotPinged; + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( !(*i)->GetReserved( ) && (*i)->GetNumPings( ) < 3 ) + { + if( NotPinged.empty( ) ) + NotPinged = (*i)->GetName( ); + else + NotPinged += ", " + (*i)->GetName( ); + } + } + + if( !NotPinged.empty( ) ) + { + SendAllChat( m_GHost->m_Language->PlayersNotYetPingedAutoStart( NotPinged ) ); + return; + } + + // if no problems found start the game + + if( StillDownloading.empty( ) && NotSpoofChecked.empty( ) && NotPinged.empty( ) ) + { + m_CountDownStarted = true; + m_CountDownCounter = 10; + } + } +} + +void CBaseGame :: StopPlayers( string reason ) +{ + // disconnect every player and set their left reason to the passed string + // we use this function when we want the code in the Update function to run before the destructor (e.g. saving players to the database) + // therefore calling this function when m_GameLoading || m_GameLoaded is roughly equivalent to setting m_Exiting = true + // the only difference is whether the code in the Update function is executed or not + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + (*i)->SetDeleteMe( true ); + (*i)->SetLeftReason( reason ); + (*i)->SetLeftCode( PLAYERLEAVE_LOST ); + } +} + +void CBaseGame :: StopLaggers( string reason ) +{ + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i)->GetLagging( ) ) + { + (*i)->SetDeleteMe( true ); + (*i)->SetLeftReason( reason ); + (*i)->SetLeftCode( PLAYERLEAVE_DISCONNECT ); + } + } +} + +void CBaseGame :: CreateVirtualHost( ) +{ + if( m_VirtualHostPID != 255 ) + return; + + m_VirtualHostPID = GetNewPID( ); + BYTEARRAY IP; + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + SendAll( m_Protocol->SEND_W3GS_PLAYERINFO( m_VirtualHostPID, m_VirtualHostName, IP, IP ) ); +} + +void CBaseGame :: DeleteVirtualHost( ) +{ + if( m_VirtualHostPID == 255 ) + return; + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( m_VirtualHostPID, PLAYERLEAVE_LOBBY ) ); + m_VirtualHostPID = 255; +} + +void CBaseGame :: CreateFakePlayer( ) +{ + if( m_FakePlayerPID != 255 ) + return; + + unsigned char SID = GetEmptySlot( false ); + + if( SID < m_Slots.size( ) ) + { + if( GetNumPlayers( ) >= 11 ) + DeleteVirtualHost( ); + + m_FakePlayerPID = GetNewPID( ); + BYTEARRAY IP; + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + IP.push_back( 0 ); + SendAll( m_Protocol->SEND_W3GS_PLAYERINFO( m_FakePlayerPID, "FakePlayer", IP, IP ) ); + m_Slots[SID] = CGameSlot( m_FakePlayerPID, 100, SLOTSTATUS_OCCUPIED, 0, m_Slots[SID].GetTeam( ), m_Slots[SID].GetColour( ), m_Slots[SID].GetRace( ) ); + SendAllSlotInfo( ); + } +} + +void CBaseGame :: DeleteFakePlayer( ) +{ + if( m_FakePlayerPID == 255 ) + return; + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + { + if( m_Slots[i].GetPID( ) == m_FakePlayerPID ) + m_Slots[i] = CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, m_Slots[i].GetTeam( ), m_Slots[i].GetColour( ), m_Slots[i].GetRace( ) ); + } + + SendAll( m_Protocol->SEND_W3GS_PLAYERLEAVE_OTHERS( m_FakePlayerPID, PLAYERLEAVE_LOBBY ) ); + SendAllSlotInfo( ); + m_FakePlayerPID = 255; +} diff --git a/ghost-legacy/game_base.h b/ghost-legacy/game_base.h new file mode 100644 index 0000000..1868c2f --- /dev/null +++ b/ghost-legacy/game_base.h @@ -0,0 +1,273 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAME_BASE_H +#define GAME_BASE_H + +#include "gameslot.h" + +// +// CBaseGame +// + +class CTCPServer; +class CGameProtocol; +class CPotentialPlayer; +class CGamePlayer; +class CMap; +class CSaveGame; +class CReplay; +class CIncomingJoinPlayer; +class CIncomingAction; +class CIncomingChatPlayer; +class CIncomingMapSize; +class CCallableScoreCheck; + +class CBaseGame +{ +public: + CGHost *m_GHost; + +protected: + CTCPServer *m_Socket; // listening socket + CGameProtocol *m_Protocol; // game protocol + vector m_Slots; // vector of slots + vector m_Potentials; // vector of potential players (connections that haven't sent a W3GS_REQJOIN packet yet) + vector m_Players; // vector of players + vector m_ScoreChecks; + queue m_Actions; // queue of actions to be sent + vector m_Reserved; // vector of player names with reserved slots (from the !hold command) + set m_IgnoredNames; // set of player names to NOT print ban messages for when joining because they've already been printed + set m_IPBlackList; // set of IP addresses to blacklist from joining (todotodo: convert to uint32's for efficiency) + vector m_EnforceSlots; // vector of slots to force players to use (used with saved games) + vector m_EnforcePlayers; // vector of pids to force players to use (used with saved games) + CMap *m_Map; // map data + CSaveGame *m_SaveGame; // savegame data (this is a pointer to global data) + CReplay *m_Replay; // replay + bool m_Exiting; // set to true and this class will be deleted next update + bool m_Saving; // if we're currently saving game data to the database + uint16_t m_HostPort; // the port to host games on + unsigned char m_GameState; // game state, public or private + unsigned char m_VirtualHostPID; // virtual host's PID + unsigned char m_FakePlayerPID; // the fake player's PID (if present) + unsigned char m_GProxyEmptyActions; + string m_GameName; // game name + string m_LastGameName; // last game name (the previous game name before it was rehosted) + string m_VirtualHostName; // virtual host's name + string m_OwnerName; // name of the player who owns this game (should be considered an admin) + string m_CreatorName; // name of the player who created this game + string m_CreatorServer; // battle.net server the player who created this game was on + string m_AnnounceMessage; // a message to be sent every m_AnnounceInterval seconds + string m_StatString; // the stat string when the game started (used when saving replays) + string m_KickVotePlayer; // the player to be kicked with the currently running kick vote + string m_HCLCommandString; // the "HostBot Command Library" command string, used to pass a limited amount of data to specially designed maps + uint32_t m_RandomSeed; // the random seed sent to the Warcraft III clients + uint32_t m_HostCounter; // a unique game number + uint32_t m_Latency; // the number of ms to wait between sending action packets (we queue any received during this time) + uint32_t m_SyncLimit; // the maximum number of packets a player can fall out of sync before starting the lag screen + uint32_t m_SyncCounter; // the number of actions sent so far (for determining if anyone is lagging) + uint32_t m_GameTicks; // ingame ticks + uint32_t m_CreationTime; // GetTime when the game was created + uint32_t m_LastPingTime; // GetTime when the last ping was sent + uint32_t m_LastRefreshTime; // GetTime when the last game refresh was sent + uint32_t m_LastDownloadTicks; // GetTicks when the last map download cycle was performed + uint32_t m_DownloadCounter; // # of map bytes downloaded in the last second + uint32_t m_LastDownloadCounterResetTicks; // GetTicks when the download counter was last reset + uint32_t m_LastAnnounceTime; // GetTime when the last announce message was sent + uint32_t m_AnnounceInterval; // how many seconds to wait between sending the m_AnnounceMessage + uint32_t m_LastAutoStartTime; // the last time we tried to auto start the game + uint32_t m_AutoStartPlayers; // auto start the game when there are this many players or more + uint32_t m_LastCountDownTicks; // GetTicks when the last countdown message was sent + uint32_t m_CountDownCounter; // the countdown is finished when this reaches zero + uint32_t m_StartedLoadingTicks; // GetTicks when the game started loading + uint32_t m_StartPlayers; // number of players when the game started + uint32_t m_LastLagScreenResetTime; // GetTime when the "lag" screen was last reset + uint32_t m_LastActionSentTicks; // GetTicks when the last action packet was sent + uint32_t m_LastActionLateBy; // the number of ticks we were late sending the last action packet by + uint32_t m_StartedLaggingTime; // GetTime when the last lag screen started + uint32_t m_LastLagScreenTime; // GetTime when the last lag screen was active (continuously updated) + uint32_t m_LastReservedSeen; // GetTime when the last reserved player was seen in the lobby + uint32_t m_StartedKickVoteTime; // GetTime when the kick vote was started + uint32_t m_GameOverTime; // GetTime when the game was over + uint32_t m_LastPlayerLeaveTicks; // GetTicks when the most recent player left the game + double m_MinimumScore; // the minimum allowed score for matchmaking mode + double m_MaximumScore; // the maximum allowed score for matchmaking mode + bool m_SlotInfoChanged; // if the slot info has changed and hasn't been sent to the players yet (optimization) + bool m_Locked; // if the game owner is the only one allowed to run game commands or not + bool m_RefreshMessages; // if we should display "game refreshed..." messages or not + bool m_RefreshError; // if there was an error refreshing the game + bool m_RefreshRehosted; // if we just rehosted and are waiting for confirmation that it was successful + bool m_MuteAll; // if we should stop forwarding ingame chat messages targeted for all players or not + bool m_MuteLobby; // if we should stop forwarding lobby chat messages + bool m_CountDownStarted; // if the game start countdown has started or not + bool m_GameLoading; // if the game is currently loading or not + bool m_GameLoaded; // if the game has loaded or not + bool m_LoadInGame; // if the load-in-game feature is enabled or not + bool m_Lagging; // if the lag screen is active or not + bool m_AutoSave; // if we should auto save the game before someone disconnects + bool m_MatchMaking; // if matchmaking mode is enabled + bool m_LocalAdminMessages; // if local admin messages should be relayed or not + +public: + CBaseGame( CGHost *nGHost, CMap *nMap, CSaveGame *nSaveGame, uint16_t nHostPort, unsigned char nGameState, string nGameName, string nOwnerName, string nCreatorName, string nCreatorServer ); + virtual ~CBaseGame( ); + + virtual vector GetEnforceSlots( ) { return m_EnforceSlots; } + virtual vector GetEnforcePlayers( ) { return m_EnforcePlayers; } + virtual CSaveGame *GetSaveGame( ) { return m_SaveGame; } + virtual uint16_t GetHostPort( ) { return m_HostPort; } + virtual unsigned char GetGameState( ) { return m_GameState; } + virtual unsigned char GetGProxyEmptyActions( ) { return m_GProxyEmptyActions; } + virtual string GetGameName( ) { return m_GameName; } + virtual string GetLastGameName( ) { return m_LastGameName; } + virtual string GetVirtualHostName( ) { return m_VirtualHostName; } + virtual string GetOwnerName( ) { return m_OwnerName; } + virtual string GetCreatorName( ) { return m_CreatorName; } + virtual string GetCreatorServer( ) { return m_CreatorServer; } + virtual uint32_t GetHostCounter( ) { return m_HostCounter; } + virtual uint32_t GetLastLagScreenTime( ) { return m_LastLagScreenTime; } + virtual bool GetLocked( ) { return m_Locked; } + virtual bool GetRefreshMessages( ) { return m_RefreshMessages; } + virtual bool GetCountDownStarted( ) { return m_CountDownStarted; } + virtual bool GetGameLoading( ) { return m_GameLoading; } + virtual bool GetGameLoaded( ) { return m_GameLoaded; } + virtual bool GetLagging( ) { return m_Lagging; } + + virtual void SetEnforceSlots( vector nEnforceSlots ) { m_EnforceSlots = nEnforceSlots; } + virtual void SetEnforcePlayers( vector nEnforcePlayers ) { m_EnforcePlayers = nEnforcePlayers; } + virtual void SetExiting( bool nExiting ) { m_Exiting = nExiting; } + virtual void SetAutoStartPlayers( uint32_t nAutoStartPlayers ) { m_AutoStartPlayers = nAutoStartPlayers; } + virtual void SetMinimumScore( double nMinimumScore ) { m_MinimumScore = nMinimumScore; } + virtual void SetMaximumScore( double nMaximumScore ) { m_MaximumScore = nMaximumScore; } + virtual void SetRefreshError( bool nRefreshError ) { m_RefreshError = nRefreshError; } + virtual void SetMatchMaking( bool nMatchMaking ) { m_MatchMaking = nMatchMaking; } + + virtual uint32_t GetNextTimedActionTicks( ); + virtual uint32_t GetSlotsOccupied( ); + virtual uint32_t GetSlotsOpen( ); + virtual uint32_t GetNumPlayers( ); + virtual uint32_t GetNumHumanPlayers( ); + virtual string GetDescription( ); + + virtual void SetAnnounce( uint32_t interval, string message ); + + // processing functions + + virtual unsigned int SetFD( void *fd, void *send_fd, int *nfds ); + virtual bool Update( void *fd, void *send_fd ); + virtual void UpdatePost( void *send_fd ); + + // generic functions to send packets to players + + virtual void Send( CGamePlayer *player, BYTEARRAY data ); + virtual void Send( unsigned char PID, BYTEARRAY data ); + virtual void Send( BYTEARRAY PIDs, BYTEARRAY data ); + virtual void SendAll( BYTEARRAY data ); + + // functions to send packets to players + + virtual void SendChat( unsigned char fromPID, CGamePlayer *player, string message ); + virtual void SendChat( unsigned char fromPID, unsigned char toPID, string message ); + virtual void SendChat( CGamePlayer *player, string message ); + virtual void SendChat( unsigned char toPID, string message ); + virtual void SendAllChat( unsigned char fromPID, string message ); + virtual void SendAllChat( string message ); + virtual void SendLocalAdminChat( string message ); + virtual void SendAllSlotInfo( ); + virtual void SendVirtualHostPlayerInfo( CGamePlayer *player ); + virtual void SendFakePlayerInfo( CGamePlayer *player ); + virtual void SendAllActions( ); + virtual void SendWelcomeMessage( CGamePlayer *player ); + virtual void SendEndMessage( ); + + // events + // note: these are only called while iterating through the m_Potentials or m_Players vectors + // therefore you can't modify those vectors and must use the player's m_DeleteMe member to flag for deletion + + virtual void EventPlayerDeleted( CGamePlayer *player ); + virtual void EventPlayerDisconnectTimedOut( CGamePlayer *player ); + virtual void EventPlayerDisconnectPlayerError( CGamePlayer *player ); + virtual void EventPlayerDisconnectSocketError( CGamePlayer *player ); + virtual void EventPlayerDisconnectConnectionClosed( CGamePlayer *player ); + virtual void EventPlayerJoined( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer ); + virtual void EventPlayerJoinedWithScore( CPotentialPlayer *potential, CIncomingJoinPlayer *joinPlayer, double score ); + virtual void EventPlayerLeft( CGamePlayer *player, uint32_t reason ); + virtual void EventPlayerLoaded( CGamePlayer *player ); + virtual void EventPlayerAction( CGamePlayer *player, CIncomingAction *action ); + virtual void EventPlayerKeepAlive( CGamePlayer *player, uint32_t checkSum ); + virtual void EventPlayerChatToHost( CGamePlayer *player, CIncomingChatPlayer *chatPlayer ); + virtual bool EventPlayerBotCommand( CGamePlayer *player, string command, string payload ); + virtual void EventPlayerChangeTeam( CGamePlayer *player, unsigned char team ); + virtual void EventPlayerChangeColour( CGamePlayer *player, unsigned char colour ); + virtual void EventPlayerChangeRace( CGamePlayer *player, unsigned char race ); + virtual void EventPlayerChangeHandicap( CGamePlayer *player, unsigned char handicap ); + virtual void EventPlayerDropRequest( CGamePlayer *player ); + virtual void EventPlayerMapSize( CGamePlayer *player, CIncomingMapSize *mapSize ); + virtual void EventPlayerPongToHost( CGamePlayer *player, uint32_t pong ); + + // these events are called outside of any iterations + + virtual void EventGameRefreshed( string server ); + virtual void EventGameStarted( ); + virtual void EventGameLoaded( ); + + // other functions + + virtual unsigned char GetSIDFromPID( unsigned char PID ); + virtual CGamePlayer *GetPlayerFromPID( unsigned char PID ); + virtual CGamePlayer *GetPlayerFromSID( unsigned char SID ); + virtual CGamePlayer *GetPlayerFromName( string name, bool sensitive ); + virtual uint32_t GetPlayerFromNamePartial( string name, CGamePlayer **player ); + virtual CGamePlayer *GetPlayerFromColour( unsigned char colour ); + virtual unsigned char GetNewPID( ); + virtual unsigned char GetNewColour( ); + virtual BYTEARRAY GetPIDs( ); + virtual BYTEARRAY GetPIDs( unsigned char excludePID ); + virtual unsigned char GetHostPID( ); + virtual unsigned char GetEmptySlot( bool reserved ); + virtual unsigned char GetEmptySlot( unsigned char team, unsigned char PID ); + virtual void SwapSlots( unsigned char SID1, unsigned char SID2 ); + virtual void OpenSlot( unsigned char SID, bool kick ); + virtual void CloseSlot( unsigned char SID, bool kick ); + virtual void ComputerSlot( unsigned char SID, unsigned char skill, bool kick ); + virtual void ColourSlot( unsigned char SID, unsigned char colour ); + virtual void OpenAllSlots( ); + virtual void CloseAllSlots( ); + virtual void ShuffleSlots( ); + virtual vector BalanceSlotsRecursive( vector PlayerIDs, unsigned char *TeamSizes, double *PlayerScores, unsigned char StartTeam ); + virtual void BalanceSlots( ); + virtual void AddToSpoofed( string server, string name, bool sendMessage ); + virtual void AddToReserved( string name ); + virtual bool IsOwner( string name ); + virtual bool IsReserved( string name ); + virtual bool IsDownloading( ); + virtual bool IsGameDataSaved( ); + virtual void SaveGameData( ); + virtual void StartCountDown( bool force ); + virtual void StartCountDownAuto( bool requireSpoofChecks ); + virtual void StopPlayers( string reason ); + virtual void StopLaggers( string reason ); + virtual void CreateVirtualHost( ); + virtual void DeleteVirtualHost( ); + virtual void CreateFakePlayer( ); + virtual void DeleteFakePlayer( ); +}; + +#endif diff --git a/ghost-legacy/gameplayer.cpp b/ghost-legacy/gameplayer.cpp new file mode 100644 index 0000000..0a78367 --- /dev/null +++ b/ghost-legacy/gameplayer.cpp @@ -0,0 +1,671 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "language.h" +#include "socket.h" +#include "commandpacket.h" +#include "bnet.h" +#include "map.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "gpsprotocol.h" +#include "game_base.h" + +// +// CPotentialPlayer +// + +CPotentialPlayer :: CPotentialPlayer( CGameProtocol *nProtocol, CBaseGame *nGame, CTCPSocket *nSocket ) +{ + m_Protocol = nProtocol; + m_Game = nGame; + m_Socket = nSocket; + m_DeleteMe = false; + m_Error = false; + m_IncomingJoinPlayer = NULL; +} + +CPotentialPlayer :: ~CPotentialPlayer( ) +{ + if( m_Socket ) + delete m_Socket; + + while( !m_Packets.empty( ) ) + { + delete m_Packets.front( ); + m_Packets.pop( ); + } + + delete m_IncomingJoinPlayer; +} + +BYTEARRAY CPotentialPlayer :: GetExternalIP( ) +{ + unsigned char Zeros[] = { 0, 0, 0, 0 }; + + if( m_Socket ) + return m_Socket->GetIP( ); + + return UTIL_CreateByteArray( Zeros, 4 ); +} + +string CPotentialPlayer :: GetExternalIPString( ) +{ + if( m_Socket ) + return m_Socket->GetIPString( ); + + return string( ); +} + +bool CPotentialPlayer :: Update( void *fd ) +{ + if( m_DeleteMe ) + return true; + + if( !m_Socket ) + return false; + + m_Socket->DoRecv( (fd_set *)fd ); + ExtractPackets( ); + ProcessPackets( ); + + // don't call DoSend here because some other players may not have updated yet and may generate a packet for this player + // also m_Socket may have been set to NULL during ProcessPackets but we're banking on the fact that m_DeleteMe has been set to true as well so it'll short circuit before dereferencing + + return m_DeleteMe || m_Error || m_Socket->HasError( ) || !m_Socket->GetConnected( ); +} + +void CPotentialPlayer :: ExtractPackets( ) +{ + if( !m_Socket ) + return; + + // extract as many packets as possible from the socket's receive buffer and put them in the m_Packets queue + + string *RecvBuffer = m_Socket->GetBytes( ); + BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); + + // a packet is at least 4 bytes so loop as long as the buffer contains 4 bytes + + while( Bytes.size( ) >= 4 ) + { + if( Bytes[0] == W3GS_HEADER_CONSTANT || Bytes[0] == GPS_HEADER_CONSTANT ) + { + // bytes 2 and 3 contain the length of the packet + + uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); + + if( Length >= 4 ) + { + if( Bytes.size( ) >= Length ) + { + m_Packets.push( new CCommandPacket( Bytes[0], Bytes[1], BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); + *RecvBuffer = RecvBuffer->substr( Length ); + Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); + } + else + return; + } + else + { + m_Error = true; + m_ErrorString = "received invalid packet from player (bad length)"; + return; + } + } + else + { + m_Error = true; + m_ErrorString = "received invalid packet from player (bad header constant)"; + return; + } + } +} + +void CPotentialPlayer :: ProcessPackets( ) +{ + if( !m_Socket ) + return; + + // process all the received packets in the m_Packets queue + + while( !m_Packets.empty( ) ) + { + CCommandPacket *Packet = m_Packets.front( ); + m_Packets.pop( ); + + if( Packet->GetPacketType( ) == W3GS_HEADER_CONSTANT ) + { + // the only packet we care about as a potential player is W3GS_REQJOIN, ignore everything else + + switch( Packet->GetID( ) ) + { + case CGameProtocol :: W3GS_REQJOIN: + delete m_IncomingJoinPlayer; + m_IncomingJoinPlayer = m_Protocol->RECEIVE_W3GS_REQJOIN( Packet->GetData( ) ); + + if( m_IncomingJoinPlayer ) + m_Game->EventPlayerJoined( this, m_IncomingJoinPlayer ); + + // don't continue looping because there may be more packets waiting and this parent class doesn't handle them + // EventPlayerJoined creates the new player, NULLs the socket, and sets the delete flag on this object so it'll be deleted shortly + // any unprocessed packets will be copied to the new CGamePlayer in the constructor or discarded if we get deleted because the game is full + + delete Packet; + return; + } + } + + delete Packet; + } +} + +void CPotentialPlayer :: Send( BYTEARRAY data ) +{ + if( m_Socket ) + m_Socket->PutBytes( data ); +} + +// +// CGamePlayer +// + +CGamePlayer :: CGamePlayer( CGameProtocol *nProtocol, CBaseGame *nGame, CTCPSocket *nSocket, unsigned char nPID, string nJoinedRealm, string nName, BYTEARRAY nInternalIP, bool nReserved ) : CPotentialPlayer( nProtocol, nGame, nSocket ) +{ + m_PID = nPID; + m_Name = nName; + m_InternalIP = nInternalIP; + m_JoinedRealm = nJoinedRealm; + m_TotalPacketsSent = 0; + m_TotalPacketsReceived = 0; + m_LeftCode = PLAYERLEAVE_LOBBY; + m_LoginAttempts = 0; + m_SyncCounter = 0; + m_JoinTime = GetTime( ); + m_LastMapPartSent = 0; + m_LastMapPartAcked = 0; + m_StartedDownloadingTicks = 0; + m_FinishedDownloadingTime = 0; + m_FinishedLoadingTicks = 0; + m_StartedLaggingTicks = 0; + m_StatsSentTime = 0; + m_StatsDotASentTime = 0; + m_LastGProxyWaitNoticeSentTime = 0; + m_Score = -100000.0; + m_LoggedIn = false; + m_Spoofed = false; + m_Reserved = nReserved; + m_WhoisShouldBeSent = false; + m_WhoisSent = false; + m_DownloadAllowed = false; + m_DownloadStarted = false; + m_DownloadFinished = false; + m_FinishedLoading = false; + m_Lagging = false; + m_DropVote = false; + m_KickVote = false; + m_Muted = false; + m_LeftMessageSent = false; + m_GProxy = false; + m_GProxyDisconnectNoticeSent = false; + m_GProxyReconnectKey = GetTicks( ); + m_LastGProxyAckTime = 0; +} + +CGamePlayer :: CGamePlayer( CPotentialPlayer *potential, unsigned char nPID, string nJoinedRealm, string nName, BYTEARRAY nInternalIP, bool nReserved ) : CPotentialPlayer( potential->m_Protocol, potential->m_Game, potential->GetSocket( ) ) +{ + // todotodo: properly copy queued packets to the new player, this just discards them + // this isn't a big problem because official Warcraft III clients don't send any packets after the join request until they receive a response + + // m_Packets = potential->GetPackets( ); + m_PID = nPID; + m_Name = nName; + m_InternalIP = nInternalIP; + m_JoinedRealm = nJoinedRealm; + m_TotalPacketsSent = 0; + + // hackhack: we initialize this to 1 because the CPotentialPlayer must have received a W3GS_REQJOIN before this class was created + // to fix this we could move the packet counters to CPotentialPlayer and copy them here + // note: we must make sure we never send a packet to a CPotentialPlayer otherwise the send counter will be incorrect too! what a mess this is... + // that said, the packet counters are only used for managing GProxy++ reconnections + + m_TotalPacketsReceived = 1; + m_LeftCode = PLAYERLEAVE_LOBBY; + m_LoginAttempts = 0; + m_SyncCounter = 0; + m_JoinTime = GetTime( ); + m_LastMapPartSent = 0; + m_LastMapPartAcked = 0; + m_StartedDownloadingTicks = 0; + m_FinishedDownloadingTime = 0; + m_FinishedLoadingTicks = 0; + m_StartedLaggingTicks = 0; + m_StatsSentTime = 0; + m_StatsDotASentTime = 0; + m_LastGProxyWaitNoticeSentTime = 0; + m_Score = -100000.0; + m_LoggedIn = false; + m_Spoofed = false; + m_Reserved = nReserved; + m_WhoisShouldBeSent = false; + m_WhoisSent = false; + m_DownloadAllowed = false; + m_DownloadStarted = false; + m_DownloadFinished = false; + m_FinishedLoading = false; + m_Lagging = false; + m_DropVote = false; + m_KickVote = false; + m_Muted = false; + m_LeftMessageSent = false; + m_GProxy = false; + m_GProxyDisconnectNoticeSent = false; + m_GProxyReconnectKey = GetTicks( ); + m_LastGProxyAckTime = 0; +} + +CGamePlayer :: ~CGamePlayer( ) +{ + +} + +string CGamePlayer :: GetNameTerminated( ) +{ + // if the player's name contains an unterminated colour code add the colour terminator to the end of their name + // this is useful because it allows you to print the player's name in a longer message which doesn't colour all the subsequent text + + string LowerName = m_Name; + transform( LowerName.begin( ), LowerName.end( ), LowerName.begin( ), (int(*)(int))tolower ); + string :: size_type Start = LowerName.find( "|c" ); + string :: size_type End = LowerName.find( "|r" ); + + if( Start != string :: npos && ( End == string :: npos || End < Start ) ) + return m_Name + "|r"; + else + return m_Name; +} + +uint32_t CGamePlayer :: GetPing( bool LCPing ) +{ + // just average all the pings in the vector, nothing fancy + + if( m_Pings.empty( ) ) + return 0; + + uint32_t AvgPing = 0; + + for( unsigned int i = 0; i < m_Pings.size( ); i++ ) + AvgPing += m_Pings[i]; + + AvgPing /= m_Pings.size( ); + + if( LCPing ) + return AvgPing / 2; + else + return AvgPing; +} + +bool CGamePlayer :: Update( void *fd ) +{ + // wait 4 seconds after joining before sending the /whois or /w + // if we send the /whois too early battle.net may not have caught up with where the player is and return erroneous results + + if( m_WhoisShouldBeSent && !m_Spoofed && !m_WhoisSent && !m_JoinedRealm.empty( ) && GetTime( ) - m_JoinTime >= 4 ) + { + // todotodo: we could get kicked from battle.net for sending a command with invalid characters, do some basic checking + + for( vector :: iterator i = m_Game->m_GHost->m_BNETs.begin( ); i != m_Game->m_GHost->m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == m_JoinedRealm ) + { + if( m_Game->GetGameState( ) == GAME_PUBLIC ) + { + if( (*i)->GetPasswordHashType( ) == "pvpgn" ) + (*i)->QueueChatCommand( "/whereis " + m_Name ); + else + (*i)->QueueChatCommand( "/whois " + m_Name ); + } + else if( m_Game->GetGameState( ) == GAME_PRIVATE ) + (*i)->QueueChatCommand( m_Game->m_GHost->m_Language->SpoofCheckByReplying( ), m_Name, true ); + } + } + + m_WhoisSent = true; + } + + // check for socket timeouts + // if we don't receive anything from a player for 30 seconds we can assume they've dropped + // this works because in the lobby we send pings every 5 seconds and expect a response to each one + // and in the game the Warcraft 3 client sends keepalives frequently (at least once per second it looks like) + + if( m_Socket && GetTime( ) - m_Socket->GetLastRecv( ) >= 30 ) + m_Game->EventPlayerDisconnectTimedOut( this ); + + // GProxy++ acks + + if( m_GProxy && GetTime( ) - m_LastGProxyAckTime >= 10 ) + { + if( m_Socket ) + m_Socket->PutBytes( m_Game->m_GHost->m_GPSProtocol->SEND_GPSS_ACK( m_TotalPacketsReceived ) ); + + m_LastGProxyAckTime = GetTime( ); + } + + // base class update + + CPotentialPlayer :: Update( fd ); + bool Deleting; + + if( m_GProxy && m_Game->GetGameLoaded( ) ) + Deleting = m_DeleteMe || m_Error; + else + Deleting = m_DeleteMe || m_Error || m_Socket->HasError( ) || !m_Socket->GetConnected( ); + + // try to find out why we're requesting deletion + // in cases other than the ones covered here m_LeftReason should have been set when m_DeleteMe was set + + if( m_Error ) + m_Game->EventPlayerDisconnectPlayerError( this ); + + if( m_Socket ) + { + if( m_Socket->HasError( ) ) + m_Game->EventPlayerDisconnectSocketError( this ); + + if( !m_Socket->GetConnected( ) ) + m_Game->EventPlayerDisconnectConnectionClosed( this ); + } + + return Deleting; +} + +void CGamePlayer :: ExtractPackets( ) +{ + if( !m_Socket ) + return; + + // extract as many packets as possible from the socket's receive buffer and put them in the m_Packets queue + + string *RecvBuffer = m_Socket->GetBytes( ); + BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); + + // a packet is at least 4 bytes so loop as long as the buffer contains 4 bytes + + while( Bytes.size( ) >= 4 ) + { + if( Bytes[0] == W3GS_HEADER_CONSTANT || Bytes[0] == GPS_HEADER_CONSTANT ) + { + // bytes 2 and 3 contain the length of the packet + + uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); + + if( Length >= 4 ) + { + if( Bytes.size( ) >= Length ) + { + m_Packets.push( new CCommandPacket( Bytes[0], Bytes[1], BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); + + if( Bytes[0] == W3GS_HEADER_CONSTANT ) + m_TotalPacketsReceived++; + + *RecvBuffer = RecvBuffer->substr( Length ); + Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); + } + else + return; + } + else + { + m_Error = true; + m_ErrorString = "received invalid packet from player (bad length)"; + return; + } + } + else + { + m_Error = true; + m_ErrorString = "received invalid packet from player (bad header constant)"; + return; + } + } +} + +void CGamePlayer :: ProcessPackets( ) +{ + if( !m_Socket ) + return; + + CIncomingAction *Action = NULL; + CIncomingChatPlayer *ChatPlayer = NULL; + CIncomingMapSize *MapSize = NULL; + bool HasMap = false; + uint32_t CheckSum = 0; + uint32_t Pong = 0; + + // process all the received packets in the m_Packets queue + + while( !m_Packets.empty( ) ) + { + CCommandPacket *Packet = m_Packets.front( ); + m_Packets.pop( ); + + if( Packet->GetPacketType( ) == W3GS_HEADER_CONSTANT ) + { + switch( Packet->GetID( ) ) + { + case CGameProtocol :: W3GS_LEAVEGAME: + m_Game->EventPlayerLeft( this, m_Protocol->RECEIVE_W3GS_LEAVEGAME( Packet->GetData( ) ) ); + break; + + case CGameProtocol :: W3GS_GAMELOADED_SELF: + if( m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF( Packet->GetData( ) ) ) + { + if( !m_FinishedLoading ) + { + m_FinishedLoading = true; + m_FinishedLoadingTicks = GetTicks( ); + m_Game->EventPlayerLoaded( this ); + } + else + { + // we received two W3GS_GAMELOADED_SELF packets from this player! + } + } + + break; + + case CGameProtocol :: W3GS_OUTGOING_ACTION: + Action = m_Protocol->RECEIVE_W3GS_OUTGOING_ACTION( Packet->GetData( ), m_PID ); + + if( Action ) + m_Game->EventPlayerAction( this, Action ); + + // don't delete Action here because the game is going to store it in a queue and delete it later + + break; + + case CGameProtocol :: W3GS_OUTGOING_KEEPALIVE: + CheckSum = m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE( Packet->GetData( ) ); + m_CheckSums.push( CheckSum ); + m_SyncCounter++; + m_Game->EventPlayerKeepAlive( this, CheckSum ); + break; + + case CGameProtocol :: W3GS_CHAT_TO_HOST: + ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST( Packet->GetData( ) ); + + if( ChatPlayer ) + m_Game->EventPlayerChatToHost( this, ChatPlayer ); + + delete ChatPlayer; + ChatPlayer = NULL; + break; + + case CGameProtocol :: W3GS_DROPREQ: + // todotodo: no idea what's in this packet + + if( !m_DropVote ) + { + m_DropVote = true; + m_Game->EventPlayerDropRequest( this ); + } + + break; + + case CGameProtocol :: W3GS_MAPSIZE: + MapSize = m_Protocol->RECEIVE_W3GS_MAPSIZE( Packet->GetData( ), m_Game->m_GHost->m_Map->GetMapSize( ) ); + + if( MapSize ) + m_Game->EventPlayerMapSize( this, MapSize ); + + delete MapSize; + MapSize = NULL; + break; + + case CGameProtocol :: W3GS_PONG_TO_HOST: + Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST( Packet->GetData( ) ); + + // we discard pong values of 1 + // the client sends one of these when connecting plus we return 1 on error to kill two birds with one stone + + if( Pong != 1 ) + { + // we also discard pong values when we're downloading because they're almost certainly inaccurate + // this statement also gives the player a 5 second grace period after downloading the map to allow queued (i.e. delayed) ping packets to be ignored + + if( !m_DownloadStarted || ( m_DownloadFinished && GetTime( ) - m_FinishedDownloadingTime >= 5 ) ) + { + // we also discard pong values when anyone else is downloading if we're configured to + + if( m_Game->m_GHost->m_PingDuringDownloads || !m_Game->IsDownloading( ) ) + { + m_Pings.push_back( GetTicks( ) - Pong ); + + if( m_Pings.size( ) > 20 ) + m_Pings.erase( m_Pings.begin( ) ); + } + } + } + + m_Game->EventPlayerPongToHost( this, Pong ); + break; + } + } + else if( Packet->GetPacketType( ) == GPS_HEADER_CONSTANT ) + { + BYTEARRAY Data = Packet->GetData( ); + + if( Packet->GetID( ) == CGPSProtocol :: GPS_INIT ) + { + if( m_Game->m_GHost->m_Reconnect ) + { + m_GProxy = true; + m_Socket->PutBytes( m_Game->m_GHost->m_GPSProtocol->SEND_GPSS_INIT( m_Game->m_GHost->m_ReconnectPort, m_PID, m_GProxyReconnectKey, m_Game->GetGProxyEmptyActions( ) ) ); + CONSOLE_Print( "[GAME: " + m_Game->GetGameName( ) + "] player [" + m_Name + "] is using GProxy++" ); + } + else + { + // todotodo: send notice that we're not permitting reconnects + // note: GProxy++ should never send a GPS_INIT message if bot_reconnect = 0 because we don't advertise the game with invalid map dimensions + // but it would be nice to cover this case anyway + } + } + else if( Packet->GetID( ) == CGPSProtocol :: GPS_RECONNECT ) + { + // this is handled in ghost.cpp + } + else if( Packet->GetID( ) == CGPSProtocol :: GPS_ACK && Data.size( ) == 8 ) + { + uint32_t LastPacket = UTIL_ByteArrayToUInt32( Data, false, 4 ); + uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); + + if( LastPacket > PacketsAlreadyUnqueued ) + { + uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; + + if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) + PacketsToUnqueue = m_GProxyBuffer.size( ); + + while( PacketsToUnqueue > 0 ) + { + m_GProxyBuffer.pop( ); + PacketsToUnqueue--; + } + } + } + } + + delete Packet; + } +} + +void CGamePlayer :: Send( BYTEARRAY data ) +{ + // must start counting packet total from beginning of connection + // but we can avoid buffering packets until we know the client is using GProxy++ since that'll be determined before the game starts + // this prevents us from buffering packets for non-GProxy++ clients + + m_TotalPacketsSent++; + + if( m_GProxy && m_Game->GetGameLoaded( ) ) + m_GProxyBuffer.push( data ); + + CPotentialPlayer :: Send( data ); +} + +void CGamePlayer :: EventGProxyReconnect( CTCPSocket *NewSocket, uint32_t LastPacket ) +{ + delete m_Socket; + m_Socket = NewSocket; + m_Socket->PutBytes( m_Game->m_GHost->m_GPSProtocol->SEND_GPSS_RECONNECT( m_TotalPacketsReceived ) ); + + uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); + + if( LastPacket > PacketsAlreadyUnqueued ) + { + uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; + + if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) + PacketsToUnqueue = m_GProxyBuffer.size( ); + + while( PacketsToUnqueue > 0 ) + { + m_GProxyBuffer.pop( ); + PacketsToUnqueue--; + } + } + + // send remaining packets from buffer, preserve buffer + + queue TempBuffer; + + while( !m_GProxyBuffer.empty( ) ) + { + m_Socket->PutBytes( m_GProxyBuffer.front( ) ); + TempBuffer.push( m_GProxyBuffer.front( ) ); + m_GProxyBuffer.pop( ); + } + + m_GProxyBuffer = TempBuffer; + m_GProxyDisconnectNoticeSent = false; + m_Game->SendAllChat( m_Game->m_GHost->m_Language->PlayerReconnectedWithGProxy( m_Name ) ); +} diff --git a/ghost-legacy/gameplayer.h b/ghost-legacy/gameplayer.h new file mode 100644 index 0000000..262d948 --- /dev/null +++ b/ghost-legacy/gameplayer.h @@ -0,0 +1,222 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAMEPLAYER_H +#define GAMEPLAYER_H + +class CTCPSocket; +class CCommandPacket; +class CGameProtocol; +class CGame; +class CIncomingJoinPlayer; + +// +// CPotentialPlayer +// + +class CPotentialPlayer +{ +public: + CGameProtocol *m_Protocol; + CBaseGame *m_Game; + +protected: + // note: we permit m_Socket to be NULL in this class to allow for the virtual host player which doesn't really exist + // it also allows us to convert CPotentialPlayers to CGamePlayers without the CPotentialPlayer's destructor closing the socket + + CTCPSocket *m_Socket; + queue m_Packets; + bool m_DeleteMe; + bool m_Error; + string m_ErrorString; + CIncomingJoinPlayer *m_IncomingJoinPlayer; + +public: + CPotentialPlayer( CGameProtocol *nProtocol, CBaseGame *nGame, CTCPSocket *nSocket ); + virtual ~CPotentialPlayer( ); + + virtual CTCPSocket *GetSocket( ) { return m_Socket; } + virtual BYTEARRAY GetExternalIP( ); + virtual string GetExternalIPString( ); + virtual queue GetPackets( ) { return m_Packets; } + virtual bool GetDeleteMe( ) { return m_DeleteMe; } + virtual bool GetError( ) { return m_Error; } + virtual string GetErrorString( ) { return m_ErrorString; } + virtual CIncomingJoinPlayer *GetJoinPlayer( ) { return m_IncomingJoinPlayer; } + + virtual void SetSocket( CTCPSocket *nSocket ) { m_Socket = nSocket; } + virtual void SetDeleteMe( bool nDeleteMe ) { m_DeleteMe = nDeleteMe; } + + // processing functions + + virtual bool Update( void *fd ); + virtual void ExtractPackets( ); + virtual void ProcessPackets( ); + + // other functions + + virtual void Send( BYTEARRAY data ); +}; + +// +// CGamePlayer +// + +class CGamePlayer : public CPotentialPlayer +{ +private: + unsigned char m_PID; + string m_Name; // the player's name + BYTEARRAY m_InternalIP; // the player's internal IP address as reported by the player when connecting + vector m_Pings; // store the last few (20) pings received so we can take an average + queue m_CheckSums; // the last few checksums the player has sent (for detecting desyncs) + string m_LeftReason; // the reason the player left the game + string m_SpoofedRealm; // the realm the player last spoof checked on + string m_JoinedRealm; // the realm the player joined on (probable, can be spoofed) + uint32_t m_TotalPacketsSent; + uint32_t m_TotalPacketsReceived; + uint32_t m_LeftCode; // the code to be sent in W3GS_PLAYERLEAVE_OTHERS for why this player left the game + uint32_t m_LoginAttempts; // the number of attempts to login (used with CAdminGame only) + uint32_t m_SyncCounter; // the number of keepalive packets received from this player + uint32_t m_JoinTime; // GetTime when the player joined the game (used to delay sending the /whois a few seconds to allow for some lag) + uint32_t m_LastMapPartSent; // the last mappart sent to the player (for sending more than one part at a time) + uint32_t m_LastMapPartAcked; // the last mappart acknowledged by the player + uint32_t m_StartedDownloadingTicks; // GetTicks when the player started downloading the map + uint32_t m_FinishedDownloadingTime; // GetTime when the player finished downloading the map + uint32_t m_FinishedLoadingTicks; // GetTicks when the player finished loading the game + uint32_t m_StartedLaggingTicks; // GetTicks when the player started lagging + uint32_t m_StatsSentTime; // GetTime when we sent this player's stats to the chat (to prevent players from spamming !stats) + uint32_t m_StatsDotASentTime; // GetTime when we sent this player's dota stats to the chat (to prevent players from spamming !statsdota) + uint32_t m_LastGProxyWaitNoticeSentTime; + queue m_LoadInGameData; // queued data to be sent when the player finishes loading when using "load in game" + double m_Score; // the player's generic "score" for the matchmaking algorithm + bool m_LoggedIn; // if the player has logged in or not (used with CAdminGame only) + bool m_Spoofed; // if the player has spoof checked or not + bool m_Reserved; // if the player is reserved (VIP) or not + bool m_WhoisShouldBeSent; // if a battle.net /whois should be sent for this player or not + bool m_WhoisSent; // if we've sent a battle.net /whois for this player yet (for spoof checking) + bool m_DownloadAllowed; // if we're allowed to download the map or not (used with permission based map downloads) + bool m_DownloadStarted; // if we've started downloading the map or not + bool m_DownloadFinished; // if we've finished downloading the map or not + bool m_FinishedLoading; // if the player has finished loading or not + bool m_Lagging; // if the player is lagging or not (on the lag screen) + bool m_DropVote; // if the player voted to drop the laggers or not (on the lag screen) + bool m_KickVote; // if the player voted to kick a player or not + bool m_Muted; // if the player is muted or not + bool m_LeftMessageSent; // if the playerleave message has been sent or not + bool m_GProxy; // if the player is using GProxy++ + bool m_GProxyDisconnectNoticeSent; // if a disconnection notice has been sent or not when using GProxy++ + queue m_GProxyBuffer; + uint32_t m_GProxyReconnectKey; + uint32_t m_LastGProxyAckTime; + +public: + CGamePlayer( CGameProtocol *nProtocol, CBaseGame *nGame, CTCPSocket *nSocket, unsigned char nPID, string nJoinedRealm, string nName, BYTEARRAY nInternalIP, bool nReserved ); + CGamePlayer( CPotentialPlayer *potential, unsigned char nPID, string nJoinedRealm, string nName, BYTEARRAY nInternalIP, bool nReserved ); + virtual ~CGamePlayer( ); + + unsigned char GetPID( ) { return m_PID; } + string GetName( ) { return m_Name; } + BYTEARRAY GetInternalIP( ) { return m_InternalIP; } + unsigned int GetNumPings( ) { return m_Pings.size( ); } + unsigned int GetNumCheckSums( ) { return m_CheckSums.size( ); } + queue *GetCheckSums( ) { return &m_CheckSums; } + string GetLeftReason( ) { return m_LeftReason; } + string GetSpoofedRealm( ) { return m_SpoofedRealm; } + string GetJoinedRealm( ) { return m_JoinedRealm; } + uint32_t GetLeftCode( ) { return m_LeftCode; } + uint32_t GetLoginAttempts( ) { return m_LoginAttempts; } + uint32_t GetSyncCounter( ) { return m_SyncCounter; } + uint32_t GetJoinTime( ) { return m_JoinTime; } + uint32_t GetLastMapPartSent( ) { return m_LastMapPartSent; } + uint32_t GetLastMapPartAcked( ) { return m_LastMapPartAcked; } + uint32_t GetStartedDownloadingTicks( ) { return m_StartedDownloadingTicks; } + uint32_t GetFinishedDownloadingTime( ) { return m_FinishedDownloadingTime; } + uint32_t GetFinishedLoadingTicks( ) { return m_FinishedLoadingTicks; } + uint32_t GetStartedLaggingTicks( ) { return m_StartedLaggingTicks; } + uint32_t GetStatsSentTime( ) { return m_StatsSentTime; } + uint32_t GetStatsDotASentTime( ) { return m_StatsDotASentTime; } + uint32_t GetLastGProxyWaitNoticeSentTime( ) { return m_LastGProxyWaitNoticeSentTime; } + queue *GetLoadInGameData( ) { return &m_LoadInGameData; } + double GetScore( ) { return m_Score; } + bool GetLoggedIn( ) { return m_LoggedIn; } + bool GetSpoofed( ) { return m_Spoofed; } + bool GetReserved( ) { return m_Reserved; } + bool GetWhoisShouldBeSent( ) { return m_WhoisShouldBeSent; } + bool GetWhoisSent( ) { return m_WhoisSent; } + bool GetDownloadAllowed( ) { return m_DownloadAllowed; } + bool GetDownloadStarted( ) { return m_DownloadStarted; } + bool GetDownloadFinished( ) { return m_DownloadFinished; } + bool GetFinishedLoading( ) { return m_FinishedLoading; } + bool GetLagging( ) { return m_Lagging; } + bool GetDropVote( ) { return m_DropVote; } + bool GetKickVote( ) { return m_KickVote; } + bool GetMuted( ) { return m_Muted; } + bool GetLeftMessageSent( ) { return m_LeftMessageSent; } + bool GetGProxy( ) { return m_GProxy; } + bool GetGProxyDisconnectNoticeSent( ) { return m_GProxyDisconnectNoticeSent; } + uint32_t GetGProxyReconnectKey( ) { return m_GProxyReconnectKey; } + + void SetLeftReason( string nLeftReason ) { m_LeftReason = nLeftReason; } + void SetSpoofedRealm( string nSpoofedRealm ) { m_SpoofedRealm = nSpoofedRealm; } + void SetLeftCode( uint32_t nLeftCode ) { m_LeftCode = nLeftCode; } + void SetLoginAttempts( uint32_t nLoginAttempts ) { m_LoginAttempts = nLoginAttempts; } + void SetSyncCounter( uint32_t nSyncCounter ) { m_SyncCounter = nSyncCounter; } + void SetLastMapPartSent( uint32_t nLastMapPartSent ) { m_LastMapPartSent = nLastMapPartSent; } + void SetLastMapPartAcked( uint32_t nLastMapPartAcked ) { m_LastMapPartAcked = nLastMapPartAcked; } + void SetStartedDownloadingTicks( uint32_t nStartedDownloadingTicks ) { m_StartedDownloadingTicks = nStartedDownloadingTicks; } + void SetFinishedDownloadingTime( uint32_t nFinishedDownloadingTime ) { m_FinishedDownloadingTime = nFinishedDownloadingTime; } + void SetStartedLaggingTicks( uint32_t nStartedLaggingTicks ) { m_StartedLaggingTicks = nStartedLaggingTicks; } + void SetStatsSentTime( uint32_t nStatsSentTime ) { m_StatsSentTime = nStatsSentTime; } + void SetStatsDotASentTime( uint32_t nStatsDotASentTime ) { m_StatsDotASentTime = nStatsDotASentTime; } + void SetLastGProxyWaitNoticeSentTime( uint32_t nLastGProxyWaitNoticeSentTime ) { m_LastGProxyWaitNoticeSentTime = nLastGProxyWaitNoticeSentTime; } + void SetScore( double nScore ) { m_Score = nScore; } + void SetLoggedIn( bool nLoggedIn ) { m_LoggedIn = nLoggedIn; } + void SetSpoofed( bool nSpoofed ) { m_Spoofed = nSpoofed; } + void SetReserved( bool nReserved ) { m_Reserved = nReserved; } + void SetWhoisShouldBeSent( bool nWhoisShouldBeSent ) { m_WhoisShouldBeSent = nWhoisShouldBeSent; } + void SetDownloadAllowed( bool nDownloadAllowed ) { m_DownloadAllowed = nDownloadAllowed; } + void SetDownloadStarted( bool nDownloadStarted ) { m_DownloadStarted = nDownloadStarted; } + void SetDownloadFinished( bool nDownloadFinished ) { m_DownloadFinished = nDownloadFinished; } + void SetLagging( bool nLagging ) { m_Lagging = nLagging; } + void SetDropVote( bool nDropVote ) { m_DropVote = nDropVote; } + void SetKickVote( bool nKickVote ) { m_KickVote = nKickVote; } + void SetMuted( bool nMuted ) { m_Muted = nMuted; } + void SetLeftMessageSent( bool nLeftMessageSent ) { m_LeftMessageSent = nLeftMessageSent; } + void SetGProxyDisconnectNoticeSent( bool nGProxyDisconnectNoticeSent ) { m_GProxyDisconnectNoticeSent = nGProxyDisconnectNoticeSent; } + + string GetNameTerminated( ); + uint32_t GetPing( bool LCPing ); + + void AddLoadInGameData( BYTEARRAY nLoadInGameData ) { m_LoadInGameData.push( nLoadInGameData ); } + + // processing functions + + virtual bool Update( void *fd ); + virtual void ExtractPackets( ); + virtual void ProcessPackets( ); + + // other functions + + virtual void Send( BYTEARRAY data ); + virtual void EventGProxyReconnect( CTCPSocket *NewSocket, uint32_t LastPacket ); +}; + +#endif diff --git a/ghost-legacy/gameprotocol.cpp b/ghost-legacy/gameprotocol.cpp new file mode 100644 index 0000000..5f505f2 --- /dev/null +++ b/ghost-legacy/gameprotocol.cpp @@ -0,0 +1,1050 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "crc32.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "game_base.h" + +// +// CGameProtocol +// + +CGameProtocol :: CGameProtocol( CGHost *nGHost ) +{ + m_GHost = nGHost; +} + +CGameProtocol :: ~CGameProtocol( ) +{ + +} + +/////////////////////// +// RECEIVE FUNCTIONS // +/////////////////////// + +CIncomingJoinPlayer *CGameProtocol :: RECEIVE_W3GS_REQJOIN( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_REQJOIN" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Host Counter (Game ID) + // 4 bytes -> Entry Key (used in LAN) + // 1 byte -> ??? + // 2 bytes -> Listen Port + // 4 bytes -> Peer Key + // null terminated string -> Name + // 4 bytes -> ??? + // 2 bytes -> InternalPort (???) + // 4 bytes -> InternalIP + + if( ValidateLength( data ) && data.size( ) >= 20 ) + { + uint32_t HostCounter = UTIL_ByteArrayToUInt32( data, false, 4 ); + BYTEARRAY Name = UTIL_ExtractCString( data, 19 ); + + if( !Name.empty( ) && data.size( ) >= Name.size( ) + 30 ) + { + BYTEARRAY InternalIP = BYTEARRAY( data.begin( ) + Name.size( ) + 26, data.begin( ) + Name.size( ) + 30 ); + return new CIncomingJoinPlayer( HostCounter, string( Name.begin( ), Name.end( ) ), InternalIP ); + } + } + + return NULL; +} + +uint32_t CGameProtocol :: RECEIVE_W3GS_LEAVEGAME( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_LEAVEGAME" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Reason + + if( ValidateLength( data ) && data.size( ) >= 8 ) + return UTIL_ByteArrayToUInt32( data, false, 4 ); + + return 0; +} + +bool CGameProtocol :: RECEIVE_W3GS_GAMELOADED_SELF( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_GAMELOADED_SELF" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + + if( ValidateLength( data ) ) + return true; + + return false; +} + +CIncomingAction *CGameProtocol :: RECEIVE_W3GS_OUTGOING_ACTION( BYTEARRAY data, unsigned char PID ) +{ + // DEBUG_Print( "RECEIVED W3GS_OUTGOING_ACTION" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> CRC + // remainder of packet -> Action + + if( PID != 255 && ValidateLength( data ) && data.size( ) >= 8 ) + { + BYTEARRAY CRC = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); + BYTEARRAY Action = BYTEARRAY( data.begin( ) + 8, data.end( ) ); + return new CIncomingAction( PID, CRC, Action ); + } + + return NULL; +} + +uint32_t CGameProtocol :: RECEIVE_W3GS_OUTGOING_KEEPALIVE( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_OUTGOING_KEEPALIVE" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 1 byte -> ??? + // 4 bytes -> CheckSum??? (used in replays) + + if( ValidateLength( data ) && data.size( ) == 9 ) + return UTIL_ByteArrayToUInt32( data, false, 5 ); + + return 0; +} + +CIncomingChatPlayer *CGameProtocol :: RECEIVE_W3GS_CHAT_TO_HOST( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_CHAT_TO_HOST" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 1 byte -> Total + // for( 1 .. Total ) + // 1 byte -> ToPID + // 1 byte -> FromPID + // 1 byte -> Flag + // if( Flag == 16 ) + // null term string -> Message + // elseif( Flag == 17 ) + // 1 byte -> Team + // elseif( Flag == 18 ) + // 1 byte -> Colour + // elseif( Flag == 19 ) + // 1 byte -> Race + // elseif( Flag == 20 ) + // 1 byte -> Handicap + // elseif( Flag == 32 ) + // 4 bytes -> ExtraFlags + // null term string -> Message + + if( ValidateLength( data ) ) + { + unsigned int i = 5; + unsigned char Total = data[4]; + + if( Total > 0 && data.size( ) >= i + Total ) + { + BYTEARRAY ToPIDs = BYTEARRAY( data.begin( ) + i, data.begin( ) + i + Total ); + i += Total; + unsigned char FromPID = data[i]; + unsigned char Flag = data[i + 1]; + i += 2; + + if( Flag == 16 && data.size( ) >= i + 1 ) + { + // chat message + + BYTEARRAY Message = UTIL_ExtractCString( data, i ); + return new CIncomingChatPlayer( FromPID, ToPIDs, Flag, string( Message.begin( ), Message.end( ) ) ); + } + else if( ( Flag >= 17 && Flag <= 20 ) && data.size( ) >= i + 1 ) + { + // team/colour/race/handicap change request + + unsigned char Byte = data[i]; + return new CIncomingChatPlayer( FromPID, ToPIDs, Flag, Byte ); + } + else if( Flag == 32 && data.size( ) >= i + 5 ) + { + // chat message with extra flags + + BYTEARRAY ExtraFlags = BYTEARRAY( data.begin( ) + i, data.begin( ) + i + 4 ); + BYTEARRAY Message = UTIL_ExtractCString( data, i + 4 ); + return new CIncomingChatPlayer( FromPID, ToPIDs, Flag, string( Message.begin( ), Message.end( ) ), ExtraFlags ); + } + } + } + + return NULL; +} + +bool CGameProtocol :: RECEIVE_W3GS_SEARCHGAME( BYTEARRAY data, unsigned char war3Version ) +{ + uint32_t ProductID = 1462982736; // "W3XP" + uint32_t Version = war3Version; + + // DEBUG_Print( "RECEIVED W3GS_SEARCHGAME" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> ProductID + // 4 bytes -> Version + // 4 bytes -> ??? (Zero) + + if( ValidateLength( data ) && data.size( ) >= 16 ) + { + if( UTIL_ByteArrayToUInt32( data, false, 4 ) == ProductID ) + { + if( UTIL_ByteArrayToUInt32( data, false, 8 ) == Version ) + { + if( UTIL_ByteArrayToUInt32( data, false, 12 ) == 0 ) + return true; + } + } + } + + return false; +} + +CIncomingMapSize *CGameProtocol :: RECEIVE_W3GS_MAPSIZE( BYTEARRAY data, BYTEARRAY mapSize ) +{ + // DEBUG_Print( "RECEIVED W3GS_MAPSIZE" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> ??? + // 1 byte -> SizeFlag (1 = have map, 3 = continue download) + // 4 bytes -> MapSize + + if( ValidateLength( data ) && data.size( ) >= 13 ) + return new CIncomingMapSize( data[8], UTIL_ByteArrayToUInt32( data, false, 9 ) ); + + return NULL; +} + +uint32_t CGameProtocol :: RECEIVE_W3GS_MAPPARTOK( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_MAPPARTOK" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 1 byte -> SenderPID + // 1 byte -> ReceiverPID + // 4 bytes -> ??? + // 4 bytes -> MapSize + + if( ValidateLength( data ) && data.size( ) >= 14 ) + return UTIL_ByteArrayToUInt32( data, false, 10 ); + + return 0; +} + +uint32_t CGameProtocol :: RECEIVE_W3GS_PONG_TO_HOST( BYTEARRAY data ) +{ + // DEBUG_Print( "RECEIVED W3GS_PONG_TO_HOST" ); + // DEBUG_Print( data ); + + // 2 bytes -> Header + // 2 bytes -> Length + // 4 bytes -> Pong + + // the pong value is just a copy of whatever was sent in SEND_W3GS_PING_FROM_HOST which was GetTicks( ) at the time of sending + // so as long as we trust that the client isn't trying to fake us out and mess with the pong value we can find the round trip time by simple subtraction + // (the subtraction is done elsewhere because the very first pong value seems to be 1 and we want to discard that one) + + if( ValidateLength( data ) && data.size( ) >= 8 ) + return UTIL_ByteArrayToUInt32( data, false, 4 ); + + return 1; +} + +//////////////////// +// SEND FUNCTIONS // +//////////////////// + +BYTEARRAY CGameProtocol :: SEND_W3GS_PING_FROM_HOST( ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_PING_FROM_HOST ); // W3GS_PING_FROM_HOST + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, GetTicks( ), false ); // ping value + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_PING_FROM_HOST" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_SLOTINFOJOIN( unsigned char PID, BYTEARRAY port, BYTEARRAY externalIP, vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ) +{ + unsigned char Zeros[] = { 0, 0, 0, 0 }; + + BYTEARRAY SlotInfo = EncodeSlotInfo( slots, randomSeed, layoutStyle, playerSlots ); + BYTEARRAY packet; + + if( port.size( ) == 2 && externalIP.size( ) == 4 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_SLOTINFOJOIN ); // W3GS_SLOTINFOJOIN + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, (uint16_t)SlotInfo.size( ), false ); // SlotInfo length + UTIL_AppendByteArrayFast( packet, SlotInfo ); // SlotInfo + packet.push_back( PID ); // PID + packet.push_back( 2 ); // AF_INET + packet.push_back( 0 ); // AF_INET continued... + UTIL_AppendByteArray( packet, port ); // port + UTIL_AppendByteArrayFast( packet, externalIP ); // external IP + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_SLOTINFOJOIN" ); + + // DEBUG_Print( "SENT W3GS_SLOTINFOJOIN" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_REJECTJOIN( uint32_t reason ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_REJECTJOIN ); // W3GS_REJECTJOIN + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, reason, false ); // reason + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_REJECTJOIN" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_PLAYERINFO( unsigned char PID, string name, BYTEARRAY externalIP, BYTEARRAY internalIP ) +{ + unsigned char PlayerJoinCounter[] = { 2, 0, 0, 0 }; + unsigned char Zeros[] = { 0, 0, 0, 0 }; + + BYTEARRAY packet; + + if( !name.empty( ) && name.size( ) <= 15 && externalIP.size( ) == 4 && internalIP.size( ) == 4 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_PLAYERINFO ); // W3GS_PLAYERINFO + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, PlayerJoinCounter, 4 ); // player join counter + packet.push_back( PID ); // PID + UTIL_AppendByteArrayFast( packet, name ); // player name + packet.push_back( 1 ); // ??? + packet.push_back( 0 ); // ??? + packet.push_back( 2 ); // AF_INET + packet.push_back( 0 ); // AF_INET continued... + packet.push_back( 0 ); // port + packet.push_back( 0 ); // port continued... + UTIL_AppendByteArrayFast( packet, externalIP ); // external IP + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + packet.push_back( 2 ); // AF_INET + packet.push_back( 0 ); // AF_INET continued... + packet.push_back( 0 ); // port + packet.push_back( 0 ); // port continued... + UTIL_AppendByteArrayFast( packet, internalIP ); // internal IP + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + UTIL_AppendByteArray( packet, Zeros, 4 ); // ??? + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_PLAYERINFO" ); + + // DEBUG_Print( "SENT W3GS_PLAYERINFO" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_PLAYERLEAVE_OTHERS( unsigned char PID, uint32_t leftCode ) +{ + BYTEARRAY packet; + + if( PID != 255 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_PLAYERLEAVE_OTHERS ); // W3GS_PLAYERLEAVE_OTHERS + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( PID ); // PID + UTIL_AppendByteArray( packet, leftCode, false ); // left code (see PLAYERLEAVE_ constants in gameprotocol.h) + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_PLAYERLEAVE_OTHERS" ); + + // DEBUG_Print( "SENT W3GS_PLAYERLEAVE_OTHERS" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_GAMELOADED_OTHERS( unsigned char PID ) +{ + BYTEARRAY packet; + + if( PID != 255 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_GAMELOADED_OTHERS ); // W3GS_GAMELOADED_OTHERS + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( PID ); // PID + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMELOADED_OTHERS" ); + + // DEBUG_Print( "SENT W3GS_GAMELOADED_OTHERS" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_SLOTINFO( vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ) +{ + BYTEARRAY SlotInfo = EncodeSlotInfo( slots, randomSeed, layoutStyle, playerSlots ); + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_SLOTINFO ); // W3GS_SLOTINFO + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, (uint16_t)SlotInfo.size( ), false ); // SlotInfo length + UTIL_AppendByteArrayFast( packet, SlotInfo ); // SlotInfo + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_SLOTINFO" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_COUNTDOWN_START( ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_COUNTDOWN_START ); // W3GS_COUNTDOWN_START + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_COUNTDOWN_START" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_COUNTDOWN_END( ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_COUNTDOWN_END ); // W3GS_COUNTDOWN_END + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_COUNTDOWN_END" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_INCOMING_ACTION( queue actions, uint16_t sendInterval ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_INCOMING_ACTION ); // W3GS_INCOMING_ACTION + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, sendInterval, false ); // send interval + + // create subpacket + + if( !actions.empty( ) ) + { + BYTEARRAY subpacket; + + while( !actions.empty( ) ) + { + CIncomingAction *Action = actions.front( ); + actions.pop( ); + subpacket.push_back( Action->GetPID( ) ); + UTIL_AppendByteArray( subpacket, (uint16_t)Action->GetAction( )->size( ), false ); + UTIL_AppendByteArrayFast( subpacket, *Action->GetAction( ) ); + } + + // calculate crc (we only care about the first 2 bytes though) + + BYTEARRAY crc32 = UTIL_CreateByteArray( m_GHost->m_CRC->FullCRC( (unsigned char *)string( subpacket.begin( ), subpacket.end( ) ).c_str( ), subpacket.size( ) ), false ); + crc32.resize( 2 ); + + // finish subpacket + + UTIL_AppendByteArrayFast( packet, crc32 ); // crc + UTIL_AppendByteArrayFast( packet, subpacket ); // subpacket + } + + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_INCOMING_ACTION" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_CHAT_FROM_HOST( unsigned char fromPID, BYTEARRAY toPIDs, unsigned char flag, BYTEARRAY flagExtra, string message ) +{ + BYTEARRAY packet; + + if( !toPIDs.empty( ) && !message.empty( ) && message.size( ) < 255 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_CHAT_FROM_HOST ); // W3GS_CHAT_FROM_HOST + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( toPIDs.size( ) ); // number of receivers + UTIL_AppendByteArrayFast( packet, toPIDs ); // receivers + packet.push_back( fromPID ); // sender + packet.push_back( flag ); // flag + UTIL_AppendByteArrayFast( packet, flagExtra ); // extra flag + UTIL_AppendByteArrayFast( packet, message ); // message + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_CHAT_FROM_HOST" ); + + // DEBUG_Print( "SENT W3GS_CHAT_FROM_HOST" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_START_LAG( vector players, bool loadInGame ) +{ + BYTEARRAY packet; + + unsigned char NumLaggers = 0; + + for( vector :: iterator i = players.begin( ); i != players.end( ); i++ ) + { + if( loadInGame ) + { + if( !(*i)->GetFinishedLoading( ) ) + NumLaggers++; + } + else + { + if( (*i)->GetLagging( ) ) + NumLaggers++; + } + } + + if( NumLaggers > 0 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_START_LAG ); // W3GS_START_LAG + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( NumLaggers ); + + for( vector :: iterator i = players.begin( ); i != players.end( ); i++ ) + { + if( loadInGame ) + { + if( !(*i)->GetFinishedLoading( ) ) + { + packet.push_back( (*i)->GetPID( ) ); + UTIL_AppendByteArray( packet, (uint32_t)0, false ); + } + } + else + { + if( (*i)->GetLagging( ) ) + { + packet.push_back( (*i)->GetPID( ) ); + UTIL_AppendByteArray( packet, GetTicks( ) - (*i)->GetStartedLaggingTicks( ), false ); + } + } + } + + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] no laggers passed to SEND_W3GS_START_LAG" ); + + // DEBUG_Print( "SENT W3GS_START_LAG" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_STOP_LAG( CGamePlayer *player, bool loadInGame ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_STOP_LAG ); // W3GS_STOP_LAG + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( player->GetPID( ) ); + + if( loadInGame ) + UTIL_AppendByteArray( packet, (uint32_t)0, false ); + else + UTIL_AppendByteArray( packet, GetTicks( ) - player->GetStartedLaggingTicks( ), false ); + + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_STOP_LAG" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_SEARCHGAME( bool TFT, unsigned char war3Version ) +{ + unsigned char ProductID_ROC[] = { 51, 82, 65, 87 }; // "WAR3" + unsigned char ProductID_TFT[] = { 80, 88, 51, 87 }; // "W3XP" + unsigned char Version[] = { war3Version, 0, 0, 0 }; + unsigned char Unknown[] = { 0, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_SEARCHGAME ); // W3GS_SEARCHGAME + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + + if( TFT ) + UTIL_AppendByteArray( packet, ProductID_TFT, 4 ); // Product ID (TFT) + else + UTIL_AppendByteArray( packet, ProductID_ROC, 4 ); // Product ID (ROC) + + UTIL_AppendByteArray( packet, Version, 4 ); // Version + UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_SEARCHGAME" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_GAMEINFO( bool TFT, unsigned char war3Version, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, uint32_t slotsTotal, uint32_t slotsOpen, uint16_t port, uint32_t hostCounter ) +{ + unsigned char ProductID_ROC[] = { 51, 82, 65, 87 }; // "WAR3" + unsigned char ProductID_TFT[] = { 80, 88, 51, 87 }; // "W3XP" + unsigned char Version[] = { war3Version, 0, 0, 0 }; + unsigned char Unknown1[] = { 1, 2, 3, 4 }; + unsigned char Unknown2[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + + if( mapGameType.size( ) == 4 && mapFlags.size( ) == 4 && mapWidth.size( ) == 2 && mapHeight.size( ) == 2 && !gameName.empty( ) && !hostName.empty( ) && !mapPath.empty( ) && mapCRC.size( ) == 4 ) + { + // make the stat string + + BYTEARRAY StatString; + UTIL_AppendByteArrayFast( StatString, mapFlags ); + StatString.push_back( 0 ); + UTIL_AppendByteArrayFast( StatString, mapWidth ); + UTIL_AppendByteArrayFast( StatString, mapHeight ); + UTIL_AppendByteArrayFast( StatString, mapCRC ); + UTIL_AppendByteArrayFast( StatString, mapPath ); + UTIL_AppendByteArrayFast( StatString, hostName ); + StatString.push_back( 0 ); + StatString = UTIL_EncodeStatString( StatString ); + + // make the rest of the packet + + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_GAMEINFO ); // W3GS_GAMEINFO + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + + if( TFT ) + UTIL_AppendByteArray( packet, ProductID_TFT, 4 ); // Product ID (TFT) + else + UTIL_AppendByteArray( packet, ProductID_ROC, 4 ); // Product ID (ROC) + + UTIL_AppendByteArray( packet, Version, 4 ); // Version + UTIL_AppendByteArray( packet, hostCounter, false ); // Host Counter + UTIL_AppendByteArray( packet, Unknown1, 4 ); // ??? (this varies wildly even between two identical games created one after another) + UTIL_AppendByteArrayFast( packet, gameName ); // Game Name + packet.push_back( 0 ); // ??? (maybe game password) + UTIL_AppendByteArrayFast( packet, StatString ); // Stat String + packet.push_back( 0 ); // Stat String null terminator (the stat string is encoded to remove all even numbers i.e. zeros) + UTIL_AppendByteArray( packet, slotsTotal, false ); // Slots Total + UTIL_AppendByteArrayFast( packet, mapGameType ); // Game Type + UTIL_AppendByteArray( packet, Unknown2, 4 ); // ??? + UTIL_AppendByteArray( packet, slotsOpen, false ); // Slots Open + UTIL_AppendByteArray( packet, upTime, false ); // time since creation + UTIL_AppendByteArray( packet, port, false ); // port + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO" ); + + // DEBUG_Print( "SENT W3GS_GAMEINFO" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_CREATEGAME( bool TFT, unsigned char war3Version ) +{ + unsigned char ProductID_ROC[] = { 51, 82, 65, 87 }; // "WAR3" + unsigned char ProductID_TFT[] = { 80, 88, 51, 87 }; // "W3XP" + unsigned char Version[] = { war3Version, 0, 0, 0 }; + unsigned char HostCounter[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_CREATEGAME ); // W3GS_CREATEGAME + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + + if( TFT ) + UTIL_AppendByteArray( packet, ProductID_TFT, 4 ); // Product ID (TFT) + else + UTIL_AppendByteArray( packet, ProductID_ROC, 4 ); // Product ID (ROC) + + UTIL_AppendByteArray( packet, Version, 4 ); // Version + UTIL_AppendByteArray( packet, HostCounter, 4 ); // Host Counter + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_CREATEGAME" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_REFRESHGAME( uint32_t players, uint32_t playerSlots ) +{ + unsigned char HostCounter[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_REFRESHGAME ); // W3GS_REFRESHGAME + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, HostCounter, 4 ); // Host Counter + UTIL_AppendByteArray( packet, players, false ); // Players + UTIL_AppendByteArray( packet, playerSlots, false ); // Player Slots + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_REFRESHGAME" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_DECREATEGAME( ) +{ + unsigned char HostCounter[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_DECREATEGAME ); // W3GS_DECREATEGAME + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, HostCounter, 4 ); // Host Counter + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_DECREATEGAME" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_MAPCHECK( string mapPath, BYTEARRAY mapSize, BYTEARRAY mapInfo, BYTEARRAY mapCRC, BYTEARRAY mapSHA1 ) +{ + unsigned char Unknown[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + + if( !mapPath.empty( ) && mapSize.size( ) == 4 && mapInfo.size( ) == 4 && mapCRC.size( ) == 4 && mapSHA1.size( ) == 20 ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_MAPCHECK ); // W3GS_MAPCHECK + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? + UTIL_AppendByteArrayFast( packet, mapPath ); // map path + UTIL_AppendByteArrayFast( packet, mapSize ); // map size + UTIL_AppendByteArrayFast( packet, mapInfo ); // map info + UTIL_AppendByteArrayFast( packet, mapCRC ); // map crc + UTIL_AppendByteArrayFast( packet, mapSHA1 ); // map sha1 + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_MAPCHECK" ); + + // DEBUG_Print( "SENT W3GS_MAPCHECK" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_STARTDOWNLOAD( unsigned char fromPID ) +{ + unsigned char Unknown[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_STARTDOWNLOAD ); // W3GS_STARTDOWNLOAD + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? + packet.push_back( fromPID ); // from PID + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_STARTDOWNLOAD" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_MAPPART( unsigned char fromPID, unsigned char toPID, uint32_t start, string *mapData ) +{ + unsigned char Unknown[] = { 1, 0, 0, 0 }; + + BYTEARRAY packet; + + if( start < mapData->size( ) ) + { + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_MAPPART ); // W3GS_MAPPART + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( toPID ); // to PID + packet.push_back( fromPID ); // from PID + UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? + UTIL_AppendByteArray( packet, start, false ); // start position + + // calculate end position (don't send more than 1442 map bytes in one packet) + + uint32_t End = start + 1442; + + if( End > mapData->size( ) ) + End = mapData->size( ); + + // calculate crc + + BYTEARRAY crc32 = UTIL_CreateByteArray( m_GHost->m_CRC->FullCRC( (unsigned char *)mapData->c_str( ) + start, End - start ), false ); + UTIL_AppendByteArrayFast( packet, crc32 ); + + // map data + + BYTEARRAY Data = UTIL_CreateByteArray( (unsigned char *)mapData->c_str( ) + start, End - start ); + UTIL_AppendByteArrayFast( packet, Data ); + AssignLength( packet ); + } + else + CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_MAPPART" ); + + // DEBUG_Print( "SENT W3GS_MAPPART" ); + // DEBUG_Print( packet ); + return packet; +} + +BYTEARRAY CGameProtocol :: SEND_W3GS_INCOMING_ACTION2( queue actions ) +{ + BYTEARRAY packet; + packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant + packet.push_back( W3GS_INCOMING_ACTION2 ); // W3GS_INCOMING_ACTION2 + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // packet length will be assigned later + packet.push_back( 0 ); // ??? (send interval?) + packet.push_back( 0 ); // ??? (send interval?) + + // create subpacket + + if( !actions.empty( ) ) + { + BYTEARRAY subpacket; + + while( !actions.empty( ) ) + { + CIncomingAction *Action = actions.front( ); + actions.pop( ); + subpacket.push_back( Action->GetPID( ) ); + UTIL_AppendByteArray( subpacket, (uint16_t)Action->GetAction( )->size( ), false ); + UTIL_AppendByteArrayFast( subpacket, *Action->GetAction( ) ); + } + + // calculate crc (we only care about the first 2 bytes though) + + BYTEARRAY crc32 = UTIL_CreateByteArray( m_GHost->m_CRC->FullCRC( (unsigned char *)string( subpacket.begin( ), subpacket.end( ) ).c_str( ), subpacket.size( ) ), false ); + crc32.resize( 2 ); + + // finish subpacket + + UTIL_AppendByteArrayFast( packet, crc32 ); // crc + UTIL_AppendByteArrayFast( packet, subpacket ); // subpacket + } + + AssignLength( packet ); + // DEBUG_Print( "SENT W3GS_INCOMING_ACTION2" ); + // DEBUG_Print( packet ); + return packet; +} + +///////////////////// +// OTHER FUNCTIONS // +///////////////////// + +bool CGameProtocol :: AssignLength( BYTEARRAY &content ) +{ + // insert the actual length of the content array into bytes 3 and 4 (indices 2 and 3) + + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes = UTIL_CreateByteArray( (uint16_t)content.size( ), false ); + content[2] = LengthBytes[0]; + content[3] = LengthBytes[1]; + return true; + } + + return false; +} + +bool CGameProtocol :: ValidateLength( BYTEARRAY &content ) +{ + // verify that bytes 3 and 4 (indices 2 and 3) of the content array describe the length + + uint16_t Length; + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes.push_back( content[2] ); + LengthBytes.push_back( content[3] ); + Length = UTIL_ByteArrayToUInt16( LengthBytes, false ); + + if( Length == content.size( ) ) + return true; + } + + return false; +} + +BYTEARRAY CGameProtocol :: EncodeSlotInfo( vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ) +{ + BYTEARRAY SlotInfo; + SlotInfo.push_back( (unsigned char)slots.size( ) ); // number of slots + + for( unsigned int i = 0; i < slots.size( ); i++ ) + UTIL_AppendByteArray( SlotInfo, slots[i].GetByteArray( ) ); + + UTIL_AppendByteArray( SlotInfo, randomSeed, false ); // random seed + SlotInfo.push_back( layoutStyle ); // LayoutStyle (0 = melee, 1 = custom forces, 3 = custom forces + fixed player settings) + SlotInfo.push_back( playerSlots ); // number of player slots (non observer) + return SlotInfo; +} + +// +// CIncomingJoinPlayer +// + +CIncomingJoinPlayer :: CIncomingJoinPlayer( uint32_t nHostCounter, string nName, BYTEARRAY &nInternalIP ) +{ + m_HostCounter = nHostCounter; + m_Name = nName; + m_InternalIP = nInternalIP; +} + +CIncomingJoinPlayer :: ~CIncomingJoinPlayer( ) +{ + +} + +// +// CIncomingAction +// + +CIncomingAction :: CIncomingAction( unsigned char nPID, BYTEARRAY &nCRC, BYTEARRAY &nAction ) +{ + m_PID = nPID; + m_CRC = nCRC; + m_Action = nAction; +} + +CIncomingAction :: ~CIncomingAction( ) +{ + +} + +// +// CIncomingChatPlayer +// + +CIncomingChatPlayer :: CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, string nMessage ) +{ + m_Type = CTH_MESSAGE; + m_FromPID = nFromPID; + m_ToPIDs = nToPIDs; + m_Flag = nFlag; + m_Message = nMessage; +} + +CIncomingChatPlayer :: CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, string nMessage, BYTEARRAY &nExtraFlags ) +{ + m_Type = CTH_MESSAGEEXTRA; + m_FromPID = nFromPID; + m_ToPIDs = nToPIDs; + m_Flag = nFlag; + m_Message = nMessage; + m_ExtraFlags = nExtraFlags; +} + +CIncomingChatPlayer :: CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, unsigned char nByte ) +{ + if( nFlag == 17 ) + m_Type = CTH_TEAMCHANGE; + else if( nFlag == 18 ) + m_Type = CTH_COLOURCHANGE; + else if( nFlag == 19 ) + m_Type = CTH_RACECHANGE; + else if( nFlag == 20 ) + m_Type = CTH_HANDICAPCHANGE; + + m_FromPID = nFromPID; + m_ToPIDs = nToPIDs; + m_Flag = nFlag; + m_Byte = nByte; +} + +CIncomingChatPlayer :: ~CIncomingChatPlayer( ) +{ + +} + +// +// CIncomingMapSize +// + +CIncomingMapSize :: CIncomingMapSize( unsigned char nSizeFlag, uint32_t nMapSize ) +{ + m_SizeFlag = nSizeFlag; + m_MapSize = nMapSize; +} + +CIncomingMapSize :: ~CIncomingMapSize( ) +{ + +} diff --git a/ghost-legacy/gameprotocol.h b/ghost-legacy/gameprotocol.h new file mode 100644 index 0000000..93ce2cd --- /dev/null +++ b/ghost-legacy/gameprotocol.h @@ -0,0 +1,249 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAMEPROTOCOL_H +#define GAMEPROTOCOL_H + +// +// CGameProtocol +// + +#define W3GS_HEADER_CONSTANT 247 + +#define GAME_NONE 0 // this case isn't part of the protocol, it's for internal use only +#define GAME_FULL 2 +#define GAME_PUBLIC 16 +#define GAME_PRIVATE 17 + +#define GAMETYPE_CUSTOM 1 +#define GAMETYPE_BLIZZARD 9 + +#define PLAYERLEAVE_DISCONNECT 1 +#define PLAYERLEAVE_LOST 7 +#define PLAYERLEAVE_LOSTBUILDINGS 8 +#define PLAYERLEAVE_WON 9 +#define PLAYERLEAVE_DRAW 10 +#define PLAYERLEAVE_OBSERVER 11 +#define PLAYERLEAVE_LOBBY 13 +#define PLAYERLEAVE_GPROXY 100 + +#define REJECTJOIN_FULL 9 +#define REJECTJOIN_STARTED 10 +#define REJECTJOIN_WRONGPASSWORD 27 + +#include "gameslot.h" + +class CGamePlayer; +class CIncomingJoinPlayer; +class CIncomingAction; +class CIncomingChatPlayer; +class CIncomingMapSize; + +class CGameProtocol +{ +public: + CGHost *m_GHost; + + enum Protocol { + W3GS_PING_FROM_HOST = 1, // 0x01 + W3GS_SLOTINFOJOIN = 4, // 0x04 + W3GS_REJECTJOIN = 5, // 0x05 + W3GS_PLAYERINFO = 6, // 0x06 + W3GS_PLAYERLEAVE_OTHERS = 7, // 0x07 + W3GS_GAMELOADED_OTHERS = 8, // 0x08 + W3GS_SLOTINFO = 9, // 0x09 + W3GS_COUNTDOWN_START = 10, // 0x0A + W3GS_COUNTDOWN_END = 11, // 0x0B + W3GS_INCOMING_ACTION = 12, // 0x0C + W3GS_CHAT_FROM_HOST = 15, // 0x0F + W3GS_START_LAG = 16, // 0x10 + W3GS_STOP_LAG = 17, // 0x11 + W3GS_HOST_KICK_PLAYER = 28, // 0x1C + W3GS_REQJOIN = 30, // 0x1E + W3GS_LEAVEGAME = 33, // 0x21 + W3GS_GAMELOADED_SELF = 35, // 0x23 + W3GS_OUTGOING_ACTION = 38, // 0x26 + W3GS_OUTGOING_KEEPALIVE = 39, // 0x27 + W3GS_CHAT_TO_HOST = 40, // 0x28 + W3GS_DROPREQ = 41, // 0x29 + W3GS_SEARCHGAME = 47, // 0x2F (UDP/LAN) + W3GS_GAMEINFO = 48, // 0x30 (UDP/LAN) + W3GS_CREATEGAME = 49, // 0x31 (UDP/LAN) + W3GS_REFRESHGAME = 50, // 0x32 (UDP/LAN) + W3GS_DECREATEGAME = 51, // 0x33 (UDP/LAN) + W3GS_CHAT_OTHERS = 52, // 0x34 + W3GS_PING_FROM_OTHERS = 53, // 0x35 + W3GS_PONG_TO_OTHERS = 54, // 0x36 + W3GS_MAPCHECK = 61, // 0x3D + W3GS_STARTDOWNLOAD = 63, // 0x3F + W3GS_MAPSIZE = 66, // 0x42 + W3GS_MAPPART = 67, // 0x43 + W3GS_MAPPARTOK = 68, // 0x44 + W3GS_MAPPARTNOTOK = 69, // 0x45 - just a guess, received this packet after forgetting to send a crc in W3GS_MAPPART (f7 45 0a 00 01 02 01 00 00 00) + W3GS_PONG_TO_HOST = 70, // 0x46 + W3GS_INCOMING_ACTION2 = 72 // 0x48 - received this packet when there are too many actions to fit in W3GS_INCOMING_ACTION + }; + + CGameProtocol( CGHost *nGHost ); + ~CGameProtocol( ); + + // receive functions + + CIncomingJoinPlayer *RECEIVE_W3GS_REQJOIN( BYTEARRAY data ); + uint32_t RECEIVE_W3GS_LEAVEGAME( BYTEARRAY data ); + bool RECEIVE_W3GS_GAMELOADED_SELF( BYTEARRAY data ); + CIncomingAction *RECEIVE_W3GS_OUTGOING_ACTION( BYTEARRAY data, unsigned char PID ); + uint32_t RECEIVE_W3GS_OUTGOING_KEEPALIVE( BYTEARRAY data ); + CIncomingChatPlayer *RECEIVE_W3GS_CHAT_TO_HOST( BYTEARRAY data ); + bool RECEIVE_W3GS_SEARCHGAME( BYTEARRAY data, unsigned char war3Version ); + CIncomingMapSize *RECEIVE_W3GS_MAPSIZE( BYTEARRAY data, BYTEARRAY mapSize ); + uint32_t RECEIVE_W3GS_MAPPARTOK( BYTEARRAY data ); + uint32_t RECEIVE_W3GS_PONG_TO_HOST( BYTEARRAY data ); + + // send functions + + BYTEARRAY SEND_W3GS_PING_FROM_HOST( ); + BYTEARRAY SEND_W3GS_SLOTINFOJOIN( unsigned char PID, BYTEARRAY port, BYTEARRAY externalIP, vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ); + BYTEARRAY SEND_W3GS_REJECTJOIN( uint32_t reason ); + BYTEARRAY SEND_W3GS_PLAYERINFO( unsigned char PID, string name, BYTEARRAY externalIP, BYTEARRAY internalIP ); + BYTEARRAY SEND_W3GS_PLAYERLEAVE_OTHERS( unsigned char PID, uint32_t leftCode ); + BYTEARRAY SEND_W3GS_GAMELOADED_OTHERS( unsigned char PID ); + BYTEARRAY SEND_W3GS_SLOTINFO( vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ); + BYTEARRAY SEND_W3GS_COUNTDOWN_START( ); + BYTEARRAY SEND_W3GS_COUNTDOWN_END( ); + BYTEARRAY SEND_W3GS_INCOMING_ACTION( queue actions, uint16_t sendInterval ); + BYTEARRAY SEND_W3GS_CHAT_FROM_HOST( unsigned char fromPID, BYTEARRAY toPIDs, unsigned char flag, BYTEARRAY flagExtra, string message ); + BYTEARRAY SEND_W3GS_START_LAG( vector players, bool loadInGame = false ); + BYTEARRAY SEND_W3GS_STOP_LAG( CGamePlayer *player, bool loadInGame = false ); + BYTEARRAY SEND_W3GS_SEARCHGAME( bool TFT, unsigned char war3Version ); + BYTEARRAY SEND_W3GS_GAMEINFO( bool TFT, unsigned char war3Version, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, uint32_t slotsTotal, uint32_t slotsOpen, uint16_t port, uint32_t hostCounter ); + BYTEARRAY SEND_W3GS_CREATEGAME( bool TFT, unsigned char war3Version ); + BYTEARRAY SEND_W3GS_REFRESHGAME( uint32_t players, uint32_t playerSlots ); + BYTEARRAY SEND_W3GS_DECREATEGAME( ); + BYTEARRAY SEND_W3GS_MAPCHECK( string mapPath, BYTEARRAY mapSize, BYTEARRAY mapInfo, BYTEARRAY mapCRC, BYTEARRAY mapSHA1 ); + BYTEARRAY SEND_W3GS_STARTDOWNLOAD( unsigned char fromPID ); + BYTEARRAY SEND_W3GS_MAPPART( unsigned char fromPID, unsigned char toPID, uint32_t start, string *mapData ); + BYTEARRAY SEND_W3GS_INCOMING_ACTION2( queue actions ); + + // other functions + +private: + bool AssignLength( BYTEARRAY &content ); + bool ValidateLength( BYTEARRAY &content ); + BYTEARRAY EncodeSlotInfo( vector &slots, uint32_t randomSeed, unsigned char layoutStyle, unsigned char playerSlots ); +}; + +// +// CIncomingJoinPlayer +// + +class CIncomingJoinPlayer +{ +private: + uint32_t m_HostCounter; + string m_Name; + BYTEARRAY m_InternalIP; + +public: + CIncomingJoinPlayer( uint32_t nHostCounter, string nName, BYTEARRAY &nInternalIP ); + ~CIncomingJoinPlayer( ); + + uint32_t GetHostCounter( ) { return m_HostCounter; } + string GetName( ) { return m_Name; } + BYTEARRAY GetInternalIP( ) { return m_InternalIP; } +}; + +// +// CIncomingAction +// + +class CIncomingAction +{ +private: + unsigned char m_PID; + BYTEARRAY m_CRC; + BYTEARRAY m_Action; + +public: + CIncomingAction( unsigned char nPID, BYTEARRAY &nCRC, BYTEARRAY &nAction ); + ~CIncomingAction( ); + + unsigned char GetPID( ) { return m_PID; } + BYTEARRAY GetCRC( ) { return m_CRC; } + BYTEARRAY *GetAction( ) { return &m_Action; } + uint32_t GetLength( ) { return m_Action.size( ) + 3; } +}; + +// +// CIncomingChatPlayer +// + +class CIncomingChatPlayer +{ +public: + enum ChatToHostType + { + CTH_MESSAGE = 0, // a chat message + CTH_MESSAGEEXTRA = 1, // a chat message with extra flags + CTH_TEAMCHANGE = 2, // a team change request + CTH_COLOURCHANGE = 3, // a colour change request + CTH_RACECHANGE = 4, // a race change request + CTH_HANDICAPCHANGE = 5 // a handicap change request + }; + +private: + ChatToHostType m_Type; + unsigned char m_FromPID; + BYTEARRAY m_ToPIDs; + unsigned char m_Flag; + string m_Message; + unsigned char m_Byte; + BYTEARRAY m_ExtraFlags; + +public: + CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, string nMessage ); + CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, string nMessage, BYTEARRAY &nExtraFlags ); + CIncomingChatPlayer( unsigned char nFromPID, BYTEARRAY &nToPIDs, unsigned char nFlag, unsigned char nByte ); + ~CIncomingChatPlayer( ); + + ChatToHostType GetType( ) { return m_Type; } + unsigned char GetFromPID( ) { return m_FromPID; } + BYTEARRAY GetToPIDs( ) { return m_ToPIDs; } + unsigned char GetFlag( ) { return m_Flag; } + string GetMessage( ) { return m_Message; } + unsigned char GetByte( ) { return m_Byte; } + BYTEARRAY GetExtraFlags( ) { return m_ExtraFlags; } +}; + +class CIncomingMapSize +{ +private: + unsigned char m_SizeFlag; + uint32_t m_MapSize; + +public: + CIncomingMapSize( unsigned char nSizeFlag, uint32_t nMapSize ); + ~CIncomingMapSize( ); + + unsigned char GetSizeFlag( ) { return m_SizeFlag; } + uint32_t GetMapSize( ) { return m_MapSize; } +}; + +#endif diff --git a/ghost-legacy/gameslot.cpp b/ghost-legacy/gameslot.cpp new file mode 100644 index 0000000..a04b203 --- /dev/null +++ b/ghost-legacy/gameslot.cpp @@ -0,0 +1,95 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "gameslot.h" + +// +// CGameSlot +// + +CGameSlot :: CGameSlot( BYTEARRAY &n ) +{ + if( n.size( ) >= 7 ) + { + m_PID = n[0]; + m_DownloadStatus = n[1]; + m_SlotStatus = n[2]; + m_Computer = n[3]; + m_Team = n[4]; + m_Colour = n[5]; + m_Race = n[6]; + + if( n.size( ) >= 8 ) + m_ComputerType = n[7]; + else + m_ComputerType = SLOTCOMP_NORMAL; + + if( n.size( ) >= 9 ) + m_Handicap = n[8]; + else + m_Handicap = 100; + } + else + { + m_PID = 0; + m_DownloadStatus = 255; + m_SlotStatus = SLOTSTATUS_OPEN; + m_Computer = 0; + m_Team = 0; + m_Colour = 1; + m_Race = SLOTRACE_RANDOM; + m_ComputerType = SLOTCOMP_NORMAL; + m_Handicap = 100; + } +} + +CGameSlot :: CGameSlot( unsigned char nPID, unsigned char nDownloadStatus, unsigned char nSlotStatus, unsigned char nComputer, unsigned char nTeam, unsigned char nColour, unsigned char nRace, unsigned char nComputerType, unsigned char nHandicap ) +{ + m_PID = nPID; + m_DownloadStatus = nDownloadStatus; + m_SlotStatus = nSlotStatus; + m_Computer = nComputer; + m_Team = nTeam; + m_Colour = nColour; + m_Race = nRace; + m_ComputerType = nComputerType; + m_Handicap = nHandicap; +} + +CGameSlot :: ~CGameSlot( ) +{ + +} + +BYTEARRAY CGameSlot :: GetByteArray( ) const +{ + BYTEARRAY b; + b.push_back( m_PID ); + b.push_back( m_DownloadStatus ); + b.push_back( m_SlotStatus ); + b.push_back( m_Computer ); + b.push_back( m_Team ); + b.push_back( m_Colour ); + b.push_back( m_Race ); + b.push_back( m_ComputerType ); + b.push_back( m_Handicap ); + return b; +} diff --git a/ghost-legacy/gameslot.h b/ghost-legacy/gameslot.h new file mode 100644 index 0000000..476bfab --- /dev/null +++ b/ghost-legacy/gameslot.h @@ -0,0 +1,84 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GAMESLOT_H +#define GAMESLOT_H + +#define SLOTSTATUS_OPEN 0 +#define SLOTSTATUS_CLOSED 1 +#define SLOTSTATUS_OCCUPIED 2 + +#define SLOTRACE_HUMAN 1 +#define SLOTRACE_ORC 2 +#define SLOTRACE_NIGHTELF 4 +#define SLOTRACE_UNDEAD 8 +#define SLOTRACE_RANDOM 32 +#define SLOTRACE_SELECTABLE 64 + +#define SLOTCOMP_EASY 0 +#define SLOTCOMP_NORMAL 1 +#define SLOTCOMP_HARD 2 + +// +// CGameSlot +// + +class CGameSlot +{ +private: + unsigned char m_PID; // player id + unsigned char m_DownloadStatus; // download status (0% to 100%) + unsigned char m_SlotStatus; // slot status (0 = open, 1 = closed, 2 = occupied) + unsigned char m_Computer; // computer (0 = no, 1 = yes) + unsigned char m_Team; // team + unsigned char m_Colour; // colour + unsigned char m_Race; // race (1 = human, 2 = orc, 4 = night elf, 8 = undead, 32 = random, 64 = selectable) + unsigned char m_ComputerType; // computer type (0 = easy, 1 = human or normal comp, 2 = hard comp) + unsigned char m_Handicap; // handicap + +public: + CGameSlot( BYTEARRAY &n ); + CGameSlot( unsigned char nPID, unsigned char nDownloadStatus, unsigned char nSlotStatus, unsigned char nComputer, unsigned char nTeam, unsigned char nColour, unsigned char nRace, unsigned char nComputerType = 1, unsigned char nHandicap = 100 ); + ~CGameSlot( ); + + unsigned char GetPID( ) { return m_PID; } + unsigned char GetDownloadStatus( ) { return m_DownloadStatus; } + unsigned char GetSlotStatus( ) { return m_SlotStatus; } + unsigned char GetComputer( ) { return m_Computer; } + unsigned char GetTeam( ) { return m_Team; } + unsigned char GetColour( ) { return m_Colour; } + unsigned char GetRace( ) { return m_Race; } + unsigned char GetComputerType( ) { return m_ComputerType; } + unsigned char GetHandicap( ) { return m_Handicap; } + + void SetPID( unsigned char nPID ) { m_PID = nPID; } + void SetDownloadStatus( unsigned char nDownloadStatus ) { m_DownloadStatus = nDownloadStatus; } + void SetSlotStatus( unsigned char nSlotStatus ) { m_SlotStatus = nSlotStatus; } + void SetComputer( unsigned char nComputer ) { m_Computer = nComputer; } + void SetTeam( unsigned char nTeam ) { m_Team = nTeam; } + void SetColour( unsigned char nColour ) { m_Colour = nColour; } + void SetRace( unsigned char nRace ) { m_Race = nRace; } + void SetComputerType( unsigned char nComputerType ) { m_ComputerType = nComputerType; } + void SetHandicap( unsigned char nHandicap ) { m_Handicap = nHandicap; } + + BYTEARRAY GetByteArray( ) const; +}; + +#endif diff --git a/ghost-legacy/ghost-legacy.vcproj b/ghost-legacy/ghost-legacy.vcproj new file mode 100644 index 0000000..619639e --- /dev/null +++ b/ghost-legacy/ghost-legacy.vcproj @@ -0,0 +1,634 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ghost-legacy/ghost.cpp b/ghost-legacy/ghost.cpp new file mode 100644 index 0000000..938e7b4 --- /dev/null +++ b/ghost-legacy/ghost.cpp @@ -0,0 +1,1783 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "crc32.h" +#include "sha1.h" +#include "csvparser.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "ghostdb.h" +#include "ghostdbsqlite.h" +#include "ghostdbmysql.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "gpsprotocol.h" +#include "game_base.h" +#include "game.h" +#include "game_admin.h" +#include "userinterface.h" + +#include +#include + +#ifdef WIN32 + #include // for WSAIoctl +#endif + +#define __STORMLIB_SELF__ +#include + +/* + +#include "ghost.h" +#include "util.h" +#include "crc32.h" +#include "sha1.h" +#include "csvparser.h" +#include "config.h" +#include "language.h" +#include "socket.h" +#include "commandpacket.h" +#include "ghostdb.h" +#include "ghostdbsqlite.h" +#include "ghostdbmysql.h" +#include "bncsutilinterface.h" +#include "warden.h" +#include "bnlsprotocol.h" +#include "bnlsclient.h" +#include "bnetprotocol.h" +#include "bnet.h" +#include "map.h" +#include "packed.h" +#include "savegame.h" +#include "replay.h" +#include "gameslot.h" +#include "gameplayer.h" +#include "gameprotocol.h" +#include "gpsprotocol.h" +#include "game_base.h" +#include "game.h" +#include "game_admin.h" +#include "stats.h" +#include "statsdota.h" +#include "sqlite3.h" + +*/ + +#ifdef WIN32 + #include + #include +#endif + +#include + +#ifndef WIN32 + #include +#endif + +#ifdef __APPLE__ + #include +#endif + +string gCFGFile; +string gLogFile; +uint32_t gLogMethod; +ofstream *gLog = NULL; +CGHost *gGHost = NULL; +CCurses *gCurses = NULL; + +uint32_t GetTime( ) +{ + return GetTicks( ) / 1000; +} + +uint32_t GetTicks( ) +{ +#ifdef WIN32 + // don't use GetTickCount anymore because it's not accurate enough (~16ms resolution) + // don't use QueryPerformanceCounter anymore because it isn't guaranteed to be strictly increasing on some systems and thus requires "smoothing" code + // use timeGetTime instead, which typically has a high resolution (5ms or more) but we request a lower resolution on startup + + return timeGetTime( ); +#elif __APPLE__ + uint64_t current = mach_absolute_time( ); + static mach_timebase_info_data_t info = { 0, 0 }; + // get timebase info + if( info.denom == 0 ) + mach_timebase_info( &info ); + uint64_t elapsednano = current * ( info.numer / info.denom ); + // convert ns to ms + return elapsednano / 1e6; +#else + uint32_t ticks; + struct timespec t; + clock_gettime( CLOCK_MONOTONIC, &t ); + ticks = t.tv_sec * 1000; + ticks += t.tv_nsec / 1000000; + return ticks; +#endif +} + +void SignalCatcher2( int s ) +{ + CONSOLE_Print( "[!!!] caught signal " + UTIL_ToString( s ) + ", exiting NOW" ); + + if( gGHost ) + { + if( gGHost->m_Exiting ) + exit( 1 ); + else + gGHost->m_Exiting = true; + } + else + exit( 1 ); +} + +void SignalCatcher( int s ) +{ + // signal( SIGABRT, SignalCatcher2 ); + signal( SIGINT, SignalCatcher2 ); + + CONSOLE_Print( "[!!!] caught signal " + UTIL_ToString( s ) + ", exiting nicely" ); + + if( gGHost ) + gGHost->m_ExitingNice = true; + else + exit( 1 ); +} + +void CONSOLE_Print( string message ) +{ + CONSOLE_Print( message, 0, true ); +} + +void CONSOLE_Print( string message, bool toMainBuffer ) +{ + CONSOLE_Print( message, 0, toMainBuffer ); +} + +void CONSOLE_Print( string message, uint32_t realmId, bool toMainBuffer ) +{ + if ( gCurses ) + gCurses->Print( message, realmId, toMainBuffer ); + else + cout << message << endl; + + // logging + + if( !gLogFile.empty( ) ) + { + if( gLogMethod == 1 ) + { + ofstream Log; + Log.open( gLogFile.c_str( ), ios :: app ); + + if( !Log.fail( ) ) + { + time_t Now = time( NULL ); + string Time = asctime( localtime( &Now ) ); + + // erase the newline + + Time.erase( Time.size( ) - 1 ); + Log << "[" << Time << "] " << message << endl; + Log.close( ); + } + } + else if( gLogMethod == 2 ) + { + if( gLog && !gLog->fail( ) ) + { + time_t Now = time( NULL ); + string Time = asctime( localtime( &Now ) ); + + // erase the newline + + Time.erase( Time.size( ) - 1 ); + *gLog << "[" << Time << "] " << message << endl; + gLog->flush( ); + } + } + } +} + +void DEBUG_Print( string message ) +{ + cout << message << endl; +} + +void DEBUG_Print( BYTEARRAY b ) +{ + cout << "{ "; + + for( unsigned int i = 0; i < b.size( ); i++ ) + cout << hex << (int)b[i] << " "; + + cout << "}" << endl; +} + +void CONSOLE_ChangeChannel( string channel, uint32_t realmId ) +{ + if ( gCurses ) + gCurses->ChangeChannel( channel, realmId ); +} + +void CONSOLE_AddChannelUser( string name, uint32_t realmId, int flag ) +{ + if ( gCurses ) + gCurses->AddChannelUser( name, realmId, flag ); +} + +void CONSOLE_UpdateChannelUser( string name, uint32_t realmId, int flag ) +{ + if ( gCurses ) + gCurses->UpdateChannelUser( name, realmId, flag ); +} + +void CONSOLE_RemoveChannelUser( string name, uint32_t realmId ) +{ + if ( gCurses ) + gCurses->RemoveChannelUser( name, realmId ); +} + +void CONSOLE_RemoveChannelUsers( uint32_t realmId ) +{ + if ( gCurses ) + gCurses->RemoveChannelUsers( realmId ); +} + +// +// main +// + +int main( int argc, char **argv ) +{ + gCFGFile = "ghost.cfg"; + + if( argc > 1 && argv[1] ) + gCFGFile = argv[1]; + + // read config file + + CConfig CFG; + CFG.Read( "default.cfg" ); + CFG.Read( gCFGFile ); + gLogFile = CFG.GetString( "bot_log", string( ) ); + gLogMethod = CFG.GetInt( "bot_logmethod", 1 ); + + if ( CFG.GetInt( "curses_enabled", 1 ) == 1 ) + gCurses = new CCurses( CFG.GetInt( "term_width", 0 ), CFG.GetInt( "term_height", 0 ), !!CFG.GetInt( "curses_splitview", 0 ), CFG.GetInt( "curses_listtype", 0 ) ); + + UTIL_Construct_UTF8_Latin1_Map( ); + + if( !gLogFile.empty( ) ) + { + if( gLogMethod == 1 ) + { + // log method 1: open, append, and close the log for every message + // this works well on Linux but poorly on Windows, particularly as the log file grows in size + // the log file can be edited/moved/deleted while GHost++ is running + } + else if( gLogMethod == 2 ) + { + // log method 2: open the log on startup, flush the log for every message, close the log on shutdown + // the log file CANNOT be edited/moved/deleted while GHost++ is running + + gLog = new ofstream( ); + gLog->open( gLogFile.c_str( ), ios :: app ); + } + } + + CONSOLE_Print( "[GHOST] starting up" ); + + if( !gLogFile.empty( ) ) + { + if( gLogMethod == 1 ) + CONSOLE_Print( "[GHOST] using log method 1, logging is enabled and [" + gLogFile + "] will not be locked" ); + else if( gLogMethod == 2 ) + { + if( gLog->fail( ) ) + CONSOLE_Print( "[GHOST] using log method 2 but unable to open [" + gLogFile + "] for appending, logging is disabled" ); + else + CONSOLE_Print( "[GHOST] using log method 2, logging is enabled and [" + gLogFile + "] is now locked" ); + } + } + else + CONSOLE_Print( "[GHOST] no log file specified, logging is disabled" ); + + // catch SIGABRT and SIGINT + + // signal( SIGABRT, SignalCatcher ); + signal( SIGINT, SignalCatcher ); + +#ifndef WIN32 + // disable SIGPIPE since some systems like OS X don't define MSG_NOSIGNAL + + signal( SIGPIPE, SIG_IGN ); +#endif + +#ifdef WIN32 + // initialize timer resolution + // attempt to set the resolution as low as possible from 1ms to 5ms + + unsigned int TimerResolution = 0; + + for( unsigned int i = 1; i <= 5; i++ ) + { + if( timeBeginPeriod( i ) == TIMERR_NOERROR ) + { + TimerResolution = i; + break; + } + else if( i < 5 ) + CONSOLE_Print( "[GHOST] error setting Windows timer resolution to " + UTIL_ToString( i ) + " milliseconds, trying a higher resolution" ); + else + { + CONSOLE_Print( "[GHOST] error setting Windows timer resolution" ); + return 1; + } + } + + CONSOLE_Print( "[GHOST] using Windows timer with resolution " + UTIL_ToString( TimerResolution ) + " milliseconds" ); +#elif __APPLE__ + // not sure how to get the resolution +#else + // print the timer resolution + + struct timespec Resolution; + + if( clock_getres( CLOCK_MONOTONIC, &Resolution ) == -1 ) + CONSOLE_Print( "[GHOST] error getting monotonic timer resolution" ); + else + CONSOLE_Print( "[GHOST] using monotonic timer with resolution " + UTIL_ToString( (double)( Resolution.tv_nsec / 1000 ), 2 ) + " microseconds" ); +#endif + +#ifdef WIN32 + // initialize winsock + + CONSOLE_Print( "[GHOST] starting winsock" ); + WSADATA wsadata; + + if( WSAStartup( MAKEWORD( 2, 2 ), &wsadata ) != 0 ) + { + CONSOLE_Print( "[GHOST] error starting winsock" ); + return 1; + } + + // increase process priority + + CONSOLE_Print( "[GHOST] setting process priority to \"above normal\"" ); + SetPriorityClass( GetCurrentProcess( ), ABOVE_NORMAL_PRIORITY_CLASS ); +#endif + + // initialize ghost + + gGHost = new CGHost( &CFG ); + + if ( gCurses ) + gCurses->SetGHost( gGHost ); + + while( 1 ) + { + // block for 50ms on all sockets - if you intend to perform any timed actions more frequently you should change this + // that said it's likely we'll loop more often than this due to there being data waiting on one of the sockets but there aren't any guarantees + + if( gGHost->Update( 50000 ) || ( gCurses && gCurses->Update( ) ) ) + break; + } + + // shutdown ghost + + CONSOLE_Print( "[GHOST] shutting down" ); + delete gGHost; + gGHost = NULL; + +#ifdef WIN32 + // shutdown winsock + + CONSOLE_Print( "[GHOST] shutting down winsock" ); + WSACleanup( ); + + // shutdown timer + + timeEndPeriod( TimerResolution ); +#endif + + if( gLog ) + { + if( !gLog->fail( ) ) + gLog->close( ); + + delete gLog; + } + + // shutdown curses + + if ( gCurses ) + { + CONSOLE_Print( "[GHOST] shutting down curses" ); + delete gCurses; + gCurses = NULL; + } + + return 0; +} + +// +// CGHost +// + +CGHost :: CGHost( CConfig *CFG ) +{ + m_UDPSocket = new CUDPSocket( ); + m_UDPSocket->SetBroadcastTarget( CFG->GetString( "udp_broadcasttarget", string( ) ) ); + m_UDPSocket->SetDontRoute( CFG->GetInt( "udp_dontroute", 0 ) == 0 ? false : true ); + m_ReconnectSocket = NULL; + m_GPSProtocol = new CGPSProtocol( ); + m_CRC = new CCRC32( ); + m_CRC->Initialize( ); + m_SHA = new CSHA1( ); + m_CurrentGame = NULL; + string DBType = CFG->GetString( "db_type", "sqlite3" ); + CONSOLE_Print( "[GHOST] opening primary database" ); + + if( DBType == "mysql" ) + { +#ifdef GHOST_MYSQL + m_DB = new CGHostDBMySQL( CFG ); +#else + CONSOLE_Print( "[GHOST] warning - this binary was not compiled with MySQL database support, using SQLite database instead" ); + m_DB = new CGHostDBSQLite( CFG ); +#endif + } + else + m_DB = new CGHostDBSQLite( CFG ); + + CONSOLE_Print( "[GHOST] opening secondary (local) database" ); + m_DBLocal = new CGHostDBSQLite( CFG ); + + // get a list of local IP addresses + // this list is used elsewhere to determine if a player connecting to the bot is local or not + + CONSOLE_Print( "[GHOST] attempting to find local IP addresses" ); + +#ifdef WIN32 + // use a more reliable Windows specific method since the portable method doesn't always work properly on Windows + // code stolen from: http://tangentsoft.net/wskfaq/examples/getifaces.html + + SOCKET sd = WSASocket( AF_INET, SOCK_DGRAM, 0, 0, 0, 0 ); + + if( sd == SOCKET_ERROR ) + CONSOLE_Print( "[GHOST] error finding local IP addresses - failed to create socket (error code " + UTIL_ToString( WSAGetLastError( ) ) + ")" ); + else + { + INTERFACE_INFO InterfaceList[20]; + unsigned long nBytesReturned; + + if( WSAIoctl( sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, sizeof(InterfaceList), &nBytesReturned, 0, 0 ) == SOCKET_ERROR ) + CONSOLE_Print( "[GHOST] error finding local IP addresses - WSAIoctl failed (error code " + UTIL_ToString( WSAGetLastError( ) ) + ")" ); + else + { + int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO); + + for( int i = 0; i < nNumInterfaces; i++ ) + { + sockaddr_in *pAddress; + pAddress = (sockaddr_in *)&(InterfaceList[i].iiAddress); + CONSOLE_Print( "[GHOST] local IP address #" + UTIL_ToString( i + 1 ) + " is [" + string( inet_ntoa( pAddress->sin_addr ) ) + "]" ); + m_LocalAddresses.push_back( UTIL_CreateByteArray( (uint32_t)pAddress->sin_addr.s_addr, false ) ); + } + } + + closesocket( sd ); + } +#else + // use a portable method + + char HostName[255]; + + if( gethostname( HostName, 255 ) == SOCKET_ERROR ) + CONSOLE_Print( "[GHOST] error finding local IP addresses - failed to get local hostname" ); + else + { + CONSOLE_Print( "[GHOST] local hostname is [" + string( HostName ) + "]" ); + struct hostent *HostEnt = gethostbyname( HostName ); + + if( !HostEnt ) + CONSOLE_Print( "[GHOST] error finding local IP addresses - gethostbyname failed" ); + else + { + for( int i = 0; HostEnt->h_addr_list[i] != NULL; i++ ) + { + struct in_addr Address; + memcpy( &Address, HostEnt->h_addr_list[i], sizeof(struct in_addr) ); + CONSOLE_Print( "[GHOST] local IP address #" + UTIL_ToString( i + 1 ) + " is [" + string( inet_ntoa( Address ) ) + "]" ); + m_LocalAddresses.push_back( UTIL_CreateByteArray( (uint32_t)Address.s_addr, false ) ); + } + } + } +#endif + + m_Language = NULL; + m_Exiting = false; + m_ExitingNice = false; + m_Enabled = true; + m_Version = "17.0 CursesMod-1.12"; + m_HostCounter = 1; + m_AutoHostMaximumGames = CFG->GetInt( "autohost_maxgames", 0 ); + m_AutoHostAutoStartPlayers = CFG->GetInt( "autohost_startplayers", 0 ); + m_AutoHostGameName = CFG->GetString( "autohost_gamename", string( ) ); + m_AutoHostOwner = CFG->GetString( "autohost_owner", string( ) ); + m_LastAutoHostTime = GetTime( ); + m_AutoHostMatchMaking = false; + m_AutoHostMinimumScore = 0.0; + m_AutoHostMaximumScore = 0.0; + m_AllGamesFinished = false; + m_AllGamesFinishedTime = 0; + m_TFT = CFG->GetInt( "bot_tft", 1 ) == 0 ? false : true; + + if( m_TFT ) + CONSOLE_Print( "[GHOST] acting as Warcraft III: The Frozen Throne" ); + else + CONSOLE_Print( "[GHOST] acting as Warcraft III: Reign of Chaos" ); + + m_HostPort = CFG->GetInt( "bot_hostport", 6112 ); + m_Reconnect = CFG->GetInt( "bot_reconnect", 1 ) == 0 ? false : true; + m_ReconnectPort = CFG->GetInt( "bot_reconnectport", 6114 ); + m_DefaultMap = CFG->GetString( "bot_defaultmap", "map" ); + m_AdminGameCreate = CFG->GetInt( "admingame_create", 0 ) == 0 ? false : true; + m_AdminGamePort = CFG->GetInt( "admingame_port", 6113 ); + m_AdminGamePassword = CFG->GetString( "admingame_password", string( ) ); + m_AdminGameMap = CFG->GetString( "admingame_map", string( ) ); + m_LANWar3Version = CFG->GetInt( "lan_war3version", 24 ); + m_ReplayWar3Version = CFG->GetInt( "replay_war3version", 24 ); + m_ReplayBuildNumber = CFG->GetInt( "replay_buildnumber", 6059 ); + SetConfigs( CFG ); + + // load the battle.net connections + // we're just loading the config data and creating the CBNET classes here, the connections are established later (in the Update function) + + for( uint32_t i = 1; i < 10; i++ ) + { + string Prefix; + + if( i == 1 ) + Prefix = "bnet_"; + else + Prefix = "bnet" + UTIL_ToString( i ) + "_"; + + string Server = CFG->GetString( Prefix + "server", string( ) ); + string ServerAlias = CFG->GetString( Prefix + "serveralias", string( ) ); + string CDKeyROC = CFG->GetString( Prefix + "cdkeyroc", string( ) ); + string CDKeyTFT = CFG->GetString( Prefix + "cdkeytft", string( ) ); + string CountryAbbrev = CFG->GetString( Prefix + "countryabbrev", "USA" ); + string Country = CFG->GetString( Prefix + "country", "United States" ); + string Locale = CFG->GetString( Prefix + "locale", "system" ); + uint32_t LocaleID; + + if( Locale == "system" ) + { +#ifdef WIN32 + LocaleID = GetUserDefaultLangID( ); +#else + LocaleID = 1033; +#endif + } + else + LocaleID = UTIL_ToUInt32( Locale ); + + string UserName = CFG->GetString( Prefix + "username", string( ) ); + string UserPassword = CFG->GetString( Prefix + "password", string( ) ); + string FirstChannel = CFG->GetString( Prefix + "firstchannel", "The Void" ); + string RootAdmin = CFG->GetString( Prefix + "rootadmin", string( ) ); + string BNETCommandTrigger = CFG->GetString( Prefix + "commandtrigger", "!" ); + + if( BNETCommandTrigger.empty( ) ) + BNETCommandTrigger = "!"; + + bool HoldFriends = CFG->GetInt( Prefix + "holdfriends", 1 ) == 0 ? false : true; + bool HoldClan = CFG->GetInt( Prefix + "holdclan", 1 ) == 0 ? false : true; + bool PublicCommands = CFG->GetInt( Prefix + "publiccommands", 1 ) == 0 ? false : true; + string BNLSServer = CFG->GetString( Prefix + "bnlsserver", string( ) ); + int BNLSPort = CFG->GetInt( Prefix + "bnlsport", 9367 ); + int BNLSWardenCookie = CFG->GetInt( Prefix + "bnlswardencookie", 0 ); + unsigned char War3Version = CFG->GetInt( Prefix + "custom_war3version", 24 ); + BYTEARRAY EXEVersion = UTIL_ExtractNumbers( CFG->GetString( Prefix + "custom_exeversion", string( ) ), 4 ); + BYTEARRAY EXEVersionHash = UTIL_ExtractNumbers( CFG->GetString( Prefix + "custom_exeversionhash", string( ) ), 4 ); + string PasswordHashType = CFG->GetString( Prefix + "custom_passwordhashtype", string( ) ); + string PVPGNRealmName = CFG->GetString( Prefix + "custom_pvpgnrealmname", "PvPGN Realm" ); + uint32_t MaxMessageLength = CFG->GetInt( Prefix + "custom_maxmessagelength", 200 ); + + if( Server.empty( ) ) + break; + + if( CDKeyROC.empty( ) ) + { + CONSOLE_Print( "[GHOST] missing " + Prefix + "cdkeyroc, skipping this battle.net connection" ); + continue; + } + + if( m_TFT && CDKeyTFT.empty( ) ) + { + CONSOLE_Print( "[GHOST] missing " + Prefix + "cdkeytft, skipping this battle.net connection" ); + continue; + } + + if( UserName.empty( ) ) + { + CONSOLE_Print( "[GHOST] missing " + Prefix + "username, skipping this battle.net connection" ); + continue; + } + + if( UserPassword.empty( ) ) + { + CONSOLE_Print( "[GHOST] missing " + Prefix + "password, skipping this battle.net connection" ); + continue; + } + + CONSOLE_Print( "[GHOST] found battle.net connection #" + UTIL_ToString( i ) + " for server [" + Server + "]" ); + + if( Locale == "system" ) + { +#ifdef WIN32 + CONSOLE_Print( "[GHOST] using system locale of " + UTIL_ToString( LocaleID ) ); +#else + CONSOLE_Print( "[GHOST] unable to get system locale, using default locale of 1033" ); +#endif + } + + m_BNETs.push_back( new CBNET( this, Server, ServerAlias, BNLSServer, (uint16_t)BNLSPort, (uint32_t)BNLSWardenCookie, CDKeyROC, CDKeyTFT, CountryAbbrev, Country, LocaleID, UserName, UserPassword, FirstChannel, RootAdmin, BNETCommandTrigger[0], HoldFriends, HoldClan, PublicCommands, War3Version, EXEVersion, EXEVersionHash, PasswordHashType, PVPGNRealmName, MaxMessageLength, i ) ); + } + + if( m_BNETs.empty( ) ) + CONSOLE_Print( "[GHOST] warning - no battle.net connections found in config file" ); + + // extract common.j and blizzard.j from War3Patch.mpq if we can + // these two files are necessary for calculating "map_crc" when loading maps so we make sure to do it before loading the default map + // see CMap :: Load for more information + + ExtractScripts( ); + + // load the default maps (note: make sure to run ExtractScripts first) + + if( m_DefaultMap.size( ) < 4 || m_DefaultMap.substr( m_DefaultMap.size( ) - 4 ) != ".cfg" ) + { + m_DefaultMap += ".cfg"; + CONSOLE_Print( "[GHOST] adding \".cfg\" to default map -> new default is [" + m_DefaultMap + "]" ); + } + + CConfig MapCFG; + MapCFG.Read( m_MapCFGPath + m_DefaultMap ); + m_Map = new CMap( this, &MapCFG, m_MapCFGPath + m_DefaultMap ); + + if( !m_AdminGameMap.empty( ) ) + { + if( m_AdminGameMap.size( ) < 4 || m_AdminGameMap.substr( m_AdminGameMap.size( ) - 4 ) != ".cfg" ) + { + m_AdminGameMap += ".cfg"; + CONSOLE_Print( "[GHOST] adding \".cfg\" to default admin game map -> new default is [" + m_AdminGameMap + "]" ); + } + + CONSOLE_Print( "[GHOST] trying to load default admin game map" ); + CConfig AdminMapCFG; + AdminMapCFG.Read( m_MapCFGPath + m_AdminGameMap ); + m_AdminMap = new CMap( this, &AdminMapCFG, m_MapCFGPath + m_AdminGameMap ); + + if( !m_AdminMap->GetValid( ) ) + { + CONSOLE_Print( "[GHOST] default admin game map isn't valid, using hardcoded admin game map instead" ); + delete m_AdminMap; + m_AdminMap = new CMap( this ); + } + } + else + { + CONSOLE_Print( "[GHOST] using hardcoded admin game map" ); + m_AdminMap = new CMap( this ); + } + + m_AutoHostMap = new CMap( *m_Map ); + m_SaveGame = new CSaveGame( ); + + // load the iptocountry data + + LoadIPToCountryData( ); + + // create the admin game + + if( m_AdminGameCreate ) + { + CONSOLE_Print( "[GHOST] creating admin game" ); + m_AdminGame = new CAdminGame( this, m_AdminMap, NULL, m_AdminGamePort, 0, "GHost++ Admin Game", m_AdminGamePassword ); + + if( m_AdminGamePort == m_HostPort ) + CONSOLE_Print( "[GHOST] warning - admingame_port and bot_hostport are set to the same value, you won't be able to host any games" ); + } + else + m_AdminGame = NULL; + + if( m_BNETs.empty( ) && !m_AdminGame ) + CONSOLE_Print( "[GHOST] warning - no battle.net connections found and no admin game created" ); + +#ifdef GHOST_MYSQL + CONSOLE_Print( "[GHOST] GHost++ Version " + m_Version + " (with MySQL support)" ); +#else + CONSOLE_Print( "[GHOST] GHost++ Version " + m_Version + " (without MySQL support)" ); +#endif +} + +CGHost :: ~CGHost( ) +{ + delete m_UDPSocket; + delete m_ReconnectSocket; + + for( vector :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); i++ ) + delete *i; + + delete m_GPSProtocol; + delete m_CRC; + delete m_SHA; + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + delete *i; + + delete m_CurrentGame; + delete m_AdminGame; + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + delete *i; + + delete m_DB; + delete m_DBLocal; + + // warning: we don't delete any entries of m_Callables here because we can't be guaranteed that the associated threads have terminated + // this is fine if the program is currently exiting because the OS will clean up after us + // but if you try to recreate the CGHost object within a single session you will probably leak resources! + + if( !m_Callables.empty( ) ) + CONSOLE_Print( "[GHOST] warning - " + UTIL_ToString( m_Callables.size( ) ) + " orphaned callables were leaked (this is not an error)" ); + + delete m_Language; + delete m_Map; + delete m_AdminMap; + delete m_AutoHostMap; + delete m_SaveGame; +} + +bool CGHost :: Update( long usecBlock ) +{ + // todotodo: do we really want to shutdown if there's a database error? is there any way to recover from this? + + if( m_DB->HasError( ) ) + { + CONSOLE_Print( "[GHOST] database error - " + m_DB->GetError( ) ); + return true; + } + + if( m_DBLocal->HasError( ) ) + { + CONSOLE_Print( "[GHOST] local database error - " + m_DBLocal->GetError( ) ); + return true; + } + + // try to exit nicely if requested to do so + + if( m_ExitingNice ) + { + if( !m_BNETs.empty( ) ) + { + CONSOLE_Print( "[GHOST] deleting all battle.net connections in preparation for exiting nicely" ); + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + delete *i; + + m_BNETs.clear( ); + } + + if( m_CurrentGame ) + { + CONSOLE_Print( "[GHOST] deleting current game in preparation for exiting nicely" ); + delete m_CurrentGame; + m_CurrentGame = NULL; + } + + if( m_AdminGame ) + { + CONSOLE_Print( "[GHOST] deleting admin game in preparation for exiting nicely" ); + delete m_AdminGame; + m_AdminGame = NULL; + } + + if( m_Games.empty( ) ) + { + if( !m_AllGamesFinished ) + { + CONSOLE_Print( "[GHOST] all games finished, waiting 60 seconds for threads to finish" ); + CONSOLE_Print( "[GHOST] there are " + UTIL_ToString( m_Callables.size( ) ) + " threads in progress" ); + m_AllGamesFinished = true; + m_AllGamesFinishedTime = GetTime( ); + } + else + { + if( m_Callables.empty( ) ) + { + CONSOLE_Print( "[GHOST] all threads finished, exiting nicely" ); + m_Exiting = true; + } + else if( GetTime( ) - m_AllGamesFinishedTime >= 60 ) + { + CONSOLE_Print( "[GHOST] waited 60 seconds for threads to finish, exiting anyway" ); + CONSOLE_Print( "[GHOST] there are " + UTIL_ToString( m_Callables.size( ) ) + " threads still in progress which will be terminated" ); + m_Exiting = true; + } + } + } + } + + // update callables + + for( vector :: iterator i = m_Callables.begin( ); i != m_Callables.end( ); ) + { + if( (*i)->GetReady( ) ) + { + m_DB->RecoverCallable( *i ); + delete *i; + i = m_Callables.erase( i ); + } + else + i++; + } + + // create the GProxy++ reconnect listener + + if( m_Reconnect ) + { + if( !m_ReconnectSocket ) + { + m_ReconnectSocket = new CTCPServer( ); + + if( m_ReconnectSocket->Listen( m_BindAddress, m_ReconnectPort ) ) + CONSOLE_Print( "[GHOST] listening for GProxy++ reconnects on port " + UTIL_ToString( m_ReconnectPort ) ); + else + { + CONSOLE_Print( "[GHOST] error listening for GProxy++ reconnects on port " + UTIL_ToString( m_ReconnectPort ) ); + delete m_ReconnectSocket; + m_ReconnectSocket = NULL; + m_Reconnect = false; + } + } + else if( m_ReconnectSocket->HasError( ) ) + { + CONSOLE_Print( "[GHOST] GProxy++ reconnect listener error (" + m_ReconnectSocket->GetErrorString( ) + ")" ); + delete m_ReconnectSocket; + m_ReconnectSocket = NULL; + m_Reconnect = false; + } + } + + unsigned int NumFDs = 0; + + // take every socket we own and throw it in one giant select statement so we can block on all sockets + + int nfds = 0; + fd_set fd; + fd_set send_fd; + FD_ZERO( &fd ); + FD_ZERO( &send_fd ); + + // 1. all battle.net sockets + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + NumFDs += (*i)->SetFD( &fd, &send_fd, &nfds ); + + // 2. the current game's server and player sockets + + if( m_CurrentGame ) + NumFDs += m_CurrentGame->SetFD( &fd, &send_fd, &nfds ); + + // 3. the admin game's server and player sockets + + if( m_AdminGame ) + NumFDs += m_AdminGame->SetFD( &fd, &send_fd, &nfds ); + + // 4. all running games' player sockets + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + NumFDs += (*i)->SetFD( &fd, &send_fd, &nfds ); + + // 5. the GProxy++ reconnect socket(s) + + if( m_Reconnect && m_ReconnectSocket ) + { + m_ReconnectSocket->SetFD( &fd, &send_fd, &nfds ); + NumFDs++; + } + + for( vector :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); i++ ) + { + (*i)->SetFD( &fd, &send_fd, &nfds ); + NumFDs++; + } + + // before we call select we need to determine how long to block for + // previously we just blocked for a maximum of the passed usecBlock microseconds + // however, in an effort to make game updates happen closer to the desired latency setting we now use a dynamic block interval + // note: we still use the passed usecBlock as a hard maximum + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + { + if( (*i)->GetNextTimedActionTicks( ) * 1000 < usecBlock ) + usecBlock = (*i)->GetNextTimedActionTicks( ) * 1000; + } + + // always block for at least 1ms just in case something goes wrong + // this prevents the bot from sucking up all the available CPU if a game keeps asking for immediate updates + // it's a bit ridiculous to include this check since, in theory, the bot is programmed well enough to never make this mistake + // however, considering who programmed it, it's worthwhile to do it anyway + + if( usecBlock < 1000 ) + usecBlock = 1000; + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = usecBlock; + + struct timeval send_tv; + send_tv.tv_sec = 0; + send_tv.tv_usec = 0; + +#ifdef WIN32 + select( 1, &fd, NULL, NULL, &tv ); + select( 1, NULL, &send_fd, NULL, &send_tv ); +#else + select( nfds + 1, &fd, NULL, NULL, &tv ); + select( nfds + 1, NULL, &send_fd, NULL, &send_tv ); +#endif + + if( NumFDs == 0 ) + { + // we don't have any sockets (i.e. we aren't connected to battle.net maybe due to a lost connection and there aren't any games running) + // select will return immediately and we'll chew up the CPU if we let it loop so just sleep for 50ms to kill some time + + MILLISLEEP( 50 ); + } + + bool AdminExit = false; + bool BNETExit = false; + + // update current game + + if( m_CurrentGame ) + { + if( m_CurrentGame->Update( &fd, &send_fd ) ) + { + CONSOLE_Print( "[GHOST] deleting current game [" + m_CurrentGame->GetGameName( ) + "]" ); + delete m_CurrentGame; + m_CurrentGame = NULL; + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + (*i)->QueueGameUncreate( ); + (*i)->QueueEnterChat( ); + } + } + else if( m_CurrentGame ) + m_CurrentGame->UpdatePost( &send_fd ); + } + + // update admin game + + if( m_AdminGame ) + { + if( m_AdminGame->Update( &fd, &send_fd ) ) + { + CONSOLE_Print( "[GHOST] deleting admin game" ); + delete m_AdminGame; + m_AdminGame = NULL; + AdminExit = true; + } + else if( m_AdminGame ) + m_AdminGame->UpdatePost( &send_fd ); + } + + // update running games + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); ) + { + if( (*i)->Update( &fd, &send_fd ) ) + { + CONSOLE_Print( "[GHOST] deleting game [" + (*i)->GetGameName( ) + "]" ); + EventGameDeleted( *i ); + delete *i; + i = m_Games.erase( i ); + } + else + { + (*i)->UpdatePost( &send_fd ); + i++; + } + } + + // update battle.net connections + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->Update( &fd, &send_fd ) ) + BNETExit = true; + } + + // update GProxy++ reliable reconnect sockets + + if( m_Reconnect && m_ReconnectSocket ) + { + CTCPSocket *NewSocket = m_ReconnectSocket->Accept( &fd ); + + if( NewSocket ) + m_ReconnectSockets.push_back( NewSocket ); + } + + for( vector :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); ) + { + if( (*i)->HasError( ) || !(*i)->GetConnected( ) || GetTime( ) - (*i)->GetLastRecv( ) >= 10 ) + { + delete *i; + i = m_ReconnectSockets.erase( i ); + continue; + } + + (*i)->DoRecv( &fd ); + string *RecvBuffer = (*i)->GetBytes( ); + BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); + + // a packet is at least 4 bytes + + if( Bytes.size( ) >= 4 ) + { + if( Bytes[0] == GPS_HEADER_CONSTANT ) + { + // bytes 2 and 3 contain the length of the packet + + uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); + + if( Length >= 4 ) + { + if( Bytes.size( ) >= Length ) + { + if( Bytes[1] == CGPSProtocol :: GPS_RECONNECT && Length == 13 ) + { + unsigned char PID = Bytes[4]; + uint32_t ReconnectKey = UTIL_ByteArrayToUInt32( Bytes, false, 5 ); + uint32_t LastPacket = UTIL_ByteArrayToUInt32( Bytes, false, 9 ); + + // look for a matching player in a running game + + CGamePlayer *Match = NULL; + + for( vector :: iterator j = m_Games.begin( ); j != m_Games.end( ); j++ ) + { + if( (*j)->GetGameLoaded( ) ) + { + CGamePlayer *Player = (*j)->GetPlayerFromPID( PID ); + + if( Player && Player->GetGProxy( ) && Player->GetGProxyReconnectKey( ) == ReconnectKey ) + { + Match = Player; + break; + } + } + } + + if( Match ) + { + // reconnect successful! + + *RecvBuffer = RecvBuffer->substr( Length ); + Match->EventGProxyReconnect( *i, LastPacket ); + i = m_ReconnectSockets.erase( i ); + continue; + } + else + { + (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_NOTFOUND ) ); + (*i)->DoSend( &send_fd ); + delete *i; + i = m_ReconnectSockets.erase( i ); + continue; + } + } + else + { + (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) ); + (*i)->DoSend( &send_fd ); + delete *i; + i = m_ReconnectSockets.erase( i ); + continue; + } + } + } + else + { + (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) ); + (*i)->DoSend( &send_fd ); + delete *i; + i = m_ReconnectSockets.erase( i ); + continue; + } + } + else + { + (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) ); + (*i)->DoSend( &send_fd ); + delete *i; + i = m_ReconnectSockets.erase( i ); + continue; + } + } + + (*i)->DoSend( &send_fd ); + i++; + } + + // autohost + + if( !m_AutoHostGameName.empty( ) && m_AutoHostMaximumGames != 0 && m_AutoHostAutoStartPlayers != 0 && GetTime( ) - m_LastAutoHostTime >= 30 ) + { + // copy all the checks from CGHost :: CreateGame here because we don't want to spam the chat when there's an error + // instead we fail silently and try again soon + + if( !m_ExitingNice && m_Enabled && !m_CurrentGame && m_Games.size( ) < m_MaxGames && m_Games.size( ) < m_AutoHostMaximumGames ) + { + if( m_AutoHostMap->GetValid( ) ) + { + string GameName = m_AutoHostGameName + " #" + UTIL_ToString( m_HostCounter ); + + if( GameName.size( ) <= 31 ) + { + CreateGame( m_AutoHostMap, GAME_PUBLIC, false, GameName, m_AutoHostOwner, m_AutoHostOwner, m_AutoHostServer, false ); + + if( m_CurrentGame ) + { + m_CurrentGame->SetAutoStartPlayers( m_AutoHostAutoStartPlayers ); + + if( m_AutoHostMatchMaking ) + { + if( !m_Map->GetMapMatchMakingCategory( ).empty( ) ) + { + if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) ) + CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory [" + m_Map->GetMapMatchMakingCategory( ) + "] found but matchmaking can only be used with fixed player settings, matchmaking disabled" ); + else + { + CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory [" + m_Map->GetMapMatchMakingCategory( ) + "] found, matchmaking enabled" ); + + m_CurrentGame->SetMatchMaking( true ); + m_CurrentGame->SetMinimumScore( m_AutoHostMinimumScore ); + m_CurrentGame->SetMaximumScore( m_AutoHostMaximumScore ); + } + } + else + CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory not found, matchmaking disabled" ); + } + } + } + else + { + CONSOLE_Print( "[GHOST] stopped auto hosting, next game name [" + GameName + "] is too long (the maximum is 31 characters)" ); + m_AutoHostGameName.clear( ); + m_AutoHostOwner.clear( ); + m_AutoHostServer.clear( ); + m_AutoHostMaximumGames = 0; + m_AutoHostAutoStartPlayers = 0; + m_AutoHostMatchMaking = false; + m_AutoHostMinimumScore = 0.0; + m_AutoHostMaximumScore = 0.0; + } + } + else + { + CONSOLE_Print( "[GHOST] stopped auto hosting, map config file [" + m_AutoHostMap->GetCFGFile( ) + "] is invalid" ); + m_AutoHostGameName.clear( ); + m_AutoHostOwner.clear( ); + m_AutoHostServer.clear( ); + m_AutoHostMaximumGames = 0; + m_AutoHostAutoStartPlayers = 0; + m_AutoHostMatchMaking = false; + m_AutoHostMinimumScore = 0.0; + m_AutoHostMaximumScore = 0.0; + } + } + + m_LastAutoHostTime = GetTime( ); + } + + return m_Exiting || AdminExit || BNETExit; +} + +void CGHost :: EventBNETConnecting( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->ConnectingToBNET( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->SendAllChat( m_Language->ConnectingToBNET( bnet->GetServer( ) ) ); +} + +void CGHost :: EventBNETConnected( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->ConnectedToBNET( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->SendAllChat( m_Language->ConnectedToBNET( bnet->GetServer( ) ) ); +} + +void CGHost :: EventBNETDisconnected( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->DisconnectedFromBNET( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->SendAllChat( m_Language->DisconnectedFromBNET( bnet->GetServer( ) ) ); +} + +void CGHost :: EventBNETLoggedIn( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->LoggedInToBNET( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->SendAllChat( m_Language->LoggedInToBNET( bnet->GetServer( ) ) ); +} + +void CGHost :: EventBNETGameRefreshed( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->BNETGameHostingSucceeded( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->EventGameRefreshed( bnet->GetServer( ) ); +} + +void CGHost :: EventBNETGameRefreshFailed( CBNET *bnet ) +{ + if( m_CurrentGame ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + (*i)->QueueChatCommand( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) ); + + if( (*i)->GetServer( ) == m_CurrentGame->GetCreatorServer( ) ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ), m_CurrentGame->GetCreatorName( ), true ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->BNETGameHostingFailed( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) ); + + m_CurrentGame->SendAllChat( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) ); + + // we take the easy route and simply close the lobby if a refresh fails + // it's possible at least one refresh succeeded and therefore the game is still joinable on at least one battle.net (plus on the local network) but we don't keep track of that + // we only close the game if it has no players since we support game rehosting (via !priv and !pub in the lobby) + + if( m_CurrentGame->GetNumHumanPlayers( ) == 0 ) + m_CurrentGame->SetExiting( true ); + + m_CurrentGame->SetRefreshError( true ); + } +} + +void CGHost :: EventBNETConnectTimedOut( CBNET *bnet ) +{ + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->ConnectingToBNETTimedOut( bnet->GetServer( ) ) ); + + if( m_CurrentGame ) + m_CurrentGame->SendAllChat( m_Language->ConnectingToBNETTimedOut( bnet->GetServer( ) ) ); +} + +void CGHost :: EventBNETWhisper( CBNET *bnet, string user, string message ) +{ + if( m_AdminGame ) + { + m_AdminGame->SendAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + if( m_CurrentGame ) + m_CurrentGame->SendLocalAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + (*i)->SendLocalAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + } +} + +void CGHost :: EventBNETChat( CBNET *bnet, string user, string message ) +{ + if( m_AdminGame ) + { + m_AdminGame->SendAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + if( m_CurrentGame ) + m_CurrentGame->SendLocalAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + (*i)->SendLocalAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + } +} + +void CGHost :: EventBNETEmote( CBNET *bnet, string user, string message ) +{ + if( m_AdminGame ) + { + m_AdminGame->SendAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + if( m_CurrentGame ) + m_CurrentGame->SendLocalAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + + for( vector :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ ) + (*i)->SendLocalAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message ); + } +} + +void CGHost :: EventGameDeleted( CBaseGame *game ) +{ + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + (*i)->QueueChatCommand( m_Language->GameIsOver( game->GetDescription( ) ) ); + + if( (*i)->GetServer( ) == game->GetCreatorServer( ) ) + (*i)->QueueChatCommand( m_Language->GameIsOver( game->GetDescription( ) ), game->GetCreatorName( ), true ); + } +} + +void CGHost :: ReloadConfigs( ) +{ + CConfig CFG; + CFG.Read( "default.cfg" ); + CFG.Read( gCFGFile ); + SetConfigs( &CFG ); +} + +void CGHost :: SetConfigs( CConfig *CFG ) +{ + // this doesn't set EVERY config value since that would potentially require reconfiguring the battle.net connections + // it just set the easily reloadable values + + m_LanguageFile = CFG->GetString( "bot_language", "language.cfg" ); + delete m_Language; + m_Language = new CLanguage( m_LanguageFile ); + m_Warcraft3Path = UTIL_AddPathSeperator( CFG->GetString( "bot_war3path", "C:\\Program Files\\Warcraft III\\" ) ); + m_BindAddress = CFG->GetString( "bot_bindaddress", string( ) ); + m_ReconnectWaitTime = CFG->GetInt( "bot_reconnectwaittime", 3 ); + m_MaxGames = CFG->GetInt( "bot_maxgames", 5 ); + string BotCommandTrigger = CFG->GetString( "bot_commandtrigger", "!" ); + + if( BotCommandTrigger.empty( ) ) + BotCommandTrigger = "!"; + + m_CommandTrigger = BotCommandTrigger[0]; + m_MapCFGPath = UTIL_AddPathSeperator( CFG->GetString( "bot_mapcfgpath", string( ) ) ); + m_SaveGamePath = UTIL_AddPathSeperator( CFG->GetString( "bot_savegamepath", string( ) ) ); + m_MapPath = UTIL_AddPathSeperator( CFG->GetString( "bot_mappath", string( ) ) ); + m_SaveReplays = CFG->GetInt( "bot_savereplays", 0 ) == 0 ? false : true; + m_ReplayPath = UTIL_AddPathSeperator( CFG->GetString( "bot_replaypath", string( ) ) ); + m_VirtualHostName = CFG->GetString( "bot_virtualhostname", "|cFF4080C0GHost" ); + m_HideIPAddresses = CFG->GetInt( "bot_hideipaddresses", 0 ) == 0 ? false : true; + m_CheckMultipleIPUsage = CFG->GetInt( "bot_checkmultipleipusage", 1 ) == 0 ? false : true; + + if( m_VirtualHostName.size( ) > 15 ) + { + m_VirtualHostName = "|cFF4080C0GHost"; + CONSOLE_Print( "[GHOST] warning - bot_virtualhostname is longer than 15 characters, using default virtual host name" ); + } + + m_SpoofChecks = CFG->GetInt( "bot_spoofchecks", 2 ); + m_RequireSpoofChecks = CFG->GetInt( "bot_requirespoofchecks", 0 ) == 0 ? false : true; + m_ReserveAdmins = CFG->GetInt( "bot_reserveadmins", 1 ) == 0 ? false : true; + m_RefreshMessages = CFG->GetInt( "bot_refreshmessages", 0 ) == 0 ? false : true; + m_AutoLock = CFG->GetInt( "bot_autolock", 0 ) == 0 ? false : true; + m_AutoSave = CFG->GetInt( "bot_autosave", 0 ) == 0 ? false : true; + m_AllowDownloads = CFG->GetInt( "bot_allowdownloads", 0 ); + m_PingDuringDownloads = CFG->GetInt( "bot_pingduringdownloads", 0 ) == 0 ? false : true; + m_MaxDownloaders = CFG->GetInt( "bot_maxdownloaders", 3 ); + m_MaxDownloadSpeed = CFG->GetInt( "bot_maxdownloadspeed", 100 ); + m_LCPings = CFG->GetInt( "bot_lcpings", 1 ) == 0 ? false : true; + m_AutoKickPing = CFG->GetInt( "bot_autokickping", 400 ); + m_BanMethod = CFG->GetInt( "bot_banmethod", 1 ); + m_IPBlackListFile = CFG->GetString( "bot_ipblacklistfile", "ipblacklist.txt" ); + m_LobbyTimeLimit = CFG->GetInt( "bot_lobbytimelimit", 10 ); + m_Latency = CFG->GetInt( "bot_latency", 100 ); + m_SyncLimit = CFG->GetInt( "bot_synclimit", 50 ); + m_VoteKickAllowed = CFG->GetInt( "bot_votekickallowed", 1 ) == 0 ? false : true; + m_VoteKickPercentage = CFG->GetInt( "bot_votekickpercentage", 100 ); + + if( m_VoteKickPercentage > 100 ) + { + m_VoteKickPercentage = 100; + CONSOLE_Print( "[GHOST] warning - bot_votekickpercentage is greater than 100, using 100 instead" ); + } + + m_MOTDFile = CFG->GetString( "bot_motdfile", "motd.txt" ); + m_GameLoadedFile = CFG->GetString( "bot_gameloadedfile", "gameloaded.txt" ); + m_GameOverFile = CFG->GetString( "bot_gameoverfile", "gameover.txt" ); + m_LocalAdminMessages = CFG->GetInt( "bot_localadminmessages", 1 ) == 0 ? false : true; + m_TCPNoDelay = CFG->GetInt( "tcp_nodelay", 0 ) == 0 ? false : true; + m_MatchMakingMethod = CFG->GetInt( "bot_matchmakingmethod", 1 ); +} + +void CGHost :: ExtractScripts( ) +{ + string PatchMPQFileName = m_Warcraft3Path + "War3Patch.mpq"; + HANDLE PatchMPQ; + + if( SFileOpenArchive( PatchMPQFileName.c_str( ), 0, MPQ_OPEN_FORCE_MPQ_V1, &PatchMPQ ) ) + { + CONSOLE_Print( "[GHOST] loading MPQ file [" + PatchMPQFileName + "]" ); + HANDLE SubFile; + + // common.j + + if( SFileOpenFileEx( PatchMPQ, "Scripts\\common.j", 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + CONSOLE_Print( "[GHOST] extracting Scripts\\common.j from MPQ file to [" + m_MapCFGPath + "common.j]" ); + UTIL_FileWrite( m_MapCFGPath + "common.j", (unsigned char *)SubFileData, BytesRead ); + } + else + CONSOLE_Print( "[GHOST] warning - unable to extract Scripts\\common.j from MPQ file" ); + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + else + CONSOLE_Print( "[GHOST] couldn't find Scripts\\common.j in MPQ file" ); + + // blizzard.j + + if( SFileOpenFileEx( PatchMPQ, "Scripts\\blizzard.j", 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + CONSOLE_Print( "[GHOST] extracting Scripts\\blizzard.j from MPQ file to [" + m_MapCFGPath + "blizzard.j]" ); + UTIL_FileWrite( m_MapCFGPath + "blizzard.j", (unsigned char *)SubFileData, BytesRead ); + } + else + CONSOLE_Print( "[GHOST] warning - unable to extract Scripts\\blizzard.j from MPQ file" ); + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + else + CONSOLE_Print( "[GHOST] couldn't find Scripts\\blizzard.j in MPQ file" ); + + SFileCloseArchive( PatchMPQ ); + } + else + CONSOLE_Print( "[GHOST] warning - unable to load MPQ file [" + PatchMPQFileName + "] - error code " + UTIL_ToString( GetLastError( ) ) ); +} + +void CGHost :: LoadIPToCountryData( ) +{ + ifstream in; + in.open( "ip-to-country.csv" ); + + if( in.fail( ) ) + CONSOLE_Print( "[GHOST] warning - unable to read file [ip-to-country.csv], iptocountry data not loaded" ); + else + { + CONSOLE_Print( "[GHOST] started loading [ip-to-country.csv]" ); + + // the begin and commit statements are optimizations + // we're about to insert ~4 MB of data into the database so if we allow the database to treat each insert as a transaction it will take a LONG time + // todotodo: handle begin/commit failures a bit more gracefully + + if( !m_DBLocal->Begin( ) ) + CONSOLE_Print( "[GHOST] warning - failed to begin local database transaction, iptocountry data not loaded" ); + else + { + unsigned char Percent = 0; + string Line; + string IP1; + string IP2; + string Country; + CSVParser parser; + + // get length of file for the progress meter + + in.seekg( 0, ios :: end ); + uint32_t FileLength = in.tellg( ); + in.seekg( 0, ios :: beg ); + + while( !in.eof( ) ) + { + getline( in, Line ); + + if( Line.empty( ) ) + continue; + + parser << Line; + parser >> IP1; + parser >> IP2; + parser >> Country; + m_DBLocal->FromAdd( UTIL_ToUInt32( IP1 ), UTIL_ToUInt32( IP2 ), Country ); + + // it's probably going to take awhile to load the iptocountry data (~10 seconds on my 3.2 GHz P4 when using SQLite3) + // so let's print a progress meter just to keep the user from getting worried + + unsigned char NewPercent = (unsigned char)( (float)in.tellg( ) / FileLength * 100 ); + + if( NewPercent != Percent && NewPercent % 10 == 0 ) + { + Percent = NewPercent; + CONSOLE_Print( "[GHOST] iptocountry data: " + UTIL_ToString( Percent ) + "% loaded" ); + } + } + + if( !m_DBLocal->Commit( ) ) + CONSOLE_Print( "[GHOST] warning - failed to commit local database transaction, iptocountry data not loaded" ); + else + CONSOLE_Print( "[GHOST] finished loading [ip-to-country.csv]" ); + } + + in.close( ); + } +} + +void CGHost :: CreateGame( CMap *map, unsigned char gameState, bool saveGame, string gameName, string ownerName, string creatorName, string creatorServer, bool whisper ) +{ + if( !m_Enabled ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameDisabled( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameDisabled( gameName ) ); + + return; + } + + if( gameName.size( ) > 31 ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameNameTooLong( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameNameTooLong( gameName ) ); + + return; + } + + if( !map->GetValid( ) ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameInvalidMap( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameInvalidMap( gameName ) ); + + return; + } + + if( saveGame ) + { + if( !m_SaveGame->GetValid( ) ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameInvalidSaveGame( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameInvalidSaveGame( gameName ) ); + + return; + } + + string MapPath1 = m_SaveGame->GetMapPath( ); + string MapPath2 = map->GetMapPath( ); + transform( MapPath1.begin( ), MapPath1.end( ), MapPath1.begin( ), (int(*)(int))tolower ); + transform( MapPath2.begin( ), MapPath2.end( ), MapPath2.begin( ), (int(*)(int))tolower ); + + if( MapPath1 != MapPath2 ) + { + CONSOLE_Print( "[GHOST] path mismatch, saved game path is [" + MapPath1 + "] but map path is [" + MapPath2 + "]" ); + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameSaveGameMapMismatch( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameSaveGameMapMismatch( gameName ) ); + + return; + } + + if( m_EnforcePlayers.empty( ) ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameMustEnforceFirst( gameName ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameMustEnforceFirst( gameName ) ); + + return; + } + } + + if( m_CurrentGame ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameAnotherGameInLobby( gameName, m_CurrentGame->GetDescription( ) ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameAnotherGameInLobby( gameName, m_CurrentGame->GetDescription( ) ) ); + + return; + } + + if( m_Games.size( ) >= m_MaxGames ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetServer( ) == creatorServer ) + (*i)->QueueChatCommand( m_Language->UnableToCreateGameMaxGamesReached( gameName, UTIL_ToString( m_MaxGames ) ), creatorName, whisper ); + } + + if( m_AdminGame ) + m_AdminGame->SendAllChat( m_Language->UnableToCreateGameMaxGamesReached( gameName, UTIL_ToString( m_MaxGames ) ) ); + + return; + } + + CONSOLE_Print( "[GHOST] creating game [" + gameName + "]" ); + + if( saveGame ) + m_CurrentGame = new CGame( this, map, m_SaveGame, m_HostPort, gameState, gameName, ownerName, creatorName, creatorServer ); + else + m_CurrentGame = new CGame( this, map, NULL, m_HostPort, gameState, gameName, ownerName, creatorName, creatorServer ); + + // todotodo: check if listening failed and report the error to the user + + if( m_SaveGame ) + { + m_CurrentGame->SetEnforcePlayers( m_EnforcePlayers ); + m_EnforcePlayers.clear( ); + } + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( whisper && (*i)->GetServer( ) == creatorServer ) + { + // note that we send this whisper only on the creator server + + if( gameState == GAME_PRIVATE ) + (*i)->QueueChatCommand( m_Language->CreatingPrivateGame( gameName, ownerName ), creatorName, whisper ); + else if( gameState == GAME_PUBLIC ) + (*i)->QueueChatCommand( m_Language->CreatingPublicGame( gameName, ownerName ), creatorName, whisper ); + } + else + { + // note that we send this chat message on all other bnet servers + + if( gameState == GAME_PRIVATE ) + (*i)->QueueChatCommand( m_Language->CreatingPrivateGame( gameName, ownerName ) ); + else if( gameState == GAME_PUBLIC ) + (*i)->QueueChatCommand( m_Language->CreatingPublicGame( gameName, ownerName ) ); + } + + if( saveGame ) + (*i)->QueueGameCreate( gameState, gameName, string( ), map, m_SaveGame, m_CurrentGame->GetHostCounter( ) ); + else + (*i)->QueueGameCreate( gameState, gameName, string( ), map, NULL, m_CurrentGame->GetHostCounter( ) ); + } + + if( m_AdminGame ) + { + if( gameState == GAME_PRIVATE ) + m_AdminGame->SendAllChat( m_Language->CreatingPrivateGame( gameName, ownerName ) ); + else if( gameState == GAME_PUBLIC ) + m_AdminGame->SendAllChat( m_Language->CreatingPublicGame( gameName, ownerName ) ); + } + + // if we're creating a private game we don't need to send any game refresh messages so we can rejoin the chat immediately + // unfortunately this doesn't work on PVPGN servers because they consider an enterchat message to be a gameuncreate message when in a game + // so don't rejoin the chat if we're using PVPGN + + if( gameState == GAME_PRIVATE ) + { + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetPasswordHashType( ) != "pvpgn" ) + (*i)->QueueEnterChat( ); + } + } + + // hold friends and/or clan members + + for( vector :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ ) + { + if( (*i)->GetHoldFriends( ) ) + (*i)->HoldFriends( m_CurrentGame ); + + if( (*i)->GetHoldClan( ) ) + (*i)->HoldClan( m_CurrentGame ); + } +} diff --git a/ghost-legacy/ghost.h b/ghost-legacy/ghost.h new file mode 100644 index 0000000..7e84ce3 --- /dev/null +++ b/ghost-legacy/ghost.h @@ -0,0 +1,167 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GHOST_H +#define GHOST_H + +#include "includes.h" + +// +// CGHost +// + +class CUDPSocket; +class CTCPServer; +class CTCPSocket; +class CGPSProtocol; +class CCRC32; +class CSHA1; +class CBNET; +class CBaseGame; +class CAdminGame; +class CGHostDB; +class CBaseCallable; +class CLanguage; +class CMap; +class CSaveGame; +class CConfig; + +class CGHost +{ +public: + CUDPSocket *m_UDPSocket; // a UDP socket for sending broadcasts and other junk (used with !sendlan) + CTCPServer *m_ReconnectSocket; // listening socket for GProxy++ reliable reconnects + vector m_ReconnectSockets;// vector of sockets attempting to reconnect (connected but not identified yet) + CGPSProtocol *m_GPSProtocol; + CCRC32 *m_CRC; // for calculating CRC's + CSHA1 *m_SHA; // for calculating SHA1's + vector m_BNETs; // all our battle.net connections (there can be more than one) + CBaseGame *m_CurrentGame; // this game is still in the lobby state + CAdminGame *m_AdminGame; // this "fake game" allows an admin who knows the password to control the bot from the local network + vector m_Games; // these games are in progress + CGHostDB *m_DB; // database + CGHostDB *m_DBLocal; // local database (for temporary data) + vector m_Callables; // vector of orphaned callables waiting to die + vector m_LocalAddresses; // vector of local IP addresses + CLanguage *m_Language; // language + CMap *m_Map; // the currently loaded map + CMap *m_AdminMap; // the map to use in the admin game + CMap *m_AutoHostMap; // the map to use when autohosting + CSaveGame *m_SaveGame; // the save game to use + vector m_EnforcePlayers; // vector of pids to force players to use in the next game (used with saved games) + bool m_Exiting; // set to true to force ghost to shutdown next update (used by SignalCatcher) + bool m_ExitingNice; // set to true to force ghost to disconnect from all battle.net connections and wait for all games to finish before shutting down + bool m_Enabled; // set to false to prevent new games from being created + string m_Version; // GHost++ version string + uint32_t m_HostCounter; // the current host counter (a unique number to identify a game, incremented each time a game is created) + string m_AutoHostGameName; // the base game name to auto host with + string m_AutoHostOwner; + string m_AutoHostServer; + uint32_t m_AutoHostMaximumGames; // maximum number of games to auto host + uint32_t m_AutoHostAutoStartPlayers; // when using auto hosting auto start the game when this many players have joined + uint32_t m_LastAutoHostTime; // GetTime when the last auto host was attempted + bool m_AutoHostMatchMaking; + double m_AutoHostMinimumScore; + double m_AutoHostMaximumScore; + bool m_AllGamesFinished; // if all games finished (used when exiting nicely) + uint32_t m_AllGamesFinishedTime; // GetTime when all games finished (used when exiting nicely) + string m_LanguageFile; // config value: language file + string m_Warcraft3Path; // config value: Warcraft 3 path + bool m_TFT; // config value: TFT enabled or not + string m_BindAddress; // config value: the address to host games on + uint16_t m_HostPort; // config value: the port to host games on + bool m_Reconnect; // config value: GProxy++ reliable reconnects enabled or not + uint16_t m_ReconnectPort; // config value: the port to listen for GProxy++ reliable reconnects on + uint32_t m_ReconnectWaitTime; // config value: the maximum number of minutes to wait for a GProxy++ reliable reconnect + uint32_t m_MaxGames; // config value: maximum number of games in progress + char m_CommandTrigger; // config value: the command trigger inside games + string m_MapCFGPath; // config value: map cfg path + string m_SaveGamePath; // config value: savegame path + string m_MapPath; // config value: map path + bool m_SaveReplays; // config value: save replays + string m_ReplayPath; // config value: replay path + string m_VirtualHostName; // config value: virtual host name + bool m_HideIPAddresses; // config value: hide IP addresses from players + bool m_CheckMultipleIPUsage; // config value: check for multiple IP address usage + uint32_t m_SpoofChecks; // config value: do automatic spoof checks or not + bool m_RequireSpoofChecks; // config value: require spoof checks or not + bool m_ReserveAdmins; // config value: consider admins to be reserved players or not + bool m_RefreshMessages; // config value: display refresh messages or not (by default) + bool m_AutoLock; // config value: auto lock games when the owner is present + bool m_AutoSave; // config value: auto save before someone disconnects + uint32_t m_AllowDownloads; // config value: allow map downloads or not + bool m_PingDuringDownloads; // config value: ping during map downloads or not + uint32_t m_MaxDownloaders; // config value: maximum number of map downloaders at the same time + uint32_t m_MaxDownloadSpeed; // config value: maximum total map download speed in KB/sec + bool m_LCPings; // config value: use LC style pings (divide actual pings by two) + uint32_t m_AutoKickPing; // config value: auto kick players with ping higher than this + uint32_t m_BanMethod; // config value: ban method (ban by name/ip/both) + string m_IPBlackListFile; // config value: IP blacklist file (ipblacklist.txt) + uint32_t m_LobbyTimeLimit; // config value: auto close the game lobby after this many minutes without any reserved players + uint32_t m_Latency; // config value: the latency (by default) + uint32_t m_SyncLimit; // config value: the maximum number of packets a player can fall out of sync before starting the lag screen (by default) + bool m_VoteKickAllowed; // config value: if votekicks are allowed or not + uint32_t m_VoteKickPercentage; // config value: percentage of players required to vote yes for a votekick to pass + string m_DefaultMap; // config value: default map (map.cfg) + string m_MOTDFile; // config value: motd.txt + string m_GameLoadedFile; // config value: gameloaded.txt + string m_GameOverFile; // config value: gameover.txt + bool m_LocalAdminMessages; // config value: send local admin messages or not + bool m_AdminGameCreate; // config value: create the admin game or not + uint16_t m_AdminGamePort; // config value: the port to host the admin game on + string m_AdminGamePassword; // config value: the admin game password + string m_AdminGameMap; // config value: the admin game map config to use + unsigned char m_LANWar3Version; // config value: LAN warcraft 3 version + uint32_t m_ReplayWar3Version; // config value: replay warcraft 3 version (for saving replays) + uint32_t m_ReplayBuildNumber; // config value: replay build number (for saving replays) + bool m_TCPNoDelay; // config value: use Nagle's algorithm or not + uint32_t m_MatchMakingMethod; // config value: the matchmaking method + + CGHost( CConfig *CFG ); + ~CGHost( ); + + // processing functions + + bool Update( long usecBlock ); + + // events + + void EventBNETConnecting( CBNET *bnet ); + void EventBNETConnected( CBNET *bnet ); + void EventBNETDisconnected( CBNET *bnet ); + void EventBNETLoggedIn( CBNET *bnet ); + void EventBNETGameRefreshed( CBNET *bnet ); + void EventBNETGameRefreshFailed( CBNET *bnet ); + void EventBNETConnectTimedOut( CBNET *bnet ); + void EventBNETWhisper( CBNET *bnet, string user, string message ); + void EventBNETChat( CBNET *bnet, string user, string message ); + void EventBNETEmote( CBNET *bnet, string user, string message ); + void EventGameDeleted( CBaseGame *game ); + + // other functions + + void ReloadConfigs( ); + void SetConfigs( CConfig *CFG ); + void ExtractScripts( ); + void LoadIPToCountryData( ); + void CreateGame( CMap *map, unsigned char gameState, bool saveGame, string gameName, string ownerName, string creatorName, string creatorServer, bool whisper ); +}; + +#endif diff --git a/ghost-legacy/ghostdb.cpp b/ghost-legacy/ghostdb.cpp new file mode 100644 index 0000000..43ec7df --- /dev/null +++ b/ghost-legacy/ghostdb.cpp @@ -0,0 +1,621 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "ghostdb.h" + +// +// CGHostDB +// + +CGHostDB :: CGHostDB( CConfig *CFG ) +{ + m_HasError = false; +} + +CGHostDB :: ~CGHostDB( ) +{ + +} + +void CGHostDB :: RecoverCallable( CBaseCallable *callable ) +{ + +} + +bool CGHostDB :: Begin( ) +{ + return true; +} + +bool CGHostDB :: Commit( ) +{ + return true; +} + +uint32_t CGHostDB :: AdminCount( string server ) +{ + return 0; +} + +bool CGHostDB :: AdminCheck( string server, string user ) +{ + return false; +} + +bool CGHostDB :: AdminAdd( string server, string user ) +{ + return false; +} + +bool CGHostDB :: AdminRemove( string server, string user ) +{ + return false; +} + +vector CGHostDB :: AdminList( string server ) +{ + return vector( ); +} + +uint32_t CGHostDB :: BanCount( string server ) +{ + return 0; +} + +CDBBan *CGHostDB :: BanCheck( string server, string user, string ip ) +{ + return NULL; +} + +bool CGHostDB :: BanAdd( string server, string user, string ip, string gamename, string admin, string reason ) +{ + return false; +} + +bool CGHostDB :: BanRemove( string server, string user ) +{ + return false; +} + +bool CGHostDB :: BanRemove( string user ) +{ + return false; +} + +vector CGHostDB :: BanList( string server ) +{ + return vector( ); +} + +uint32_t CGHostDB :: GameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + return 0; +} + +uint32_t CGHostDB :: GamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + return 0; +} + +uint32_t CGHostDB :: GamePlayerCount( string name ) +{ + return 0; +} + +CDBGamePlayerSummary *CGHostDB :: GamePlayerSummaryCheck( string name ) +{ + return NULL; +} + +uint32_t CGHostDB :: DotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + return 0; +} + +uint32_t CGHostDB :: DotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + return 0; +} + +uint32_t CGHostDB :: DotAPlayerCount( string name ) +{ + return 0; +} + +CDBDotAPlayerSummary *CGHostDB :: DotAPlayerSummaryCheck( string name ) +{ + return NULL; +} + +string CGHostDB :: FromCheck( uint32_t ip ) +{ + return "??"; +} + +bool CGHostDB :: FromAdd( uint32_t ip1, uint32_t ip2, string country ) +{ + return false; +} + +bool CGHostDB :: DownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + return false; +} + +uint32_t CGHostDB :: W3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + return 0; +} + +bool CGHostDB :: W3MMDVarAdd( uint32_t gameid, map var_ints ) +{ + return false; +} + +bool CGHostDB :: W3MMDVarAdd( uint32_t gameid, map var_reals ) +{ + return false; +} + +bool CGHostDB :: W3MMDVarAdd( uint32_t gameid, map var_strings ) +{ + return false; +} + +void CGHostDB :: CreateThread( CBaseCallable *callable ) +{ + callable->SetReady( true ); +} + +CCallableAdminCount *CGHostDB :: ThreadedAdminCount( string server ) +{ + return NULL; +} + +CCallableAdminCheck *CGHostDB :: ThreadedAdminCheck( string server, string user ) +{ + return NULL; +} + +CCallableAdminAdd *CGHostDB :: ThreadedAdminAdd( string server, string user ) +{ + return NULL; +} + +CCallableAdminRemove *CGHostDB :: ThreadedAdminRemove( string server, string user ) +{ + return NULL; +} + +CCallableAdminList *CGHostDB :: ThreadedAdminList( string server ) +{ + return NULL; +} + +CCallableBanCount *CGHostDB :: ThreadedBanCount( string server ) +{ + return NULL; +} + +CCallableBanCheck *CGHostDB :: ThreadedBanCheck( string server, string user, string ip ) +{ + return NULL; +} + +CCallableBanAdd *CGHostDB :: ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ) +{ + return NULL; +} + +CCallableBanRemove *CGHostDB :: ThreadedBanRemove( string server, string user ) +{ + return NULL; +} + +CCallableBanRemove *CGHostDB :: ThreadedBanRemove( string user ) +{ + return NULL; +} + +CCallableBanList *CGHostDB :: ThreadedBanList( string server ) +{ + return NULL; +} + +CCallableGameAdd *CGHostDB :: ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + return NULL; +} + +CCallableGamePlayerAdd *CGHostDB :: ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + return NULL; +} + +CCallableGamePlayerSummaryCheck *CGHostDB :: ThreadedGamePlayerSummaryCheck( string name ) +{ + return NULL; +} + +CCallableDotAGameAdd *CGHostDB :: ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + return NULL; +} + +CCallableDotAPlayerAdd *CGHostDB :: ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + return NULL; +} + +CCallableDotAPlayerSummaryCheck *CGHostDB :: ThreadedDotAPlayerSummaryCheck( string name ) +{ + return NULL; +} + +CCallableDownloadAdd *CGHostDB :: ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + return NULL; +} + +CCallableScoreCheck *CGHostDB :: ThreadedScoreCheck( string category, string name, string server ) +{ + return NULL; +} + +CCallableW3MMDPlayerAdd *CGHostDB :: ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + return NULL; +} + +CCallableW3MMDVarAdd *CGHostDB :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ) +{ + return NULL; +} + +CCallableW3MMDVarAdd *CGHostDB :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ) +{ + return NULL; +} + +CCallableW3MMDVarAdd *CGHostDB :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ) +{ + return NULL; +} + +// +// Callables +// + +void CBaseCallable :: Init( ) +{ + m_StartTicks = GetTicks( ); +} + +void CBaseCallable :: Close( ) +{ + m_EndTicks = GetTicks( ); + m_Ready = true; +} + +CCallableAdminCount :: ~CCallableAdminCount( ) +{ + +} + +CCallableAdminCheck :: ~CCallableAdminCheck( ) +{ + +} + +CCallableAdminAdd :: ~CCallableAdminAdd( ) +{ + +} + +CCallableAdminRemove :: ~CCallableAdminRemove( ) +{ + +} + +CCallableAdminList :: ~CCallableAdminList( ) +{ + +} + +CCallableBanCount :: ~CCallableBanCount( ) +{ + +} + +CCallableBanCheck :: ~CCallableBanCheck( ) +{ + delete m_Result; +} + +CCallableBanAdd :: ~CCallableBanAdd( ) +{ + +} + +CCallableBanRemove :: ~CCallableBanRemove( ) +{ + +} + +CCallableBanList :: ~CCallableBanList( ) +{ + // don't delete anything in m_Result here, it's the caller's responsibility +} + +CCallableGameAdd :: ~CCallableGameAdd( ) +{ + +} + +CCallableGamePlayerAdd :: ~CCallableGamePlayerAdd( ) +{ + +} + +CCallableGamePlayerSummaryCheck :: ~CCallableGamePlayerSummaryCheck( ) +{ + delete m_Result; +} + +CCallableDotAGameAdd :: ~CCallableDotAGameAdd( ) +{ + +} + +CCallableDotAPlayerAdd :: ~CCallableDotAPlayerAdd( ) +{ + +} + +CCallableDotAPlayerSummaryCheck :: ~CCallableDotAPlayerSummaryCheck( ) +{ + delete m_Result; +} + +CCallableDownloadAdd :: ~CCallableDownloadAdd( ) +{ + +} + +CCallableScoreCheck :: ~CCallableScoreCheck( ) +{ + +} + +CCallableW3MMDPlayerAdd :: ~CCallableW3MMDPlayerAdd( ) +{ + +} + +CCallableW3MMDVarAdd :: ~CCallableW3MMDVarAdd( ) +{ + +} + +// +// CDBBan +// + +CDBBan :: CDBBan( string nServer, string nName, string nIP, string nDate, string nGameName, string nAdmin, string nReason ) +{ + m_Server = nServer; + m_Name = nName; + m_IP = nIP; + m_Date = nDate; + m_GameName = nGameName; + m_Admin = nAdmin; + m_Reason = nReason; +} + +CDBBan :: ~CDBBan( ) +{ + +} + +// +// CDBGame +// + +CDBGame :: CDBGame( uint32_t nID, string nServer, string nMap, string nDateTime, string nGameName, string nOwnerName, uint32_t nDuration ) +{ + m_ID = nID; + m_Server = nServer; + m_Map = nMap; + m_DateTime = nDateTime; + m_GameName = nGameName; + m_OwnerName = nOwnerName; + m_Duration = nDuration; +} + +CDBGame :: ~CDBGame( ) +{ + +} + +// +// CDBGamePlayer +// + +CDBGamePlayer :: CDBGamePlayer( uint32_t nID, uint32_t nGameID, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nReserved, uint32_t nLoadingTime, uint32_t nLeft, string nLeftReason, uint32_t nTeam, uint32_t nColour ) +{ + m_ID = nID; + m_GameID = nGameID; + m_Name = nName; + m_IP = nIP; + m_Spoofed = nSpoofed; + m_SpoofedRealm = nSpoofedRealm; + m_Reserved = nReserved; + m_LoadingTime = nLoadingTime; + m_Left = nLeft; + m_LeftReason = nLeftReason; + m_Team = nTeam; + m_Colour = nColour; +} + +CDBGamePlayer :: ~CDBGamePlayer( ) +{ + +} + +// +// CDBGamePlayerSummary +// + +CDBGamePlayerSummary :: CDBGamePlayerSummary( string nServer, string nName, string nFirstGameDateTime, string nLastGameDateTime, uint32_t nTotalGames, uint32_t nMinLoadingTime, uint32_t nAvgLoadingTime, uint32_t nMaxLoadingTime, uint32_t nMinLeftPercent, uint32_t nAvgLeftPercent, uint32_t nMaxLeftPercent, uint32_t nMinDuration, uint32_t nAvgDuration, uint32_t nMaxDuration ) +{ + m_Server = nServer; + m_Name = nName; + m_FirstGameDateTime = nFirstGameDateTime; + m_LastGameDateTime = nLastGameDateTime; + m_TotalGames = nTotalGames; + m_MinLoadingTime = nMinLoadingTime; + m_AvgLoadingTime = nAvgLoadingTime; + m_MaxLoadingTime = nMaxLoadingTime; + m_MinLeftPercent = nMinLeftPercent; + m_AvgLeftPercent = nAvgLeftPercent; + m_MaxLeftPercent = nMaxLeftPercent; + m_MinDuration = nMinDuration; + m_AvgDuration = nAvgDuration; + m_MaxDuration = nMaxDuration; +} + +CDBGamePlayerSummary :: ~CDBGamePlayerSummary( ) +{ + +} + +// +// CDBDotAGame +// + +CDBDotAGame :: CDBDotAGame( uint32_t nID, uint32_t nGameID, uint32_t nWinner, uint32_t nMin, uint32_t nSec ) +{ + m_ID = nID; + m_GameID = nGameID; + m_Winner = nWinner; + m_Min = nMin; + m_Sec = nSec; +} + +CDBDotAGame :: ~CDBDotAGame( ) +{ + +} + +// +// CDBDotAPlayer +// + +CDBDotAPlayer :: CDBDotAPlayer( ) +{ + m_ID = 0; + m_GameID = 0; + m_Colour = 0; + m_Kills = 0; + m_Deaths = 0; + m_CreepKills = 0; + m_CreepDenies = 0; + m_Assists = 0; + m_Gold = 0; + m_NeutralKills = 0; + m_NewColour = 0; + m_TowerKills = 0; + m_RaxKills = 0; + m_CourierKills = 0; +} + +CDBDotAPlayer :: CDBDotAPlayer( uint32_t nID, uint32_t nGameID, uint32_t nColour, uint32_t nKills, uint32_t nDeaths, uint32_t nCreepKills, uint32_t nCreepDenies, uint32_t nAssists, uint32_t nGold, uint32_t nNeutralKills, string nItem1, string nItem2, string nItem3, string nItem4, string nItem5, string nItem6, string nHero, uint32_t nNewColour, uint32_t nTowerKills, uint32_t nRaxKills, uint32_t nCourierKills ) +{ + m_ID = nID; + m_GameID = nGameID; + m_Colour = nColour; + m_Kills = nKills; + m_Deaths = nDeaths; + m_CreepKills = nCreepKills; + m_CreepDenies = nCreepDenies; + m_Assists = nAssists; + m_Gold = nGold; + m_NeutralKills = nNeutralKills; + m_Items[0] = nItem1; + m_Items[1] = nItem2; + m_Items[2] = nItem3; + m_Items[3] = nItem4; + m_Items[4] = nItem5; + m_Items[5] = nItem6; + m_Hero = nHero; + m_NewColour = nNewColour; + m_TowerKills = nTowerKills; + m_RaxKills = nRaxKills; + m_CourierKills = nCourierKills; +} + +CDBDotAPlayer :: ~CDBDotAPlayer( ) +{ + +} + +string CDBDotAPlayer :: GetItem( unsigned int i ) +{ + if( i < 6 ) + return m_Items[i]; + + return string( ); +} + +void CDBDotAPlayer :: SetItem( unsigned int i, string item ) +{ + if( i < 6 ) + m_Items[i] = item; +} + +// +// CDBDotAPlayerSummary +// + +CDBDotAPlayerSummary :: CDBDotAPlayerSummary( string nServer, string nName, uint32_t nTotalGames, uint32_t nTotalWins, uint32_t nTotalLosses, uint32_t nTotalKills, uint32_t nTotalDeaths, uint32_t nTotalCreepKills, uint32_t nTotalCreepDenies, uint32_t nTotalAssists, uint32_t nTotalNeutralKills, uint32_t nTotalTowerKills, uint32_t nTotalRaxKills, uint32_t nTotalCourierKills ) +{ + m_Server = nServer; + m_Name = nName; + m_TotalGames = nTotalGames; + m_TotalWins = nTotalWins; + m_TotalLosses = nTotalLosses; + m_TotalKills = nTotalKills; + m_TotalDeaths = nTotalDeaths; + m_TotalCreepKills = nTotalCreepKills; + m_TotalCreepDenies = nTotalCreepDenies; + m_TotalAssists = nTotalAssists; + m_TotalNeutralKills = nTotalNeutralKills; + m_TotalTowerKills = nTotalTowerKills; + m_TotalRaxKills = nTotalRaxKills; + m_TotalCourierKills = nTotalCourierKills; +} + +CDBDotAPlayerSummary :: ~CDBDotAPlayerSummary( ) +{ + +} diff --git a/ghost-legacy/ghostdb.h b/ghost-legacy/ghostdb.h new file mode 100644 index 0000000..48934f9 --- /dev/null +++ b/ghost-legacy/ghostdb.h @@ -0,0 +1,837 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GHOSTDB_H +#define GHOSTDB_H + +// +// CGHostDB +// + +class CBaseCallable; +class CCallableAdminCount; +class CCallableAdminCheck; +class CCallableAdminAdd; +class CCallableAdminRemove; +class CCallableAdminList; +class CCallableBanCount; +class CCallableBanCheck; +class CCallableBanAdd; +class CCallableBanRemove; +class CCallableBanList; +class CCallableGameAdd; +class CCallableGamePlayerAdd; +class CCallableGamePlayerSummaryCheck; +class CCallableDotAGameAdd; +class CCallableDotAPlayerAdd; +class CCallableDotAPlayerSummaryCheck; +class CCallableDownloadAdd; +class CCallableScoreCheck; +class CCallableW3MMDPlayerAdd; +class CCallableW3MMDVarAdd; +class CDBBan; +class CDBGame; +class CDBGamePlayer; +class CDBGamePlayerSummary; +class CDBDotAPlayerSummary; + +typedef pair VarP; + +class CGHostDB +{ +protected: + bool m_HasError; + string m_Error; + +public: + CGHostDB( CConfig *CFG ); + virtual ~CGHostDB( ); + + bool HasError( ) { return m_HasError; } + string GetError( ) { return m_Error; } + virtual string GetStatus( ) { return "DB STATUS --- OK"; } + + virtual void RecoverCallable( CBaseCallable *callable ); + + // standard (non-threaded) database functions + + virtual bool Begin( ); + virtual bool Commit( ); + virtual uint32_t AdminCount( string server ); + virtual bool AdminCheck( string server, string user ); + virtual bool AdminAdd( string server, string user ); + virtual bool AdminRemove( string server, string user ); + virtual vector AdminList( string server ); + virtual uint32_t BanCount( string server ); + virtual CDBBan *BanCheck( string server, string user, string ip ); + virtual bool BanAdd( string server, string user, string ip, string gamename, string admin, string reason ); + virtual bool BanRemove( string server, string user ); + virtual bool BanRemove( string user ); + virtual vector BanList( string server ); + virtual uint32_t GameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); + virtual uint32_t GamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); + virtual uint32_t GamePlayerCount( string name ); + virtual CDBGamePlayerSummary *GamePlayerSummaryCheck( string name ); + virtual uint32_t DotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); + virtual uint32_t DotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); + virtual uint32_t DotAPlayerCount( string name ); + virtual CDBDotAPlayerSummary *DotAPlayerSummaryCheck( string name ); + virtual string FromCheck( uint32_t ip ); + virtual bool FromAdd( uint32_t ip1, uint32_t ip2, string country ); + virtual bool DownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); + virtual uint32_t W3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_ints ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_reals ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_strings ); + + // threaded database functions + + virtual void CreateThread( CBaseCallable *callable ); + virtual CCallableAdminCount *ThreadedAdminCount( string server ); + virtual CCallableAdminCheck *ThreadedAdminCheck( string server, string user ); + virtual CCallableAdminAdd *ThreadedAdminAdd( string server, string user ); + virtual CCallableAdminRemove *ThreadedAdminRemove( string server, string user ); + virtual CCallableAdminList *ThreadedAdminList( string server ); + virtual CCallableBanCount *ThreadedBanCount( string server ); + virtual CCallableBanCheck *ThreadedBanCheck( string server, string user, string ip ); + virtual CCallableBanAdd *ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ); + virtual CCallableBanRemove *ThreadedBanRemove( string server, string user ); + virtual CCallableBanRemove *ThreadedBanRemove( string user ); + virtual CCallableBanList *ThreadedBanList( string server ); + virtual CCallableGameAdd *ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); + virtual CCallableGamePlayerAdd *ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); + virtual CCallableGamePlayerSummaryCheck *ThreadedGamePlayerSummaryCheck( string name ); + virtual CCallableDotAGameAdd *ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); + virtual CCallableDotAPlayerAdd *ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); + virtual CCallableDotAPlayerSummaryCheck *ThreadedDotAPlayerSummaryCheck( string name ); + virtual CCallableDownloadAdd *ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); + virtual CCallableScoreCheck *ThreadedScoreCheck( string category, string name, string server ); + virtual CCallableW3MMDPlayerAdd *ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ); +}; + +// +// Callables +// + +// life cycle of a callable: +// - the callable is created in one of the database's ThreadedXXX functions +// - initially the callable is NOT ready (i.e. m_Ready = false) +// - the ThreadedXXX function normally creates a thread to perform some query and (potentially) store some result in the callable +// - at the time of this writing all threads are immediately detached, the code does not join any threads (the callable's "readiness" is used for this purpose instead) +// - when the thread completes it will set m_Ready = true +// - DO NOT DO *ANYTHING* TO THE CALLABLE UNTIL IT'S READY OR YOU WILL CREATE A CONCURRENCY MESS +// - THE ONLY SAFE FUNCTION IN THE CALLABLE IS GetReady +// - when the callable is ready you may access the callable's result which will have been set within the (now terminated) thread + +// example usage: +// - normally you will call a ThreadedXXX function, store the callable in a vector, and periodically check if the callable is ready +// - when the callable is ready you will consume the result then you will pass the callable back to the database via the RecoverCallable function +// - the RecoverCallable function allows the database to recover some of the callable's resources to be reused later (e.g. MySQL connections) +// - note that this will NOT free the callable's memory, you must do that yourself after calling the RecoverCallable function +// - be careful not to leak any callables, it's NOT safe to delete a callable even if you decide that you don't want the result anymore +// - you should deliver any to-be-orphaned callables to the main vector in CGHost so they can be properly deleted when ready even if you don't care about the result anymore +// - e.g. if a player does a stats check immediately before a game is deleted you can't just delete the callable on game deletion unless it's ready + +class CBaseCallable +{ +protected: + string m_Error; + volatile bool m_Ready; + uint32_t m_StartTicks; + uint32_t m_EndTicks; + +public: + CBaseCallable( ) : m_Error( ), m_Ready( false ), m_StartTicks( 0 ), m_EndTicks( 0 ) { } + virtual ~CBaseCallable( ) { } + + virtual void operator( )( ) { } + + virtual void Init( ); + virtual void Close( ); + + virtual string GetError( ) { return m_Error; } + virtual bool GetReady( ) { return m_Ready; } + virtual void SetReady( bool nReady ) { m_Ready = nReady; } + virtual uint32_t GetElapsed( ) { return m_Ready ? m_EndTicks - m_StartTicks : 0; } +}; + +class CCallableAdminCount : virtual public CBaseCallable +{ +protected: + string m_Server; + uint32_t m_Result; + +public: + CCallableAdminCount( string nServer ) : CBaseCallable( ), m_Server( nServer ), m_Result( 0 ) { } + virtual ~CCallableAdminCount( ); + + virtual string GetServer( ) { return m_Server; } + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableAdminCheck : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + bool m_Result; + +public: + CCallableAdminCheck( string nServer, string nUser ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_Result( false ) { } + virtual ~CCallableAdminCheck( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableAdminAdd : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + bool m_Result; + +public: + CCallableAdminAdd( string nServer, string nUser ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_Result( false ) { } + virtual ~CCallableAdminAdd( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableAdminRemove : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + bool m_Result; + +public: + CCallableAdminRemove( string nServer, string nUser ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_Result( false ) { } + virtual ~CCallableAdminRemove( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableAdminList : virtual public CBaseCallable +{ +protected: + string m_Server; + vector m_Result; + +public: + CCallableAdminList( string nServer ) : CBaseCallable( ), m_Server( nServer ) { } + virtual ~CCallableAdminList( ); + + virtual vector GetResult( ) { return m_Result; } + virtual void SetResult( vector nResult ) { m_Result = nResult; } +}; + +class CCallableBanCount : virtual public CBaseCallable +{ +protected: + string m_Server; + uint32_t m_Result; + +public: + CCallableBanCount( string nServer ) : CBaseCallable( ), m_Server( nServer ), m_Result( 0 ) { } + virtual ~CCallableBanCount( ); + + virtual string GetServer( ) { return m_Server; } + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableBanCheck : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + string m_IP; + CDBBan *m_Result; + +public: + CCallableBanCheck( string nServer, string nUser, string nIP ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_IP( nIP ), m_Result( NULL ) { } + virtual ~CCallableBanCheck( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual string GetIP( ) { return m_IP; } + virtual CDBBan *GetResult( ) { return m_Result; } + virtual void SetResult( CDBBan *nResult ) { m_Result = nResult; } +}; + +class CCallableBanAdd : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + string m_IP; + string m_GameName; + string m_Admin; + string m_Reason; + bool m_Result; + +public: + CCallableBanAdd( string nServer, string nUser, string nIP, string nGameName, string nAdmin, string nReason ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_IP( nIP ), m_GameName( nGameName ), m_Admin( nAdmin ), m_Reason( nReason ), m_Result( false ) { } + virtual ~CCallableBanAdd( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual string GetIP( ) { return m_IP; } + virtual string GetGameName( ) { return m_GameName; } + virtual string GetAdmin( ) { return m_Admin; } + virtual string GetReason( ) { return m_Reason; } + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableBanRemove : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_User; + bool m_Result; + +public: + CCallableBanRemove( string nServer, string nUser ) : CBaseCallable( ), m_Server( nServer ), m_User( nUser ), m_Result( false ) { } + virtual ~CCallableBanRemove( ); + + virtual string GetServer( ) { return m_Server; } + virtual string GetUser( ) { return m_User; } + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableBanList : virtual public CBaseCallable +{ +protected: + string m_Server; + vector m_Result; + +public: + CCallableBanList( string nServer ) : CBaseCallable( ), m_Server( nServer ) { } + virtual ~CCallableBanList( ); + + virtual vector GetResult( ) { return m_Result; } + virtual void SetResult( vector nResult ) { m_Result = nResult; } +}; + +class CCallableGameAdd : virtual public CBaseCallable +{ +protected: + string m_Server; + string m_Map; + string m_GameName; + string m_OwnerName; + uint32_t m_Duration; + uint32_t m_GameState; + string m_CreatorName; + string m_CreatorServer; + uint32_t m_Result; + +public: + CCallableGameAdd( string nServer, string nMap, string nGameName, string nOwnerName, uint32_t nDuration, uint32_t nGameState, string nCreatorName, string nCreatorServer ) : CBaseCallable( ), m_Server( nServer ), m_Map( nMap ), m_GameName( nGameName ), m_OwnerName( nOwnerName ), m_Duration( nDuration ), m_GameState( nGameState ), m_CreatorName( nCreatorName ), m_CreatorServer( nCreatorServer ), m_Result( 0 ) { } + virtual ~CCallableGameAdd( ); + + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableGamePlayerAdd : virtual public CBaseCallable +{ +protected: + uint32_t m_GameID; + string m_Name; + string m_IP; + uint32_t m_Spoofed; + string m_SpoofedRealm; + uint32_t m_Reserved; + uint32_t m_LoadingTime; + uint32_t m_Left; + string m_LeftReason; + uint32_t m_Team; + uint32_t m_Colour; + uint32_t m_Result; + +public: + CCallableGamePlayerAdd( uint32_t nGameID, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nReserved, uint32_t nLoadingTime, uint32_t nLeft, string nLeftReason, uint32_t nTeam, uint32_t nColour ) : CBaseCallable( ), m_GameID( nGameID ), m_Name( nName ), m_IP( nIP ), m_Spoofed( nSpoofed ), m_SpoofedRealm( nSpoofedRealm ), m_Reserved( nReserved ), m_LoadingTime( nLoadingTime ), m_Left( nLeft ), m_LeftReason( nLeftReason ), m_Team( nTeam ), m_Colour( nColour ), m_Result( 0 ) { } + virtual ~CCallableGamePlayerAdd( ); + + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableGamePlayerSummaryCheck : virtual public CBaseCallable +{ +protected: + string m_Name; + CDBGamePlayerSummary *m_Result; + +public: + CCallableGamePlayerSummaryCheck( string nName ) : CBaseCallable( ), m_Name( nName ), m_Result( NULL ) { } + virtual ~CCallableGamePlayerSummaryCheck( ); + + virtual string GetName( ) { return m_Name; } + virtual CDBGamePlayerSummary *GetResult( ) { return m_Result; } + virtual void SetResult( CDBGamePlayerSummary *nResult ) { m_Result = nResult; } +}; + +class CCallableDotAGameAdd : virtual public CBaseCallable +{ +protected: + uint32_t m_GameID; + uint32_t m_Winner; + uint32_t m_Min; + uint32_t m_Sec; + uint32_t m_Result; + +public: + CCallableDotAGameAdd( uint32_t nGameID, uint32_t nWinner, uint32_t nMin, uint32_t nSec ) : CBaseCallable( ), m_GameID( nGameID ), m_Winner( nWinner ), m_Min( nMin ), m_Sec( nSec ), m_Result( 0 ) { } + virtual ~CCallableDotAGameAdd( ); + + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableDotAPlayerAdd : virtual public CBaseCallable +{ +protected: + uint32_t m_GameID; + uint32_t m_Colour; + uint32_t m_Kills; + uint32_t m_Deaths; + uint32_t m_CreepKills; + uint32_t m_CreepDenies; + uint32_t m_Assists; + uint32_t m_Gold; + uint32_t m_NeutralKills; + string m_Item1; + string m_Item2; + string m_Item3; + string m_Item4; + string m_Item5; + string m_Item6; + string m_Hero; + uint32_t m_NewColour; + uint32_t m_TowerKills; + uint32_t m_RaxKills; + uint32_t m_CourierKills; + uint32_t m_Result; + +public: + CCallableDotAPlayerAdd( uint32_t nGameID, uint32_t nColour, uint32_t nKills, uint32_t nDeaths, uint32_t nCreepKills, uint32_t nCreepDenies, uint32_t nAssists, uint32_t nGold, uint32_t nNeutralKills, string nItem1, string nItem2, string nItem3, string nItem4, string nItem5, string nItem6, string nHero, uint32_t nNewColour, uint32_t nTowerKills, uint32_t nRaxKills, uint32_t nCourierKills ) : CBaseCallable( ), m_GameID( nGameID ), m_Colour( nColour ), m_Kills( nKills ), m_Deaths( nDeaths ), m_CreepKills( nCreepKills ), m_CreepDenies( nCreepDenies ), m_Assists( nAssists ), m_Gold( nGold ), m_NeutralKills( nNeutralKills ), m_Item1( nItem1 ), m_Item2( nItem2 ), m_Item3( nItem3 ), m_Item4( nItem4 ), m_Item5( nItem5 ), m_Item6( nItem6 ), m_Hero( nHero ), m_NewColour( nNewColour ), m_TowerKills( nTowerKills ), m_RaxKills( nRaxKills ), m_CourierKills( nCourierKills ), m_Result( 0 ) { } + virtual ~CCallableDotAPlayerAdd( ); + + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableDotAPlayerSummaryCheck : virtual public CBaseCallable +{ +protected: + string m_Name; + CDBDotAPlayerSummary *m_Result; + +public: + CCallableDotAPlayerSummaryCheck( string nName ) : CBaseCallable( ), m_Name( nName ), m_Result( NULL ) { } + virtual ~CCallableDotAPlayerSummaryCheck( ); + + virtual string GetName( ) { return m_Name; } + virtual CDBDotAPlayerSummary *GetResult( ) { return m_Result; } + virtual void SetResult( CDBDotAPlayerSummary *nResult ) { m_Result = nResult; } +}; + +class CCallableDownloadAdd : virtual public CBaseCallable +{ +protected: + string m_Map; + uint32_t m_MapSize; + string m_Name; + string m_IP; + uint32_t m_Spoofed; + string m_SpoofedRealm; + uint32_t m_DownloadTime; + bool m_Result; + +public: + CCallableDownloadAdd( string nMap, uint32_t nMapSize, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nDownloadTime ) : CBaseCallable( ), m_Map( nMap ), m_MapSize( nMapSize ), m_Name( nName ), m_IP( nIP ), m_Spoofed( nSpoofed ), m_SpoofedRealm( nSpoofedRealm ), m_DownloadTime( nDownloadTime ), m_Result( false ) { } + virtual ~CCallableDownloadAdd( ); + + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +class CCallableScoreCheck : virtual public CBaseCallable +{ +protected: + string m_Category; + string m_Name; + string m_Server; + double m_Result; + +public: + CCallableScoreCheck( string nCategory, string nName, string nServer ) : CBaseCallable( ), m_Category( nCategory ), m_Name( nName ), m_Server( nServer ), m_Result( 0.0 ) { } + virtual ~CCallableScoreCheck( ); + + virtual string GetName( ) { return m_Name; } + virtual double GetResult( ) { return m_Result; } + virtual void SetResult( double nResult ) { m_Result = nResult; } +}; + +class CCallableW3MMDPlayerAdd : virtual public CBaseCallable +{ +protected: + string m_Category; + uint32_t m_GameID; + uint32_t m_PID; + string m_Name; + string m_Flag; + uint32_t m_Leaver; + uint32_t m_Practicing; + uint32_t m_Result; + +public: + CCallableW3MMDPlayerAdd( string nCategory, uint32_t nGameID, uint32_t nPID, string nName, string nFlag, uint32_t nLeaver, uint32_t nPracticing ) : CBaseCallable( ), m_Category( nCategory ), m_GameID( nGameID ), m_PID( nPID ), m_Name( nName ), m_Flag( nFlag ), m_Leaver( nLeaver ), m_Practicing( nPracticing ), m_Result( 0 ) { } + virtual ~CCallableW3MMDPlayerAdd( ); + + virtual uint32_t GetResult( ) { return m_Result; } + virtual void SetResult( uint32_t nResult ) { m_Result = nResult; } +}; + +class CCallableW3MMDVarAdd : virtual public CBaseCallable +{ +protected: + uint32_t m_GameID; + map m_VarInts; + map m_VarReals; + map m_VarStrings; + + enum ValueType { + VALUETYPE_INT = 1, + VALUETYPE_REAL = 2, + VALUETYPE_STRING = 3 + }; + + ValueType m_ValueType; + bool m_Result; + +public: + CCallableW3MMDVarAdd( uint32_t nGameID, map nVarInts ) : CBaseCallable( ), m_GameID( nGameID ), m_VarInts( nVarInts ), m_ValueType( VALUETYPE_INT ), m_Result( false ) { } + CCallableW3MMDVarAdd( uint32_t nGameID, map nVarReals ) : CBaseCallable( ), m_GameID( nGameID ), m_VarReals( nVarReals ), m_ValueType( VALUETYPE_REAL ), m_Result( false ) { } + CCallableW3MMDVarAdd( uint32_t nGameID, map nVarStrings ) : CBaseCallable( ), m_GameID( nGameID ), m_VarStrings( nVarStrings ), m_ValueType( VALUETYPE_STRING ), m_Result( false ) { } + virtual ~CCallableW3MMDVarAdd( ); + + virtual bool GetResult( ) { return m_Result; } + virtual void SetResult( bool nResult ) { m_Result = nResult; } +}; + +// +// CDBBan +// + +class CDBBan +{ +private: + string m_Server; + string m_Name; + string m_IP; + string m_Date; + string m_GameName; + string m_Admin; + string m_Reason; + +public: + CDBBan( string nServer, string nName, string nIP, string nDate, string nGameName, string nAdmin, string nReason ); + ~CDBBan( ); + + string GetServer( ) { return m_Server; } + string GetName( ) { return m_Name; } + string GetIP( ) { return m_IP; } + string GetDate( ) { return m_Date; } + string GetGameName( ) { return m_GameName; } + string GetAdmin( ) { return m_Admin; } + string GetReason( ) { return m_Reason; } +}; + +// +// CDBGame +// + +class CDBGame +{ +private: + uint32_t m_ID; + string m_Server; + string m_Map; + string m_DateTime; + string m_GameName; + string m_OwnerName; + uint32_t m_Duration; + +public: + CDBGame( uint32_t nID, string nServer, string nMap, string nDateTime, string nGameName, string nOwnerName, uint32_t nDuration ); + ~CDBGame( ); + + uint32_t GetID( ) { return m_ID; } + string GetServer( ) { return m_Server; } + string GetMap( ) { return m_Map; } + string GetDateTime( ) { return m_DateTime; } + string GetGameName( ) { return m_GameName; } + string GetOwnerName( ) { return m_OwnerName; } + uint32_t GetDuration( ) { return m_Duration; } + + void SetDuration( uint32_t nDuration ) { m_Duration = nDuration; } +}; + +// +// CDBGamePlayer +// + +class CDBGamePlayer +{ +private: + uint32_t m_ID; + uint32_t m_GameID; + string m_Name; + string m_IP; + uint32_t m_Spoofed; + string m_SpoofedRealm; + uint32_t m_Reserved; + uint32_t m_LoadingTime; + uint32_t m_Left; + string m_LeftReason; + uint32_t m_Team; + uint32_t m_Colour; + +public: + CDBGamePlayer( uint32_t nID, uint32_t nGameID, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nReserved, uint32_t nLoadingTime, uint32_t nLeft, string nLeftReason, uint32_t nTeam, uint32_t nColour ); + ~CDBGamePlayer( ); + + uint32_t GetID( ) { return m_ID; } + uint32_t GetGameID( ) { return m_GameID; } + string GetName( ) { return m_Name; } + string GetIP( ) { return m_IP; } + uint32_t GetSpoofed( ) { return m_Spoofed; } + string GetSpoofedRealm( ) { return m_SpoofedRealm; } + uint32_t GetReserved( ) { return m_Reserved; } + uint32_t GetLoadingTime( ) { return m_LoadingTime; } + uint32_t GetLeft( ) { return m_Left; } + string GetLeftReason( ) { return m_LeftReason; } + uint32_t GetTeam( ) { return m_Team; } + uint32_t GetColour( ) { return m_Colour; } + + void SetLoadingTime( uint32_t nLoadingTime ) { m_LoadingTime = nLoadingTime; } + void SetLeft( uint32_t nLeft ) { m_Left = nLeft; } + void SetLeftReason( string nLeftReason ) { m_LeftReason = nLeftReason; } +}; + +// +// CDBGamePlayerSummary +// + +class CDBGamePlayerSummary +{ +private: + string m_Server; + string m_Name; + string m_FirstGameDateTime; // datetime of first game played + string m_LastGameDateTime; // datetime of last game played + uint32_t m_TotalGames; // total number of games played + uint32_t m_MinLoadingTime; // minimum loading time in milliseconds (this could be skewed because different maps have different load times) + uint32_t m_AvgLoadingTime; // average loading time in milliseconds (this could be skewed because different maps have different load times) + uint32_t m_MaxLoadingTime; // maximum loading time in milliseconds (this could be skewed because different maps have different load times) + uint32_t m_MinLeftPercent; // minimum time at which the player left the game expressed as a percentage of the game duration (0-100) + uint32_t m_AvgLeftPercent; // average time at which the player left the game expressed as a percentage of the game duration (0-100) + uint32_t m_MaxLeftPercent; // maximum time at which the player left the game expressed as a percentage of the game duration (0-100) + uint32_t m_MinDuration; // minimum game duration in seconds + uint32_t m_AvgDuration; // average game duration in seconds + uint32_t m_MaxDuration; // maximum game duration in seconds + +public: + CDBGamePlayerSummary( string nServer, string nName, string nFirstGameDateTime, string nLastGameDateTime, uint32_t nTotalGames, uint32_t nMinLoadingTime, uint32_t nAvgLoadingTime, uint32_t nMaxLoadingTime, uint32_t nMinLeftPercent, uint32_t nAvgLeftPercent, uint32_t nMaxLeftPercent, uint32_t nMinDuration, uint32_t nAvgDuration, uint32_t nMaxDuration ); + ~CDBGamePlayerSummary( ); + + string GetServer( ) { return m_Server; } + string GetName( ) { return m_Name; } + string GetFirstGameDateTime( ) { return m_FirstGameDateTime; } + string GetLastGameDateTime( ) { return m_LastGameDateTime; } + uint32_t GetTotalGames( ) { return m_TotalGames; } + uint32_t GetMinLoadingTime( ) { return m_MinLoadingTime; } + uint32_t GetAvgLoadingTime( ) { return m_AvgLoadingTime; } + uint32_t GetMaxLoadingTime( ) { return m_MaxLoadingTime; } + uint32_t GetMinLeftPercent( ) { return m_MinLeftPercent; } + uint32_t GetAvgLeftPercent( ) { return m_AvgLeftPercent; } + uint32_t GetMaxLeftPercent( ) { return m_MaxLeftPercent; } + uint32_t GetMinDuration( ) { return m_MinDuration; } + uint32_t GetAvgDuration( ) { return m_AvgDuration; } + uint32_t GetMaxDuration( ) { return m_MaxDuration; } +}; + +// +// CDBDotAGame +// + +class CDBDotAGame +{ +private: + uint32_t m_ID; + uint32_t m_GameID; + uint32_t m_Winner; + uint32_t m_Min; + uint32_t m_Sec; + +public: + CDBDotAGame( uint32_t nID, uint32_t nGameID, uint32_t nWinner, uint32_t nMin, uint32_t nSec ); + ~CDBDotAGame( ); + + uint32_t GetID( ) { return m_ID; } + uint32_t GetGameID( ) { return m_GameID; } + uint32_t GetWinner( ) { return m_Winner; } + uint32_t GetMin( ) { return m_Min; } + uint32_t GetSec( ) { return m_Sec; } +}; + +// +// CDBDotAPlayer +// + +class CDBDotAPlayer +{ +private: + uint32_t m_ID; + uint32_t m_GameID; + uint32_t m_Colour; + uint32_t m_Kills; + uint32_t m_Deaths; + uint32_t m_CreepKills; + uint32_t m_CreepDenies; + uint32_t m_Assists; + uint32_t m_Gold; + uint32_t m_NeutralKills; + string m_Items[6]; + string m_Hero; + uint32_t m_NewColour; + uint32_t m_TowerKills; + uint32_t m_RaxKills; + uint32_t m_CourierKills; + +public: + CDBDotAPlayer( ); + CDBDotAPlayer( uint32_t nID, uint32_t nGameID, uint32_t nColour, uint32_t nKills, uint32_t nDeaths, uint32_t nCreepKills, uint32_t nCreepDenies, uint32_t nAssists, uint32_t nGold, uint32_t nNeutralKills, string nItem1, string nItem2, string nItem3, string nItem4, string nItem5, string nItem6, string nHero, uint32_t nNewColour, uint32_t nTowerKills, uint32_t nRaxKills, uint32_t nCourierKills ); + ~CDBDotAPlayer( ); + + uint32_t GetID( ) { return m_ID; } + uint32_t GetGameID( ) { return m_GameID; } + uint32_t GetColour( ) { return m_Colour; } + uint32_t GetKills( ) { return m_Kills; } + uint32_t GetDeaths( ) { return m_Deaths; } + uint32_t GetCreepKills( ) { return m_CreepKills; } + uint32_t GetCreepDenies( ) { return m_CreepDenies; } + uint32_t GetAssists( ) { return m_Assists; } + uint32_t GetGold( ) { return m_Gold; } + uint32_t GetNeutralKills( ) { return m_NeutralKills; } + string GetItem( unsigned int i ); + string GetHero( ) { return m_Hero; } + uint32_t GetNewColour( ) { return m_NewColour; } + uint32_t GetTowerKills( ) { return m_TowerKills; } + uint32_t GetRaxKills( ) { return m_RaxKills; } + uint32_t GetCourierKills( ) { return m_CourierKills; } + + void SetColour( uint32_t nColour ) { m_Colour = nColour; } + void SetKills( uint32_t nKills ) { m_Kills = nKills; } + void SetDeaths( uint32_t nDeaths ) { m_Deaths = nDeaths; } + void SetCreepKills( uint32_t nCreepKills ) { m_CreepKills = nCreepKills; } + void SetCreepDenies( uint32_t nCreepDenies ) { m_CreepDenies = nCreepDenies; } + void SetAssists( uint32_t nAssists ) { m_Assists = nAssists; } + void SetGold( uint32_t nGold ) { m_Gold = nGold; } + void SetNeutralKills( uint32_t nNeutralKills ) { m_NeutralKills = nNeutralKills; } + void SetItem( unsigned int i, string item ); + void SetHero( string nHero ) { m_Hero = nHero; } + void SetNewColour( uint32_t nNewColour ) { m_NewColour = nNewColour; } + void SetTowerKills( uint32_t nTowerKills ) { m_TowerKills = nTowerKills; } + void SetRaxKills( uint32_t nRaxKills ) { m_RaxKills = nRaxKills; } + void SetCourierKills( uint32_t nCourierKills ) { m_CourierKills = nCourierKills; } +}; + +// +// CDBDotAPlayerSummary +// + +class CDBDotAPlayerSummary +{ +private: + string m_Server; + string m_Name; + uint32_t m_TotalGames; // total number of dota games played + uint32_t m_TotalWins; // total number of dota games won + uint32_t m_TotalLosses; // total number of dota games lost + uint32_t m_TotalKills; // total number of hero kills + uint32_t m_TotalDeaths; // total number of deaths + uint32_t m_TotalCreepKills; // total number of creep kills + uint32_t m_TotalCreepDenies; // total number of creep denies + uint32_t m_TotalAssists; // total number of assists + uint32_t m_TotalNeutralKills; // total number of neutral kills + uint32_t m_TotalTowerKills; // total number of tower kills + uint32_t m_TotalRaxKills; // total number of rax kills + uint32_t m_TotalCourierKills; // total number of courier kills + +public: + CDBDotAPlayerSummary( string nServer, string nName, uint32_t nTotalGames, uint32_t nTotalWins, uint32_t nTotalLosses, uint32_t nTotalKills, uint32_t nTotalDeaths, uint32_t nTotalCreepKills, uint32_t nTotalCreepDenies, uint32_t nTotalAssists, uint32_t nTotalNeutralKills, uint32_t nTotalTowerKills, uint32_t nTotalRaxKills, uint32_t nTotalCourierKills ); + ~CDBDotAPlayerSummary( ); + + string GetServer( ) { return m_Server; } + string GetName( ) { return m_Name; } + uint32_t GetTotalGames( ) { return m_TotalGames; } + uint32_t GetTotalWins( ) { return m_TotalWins; } + uint32_t GetTotalLosses( ) { return m_TotalLosses; } + uint32_t GetTotalKills( ) { return m_TotalKills; } + uint32_t GetTotalDeaths( ) { return m_TotalDeaths; } + uint32_t GetTotalCreepKills( ) { return m_TotalCreepKills; } + uint32_t GetTotalCreepDenies( ) { return m_TotalCreepDenies; } + uint32_t GetTotalAssists( ) { return m_TotalAssists; } + uint32_t GetTotalNeutralKills( ) { return m_TotalNeutralKills; } + uint32_t GetTotalTowerKills( ) { return m_TotalTowerKills; } + uint32_t GetTotalRaxKills( ) { return m_TotalRaxKills; } + uint32_t GetTotalCourierKills( ) { return m_TotalCourierKills; } + + float GetAvgKills( ) { return m_TotalGames > 0 ? (float)m_TotalKills / m_TotalGames : 0; } + float GetAvgDeaths( ) { return m_TotalGames > 0 ? (float)m_TotalDeaths / m_TotalGames : 0; } + float GetAvgCreepKills( ) { return m_TotalGames > 0 ? (float)m_TotalCreepKills / m_TotalGames : 0; } + float GetAvgCreepDenies( ) { return m_TotalGames > 0 ? (float)m_TotalCreepDenies / m_TotalGames : 0; } + float GetAvgAssists( ) { return m_TotalGames > 0 ? (float)m_TotalAssists / m_TotalGames : 0; } + float GetAvgNeutralKills( ) { return m_TotalGames > 0 ? (float)m_TotalNeutralKills / m_TotalGames : 0; } + float GetAvgTowerKills( ) { return m_TotalGames > 0 ? (float)m_TotalTowerKills / m_TotalGames : 0; } + float GetAvgRaxKills( ) { return m_TotalGames > 0 ? (float)m_TotalRaxKills / m_TotalGames : 0; } + float GetAvgCourierKills( ) { return m_TotalGames > 0 ? (float)m_TotalCourierKills / m_TotalGames : 0; } +}; + +#endif diff --git a/ghost-legacy/ghostdbmysql.cpp b/ghost-legacy/ghostdbmysql.cpp new file mode 100644 index 0000000..ae7b7bf --- /dev/null +++ b/ghost-legacy/ghostdbmysql.cpp @@ -0,0 +1,1391 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifdef GHOST_MYSQL + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "ghostdb.h" +#include "ghostdbmysql.h" + +#include + +#ifdef WIN32 + #include +#endif + +#include +#include + +// +// CGHostDBMySQL +// + +CGHostDBMySQL :: CGHostDBMySQL( CConfig *CFG ) : CGHostDB( CFG ) +{ + m_Server = CFG->GetString( "db_mysql_server", string( ) ); + m_Database = CFG->GetString( "db_mysql_database", "ghost" ); + m_User = CFG->GetString( "db_mysql_user", string( ) ); + m_Password = CFG->GetString( "db_mysql_password", string( ) ); + m_Port = CFG->GetInt( "db_mysql_port", 0 ); + m_BotID = CFG->GetInt( "db_mysql_botid", 0 ); + m_NumConnections = 1; + m_OutstandingCallables = 0; + + mysql_library_init( 0, NULL, NULL ); + + // create the first connection + + CONSOLE_Print( "[MYSQL] connecting to database server" ); + MYSQL *Connection = NULL; + + if( !( Connection = mysql_init( NULL ) ) ) + { + CONSOLE_Print( string( "[MYSQL] " ) + mysql_error( Connection ) ); + m_HasError = true; + m_Error = "error initializing MySQL connection"; + return; + } + + my_bool Reconnect = true; + mysql_options( Connection, MYSQL_OPT_RECONNECT, &Reconnect ); + + if( !( mysql_real_connect( Connection, m_Server.c_str( ), m_User.c_str( ), m_Password.c_str( ), m_Database.c_str( ), m_Port, NULL, 0 ) ) ) + { + CONSOLE_Print( string( "[MYSQL] " ) + mysql_error( Connection ) ); + m_HasError = true; + m_Error = "error connecting to MySQL server"; + return; + } + + m_IdleConnections.push( Connection ); +} + +CGHostDBMySQL :: ~CGHostDBMySQL( ) +{ + CONSOLE_Print( "[MYSQL] closing " + UTIL_ToString( m_IdleConnections.size( ) ) + "/" + UTIL_ToString( m_NumConnections ) + " idle MySQL connections" ); + + while( !m_IdleConnections.empty( ) ) + { + mysql_close( (MYSQL *)m_IdleConnections.front( ) ); + m_IdleConnections.pop( ); + } + + if( m_OutstandingCallables > 0 ) + CONSOLE_Print( "[MYSQL] " + UTIL_ToString( m_OutstandingCallables ) + " outstanding callables were never recovered" ); + + mysql_library_end( ); +} + +string CGHostDBMySQL :: GetStatus( ) +{ + return "DB STATUS --- Connections: " + UTIL_ToString( m_IdleConnections.size( ) ) + "/" + UTIL_ToString( m_NumConnections ) + " idle. Outstanding callables: " + UTIL_ToString( m_OutstandingCallables ) + "."; +} + +void CGHostDBMySQL :: RecoverCallable( CBaseCallable *callable ) +{ + CMySQLCallable *MySQLCallable = dynamic_cast( callable ); + + if( MySQLCallable ) + { + if( m_IdleConnections.size( ) > 30 ) + { + mysql_close( (MYSQL *)MySQLCallable->GetConnection( ) ); + m_NumConnections--; + } + else + m_IdleConnections.push( MySQLCallable->GetConnection( ) ); + + if( m_OutstandingCallables == 0 ) + CONSOLE_Print( "[MYSQL] recovered a mysql callable with zero outstanding" ); + else + m_OutstandingCallables--; + + if( !MySQLCallable->GetError( ).empty( ) ) + CONSOLE_Print( "[MYSQL] error --- " + MySQLCallable->GetError( ) ); + } + else + CONSOLE_Print( "[MYSQL] tried to recover a non-mysql callable" ); +} + +void CGHostDBMySQL :: CreateThread( CBaseCallable *callable ) +{ + try + { + boost :: thread Thread( boost :: ref( *callable ) ); + } + catch( boost :: thread_resource_error tre ) + { + CONSOLE_Print( "[MYSQL] error spawning thread on attempt #1 [" + string( tre.what( ) ) + "], pausing execution and trying again in 50ms" ); + MILLISLEEP( 50 ); + + try + { + boost :: thread Thread( boost :: ref( *callable ) ); + } + catch( boost :: thread_resource_error tre2 ) + { + CONSOLE_Print( "[MYSQL] error spawning thread on attempt #2 [" + string( tre2.what( ) ) + "], giving up" ); + callable->SetReady( true ); + } + } +} + +CCallableAdminCount *CGHostDBMySQL :: ThreadedAdminCount( string server ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableAdminCount *Callable = new CMySQLCallableAdminCount( server, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableAdminCheck *CGHostDBMySQL :: ThreadedAdminCheck( string server, string user ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableAdminCheck *Callable = new CMySQLCallableAdminCheck( server, user, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableAdminAdd *CGHostDBMySQL :: ThreadedAdminAdd( string server, string user ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableAdminAdd *Callable = new CMySQLCallableAdminAdd( server, user, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableAdminRemove *CGHostDBMySQL :: ThreadedAdminRemove( string server, string user ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableAdminRemove *Callable = new CMySQLCallableAdminRemove( server, user, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableAdminList *CGHostDBMySQL :: ThreadedAdminList( string server ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableAdminList *Callable = new CMySQLCallableAdminList( server, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanCount *CGHostDBMySQL :: ThreadedBanCount( string server ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanCount *Callable = new CMySQLCallableBanCount( server, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanCheck *CGHostDBMySQL :: ThreadedBanCheck( string server, string user, string ip ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanCheck *Callable = new CMySQLCallableBanCheck( server, user, ip, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanAdd *CGHostDBMySQL :: ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanAdd *Callable = new CMySQLCallableBanAdd( server, user, ip, gamename, admin, reason, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanRemove *CGHostDBMySQL :: ThreadedBanRemove( string server, string user ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanRemove *Callable = new CMySQLCallableBanRemove( server, user, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanRemove *CGHostDBMySQL :: ThreadedBanRemove( string user ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanRemove *Callable = new CMySQLCallableBanRemove( string( ), user, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableBanList *CGHostDBMySQL :: ThreadedBanList( string server ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableBanList *Callable = new CMySQLCallableBanList( server, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableGameAdd *CGHostDBMySQL :: ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableGameAdd *Callable = new CMySQLCallableGameAdd( server, map, gamename, ownername, duration, gamestate, creatorname, creatorserver, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableGamePlayerAdd *CGHostDBMySQL :: ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableGamePlayerAdd *Callable = new CMySQLCallableGamePlayerAdd( gameid, name, ip, spoofed, spoofedrealm, reserved, loadingtime, left, leftreason, team, colour, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableGamePlayerSummaryCheck *CGHostDBMySQL :: ThreadedGamePlayerSummaryCheck( string name ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableGamePlayerSummaryCheck *Callable = new CMySQLCallableGamePlayerSummaryCheck( name, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableDotAGameAdd *CGHostDBMySQL :: ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableDotAGameAdd *Callable = new CMySQLCallableDotAGameAdd( gameid, winner, min, sec, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableDotAPlayerAdd *CGHostDBMySQL :: ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableDotAPlayerAdd *Callable = new CMySQLCallableDotAPlayerAdd( gameid, colour, kills, deaths, creepkills, creepdenies, assists, gold, neutralkills, item1, item2, item3, item4, item5, item6, hero, newcolour, towerkills, raxkills, courierkills, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableDotAPlayerSummaryCheck *CGHostDBMySQL :: ThreadedDotAPlayerSummaryCheck( string name ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableDotAPlayerSummaryCheck *Callable = new CMySQLCallableDotAPlayerSummaryCheck( name, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableDownloadAdd *CGHostDBMySQL :: ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableDownloadAdd *Callable = new CMySQLCallableDownloadAdd( map, mapsize, name, ip, spoofed, spoofedrealm, downloadtime, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableScoreCheck *CGHostDBMySQL :: ThreadedScoreCheck( string category, string name, string server ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableScoreCheck *Callable = new CMySQLCallableScoreCheck( category, name, server, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableW3MMDPlayerAdd *CGHostDBMySQL :: ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableW3MMDPlayerAdd *Callable = new CMySQLCallableW3MMDPlayerAdd( category, gameid, pid, name, flag, leaver, practicing, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBMySQL :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableW3MMDVarAdd *Callable = new CMySQLCallableW3MMDVarAdd( gameid, var_ints, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBMySQL :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableW3MMDVarAdd *Callable = new CMySQLCallableW3MMDVarAdd( gameid, var_reals, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBMySQL :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ) +{ + void *Connection = GetIdleConnection( ); + + if( !Connection ) + m_NumConnections++; + + CCallableW3MMDVarAdd *Callable = new CMySQLCallableW3MMDVarAdd( gameid, var_strings, Connection, m_BotID, m_Server, m_Database, m_User, m_Password, m_Port ); + CreateThread( Callable ); + m_OutstandingCallables++; + return Callable; +} + +void *CGHostDBMySQL :: GetIdleConnection( ) +{ + void *Connection = NULL; + + if( !m_IdleConnections.empty( ) ) + { + Connection = m_IdleConnections.front( ); + m_IdleConnections.pop( ); + } + + return Connection; +} + +// +// unprototyped global helper functions +// + +string MySQLEscapeString( void *conn, string str ) +{ + char *to = new char[str.size( ) * 2 + 1]; + unsigned long size = mysql_real_escape_string( (MYSQL *)conn, to, str.c_str( ), str.size( ) ); + string result( to, size ); + delete [] to; + return result; +} + +vector MySQLFetchRow( MYSQL_RES *res ) +{ + vector Result; + + MYSQL_ROW Row = mysql_fetch_row( res ); + + if( Row ) + { + unsigned long *Lengths; + Lengths = mysql_fetch_lengths( res ); + + for( unsigned int i = 0; i < mysql_num_fields( res ); i++ ) + { + if( Row[i] ) + Result.push_back( string( Row[i], Lengths[i] ) ); + else + Result.push_back( string( ) ); + } + } + + return Result; +} + +// +// global helper functions +// + +uint32_t MySQLAdminCount( void *conn, string *error, uint32_t botid, string server ) +{ + string EscServer = MySQLEscapeString( conn, server ); + uint32_t Count = 0; + string Query = "SELECT COUNT(*) FROM admins WHERE server='" + EscServer + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 1 ) + Count = UTIL_ToUInt32( Row[0] ); + else + *error = "error counting admins [" + server + "] - row doesn't have 1 column"; + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return Count; +} + +bool MySQLAdminCheck( void *conn, string *error, uint32_t botid, string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + bool IsAdmin = false; + string Query = "SELECT * FROM admins WHERE server='" + EscServer + "' AND name='" + EscUser + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( !Row.empty( ) ) + IsAdmin = true; + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return IsAdmin; +} + +bool MySQLAdminAdd( void *conn, string *error, uint32_t botid, string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + bool Success = false; + string Query = "INSERT INTO admins ( botid, server, name ) VALUES ( " + UTIL_ToString( botid ) + ", '" + EscServer + "', '" + EscUser + "' )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +bool MySQLAdminRemove( void *conn, string *error, uint32_t botid, string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + bool Success = false; + string Query = "DELETE FROM admins WHERE server='" + EscServer + "' AND name='" + EscUser + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +vector MySQLAdminList( void *conn, string *error, uint32_t botid, string server ) +{ + string EscServer = MySQLEscapeString( conn, server ); + vector AdminList; + string Query = "SELECT name FROM admins WHERE server='" + EscServer + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + while( !Row.empty( ) ) + { + AdminList.push_back( Row[0] ); + Row = MySQLFetchRow( Result ); + } + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return AdminList; +} + +uint32_t MySQLBanCount( void *conn, string *error, uint32_t botid, string server ) +{ + string EscServer = MySQLEscapeString( conn, server ); + uint32_t Count = 0; + string Query = "SELECT COUNT(*) FROM bans WHERE server='" + EscServer + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 1 ) + Count = UTIL_ToUInt32( Row[0] ); + else + *error = "error counting bans [" + server + "] - row doesn't have 1 column"; + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return Count; +} + +CDBBan *MySQLBanCheck( void *conn, string *error, uint32_t botid, string server, string user, string ip ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + string EscIP = MySQLEscapeString( conn, ip ); + CDBBan *Ban = NULL; + string Query; + + if( ip.empty( ) ) + Query = "SELECT name, ip, DATE(date), gamename, admin, reason FROM bans WHERE server='" + EscServer + "' AND name='" + EscUser + "'"; + else + Query = "SELECT name, ip, DATE(date), gamename, admin, reason FROM bans WHERE (server='" + EscServer + "' AND name='" + EscUser + "') OR ip='" + EscIP + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 6 ) + Ban = new CDBBan( server, Row[0], Row[1], Row[2], Row[3], Row[4], Row[5] ); + /* else + *error = "error checking ban [" + server + " : " + user + "] - row doesn't have 6 columns"; */ + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return Ban; +} + +bool MySQLBanAdd( void *conn, string *error, uint32_t botid, string server, string user, string ip, string gamename, string admin, string reason ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + string EscIP = MySQLEscapeString( conn, ip ); + string EscGameName = MySQLEscapeString( conn, gamename ); + string EscAdmin = MySQLEscapeString( conn, admin ); + string EscReason = MySQLEscapeString( conn, reason ); + bool Success = false; + string Query = "INSERT INTO bans ( botid, server, name, ip, date, gamename, admin, reason ) VALUES ( " + UTIL_ToString( botid ) + ", '" + EscServer + "', '" + EscUser + "', '" + EscIP + "', CURDATE( ), '" + EscGameName + "', '" + EscAdmin + "', '" + EscReason + "' )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +bool MySQLBanRemove( void *conn, string *error, uint32_t botid, string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscServer = MySQLEscapeString( conn, server ); + string EscUser = MySQLEscapeString( conn, user ); + bool Success = false; + string Query = "DELETE FROM bans WHERE server='" + EscServer + "' AND name='" + EscUser + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +bool MySQLBanRemove( void *conn, string *error, uint32_t botid, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + string EscUser = MySQLEscapeString( conn, user ); + bool Success = false; + string Query = "DELETE FROM bans WHERE name='" + EscUser + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +vector MySQLBanList( void *conn, string *error, uint32_t botid, string server ) +{ + string EscServer = MySQLEscapeString( conn, server ); + vector BanList; + string Query = "SELECT name, ip, DATE(date), gamename, admin, reason FROM bans WHERE server='" + EscServer + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + while( Row.size( ) == 6 ) + { + BanList.push_back( new CDBBan( server, Row[0], Row[1], Row[2], Row[3], Row[4], Row[5] ) ); + Row = MySQLFetchRow( Result ); + } + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return BanList; +} + +uint32_t MySQLGameAdd( void *conn, string *error, uint32_t botid, string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + uint32_t RowID = 0; + string EscServer = MySQLEscapeString( conn, server ); + string EscMap = MySQLEscapeString( conn, map ); + string EscGameName = MySQLEscapeString( conn, gamename ); + string EscOwnerName = MySQLEscapeString( conn, ownername ); + string EscCreatorName = MySQLEscapeString( conn, creatorname ); + string EscCreatorServer = MySQLEscapeString( conn, creatorserver ); + string Query = "INSERT INTO games ( botid, server, map, datetime, gamename, ownername, duration, gamestate, creatorname, creatorserver ) VALUES ( " + UTIL_ToString( botid ) + ", '" + EscServer + "', '" + EscMap + "', NOW( ), '" + EscGameName + "', '" + EscOwnerName + "', " + UTIL_ToString( duration ) + ", " + UTIL_ToString( gamestate ) + ", '" + EscCreatorName + "', '" + EscCreatorServer + "' )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + RowID = mysql_insert_id( (MYSQL *)conn ); + + return RowID; +} + +uint32_t MySQLGamePlayerAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t RowID = 0; + string EscName = MySQLEscapeString( conn, name ); + string EscIP = MySQLEscapeString( conn, ip ); + string EscSpoofedRealm = MySQLEscapeString( conn, spoofedrealm ); + string EscLeftReason = MySQLEscapeString( conn, leftreason ); + string Query = "INSERT INTO gameplayers ( botid, gameid, name, ip, spoofed, reserved, loadingtime, `left`, leftreason, team, colour, spoofedrealm ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", '" + EscName + "', '" + EscIP + "', " + UTIL_ToString( spoofed ) + ", " + UTIL_ToString( reserved ) + ", " + UTIL_ToString( loadingtime ) + ", " + UTIL_ToString( left ) + ", '" + EscLeftReason + "', " + UTIL_ToString( team ) + ", " + UTIL_ToString( colour ) + ", '" + EscSpoofedRealm + "' )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + RowID = mysql_insert_id( (MYSQL *)conn ); + + return RowID; +} + +CDBGamePlayerSummary *MySQLGamePlayerSummaryCheck( void *conn, string *error, uint32_t botid, string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + string EscName = MySQLEscapeString( conn, name ); + CDBGamePlayerSummary *GamePlayerSummary = NULL; + string Query = "SELECT MIN(DATE(datetime)), MAX(DATE(datetime)), COUNT(*), MIN(loadingtime), AVG(loadingtime), MAX(loadingtime), MIN(`left`/duration)*100, AVG(`left`/duration)*100, MAX(`left`/duration)*100, MIN(duration), AVG(duration), MAX(duration) FROM gameplayers LEFT JOIN games ON games.id=gameid WHERE LOWER(name)='" + EscName + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 12 ) + { + string FirstGameDateTime = Row[0]; + string LastGameDateTime = Row[1]; + uint32_t TotalGames = UTIL_ToUInt32( Row[2] ); + uint32_t MinLoadingTime = UTIL_ToUInt32( Row[3] ); + uint32_t AvgLoadingTime = UTIL_ToUInt32( Row[4] ); + uint32_t MaxLoadingTime = UTIL_ToUInt32( Row[5] ); + uint32_t MinLeftPercent = UTIL_ToUInt32( Row[6] ); + uint32_t AvgLeftPercent = UTIL_ToUInt32( Row[7] ); + uint32_t MaxLeftPercent = UTIL_ToUInt32( Row[8] ); + uint32_t MinDuration = UTIL_ToUInt32( Row[9] ); + uint32_t AvgDuration = UTIL_ToUInt32( Row[10] ); + uint32_t MaxDuration = UTIL_ToUInt32( Row[11] ); + GamePlayerSummary = new CDBGamePlayerSummary( string( ), name, FirstGameDateTime, LastGameDateTime, TotalGames, MinLoadingTime, AvgLoadingTime, MaxLoadingTime, MinLeftPercent, AvgLeftPercent, MaxLeftPercent, MinDuration, AvgDuration, MaxDuration ); + } + else + *error = "error checking gameplayersummary [" + name + "] - row doesn't have 12 columns"; + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return GamePlayerSummary; +} + +uint32_t MySQLDotAGameAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + uint32_t RowID = 0; + string Query = "INSERT INTO dotagames ( botid, gameid, winner, min, sec ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( winner ) + ", " + UTIL_ToString( min ) + ", " + UTIL_ToString( sec ) + " )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + RowID = mysql_insert_id( (MYSQL *)conn ); + + return RowID; +} + +uint32_t MySQLDotAPlayerAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + uint32_t RowID = 0; + string EscItem1 = MySQLEscapeString( conn, item1 ); + string EscItem2 = MySQLEscapeString( conn, item2 ); + string EscItem3 = MySQLEscapeString( conn, item3 ); + string EscItem4 = MySQLEscapeString( conn, item4 ); + string EscItem5 = MySQLEscapeString( conn, item5 ); + string EscItem6 = MySQLEscapeString( conn, item6 ); + string EscHero = MySQLEscapeString( conn, hero ); + string Query = "INSERT INTO dotaplayers ( botid, gameid, colour, kills, deaths, creepkills, creepdenies, assists, gold, neutralkills, item1, item2, item3, item4, item5, item6, hero, newcolour, towerkills, raxkills, courierkills ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( colour ) + ", " + UTIL_ToString( kills ) + ", " + UTIL_ToString( deaths ) + ", " + UTIL_ToString( creepkills ) + ", " + UTIL_ToString( creepdenies ) + ", " + UTIL_ToString( assists ) + ", " + UTIL_ToString( gold ) + ", " + UTIL_ToString( neutralkills ) + ", '" + EscItem1 + "', '" + EscItem2 + "', '" + EscItem3 + "', '" + EscItem4 + "', '" + EscItem5 + "', '" + EscItem6 + "', '" + EscHero + "', " + UTIL_ToString( newcolour ) + ", " + UTIL_ToString( towerkills ) + ", " + UTIL_ToString( raxkills ) + ", " + UTIL_ToString( courierkills ) + " )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + RowID = mysql_insert_id( (MYSQL *)conn ); + + return RowID; +} + +CDBDotAPlayerSummary *MySQLDotAPlayerSummaryCheck( void *conn, string *error, uint32_t botid, string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + string EscName = MySQLEscapeString( conn, name ); + CDBDotAPlayerSummary *DotAPlayerSummary = NULL; + string Query = "SELECT COUNT(dotaplayers.id), SUM(kills), SUM(deaths), SUM(creepkills), SUM(creepdenies), SUM(assists), SUM(neutralkills), SUM(towerkills), SUM(raxkills), SUM(courierkills) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour WHERE LOWER(name)='" + EscName + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 10 ) + { + uint32_t TotalGames = UTIL_ToUInt32( Row[0] ); + + if( TotalGames > 0 ) + { + uint32_t TotalWins = 0; + uint32_t TotalLosses = 0; + uint32_t TotalKills = UTIL_ToUInt32( Row[1] ); + uint32_t TotalDeaths = UTIL_ToUInt32( Row[2] ); + uint32_t TotalCreepKills = UTIL_ToUInt32( Row[3] ); + uint32_t TotalCreepDenies = UTIL_ToUInt32( Row[4] ); + uint32_t TotalAssists = UTIL_ToUInt32( Row[5] ); + uint32_t TotalNeutralKills = UTIL_ToUInt32( Row[6] ); + uint32_t TotalTowerKills = UTIL_ToUInt32( Row[7] ); + uint32_t TotalRaxKills = UTIL_ToUInt32( Row[8] ); + uint32_t TotalCourierKills = UTIL_ToUInt32( Row[9] ); + + // calculate total wins + + string Query2 = "SELECT COUNT(*) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour LEFT JOIN dotagames ON games.id=dotagames.gameid WHERE name='" + EscName + "' AND ((winner=1 AND dotaplayers.newcolour>=1 AND dotaplayers.newcolour<=5) OR (winner=2 AND dotaplayers.newcolour>=7 AND dotaplayers.newcolour<=11))"; + + if( mysql_real_query( (MYSQL *)conn, Query2.c_str( ), Query2.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result2 = mysql_store_result( (MYSQL *)conn ); + + if( Result2 ) + { + vector Row2 = MySQLFetchRow( Result2 ); + + if( Row2.size( ) == 1 ) + TotalWins = UTIL_ToUInt32( Row2[0] ); + else + *error = "error checking dotaplayersummary wins [" + name + "] - row doesn't have 1 column"; + + mysql_free_result( Result2 ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + // calculate total losses + + string Query3 = "SELECT COUNT(*) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour LEFT JOIN dotagames ON games.id=dotagames.gameid WHERE name='" + EscName + "' AND ((winner=2 AND dotaplayers.newcolour>=1 AND dotaplayers.newcolour<=5) OR (winner=1 AND dotaplayers.newcolour>=7 AND dotaplayers.newcolour<=11))"; + + if( mysql_real_query( (MYSQL *)conn, Query3.c_str( ), Query3.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result3 = mysql_store_result( (MYSQL *)conn ); + + if( Result3 ) + { + vector Row3 = MySQLFetchRow( Result3 ); + + if( Row3.size( ) == 1 ) + TotalLosses = UTIL_ToUInt32( Row3[0] ); + else + *error = "error checking dotaplayersummary losses [" + name + "] - row doesn't have 1 column"; + + mysql_free_result( Result3 ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + // done + + DotAPlayerSummary = new CDBDotAPlayerSummary( string( ), name, TotalGames, TotalWins, TotalLosses, TotalKills, TotalDeaths, TotalCreepKills, TotalCreepDenies, TotalAssists, TotalNeutralKills, TotalTowerKills, TotalRaxKills, TotalCourierKills ); + } + } + else + *error = "error checking dotaplayersummary [" + name + "] - row doesn't have 10 columns"; + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return DotAPlayerSummary; +} + +bool MySQLDownloadAdd( void *conn, string *error, uint32_t botid, string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + bool Success = false; + string EscMap = MySQLEscapeString( conn, map ); + string EscName = MySQLEscapeString( conn, name ); + string EscIP = MySQLEscapeString( conn, ip ); + string EscSpoofedRealm = MySQLEscapeString( conn, spoofedrealm ); + string Query = "INSERT INTO downloads ( botid, map, mapsize, datetime, name, ip, spoofed, spoofedrealm, downloadtime ) VALUES ( " + UTIL_ToString( botid ) + ", '" + EscMap + "', " + UTIL_ToString( mapsize ) + ", NOW( ), '" + EscName + "', '" + EscIP + "', " + UTIL_ToString( spoofed ) + ", '" + EscSpoofedRealm + "', " + UTIL_ToString( downloadtime ) + " )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +double MySQLScoreCheck( void *conn, string *error, uint32_t botid, string category, string name, string server ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + string EscCategory = MySQLEscapeString( conn, category ); + string EscName = MySQLEscapeString( conn, name ); + string EscServer = MySQLEscapeString( conn, server ); + double Score = -100000.0; + string Query = "SELECT score FROM scores WHERE category='" + EscCategory + "' AND name='" + EscName + "' AND server='" + EscServer + "'"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + { + MYSQL_RES *Result = mysql_store_result( (MYSQL *)conn ); + + if( Result ) + { + vector Row = MySQLFetchRow( Result ); + + if( Row.size( ) == 1 ) + Score = UTIL_ToDouble( Row[0] ); + /* else + *error = "error checking score [" + category + " : " + name + " : " + server + "] - row doesn't have 1 column"; */ + + mysql_free_result( Result ); + } + else + *error = mysql_error( (MYSQL *)conn ); + } + + return Score; +} + +uint32_t MySQLW3MMDPlayerAdd( void *conn, string *error, uint32_t botid, string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t RowID = 0; + string EscCategory = MySQLEscapeString( conn, category ); + string EscName = MySQLEscapeString( conn, name ); + string EscFlag = MySQLEscapeString( conn, flag ); + string Query = "INSERT INTO w3mmdplayers ( botid, category, gameid, pid, name, flag, leaver, practicing ) VALUES ( " + UTIL_ToString( botid ) + ", '" + EscCategory + "', " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( pid ) + ", '" + EscName + "', '" + EscFlag + "', " + UTIL_ToString( leaver ) + ", " + UTIL_ToString( practicing ) + " )"; + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + RowID = mysql_insert_id( (MYSQL *)conn ); + + return RowID; +} + +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_ints ) +{ + if( var_ints.empty( ) ) + return false; + + bool Success = false; + string Query; + + for( map :: iterator i = var_ints.begin( ); i != var_ints.end( ); i++ ) + { + string EscVarName = MySQLEscapeString( conn, i->first.second ); + + if( Query.empty( ) ) + Query = "INSERT INTO w3mmdvars ( botid, gameid, pid, varname, value_int ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', " + UTIL_ToString( i->second ) + " )"; + else + Query += ", ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', " + UTIL_ToString( i->second ) + " )"; + } + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_reals ) +{ + if( var_reals.empty( ) ) + return false; + + bool Success = false; + string Query; + + for( map :: iterator i = var_reals.begin( ); i != var_reals.end( ); i++ ) + { + string EscVarName = MySQLEscapeString( conn, i->first.second ); + + if( Query.empty( ) ) + Query = "INSERT INTO w3mmdvars ( botid, gameid, pid, varname, value_real ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', " + UTIL_ToString( i->second, 10 ) + " )"; + else + Query += ", ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', " + UTIL_ToString( i->second, 10 ) + " )"; + } + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_strings ) +{ + if( var_strings.empty( ) ) + return false; + + bool Success = false; + string Query; + + for( map :: iterator i = var_strings.begin( ); i != var_strings.end( ); i++ ) + { + string EscVarName = MySQLEscapeString( conn, i->first.second ); + string EscValueString = MySQLEscapeString( conn, i->second ); + + if( Query.empty( ) ) + Query = "INSERT INTO w3mmdvars ( botid, gameid, pid, varname, value_string ) VALUES ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', '" + EscValueString + "' )"; + else + Query += ", ( " + UTIL_ToString( botid ) + ", " + UTIL_ToString( gameid ) + ", " + UTIL_ToString( i->first.first ) + ", '" + EscVarName + "', '" + EscValueString + "' )"; + } + + if( mysql_real_query( (MYSQL *)conn, Query.c_str( ), Query.size( ) ) != 0 ) + *error = mysql_error( (MYSQL *)conn ); + else + Success = true; + + return Success; +} + +// +// MySQL Callables +// + +void CMySQLCallable :: Init( ) +{ + CBaseCallable :: Init( ); + +#ifndef WIN32 + // disable SIGPIPE since this is (or should be) a new thread and it doesn't inherit the spawning thread's signal handlers + // MySQL should automatically disable SIGPIPE when we initialize it but we do so anyway here + + signal( SIGPIPE, SIG_IGN ); +#endif + + mysql_thread_init( ); + + if( !m_Connection ) + { + if( !( m_Connection = mysql_init( NULL ) ) ) + m_Error = mysql_error( (MYSQL *)m_Connection ); + + my_bool Reconnect = true; + mysql_options( (MYSQL *)m_Connection, MYSQL_OPT_RECONNECT, &Reconnect ); + + if( !( mysql_real_connect( (MYSQL *)m_Connection, m_SQLServer.c_str( ), m_SQLUser.c_str( ), m_SQLPassword.c_str( ), m_SQLDatabase.c_str( ), m_SQLPort, NULL, 0 ) ) ) + m_Error = mysql_error( (MYSQL *)m_Connection ); + } + else if( mysql_ping( (MYSQL *)m_Connection ) != 0 ) + m_Error = mysql_error( (MYSQL *)m_Connection ); +} + +void CMySQLCallable :: Close( ) +{ + mysql_thread_end( ); + + CBaseCallable :: Close( ); +} + +void CMySQLCallableAdminCount :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLAdminCount( m_Connection, &m_Error, m_SQLBotID, m_Server ); + + Close( ); +} + +void CMySQLCallableAdminCheck :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLAdminCheck( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User ); + + Close( ); +} + +void CMySQLCallableAdminAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLAdminAdd( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User ); + + Close( ); +} + +void CMySQLCallableAdminRemove :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLAdminRemove( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User ); + + Close( ); +} + +void CMySQLCallableAdminList :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLAdminList( m_Connection, &m_Error, m_SQLBotID, m_Server ); + + Close( ); +} + +void CMySQLCallableBanCount :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLBanCount( m_Connection, &m_Error, m_SQLBotID, m_Server ); + + Close( ); +} + +void CMySQLCallableBanCheck :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLBanCheck( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User, m_IP ); + + Close( ); +} + +void CMySQLCallableBanAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLBanAdd( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User, m_IP, m_GameName, m_Admin, m_Reason ); + + Close( ); +} + +void CMySQLCallableBanRemove :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + { + if( m_Server.empty( ) ) + m_Result = MySQLBanRemove( m_Connection, &m_Error, m_SQLBotID, m_User ); + else + m_Result = MySQLBanRemove( m_Connection, &m_Error, m_SQLBotID, m_Server, m_User ); + } + + Close( ); +} + +void CMySQLCallableBanList :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLBanList( m_Connection, &m_Error, m_SQLBotID, m_Server ); + + Close( ); +} + +void CMySQLCallableGameAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLGameAdd( m_Connection, &m_Error, m_SQLBotID, m_Server, m_Map, m_GameName, m_OwnerName, m_Duration, m_GameState, m_CreatorName, m_CreatorServer ); + + Close( ); +} + +void CMySQLCallableGamePlayerAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLGamePlayerAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_Name, m_IP, m_Spoofed, m_SpoofedRealm, m_Reserved, m_LoadingTime, m_Left, m_LeftReason, m_Team, m_Colour ); + + Close( ); +} + +void CMySQLCallableGamePlayerSummaryCheck :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLGamePlayerSummaryCheck( m_Connection, &m_Error, m_SQLBotID, m_Name ); + + Close( ); +} + +void CMySQLCallableDotAGameAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLDotAGameAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_Winner, m_Min, m_Sec ); + + Close( ); +} + +void CMySQLCallableDotAPlayerAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLDotAPlayerAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_Colour, m_Kills, m_Deaths, m_CreepKills, m_CreepDenies, m_Assists, m_Gold, m_NeutralKills, m_Item1, m_Item2, m_Item3, m_Item4, m_Item5, m_Item6, m_Hero, m_NewColour, m_TowerKills, m_RaxKills, m_CourierKills ); + + Close( ); +} + +void CMySQLCallableDotAPlayerSummaryCheck :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLDotAPlayerSummaryCheck( m_Connection, &m_Error, m_SQLBotID, m_Name ); + + Close( ); +} + +void CMySQLCallableDownloadAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLDownloadAdd( m_Connection, &m_Error, m_SQLBotID, m_Map, m_MapSize, m_Name, m_IP, m_Spoofed, m_SpoofedRealm, m_DownloadTime ); + + Close( ); +} + +void CMySQLCallableScoreCheck :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLScoreCheck( m_Connection, &m_Error, m_SQLBotID, m_Category, m_Name, m_Server ); + + Close( ); +} + +void CMySQLCallableW3MMDPlayerAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + m_Result = MySQLW3MMDPlayerAdd( m_Connection, &m_Error, m_SQLBotID, m_Category, m_GameID, m_PID, m_Name, m_Flag, m_Leaver, m_Practicing ); + + Close( ); +} + +void CMySQLCallableW3MMDVarAdd :: operator( )( ) +{ + Init( ); + + if( m_Error.empty( ) ) + { + if( m_ValueType == VALUETYPE_INT ) + m_Result = MySQLW3MMDVarAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_VarInts ); + else if( m_ValueType == VALUETYPE_REAL ) + m_Result = MySQLW3MMDVarAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_VarReals ); + else + m_Result = MySQLW3MMDVarAdd( m_Connection, &m_Error, m_SQLBotID, m_GameID, m_VarStrings ); + } + + Close( ); +} + +#endif diff --git a/ghost-legacy/ghostdbmysql.h b/ghost-legacy/ghostdbmysql.h new file mode 100644 index 0000000..f074871 --- /dev/null +++ b/ghost-legacy/ghostdbmysql.h @@ -0,0 +1,497 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifdef GHOST_MYSQL + +#ifndef GHOSTDBMYSQL_H +#define GHOSTDBMYSQL_H + +/************** + *** SCHEMA *** + ************** + +CREATE TABLE admins ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + name VARCHAR(15) NOT NULL, + server VARCHAR(100) NOT NULL +) + +CREATE TABLE bans ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + server VARCHAR(100) NOT NULL, + name VARCHAR(15) NOT NULL, + ip VARCHAR(15) NOT NULL, + date DATETIME NOT NULL, + gamename VARCHAR(31) NOT NULL, + admin VARCHAR(15) NOT NULL, + reason VARCHAR(255) NOT NULL +) + +CREATE TABLE games ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + server VARCHAR(100) NOT NULL, + map VARCHAR(100) NOT NULL, + datetime DATETIME NOT NULL, + gamename VARCHAR(31) NOT NULL, + ownername VARCHAR(15) NOT NULL, + duration INT NOT NULL, + gamestate INT NOT NULL, + creatorname VARCHAR(15) NOT NULL, + creatorserver VARCHAR(100) NOT NULL +) + +CREATE TABLE gameplayers ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + gameid INT NOT NULL, + name VARCHAR(15) NOT NULL, + ip VARCHAR(15) NOT NULL, + spoofed INT NOT NULL, + reserved INT NOT NULL, + loadingtime INT NOT NULL, + `left` INT NOT NULL, + leftreason VARCHAR(100) NOT NULL, + team INT NOT NULL, + colour INT NOT NULL, + spoofedrealm VARCHAR(100) NOT NULL, + INDEX( gameid ) +) + +CREATE TABLE dotagames ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + gameid INT NOT NULL, + winner INT NOT NULL, + min INT NOT NULL, + sec INT NOT NULL +) + +CREATE TABLE dotaplayers ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + gameid INT NOT NULL, + colour INT NOT NULL, + kills INT NOT NULL, + deaths INT NOT NULL, + creepkills INT NOT NULL, + creepdenies INT NOT NULL, + assists INT NOT NULL, + gold INT NOT NULL, + neutralkills INT NOT NULL, + item1 CHAR(4) NOT NULL, + item2 CHAR(4) NOT NULL, + item3 CHAR(4) NOT NULL, + item4 CHAR(4) NOT NULL, + item5 CHAR(4) NOT NULL, + item6 CHAR(4) NOT NULL, + hero CHAR(4) NOT NULL, + newcolour INT NOT NULL, + towerkills INT NOT NULL, + raxkills INT NOT NULL, + courierkills INT NOT NULL, + INDEX( gameid, colour ) +) + +CREATE TABLE downloads ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + map VARCHAR(100) NOT NULL, + mapsize INT NOT NULL, + datetime DATETIME NOT NULL, + name VARCHAR(15) NOT NULL, + ip VARCHAR(15) NOT NULL, + spoofed INT NOT NULL, + spoofedrealm VARCHAR(100) NOT NULL, + downloadtime INT NOT NULL +) + +CREATE TABLE scores ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + category VARCHAR(25) NOT NULL, + name VARCHAR(15) NOT NULL, + server VARCHAR(100) NOT NULL, + score REAL NOT NULL +) + +CREATE TABLE w3mmdplayers ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + category VARCHAR(25) NOT NULL, + gameid INT NOT NULL, + pid INT NOT NULL, + name VARCHAR(15) NOT NULL, + flag VARCHAR(32) NOT NULL, + leaver INT NOT NULL, + practicing INT NOT NULL +) + +CREATE TABLE w3mmdvars ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + botid INT NOT NULL, + gameid INT NOT NULL, + pid INT NOT NULL, + varname VARCHAR(25) NOT NULL, + value_int INT DEFAULT NULL, + value_real REAL DEFAULT NULL, + value_string VARCHAR(100) DEFAULT NULL +) + + ************** + *** SCHEMA *** + **************/ + +// +// CGHostDBMySQL +// + +class CGHostDBMySQL : public CGHostDB +{ +private: + string m_Server; + string m_Database; + string m_User; + string m_Password; + uint16_t m_Port; + uint32_t m_BotID; + queue m_IdleConnections; + uint32_t m_NumConnections; + uint32_t m_OutstandingCallables; + +public: + CGHostDBMySQL( CConfig *CFG ); + virtual ~CGHostDBMySQL( ); + + virtual string GetStatus( ); + + virtual void RecoverCallable( CBaseCallable *callable ); + + // threaded database functions + + virtual void CreateThread( CBaseCallable *callable ); + virtual CCallableAdminCount *ThreadedAdminCount( string server ); + virtual CCallableAdminCheck *ThreadedAdminCheck( string server, string user ); + virtual CCallableAdminAdd *ThreadedAdminAdd( string server, string user ); + virtual CCallableAdminRemove *ThreadedAdminRemove( string server, string user ); + virtual CCallableAdminList *ThreadedAdminList( string server ); + virtual CCallableBanCount *ThreadedBanCount( string server ); + virtual CCallableBanCheck *ThreadedBanCheck( string server, string user, string ip ); + virtual CCallableBanAdd *ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ); + virtual CCallableBanRemove *ThreadedBanRemove( string server, string user ); + virtual CCallableBanRemove *ThreadedBanRemove( string user ); + virtual CCallableBanList *ThreadedBanList( string server ); + virtual CCallableGameAdd *ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); + virtual CCallableGamePlayerAdd *ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); + virtual CCallableGamePlayerSummaryCheck *ThreadedGamePlayerSummaryCheck( string name ); + virtual CCallableDotAGameAdd *ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); + virtual CCallableDotAPlayerAdd *ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); + virtual CCallableDotAPlayerSummaryCheck *ThreadedDotAPlayerSummaryCheck( string name ); + virtual CCallableDownloadAdd *ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); + virtual CCallableScoreCheck *ThreadedScoreCheck( string category, string name, string server ); + virtual CCallableW3MMDPlayerAdd *ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ); + + // other database functions + + virtual void *GetIdleConnection( ); +}; + +// +// global helper functions +// + +uint32_t MySQLAdminCount( void *conn, string *error, uint32_t botid, string server ); +bool MySQLAdminCheck( void *conn, string *error, uint32_t botid, string server, string user ); +bool MySQLAdminAdd( void *conn, string *error, uint32_t botid, string server, string user ); +bool MySQLAdminRemove( void *conn, string *error, uint32_t botid, string server, string user ); +vector MySQLAdminList( void *conn, string *error, uint32_t botid, string server ); +uint32_t MySQLBanCount( void *conn, string *error, uint32_t botid, string server ); +CDBBan *MySQLBanCheck( void *conn, string *error, uint32_t botid, string server, string user, string ip ); +bool MySQLBanAdd( void *conn, string *error, uint32_t botid, string server, string user, string ip, string gamename, string admin, string reason ); +bool MySQLBanRemove( void *conn, string *error, uint32_t botid, string server, string user ); +bool MySQLBanRemove( void *conn, string *error, uint32_t botid, string user ); +vector MySQLBanList( void *conn, string *error, uint32_t botid, string server ); +uint32_t MySQLGameAdd( void *conn, string *error, uint32_t botid, string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); +uint32_t MySQLGamePlayerAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); +CDBGamePlayerSummary *MySQLGamePlayerSummaryCheck( void *conn, string *error, uint32_t botid, string name ); +uint32_t MySQLDotAGameAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); +uint32_t MySQLDotAPlayerAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); +CDBDotAPlayerSummary *MySQLDotAPlayerSummaryCheck( void *conn, string *error, uint32_t botid, string name ); +bool MySQLDownloadAdd( void *conn, string *error, uint32_t botid, string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); +double MySQLScoreCheck( void *conn, string *error, uint32_t botid, string category, string name, string server ); +uint32_t MySQLW3MMDPlayerAdd( void *conn, string *error, uint32_t botid, string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_ints ); +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_reals ); +bool MySQLW3MMDVarAdd( void *conn, string *error, uint32_t botid, uint32_t gameid, map var_strings ); + +// +// MySQL Callables +// + +class CMySQLCallable : virtual public CBaseCallable +{ +protected: + void *m_Connection; + string m_SQLServer; + string m_SQLDatabase; + string m_SQLUser; + string m_SQLPassword; + uint16_t m_SQLPort; + uint32_t m_SQLBotID; + +public: + CMySQLCallable( void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), m_Connection( nConnection ), m_SQLBotID( nSQLBotID ), m_SQLServer( nSQLServer ), m_SQLDatabase( nSQLDatabase ), m_SQLUser( nSQLUser ), m_SQLPassword( nSQLPassword ), m_SQLPort( nSQLPort ) { } + virtual ~CMySQLCallable( ) { } + + virtual void *GetConnection( ) { return m_Connection; } + + virtual void Init( ); + virtual void Close( ); +}; + +class CMySQLCallableAdminCount : public CCallableAdminCount, public CMySQLCallable +{ +public: + CMySQLCallableAdminCount( string nServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableAdminCount( nServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableAdminCount( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableAdminCheck : public CCallableAdminCheck, public CMySQLCallable +{ +public: + CMySQLCallableAdminCheck( string nServer, string nUser, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableAdminCheck( nServer, nUser ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableAdminCheck( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableAdminAdd : public CCallableAdminAdd, public CMySQLCallable +{ +public: + CMySQLCallableAdminAdd( string nServer, string nUser, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableAdminAdd( nServer, nUser ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableAdminAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableAdminRemove : public CCallableAdminRemove, public CMySQLCallable +{ +public: + CMySQLCallableAdminRemove( string nServer, string nUser, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableAdminRemove( nServer, nUser ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableAdminRemove( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableAdminList : public CCallableAdminList, public CMySQLCallable +{ +public: + CMySQLCallableAdminList( string nServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableAdminList( nServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableAdminList( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableBanCount : public CCallableBanCount, public CMySQLCallable +{ +public: + CMySQLCallableBanCount( string nServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableBanCount( nServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableBanCount( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableBanCheck : public CCallableBanCheck, public CMySQLCallable +{ +public: + CMySQLCallableBanCheck( string nServer, string nUser, string nIP, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableBanCheck( nServer, nUser, nIP ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableBanCheck( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableBanAdd : public CCallableBanAdd, public CMySQLCallable +{ +public: + CMySQLCallableBanAdd( string nServer, string nUser, string nIP, string nGameName, string nAdmin, string nReason, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableBanAdd( nServer, nUser, nIP, nGameName, nAdmin, nReason ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableBanAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableBanRemove : public CCallableBanRemove, public CMySQLCallable +{ +public: + CMySQLCallableBanRemove( string nServer, string nUser, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableBanRemove( nServer, nUser ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableBanRemove( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableBanList : public CCallableBanList, public CMySQLCallable +{ +public: + CMySQLCallableBanList( string nServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableBanList( nServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableBanList( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableGameAdd : public CCallableGameAdd, public CMySQLCallable +{ +public: + CMySQLCallableGameAdd( string nServer, string nMap, string nGameName, string nOwnerName, uint32_t nDuration, uint32_t nGameState, string nCreatorName, string nCreatorServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableGameAdd( nServer, nMap, nGameName, nOwnerName, nDuration, nGameState, nCreatorName, nCreatorServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableGameAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableGamePlayerAdd : public CCallableGamePlayerAdd, public CMySQLCallable +{ +public: + CMySQLCallableGamePlayerAdd( uint32_t nGameID, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nReserved, uint32_t nLoadingTime, uint32_t nLeft, string nLeftReason, uint32_t nTeam, uint32_t nColour, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableGamePlayerAdd( nGameID, nName, nIP, nSpoofed, nSpoofedRealm, nReserved, nLoadingTime, nLeft, nLeftReason, nTeam, nColour ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableGamePlayerAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableGamePlayerSummaryCheck : public CCallableGamePlayerSummaryCheck, public CMySQLCallable +{ +public: + CMySQLCallableGamePlayerSummaryCheck( string nName, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableGamePlayerSummaryCheck( nName ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableGamePlayerSummaryCheck( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableDotAGameAdd : public CCallableDotAGameAdd, public CMySQLCallable +{ +public: + CMySQLCallableDotAGameAdd( uint32_t nGameID, uint32_t nWinner, uint32_t nMin, uint32_t nSec, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableDotAGameAdd( nGameID, nWinner, nMin, nSec ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableDotAGameAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableDotAPlayerAdd : public CCallableDotAPlayerAdd, public CMySQLCallable +{ +public: + CMySQLCallableDotAPlayerAdd( uint32_t nGameID, uint32_t nColour, uint32_t nKills, uint32_t nDeaths, uint32_t nCreepKills, uint32_t nCreepDenies, uint32_t nAssists, uint32_t nGold, uint32_t nNeutralKills, string nItem1, string nItem2, string nItem3, string nItem4, string nItem5, string nItem6, string nHero, uint32_t nNewColour, uint32_t nTowerKills, uint32_t nRaxKills, uint32_t nCourierKills, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableDotAPlayerAdd( nGameID, nColour, nKills, nDeaths, nCreepKills, nCreepDenies, nAssists, nGold, nNeutralKills, nItem1, nItem2, nItem3, nItem4, nItem5, nItem6, nHero, nNewColour, nTowerKills, nRaxKills, nCourierKills ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableDotAPlayerAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableDotAPlayerSummaryCheck : public CCallableDotAPlayerSummaryCheck, public CMySQLCallable +{ +public: + CMySQLCallableDotAPlayerSummaryCheck( string nName, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableDotAPlayerSummaryCheck( nName ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableDotAPlayerSummaryCheck( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableDownloadAdd : public CCallableDownloadAdd, public CMySQLCallable +{ +public: + CMySQLCallableDownloadAdd( string nMap, uint32_t nMapSize, string nName, string nIP, uint32_t nSpoofed, string nSpoofedRealm, uint32_t nDownloadTime, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableDownloadAdd( nMap, nMapSize, nName, nIP, nSpoofed, nSpoofedRealm, nDownloadTime ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableDownloadAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableScoreCheck : public CCallableScoreCheck, public CMySQLCallable +{ +public: + CMySQLCallableScoreCheck( string nCategory, string nName, string nServer, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableScoreCheck( nCategory, nName, nServer ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableScoreCheck( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableW3MMDPlayerAdd : public CCallableW3MMDPlayerAdd, public CMySQLCallable +{ +public: + CMySQLCallableW3MMDPlayerAdd( string nCategory, uint32_t nGameID, uint32_t nPID, string nName, string nFlag, uint32_t nLeaver, uint32_t nPracticing, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableW3MMDPlayerAdd( nCategory, nGameID, nPID, nName, nFlag, nLeaver, nPracticing ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableW3MMDPlayerAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +class CMySQLCallableW3MMDVarAdd : public CCallableW3MMDVarAdd, public CMySQLCallable +{ +public: + CMySQLCallableW3MMDVarAdd( uint32_t nGameID, map nVarInts, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableW3MMDVarAdd( nGameID, nVarInts ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + CMySQLCallableW3MMDVarAdd( uint32_t nGameID, map nVarReals, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableW3MMDVarAdd( nGameID, nVarReals ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + CMySQLCallableW3MMDVarAdd( uint32_t nGameID, map nVarStrings, void *nConnection, uint32_t nSQLBotID, string nSQLServer, string nSQLDatabase, string nSQLUser, string nSQLPassword, uint16_t nSQLPort ) : CBaseCallable( ), CCallableW3MMDVarAdd( nGameID, nVarStrings ), CMySQLCallable( nConnection, nSQLBotID, nSQLServer, nSQLDatabase, nSQLUser, nSQLPassword, nSQLPort ) { } + virtual ~CMySQLCallableW3MMDVarAdd( ) { } + + virtual void operator( )( ); + virtual void Init( ) { CMySQLCallable :: Init( ); } + virtual void Close( ) { CMySQLCallable :: Close( ); } +}; + +#endif + +#endif diff --git a/ghost-legacy/ghostdbsqlite.cpp b/ghost-legacy/ghostdbsqlite.cpp new file mode 100644 index 0000000..2c08443 --- /dev/null +++ b/ghost-legacy/ghostdbsqlite.cpp @@ -0,0 +1,1648 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "ghostdb.h" +#include "ghostdbsqlite.h" +#include "sqlite3.h" + +// +// CQSLITE3 (wrapper class) +// + +CSQLITE3 :: CSQLITE3( string filename ) +{ + m_Ready = true; + + if( sqlite3_open_v2( filename.c_str( ), (sqlite3 **)&m_DB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL ) != SQLITE_OK ) + m_Ready = false; +} + +CSQLITE3 :: ~CSQLITE3( ) +{ + sqlite3_close( (sqlite3 *)m_DB ); +} + +string CSQLITE3 :: GetError( ) +{ + return sqlite3_errmsg( (sqlite3 *)m_DB ); +} + +int CSQLITE3 :: Prepare( string query, void **Statement ) +{ + return sqlite3_prepare_v2( (sqlite3 *)m_DB, query.c_str( ), -1, (sqlite3_stmt **)Statement, NULL ); +} + +int CSQLITE3 :: Step( void *Statement ) +{ + int RC = sqlite3_step( (sqlite3_stmt *)Statement ); + + if( RC == SQLITE_ROW ) + { + m_Row.clear( ); + + for( int i = 0; i < sqlite3_column_count( (sqlite3_stmt *)Statement ); i++ ) + { + char *ColumnText = (char *)sqlite3_column_text( (sqlite3_stmt *)Statement, i ); + + if( ColumnText ) + m_Row.push_back( ColumnText ); + else + m_Row.push_back( string( ) ); + } + } + + return RC; +} + +int CSQLITE3 :: Finalize( void *Statement ) +{ + return sqlite3_finalize( (sqlite3_stmt *)Statement ); +} + +int CSQLITE3 :: Reset( void *Statement ) +{ + return sqlite3_reset( (sqlite3_stmt *)Statement ); +} + +int CSQLITE3 :: ClearBindings( void *Statement ) +{ + return sqlite3_clear_bindings( (sqlite3_stmt *)Statement ); +} + +int CSQLITE3 :: Exec( string query ) +{ + return sqlite3_exec( (sqlite3 *)m_DB, query.c_str( ), NULL, NULL, NULL ); +} + +uint32_t CSQLITE3 :: LastRowID( ) +{ + return (uint32_t)sqlite3_last_insert_rowid( (sqlite3 *)m_DB ); +} + +// +// CGHostDBSQLite +// + +CGHostDBSQLite :: CGHostDBSQLite( CConfig *CFG ) : CGHostDB( CFG ) +{ + m_File = CFG->GetString( "db_sqlite3_file", "ghost.dbs" ); + CONSOLE_Print( "[SQLITE3] version " + string( SQLITE_VERSION ) ); + CONSOLE_Print( "[SQLITE3] opening database [" + m_File + "]" ); + m_DB = new CSQLITE3( m_File ); + + if( !m_DB->GetReady( ) ) + { + // setting m_HasError to true indicates there's been a critical error and we want GHost to shutdown + // this is okay here because we're in the constructor so we're not dropping any games or players + + CONSOLE_Print( string( "[SQLITE3] error opening database [" + m_File + "] - " ) + m_DB->GetError( ) ); + m_HasError = true; + m_Error = "error opening database"; + return; + } + + // find the schema number so we can determine whether we need to upgrade or not + + string SchemaNumber; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT value FROM config WHERE name=\"schema_number\"", (void **)&Statement ); + + if( Statement ) + { + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + { + vector *Row = m_DB->GetRow( ); + + if( Row->size( ) == 1 ) + SchemaNumber = (*Row)[0]; + else + CONSOLE_Print( "[SQLITE3] error getting schema number - row doesn't have 1 column" ); + } + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error getting schema number - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error getting schema number - " + m_DB->GetError( ) ); + + if( SchemaNumber.empty( ) ) + { + // couldn't find the schema number + // unfortunately the very first schema didn't have a config table + // so we might actually be looking at schema version 1 rather than an empty database + // try to confirm this by looking for the admins table + + CONSOLE_Print( "[SQLITE3] couldn't find schema number, looking for admins table" ); + bool AdminTable = false; + m_DB->Prepare( "SELECT * FROM sqlite_master WHERE type=\"table\" AND name=\"admins\"", (void **)&Statement ); + + if( Statement ) + { + int RC = m_DB->Step( Statement ); + + // we're just checking to see if the query returned a row, we don't need to check the row data itself + + if( RC == SQLITE_ROW ) + AdminTable = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error looking for admins table - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error looking for admins table - " + m_DB->GetError( ) ); + + if( AdminTable ) + { + // the admins table exists, assume we're looking at schema version 1 + + CONSOLE_Print( "[SQLITE3] found admins table, assuming schema number [1]" ); + SchemaNumber = "1"; + } + else + { + // the admins table doesn't exist, assume the database is empty + // note to self: update the SchemaNumber and the database structure when making a new schema + + CONSOLE_Print( "[SQLITE3] couldn't find admins table, assuming database is empty" ); + SchemaNumber = "8"; + + if( m_DB->Exec( "CREATE TABLE admins ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, server TEXT NOT NULL DEFAULT \"\" )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating admins table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE bans ( id INTEGER PRIMARY KEY, server TEXT NOT NULL, name TEXT NOT NULL, ip TEXT, date TEXT NOT NULL, gamename TEXT, admin TEXT NOT NULL, reason TEXT )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating bans table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE games ( id INTEGER PRIMARY KEY, server TEXT NOT NULL, map TEXT NOT NULL, datetime TEXT NOT NULL, gamename TEXT NOT NULL, ownername TEXT NOT NULL, duration INTEGER NOT NULL, gamestate INTEGER NOT NULL DEFAULT 0, creatorname TEXT NOT NULL DEFAULT \"\", creatorserver TEXT NOT NULL DEFAULT \"\" )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating games table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE gameplayers ( id INTEGER PRIMARY KEY, gameid INTEGER NOT NULL, name TEXT NOT NULL, ip TEXT NOT NULL, spoofed INTEGER NOT NULL, reserved INTEGER NOT NULL, loadingtime INTEGER NOT NULL, left INTEGER NOT NULL, leftreason TEXT NOT NULL, team INTEGER NOT NULL, colour INTEGER NOT NULL, spoofedrealm TEXT NOT NULL DEFAULT \"\" )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating gameplayers table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE dotagames ( id INTEGER PRIMARY KEY, gameid INTEGER NOT NULL, winner INTEGER NOT NULL, min INTEGER NOT NULL DEFAULT 0, sec INTEGER NOT NULL DEFAULT 0 )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating dotagames table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE dotaplayers ( id INTEGER PRIMARY KEY, gameid INTEGER NOT NULL, colour INTEGER NOT NULL, kills INTEGER NOT NULL, deaths INTEGER NOT NULL, creepkills INTEGER NOT NULL, creepdenies INTEGER NOT NULL, assists INTEGER NOT NULL, gold INTEGER NOT NULL, neutralkills INTEGER NOT NULL, item1 TEXT NOT NULL, item2 TEXT NOT NULL, item3 TEXT NOT NULL, item4 TEXT NOT NULL, item5 TEXT NOT NULL, item6 TEXT NOT NULL, hero TEXT NOT NULL DEFAULT \"\", newcolour NOT NULL DEFAULT 0, towerkills NOT NULL DEFAULT 0, raxkills NOT NULL DEFAULT 0, courierkills NOT NULL DEFAULT 0 )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating dotaplayers table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE config ( name TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating config table - " + m_DB->GetError( ) ); + + m_DB->Prepare( "INSERT INTO config VALUES ( \"schema_number\", ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, SchemaNumber.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error inserting schema number [" + SchemaNumber + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error inserting schema number [" + SchemaNumber + "] - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE downloads ( id INTEGER PRIMARY KEY, map TEXT NOT NULL, mapsize INTEGER NOT NULL, datetime TEXT NOT NULL, name TEXT NOT NULL, ip TEXT NOT NULL, spoofed INTEGER NOT NULL, spoofedrealm TEXT NOT NULL, downloadtime INTEGER NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating downloads table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE w3mmdplayers ( id INTEGER PRIMARY KEY, category TEXT NOT NULL, gameid INTEGER NOT NULL, pid INTEGER NOT NULL, name TEXT NOT NULL, flag TEXT NOT NULL, leaver INTEGER NOT NULL, practicing INTEGER NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating w3mmdplayers table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE TABLE w3mmdvars ( id INTEGER PRIMARY KEY, gameid INTEGER NOT NULL, pid INTEGER NOT NULL, varname TEXT NOT NULL, value_int INTEGER DEFAULT NULL, value_real REAL DEFAULT NULL, value_string TEXT DEFAULT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating w3mmdvars table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE INDEX idx_gameid ON gameplayers ( gameid )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating idx_gameid index on gameplayers table - " + m_DB->GetError( ) ); + + if( m_DB->Exec( "CREATE INDEX idx_gameid_colour ON dotaplayers ( gameid, colour )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating idx_gameid_colour index on dotaplayers table - " + m_DB->GetError( ) ); + } + } + else + CONSOLE_Print( "[SQLITE3] found schema number [" + SchemaNumber + "]" ); + + if( SchemaNumber == "1" ) + { + Upgrade1_2( ); + SchemaNumber = "2"; + } + + if( SchemaNumber == "2" ) + { + Upgrade2_3( ); + SchemaNumber = "3"; + } + + if( SchemaNumber == "3" ) + { + Upgrade3_4( ); + SchemaNumber = "4"; + } + + if( SchemaNumber == "4" ) + { + Upgrade4_5( ); + SchemaNumber = "5"; + } + + if( SchemaNumber == "5" ) + { + Upgrade5_6( ); + SchemaNumber = "6"; + } + + if( SchemaNumber == "6" ) + { + Upgrade6_7( ); + SchemaNumber = "7"; + } + + if( SchemaNumber == "7" ) + { + Upgrade7_8( ); + SchemaNumber = "8"; + } + + if( m_DB->Exec( "CREATE TEMPORARY TABLE iptocountry ( ip1 INTEGER NOT NULL, ip2 INTEGER NOT NULL, country TEXT NOT NULL, PRIMARY KEY ( ip1, ip2 ) )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating temporary iptocountry table - " + m_DB->GetError( ) ); + + FromAddStmt = NULL; +} + +CGHostDBSQLite :: ~CGHostDBSQLite( ) +{ + if( FromAddStmt ) + m_DB->Finalize( FromAddStmt ); + + CONSOLE_Print( "[SQLITE3] closing database [" + m_File + "]" ); + delete m_DB; +} + +void CGHostDBSQLite :: Upgrade1_2( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v1 to v2 started" ); + + // add new column to table dotaplayers + // + hero TEXT NOT NULL DEFAULT "" + + if( m_DB->Exec( "ALTER TABLE dotaplayers ADD hero TEXT NOT NULL DEFAULT \"\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column hero to table dotaplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column hero to table dotaplayers" ); + + // add new columns to table dotagames + // + min INTEGER NOT NULL DEFAULT 0 + // + sec INTEGER NOT NULL DEFAULT 0 + + if( m_DB->Exec( "ALTER TABLE dotagames ADD min INTEGER NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column min to table dotagames - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column min to table dotagames" ); + + if( m_DB->Exec( "ALTER TABLE dotagames ADD sec INTEGER NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column sec to table dotagames - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column sec to table dotagames" ); + + // add new table config + + if( m_DB->Exec( "CREATE TABLE config ( name TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating config table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] created config table" ); + + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO config VALUES ( \"schema_number\", \"2\" )", (void **)&Statement ); + + if( Statement ) + { + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + CONSOLE_Print( "[SQLITE3] inserted schema number [2]" ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error inserting schema number [2] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error inserting schema number [2] - " + m_DB->GetError( ) ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v1 to v2 finished" ); +} + +void CGHostDBSQLite :: Upgrade2_3( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v2 to v3 started" ); + + // add new column to table admins + // + server TEXT NOT NULL DEFAULT "" + + if( m_DB->Exec( "ALTER TABLE admins ADD server TEXT NOT NULL DEFAULT \"\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column server to table admins - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column server to table admins" ); + + // add new column to table games + // + gamestate INTEGER NOT NULL DEFAULT 0 + + if( m_DB->Exec( "ALTER TABLE games ADD gamestate INTEGER NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column gamestate to table games - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column gamestate to table games" ); + + // add new column to table gameplayers + // + spoofedrealm TEXT NOT NULL DEFAULT "" + + if( m_DB->Exec( "ALTER TABLE gameplayers ADD spoofedrealm TEXT NOT NULL DEFAULT \"\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column spoofedrealm to table gameplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column spoofedrealm to table gameplayers" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"3\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [3] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [3]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v2 to v3 finished" ); +} + +void CGHostDBSQLite :: Upgrade3_4( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v3 to v4 started" ); + + // add new columns to table games + // + creatorname TEXT NOT NULL DEFAULT "" + // + creatorserver TEXT NOT NULL DEFAULT "" + + if( m_DB->Exec( "ALTER TABLE games ADD creatorname TEXT NOT NULL DEFAULT \"\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column creatorname to table games - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column creatorname to table games" ); + + if( m_DB->Exec( "ALTER TABLE games ADD creatorserver TEXT NOT NULL DEFAULT \"\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column creatorserver to table games - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column creatorserver to table games" ); + + // add new table downloads + + if( m_DB->Exec( "CREATE TABLE downloads ( id INTEGER PRIMARY KEY, map TEXT NOT NULL, mapsize INTEGER NOT NULL, datetime TEXT NOT NULL, name TEXT NOT NULL, ip TEXT NOT NULL, spoofed INTEGER NOT NULL, spoofedrealm TEXT NOT NULL, downloadtime INTEGER NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating downloads table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] created downloads table" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"4\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [4] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [4]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v3 to v4 finished" ); +} + +void CGHostDBSQLite :: Upgrade4_5( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v4 to v5 started" ); + + // add new column to table dotaplayers + // + newcolour NOT NULL DEFAULT 0 + + if( m_DB->Exec( "ALTER TABLE dotaplayers ADD newcolour NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column newcolour to table dotaplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column newcolour to table dotaplayers" ); + + // set newcolour = colour on all existing dotaplayers rows + + if( m_DB->Exec( "UPDATE dotaplayers SET newcolour=colour WHERE newcolour=0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error setting newcolour = colour on all existing dotaplayers rows - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] set newcolour = colour on all existing dotaplayers rows" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"5\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [5] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [5]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v4 to v5 finished" ); +} + +void CGHostDBSQLite :: Upgrade5_6( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v5 to v6 started" ); + + // add new columns to table dotaplayers + // + towerkills NOT NULL DEFAULT 0 + // + raxkills NOT NULL DEFAULT 0 + // + courierkills NOT NULL DEFAULT 0 + + if( m_DB->Exec( "ALTER TABLE dotaplayers ADD towerkills NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column towerkills to table dotaplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column towerkills to table dotaplayers" ); + + if( m_DB->Exec( "ALTER TABLE dotaplayers ADD raxkills NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column raxkills to table dotaplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column raxkills to table dotaplayers" ); + + if( m_DB->Exec( "ALTER TABLE dotaplayers ADD courierkills NOT NULL DEFAULT 0" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error adding new column courierkills to table dotaplayers - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new column courierkills to table dotaplayers" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"6\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [6] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [6]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v5 to v6 finished" ); +} + +void CGHostDBSQLite :: Upgrade6_7( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v6 to v7 started" ); + + // add new index to table gameplayers + + if( m_DB->Exec( "CREATE INDEX idx_gameid ON gameplayers ( gameid )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating idx_gameid index on gameplayers table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new index idx_gameid to table gameplayers" ); + + // add new index to table dotaplayers + + if( m_DB->Exec( "CREATE INDEX idx_gameid_colour ON dotaplayers ( gameid, colour )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating idx_gameid_colour index on dotaplayers table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] added new index idx_gameid_colour to table dotaplayers" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"7\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [7] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [7]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v6 to v7 finished" ); +} + +void CGHostDBSQLite :: Upgrade7_8( ) +{ + CONSOLE_Print( "[SQLITE3] schema upgrade v7 to v8 started" ); + + // create new tables + + if( m_DB->Exec( "CREATE TABLE w3mmdplayers ( id INTEGER PRIMARY KEY, category TEXT NOT NULL, gameid INTEGER NOT NULL, pid INTEGER NOT NULL, name TEXT NOT NULL, flag TEXT NOT NULL, leaver INTEGER NOT NULL, practicing INTEGER NOT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating w3mmdplayers table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] created w3mmdplayers table" ); + + if( m_DB->Exec( "CREATE TABLE w3mmdvars ( id INTEGER PRIMARY KEY, gameid INTEGER NOT NULL, pid INTEGER NOT NULL, varname TEXT NOT NULL, value_int INTEGER DEFAULT NULL, value_real REAL DEFAULT NULL, value_string TEXT DEFAULT NULL )" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error creating w3mmdvars table - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] created w3mmdvars table" ); + + // update schema number + + if( m_DB->Exec( "UPDATE config SET value=\"8\" where name=\"schema_number\"" ) != SQLITE_OK ) + CONSOLE_Print( "[SQLITE3] error updating schema number [8] - " + m_DB->GetError( ) ); + else + CONSOLE_Print( "[SQLITE3] updated schema number [8]" ); + + CONSOLE_Print( "[SQLITE3] schema upgrade v7 to v8 finished" ); +} + +bool CGHostDBSQLite :: Begin( ) +{ + return m_DB->Exec( "BEGIN TRANSACTION" ) == SQLITE_OK; +} + +bool CGHostDBSQLite :: Commit( ) +{ + return m_DB->Exec( "COMMIT TRANSACTION" ) == SQLITE_OK; +} + +uint32_t CGHostDBSQLite :: AdminCount( string server ) +{ + uint32_t Count = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT COUNT(*) FROM admins WHERE server=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + Count = sqlite3_column_int( Statement, 0 ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting admins [" + server + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting admins [" + server + "] - " + m_DB->GetError( ) ); + + return Count; +} + +bool CGHostDBSQLite :: AdminCheck( string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool IsAdmin = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT * FROM admins WHERE server=? AND name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + // we're just checking to see if the query returned a row, we don't need to check the row data itself + + if( RC == SQLITE_ROW ) + IsAdmin = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error checking admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error checking admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + return IsAdmin; +} + +bool CGHostDBSQLite :: AdminAdd( string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO admins ( server, name ) VALUES ( ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + return Success; +} + +bool CGHostDBSQLite :: AdminRemove( string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "DELETE FROM admins WHERE server=? AND name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error removing admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error removing admin [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + return Success; +} + +vector CGHostDBSQLite :: AdminList( string server ) +{ + vector AdminList; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT name FROM admins WHERE server=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + while( RC == SQLITE_ROW ) + { + vector *Row = m_DB->GetRow( ); + + if( Row->size( ) == 1 ) + AdminList.push_back( (*Row)[0] ); + + RC = m_DB->Step( Statement ); + } + + if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error retrieving admin list [" + server + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error retrieving admin list [" + server + "] - " + m_DB->GetError( ) ); + + return AdminList; +} + +uint32_t CGHostDBSQLite :: BanCount( string server ) +{ + uint32_t Count = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT COUNT(*) FROM bans WHERE server=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + Count = sqlite3_column_int( Statement, 0 ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting bans [" + server + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting bans [" + server + "] - " + m_DB->GetError( ) ); + + return Count; +} + +CDBBan *CGHostDBSQLite :: BanCheck( string server, string user, string ip ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + CDBBan *Ban = NULL; + sqlite3_stmt *Statement; + + if( ip.empty( ) ) + m_DB->Prepare( "SELECT name, ip, date, gamename, admin, reason FROM bans WHERE server=? AND name=?", (void **)&Statement ); + else + m_DB->Prepare( "SELECT name, ip, date, gamename, admin, reason FROM bans WHERE (server=? AND name=?) OR ip=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + + if( !ip.empty( ) ) + sqlite3_bind_text( Statement, 3, ip.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + { + vector *Row = m_DB->GetRow( ); + + if( Row->size( ) == 6 ) + Ban = new CDBBan( server, (*Row)[0], (*Row)[1], (*Row)[2], (*Row)[3], (*Row)[4], (*Row)[5] ); + else + CONSOLE_Print( "[SQLITE3] error checking ban [" + server + " : " + user + " : " + ip + "] - row doesn't have 6 columns" ); + } + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error checking ban [" + server + " : " + user + " : " + ip + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error checking ban [" + server + " : " + user + " : " + ip + "] - " + m_DB->GetError( ) ); + + return Ban; +} + +bool CGHostDBSQLite :: BanAdd( string server, string user, string ip, string gamename, string admin, string reason ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO bans ( server, name, ip, date, gamename, admin, reason ) VALUES ( ?, ?, ?, date('now'), ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 3, ip.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 4, gamename.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 5, admin.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 6, reason.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding ban [" + server + " : " + user + " : " + ip + " : " + gamename + " : " + admin + " : " + reason + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding ban [" + server + " : " + user + " : " + ip + " : " + gamename + " : " + admin + " : " + reason + "] - " + m_DB->GetError( ) ); + + return Success; +} + +bool CGHostDBSQLite :: BanRemove( string server, string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "DELETE FROM bans WHERE server=? AND name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, user.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error removing ban [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error removing ban [" + server + " : " + user + "] - " + m_DB->GetError( ) ); + + return Success; +} + +bool CGHostDBSQLite :: BanRemove( string user ) +{ + transform( user.begin( ), user.end( ), user.begin( ), (int(*)(int))tolower ); + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "DELETE FROM bans WHERE name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, user.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error removing ban [" + user + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error removing ban [" + user + "] - " + m_DB->GetError( ) ); + + return Success; +} + +vector CGHostDBSQLite :: BanList( string server ) +{ + vector BanList; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT name, ip, date, gamename, admin, reason FROM bans WHERE server=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + while( RC == SQLITE_ROW ) + { + vector *Row = m_DB->GetRow( ); + + if( Row->size( ) == 6 ) + BanList.push_back( new CDBBan( server, (*Row)[0], (*Row)[1], (*Row)[2], (*Row)[3], (*Row)[4], (*Row)[5] ) ); + + RC = m_DB->Step( Statement ); + } + + if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error retrieving ban list [" + server + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error retrieving ban list [" + server + "] - " + m_DB->GetError( ) ); + + return BanList; +} + +uint32_t CGHostDBSQLite :: GameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + uint32_t RowID = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO games ( server, map, datetime, gamename, ownername, duration, gamestate, creatorname, creatorserver ) VALUES ( ?, ?, datetime('now'), ?, ?, ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, server.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 2, map.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 3, gamename.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 4, ownername.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 5, duration ); + sqlite3_bind_int( Statement, 6, gamestate ); + sqlite3_bind_text( Statement, 7, creatorname.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 8, creatorserver.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + RowID = m_DB->LastRowID( ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding game [" + server + " : " + map + " : " + gamename + " : " + ownername + " : " + UTIL_ToString( duration ) + " : " + UTIL_ToString( gamestate ) + " : " + creatorname + " : " + creatorserver + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding game [" + server + " : " + map + " : " + gamename + " : " + ownername + " : " + UTIL_ToString( duration ) + " : " + UTIL_ToString( gamestate ) + " : " + creatorname + " : " + creatorserver + "] - " + m_DB->GetError( ) ); + + return RowID; +} + +uint32_t CGHostDBSQLite :: GamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t RowID = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO gameplayers ( gameid, name, ip, spoofed, reserved, loadingtime, left, leftreason, team, colour, spoofedrealm ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_text( Statement, 2, name.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 3, ip.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 4, spoofed ); + sqlite3_bind_int( Statement, 5, reserved ); + sqlite3_bind_int( Statement, 6, loadingtime ); + sqlite3_bind_int( Statement, 7, left ); + sqlite3_bind_text( Statement, 8, leftreason.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 9, team ); + sqlite3_bind_int( Statement, 10, colour ); + sqlite3_bind_text( Statement, 11, spoofedrealm.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + RowID = m_DB->LastRowID( ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding gameplayer [" + UTIL_ToString( gameid ) + " : " + name + " : " + ip + " : " + UTIL_ToString( spoofed ) + " : " + spoofedrealm + " : " + UTIL_ToString( reserved ) + " : " + UTIL_ToString( loadingtime ) + " : " + UTIL_ToString( left ) + " : " + leftreason + " : " + UTIL_ToString( team ) + " : " + UTIL_ToString( colour ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding gameplayer [" + UTIL_ToString( gameid ) + " : " + name + " : " + ip + " : " + UTIL_ToString( spoofed ) + " : " + spoofedrealm + " : " + UTIL_ToString( reserved ) + " : " + UTIL_ToString( loadingtime ) + " : " + UTIL_ToString( left ) + " : " + leftreason + " : " + UTIL_ToString( team ) + " : " + UTIL_ToString( colour ) + "] - " + m_DB->GetError( ) ); + + return RowID; +} + +uint32_t CGHostDBSQLite :: GamePlayerCount( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t Count = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT COUNT(*) FROM gameplayers LEFT JOIN games ON games.id=gameid WHERE name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + Count = sqlite3_column_int( Statement, 0 ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting gameplayers [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting gameplayers [" + name + "] - " + m_DB->GetError( ) ); + + return Count; +} + +CDBGamePlayerSummary *CGHostDBSQLite :: GamePlayerSummaryCheck( string name ) +{ + if( GamePlayerCount( name ) == 0 ) + return NULL; + + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + CDBGamePlayerSummary *GamePlayerSummary = NULL; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT MIN(datetime), MAX(datetime), COUNT(*), MIN(loadingtime), AVG(loadingtime), MAX(loadingtime), MIN(left/CAST(duration AS REAL))*100, AVG(left/CAST(duration AS REAL))*100, MAX(left/CAST(duration AS REAL))*100, MIN(duration), AVG(duration), MAX(duration) FROM gameplayers LEFT JOIN games ON games.id=gameid WHERE name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + { + if( sqlite3_column_count( (sqlite3_stmt *)Statement ) == 12 ) + { + char *First = (char *)sqlite3_column_text( (sqlite3_stmt *)Statement, 0 ); + char *Last = (char *)sqlite3_column_text( (sqlite3_stmt *)Statement, 1 ); + string FirstGameDateTime; + string LastGameDateTime; + + if( First ) + FirstGameDateTime = First; + + if( Last ) + LastGameDateTime = Last; + + uint32_t TotalGames = sqlite3_column_int( (sqlite3_stmt *)Statement, 2 ); + uint32_t MinLoadingTime = sqlite3_column_int( (sqlite3_stmt *)Statement, 3 ); + uint32_t AvgLoadingTime = sqlite3_column_int( (sqlite3_stmt *)Statement, 4 ); + uint32_t MaxLoadingTime = sqlite3_column_int( (sqlite3_stmt *)Statement, 5 ); + uint32_t MinLeftPercent = sqlite3_column_int( (sqlite3_stmt *)Statement, 6 ); + uint32_t AvgLeftPercent = sqlite3_column_int( (sqlite3_stmt *)Statement, 7 ); + uint32_t MaxLeftPercent = sqlite3_column_int( (sqlite3_stmt *)Statement, 8 ); + uint32_t MinDuration = sqlite3_column_int( (sqlite3_stmt *)Statement, 9 ); + uint32_t AvgDuration = sqlite3_column_int( (sqlite3_stmt *)Statement, 10 ); + uint32_t MaxDuration = sqlite3_column_int( (sqlite3_stmt *)Statement, 11 ); + GamePlayerSummary = new CDBGamePlayerSummary( string( ), name, FirstGameDateTime, LastGameDateTime, TotalGames, MinLoadingTime, AvgLoadingTime, MaxLoadingTime, MinLeftPercent, AvgLeftPercent, MaxLeftPercent, MinDuration, AvgDuration, MaxDuration ); + } + else + CONSOLE_Print( "[SQLITE3] error checking gameplayersummary [" + name + "] - row doesn't have 12 columns" ); + } + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error checking gameplayersummary [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error checking gameplayersummary [" + name + "] - " + m_DB->GetError( ) ); + + return GamePlayerSummary; +} + +uint32_t CGHostDBSQLite :: DotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + uint32_t RowID = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO dotagames ( gameid, winner, min, sec ) VALUES ( ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_int( Statement, 2, winner ); + sqlite3_bind_int( Statement, 3, min ); + sqlite3_bind_int( Statement, 4, sec ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + RowID = m_DB->LastRowID( ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding dotagame [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( winner ) + " : " + UTIL_ToString( min ) + " : " + UTIL_ToString( sec ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding dotagame [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( winner ) + " : " + UTIL_ToString( min ) + " : " + UTIL_ToString( sec ) + "] - " + m_DB->GetError( ) ); + + return RowID; +} + +uint32_t CGHostDBSQLite :: DotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + uint32_t RowID = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO dotaplayers ( gameid, colour, kills, deaths, creepkills, creepdenies, assists, gold, neutralkills, item1, item2, item3, item4, item5, item6, hero, newcolour, towerkills, raxkills, courierkills ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_int( Statement, 2, colour ); + sqlite3_bind_int( Statement, 3, kills ); + sqlite3_bind_int( Statement, 4, deaths ); + sqlite3_bind_int( Statement, 5, creepkills ); + sqlite3_bind_int( Statement, 6, creepdenies ); + sqlite3_bind_int( Statement, 7, assists ); + sqlite3_bind_int( Statement, 8, gold ); + sqlite3_bind_int( Statement, 9, neutralkills ); + sqlite3_bind_text( Statement, 10, item1.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 11, item2.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 12, item3.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 13, item4.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 14, item5.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 15, item6.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 16, hero.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 17, newcolour ); + sqlite3_bind_int( Statement, 18, towerkills ); + sqlite3_bind_int( Statement, 19, raxkills ); + sqlite3_bind_int( Statement, 20, courierkills ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + RowID = m_DB->LastRowID( ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding dotaplayer [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( colour ) + " : " + UTIL_ToString( kills ) + " : " + UTIL_ToString( deaths ) + " : " + UTIL_ToString( creepkills ) + " : " + UTIL_ToString( creepdenies ) + " : " + UTIL_ToString( assists ) + " : " + UTIL_ToString( gold ) + " : " + UTIL_ToString( neutralkills ) + " : " + item1 + " : " + item2 + " : " + item3 + " : " + item4 + " : " + item5 + " : " + item6 + " : " + hero + " : " + UTIL_ToString( newcolour ) + " : " + UTIL_ToString( towerkills ) + " : " + UTIL_ToString( raxkills ) + " : " + UTIL_ToString( courierkills ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding dotaplayer [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( colour ) + " : " + UTIL_ToString( kills ) + " : " + UTIL_ToString( deaths ) + " : " + UTIL_ToString( creepkills ) + " : " + UTIL_ToString( creepdenies ) + " : " + UTIL_ToString( assists ) + " : " + UTIL_ToString( gold ) + " : " + UTIL_ToString( neutralkills ) + " : " + item1 + " : " + item2 + " : " + item3 + " : " + item4 + " : " + item5 + " : " + item6 + " : " + hero + " : " + UTIL_ToString( newcolour ) + " : " + UTIL_ToString( towerkills ) + " : " + UTIL_ToString( raxkills ) + " : " + UTIL_ToString( courierkills ) + "] - " + m_DB->GetError( ) ); + + return RowID; +} + +uint32_t CGHostDBSQLite :: DotAPlayerCount( string name ) +{ + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + uint32_t Count = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT COUNT(dotaplayers.id) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour WHERE name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + Count = sqlite3_column_int( Statement, 0 ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting dotaplayers [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting dotaplayers [" + name + "] - " + m_DB->GetError( ) ); + + return Count; +} + +CDBDotAPlayerSummary *CGHostDBSQLite :: DotAPlayerSummaryCheck( string name ) +{ + if( DotAPlayerCount( name ) == 0 ) + return NULL; + + transform( name.begin( ), name.end( ), name.begin( ), (int(*)(int))tolower ); + CDBDotAPlayerSummary *DotAPlayerSummary = NULL; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT COUNT(dotaplayers.id), SUM(kills), SUM(deaths), SUM(creepkills), SUM(creepdenies), SUM(assists), SUM(neutralkills), SUM(towerkills), SUM(raxkills), SUM(courierkills) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour WHERE name=?", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + { + if( sqlite3_column_count( (sqlite3_stmt *)Statement ) == 10 ) + { + uint32_t TotalGames = sqlite3_column_int( (sqlite3_stmt *)Statement, 0 ); + uint32_t TotalWins = 0; + uint32_t TotalLosses = 0; + uint32_t TotalKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 1 ); + uint32_t TotalDeaths = sqlite3_column_int( (sqlite3_stmt *)Statement, 2 ); + uint32_t TotalCreepKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 3 ); + uint32_t TotalCreepDenies = sqlite3_column_int( (sqlite3_stmt *)Statement, 4 ); + uint32_t TotalAssists = sqlite3_column_int( (sqlite3_stmt *)Statement, 5 ); + uint32_t TotalNeutralKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 6 ); + uint32_t TotalTowerKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 7 ); + uint32_t TotalRaxKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 8 ); + uint32_t TotalCourierKills = sqlite3_column_int( (sqlite3_stmt *)Statement, 9 ); + + // calculate total wins + + sqlite3_stmt *Statement2; + m_DB->Prepare( "SELECT COUNT(*) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour LEFT JOIN dotagames ON games.id=dotagames.gameid WHERE name=? AND ((winner=1 AND dotaplayers.newcolour>=1 AND dotaplayers.newcolour<=5) OR (winner=2 AND dotaplayers.newcolour>=7 AND dotaplayers.newcolour<=11))", (void **)&Statement2 ); + + if( Statement2 ) + { + sqlite3_bind_text( Statement2, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC2 = m_DB->Step( Statement2 ); + + if( RC2 == SQLITE_ROW ) + TotalWins = sqlite3_column_int( Statement2, 0 ); + else if( RC2 == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting dotaplayersummary wins [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement2 ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting dotaplayersummary wins [" + name + "] - " + m_DB->GetError( ) ); + + // calculate total losses + + sqlite3_stmt *Statement3; + m_DB->Prepare( "SELECT COUNT(*) FROM gameplayers LEFT JOIN games ON games.id=gameplayers.gameid LEFT JOIN dotaplayers ON dotaplayers.gameid=games.id AND dotaplayers.colour=gameplayers.colour LEFT JOIN dotagames ON games.id=dotagames.gameid WHERE name=? AND ((winner=2 AND dotaplayers.newcolour>=1 AND dotaplayers.newcolour<=5) OR (winner=1 AND dotaplayers.newcolour>=7 AND dotaplayers.newcolour<=11))", (void **)&Statement3 ); + + if( Statement3 ) + { + sqlite3_bind_text( Statement3, 1, name.c_str( ), -1, SQLITE_TRANSIENT ); + int RC3 = m_DB->Step( Statement3 ); + + if( RC3 == SQLITE_ROW ) + TotalLosses = sqlite3_column_int( Statement3, 0 ); + else if( RC3 == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error counting dotaplayersummary losses [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement3 ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error counting dotaplayersummary losses [" + name + "] - " + m_DB->GetError( ) ); + + // done + + DotAPlayerSummary = new CDBDotAPlayerSummary( string( ), name, TotalGames, TotalWins, TotalLosses, TotalKills, TotalDeaths, TotalCreepKills, TotalCreepDenies, TotalAssists, TotalNeutralKills, TotalTowerKills, TotalRaxKills, TotalCourierKills ); + } + else + CONSOLE_Print( "[SQLITE3] error checking dotaplayersummary [" + name + "] - row doesn't have 7 columns" ); + } + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error checking dotaplayersummary [" + name + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error checking dotaplayersummary [" + name + "] - " + m_DB->GetError( ) ); + + return DotAPlayerSummary; +} + +string CGHostDBSQLite :: FromCheck( uint32_t ip ) +{ + // a big thank you to tjado for help with the iptocountry feature + + string From = "??"; + sqlite3_stmt *Statement; + m_DB->Prepare( "SELECT country FROM iptocountry WHERE ip1<=? AND ip2>=?", (void **)&Statement ); + + if( Statement ) + { + // we bind the ip as an int64 because SQLite treats it as signed + + sqlite3_bind_int64( Statement, 1, ip ); + sqlite3_bind_int64( Statement, 2, ip ); + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ROW ) + { + vector *Row = m_DB->GetRow( ); + + if( Row->size( ) == 1 ) + From = (*Row)[0]; + else + CONSOLE_Print( "[SQLITE3] error checking iptocountry [" + UTIL_ToString( ip ) + "] - row doesn't have 1 column" ); + } + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error checking iptocountry [" + UTIL_ToString( ip ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error checking iptocountry [" + UTIL_ToString( ip ) + "] - " + m_DB->GetError( ) ); + + return From; +} + +bool CGHostDBSQLite :: FromAdd( uint32_t ip1, uint32_t ip2, string country ) +{ + // a big thank you to tjado for help with the iptocountry feature + + bool Success = false; + + if( !FromAddStmt ) + m_DB->Prepare( "INSERT INTO iptocountry VALUES ( ?, ?, ? )", (void **)&FromAddStmt ); + + if( FromAddStmt ) + { + // we bind the ip as an int64 because SQLite treats it as signed + + sqlite3_bind_int64( (sqlite3_stmt *)FromAddStmt, 1, ip1 ); + sqlite3_bind_int64( (sqlite3_stmt *)FromAddStmt, 2, ip2 ); + sqlite3_bind_text( (sqlite3_stmt *)FromAddStmt, 3, country.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( FromAddStmt ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding iptocountry [" + UTIL_ToString( ip1 ) + " : " + UTIL_ToString( ip2 ) + " : " + country + "] - " + m_DB->GetError( ) ); + + m_DB->Reset( FromAddStmt ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding iptocountry [" + UTIL_ToString( ip1 ) + " : " + UTIL_ToString( ip2 ) + " : " + country + "] - " + m_DB->GetError( ) ); + + return Success; +} + +bool CGHostDBSQLite :: DownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + bool Success = false; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO downloads ( map, mapsize, datetime, name, ip, spoofed, spoofedrealm, downloadtime ) VALUES ( ?, ?, datetime('now'), ?, ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, map.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 2, mapsize ); + sqlite3_bind_text( Statement, 3, name.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 4, ip.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 5, spoofed ); + sqlite3_bind_text( Statement, 6, spoofedrealm.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 7, downloadtime ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + Success = true; + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding download [" + map + " : " + UTIL_ToString( mapsize ) + " : " + name + " : " + ip + " : " + UTIL_ToString( spoofed ) + " : " + spoofedrealm + " : " + UTIL_ToString( downloadtime ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding download [" + map + " : " + UTIL_ToString( mapsize ) + " : " + name + " : " + ip + " : " + UTIL_ToString( spoofed ) + " : " + spoofedrealm + " : " + UTIL_ToString( downloadtime ) + "] - " + m_DB->GetError( ) ); + + return Success; +} + +uint32_t CGHostDBSQLite :: W3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + uint32_t RowID = 0; + sqlite3_stmt *Statement; + m_DB->Prepare( "INSERT INTO w3mmdplayers ( category, gameid, pid, name, flag, leaver, practicing ) VALUES ( ?, ?, ?, ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_text( Statement, 1, category.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 2, gameid ); + sqlite3_bind_int( Statement, 3, pid ); + sqlite3_bind_text( Statement, 4, name.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 5, flag.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 6, leaver ); + sqlite3_bind_int( Statement, 7, practicing ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_DONE ) + RowID = m_DB->LastRowID( ); + else if( RC == SQLITE_ERROR ) + CONSOLE_Print( "[SQLITE3] error adding w3mmdplayer [" + category + " : " + UTIL_ToString( gameid ) + " : " + UTIL_ToString( pid ) + " : " + name + " : " + flag + " : " + UTIL_ToString( leaver ) + " : " + UTIL_ToString( practicing ) + "] - " + m_DB->GetError( ) ); + + m_DB->Finalize( Statement ); + } + else + CONSOLE_Print( "[SQLITE3] prepare error adding w3mmdplayer [" + category + " : " + UTIL_ToString( gameid ) + " : " + UTIL_ToString( pid ) + " : " + name + " : " + flag + " : " + UTIL_ToString( leaver ) + " : " + UTIL_ToString( practicing ) + "] - " + m_DB->GetError( ) ); + + return RowID; +} + +bool CGHostDBSQLite :: W3MMDVarAdd( uint32_t gameid, map var_ints ) +{ + if( var_ints.empty( ) ) + return false; + + bool Success = true; + sqlite3_stmt *Statement = NULL; + + for( map :: iterator i = var_ints.begin( ); i != var_ints.end( ); i++ ) + { + if( !Statement ) + m_DB->Prepare( "INSERT INTO w3mmdvars ( gameid, pid, varname, value_int ) VALUES ( ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_int( Statement, 2, i->first.first ); + sqlite3_bind_text( Statement, 3, i->first.second.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_int( Statement, 4, i->second ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ERROR ) + { + Success = false; + CONSOLE_Print( "[SQLITE3] error adding w3mmdvar-int [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + UTIL_ToString( i->second ) + "] - " + m_DB->GetError( ) ); + break; + } + + m_DB->Reset( Statement ); + } + else + { + Success = false; + CONSOLE_Print( "[SQLITE3] prepare error adding w3mmdvar-int [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + UTIL_ToString( i->second ) + "] - " + m_DB->GetError( ) ); + break; + } + } + + if( Statement ) + m_DB->Finalize( Statement ); + + return Success; +} + +bool CGHostDBSQLite :: W3MMDVarAdd( uint32_t gameid, map var_reals ) +{ + if( var_reals.empty( ) ) + return false; + + bool Success = true; + sqlite3_stmt *Statement = NULL; + + for( map :: iterator i = var_reals.begin( ); i != var_reals.end( ); i++ ) + { + if( !Statement ) + m_DB->Prepare( "INSERT INTO w3mmdvars ( gameid, pid, varname, value_real ) VALUES ( ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_int( Statement, 2, i->first.first ); + sqlite3_bind_text( Statement, 3, i->first.second.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_double( Statement, 4, i->second ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ERROR ) + { + Success = false; + CONSOLE_Print( "[SQLITE3] error adding w3mmdvar-real [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + UTIL_ToString( i->second, 10 ) + "] - " + m_DB->GetError( ) ); + break; + } + + m_DB->Reset( Statement ); + } + else + { + Success = false; + CONSOLE_Print( "[SQLITE3] prepare error adding w3mmdvar-real [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + UTIL_ToString( i->second, 10 ) + "] - " + m_DB->GetError( ) ); + break; + } + } + + if( Statement ) + m_DB->Finalize( Statement ); + + return Success; +} + +bool CGHostDBSQLite :: W3MMDVarAdd( uint32_t gameid, map var_strings ) +{ + if( var_strings.empty( ) ) + return false; + + bool Success = true; + sqlite3_stmt *Statement = NULL; + + for( map :: iterator i = var_strings.begin( ); i != var_strings.end( ); i++ ) + { + if( !Statement ) + m_DB->Prepare( "INSERT INTO w3mmdvars ( gameid, pid, varname, value_string ) VALUES ( ?, ?, ?, ? )", (void **)&Statement ); + + if( Statement ) + { + sqlite3_bind_int( Statement, 1, gameid ); + sqlite3_bind_int( Statement, 2, i->first.first ); + sqlite3_bind_text( Statement, 3, i->first.second.c_str( ), -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( Statement, 4, i->second.c_str( ), -1, SQLITE_TRANSIENT ); + + int RC = m_DB->Step( Statement ); + + if( RC == SQLITE_ERROR ) + { + Success = false; + CONSOLE_Print( "[SQLITE3] error adding w3mmdvar-string [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + i->second + "] - " + m_DB->GetError( ) ); + break; + } + + m_DB->Reset( Statement ); + } + else + { + Success = false; + CONSOLE_Print( "[SQLITE3] prepare error adding w3mmdvar-string [" + UTIL_ToString( gameid ) + " : " + UTIL_ToString( i->first.first ) + " : " + i->first.second + " : " + i->second + "] - " + m_DB->GetError( ) ); + break; + } + } + + if( Statement ) + m_DB->Finalize( Statement ); + + return Success; +} + +CCallableAdminCount *CGHostDBSQLite :: ThreadedAdminCount( string server ) +{ + CCallableAdminCount *Callable = new CCallableAdminCount( server ); + Callable->SetResult( AdminCount( server ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableAdminCheck *CGHostDBSQLite :: ThreadedAdminCheck( string server, string user ) +{ + CCallableAdminCheck *Callable = new CCallableAdminCheck( server, user ); + Callable->SetResult( AdminCheck( server, user ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableAdminAdd *CGHostDBSQLite :: ThreadedAdminAdd( string server, string user ) +{ + CCallableAdminAdd *Callable = new CCallableAdminAdd( server, user ); + Callable->SetResult( AdminAdd( server, user ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableAdminRemove *CGHostDBSQLite :: ThreadedAdminRemove( string server, string user ) +{ + CCallableAdminRemove *Callable = new CCallableAdminRemove( server, user ); + Callable->SetResult( AdminRemove( server, user ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableAdminList *CGHostDBSQLite :: ThreadedAdminList( string server ) +{ + CCallableAdminList *Callable = new CCallableAdminList( server ); + Callable->SetResult( AdminList( server ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanCount *CGHostDBSQLite :: ThreadedBanCount( string server ) +{ + CCallableBanCount *Callable = new CCallableBanCount( server ); + Callable->SetResult( BanCount( server ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanCheck *CGHostDBSQLite :: ThreadedBanCheck( string server, string user, string ip ) +{ + CCallableBanCheck *Callable = new CCallableBanCheck( server, user, ip ); + Callable->SetResult( BanCheck( server, user, ip ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanAdd *CGHostDBSQLite :: ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ) +{ + CCallableBanAdd *Callable = new CCallableBanAdd( server, user, ip, gamename, admin, reason ); + Callable->SetResult( BanAdd( server, user, ip, gamename, admin, reason ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanRemove *CGHostDBSQLite :: ThreadedBanRemove( string server, string user ) +{ + CCallableBanRemove *Callable = new CCallableBanRemove( server, user ); + Callable->SetResult( BanRemove( server, user ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanRemove *CGHostDBSQLite :: ThreadedBanRemove( string user ) +{ + CCallableBanRemove *Callable = new CCallableBanRemove( string( ), user ); + Callable->SetResult( BanRemove( user ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableBanList *CGHostDBSQLite :: ThreadedBanList( string server ) +{ + CCallableBanList *Callable = new CCallableBanList( server ); + Callable->SetResult( BanList( server ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableGameAdd *CGHostDBSQLite :: ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ) +{ + CCallableGameAdd *Callable = new CCallableGameAdd( server, map, gamename, ownername, duration, gamestate, creatorname, creatorserver ); + Callable->SetResult( GameAdd( server, map, gamename, ownername, duration, gamestate, creatorname, creatorserver ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableGamePlayerAdd *CGHostDBSQLite :: ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ) +{ + CCallableGamePlayerAdd *Callable = new CCallableGamePlayerAdd( gameid, name, ip, spoofed, spoofedrealm, reserved, loadingtime, left, leftreason, team, colour ); + Callable->SetResult( GamePlayerAdd( gameid, name, ip, spoofed, spoofedrealm, reserved, loadingtime, left, leftreason, team, colour ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableGamePlayerSummaryCheck *CGHostDBSQLite :: ThreadedGamePlayerSummaryCheck( string name ) +{ + CCallableGamePlayerSummaryCheck *Callable = new CCallableGamePlayerSummaryCheck( name ); + Callable->SetResult( GamePlayerSummaryCheck( name ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableDotAGameAdd *CGHostDBSQLite :: ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ) +{ + CCallableDotAGameAdd *Callable = new CCallableDotAGameAdd( gameid, winner, min, sec ); + Callable->SetResult( DotAGameAdd( gameid, winner, min, sec ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableDotAPlayerAdd *CGHostDBSQLite :: ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ) +{ + CCallableDotAPlayerAdd *Callable = new CCallableDotAPlayerAdd( gameid, colour, kills, deaths, creepkills, creepdenies, assists, gold, neutralkills, item1, item2, item3, item4, item5, item6, hero, newcolour, towerkills, raxkills, courierkills ); + Callable->SetResult( DotAPlayerAdd( gameid, colour, kills, deaths, creepkills, creepdenies, assists, gold, neutralkills, item1, item2, item3, item4, item5, item6, hero, newcolour, towerkills, raxkills, courierkills ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableDotAPlayerSummaryCheck *CGHostDBSQLite :: ThreadedDotAPlayerSummaryCheck( string name ) +{ + CCallableDotAPlayerSummaryCheck *Callable = new CCallableDotAPlayerSummaryCheck( name ); + Callable->SetResult( DotAPlayerSummaryCheck( name ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableDownloadAdd *CGHostDBSQLite :: ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ) +{ + CCallableDownloadAdd *Callable = new CCallableDownloadAdd( map, mapsize, name, ip, spoofed, spoofedrealm, downloadtime ); + Callable->SetResult( DownloadAdd( map, mapsize, name, ip, spoofed, spoofedrealm, downloadtime ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableW3MMDPlayerAdd *CGHostDBSQLite :: ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ) +{ + CCallableW3MMDPlayerAdd *Callable = new CCallableW3MMDPlayerAdd( category, gameid, pid, name, flag, leaver, practicing ); + Callable->SetResult( W3MMDPlayerAdd( category, gameid, pid, name, flag, leaver, practicing ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBSQLite :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ) +{ + CCallableW3MMDVarAdd *Callable = new CCallableW3MMDVarAdd( gameid, var_ints ); + Callable->SetResult( W3MMDVarAdd( gameid, var_ints ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBSQLite :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ) +{ + CCallableW3MMDVarAdd *Callable = new CCallableW3MMDVarAdd( gameid, var_reals ); + Callable->SetResult( W3MMDVarAdd( gameid, var_reals ) ); + Callable->SetReady( true ); + return Callable; +} + +CCallableW3MMDVarAdd *CGHostDBSQLite :: ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ) +{ + CCallableW3MMDVarAdd *Callable = new CCallableW3MMDVarAdd( gameid, var_strings ); + Callable->SetResult( W3MMDVarAdd( gameid, var_strings ) ); + Callable->SetReady( true ); + return Callable; +} diff --git a/ghost-legacy/ghostdbsqlite.h b/ghost-legacy/ghostdbsqlite.h new file mode 100644 index 0000000..fe0d03e --- /dev/null +++ b/ghost-legacy/ghostdbsqlite.h @@ -0,0 +1,270 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef GHOSTDBSQLITE_H +#define GHOSTDBSQLITE_H + +/************** + *** SCHEMA *** + ************** + +CREATE TABLE admins ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + server TEXT NOT NULL DEFAULT "" +) + +CREATE TABLE bans ( + id INTEGER PRIMARY KEY, + server TEXT NOT NULL, + name TEXT NOT NULL, + ip TEXT, + date TEXT NOT NULL, + gamename TEXT, + admin TEXT NOT NULL, + reason TEXT +) + +CREATE TABLE games ( + id INTEGER PRIMARY KEY, + server TEXT NOT NULL, + map TEXT NOT NULL, + datetime TEXT NOT NULL, + gamename TEXT NOT NULL, + ownername TEXT NOT NULL, + duration INTEGER NOT NULL, + gamestate INTEGER NOT NULL DEFAULT 0, + creatorname TEXT NOT NULL DEFAULT "", + creatorserver TEXT NOT NULL DEFAULT "" +) + +CREATE TABLE gameplayers ( + id INTEGER PRIMARY KEY, + gameid INTEGER NOT NULL, + name TEXT NOT NULL, + ip TEXT NOT NULL, + spoofed INTEGER NOT NULL, + reserved INTEGER NOT NULL, + loadingtime INTEGER NOT NULL, + left INTEGER NOT NULL, + leftreason TEXT NOT NULL, + team INTEGER NOT NULL, + colour INTEGER NOT NULL, + spoofedrealm TEXT NOT NULL DEFAULT "" +) + +CREATE TABLE dotagames ( + id INTEGER PRIMARY KEY, + gameid INTEGER NOT NULL, + winner INTEGER NOT NULL, + min INTEGER NOT NULL DEFAULT 0, + sec INTEGER NOT NULL DEFAULT 0 +) + +CREATE TABLE dotaplayers ( + id INTEGER PRIMARY KEY, + gameid INTEGER NOT NULL, + colour INTEGER NOT NULL, + kills INTEGER NOT NULL, + deaths INTEGER NOT NULL, + creepkills INTEGER NOT NULL, + creepdenies INTEGER NOT NULL, + assists INTEGER NOT NULL, + gold INTEGER NOT NULL, + neutralkills INTEGER NOT NULL, + item1 TEXT NOT NULL, + item2 TEXT NOT NULL, + item3 TEXT NOT NULL, + item4 TEXT NOT NULL, + item5 TEXT NOT NULL, + item6 TEXT NOT NULL, + hero TEXT NOT NULL DEFAULT "", + newcolour NOT NULL DEFAULT 0, + towerkills NOT NULL DEFAULT 0, + raxkills NOT NULL DEFAULT 0, + courierkills NOT NULL DEFAULT 0 +) + +CREATE TABLE config ( + name TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL +) + +CREATE TABLE downloads ( + id INTEGER PRIMARY KEY, + map TEXT NOT NULL, + mapsize INTEGER NOT NULL, + datetime TEXT NOT NULL, + name TEXT NOT NULL, + ip TEXT NOT NULL, + spoofed INTEGER NOT NULL, + spoofedrealm TEXT NOT NULL, + downloadtime INTEGER NOT NULL +) + +CREATE TABLE w3mmdplayers ( + id INTEGER PRIMARY KEY, + category TEXT NOT NULL, + gameid INTEGER NOT NULL, + pid INTEGER NOT NULL, + name TEXT NOT NULL, + flag TEXT NOT NULL, + leaver INTEGER NOT NULL, + practicing INTEGER NOT NULL +) + +CREATE TABLE w3mmdvars ( + id INTEGER PRIMARY KEY, + gameid INTEGER NOT NULL, + pid INTEGER NOT NULL, + varname TEXT NOT NULL, + value_int INTEGER DEFAULT NULL, + value_real REAL DEFAULT NULL, + value_string TEXT DEFAULT NULL +) + +CREATE TEMPORARY TABLE iptocountry ( + ip1 INTEGER NOT NULL, + ip2 INTEGER NOT NULL, + country TEXT NOT NULL, + PRIMARY KEY ( ip1, ip2 ) +) + +CREATE INDEX idx_gameid ON gameplayers ( gameid ) +CREATE INDEX idx_gameid_colour ON dotaplayers ( gameid, colour ) + + ************** + *** SCHEMA *** + **************/ + +// +// CSQLITE3 (wrapper class) +// + +class CSQLITE3 +{ +private: + void *m_DB; + bool m_Ready; + vector m_Row; + +public: + CSQLITE3( string filename ); + ~CSQLITE3( ); + + bool GetReady( ) { return m_Ready; } + vector *GetRow( ) { return &m_Row; } + string GetError( ); + + int Prepare( string query, void **Statement ); + int Step( void *Statement ); + int Finalize( void *Statement ); + int Reset( void *Statement ); + int ClearBindings( void *Statement ); + int Exec( string query ); + uint32_t LastRowID( ); +}; + +// +// CGHostDBSQLite +// + +class CGHostDBSQLite : public CGHostDB +{ +private: + string m_File; + CSQLITE3 *m_DB; + + // we keep some prepared statements in memory rather than recreating them each function call + // this is an optimization because preparing statements takes time + // however it only pays off if you're going to be using the statement extremely often + + void *FromAddStmt; + +public: + CGHostDBSQLite( CConfig *CFG ); + virtual ~CGHostDBSQLite( ); + + virtual void Upgrade1_2( ); + virtual void Upgrade2_3( ); + virtual void Upgrade3_4( ); + virtual void Upgrade4_5( ); + virtual void Upgrade5_6( ); + virtual void Upgrade6_7( ); + virtual void Upgrade7_8( ); + + virtual bool Begin( ); + virtual bool Commit( ); + virtual uint32_t AdminCount( string server ); + virtual bool AdminCheck( string server, string user ); + virtual bool AdminAdd( string server, string user ); + virtual bool AdminRemove( string server, string user ); + virtual vector AdminList( string server ); + virtual uint32_t BanCount( string server ); + virtual CDBBan *BanCheck( string server, string user, string ip ); + virtual bool BanAdd( string server, string user, string ip, string gamename, string admin, string reason ); + virtual bool BanRemove( string server, string user ); + virtual bool BanRemove( string user ); + virtual vector BanList( string server ); + virtual uint32_t GameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); + virtual uint32_t GamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); + virtual uint32_t GamePlayerCount( string name ); + virtual CDBGamePlayerSummary *GamePlayerSummaryCheck( string name ); + virtual uint32_t DotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); + virtual uint32_t DotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); + virtual uint32_t DotAPlayerCount( string name ); + virtual CDBDotAPlayerSummary *DotAPlayerSummaryCheck( string name ); + virtual string FromCheck( uint32_t ip ); + virtual bool FromAdd( uint32_t ip1, uint32_t ip2, string country ); + virtual bool DownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); + virtual uint32_t W3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_ints ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_reals ); + virtual bool W3MMDVarAdd( uint32_t gameid, map var_strings ); + + // threaded database functions + // note: these are not actually implemented with threads at the moment, they WILL block until the query is complete + // todotodo: implement threads here + + virtual CCallableAdminCount *ThreadedAdminCount( string server ); + virtual CCallableAdminCheck *ThreadedAdminCheck( string server, string user ); + virtual CCallableAdminAdd *ThreadedAdminAdd( string server, string user ); + virtual CCallableAdminRemove *ThreadedAdminRemove( string server, string user ); + virtual CCallableAdminList *ThreadedAdminList( string server ); + virtual CCallableBanCount *ThreadedBanCount( string server ); + virtual CCallableBanCheck *ThreadedBanCheck( string server, string user, string ip ); + virtual CCallableBanAdd *ThreadedBanAdd( string server, string user, string ip, string gamename, string admin, string reason ); + virtual CCallableBanRemove *ThreadedBanRemove( string server, string user ); + virtual CCallableBanRemove *ThreadedBanRemove( string user ); + virtual CCallableBanList *ThreadedBanList( string server ); + virtual CCallableGameAdd *ThreadedGameAdd( string server, string map, string gamename, string ownername, uint32_t duration, uint32_t gamestate, string creatorname, string creatorserver ); + virtual CCallableGamePlayerAdd *ThreadedGamePlayerAdd( uint32_t gameid, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t reserved, uint32_t loadingtime, uint32_t left, string leftreason, uint32_t team, uint32_t colour ); + virtual CCallableGamePlayerSummaryCheck *ThreadedGamePlayerSummaryCheck( string name ); + virtual CCallableDotAGameAdd *ThreadedDotAGameAdd( uint32_t gameid, uint32_t winner, uint32_t min, uint32_t sec ); + virtual CCallableDotAPlayerAdd *ThreadedDotAPlayerAdd( uint32_t gameid, uint32_t colour, uint32_t kills, uint32_t deaths, uint32_t creepkills, uint32_t creepdenies, uint32_t assists, uint32_t gold, uint32_t neutralkills, string item1, string item2, string item3, string item4, string item5, string item6, string hero, uint32_t newcolour, uint32_t towerkills, uint32_t raxkills, uint32_t courierkills ); + virtual CCallableDotAPlayerSummaryCheck *ThreadedDotAPlayerSummaryCheck( string name ); + virtual CCallableDownloadAdd *ThreadedDownloadAdd( string map, uint32_t mapsize, string name, string ip, uint32_t spoofed, string spoofedrealm, uint32_t downloadtime ); + virtual CCallableW3MMDPlayerAdd *ThreadedW3MMDPlayerAdd( string category, uint32_t gameid, uint32_t pid, string name, string flag, uint32_t leaver, uint32_t practicing ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_ints ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_reals ); + virtual CCallableW3MMDVarAdd *ThreadedW3MMDVarAdd( uint32_t gameid, map var_strings ); +}; + +#endif diff --git a/ghost-legacy/gpsprotocol.cpp b/ghost-legacy/gpsprotocol.cpp new file mode 100644 index 0000000..60de892 --- /dev/null +++ b/ghost-legacy/gpsprotocol.cpp @@ -0,0 +1,173 @@ +/* + + Copyright 2010 Trevor Hogan + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#include "ghost.h" +#include "util.h" +#include "gpsprotocol.h" + +// +// CGPSProtocol +// + +CGPSProtocol :: CGPSProtocol( ) +{ + +} + +CGPSProtocol :: ~CGPSProtocol( ) +{ + +} + +/////////////////////// +// RECEIVE FUNCTIONS // +/////////////////////// + +//////////////////// +// SEND FUNCTIONS // +//////////////////// + +BYTEARRAY CGPSProtocol :: SEND_GPSC_INIT( uint32_t version ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_INIT ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, version, false ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSC_RECONNECT( unsigned char PID, uint32_t reconnectKey, uint32_t lastPacket ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_RECONNECT ); + packet.push_back( 0 ); + packet.push_back( 0 ); + packet.push_back( PID ); + UTIL_AppendByteArray( packet, reconnectKey, false ); + UTIL_AppendByteArray( packet, lastPacket, false ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSC_ACK( uint32_t lastPacket ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_ACK ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, lastPacket, false ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSS_INIT( uint16_t reconnectPort, unsigned char PID, uint32_t reconnectKey, unsigned char numEmptyActions ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_INIT ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, reconnectPort, false ); + packet.push_back( PID ); + UTIL_AppendByteArray( packet, reconnectKey, false ); + packet.push_back( numEmptyActions ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSS_RECONNECT( uint32_t lastPacket ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_RECONNECT ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, lastPacket, false ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSS_ACK( uint32_t lastPacket ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_ACK ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, lastPacket, false ); + AssignLength( packet ); + return packet; +} + +BYTEARRAY CGPSProtocol :: SEND_GPSS_REJECT( uint32_t reason ) +{ + BYTEARRAY packet; + packet.push_back( GPS_HEADER_CONSTANT ); + packet.push_back( GPS_REJECT ); + packet.push_back( 0 ); + packet.push_back( 0 ); + UTIL_AppendByteArray( packet, reason, false ); + AssignLength( packet ); + return packet; +} + +///////////////////// +// OTHER FUNCTIONS // +///////////////////// + +bool CGPSProtocol :: AssignLength( BYTEARRAY &content ) +{ + // insert the actual length of the content array into bytes 3 and 4 (indices 2 and 3) + + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes = UTIL_CreateByteArray( (uint16_t)content.size( ), false ); + content[2] = LengthBytes[0]; + content[3] = LengthBytes[1]; + return true; + } + + return false; +} + +bool CGPSProtocol :: ValidateLength( BYTEARRAY &content ) +{ + // verify that bytes 3 and 4 (indices 2 and 3) of the content array describe the length + + uint16_t Length; + BYTEARRAY LengthBytes; + + if( content.size( ) >= 4 && content.size( ) <= 65535 ) + { + LengthBytes.push_back( content[2] ); + LengthBytes.push_back( content[3] ); + Length = UTIL_ByteArrayToUInt16( LengthBytes, false ); + + if( Length == content.size( ) ) + return true; + } + + return false; +} diff --git a/ghost-legacy/gpsprotocol.h b/ghost-legacy/gpsprotocol.h new file mode 100644 index 0000000..2c38bea --- /dev/null +++ b/ghost-legacy/gpsprotocol.h @@ -0,0 +1,64 @@ +/* + + Copyright 2010 Trevor Hogan + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#ifndef GPSPROTOCOL_H +#define GPSPROTOCOL_H + +// +// CGameProtocol +// + +#define GPS_HEADER_CONSTANT 248 + +#define REJECTGPS_INVALID 1 +#define REJECTGPS_NOTFOUND 2 + +class CGPSProtocol +{ +public: + enum Protocol { + GPS_INIT = 1, + GPS_RECONNECT = 2, + GPS_ACK = 3, + GPS_REJECT = 4 + }; + + CGPSProtocol( ); + ~CGPSProtocol( ); + + // receive functions + + // send functions + + BYTEARRAY SEND_GPSC_INIT( uint32_t version ); + BYTEARRAY SEND_GPSC_RECONNECT( unsigned char PID, uint32_t reconnectKey, uint32_t lastPacket ); + BYTEARRAY SEND_GPSC_ACK( uint32_t lastPacket ); + + BYTEARRAY SEND_GPSS_INIT( uint16_t reconnectPort, unsigned char PID, uint32_t reconnectKey, unsigned char numEmptyActions ); + BYTEARRAY SEND_GPSS_RECONNECT( uint32_t lastPacket ); + BYTEARRAY SEND_GPSS_ACK( uint32_t lastPacket ); + BYTEARRAY SEND_GPSS_REJECT( uint32_t reason ); + + // other functions + +private: + bool AssignLength( BYTEARRAY &content ); + bool ValidateLength( BYTEARRAY &content ); +}; + +#endif diff --git a/ghost-legacy/includes.h b/ghost-legacy/includes.h new file mode 100644 index 0000000..035fc0b --- /dev/null +++ b/ghost-legacy/includes.h @@ -0,0 +1,80 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef INCLUDES_H +#define INCLUDES_H + +// standard integer sizes for 64 bit compatibility + +#ifdef WIN32 + #include "ms_stdint.h" +#else + #include +#endif + +// STL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +typedef vector BYTEARRAY; +typedef pair PIDPlayer; + +// time + +uint32_t GetTime( ); // seconds +uint32_t GetTicks( ); // milliseconds + +#ifdef WIN32 + #define MILLISLEEP( x ) Sleep( x ) +#else + #define MILLISLEEP( x ) usleep( ( x ) * 1000 ) +#endif + +// network + +#undef FD_SETSIZE +#define FD_SETSIZE 512 + +// output + +void CONSOLE_Print( string message ); +void CONSOLE_Print( string message, bool toMainBuffer ); +void CONSOLE_Print( string message, uint32_t realmId, bool toMainBuffer = true ); +void DEBUG_Print( string message ); +void DEBUG_Print( BYTEARRAY b ); + +void CONSOLE_ChangeChannel( string channel, uint32_t realmId ); +void CONSOLE_AddChannelUser( string name, uint32_t realmId, int flag ); +void CONSOLE_UpdateChannelUser ( string name, uint32_t realmId, int flag ); +void CONSOLE_RemoveChannelUser( string name, uint32_t realmId ); +void CONSOLE_RemoveChannelUsers( uint32_t realmId ); + +#endif diff --git a/ghost-legacy/language.cpp b/ghost-legacy/language.cpp new file mode 100644 index 0000000..7802ceb --- /dev/null +++ b/ghost-legacy/language.cpp @@ -0,0 +1,1535 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" + +// +// CLanguage +// + +CLanguage :: CLanguage( string nCFGFile ) +{ + m_CFG = new CConfig( ); + m_CFG->Read( nCFGFile ); +} + +CLanguage :: ~CLanguage( ) +{ + delete m_CFG; +} + +string CLanguage :: UnableToCreateGameTryAnotherName( string server, string gamename ) +{ + string Out = m_CFG->GetString( "lang_0001", "lang_0001" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UserIsAlreadyAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0002", "lang_0002" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: AddedUserToAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0003", "lang_0003" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: ErrorAddingUserToAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0004", "lang_0004" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: YouDontHaveAccessToThatCommand( ) +{ + return m_CFG->GetString( "lang_0005", "lang_0005" ); +} + +string CLanguage :: UserIsAlreadyBanned( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0006", "lang_0006" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: BannedUser( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0007", "lang_0007" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorBanningUser( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0008", "lang_0008" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UserIsAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0009", "lang_0009" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UserIsNotAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0010", "lang_0010" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UserWasBannedOnByBecause( string server, string victim, string date, string admin, string reason ) +{ + string Out = m_CFG->GetString( "lang_0011", "lang_0011" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$DATE$", date ); + UTIL_Replace( Out, "$ADMIN$", admin ); + UTIL_Replace( Out, "$REASON$", reason ); + return Out; +} + +string CLanguage :: UserIsNotBanned( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0012", "lang_0012" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ThereAreNoAdmins( string server ) +{ + string Out = m_CFG->GetString( "lang_0013", "lang_0013" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereIsAdmin( string server ) +{ + string Out = m_CFG->GetString( "lang_0014", "lang_0014" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereAreAdmins( string server, string count ) +{ + string Out = m_CFG->GetString( "lang_0015", "lang_0015" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$COUNT$", count ); + return Out; +} + +string CLanguage :: ThereAreNoBannedUsers( string server ) +{ + string Out = m_CFG->GetString( "lang_0016", "lang_0016" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereIsBannedUser( string server ) +{ + string Out = m_CFG->GetString( "lang_0017", "lang_0017" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereAreBannedUsers( string server, string count ) +{ + string Out = m_CFG->GetString( "lang_0018", "lang_0018" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$COUNT$", count ); + return Out; +} + +string CLanguage :: YouCantDeleteTheRootAdmin( ) +{ + return m_CFG->GetString( "lang_0019", "lang_0019" ); +} + +string CLanguage :: DeletedUserFromAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0020", "lang_0020" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: ErrorDeletingUserFromAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0021", "lang_0021" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnbannedUser( string victim ) +{ + string Out = m_CFG->GetString( "lang_0022", "lang_0022" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorUnbanningUser( string victim ) +{ + string Out = m_CFG->GetString( "lang_0023", "lang_0023" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: GameNumberIs( string number, string description ) +{ + string Out = m_CFG->GetString( "lang_0024", "lang_0024" ); + UTIL_Replace( Out, "$NUMBER$", number ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: GameNumberDoesntExist( string number ) +{ + string Out = m_CFG->GetString( "lang_0025", "lang_0025" ); + UTIL_Replace( Out, "$NUMBER$", number ); + return Out; +} + +string CLanguage :: GameIsInTheLobby( string description, string current, string max ) +{ + string Out = m_CFG->GetString( "lang_0026", "lang_0026" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + UTIL_Replace( Out, "$CURRENT$", current ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: ThereIsNoGameInTheLobby( string current, string max ) +{ + string Out = m_CFG->GetString( "lang_0027", "lang_0027" ); + UTIL_Replace( Out, "$CURRENT$", current ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: UnableToLoadConfigFilesOutside( ) +{ + return m_CFG->GetString( "lang_0028", "lang_0028" ); +} + +string CLanguage :: LoadingConfigFile( string file ) +{ + string Out = m_CFG->GetString( "lang_0029", "lang_0029" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadConfigFileDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0030", "lang_0030" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: CreatingPrivateGame( string gamename, string user ) +{ + string Out = m_CFG->GetString( "lang_0031", "lang_0031" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: CreatingPublicGame( string gamename, string user ) +{ + string Out = m_CFG->GetString( "lang_0032", "lang_0032" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToUnhostGameCountdownStarted( string description ) +{ + string Out = m_CFG->GetString( "lang_0033", "lang_0033" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnhostingGame( string description ) +{ + string Out = m_CFG->GetString( "lang_0034", "lang_0034" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnableToUnhostGameNoGameInLobby( ) +{ + return m_CFG->GetString( "lang_0035", "lang_0035" ); +} + +string CLanguage :: VersionAdmin( string version ) +{ + string Out = m_CFG->GetString( "lang_0036", "lang_0036" ); + UTIL_Replace( Out, "$VERSION$", version ); + return Out; +} + +string CLanguage :: VersionNotAdmin( string version ) +{ + string Out = m_CFG->GetString( "lang_0037", "lang_0037" ); + UTIL_Replace( Out, "$VERSION$", version ); + return Out; +} + +string CLanguage :: UnableToCreateGameAnotherGameInLobby( string gamename, string description ) +{ + string Out = m_CFG->GetString( "lang_0038", "lang_0038" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnableToCreateGameMaxGamesReached( string gamename, string max ) +{ + string Out = m_CFG->GetString( "lang_0039", "lang_0039" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: GameIsOver( string description ) +{ + string Out = m_CFG->GetString( "lang_0040", "lang_0040" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: SpoofCheckByReplying( ) +{ + return m_CFG->GetString( "lang_0041", "lang_0041" ); +} + +string CLanguage :: GameRefreshed( ) +{ + return m_CFG->GetString( "lang_0042", "lang_0042" ); +} + +string CLanguage :: SpoofPossibleIsAway( string user ) +{ + string Out = m_CFG->GetString( "lang_0043", "lang_0043" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofPossibleIsUnavailable( string user ) +{ + string Out = m_CFG->GetString( "lang_0044", "lang_0044" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofPossibleIsRefusingMessages( string user ) +{ + string Out = m_CFG->GetString( "lang_0045", "lang_0045" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsNotInGame( string user ) +{ + string Out = m_CFG->GetString( "lang_0046", "lang_0046" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsInPrivateChannel( string user ) +{ + string Out = m_CFG->GetString( "lang_0047", "lang_0047" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsInAnotherGame( string user ) +{ + string Out = m_CFG->GetString( "lang_0048", "lang_0048" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: CountDownAborted( ) +{ + return m_CFG->GetString( "lang_0049", "lang_0049" ); +} + +string CLanguage :: TryingToJoinTheGameButBanned( string victim ) +{ + string Out = m_CFG->GetString( "lang_0050", "lang_0050" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToBanNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0051", "lang_0051" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: PlayerWasBannedByPlayer( string server, string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0052", "lang_0052" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToBanFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0053", "lang_0053" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: AddedPlayerToTheHoldList( string user ) +{ + string Out = m_CFG->GetString( "lang_0054", "lang_0054" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToKickNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0055", "lang_0055" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToKickFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0056", "lang_0056" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: SettingLatencyToMinimum( string min ) +{ + string Out = m_CFG->GetString( "lang_0057", "lang_0057" ); + UTIL_Replace( Out, "$MIN$", min ); + return Out; +} + +string CLanguage :: SettingLatencyToMaximum( string max ) +{ + string Out = m_CFG->GetString( "lang_0058", "lang_0058" ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: SettingLatencyTo( string latency ) +{ + string Out = m_CFG->GetString( "lang_0059", "lang_0059" ); + UTIL_Replace( Out, "$LATENCY$", latency ); + return Out; +} + +string CLanguage :: KickingPlayersWithPingsGreaterThan( string total, string ping ) +{ + string Out = m_CFG->GetString( "lang_0060", "lang_0060" ); + UTIL_Replace( Out, "$TOTAL$", total ); + UTIL_Replace( Out, "$PING$", ping ); + return Out; +} + +string CLanguage :: HasPlayedGamesWithThisBot( string user, string firstgame, string lastgame, string totalgames, string avgloadingtime, string avgstay ) +{ + string Out = m_CFG->GetString( "lang_0061", "lang_0061" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$FIRSTGAME$", firstgame ); + UTIL_Replace( Out, "$LASTGAME$", lastgame ); + UTIL_Replace( Out, "$TOTALGAMES$", totalgames ); + UTIL_Replace( Out, "$AVGLOADINGTIME$", avgloadingtime ); + UTIL_Replace( Out, "$AVGSTAY$", avgstay ); + return Out; +} + +string CLanguage :: HasntPlayedGamesWithThisBot( string user ) +{ + string Out = m_CFG->GetString( "lang_0062", "lang_0062" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: AutokickingPlayerForExcessivePing( string victim, string ping ) +{ + string Out = m_CFG->GetString( "lang_0063", "lang_0063" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$PING$", ping ); + return Out; +} + +string CLanguage :: SpoofCheckAcceptedFor( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0064", "lang_0064" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: PlayersNotYetSpoofChecked( string notspoofchecked ) +{ + string Out = m_CFG->GetString( "lang_0065", "lang_0065" ); + UTIL_Replace( Out, "$NOTSPOOFCHECKED$", notspoofchecked ); + return Out; +} + +string CLanguage :: ManuallySpoofCheckByWhispering( string hostname ) +{ + string Out = m_CFG->GetString( "lang_0066", "lang_0066" ); + UTIL_Replace( Out, "$HOSTNAME$", hostname ); + return Out; +} + +string CLanguage :: SpoofCheckByWhispering( string hostname ) +{ + string Out = m_CFG->GetString( "lang_0067", "lang_0067" ); + UTIL_Replace( Out, "$HOSTNAME$", hostname ); + return Out; +} + +string CLanguage :: EveryoneHasBeenSpoofChecked( ) +{ + return m_CFG->GetString( "lang_0068", "lang_0068" ); +} + +string CLanguage :: PlayersNotYetPinged( string notpinged ) +{ + string Out = m_CFG->GetString( "lang_0069", "lang_0069" ); + UTIL_Replace( Out, "$NOTPINGED$", notpinged ); + return Out; +} + +string CLanguage :: EveryoneHasBeenPinged( ) +{ + return m_CFG->GetString( "lang_0070", "lang_0070" ); +} + +string CLanguage :: ShortestLoadByPlayer( string user, string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0071", "lang_0071" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: LongestLoadByPlayer( string user, string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0072", "lang_0072" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: YourLoadingTimeWas( string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0073", "lang_0073" ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: HasPlayedDotAGamesWithThisBot( string user, string totalgames, string totalwins, string totallosses, string totalkills, string totaldeaths, string totalcreepkills, string totalcreepdenies, string totalassists, string totalneutralkills, string totaltowerkills, string totalraxkills, string totalcourierkills, string avgkills, string avgdeaths, string avgcreepkills, string avgcreepdenies, string avgassists, string avgneutralkills, string avgtowerkills, string avgraxkills, string avgcourierkills ) +{ + string Out = m_CFG->GetString( "lang_0074", "lang_0074" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$TOTALGAMES$", totalgames ); + UTIL_Replace( Out, "$TOTALWINS$", totalwins ); + UTIL_Replace( Out, "$TOTALLOSSES$", totallosses ); + UTIL_Replace( Out, "$TOTALKILLS$", totalkills ); + UTIL_Replace( Out, "$TOTALDEATHS$", totaldeaths ); + UTIL_Replace( Out, "$TOTALCREEPKILLS$", totalcreepkills ); + UTIL_Replace( Out, "$TOTALCREEPDENIES$", totalcreepdenies ); + UTIL_Replace( Out, "$TOTALASSISTS$", totalassists ); + UTIL_Replace( Out, "$TOTALNEUTRALKILLS$", totalneutralkills ); + UTIL_Replace( Out, "$TOTALTOWERKILLS$", totaltowerkills ); + UTIL_Replace( Out, "$TOTALRAXKILLS$", totalraxkills ); + UTIL_Replace( Out, "$TOTALCOURIERKILLS$", totalcourierkills ); + UTIL_Replace( Out, "$AVGKILLS$", avgkills ); + UTIL_Replace( Out, "$AVGDEATHS$", avgdeaths ); + UTIL_Replace( Out, "$AVGCREEPKILLS$", avgcreepkills ); + UTIL_Replace( Out, "$AVGCREEPDENIES$", avgcreepdenies ); + UTIL_Replace( Out, "$AVGASSISTS$", avgassists ); + UTIL_Replace( Out, "$AVGNEUTRALKILLS$", avgneutralkills ); + UTIL_Replace( Out, "$AVGTOWERKILLS$", avgtowerkills ); + UTIL_Replace( Out, "$AVGRAXKILLS$", avgraxkills ); + UTIL_Replace( Out, "$AVGCOURIERKILLS$", avgcourierkills ); + return Out; +} + +string CLanguage :: HasntPlayedDotAGamesWithThisBot( string user ) +{ + string Out = m_CFG->GetString( "lang_0075", "lang_0075" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: WasKickedForReservedPlayer( string reserved ) +{ + string Out = m_CFG->GetString( "lang_0076", "lang_0076" ); + UTIL_Replace( Out, "$RESERVED$", reserved ); + return Out; +} + +string CLanguage :: WasKickedForOwnerPlayer( string owner ) +{ + string Out = m_CFG->GetString( "lang_0077", "lang_0077" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: WasKickedByPlayer( string user ) +{ + string Out = m_CFG->GetString( "lang_0078", "lang_0078" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: HasLostConnectionPlayerError( string error ) +{ + string Out = m_CFG->GetString( "lang_0079", "lang_0079" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionSocketError( string error ) +{ + string Out = m_CFG->GetString( "lang_0080", "lang_0080" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionClosedByRemoteHost( ) +{ + return m_CFG->GetString( "lang_0081", "lang_0081" ); +} + +string CLanguage :: HasLeftVoluntarily( ) +{ + return m_CFG->GetString( "lang_0082", "lang_0082" ); +} + +string CLanguage :: EndingGame( string description ) +{ + string Out = m_CFG->GetString( "lang_0083", "lang_0083" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: HasLostConnectionTimedOut( ) +{ + return m_CFG->GetString( "lang_0084", "lang_0084" ); +} + +string CLanguage :: GlobalChatMuted( ) +{ + return m_CFG->GetString( "lang_0085", "lang_0085" ); +} + +string CLanguage :: GlobalChatUnmuted( ) +{ + return m_CFG->GetString( "lang_0086", "lang_0086" ); +} + +string CLanguage :: ShufflingPlayers( ) +{ + return m_CFG->GetString( "lang_0087", "lang_0087" ); +} + +string CLanguage :: UnableToLoadConfigFileGameInLobby( ) +{ + return m_CFG->GetString( "lang_0088", "lang_0088" ); +} + +string CLanguage :: PlayersStillDownloading( string stilldownloading ) +{ + string Out = m_CFG->GetString( "lang_0089", "lang_0089" ); + UTIL_Replace( Out, "$STILLDOWNLOADING$", stilldownloading ); + return Out; +} + +string CLanguage :: RefreshMessagesEnabled( ) +{ + return m_CFG->GetString( "lang_0090", "lang_0090" ); +} + +string CLanguage :: RefreshMessagesDisabled( ) +{ + return m_CFG->GetString( "lang_0091", "lang_0091" ); +} + +string CLanguage :: AtLeastOneGameActiveUseForceToShutdown( ) +{ + return m_CFG->GetString( "lang_0092", "lang_0092" ); +} + +string CLanguage :: CurrentlyLoadedMapCFGIs( string mapcfg ) +{ + string Out = m_CFG->GetString( "lang_0093", "lang_0093" ); + UTIL_Replace( Out, "$MAPCFG$", mapcfg ); + return Out; +} + +string CLanguage :: LaggedOutDroppedByAdmin( ) +{ + return m_CFG->GetString( "lang_0094", "lang_0094" ); +} + +string CLanguage :: LaggedOutDroppedByVote( ) +{ + return m_CFG->GetString( "lang_0095", "lang_0095" ); +} + +string CLanguage :: PlayerVotedToDropLaggers( string user ) +{ + string Out = m_CFG->GetString( "lang_0096", "lang_0096" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: LatencyIs( string latency ) +{ + string Out = m_CFG->GetString( "lang_0097", "lang_0097" ); + UTIL_Replace( Out, "$LATENCY$", latency ); + return Out; +} + +string CLanguage :: SyncLimitIs( string synclimit ) +{ + string Out = m_CFG->GetString( "lang_0098", "lang_0098" ); + UTIL_Replace( Out, "$SYNCLIMIT$", synclimit ); + return Out; +} + +string CLanguage :: SettingSyncLimitToMinimum( string min ) +{ + string Out = m_CFG->GetString( "lang_0099", "lang_0099" ); + UTIL_Replace( Out, "$MIN$", min ); + return Out; +} + +string CLanguage :: SettingSyncLimitToMaximum( string max ) +{ + string Out = m_CFG->GetString( "lang_0100", "lang_0100" ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: SettingSyncLimitTo( string synclimit ) +{ + string Out = m_CFG->GetString( "lang_0101", "lang_0101" ); + UTIL_Replace( Out, "$SYNCLIMIT$", synclimit ); + return Out; +} + +string CLanguage :: UnableToCreateGameNotLoggedIn( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0102", "lang_0102" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: AdminLoggedIn( ) +{ + return m_CFG->GetString( "lang_0103", "lang_0103" ); +} + +string CLanguage :: AdminInvalidPassword( string attempt ) +{ + string Out = m_CFG->GetString( "lang_0104", "lang_0104" ); + UTIL_Replace( Out, "$ATTEMPT$", attempt ); + return Out; +} + +string CLanguage :: ConnectingToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0105", "lang_0105" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ConnectedToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0106", "lang_0106" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: DisconnectedFromBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0107", "lang_0107" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: LoggedInToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0108", "lang_0108" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: BNETGameHostingSucceeded( string server ) +{ + string Out = m_CFG->GetString( "lang_0109", "lang_0109" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: BNETGameHostingFailed( string server, string gamename ) +{ + string Out = m_CFG->GetString( "lang_0110", "lang_0110" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: ConnectingToBNETTimedOut( string server ) +{ + string Out = m_CFG->GetString( "lang_0111", "lang_0111" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: PlayerDownloadedTheMap( string user, string seconds, string rate ) +{ + string Out = m_CFG->GetString( "lang_0112", "lang_0112" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + UTIL_Replace( Out, "$RATE$", rate ); + return Out; +} + +string CLanguage :: UnableToCreateGameNameTooLong( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0113", "lang_0113" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: SettingGameOwnerTo( string owner ) +{ + string Out = m_CFG->GetString( "lang_0114", "lang_0114" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: TheGameIsLocked( ) +{ + return m_CFG->GetString( "lang_0115", "lang_0115" ); +} + +string CLanguage :: GameLocked( ) +{ + return m_CFG->GetString( "lang_0116", "lang_0116" ); +} + +string CLanguage :: GameUnlocked( ) +{ + return m_CFG->GetString( "lang_0117", "lang_0117" ); +} + +string CLanguage :: UnableToStartDownloadNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0118", "lang_0118" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToStartDownloadFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0119", "lang_0119" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToSetGameOwner( string owner ) +{ + string Out = m_CFG->GetString( "lang_0120", "lang_0120" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: UnableToCheckPlayerNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0121", "lang_0121" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: CheckedPlayer( string victim, string ping, string from, string admin, string owner, string spoofed, string spoofedrealm, string reserved ) +{ + string Out = m_CFG->GetString( "lang_0122", "lang_0122" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$PING$", ping ); + UTIL_Replace( Out, "$FROM$", from ); + UTIL_Replace( Out, "$ADMIN$", admin ); + UTIL_Replace( Out, "$OWNER$", owner ); + UTIL_Replace( Out, "$SPOOFED$", spoofed ); + UTIL_Replace( Out, "$SPOOFEDREALM$", spoofedrealm ); + UTIL_Replace( Out, "$RESERVED$", reserved ); + return Out; +} + +string CLanguage :: UnableToCheckPlayerFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0123", "lang_0123" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: TheGameIsLockedBNET( ) +{ + return m_CFG->GetString( "lang_0124", "lang_0124" ); +} + +string CLanguage :: UnableToCreateGameDisabled( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0125", "lang_0125" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: BotDisabled( ) +{ + return m_CFG->GetString( "lang_0126", "lang_0126" ); +} + +string CLanguage :: BotEnabled( ) +{ + return m_CFG->GetString( "lang_0127", "lang_0127" ); +} + +string CLanguage :: UnableToCreateGameInvalidMap( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0128", "lang_0128" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: WaitingForPlayersBeforeAutoStart( string players, string playersleft ) +{ + string Out = m_CFG->GetString( "lang_0129", "lang_0129" ); + UTIL_Replace( Out, "$PLAYERS$", players ); + UTIL_Replace( Out, "$PLAYERSLEFT$", playersleft ); + return Out; +} + +string CLanguage :: AutoStartDisabled( ) +{ + return m_CFG->GetString( "lang_0130", "lang_0130" ); +} + +string CLanguage :: AutoStartEnabled( string players ) +{ + string Out = m_CFG->GetString( "lang_0131", "lang_0131" ); + UTIL_Replace( Out, "$PLAYERS$", players ); + return Out; +} + +string CLanguage :: AnnounceMessageEnabled( ) +{ + return m_CFG->GetString( "lang_0132", "lang_0132" ); +} + +string CLanguage :: AnnounceMessageDisabled( ) +{ + return m_CFG->GetString( "lang_0133", "lang_0133" ); +} + +string CLanguage :: AutoHostEnabled( ) +{ + return m_CFG->GetString( "lang_0134", "lang_0134" ); +} + +string CLanguage :: AutoHostDisabled( ) +{ + return m_CFG->GetString( "lang_0135", "lang_0135" ); +} + +string CLanguage :: UnableToLoadSaveGamesOutside( ) +{ + return m_CFG->GetString( "lang_0136", "lang_0136" ); +} + +string CLanguage :: UnableToLoadSaveGameGameInLobby( ) +{ + return m_CFG->GetString( "lang_0137", "lang_0137" ); +} + +string CLanguage :: LoadingSaveGame( string file ) +{ + string Out = m_CFG->GetString( "lang_0138", "lang_0138" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadSaveGameDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0139", "lang_0139" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToCreateGameInvalidSaveGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0140", "lang_0140" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UnableToCreateGameSaveGameMapMismatch( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0141", "lang_0141" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: AutoSaveEnabled( ) +{ + return m_CFG->GetString( "lang_0142", "lang_0142" ); +} + +string CLanguage :: AutoSaveDisabled( ) +{ + return m_CFG->GetString( "lang_0143", "lang_0143" ); +} + +string CLanguage :: DesyncDetected( ) +{ + return m_CFG->GetString( "lang_0144", "lang_0144" ); +} + +string CLanguage :: UnableToMuteNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0145", "lang_0145" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: MutedPlayer( string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0146", "lang_0146" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnmutedPlayer( string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0147", "lang_0147" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToMuteFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0148", "lang_0148" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: PlayerIsSavingTheGame( string player ) +{ + string Out = m_CFG->GetString( "lang_0149", "lang_0149" ); + UTIL_Replace( Out, "$PLAYER$", player ); + return Out; +} + +string CLanguage :: UpdatingClanList( ) +{ + return m_CFG->GetString( "lang_0150", "lang_0150" ); +} + +string CLanguage :: UpdatingFriendsList( ) +{ + return m_CFG->GetString( "lang_0151", "lang_0151" ); +} + +string CLanguage :: MultipleIPAddressUsageDetected( string player, string others ) +{ + string Out = m_CFG->GetString( "lang_0152", "lang_0152" ); + UTIL_Replace( Out, "$PLAYER$", player ); + UTIL_Replace( Out, "$OTHERS$", others ); + return Out; +} + +string CLanguage :: UnableToVoteKickAlreadyInProgress( ) +{ + return m_CFG->GetString( "lang_0153", "lang_0153" ); +} + +string CLanguage :: UnableToVoteKickNotEnoughPlayers( ) +{ + return m_CFG->GetString( "lang_0154", "lang_0154" ); +} + +string CLanguage :: UnableToVoteKickNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0155", "lang_0155" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToVoteKickPlayerIsReserved( string victim ) +{ + string Out = m_CFG->GetString( "lang_0156", "lang_0156" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: StartedVoteKick( string victim, string user, string votesneeded ) +{ + string Out = m_CFG->GetString( "lang_0157", "lang_0157" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$VOTESNEEDED$", votesneeded ); + return Out; +} + +string CLanguage :: UnableToVoteKickFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0158", "lang_0158" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickPassed( string victim ) +{ + string Out = m_CFG->GetString( "lang_0159", "lang_0159" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorVoteKickingPlayer( string victim ) +{ + string Out = m_CFG->GetString( "lang_0160", "lang_0160" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickAcceptedNeedMoreVotes( string victim, string user, string votes ) +{ + string Out = m_CFG->GetString( "lang_0161", "lang_0161" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$VOTES$", votes ); + return Out; +} + +string CLanguage :: VoteKickCancelled( string victim ) +{ + string Out = m_CFG->GetString( "lang_0162", "lang_0162" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickExpired( string victim ) +{ + string Out = m_CFG->GetString( "lang_0163", "lang_0163" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: WasKickedByVote( ) +{ + return m_CFG->GetString( "lang_0164", "lang_0164" ); +} + +string CLanguage :: TypeYesToVote( string commandtrigger ) +{ + string Out = m_CFG->GetString( "lang_0165", "lang_0165" ); + UTIL_Replace( Out, "$COMMANDTRIGGER$", commandtrigger ); + return Out; +} + +string CLanguage :: PlayersNotYetPingedAutoStart( string notpinged ) +{ + string Out = m_CFG->GetString( "lang_0166", "lang_0166" ); + UTIL_Replace( Out, "$NOTPINGED$", notpinged ); + return Out; +} + +string CLanguage :: WasKickedForNotSpoofChecking( ) +{ + return m_CFG->GetString( "lang_0167", "lang_0167" ); +} + +string CLanguage :: WasKickedForHavingFurthestScore( string score, string average ) +{ + string Out = m_CFG->GetString( "lang_0168", "lang_0168" ); + UTIL_Replace( Out, "$SCORE$", score ); + UTIL_Replace( Out, "$AVERAGE$", average ); + return Out; +} + +string CLanguage :: PlayerHasScore( string player, string score ) +{ + string Out = m_CFG->GetString( "lang_0169", "lang_0169" ); + UTIL_Replace( Out, "$PLAYER$", player ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: RatedPlayersSpread( string rated, string total, string spread ) +{ + string Out = m_CFG->GetString( "lang_0170", "lang_0170" ); + UTIL_Replace( Out, "$RATED$", rated ); + UTIL_Replace( Out, "$TOTAL$", total ); + UTIL_Replace( Out, "$SPREAD$", spread ); + return Out; +} + +string CLanguage :: ErrorListingMaps( ) +{ + return m_CFG->GetString( "lang_0171", "lang_0171" ); +} + +string CLanguage :: FoundMaps( string maps ) +{ + string Out = m_CFG->GetString( "lang_0172", "lang_0172" ); + UTIL_Replace( Out, "$MAPS$", maps ); + return Out; +} + +string CLanguage :: NoMapsFound( ) +{ + return m_CFG->GetString( "lang_0173", "lang_0173" ); +} + +string CLanguage :: ErrorListingMapConfigs( ) +{ + return m_CFG->GetString( "lang_0174", "lang_0174" ); +} + +string CLanguage :: FoundMapConfigs( string mapconfigs ) +{ + string Out = m_CFG->GetString( "lang_0175", "lang_0175" ); + UTIL_Replace( Out, "$MAPCONFIGS$", mapconfigs ); + return Out; +} + +string CLanguage :: NoMapConfigsFound( ) +{ + return m_CFG->GetString( "lang_0176", "lang_0176" ); +} + +string CLanguage :: PlayerFinishedLoading( string user ) +{ + string Out = m_CFG->GetString( "lang_0177", "lang_0177" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: PleaseWaitPlayersStillLoading( ) +{ + return m_CFG->GetString( "lang_0178", "lang_0178" ); +} + +string CLanguage :: MapDownloadsDisabled( ) +{ + return m_CFG->GetString( "lang_0179", "lang_0179" ); +} + +string CLanguage :: MapDownloadsEnabled( ) +{ + return m_CFG->GetString( "lang_0180", "lang_0180" ); +} + +string CLanguage :: MapDownloadsConditional( ) +{ + return m_CFG->GetString( "lang_0181", "lang_0181" ); +} + +string CLanguage :: SettingHCL( string HCL ) +{ + string Out = m_CFG->GetString( "lang_0182", "lang_0182" ); + UTIL_Replace( Out, "$HCL$", HCL ); + return Out; +} + +string CLanguage :: UnableToSetHCLInvalid( ) +{ + return m_CFG->GetString( "lang_0183", "lang_0183" ); +} + +string CLanguage :: UnableToSetHCLTooLong( ) +{ + return m_CFG->GetString( "lang_0184", "lang_0184" ); +} + +string CLanguage :: TheHCLIs( string HCL ) +{ + string Out = m_CFG->GetString( "lang_0185", "lang_0185" ); + UTIL_Replace( Out, "$HCL$", HCL ); + return Out; +} + +string CLanguage :: TheHCLIsTooLongUseForceToStart( ) +{ + return m_CFG->GetString( "lang_0186", "lang_0186" ); +} + +string CLanguage :: ClearingHCL( ) +{ + return m_CFG->GetString( "lang_0187", "lang_0187" ); +} + +string CLanguage :: TryingToRehostAsPrivateGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0188", "lang_0188" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: TryingToRehostAsPublicGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0189", "lang_0189" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: RehostWasSuccessful( ) +{ + return m_CFG->GetString( "lang_0190", "lang_0190" ); +} + +string CLanguage :: TryingToJoinTheGameButBannedByName( string victim ) +{ + string Out = m_CFG->GetString( "lang_0191", "lang_0191" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: TryingToJoinTheGameButBannedByIP( string victim, string ip, string bannedname ) +{ + string Out = m_CFG->GetString( "lang_0192", "lang_0192" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$IP$", ip ); + UTIL_Replace( Out, "$BANNEDNAME$", bannedname ); + return Out; +} + +string CLanguage :: HasBannedName( string victim ) +{ + string Out = m_CFG->GetString( "lang_0193", "lang_0193" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: HasBannedIP( string victim, string ip, string bannedname ) +{ + string Out = m_CFG->GetString( "lang_0194", "lang_0194" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$IP$", ip ); + UTIL_Replace( Out, "$BANNEDNAME$", bannedname ); + return Out; +} + +string CLanguage :: PlayersInGameState( string number, string players ) +{ + string Out = m_CFG->GetString( "lang_0195", "lang_0195" ); + UTIL_Replace( Out, "$NUMBER$", number ); + UTIL_Replace( Out, "$PLAYERS$", players ); + return Out; +} + +string CLanguage :: ValidServers( string servers ) +{ + string Out = m_CFG->GetString( "lang_0196", "lang_0196" ); + UTIL_Replace( Out, "$SERVERS$", servers ); + return Out; +} + +string CLanguage :: TeamCombinedScore( string team, string score ) +{ + string Out = m_CFG->GetString( "lang_0197", "lang_0197" ); + UTIL_Replace( Out, "$TEAM$", team ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: BalancingSlotsCompleted( ) +{ + return m_CFG->GetString( "lang_0198", "lang_0198" ); +} + +string CLanguage :: PlayerWasKickedForFurthestScore( string name, string score, string average ) +{ + string Out = m_CFG->GetString( "lang_0199", "lang_0199" ); + UTIL_Replace( Out, "$NAME$", name ); + UTIL_Replace( Out, "$SCORE$", score ); + UTIL_Replace( Out, "$AVERAGE$", average ); + return Out; +} + +string CLanguage :: LocalAdminMessagesEnabled( ) +{ + return m_CFG->GetString( "lang_0200", "lang_0200" ); +} + +string CLanguage :: LocalAdminMessagesDisabled( ) +{ + return m_CFG->GetString( "lang_0201", "lang_0201" ); +} + +string CLanguage :: WasDroppedDesync( ) +{ + return m_CFG->GetString( "lang_0202", "lang_0202" ); +} + +string CLanguage :: WasKickedForHavingLowestScore( string score ) +{ + string Out = m_CFG->GetString( "lang_0203", "lang_0203" ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: PlayerWasKickedForLowestScore( string name, string score ) +{ + string Out = m_CFG->GetString( "lang_0204", "lang_0204" ); + UTIL_Replace( Out, "$NAME$", name ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: ReloadingConfigurationFiles( ) +{ + return m_CFG->GetString( "lang_0205", "lang_0205" ); +} + +string CLanguage :: CountDownAbortedSomeoneLeftRecently( ) +{ + return m_CFG->GetString( "lang_0206", "lang_0206" ); +} + +string CLanguage :: UnableToCreateGameMustEnforceFirst( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0207", "lang_0207" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UnableToLoadReplaysOutside( ) +{ + return m_CFG->GetString( "lang_0208", "lang_0208" ); +} + +string CLanguage :: LoadingReplay( string file ) +{ + string Out = m_CFG->GetString( "lang_0209", "lang_0209" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadReplayDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0210", "lang_0210" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: CommandTrigger( string trigger ) +{ + string Out = m_CFG->GetString( "lang_0211", "lang_0211" ); + UTIL_Replace( Out, "$TRIGGER$", trigger ); + return Out; +} + +string CLanguage :: CantEndGameOwnerIsStillPlaying( string owner ) +{ + string Out = m_CFG->GetString( "lang_0212", "lang_0212" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: CantUnhostGameOwnerIsPresent( string owner ) +{ + string Out = m_CFG->GetString( "lang_0213", "lang_0213" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: WasAutomaticallyDroppedAfterSeconds( string seconds ) +{ + string Out = m_CFG->GetString( "lang_0214", "lang_0214" ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + return Out; +} + +string CLanguage :: HasLostConnectionTimedOutGProxy( ) +{ + return m_CFG->GetString( "lang_0215", "lang_0215" ); +} + +string CLanguage :: HasLostConnectionSocketErrorGProxy( string error ) +{ + string Out = m_CFG->GetString( "lang_0216", "lang_0216" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionClosedByRemoteHostGProxy( ) +{ + return m_CFG->GetString( "lang_0217", "lang_0217" ); +} + +string CLanguage :: WaitForReconnectSecondsRemain( string seconds ) +{ + string Out = m_CFG->GetString( "lang_0218", "lang_0218" ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + return Out; +} + +string CLanguage :: WasUnrecoverablyDroppedFromGProxy( ) +{ + return m_CFG->GetString( "lang_0219", "lang_0219" ); +} + +string CLanguage :: PlayerReconnectedWithGProxy( string name ) +{ + string Out = m_CFG->GetString( "lang_0220", "lang_0220" ); + UTIL_Replace( Out, "$NAME$", name ); + return Out; +} diff --git a/ghost-legacy/language.cpp.alt b/ghost-legacy/language.cpp.alt new file mode 100644 index 0000000..2a00157 --- /dev/null +++ b/ghost-legacy/language.cpp.alt @@ -0,0 +1,1548 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "config.h" +#include "language.h" + +// +// CLanguage +// + +CLanguage :: CLanguage( string nCFGFile ) +{ + m_CFG = new CConfig( ); + m_CFG->Read( nCFGFile ); +} + +CLanguage :: ~CLanguage( ) +{ + delete m_CFG; +} + +string CLanguage :: UnableToCreateGameTryAnotherName( string server, string gamename ) +{ + string Out = m_CFG->GetString( "lang_0001", "lang_0001" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UserIsAlreadyAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0002", "lang_0002" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: AddedUserToAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0003", "lang_0003" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: ErrorAddingUserToAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0004", "lang_0004" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: YouDontHaveAccessToThatCommand( ) +{ + return m_CFG->GetString( "lang_0005", "lang_0005" ); +} + +string CLanguage :: UserIsAlreadyBanned( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0006", "lang_0006" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: BannedUser( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0007", "lang_0007" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorBanningUser( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0008", "lang_0008" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UserIsAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0009", "lang_0009" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UserIsNotAnAdmin( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0010", "lang_0010" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UserWasBannedOnByBecause( string server, string victim, string date, string admin, string reason ) +{ + string Out = m_CFG->GetString( "lang_0011", "lang_0011" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$DATE$", date ); + UTIL_Replace( Out, "$ADMIN$", admin ); + UTIL_Replace( Out, "$REASON$", reason ); + return Out; +} + +string CLanguage :: UserIsNotBanned( string server, string victim ) +{ + string Out = m_CFG->GetString( "lang_0012", "lang_0012" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ThereAreNoAdmins( string server ) +{ + string Out = m_CFG->GetString( "lang_0013", "lang_0013" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereIsAdmin( string server ) +{ + string Out = m_CFG->GetString( "lang_0014", "lang_0014" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereAreAdmins( string server, string count ) +{ + string Out = m_CFG->GetString( "lang_0015", "lang_0015" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$COUNT$", count ); + return Out; +} + +string CLanguage :: ThereAreNoBannedUsers( string server ) +{ + string Out = m_CFG->GetString( "lang_0016", "lang_0016" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereIsBannedUser( string server ) +{ + string Out = m_CFG->GetString( "lang_0017", "lang_0017" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ThereAreBannedUsers( string server, string count ) +{ + string Out = m_CFG->GetString( "lang_0018", "lang_0018" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$COUNT$", count ); + return Out; +} + +string CLanguage :: YouCantDeleteTheRootAdmin( ) +{ + return m_CFG->GetString( "lang_0019", "lang_0019" ); +} + +string CLanguage :: DeletedUserFromAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0020", "lang_0020" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: ErrorDeletingUserFromAdminDatabase( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0021", "lang_0021" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnbannedUser( string victim ) +{ + string Out = m_CFG->GetString( "lang_0022", "lang_0022" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorUnbanningUser( string victim ) +{ + string Out = m_CFG->GetString( "lang_0023", "lang_0023" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: GameNumberIs( string number, string description ) +{ + string Out = m_CFG->GetString( "lang_0024", "lang_0024" ); + UTIL_Replace( Out, "$NUMBER$", number ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: GameNumberDoesntExist( string number ) +{ + string Out = m_CFG->GetString( "lang_0025", "lang_0025" ); + UTIL_Replace( Out, "$NUMBER$", number ); + return Out; +} + +string CLanguage :: GameIsInTheLobby( string description, string current, string max ) +{ + string Out = m_CFG->GetString( "lang_0026", "lang_0026" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + UTIL_Replace( Out, "$CURRENT$", current ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: ThereIsNoGameInTheLobby( string current, string max ) +{ + string Out = m_CFG->GetString( "lang_0027", "lang_0027" ); + UTIL_Replace( Out, "$CURRENT$", current ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: UnableToLoadConfigFilesOutside( ) +{ + return m_CFG->GetString( "lang_0028", "lang_0028" ); +} + +string CLanguage :: LoadingConfigFile( string file ) +{ + string Out = m_CFG->GetString( "lang_0029", "lang_0029" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadConfigFileDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0030", "lang_0030" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: CreatingPrivateGame( string gamename, string user ) +{ + string Out = m_CFG->GetString( "lang_0031", "lang_0031" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: CreatingPublicGame( string gamename, string user ) +{ + string Out = m_CFG->GetString( "lang_0032", "lang_0032" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToUnhostGameCountdownStarted( string description ) +{ + string Out = m_CFG->GetString( "lang_0033", "lang_0033" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnhostingGame( string description ) +{ + string Out = m_CFG->GetString( "lang_0034", "lang_0034" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnableToUnhostGameNoGameInLobby( ) +{ + return m_CFG->GetString( "lang_0035", "lang_0035" ); +} + +string CLanguage :: VersionAdmin( string version ) +{ + string Out = m_CFG->GetString( "lang_0036", "lang_0036" ); + UTIL_Replace( Out, "$VERSION$", version ); + return Out; +} + +string CLanguage :: VersionNotAdmin( string version ) +{ + string Out = m_CFG->GetString( "lang_0037", "lang_0037" ); + UTIL_Replace( Out, "$VERSION$", version ); + return Out; +} + +string CLanguage :: UnableToCreateGameAnotherGameInLobby( string gamename, string description ) +{ + string Out = m_CFG->GetString( "lang_0038", "lang_0038" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: UnableToCreateGameMaxGamesReached( string gamename, string max ) +{ + string Out = m_CFG->GetString( "lang_0039", "lang_0039" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: GameIsOver( string description ) +{ + string Out = m_CFG->GetString( "lang_0040", "lang_0040" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: SpoofCheckByReplying( ) +{ + return m_CFG->GetString( "lang_0041", "lang_0041" ); +} + +string CLanguage :: GameRefreshed( ) +{ + return m_CFG->GetString( "lang_0042", "lang_0042" ); +} + +string CLanguage :: SpoofPossibleIsAway( string user ) +{ + string Out = m_CFG->GetString( "lang_0043", "lang_0043" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofPossibleIsUnavailable( string user ) +{ + string Out = m_CFG->GetString( "lang_0044", "lang_0044" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofPossibleIsRefusingMessages( string user ) +{ + string Out = m_CFG->GetString( "lang_0045", "lang_0045" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsNotInGame( string user ) +{ + string Out = m_CFG->GetString( "lang_0046", "lang_0046" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsInPrivateChannel( string user ) +{ + string Out = m_CFG->GetString( "lang_0047", "lang_0047" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: SpoofDetectedIsInAnotherGame( string user ) +{ + string Out = m_CFG->GetString( "lang_0048", "lang_0048" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: CountDownAborted( ) +{ + return m_CFG->GetString( "lang_0049", "lang_0049" ); +} + +string CLanguage :: TryingToJoinTheGameButBanned( string victim ) +{ + string Out = m_CFG->GetString( "lang_0050", "lang_0050" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToBanNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0051", "lang_0051" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: PlayerWasBannedByPlayer( string server, string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0052", "lang_0052" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToBanFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0053", "lang_0053" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: AddedPlayerToTheHoldList( string user ) +{ + string Out = m_CFG->GetString( "lang_0054", "lang_0054" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToKickNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0055", "lang_0055" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToKickFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0056", "lang_0056" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: SettingLatencyToMinimum( string min ) +{ + string Out = m_CFG->GetString( "lang_0057", "lang_0057" ); + UTIL_Replace( Out, "$MIN$", min ); + return Out; +} + +string CLanguage :: SettingLatencyToMaximum( string max ) +{ + string Out = m_CFG->GetString( "lang_0058", "lang_0058" ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: SettingLatencyTo( string latency ) +{ + string Out = m_CFG->GetString( "lang_0059", "lang_0059" ); + UTIL_Replace( Out, "$LATENCY$", latency ); + return Out; +} + +string CLanguage :: KickingPlayersWithPingsGreaterThan( string total, string ping ) +{ + string Out = m_CFG->GetString( "lang_0060", "lang_0060" ); + UTIL_Replace( Out, "$TOTAL$", total ); + UTIL_Replace( Out, "$PING$", ping ); + return Out; +} + +string CLanguage :: HasPlayedGamesWithThisBot( string user, string firstgame, string lastgame, string totalgames, string avgloadingtime, string avgstay ) +{ + string Out = m_CFG->GetString( "lang_0061", "lang_0061" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$FIRSTGAME$", firstgame ); + UTIL_Replace( Out, "$LASTGAME$", lastgame ); + UTIL_Replace( Out, "$TOTALGAMES$", totalgames ); + UTIL_Replace( Out, "$AVGLOADINGTIME$", avgloadingtime ); + UTIL_Replace( Out, "$AVGSTAY$", avgstay ); + return Out; +} + +string CLanguage :: HasntPlayedGamesWithThisBot( string user ) +{ + string Out = m_CFG->GetString( "lang_0062", "lang_0062" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: AutokickingPlayerForExcessivePing( string victim, string ping ) +{ + string Out = m_CFG->GetString( "lang_0063", "lang_0063" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$PING$", ping ); + return Out; +} + +string CLanguage :: SpoofCheckAcceptedFor( string server, string user ) +{ + string Out = m_CFG->GetString( "lang_0064", "lang_0064" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: PlayersNotYetSpoofChecked( string notspoofchecked ) +{ + string Out = m_CFG->GetString( "lang_0065", "lang_0065" ); + UTIL_Replace( Out, "$NOTSPOOFCHECKED$", notspoofchecked ); + return Out; +} + +string CLanguage :: ManuallySpoofCheckByWhispering( string hostname ) +{ + string Out = m_CFG->GetString( "lang_0066", "lang_0066" ); + UTIL_Replace( Out, "$HOSTNAME$", hostname ); + return Out; +} + +string CLanguage :: SpoofCheckByWhispering( string hostname ) +{ + string Out = m_CFG->GetString( "lang_0067", "lang_0067" ); + UTIL_Replace( Out, "$HOSTNAME$", hostname ); + return Out; +} + +string CLanguage :: EveryoneHasBeenSpoofChecked( ) +{ + return m_CFG->GetString( "lang_0068", "lang_0068" ); +} + +string CLanguage :: PlayersNotYetPinged( string notpinged ) +{ + string Out = m_CFG->GetString( "lang_0069", "lang_0069" ); + UTIL_Replace( Out, "$NOTPINGED$", notpinged ); + return Out; +} + +string CLanguage :: EveryoneHasBeenPinged( ) +{ + return m_CFG->GetString( "lang_0070", "lang_0070" ); +} + +string CLanguage :: ShortestLoadByPlayer( string user, string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0071", "lang_0071" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: LongestLoadByPlayer( string user, string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0072", "lang_0072" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: YourLoadingTimeWas( string loadingtime ) +{ + string Out = m_CFG->GetString( "lang_0073", "lang_0073" ); + UTIL_Replace( Out, "$LOADINGTIME$", loadingtime ); + return Out; +} + +string CLanguage :: HasPlayedDotAGamesWithThisBot( string user, string totalgames, string totalwins, string totallosses, string totalkills, string totaldeaths, string totalcreepkills, string totalcreepdenies, string totalassists, string totalneutralkills, string totaltowerkills, string totalraxkills, string totalcourierkills, string avgkills, string avgdeaths, string avgcreepkills, string avgcreepdenies, string avgassists, string avgneutralkills, string avgtowerkills, string avgraxkills, string avgcourierkills ) +{ + string Out; + + //alternative statsdota, config: alt_statsdota = 1 + //name wins/losses (Kills/Deaths/Assists - Creeps/Denies) AVG(Kills/Deaths/Assists - Creeps/Denies) + if( m_CFG->GetInt( "alt_statsdota", 0 ) == 1 ) + { + Out = user + " {" + totalwins + "/" + totallosses + "} (" + totalkills + "/" + totaldeaths + "/" + totalassists + + " - " + totalcreepkills + "/" + totalcreepdenies + ") AVG(" + avgkills + "/" + avgdeaths + "/" + avgassists + + " - " + avgcreepkills + "/" + avgcreepdenies + ")"; + } + else + { + Out = m_CFG->GetString( "lang_0074", "lang_0074" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$TOTALGAMES$", totalgames ); + UTIL_Replace( Out, "$TOTALWINS$", totalwins ); + UTIL_Replace( Out, "$TOTALLOSSES$", totallosses ); + UTIL_Replace( Out, "$TOTALKILLS$", totalkills ); + UTIL_Replace( Out, "$TOTALDEATHS$", totaldeaths ); + UTIL_Replace( Out, "$TOTALCREEPKILLS$", totalcreepkills ); + UTIL_Replace( Out, "$TOTALCREEPDENIES$", totalcreepdenies ); + UTIL_Replace( Out, "$TOTALASSISTS$", totalassists ); + UTIL_Replace( Out, "$TOTALNEUTRALKILLS$", totalneutralkills ); + UTIL_Replace( Out, "$TOTALTOWERKILLS$", totaltowerkills ); + UTIL_Replace( Out, "$TOTALRAXKILLS$", totalraxkills ); + UTIL_Replace( Out, "$TOTALCOURIERKILLS$", totalcourierkills ); + UTIL_Replace( Out, "$AVGKILLS$", avgkills ); + UTIL_Replace( Out, "$AVGDEATHS$", avgdeaths ); + UTIL_Replace( Out, "$AVGCREEPKILLS$", avgcreepkills ); + UTIL_Replace( Out, "$AVGCREEPDENIES$", avgcreepdenies ); + UTIL_Replace( Out, "$AVGASSISTS$", avgassists ); + UTIL_Replace( Out, "$AVGNEUTRALKILLS$", avgneutralkills ); + UTIL_Replace( Out, "$AVGTOWERKILLS$", avgtowerkills ); + UTIL_Replace( Out, "$AVGRAXKILLS$", avgraxkills ); + UTIL_Replace( Out, "$AVGCOURIERKILLS$", avgcourierkills ); + } + return Out; +} + +string CLanguage :: HasntPlayedDotAGamesWithThisBot( string user ) +{ + string Out = m_CFG->GetString( "lang_0075", "lang_0075" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: WasKickedForReservedPlayer( string reserved ) +{ + string Out = m_CFG->GetString( "lang_0076", "lang_0076" ); + UTIL_Replace( Out, "$RESERVED$", reserved ); + return Out; +} + +string CLanguage :: WasKickedForOwnerPlayer( string owner ) +{ + string Out = m_CFG->GetString( "lang_0077", "lang_0077" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: WasKickedByPlayer( string user ) +{ + string Out = m_CFG->GetString( "lang_0078", "lang_0078" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: HasLostConnectionPlayerError( string error ) +{ + string Out = m_CFG->GetString( "lang_0079", "lang_0079" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionSocketError( string error ) +{ + string Out = m_CFG->GetString( "lang_0080", "lang_0080" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionClosedByRemoteHost( ) +{ + return m_CFG->GetString( "lang_0081", "lang_0081" ); +} + +string CLanguage :: HasLeftVoluntarily( ) +{ + return m_CFG->GetString( "lang_0082", "lang_0082" ); +} + +string CLanguage :: EndingGame( string description ) +{ + string Out = m_CFG->GetString( "lang_0083", "lang_0083" ); + UTIL_Replace( Out, "$DESCRIPTION$", description ); + return Out; +} + +string CLanguage :: HasLostConnectionTimedOut( ) +{ + return m_CFG->GetString( "lang_0084", "lang_0084" ); +} + +string CLanguage :: GlobalChatMuted( ) +{ + return m_CFG->GetString( "lang_0085", "lang_0085" ); +} + +string CLanguage :: GlobalChatUnmuted( ) +{ + return m_CFG->GetString( "lang_0086", "lang_0086" ); +} + +string CLanguage :: ShufflingPlayers( ) +{ + return m_CFG->GetString( "lang_0087", "lang_0087" ); +} + +string CLanguage :: UnableToLoadConfigFileGameInLobby( ) +{ + return m_CFG->GetString( "lang_0088", "lang_0088" ); +} + +string CLanguage :: PlayersStillDownloading( string stilldownloading ) +{ + string Out = m_CFG->GetString( "lang_0089", "lang_0089" ); + UTIL_Replace( Out, "$STILLDOWNLOADING$", stilldownloading ); + return Out; +} + +string CLanguage :: RefreshMessagesEnabled( ) +{ + return m_CFG->GetString( "lang_0090", "lang_0090" ); +} + +string CLanguage :: RefreshMessagesDisabled( ) +{ + return m_CFG->GetString( "lang_0091", "lang_0091" ); +} + +string CLanguage :: AtLeastOneGameActiveUseForceToShutdown( ) +{ + return m_CFG->GetString( "lang_0092", "lang_0092" ); +} + +string CLanguage :: CurrentlyLoadedMapCFGIs( string mapcfg ) +{ + string Out = m_CFG->GetString( "lang_0093", "lang_0093" ); + UTIL_Replace( Out, "$MAPCFG$", mapcfg ); + return Out; +} + +string CLanguage :: LaggedOutDroppedByAdmin( ) +{ + return m_CFG->GetString( "lang_0094", "lang_0094" ); +} + +string CLanguage :: LaggedOutDroppedByVote( ) +{ + return m_CFG->GetString( "lang_0095", "lang_0095" ); +} + +string CLanguage :: PlayerVotedToDropLaggers( string user ) +{ + string Out = m_CFG->GetString( "lang_0096", "lang_0096" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: LatencyIs( string latency ) +{ + string Out = m_CFG->GetString( "lang_0097", "lang_0097" ); + UTIL_Replace( Out, "$LATENCY$", latency ); + return Out; +} + +string CLanguage :: SyncLimitIs( string synclimit ) +{ + string Out = m_CFG->GetString( "lang_0098", "lang_0098" ); + UTIL_Replace( Out, "$SYNCLIMIT$", synclimit ); + return Out; +} + +string CLanguage :: SettingSyncLimitToMinimum( string min ) +{ + string Out = m_CFG->GetString( "lang_0099", "lang_0099" ); + UTIL_Replace( Out, "$MIN$", min ); + return Out; +} + +string CLanguage :: SettingSyncLimitToMaximum( string max ) +{ + string Out = m_CFG->GetString( "lang_0100", "lang_0100" ); + UTIL_Replace( Out, "$MAX$", max ); + return Out; +} + +string CLanguage :: SettingSyncLimitTo( string synclimit ) +{ + string Out = m_CFG->GetString( "lang_0101", "lang_0101" ); + UTIL_Replace( Out, "$SYNCLIMIT$", synclimit ); + return Out; +} + +string CLanguage :: UnableToCreateGameNotLoggedIn( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0102", "lang_0102" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: AdminLoggedIn( ) +{ + return m_CFG->GetString( "lang_0103", "lang_0103" ); +} + +string CLanguage :: AdminInvalidPassword( string attempt ) +{ + string Out = m_CFG->GetString( "lang_0104", "lang_0104" ); + UTIL_Replace( Out, "$ATTEMPT$", attempt ); + return Out; +} + +string CLanguage :: ConnectingToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0105", "lang_0105" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: ConnectedToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0106", "lang_0106" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: DisconnectedFromBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0107", "lang_0107" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: LoggedInToBNET( string server ) +{ + string Out = m_CFG->GetString( "lang_0108", "lang_0108" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: BNETGameHostingSucceeded( string server ) +{ + string Out = m_CFG->GetString( "lang_0109", "lang_0109" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: BNETGameHostingFailed( string server, string gamename ) +{ + string Out = m_CFG->GetString( "lang_0110", "lang_0110" ); + UTIL_Replace( Out, "$SERVER$", server ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: ConnectingToBNETTimedOut( string server ) +{ + string Out = m_CFG->GetString( "lang_0111", "lang_0111" ); + UTIL_Replace( Out, "$SERVER$", server ); + return Out; +} + +string CLanguage :: PlayerDownloadedTheMap( string user, string seconds, string rate ) +{ + string Out = m_CFG->GetString( "lang_0112", "lang_0112" ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + UTIL_Replace( Out, "$RATE$", rate ); + return Out; +} + +string CLanguage :: UnableToCreateGameNameTooLong( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0113", "lang_0113" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: SettingGameOwnerTo( string owner ) +{ + string Out = m_CFG->GetString( "lang_0114", "lang_0114" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: TheGameIsLocked( ) +{ + return m_CFG->GetString( "lang_0115", "lang_0115" ); +} + +string CLanguage :: GameLocked( ) +{ + return m_CFG->GetString( "lang_0116", "lang_0116" ); +} + +string CLanguage :: GameUnlocked( ) +{ + return m_CFG->GetString( "lang_0117", "lang_0117" ); +} + +string CLanguage :: UnableToStartDownloadNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0118", "lang_0118" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToStartDownloadFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0119", "lang_0119" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToSetGameOwner( string owner ) +{ + string Out = m_CFG->GetString( "lang_0120", "lang_0120" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: UnableToCheckPlayerNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0121", "lang_0121" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: CheckedPlayer( string victim, string ping, string from, string admin, string owner, string spoofed, string spoofedrealm, string reserved ) +{ + string Out = m_CFG->GetString( "lang_0122", "lang_0122" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$PING$", ping ); + UTIL_Replace( Out, "$FROM$", from ); + UTIL_Replace( Out, "$ADMIN$", admin ); + UTIL_Replace( Out, "$OWNER$", owner ); + UTIL_Replace( Out, "$SPOOFED$", spoofed ); + UTIL_Replace( Out, "$SPOOFEDREALM$", spoofedrealm ); + UTIL_Replace( Out, "$RESERVED$", reserved ); + return Out; +} + +string CLanguage :: UnableToCheckPlayerFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0123", "lang_0123" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: TheGameIsLockedBNET( ) +{ + return m_CFG->GetString( "lang_0124", "lang_0124" ); +} + +string CLanguage :: UnableToCreateGameDisabled( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0125", "lang_0125" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: BotDisabled( ) +{ + return m_CFG->GetString( "lang_0126", "lang_0126" ); +} + +string CLanguage :: BotEnabled( ) +{ + return m_CFG->GetString( "lang_0127", "lang_0127" ); +} + +string CLanguage :: UnableToCreateGameInvalidMap( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0128", "lang_0128" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: WaitingForPlayersBeforeAutoStart( string players, string playersleft ) +{ + string Out = m_CFG->GetString( "lang_0129", "lang_0129" ); + UTIL_Replace( Out, "$PLAYERS$", players ); + UTIL_Replace( Out, "$PLAYERSLEFT$", playersleft ); + return Out; +} + +string CLanguage :: AutoStartDisabled( ) +{ + return m_CFG->GetString( "lang_0130", "lang_0130" ); +} + +string CLanguage :: AutoStartEnabled( string players ) +{ + string Out = m_CFG->GetString( "lang_0131", "lang_0131" ); + UTIL_Replace( Out, "$PLAYERS$", players ); + return Out; +} + +string CLanguage :: AnnounceMessageEnabled( ) +{ + return m_CFG->GetString( "lang_0132", "lang_0132" ); +} + +string CLanguage :: AnnounceMessageDisabled( ) +{ + return m_CFG->GetString( "lang_0133", "lang_0133" ); +} + +string CLanguage :: AutoHostEnabled( ) +{ + return m_CFG->GetString( "lang_0134", "lang_0134" ); +} + +string CLanguage :: AutoHostDisabled( ) +{ + return m_CFG->GetString( "lang_0135", "lang_0135" ); +} + +string CLanguage :: UnableToLoadSaveGamesOutside( ) +{ + return m_CFG->GetString( "lang_0136", "lang_0136" ); +} + +string CLanguage :: UnableToLoadSaveGameGameInLobby( ) +{ + return m_CFG->GetString( "lang_0137", "lang_0137" ); +} + +string CLanguage :: LoadingSaveGame( string file ) +{ + string Out = m_CFG->GetString( "lang_0138", "lang_0138" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadSaveGameDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0139", "lang_0139" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToCreateGameInvalidSaveGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0140", "lang_0140" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UnableToCreateGameSaveGameMapMismatch( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0141", "lang_0141" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: AutoSaveEnabled( ) +{ + return m_CFG->GetString( "lang_0142", "lang_0142" ); +} + +string CLanguage :: AutoSaveDisabled( ) +{ + return m_CFG->GetString( "lang_0143", "lang_0143" ); +} + +string CLanguage :: DesyncDetected( ) +{ + return m_CFG->GetString( "lang_0144", "lang_0144" ); +} + +string CLanguage :: UnableToMuteNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0145", "lang_0145" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: MutedPlayer( string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0146", "lang_0146" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnmutedPlayer( string victim, string user ) +{ + string Out = m_CFG->GetString( "lang_0147", "lang_0147" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: UnableToMuteFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0148", "lang_0148" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: PlayerIsSavingTheGame( string player ) +{ + string Out = m_CFG->GetString( "lang_0149", "lang_0149" ); + UTIL_Replace( Out, "$PLAYER$", player ); + return Out; +} + +string CLanguage :: UpdatingClanList( ) +{ + return m_CFG->GetString( "lang_0150", "lang_0150" ); +} + +string CLanguage :: UpdatingFriendsList( ) +{ + return m_CFG->GetString( "lang_0151", "lang_0151" ); +} + +string CLanguage :: MultipleIPAddressUsageDetected( string player, string others ) +{ + string Out = m_CFG->GetString( "lang_0152", "lang_0152" ); + UTIL_Replace( Out, "$PLAYER$", player ); + UTIL_Replace( Out, "$OTHERS$", others ); + return Out; +} + +string CLanguage :: UnableToVoteKickAlreadyInProgress( ) +{ + return m_CFG->GetString( "lang_0153", "lang_0153" ); +} + +string CLanguage :: UnableToVoteKickNotEnoughPlayers( ) +{ + return m_CFG->GetString( "lang_0154", "lang_0154" ); +} + +string CLanguage :: UnableToVoteKickNoMatchesFound( string victim ) +{ + string Out = m_CFG->GetString( "lang_0155", "lang_0155" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: UnableToVoteKickPlayerIsReserved( string victim ) +{ + string Out = m_CFG->GetString( "lang_0156", "lang_0156" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: StartedVoteKick( string victim, string user, string votesneeded ) +{ + string Out = m_CFG->GetString( "lang_0157", "lang_0157" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$VOTESNEEDED$", votesneeded ); + return Out; +} + +string CLanguage :: UnableToVoteKickFoundMoreThanOneMatch( string victim ) +{ + string Out = m_CFG->GetString( "lang_0158", "lang_0158" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickPassed( string victim ) +{ + string Out = m_CFG->GetString( "lang_0159", "lang_0159" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: ErrorVoteKickingPlayer( string victim ) +{ + string Out = m_CFG->GetString( "lang_0160", "lang_0160" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickAcceptedNeedMoreVotes( string victim, string user, string votes ) +{ + string Out = m_CFG->GetString( "lang_0161", "lang_0161" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$USER$", user ); + UTIL_Replace( Out, "$VOTES$", votes ); + return Out; +} + +string CLanguage :: VoteKickCancelled( string victim ) +{ + string Out = m_CFG->GetString( "lang_0162", "lang_0162" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: VoteKickExpired( string victim ) +{ + string Out = m_CFG->GetString( "lang_0163", "lang_0163" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: WasKickedByVote( ) +{ + return m_CFG->GetString( "lang_0164", "lang_0164" ); +} + +string CLanguage :: TypeYesToVote( string commandtrigger ) +{ + string Out = m_CFG->GetString( "lang_0165", "lang_0165" ); + UTIL_Replace( Out, "$COMMANDTRIGGER$", commandtrigger ); + return Out; +} + +string CLanguage :: PlayersNotYetPingedAutoStart( string notpinged ) +{ + string Out = m_CFG->GetString( "lang_0166", "lang_0166" ); + UTIL_Replace( Out, "$NOTPINGED$", notpinged ); + return Out; +} + +string CLanguage :: WasKickedForNotSpoofChecking( ) +{ + return m_CFG->GetString( "lang_0167", "lang_0167" ); +} + +string CLanguage :: WasKickedForHavingFurthestScore( string score, string average ) +{ + string Out = m_CFG->GetString( "lang_0168", "lang_0168" ); + UTIL_Replace( Out, "$SCORE$", score ); + UTIL_Replace( Out, "$AVERAGE$", average ); + return Out; +} + +string CLanguage :: PlayerHasScore( string player, string score ) +{ + string Out = m_CFG->GetString( "lang_0169", "lang_0169" ); + UTIL_Replace( Out, "$PLAYER$", player ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: RatedPlayersSpread( string rated, string total, string spread ) +{ + string Out = m_CFG->GetString( "lang_0170", "lang_0170" ); + UTIL_Replace( Out, "$RATED$", rated ); + UTIL_Replace( Out, "$TOTAL$", total ); + UTIL_Replace( Out, "$SPREAD$", spread ); + return Out; +} + +string CLanguage :: ErrorListingMaps( ) +{ + return m_CFG->GetString( "lang_0171", "lang_0171" ); +} + +string CLanguage :: FoundMaps( string maps ) +{ + string Out = m_CFG->GetString( "lang_0172", "lang_0172" ); + UTIL_Replace( Out, "$MAPS$", maps ); + return Out; +} + +string CLanguage :: NoMapsFound( ) +{ + return m_CFG->GetString( "lang_0173", "lang_0173" ); +} + +string CLanguage :: ErrorListingMapConfigs( ) +{ + return m_CFG->GetString( "lang_0174", "lang_0174" ); +} + +string CLanguage :: FoundMapConfigs( string mapconfigs ) +{ + string Out = m_CFG->GetString( "lang_0175", "lang_0175" ); + UTIL_Replace( Out, "$MAPCONFIGS$", mapconfigs ); + return Out; +} + +string CLanguage :: NoMapConfigsFound( ) +{ + return m_CFG->GetString( "lang_0176", "lang_0176" ); +} + +string CLanguage :: PlayerFinishedLoading( string user ) +{ + string Out = m_CFG->GetString( "lang_0177", "lang_0177" ); + UTIL_Replace( Out, "$USER$", user ); + return Out; +} + +string CLanguage :: PleaseWaitPlayersStillLoading( ) +{ + return m_CFG->GetString( "lang_0178", "lang_0178" ); +} + +string CLanguage :: MapDownloadsDisabled( ) +{ + return m_CFG->GetString( "lang_0179", "lang_0179" ); +} + +string CLanguage :: MapDownloadsEnabled( ) +{ + return m_CFG->GetString( "lang_0180", "lang_0180" ); +} + +string CLanguage :: MapDownloadsConditional( ) +{ + return m_CFG->GetString( "lang_0181", "lang_0181" ); +} + +string CLanguage :: SettingHCL( string HCL ) +{ + string Out = m_CFG->GetString( "lang_0182", "lang_0182" ); + UTIL_Replace( Out, "$HCL$", HCL ); + return Out; +} + +string CLanguage :: UnableToSetHCLInvalid( ) +{ + return m_CFG->GetString( "lang_0183", "lang_0183" ); +} + +string CLanguage :: UnableToSetHCLTooLong( ) +{ + return m_CFG->GetString( "lang_0184", "lang_0184" ); +} + +string CLanguage :: TheHCLIs( string HCL ) +{ + string Out = m_CFG->GetString( "lang_0185", "lang_0185" ); + UTIL_Replace( Out, "$HCL$", HCL ); + return Out; +} + +string CLanguage :: TheHCLIsTooLongUseForceToStart( ) +{ + return m_CFG->GetString( "lang_0186", "lang_0186" ); +} + +string CLanguage :: ClearingHCL( ) +{ + return m_CFG->GetString( "lang_0187", "lang_0187" ); +} + +string CLanguage :: TryingToRehostAsPrivateGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0188", "lang_0188" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: TryingToRehostAsPublicGame( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0189", "lang_0189" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: RehostWasSuccessful( ) +{ + return m_CFG->GetString( "lang_0190", "lang_0190" ); +} + +string CLanguage :: TryingToJoinTheGameButBannedByName( string victim ) +{ + string Out = m_CFG->GetString( "lang_0191", "lang_0191" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: TryingToJoinTheGameButBannedByIP( string victim, string ip, string bannedname ) +{ + string Out = m_CFG->GetString( "lang_0192", "lang_0192" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$IP$", ip ); + UTIL_Replace( Out, "$BANNEDNAME$", bannedname ); + return Out; +} + +string CLanguage :: HasBannedName( string victim ) +{ + string Out = m_CFG->GetString( "lang_0193", "lang_0193" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + return Out; +} + +string CLanguage :: HasBannedIP( string victim, string ip, string bannedname ) +{ + string Out = m_CFG->GetString( "lang_0194", "lang_0194" ); + UTIL_Replace( Out, "$VICTIM$", victim ); + UTIL_Replace( Out, "$IP$", ip ); + UTIL_Replace( Out, "$BANNEDNAME$", bannedname ); + return Out; +} + +string CLanguage :: PlayersInGameState( string number, string players ) +{ + string Out = m_CFG->GetString( "lang_0195", "lang_0195" ); + UTIL_Replace( Out, "$NUMBER$", number ); + UTIL_Replace( Out, "$PLAYERS$", players ); + return Out; +} + +string CLanguage :: ValidServers( string servers ) +{ + string Out = m_CFG->GetString( "lang_0196", "lang_0196" ); + UTIL_Replace( Out, "$SERVERS$", servers ); + return Out; +} + +string CLanguage :: TeamCombinedScore( string team, string score ) +{ + string Out = m_CFG->GetString( "lang_0197", "lang_0197" ); + UTIL_Replace( Out, "$TEAM$", team ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: BalancingSlotsCompleted( ) +{ + return m_CFG->GetString( "lang_0198", "lang_0198" ); +} + +string CLanguage :: PlayerWasKickedForFurthestScore( string name, string score, string average ) +{ + string Out = m_CFG->GetString( "lang_0199", "lang_0199" ); + UTIL_Replace( Out, "$NAME$", name ); + UTIL_Replace( Out, "$SCORE$", score ); + UTIL_Replace( Out, "$AVERAGE$", average ); + return Out; +} + +string CLanguage :: LocalAdminMessagesEnabled( ) +{ + return m_CFG->GetString( "lang_0200", "lang_0200" ); +} + +string CLanguage :: LocalAdminMessagesDisabled( ) +{ + return m_CFG->GetString( "lang_0201", "lang_0201" ); +} + +string CLanguage :: WasDroppedDesync( ) +{ + return m_CFG->GetString( "lang_0202", "lang_0202" ); +} + +string CLanguage :: WasKickedForHavingLowestScore( string score ) +{ + string Out = m_CFG->GetString( "lang_0203", "lang_0203" ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: PlayerWasKickedForLowestScore( string name, string score ) +{ + string Out = m_CFG->GetString( "lang_0204", "lang_0204" ); + UTIL_Replace( Out, "$NAME$", name ); + UTIL_Replace( Out, "$SCORE$", score ); + return Out; +} + +string CLanguage :: ReloadingConfigurationFiles( ) +{ + return m_CFG->GetString( "lang_0205", "lang_0205" ); +} + +string CLanguage :: CountDownAbortedSomeoneLeftRecently( ) +{ + return m_CFG->GetString( "lang_0206", "lang_0206" ); +} + +string CLanguage :: UnableToCreateGameMustEnforceFirst( string gamename ) +{ + string Out = m_CFG->GetString( "lang_0207", "lang_0207" ); + UTIL_Replace( Out, "$GAMENAME$", gamename ); + return Out; +} + +string CLanguage :: UnableToLoadReplaysOutside( ) +{ + return m_CFG->GetString( "lang_0208", "lang_0208" ); +} + +string CLanguage :: LoadingReplay( string file ) +{ + string Out = m_CFG->GetString( "lang_0209", "lang_0209" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: UnableToLoadReplayDoesntExist( string file ) +{ + string Out = m_CFG->GetString( "lang_0210", "lang_0210" ); + UTIL_Replace( Out, "$FILE$", file ); + return Out; +} + +string CLanguage :: CommandTrigger( string trigger ) +{ + string Out = m_CFG->GetString( "lang_0211", "lang_0211" ); + UTIL_Replace( Out, "$TRIGGER$", trigger ); + return Out; +} + +string CLanguage :: CantEndGameOwnerIsStillPlaying( string owner ) +{ + string Out = m_CFG->GetString( "lang_0212", "lang_0212" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: CantUnhostGameOwnerIsPresent( string owner ) +{ + string Out = m_CFG->GetString( "lang_0213", "lang_0213" ); + UTIL_Replace( Out, "$OWNER$", owner ); + return Out; +} + +string CLanguage :: WasAutomaticallyDroppedAfterSeconds( string seconds ) +{ + string Out = m_CFG->GetString( "lang_0214", "lang_0214" ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + return Out; +} + +string CLanguage :: HasLostConnectionTimedOutGProxy( ) +{ + return m_CFG->GetString( "lang_0215", "lang_0215" ); +} + +string CLanguage :: HasLostConnectionSocketErrorGProxy( string error ) +{ + string Out = m_CFG->GetString( "lang_0216", "lang_0216" ); + UTIL_Replace( Out, "$ERROR$", error ); + return Out; +} + +string CLanguage :: HasLostConnectionClosedByRemoteHostGProxy( ) +{ + return m_CFG->GetString( "lang_0217", "lang_0217" ); +} + +string CLanguage :: WaitForReconnectSecondsRemain( string seconds ) +{ + string Out = m_CFG->GetString( "lang_0218", "lang_0218" ); + UTIL_Replace( Out, "$SECONDS$", seconds ); + return Out; +} + +string CLanguage :: WasUnrecoverablyDroppedFromGProxy( ) +{ + return m_CFG->GetString( "lang_0219", "lang_0219" ); +} + +string CLanguage :: PlayerReconnectedWithGProxy( string name ) +{ + string Out = m_CFG->GetString( "lang_0220", "lang_0220" ); + UTIL_Replace( Out, "$NAME$", name ); + return Out; +} diff --git a/ghost-legacy/language.h b/ghost-legacy/language.h new file mode 100644 index 0000000..3d12a5c --- /dev/null +++ b/ghost-legacy/language.h @@ -0,0 +1,259 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef LANGUAGE_H +#define LANGUAGE_H + +// +// CLanguage +// + +class CLanguage +{ +private: + CConfig *m_CFG; + +public: + CLanguage( string nCFGFile ); + ~CLanguage( ); + + string UnableToCreateGameTryAnotherName( string server, string gamename ); + string UserIsAlreadyAnAdmin( string server, string user ); + string AddedUserToAdminDatabase( string server, string user ); + string ErrorAddingUserToAdminDatabase( string server, string user ); + string YouDontHaveAccessToThatCommand( ); + string UserIsAlreadyBanned( string server, string victim ); + string BannedUser( string server, string victim ); + string ErrorBanningUser( string server, string victim ); + string UserIsAnAdmin( string server, string user ); + string UserIsNotAnAdmin( string server, string user ); + string UserWasBannedOnByBecause( string server, string victim, string date, string admin, string reason ); + string UserIsNotBanned( string server, string victim ); + string ThereAreNoAdmins( string server ); + string ThereIsAdmin( string server ); + string ThereAreAdmins( string server, string count ); + string ThereAreNoBannedUsers( string server ); + string ThereIsBannedUser( string server ); + string ThereAreBannedUsers( string server, string count ); + string YouCantDeleteTheRootAdmin( ); + string DeletedUserFromAdminDatabase( string server, string user ); + string ErrorDeletingUserFromAdminDatabase( string server, string user ); + string UnbannedUser( string victim ); + string ErrorUnbanningUser( string victim ); + string GameNumberIs( string number, string description ); + string GameNumberDoesntExist( string number ); + string GameIsInTheLobby( string description, string current, string max ); + string ThereIsNoGameInTheLobby( string current, string max ); + string UnableToLoadConfigFilesOutside( ); + string LoadingConfigFile( string file ); + string UnableToLoadConfigFileDoesntExist( string file ); + string CreatingPrivateGame( string gamename, string user ); + string CreatingPublicGame( string gamename, string user ); + string UnableToUnhostGameCountdownStarted( string description ); + string UnhostingGame( string description ); + string UnableToUnhostGameNoGameInLobby( ); + string VersionAdmin( string version ); + string VersionNotAdmin( string version ); + string UnableToCreateGameAnotherGameInLobby( string gamename, string description ); + string UnableToCreateGameMaxGamesReached( string gamename, string max ); + string GameIsOver( string description ); + string SpoofCheckByReplying( ); + string GameRefreshed( ); + string SpoofPossibleIsAway( string user ); + string SpoofPossibleIsUnavailable( string user ); + string SpoofPossibleIsRefusingMessages( string user ); + string SpoofDetectedIsNotInGame( string user ); + string SpoofDetectedIsInPrivateChannel( string user ); + string SpoofDetectedIsInAnotherGame( string user ); + string CountDownAborted( ); + string TryingToJoinTheGameButBanned( string victim ); + string UnableToBanNoMatchesFound( string victim ); + string PlayerWasBannedByPlayer( string server, string victim, string user ); + string UnableToBanFoundMoreThanOneMatch( string victim ); + string AddedPlayerToTheHoldList( string user ); + string UnableToKickNoMatchesFound( string victim ); + string UnableToKickFoundMoreThanOneMatch( string victim ); + string SettingLatencyToMinimum( string min ); + string SettingLatencyToMaximum( string max ); + string SettingLatencyTo( string latency ); + string KickingPlayersWithPingsGreaterThan( string total, string ping ); + string HasPlayedGamesWithThisBot( string user, string firstgame, string lastgame, string totalgames, string avgloadingtime, string avgstay ); + string HasntPlayedGamesWithThisBot( string user ); + string AutokickingPlayerForExcessivePing( string victim, string ping ); + string SpoofCheckAcceptedFor( string server, string user ); + string PlayersNotYetSpoofChecked( string notspoofchecked ); + string ManuallySpoofCheckByWhispering( string hostname ); + string SpoofCheckByWhispering( string hostname ); + string EveryoneHasBeenSpoofChecked( ); + string PlayersNotYetPinged( string notpinged ); + string EveryoneHasBeenPinged( ); + string ShortestLoadByPlayer( string user, string loadingtime ); + string LongestLoadByPlayer( string user, string loadingtime ); + string YourLoadingTimeWas( string loadingtime ); + string HasPlayedDotAGamesWithThisBot( string user, string totalgames, string totalwins, string totallosses, string totalkills, string totaldeaths, string totalcreepkills, string totalcreepdenies, string totalassists, string totalneutralkills, string totaltowerkills, string totalraxkills, string totalcourierkills, string avgkills, string avgdeaths, string avgcreepkills, string avgcreepdenies, string avgassists, string avgneutralkills, string avgtowerkills, string avgraxkills, string avgcourierkills ); + string HasntPlayedDotAGamesWithThisBot( string user ); + string WasKickedForReservedPlayer( string reserved ); + string WasKickedForOwnerPlayer( string owner ); + string WasKickedByPlayer( string user ); + string HasLostConnectionPlayerError( string error ); + string HasLostConnectionSocketError( string error ); + string HasLostConnectionClosedByRemoteHost( ); + string HasLeftVoluntarily( ); + string EndingGame( string description ); + string HasLostConnectionTimedOut( ); + string GlobalChatMuted( ); + string GlobalChatUnmuted( ); + string ShufflingPlayers( ); + string UnableToLoadConfigFileGameInLobby( ); + string PlayersStillDownloading( string stilldownloading ); + string RefreshMessagesEnabled( ); + string RefreshMessagesDisabled( ); + string AtLeastOneGameActiveUseForceToShutdown( ); + string CurrentlyLoadedMapCFGIs( string mapcfg ); + string LaggedOutDroppedByAdmin( ); + string LaggedOutDroppedByVote( ); + string PlayerVotedToDropLaggers( string user ); + string LatencyIs( string latency ); + string SyncLimitIs( string synclimit ); + string SettingSyncLimitToMinimum( string min ); + string SettingSyncLimitToMaximum( string max ); + string SettingSyncLimitTo( string synclimit ); + string UnableToCreateGameNotLoggedIn( string gamename ); + string AdminLoggedIn( ); + string AdminInvalidPassword( string attempt ); + string ConnectingToBNET( string server ); + string ConnectedToBNET( string server ); + string DisconnectedFromBNET( string server ); + string LoggedInToBNET( string server ); + string BNETGameHostingSucceeded( string server ); + string BNETGameHostingFailed( string server, string gamename ); + string ConnectingToBNETTimedOut( string server ); + string PlayerDownloadedTheMap( string user, string seconds, string rate ); + string UnableToCreateGameNameTooLong( string gamename ); + string SettingGameOwnerTo( string owner ); + string TheGameIsLocked( ); + string GameLocked( ); + string GameUnlocked( ); + string UnableToStartDownloadNoMatchesFound( string victim ); + string UnableToStartDownloadFoundMoreThanOneMatch( string victim ); + string UnableToSetGameOwner( string owner ); + string UnableToCheckPlayerNoMatchesFound( string victim ); + string CheckedPlayer( string victim, string ping, string from, string admin, string owner, string spoofed, string spoofedrealm, string reserved ); + string UnableToCheckPlayerFoundMoreThanOneMatch( string victim ); + string TheGameIsLockedBNET( ); + string UnableToCreateGameDisabled( string gamename ); + string BotDisabled( ); + string BotEnabled( ); + string UnableToCreateGameInvalidMap( string gamename ); + string WaitingForPlayersBeforeAutoStart( string players, string playersleft ); + string AutoStartDisabled( ); + string AutoStartEnabled( string players ); + string AnnounceMessageEnabled( ); + string AnnounceMessageDisabled( ); + string AutoHostEnabled( ); + string AutoHostDisabled( ); + string UnableToLoadSaveGamesOutside( ); + string UnableToLoadSaveGameGameInLobby( ); + string LoadingSaveGame( string file ); + string UnableToLoadSaveGameDoesntExist( string file ); + string UnableToCreateGameInvalidSaveGame( string gamename ); + string UnableToCreateGameSaveGameMapMismatch( string gamename ); + string AutoSaveEnabled( ); + string AutoSaveDisabled( ); + string DesyncDetected( ); + string UnableToMuteNoMatchesFound( string victim ); + string MutedPlayer( string victim, string user ); + string UnmutedPlayer( string victim, string user ); + string UnableToMuteFoundMoreThanOneMatch( string victim ); + string PlayerIsSavingTheGame( string player ); + string UpdatingClanList( ); + string UpdatingFriendsList( ); + string MultipleIPAddressUsageDetected( string player, string others ); + string UnableToVoteKickAlreadyInProgress( ); + string UnableToVoteKickNotEnoughPlayers( ); + string UnableToVoteKickNoMatchesFound( string victim ); + string UnableToVoteKickPlayerIsReserved( string victim ); + string StartedVoteKick( string victim, string user, string votesneeded ); + string UnableToVoteKickFoundMoreThanOneMatch( string victim ); + string VoteKickPassed( string victim ); + string ErrorVoteKickingPlayer( string victim ); + string VoteKickAcceptedNeedMoreVotes( string victim, string user, string votes ); + string VoteKickCancelled( string victim ); + string VoteKickExpired( string victim ); + string WasKickedByVote( ); + string TypeYesToVote( string commandtrigger ); + string PlayersNotYetPingedAutoStart( string notpinged ); + string WasKickedForNotSpoofChecking( ); + string WasKickedForHavingFurthestScore( string score, string average ); + string PlayerHasScore( string player, string score ); + string RatedPlayersSpread( string rated, string total, string spread ); + string ErrorListingMaps( ); + string FoundMaps( string maps ); + string NoMapsFound( ); + string ErrorListingMapConfigs( ); + string FoundMapConfigs( string mapconfigs ); + string NoMapConfigsFound( ); + string PlayerFinishedLoading( string user ); + string PleaseWaitPlayersStillLoading( ); + string MapDownloadsDisabled( ); + string MapDownloadsEnabled( ); + string MapDownloadsConditional( ); + string SettingHCL( string HCL ); + string UnableToSetHCLInvalid( ); + string UnableToSetHCLTooLong( ); + string TheHCLIs( string HCL ); + string TheHCLIsTooLongUseForceToStart( ); + string ClearingHCL( ); + string TryingToRehostAsPrivateGame( string gamename ); + string TryingToRehostAsPublicGame( string gamename ); + string RehostWasSuccessful( ); + string TryingToJoinTheGameButBannedByName( string victim ); + string TryingToJoinTheGameButBannedByIP( string victim, string ip, string bannedname ); + string HasBannedName( string victim ); + string HasBannedIP( string victim, string ip, string bannedname ); + string PlayersInGameState( string number, string players ); + string ValidServers( string servers ); + string TeamCombinedScore( string team, string score ); + string BalancingSlotsCompleted( ); + string PlayerWasKickedForFurthestScore( string name, string score, string average ); + string LocalAdminMessagesEnabled( ); + string LocalAdminMessagesDisabled( ); + string WasDroppedDesync( ); + string WasKickedForHavingLowestScore( string score ); + string PlayerWasKickedForLowestScore( string name, string score ); + string ReloadingConfigurationFiles( ); + string CountDownAbortedSomeoneLeftRecently( ); + string UnableToCreateGameMustEnforceFirst( string gamename ); + string UnableToLoadReplaysOutside( ); + string LoadingReplay( string file ); + string UnableToLoadReplayDoesntExist( string file ); + string CommandTrigger( string trigger ); + string CantEndGameOwnerIsStillPlaying( string owner ); + string CantUnhostGameOwnerIsPresent( string owner ); + string WasAutomaticallyDroppedAfterSeconds( string seconds ); + string HasLostConnectionTimedOutGProxy( ); + string HasLostConnectionSocketErrorGProxy( string error ); + string HasLostConnectionClosedByRemoteHostGProxy( ); + string WaitForReconnectSecondsRemain( string seconds ); + string WasUnrecoverablyDroppedFromGProxy( ); + string PlayerReconnectedWithGProxy( string name ); +}; + +#endif diff --git a/ghost-legacy/map.cpp b/ghost-legacy/map.cpp new file mode 100644 index 0000000..857a997 --- /dev/null +++ b/ghost-legacy/map.cpp @@ -0,0 +1,1001 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "crc32.h" +#include "sha1.h" +#include "config.h" +#include "map.h" + +#define __STORMLIB_SELF__ +#include + +#define ROTL(x,n) ((x)<<(n))|((x)>>(32-(n))) // this won't work with signed types +#define ROTR(x,n) ((x)>>(n))|((x)<<(32-(n))) // this won't work with signed types + +// +// CMap +// + +CMap :: CMap( CGHost *nGHost ) +{ + CONSOLE_Print( "[MAP] using hardcoded Emerald Gardens map data for Warcraft 3 version 1.24 & 1.24b" ); + m_GHost = nGHost; + m_Valid = true; + m_MapPath = "Maps\\FrozenThrone\\(12)EmeraldGardens.w3x"; + m_MapSize = UTIL_ExtractNumbers( "174 221 4 0", 4 ); + m_MapInfo = UTIL_ExtractNumbers( "251 57 68 98", 4 ); + m_MapCRC = UTIL_ExtractNumbers( "108 250 204 59", 4 ); + m_MapSHA1 = UTIL_ExtractNumbers( "35 81 104 182 223 63 204 215 1 17 87 234 220 66 3 185 82 99 6 13", 20 ); + m_MapSpeed = MAPSPEED_FAST; + m_MapVisibility = MAPVIS_DEFAULT; + m_MapObservers = MAPOBS_NONE; + m_MapFlags = MAPFLAG_TEAMSTOGETHER | MAPFLAG_FIXEDTEAMS; + m_MapFilterMaker = MAPFILTER_MAKER_BLIZZARD; + m_MapFilterType = MAPFILTER_TYPE_MELEE; + m_MapFilterSize = MAPFILTER_SIZE_LARGE; + m_MapFilterObs = MAPFILTER_OBS_NONE; + m_MapOptions = MAPOPT_MELEE; + m_MapWidth = UTIL_ExtractNumbers( "172 0", 2 ); + m_MapHeight = UTIL_ExtractNumbers( "172 0", 2 ); + m_MapLoadInGame = false; + m_MapNumPlayers = 12; + m_MapNumTeams = 12; + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 0, 0, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 1, 1, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 2, 2, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 3, 3, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 4, 4, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 5, 5, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 6, 6, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 7, 7, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 8, 8, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 9, 9, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 10, 10, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 11, 11, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) ); +} + +CMap :: CMap( CGHost *nGHost, CConfig *CFG, string nCFGFile ) +{ + m_GHost = nGHost; + Load( CFG, nCFGFile ); +} + +CMap :: ~CMap( ) +{ + +} + +BYTEARRAY CMap :: GetMapGameFlags( ) +{ + /* + + Speed: (mask 0x00000003) cannot be combined + 0x00000000 - Slow game speed + 0x00000001 - Normal game speed + 0x00000002 - Fast game speed + Visibility: (mask 0x00000F00) cannot be combined + 0x00000100 - Hide terrain + 0x00000200 - Map explored + 0x00000400 - Always visible (no fog of war) + 0x00000800 - Default + Observers/Referees: (mask 0x40003000) cannot be combined + 0x00000000 - No Observers + 0x00002000 - Observers on Defeat + 0x00003000 - Additional players as observer allowed + 0x40000000 - Referees + Teams/Units/Hero/Race: (mask 0x07064000) can be combined + 0x00004000 - Teams Together (team members are placed at neighbored starting locations) + 0x00060000 - Fixed teams + 0x01000000 - Unit share + 0x02000000 - Random hero + 0x04000000 - Random races + + */ + + uint32_t GameFlags = 0; + + // speed + + if( m_MapSpeed == MAPSPEED_SLOW ) + GameFlags = 0x00000000; + else if( m_MapSpeed == MAPSPEED_NORMAL ) + GameFlags = 0x00000001; + else + GameFlags = 0x00000002; + + // visibility + + if( m_MapVisibility == MAPVIS_HIDETERRAIN ) + GameFlags |= 0x00000100; + else if( m_MapVisibility == MAPVIS_EXPLORED ) + GameFlags |= 0x00000200; + else if( m_MapVisibility == MAPVIS_ALWAYSVISIBLE ) + GameFlags |= 0x00000400; + else + GameFlags |= 0x00000800; + + // observers + + if( m_MapObservers == MAPOBS_ONDEFEAT ) + GameFlags |= 0x00002000; + else if( m_MapObservers == MAPOBS_ALLOWED ) + GameFlags |= 0x00003000; + else if( m_MapObservers == MAPOBS_REFEREES ) + GameFlags |= 0x40000000; + + // teams/units/hero/race + + if( m_MapFlags & MAPFLAG_TEAMSTOGETHER ) + GameFlags |= 0x00004000; + if( m_MapFlags & MAPFLAG_FIXEDTEAMS ) + GameFlags |= 0x00060000; + if( m_MapFlags & MAPFLAG_UNITSHARE ) + GameFlags |= 0x01000000; + if( m_MapFlags & MAPFLAG_RANDOMHERO ) + GameFlags |= 0x02000000; + if( m_MapFlags & MAPFLAG_RANDOMRACES ) + GameFlags |= 0x04000000; + + return UTIL_CreateByteArray( GameFlags, false ); +} + +uint32_t CMap :: GetMapGameType( ) +{ + /* spec stolen from Strilanc as follows: + + Public Enum GameTypes As UInteger + None = 0 + Unknown0 = 1 << 0 '[always seems to be set?] + + '''Setting this bit causes wc3 to check the map and disc if it is not signed by Blizzard + AuthenticatedMakerBlizzard = 1 << 3 + OfficialMeleeGame = 1 << 5 + + SavedGame = 1 << 9 + PrivateGame = 1 << 11 + + MakerUser = 1 << 13 + MakerBlizzard = 1 << 14 + TypeMelee = 1 << 15 + TypeScenario = 1 << 16 + SizeSmall = 1 << 17 + SizeMedium = 1 << 18 + SizeLarge = 1 << 19 + ObsFull = 1 << 20 + ObsOnDeath = 1 << 21 + ObsNone = 1 << 22 + + MaskObs = ObsFull Or ObsOnDeath Or ObsNone + MaskMaker = MakerBlizzard Or MakerUser + MaskType = TypeMelee Or TypeScenario + MaskSize = SizeLarge Or SizeMedium Or SizeSmall + MaskFilterable = MaskObs Or MaskMaker Or MaskType Or MaskSize + End Enum + + */ + + // note: we allow "conflicting" flags to be set at the same time (who knows if this is a good idea) + // we also don't set any flags this class is unaware of such as Unknown0, SavedGame, and PrivateGame + + uint32_t GameType = 0; + + // maker + + if( m_MapFilterMaker & MAPFILTER_MAKER_USER ) + GameType |= MAPGAMETYPE_MAKERUSER; + if( m_MapFilterMaker & MAPFILTER_MAKER_BLIZZARD ) + GameType |= MAPGAMETYPE_MAKERBLIZZARD; + + // type + + if( m_MapFilterType & MAPFILTER_TYPE_MELEE ) + GameType |= MAPGAMETYPE_TYPEMELEE; + if( m_MapFilterType & MAPFILTER_TYPE_SCENARIO ) + GameType |= MAPGAMETYPE_TYPESCENARIO; + + // size + + if( m_MapFilterSize & MAPFILTER_SIZE_SMALL ) + GameType |= MAPGAMETYPE_SIZESMALL; + if( m_MapFilterSize & MAPFILTER_SIZE_MEDIUM ) + GameType |= MAPGAMETYPE_SIZEMEDIUM; + if( m_MapFilterSize & MAPFILTER_SIZE_LARGE ) + GameType |= MAPGAMETYPE_SIZELARGE; + + // obs + + if( m_MapFilterObs & MAPFILTER_OBS_FULL ) + GameType |= MAPGAMETYPE_OBSFULL; + if( m_MapFilterObs & MAPFILTER_OBS_ONDEATH ) + GameType |= MAPGAMETYPE_OBSONDEATH; + if( m_MapFilterObs & MAPFILTER_OBS_NONE ) + GameType |= MAPGAMETYPE_OBSNONE; + + return GameType; +} + +unsigned char CMap :: GetMapLayoutStyle( ) +{ + // 0 = melee + // 1 = custom forces + // 2 = fixed player settings (not possible with the Warcraft III map editor) + // 3 = custom forces + fixed player settings + + if( !( m_MapOptions & MAPOPT_CUSTOMFORCES ) ) + return 0; + + if( !( m_MapOptions & MAPOPT_FIXEDPLAYERSETTINGS ) ) + return 1; + + return 3; +} + +void CMap :: Load( CConfig *CFG, string nCFGFile ) +{ + m_Valid = true; + m_CFGFile = nCFGFile; + + // load the map data + + m_MapLocalPath = CFG->GetString( "map_localpath", string( ) ); + m_MapData.clear( ); + + if( !m_MapLocalPath.empty( ) ) + m_MapData = UTIL_FileRead( m_GHost->m_MapPath + m_MapLocalPath ); + + // load the map MPQ + + string MapMPQFileName = m_GHost->m_MapPath + m_MapLocalPath; + HANDLE MapMPQ; + bool MapMPQReady = false; + + if( SFileOpenArchive( MapMPQFileName.c_str( ), 0, MPQ_OPEN_FORCE_MPQ_V1, &MapMPQ ) ) + { + CONSOLE_Print( "[MAP] loading MPQ file [" + MapMPQFileName + "]" ); + MapMPQReady = true; + } + else + CONSOLE_Print( "[MAP] warning - unable to load MPQ file [" + MapMPQFileName + "]" ); + + // try to calculate map_size, map_info, map_crc, map_sha1 + + BYTEARRAY MapSize; + BYTEARRAY MapInfo; + BYTEARRAY MapCRC; + BYTEARRAY MapSHA1; + + if( !m_MapData.empty( ) ) + { + m_GHost->m_SHA->Reset( ); + + // calculate map_size + + MapSize = UTIL_CreateByteArray( (uint32_t)m_MapData.size( ), false ); + CONSOLE_Print( "[MAP] calculated map_size = " + UTIL_ByteArrayToDecString( MapSize ) ); + + // calculate map_info (this is actually the CRC) + + MapInfo = UTIL_CreateByteArray( (uint32_t)m_GHost->m_CRC->FullCRC( (unsigned char *)m_MapData.c_str( ), m_MapData.size( ) ), false ); + CONSOLE_Print( "[MAP] calculated map_info = " + UTIL_ByteArrayToDecString( MapInfo ) ); + + // calculate map_crc (this is not the CRC) and map_sha1 + // a big thank you to Strilanc for figuring the map_crc algorithm out + + string CommonJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "common.j" ); + + if( CommonJ.empty( ) ) + CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "common.j]" ); + else + { + string BlizzardJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "blizzard.j" ); + + if( BlizzardJ.empty( ) ) + CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "blizzard.j]" ); + else + { + uint32_t Val = 0; + + // update: it's possible for maps to include their own copies of common.j and/or blizzard.j + // this code now overrides the default copies if required + + bool OverrodeCommonJ = false; + bool OverrodeBlizzardJ = false; + + if( MapMPQReady ) + { + HANDLE SubFile; + + // override common.j + + if( SFileOpenFileEx( MapMPQ, "Scripts\\common.j", 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + CONSOLE_Print( "[MAP] overriding default common.j with map copy while calculating map_crc/sha1" ); + OverrodeCommonJ = true; + Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ); + m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); + } + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + } + + if( !OverrodeCommonJ ) + { + Val = Val ^ XORRotateLeft( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) ); + m_GHost->m_SHA->Update( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) ); + } + + if( MapMPQReady ) + { + HANDLE SubFile; + + // override blizzard.j + + if( SFileOpenFileEx( MapMPQ, "Scripts\\blizzard.j", 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + CONSOLE_Print( "[MAP] overriding default blizzard.j with map copy while calculating map_crc/sha1" ); + OverrodeBlizzardJ = true; + Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ); + m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); + } + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + } + + if( !OverrodeBlizzardJ ) + { + Val = Val ^ XORRotateLeft( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) ); + m_GHost->m_SHA->Update( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) ); + } + + Val = ROTL( Val, 3 ); + Val = ROTL( Val ^ 0x03F1379E, 3 ); + m_GHost->m_SHA->Update( (unsigned char *)"\x9E\x37\xF1\x03", 4 ); + + if( MapMPQReady ) + { + vector FileList; + FileList.push_back( "war3map.j" ); + FileList.push_back( "scripts\\war3map.j" ); + FileList.push_back( "war3map.w3e" ); + FileList.push_back( "war3map.wpm" ); + FileList.push_back( "war3map.doo" ); + FileList.push_back( "war3map.w3u" ); + FileList.push_back( "war3map.w3b" ); + FileList.push_back( "war3map.w3d" ); + FileList.push_back( "war3map.w3a" ); + FileList.push_back( "war3map.w3q" ); + bool FoundScript = false; + + for( vector :: iterator i = FileList.begin( ); i != FileList.end( ); i++ ) + { + // don't use scripts\war3map.j if we've already used war3map.j (yes, some maps have both but only war3map.j is used) + + if( FoundScript && *i == "scripts\\war3map.j" ) + continue; + + HANDLE SubFile; + + if( SFileOpenFileEx( MapMPQ, (*i).c_str( ), 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + if( *i == "war3map.j" || *i == "scripts\\war3map.j" ) + FoundScript = true; + + Val = ROTL( Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ), 3 ); + m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); + // DEBUG_Print( "*** found: " + *i ); + } + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + else + { + // DEBUG_Print( "*** not found: " + *i ); + } + } + + if( !FoundScript ) + CONSOLE_Print( "[MAP] couldn't find war3map.j or scripts\\war3map.j in MPQ file, calculated map_crc/sha1 is probably wrong" ); + + MapCRC = UTIL_CreateByteArray( Val, false ); + CONSOLE_Print( "[MAP] calculated map_crc = " + UTIL_ByteArrayToDecString( MapCRC ) ); + + m_GHost->m_SHA->Final( ); + unsigned char SHA1[20]; + memset( SHA1, 0, sizeof( unsigned char ) * 20 ); + m_GHost->m_SHA->GetHash( SHA1 ); + MapSHA1 = UTIL_CreateByteArray( SHA1, 20 ); + CONSOLE_Print( "[MAP] calculated map_sha1 = " + UTIL_ByteArrayToDecString( MapSHA1 ) ); + } + else + CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - map MPQ file not loaded" ); + } + } + } + else + CONSOLE_Print( "[MAP] no map data available, using config file for map_size, map_info, map_crc, map_sha1" ); + + // try to calculate map_width, map_height, map_slot, map_numplayers, map_numteams + + uint32_t MapOptions = 0; + BYTEARRAY MapWidth; + BYTEARRAY MapHeight; + uint32_t MapNumPlayers = 0; + uint32_t MapNumTeams = 0; + vector Slots; + + if( !m_MapData.empty( ) ) + { + if( MapMPQReady ) + { + HANDLE SubFile; + + if( SFileOpenFileEx( MapMPQ, "war3map.w3i", 0, &SubFile ) ) + { + uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); + + if( FileLength > 0 && FileLength != 0xFFFFFFFF ) + { + char *SubFileData = new char[FileLength]; + DWORD BytesRead = 0; + + if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) + { + istringstream ISS( string( SubFileData, BytesRead ) ); + + // war3map.w3i format found at http://www.wc3campaigns.net/tools/specs/index.html by Zepir/PitzerMike + + string GarbageString; + uint32_t FileFormat; + uint32_t RawMapWidth; + uint32_t RawMapHeight; + uint32_t RawMapFlags; + uint32_t RawMapNumPlayers; + uint32_t RawMapNumTeams; + + ISS.read( (char *)&FileFormat, 4 ); // file format (18 = ROC, 25 = TFT) + + if( FileFormat == 18 || FileFormat == 25 ) + { + ISS.seekg( 4, ios :: cur ); // number of saves + ISS.seekg( 4, ios :: cur ); // editor version + getline( ISS, GarbageString, '\0' ); // map name + getline( ISS, GarbageString, '\0' ); // map author + getline( ISS, GarbageString, '\0' ); // map description + getline( ISS, GarbageString, '\0' ); // players recommended + ISS.seekg( 32, ios :: cur ); // camera bounds + ISS.seekg( 16, ios :: cur ); // camera bounds complements + ISS.read( (char *)&RawMapWidth, 4 ); // map width + ISS.read( (char *)&RawMapHeight, 4 ); // map height + ISS.read( (char *)&RawMapFlags, 4 ); // flags + ISS.seekg( 1, ios :: cur ); // map main ground type + + if( FileFormat == 18 ) + ISS.seekg( 4, ios :: cur ); // campaign background number + else if( FileFormat == 25 ) + { + ISS.seekg( 4, ios :: cur ); // loading screen background number + getline( ISS, GarbageString, '\0' ); // path of custom loading screen model + } + + getline( ISS, GarbageString, '\0' ); // map loading screen text + getline( ISS, GarbageString, '\0' ); // map loading screen title + getline( ISS, GarbageString, '\0' ); // map loading screen subtitle + + if( FileFormat == 18 ) + ISS.seekg( 4, ios :: cur ); // map loading screen number + else if( FileFormat == 25 ) + { + ISS.seekg( 4, ios :: cur ); // used game data set + getline( ISS, GarbageString, '\0' ); // prologue screen path + } + + getline( ISS, GarbageString, '\0' ); // prologue screen text + getline( ISS, GarbageString, '\0' ); // prologue screen title + getline( ISS, GarbageString, '\0' ); // prologue screen subtitle + + if( FileFormat == 25 ) + { + ISS.seekg( 4, ios :: cur ); // uses terrain fog + ISS.seekg( 4, ios :: cur ); // fog start z height + ISS.seekg( 4, ios :: cur ); // fog end z height + ISS.seekg( 4, ios :: cur ); // fog density + ISS.seekg( 1, ios :: cur ); // fog red value + ISS.seekg( 1, ios :: cur ); // fog green value + ISS.seekg( 1, ios :: cur ); // fog blue value + ISS.seekg( 1, ios :: cur ); // fog alpha value + ISS.seekg( 4, ios :: cur ); // global weather id + getline( ISS, GarbageString, '\0' ); // custom sound environment + ISS.seekg( 1, ios :: cur ); // tileset id of the used custom light environment + ISS.seekg( 1, ios :: cur ); // custom water tinting red value + ISS.seekg( 1, ios :: cur ); // custom water tinting green value + ISS.seekg( 1, ios :: cur ); // custom water tinting blue value + ISS.seekg( 1, ios :: cur ); // custom water tinting alpha value + } + + ISS.read( (char *)&RawMapNumPlayers, 4 ); // number of players + uint32_t ClosedSlots = 0; + + for( uint32_t i = 0; i < RawMapNumPlayers; i++ ) + { + CGameSlot Slot( 0, 255, SLOTSTATUS_OPEN, 0, 0, 1, SLOTRACE_RANDOM ); + uint32_t Colour; + uint32_t Status; + uint32_t Race; + + ISS.read( (char *)&Colour, 4 ); // colour + Slot.SetColour( Colour ); + ISS.read( (char *)&Status, 4 ); // status + + if( Status == 1 ) + Slot.SetSlotStatus( SLOTSTATUS_OPEN ); + else if( Status == 2 ) + { + Slot.SetSlotStatus( SLOTSTATUS_OCCUPIED ); + Slot.SetComputer( 1 ); + Slot.SetComputerType( SLOTCOMP_NORMAL ); + } + else + { + Slot.SetSlotStatus( SLOTSTATUS_CLOSED ); + ClosedSlots++; + } + + ISS.read( (char *)&Race, 4 ); // race + + if( Race == 1 ) + Slot.SetRace( SLOTRACE_HUMAN ); + else if( Race == 2 ) + Slot.SetRace( SLOTRACE_ORC ); + else if( Race == 3 ) + Slot.SetRace( SLOTRACE_UNDEAD ); + else if( Race == 4 ) + Slot.SetRace( SLOTRACE_NIGHTELF ); + else + Slot.SetRace( SLOTRACE_RANDOM ); + + ISS.seekg( 4, ios :: cur ); // fixed start position + getline( ISS, GarbageString, '\0' ); // player name + ISS.seekg( 4, ios :: cur ); // start position x + ISS.seekg( 4, ios :: cur ); // start position y + ISS.seekg( 4, ios :: cur ); // ally low priorities + ISS.seekg( 4, ios :: cur ); // ally high priorities + + if( Slot.GetSlotStatus( ) != SLOTSTATUS_CLOSED ) + Slots.push_back( Slot ); + } + + ISS.read( (char *)&RawMapNumTeams, 4 ); // number of teams + + for( uint32_t i = 0; i < RawMapNumTeams; i++ ) + { + uint32_t Flags; + uint32_t PlayerMask; + + ISS.read( (char *)&Flags, 4 ); // flags + ISS.read( (char *)&PlayerMask, 4 ); // player mask + + for( unsigned char j = 0; j < 12; j++ ) + { + if( PlayerMask & 1 ) + { + for( vector :: iterator k = Slots.begin( ); k != Slots.end( ); k++ ) + { + if( (*k).GetColour( ) == j ) + (*k).SetTeam( i ); + } + } + + PlayerMask >>= 1; + } + + getline( ISS, GarbageString, '\0' ); // team name + } + + // the bot only cares about the following options: melee, fixed player settings, custom forces + // let's not confuse the user by displaying erroneous map options so zero them out now + + MapOptions = RawMapFlags & ( MAPOPT_MELEE | MAPOPT_FIXEDPLAYERSETTINGS | MAPOPT_CUSTOMFORCES ); + CONSOLE_Print( "[MAP] calculated map_options = " + UTIL_ToString( MapOptions ) ); + MapWidth = UTIL_CreateByteArray( (uint16_t)RawMapWidth, false ); + CONSOLE_Print( "[MAP] calculated map_width = " + UTIL_ByteArrayToDecString( MapWidth ) ); + MapHeight = UTIL_CreateByteArray( (uint16_t)RawMapHeight, false ); + CONSOLE_Print( "[MAP] calculated map_height = " + UTIL_ByteArrayToDecString( MapHeight ) ); + MapNumPlayers = RawMapNumPlayers - ClosedSlots; + CONSOLE_Print( "[MAP] calculated map_numplayers = " + UTIL_ToString( MapNumPlayers ) ); + MapNumTeams = RawMapNumTeams; + CONSOLE_Print( "[MAP] calculated map_numteams = " + UTIL_ToString( MapNumTeams ) ); + + uint32_t SlotNum = 1; + + for( vector :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) + { + CONSOLE_Print( "[MAP] calculated map_slot" + UTIL_ToString( SlotNum ) + " = " + UTIL_ByteArrayToDecString( (*i).GetByteArray( ) ) ); + SlotNum++; + } + + if( MapOptions & MAPOPT_MELEE ) + { + CONSOLE_Print( "[MAP] found melee map, initializing slots" ); + + // give each slot a different team and set the race to random + + unsigned char Team = 0; + + for( vector :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) + { + (*i).SetTeam( Team++ ); + (*i).SetRace( SLOTRACE_RANDOM ); + } + } + + if( !( MapOptions & MAPOPT_FIXEDPLAYERSETTINGS ) ) + { + // make races selectable + + for( vector :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) + (*i).SetRace( (*i).GetRace( ) | SLOTRACE_SELECTABLE ); + } + } + } + else + CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot, map_numplayers, map_numteams - unable to extract war3map.w3i from MPQ file" ); + + delete [] SubFileData; + } + + SFileCloseFile( SubFile ); + } + else + CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot, map_numplayers, map_numteams - couldn't find war3map.w3i in MPQ file" ); + } + else + CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot, map_numplayers, map_numteams - map MPQ file not loaded" ); + } + else + CONSOLE_Print( "[MAP] no map data available, using config file for map_options, map_width, map_height, map_slot, map_numplayers, map_numteams" ); + + // close the map MPQ + + if( MapMPQReady ) + SFileCloseArchive( MapMPQ ); + + m_MapPath = CFG->GetString( "map_path", string( ) ); + + if( MapSize.empty( ) ) + MapSize = UTIL_ExtractNumbers( CFG->GetString( "map_size", string( ) ), 4 ); + else if( CFG->Exists( "map_size" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_size with config value map_size = " + CFG->GetString( "map_size", string( ) ) ); + MapSize = UTIL_ExtractNumbers( CFG->GetString( "map_size", string( ) ), 4 ); + } + + m_MapSize = MapSize; + + if( MapInfo.empty( ) ) + MapInfo = UTIL_ExtractNumbers( CFG->GetString( "map_info", string( ) ), 4 ); + else if( CFG->Exists( "map_info" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_info with config value map_info = " + CFG->GetString( "map_info", string( ) ) ); + MapInfo = UTIL_ExtractNumbers( CFG->GetString( "map_info", string( ) ), 4 ); + } + + m_MapInfo = MapInfo; + + if( MapCRC.empty( ) ) + MapCRC = UTIL_ExtractNumbers( CFG->GetString( "map_crc", string( ) ), 4 ); + else if( CFG->Exists( "map_crc" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_crc with config value map_crc = " + CFG->GetString( "map_crc", string( ) ) ); + MapCRC = UTIL_ExtractNumbers( CFG->GetString( "map_crc", string( ) ), 4 ); + } + + m_MapCRC = MapCRC; + + if( MapSHA1.empty( ) ) + MapSHA1 = UTIL_ExtractNumbers( CFG->GetString( "map_sha1", string( ) ), 20 ); + else if( CFG->Exists( "map_sha1" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_sha1 with config value map_sha1 = " + CFG->GetString( "map_sha1", string( ) ) ); + MapSHA1 = UTIL_ExtractNumbers( CFG->GetString( "map_sha1", string( ) ), 20 ); + } + + m_MapSHA1 = MapSHA1; + m_MapSpeed = CFG->GetInt( "map_speed", MAPSPEED_FAST ); + m_MapVisibility = CFG->GetInt( "map_visibility", MAPVIS_DEFAULT ); + m_MapObservers = CFG->GetInt( "map_observers", MAPOBS_NONE ); + m_MapFlags = CFG->GetInt( "map_flags", MAPFLAG_TEAMSTOGETHER | MAPFLAG_FIXEDTEAMS ); + m_MapFilterMaker = CFG->GetInt( "map_filter_maker", MAPFILTER_MAKER_USER ); + m_MapFilterType = CFG->GetInt( "map_filter_type", 0 ); + m_MapFilterSize = CFG->GetInt( "map_filter_size", MAPFILTER_SIZE_LARGE ); + m_MapFilterObs = CFG->GetInt( "map_filter_obs", MAPFILTER_OBS_NONE ); + + // todotodo: it might be possible for MapOptions to legitimately be zero so this is not a valid way of checking if it wasn't parsed out earlier + + if( MapOptions == 0 ) + MapOptions = CFG->GetInt( "map_options", 0 ); + else if( CFG->Exists( "map_options" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_options with config value map_options = " + CFG->GetString( "map_options", string( ) ) ); + MapOptions = CFG->GetInt( "map_options", 0 ); + } + + m_MapOptions = MapOptions; + + if( MapWidth.empty( ) ) + MapWidth = UTIL_ExtractNumbers( CFG->GetString( "map_width", string( ) ), 2 ); + else if( CFG->Exists( "map_width" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_width with config value map_width = " + CFG->GetString( "map_width", string( ) ) ); + MapWidth = UTIL_ExtractNumbers( CFG->GetString( "map_width", string( ) ), 2 ); + } + + m_MapWidth = MapWidth; + + if( MapHeight.empty( ) ) + MapHeight = UTIL_ExtractNumbers( CFG->GetString( "map_height", string( ) ), 2 ); + else if( CFG->Exists( "map_height" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_height with config value map_height = " + CFG->GetString( "map_height", string( ) ) ); + MapHeight = UTIL_ExtractNumbers( CFG->GetString( "map_height", string( ) ), 2 ); + } + + m_MapHeight = MapHeight; + m_MapType = CFG->GetString( "map_type", string( ) ); + m_MapMatchMakingCategory = CFG->GetString( "map_matchmakingcategory", string( ) ); + m_MapStatsW3MMDCategory = CFG->GetString( "map_statsw3mmdcategory", string( ) ); + m_MapDefaultHCL = CFG->GetString( "map_defaulthcl", string( ) ); + m_MapDefaultPlayerScore = CFG->GetInt( "map_defaultplayerscore", 1000 ); + m_MapLoadInGame = CFG->GetInt( "map_loadingame", 0 ) == 0 ? false : true; + + if( MapNumPlayers == 0 ) + MapNumPlayers = CFG->GetInt( "map_numplayers", 0 ); + else if( CFG->Exists( "map_numplayers" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_numplayers with config value map_numplayers = " + CFG->GetString( "map_numplayers", string( ) ) ); + MapNumPlayers = CFG->GetInt( "map_numplayers", 0 ); + } + + m_MapNumPlayers = MapNumPlayers; + + if( MapNumTeams == 0 ) + MapNumTeams = CFG->GetInt( "map_numteams", 0 ); + else if( CFG->Exists( "map_numteams" ) ) + { + CONSOLE_Print( "[MAP] overriding calculated map_numteams with config value map_numteams = " + CFG->GetString( "map_numteams", string( ) ) ); + MapNumTeams = CFG->GetInt( "map_numteams", 0 ); + } + + m_MapNumTeams = MapNumTeams; + + if( Slots.empty( ) ) + { + for( uint32_t Slot = 1; Slot <= 12; Slot++ ) + { + string SlotString = CFG->GetString( "map_slot" + UTIL_ToString( Slot ), string( ) ); + + if( SlotString.empty( ) ) + break; + + BYTEARRAY SlotData = UTIL_ExtractNumbers( SlotString, 9 ); + Slots.push_back( CGameSlot( SlotData ) ); + } + } + else if( CFG->Exists( "map_slot1" ) ) + { + CONSOLE_Print( "[MAP] overriding slots" ); + Slots.clear( ); + + for( uint32_t Slot = 1; Slot <= 12; Slot++ ) + { + string SlotString = CFG->GetString( "map_slot" + UTIL_ToString( Slot ), string( ) ); + + if( SlotString.empty( ) ) + break; + + BYTEARRAY SlotData = UTIL_ExtractNumbers( SlotString, 9 ); + Slots.push_back( CGameSlot( SlotData ) ); + } + } + + m_Slots = Slots; + + // if random races is set force every slot's race to random + + if( m_MapFlags & MAPFLAG_RANDOMRACES ) + { + CONSOLE_Print( "[MAP] forcing races to random" ); + + for( vector :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); i++ ) + (*i).SetRace( SLOTRACE_RANDOM ); + } + + // add observer slots + + if( m_MapObservers == MAPOBS_ALLOWED || m_MapObservers == MAPOBS_REFEREES ) + { + CONSOLE_Print( "[MAP] adding " + UTIL_ToString( 12 - m_Slots.size( ) ) + " observer slots" ); + + while( m_Slots.size( ) < 12 ) + m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 12, 12, SLOTRACE_RANDOM ) ); + } + + CheckValid( ); +} + +void CMap :: CheckValid( ) +{ + // todotodo: should this code fix any errors it sees rather than just warning the user? + + if( m_MapPath.empty( ) ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_path detected" ); + } + else if( m_MapPath[0] == '\\' ) + CONSOLE_Print( "[MAP] warning - map_path starts with '\\', any replays saved by GHost++ will not be playable in Warcraft III" ); + + if( m_MapPath.find( '/' ) != string :: npos ) + CONSOLE_Print( "[MAP] warning - map_path contains forward slashes '/' but it must use Windows style back slashes '\\'" ); + + if( m_MapSize.size( ) != 4 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_size detected" ); + } + else if( !m_MapData.empty( ) && m_MapData.size( ) != UTIL_ByteArrayToUInt32( m_MapSize, false ) ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_size detected - size mismatch with actual map data" ); + } + + if( m_MapInfo.size( ) != 4 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_info detected" ); + } + + if( m_MapCRC.size( ) != 4 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_crc detected" ); + } + + if( m_MapSHA1.size( ) != 20 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_sha1 detected" ); + } + + if( m_MapSpeed != MAPSPEED_SLOW && m_MapSpeed != MAPSPEED_NORMAL && m_MapSpeed != MAPSPEED_FAST ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_speed detected" ); + } + + if( m_MapVisibility != MAPVIS_HIDETERRAIN && m_MapVisibility != MAPVIS_EXPLORED && m_MapVisibility != MAPVIS_ALWAYSVISIBLE && m_MapVisibility != MAPVIS_DEFAULT ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_visibility detected" ); + } + + if( m_MapObservers != MAPOBS_NONE && m_MapObservers != MAPOBS_ONDEFEAT && m_MapObservers != MAPOBS_ALLOWED && m_MapObservers != MAPOBS_REFEREES ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_observers detected" ); + } + + // todotodo: m_MapFlags + // todotodo: m_MapFilterMaker, m_MapFilterType, m_MapFilterSize, m_MapFilterObs + + if( m_MapWidth.size( ) != 2 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_width detected" ); + } + + if( m_MapHeight.size( ) != 2 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_height detected" ); + } + + if( m_MapNumPlayers == 0 || m_MapNumPlayers > 12 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_numplayers detected" ); + } + + if( m_MapNumTeams == 0 || m_MapNumTeams > 12 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_numteams detected" ); + } + + if( m_Slots.empty( ) || m_Slots.size( ) > 12 ) + { + m_Valid = false; + CONSOLE_Print( "[MAP] invalid map_slot detected" ); + } +} + +uint32_t CMap :: XORRotateLeft( unsigned char *data, uint32_t length ) +{ + // a big thank you to Strilanc for figuring this out + + uint32_t i = 0; + uint32_t Val = 0; + + if( length > 3 ) + { + while( i < length - 3 ) + { + Val = ROTL( Val ^ ( (uint32_t)data[i] + (uint32_t)( data[i + 1] << 8 ) + (uint32_t)( data[i + 2] << 16 ) + (uint32_t)( data[i + 3] << 24 ) ), 3 ); + i += 4; + } + } + + while( i < length ) + { + Val = ROTL( Val ^ data[i], 3 ); + i++; + } + + return Val; +} diff --git a/ghost-legacy/map.h b/ghost-legacy/map.h new file mode 100644 index 0000000..48acc3f --- /dev/null +++ b/ghost-legacy/map.h @@ -0,0 +1,167 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef MAP_H +#define MAP_H + +#define MAPSPEED_SLOW 1 +#define MAPSPEED_NORMAL 2 +#define MAPSPEED_FAST 3 + +#define MAPVIS_HIDETERRAIN 1 +#define MAPVIS_EXPLORED 2 +#define MAPVIS_ALWAYSVISIBLE 3 +#define MAPVIS_DEFAULT 4 + +#define MAPOBS_NONE 1 +#define MAPOBS_ONDEFEAT 2 +#define MAPOBS_ALLOWED 3 +#define MAPOBS_REFEREES 4 + +#define MAPFLAG_TEAMSTOGETHER 1 +#define MAPFLAG_FIXEDTEAMS 2 +#define MAPFLAG_UNITSHARE 4 +#define MAPFLAG_RANDOMHERO 8 +#define MAPFLAG_RANDOMRACES 16 + +#define MAPOPT_HIDEMINIMAP 1 << 0 +#define MAPOPT_MODIFYALLYPRIORITIES 1 << 1 +#define MAPOPT_MELEE 1 << 2 // the bot cares about this one... +#define MAPOPT_REVEALTERRAIN 1 << 4 +#define MAPOPT_FIXEDPLAYERSETTINGS 1 << 5 // and this one... +#define MAPOPT_CUSTOMFORCES 1 << 6 // and this one, the rest don't affect the bot's logic +#define MAPOPT_CUSTOMTECHTREE 1 << 7 +#define MAPOPT_CUSTOMABILITIES 1 << 8 +#define MAPOPT_CUSTOMUPGRADES 1 << 9 +#define MAPOPT_WATERWAVESONCLIFFSHORES 1 << 11 +#define MAPOPT_WATERWAVESONSLOPESHORES 1 << 12 + +#define MAPFILTER_MAKER_USER 1 +#define MAPFILTER_MAKER_BLIZZARD 2 + +#define MAPFILTER_TYPE_MELEE 1 +#define MAPFILTER_TYPE_SCENARIO 2 + +#define MAPFILTER_SIZE_SMALL 1 +#define MAPFILTER_SIZE_MEDIUM 2 +#define MAPFILTER_SIZE_LARGE 4 + +#define MAPFILTER_OBS_FULL 1 +#define MAPFILTER_OBS_ONDEATH 2 +#define MAPFILTER_OBS_NONE 4 + +#define MAPGAMETYPE_UNKNOWN0 1 // always set except for saved games? +// AuthenticatedMakerBlizzard = 1 << 3 +// OfficialMeleeGame = 1 << 5 +#define MAPGAMETYPE_SAVEDGAME 1 << 9 +#define MAPGAMETYPE_PRIVATEGAME 1 << 11 +#define MAPGAMETYPE_MAKERUSER 1 << 13 +#define MAPGAMETYPE_MAKERBLIZZARD 1 << 14 +#define MAPGAMETYPE_TYPEMELEE 1 << 15 +#define MAPGAMETYPE_TYPESCENARIO 1 << 16 +#define MAPGAMETYPE_SIZESMALL 1 << 17 +#define MAPGAMETYPE_SIZEMEDIUM 1 << 18 +#define MAPGAMETYPE_SIZELARGE 1 << 19 +#define MAPGAMETYPE_OBSFULL 1 << 20 +#define MAPGAMETYPE_OBSONDEATH 1 << 21 +#define MAPGAMETYPE_OBSNONE 1 << 22 + +#include "gameslot.h" + +// +// CMap +// + +class CMap +{ +public: + CGHost *m_GHost; + +private: + bool m_Valid; + string m_CFGFile; + string m_MapPath; // config value: map path + BYTEARRAY m_MapSize; // config value: map size (4 bytes) + BYTEARRAY m_MapInfo; // config value: map info (4 bytes) -> this is the real CRC + BYTEARRAY m_MapCRC; // config value: map crc (4 bytes) -> this is not the real CRC, it's the "xoro" value + BYTEARRAY m_MapSHA1; // config value: map sha1 (20 bytes) + unsigned char m_MapSpeed; + unsigned char m_MapVisibility; + unsigned char m_MapObservers; + unsigned char m_MapFlags; + unsigned char m_MapFilterMaker; + unsigned char m_MapFilterType; + unsigned char m_MapFilterSize; + unsigned char m_MapFilterObs; + uint32_t m_MapOptions; + BYTEARRAY m_MapWidth; // config value: map width (2 bytes) + BYTEARRAY m_MapHeight; // config value: map height (2 bytes) + string m_MapType; // config value: map type (for stats class) + string m_MapMatchMakingCategory; // config value: map matchmaking category (for matchmaking) + string m_MapStatsW3MMDCategory; // config value: map stats w3mmd category (for saving w3mmd stats) + string m_MapDefaultHCL; // config value: map default HCL to use (this should really be specified elsewhere and not part of the map config) + uint32_t m_MapDefaultPlayerScore; // config value: map default player score (for matchmaking) + string m_MapLocalPath; // config value: map local path + bool m_MapLoadInGame; + string m_MapData; // the map data itself, for sending the map to players + uint32_t m_MapNumPlayers; + uint32_t m_MapNumTeams; + vector m_Slots; + +public: + CMap( CGHost *nGHost ); + CMap( CGHost *nGHost, CConfig *CFG, string nCFGFile ); + ~CMap( ); + + bool GetValid( ) { return m_Valid; } + string GetCFGFile( ) { return m_CFGFile; } + string GetMapPath( ) { return m_MapPath; } + BYTEARRAY GetMapSize( ) { return m_MapSize; } + BYTEARRAY GetMapInfo( ) { return m_MapInfo; } + BYTEARRAY GetMapCRC( ) { return m_MapCRC; } + BYTEARRAY GetMapSHA1( ) { return m_MapSHA1; } + unsigned char GetMapSpeed( ) { return m_MapSpeed; } + unsigned char GetMapVisibility( ) { return m_MapVisibility; } + unsigned char GetMapObservers( ) { return m_MapObservers; } + unsigned char GetMapFlags( ) { return m_MapFlags; } + BYTEARRAY GetMapGameFlags( ); + uint32_t GetMapGameType( ); + uint32_t GetMapOptions( ) { return m_MapOptions; } + unsigned char GetMapLayoutStyle( ); + BYTEARRAY GetMapWidth( ) { return m_MapWidth; } + BYTEARRAY GetMapHeight( ) { return m_MapHeight; } + string GetMapType( ) { return m_MapType; } + string GetMapMatchMakingCategory( ) { return m_MapMatchMakingCategory; } + string GetMapStatsW3MMDCategory( ) { return m_MapStatsW3MMDCategory; } + string GetMapDefaultHCL( ) { return m_MapDefaultHCL; } + uint32_t GetMapDefaultPlayerScore( ) { return m_MapDefaultPlayerScore; } + string GetMapLocalPath( ) { return m_MapLocalPath; } + bool GetMapLoadInGame( ) { return m_MapLoadInGame; } + string *GetMapData( ) { return &m_MapData; } + uint32_t GetMapNumPlayers( ) { return m_MapNumPlayers; } + uint32_t GetMapNumTeams( ) { return m_MapNumTeams; } + vector GetSlots( ) { return m_Slots; } + + void Load( CConfig *CFG, string nCFGFile ); + void CheckValid( ); + uint32_t XORRotateLeft( unsigned char *data, uint32_t length ); +}; + +#endif diff --git a/ghost-legacy/ms_stdint.h b/ghost-legacy/ms_stdint.h new file mode 100644 index 0000000..e032ff1 --- /dev/null +++ b/ghost-legacy/ms_stdint.h @@ -0,0 +1,232 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if (_MSC_VER < 1300) && defined(__cplusplus) + extern "C++" { +#endif +# include +#if (_MSC_VER < 1300) && defined(__cplusplus) + } +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/ghost-legacy/next_combination.h b/ghost-legacy/next_combination.h new file mode 100644 index 0000000..ba8b785 --- /dev/null +++ b/ghost-legacy/next_combination.h @@ -0,0 +1,110 @@ +// next_combination.h. This code is in the public domain. Created by Hannu Helminen. +// stolen from http://hannu.helminen.googlepages.com/next_combination + +#ifndef NEXT_COMBINATION_H +#define NEXT_COMBINATION_H + +#include + +template +void disjoint_rotate(Iterator begin1, Iterator end1, size_t size1, + Iterator begin2, Iterator end2, size_t size2, + Value *type) { + const size_t total = size1 + size2; + size_t gcd = total; + for (size_t div = size1; div != 0; ) { + gcd %= div; + std::swap(gcd, div); + } + const size_t skip = total / gcd - 1; + for (size_t i = 0; i < gcd; ++i) { + Iterator curr((i < size1) ? begin1 + i : begin2 + (i - size1)); + size_t ctr = i; + const Value v(*curr); + for (size_t j = 0; j < skip; ++j) { + ctr = (ctr + size1) % total; + Iterator next((ctr < size1) ? begin1 + ctr : begin2 + (ctr - size1)); + *curr = *next; + curr = next; + } + *curr = v; + } +} + +template +bool next_combination(Iterator begin, Iterator mid, Iterator end) { + if (begin == mid || mid == end) { + return false; + } + // Starting from mid backwards, find first char that is + // less than last char. Call it head_pos. This is the one + // which we will increment (swap) + Iterator tail_pos(end); + --tail_pos; + Iterator head_pos(mid); + --head_pos; + size_t head_len = 1; + while (head_pos != begin && !(*head_pos < *tail_pos)) { + --head_pos; + ++head_len; + } + if (head_pos == begin && !(*head_pos < *tail_pos)) { + // Last combination. We know that the smallest elements are + // in tail (in order) and largest elements are in head (also + // in order). rotate everything back into order and return false. + std::rotate(begin, mid, end); + return false; + } + // Now decrement tail_pos as long as it is larger than *head_pos. + // This way we'll find the two positions to swap. + size_t tail_len = 1; + while (tail_pos > mid) { + --tail_pos; + ++tail_len; + if (!(*tail_pos > *head_pos)) { + ++tail_pos; + --tail_len; + break; + } + } + // Now we have head_pos and tail_pos. Lets call + // - 'h': the element at head_pos + // - 'H': elements head_pos+1 .. mid + // - 't': the element at tail_pos + // - 'T': elements tail_pos+1 .. end + // First, we know that the following is a non-decreasing sequence: + // h <= t <= T <= H. (The sequences are also ordered.) + // We should now re-order the elements so that h and t swap places, + // and the rest of T and H are arranged in the the remaining places + // in increasing order. Let's call the gap between mid and tail_pos ';'. + // Using this notation, the reorder is h H ; t T -> t TH ; h TH + // where the repeated TH means that the concatenation of sequences TH is + // split into two pieces to take place of the former members. + + // Some special cases to avoid unnecessary work. + // If head_len == 1 (H == ''), a simple swap of h and t will always suffice. + // Proof: h ; t T -> t ; h T, so T will occupy the same location as before. + + // Same is true if tail_len == 1 (T == ''). Proof: h H ; t -> t H ; h, + // so this time H will stay in place and swap will suffice. + if (head_len == 1 || tail_len == 1) { + std::iter_swap(head_pos, tail_pos); + return true; + } + // If head_len == tail_len, this operation reduces to a swap_ranges. + // Proof: since len(H) == len(T), the swap is h H ; t T -> t T ; h H + if (head_len == tail_len) { + std::swap_ranges(head_pos, mid, tail_pos); + return true; + } + // Finally we have to do the full reorder. Simply swap h and t, then + // do what std::rotate would do with the sequence H T with the + // constraint that the elements are not stored in consecutive locations. + std::iter_swap(head_pos, tail_pos); + disjoint_rotate(head_pos + 1, mid, head_len - 1, + tail_pos + 1, end, tail_len - 1, + &*head_pos); + return true; +} + +#endif diff --git a/ghost-legacy/packed.cpp b/ghost-legacy/packed.cpp new file mode 100644 index 0000000..f0a9f6a --- /dev/null +++ b/ghost-legacy/packed.cpp @@ -0,0 +1,406 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "crc32.h" +#include "packed.h" + +#include + +// we can't use zlib's uncompress function because it expects a complete compressed buffer +// however, we're going to be passing it chunks of incomplete data +// this custom tzuncompress function will do the job + +int tzuncompress( Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen ) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_SYNC_FLUSH); + if (err != Z_STREAM_END && err != Z_OK) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +// +// CPacked +// + +CPacked :: CPacked( ) +{ + m_CRC = new CCRC32( ); + m_CRC->Initialize( ); + m_Valid = true; + m_HeaderSize = 0; + m_CompressedSize = 0; + m_HeaderVersion = 0; + m_DecompressedSize = 0; + m_NumBlocks = 0; + m_War3Identifier = 0; + m_War3Version = 0; + m_BuildNumber = 0; + m_Flags = 0; + m_ReplayLength = 0; +} + +CPacked :: ~CPacked( ) +{ + delete m_CRC; +} + +void CPacked :: Load( string fileName, bool allBlocks ) +{ + m_Valid = true; + CONSOLE_Print( "[PACKED] loading data from file [" + fileName + "]" ); + m_Compressed = UTIL_FileRead( fileName ); + Decompress( allBlocks ); +} + +bool CPacked :: Save( bool TFT, string fileName ) +{ + Compress( TFT ); + + if( m_Valid ) + { + CONSOLE_Print( "[PACKED] saving data to file [" + fileName + "]" ); + return UTIL_FileWrite( fileName, (unsigned char *)m_Compressed.c_str( ), m_Compressed.size( ) ); + } + else + return false; +} + +bool CPacked :: Extract( string inFileName, string outFileName ) +{ + m_Valid = true; + CONSOLE_Print( "[PACKED] extracting data from file [" + inFileName + "] to file [" + outFileName + "]" ); + m_Compressed = UTIL_FileRead( inFileName ); + Decompress( true ); + + if( m_Valid ) + return UTIL_FileWrite( outFileName, (unsigned char *)m_Decompressed.c_str( ), m_Decompressed.size( ) ); + else + return false; +} + +bool CPacked :: Pack( bool TFT, string inFileName, string outFileName ) +{ + m_Valid = true; + CONSOLE_Print( "[PACKET] packing data from file [" + inFileName + "] to file [" + outFileName + "]" ); + m_Decompressed = UTIL_FileRead( inFileName ); + Compress( TFT ); + + if( m_Valid ) + return UTIL_FileWrite( outFileName, (unsigned char *)m_Compressed.c_str( ), m_Compressed.size( ) ); + else + return false; +} + +void CPacked :: Decompress( bool allBlocks ) +{ + CONSOLE_Print( "[PACKED] decompressing data" ); + + // format found at http://www.thehelper.net/forums/showthread.php?t=42787 + + m_Decompressed.clear( ); + istringstream ISS( m_Compressed ); + string GarbageString; + + // read header + + getline( ISS, GarbageString, '\0' ); + + if( GarbageString != "Warcraft III recorded game\x01A" ) + { + CONSOLE_Print( "[PACKED] not a valid packed file" ); + m_Valid = false; + return; + } + + ISS.read( (char *)&m_HeaderSize, 4 ); // header size + ISS.read( (char *)&m_CompressedSize, 4 ); // compressed file size + ISS.read( (char *)&m_HeaderVersion, 4 ); // header version + ISS.read( (char *)&m_DecompressedSize, 4 ); // decompressed file size + ISS.read( (char *)&m_NumBlocks, 4 ); // number of blocks + + if( m_HeaderVersion == 0 ) + { + ISS.seekg( 2, ios :: cur ); // unknown + ISS.seekg( 2, ios :: cur ); // version number + + CONSOLE_Print( "[PACKED] header version is too old" ); + m_Valid = false; + return; + } + else + { + ISS.read( (char *)&m_War3Identifier, 4 ); // version identifier + ISS.read( (char *)&m_War3Version, 4 ); // version number + } + + ISS.read( (char *)&m_BuildNumber, 2 ); // build number + ISS.read( (char *)&m_Flags, 2 ); // flags + ISS.read( (char *)&m_ReplayLength, 4 ); // replay length + ISS.seekg( 4, ios :: cur ); // CRC + + if( ISS.fail( ) ) + { + CONSOLE_Print( "[PACKED] failed to read header" ); + m_Valid = false; + return; + } + + if( allBlocks ) + CONSOLE_Print( "[PACKED] reading " + UTIL_ToString( m_NumBlocks ) + " blocks" ); + else + CONSOLE_Print( "[PACKED] reading 1/" + UTIL_ToString( m_NumBlocks ) + " blocks" ); + + // read blocks + + for( uint32_t i = 0; i < m_NumBlocks; i++ ) + { + uint16_t BlockCompressed; + uint16_t BlockDecompressed; + + // read block header + + ISS.read( (char *)&BlockCompressed, 2 ); // block compressed size + ISS.read( (char *)&BlockDecompressed, 2 ); // block decompressed size + ISS.seekg( 4, ios :: cur ); // checksum + + if( ISS.fail( ) ) + { + CONSOLE_Print( "[PACKED] failed to read block header" ); + m_Valid = false; + return; + } + + // read block data + + uLongf BlockCompressedLong = BlockCompressed; + uLongf BlockDecompressedLong = BlockDecompressed; + unsigned char *CompressedData = new unsigned char[BlockCompressed]; + unsigned char *DecompressedData = new unsigned char[BlockDecompressed]; + ISS.read( (char*)CompressedData, BlockCompressed ); + + if( ISS.fail( ) ) + { + CONSOLE_Print( "[PACKED] failed to read block data" ); + delete [] DecompressedData; + delete [] CompressedData; + m_Valid = false; + return; + } + + // decompress block data + + int Result = tzuncompress( DecompressedData, &BlockDecompressedLong, CompressedData, BlockCompressedLong ); + + if( Result != Z_OK ) + { + CONSOLE_Print( "[PACKED] tzuncompress error " + UTIL_ToString( Result ) ); + delete [] DecompressedData; + delete [] CompressedData; + m_Valid = false; + return; + } + + if( BlockDecompressedLong != (uLongf)BlockDecompressed ) + { + CONSOLE_Print( "[PACKED] block decompressed size mismatch, actual = " + UTIL_ToString( BlockDecompressedLong ) + ", expected = " + UTIL_ToString( BlockDecompressed ) ); + delete [] DecompressedData; + delete [] CompressedData; + m_Valid = false; + return; + } + + m_Decompressed += string( (char *)DecompressedData, BlockDecompressedLong ); + delete [] DecompressedData; + delete [] CompressedData; + + // stop after one iteration if not decompressing all blocks + + if( !allBlocks ) + break; + } + + CONSOLE_Print( "[PACKED] decompressed " + UTIL_ToString( m_Decompressed.size( ) ) + " bytes" ); + + if( allBlocks || m_NumBlocks == 1 ) + { + if( m_DecompressedSize > m_Decompressed.size( ) ) + { + CONSOLE_Print( "[PACKED] not enough decompressed data" ); + m_Valid = false; + return; + } + + // the last block is padded with zeros, discard them + + CONSOLE_Print( "[PACKED] discarding " + UTIL_ToString( m_Decompressed.size( ) - m_DecompressedSize ) + " bytes" ); + m_Decompressed.erase( m_DecompressedSize ); + } +} + +void CPacked :: Compress( bool TFT ) +{ + CONSOLE_Print( "[PACKED] compressing data" ); + + // format found at http://www.thehelper.net/forums/showthread.php?t=42787 + + m_Compressed.clear( ); + + // compress data into blocks of size 8192 bytes + // use a buffer of size 8213 bytes because in the worst case zlib will grow the data 0.1% plus 12 bytes + + uint32_t CompressedSize = 0; + string Padded = m_Decompressed; + Padded.append( 8192 - ( Padded.size( ) % 8192 ), 0 ); + vector CompressedBlocks; + string :: size_type Position = 0; + unsigned char *CompressedData = new unsigned char[8213]; + + while( Position < Padded.size( ) ) + { + uLongf BlockCompressedLong = 8213; + int Result = compress( CompressedData, &BlockCompressedLong, (const Bytef *)Padded.c_str( ) + Position, 8192 ); + + if( Result != Z_OK ) + { + CONSOLE_Print( "[PACKED] compress error " + UTIL_ToString( Result ) ); + delete [] CompressedData; + m_Valid = false; + return; + } + + CompressedBlocks.push_back( string( (char *)CompressedData, BlockCompressedLong ) ); + CompressedSize += BlockCompressedLong; + Position += 8192; + } + + delete [] CompressedData; + + // build header + + uint32_t HeaderSize = 68; + uint32_t HeaderCompressedSize = HeaderSize + CompressedSize + CompressedBlocks.size( ) * 8; + uint32_t HeaderVersion = 1; + BYTEARRAY Header; + UTIL_AppendByteArray( Header, "Warcraft III recorded game\x01A" ); + UTIL_AppendByteArray( Header, HeaderSize, false ); + UTIL_AppendByteArray( Header, HeaderCompressedSize, false ); + UTIL_AppendByteArray( Header, HeaderVersion, false ); + UTIL_AppendByteArray( Header, (uint32_t)m_Decompressed.size( ), false ); + UTIL_AppendByteArray( Header, (uint32_t)CompressedBlocks.size( ), false ); + + if( TFT ) + { + Header.push_back( 'P' ); // "W3XP" + Header.push_back( 'X' ); + Header.push_back( '3' ); + Header.push_back( 'W' ); + } + else + { + Header.push_back( '3' ); // "WAR3" + Header.push_back( 'R' ); + Header.push_back( 'A' ); + Header.push_back( 'W' ); + } + + UTIL_AppendByteArray( Header, m_War3Version, false ); + UTIL_AppendByteArray( Header, m_BuildNumber, false ); + UTIL_AppendByteArray( Header, m_Flags, false ); + UTIL_AppendByteArray( Header, m_ReplayLength, false ); + + // append zero header CRC + // the header CRC is calculated over the entire header with itself set to zero + // we'll overwrite the zero header CRC after we calculate it + + UTIL_AppendByteArray( Header, (uint32_t)0, false ); + + // calculate header CRC + + string HeaderString = string( Header.begin( ), Header.end( ) ); + uint32_t CRC = m_CRC->FullCRC( (unsigned char *)HeaderString.c_str( ), HeaderString.size( ) ); + + // overwrite the (currently zero) header CRC with the calculated CRC + + Header.erase( Header.end( ) - 4, Header.end( ) ); + UTIL_AppendByteArray( Header, CRC, false ); + + // append header + + m_Compressed += string( Header.begin( ), Header.end( ) ); + + // append blocks + + for( vector :: iterator i = CompressedBlocks.begin( ); i != CompressedBlocks.end( ); i++ ) + { + BYTEARRAY BlockHeader; + UTIL_AppendByteArray( BlockHeader, (uint16_t)(*i).size( ), false ); + UTIL_AppendByteArray( BlockHeader, (uint16_t)8192, false ); + + // append zero block header CRC + + UTIL_AppendByteArray( BlockHeader, (uint32_t)0, false ); + + // calculate block header CRC + + string BlockHeaderString = string( BlockHeader.begin( ), BlockHeader.end( ) ); + uint32_t CRC1 = m_CRC->FullCRC( (unsigned char *)BlockHeaderString.c_str( ), BlockHeaderString.size( ) ); + CRC1 = CRC1 ^ ( CRC1 >> 16 ); + uint32_t CRC2 = m_CRC->FullCRC( (unsigned char *)(*i).c_str( ), (*i).size( ) ); + CRC2 = CRC2 ^ ( CRC2 >> 16 ); + uint32_t BlockCRC = ( CRC1 & 0xFFFF ) | ( CRC2 << 16 ); + + // overwrite the block header CRC with the calculated CRC + + BlockHeader.erase( BlockHeader.end( ) - 4, BlockHeader.end( ) ); + UTIL_AppendByteArray( BlockHeader, BlockCRC, false ); + + // append block header and data + + m_Compressed += string( BlockHeader.begin( ), BlockHeader.end( ) ); + m_Compressed += *i; + } +} diff --git a/ghost-legacy/packed.h b/ghost-legacy/packed.h new file mode 100644 index 0000000..d33b839 --- /dev/null +++ b/ghost-legacy/packed.h @@ -0,0 +1,79 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef PACKED_H +#define PACKED_H + +// +// CPacked +// + +class CCRC32; + +class CPacked +{ +public: + CCRC32 *m_CRC; + +protected: + bool m_Valid; + string m_Compressed; + string m_Decompressed; + uint32_t m_HeaderSize; + uint32_t m_CompressedSize; + uint32_t m_HeaderVersion; + uint32_t m_DecompressedSize; + uint32_t m_NumBlocks; + uint32_t m_War3Identifier; + uint32_t m_War3Version; + uint16_t m_BuildNumber; + uint16_t m_Flags; + uint32_t m_ReplayLength; + +public: + CPacked( ); + virtual ~CPacked( ); + + virtual bool GetValid( ) { return m_Valid; } + virtual uint32_t GetHeaderSize( ) { return m_HeaderSize; } + virtual uint32_t GetCompressedSize( ) { return m_CompressedSize; } + virtual uint32_t GetHeaderVersion( ) { return m_HeaderVersion; } + virtual uint32_t GetDecompressedSize( ) { return m_DecompressedSize; } + virtual uint32_t GetNumBlocks( ) { return m_NumBlocks; } + virtual uint32_t GetWar3Identifier( ) { return m_War3Identifier; } + virtual uint32_t GetWar3Version( ) { return m_War3Version; } + virtual uint16_t GetBuildNumber( ) { return m_BuildNumber; } + virtual uint16_t GetFlags( ) { return m_Flags; } + virtual uint32_t GetReplayLength( ) { return m_ReplayLength; } + + virtual void SetWar3Version( uint32_t nWar3Version ) { m_War3Version = nWar3Version; } + virtual void SetBuildNumber( uint16_t nBuildNumber ) { m_BuildNumber = nBuildNumber; } + virtual void SetFlags( uint16_t nFlags ) { m_Flags = nFlags; } + virtual void SetReplayLength( uint32_t nReplayLength ) { m_ReplayLength = nReplayLength; } + + virtual void Load( string fileName, bool allBlocks ); + virtual bool Save( bool TFT, string fileName ); + virtual bool Extract( string inFileName, string outFileName ); + virtual bool Pack( bool TFT, string inFileName, string outFileName ); + virtual void Decompress( bool allBlocks ); + virtual void Compress( bool TFT ); +}; + +#endif diff --git a/ghost-legacy/replay.cpp b/ghost-legacy/replay.cpp new file mode 100644 index 0000000..f9ff04d --- /dev/null +++ b/ghost-legacy/replay.cpp @@ -0,0 +1,582 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "packed.h" +#include "replay.h" +#include "gameprotocol.h" + +// +// CReplay +// + +CReplay :: CReplay( ) : CPacked( ) +{ + m_HostPID = 0; + m_PlayerCount = 0; + m_MapGameType = 0; + m_RandomSeed = 0; + m_SelectMode = 0; + m_StartSpotCount = 0; + m_CompiledBlocks.reserve( 262144 ); +} + +CReplay :: ~CReplay( ) +{ + +} + +void CReplay :: AddLeaveGame( uint32_t reason, unsigned char PID, uint32_t result ) +{ + BYTEARRAY Block; + Block.push_back( REPLAY_LEAVEGAME ); + UTIL_AppendByteArray( Block, reason, false ); + Block.push_back( PID ); + UTIL_AppendByteArray( Block, result, false ); + UTIL_AppendByteArray( Block, (uint32_t)1, false ); + m_CompiledBlocks += string( Block.begin( ), Block.end( ) ); +} + +void CReplay :: AddLeaveGameDuringLoading( uint32_t reason, unsigned char PID, uint32_t result ) +{ + BYTEARRAY Block; + Block.push_back( REPLAY_LEAVEGAME ); + UTIL_AppendByteArray( Block, reason, false ); + Block.push_back( PID ); + UTIL_AppendByteArray( Block, result, false ); + UTIL_AppendByteArray( Block, (uint32_t)1, false ); + m_LoadingBlocks.push( Block ); +} + +void CReplay :: AddTimeSlot2( queue actions ) +{ + BYTEARRAY Block; + Block.push_back( REPLAY_TIMESLOT2 ); + UTIL_AppendByteArray( Block, (uint16_t)0, false ); + UTIL_AppendByteArray( Block, (uint16_t)0, false ); + + while( !actions.empty( ) ) + { + CIncomingAction *Action = actions.front( ); + actions.pop( ); + Block.push_back( Action->GetPID( ) ); + UTIL_AppendByteArray( Block, (uint16_t)Action->GetAction( )->size( ), false ); + UTIL_AppendByteArrayFast( Block, *Action->GetAction( ) ); + } + + // assign length + + BYTEARRAY LengthBytes = UTIL_CreateByteArray( (uint16_t)( Block.size( ) - 3 ), false ); + Block[1] = LengthBytes[0]; + Block[2] = LengthBytes[1]; + m_CompiledBlocks += string( Block.begin( ), Block.end( ) ); +} + +void CReplay :: AddTimeSlot( uint16_t timeIncrement, queue actions ) +{ + BYTEARRAY Block; + Block.push_back( REPLAY_TIMESLOT ); + UTIL_AppendByteArray( Block, (uint16_t)0, false ); + UTIL_AppendByteArray( Block, timeIncrement, false ); + + while( !actions.empty( ) ) + { + CIncomingAction *Action = actions.front( ); + actions.pop( ); + Block.push_back( Action->GetPID( ) ); + UTIL_AppendByteArray( Block, (uint16_t)Action->GetAction( )->size( ), false ); + UTIL_AppendByteArrayFast( Block, *Action->GetAction( ) ); + } + + // assign length + + BYTEARRAY LengthBytes = UTIL_CreateByteArray( (uint16_t)( Block.size( ) - 3 ), false ); + Block[1] = LengthBytes[0]; + Block[2] = LengthBytes[1]; + m_CompiledBlocks += string( Block.begin( ), Block.end( ) ); + m_ReplayLength += timeIncrement; +} + +void CReplay :: AddChatMessage( unsigned char PID, unsigned char flags, uint32_t chatMode, string message ) +{ + BYTEARRAY Block; + Block.push_back( REPLAY_CHATMESSAGE ); + Block.push_back( PID ); + UTIL_AppendByteArray( Block, (uint16_t)0, false ); + Block.push_back( flags ); + UTIL_AppendByteArray( Block, chatMode, false ); + UTIL_AppendByteArrayFast( Block, message ); + + // assign length + + BYTEARRAY LengthBytes = UTIL_CreateByteArray( (uint16_t)( Block.size( ) - 4 ), false ); + Block[2] = LengthBytes[0]; + Block[3] = LengthBytes[1]; + m_CompiledBlocks += string( Block.begin( ), Block.end( ) ); +} + +void CReplay :: AddLoadingBlock( BYTEARRAY &loadingBlock ) +{ + m_LoadingBlocks.push( loadingBlock ); +} + +void CReplay :: BuildReplay( string gameName, string statString, uint32_t war3Version, uint16_t buildNumber ) +{ + m_War3Version = war3Version; + m_BuildNumber = buildNumber; + m_Flags = 32768; + + CONSOLE_Print( "[REPLAY] building replay" ); + + uint32_t LanguageID = 0x0012F8B0; + + BYTEARRAY Replay; + Replay.push_back( 16 ); // Unknown (4.0) + Replay.push_back( 1 ); // Unknown (4.0) + Replay.push_back( 0 ); // Unknown (4.0) + Replay.push_back( 0 ); // Unknown (4.0) + Replay.push_back( 0 ); // Host RecordID (4.1) + Replay.push_back( m_HostPID ); // Host PlayerID (4.1) + UTIL_AppendByteArrayFast( Replay, m_HostName ); // Host PlayerName (4.1) + Replay.push_back( 1 ); // Host AdditionalSize (4.1) + Replay.push_back( 0 ); // Host AdditionalData (4.1) + UTIL_AppendByteArrayFast( Replay, gameName ); // GameName (4.2) + Replay.push_back( 0 ); // Null (4.0) + UTIL_AppendByteArrayFast( Replay, statString ); // StatString (4.3) + UTIL_AppendByteArray( Replay, (uint32_t)m_Slots.size( ), false ); // PlayerCount (4.6) + UTIL_AppendByteArray( Replay, m_MapGameType, false ); // GameType (4.7) + UTIL_AppendByteArray( Replay, LanguageID, false ); // LanguageID (4.8) + + // PlayerList (4.9) + + for( vector :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) + { + if( (*i).first != m_HostPID ) + { + Replay.push_back( 22 ); // Player RecordID (4.1) + Replay.push_back( (*i).first ); // Player PlayerID (4.1) + UTIL_AppendByteArrayFast( Replay, (*i).second ); // Player PlayerName (4.1) + Replay.push_back( 1 ); // Player AdditionalSize (4.1) + Replay.push_back( 0 ); // Player AdditionalData (4.1) + UTIL_AppendByteArray( Replay, (uint32_t)0, false ); // Unknown + } + } + + // GameStartRecord (4.10) + + Replay.push_back( 25 ); // RecordID (4.10) + UTIL_AppendByteArray( Replay, (uint16_t)( 7 + m_Slots.size( ) * 9 ), false ); // Size (4.10) + Replay.push_back( m_Slots.size( ) ); // NumSlots (4.10) + + for( unsigned char i = 0; i < m_Slots.size( ); i++ ) + UTIL_AppendByteArray( Replay, m_Slots[i].GetByteArray( ) ); + + UTIL_AppendByteArray( Replay, m_RandomSeed, false ); // RandomSeed (4.10) + Replay.push_back( m_SelectMode ); // SelectMode (4.10) + Replay.push_back( m_StartSpotCount ); // StartSpotCount (4.10) + + // ReplayData (5.0) + + Replay.push_back( REPLAY_FIRSTSTARTBLOCK ); + UTIL_AppendByteArray( Replay, (uint32_t)1, false ); + Replay.push_back( REPLAY_SECONDSTARTBLOCK ); + UTIL_AppendByteArray( Replay, (uint32_t)1, false ); + + // leavers during loading need to be stored between the second and third start blocks + + while( !m_LoadingBlocks.empty( ) ) + { + UTIL_AppendByteArray( Replay, m_LoadingBlocks.front( ) ); + m_LoadingBlocks.pop( ); + } + + Replay.push_back( REPLAY_THIRDSTARTBLOCK ); + UTIL_AppendByteArray( Replay, (uint32_t)1, false ); + + // done + + m_Decompressed = string( Replay.begin( ), Replay.end( ) ); + m_Decompressed += m_CompiledBlocks; +} + +#define READB( x, y, z ) (x).read( (char *)(y), (z) ) +#define READSTR( x, y ) getline( (x), (y), '\0' ) + +void CReplay :: ParseReplay( bool parseBlocks ) +{ + m_HostPID = 0; + m_HostName.clear( ); + m_GameName.clear( ); + m_StatString.clear( ); + m_PlayerCount = 0; + m_MapGameType = 0; + m_Players.clear( ); + m_Slots.clear( ); + m_RandomSeed = 0; + m_SelectMode = 0; + m_StartSpotCount = 0; + m_LoadingBlocks = queue( ); + m_Blocks = queue( ); + m_CheckSums = queue( ); + + if( m_Flags != 32768 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (flags mismatch)" ); + m_Valid = false; + return; + } + + istringstream ISS( m_Decompressed ); + + unsigned char Garbage1; + uint32_t Garbage4; + string GarbageString; + unsigned char GarbageData[65535]; + + READB( ISS, &Garbage4, 4 ); // Unknown (4.0) + + if( Garbage4 != 272 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.0 Unknown mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage1, 1 ); // Host RecordID (4.1) + + if( Garbage1 != 0 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.1 Host RecordID mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &m_HostPID, 1 ); + + if( m_HostPID > 15 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.1 Host PlayerID is invalid)" ); + m_Valid = false; + return; + } + + READSTR( ISS, m_HostName ); // Host PlayerName (4.1) + READB( ISS, &Garbage1, 1 ); // Host AdditionalSize (4.1) + + if( Garbage1 != 1 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.1 Host AdditionalSize mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage1, 1 ); // Host AdditionalData (4.1) + + if( Garbage1 != 0 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.1 Host AdditionalData mismatch)" ); + m_Valid = false; + return; + } + + AddPlayer( m_HostPID, m_HostName ); + READSTR( ISS, m_GameName ); // GameName (4.2) + READSTR( ISS, GarbageString ); // Null (4.0) + READSTR( ISS, m_StatString ); // StatString (4.3) + READB( ISS, &m_PlayerCount, 4 ); // PlayerCount (4.6) + + if( m_PlayerCount > 12 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.6 PlayerCount is invalid)" ); + m_Valid = false; + return; + } + + READB( ISS, &m_MapGameType, 4 ); // GameType (4.7) + READB( ISS, &Garbage4, 4 ); // LanguageID (4.8) + + while( 1 ) + { + READB( ISS, &Garbage1, 1 ); // Player RecordID (4.1) + + if( Garbage1 == 22 ) + { + unsigned char PlayerID; + string PlayerName; + READB( ISS, &PlayerID, 1 ); // Player PlayerID (4.1) + + if( PlayerID > 15 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.9 Player PlayerID is invalid)" ); + m_Valid = false; + return; + } + + READSTR( ISS, PlayerName ); // Player PlayerName (4.1) + READB( ISS, &Garbage1, 1 ); // Player AdditionalSize (4.1) + + if( Garbage1 != 1 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.9 Player AdditionalSize mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage1, 1 ); // Player AdditionalData (4.1) + + if( Garbage1 != 0 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.9 Player AdditionalData mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage4, 4 ); // Unknown + + if( Garbage4 != 0 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.9 Unknown mismatch)" ); + m_Valid = false; + return; + } + + AddPlayer( PlayerID, PlayerName ); + } + else if( Garbage1 == 25 ) + break; + else + { + CONSOLE_Print( "[REPLAY] invalid replay (4.9 Player RecordID mismatch)" ); + m_Valid = false; + return; + } + } + + uint16_t Size; + unsigned char NumSlots; + READB( ISS, &Size, 2 ); // Size (4.10) + READB( ISS, &NumSlots, 1 ); // NumSlots (4.10) + + if( Size != 7 + NumSlots * 9 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.10 Size is invalid)" ); + m_Valid = false; + return; + } + + if( NumSlots == 0 || NumSlots > 12 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (4.10 NumSlots is invalid)" ); + m_Valid = false; + return; + } + + for( int i = 0; i < NumSlots; i++ ) + { + unsigned char SlotData[9]; + READB( ISS, SlotData, 9 ); + BYTEARRAY SlotDataBA = UTIL_CreateByteArray( SlotData, 9 ); + m_Slots.push_back( CGameSlot( SlotDataBA ) ); + } + + READB( ISS, &m_RandomSeed, 4 ); // RandomSeed (4.10) + READB( ISS, &m_SelectMode, 1 ); // SelectMode (4.10) + READB( ISS, &m_StartSpotCount, 1 ); // StartSpotCount (4.10) + + if( ISS.eof( ) || ISS.fail( ) ) + { + CONSOLE_Print( "[SAVEGAME] failed to parse replay header" ); + m_Valid = false; + return; + } + + if( !parseBlocks ) + return; + + READB( ISS, &Garbage1, 1 ); // first start block ID (5.0) + + if( Garbage1 != CReplay :: REPLAY_FIRSTSTARTBLOCK ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 first start block ID mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage4, 4 ); // first start block data (5.0) + + if( Garbage4 != 1 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 first start block data mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage1, 1 ); // second start block ID (5.0) + + if( Garbage1 != CReplay :: REPLAY_SECONDSTARTBLOCK ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 second start block ID mismatch)" ); + m_Valid = false; + return; + } + + READB( ISS, &Garbage4, 4 ); // second start block data (5.0) + + if( Garbage4 != 1 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 second start block data mismatch)" ); + m_Valid = false; + return; + } + + while( 1 ) + { + READB( ISS, &Garbage1, 1 ); // third start block ID *or* loading block ID (5.0) + + if( ISS.eof( ) || ISS.fail( ) ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 third start block unexpected end of file found)" ); + m_Valid = false; + return; + } + if( Garbage1 == CReplay :: REPLAY_LEAVEGAME ) + { + READB( ISS, GarbageData, 13 ); + BYTEARRAY LoadingBlock; + LoadingBlock.push_back( Garbage1 ); + UTIL_AppendByteArray( LoadingBlock, GarbageData, 13 ); + m_LoadingBlocks.push( LoadingBlock ); + } + else if( Garbage1 == CReplay :: REPLAY_THIRDSTARTBLOCK ) + break; + else + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 third start block ID mismatch)" ); + m_Valid = false; + return; + } + } + + READB( ISS, &Garbage4, 4 ); // third start block data (5.0) + + if( Garbage4 != 1 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 third start block data mismatch)" ); + m_Valid = false; + return; + } + + if( ISS.eof( ) || ISS.fail( ) ) + { + CONSOLE_Print( "[SAVEGAME] failed to parse replay start blocks" ); + m_Valid = false; + return; + } + + uint32_t ActualReplayLength = 0; + + while( 1 ) + { + READB( ISS, &Garbage1, 1 ); // block ID (5.0) + + if( ISS.eof( ) || ISS.fail( ) ) + break; + else if( Garbage1 == CReplay :: REPLAY_LEAVEGAME ) + { + READB( ISS, GarbageData, 13 ); + + // reconstruct the block + + BYTEARRAY Block; + Block.push_back( CReplay :: REPLAY_LEAVEGAME ); + UTIL_AppendByteArray( Block, GarbageData, 13 ); + m_Blocks.push( Block ); + } + else if( Garbage1 == CReplay :: REPLAY_TIMESLOT ) + { + uint16_t BlockSize; + READB( ISS, &BlockSize, 2 ); + READB( ISS, GarbageData, BlockSize ); + + if( BlockSize >= 2 ) + ActualReplayLength += GarbageData[0] | GarbageData[1] << 8; + + // reconstruct the block + + BYTEARRAY Block; + Block.push_back( CReplay :: REPLAY_TIMESLOT ); + UTIL_AppendByteArray( Block, BlockSize, false ); + UTIL_AppendByteArray( Block, GarbageData, BlockSize ); + m_Blocks.push( Block ); + } + else if( Garbage1 == CReplay :: REPLAY_CHATMESSAGE ) + { + unsigned char PID; + uint16_t BlockSize; + READB( ISS, &PID, 1 ); + + if( PID > 15 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 chatmessage pid is invalid)" ); + m_Valid = false; + return; + } + + READB( ISS, &BlockSize, 2 ); + READB( ISS, GarbageData, BlockSize ); + + // reconstruct the block + + BYTEARRAY Block; + Block.push_back( CReplay :: REPLAY_CHATMESSAGE ); + Block.push_back( PID ); + UTIL_AppendByteArray( Block, BlockSize, false ); + UTIL_AppendByteArray( Block, GarbageData, BlockSize ); + m_Blocks.push( Block ); + } + else if( Garbage1 == CReplay :: REPLAY_CHECKSUM ) + { + READB( ISS, &Garbage1, 1 ); + + if( Garbage1 != 4 ) + { + CONSOLE_Print( "[REPLAY] invalid replay (5.0 checksum unknown mismatch)" ); + m_Valid = false; + return; + } + + uint32_t CheckSum; + READB( ISS, &CheckSum, 4 ); + m_CheckSums.push( CheckSum ); + } + else + { + // it's not necessarily an error if we encounter an unknown block ID since replays can contain extra data + + break; + } + } + + if( m_ReplayLength != ActualReplayLength ) + CONSOLE_Print( "[REPLAY] warning - replay length mismatch (" + UTIL_ToString( m_ReplayLength ) + "ms/" + UTIL_ToString( ActualReplayLength ) + "ms)" ); + + m_Valid = true; +} diff --git a/ghost-legacy/replay.h b/ghost-legacy/replay.h new file mode 100644 index 0000000..b38a313 --- /dev/null +++ b/ghost-legacy/replay.h @@ -0,0 +1,103 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef REPLAY_H +#define REPLAY_H + +#include "gameslot.h" + +// +// CReplay +// + +class CIncomingAction; + +class CReplay : public CPacked +{ +public: + enum BlockID { + REPLAY_LEAVEGAME = 0x17, + REPLAY_FIRSTSTARTBLOCK = 0x1A, + REPLAY_SECONDSTARTBLOCK = 0x1B, + REPLAY_THIRDSTARTBLOCK = 0x1C, + REPLAY_TIMESLOT2 = 0x1E, // corresponds to W3GS_INCOMING_ACTION2 + REPLAY_TIMESLOT = 0x1F, // corresponds to W3GS_INCOMING_ACTION + REPLAY_CHATMESSAGE = 0x20, + REPLAY_CHECKSUM = 0x22, // corresponds to W3GS_OUTGOING_KEEPALIVE + REPLAY_DESYNC = 0x23 + }; + +private: + unsigned char m_HostPID; + string m_HostName; + string m_GameName; + string m_StatString; + uint32_t m_PlayerCount; + uint32_t m_MapGameType; + vector m_Players; + vector m_Slots; + uint32_t m_RandomSeed; + unsigned char m_SelectMode; // also known as the "layout style" elsewhere in this project + unsigned char m_StartSpotCount; + queue m_LoadingBlocks; + queue m_Blocks; + queue m_CheckSums; + string m_CompiledBlocks; + +public: + CReplay( ); + virtual ~CReplay( ); + + unsigned char GetHostPID( ) { return m_HostPID; } + string GetHostName( ) { return m_HostName; } + string GetGameName( ) { return m_GameName; } + string GetStatString( ) { return m_StatString; } + uint32_t GetPlayerCount( ) { return m_PlayerCount; } + uint32_t GetMapGameType( ) { return m_MapGameType; } + vector GetPlayers( ) { return m_Players; } + vector GetSlots( ) { return m_Slots; } + uint32_t GetRandomSeed( ) { return m_RandomSeed; } + unsigned char GetSelectMode( ) { return m_SelectMode; } + unsigned char GetStartSpotCount( ) { return m_StartSpotCount; } + queue *GetLoadingBlocks( ) { return &m_LoadingBlocks; } + queue *GetBlocks( ) { return &m_Blocks; } + queue *GetCheckSums( ) { return &m_CheckSums; } + + void AddPlayer( unsigned char nPID, string nName ) { m_Players.push_back( PIDPlayer( nPID, nName ) ); } + void SetSlots( vector nSlots ) { m_Slots = nSlots; } + void SetRandomSeed( uint32_t nRandomSeed ) { m_RandomSeed = nRandomSeed; } + void SetSelectMode( unsigned char nSelectMode ) { m_SelectMode = nSelectMode; } + void SetStartSpotCount( unsigned char nStartSpotCount ) { m_StartSpotCount = nStartSpotCount; } + void SetMapGameType( uint32_t nMapGameType ) { m_MapGameType = nMapGameType; } + void SetHostPID( unsigned char nHostPID ) { m_HostPID = nHostPID; } + void SetHostName( string nHostName ) { m_HostName = nHostName; } + + void AddLeaveGame( uint32_t reason, unsigned char PID, uint32_t result ); + void AddLeaveGameDuringLoading( uint32_t reason, unsigned char PID, uint32_t result ); + void AddTimeSlot2( queue actions ); + void AddTimeSlot( uint16_t timeIncrement, queue actions ); + void AddChatMessage( unsigned char PID, unsigned char flags, uint32_t chatMode, string message ); + void AddLoadingBlock( BYTEARRAY &loadingBlock ); + void BuildReplay( string gameName, string statString, uint32_t war3Version, uint16_t buildNumber ); + + void ParseReplay( bool parseBlocks ); +}; + +#endif diff --git a/ghost-legacy/savegame.cpp b/ghost-legacy/savegame.cpp new file mode 100644 index 0000000..9c9ce6f --- /dev/null +++ b/ghost-legacy/savegame.cpp @@ -0,0 +1,118 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "packed.h" +#include "savegame.h" + +// +// CSaveGame +// + +CSaveGame :: CSaveGame( ) : CPacked( ) +{ + m_NumSlots = 0; + m_RandomSeed = 0; +} + +CSaveGame :: ~CSaveGame( ) +{ + +} + +#define READB( x, y, z ) (x).read( (char *)(y), (z) ) +#define READSTR( x, y ) getline( (x), (y), '\0' ) + +void CSaveGame :: ParseSaveGame( ) +{ + m_MapPath.clear( ); + m_GameName.clear( ); + m_NumSlots = 0; + m_Slots.clear( ); + m_RandomSeed = 0; + m_MagicNumber.clear( ); + + if( m_Flags != 0 ) + { + CONSOLE_Print( "[SAVEGAME] invalid replay (flags mismatch)" ); + m_Valid = false; + return; + } + + istringstream ISS( m_Decompressed ); + + // savegame format figured out by Varlock: + // string -> map path + // 0 (string?) -> ??? (no idea what this is) + // string -> game name + // 0 (string?) -> ??? (maybe original game password) + // string -> stat string + // 4 bytes -> ??? (seems to be # of slots) + // 4 bytes -> ??? (seems to be 0x01 0x28 0x49 0x00 on both of the savegames examined) + // 2 bytes -> ??? (no idea what this is) + // slot structure + // 4 bytes -> magic number + + unsigned char Garbage1; + uint16_t Garbage2; + uint32_t Garbage4; + string GarbageString; + uint32_t MagicNumber; + + READSTR( ISS, m_MapPath ); // map path + READSTR( ISS, GarbageString ); // ??? + READSTR( ISS, m_GameName ); // game name + READSTR( ISS, GarbageString ); // ??? + READSTR( ISS, GarbageString ); // stat string + READB( ISS, &Garbage4, 4 ); // ??? + READB( ISS, &Garbage4, 4 ); // ??? + READB( ISS, &Garbage2, 2 ); // ??? + READB( ISS, &m_NumSlots, 1 ); // number of slots + + if( m_NumSlots > 12 ) + { + CONSOLE_Print( "[SAVEGAME] invalid savegame (too many slots)" ); + m_Valid = false; + return; + } + + for( unsigned char i = 0; i < m_NumSlots; i++ ) + { + unsigned char SlotData[9]; + READB( ISS, SlotData, 9 ); // slot data + m_Slots.push_back( CGameSlot( SlotData[0], SlotData[1], SlotData[2], SlotData[3], SlotData[4], SlotData[5], SlotData[6], SlotData[7], SlotData[8] ) ); + } + + READB( ISS, &m_RandomSeed, 4 ); // random seed + READB( ISS, &Garbage1, 1 ); // GameType + READB( ISS, &Garbage1, 1 ); // number of player slots (non observer) + READB( ISS, &MagicNumber, 4 ); // magic number + + if( ISS.eof( ) || ISS.fail( ) ) + { + CONSOLE_Print( "[SAVEGAME] failed to parse savegame header" ); + m_Valid = false; + return; + } + + m_MagicNumber = UTIL_CreateByteArray( MagicNumber, false ); + m_Valid = true; +} diff --git a/ghost-legacy/savegame.h b/ghost-legacy/savegame.h new file mode 100644 index 0000000..326bcf5 --- /dev/null +++ b/ghost-legacy/savegame.h @@ -0,0 +1,61 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef SAVEGAME_H +#define SAVEGAME_H + +#include "gameslot.h" + +// +// CSaveGame +// + +class CSaveGame : public CPacked +{ +private: + string m_FileName; + string m_FileNameNoPath; + string m_MapPath; + string m_GameName; + unsigned char m_NumSlots; + vector m_Slots; + uint32_t m_RandomSeed; + BYTEARRAY m_MagicNumber; + +public: + CSaveGame( ); + virtual ~CSaveGame( ); + + string GetFileName( ) { return m_FileName; } + string GetFileNameNoPath( ) { return m_FileNameNoPath; } + string GetMapPath( ) { return m_MapPath; } + string GetGameName( ) { return m_GameName; } + unsigned char GetNumSlots( ) { return m_NumSlots; } + vector GetSlots( ) { return m_Slots; } + uint32_t GetRandomSeed( ) { return m_RandomSeed; } + BYTEARRAY GetMagicNumber( ) { return m_MagicNumber; } + + void SetFileName( string nFileName ) { m_FileName = nFileName; } + void SetFileNameNoPath( string nFileNameNoPath ) { m_FileNameNoPath = nFileNameNoPath; } + + void ParseSaveGame( ); +}; + +#endif diff --git a/ghost-legacy/sha1.cpp b/ghost-legacy/sha1.cpp new file mode 100644 index 0000000..82fb7fa --- /dev/null +++ b/ghost-legacy/sha1.cpp @@ -0,0 +1,194 @@ +/* + 100% free public domain implementation of the SHA-1 + algorithm by Dominik Reichl + + * modified by Trevor Hogan for use with GHost++ * + + === Test Vectors (from FIPS PUB 180-1) === + + "abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + + A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + + +#include "sha1.h" + + +CSHA1::CSHA1() +{ + Reset(); +} + +CSHA1::~CSHA1() +{ + Reset(); +} + + +void CSHA1::Reset() +{ + // SHA1 initialization constants + m_state[0] = 0x67452301; + m_state[1] = 0xEFCDAB89; + m_state[2] = 0x98BADCFE; + m_state[3] = 0x10325476; + m_state[4] = 0xC3D2E1F0; + + m_count[0] = 0; + m_count[1] = 0; +} + +void CSHA1::Transform(uint32_t state[5], unsigned char buffer[64]) +{ + uint32_t a = 0, b = 0, c = 0, d = 0, e = 0; + + SHA1_WORKSPACE_BLOCK* block; + static unsigned char workspace[64]; + block = (SHA1_WORKSPACE_BLOCK *)workspace; + memcpy(block, buffer, 64); + + // Copy state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = 0; b = 0; c = 0; d = 0; e = 0; +} + +// Use this function to hash in binary data and strings +void CSHA1::Update(unsigned char* data, unsigned int len) +{ + uint32_t i = 0, j = 0; + + j = (m_count[0] >> 3) & 63; + + if((m_count[0] += len << 3) < (len << 3)) m_count[1]++; + + m_count[1] += (len >> 29); + + if((j + len) > 63) + { + memcpy(&m_buffer[j], data, (i = 64 - j)); + Transform(m_state, m_buffer); + + for (; i+63 < len; i += 64) + { + Transform(m_state, &data[i]); + } + + j = 0; + } + else i = 0; + + memcpy(&m_buffer[j], &data[i], len - i); +} + +void CSHA1::Final() +{ + uint32_t i = 0, j = 0; + unsigned char finalcount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + for (i = 0; i < 8; i++) + finalcount[i] = (unsigned char)((m_count[(i >= 4 ? 0 : 1)] + >> ((3 - (i & 3)) * 8) ) & 255); // Endian independent + + Update((unsigned char *)"\200", 1); + + while ((m_count[0] & 504) != 448) + Update((unsigned char *)"\0", 1); + + Update(finalcount, 8); // Cause a SHA1Transform() + + for (i = 0; i < 20; i++) + { + m_digest[i] = (unsigned char)((m_state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255); + } + + // Wipe variables for security reasons + i = 0; j = 0; + memset(m_buffer, 0, 64); + memset(m_state, 0, 20); + memset(m_count, 0, 8); + memset(finalcount, 0, 8); + + Transform(m_state, m_buffer); +} + +// Get the final hash as a pre-formatted string +void CSHA1::ReportHash(char *szReport, unsigned char uReportType) +{ + /* + + unsigned char i = 0; + char szTemp[4]; + + if(uReportType == REPORT_HEX) + { + sprintf(szTemp, "%02x", m_digest[0]); + strcat(szReport, szTemp); + + for(i = 1; i < 20; i++) + { + sprintf(szTemp, "%02x", m_digest[i]); + strcat(szReport, szTemp); + } + } + else if(uReportType == REPORT_DIGIT) + { + sprintf(szTemp, "%u", m_digest[0]); + strcat(szReport, szTemp); + + for(i = 1; i < 20; i++) + { + sprintf(szTemp, " %u", m_digest[i]); + strcat(szReport, szTemp); + } + } + else strcpy(szReport, "Error: Unknown report type!"); + + */ +} + +// Get the raw message digest +void CSHA1::GetHash(unsigned char *uDest) +{ + memcpy(uDest, m_digest, 20); +} diff --git a/ghost-legacy/sha1.h b/ghost-legacy/sha1.h new file mode 100644 index 0000000..dc04677 --- /dev/null +++ b/ghost-legacy/sha1.h @@ -0,0 +1,91 @@ +/* + 100% free public domain implementation of the SHA-1 + algorithm by Dominik Reichl + + * modified by Trevor Hogan for use with GHost++ * + + === Test Vectors (from FIPS PUB 180-1) === + + "abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + + A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +#ifndef ___SHA1_H___ +#define ___SHA1_H___ + +#include // Needed for file access +#include // Needed for memset and memcpy +#include // Needed for strcat and strcpy + +// standard integer sizes for 64 bit compatibility + +#ifdef WIN32 + #include "ms_stdint.h" +#else + #include +#endif + +#define MAX_FILE_READ_BUFFER 8000 + +class CSHA1 +{ +public: + // Rotate x bits to the left + #define ROL32(value, bits) (((value)<<(bits))|((value)>>(32-(bits)))) + + #ifdef GHOST_BIG_ENDIAN + #define SHABLK0(i) (block->l[i]) + #else + #define SHABLK0(i) (block->l[i] = (ROL32(block->l[i],24) & 0xFF00FF00) \ + | (ROL32(block->l[i],8) & 0x00FF00FF)) + #endif + + #define SHABLK(i) (block->l[i&15] = ROL32(block->l[(i+13)&15] ^ block->l[(i+8)&15] \ + ^ block->l[(i+2)&15] ^ block->l[i&15],1)) + + // SHA-1 rounds + #define R0(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK0(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } + #define R1(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } + #define R2(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0x6ED9EBA1+ROL32(v,5); w=ROL32(w,30); } + #define R3(v,w,x,y,z,i) { z+=(((w|x)&y)|(w&x))+SHABLK(i)+0x8F1BBCDC+ROL32(v,5); w=ROL32(w,30); } + #define R4(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0xCA62C1D6+ROL32(v,5); w=ROL32(w,30); } + + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } SHA1_WORKSPACE_BLOCK; + + // Two different formats for ReportHash(...) + enum { REPORT_HEX = 0, REPORT_DIGIT = 1 }; + + // Constructor and Destructor + CSHA1(); + virtual ~CSHA1(); + + uint32_t m_state[5]; + uint32_t m_count[2]; + unsigned char m_buffer[64]; + unsigned char m_digest[20]; + + void Reset(); + + // Update the hash value + void Update(unsigned char* data, unsigned int len); + + // Finalize hash and report + void Final(); + void ReportHash(char *szReport, unsigned char uReportType = REPORT_HEX); + void GetHash(unsigned char *uDest); + +private: + // Private SHA-1 transformation + void Transform(uint32_t state[5], unsigned char buffer[64]); +}; + +#endif // ___SHA1_H___ diff --git a/ghost-legacy/socket.cpp b/ghost-legacy/socket.cpp new file mode 100644 index 0000000..f25e915 --- /dev/null +++ b/ghost-legacy/socket.cpp @@ -0,0 +1,797 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#include "ghost.h" +#include "util.h" +#include "socket.h" + +#include + +#ifndef WIN32 + int GetLastError( ) { return errno; } +#endif + +// +// CSocket +// + +CSocket :: CSocket( ) +{ + m_Socket = INVALID_SOCKET; + memset( &m_SIN, 0, sizeof( m_SIN ) ); + m_HasError = false; + m_Error = 0; +} + +CSocket :: CSocket( SOCKET nSocket, struct sockaddr_in nSIN ) +{ + m_Socket = nSocket; + m_SIN = nSIN; + m_HasError = false; + m_Error = 0; +} + +CSocket :: ~CSocket( ) +{ + if( m_Socket != INVALID_SOCKET ) + closesocket( m_Socket ); +} + +BYTEARRAY CSocket :: GetPort( ) +{ + return UTIL_CreateByteArray( m_SIN.sin_port, false ); +} + +BYTEARRAY CSocket :: GetIP( ) +{ + return UTIL_CreateByteArray( (uint32_t)m_SIN.sin_addr.s_addr, false ); +} + +string CSocket :: GetIPString( ) +{ + return inet_ntoa( m_SIN.sin_addr ); +} + +string CSocket :: GetErrorString( ) +{ + if( !m_HasError ) + return "NO ERROR"; + + switch( m_Error ) + { + case EWOULDBLOCK: return "EWOULDBLOCK"; + case EINPROGRESS: return "EINPROGRESS"; + case EALREADY: return "EALREADY"; + case ENOTSOCK: return "ENOTSOCK"; + case EDESTADDRREQ: return "EDESTADDRREQ"; + case EMSGSIZE: return "EMSGSIZE"; + case EPROTOTYPE: return "EPROTOTYPE"; + case ENOPROTOOPT: return "ENOPROTOOPT"; + case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; + case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; + case EOPNOTSUPP: return "EOPNOTSUPP"; + case EPFNOSUPPORT: return "EPFNOSUPPORT"; + case EAFNOSUPPORT: return "EAFNOSUPPORT"; + case EADDRINUSE: return "EADDRINUSE"; + case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; + case ENETDOWN: return "ENETDOWN"; + case ENETUNREACH: return "ENETUNREACH"; + case ENETRESET: return "ENETRESET"; + case ECONNABORTED: return "ECONNABORTED"; + case ECONNRESET: return "ECONNRESET"; + case ENOBUFS: return "ENOBUFS"; + case EISCONN: return "EISCONN"; + case ENOTCONN: return "ENOTCONN"; + case ESHUTDOWN: return "ESHUTDOWN"; + case ETOOMANYREFS: return "ETOOMANYREFS"; + case ETIMEDOUT: return "ETIMEDOUT"; + case ECONNREFUSED: return "ECONNREFUSED"; + case ELOOP: return "ELOOP"; + case ENAMETOOLONG: return "ENAMETOOLONG"; + case EHOSTDOWN: return "EHOSTDOWN"; + case EHOSTUNREACH: return "EHOSTUNREACH"; + case ENOTEMPTY: return "ENOTEMPTY"; + case EUSERS: return "EUSERS"; + case EDQUOT: return "EDQUOT"; + case ESTALE: return "ESTALE"; + case EREMOTE: return "EREMOTE"; + } + + return "UNKNOWN ERROR (" + UTIL_ToString( m_Error ) + ")"; +} + +void CSocket :: SetFD( fd_set *fd, fd_set *send_fd, int *nfds ) +{ + if( m_Socket == INVALID_SOCKET ) + return; + + FD_SET( m_Socket, fd ); + FD_SET( m_Socket, send_fd ); + +#ifndef WIN32 + if( m_Socket > *nfds ) + *nfds = m_Socket; +#endif +} + +void CSocket :: Allocate( int type ) +{ + m_Socket = socket( AF_INET, type, 0 ); + + if( m_Socket == INVALID_SOCKET ) + { + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[SOCKET] error (socket) - " + GetErrorString( ) ); + return; + } +} + +void CSocket :: Reset( ) +{ + if( m_Socket != INVALID_SOCKET ) + closesocket( m_Socket ); + + m_Socket = INVALID_SOCKET; + memset( &m_SIN, 0, sizeof( m_SIN ) ); + m_HasError = false; + m_Error = 0; +} + +// +// CTCPSocket +// + +CTCPSocket :: CTCPSocket( ) : CSocket( ) +{ + Allocate( SOCK_STREAM ); + m_Connected = false; + m_LastRecv = GetTime( ); + m_LastSend = GetTime( ); + + // make socket non blocking + +#ifdef WIN32 + int iMode = 1; + ioctlsocket( m_Socket, FIONBIO, (u_long FAR *)&iMode ); +#else + fcntl( m_Socket, F_SETFL, fcntl( m_Socket, F_GETFL ) | O_NONBLOCK ); +#endif +} + +CTCPSocket :: CTCPSocket( SOCKET nSocket, struct sockaddr_in nSIN ) : CSocket( nSocket, nSIN ) +{ + m_Connected = true; + m_LastRecv = GetTime( ); + m_LastSend = GetTime( ); + + // make socket non blocking + +#ifdef WIN32 + int iMode = 1; + ioctlsocket( m_Socket, FIONBIO, (u_long FAR *)&iMode ); +#else + fcntl( m_Socket, F_SETFL, fcntl( m_Socket, F_GETFL ) | O_NONBLOCK ); +#endif +} + +CTCPSocket :: ~CTCPSocket( ) +{ + +} + +void CTCPSocket :: Reset( ) +{ + CSocket :: Reset( ); + + Allocate( SOCK_STREAM ); + m_Connected = false; + m_RecvBuffer.clear( ); + m_SendBuffer.clear( ); + m_LastRecv = GetTime( ); + m_LastSend = GetTime( ); + + // make socket non blocking + +#ifdef WIN32 + int iMode = 1; + ioctlsocket( m_Socket, FIONBIO, (u_long FAR *)&iMode ); +#else + fcntl( m_Socket, F_SETFL, fcntl( m_Socket, F_GETFL ) | O_NONBLOCK ); +#endif + + if( !m_LogFile.empty( ) ) + { + ofstream Log; + Log.open( m_LogFile.c_str( ), ios :: app ); + + if( !Log.fail( ) ) + { + Log << "----------RESET----------" << endl; + Log.close( ); + } + } +} + +void CTCPSocket :: PutBytes( string bytes ) +{ + m_SendBuffer += bytes; +} + +void CTCPSocket :: PutBytes( BYTEARRAY bytes ) +{ + m_SendBuffer += string( bytes.begin( ), bytes.end( ) ); +} + +void CTCPSocket :: DoRecv( fd_set *fd ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError || !m_Connected ) + return; + + if( FD_ISSET( m_Socket, fd ) ) + { + // data is waiting, receive it + + char buffer[1024]; + int c = recv( m_Socket, buffer, 1024, 0 ); + + if( c == SOCKET_ERROR && GetLastError( ) != EWOULDBLOCK ) + { + // receive error + + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPSOCKET] error (recv) - " + GetErrorString( ) ); + return; + } + else if( c == 0 ) + { + // the other end closed the connection + + CONSOLE_Print( "[TCPSOCKET] closed by remote host" ); + m_Connected = false; + } + else if( c > 0 ) + { + // success! add the received data to the buffer + + if( !m_LogFile.empty( ) ) + { + ofstream Log; + Log.open( m_LogFile.c_str( ), ios :: app ); + + if( !Log.fail( ) ) + { + Log << " RECEIVE <<< " << UTIL_ByteArrayToHexString( UTIL_CreateByteArray( (unsigned char *)buffer, c ) ) << endl; + Log.close( ); + } + } + + m_RecvBuffer += string( buffer, c ); + m_LastRecv = GetTime( ); + } + } +} + +void CTCPSocket :: DoSend( fd_set *send_fd ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError || !m_Connected || m_SendBuffer.empty( ) ) + return; + + if( FD_ISSET( m_Socket, send_fd ) ) + { + // socket is ready, send it + + int s = send( m_Socket, m_SendBuffer.c_str( ), (int)m_SendBuffer.size( ), MSG_NOSIGNAL ); + + if( s == SOCKET_ERROR && GetLastError( ) != EWOULDBLOCK ) + { + // send error + + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPSOCKET] error (send) - " + GetErrorString( ) ); + return; + } + else if( s > 0 ) + { + // success! only some of the data may have been sent, remove it from the buffer + + if( !m_LogFile.empty( ) ) + { + ofstream Log; + Log.open( m_LogFile.c_str( ), ios :: app ); + + if( !Log.fail( ) ) + { + Log << "SEND >>> " << UTIL_ByteArrayToHexString( BYTEARRAY( m_SendBuffer.begin( ), m_SendBuffer.begin( ) + s ) ) << endl; + Log.close( ); + } + } + + m_SendBuffer = m_SendBuffer.substr( s ); + m_LastSend = GetTime( ); + } + } +} + +void CTCPSocket :: Disconnect( ) +{ + if( m_Socket != INVALID_SOCKET ) + shutdown( m_Socket, SHUT_RDWR ); + + m_Connected = false; +} + +void CTCPSocket :: SetNoDelay( bool noDelay ) +{ + int OptVal = 0; + + if( noDelay ) + OptVal = 1; + + setsockopt( m_Socket, IPPROTO_TCP, TCP_NODELAY, (const char *)&OptVal, sizeof( int ) ); +} + +// +// CTCPClient +// + +CTCPClient :: CTCPClient( ) : CTCPSocket( ) +{ + m_Connecting = false; +} + +CTCPClient :: ~CTCPClient( ) +{ + +} + +void CTCPClient :: Reset( ) +{ + CTCPSocket :: Reset( ); + m_Connecting = false; +} + +void CTCPClient :: Disconnect( ) +{ + CTCPSocket :: Disconnect( ); + m_Connecting = false; +} + +void CTCPClient :: Connect( string localaddress, string address, uint16_t port ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError || m_Connecting || m_Connected ) + return; + + if( !localaddress.empty( ) ) + { + struct sockaddr_in LocalSIN; + memset( &LocalSIN, 0, sizeof( LocalSIN ) ); + LocalSIN.sin_family = AF_INET; + + if( ( LocalSIN.sin_addr.s_addr = inet_addr( localaddress.c_str( ) ) ) == INADDR_NONE ) + LocalSIN.sin_addr.s_addr = INADDR_ANY; + + LocalSIN.sin_port = htons( 0 ); + + if( bind( m_Socket, (struct sockaddr *)&LocalSIN, sizeof( LocalSIN ) ) == SOCKET_ERROR ) + { + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPCLIENT] error (bind) - " + GetErrorString( ) ); + return; + } + } + + // get IP address + + struct hostent *HostInfo; + uint32_t HostAddress; + HostInfo = gethostbyname( address.c_str( ) ); + + if( !HostInfo ) + { + m_HasError = true; + // m_Error = h_error; + CONSOLE_Print( "[TCPCLIENT] error (gethostbyname)" ); + return; + } + + memcpy( &HostAddress, HostInfo->h_addr, HostInfo->h_length ); + + // connect + + m_SIN.sin_family = AF_INET; + m_SIN.sin_addr.s_addr = HostAddress; + m_SIN.sin_port = htons( port ); + + if( connect( m_Socket, (struct sockaddr *)&m_SIN, sizeof( m_SIN ) ) == SOCKET_ERROR ) + { + if( GetLastError( ) != EINPROGRESS && GetLastError( ) != EWOULDBLOCK ) + { + // connect error + + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPCLIENT] error (connect) - " + GetErrorString( ) ); + return; + } + } + + m_Connecting = true; +} + +bool CTCPClient :: CheckConnect( ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError || !m_Connecting ) + return false; + + fd_set fd; + FD_ZERO( &fd ); + FD_SET( m_Socket, &fd ); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + // check if the socket is connected + +#ifdef WIN32 + if( select( 1, NULL, &fd, NULL, &tv ) == SOCKET_ERROR ) +#else + if( select( m_Socket + 1, NULL, &fd, NULL, &tv ) == SOCKET_ERROR ) +#endif + { + m_HasError = true; + m_Error = GetLastError( ); + return false; + } + + if( FD_ISSET( m_Socket, &fd ) ) + { + m_Connecting = false; + m_Connected = true; + return true; + } + + return false; +} + +// +// CTCPServer +// + +CTCPServer :: CTCPServer( ) : CTCPSocket( ) +{ + // set the socket to reuse the address in case it hasn't been released yet + + int optval = 1; + +#ifdef WIN32 + setsockopt( m_Socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( int ) ); +#else + setsockopt( m_Socket, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof( int ) ); +#endif +} + +CTCPServer :: ~CTCPServer( ) +{ + +} + +bool CTCPServer :: Listen( string address, uint16_t port ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + m_SIN.sin_family = AF_INET; + + if( !address.empty( ) ) + { + if( ( m_SIN.sin_addr.s_addr = inet_addr( address.c_str( ) ) ) == INADDR_NONE ) + m_SIN.sin_addr.s_addr = INADDR_ANY; + } + else + m_SIN.sin_addr.s_addr = INADDR_ANY; + + m_SIN.sin_port = htons( port ); + + if( bind( m_Socket, (struct sockaddr *)&m_SIN, sizeof( m_SIN ) ) == SOCKET_ERROR ) + { + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPSERVER] error (bind) - " + GetErrorString( ) ); + return false; + } + + // listen, queue length 8 + + if( listen( m_Socket, 8 ) == SOCKET_ERROR ) + { + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[TCPSERVER] error (listen) - " + GetErrorString( ) ); + return false; + } + + return true; +} + +CTCPSocket *CTCPServer :: Accept( fd_set *fd ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return NULL; + + if( FD_ISSET( m_Socket, fd ) ) + { + // a connection is waiting, accept it + + struct sockaddr_in Addr; + int AddrLen = sizeof( Addr ); + SOCKET NewSocket; + +#ifdef WIN32 + if( ( NewSocket = accept( m_Socket, (struct sockaddr *)&Addr, &AddrLen ) ) == INVALID_SOCKET ) +#else + if( ( NewSocket = accept( m_Socket, (struct sockaddr *)&Addr, (socklen_t *)&AddrLen ) ) == INVALID_SOCKET ) +#endif + { + // accept error, ignore it + } + else + { + // success! return the new socket + + return new CTCPSocket( NewSocket, Addr ); + } + } + + return NULL; +} + +// +// CUDPSocket +// + +CUDPSocket :: CUDPSocket( ) : CSocket( ) +{ + Allocate( SOCK_DGRAM ); + + // enable broadcast support + + int OptVal = 1; + setsockopt( m_Socket, SOL_SOCKET, SO_BROADCAST, (const char *)&OptVal, sizeof( int ) ); + + // set default broadcast target + m_BroadcastTarget.s_addr = INADDR_BROADCAST; +} + +CUDPSocket :: ~CUDPSocket( ) +{ + +} + +bool CUDPSocket :: SendTo( struct sockaddr_in sin, BYTEARRAY message ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + string MessageString = string( message.begin( ), message.end( ) ); + + if( sendto( m_Socket, MessageString.c_str( ), MessageString.size( ), 0, (struct sockaddr *)&sin, sizeof( sin ) ) == -1 ) + return false; + + return true; +} + +bool CUDPSocket :: SendTo( string address, uint16_t port, BYTEARRAY message ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + // get IP address + + struct hostent *HostInfo; + uint32_t HostAddress; + HostInfo = gethostbyname( address.c_str( ) ); + + if( !HostInfo ) + { + m_HasError = true; + // m_Error = h_error; + CONSOLE_Print( "[UDPSOCKET] error (gethostbyname)" ); + return false; + } + + memcpy( &HostAddress, HostInfo->h_addr, HostInfo->h_length ); + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = HostAddress; + sin.sin_port = htons( port ); + + return SendTo( sin, message ); +} + +bool CUDPSocket :: Broadcast( uint16_t port, BYTEARRAY message ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = m_BroadcastTarget.s_addr; + sin.sin_port = htons( port ); + + string MessageString = string( message.begin( ), message.end( ) ); + + if( sendto( m_Socket, MessageString.c_str( ), MessageString.size( ), 0, (struct sockaddr *)&sin, sizeof( sin ) ) == -1 ) + { + CONSOLE_Print( "[UDPSOCKET] failed to broadcast packet (port " + UTIL_ToString( port ) + ", size " + UTIL_ToString( MessageString.size( ) ) + " bytes)" ); + return false; + } + + return true; +} + +void CUDPSocket :: SetBroadcastTarget( string subnet ) +{ + if( subnet.empty( ) ) + { + CONSOLE_Print( "[UDPSOCKET] using default broadcast target" ); + m_BroadcastTarget.s_addr = INADDR_BROADCAST; + } + else + { + // this function does not check whether the given subnet is a valid subnet the user is on + // convert string representation of ip/subnet to in_addr + + CONSOLE_Print( "[UDPSOCKET] using broadcast target [" + subnet + "]" ); + m_BroadcastTarget.s_addr = inet_addr( subnet.c_str( ) ); + + // if conversion fails, inet_addr( ) returns INADDR_NONE + + if( m_BroadcastTarget.s_addr == INADDR_NONE ) + { + CONSOLE_Print( "[UDPSOCKET] invalid broadcast target, using default broadcast target" ); + m_BroadcastTarget.s_addr = INADDR_BROADCAST; + } + } +} + +void CUDPSocket :: SetDontRoute( bool dontRoute ) +{ + int OptVal = 0; + + if( dontRoute ) + OptVal = 1; + + // don't route packets; make them ignore routes set by routing table and send them to the interface + // belonging to the target address directly + + setsockopt( m_Socket, SOL_SOCKET, SO_DONTROUTE, (const char *)&OptVal, sizeof( int ) ); +} + +// +// CUDPServer +// + +CUDPServer :: CUDPServer( ) : CUDPSocket( ) +{ + // make socket non blocking + +#ifdef WIN32 + int iMode = 1; + ioctlsocket( m_Socket, FIONBIO, (u_long FAR *)&iMode ); +#else + fcntl( m_Socket, F_SETFL, fcntl( m_Socket, F_GETFL ) | O_NONBLOCK ); +#endif + + // set the socket to reuse the address + // with UDP sockets this allows more than one program to listen on the same port + + int optval = 1; + +#ifdef WIN32 + setsockopt( m_Socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( int ) ); +#else + setsockopt( m_Socket, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof( int ) ); +#endif +} + +CUDPServer :: ~CUDPServer( ) +{ + +} + +bool CUDPServer :: Bind( struct sockaddr_in sin ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + m_SIN = sin; + + if( bind( m_Socket, (struct sockaddr *)&m_SIN, sizeof( m_SIN ) ) == SOCKET_ERROR ) + { + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[UDPSERVER] error (bind) - " + GetErrorString( ) ); + return false; + } + + return true; +} + +bool CUDPServer :: Bind( string address, uint16_t port ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError ) + return false; + + struct sockaddr_in sin; + sin.sin_family = AF_INET; + + if( !address.empty( ) ) + { + if( ( sin.sin_addr.s_addr = inet_addr( address.c_str( ) ) ) == INADDR_NONE ) + sin.sin_addr.s_addr = INADDR_ANY; + } + else + sin.sin_addr.s_addr = INADDR_ANY; + + sin.sin_port = htons( port ); + + return Bind( sin ); +} + +void CUDPServer :: RecvFrom( fd_set *fd, struct sockaddr_in *sin, string *message ) +{ + if( m_Socket == INVALID_SOCKET || m_HasError || !sin || !message ) + return; + + int AddrLen = sizeof( *sin ); + + if( FD_ISSET( m_Socket, fd ) ) + { + // data is waiting, receive it + + char buffer[1024]; + +#ifdef WIN32 + int c = recvfrom( m_Socket, buffer, 1024, 0, (struct sockaddr *)sin, &AddrLen ); +#else + int c = recvfrom( m_Socket, buffer, 1024, 0, (struct sockaddr *)sin, (socklen_t *)&AddrLen ); +#endif + + if( c == SOCKET_ERROR && GetLastError( ) != EWOULDBLOCK ) + { + // receive error + + m_HasError = true; + m_Error = GetLastError( ); + CONSOLE_Print( "[UDPSERVER] error (recvfrom) - " + GetErrorString( ) ); + } + else if( c > 0 ) + { + // success! + + *message = string( buffer, c ); + } + } +} diff --git a/ghost-legacy/socket.h b/ghost-legacy/socket.h new file mode 100644 index 0000000..8fa879b --- /dev/null +++ b/ghost-legacy/socket.h @@ -0,0 +1,231 @@ +/* + + Copyright [2008] [Trevor Hogan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/ + +*/ + +#ifndef SOCKET_H +#define SOCKET_H + +#ifdef WIN32 + #include + #include + + #define EADDRINUSE WSAEADDRINUSE + #define EADDRNOTAVAIL WSAEADDRNOTAVAIL + #define EAFNOSUPPORT WSAEAFNOSUPPORT + #define EALREADY WSAEALREADY + #define ECONNABORTED WSAECONNABORTED + #define ECONNREFUSED WSAECONNREFUSED + #define ECONNRESET WSAECONNRESET + #define EDESTADDRREQ WSAEDESTADDRREQ + #define EDQUOT WSAEDQUOT + #define EHOSTDOWN WSAEHOSTDOWN + #define EHOSTUNREACH WSAEHOSTUNREACH + #define EINPROGRESS WSAEINPROGRESS + #define EISCONN WSAEISCONN + #define ELOOP WSAELOOP + #define EMSGSIZE WSAEMSGSIZE + // #define ENAMETOOLONG WSAENAMETOOLONG + #define ENETDOWN WSAENETDOWN + #define ENETRESET WSAENETRESET + #define ENETUNREACH WSAENETUNREACH + #define ENOBUFS WSAENOBUFS + #define ENOPROTOOPT WSAENOPROTOOPT + #define ENOTCONN WSAENOTCONN + // #define ENOTEMPTY WSAENOTEMPTY + #define ENOTSOCK WSAENOTSOCK + #define EOPNOTSUPP WSAEOPNOTSUPP + #define EPFNOSUPPORT WSAEPFNOSUPPORT + #define EPROTONOSUPPORT WSAEPROTONOSUPPORT + #define EPROTOTYPE WSAEPROTOTYPE + #define EREMOTE WSAEREMOTE + #define ESHUTDOWN WSAESHUTDOWN + #define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT + #define ESTALE WSAESTALE + #define ETIMEDOUT WSAETIMEDOUT + #define ETOOMANYREFS WSAETOOMANYREFS + #define EUSERS WSAEUSERS + #define EWOULDBLOCK WSAEWOULDBLOCK +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + typedef int SOCKET; + + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + + #define closesocket close + + extern int GetLastError( ); +#endif + +#ifndef INADDR_NONE + #define INADDR_NONE -1 +#endif + +#ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 +#endif + +#ifdef WIN32 + #define SHUT_RDWR 2 +#endif + +// +// CSocket +// + +class CSocket +{ +protected: + SOCKET m_Socket; + struct sockaddr_in m_SIN; + bool m_HasError; + int m_Error; + +public: + CSocket( ); + CSocket( SOCKET nSocket, struct sockaddr_in nSIN ); + ~CSocket( ); + + virtual BYTEARRAY GetPort( ); + virtual BYTEARRAY GetIP( ); + virtual string GetIPString( ); + virtual bool HasError( ) { return m_HasError; } + virtual int GetError( ) { return m_Error; } + virtual string GetErrorString( ); + virtual void SetFD( fd_set *fd, fd_set *send_fd, int *nfds ); + virtual void Allocate( int type ); + virtual void Reset( ); +}; + +// +// CTCPSocket +// + +class CTCPSocket : public CSocket +{ +protected: + bool m_Connected; + string m_LogFile; + +private: + string m_RecvBuffer; + string m_SendBuffer; + uint32_t m_LastRecv; + uint32_t m_LastSend; + +public: + CTCPSocket( ); + CTCPSocket( SOCKET nSocket, struct sockaddr_in nSIN ); + virtual ~CTCPSocket( ); + + virtual void Reset( ); + virtual bool GetConnected( ) { return m_Connected; } + virtual string *GetBytes( ) { return &m_RecvBuffer; } + virtual void PutBytes( string bytes ); + virtual void PutBytes( BYTEARRAY bytes ); + virtual void ClearRecvBuffer( ) { m_RecvBuffer.clear( ); } + virtual void ClearSendBuffer( ) { m_SendBuffer.clear( ); } + virtual uint32_t GetLastRecv( ) { return m_LastRecv; } + virtual uint32_t GetLastSend( ) { return m_LastSend; } + virtual void DoRecv( fd_set *fd ); + virtual void DoSend( fd_set *send_fd ); + virtual void Disconnect( ); + virtual void SetNoDelay( bool noDelay ); + virtual void SetLogFile( string nLogFile ) { m_LogFile = nLogFile; } +}; + +// +// CTCPClient +// + +class CTCPClient : public CTCPSocket +{ +protected: + bool m_Connecting; + +public: + CTCPClient( ); + virtual ~CTCPClient( ); + + virtual void Reset( ); + virtual void Disconnect( ); + virtual bool GetConnecting( ) { return m_Connecting; } + virtual void Connect( string localaddress, string address, uint16_t port ); + virtual bool CheckConnect( ); +}; + +// +// CTCPServer +// + +class CTCPServer : public CTCPSocket +{ +public: + CTCPServer( ); + virtual ~CTCPServer( ); + + virtual bool Listen( string address, uint16_t port ); + virtual CTCPSocket *Accept( fd_set *fd ); +}; + +// +// CUDPSocket +// + +class CUDPSocket : public CSocket +{ +protected: + struct in_addr m_BroadcastTarget; +public: + CUDPSocket( ); + virtual ~CUDPSocket( ); + + virtual bool SendTo( struct sockaddr_in sin, BYTEARRAY message ); + virtual bool SendTo( string address, uint16_t port, BYTEARRAY message ); + virtual bool Broadcast( uint16_t port, BYTEARRAY message ); + virtual void SetBroadcastTarget( string subnet ); + virtual void SetDontRoute( bool dontRoute ); +}; + +// +// CUDPServer +// + +class CUDPServer : public CUDPSocket +{ +public: + CUDPServer( ); + virtual ~CUDPServer( ); + + virtual bool Bind( struct sockaddr_in sin ); + virtual bool Bind( string address, uint16_t port ); + virtual void RecvFrom( fd_set *fd, struct sockaddr_in *sin, string *message ); +}; + +#endif diff --git a/ghost-legacy/sqlite3.c b/ghost-legacy/sqlite3.c new file mode 100644 index 0000000..fe2775d --- /dev/null +++ b/ghost-legacy/sqlite3.c @@ -0,0 +1,106729 @@ +/****************************************************************************** +** This file is an amalgamation of many separate C source files from SQLite +** version 3.6.16. By combining all the individual C code files into this +** single large file, the entire code can be compiled as a one translation +** unit. This allows many compilers to do optimizations that would not be +** possible if the files were compiled separately. Performance improvements +** of 5% are more are commonly seen when SQLite is compiled as a single +** translation unit. +** +** This file is all you need to compile SQLite. To use SQLite in other +** programs, you need this file and the "sqlite3.h" header file that defines +** the programming interface to the SQLite library. (If you do not have +** the "sqlite3.h" header file at hand, you will find a copy in the first +** 5626 lines past this header comment.) Additional code files may be +** needed if you want a wrapper to interface SQLite with your choice of +** programming language. The code for the "sqlite3" command-line shell +** is also in a separate file. This file contains only code for the core +** SQLite library. +** +** This amalgamation was generated on 2009-06-27 14:10:06 UTC. +*/ +#define SQLITE_CORE 1 +#define SQLITE_AMALGAMATION 1 +#ifndef SQLITE_PRIVATE +# define SQLITE_PRIVATE static +#endif +#ifndef SQLITE_API +# define SQLITE_API +#endif +/************** Begin file sqliteInt.h ***************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Internal interface definitions for SQLite. +** +** @(#) $Id: sqliteInt.h,v 1.890 2009/06/26 15:14:55 drh Exp $ +*/ +#ifndef _SQLITEINT_H_ +#define _SQLITEINT_H_ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#ifdef _HAVE_SQLITE_CONFIG_H +#include "config.h" +#endif + +/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ +/************** Begin file sqliteLimit.h *************************************/ +/* +** 2007 May 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file defines various limits of what SQLite can process. +** +** @(#) $Id: sqliteLimit.h,v 1.10 2009/01/10 16:15:09 danielk1977 Exp $ +*/ + +/* +** The maximum length of a TEXT or BLOB in bytes. This also +** limits the size of a row in a table or index. +** +** The hard limit is the ability of a 32-bit signed integer +** to count the size: 2^31-1 or 2147483647. +*/ +#ifndef SQLITE_MAX_LENGTH +# define SQLITE_MAX_LENGTH 1000000000 +#endif + +/* +** This is the maximum number of +** +** * Columns in a table +** * Columns in an index +** * Columns in a view +** * Terms in the SET clause of an UPDATE statement +** * Terms in the result set of a SELECT statement +** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. +** * Terms in the VALUES clause of an INSERT statement +** +** The hard upper limit here is 32676. Most database people will +** tell you that in a well-normalized database, you usually should +** not have more than a dozen or so columns in any table. And if +** that is the case, there is no point in having more than a few +** dozen values in any of the other situations described above. +*/ +#ifndef SQLITE_MAX_COLUMN +# define SQLITE_MAX_COLUMN 2000 +#endif + +/* +** The maximum length of a single SQL statement in bytes. +** +** It used to be the case that setting this value to zero would +** turn the limit off. That is no longer true. It is not possible +** to turn this limit off. +*/ +#ifndef SQLITE_MAX_SQL_LENGTH +# define SQLITE_MAX_SQL_LENGTH 1000000000 +#endif + +/* +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an +** expression. +** +** A value of 0 used to mean that the limit was not enforced. +** But that is no longer true. The limit is now strictly enforced +** at all times. +*/ +#ifndef SQLITE_MAX_EXPR_DEPTH +# define SQLITE_MAX_EXPR_DEPTH 1000 +#endif + +/* +** The maximum number of terms in a compound SELECT statement. +** The code generator for compound SELECT statements does one +** level of recursion for each term. A stack overflow can result +** if the number of terms is too large. In practice, most SQL +** never has more than 3 or 4 terms. Use a value of 0 to disable +** any limit on the number of terms in a compount SELECT. +*/ +#ifndef SQLITE_MAX_COMPOUND_SELECT +# define SQLITE_MAX_COMPOUND_SELECT 500 +#endif + +/* +** The maximum number of opcodes in a VDBE program. +** Not currently enforced. +*/ +#ifndef SQLITE_MAX_VDBE_OP +# define SQLITE_MAX_VDBE_OP 25000 +#endif + +/* +** The maximum number of arguments to an SQL function. +*/ +#ifndef SQLITE_MAX_FUNCTION_ARG +# define SQLITE_MAX_FUNCTION_ARG 127 +#endif + +/* +** The maximum number of in-memory pages to use for the main database +** table and for temporary tables. The SQLITE_DEFAULT_CACHE_SIZE +*/ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE 2000 +#endif +#ifndef SQLITE_DEFAULT_TEMP_CACHE_SIZE +# define SQLITE_DEFAULT_TEMP_CACHE_SIZE 500 +#endif + +/* +** The maximum number of attached databases. This must be between 0 +** and 30. The upper bound on 30 is because a 32-bit integer bitmap +** is used internally to track attached databases. +*/ +#ifndef SQLITE_MAX_ATTACHED +# define SQLITE_MAX_ATTACHED 10 +#endif + + +/* +** The maximum value of a ?nnn wildcard that the parser will accept. +*/ +#ifndef SQLITE_MAX_VARIABLE_NUMBER +# define SQLITE_MAX_VARIABLE_NUMBER 999 +#endif + +/* Maximum page size. The upper bound on this value is 32768. This a limit +** imposed by the necessity of storing the value in a 2-byte unsigned integer +** and the fact that the page size must be a power of 2. +** +** If this limit is changed, then the compiled library is technically +** incompatible with an SQLite library compiled with a different limit. If +** a process operating on a database with a page-size of 65536 bytes +** crashes, then an instance of SQLite compiled with the default page-size +** limit will not be able to rollback the aborted transaction. This could +** lead to database corruption. +*/ +#ifndef SQLITE_MAX_PAGE_SIZE +# define SQLITE_MAX_PAGE_SIZE 32768 +#endif + + +/* +** The default size of a database page. +*/ +#ifndef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE 1024 +#endif +#if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + +/* +** Ordinarily, if no value is explicitly provided, SQLite creates databases +** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain +** device characteristics (sector-size and atomic write() support), +** SQLite may choose a larger value. This constant is the maximum value +** SQLite will choose on its own. +*/ +#ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192 +#endif +#if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + + +/* +** Maximum number of pages in one database file. +** +** This is really just the default value for the max_page_count pragma. +** This value can be lowered (or raised) at run-time using that the +** max_page_count macro. +*/ +#ifndef SQLITE_MAX_PAGE_COUNT +# define SQLITE_MAX_PAGE_COUNT 1073741823 +#endif + +/* +** Maximum length (in bytes) of the pattern in a LIKE or GLOB +** operator. +*/ +#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH +# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 +#endif + +/************** End of sqliteLimit.h *****************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* Disable nuisance warnings on Borland compilers */ +#if defined(__BORLANDC__) +#pragma warn -rch /* unreachable code */ +#pragma warn -ccc /* Condition is always true or false */ +#pragma warn -aus /* Assigned value is never used */ +#pragma warn -csu /* Comparing signed and unsigned */ +#pragma warn -spa /* Suspicious pointer arithmetic */ +#endif + +/* Needed for various definitions... */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +/* +** Include standard header files as necessary +*/ +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif + +/* +** This macro is used to "hide" some ugliness in casting an int +** value to a ptr value under the MSVC 64-bit compiler. Casting +** non 64-bit values to ptr types results in a "hard" error with +** the MSVC 64-bit compiler which this attempts to avoid. +** +** A simple compiler pragma or casting sequence could not be found +** to correct this in all situations, so this macro was introduced. +** +** It could be argued that the intptr_t type could be used in this +** case, but that type is not available on all compilers, or +** requires the #include of specific headers which differs between +** platforms. +** +** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on +** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). +** So we have to define the macros in different ways depending on the +** compiler. +*/ +#if defined(__GNUC__) +# if defined(HAVE_STDINT_H) +# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) +# else +# define SQLITE_INT_TO_PTR(X) ((void*)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(X)) +# endif +#else +# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#endif + +/* +** These #defines should enable >2GB file support on POSIX if the +** underlying operating system supports it. If the OS lacks +** large file support, or if the OS is windows, these should be no-ops. +** +** Ticket #2739: The _LARGEFILE_SOURCE macro must appear before any +** system #includes. Hence, this block of code must be the very first +** code in all source files. +** +** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch +** on the compiler command line. This is necessary if you are compiling +** on a recent machine (ex: Red Hat 7.2) but you want your code to work +** on an older machine (ex: Red Hat 6.0). If you compile on Red Hat 7.2 +** without this option, LFS is enable. But LFS does not exist in the kernel +** in Red Hat 6.0, so the code won't work. Hence, for maximum binary +** portability you should omit LFS. +** +** Similar is true for Mac OS X. LFS is only supported on Mac OS X 9 and later. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + + +/* +** The SQLITE_THREADSAFE macro must be defined as either 0 or 1. +** Older versions of SQLite used an optional THREADSAFE macro. +** We support that for legacy +*/ +#if !defined(SQLITE_THREADSAFE) +#if defined(THREADSAFE) +# define SQLITE_THREADSAFE THREADSAFE +#else +# define SQLITE_THREADSAFE 1 +#endif +#endif + +/* +** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1. +** It determines whether or not the features related to +** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can +** be overridden at runtime using the sqlite3_config() API. +*/ +#if !defined(SQLITE_DEFAULT_MEMSTATUS) +# define SQLITE_DEFAULT_MEMSTATUS 1 +#endif + +/* +** Exactly one of the following macros must be defined in order to +** specify which memory allocation subsystem to use. +** +** SQLITE_SYSTEM_MALLOC // Use normal system malloc() +** SQLITE_MEMDEBUG // Debugging version of system malloc() +** SQLITE_MEMORY_SIZE // internal allocator #1 +** SQLITE_MMAP_HEAP_SIZE // internal mmap() allocator +** SQLITE_POW2_MEMORY_SIZE // internal power-of-two allocator +** +** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as +** the default. +*/ +#if defined(SQLITE_SYSTEM_MALLOC)+defined(SQLITE_MEMDEBUG)+\ + defined(SQLITE_MEMORY_SIZE)+defined(SQLITE_MMAP_HEAP_SIZE)+\ + defined(SQLITE_POW2_MEMORY_SIZE)>1 +# error "At most one of the following compile-time configuration options\ + is allows: SQLITE_SYSTEM_MALLOC, SQLITE_MEMDEBUG, SQLITE_MEMORY_SIZE,\ + SQLITE_MMAP_HEAP_SIZE, SQLITE_POW2_MEMORY_SIZE" +#endif +#if defined(SQLITE_SYSTEM_MALLOC)+defined(SQLITE_MEMDEBUG)+\ + defined(SQLITE_MEMORY_SIZE)+defined(SQLITE_MMAP_HEAP_SIZE)+\ + defined(SQLITE_POW2_MEMORY_SIZE)==0 +# define SQLITE_SYSTEM_MALLOC 1 +#endif + +/* +** If SQLITE_MALLOC_SOFT_LIMIT is not zero, then try to keep the +** sizes of memory allocations below this value where possible. +*/ +#if !defined(SQLITE_MALLOC_SOFT_LIMIT) +# define SQLITE_MALLOC_SOFT_LIMIT 1024 +#endif + +/* +** We need to define _XOPEN_SOURCE as follows in order to enable +** recursive mutexes on most Unix systems. But Mac OS X is different. +** The _XOPEN_SOURCE define causes problems for Mac OS X we are told, +** so it is omitted there. See ticket #2673. +** +** Later we learn that _XOPEN_SOURCE is poorly or incorrectly +** implemented on some systems. So we avoid defining it at all +** if it is already defined or if it is unneeded because we are +** not doing a threadsafe build. Ticket #2681. +** +** See also ticket #2741. +*/ +#if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) && SQLITE_THREADSAFE +# define _XOPEN_SOURCE 500 /* Needed to enable pthread recursive mutexes */ +#endif + +/* +** The TCL headers are only needed when compiling the TCL bindings. +*/ +#if defined(SQLITE_TCL) || defined(TCLSH) +# include +#endif + +/* +** Many people are failing to set -DNDEBUG=1 when compiling SQLite. +** Setting NDEBUG makes the code smaller and run faster. So the following +** lines are added to automatically set NDEBUG unless the -DSQLITE_DEBUG=1 +** option is set. Thus NDEBUG becomes an opt-in rather than an opt-out +** feature. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif + +/* +** The testcase() macro is used to aid in coverage testing. When +** doing coverage testing, the condition inside the argument to +** testcase() must be evaluated both true and false in order to +** get full branch coverage. The testcase() macro is inserted +** to help ensure adequate test coverage in places where simple +** condition/decision coverage is inadequate. For example, testcase() +** can be used to make sure boundary values are tested. For +** bitmask tests, testcase() can be used to make sure each bit +** is significant and used at least once. On switch statements +** where multiple cases go to the same block of code, testcase() +** can insure that all cases are evaluated. +** +*/ +#ifdef SQLITE_COVERAGE_TEST +SQLITE_PRIVATE void sqlite3Coverage(int); +# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } +#else +# define testcase(X) +#endif + +/* +** The TESTONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within testcase() and assert() macros. +*/ +#if !defined(NDEBUG) || defined(SQLITE_COVERAGE_TEST) +# define TESTONLY(X) X +#else +# define TESTONLY(X) +#endif + +/* +** Sometimes we need a small amount of code such as a variable initialization +** to setup for a later assert() statement. We do not want this code to +** appear when assert() is disabled. The following macro is therefore +** used to contain that setup code. The "VVA" acronym stands for +** "Verification, Validation, and Accreditation". In other words, the +** code within VVA_ONLY() will only run during verification processes. +*/ +#ifndef NDEBUG +# define VVA_ONLY(X) X +#else +# define VVA_ONLY(X) +#endif + +/* +** The ALWAYS and NEVER macros surround boolean expressions which +** are intended to always be true or false, respectively. Such +** expressions could be omitted from the code completely. But they +** are included in a few cases in order to enhance the resilience +** of SQLite to unexpected behavior - to make the code "self-healing" +** or "ductile" rather than being "brittle" and crashing at the first +** hint of unplanned behavior. +** +** In other words, ALWAYS and NEVER are added for defensive code. +** +** When doing coverage testing ALWAYS and NEVER are hard-coded to +** be true and false so that the unreachable code then specify will +** not be counted as untested code. +*/ +#if defined(SQLITE_COVERAGE_TEST) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +/* +** The macro unlikely() is a hint that surrounds a boolean +** expression that is usually false. Macro likely() surrounds +** a boolean expression that is usually true. GCC is able to +** use these hints to generate better code, sometimes. +*/ +#if defined(__GNUC__) && 0 +# define likely(X) __builtin_expect((X),1) +# define unlikely(X) __builtin_expect((X),0) +#else +# define likely(X) !!(X) +# define unlikely(X) !!(X) +#endif + +/************** Include sqlite3.h in the middle of sqliteInt.h ***************/ +/************** Begin file sqlite3.h *****************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. If a C-function, structure, datatype, +** or constant definition does not appear in this file, then it is +** not a published API of SQLite, is subject to change without +** notice, and should not be referenced by programs that use SQLite. +** +** Some of the definitions that are in this file are marked as +** "experimental". Experimental interfaces are normally new +** features recently added to SQLite. We do not anticipate changes +** to experimental interfaces but reserve to make minor changes if +** experience from use "in the wild" suggest such changes are prudent. +** +** The official C-language API documentation for SQLite is derived +** from comments in this file. This file is the authoritative source +** on how SQLite interfaces are suppose to operate. +** +** The name of this file under configuration management is "sqlite.h.in". +** The makefile makes some minor changes to this file (such as inserting +** the version number) and changes its name to "sqlite3.h" as +** part of the build process. +** +** @(#) $Id: sqlite.h.in,v 1.458 2009/06/19 22:50:31 drh Exp $ +*/ +#ifndef _SQLITE3_H_ +#define _SQLITE3_H_ +#include /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#if 0 +extern "C" { +#endif + + +/* +** Add the ability to override 'extern' +*/ +#ifndef SQLITE_EXTERN +# define SQLITE_EXTERN extern +#endif + +/* +** These no-op macros are used in front of interfaces to mark those +** interfaces as either deprecated or experimental. New applications +** should not use deprecated intrfaces - they are support for backwards +** compatibility only. Application writers should be aware that +** experimental interfaces are subject to change in point releases. +** +** These macros used to resolve to various kinds of compiler magic that +** would generate warning messages when they were used. But that +** compiler magic ended up generating such a flurry of bug reports +** that we have taken it all out and gone back to using simple +** noop macros. +*/ +#define SQLITE_DEPRECATED +#define SQLITE_EXPERIMENTAL + +/* +** Ensure these symbols were not defined by some previous header file. +*/ +#ifdef SQLITE_VERSION +# undef SQLITE_VERSION +#endif +#ifdef SQLITE_VERSION_NUMBER +# undef SQLITE_VERSION_NUMBER +#endif + +/* +** CAPI3REF: Compile-Time Library Version Numbers {H10010} +** +** The SQLITE_VERSION and SQLITE_VERSION_NUMBER #defines in +** the sqlite3.h file specify the version of SQLite with which +** that header file is associated. +** +** The "version" of SQLite is a string of the form "X.Y.Z". +** The phrase "alpha" or "beta" might be appended after the Z. +** The X value is major version number always 3 in SQLite3. +** The X value only changes when backwards compatibility is +** broken and we intend to never break backwards compatibility. +** The Y value is the minor version number and only changes when +** there are major feature enhancements that are forwards compatible +** but not backwards compatible. +** The Z value is the release number and is incremented with +** each release but resets back to 0 whenever Y is incremented. +** +** See also: [sqlite3_libversion()] and [sqlite3_libversion_number()]. +** +** Requirements: [H10011] [H10014] +*/ +#define SQLITE_VERSION "3.6.16" +#define SQLITE_VERSION_NUMBER 3006016 + +/* +** CAPI3REF: Run-Time Library Version Numbers {H10020} +** KEYWORDS: sqlite3_version +** +** These features provide the same information as the [SQLITE_VERSION] +** and [SQLITE_VERSION_NUMBER] #defines in the header, but are associated +** with the library instead of the header file. Cautious programmers might +** include a check in their application to verify that +** sqlite3_libversion_number() always returns the value +** [SQLITE_VERSION_NUMBER]. +** +** The sqlite3_libversion() function returns the same information as is +** in the sqlite3_version[] string constant. The function is provided +** for use in DLLs since DLL users usually do not have direct access to string +** constants within the DLL. +** +** Requirements: [H10021] [H10022] [H10023] +*/ +SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; +SQLITE_API const char *sqlite3_libversion(void); +SQLITE_API int sqlite3_libversion_number(void); + +/* +** CAPI3REF: Test To See If The Library Is Threadsafe {H10100} +** +** SQLite can be compiled with or without mutexes. When +** the [SQLITE_THREADSAFE] C preprocessor macro 1 or 2, mutexes +** are enabled and SQLite is threadsafe. When the +** [SQLITE_THREADSAFE] macro is 0, +** the mutexes are omitted. Without the mutexes, it is not safe +** to use SQLite concurrently from more than one thread. +** +** Enabling mutexes incurs a measurable performance penalty. +** So if speed is of utmost importance, it makes sense to disable +** the mutexes. But for maximum safety, mutexes should be enabled. +** The default behavior is for mutexes to be enabled. +** +** This interface can be used by a program to make sure that the +** version of SQLite that it is linking against was compiled with +** the desired setting of the [SQLITE_THREADSAFE] macro. +** +** This interface only reports on the compile-time mutex setting +** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with +** SQLITE_THREADSAFE=1 then mutexes are enabled by default but +** can be fully or partially disabled using a call to [sqlite3_config()] +** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], +** or [SQLITE_CONFIG_MUTEX]. The return value of this function shows +** only the default compile-time setting, not any run-time changes +** to that setting. +** +** See the [threading mode] documentation for additional information. +** +** Requirements: [H10101] [H10102] +*/ +SQLITE_API int sqlite3_threadsafe(void); + +/* +** CAPI3REF: Database Connection Handle {H12000} +** KEYWORDS: {database connection} {database connections} +** +** Each open SQLite database is represented by a pointer to an instance of +** the opaque structure named "sqlite3". It is useful to think of an sqlite3 +** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and +** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()] +** is its destructor. There are many other interfaces (such as +** [sqlite3_prepare_v2()], [sqlite3_create_function()], and +** [sqlite3_busy_timeout()] to name but three) that are methods on an +** sqlite3 object. +*/ +typedef struct sqlite3 sqlite3; + +/* +** CAPI3REF: 64-Bit Integer Types {H10200} +** KEYWORDS: sqlite_int64 sqlite_uint64 +** +** Because there is no cross-platform way to specify 64-bit integer types +** SQLite includes typedefs for 64-bit signed and unsigned integers. +** +** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions. +** The sqlite_int64 and sqlite_uint64 types are supported for backwards +** compatibility only. +** +** Requirements: [H10201] [H10202] +*/ +#ifdef SQLITE_INT64_TYPE + typedef SQLITE_INT64_TYPE sqlite_int64; + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +#elif defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; +#else + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; +#endif +typedef sqlite_int64 sqlite3_int64; +typedef sqlite_uint64 sqlite3_uint64; + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite3_int64 +#endif + +/* +** CAPI3REF: Closing A Database Connection {H12010} +** +** This routine is the destructor for the [sqlite3] object. +** +** Applications should [sqlite3_finalize | finalize] all [prepared statements] +** and [sqlite3_blob_close | close] all [BLOB handles] associated with +** the [sqlite3] object prior to attempting to close the object. +** The [sqlite3_next_stmt()] interface can be used to locate all +** [prepared statements] associated with a [database connection] if desired. +** Typical code might look like this: +** +**
+** sqlite3_stmt *pStmt;
+** while( (pStmt = sqlite3_next_stmt(db, 0))!=0 ){
+**     sqlite3_finalize(pStmt);
+** }
+** 
+** +** If [sqlite3_close()] is invoked while a transaction is open, +** the transaction is automatically rolled back. +** +** The C parameter to [sqlite3_close(C)] must be either a NULL +** pointer or an [sqlite3] object pointer obtained +** from [sqlite3_open()], [sqlite3_open16()], or +** [sqlite3_open_v2()], and not previously closed. +** +** Requirements: +** [H12011] [H12012] [H12013] [H12014] [H12015] [H12019] +*/ +SQLITE_API int sqlite3_close(sqlite3 *); + +/* +** The type for a callback function. +** This is legacy and deprecated. It is included for historical +** compatibility and is not documented. +*/ +typedef int (*sqlite3_callback)(void*,int,char**, char**); + +/* +** CAPI3REF: One-Step Query Execution Interface {H12100} +** +** The sqlite3_exec() interface is a convenient way of running one or more +** SQL statements without having to write a lot of C code. The UTF-8 encoded +** SQL statements are passed in as the second parameter to sqlite3_exec(). +** The statements are evaluated one by one until either an error or +** an interrupt is encountered, or until they are all done. The 3rd parameter +** is an optional callback that is invoked once for each row of any query +** results produced by the SQL statements. The 5th parameter tells where +** to write any error messages. +** +** The error message passed back through the 5th parameter is held +** in memory obtained from [sqlite3_malloc()]. To avoid a memory leak, +** the calling application should call [sqlite3_free()] on any error +** message returned through the 5th parameter when it has finished using +** the error message. +** +** If the SQL statement in the 2nd parameter is NULL or an empty string +** or a string containing only whitespace and comments, then no SQL +** statements are evaluated and the database is not changed. +** +** The sqlite3_exec() interface is implemented in terms of +** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()]. +** The sqlite3_exec() routine does nothing to the database that cannot be done +** by [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()]. +** +** The first parameter to [sqlite3_exec()] must be an valid and open +** [database connection]. +** +** The database connection must not be closed while +** [sqlite3_exec()] is running. +** +** The calling function should use [sqlite3_free()] to free +** the memory that *errmsg is left pointing at once the error +** message is no longer needed. +** +** The SQL statement text in the 2nd parameter to [sqlite3_exec()] +** must remain unchanged while [sqlite3_exec()] is running. +** +** Requirements: +** [H12101] [H12102] [H12104] [H12105] [H12107] [H12110] [H12113] [H12116] +** [H12119] [H12122] [H12125] [H12131] [H12134] [H12137] [H12138] +*/ +SQLITE_API int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be evaluated */ + int (*callback)(void*,int,char**,char**), /* Callback function */ + void *, /* 1st argument to callback */ + char **errmsg /* Error msg written here */ +); + +/* +** CAPI3REF: Result Codes {H10210} +** KEYWORDS: SQLITE_OK {error code} {error codes} +** KEYWORDS: {result code} {result codes} +** +** Many SQLite functions return an integer result code from the set shown +** here in order to indicates success or failure. +** +** New error codes may be added in future versions of SQLite. +** +** See also: [SQLITE_IOERR_READ | extended result codes] +*/ +#define SQLITE_OK 0 /* Successful result */ +/* beginning-of-error-codes */ +#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* NOT USED. Table or record not found */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* NOT USED. Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Database is empty */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +/* end-of-error-codes */ + +/* +** CAPI3REF: Extended Result Codes {H10220} +** KEYWORDS: {extended error code} {extended error codes} +** KEYWORDS: {extended result code} {extended result codes} +** +** In its default configuration, SQLite API routines return one of 26 integer +** [SQLITE_OK | result codes]. However, experience has shown that many of +** these result codes are too coarse-grained. They do not provide as +** much information about problems as programmers might like. In an effort to +** address this, newer versions of SQLite (version 3.3.8 and later) include +** support for additional result codes that provide more detailed information +** about errors. The extended result codes are enabled or disabled +** on a per database connection basis using the +** [sqlite3_extended_result_codes()] API. +** +** Some of the available extended result codes are listed here. +** One may expect the number of extended result codes will be expand +** over time. Software that uses extended result codes should expect +** to see new result codes in future releases of SQLite. +** +** The SQLITE_OK result code will never be extended. It will always +** be exactly zero. +*/ +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8) ) + +/* +** CAPI3REF: Flags For File Open Operations {H10230} +** +** These bit values are intended for use in the +** 3rd parameter to the [sqlite3_open_v2()] interface and +** in the 4th parameter to the xOpen method of the +** [sqlite3_vfs] object. +*/ +#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ +#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ +#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ +#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ +#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ +#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ +#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ + +/* +** CAPI3REF: Device Characteristics {H10240} +** +** The xDeviceCapabilities method of the [sqlite3_io_methods] +** object returns an integer which is a vector of the these +** bit values expressing I/O characteristics of the mass storage +** device that holds the file that the [sqlite3_io_methods] +** refers to. +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +*/ +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 + +/* +** CAPI3REF: File Locking Levels {H10250} +** +** SQLite uses one of these integer values as the second +** argument to calls it makes to the xLock() and xUnlock() methods +** of an [sqlite3_io_methods] object. +*/ +#define SQLITE_LOCK_NONE 0 +#define SQLITE_LOCK_SHARED 1 +#define SQLITE_LOCK_RESERVED 2 +#define SQLITE_LOCK_PENDING 3 +#define SQLITE_LOCK_EXCLUSIVE 4 + +/* +** CAPI3REF: Synchronization Type Flags {H10260} +** +** When SQLite invokes the xSync() method of an +** [sqlite3_io_methods] object it uses a combination of +** these integer values as the second argument. +** +** When the SQLITE_SYNC_DATAONLY flag is used, it means that the +** sync operation only needs to flush data to mass storage. Inode +** information need not be flushed. If the lower four bits of the flag +** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. +** If the lower four bits equal SQLITE_SYNC_FULL, that means +** to use Mac OS X style fullsync instead of fsync(). +*/ +#define SQLITE_SYNC_NORMAL 0x00002 +#define SQLITE_SYNC_FULL 0x00003 +#define SQLITE_SYNC_DATAONLY 0x00010 + +/* +** CAPI3REF: OS Interface Open File Handle {H11110} +** +** An [sqlite3_file] object represents an open file in the OS +** interface layer. Individual OS interface implementations will +** want to subclass this object by appending additional fields +** for their own use. The pMethods entry is a pointer to an +** [sqlite3_io_methods] object that defines methods for performing +** I/O operations on the open file. +*/ +typedef struct sqlite3_file sqlite3_file; +struct sqlite3_file { + const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ +}; + +/* +** CAPI3REF: OS Interface File Virtual Methods Object {H11120} +** +** Every file opened by the [sqlite3_vfs] xOpen method populates an +** [sqlite3_file] object (or, more commonly, a subclass of the +** [sqlite3_file] object) with a pointer to an instance of this object. +** This object defines the methods used to perform various operations +** against the open file represented by the [sqlite3_file] object. +** +** If the xOpen method sets the sqlite3_file.pMethods element +** to a non-NULL pointer, then the sqlite3_io_methods.xClose method +** may be invoked even if the xOpen reported that it failed. The +** only way to prevent a call to xClose following a failed xOpen +** is for the xOpen to set the sqlite3_file.pMethods element to NULL. +** +** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or +** [SQLITE_SYNC_FULL]. The first choice is the normal fsync(). +** The second choice is a Mac OS X style fullsync. The [SQLITE_SYNC_DATAONLY] +** flag may be ORed in to indicate that only the data of the file +** and not its inode needs to be synced. +** +** The integer values to xLock() and xUnlock() are one of +**
    +**
  • [SQLITE_LOCK_NONE], +**
  • [SQLITE_LOCK_SHARED], +**
  • [SQLITE_LOCK_RESERVED], +**
  • [SQLITE_LOCK_PENDING], or +**
  • [SQLITE_LOCK_EXCLUSIVE]. +**
+** xLock() increases the lock. xUnlock() decreases the lock. +** The xCheckReservedLock() method checks whether any database connection, +** either in this process or in some other process, is holding a RESERVED, +** PENDING, or EXCLUSIVE lock on the file. It returns true +** if such a lock exists and false otherwise. +** +** The xFileControl() method is a generic interface that allows custom +** VFS implementations to directly control an open file using the +** [sqlite3_file_control()] interface. The second "op" argument is an +** integer opcode. The third argument is a generic pointer intended to +** point to a structure that may contain arguments or space in which to +** write return values. Potential uses for xFileControl() might be +** functions to enable blocking locks with timeouts, to change the +** locking strategy (for example to use dot-file locks), to inquire +** about the status of a lock, or to break stale locks. The SQLite +** core reserves all opcodes less than 100 for its own use. +** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available. +** Applications that define a custom xFileControl method should use opcodes +** greater than 100 to avoid conflicts. +** +** The xSectorSize() method returns the sector size of the +** device that underlies the file. The sector size is the +** minimum write that can be performed without disturbing +** other bytes in the file. The xDeviceCharacteristics() +** method returns a bit vector describing behaviors of the +** underlying device: +** +**
    +**
  • [SQLITE_IOCAP_ATOMIC] +**
  • [SQLITE_IOCAP_ATOMIC512] +**
  • [SQLITE_IOCAP_ATOMIC1K] +**
  • [SQLITE_IOCAP_ATOMIC2K] +**
  • [SQLITE_IOCAP_ATOMIC4K] +**
  • [SQLITE_IOCAP_ATOMIC8K] +**
  • [SQLITE_IOCAP_ATOMIC16K] +**
  • [SQLITE_IOCAP_ATOMIC32K] +**
  • [SQLITE_IOCAP_ATOMIC64K] +**
  • [SQLITE_IOCAP_SAFE_APPEND] +**
  • [SQLITE_IOCAP_SEQUENTIAL] +**
+** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +** +** If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill +** in the unread portions of the buffer with zeros. A VFS that +** fails to zero-fill short reads might seem to work. However, +** failure to zero-fill short reads will eventually lead to +** database corruption. +*/ +typedef struct sqlite3_io_methods sqlite3_io_methods; +struct sqlite3_io_methods { + int iVersion; + int (*xClose)(sqlite3_file*); + int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); + int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); + int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); + int (*xSync)(sqlite3_file*, int flags); + int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); + int (*xLock)(sqlite3_file*, int); + int (*xUnlock)(sqlite3_file*, int); + int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); + int (*xFileControl)(sqlite3_file*, int op, void *pArg); + int (*xSectorSize)(sqlite3_file*); + int (*xDeviceCharacteristics)(sqlite3_file*); + /* Additional methods may be added in future releases */ +}; + +/* +** CAPI3REF: Standard File Control Opcodes {H11310} +** +** These integer constants are opcodes for the xFileControl method +** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] +** interface. +** +** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This +** opcode causes the xFileControl method to write the current state of +** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], +** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) +** into an integer that the pArg argument points to. This capability +** is used during testing and only needs to be supported when SQLITE_TEST +** is defined. +*/ +#define SQLITE_FCNTL_LOCKSTATE 1 +#define SQLITE_GET_LOCKPROXYFILE 2 +#define SQLITE_SET_LOCKPROXYFILE 3 +#define SQLITE_LAST_ERRNO 4 + +/* +** CAPI3REF: Mutex Handle {H17110} +** +** The mutex module within SQLite defines [sqlite3_mutex] to be an +** abstract type for a mutex object. The SQLite core never looks +** at the internal representation of an [sqlite3_mutex]. It only +** deals with pointers to the [sqlite3_mutex] object. +** +** Mutexes are created using [sqlite3_mutex_alloc()]. +*/ +typedef struct sqlite3_mutex sqlite3_mutex; + +/* +** CAPI3REF: OS Interface Object {H11140} +** +** An instance of the sqlite3_vfs object defines the interface between +** the SQLite core and the underlying operating system. The "vfs" +** in the name of the object stands for "virtual file system". +** +** The value of the iVersion field is initially 1 but may be larger in +** future versions of SQLite. Additional fields may be appended to this +** object when the iVersion value is increased. Note that the structure +** of the sqlite3_vfs object changes in the transaction between +** SQLite version 3.5.9 and 3.6.0 and yet the iVersion field was not +** modified. +** +** The szOsFile field is the size of the subclassed [sqlite3_file] +** structure used by this VFS. mxPathname is the maximum length of +** a pathname in this VFS. +** +** Registered sqlite3_vfs objects are kept on a linked list formed by +** the pNext pointer. The [sqlite3_vfs_register()] +** and [sqlite3_vfs_unregister()] interfaces manage this list +** in a thread-safe way. The [sqlite3_vfs_find()] interface +** searches the list. Neither the application code nor the VFS +** implementation should use the pNext pointer. +** +** The pNext field is the only field in the sqlite3_vfs +** structure that SQLite will ever modify. SQLite will only access +** or modify this field while holding a particular static mutex. +** The application should never modify anything within the sqlite3_vfs +** object once the object has been registered. +** +** The zName field holds the name of the VFS module. The name must +** be unique across all VFS modules. +** +** SQLite will guarantee that the zFilename parameter to xOpen +** is either a NULL pointer or string obtained +** from xFullPathname(). SQLite further guarantees that +** the string will be valid and unchanged until xClose() is +** called. Because of the previous sentence, +** the [sqlite3_file] can safely store a pointer to the +** filename if it needs to remember the filename for some reason. +** If the zFilename parameter is xOpen is a NULL pointer then xOpen +** must invent its own temporary name for the file. Whenever the +** xFilename parameter is NULL it will also be the case that the +** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. +** +** The flags argument to xOpen() includes all bits set in +** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] +** or [sqlite3_open16()] is used, then flags includes at least +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** If xOpen() opens a file read-only then it sets *pOutFlags to +** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. +** +** SQLite will also add one of the following flags to the xOpen() +** call, depending on the object being opened: +** +**
    +**
  • [SQLITE_OPEN_MAIN_DB] +**
  • [SQLITE_OPEN_MAIN_JOURNAL] +**
  • [SQLITE_OPEN_TEMP_DB] +**
  • [SQLITE_OPEN_TEMP_JOURNAL] +**
  • [SQLITE_OPEN_TRANSIENT_DB] +**
  • [SQLITE_OPEN_SUBJOURNAL] +**
  • [SQLITE_OPEN_MASTER_JOURNAL] +**
+** +** The file I/O implementation can use the object type flags to +** change the way it deals with files. For example, an application +** that does not care about crash recovery or rollback might make +** the open of a journal file a no-op. Writes to this journal would +** also be no-ops, and any attempt to read the journal would return +** SQLITE_IOERR. Or the implementation might recognize that a database +** file will be doing page-aligned sector reads and writes in a random +** order and set up its I/O subsystem accordingly. +** +** SQLite might also add one of the following flags to the xOpen method: +** +**
    +**
  • [SQLITE_OPEN_DELETEONCLOSE] +**
  • [SQLITE_OPEN_EXCLUSIVE] +**
+** +** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be +** deleted when it is closed. The [SQLITE_OPEN_DELETEONCLOSE] +** will be set for TEMP databases, journals and for subjournals. +** +** The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction +** with the [SQLITE_OPEN_CREATE] flag, which are both directly +** analogous to the O_EXCL and O_CREAT flags of the POSIX open() +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** SQLITE_OPEN_CREATE, is used to indicate that file should always +** be created, and that it is an error if it already exists. +** It is not used to indicate the file should be opened +** for exclusive access. +** +** At least szOsFile bytes of memory are allocated by SQLite +** to hold the [sqlite3_file] structure passed as the third +** argument to xOpen. The xOpen method does not have to +** allocate the structure; it should just fill it in. Note that +** the xOpen method must set the sqlite3_file.pMethods to either +** a valid [sqlite3_io_methods] object or to NULL. xOpen must do +** this even if the open fails. SQLite expects that the sqlite3_file.pMethods +** element will be valid after xOpen returns regardless of the success +** or failure of the xOpen call. +** +** The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] +** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to +** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] +** to test whether a file is at least readable. The file can be a +** directory. +** +** SQLite will always allocate at least mxPathname+1 bytes for the +** output buffer xFullPathname. The exact size of the output buffer +** is also passed as a parameter to both methods. If the output buffer +** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is +** handled as a fatal error by SQLite, vfs implementations should endeavor +** to prevent this by setting mxPathname to a sufficiently large value. +** +** The xRandomness(), xSleep(), and xCurrentTime() interfaces +** are not strictly a part of the filesystem, but they are +** included in the VFS structure for completeness. +** The xRandomness() function attempts to return nBytes bytes +** of good-quality randomness into zOut. The return value is +** the actual number of bytes of randomness obtained. +** The xSleep() method causes the calling thread to sleep for at +** least the number of microseconds given. The xCurrentTime() +** method returns a Julian Day Number for the current date and time. +** +*/ +typedef struct sqlite3_vfs sqlite3_vfs; +struct sqlite3_vfs { + int iVersion; /* Structure version number */ + int szOsFile; /* Size of subclassed sqlite3_file */ + int mxPathname; /* Maximum file pathname length */ + sqlite3_vfs *pNext; /* Next registered VFS */ + const char *zName; /* Name of this virtual file system */ + void *pAppData; /* Pointer to application-specific data */ + int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, + int flags, int *pOutFlags); + int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); + int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); + void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); + void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); + void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); + void (*xDlClose)(sqlite3_vfs*, void*); + int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); + int (*xSleep)(sqlite3_vfs*, int microseconds); + int (*xCurrentTime)(sqlite3_vfs*, double*); + int (*xGetLastError)(sqlite3_vfs*, int, char *); + /* New fields may be appended in figure versions. The iVersion + ** value will increment whenever this happens. */ +}; + +/* +** CAPI3REF: Flags for the xAccess VFS method {H11190} +** +** These integer constants can be used as the third parameter to +** the xAccess method of an [sqlite3_vfs] object. {END} They determine +** what kind of permissions the xAccess method is looking for. +** With SQLITE_ACCESS_EXISTS, the xAccess method +** simply checks whether the file exists. +** With SQLITE_ACCESS_READWRITE, the xAccess method +** checks whether the file is both readable and writable. +** With SQLITE_ACCESS_READ, the xAccess method +** checks whether the file is readable. +*/ +#define SQLITE_ACCESS_EXISTS 0 +#define SQLITE_ACCESS_READWRITE 1 +#define SQLITE_ACCESS_READ 2 + +/* +** CAPI3REF: Initialize The SQLite Library {H10130} +** +** The sqlite3_initialize() routine initializes the +** SQLite library. The sqlite3_shutdown() routine +** deallocates any resources that were allocated by sqlite3_initialize(). +** +** A call to sqlite3_initialize() is an "effective" call if it is +** the first time sqlite3_initialize() is invoked during the lifetime of +** the process, or if it is the first time sqlite3_initialize() is invoked +** following a call to sqlite3_shutdown(). Only an effective call +** of sqlite3_initialize() does any initialization. All other calls +** are harmless no-ops. +** +** A call to sqlite3_shutdown() is an "effective" call if it is the first +** call to sqlite3_shutdown() since the last sqlite3_initialize(). Only +** an effective call to sqlite3_shutdown() does any deinitialization. +** All other calls to sqlite3_shutdown() are harmless no-ops. +** +** Among other things, sqlite3_initialize() shall invoke +** sqlite3_os_init(). Similarly, sqlite3_shutdown() +** shall invoke sqlite3_os_end(). +** +** The sqlite3_initialize() routine returns [SQLITE_OK] on success. +** If for some reason, sqlite3_initialize() is unable to initialize +** the library (perhaps it is unable to allocate a needed resource such +** as a mutex) it returns an [error code] other than [SQLITE_OK]. +** +** The sqlite3_initialize() routine is called internally by many other +** SQLite interfaces so that an application usually does not need to +** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] +** calls sqlite3_initialize() so the SQLite library will be automatically +** initialized when [sqlite3_open()] is called if it has not be initialized +** already. However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] +** compile-time option, then the automatic calls to sqlite3_initialize() +** are omitted and the application must call sqlite3_initialize() directly +** prior to using any other SQLite interface. For maximum portability, +** it is recommended that applications always invoke sqlite3_initialize() +** directly prior to using any other SQLite interface. Future releases +** of SQLite may require this. In other words, the behavior exhibited +** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the +** default behavior in some future release of SQLite. +** +** The sqlite3_os_init() routine does operating-system specific +** initialization of the SQLite library. The sqlite3_os_end() +** routine undoes the effect of sqlite3_os_init(). Typical tasks +** performed by these routines include allocation or deallocation +** of static resources, initialization of global variables, +** setting up a default [sqlite3_vfs] module, or setting up +** a default configuration using [sqlite3_config()]. +** +** The application should never invoke either sqlite3_os_init() +** or sqlite3_os_end() directly. The application should only invoke +** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init() +** interface is called automatically by sqlite3_initialize() and +** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate +** implementations for sqlite3_os_init() and sqlite3_os_end() +** are built into SQLite when it is compiled for unix, windows, or os/2. +** When built for other platforms (using the [SQLITE_OS_OTHER=1] compile-time +** option) the application must supply a suitable implementation for +** sqlite3_os_init() and sqlite3_os_end(). An application-supplied +** implementation of sqlite3_os_init() or sqlite3_os_end() +** must return [SQLITE_OK] on success and some other [error code] upon +** failure. +*/ +SQLITE_API int sqlite3_initialize(void); +SQLITE_API int sqlite3_shutdown(void); +SQLITE_API int sqlite3_os_init(void); +SQLITE_API int sqlite3_os_end(void); + +/* +** CAPI3REF: Configuring The SQLite Library {H14100} +** EXPERIMENTAL +** +** The sqlite3_config() interface is used to make global configuration +** changes to SQLite in order to tune SQLite to the specific needs of +** the application. The default configuration is recommended for most +** applications and so this routine is usually not necessary. It is +** provided to support rare applications with unusual needs. +** +** The sqlite3_config() interface is not threadsafe. The application +** must insure that no other SQLite interfaces are invoked by other +** threads while sqlite3_config() is running. Furthermore, sqlite3_config() +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** Note, however, that sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** +** The first argument to sqlite3_config() is an integer +** [SQLITE_CONFIG_SINGLETHREAD | configuration option] that determines +** what property of SQLite is to be configured. Subsequent arguments +** vary depending on the [SQLITE_CONFIG_SINGLETHREAD | configuration option] +** in the first argument. +** +** When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. +** If the option is unknown or SQLite is unable to set the option +** then this routine returns a non-zero [error code]. +** +** Requirements: +** [H14103] [H14106] [H14120] [H14123] [H14126] [H14129] [H14132] [H14135] +** [H14138] [H14141] [H14144] [H14147] [H14150] [H14153] [H14156] [H14159] +** [H14162] [H14165] [H14168] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_config(int, ...); + +/* +** CAPI3REF: Configure database connections {H14200} +** EXPERIMENTAL +** +** The sqlite3_db_config() interface is used to make configuration +** changes to a [database connection]. The interface is similar to +** [sqlite3_config()] except that the changes apply to a single +** [database connection] (specified in the first argument). The +** sqlite3_db_config() interface can only be used immediately after +** the database connection is created using [sqlite3_open()], +** [sqlite3_open16()], or [sqlite3_open_v2()]. +** +** The second argument to sqlite3_db_config(D,V,...) is the +** configuration verb - an integer code that indicates what +** aspect of the [database connection] is being configured. +** The only choice for this value is [SQLITE_DBCONFIG_LOOKASIDE]. +** New verbs are likely to be added in future releases of SQLite. +** Additional arguments depend on the verb. +** +** Requirements: +** [H14203] [H14206] [H14209] [H14212] [H14215] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_db_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Memory Allocation Routines {H10155} +** EXPERIMENTAL +** +** An instance of this object defines the interface between SQLite +** and low-level memory allocation routines. +** +** This object is used in only one place in the SQLite interface. +** A pointer to an instance of this object is the argument to +** [sqlite3_config()] when the configuration option is +** [SQLITE_CONFIG_MALLOC]. By creating an instance of this object +** and passing it to [sqlite3_config()] during configuration, an +** application can specify an alternative memory allocation subsystem +** for SQLite to use for all of its dynamic memory needs. +** +** Note that SQLite comes with a built-in memory allocator that is +** perfectly adequate for the overwhelming majority of applications +** and that this object is only useful to a tiny minority of applications +** with specialized memory allocation requirements. This object is +** also used during testing of SQLite in order to specify an alternative +** memory allocator that simulates memory out-of-memory conditions in +** order to verify that SQLite recovers gracefully from such +** conditions. +** +** The xMalloc, xFree, and xRealloc methods must work like the +** malloc(), free(), and realloc() functions from the standard library. +** +** xSize should return the allocated size of a memory allocation +** previously obtained from xMalloc or xRealloc. The allocated size +** is always at least as big as the requested size but may be larger. +** +** The xRoundup method returns what would be the allocated size of +** a memory allocation given a particular requested size. Most memory +** allocators round up memory allocations at least to the next multiple +** of 8. Some allocators round up to a larger multiple or to a power of 2. +** +** The xInit method initializes the memory allocator. (For example, +** it might allocate any require mutexes or initialize internal data +** structures. The xShutdown method is invoked (indirectly) by +** [sqlite3_shutdown()] and should deallocate any resources acquired +** by xInit. The pAppData pointer is used as the only parameter to +** xInit and xShutdown. +*/ +typedef struct sqlite3_mem_methods sqlite3_mem_methods; +struct sqlite3_mem_methods { + void *(*xMalloc)(int); /* Memory allocation function */ + void (*xFree)(void*); /* Free a prior allocation */ + void *(*xRealloc)(void*,int); /* Resize an allocation */ + int (*xSize)(void*); /* Return the size of an allocation */ + int (*xRoundup)(int); /* Round up request size to allocation size */ + int (*xInit)(void*); /* Initialize the memory allocator */ + void (*xShutdown)(void*); /* Deinitialize the memory allocator */ + void *pAppData; /* Argument to xInit() and xShutdown() */ +}; + +/* +** CAPI3REF: Configuration Options {H10160} +** EXPERIMENTAL +** +** These constants are the available integer configuration options that +** can be passed as the first argument to the [sqlite3_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_config()] to make sure that +** the call worked. The [sqlite3_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +**
+**
SQLITE_CONFIG_SINGLETHREAD
+**
There are no arguments to this option. This option disables +** all mutexing and puts SQLite into a mode where it can only be used +** by a single thread.
+** +**
SQLITE_CONFIG_MULTITHREAD
+**
There are no arguments to this option. This option disables +** mutexing on [database connection] and [prepared statement] objects. +** The application is responsible for serializing access to +** [database connections] and [prepared statements]. But other mutexes +** are enabled so that SQLite will be safe to use in a multi-threaded +** environment as long as no two threads attempt to use the same +** [database connection] at the same time. See the [threading mode] +** documentation for additional information.
+** +**
SQLITE_CONFIG_SERIALIZED
+**
There are no arguments to this option. This option enables +** all mutexes including the recursive +** mutexes on [database connection] and [prepared statement] objects. +** In this mode (which is the default when SQLite is compiled with +** [SQLITE_THREADSAFE=1]) the SQLite library will itself serialize access +** to [database connections] and [prepared statements] so that the +** application is free to use the same [database connection] or the +** same [prepared statement] in different threads at the same time. +** See the [threading mode] documentation for additional information.
+** +**
SQLITE_CONFIG_MALLOC
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mem_methods] structure. The argument specifies +** alternative low-level memory allocation routines to be used in place of +** the memory allocation routines built into SQLite.
+** +**
SQLITE_CONFIG_GETMALLOC
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +** structure is filled with the currently defined memory allocation routines. +** This option can be used to overload the default memory allocation +** routines with a wrapper that simulations memory allocation failure or +** tracks memory usage, for example.
+** +**
SQLITE_CONFIG_MEMSTATUS
+**
This option takes single argument of type int, interpreted as a +** boolean, which enables or disables the collection of memory allocation +** statistics. When disabled, the following SQLite interfaces become +** non-operational: +**
    +**
  • [sqlite3_memory_used()] +**
  • [sqlite3_memory_highwater()] +**
  • [sqlite3_soft_heap_limit()] +**
  • [sqlite3_status()] +**
+**
+** +**
SQLITE_CONFIG_SCRATCH
+**
This option specifies a static memory buffer that SQLite can use for +** scratch memory. There are three arguments: A pointer an 8-byte +** aligned memory buffer from which the scrach allocations will be +** drawn, the size of each scratch allocation (sz), +** and the maximum number of scratch allocations (N). The sz +** argument must be a multiple of 16. The sz parameter should be a few bytes +** larger than the actual scratch space required due to internal overhead. +** The first argument should pointer to an 8-byte aligned buffer +** of at least sz*N bytes of memory. +** SQLite will use no more than one scratch buffer at once per thread, so +** N should be set to the expected maximum number of threads. The sz +** parameter should be 6 times the size of the largest database page size. +** Scratch buffers are used as part of the btree balance operation. If +** The btree balancer needs additional memory beyond what is provided by +** scratch buffers or if no scratch buffer space is specified, then SQLite +** goes to [sqlite3_malloc()] to obtain the memory it needs.
+** +**
SQLITE_CONFIG_PAGECACHE
+**
This option specifies a static memory buffer that SQLite can use for +** the database page cache with the default page cache implemenation. +** This configuration should not be used if an application-define page +** cache implementation is loaded using the SQLITE_CONFIG_PCACHE option. +** There are three arguments to this option: A pointer to 8-byte aligned +** memory, the size of each page buffer (sz), and the number of pages (N). +** The sz argument should be the size of the largest database page +** (a power of two between 512 and 32768) plus a little extra for each +** page header. The page header size is 20 to 40 bytes depending on +** the host architecture. It is harmless, apart from the wasted memory, +** to make sz a little too large. The first +** argument should point to an allocation of at least sz*N bytes of memory. +** SQLite will use the memory provided by the first argument to satisfy its +** memory needs for the first N pages that it adds to cache. If additional +** page cache memory is needed beyond what is provided by this option, then +** SQLite goes to [sqlite3_malloc()] for the additional storage space. +** The implementation might use one or more of the N buffers to hold +** memory accounting information. The pointer in the first argument must +** be aligned to an 8-byte boundary or subsequent behavior of SQLite +** will be undefined.
+** +**
SQLITE_CONFIG_HEAP
+**
This option specifies a static memory buffer that SQLite will use +** for all of its dynamic memory allocation needs beyond those provided +** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. +** There are three arguments: An 8-byte aligned pointer to the memory, +** the number of bytes in the memory buffer, and the minimum allocation size. +** If the first pointer (the memory pointer) is NULL, then SQLite reverts +** to using its default memory allocator (the system malloc() implementation), +** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. If the +** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or +** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** allocator is engaged to handle all of SQLites memory allocation needs. +** The first pointer (the memory pointer) must be aligned to an 8-byte +** boundary or subsequent behavior of SQLite will be undefined.
+** +**
SQLITE_CONFIG_MUTEX
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mutex_methods] structure. The argument specifies +** alternative low-level mutex routines to be used in place +** the mutex routines built into SQLite.
+** +**
SQLITE_CONFIG_GETMUTEX
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mutex_methods] structure. The +** [sqlite3_mutex_methods] +** structure is filled with the currently defined mutex routines. +** This option can be used to overload the default mutex allocation +** routines with a wrapper used to track mutex usage for performance +** profiling or testing, for example.
+** +**
SQLITE_CONFIG_LOOKASIDE
+**
This option takes two arguments that determine the default +** memory allcation lookaside optimization. The first argument is the +** size of each lookaside buffer slot and the second is the number of +** slots allocated to each database connection.
+** +**
SQLITE_CONFIG_PCACHE
+**
This option takes a single argument which is a pointer to +** an [sqlite3_pcache_methods] object. This object specifies the interface +** to a custom page cache implementation. SQLite makes a copy of the +** object and uses it for page cache memory allocations.
+** +**
SQLITE_CONFIG_GETPCACHE
+**
This option takes a single argument which is a pointer to an +** [sqlite3_pcache_methods] object. SQLite copies of the current +** page cache implementation into that object.
+** +**
+*/ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* sqlite3_pcache_methods* */ +#define SQLITE_CONFIG_GETPCACHE 15 /* sqlite3_pcache_methods* */ + +/* +** CAPI3REF: Configuration Options {H10170} +** EXPERIMENTAL +** +** These constants are the available integer configuration options that +** can be passed as the second argument to the [sqlite3_db_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_db_config()] to make sure that +** the call worked. The [sqlite3_db_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +**
+**
SQLITE_DBCONFIG_LOOKASIDE
+**
This option takes three additional arguments that determine the +** [lookaside memory allocator] configuration for the [database connection]. +** The first argument (the third parameter to [sqlite3_db_config()] is a +** pointer to an 8-byte aligned memory buffer to use for lookaside memory. +** The first argument may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. The second argument is the +** size of each lookaside buffer slot and the third argument is the number of +** slots. The size of the buffer in the first argument must be greater than +** or equal to the product of the second and third arguments.
+** +**
+*/ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ + + +/* +** CAPI3REF: Enable Or Disable Extended Result Codes {H12200} +** +** The sqlite3_extended_result_codes() routine enables or disables the +** [extended result codes] feature of SQLite. The extended result +** codes are disabled by default for historical compatibility considerations. +** +** Requirements: +** [H12201] [H12202] +*/ +SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); + +/* +** CAPI3REF: Last Insert Rowid {H12220} +** +** Each entry in an SQLite table has a unique 64-bit signed +** integer key called the [ROWID | "rowid"]. The rowid is always available +** as an undeclared column named ROWID, OID, or _ROWID_ as long as those +** names are not also used by explicitly declared columns. If +** the table has a column of type [INTEGER PRIMARY KEY] then that column +** is another alias for the rowid. +** +** This routine returns the [rowid] of the most recent +** successful [INSERT] into the database from the [database connection] +** in the first argument. If no successful [INSERT]s +** have ever occurred on that database connection, zero is returned. +** +** If an [INSERT] occurs within a trigger, then the [rowid] of the inserted +** row is returned by this routine as long as the trigger is running. +** But once the trigger terminates, the value returned by this routine +** reverts to the last value inserted before the trigger fired. +** +** An [INSERT] that fails due to a constraint violation is not a +** successful [INSERT] and does not change the value returned by this +** routine. Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, +** and INSERT OR ABORT make no changes to the return value of this +** routine when their insertion fails. When INSERT OR REPLACE +** encounters a constraint violation, it does not fail. The +** INSERT continues to completion after deleting rows that caused +** the constraint problem so INSERT OR REPLACE will always change +** the return value of this interface. +** +** For the purposes of this routine, an [INSERT] is considered to +** be successful even if it is subsequently rolled back. +** +** Requirements: +** [H12221] [H12223] +** +** If a separate thread performs a new [INSERT] on the same +** database connection while the [sqlite3_last_insert_rowid()] +** function is running and thus changes the last insert [rowid], +** then the value returned by [sqlite3_last_insert_rowid()] is +** unpredictable and might not equal either the old or the new +** last insert [rowid]. +*/ +SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); + +/* +** CAPI3REF: Count The Number Of Rows Modified {H12240} +** +** This function returns the number of database rows that were changed +** or inserted or deleted by the most recently completed SQL statement +** on the [database connection] specified by the first parameter. +** Only changes that are directly specified by the [INSERT], [UPDATE], +** or [DELETE] statement are counted. Auxiliary changes caused by +** triggers are not counted. Use the [sqlite3_total_changes()] function +** to find the total number of changes including changes caused by triggers. +** +** Changes to a view that are simulated by an [INSTEAD OF trigger] +** are not counted. Only real table changes are counted. +** +** A "row change" is a change to a single row of a single table +** caused by an INSERT, DELETE, or UPDATE statement. Rows that +** are changed as side effects of [REPLACE] constraint resolution, +** rollback, ABORT processing, [DROP TABLE], or by any other +** mechanisms do not count as direct row changes. +** +** A "trigger context" is a scope of execution that begins and +** ends with the script of a [CREATE TRIGGER | trigger]. +** Most SQL statements are +** evaluated outside of any trigger. This is the "top level" +** trigger context. If a trigger fires from the top level, a +** new trigger context is entered for the duration of that one +** trigger. Subtriggers create subcontexts for their duration. +** +** Calling [sqlite3_exec()] or [sqlite3_step()] recursively does +** not create a new trigger context. +** +** This function returns the number of direct row changes in the +** most recent INSERT, UPDATE, or DELETE statement within the same +** trigger context. +** +** Thus, when called from the top level, this function returns the +** number of changes in the most recent INSERT, UPDATE, or DELETE +** that also occurred at the top level. Within the body of a trigger, +** the sqlite3_changes() interface can be called to find the number of +** changes in the most recently completed INSERT, UPDATE, or DELETE +** statement within the body of the same trigger. +** However, the number returned does not include changes +** caused by subtriggers since those have their own context. +** +** See also the [sqlite3_total_changes()] interface and the +** [count_changes pragma]. +** +** Requirements: +** [H12241] [H12243] +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_changes()] is running then the value returned +** is unpredictable and not meaningful. +*/ +SQLITE_API int sqlite3_changes(sqlite3*); + +/* +** CAPI3REF: Total Number Of Rows Modified {H12260} +** +** This function returns the number of row changes caused by [INSERT], +** [UPDATE] or [DELETE] statements since the [database connection] was opened. +** The count includes all changes from all +** [CREATE TRIGGER | trigger] contexts. However, +** the count does not include changes used to implement [REPLACE] constraints, +** do rollbacks or ABORT processing, or [DROP TABLE] processing. The +** count does not include rows of views that fire an [INSTEAD OF trigger], +** though if the INSTEAD OF trigger makes changes of its own, those changes +** are counted. +** The changes are counted as soon as the statement that makes them is +** completed (when the statement handle is passed to [sqlite3_reset()] or +** [sqlite3_finalize()]). +** +** See also the [sqlite3_changes()] interface and the +** [count_changes pragma]. +** +** Requirements: +** [H12261] [H12263] +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_total_changes()] is running then the value +** returned is unpredictable and not meaningful. +*/ +SQLITE_API int sqlite3_total_changes(sqlite3*); + +/* +** CAPI3REF: Interrupt A Long-Running Query {H12270} +** +** This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +** +** It is safe to call this routine from a thread different from the +** thread that is currently running the database operation. But it +** is not safe to call this routine with a [database connection] that +** is closed or might close before sqlite3_interrupt() returns. +** +** If an SQL operation is very nearly finished at the time when +** sqlite3_interrupt() is called, then it might not have an opportunity +** to be interrupted and might continue to completion. +** +** An SQL operation that is interrupted will return [SQLITE_INTERRUPT]. +** If the interrupted SQL operation is an INSERT, UPDATE, or DELETE +** that is inside an explicit transaction, then the entire transaction +** will be rolled back automatically. +** +** The sqlite3_interrupt(D) call is in effect until all currently running +** SQL statements on [database connection] D complete. Any new SQL statements +** that are started after the sqlite3_interrupt() call and before the +** running statements reaches zero are interrupted as if they had been +** running prior to the sqlite3_interrupt() call. New SQL statements +** that are started after the running statement count reaches zero are +** not effected by the sqlite3_interrupt(). +** A call to sqlite3_interrupt(D) that occurs when there are no running +** SQL statements is a no-op and has no effect on SQL statements +** that are started after the sqlite3_interrupt() call returns. +** +** Requirements: +** [H12271] [H12272] +** +** If the database connection closes while [sqlite3_interrupt()] +** is running then bad things will likely happen. +*/ +SQLITE_API void sqlite3_interrupt(sqlite3*); + +/* +** CAPI3REF: Determine If An SQL Statement Is Complete {H10510} +** +** These routines are useful during command-line input to determine if the +** currently entered text seems to form a complete SQL statement or +** if additional input is needed before sending the text into +** SQLite for parsing. These routines return 1 if the input string +** appears to be a complete SQL statement. A statement is judged to be +** complete if it ends with a semicolon token and is not a prefix of a +** well-formed CREATE TRIGGER statement. Semicolons that are embedded within +** string literals or quoted identifier names or comments are not +** independent tokens (they are part of the token in which they are +** embedded) and thus do not count as a statement terminator. Whitespace +** and comments that follow the final semicolon are ignored. +** +** These routines return 0 if the statement is incomplete. If a +** memory allocation fails, then SQLITE_NOMEM is returned. +** +** These routines do not parse the SQL statements thus +** will not detect syntactically incorrect SQL. +** +** If SQLite has not been initialized using [sqlite3_initialize()] prior +** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked +** automatically by sqlite3_complete16(). If that initialization fails, +** then the return value from sqlite3_complete16() will be non-zero +** regardless of whether or not the input SQL is complete. +** +** Requirements: [H10511] [H10512] +** +** The input to [sqlite3_complete()] must be a zero-terminated +** UTF-8 string. +** +** The input to [sqlite3_complete16()] must be a zero-terminated +** UTF-16 string in native byte order. +*/ +SQLITE_API int sqlite3_complete(const char *sql); +SQLITE_API int sqlite3_complete16(const void *sql); + +/* +** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors {H12310} +** +** This routine sets a callback function that might be invoked whenever +** an attempt is made to open a database table that another thread +** or process has locked. +** +** If the busy callback is NULL, then [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] +** is returned immediately upon encountering the lock. If the busy callback +** is not NULL, then the callback will be invoked with two arguments. +** +** The first argument to the handler is a copy of the void* pointer which +** is the third argument to sqlite3_busy_handler(). The second argument to +** the handler callback is the number of times that the busy handler has +** been invoked for this locking event. If the +** busy callback returns 0, then no additional attempts are made to +** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned. +** If the callback returns non-zero, then another attempt +** is made to open the database for reading and the cycle repeats. +** +** The presence of a busy handler does not guarantee that it will be invoked +** when there is lock contention. If SQLite determines that invoking the busy +** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] +** or [SQLITE_IOERR_BLOCKED] instead of invoking the busy handler. +** Consider a scenario where one process is holding a read lock that +** it is trying to promote to a reserved lock and +** a second process is holding a reserved lock that it is trying +** to promote to an exclusive lock. The first process cannot proceed +** because it is blocked by the second and the second process cannot +** proceed because it is blocked by the first. If both processes +** invoke the busy handlers, neither will make any progress. Therefore, +** SQLite returns [SQLITE_BUSY] for the first process, hoping that this +** will induce the first process to release its read lock and allow +** the second process to proceed. +** +** The default busy callback is NULL. +** +** The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED] +** when SQLite is in the middle of a large transaction where all the +** changes will not fit into the in-memory cache. SQLite will +** already hold a RESERVED lock on the database file, but it needs +** to promote this lock to EXCLUSIVE so that it can spill cache +** pages into the database file without harm to concurrent +** readers. If it is unable to promote the lock, then the in-memory +** cache will be left in an inconsistent state and so the error +** code is promoted from the relatively benign [SQLITE_BUSY] to +** the more severe [SQLITE_IOERR_BLOCKED]. This error code promotion +** forces an automatic rollback of the changes. See the +** +** CorruptionFollowingBusyError wiki page for a discussion of why +** this is important. +** +** There can only be a single busy handler defined for each +** [database connection]. Setting a new busy handler clears any +** previously set handler. Note that calling [sqlite3_busy_timeout()] +** will also set or clear the busy handler. +** +** The busy callback should not take any actions which modify the +** database connection that invoked the busy handler. Any such actions +** result in undefined behavior. +** +** Requirements: +** [H12311] [H12312] [H12314] [H12316] [H12318] +** +** A busy handler must not close the database connection +** or [prepared statement] that invoked the busy handler. +*/ +SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); + +/* +** CAPI3REF: Set A Busy Timeout {H12340} +** +** This routine sets a [sqlite3_busy_handler | busy handler] that sleeps +** for a specified amount of time when a table is locked. The handler +** will sleep multiple times until at least "ms" milliseconds of sleeping +** have accumulated. {H12343} After "ms" milliseconds of sleeping, +** the handler returns 0 which causes [sqlite3_step()] to return +** [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED]. +** +** Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +** +** There can only be a single busy handler for a particular +** [database connection] any any given moment. If another busy handler +** was defined (using [sqlite3_busy_handler()]) prior to calling +** this routine, that other busy handler is cleared. +** +** Requirements: +** [H12341] [H12343] [H12344] +*/ +SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** CAPI3REF: Convenience Routines For Running Queries {H12370} +** +** Definition: A result table is memory data structure created by the +** [sqlite3_get_table()] interface. A result table records the +** complete query results from one or more queries. +** +** The table conceptually has a number of rows and columns. But +** these numbers are not part of the result table itself. These +** numbers are obtained separately. Let N be the number of rows +** and M be the number of columns. +** +** A result table is an array of pointers to zero-terminated UTF-8 strings. +** There are (N+1)*M elements in the array. The first M pointers point +** to zero-terminated strings that contain the names of the columns. +** The remaining entries all point to query results. NULL values result +** in NULL pointers. All other values are in their UTF-8 zero-terminated +** string representation as returned by [sqlite3_column_text()]. +** +** A result table might consist of one or more memory allocations. +** It is not safe to pass a result table directly to [sqlite3_free()]. +** A result table should be deallocated using [sqlite3_free_table()]. +** +** As an example of the result table format, suppose a query result +** is as follows: +** +**
+**        Name        | Age
+**        -----------------------
+**        Alice       | 43
+**        Bob         | 28
+**        Cindy       | 21
+** 
+** +** There are two column (M==2) and three rows (N==3). Thus the +** result table has 8 entries. Suppose the result table is stored +** in an array names azResult. Then azResult holds this content: +** +**
+**        azResult[0] = "Name";
+**        azResult[1] = "Age";
+**        azResult[2] = "Alice";
+**        azResult[3] = "43";
+**        azResult[4] = "Bob";
+**        azResult[5] = "28";
+**        azResult[6] = "Cindy";
+**        azResult[7] = "21";
+** 
+** +** The sqlite3_get_table() function evaluates one or more +** semicolon-separated SQL statements in the zero-terminated UTF-8 +** string of its 2nd parameter. It returns a result table to the +** pointer given in its 3rd parameter. +** +** After the calling function has finished using the result, it should +** pass the pointer to the result table to sqlite3_free_table() in order to +** release the memory that was malloced. Because of the way the +** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling +** function must not try to call [sqlite3_free()] directly. Only +** [sqlite3_free_table()] is able to release the memory properly and safely. +** +** The sqlite3_get_table() interface is implemented as a wrapper around +** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access +** to any internal data structures of SQLite. It uses only the public +** interface defined here. As a consequence, errors that occur in the +** wrapper layer outside of the internal [sqlite3_exec()] call are not +** reflected in subsequent calls to [sqlite3_errcode()] or [sqlite3_errmsg()]. +** +** Requirements: +** [H12371] [H12373] [H12374] [H12376] [H12379] [H12382] +*/ +SQLITE_API int sqlite3_get_table( + sqlite3 *db, /* An open database */ + const char *zSql, /* SQL to be evaluated */ + char ***pazResult, /* Results of the query */ + int *pnRow, /* Number of result rows written here */ + int *pnColumn, /* Number of result columns written here */ + char **pzErrmsg /* Error msg written here */ +); +SQLITE_API void sqlite3_free_table(char **result); + +/* +** CAPI3REF: Formatted String Printing Functions {H17400} +** +** These routines are workalikes of the "printf()" family of functions +** from the standard C library. +** +** The sqlite3_mprintf() and sqlite3_vmprintf() routines write their +** results into memory obtained from [sqlite3_malloc()]. +** The strings returned by these two routines should be +** released by [sqlite3_free()]. Both routines return a +** NULL pointer if [sqlite3_malloc()] is unable to allocate enough +** memory to hold the resulting string. +** +** In sqlite3_snprintf() routine is similar to "snprintf()" from +** the standard C library. The result is written into the +** buffer supplied as the second parameter whose size is given by +** the first parameter. Note that the order of the +** first two parameters is reversed from snprintf(). This is an +** historical accident that cannot be fixed without breaking +** backwards compatibility. Note also that sqlite3_snprintf() +** returns a pointer to its buffer instead of the number of +** characters actually written into the buffer. We admit that +** the number of characters written would be a more useful return +** value but we cannot change the implementation of sqlite3_snprintf() +** now without breaking compatibility. +** +** As long as the buffer size is greater than zero, sqlite3_snprintf() +** guarantees that the buffer is always zero-terminated. The first +** parameter "n" is the total size of the buffer, including space for +** the zero terminator. So the longest string that can be completely +** written will be n-1 characters. +** +** These routines all implement some additional formatting +** options that are useful for constructing SQL statements. +** All of the usual printf() formatting options apply. In addition, there +** is are "%q", "%Q", and "%z" options. +** +** The %q option works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also doubles every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character it escapes that character and allows it to be inserted into +** the string. +** +** For example, assume the string variable zText contains text as follows: +** +**
+**  char *zText = "It's a happy day!";
+** 
+** +** One can use this text in an SQL statement as follows: +** +**
+**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
+**  sqlite3_exec(db, zSQL, 0, 0, 0);
+**  sqlite3_free(zSQL);
+** 
+** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +**
+**  INSERT INTO table1 VALUES('It''s a happy day!')
+** 
+** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +**
+**  INSERT INTO table1 VALUES('It's a happy day!');
+** 
+** +** This second example is an SQL syntax error. As a general rule you should +** always use %q instead of %s when inserting text into a string literal. +** +** The %Q option works like %q except it also adds single quotes around +** the outside of the total string. Additionally, if the parameter in the +** argument list is a NULL pointer, %Q substitutes the text "NULL" (without +** single quotes) in place of the %Q option. So, for example, one could say: +** +**
+**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
+**  sqlite3_exec(db, zSQL, 0, 0, 0);
+**  sqlite3_free(zSQL);
+** 
+** +** The code above will render a correct SQL statement in the zSQL +** variable even if the zText variable is a NULL pointer. +** +** The "%z" formatting option works exactly like "%s" with the +** addition that after the string has been read and copied into +** the result, [sqlite3_free()] is called on the input string. {END} +** +** Requirements: +** [H17403] [H17406] [H17407] +*/ +SQLITE_API char *sqlite3_mprintf(const char*,...); +SQLITE_API char *sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); + +/* +** CAPI3REF: Memory Allocation Subsystem {H17300} +** +** The SQLite core uses these three routines for all of its own +** internal memory allocation needs. "Core" in the previous sentence +** does not include operating-system specific VFS implementation. The +** Windows VFS uses native malloc() and free() for some operations. +** +** The sqlite3_malloc() routine returns a pointer to a block +** of memory at least N bytes in length, where N is the parameter. +** If sqlite3_malloc() is unable to obtain sufficient free +** memory, it returns a NULL pointer. If the parameter N to +** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns +** a NULL pointer. +** +** Calling sqlite3_free() with a pointer previously returned +** by sqlite3_malloc() or sqlite3_realloc() releases that memory so +** that it might be reused. The sqlite3_free() routine is +** a no-op if is called with a NULL pointer. Passing a NULL pointer +** to sqlite3_free() is harmless. After being freed, memory +** should neither be read nor written. Even reading previously freed +** memory might result in a segmentation fault or other severe error. +** Memory corruption, a segmentation fault, or other severe error +** might result if sqlite3_free() is called with a non-NULL pointer that +** was not obtained from sqlite3_malloc() or sqlite3_realloc(). +** +** The sqlite3_realloc() interface attempts to resize a +** prior memory allocation to be at least N bytes, where N is the +** second parameter. The memory allocation to be resized is the first +** parameter. If the first parameter to sqlite3_realloc() +** is a NULL pointer then its behavior is identical to calling +** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc(). +** If the second parameter to sqlite3_realloc() is zero or +** negative then the behavior is exactly the same as calling +** sqlite3_free(P) where P is the first parameter to sqlite3_realloc(). +** sqlite3_realloc() returns a pointer to a memory allocation +** of at least N bytes in size or NULL if sufficient memory is unavailable. +** If M is the size of the prior allocation, then min(N,M) bytes +** of the prior allocation are copied into the beginning of buffer returned +** by sqlite3_realloc() and the prior allocation is freed. +** If sqlite3_realloc() returns NULL, then the prior allocation +** is not freed. +** +** The memory returned by sqlite3_malloc() and sqlite3_realloc() +** is always aligned to at least an 8 byte boundary. {END} +** +** The default implementation of the memory allocation subsystem uses +** the malloc(), realloc() and free() provided by the standard C library. +** {H17382} However, if SQLite is compiled with the +** SQLITE_MEMORY_SIZE=NNN C preprocessor macro (where NNN +** is an integer), then SQLite create a static array of at least +** NNN bytes in size and uses that array for all of its dynamic +** memory allocation needs. {END} Additional memory allocator options +** may be added in future releases. +** +** In SQLite version 3.5.0 and 3.5.1, it was possible to define +** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in +** implementation of these routines to be omitted. That capability +** is no longer provided. Only built-in memory allocators can be used. +** +** The Windows OS interface layer calls +** the system malloc() and free() directly when converting +** filenames between the UTF-8 encoding used by SQLite +** and whatever filename encoding is used by the particular Windows +** installation. Memory allocation errors are detected, but +** they are reported back as [SQLITE_CANTOPEN] or +** [SQLITE_IOERR] rather than [SQLITE_NOMEM]. +** +** Requirements: +** [H17303] [H17304] [H17305] [H17306] [H17310] [H17312] [H17315] [H17318] +** [H17321] [H17322] [H17323] +** +** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] +** must be either NULL or else pointers obtained from a prior +** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have +** not yet been released. +** +** The application must not read or write any part of +** a block of memory after it has been released using +** [sqlite3_free()] or [sqlite3_realloc()]. +*/ +SQLITE_API void *sqlite3_malloc(int); +SQLITE_API void *sqlite3_realloc(void*, int); +SQLITE_API void sqlite3_free(void*); + +/* +** CAPI3REF: Memory Allocator Statistics {H17370} +** +** SQLite provides these two interfaces for reporting on the status +** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()] +** routines, which form the built-in memory allocation subsystem. +** +** Requirements: +** [H17371] [H17373] [H17374] [H17375] +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); + +/* +** CAPI3REF: Pseudo-Random Number Generator {H17390} +** +** SQLite contains a high-quality pseudo-random number generator (PRNG) used to +** select random [ROWID | ROWIDs] when inserting new records into a table that +** already uses the largest possible [ROWID]. The PRNG is also used for +** the build-in random() and randomblob() SQL functions. This interface allows +** applications to access the same PRNG for other purposes. +** +** A call to this routine stores N bytes of randomness into buffer P. +** +** The first time this routine is invoked (either internally or by +** the application) the PRNG is seeded using randomness obtained +** from the xRandomness method of the default [sqlite3_vfs] object. +** On all subsequent invocations, the pseudo-randomness is generated +** internally and without recourse to the [sqlite3_vfs] xRandomness +** method. +** +** Requirements: +** [H17392] +*/ +SQLITE_API void sqlite3_randomness(int N, void *P); + +/* +** CAPI3REF: Compile-Time Authorization Callbacks {H12500} +** +** This routine registers a authorizer callback with a particular +** [database connection], supplied in the first argument. +** The authorizer callback is invoked as SQL statements are being compiled +** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], +** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. At various +** points during the compilation process, as logic is being created +** to perform various actions, the authorizer callback is invoked to +** see if those actions are allowed. The authorizer callback should +** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the +** specific action but allow the SQL statement to continue to be +** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be +** rejected with an error. If the authorizer callback returns +** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] +** then the [sqlite3_prepare_v2()] or equivalent call that triggered +** the authorizer will fail with an error message. +** +** When the callback returns [SQLITE_OK], that means the operation +** requested is ok. When the callback returns [SQLITE_DENY], the +** [sqlite3_prepare_v2()] or equivalent call that triggered the +** authorizer will fail with an error message explaining that +** access is denied. +** +** The first parameter to the authorizer callback is a copy of the third +** parameter to the sqlite3_set_authorizer() interface. The second parameter +** to the callback is an integer [SQLITE_COPY | action code] that specifies +** the particular action to be authorized. The third through sixth parameters +** to the callback are zero-terminated strings that contain additional +** details about the action to be authorized. +** +** If the action code is [SQLITE_READ] +** and the callback returns [SQLITE_IGNORE] then the +** [prepared statement] statement is constructed to substitute +** a NULL value in place of the table column that would have +** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] +** return can be used to deny an untrusted user access to individual +** columns of a table. +** If the action code is [SQLITE_DELETE] and the callback returns +** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the +** [truncate optimization] is disabled and all rows are deleted individually. +** +** An authorizer is used when [sqlite3_prepare | preparing] +** SQL statements from an untrusted source, to ensure that the SQL statements +** do not try to access data they are not allowed to see, or that they do not +** try to execute malicious statements that damage the database. For +** example, an application may allow a user to enter arbitrary +** SQL queries for evaluation by a database. But the application does +** not want the user to be able to make arbitrary changes to the +** database. An authorizer could then be put in place while the +** user-entered SQL is being [sqlite3_prepare | prepared] that +** disallows everything except [SELECT] statements. +** +** Applications that need to process SQL from untrusted sources +** might also consider lowering resource limits using [sqlite3_limit()] +** and limiting database size using the [max_page_count] [PRAGMA] +** in addition to using an authorizer. +** +** Only a single authorizer can be in place on a database connection +** at a time. Each call to sqlite3_set_authorizer overrides the +** previous call. Disable the authorizer by installing a NULL callback. +** The authorizer is disabled by default. +** +** The authorizer callback must not do anything that will modify +** the database connection that invoked the authorizer callback. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** When [sqlite3_prepare_v2()] is used to prepare a statement, the +** statement might be reprepared during [sqlite3_step()] due to a +** schema change. Hence, the application should ensure that the +** correct authorizer callback remains in place during the [sqlite3_step()]. +** +** Note that the authorizer callback is invoked only during +** [sqlite3_prepare()] or its variants. Authorization is not +** performed during statement evaluation in [sqlite3_step()], unless +** as stated in the previous paragraph, sqlite3_step() invokes +** sqlite3_prepare_v2() to reprepare a statement after a schema change. +** +** Requirements: +** [H12501] [H12502] [H12503] [H12504] [H12505] [H12506] [H12507] [H12510] +** [H12511] [H12512] [H12520] [H12521] [H12522] +*/ +SQLITE_API int sqlite3_set_authorizer( + sqlite3*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** CAPI3REF: Authorizer Return Codes {H12590} +** +** The [sqlite3_set_authorizer | authorizer callback function] must +** return either [SQLITE_OK] or one of these two constants in order +** to signal SQLite whether or not the action is permitted. See the +** [sqlite3_set_authorizer | authorizer documentation] for additional +** information. +*/ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** CAPI3REF: Authorizer Action Codes {H12550} +** +** The [sqlite3_set_authorizer()] interface registers a callback function +** that is invoked to authorize certain SQL statement actions. The +** second parameter to the callback is an integer code that specifies +** what action is being authorized. These are the integer action codes that +** the authorizer callback may be passed. +** +** These action code values signify what kind of operation is to be +** authorized. The 3rd and 4th parameters to the authorization +** callback function will be parameters or NULL depending on which of these +** codes is used as the second parameter. The 5th parameter to the +** authorizer callback is the name of the database ("main", "temp", +** etc.) if applicable. The 6th parameter to the authorizer callback +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** top-level SQL code. +** +** Requirements: +** [H12551] [H12552] [H12553] [H12554] +*/ +/******************************************* 3rd ************ 4th ***********/ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* Operation NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ +#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */ +#define SQLITE_REINDEX 27 /* Index Name NULL */ +#define SQLITE_ANALYZE 28 /* Table Name NULL */ +#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ +#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ +#define SQLITE_FUNCTION 31 /* NULL Function Name */ +#define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ +#define SQLITE_COPY 0 /* No longer used */ + +/* +** CAPI3REF: Tracing And Profiling Functions {H12280} +** EXPERIMENTAL +** +** These routines register callback functions that can be used for +** tracing and profiling the execution of SQL statements. +** +** The callback function registered by sqlite3_trace() is invoked at +** various times when an SQL statement is being run by [sqlite3_step()]. +** The callback returns a UTF-8 rendering of the SQL statement text +** as the statement first begins executing. Additional callbacks occur +** as each triggered subprogram is entered. The callbacks for triggers +** contain a UTF-8 SQL comment that identifies the trigger. +** +** The callback function registered by sqlite3_profile() is invoked +** as each SQL statement finishes. The profile callback contains +** the original statement text and an estimate of wall-clock time +** of how long that statement took to run. +** +** Requirements: +** [H12281] [H12282] [H12283] [H12284] [H12285] [H12287] [H12288] [H12289] +** [H12290] +*/ +SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, + void(*xProfile)(void*,const char*,sqlite3_uint64), void*); + +/* +** CAPI3REF: Query Progress Callbacks {H12910} +** +** This routine configures a callback function - the +** progress callback - that is invoked periodically during long +** running calls to [sqlite3_exec()], [sqlite3_step()] and +** [sqlite3_get_table()]. An example use for this +** interface is to keep a GUI updated during a large query. +** +** If the progress callback returns non-zero, the operation is +** interrupted. This feature can be used to implement a +** "Cancel" button on a GUI progress dialog box. +** +** The progress handler must not do anything that will modify +** the database connection that invoked the progress handler. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** Requirements: +** [H12911] [H12912] [H12913] [H12914] [H12915] [H12916] [H12917] [H12918] +** +*/ +SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); + +/* +** CAPI3REF: Opening A New Database Connection {H12700} +** +** These routines open an SQLite database file whose name is given by the +** filename argument. The filename argument is interpreted as UTF-8 for +** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte +** order for sqlite3_open16(). A [database connection] handle is usually +** returned in *ppDb, even if an error occurs. The only exception is that +** if SQLite is unable to allocate memory to hold the [sqlite3] object, +** a NULL will be written into *ppDb instead of a pointer to the [sqlite3] +** object. If the database is opened (and/or created) successfully, then +** [SQLITE_OK] is returned. Otherwise an [error code] is returned. The +** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain +** an English language description of the error. +** +** The default encoding for the database will be UTF-8 if +** sqlite3_open() or sqlite3_open_v2() is called and +** UTF-16 in the native byte order if sqlite3_open16() is used. +** +** Whether or not an error occurs when it is opened, resources +** associated with the [database connection] handle should be released by +** passing it to [sqlite3_close()] when it is no longer required. +** +** The sqlite3_open_v2() interface works like sqlite3_open() +** except that it accepts two additional parameters for additional control +** over the new database connection. The flags parameter can take one of +** the following three values, optionally combined with the +** [SQLITE_OPEN_NOMUTEX] or [SQLITE_OPEN_FULLMUTEX] flags: +** +**
+**
[SQLITE_OPEN_READONLY]
+**
The database is opened in read-only mode. If the database does not +** already exist, an error is returned.
+** +**
[SQLITE_OPEN_READWRITE]
+**
The database is opened for reading and writing if possible, or reading +** only if the file is write protected by the operating system. In either +** case the database must already exist, otherwise an error is returned.
+** +**
[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
+**
The database is opened for reading and writing, and is creates it if +** it does not already exist. This is the behavior that is always used for +** sqlite3_open() and sqlite3_open16().
+**
+** +** If the 3rd parameter to sqlite3_open_v2() is not one of the +** combinations shown above or one of the combinations shown above combined +** with the [SQLITE_OPEN_NOMUTEX] or [SQLITE_OPEN_FULLMUTEX] flags, +** then the behavior is undefined. +** +** If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection +** opens in the multi-thread [threading mode] as long as the single-thread +** mode has not been set at compile-time or start-time. If the +** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens +** in the serialized [threading mode] unless single-thread was +** previously selected at compile-time or start-time. +** +** If the filename is ":memory:", then a private, temporary in-memory database +** is created for the connection. This in-memory database will vanish when +** the database connection is closed. Future versions of SQLite might +** make use of additional special filenames that begin with the ":" character. +** It is recommended that when a database filename actually does begin with +** a ":" character you should prefix the filename with a pathname such as +** "./" to avoid ambiguity. +** +** If the filename is an empty string, then a private, temporary +** on-disk database will be created. This private database will be +** automatically deleted as soon as the database connection is closed. +** +** The fourth parameter to sqlite3_open_v2() is the name of the +** [sqlite3_vfs] object that defines the operating system interface that +** the new database connection should use. If the fourth parameter is +** a NULL pointer then the default [sqlite3_vfs] object is used. +** +** Note to Windows users: The encoding used for the filename argument +** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever +** codepage is currently defined. Filenames containing international +** characters must be converted to UTF-8 prior to passing them into +** sqlite3_open() or sqlite3_open_v2(). +** +** Requirements: +** [H12701] [H12702] [H12703] [H12704] [H12706] [H12707] [H12709] [H12711] +** [H12712] [H12713] [H12714] [H12717] [H12719] [H12721] [H12723] +*/ +SQLITE_API int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +); + +/* +** CAPI3REF: Error Codes And Messages {H12800} +** +** The sqlite3_errcode() interface returns the numeric [result code] or +** [extended result code] for the most recent failed sqlite3_* API call +** associated with a [database connection]. If a prior API call failed +** but the most recent API call succeeded, the return value from +** sqlite3_errcode() is undefined. The sqlite3_extended_errcode() +** interface is the same except that it always returns the +** [extended result code] even when extended result codes are +** disabled. +** +** The sqlite3_errmsg() and sqlite3_errmsg16() return English-language +** text that describes the error, as either UTF-8 or UTF-16 respectively. +** Memory to hold the error message string is managed internally. +** The application does not need to worry about freeing the result. +** However, the error string might be overwritten or deallocated by +** subsequent calls to other SQLite interface functions. +** +** When the serialized [threading mode] is in use, it might be the +** case that a second error occurs on a separate thread in between +** the time of the first error and the call to these interfaces. +** When that happens, the second error will be reported since these +** interfaces always report the most recent result. To avoid +** this, each thread can obtain exclusive use of the [database connection] D +** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning +** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after +** all calls to the interfaces listed here are completed. +** +** If an interface fails with SQLITE_MISUSE, that means the interface +** was invoked incorrectly by the application. In that case, the +** error code and message may or may not be set. +** +** Requirements: +** [H12801] [H12802] [H12803] [H12807] [H12808] [H12809] +*/ +SQLITE_API int sqlite3_errcode(sqlite3 *db); +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *sqlite3_errmsg(sqlite3*); +SQLITE_API const void *sqlite3_errmsg16(sqlite3*); + +/* +** CAPI3REF: SQL Statement Object {H13000} +** KEYWORDS: {prepared statement} {prepared statements} +** +** An instance of this object represents a single SQL statement. +** This object is variously known as a "prepared statement" or a +** "compiled SQL statement" or simply as a "statement". +** +** The life of a statement object goes something like this: +** +**
    +**
  1. Create the object using [sqlite3_prepare_v2()] or a related +** function. +**
  2. Bind values to [host parameters] using the sqlite3_bind_*() +** interfaces. +**
  3. Run the SQL by calling [sqlite3_step()] one or more times. +**
  4. Reset the statement using [sqlite3_reset()] then go back +** to step 2. Do this zero or more times. +**
  5. Destroy the object using [sqlite3_finalize()]. +**
+** +** Refer to documentation on individual methods above for additional +** information. +*/ +typedef struct sqlite3_stmt sqlite3_stmt; + +/* +** CAPI3REF: Run-time Limits {H12760} +** +** This interface allows the size of various constructs to be limited +** on a connection by connection basis. The first parameter is the +** [database connection] whose limit is to be set or queried. The +** second parameter is one of the [limit categories] that define a +** class of constructs to be size limited. The third parameter is the +** new limit for that construct. The function returns the old limit. +** +** If the new limit is a negative number, the limit is unchanged. +** For the limit category of SQLITE_LIMIT_XYZ there is a +** [limits | hard upper bound] +** set by a compile-time C preprocessor macro named +** [limits | SQLITE_MAX_XYZ]. +** (The "_LIMIT_" in the name is changed to "_MAX_".) +** Attempts to increase a limit above its hard upper bound are +** silently truncated to the hard upper limit. +** +** Run time limits are intended for use in applications that manage +** both their own internal database and also databases that are controlled +** by untrusted external sources. An example application might be a +** web browser that has its own databases for storing history and +** separate databases controlled by JavaScript applications downloaded +** off the Internet. The internal databases can be given the +** large, default limits. Databases managed by external sources can +** be given much smaller limits designed to prevent a denial of service +** attack. Developers might also want to use the [sqlite3_set_authorizer()] +** interface to further control untrusted SQL. The size of the database +** created by an untrusted script can be contained using the +** [max_page_count] [PRAGMA]. +** +** New run-time limit categories may be added in future releases. +** +** Requirements: +** [H12762] [H12766] [H12769] +*/ +SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + +/* +** CAPI3REF: Run-Time Limit Categories {H12790} +** KEYWORDS: {limit category} {limit categories} +** +** These constants define various performance limits +** that can be lowered at run-time using [sqlite3_limit()]. +** The synopsis of the meanings of the various limits is shown below. +** Additional information is available at [limits | Limits in SQLite]. +** +**
+**
SQLITE_LIMIT_LENGTH
+**
The maximum size of any string or BLOB or table row.
+** +**
SQLITE_LIMIT_SQL_LENGTH
+**
The maximum length of an SQL statement.
+** +**
SQLITE_LIMIT_COLUMN
+**
The maximum number of columns in a table definition or in the +** result set of a [SELECT] or the maximum number of columns in an index +** or in an ORDER BY or GROUP BY clause.
+** +**
SQLITE_LIMIT_EXPR_DEPTH
+**
The maximum depth of the parse tree on any expression.
+** +**
SQLITE_LIMIT_COMPOUND_SELECT
+**
The maximum number of terms in a compound SELECT statement.
+** +**
SQLITE_LIMIT_VDBE_OP
+**
The maximum number of instructions in a virtual machine program +** used to implement an SQL statement.
+** +**
SQLITE_LIMIT_FUNCTION_ARG
+**
The maximum number of arguments on a function.
+** +**
SQLITE_LIMIT_ATTACHED
+**
The maximum number of [ATTACH | attached databases].
+** +**
SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+**
The maximum length of the pattern argument to the [LIKE] or +** [GLOB] operators.
+** +**
SQLITE_LIMIT_VARIABLE_NUMBER
+**
The maximum number of variables in an SQL statement that can +** be bound.
+**
+*/ +#define SQLITE_LIMIT_LENGTH 0 +#define SQLITE_LIMIT_SQL_LENGTH 1 +#define SQLITE_LIMIT_COLUMN 2 +#define SQLITE_LIMIT_EXPR_DEPTH 3 +#define SQLITE_LIMIT_COMPOUND_SELECT 4 +#define SQLITE_LIMIT_VDBE_OP 5 +#define SQLITE_LIMIT_FUNCTION_ARG 6 +#define SQLITE_LIMIT_ATTACHED 7 +#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +#define SQLITE_LIMIT_VARIABLE_NUMBER 9 + +/* +** CAPI3REF: Compiling An SQL Statement {H13010} +** KEYWORDS: {SQL statement compiler} +** +** To execute an SQL query, it must first be compiled into a byte-code +** program using one of these routines. +** +** The first argument, "db", is a [database connection] obtained from a +** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or +** [sqlite3_open16()]. The database connection must not have been closed. +** +** The second argument, "zSql", is the statement to be compiled, encoded +** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() +** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() +** use UTF-16. +** +** If the nByte argument is less than zero, then zSql is read up to the +** first zero terminator. If nByte is non-negative, then it is the maximum +** number of bytes read from zSql. When nByte is non-negative, the +** zSql string ends at either the first '\000' or '\u0000' character or +** the nByte-th byte, whichever comes first. If the caller knows +** that the supplied string is nul-terminated, then there is a small +** performance advantage to be gained by passing an nByte parameter that +** is equal to the number of bytes in the input string including +** the nul-terminator bytes. +** +** If pzTail is not NULL then *pzTail is made to point to the first byte +** past the end of the first SQL statement in zSql. These routines only +** compile the first statement in zSql, so *pzTail is left pointing to +** what remains uncompiled. +** +** *ppStmt is left pointing to a compiled [prepared statement] that can be +** executed using [sqlite3_step()]. If there is an error, *ppStmt is set +** to NULL. If the input text contains no SQL (if the input is an empty +** string or a comment) then *ppStmt is set to NULL. +** The calling procedure is responsible for deleting the compiled +** SQL statement using [sqlite3_finalize()] after it has finished with it. +** ppStmt may not be NULL. +** +** On success, [SQLITE_OK] is returned, otherwise an [error code] is returned. +** +** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are +** recommended for all new programs. The two older interfaces are retained +** for backwards compatibility, but their use is discouraged. +** In the "v2" interfaces, the prepared statement +** that is returned (the [sqlite3_stmt] object) contains a copy of the +** original SQL text. This causes the [sqlite3_step()] interface to +** behave a differently in two ways: +** +**
    +**
  1. +** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it +** always used to do, [sqlite3_step()] will automatically recompile the SQL +** statement and try to run it again. If the schema has changed in +** a way that makes the statement no longer valid, [sqlite3_step()] will still +** return [SQLITE_SCHEMA]. But unlike the legacy behavior, [SQLITE_SCHEMA] is +** now a fatal error. Calling [sqlite3_prepare_v2()] again will not make the +** error go away. Note: use [sqlite3_errmsg()] to find the text +** of the parsing error that results in an [SQLITE_SCHEMA] return. +**
  2. +** +**
  3. +** When an error occurs, [sqlite3_step()] will return one of the detailed +** [error codes] or [extended error codes]. The legacy behavior was that +** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code +** and you would have to make a second call to [sqlite3_reset()] in order +** to find the underlying cause of the problem. With the "v2" prepare +** interfaces, the underlying reason for the error is returned immediately. +**
  4. +**
+** +** Requirements: +** [H13011] [H13012] [H13013] [H13014] [H13015] [H13016] [H13019] [H13021] +** +*/ +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* +** CAPI3REF: Retrieving Statement SQL {H13100} +** +** This interface can be used to retrieve a saved copy of the original +** SQL text used to create a [prepared statement] if that statement was +** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. +** +** Requirements: +** [H13101] [H13102] [H13103] +*/ +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Dynamically Typed Value Object {H15000} +** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} +** +** SQLite uses the sqlite3_value object to represent all values +** that can be stored in a database table. SQLite uses dynamic typing +** for the values it stores. Values stored in sqlite3_value objects +** can be integers, floating point values, strings, BLOBs, or NULL. +** +** An sqlite3_value object may be either "protected" or "unprotected". +** Some interfaces require a protected sqlite3_value. Other interfaces +** will accept either a protected or an unprotected sqlite3_value. +** Every interface that accepts sqlite3_value arguments specifies +** whether or not it requires a protected sqlite3_value. +** +** The terms "protected" and "unprotected" refer to whether or not +** a mutex is held. A internal mutex is held for a protected +** sqlite3_value object but no mutex is held for an unprotected +** sqlite3_value object. If SQLite is compiled to be single-threaded +** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) +** or if SQLite is run in one of reduced mutex modes +** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] +** then there is no distinction between protected and unprotected +** sqlite3_value objects and they can be used interchangeably. However, +** for maximum code portability it is recommended that applications +** still make the distinction between between protected and unprotected +** sqlite3_value objects even when not strictly required. +** +** The sqlite3_value objects that are passed as parameters into the +** implementation of [application-defined SQL functions] are protected. +** The sqlite3_value object returned by +** [sqlite3_column_value()] is unprotected. +** Unprotected sqlite3_value objects may only be used with +** [sqlite3_result_value()] and [sqlite3_bind_value()]. +** The [sqlite3_value_blob | sqlite3_value_type()] family of +** interfaces require protected sqlite3_value objects. +*/ +typedef struct Mem sqlite3_value; + +/* +** CAPI3REF: SQL Function Context Object {H16001} +** +** The context in which an SQL function executes is stored in an +** sqlite3_context object. A pointer to an sqlite3_context object +** is always first parameter to [application-defined SQL functions]. +** The application-defined SQL function implementation will pass this +** pointer through into calls to [sqlite3_result_int | sqlite3_result()], +** [sqlite3_aggregate_context()], [sqlite3_user_data()], +** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], +** and/or [sqlite3_set_auxdata()]. +*/ +typedef struct sqlite3_context sqlite3_context; + +/* +** CAPI3REF: Binding Values To Prepared Statements {H13500} +** KEYWORDS: {host parameter} {host parameters} {host parameter name} +** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} +** +** In the SQL strings input to [sqlite3_prepare_v2()] and its variants, +** literals may be replaced by a [parameter] in one of these forms: +** +**
    +**
  • ? +**
  • ?NNN +**
  • :VVV +**
  • @VVV +**
  • $VVV +**
+** +** In the parameter forms shown above NNN is an integer literal, +** and VVV is an alpha-numeric parameter name. The values of these +** parameters (also called "host parameter names" or "SQL parameters") +** can be set using the sqlite3_bind_*() routines defined here. +** +** The first argument to the sqlite3_bind_*() routines is always +** a pointer to the [sqlite3_stmt] object returned from +** [sqlite3_prepare_v2()] or its variants. +** +** The second argument is the index of the SQL parameter to be set. +** The leftmost SQL parameter has an index of 1. When the same named +** SQL parameter is used more than once, second and subsequent +** occurrences have the same index as the first occurrence. +** The index for named parameters can be looked up using the +** [sqlite3_bind_parameter_index()] API if desired. The index +** for "?NNN" parameters is the value of NNN. +** The NNN value must be between 1 and the [sqlite3_limit()] +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999). +** +** The third argument is the value to bind to the parameter. +** +** In those routines that have a fourth argument, its value is the +** number of bytes in the parameter. To be clear: the value is the +** number of bytes in the value, not the number of characters. +** If the fourth parameter is negative, the length of the string is +** the number of bytes up to the first zero terminator. +** +** The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and +** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or +** string after SQLite has finished with it. If the fifth argument is +** the special value [SQLITE_STATIC], then SQLite assumes that the +** information is in static, unmanaged space and does not need to be freed. +** If the fifth argument has the value [SQLITE_TRANSIENT], then +** SQLite makes its own private copy of the data immediately, before +** the sqlite3_bind_*() routine returns. +** +** The sqlite3_bind_zeroblob() routine binds a BLOB of length N that +** is filled with zeroes. A zeroblob uses a fixed amount of memory +** (just an integer to hold its size) while it is being processed. +** Zeroblobs are intended to serve as placeholders for BLOBs whose +** content is later written using +** [sqlite3_blob_open | incremental BLOB I/O] routines. +** A negative value for the zeroblob results in a zero-length BLOB. +** +** The sqlite3_bind_*() routines must be called after +** [sqlite3_prepare_v2()] (and its variants) or [sqlite3_reset()] and +** before [sqlite3_step()]. +** Bindings are not cleared by the [sqlite3_reset()] routine. +** Unbound parameters are interpreted as NULL. +** +** These routines return [SQLITE_OK] on success or an error code if +** anything goes wrong. [SQLITE_RANGE] is returned if the parameter +** index is out of range. [SQLITE_NOMEM] is returned if malloc() fails. +** [SQLITE_MISUSE] might be returned if these routines are called on a +** virtual machine that is the wrong state or which has already been finalized. +** Detection of misuse is unreliable. Applications should not depend +** on SQLITE_MISUSE returns. SQLITE_MISUSE is intended to indicate a +** a logic error in the application. Future versions of SQLite might +** panic rather than return SQLITE_MISUSE. +** +** See also: [sqlite3_bind_parameter_count()], +** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13506] [H13509] [H13512] [H13515] [H13518] [H13521] [H13524] [H13527] +** [H13530] [H13533] [H13536] [H13539] [H13542] [H13545] [H13548] [H13551] +** +*/ +SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); + +/* +** CAPI3REF: Number Of SQL Parameters {H13600} +** +** This routine can be used to find the number of [SQL parameters] +** in a [prepared statement]. SQL parameters are tokens of the +** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as +** placeholders for values that are [sqlite3_bind_blob | bound] +** to the parameters at a later time. +** +** This routine actually returns the index of the largest (rightmost) +** parameter. For all forms except ?NNN, this will correspond to the +** number of unique parameters. If parameters of the ?NNN are used, +** there may be gaps in the list. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_name()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13601] +*/ +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/* +** CAPI3REF: Name Of A Host Parameter {H13620} +** +** This routine returns a pointer to the name of the n-th +** [SQL parameter] in a [prepared statement]. +** SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA" +** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA" +** respectively. +** In other words, the initial ":" or "$" or "@" or "?" +** is included as part of the name. +** Parameters of the form "?" without a following integer have no name +** and are also referred to as "anonymous parameters". +** +** The first host parameter has an index of 1, not 0. +** +** If the value n is out of range or if the n-th parameter is +** nameless, then NULL is returned. The returned string is +** always in UTF-8 encoding even if the named parameter was +** originally specified as UTF-16 in [sqlite3_prepare16()] or +** [sqlite3_prepare16_v2()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13621] +*/ +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/* +** CAPI3REF: Index Of A Parameter With A Given Name {H13640} +** +** Return the index of an SQL parameter given its name. The +** index value returned is suitable for use as the second +** parameter to [sqlite3_bind_blob|sqlite3_bind()]. A zero +** is returned if no matching parameter is found. The parameter +** name must be given in UTF-8 even if the original statement +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13641] +*/ +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/* +** CAPI3REF: Reset All Bindings On A Prepared Statement {H13660} +** +** Contrary to the intuition of many, [sqlite3_reset()] does not reset +** the [sqlite3_bind_blob | bindings] on a [prepared statement]. +** Use this routine to reset all host parameters to NULL. +** +** Requirements: +** [H13661] +*/ +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); + +/* +** CAPI3REF: Number Of Columns In A Result Set {H13710} +** +** Return the number of columns in the result set returned by the +** [prepared statement]. This routine returns 0 if pStmt is an SQL +** statement that does not return data (for example an [UPDATE]). +** +** Requirements: +** [H13711] +*/ +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Column Names In A Result Set {H13720} +** +** These routines return the name assigned to a particular column +** in the result set of a [SELECT] statement. The sqlite3_column_name() +** interface returns a pointer to a zero-terminated UTF-8 string +** and sqlite3_column_name16() returns a pointer to a zero-terminated +** UTF-16 string. The first parameter is the [prepared statement] +** that implements the [SELECT] statement. The second parameter is the +** column number. The leftmost column is number 0. +** +** The returned string pointer is valid until either the [prepared statement] +** is destroyed by [sqlite3_finalize()] or until the next call to +** sqlite3_column_name() or sqlite3_column_name16() on the same column. +** +** If sqlite3_malloc() fails during the processing of either routine +** (for example during a conversion from UTF-8 to UTF-16) then a +** NULL pointer is returned. +** +** The name of a result column is the value of the "AS" clause for +** that column, if there is an AS clause. If there is no AS clause +** then the name of the column is unspecified and may change from +** one release of SQLite to the next. +** +** Requirements: +** [H13721] [H13723] [H13724] [H13725] [H13726] [H13727] +*/ +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); + +/* +** CAPI3REF: Source Of Data In A Query Result {H13740} +** +** These routines provide a means to determine what column of what +** table in which database a result of a [SELECT] statement comes from. +** The name of the database or table or column can be returned as +** either a UTF-8 or UTF-16 string. The _database_ routines return +** the database name, the _table_ routines return the table name, and +** the origin_ routines return the column name. +** The returned string is valid until the [prepared statement] is destroyed +** using [sqlite3_finalize()] or until the same information is requested +** again in a different encoding. +** +** The names returned are the original un-aliased names of the +** database, table, and column. +** +** The first argument to the following calls is a [prepared statement]. +** These functions return information about the Nth column returned by +** the statement, where N is the second function argument. +** +** If the Nth column returned by the statement is an expression or +** subquery and is not a column value, then all of these functions return +** NULL. These routine might also return NULL if a memory allocation error +** occurs. Otherwise, they return the name of the attached database, table +** and column that query result column was extracted from. +** +** As with all other SQLite APIs, those postfixed with "16" return +** UTF-16 encoded strings, the other functions return UTF-8. {END} +** +** These APIs are only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** +** {A13751} +** If two or more threads call one or more of these routines against the same +** prepared statement and column at the same time then the results are +** undefined. +** +** Requirements: +** [H13741] [H13742] [H13743] [H13744] [H13745] [H13746] [H13748] +** +** If two or more threads call one or more +** [sqlite3_column_database_name | column metadata interfaces] +** for the same [prepared statement] and result column +** at the same time then the results are undefined. +*/ +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Declared Datatype Of A Query Result {H13760} +** +** The first parameter is a [prepared statement]. +** If this statement is a [SELECT] statement and the Nth column of the +** returned result set of that [SELECT] is a table column (not an +** expression or subquery) then the declared type of the table +** column is returned. If the Nth column of the result set is an +** expression or subquery, then a NULL pointer is returned. +** The returned string is always UTF-8 encoded. {END} +** +** For example, given the database schema: +** +** CREATE TABLE t1(c1 VARIANT); +** +** and the following statement to be compiled: +** +** SELECT c1 + 1, c1 FROM t1; +** +** this routine would return the string "VARIANT" for the second result +** column (i==1), and a NULL pointer for the first result column (i==0). +** +** SQLite uses dynamic run-time typing. So just because a column +** is declared to contain a particular type does not mean that the +** data stored in that column is of the declared type. SQLite is +** strongly typed, but the typing is dynamic not static. Type +** is associated with individual values, not with the containers +** used to hold those values. +** +** Requirements: +** [H13761] [H13762] [H13763] +*/ +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Evaluate An SQL Statement {H13200} +** +** After a [prepared statement] has been prepared using either +** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy +** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function +** must be called one or more times to evaluate the statement. +** +** The details of the behavior of the sqlite3_step() interface depend +** on whether the statement was prepared using the newer "v2" interface +** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy +** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "v2" interface is recommended for new applications but the legacy +** interface will continue to be supported. +** +** In the legacy interface, the return value will be either [SQLITE_BUSY], +** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE]. +** With the "v2" interface, any of the other [result codes] or +** [extended result codes] might be returned as well. +** +** [SQLITE_BUSY] means that the database engine was unable to acquire the +** database locks it needs to do its job. If the statement is a [COMMIT] +** or occurs outside of an explicit transaction, then you can retry the +** statement. If the statement is not a [COMMIT] and occurs within a +** explicit transaction then you should rollback the transaction before +** continuing. +** +** [SQLITE_DONE] means that the statement has finished executing +** successfully. sqlite3_step() should not be called again on this virtual +** machine without first calling [sqlite3_reset()] to reset the virtual +** machine back to its initial state. +** +** If the SQL statement being executed returns any data, then [SQLITE_ROW] +** is returned each time a new row of data is ready for processing by the +** caller. The values may be accessed using the [column access functions]. +** sqlite3_step() is called again to retrieve the next row of data. +** +** [SQLITE_ERROR] means that a run-time error (such as a constraint +** violation) has occurred. sqlite3_step() should not be called again on +** the VM. More information may be found by calling [sqlite3_errmsg()]. +** With the legacy interface, a more specific error code (for example, +** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth) +** can be obtained by calling [sqlite3_reset()] on the +** [prepared statement]. In the "v2" interface, +** the more specific error code is returned directly by sqlite3_step(). +** +** [SQLITE_MISUSE] means that the this routine was called inappropriately. +** Perhaps it was called on a [prepared statement] that has +** already been [sqlite3_finalize | finalized] or on one that had +** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could +** be the case that the same database connection is being used by two or +** more threads at the same moment in time. +** +** Goofy Interface Alert: In the legacy interface, the sqlite3_step() +** API always returns a generic error code, [SQLITE_ERROR], following any +** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call +** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the +** specific [error codes] that better describes the error. +** We admit that this is a goofy design. The problem has been fixed +** with the "v2" interface. If you prepare all of your SQL statements +** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead +** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, +** then the more specific [error codes] are returned directly +** by sqlite3_step(). The use of the "v2" interface is recommended. +** +** Requirements: +** [H13202] [H15304] [H15306] [H15308] [H15310] +*/ +SQLITE_API int sqlite3_step(sqlite3_stmt*); + +/* +** CAPI3REF: Number of columns in a result set {H13770} +** +** Returns the number of values in the current row of the result set. +** +** Requirements: +** [H13771] [H13772] +*/ +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Fundamental Datatypes {H10265} +** KEYWORDS: SQLITE_TEXT +** +** {H10266} Every value in SQLite has one of five fundamental datatypes: +** +**
    +**
  • 64-bit signed integer +**
  • 64-bit IEEE floating point number +**
  • string +**
  • BLOB +**
  • NULL +**
{END} +** +** These constants are codes for each of those types. +** +** Note that the SQLITE_TEXT constant was also used in SQLite version 2 +** for a completely different meaning. Software that links against both +** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not +** SQLITE_TEXT. +*/ +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 +#ifdef SQLITE_TEXT +# undef SQLITE_TEXT +#else +# define SQLITE_TEXT 3 +#endif +#define SQLITE3_TEXT 3 + +/* +** CAPI3REF: Result Values From A Query {H13800} +** KEYWORDS: {column access functions} +** +** These routines form the "result set query" interface. +** +** These routines return information about a single column of the current +** result row of a query. In every case the first argument is a pointer +** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*] +** that was returned from [sqlite3_prepare_v2()] or one of its variants) +** and the second argument is the index of the column for which information +** should be returned. The leftmost column of the result set has the index 0. +** +** If the SQL statement does not currently point to a valid row, or if the +** column index is out of range, the result is undefined. +** These routines may only be called when the most recent call to +** [sqlite3_step()] has returned [SQLITE_ROW] and neither +** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently. +** If any of these routines are called after [sqlite3_reset()] or +** [sqlite3_finalize()] or after [sqlite3_step()] has returned +** something other than [SQLITE_ROW], the results are undefined. +** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()] +** are called from a different thread while any of these routines +** are pending, then the results are undefined. +** +** The sqlite3_column_type() routine returns the +** [SQLITE_INTEGER | datatype code] for the initial data type +** of the result column. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value +** returned by sqlite3_column_type() is only meaningful if no type +** conversions have occurred as described below. After a type conversion, +** the value returned by sqlite3_column_type() is undefined. Future +** versions of SQLite may change the behavior of sqlite3_column_type() +** following a type conversion. +** +** If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() +** routine returns the number of bytes in that BLOB or string. +** If the result is a UTF-16 string, then sqlite3_column_bytes() converts +** the string to UTF-8 and then returns the number of bytes. +** If the result is a numeric value then sqlite3_column_bytes() uses +** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns +** the number of bytes in that string. +** The value returned does not include the zero terminator at the end +** of the string. For clarity: the value returned is the number of +** bytes in the string, not the number of characters. +** +** Strings returned by sqlite3_column_text() and sqlite3_column_text16(), +** even empty strings, are always zero terminated. The return +** value from sqlite3_column_blob() for a zero-length BLOB is an arbitrary +** pointer, possibly even a NULL pointer. +** +** The sqlite3_column_bytes16() routine is similar to sqlite3_column_bytes() +** but leaves the result in UTF-16 in native byte order instead of UTF-8. +** The zero terminator is not included in this count. +** +** The object returned by [sqlite3_column_value()] is an +** [unprotected sqlite3_value] object. An unprotected sqlite3_value object +** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()]. +** If the [unprotected sqlite3_value] object returned by +** [sqlite3_column_value()] is used in any other way, including calls +** to routines like [sqlite3_value_int()], [sqlite3_value_text()], +** or [sqlite3_value_bytes()], then the behavior is undefined. +** +** These routines attempt to convert the value where appropriate. For +** example, if the internal representation is FLOAT and a text result +** is requested, [sqlite3_snprintf()] is used internally to perform the +** conversion automatically. The following table details the conversions +** that are applied: +** +**
+** +**
Internal
Type
Requested
Type
Conversion +** +**
NULL INTEGER Result is 0 +**
NULL FLOAT Result is 0.0 +**
NULL TEXT Result is NULL pointer +**
NULL BLOB Result is NULL pointer +**
INTEGER FLOAT Convert from integer to float +**
INTEGER TEXT ASCII rendering of the integer +**
INTEGER BLOB Same as INTEGER->TEXT +**
FLOAT INTEGER Convert from float to integer +**
FLOAT TEXT ASCII rendering of the float +**
FLOAT BLOB Same as FLOAT->TEXT +**
TEXT INTEGER Use atoi() +**
TEXT FLOAT Use atof() +**
TEXT BLOB No change +**
BLOB INTEGER Convert to TEXT then use atoi() +**
BLOB FLOAT Convert to TEXT then use atof() +**
BLOB TEXT Add a zero terminator if needed +**
+**
+** +** The table above makes reference to standard C library functions atoi() +** and atof(). SQLite does not really use these functions. It has its +** own equivalent internal routines. The atoi() and atof() names are +** used in the table for brevity and because they are familiar to most +** C programmers. +** +** Note that when type conversions occur, pointers returned by prior +** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or +** sqlite3_column_text16() may be invalidated. +** Type conversions and pointer invalidations might occur +** in the following cases: +** +**
    +**
  • The initial content is a BLOB and sqlite3_column_text() or +** sqlite3_column_text16() is called. A zero-terminator might +** need to be added to the string.
  • +**
  • The initial content is UTF-8 text and sqlite3_column_bytes16() or +** sqlite3_column_text16() is called. The content must be converted +** to UTF-16.
  • +**
  • The initial content is UTF-16 text and sqlite3_column_bytes() or +** sqlite3_column_text() is called. The content must be converted +** to UTF-8.
  • +**
+** +** Conversions between UTF-16be and UTF-16le are always done in place and do +** not invalidate a prior pointer, though of course the content of the buffer +** that the prior pointer points to will have been modified. Other kinds +** of conversion are done in place when it is possible, but sometimes they +** are not possible and in those cases prior pointers are invalidated. +** +** The safest and easiest to remember policy is to invoke these routines +** in one of the following ways: +** +**
    +**
  • sqlite3_column_text() followed by sqlite3_column_bytes()
  • +**
  • sqlite3_column_blob() followed by sqlite3_column_bytes()
  • +**
  • sqlite3_column_text16() followed by sqlite3_column_bytes16()
  • +**
+** +** In other words, you should call sqlite3_column_text(), +** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result +** into the desired format, then invoke sqlite3_column_bytes() or +** sqlite3_column_bytes16() to find the size of the result. Do not mix calls +** to sqlite3_column_text() or sqlite3_column_blob() with calls to +** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16() +** with calls to sqlite3_column_bytes(). +** +** The pointers returned are valid until a type conversion occurs as +** described above, or until [sqlite3_step()] or [sqlite3_reset()] or +** [sqlite3_finalize()] is called. The memory space used to hold strings +** and BLOBs is freed automatically. Do not pass the pointers returned +** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** [sqlite3_free()]. +** +** If a memory allocation error occurs during the evaluation of any +** of these routines, a default value is returned. The default value +** is either the integer 0, the floating point number 0.0, or a NULL +** pointer. Subsequent calls to [sqlite3_errcode()] will return +** [SQLITE_NOMEM]. +** +** Requirements: +** [H13803] [H13806] [H13809] [H13812] [H13815] [H13818] [H13821] [H13824] +** [H13827] [H13830] +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); + +/* +** CAPI3REF: Destroy A Prepared Statement Object {H13300} +** +** The sqlite3_finalize() function is called to delete a [prepared statement]. +** If the statement was executed successfully or not executed at all, then +** SQLITE_OK is returned. If execution of the statement failed then an +** [error code] or [extended error code] is returned. +** +** This routine can be called at any point during the execution of the +** [prepared statement]. If the virtual machine has not +** completed execution when this routine is called, that is like +** encountering an error or an [sqlite3_interrupt | interrupt]. +** Incomplete updates may be rolled back and transactions canceled, +** depending on the circumstances, and the +** [error code] returned will be [SQLITE_ABORT]. +** +** Requirements: +** [H11302] [H11304] +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Reset A Prepared Statement Object {H13330} +** +** The sqlite3_reset() function is called to reset a [prepared statement] +** object back to its initial state, ready to be re-executed. +** Any SQL statement variables that had values bound to them using +** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. +** Use [sqlite3_clear_bindings()] to reset the bindings. +** +** {H11332} The [sqlite3_reset(S)] interface resets the [prepared statement] S +** back to the beginning of its program. +** +** {H11334} If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], +** or if [sqlite3_step(S)] has never before been called on S, +** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** +** {H11336} If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S indicated an error, then +** [sqlite3_reset(S)] returns an appropriate [error code]. +** +** {H11338} The [sqlite3_reset(S)] interface does not change the values +** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Create Or Redefine SQL Functions {H16100} +** KEYWORDS: {function creation routines} +** KEYWORDS: {application-defined SQL function} +** KEYWORDS: {application-defined SQL functions} +** +** These two functions (collectively known as "function creation routines") +** are used to add SQL functions or aggregates or to redefine the behavior +** of existing SQL functions or aggregates. The only difference between the +** two is that the second parameter, the name of the (scalar) function or +** aggregate, is encoded in UTF-8 for sqlite3_create_function() and UTF-16 +** for sqlite3_create_function16(). +** +** The first parameter is the [database connection] to which the SQL +** function is to be added. If a single program uses more than one database +** connection internally, then SQL functions must be added individually to +** each database connection. +** +** The second parameter is the name of the SQL function to be created or +** redefined. The length of the name is limited to 255 bytes, exclusive of +** the zero-terminator. Note that the name length limit is in bytes, not +** characters. Any attempt to create a function with a longer name +** will result in [SQLITE_ERROR] being returned. +** +** The third parameter (nArg) +** is the number of arguments that the SQL function or +** aggregate takes. If this parameter is -1, then the SQL function or +** aggregate may take any number of arguments between 0 and the limit +** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third +** parameter is less than -1 or greater than 127 then the behavior is +** undefined. +** +** The fourth parameter, eTextRep, specifies what +** [SQLITE_UTF8 | text encoding] this SQL function prefers for +** its parameters. Any SQL function implementation should be able to work +** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be +** more efficient with one encoding than another. It is allowed to +** invoke sqlite3_create_function() or sqlite3_create_function16() multiple +** times with the same function but with different values of eTextRep. +** When multiple implementations of the same function are available, SQLite +** will pick the one that involves the least amount of data conversion. +** If there is only a single implementation which does not care what text +** encoding is used, then the fourth argument should be [SQLITE_ANY]. +** +** The fifth parameter is an arbitrary pointer. The implementation of the +** function can gain access to this pointer using [sqlite3_user_data()]. +** +** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are +** pointers to C-language functions that implement the SQL function or +** aggregate. A scalar SQL function requires an implementation of the xFunc +** callback only, NULL pointers should be passed as the xStep and xFinal +** parameters. An aggregate SQL function requires an implementation of xStep +** and xFinal and NULL should be passed for xFunc. To delete an existing +** SQL function or aggregate, pass NULL for all three function callbacks. +** +** It is permitted to register multiple implementations of the same +** functions with the same name but with either differing numbers of +** arguments or differing preferred text encodings. SQLite will use +** the implementation most closely matches the way in which the +** SQL function is used. A function implementation with a non-negative +** nArg parameter is a better match than a function implementation with +** a negative nArg. A function where the preferred text encoding +** matches the database encoding is a better +** match than a function where the encoding is different. +** A function where the encoding difference is between UTF16le and UTF16be +** is a closer match than a function where the encoding difference is +** between UTF8 and UTF16. +** +** Built-in functions may be overloaded by new application-defined functions. +** The first application-defined function with a given name overrides all +** built-in functions in the same [database connection] with the same name. +** Subsequent application-defined functions of the same name only override +** prior application-defined functions that are an exact match for the +** number of parameters and preferred encoding. +** +** An application-defined function is permitted to call other +** SQLite interfaces. However, such calls must not +** close the database connection nor finalize or reset the prepared +** statement in which the function is running. +** +** Requirements: +** [H16103] [H16106] [H16109] [H16112] [H16118] [H16121] [H16127] +** [H16130] [H16133] [H16136] [H16139] [H16142] +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); + +/* +** CAPI3REF: Text Encodings {H10267} +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +#define SQLITE_UTF8 1 +#define SQLITE_UTF16LE 2 +#define SQLITE_UTF16BE 3 +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* sqlite3_create_function only */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ + +/* +** CAPI3REF: Deprecated Functions +** DEPRECATED +** +** These functions are [deprecated]. In order to maintain +** backwards compatibility with older code, these functions continue +** to be supported. However, new applications should avoid +** the use of these functions. To help encourage people to avoid +** using these functions, we are not going to tell you what they do. +*/ +#ifndef SQLITE_OMIT_DEPRECATED +SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64); +#endif + +/* +** CAPI3REF: Obtaining SQL Function Parameter Values {H15100} +** +** The C-language implementation of SQL functions and aggregates uses +** this set of interface routines to access the parameter values on +** the function or aggregate. +** +** The xFunc (for scalar functions) or xStep (for aggregates) parameters +** to [sqlite3_create_function()] and [sqlite3_create_function16()] +** define callbacks that implement the SQL functions and aggregates. +** The 4th parameter to these callbacks is an array of pointers to +** [protected sqlite3_value] objects. There is one [sqlite3_value] object for +** each parameter to the SQL function. These routines are used to +** extract values from the [sqlite3_value] objects. +** +** These routines work only with [protected sqlite3_value] objects. +** Any attempt to use these routines on an [unprotected sqlite3_value] +** object results in undefined behavior. +** +** These routines work just like the corresponding [column access functions] +** except that these routines take a single [protected sqlite3_value] object +** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. +** +** The sqlite3_value_text16() interface extracts a UTF-16 string +** in the native byte-order of the host machine. The +** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces +** extract UTF-16 strings as big-endian and little-endian respectively. +** +** The sqlite3_value_numeric_type() interface attempts to apply +** numeric affinity to the value. This means that an attempt is +** made to convert the value to an integer or floating point. If +** such a conversion is possible without loss of information (in other +** words, if the value is a string that looks like a number) +** then the conversion is performed. Otherwise no conversion occurs. +** The [SQLITE_INTEGER | datatype] after conversion is returned. +** +** Please pay particular attention to the fact that the pointer returned +** from [sqlite3_value_blob()], [sqlite3_value_text()], or +** [sqlite3_value_text16()] can be invalidated by a subsequent call to +** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], +** or [sqlite3_value_text16()]. +** +** These routines must be called from the same thread as +** the SQL function that supplied the [sqlite3_value*] parameters. +** +** Requirements: +** [H15103] [H15106] [H15109] [H15112] [H15115] [H15118] [H15121] [H15124] +** [H15127] [H15130] [H15133] [H15136] +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); + +/* +** CAPI3REF: Obtain Aggregate Function Context {H16210} +** +** The implementation of aggregate SQL functions use this routine to allocate +** a structure for storing their state. +** +** The first time the sqlite3_aggregate_context() routine is called for a +** particular aggregate, SQLite allocates nBytes of memory, zeroes out that +** memory, and returns a pointer to it. On second and subsequent calls to +** sqlite3_aggregate_context() for the same aggregate function index, +** the same buffer is returned. The implementation of the aggregate can use +** the returned buffer to accumulate data. +** +** SQLite automatically frees the allocated buffer when the aggregate +** query concludes. +** +** The first parameter should be a copy of the +** [sqlite3_context | SQL function context] that is the first parameter +** to the callback routine that implements the aggregate function. +** +** This routine must be called from the same thread in which +** the aggregate SQL function is running. +** +** Requirements: +** [H16211] [H16213] [H16215] [H16217] +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** CAPI3REF: User Data For Functions {H16240} +** +** The sqlite3_user_data() interface returns a copy of +** the pointer that was the pUserData parameter (the 5th parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. {END} +** +** This routine must be called from the same thread in which +** the application-defined function is running. +** +** Requirements: +** [H16243] +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context*); + +/* +** CAPI3REF: Database Connection For Functions {H16250} +** +** The sqlite3_context_db_handle() interface returns a copy of +** the pointer to the [database connection] (the 1st parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +** +** Requirements: +** [H16253] +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/* +** CAPI3REF: Function Auxiliary Data {H16270} +** +** The following two functions may be used by scalar SQL functions to +** associate metadata with argument values. If the same value is passed to +** multiple invocations of the same SQL function during query execution, under +** some circumstances the associated metadata may be preserved. This may +** be used, for example, to add a regular-expression matching scalar +** function. The compiled version of the regular expression is stored as +** metadata associated with the SQL value passed as the regular expression +** pattern. The compiled regular expression can be reused on multiple +** invocations of the same function so that the original pattern string +** does not need to be recompiled on each invocation. +** +** The sqlite3_get_auxdata() interface returns a pointer to the metadata +** associated by the sqlite3_set_auxdata() function with the Nth argument +** value to the application-defined function. If no metadata has been ever +** been set for the Nth argument of the function, or if the corresponding +** function parameter has changed since the meta-data was set, +** then sqlite3_get_auxdata() returns a NULL pointer. +** +** The sqlite3_set_auxdata() interface saves the metadata +** pointed to by its 3rd parameter as the metadata for the N-th +** argument of the application-defined function. Subsequent +** calls to sqlite3_get_auxdata() might return this data, if it has +** not been destroyed. +** If it is not NULL, SQLite will invoke the destructor +** function given by the 4th parameter to sqlite3_set_auxdata() on +** the metadata when the corresponding function parameter changes +** or when the SQL statement completes, whichever comes first. +** +** SQLite is free to call the destructor and drop metadata on any +** parameter of any function at any time. The only guarantee is that +** the destructor will be called before the metadata is dropped. +** +** In practice, metadata is preserved between function calls for +** expressions that are constant at compile time. This includes literal +** values and SQL variables. +** +** These routines must be called from the same thread in which +** the SQL function is running. +** +** Requirements: +** [H16272] [H16274] [H16276] [H16277] [H16278] [H16279] +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + + +/* +** CAPI3REF: Constants Defining Special Destructor Behavior {H10280} +** +** These are special values for the destructor that is passed in as the +** final argument to routines like [sqlite3_result_blob()]. If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +** +** The typedef is necessary to work around problems in certain +** C++ compilers. See ticket #2191. +*/ +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +/* +** CAPI3REF: Setting The Result Of An SQL Function {H16400} +** +** These routines are used by the xFunc or xFinal callbacks that +** implement SQL functions and aggregates. See +** [sqlite3_create_function()] and [sqlite3_create_function16()] +** for additional information. +** +** These functions work very much like the [parameter binding] family of +** functions used to bind values to host parameters in prepared statements. +** Refer to the [SQL parameter] documentation for additional information. +** +** The sqlite3_result_blob() interface sets the result from +** an application-defined function to be the BLOB whose content is pointed +** to by the second parameter and which is N bytes long where N is the +** third parameter. +** +** The sqlite3_result_zeroblob() interfaces set the result of +** the application-defined function to be a BLOB containing all zero +** bytes and N bytes in size, where N is the value of the 2nd parameter. +** +** The sqlite3_result_double() interface sets the result from +** an application-defined function to be a floating point value specified +** by its 2nd argument. +** +** The sqlite3_result_error() and sqlite3_result_error16() functions +** cause the implemented SQL function to throw an exception. +** SQLite uses the string pointed to by the +** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() +** as the text of an error message. SQLite interprets the error +** message string from sqlite3_result_error() as UTF-8. SQLite +** interprets the string from sqlite3_result_error16() as UTF-16 in native +** byte order. If the third parameter to sqlite3_result_error() +** or sqlite3_result_error16() is negative then SQLite takes as the error +** message all text up through the first zero character. +** If the third parameter to sqlite3_result_error() or +** sqlite3_result_error16() is non-negative then SQLite takes that many +** bytes (not characters) from the 2nd parameter as the error message. +** The sqlite3_result_error() and sqlite3_result_error16() +** routines make a private copy of the error message text before +** they return. Hence, the calling function can deallocate or +** modify the text after they return without harm. +** The sqlite3_result_error_code() function changes the error code +** returned by SQLite as a result of an error in a function. By default, +** the error code is SQLITE_ERROR. A subsequent call to sqlite3_result_error() +** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. +** +** The sqlite3_result_toobig() interface causes SQLite to throw an error +** indicating that a string or BLOB is to long to represent. +** +** The sqlite3_result_nomem() interface causes SQLite to throw an error +** indicating that a memory allocation failed. +** +** The sqlite3_result_int() interface sets the return value +** of the application-defined function to be the 32-bit signed integer +** value given in the 2nd argument. +** The sqlite3_result_int64() interface sets the return value +** of the application-defined function to be the 64-bit signed integer +** value given in the 2nd argument. +** +** The sqlite3_result_null() interface sets the return value +** of the application-defined function to be NULL. +** +** The sqlite3_result_text(), sqlite3_result_text16(), +** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces +** set the return value of the application-defined function to be +** a text string which is represented as UTF-8, UTF-16 native byte order, +** UTF-16 little endian, or UTF-16 big endian, respectively. +** SQLite takes the text result from the application from +** the 2nd parameter of the sqlite3_result_text* interfaces. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is negative, then SQLite takes result text from the 2nd parameter +** through the first zero character. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is non-negative, then as many bytes (not characters) of the text +** pointed to by the 2nd parameter are taken as the application-defined +** function result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that +** function as the destructor on the text or BLOB result when it has +** finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces or +** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite +** assumes that the text or BLOB result is in constant space and does not +** copy the it or call a destructor when it has finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT +** then SQLite makes a copy of the result into space obtained from +** from [sqlite3_malloc()] before it returns. +** +** The sqlite3_result_value() interface sets the result of +** the application-defined function to be a copy the +** [unprotected sqlite3_value] object specified by the 2nd parameter. The +** sqlite3_result_value() interface makes a copy of the [sqlite3_value] +** so that the [sqlite3_value] specified in the parameter may change or +** be deallocated after sqlite3_result_value() returns without harm. +** A [protected sqlite3_value] object may always be used where an +** [unprotected sqlite3_value] object is required, so either +** kind of [sqlite3_value] object can be used with this interface. +** +** If these routines are called from within the different thread +** than the one containing the application-defined function that received +** the [sqlite3_context] pointer, the results are undefined. +** +** Requirements: +** [H16403] [H16406] [H16409] [H16412] [H16415] [H16418] [H16421] [H16424] +** [H16427] [H16430] [H16433] [H16436] [H16439] [H16442] [H16445] [H16448] +** [H16451] [H16454] [H16457] [H16460] [H16463] +*/ +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); + +/* +** CAPI3REF: Define New Collating Sequences {H16600} +** +** These functions are used to add new collation sequences to the +** [database connection] specified as the first argument. +** +** The name of the new collation sequence is specified as a UTF-8 string +** for sqlite3_create_collation() and sqlite3_create_collation_v2() +** and a UTF-16 string for sqlite3_create_collation16(). In all cases +** the name is passed as the second function argument. +** +** The third argument may be one of the constants [SQLITE_UTF8], +** [SQLITE_UTF16LE], or [SQLITE_UTF16BE], indicating that the user-supplied +** routine expects to be passed pointers to strings encoded using UTF-8, +** UTF-16 little-endian, or UTF-16 big-endian, respectively. The +** third argument might also be [SQLITE_UTF16] to indicate that the routine +** expects pointers to be UTF-16 strings in the native byte order, or the +** argument can be [SQLITE_UTF16_ALIGNED] if the +** the routine expects pointers to 16-bit word aligned strings +** of UTF-16 in the native byte order. +** +** A pointer to the user supplied routine must be passed as the fifth +** argument. If it is NULL, this is the same as deleting the collation +** sequence (so that SQLite cannot call it anymore). +** Each time the application supplied function is invoked, it is passed +** as its first parameter a copy of the void* passed as the fourth argument +** to sqlite3_create_collation() or sqlite3_create_collation16(). +** +** The remaining arguments to the application-supplied routine are two strings, +** each represented by a (length, data) pair and encoded in the encoding +** that was passed as the third argument when the collation sequence was +** registered. {END} The application defined collation routine should +** return negative, zero or positive if the first string is less than, +** equal to, or greater than the second string. i.e. (STRING1 - STRING2). +** +** The sqlite3_create_collation_v2() works like sqlite3_create_collation() +** except that it takes an extra argument which is a destructor for +** the collation. The destructor is called when the collation is +** destroyed and is passed a copy of the fourth parameter void* pointer +** of the sqlite3_create_collation_v2(). +** Collations are destroyed when they are overridden by later calls to the +** collation creation functions or when the [database connection] is closed +** using [sqlite3_close()]. +** +** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. +** +** Requirements: +** [H16603] [H16604] [H16606] [H16609] [H16612] [H16615] [H16618] [H16621] +** [H16624] [H16627] [H16630] +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const void *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** CAPI3REF: Collation Needed Callbacks {H16700} +** +** To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** [database connection] to be called whenever an undefined collation +** sequence is required. +** +** If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. {H16703} If sqlite3_collation_needed16() is used, +** the names are passed as UTF-16 in machine native byte order. +** A call to either function replaces any existing callback. +** +** When the callback is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE], indicating the most desirable form of the collation +** sequence function required. The fourth parameter is the name of the +** required collation sequence. +** +** The callback function should register the desired collation using +** [sqlite3_create_collation()], [sqlite3_create_collation16()], or +** [sqlite3_create_collation_v2()]. +** +** Requirements: +** [H16702] [H16704] [H16706] +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +/* +** Specify the key for an encrypted database. This routine should be +** called right after sqlite3_open(). +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_key( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The key */ +); + +/* +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_rekey( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The new key */ +); + +/* +** CAPI3REF: Suspend Execution For A Short Time {H10530} +** +** The sqlite3_sleep() function causes the current thread to suspend execution +** for at least a number of milliseconds specified in its parameter. +** +** If the operating system does not support sleep requests with +** millisecond time resolution, then the time will be rounded up to +** the nearest second. The number of milliseconds of sleep actually +** requested from the operating system is returned. +** +** SQLite implements this interface by calling the xSleep() +** method of the default [sqlite3_vfs] object. +** +** Requirements: [H10533] [H10536] +*/ +SQLITE_API int sqlite3_sleep(int); + +/* +** CAPI3REF: Name Of The Folder Holding Temporary Files {H10310} +** +** If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all temporary files +** created by SQLite will be placed in that directory. If this variable +** is a NULL pointer, then SQLite performs a search for an appropriate +** temporary file directory. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** The [temp_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. Furthermore, +** the [temp_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [temp_store_directory pragma] should be avoided. +*/ +SQLITE_API char *sqlite3_temp_directory; + +/* +** CAPI3REF: Test For Auto-Commit Mode {H12930} +** KEYWORDS: {autocommit mode} +** +** The sqlite3_get_autocommit() interface returns non-zero or +** zero if the given database connection is or is not in autocommit mode, +** respectively. Autocommit mode is on by default. +** Autocommit mode is disabled by a [BEGIN] statement. +** Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK]. +** +** If certain kinds of errors occur on a statement within a multi-statement +** transaction (errors including [SQLITE_FULL], [SQLITE_IOERR], +** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the +** transaction might be rolled back automatically. The only way to +** find out whether SQLite automatically rolled back the transaction after +** an error is to use this function. +** +** If another thread changes the autocommit status of the database +** connection while this routine is running, then the return value +** is undefined. +** +** Requirements: [H12931] [H12932] [H12933] [H12934] +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3*); + +/* +** CAPI3REF: Find The Database Handle Of A Prepared Statement {H13120} +** +** The sqlite3_db_handle interface returns the [database connection] handle +** to which a [prepared statement] belongs. The [database connection] +** returned by sqlite3_db_handle is the same [database connection] that was the first argument +** to the [sqlite3_prepare_v2()] call (or its variants) that was used to +** create the statement in the first place. +** +** Requirements: [H13123] +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + +/* +** CAPI3REF: Find the next prepared statement {H13140} +** +** This interface returns a pointer to the next [prepared statement] after +** pStmt associated with the [database connection] pDb. If pStmt is NULL +** then this interface returns a pointer to the first prepared statement +** associated with the database connection pDb. If no prepared statement +** satisfies the conditions of this routine, it returns NULL. +** +** The [database connection] pointer D in a call to +** [sqlite3_next_stmt(D,S)] must refer to an open database +** connection and in particular must not be a NULL pointer. +** +** Requirements: [H13143] [H13146] [H13149] [H13152] +*/ +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Commit And Rollback Notification Callbacks {H12950} +** +** The sqlite3_commit_hook() interface registers a callback +** function to be invoked whenever a transaction is [COMMIT | committed]. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The sqlite3_rollback_hook() interface registers a callback +** function to be invoked whenever a transaction is [ROLLBACK | rolled back]. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The pArg argument is passed through to the callback. +** If the callback on a commit hook function returns non-zero, +** then the commit is converted into a rollback. +** +** If another function was previously registered, its +** pArg value is returned. Otherwise NULL is returned. +** +** The callback implementation must not do anything that will modify +** the database connection that invoked the callback. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the commit +** or rollback hook in the first place. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** Registering a NULL function disables the callback. +** +** When the commit hook callback routine returns zero, the [COMMIT] +** operation is allowed to continue normally. If the commit hook +** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. +** The rollback hook is invoked on a rollback that results from a commit +** hook returning non-zero, just as it would be with any other rollback. +** +** For the purposes of this API, a transaction is said to have been +** rolled back if an explicit "ROLLBACK" statement is executed, or +** an error or constraint causes an implicit rollback to occur. +** The rollback callback is not invoked if a transaction is +** automatically rolled back because the database connection is closed. +** The rollback callback is not invoked if a transaction is +** rolled back because a commit callback returned non-zero. +** Check on this +** +** See also the [sqlite3_update_hook()] interface. +** +** Requirements: +** [H12951] [H12952] [H12953] [H12954] [H12955] +** [H12961] [H12962] [H12963] [H12964] +*/ +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + +/* +** CAPI3REF: Data Change Notification Callbacks {H12970} +** +** The sqlite3_update_hook() interface registers a callback function +** with the [database connection] identified by the first argument +** to be invoked whenever a row is updated, inserted or deleted. +** Any callback set by a previous call to this function +** for the same database connection is overridden. +** +** The second argument is a pointer to the function to invoke when a +** row is updated, inserted or deleted. +** The first argument to the callback is a copy of the third argument +** to sqlite3_update_hook(). +** The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], +** or [SQLITE_UPDATE], depending on the operation that caused the callback +** to be invoked. +** The third and fourth arguments to the callback contain pointers to the +** database and table name containing the affected row. +** The final callback parameter is the [rowid] of the row. +** In the case of an update, this is the [rowid] after the update takes place. +** +** The update hook is not invoked when internal system tables are +** modified (i.e. sqlite_master and sqlite_sequence). +** +** In the current implementation, the update hook +** is not invoked when duplication rows are deleted because of an +** [ON CONFLICT | ON CONFLICT REPLACE] clause. Nor is the update hook +** invoked when rows are deleted using the [truncate optimization]. +** The exceptions defined in this paragraph might change in a future +** release of SQLite. +** +** The update hook implementation must not do anything that will modify +** the database connection that invoked the update hook. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the update hook. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** If another function was previously registered, its pArg value +** is returned. Otherwise NULL is returned. +** +** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] +** interfaces. +** +** Requirements: +** [H12971] [H12973] [H12975] [H12977] [H12979] [H12981] [H12983] [H12986] +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); + +/* +** CAPI3REF: Enable Or Disable Shared Pager Cache {H10330} +** KEYWORDS: {shared cache} {shared cache mode} +** +** This routine enables or disables the sharing of the database cache +** and schema data structures between [database connection | connections] +** to the same database. Sharing is enabled if the argument is true +** and disabled if the argument is false. +** +** Cache sharing is enabled and disabled for an entire process. +** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, +** sharing was enabled or disabled for each thread separately. +** +** The cache sharing mode set by this interface effects all subsequent +** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. +** Existing database connections continue use the sharing mode +** that was in effect at the time they were opened. +** +** Virtual tables cannot be used with a shared cache. When shared +** cache is enabled, the [sqlite3_create_module()] API used to register +** virtual tables will always return an error. +** +** This routine returns [SQLITE_OK] if shared cache was enabled or disabled +** successfully. An [error code] is returned otherwise. +** +** Shared cache is disabled by default. But this might change in +** future releases of SQLite. Applications that care about shared +** cache setting should set it explicitly. +** +** See Also: [SQLite Shared-Cache Mode] +** +** Requirements: [H10331] [H10336] [H10337] [H10339] +*/ +SQLITE_API int sqlite3_enable_shared_cache(int); + +/* +** CAPI3REF: Attempt To Free Heap Memory {H17340} +** +** The sqlite3_release_memory() interface attempts to free N bytes +** of heap memory by deallocating non-essential memory allocations +** held by the database library. {END} Memory used to cache database +** pages to improve performance is an example of non-essential memory. +** sqlite3_release_memory() returns the number of bytes actually freed, +** which might be more or less than the amount requested. +** +** Requirements: [H17341] [H17342] +*/ +SQLITE_API int sqlite3_release_memory(int); + +/* +** CAPI3REF: Impose A Limit On Heap Size {H17350} +** +** The sqlite3_soft_heap_limit() interface places a "soft" limit +** on the amount of heap memory that may be allocated by SQLite. +** If an internal allocation is requested that would exceed the +** soft heap limit, [sqlite3_release_memory()] is invoked one or +** more times to free up some space before the allocation is performed. +** +** The limit is called "soft", because if [sqlite3_release_memory()] +** cannot free sufficient memory to prevent the limit from being exceeded, +** the memory is allocated anyway and the current operation proceeds. +** +** A negative or zero value for N means that there is no soft heap limit and +** [sqlite3_release_memory()] will only be called when memory is exhausted. +** The default value for the soft heap limit is zero. +** +** SQLite makes a best effort to honor the soft heap limit. +** But if the soft heap limit cannot be honored, execution will +** continue without error or notification. This is why the limit is +** called a "soft" limit. It is advisory only. +** +** Prior to SQLite version 3.5.0, this routine only constrained the memory +** allocated by a single thread - the same thread in which this routine +** runs. Beginning with SQLite version 3.5.0, the soft heap limit is +** applied to all threads. The value specified for the soft heap limit +** is an upper bound on the total memory allocation for all threads. In +** version 3.5.0 there is no mechanism for limiting the heap usage for +** individual threads. +** +** Requirements: +** [H16351] [H16352] [H16353] [H16354] [H16355] [H16358] +*/ +SQLITE_API void sqlite3_soft_heap_limit(int); + +/* +** CAPI3REF: Extract Metadata About A Column Of A Table {H12850} +** +** This routine returns metadata about a specific column of a specific +** database table accessible using the [database connection] handle +** passed as the first function argument. +** +** The column is identified by the second, third and fourth parameters to +** this function. The second parameter is either the name of the database +** (i.e. "main", "temp" or an attached database) containing the specified +** table or NULL. If it is NULL, then all attached databases are searched +** for the table using the same algorithm used by the database engine to +** resolve unqualified table references. +** +** The third and fourth parameters to this function are the table and column +** name of the desired column, respectively. Neither of these parameters +** may be NULL. +** +** Metadata is returned by writing to the memory locations passed as the 5th +** and subsequent parameters to this function. Any of these arguments may be +** NULL, in which case the corresponding element of metadata is omitted. +** +**
+** +**
Parameter Output
Type
Description +** +**
5th const char* Data type +**
6th const char* Name of default collation sequence +**
7th int True if column has a NOT NULL constraint +**
8th int True if column is part of the PRIMARY KEY +**
9th int True if column is [AUTOINCREMENT] +**
+**
+** +** The memory pointed to by the character pointers returned for the +** declaration type and collation sequence is valid only until the next +** call to any SQLite API function. +** +** If the specified table is actually a view, an [error code] is returned. +** +** If the specified column is "rowid", "oid" or "_rowid_" and an +** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output +** parameters are set for the explicitly declared column. If there is no +** explicitly declared [INTEGER PRIMARY KEY] column, then the output +** parameters are set as follows: +** +**
+**     data type: "INTEGER"
+**     collation sequence: "BINARY"
+**     not null: 0
+**     primary key: 1
+**     auto increment: 0
+** 
+** +** This function may load one or more schemas from database files. If an +** error occurs during this process, or if the requested table or column +** cannot be found, an [error code] is returned and an error message left +** in the [database connection] (to be retrieved using sqlite3_errmsg()). +** +** This API is only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* +** CAPI3REF: Load An Extension {H12600} +** +** This interface loads an SQLite extension library from the named file. +** +** {H12601} The sqlite3_load_extension() interface attempts to load an +** SQLite extension library contained in the file zFile. +** +** {H12602} The entry point is zProc. +** +** {H12603} zProc may be 0, in which case the name of the entry point +** defaults to "sqlite3_extension_init". +** +** {H12604} The sqlite3_load_extension() interface shall return +** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. +** +** {H12605} If an error occurs and pzErrMsg is not 0, then the +** [sqlite3_load_extension()] interface shall attempt to +** fill *pzErrMsg with error message text stored in memory +** obtained from [sqlite3_malloc()]. {END} The calling function +** should free this memory by calling [sqlite3_free()]. +** +** {H12606} Extension loading must be enabled using +** [sqlite3_enable_load_extension()] prior to calling this API, +** otherwise an error will be returned. +*/ +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); + +/* +** CAPI3REF: Enable Or Disable Extension Loading {H12620} +** +** So as not to open security holes in older applications that are +** unprepared to deal with extension loading, and as a means of disabling +** extension loading while evaluating user-entered SQL, the following API +** is provided to turn the [sqlite3_load_extension()] mechanism on and off. +** +** Extension loading is off by default. See ticket #1863. +** +** {H12621} Call the sqlite3_enable_load_extension() routine with onoff==1 +** to turn extension loading on and call it with onoff==0 to turn +** it back off again. +** +** {H12622} Extension loading is off by default. +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/* +** CAPI3REF: Automatically Load An Extensions {H12640} +** +** This API can be invoked at program startup in order to register +** one or more statically linked extensions that will be available +** to all new [database connections]. {END} +** +** This routine stores a pointer to the extension in an array that is +** obtained from [sqlite3_malloc()]. If you run a memory leak checker +** on your program and it reports a leak because of this array, invoke +** [sqlite3_reset_auto_extension()] prior to shutdown to free the memory. +** +** {H12641} This function registers an extension entry point that is +** automatically invoked whenever a new [database connection] +** is opened using [sqlite3_open()], [sqlite3_open16()], +** or [sqlite3_open_v2()]. +** +** {H12642} Duplicate extensions are detected so calling this routine +** multiple times with the same extension is harmless. +** +** {H12643} This routine stores a pointer to the extension in an array +** that is obtained from [sqlite3_malloc()]. +** +** {H12644} Automatic extensions apply across all threads. +*/ +SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); + +/* +** CAPI3REF: Reset Automatic Extension Loading {H12660} +** +** This function disables all previously registered automatic +** extensions. {END} It undoes the effect of all prior +** [sqlite3_auto_extension()] calls. +** +** {H12661} This function disables all previously registered +** automatic extensions. +** +** {H12662} This function disables automatic extensions in all threads. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void); + +/* +****** EXPERIMENTAL - subject to change without notice ************** +** +** The interface to the virtual-table mechanism is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +*/ + +/* +** Structures used by the virtual table interface +*/ +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; + +/* +** CAPI3REF: Virtual Table Object {H18000} +** KEYWORDS: sqlite3_module {virtual table module} +** EXPERIMENTAL +** +** This structure, sometimes called a a "virtual table module", +** defines the implementation of a [virtual tables]. +** This structure consists mostly of methods for the module. +** +** A virtual table module is created by filling in a persistent +** instance of this structure and passing a pointer to that instance +** to [sqlite3_create_module()] or [sqlite3_create_module_v2()]. +** The registration remains valid until it is replaced by a different +** module or until the [database connection] closes. The content +** of this structure must not change while it is registered with +** any database connection. +*/ +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); +}; + +/* +** CAPI3REF: Virtual Table Indexing Information {H18100} +** KEYWORDS: sqlite3_index_info +** EXPERIMENTAL +** +** The sqlite3_index_info structure and its substructures is used to +** pass information into and receive the reply from the [xBestIndex] +** method of a [virtual table module]. The fields under **Inputs** are the +** inputs to xBestIndex and are read-only. xBestIndex inserts its +** results into the **Outputs** fields. +** +** The aConstraint[] array records WHERE clause constraints of the form: +** +**
column OP expr
+** +** where OP is =, <, <=, >, or >=. The particular operator is +** stored in aConstraint[].op. The index of the column is stored in +** aConstraint[].iColumn. aConstraint[].usable is TRUE if the +** expr on the right-hand side can be evaluated (and thus the constraint +** is usable) and false if it cannot. +** +** The optimizer automatically inverts terms of the form "expr OP column" +** and makes other simplifications to the WHERE clause in an attempt to +** get as many WHERE clause terms into the form shown above as possible. +** The aConstraint[] array only reports WHERE clause terms in the correct +** form that refer to the particular virtual table being queried. +** +** Information about the ORDER BY clause is stored in aOrderBy[]. +** Each term of aOrderBy records a column of the ORDER BY clause. +** +** The [xBestIndex] method must fill aConstraintUsage[] with information +** about what parameters to pass to xFilter. If argvIndex>0 then +** the right-hand side of the corresponding aConstraint[] is evaluated +** and becomes the argvIndex-th entry in argv. If aConstraintUsage[].omit +** is true, then the constraint is assumed to be fully handled by the +** virtual table and is not checked again by SQLite. +** +** The idxNum and idxPtr values are recorded and passed into the +** [xFilter] method. +** [sqlite3_free()] is used to free idxPtr if and only iff +** needToFreeIdxPtr is true. +** +** The orderByConsumed means that output from [xFilter]/[xNext] will occur in +** the correct order to satisfy the ORDER BY clause so that no separate +** sorting step is required. +** +** The estimatedCost value is an estimate of the cost of doing the +** particular lookup. A full scan of a table with N entries should have +** a cost of N. A binary search of a table of N entries should have a +** cost of approximately log(N). +*/ +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column on left-hand side of constraint */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ +}; +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 + +/* +** CAPI3REF: Register A Virtual Table Implementation {H18200} +** EXPERIMENTAL +** +** This routine is used to register a new [virtual table module] name. +** Module names must be registered before +** creating a new [virtual table] using the module, or before using a +** preexisting [virtual table] for the module. +** +** The module name is registered on the [database connection] specified +** by the first parameter. The name of the module is given by the +** second parameter. The third parameter is a pointer to +** the implementation of the [virtual table module]. The fourth +** parameter is an arbitrary client data pointer that is passed through +** into the [xCreate] and [xConnect] methods of the virtual table module +** when a new virtual table is be being created or reinitialized. +** +** This interface has exactly the same effect as calling +** [sqlite3_create_module_v2()] with a NULL client data destructor. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); + +/* +** CAPI3REF: Register A Virtual Table Implementation {H18210} +** EXPERIMENTAL +** +** This routine is identical to the [sqlite3_create_module()] method, +** except that it has an extra parameter to specify +** a destructor function for the client data pointer. SQLite will +** invoke the destructor function (if it is not NULL) when SQLite +** no longer needs the pClientData pointer. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Virtual Table Instance Object {H18010} +** KEYWORDS: sqlite3_vtab +** EXPERIMENTAL +** +** Every [virtual table module] implementation uses a subclass +** of the following structure to describe a particular instance +** of the [virtual table]. Each subclass will +** be tailored to the specific needs of the module implementation. +** The purpose of this superclass is to define certain fields that are +** common to all module implementations. +** +** Virtual tables methods can set an error message by assigning a +** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should +** take care that any prior string is freed by a call to [sqlite3_free()] +** prior to assigning a new string to zErrMsg. After the error message +** is delivered up to the client application, the string will be automatically +** freed by sqlite3_free() and the zErrMsg field will be zeroed. +*/ +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* Used internally */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Virtual Table Cursor Object {H18020} +** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} +** EXPERIMENTAL +** +** Every [virtual table module] implementation uses a subclass of the +** following structure to describe cursors that point into the +** [virtual table] and are used +** to loop through the virtual table. Cursors are created using the +** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed +** by the [sqlite3_module.xClose | xClose] method. Cussors are used +** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods +** of the module. Each module implementation will define +** the content of a cursor structure to suit its own needs. +** +** This superclass exists in order to define fields of the cursor that +** are common to all implementations. +*/ +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Declare The Schema Of A Virtual Table {H18280} +** EXPERIMENTAL +** +** The [xCreate] and [xConnect] methods of a +** [virtual table module] call this interface +** to declare the format (the names and datatypes of the columns) of +** the virtual tables they implement. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_declare_vtab(sqlite3*, const char *zSQL); + +/* +** CAPI3REF: Overload A Function For A Virtual Table {H18300} +** EXPERIMENTAL +** +** Virtual tables can provide alternative implementations of functions +** using the [xFindFunction] method of the [virtual table module]. +** But global versions of those functions +** must exist in order to be overloaded. +** +** This API makes sure a global version of a function with a particular +** name and number of parameters exists. If no such function exists +** before this API is called, a new function is created. The implementation +** of the new function always causes an exception to be thrown. So +** the new function is not good for anything by itself. Its only +** purpose is to be a placeholder function that can be overloaded +** by a [virtual table]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/* +** The interface to the virtual-table mechanism defined above (back up +** to a comment remarkably similar to this one) is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +** +****** EXPERIMENTAL - subject to change without notice ************** +*/ + +/* +** CAPI3REF: A Handle To An Open BLOB {H17800} +** KEYWORDS: {BLOB handle} {BLOB handles} +** +** An instance of this object represents an open BLOB on which +** [sqlite3_blob_open | incremental BLOB I/O] can be performed. +** Objects of this type are created by [sqlite3_blob_open()] +** and destroyed by [sqlite3_blob_close()]. +** The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces +** can be used to read or write small subsections of the BLOB. +** The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. +*/ +typedef struct sqlite3_blob sqlite3_blob; + +/* +** CAPI3REF: Open A BLOB For Incremental I/O {H17810} +** +** This interfaces opens a [BLOB handle | handle] to the BLOB located +** in row iRow, column zColumn, table zTable in database zDb; +** in other words, the same BLOB that would be selected by: +** +**
+**     SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
+** 
{END} +** +** If the flags parameter is non-zero, then the BLOB is opened for read +** and write access. If it is zero, the BLOB is opened for read access. +** +** Note that the database name is not the filename that contains +** the database but rather the symbolic name of the database that +** is assigned when the database is connected using [ATTACH]. +** For the main database file, the database name is "main". +** For TEMP tables, the database name is "temp". +** +** On success, [SQLITE_OK] is returned and the new [BLOB handle] is written +** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set +** to be a null pointer. +** This function sets the [database connection] error code and message +** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related +** functions. Note that the *ppBlob variable is always initialized in a +** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob +** regardless of the success or failure of this routine. +** +** If the row that a BLOB handle points to is modified by an +** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects +** then the BLOB handle is marked as "expired". +** This is true if any column of the row is changed, even a column +** other than the one the BLOB handle is open on. +** Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for +** a expired BLOB handle fail with an return code of [SQLITE_ABORT]. +** Changes written into a BLOB prior to the BLOB expiring are not +** rollback by the expiration of the BLOB. Such changes will eventually +** commit if the transaction continues to completion. +** +** Use the [sqlite3_blob_bytes()] interface to determine the size of +** the opened blob. The size of a blob may not be changed by this +** underface. Use the [UPDATE] SQL command to change the size of a +** blob. +** +** The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces +** and the built-in [zeroblob] SQL function can be used, if desired, +** to create an empty, zero-filled blob in which to read or write using +** this interface. +** +** To avoid a resource leak, every open [BLOB handle] should eventually +** be released by a call to [sqlite3_blob_close()]. +** +** Requirements: +** [H17813] [H17814] [H17816] [H17819] [H17821] [H17824] +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/* +** CAPI3REF: Close A BLOB Handle {H17830} +** +** Closes an open [BLOB handle]. +** +** Closing a BLOB shall cause the current transaction to commit +** if there are no other BLOBs, no pending prepared statements, and the +** database connection is in [autocommit mode]. +** If any writes were made to the BLOB, they might be held in cache +** until the close operation if they will fit. +** +** Closing the BLOB often forces the changes +** out to disk and so if any I/O errors occur, they will likely occur +** at the time when the BLOB is closed. Any errors that occur during +** closing are reported as a non-zero return value. +** +** The BLOB is closed unconditionally. Even if this routine returns +** an error code, the BLOB is still closed. +** +** Calling this routine with a null pointer (which as would be returned +** by failed call to [sqlite3_blob_open()]) is a harmless no-op. +** +** Requirements: +** [H17833] [H17836] [H17839] +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); + +/* +** CAPI3REF: Return The Size Of An Open BLOB {H17840} +** +** Returns the size in bytes of the BLOB accessible via the +** successfully opened [BLOB handle] in its only argument. The +** incremental blob I/O routines can only read or overwriting existing +** blob content; they cannot change the size of a blob. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** Requirements: +** [H17843] +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); + +/* +** CAPI3REF: Read Data From A BLOB Incrementally {H17850} +** +** This function is used to read data from an open [BLOB handle] into a +** caller-supplied buffer. N bytes of data are copied into buffer Z +** from the open BLOB, starting at offset iOffset. +** +** If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is read. If N or iOffset is +** less than zero, [SQLITE_ERROR] is returned and no data is read. +** The size of the blob (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** An attempt to read from an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. +** +** On success, SQLITE_OK is returned. +** Otherwise, an [error code] or an [extended error code] is returned. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_write()]. +** +** Requirements: +** [H17853] [H17856] [H17859] [H17862] [H17863] [H17865] [H17868] +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/* +** CAPI3REF: Write Data Into A BLOB Incrementally {H17870} +** +** This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset. +** +** If the [BLOB handle] passed as the first argument was not opened for +** writing (the flags parameter to [sqlite3_blob_open()] was zero), +** this function returns [SQLITE_READONLY]. +** +** This function may only modify the contents of the BLOB; it is +** not possible to increase the size of a BLOB using this API. +** If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is written. If N is +** less than zero [SQLITE_ERROR] is returned and no data is written. +** The size of the BLOB (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** An attempt to write to an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. Writes to the BLOB that occurred +** before the [BLOB handle] expired are not rolled back by the +** expiration of the handle, though of course those changes might +** have been overwritten by the statement that expired the BLOB handle +** or by other independent statements. +** +** On success, SQLITE_OK is returned. +** Otherwise, an [error code] or an [extended error code] is returned. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_read()]. +** +** Requirements: +** [H17873] [H17874] [H17875] [H17876] [H17877] [H17879] [H17882] [H17885] +** [H17888] +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/* +** CAPI3REF: Virtual File System Objects {H11200} +** +** A virtual filesystem (VFS) is an [sqlite3_vfs] object +** that SQLite uses to interact +** with the underlying operating system. Most SQLite builds come with a +** single default VFS that is appropriate for the host computer. +** New VFSes can be registered and existing VFSes can be unregistered. +** The following interfaces are provided. +** +** The sqlite3_vfs_find() interface returns a pointer to a VFS given its name. +** Names are case sensitive. +** Names are zero-terminated UTF-8 strings. +** If there is no match, a NULL pointer is returned. +** If zVfsName is NULL then the default VFS is returned. +** +** New VFSes are registered with sqlite3_vfs_register(). +** Each new VFS becomes the default VFS if the makeDflt flag is set. +** The same VFS can be registered multiple times without injury. +** To make an existing VFS into the default VFS, register it again +** with the makeDflt flag set. If two different VFSes with the +** same name are registered, the behavior is undefined. If a +** VFS is registered with a name that is NULL or an empty string, +** then the behavior is undefined. +** +** Unregister a VFS with the sqlite3_vfs_unregister() interface. +** If the default VFS is unregistered, another VFS is chosen as +** the default. The choice for the new VFS is arbitrary. +** +** Requirements: +** [H11203] [H11206] [H11209] [H11212] [H11215] [H11218] +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); + +/* +** CAPI3REF: Mutexes {H17000} +** +** The SQLite core uses these routines for thread +** synchronization. Though they are intended for internal +** use by SQLite, code that links against SQLite is +** permitted to use any of these routines. +** +** The SQLite source code contains multiple implementations +** of these mutex routines. An appropriate implementation +** is selected automatically at compile-time. The following +** implementations are available in the SQLite core: +** +**
    +**
  • SQLITE_MUTEX_OS2 +**
  • SQLITE_MUTEX_PTHREAD +**
  • SQLITE_MUTEX_W32 +**
  • SQLITE_MUTEX_NOOP +**
+** +** The SQLITE_MUTEX_NOOP implementation is a set of routines +** that does no real locking and is appropriate for use in +** a single-threaded application. The SQLITE_MUTEX_OS2, +** SQLITE_MUTEX_PTHREAD, and SQLITE_MUTEX_W32 implementations +** are appropriate for use on OS/2, Unix, and Windows. +** +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex +** implementation is included with the library. In this case the +** application must supply a custom mutex implementation using the +** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function +** before calling sqlite3_initialize() or any other public sqlite3_ +** function that calls sqlite3_initialize(). +** +** {H17011} The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. {H17012} If it returns NULL +** that means that a mutex could not be allocated. {H17013} SQLite +** will unwind its stack and return an error. {H17014} The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +**
    +**
  • SQLITE_MUTEX_FAST +**
  • SQLITE_MUTEX_RECURSIVE +**
  • SQLITE_MUTEX_STATIC_MASTER +**
  • SQLITE_MUTEX_STATIC_MEM +**
  • SQLITE_MUTEX_STATIC_MEM2 +**
  • SQLITE_MUTEX_STATIC_PRNG +**
  • SQLITE_MUTEX_STATIC_LRU +**
  • SQLITE_MUTEX_STATIC_LRU2 +**
+** +** {H17015} The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. {END} +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. {H17016} But SQLite will only request a recursive mutex in +** cases where it really needs one. {END} If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** {H17017} The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. {END} Four static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** {H17018} Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. {H17034} But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +** +** {H17019} The sqlite3_mutex_free() routine deallocates a previously +** allocated dynamic mutex. {H17020} SQLite is careful to deallocate every +** dynamic mutex that it allocates. {A17021} The dynamic mutexes must not be in +** use when they are deallocated. {A17022} Attempting to deallocate a static +** mutex results in undefined behavior. {H17023} SQLite never deallocates +** a static mutex. {END} +** +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. {H17024} If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. {H17025} The sqlite3_mutex_try() interface returns [SQLITE_OK] +** upon successful entry. {H17026} Mutexes created using +** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. +** {H17027} In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. {A17028} If the same thread tries to enter any other +** kind of mutex more than once, the behavior is undefined. +** {H17029} SQLite will never exhibit +** such behavior in its own use of mutexes. +** +** Some systems (for example, Windows 95) do not support the operation +** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() +** will always return SQLITE_BUSY. {H17030} The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable behavior. +** +** {H17031} The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. {A17032} The behavior +** is undefined if the mutex is not currently entered by the +** calling thread or is not currently allocated. {H17033} SQLite will +** never do either. {END} +** +** If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or +** sqlite3_mutex_leave() is a NULL pointer, then all three routines +** behave as no-ops. +** +** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Methods Object {H17120} +** EXPERIMENTAL +** +** An instance of this structure defines the low-level routines +** used to allocate and use mutexes. +** +** Usually, the default mutex implementations provided by SQLite are +** sufficient, however the user has the option of substituting a custom +** implementation for specialized deployments or systems for which SQLite +** does not provide a suitable implementation. In this case, the user +** creates and populates an instance of this structure to pass +** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. +** Additionally, an instance of this structure can be used as an +** output variable when querying the system for the current mutex +** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. +** +** The xMutexInit method defined by this structure is invoked as +** part of system initialization by the sqlite3_initialize() function. +** {H17001} The xMutexInit routine shall be called by SQLite once for each +** effective call to [sqlite3_initialize()]. +** +** The xMutexEnd method defined by this structure is invoked as +** part of system shutdown by the sqlite3_shutdown() function. The +** implementation of this method is expected to release all outstanding +** resources obtained by the mutex methods implementation, especially +** those obtained by the xMutexInit method. {H17003} The xMutexEnd() +** interface shall be invoked once for each call to [sqlite3_shutdown()]. +** +** The remaining seven methods defined by this structure (xMutexAlloc, +** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and +** xMutexNotheld) implement the following interfaces (respectively): +** +**
    +**
  • [sqlite3_mutex_alloc()]
  • +**
  • [sqlite3_mutex_free()]
  • +**
  • [sqlite3_mutex_enter()]
  • +**
  • [sqlite3_mutex_try()]
  • +**
  • [sqlite3_mutex_leave()]
  • +**
  • [sqlite3_mutex_held()]
  • +**
  • [sqlite3_mutex_notheld()]
  • +**
+** +** The only difference is that the public sqlite3_XXX functions enumerated +** above silently ignore any invocations that pass a NULL pointer instead +** of a valid mutex handle. The implementations of the methods defined +** by this structure are not required to handle this case, the results +** of passing a NULL pointer instead of a valid mutex handle are undefined +** (i.e. it is acceptable to provide an implementation that segfaults if +** it is passed a NULL pointer). +*/ +typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; +struct sqlite3_mutex_methods { + int (*xMutexInit)(void); + int (*xMutexEnd)(void); + sqlite3_mutex *(*xMutexAlloc)(int); + void (*xMutexFree)(sqlite3_mutex *); + void (*xMutexEnter)(sqlite3_mutex *); + int (*xMutexTry)(sqlite3_mutex *); + void (*xMutexLeave)(sqlite3_mutex *); + int (*xMutexHeld)(sqlite3_mutex *); + int (*xMutexNotheld)(sqlite3_mutex *); +}; + +/* +** CAPI3REF: Mutex Verification Routines {H17080} +** +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines +** are intended for use inside assert() statements. {H17081} The SQLite core +** never uses these routines except inside an assert() and applications +** are advised to follow the lead of the core. {H17082} The core only +** provides implementations for these routines when it is compiled +** with the SQLITE_DEBUG flag. {A17087} External mutex implementations +** are only required to provide these routines if SQLITE_DEBUG is +** defined and if NDEBUG is not defined. +** +** {H17083} These routines should return true if the mutex in their argument +** is held or not held, respectively, by the calling thread. +** +** {X17084} The implementation is not required to provided versions of these +** routines that actually work. If the implementation does not provide working +** versions of these routines, it should at least provide stubs that always +** return true so that one does not get spurious assertion failures. +** +** {H17085} If the argument to sqlite3_mutex_held() is a NULL pointer then +** the routine should return 1. {END} This seems counter-intuitive since +** clearly the mutex cannot be held if it does not exist. But the +** the reason the mutex does not exist is because the build is not +** using mutexes. And we do not want the assert() containing the +** call to sqlite3_mutex_held() to fail, so a non-zero return is +** the appropriate thing to do. {H17086} The sqlite3_mutex_notheld() +** interface should also return 1 when given a NULL pointer. +*/ +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Types {H17001} +** +** The [sqlite3_mutex_alloc()] interface takes a single argument +** which is one of these integer constants. +** +** The set of static mutexes may change from one SQLite release to the +** next. Applications that override the built-in mutex logic must be +** prepared to accommodate additional static mutexes. +*/ +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MASTER 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* lru page list */ + +/* +** CAPI3REF: Retrieve the mutex for a database connection {H17002} +** +** This interface returns a pointer the [sqlite3_mutex] object that +** serializes access to the [database connection] given in the argument +** when the [threading mode] is Serialized. +** If the [threading mode] is Single-thread or Multi-thread then this +** routine returns a NULL pointer. +*/ +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); + +/* +** CAPI3REF: Low-Level Control Of Database Files {H11300} +** +** {H11301} The [sqlite3_file_control()] interface makes a direct call to the +** xFileControl method for the [sqlite3_io_methods] object associated +** with a particular database identified by the second argument. {H11302} The +** name of the database is the name assigned to the database by the +** ATTACH SQL command that opened the +** database. {H11303} To control the main database file, use the name "main" +** or a NULL pointer. {H11304} The third and fourth parameters to this routine +** are passed directly through to the second and third parameters of +** the xFileControl method. {H11305} The return value of the xFileControl +** method becomes the return value of this routine. +** +** {H11306} If the second parameter (zDbName) does not match the name of any +** open database file, then SQLITE_ERROR is returned. {H11307} This error +** code is not remembered and will not be recalled by [sqlite3_errcode()] +** or [sqlite3_errmsg()]. {A11308} The underlying xFileControl method might +** also return SQLITE_ERROR. {A11309} There is no way to distinguish between +** an incorrect zDbName and an SQLITE_ERROR return from the underlying +** xFileControl method. {END} +** +** See also: [SQLITE_FCNTL_LOCKSTATE] +*/ +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/* +** CAPI3REF: Testing Interface {H11400} +** +** The sqlite3_test_control() interface is used to read out internal +** state of SQLite and to inject faults into SQLite for testing +** purposes. The first parameter is an operation code that determines +** the number, meaning, and operation of all subsequent parameters. +** +** This interface is not for use by applications. It exists solely +** for verifying the correct operation of the SQLite library. Depending +** on how the SQLite library is compiled, this interface might not exist. +** +** The details of the operation codes, their meanings, the parameters +** they take, and what they do are all subject to change without notice. +** Unlike most of the SQLite API, this function is not guaranteed to +** operate consistently from one release to the next. +*/ +SQLITE_API int sqlite3_test_control(int op, ...); + +/* +** CAPI3REF: Testing Interface Operation Codes {H11410} +** +** These constants are the valid operation code parameters used +** as the first argument to [sqlite3_test_control()]. +** +** These parameters and their meanings are subject to change +** without notice. These values are for testing purposes only. +** Applications should not use any of these parameters or the +** [sqlite3_test_control()] interface. +*/ +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 +#define SQLITE_TESTCTRL_BITVEC_TEST 8 +#define SQLITE_TESTCTRL_FAULT_INSTALL 9 +#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 +#define SQLITE_TESTCTRL_PENDING_BYTE 11 +#define SQLITE_TESTCTRL_ASSERT 12 +#define SQLITE_TESTCTRL_ALWAYS 13 + +/* +** CAPI3REF: SQLite Runtime Status {H17200} +** EXPERIMENTAL +** +** This interface is used to retrieve runtime status information +** about the preformance of SQLite, and optionally to reset various +** highwater marks. The first argument is an integer code for +** the specific parameter to measure. Recognized integer codes +** are of the form [SQLITE_STATUS_MEMORY_USED | SQLITE_STATUS_...]. +** The current value of the parameter is returned into *pCurrent. +** The highest recorded value is returned in *pHighwater. If the +** resetFlag is true, then the highest record value is reset after +** *pHighwater is written. Some parameters do not record the highest +** value. For those parameters +** nothing is written into *pHighwater and the resetFlag is ignored. +** Other parameters record only the highwater mark and not the current +** value. For these latter parameters nothing is written into *pCurrent. +** +** This routine returns SQLITE_OK on success and a non-zero +** [error code] on failure. +** +** This routine is threadsafe but is not atomic. This routine can +** called while other threads are running the same or different SQLite +** interfaces. However the values returned in *pCurrent and +** *pHighwater reflect the status of SQLite at different points in time +** and it is possible that another thread might change the parameter +** in between the times when *pCurrent and *pHighwater are written. +** +** See also: [sqlite3_db_status()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); + + +/* +** CAPI3REF: Status Parameters {H17250} +** EXPERIMENTAL +** +** These integer constants designate various run-time status parameters +** that can be returned by [sqlite3_status()]. +** +**
+**
SQLITE_STATUS_MEMORY_USED
+**
This parameter is the current amount of memory checked out +** using [sqlite3_malloc()], either directly or indirectly. The +** figure includes calls made to [sqlite3_malloc()] by the application +** and internal memory usage by the SQLite library. Scratch memory +** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache +** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in +** this parameter. The amount returned is the sum of the allocation +** sizes as reported by the xSize method in [sqlite3_mem_methods].
+** +**
SQLITE_STATUS_MALLOC_SIZE
+**
This parameter records the largest memory allocation request +** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their +** internal equivalents). Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_PAGECACHE_USED
+**
This parameter returns the number of pages used out of the +** [pagecache memory allocator] that was configured using +** [SQLITE_CONFIG_PAGECACHE]. The +** value returned is in pages, not in bytes.
+** +**
SQLITE_STATUS_PAGECACHE_OVERFLOW
+**
This parameter returns the number of bytes of page cache +** allocation which could not be statisfied by the [SQLITE_CONFIG_PAGECACHE] +** buffer and where forced to overflow to [sqlite3_malloc()]. The +** returned value includes allocations that overflowed because they +** where too large (they were larger than the "sz" parameter to +** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because +** no space was left in the page cache.
+** +**
SQLITE_STATUS_PAGECACHE_SIZE
+**
This parameter records the largest memory allocation request +** handed to [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_SCRATCH_USED
+**
This parameter returns the number of allocations used out of the +** [scratch memory allocator] configured using +** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not +** in bytes. Since a single thread may only have one scratch allocation +** outstanding at time, this parameter also reports the number of threads +** using scratch memory at the same time.
+** +**
SQLITE_STATUS_SCRATCH_OVERFLOW
+**
This parameter returns the number of bytes of scratch memory +** allocation which could not be statisfied by the [SQLITE_CONFIG_SCRATCH] +** buffer and where forced to overflow to [sqlite3_malloc()]. The values +** returned include overflows because the requested allocation was too +** larger (that is, because the requested allocation was larger than the +** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer +** slots were available. +**
+** +**
SQLITE_STATUS_SCRATCH_SIZE
+**
This parameter records the largest memory allocation request +** handed to [scratch memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_PARSER_STACK
+**
This parameter records the deepest parser stack. It is only +** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
+**
+** +** New status parameters may be added from time to time. +*/ +#define SQLITE_STATUS_MEMORY_USED 0 +#define SQLITE_STATUS_PAGECACHE_USED 1 +#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 +#define SQLITE_STATUS_SCRATCH_USED 3 +#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 +#define SQLITE_STATUS_MALLOC_SIZE 5 +#define SQLITE_STATUS_PARSER_STACK 6 +#define SQLITE_STATUS_PAGECACHE_SIZE 7 +#define SQLITE_STATUS_SCRATCH_SIZE 8 + +/* +** CAPI3REF: Database Connection Status {H17500} +** EXPERIMENTAL +** +** This interface is used to retrieve runtime status information +** about a single [database connection]. The first argument is the +** database connection object to be interrogated. The second argument +** is the parameter to interrogate. Currently, the only allowed value +** for the second parameter is [SQLITE_DBSTATUS_LOOKASIDE_USED]. +** Additional options will likely appear in future releases of SQLite. +** +** The current value of the requested parameter is written into *pCur +** and the highest instantaneous value is written into *pHiwtr. If +** the resetFlg is true, then the highest instantaneous value is +** reset back down to the current value. +** +** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); + +/* +** CAPI3REF: Status Parameters for database connections {H17520} +** EXPERIMENTAL +** +** Status verbs for [sqlite3_db_status()]. +** +**
+**
SQLITE_DBSTATUS_LOOKASIDE_USED
+**
This parameter returns the number of lookaside memory slots currently +** checked out.
+**
+*/ +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 + + +/* +** CAPI3REF: Prepared Statement Status {H17550} +** EXPERIMENTAL +** +** Each prepared statement maintains various +** [SQLITE_STMTSTATUS_SORT | counters] that measure the number +** of times it has performed specific operations. These counters can +** be used to monitor the performance characteristics of the prepared +** statements. For example, if the number of table steps greatly exceeds +** the number of table searches or result rows, that would tend to indicate +** that the prepared statement is using a full table scan rather than +** an index. +** +** This interface is used to retrieve and reset counter values from +** a [prepared statement]. The first argument is the prepared statement +** object to be interrogated. The second argument +** is an integer code for a specific [SQLITE_STMTSTATUS_SORT | counter] +** to be interrogated. +** The current value of the requested counter is returned. +** If the resetFlg is true, then the counter is reset to zero after this +** interface call returns. +** +** See also: [sqlite3_status()] and [sqlite3_db_status()]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); + +/* +** CAPI3REF: Status Parameters for prepared statements {H17570} +** EXPERIMENTAL +** +** These preprocessor macros define integer codes that name counter +** values associated with the [sqlite3_stmt_status()] interface. +** The meanings of the various counters are as follows: +** +**
+**
SQLITE_STMTSTATUS_FULLSCAN_STEP
+**
This is the number of times that SQLite has stepped forward in +** a table as part of a full table scan. Large numbers for this counter +** may indicate opportunities for performance improvement through +** careful use of indices.
+** +**
SQLITE_STMTSTATUS_SORT
+**
This is the number of sort operations that have occurred. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance through careful use of indices.
+** +**
+*/ +#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 +#define SQLITE_STMTSTATUS_SORT 2 + +/* +** CAPI3REF: Custom Page Cache Object +** EXPERIMENTAL +** +** The sqlite3_pcache type is opaque. It is implemented by +** the pluggable module. The SQLite core has no knowledge of +** its size or internal structure and never deals with the +** sqlite3_pcache object except by holding and passing pointers +** to the object. +** +** See [sqlite3_pcache_methods] for additional information. +*/ +typedef struct sqlite3_pcache sqlite3_pcache; + +/* +** CAPI3REF: Application Defined Page Cache. +** EXPERIMENTAL +** +** The [sqlite3_config]([SQLITE_CONFIG_PCACHE], ...) interface can +** register an alternative page cache implementation by passing in an +** instance of the sqlite3_pcache_methods structure. The majority of the +** heap memory used by sqlite is used by the page cache to cache data read +** from, or ready to be written to, the database file. By implementing a +** custom page cache using this API, an application can control more +** precisely the amount of memory consumed by sqlite, the way in which +** said memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for +** how long. +** +** The contents of the structure are copied to an internal buffer by sqlite +** within the call to [sqlite3_config]. +** +** The xInit() method is called once for each call to [sqlite3_initialize()] +** (usually only once during the lifetime of the process). It is passed +** a copy of the sqlite3_pcache_methods.pArg value. It can be used to set +** up global structures and mutexes required by the custom page cache +** implementation. The xShutdown() method is called from within +** [sqlite3_shutdown()], if the application invokes this API. It can be used +** to clean up any outstanding resources before process shutdown, if required. +** +** The xCreate() method is used to construct a new cache instance. The +** first parameter, szPage, is the size in bytes of the pages that must +** be allocated by the cache. szPage will not be a power of two. The +** second argument, bPurgeable, is true if the cache being created will +** be used to cache database pages read from a file stored on disk, or +** false if it is used for an in-memory database. The cache implementation +** does not have to do anything special based on the value of bPurgeable, +** it is purely advisory. +** +** The xCachesize() method may be called at any time by SQLite to set the +** suggested maximum cache-size (number of pages stored by) the cache +** instance passed as the first argument. This is the value configured using +** the SQLite "[PRAGMA cache_size]" command. As with the bPurgeable parameter, +** the implementation is not required to do anything special with this +** value, it is advisory only. +** +** The xPagecount() method should return the number of pages currently +** stored in the cache supplied as an argument. +** +** The xFetch() method is used to fetch a page and return a pointer to it. +** A 'page', in this context, is a buffer of szPage bytes aligned at an +** 8-byte boundary. The page to be fetched is determined by the key. The +** mimimum key value is 1. After it has been retrieved using xFetch, the page +** is considered to be pinned. +** +** If the requested page is already in the page cache, then a pointer to +** the cached buffer should be returned with its contents intact. If the +** page is not already in the cache, then the expected behaviour of the +** cache is determined by the value of the createFlag parameter passed +** to xFetch, according to the following table: +** +** +**
createFlagExpected Behaviour +**
0NULL should be returned. No new cache entry is created. +**
1If createFlag is set to 1, this indicates that +** SQLite is holding pinned pages that can be unpinned +** by writing their contents to the database file (a +** relatively expensive operation). In this situation the +** cache implementation has two choices: it can return NULL, +** in which case SQLite will attempt to unpin one or more +** pages before re-requesting the same page, or it can +** allocate a new page and return a pointer to it. If a new +** page is allocated, then the first sizeof(void*) bytes of +** it (at least) must be zeroed before it is returned. +**
2If createFlag is set to 2, then SQLite is not holding any +** pinned pages associated with the specific cache passed +** as the first argument to xFetch() that can be unpinned. The +** cache implementation should attempt to allocate a new +** cache entry and return a pointer to it. Again, the first +** sizeof(void*) bytes of the page should be zeroed before +** it is returned. If the xFetch() method returns NULL when +** createFlag==2, SQLite assumes that a memory allocation +** failed and returns SQLITE_NOMEM to the user. +**
+** +** xUnpin() is called by SQLite with a pointer to a currently pinned page +** as its second argument. If the third parameter, discard, is non-zero, +** then the page should be evicted from the cache. In this case SQLite +** assumes that the next time the page is retrieved from the cache using +** the xFetch() method, it will be zeroed. If the discard parameter is +** zero, then the page is considered to be unpinned. The cache implementation +** may choose to reclaim (free or recycle) unpinned pages at any time. +** SQLite assumes that next time the page is retrieved from the cache +** it will either be zeroed, or contain the same data that it did when it +** was unpinned. +** +** The cache is not required to perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls +** to xFetch(). +** +** The xRekey() method is used to change the key value associated with the +** page passed as the second argument from oldKey to newKey. If the cache +** previously contains an entry associated with newKey, it should be +** discarded. Any prior cache entry associated with newKey is guaranteed not +** to be pinned. +** +** When SQLite calls the xTruncate() method, the cache must discard all +** existing cache entries with page numbers (keys) greater than or equal +** to the value of the iLimit parameter passed to xTruncate(). If any +** of these pages are pinned, they are implicitly unpinned, meaning that +** they can be safely discarded. +** +** The xDestroy() method is used to delete a cache allocated by xCreate(). +** All resources associated with the specified cache should be freed. After +** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] +** handle invalid, and will not use it with any other sqlite3_pcache_methods +** functions. +*/ +typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; +struct sqlite3_pcache_methods { + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, void*, int discard); + void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); +}; + +/* +** CAPI3REF: Online Backup Object +** EXPERIMENTAL +** +** The sqlite3_backup object records state information about an ongoing +** online backup operation. The sqlite3_backup object is created by +** a call to [sqlite3_backup_init()] and is destroyed by a call to +** [sqlite3_backup_finish()]. +** +** See Also: [Using the SQLite Online Backup API] +*/ +typedef struct sqlite3_backup sqlite3_backup; + +/* +** CAPI3REF: Online Backup API. +** EXPERIMENTAL +** +** This API is used to overwrite the contents of one database with that +** of another. It is useful either for creating backups of databases or +** for copying in-memory databases to or from persistent files. +** +** See Also: [Using the SQLite Online Backup API] +** +** Exclusive access is required to the destination database for the +** duration of the operation. However the source database is only +** read-locked while it is actually being read, it is not locked +** continuously for the entire operation. Thus, the backup may be +** performed on a live database without preventing other users from +** writing to the database for an extended period of time. +** +** To perform a backup operation: +**
    +**
  1. sqlite3_backup_init() is called once to initialize the +** backup, +**
  2. sqlite3_backup_step() is called one or more times to transfer +** the data between the two databases, and finally +**
  3. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. +**
+** There should be exactly one call to sqlite3_backup_finish() for each +** successful call to sqlite3_backup_init(). +** +** sqlite3_backup_init() +** +** The first two arguments passed to [sqlite3_backup_init()] are the database +** handle associated with the destination database and the database name +** used to attach the destination database to the handle. The database name +** is "main" for the main database, "temp" for the temporary database, or +** the name specified as part of the [ATTACH] statement if the destination is +** an attached database. The third and fourth arguments passed to +** sqlite3_backup_init() identify the [database connection] +** and database name used +** to access the source database. The values passed for the source and +** destination [database connection] parameters must not be the same. +** +** If an error occurs within sqlite3_backup_init(), then NULL is returned +** and an error code and error message written into the [database connection] +** passed as the first argument. They may be retrieved using the +** [sqlite3_errcode()], [sqlite3_errmsg()], and [sqlite3_errmsg16()] functions. +** Otherwise, if successful, a pointer to an [sqlite3_backup] object is +** returned. This pointer may be used with the sqlite3_backup_step() and +** sqlite3_backup_finish() functions to perform the specified backup +** operation. +** +** sqlite3_backup_step() +** +** Function [sqlite3_backup_step()] is used to copy up to nPage pages between +** the source and destination databases, where nPage is the value of the +** second parameter passed to sqlite3_backup_step(). If nPage is a negative +** value, all remaining source pages are copied. If the required pages are +** succesfully copied, but there are still more pages to copy before the +** backup is complete, it returns [SQLITE_OK]. If no error occured and there +** are no more pages to copy, then [SQLITE_DONE] is returned. If an error +** occurs, then an SQLite error code is returned. As well as [SQLITE_OK] and +** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY], +** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code. +** +** As well as the case where the destination database file was opened for +** read-only access, sqlite3_backup_step() may return [SQLITE_READONLY] if +** the destination is an in-memory database with a different page size +** from the source database. +** +** If sqlite3_backup_step() cannot obtain a required file-system lock, then +** the [sqlite3_busy_handler | busy-handler function] +** is invoked (if one is specified). If the +** busy-handler returns non-zero before the lock is available, then +** [SQLITE_BUSY] is returned to the caller. In this case the call to +** sqlite3_backup_step() can be retried later. If the source +** [database connection] +** is being used to write to the source database when sqlite3_backup_step() +** is called, then [SQLITE_LOCKED] is returned immediately. Again, in this +** case the call to sqlite3_backup_step() can be retried later on. If +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal. At this point the application must accept +** that the backup operation has failed and pass the backup operation handle +** to the sqlite3_backup_finish() to release associated resources. +** +** Following the first call to sqlite3_backup_step(), an exclusive lock is +** obtained on the destination file. It is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete +** and sqlite3_backup_step() returns [SQLITE_DONE]. Additionally, each time +** a call to sqlite3_backup_step() is made a [shared lock] is obtained on +** the source database file. This lock is released before the +** sqlite3_backup_step() call returns. Because the source database is not +** locked between calls to sqlite3_backup_step(), it may be modified mid-way +** through the backup procedure. If the source database is modified by an +** external process or via a database connection other than the one being +** used by the backup operation, then the backup will be transparently +** restarted by the next call to sqlite3_backup_step(). If the source +** database is modified by the using the same database connection as is used +** by the backup operation, then the backup database is transparently +** updated at the same time. +** +** sqlite3_backup_finish() +** +** Once sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** application wishes to abandon the backup operation, the [sqlite3_backup] +** object should be passed to sqlite3_backup_finish(). This releases all +** resources associated with the backup operation. If sqlite3_backup_step() +** has not yet returned [SQLITE_DONE], then any active write-transaction on the +** destination database is rolled back. The [sqlite3_backup] object is invalid +** and may not be used following a call to sqlite3_backup_finish(). +** +** The value returned by sqlite3_backup_finish is [SQLITE_OK] if no error +** occurred, regardless or whether or not sqlite3_backup_step() was called +** a sufficient number of times to complete the backup operation. Or, if +** an out-of-memory condition or IO error occured during a call to +** sqlite3_backup_step() then [SQLITE_NOMEM] or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] error code +** is returned. In this case the error code and an error message are +** written to the destination [database connection]. +** +** A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() is +** not a permanent error and does not affect the return value of +** sqlite3_backup_finish(). +** +** sqlite3_backup_remaining(), sqlite3_backup_pagecount() +** +** Each call to sqlite3_backup_step() sets two values stored internally +** by an [sqlite3_backup] object. The number of pages still to be backed +** up, which may be queried by sqlite3_backup_remaining(), and the total +** number of pages in the source database file, which may be queried by +** sqlite3_backup_pagecount(). +** +** The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified during a backup +** operation, then the values are not updated to account for any extra +** pages that need to be updated or the size of the source database file +** changing. +** +** Concurrent Usage of Database Handles +** +** The source [database connection] may be used by the application for other +** purposes while a backup operation is underway or being initialized. +** If SQLite is compiled and configured to support threadsafe database +** connections, then the source database connection may be used concurrently +** from within other threads. +** +** However, the application must guarantee that the destination database +** connection handle is not passed to any other API (by any thread) after +** sqlite3_backup_init() is called and before the corresponding call to +** sqlite3_backup_finish(). Unfortunately SQLite does not currently check +** for this, if the application does use the destination [database connection] +** for some other purpose during a backup operation, things may appear to +** work correctly but in fact be subtly malfunctioning. Use of the +** destination database connection while a backup is in progress might +** also cause a mutex deadlock. +** +** Furthermore, if running in [shared cache mode], the application must +** guarantee that the shared cache used by the destination database +** is not accessed while the backup is running. In practice this means +** that the application must guarantee that the file-system file being +** backed up to is not accessed by any connection within the process, +** not just the specific connection that was passed to sqlite3_backup_init(). +** +** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** threads may safely make multiple concurrent calls to sqlite3_backup_step(). +** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() +** APIs are not strictly speaking threadsafe. If they are invoked at the +** same time as another thread is invoking sqlite3_backup_step() it is +** possible that they return invalid values. +*/ +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /* Destination database handle */ + const char *zDestName, /* Destination database name */ + sqlite3 *pSource, /* Source database handle */ + const char *zSourceName /* Source database name */ +); +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); + +/* +** CAPI3REF: Unlock Notification +** EXPERIMENTAL +** +** When running in shared-cache mode, a database operation may fail with +** an [SQLITE_LOCKED] error if the required locks on the shared-cache or +** individual tables within the shared-cache cannot be obtained. See +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** This API may be used to register a callback that SQLite will invoke +** when the connection currently holding the required lock relinquishes it. +** This API is only available if the library was compiled with the +** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. +** +** See Also: [Using the SQLite Unlock Notification Feature]. +** +** Shared-cache locks are released when a database connection concludes +** its current transaction, either by committing it or rolling it back. +** +** When a connection (known as the blocked connection) fails to obtain a +** shared-cache lock and SQLITE_LOCKED is returned to the caller, the +** identity of the database connection (the blocking connection) that +** has locked the required resource is stored internally. After an +** application receives an SQLITE_LOCKED error, it may call the +** sqlite3_unlock_notify() method with the blocked connection handle as +** the first argument to register for a callback that will be invoked +** when the blocking connections current transaction is concluded. The +** callback is invoked from within the [sqlite3_step] or [sqlite3_close] +** call that concludes the blocking connections transaction. +** +** If sqlite3_unlock_notify() is called in a multi-threaded application, +** there is a chance that the blocking connection will have already +** concluded its transaction by the time sqlite3_unlock_notify() is invoked. +** If this happens, then the specified callback is invoked immediately, +** from within the call to sqlite3_unlock_notify(). +** +** If the blocked connection is attempting to obtain a write-lock on a +** shared-cache table, and more than one other connection currently holds +** a read-lock on the same table, then SQLite arbitrarily selects one of +** the other connections to use as the blocking connection. +** +** There may be at most one unlock-notify callback registered by a +** blocked connection. If sqlite3_unlock_notify() is called when the +** blocked connection already has a registered unlock-notify callback, +** then the new callback replaces the old. If sqlite3_unlock_notify() is +** called with a NULL pointer as its second argument, then any existing +** unlock-notify callback is cancelled. The blocked connections +** unlock-notify callback may also be canceled by closing the blocked +** connection using [sqlite3_close()]. +** +** The unlock-notify callback is not reentrant. If an application invokes +** any sqlite3_xxx API functions from within an unlock-notify callback, a +** crash or deadlock may be the result. +** +** Unless deadlock is detected (see below), sqlite3_unlock_notify() always +** returns SQLITE_OK. +** +** Callback Invocation Details +** +** When an unlock-notify callback is registered, the application provides a +** single void* pointer that is passed to the callback when it is invoked. +** However, the signature of the callback function allows SQLite to pass +** it an array of void* context pointers. The first argument passed to +** an unlock-notify callback is a pointer to an array of void* pointers, +** and the second is the number of entries in the array. +** +** When a blocking connections transaction is concluded, there may be +** more than one blocked connection that has registered for an unlock-notify +** callback. If two or more such blocked connections have specified the +** same callback function, then instead of invoking the callback function +** multiple times, it is invoked once with the set of void* context pointers +** specified by the blocked connections bundled together into an array. +** This gives the application an opportunity to prioritize any actions +** related to the set of unblocked database connections. +** +** Deadlock Detection +** +** Assuming that after registering for an unlock-notify callback a +** database waits for the callback to be issued before taking any further +** action (a reasonable assumption), then using this API may cause the +** application to deadlock. For example, if connection X is waiting for +** connection Y's transaction to be concluded, and similarly connection +** Y is waiting on connection X's transaction, then neither connection +** will proceed and the system may remain deadlocked indefinitely. +** +** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock +** detection. If a given call to sqlite3_unlock_notify() would put the +** system in a deadlocked state, then SQLITE_LOCKED is returned and no +** unlock-notify callback is registered. The system is said to be in +** a deadlocked state if connection A has registered for an unlock-notify +** callback on the conclusion of connection B's transaction, and connection +** B has itself registered for an unlock-notify callback when connection +** A's transaction is concluded. Indirect deadlock is also detected, so +** the system is also considered to be deadlocked if connection B has +** registered for an unlock-notify callback on the conclusion of connection +** C's transaction, where connection C is waiting on connection A. Any +** number of levels of indirection are allowed. +** +** The "DROP TABLE" Exception +** +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** always appropriate to call sqlite3_unlock_notify(). There is however, +** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, +** SQLite checks if there are any currently executing SELECT statements +** that belong to the same connection. If there are, SQLITE_LOCKED is +** returned. In this case there is no "blocking connection", so invoking +** sqlite3_unlock_notify() results in the unlock-notify callback being +** invoked immediately. If the application then re-attempts the "DROP TABLE" +** or "DROP INDEX" query, an infinite loop might be the result. +** +** One way around this problem is to check the extended error code returned +** by an sqlite3_step() call. If there is a blocking connection, then the +** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in +** the special "DROP TABLE/INDEX" case, the extended error code is just +** SQLITE_LOCKED. +*/ +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *pBlocked, /* Waiting connection */ + void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ + void *pNotifyArg /* Argument to pass to xNotify */ +); + +/* +** Undo the hack that converts floating point types to integer for +** builds on processors without floating point support. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# undef double +#endif + +#if 0 +} /* End of the 'extern "C"' block */ +#endif +#endif + +/************** End of sqlite3.h *********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include hash.h in the middle of sqliteInt.h ******************/ +/************** Begin file hash.h ********************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implemenation +** used in SQLite. +** +** $Id: hash.h,v 1.15 2009/05/02 13:29:38 drh Exp $ +*/ +#ifndef _SQLITE_HASH_H_ +#define _SQLITE_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, some of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +** +** All elements of the hash table are on a single doubly-linked list. +** Hash.first points to the head of this list. +** +** There are Hash.htsize buckets. Each bucket points to a spot in +** the global doubly-linked list. The contents of the bucket are the +** element pointed to plus the next _ht.count-1 elements in the list. +** +** Hash.htsize and Hash.ht may be zero. In that case lookup is done +** by a linear search of the global list. For small tables, the +** Hash.ht table is never allocated because if there are few elements +** in the table, it is faster to do a linear search than to manage +** the hash table. +*/ +struct Hash { + unsigned int htsize; /* Number of buckets in the hash table */ + unsigned int count; /* Number of entries in this table */ + HashElem *first; /* The first element of the array */ + struct _ht { /* the hash table */ + int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + const char *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +SQLITE_PRIVATE void sqlite3HashInit(Hash*); +SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, int nKey, void *pData); +SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey, int nKey); +SQLITE_PRIVATE void sqlite3HashClear(Hash*); + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ +** SomeStructure *pData = sqliteHashData(p); +** // do something with pData +** } +*/ +#define sqliteHashFirst(H) ((H)->first) +#define sqliteHashNext(E) ((E)->next) +#define sqliteHashData(E) ((E)->data) +/* #define sqliteHashKey(E) ((E)->pKey) // NOT USED */ +/* #define sqliteHashKeysize(E) ((E)->nKey) // NOT USED */ + +/* +** Number of entries in a hash table +*/ +/* #define sqliteHashCount(H) ((H)->count) // NOT USED */ + +#endif /* _SQLITE_HASH_H_ */ + +/************** End of hash.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include parse.h in the middle of sqliteInt.h *****************/ +/************** Begin file parse.h *******************************************/ +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_SAVEPOINT 13 +#define TK_RELEASE 14 +#define TK_TO 15 +#define TK_TABLE 16 +#define TK_CREATE 17 +#define TK_IF 18 +#define TK_NOT 19 +#define TK_EXISTS 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_COMMA 25 +#define TK_ID 26 +#define TK_INDEXED 27 +#define TK_ABORT 28 +#define TK_AFTER 29 +#define TK_ANALYZE 30 +#define TK_ASC 31 +#define TK_ATTACH 32 +#define TK_BEFORE 33 +#define TK_BY 34 +#define TK_CASCADE 35 +#define TK_CAST 36 +#define TK_COLUMNKW 37 +#define TK_CONFLICT 38 +#define TK_DATABASE 39 +#define TK_DESC 40 +#define TK_DETACH 41 +#define TK_EACH 42 +#define TK_FAIL 43 +#define TK_FOR 44 +#define TK_IGNORE 45 +#define TK_INITIALLY 46 +#define TK_INSTEAD 47 +#define TK_LIKE_KW 48 +#define TK_MATCH 49 +#define TK_KEY 50 +#define TK_OF 51 +#define TK_OFFSET 52 +#define TK_PRAGMA 53 +#define TK_RAISE 54 +#define TK_REPLACE 55 +#define TK_RESTRICT 56 +#define TK_ROW 57 +#define TK_TRIGGER 58 +#define TK_VACUUM 59 +#define TK_VIEW 60 +#define TK_VIRTUAL 61 +#define TK_REINDEX 62 +#define TK_RENAME 63 +#define TK_CTIME_KW 64 +#define TK_ANY 65 +#define TK_OR 66 +#define TK_AND 67 +#define TK_IS 68 +#define TK_BETWEEN 69 +#define TK_IN 70 +#define TK_ISNULL 71 +#define TK_NOTNULL 72 +#define TK_NE 73 +#define TK_EQ 74 +#define TK_GT 75 +#define TK_LE 76 +#define TK_LT 77 +#define TK_GE 78 +#define TK_ESCAPE 79 +#define TK_BITAND 80 +#define TK_BITOR 81 +#define TK_LSHIFT 82 +#define TK_RSHIFT 83 +#define TK_PLUS 84 +#define TK_MINUS 85 +#define TK_STAR 86 +#define TK_SLASH 87 +#define TK_REM 88 +#define TK_CONCAT 89 +#define TK_COLLATE 90 +#define TK_UMINUS 91 +#define TK_UPLUS 92 +#define TK_BITNOT 93 +#define TK_STRING 94 +#define TK_JOIN_KW 95 +#define TK_CONSTRAINT 96 +#define TK_DEFAULT 97 +#define TK_NULL 98 +#define TK_PRIMARY 99 +#define TK_UNIQUE 100 +#define TK_CHECK 101 +#define TK_REFERENCES 102 +#define TK_AUTOINCR 103 +#define TK_ON 104 +#define TK_DELETE 105 +#define TK_UPDATE 106 +#define TK_INSERT 107 +#define TK_SET 108 +#define TK_DEFERRABLE 109 +#define TK_FOREIGN 110 +#define TK_DROP 111 +#define TK_UNION 112 +#define TK_ALL 113 +#define TK_EXCEPT 114 +#define TK_INTERSECT 115 +#define TK_SELECT 116 +#define TK_DISTINCT 117 +#define TK_DOT 118 +#define TK_FROM 119 +#define TK_JOIN 120 +#define TK_USING 121 +#define TK_ORDER 122 +#define TK_GROUP 123 +#define TK_HAVING 124 +#define TK_LIMIT 125 +#define TK_WHERE 126 +#define TK_INTO 127 +#define TK_VALUES 128 +#define TK_INTEGER 129 +#define TK_FLOAT 130 +#define TK_BLOB 131 +#define TK_REGISTER 132 +#define TK_VARIABLE 133 +#define TK_CASE 134 +#define TK_WHEN 135 +#define TK_THEN 136 +#define TK_ELSE 137 +#define TK_INDEX 138 +#define TK_ALTER 139 +#define TK_ADD 140 +#define TK_TO_TEXT 141 +#define TK_TO_BLOB 142 +#define TK_TO_NUMERIC 143 +#define TK_TO_INT 144 +#define TK_TO_REAL 145 +#define TK_END_OF_FILE 146 +#define TK_ILLEGAL 147 +#define TK_SPACE 148 +#define TK_UNCLOSED_STRING 149 +#define TK_FUNCTION 150 +#define TK_COLUMN 151 +#define TK_AGG_FUNCTION 152 +#define TK_AGG_COLUMN 153 +#define TK_CONST_FUNC 154 + +/************** End of parse.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +#include +#include +#include +#include +#include + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite_int64 +# define LONGDOUBLE_TYPE sqlite_int64 +# ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (((sqlite3_int64)1)<<60) +# endif +# define SQLITE_OMIT_DATETIME_FUNCS 1 +# define SQLITE_OMIT_TRACE 1 +# undef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +# undef SQLITE_HAVE_ISNAN +#endif +#ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (1e99) +#endif + +/* +** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 +** afterward. Having this macro allows us to cause the C compiler +** to omit code used by TEMP tables without messy #ifndef statements. +*/ +#ifdef SQLITE_OMIT_TEMPDB +#define OMIT_TEMPDB 1 +#else +#define OMIT_TEMPDB 0 +#endif + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct when determining whether or not two entries are the same +** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL, +** OCELOT, and Firebird all work. The SQL92 spec explicitly says this +** is the way things are suppose to work. +** +** If the following macro is set to 0, the NULLs are indistinct for +** a UNIQUE index. In this mode, you can only have a single NULL entry +** for a column declared UNIQUE. This is the way Informix and SQL Server +** work. +*/ +#define NULL_DISTINCT_FOR_UNIQUE 1 + +/* +** The "file format" number is an integer that is incremented whenever +** the VDBE-level file format changes. The following macros define the +** the default file format for new databases and the maximum file format +** that the library can read. +*/ +#define SQLITE_MAX_FILE_FORMAT 4 +#ifndef SQLITE_DEFAULT_FILE_FORMAT +# define SQLITE_DEFAULT_FILE_FORMAT 1 +#endif + +/* +** Provide a default value for SQLITE_TEMP_STORE in case it is not specified +** on the command-line +*/ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 1 +#endif + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) +#endif + +/* +** Check to see if this machine uses EBCDIC. (Yes, believe it or +** not, there are still machines out there that use EBCDIC.) +*/ +#if 'A' == '\301' +# define SQLITE_EBCDIC 1 +#else +# define SQLITE_ASCII 1 +#endif + +/* +** Integers of known sizes. These typedefs might change for architectures +** where the sizes very. Preprocessor macros are available so that the +** types can be conveniently redefined at compile-type. Like this: +** +** cc '-DUINTPTR_TYPE=long long int' ... +*/ +#ifndef UINT32_TYPE +# ifdef HAVE_UINT32_T +# define UINT32_TYPE uint32_t +# else +# define UINT32_TYPE unsigned int +# endif +#endif +#ifndef UINT16_TYPE +# ifdef HAVE_UINT16_T +# define UINT16_TYPE uint16_t +# else +# define UINT16_TYPE unsigned short int +# endif +#endif +#ifndef INT16_TYPE +# ifdef HAVE_INT16_T +# define INT16_TYPE int16_t +# else +# define INT16_TYPE short int +# endif +#endif +#ifndef UINT8_TYPE +# ifdef HAVE_UINT8_T +# define UINT8_TYPE uint8_t +# else +# define UINT8_TYPE unsigned char +# endif +#endif +#ifndef INT8_TYPE +# ifdef HAVE_INT8_T +# define INT8_TYPE int8_t +# else +# define INT8_TYPE signed char +# endif +#endif +#ifndef LONGDOUBLE_TYPE +# define LONGDOUBLE_TYPE long double +#endif +typedef sqlite_int64 i64; /* 8-byte signed integer */ +typedef sqlite_uint64 u64; /* 8-byte unsigned integer */ +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ +typedef INT16_TYPE i16; /* 2-byte signed integer */ +typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ +typedef INT8_TYPE i8; /* 1-byte signed integer */ + +/* +** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value +** that can be stored in a u32 without loss of data. The value +** is 0x00000000ffffffff. But because of quirks of some compilers, we +** have to specify the value in the less intuitive manner shown: +*/ +#define SQLITE_MAX_U32 ((((u64)1)<<32)-1) + +/* +** Macros to determine whether the machine is big or little endian, +** evaluated at runtime. +*/ +#ifdef SQLITE_AMALGAMATION +SQLITE_PRIVATE const int sqlite3one = 1; +#else +SQLITE_PRIVATE const int sqlite3one; +#endif +#if defined(i386) || defined(__i386__) || defined(_M_IX86)\ + || defined(__x86_64) || defined(__x86_64__) +# define SQLITE_BIGENDIAN 0 +# define SQLITE_LITTLEENDIAN 1 +# define SQLITE_UTF16NATIVE SQLITE_UTF16LE +#else +# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) +# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) +# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) +#endif + +/* +** Constants for the largest and smallest possible 64-bit signed integers. +** These macros are designed to work correctly on both 32-bit and 64-bit +** compilers. +*/ +#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +/* +** Round up a number to the next larger multiple of 8. This is used +** to force 8-byte alignment on 64-bit architectures. +*/ +#define ROUND8(x) (((x)+7)&~7) + +/* +** Round down to the nearest multiple of 8 +*/ +#define ROUNDDOWN8(x) ((x)&~7) + +/* +** Assert that the pointer X is aligned to an 8-byte boundary. +*/ +#define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) + + +/* +** An instance of the following structure is used to store the busy-handler +** callback for a given sqlite handle. +** +** The sqlite.busyHandler member of the sqlite struct contains the busy +** callback for the database handle. Each pager opened via the sqlite +** handle is passed a pointer to sqlite.busyHandler. The busy-handler +** callback is currently invoked only from within pager.c. +*/ +typedef struct BusyHandler BusyHandler; +struct BusyHandler { + int (*xFunc)(void *,int); /* The busy callback */ + void *pArg; /* First arg to busy callback */ + int nBusy; /* Incremented with each busy call */ +}; + +/* +** Name of the master database table. The master database table +** is a special table that holds the names and attributes of all +** user tables and indices. +*/ +#define MASTER_NAME "sqlite_master" +#define TEMP_MASTER_NAME "sqlite_temp_master" + +/* +** The root-page of the master database table. +*/ +#define MASTER_ROOT 1 + +/* +** The name of the schema table. +*/ +#define SCHEMA_TABLE(x) ((!OMIT_TEMPDB)&&(x==1)?TEMP_MASTER_NAME:MASTER_NAME) + +/* +** A convenience macro that returns the number of elements in +** an array. +*/ +#define ArraySize(X) ((int)(sizeof(X)/sizeof(X[0]))) + +/* +** The following value as a destructor means to use sqlite3DbFree(). +** This is an internal extension to SQLITE_STATIC and SQLITE_TRANSIENT. +*/ +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3DbFree) + +/* +** When SQLITE_OMIT_WSD is defined, it means that the target platform does +** not support Writable Static Data (WSD) such as global and static variables. +** All variables must either be on the stack or dynamically allocated from +** the heap. When WSD is unsupported, the variable declarations scattered +** throughout the SQLite code must become constants instead. The SQLITE_WSD +** macro is used for this purpose. And instead of referencing the variable +** directly, we use its constant as a key to lookup the run-time allocated +** buffer that holds real variable. The constant is also the initializer +** for the run-time allocated buffer. +** +** In the usual case where WSD is supported, the SQLITE_WSD and GLOBAL +** macros become no-ops and have zero performance impact. +*/ +#ifdef SQLITE_OMIT_WSD + #define SQLITE_WSD const + #define GLOBAL(t,v) (*(t*)sqlite3_wsd_find((void*)&(v), sizeof(v))) + #define sqlite3GlobalConfig GLOBAL(struct Sqlite3Config, sqlite3Config) +SQLITE_API int sqlite3_wsd_init(int N, int J); +SQLITE_API void *sqlite3_wsd_find(void *K, int L); +#else + #define SQLITE_WSD + #define GLOBAL(t,v) v + #define sqlite3GlobalConfig sqlite3Config +#endif + +/* +** The following macros are used to suppress compiler warnings and to +** make it clear to human readers when a function parameter is deliberately +** left unused within the body of a function. This usually happens when +** a function is called via a function pointer. For example the +** implementation of an SQL aggregate step callback may not use the +** parameter indicating the number of arguments passed to the aggregate, +** if it knows that this is enforced elsewhere. +** +** When a function parameter is not used at all within the body of a function, +** it is generally named "NotUsed" or "NotUsed2" to make things even clearer. +** However, these macros may also be used to suppress warnings related to +** parameters that may or may not be used depending on compilation options. +** For example those parameters only used in assert() statements. In these +** cases the parameters are named as per the usual conventions. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#define UNUSED_PARAMETER2(x,y) UNUSED_PARAMETER(x),UNUSED_PARAMETER(y) + +/* +** Forward references to structures +*/ +typedef struct AggInfo AggInfo; +typedef struct AuthContext AuthContext; +typedef struct AutoincInfo AutoincInfo; +typedef struct Bitvec Bitvec; +typedef struct RowSet RowSet; +typedef struct CollSeq CollSeq; +typedef struct Column Column; +typedef struct Db Db; +typedef struct Schema Schema; +typedef struct Expr Expr; +typedef struct ExprList ExprList; +typedef struct ExprSpan ExprSpan; +typedef struct FKey FKey; +typedef struct FuncDef FuncDef; +typedef struct FuncDefHash FuncDefHash; +typedef struct IdList IdList; +typedef struct Index Index; +typedef struct KeyClass KeyClass; +typedef struct KeyInfo KeyInfo; +typedef struct Lookaside Lookaside; +typedef struct LookasideSlot LookasideSlot; +typedef struct Module Module; +typedef struct NameContext NameContext; +typedef struct Parse Parse; +typedef struct Savepoint Savepoint; +typedef struct Select Select; +typedef struct SrcList SrcList; +typedef struct StrAccum StrAccum; +typedef struct Table Table; +typedef struct TableLock TableLock; +typedef struct Token Token; +typedef struct TriggerStack TriggerStack; +typedef struct TriggerStep TriggerStep; +typedef struct Trigger Trigger; +typedef struct UnpackedRecord UnpackedRecord; +typedef struct Walker Walker; +typedef struct WherePlan WherePlan; +typedef struct WhereInfo WhereInfo; +typedef struct WhereLevel WhereLevel; + +/* +** Defer sourcing vdbe.h and btree.h until after the "u8" and +** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque +** pointer types (i.e. FuncDef) defined above. +*/ +/************** Include btree.h in the middle of sqliteInt.h *****************/ +/************** Begin file btree.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite B-Tree file +** subsystem. See comments in the source code for a detailed description +** of what each interface routine does. +** +** @(#) $Id: btree.h,v 1.116 2009/06/03 11:25:07 danielk1977 Exp $ +*/ +#ifndef _BTREE_H_ +#define _BTREE_H_ + +/* TODO: This definition is just included so other modules compile. It +** needs to be revisited. +*/ +#define SQLITE_N_BTREE_META 10 + +/* +** If defined as non-zero, auto-vacuum is enabled by default. Otherwise +** it must be turned on for each database using "PRAGMA auto_vacuum = 1". +*/ +#ifndef SQLITE_DEFAULT_AUTOVACUUM + #define SQLITE_DEFAULT_AUTOVACUUM 0 +#endif + +#define BTREE_AUTOVACUUM_NONE 0 /* Do not do auto-vacuum */ +#define BTREE_AUTOVACUUM_FULL 1 /* Do full auto-vacuum */ +#define BTREE_AUTOVACUUM_INCR 2 /* Incremental vacuum */ + +/* +** Forward declarations of structure +*/ +typedef struct Btree Btree; +typedef struct BtCursor BtCursor; +typedef struct BtShared BtShared; +typedef struct BtreeMutexArray BtreeMutexArray; + +/* +** This structure records all of the Btrees that need to hold +** a mutex before we enter sqlite3VdbeExec(). The Btrees are +** are placed in aBtree[] in order of aBtree[]->pBt. That way, +** we can always lock and unlock them all quickly. +*/ +struct BtreeMutexArray { + int nMutex; + Btree *aBtree[SQLITE_MAX_ATTACHED+1]; +}; + + +SQLITE_PRIVATE int sqlite3BtreeOpen( + const char *zFilename, /* Name of database file to open */ + sqlite3 *db, /* Associated database connection */ + Btree **ppBtree, /* Return open Btree* here */ + int flags, /* Flags */ + int vfsFlags /* Flags passed through to VFS open */ +); + +/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the +** following values. +** +** NOTE: These values must match the corresponding PAGER_ values in +** pager.h. +*/ +#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */ +#define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */ +#define BTREE_MEMORY 4 /* In-memory DB. No argument */ +#define BTREE_READONLY 8 /* Open the database in read-only mode */ +#define BTREE_READWRITE 16 /* Open for both reading and writing */ +#define BTREE_CREATE 32 /* Create the database if it does not exist */ + +SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeSetSafetyLevel(Btree*,int,int); +SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); +SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); +SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); +SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*); +SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*); +SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags); +SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*); +SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); +SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); +SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); +SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *); +SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *, int, u8); +SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int); + +SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); +SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); + +SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); + +/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR +** of the following flags: +*/ +#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ +#define BTREE_ZERODATA 2 /* Table has keys only - no data */ +#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */ + +SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*); +SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree*, int); + +SQLITE_PRIVATE int sqlite3BtreeGetMeta(Btree*, int idx, u32 *pValue); +SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); + +/* +** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta +** should be one of the following values. The integer values are assigned +** to constants so that the offset of the corresponding field in an +** SQLite database header may be found using the following formula: +** +** offset = 36 + (idx * 4) +** +** For example, the free-page-count field is located at byte offset 36 of +** the database file header. The incr-vacuum-flag field is located at +** byte offset 64 (== 36+4*7). +*/ +#define BTREE_FREE_PAGE_COUNT 0 +#define BTREE_SCHEMA_VERSION 1 +#define BTREE_FILE_FORMAT 2 +#define BTREE_DEFAULT_CACHE_SIZE 3 +#define BTREE_LARGEST_ROOT_PAGE 4 +#define BTREE_TEXT_ENCODING 5 +#define BTREE_USER_VERSION 6 +#define BTREE_INCR_VACUUM 7 + +SQLITE_PRIVATE int sqlite3BtreeCursor( + Btree*, /* BTree containing table to open */ + int iTable, /* Index of root page */ + int wrFlag, /* 1 for writing. 0 for read-only */ + struct KeyInfo*, /* First argument to compare function */ + BtCursor *pCursor /* Space to write cursor structure */ +); +SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); + +SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeMoveto( + BtCursor*, + const void *pKey, + i64 nKey, + int bias, + int *pRes +); +SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( + BtCursor*, + UnpackedRecord *pUnKey, + i64 intKey, + int bias, + int *pRes +); +SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*, int*); +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, + const void *pData, int nData, + int nZero, int bias, int seekResult); +SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeFlags(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor*, i64 *pSize); +SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt); +SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt); +SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor*, u32 *pSize); +SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE void sqlite3BtreeSetCachedRowid(BtCursor*, sqlite3_int64); +SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeGetCachedRowid(BtCursor*); + +SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); +SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); + +SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *); +SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); + +#ifndef SQLITE_OMIT_BTREECOUNT +SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *, i64 *); +#endif + +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int); +SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*); +#endif + +/* +** If we are not using shared cache, then there is no need to +** use mutexes to access the BtShared structures. So make the +** Enter and Leave procedures no-ops. +*/ +#ifndef SQLITE_OMIT_SHARED_CACHE +SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*); +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*); +#else +# define sqlite3BtreeEnter(X) +# define sqlite3BtreeEnterAll(X) +#endif + +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE +SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*); +SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayEnter(BtreeMutexArray*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayLeave(BtreeMutexArray*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayInsert(BtreeMutexArray*, Btree*); +#ifndef NDEBUG + /* These routines are used inside assert() statements only. */ +SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*); +SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*); +#endif +#else + +# define sqlite3BtreeLeave(X) +# define sqlite3BtreeEnterCursor(X) +# define sqlite3BtreeLeaveCursor(X) +# define sqlite3BtreeLeaveAll(X) +# define sqlite3BtreeMutexArrayEnter(X) +# define sqlite3BtreeMutexArrayLeave(X) +# define sqlite3BtreeMutexArrayInsert(X,Y) + +# define sqlite3BtreeHoldsMutex(X) 1 +# define sqlite3BtreeHoldsAllMutexes(X) 1 +#endif + + +#endif /* _BTREE_H_ */ + +/************** End of btree.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include vdbe.h in the middle of sqliteInt.h ******************/ +/************** Begin file vdbe.h ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Header file for the Virtual DataBase Engine (VDBE) +** +** This header defines the interface to the virtual database engine +** or VDBE. The VDBE implements an abstract machine that runs a +** simple program to access and modify the underlying database. +** +** $Id: vdbe.h,v 1.141 2009/04/10 00:56:29 drh Exp $ +*/ +#ifndef _SQLITE_VDBE_H_ +#define _SQLITE_VDBE_H_ + +/* +** A single VDBE is an opaque structure named "Vdbe". Only routines +** in the source file sqliteVdbe.c are allowed to see the insides +** of this structure. +*/ +typedef struct Vdbe Vdbe; + +/* +** The names of the following types declared in vdbeInt.h are required +** for the VdbeOp definition. +*/ +typedef struct VdbeFunc VdbeFunc; +typedef struct Mem Mem; + +/* +** A single instruction of the virtual machine has an opcode +** and as many as three operands. The instruction is recorded +** as an instance of the following structure: +*/ +struct VdbeOp { + u8 opcode; /* What operation to perform */ + signed char p4type; /* One of the P4_xxx constants for p4 */ + u8 opflags; /* Not currently used */ + u8 p5; /* Fifth parameter is an unsigned character */ + int p1; /* First operand */ + int p2; /* Second parameter (often the jump destination) */ + int p3; /* The third parameter */ + union { /* forth parameter */ + int i; /* Integer value if p4type==P4_INT32 */ + void *p; /* Generic pointer */ + char *z; /* Pointer to data for string (char array) types */ + i64 *pI64; /* Used when p4type is P4_INT64 */ + double *pReal; /* Used when p4type is P4_REAL */ + FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */ + VdbeFunc *pVdbeFunc; /* Used when p4type is P4_VDBEFUNC */ + CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */ + Mem *pMem; /* Used when p4type is P4_MEM */ + sqlite3_vtab *pVtab; /* Used when p4type is P4_VTAB */ + KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ + int *ai; /* Used when p4type is P4_INTARRAY */ + } p4; +#ifdef SQLITE_DEBUG + char *zComment; /* Comment to improve readability */ +#endif +#ifdef VDBE_PROFILE + int cnt; /* Number of times this instruction was executed */ + u64 cycles; /* Total time spent executing this instruction */ +#endif +}; +typedef struct VdbeOp VdbeOp; + +/* +** A smaller version of VdbeOp used for the VdbeAddOpList() function because +** it takes up less space. +*/ +struct VdbeOpList { + u8 opcode; /* What operation to perform */ + signed char p1; /* First operand */ + signed char p2; /* Second parameter (often the jump destination) */ + signed char p3; /* Third parameter */ +}; +typedef struct VdbeOpList VdbeOpList; + +/* +** Allowed values of VdbeOp.p3type +*/ +#define P4_NOTUSED 0 /* The P4 parameter is not used */ +#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ +#define P4_STATIC (-2) /* Pointer to a static string */ +#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ +#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */ +#define P4_VDBEFUNC (-7) /* P4 is a pointer to a VdbeFunc structure */ +#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */ +#define P4_TRANSIENT (-9) /* P4 is a pointer to a transient string */ +#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ +#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ +#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ +#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ + +/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure +** is made. That copy is freed when the Vdbe is finalized. But if the +** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still +** gets freed when the Vdbe is finalized so it still should be obtained +** from a single sqliteMalloc(). But no copy is made and the calling +** function should *not* try to free the KeyInfo. +*/ +#define P4_KEYINFO_HANDOFF (-16) +#define P4_KEYINFO_STATIC (-17) + +/* +** The Vdbe.aColName array contains 5n Mem structures, where n is the +** number of columns of data returned by the statement. +*/ +#define COLNAME_NAME 0 +#define COLNAME_DECLTYPE 1 +#define COLNAME_DATABASE 2 +#define COLNAME_TABLE 3 +#define COLNAME_COLUMN 4 +#ifdef SQLITE_ENABLE_COLUMN_METADATA +# define COLNAME_N 5 /* Number of COLNAME_xxx symbols */ +#else +# ifdef SQLITE_OMIT_DECLTYPE +# define COLNAME_N 1 /* Store only the name */ +# else +# define COLNAME_N 2 /* Store the name and decltype */ +# endif +#endif + +/* +** The following macro converts a relative address in the p2 field +** of a VdbeOp structure into a negative number so that +** sqlite3VdbeAddOpList() knows that the address is relative. Calling +** the macro again restores the address. +*/ +#define ADDR(X) (-1-(X)) + +/* +** The makefile scans the vdbe.c source file and creates the "opcodes.h" +** header file that defines a number for each opcode used by the VDBE. +*/ +/************** Include opcodes.h in the middle of vdbe.h ********************/ +/************** Begin file opcodes.h *****************************************/ +/* Automatically generated. Do not edit */ +/* See the mkopcodeh.awk script for details */ +#define OP_VNext 1 +#define OP_Affinity 2 +#define OP_Column 3 +#define OP_SetCookie 4 +#define OP_Seek 5 +#define OP_Real 130 /* same as TK_FLOAT */ +#define OP_Sequence 6 +#define OP_Savepoint 7 +#define OP_Ge 78 /* same as TK_GE */ +#define OP_RowKey 8 +#define OP_SCopy 9 +#define OP_Eq 74 /* same as TK_EQ */ +#define OP_OpenWrite 10 +#define OP_NotNull 72 /* same as TK_NOTNULL */ +#define OP_If 11 +#define OP_ToInt 144 /* same as TK_TO_INT */ +#define OP_String8 94 /* same as TK_STRING */ +#define OP_CollSeq 12 +#define OP_OpenRead 13 +#define OP_Expire 14 +#define OP_AutoCommit 15 +#define OP_Gt 75 /* same as TK_GT */ +#define OP_Pagecount 16 +#define OP_IntegrityCk 17 +#define OP_Sort 18 +#define OP_Copy 20 +#define OP_Trace 21 +#define OP_Function 22 +#define OP_IfNeg 23 +#define OP_And 67 /* same as TK_AND */ +#define OP_Subtract 85 /* same as TK_MINUS */ +#define OP_Noop 24 +#define OP_Return 25 +#define OP_Remainder 88 /* same as TK_REM */ +#define OP_NewRowid 26 +#define OP_Multiply 86 /* same as TK_STAR */ +#define OP_Variable 27 +#define OP_String 28 +#define OP_RealAffinity 29 +#define OP_VRename 30 +#define OP_ParseSchema 31 +#define OP_VOpen 32 +#define OP_Close 33 +#define OP_CreateIndex 34 +#define OP_IsUnique 35 +#define OP_NotFound 36 +#define OP_Int64 37 +#define OP_MustBeInt 38 +#define OP_Halt 39 +#define OP_Rowid 40 +#define OP_IdxLT 41 +#define OP_AddImm 42 +#define OP_Statement 43 +#define OP_RowData 44 +#define OP_MemMax 45 +#define OP_Or 66 /* same as TK_OR */ +#define OP_NotExists 46 +#define OP_Gosub 47 +#define OP_Divide 87 /* same as TK_SLASH */ +#define OP_Integer 48 +#define OP_ToNumeric 143 /* same as TK_TO_NUMERIC*/ +#define OP_Prev 49 +#define OP_RowSetRead 50 +#define OP_Concat 89 /* same as TK_CONCAT */ +#define OP_RowSetAdd 51 +#define OP_BitAnd 80 /* same as TK_BITAND */ +#define OP_VColumn 52 +#define OP_CreateTable 53 +#define OP_Last 54 +#define OP_SeekLe 55 +#define OP_IsNull 71 /* same as TK_ISNULL */ +#define OP_IncrVacuum 56 +#define OP_IdxRowid 57 +#define OP_ShiftRight 83 /* same as TK_RSHIFT */ +#define OP_ResetCount 58 +#define OP_ContextPush 59 +#define OP_Yield 60 +#define OP_DropTrigger 61 +#define OP_DropIndex 62 +#define OP_IdxGE 63 +#define OP_IdxDelete 64 +#define OP_Vacuum 65 +#define OP_IfNot 68 +#define OP_DropTable 69 +#define OP_SeekLt 70 +#define OP_MakeRecord 79 +#define OP_ToBlob 142 /* same as TK_TO_BLOB */ +#define OP_ResultRow 90 +#define OP_Delete 91 +#define OP_AggFinal 92 +#define OP_Compare 95 +#define OP_ShiftLeft 82 /* same as TK_LSHIFT */ +#define OP_Goto 96 +#define OP_TableLock 97 +#define OP_Clear 98 +#define OP_Le 76 /* same as TK_LE */ +#define OP_VerifyCookie 99 +#define OP_AggStep 100 +#define OP_ToText 141 /* same as TK_TO_TEXT */ +#define OP_Not 19 /* same as TK_NOT */ +#define OP_ToReal 145 /* same as TK_TO_REAL */ +#define OP_SetNumColumns 101 +#define OP_Transaction 102 +#define OP_VFilter 103 +#define OP_Ne 73 /* same as TK_NE */ +#define OP_VDestroy 104 +#define OP_ContextPop 105 +#define OP_BitOr 81 /* same as TK_BITOR */ +#define OP_Next 106 +#define OP_Count 107 +#define OP_IdxInsert 108 +#define OP_Lt 77 /* same as TK_LT */ +#define OP_SeekGe 109 +#define OP_Insert 110 +#define OP_Destroy 111 +#define OP_ReadCookie 112 +#define OP_RowSetTest 113 +#define OP_LoadAnalysis 114 +#define OP_Explain 115 +#define OP_HaltIfNull 116 +#define OP_OpenPseudo 117 +#define OP_OpenEphemeral 118 +#define OP_Null 119 +#define OP_Move 120 +#define OP_Blob 121 +#define OP_Add 84 /* same as TK_PLUS */ +#define OP_Rewind 122 +#define OP_SeekGt 123 +#define OP_VBegin 124 +#define OP_VUpdate 125 +#define OP_IfZero 126 +#define OP_BitNot 93 /* same as TK_BITNOT */ +#define OP_VCreate 127 +#define OP_Found 128 +#define OP_IfPos 129 +#define OP_NullRow 131 +#define OP_Jump 132 +#define OP_Permutation 133 + +/* The following opcode values are never used */ +#define OP_NotUsed_134 134 +#define OP_NotUsed_135 135 +#define OP_NotUsed_136 136 +#define OP_NotUsed_137 137 +#define OP_NotUsed_138 138 +#define OP_NotUsed_139 139 +#define OP_NotUsed_140 140 + + +/* Properties such as "out2" or "jump" that are specified in +** comments following the "case" for each opcode in the vdbe.c +** are encoded into bitvectors as follows: +*/ +#define OPFLG_JUMP 0x0001 /* jump: P2 holds jmp target */ +#define OPFLG_OUT2_PRERELEASE 0x0002 /* out2-prerelease: */ +#define OPFLG_IN1 0x0004 /* in1: P1 is an input */ +#define OPFLG_IN2 0x0008 /* in2: P2 is an input */ +#define OPFLG_IN3 0x0010 /* in3: P3 is an input */ +#define OPFLG_OUT3 0x0020 /* out3: P3 is an output */ +#define OPFLG_INITIALIZER {\ +/* 0 */ 0x00, 0x01, 0x00, 0x00, 0x10, 0x08, 0x02, 0x00,\ +/* 8 */ 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,\ +/* 16 */ 0x02, 0x00, 0x01, 0x04, 0x04, 0x00, 0x00, 0x05,\ +/* 24 */ 0x00, 0x04, 0x02, 0x00, 0x02, 0x04, 0x00, 0x00,\ +/* 32 */ 0x00, 0x00, 0x02, 0x11, 0x11, 0x02, 0x05, 0x00,\ +/* 40 */ 0x02, 0x11, 0x04, 0x00, 0x00, 0x0c, 0x11, 0x01,\ +/* 48 */ 0x02, 0x01, 0x21, 0x08, 0x00, 0x02, 0x01, 0x11,\ +/* 56 */ 0x01, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x11,\ +/* 64 */ 0x00, 0x00, 0x2c, 0x2c, 0x05, 0x00, 0x11, 0x05,\ +/* 72 */ 0x05, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x00,\ +/* 80 */ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,\ +/* 88 */ 0x2c, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00,\ +/* 96 */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\ +/* 104 */ 0x00, 0x00, 0x01, 0x02, 0x08, 0x11, 0x00, 0x02,\ +/* 112 */ 0x02, 0x15, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02,\ +/* 120 */ 0x00, 0x02, 0x01, 0x11, 0x00, 0x00, 0x05, 0x00,\ +/* 128 */ 0x11, 0x05, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,\ +/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04,\ +/* 144 */ 0x04, 0x04,} + +/************** End of opcodes.h *********************************************/ +/************** Continuing where we left off in vdbe.h ***********************/ + +/* +** Prototypes for the VDBE interface. See comments on the implementation +** for a description of what each of these routines does. +*/ +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3*); +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); +SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp); +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5); +SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); +SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe*, int addr, int N); +SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); +SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); +SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe*,FILE*); +#endif +SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); +SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*); +SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int); +SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*); + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +SQLITE_PRIVATE int sqlite3VdbeReleaseMemory(int); +#endif +SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,char*,int); +SQLITE_PRIVATE void sqlite3VdbeDeleteUnpackedRecord(UnpackedRecord*); +SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); + + +#ifndef NDEBUG +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...); +# define VdbeComment(X) sqlite3VdbeComment X +SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); +# define VdbeNoopComment(X) sqlite3VdbeNoopComment X +#else +# define VdbeComment(X) +# define VdbeNoopComment(X) +#endif + +#endif + +/************** End of vdbe.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pager.h in the middle of sqliteInt.h *****************/ +/************** Begin file pager.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +** +** @(#) $Id: pager.h,v 1.102 2009/06/18 17:22:39 drh Exp $ +*/ + +#ifndef _PAGER_H_ +#define _PAGER_H_ + +/* +** Default maximum size for persistent journal files. A negative +** value means no limit. This value may be overridden using the +** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit". +*/ +#ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + #define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1 +#endif + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef u32 Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + +/* +** Handle type for pages. +*/ +typedef struct PgHdr DbPage; + +/* +** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is +** reserved for working around a windows/posix incompatibility). It is +** used in the journal to signify that the remainder of the journal file +** is devoted to storing a master journal name - there are no more pages to +** roll back. See comments for function writeMasterJournal() in pager.c +** for details. +*/ +#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) + +/* +** Allowed values for the flags parameter to sqlite3PagerOpen(). +** +** NOTE: These values must match the corresponding BTREE_ values in btree.h. +*/ +#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ +#define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */ + +/* +** Valid values for the second argument to sqlite3PagerLockingMode(). +*/ +#define PAGER_LOCKINGMODE_QUERY -1 +#define PAGER_LOCKINGMODE_NORMAL 0 +#define PAGER_LOCKINGMODE_EXCLUSIVE 1 + +/* +** Valid values for the second argument to sqlite3PagerJournalMode(). +*/ +#define PAGER_JOURNALMODE_QUERY -1 +#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ +#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ +#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ +#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ +#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ + +/* +** The remainder of this file contains the declarations of the functions +** that make up the Pager sub-system API. See source code comments for +** a detailed description of each routine. +*/ + +/* Open and close a Pager connection. */ +SQLITE_PRIVATE int sqlite3PagerOpen(sqlite3_vfs *, Pager **ppPager, const char*, int,int,int); +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); + +/* Functions used to configure a Pager object. */ +SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *); +SQLITE_PRIVATE void sqlite3PagerSetReiniter(Pager*, void(*)(DbPage*)); +SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u16*, int); +SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerSetSafetyLevel(Pager*,int,int); +SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerJournalMode(Pager *, int); +SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64); +SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*); + +/* Functions used to obtain and release page references. */ +SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); +#define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) +SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); +SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); + +/* Operations on page references. */ +SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); +SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); +SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); +SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*); +SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); +SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); + +/* Functions used to manage pager transactions and savepoints. */ +SQLITE_PRIVATE int sqlite3PagerPagecount(Pager*, int*); +SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); +SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); +SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); +SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); + +/* Functions used to query pager state and configuration. */ +SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*); +SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); +SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); +SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); +SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); + +/* Functions used to truncate the database file. */ +SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); + +/* Functions to support testing and debugging. */ +#if !defined(NDEBUG) || defined(SQLITE_TEST) +SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); +SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); +#endif +#ifdef SQLITE_TEST +SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); +SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); + void disable_simulated_io_errors(void); + void enable_simulated_io_errors(void); +#else +# define disable_simulated_io_errors() +# define enable_simulated_io_errors() +#endif + +#endif /* _PAGER_H_ */ + +/************** End of pager.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pcache.h in the middle of sqliteInt.h ****************/ +/************** Begin file pcache.h ******************************************/ +/* +** 2008 August 05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. +** +** @(#) $Id: pcache.h,v 1.19 2009/01/20 17:06:27 danielk1977 Exp $ +*/ + +#ifndef _PCACHE_H_ + +typedef struct PgHdr PgHdr; +typedef struct PCache PCache; + +/* +** Every page in the cache is controlled by an instance of the following +** structure. +*/ +struct PgHdr { + void *pData; /* Content of this page */ + void *pExtra; /* Extra content */ + PgHdr *pDirty; /* Transient list of dirty pages */ + Pgno pgno; /* Page number for this page */ + Pager *pPager; /* The pager this page is part of */ +#ifdef SQLITE_CHECK_PAGES + u32 pageHash; /* Hash of page content */ +#endif + u16 flags; /* PGHDR flags defined below */ + + /********************************************************************** + ** Elements above are public. All that follows is private to pcache.c + ** and should not be accessed by other modules. + */ + i16 nRef; /* Number of users of this page */ + PCache *pCache; /* Cache that owns this page */ + + PgHdr *pDirtyNext; /* Next element in list of dirty pages */ + PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ +}; + +/* Bit values for PgHdr.flags */ +#define PGHDR_DIRTY 0x002 /* Page has changed */ +#define PGHDR_NEED_SYNC 0x004 /* Fsync the rollback journal before + ** writing this page to the database */ +#define PGHDR_NEED_READ 0x008 /* Content is unread */ +#define PGHDR_REUSE_UNLIKELY 0x010 /* A hint that reuse is unlikely */ +#define PGHDR_DONT_WRITE 0x020 /* Do not write content to disk */ + +/* Initialize and shutdown the page cache subsystem */ +SQLITE_PRIVATE int sqlite3PcacheInitialize(void); +SQLITE_PRIVATE void sqlite3PcacheShutdown(void); + +/* Page cache buffer management: +** These routines implement SQLITE_CONFIG_PAGECACHE. +*/ +SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *, int sz, int n); + +/* Create a new pager cache. +** Under memory stress, invoke xStress to try to make pages clean. +** Only clean and unpinned pages can be reclaimed. +*/ +SQLITE_PRIVATE void sqlite3PcacheOpen( + int szPage, /* Size of every page */ + int szExtra, /* Extra space associated with each page */ + int bPurgeable, /* True if pages are on backing store */ + int (*xStress)(void*, PgHdr*), /* Call to try to make pages clean */ + void *pStress, /* Argument to xStress */ + PCache *pToInit /* Preallocated space for the PCache */ +); + +/* Modify the page-size after the cache has been created. */ +SQLITE_PRIVATE void sqlite3PcacheSetPageSize(PCache *, int); + +/* Return the size in bytes of a PCache object. Used to preallocate +** storage space. +*/ +SQLITE_PRIVATE int sqlite3PcacheSize(void); + +/* One release per successful fetch. Page is pinned until released. +** Reference counted. +*/ +SQLITE_PRIVATE int sqlite3PcacheFetch(PCache*, Pgno, int createFlag, PgHdr**); +SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr*); + +SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */ +SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */ +SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */ +SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */ + +/* Change a page number. Used by incr-vacuum. */ +SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr*, Pgno); + +/* Remove all pages with pgno>x. Reset the cache if x==0 */ +SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache*, Pgno x); + +/* Get a list of all dirty pages in the cache, sorted by page number */ +SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache*); + +/* Reset and close the cache object */ +SQLITE_PRIVATE void sqlite3PcacheClose(PCache*); + +/* Clear flags from pages of the page cache */ +SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *); + +/* Discard the contents of the cache */ +SQLITE_PRIVATE void sqlite3PcacheClear(PCache*); + +/* Return the total number of outstanding page references */ +SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache*); + +/* Increment the reference count of an existing page */ +SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*); + +SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr*); + +/* Return the total number of pages stored in the cache */ +SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*); + +#ifdef SQLITE_CHECK_PAGES +/* Iterate through all dirty pages currently stored in the cache. This +** interface is only available if SQLITE_CHECK_PAGES is defined when the +** library is built. +*/ +SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)); +#endif + +/* Set and get the suggested cache-size for the specified pager-cache. +** +** If no global maximum is configured, then the system attempts to limit +** the total number of pages cached by purgeable pager-caches to the sum +** of the suggested cache-sizes. +*/ +SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *, int); +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *); +#endif + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +/* Try to return memory used by the pcache module to the main memory heap */ +SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int); +#endif + +#ifdef SQLITE_TEST +SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*); +#endif + +SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); + +#endif /* _PCACHE_H_ */ + +/************** End of pcache.h **********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/************** Include os.h in the middle of sqliteInt.h ********************/ +/************** Begin file os.h **********************************************/ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +** +** This header file is #include-ed by sqliteInt.h and thus ends up +** being included by every source file. +** +** $Id: os.h,v 1.108 2009/02/05 16:31:46 drh Exp $ +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Figure out if we are dealing with Unix, Windows, or some other +** operating system. After the following block of preprocess macros, +** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER +** will defined to either 1 or 0. One of the four will be 1. The other +** three will be 0. +*/ +#if defined(SQLITE_OS_OTHER) +# if SQLITE_OS_OTHER==1 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# undef SQLITE_OS_OS2 +# define SQLITE_OS_OS2 0 +# else +# undef SQLITE_OS_OTHER +# endif +#endif +#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) +# define SQLITE_OS_OTHER 0 +# ifndef SQLITE_OS_WIN +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 0 +# elif defined(__EMX__) || defined(_OS2) || defined(OS2) || defined(_OS2_) || defined(__OS2__) +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 1 +# else +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 +# define SQLITE_OS_OS2 0 +# endif +# else +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 0 +# endif +#else +# ifndef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# endif +#endif + +/* +** Determine if we are dealing with WindowsCE - which has a much +** reduced API. +*/ +#if defined(_WIN32_WCE) +# define SQLITE_OS_WINCE 1 +#else +# define SQLITE_OS_WINCE 0 +#endif + + +/* +** Define the maximum size of a temporary filename +*/ +#if SQLITE_OS_WIN +# include +# define SQLITE_TEMPNAME_SIZE (MAX_PATH+50) +#elif SQLITE_OS_OS2 +# if (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 3) && defined(OS2_HIGH_MEMORY) +# include /* has to be included before os2.h for linking to work */ +# endif +# define INCL_DOSDATETIME +# define INCL_DOSFILEMGR +# define INCL_DOSERRORS +# define INCL_DOSMISC +# define INCL_DOSPROCESS +# define INCL_DOSMODULEMGR +# define INCL_DOSSEMAPHORES +# include +# include +# define SQLITE_TEMPNAME_SIZE (CCHMAXPATHCOMP) +#else +# define SQLITE_TEMPNAME_SIZE 200 +#endif + +/* If the SET_FULLSYNC macro is not defined above, then make it +** a no-op +*/ +#ifndef SET_FULLSYNC +# define SET_FULLSYNC(x,y) +#endif + +/* +** The default size of a disk sector +*/ +#ifndef SQLITE_DEFAULT_SECTOR_SIZE +# define SQLITE_DEFAULT_SECTOR_SIZE 512 +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. +** +** 2006-10-31: The default prefix used to be "sqlite_". But then +** Mcafee started using SQLite in their anti-virus product and it +** started putting files with the "sqlite" name in the c:/temp folder. +** This annoyed many windows users. Those users would then do a +** Google search for "sqlite", find the telephone numbers of the +** developers and call to wake them up at night and complain. +** For this reason, the default name prefix is changed to be "sqlite" +** spelled backwards. So the temp files are still identified, but +** anybody smart enough to figure out the code is also likely smart +** enough to know that calling the developer will not help get rid +** of the file. +*/ +#ifndef SQLITE_TEMP_FILE_PREFIX +# define SQLITE_TEMP_FILE_PREFIX "etilqs_" +#endif + +/* +** The following values may be passed as the second argument to +** sqlite3OsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** sqlite3OsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 + +/* +** File Locking Notes: (Mostly about windows but also some info for Unix) +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. +** There can only be one writer. A RESERVED_LOCK is obtained by locking +** a single byte of the file that is designated as the reserved lock byte. +** A PENDING_LOCK is obtained by locking a designated byte different from +** the RESERVED_LOCK byte. +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader/writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** The following #defines specify the range of bytes used for locking. +** SHARED_SIZE is the number of bytes available in the pool from which +** a random byte is selected for a shared lock. The pool of bytes for +** shared locks begins at SHARED_FIRST. +** +** The same locking strategy and +** byte ranges are used for Unix. This leaves open the possiblity of having +** clients on win95, winNT, and unix all talking to the same shared file +** and all locking correctly. To do so would require that samba (or whatever +** tool is being used for file sharing) implements locks correctly between +** windows and unix. I'm guessing that isn't likely to happen, but by +** using the same locking range we are at least open to the possibility. +** +** Locking in windows is manditory. For this reason, we cannot store +** actual data in the bytes used for locking. The pager never allocates +** the pages involved in locking therefore. SHARED_SIZE is selected so +** that all locks will fit on a single page even at the minimum page size. +** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE +** is set high so that we don't have to allocate an unused page except +** for very large databases. But one should test the page skipping logic +** by setting PENDING_BYTE low and running the entire regression suite. +** +** Changing the value of PENDING_BYTE results in a subtly incompatible +** file format. Depending on how it is changed, you might not notice +** the incompatibility right away, even running a full regression test. +** The default location of PENDING_BYTE is the first byte past the +** 1GB boundary. +** +*/ +#define PENDING_BYTE sqlite3PendingByte +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 + +/* +** Functions for accessing sqlite3_file methods +*/ +SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*); +SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); +SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); +SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); +#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0 +SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); + +/* +** Functions for accessing sqlite3_vfs methods +*/ +SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); +SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); +SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); +#ifndef SQLITE_OMIT_LOAD_EXTENSION +SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); +SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void); +SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ +SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); +SQLITE_PRIVATE int sqlite3OsCurrentTime(sqlite3_vfs *, double*); + +/* +** Convenience functions for opening and closing files using +** sqlite3_malloc() to obtain space for the file-handle structure. +*/ +SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); +SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); + +#endif /* _SQLITE_OS_H_ */ + +/************** End of os.h **************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include mutex.h in the middle of sqliteInt.h *****************/ +/************** Begin file mutex.h *******************************************/ +/* +** 2007 August 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the common header for all mutex implementations. +** The sqliteInt.h header #includes this file so that it is available +** to all source files. We break it out in an effort to keep the code +** better organized. +** +** NOTE: source files should *not* #include this header file directly. +** Source files should #include the sqliteInt.h file and let that file +** include this one indirectly. +** +** $Id: mutex.h,v 1.9 2008/10/07 15:25:48 drh Exp $ +*/ + + +/* +** Figure out what version of the code to use. The choices are +** +** SQLITE_MUTEX_OMIT No mutex logic. Not even stubs. The +** mutexes implemention cannot be overridden +** at start-time. +** +** SQLITE_MUTEX_NOOP For single-threaded applications. No +** mutual exclusion is provided. But this +** implementation can be overridden at +** start-time. +** +** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix. +** +** SQLITE_MUTEX_W32 For multi-threaded applications on Win32. +** +** SQLITE_MUTEX_OS2 For multi-threaded applications on OS/2. +*/ +#if !SQLITE_THREADSAFE +# define SQLITE_MUTEX_OMIT +#endif +#if SQLITE_THREADSAFE && !defined(SQLITE_MUTEX_NOOP) +# if SQLITE_OS_UNIX +# define SQLITE_MUTEX_PTHREADS +# elif SQLITE_OS_WIN +# define SQLITE_MUTEX_W32 +# elif SQLITE_OS_OS2 +# define SQLITE_MUTEX_OS2 +# else +# define SQLITE_MUTEX_NOOP +# endif +#endif + +#ifdef SQLITE_MUTEX_OMIT +/* +** If this is a no-op implementation, implement everything as macros. +*/ +#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) +#define sqlite3_mutex_free(X) +#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_try(X) SQLITE_OK +#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_held(X) 1 +#define sqlite3_mutex_notheld(X) 1 +#define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) +#define sqlite3MutexInit() SQLITE_OK +#define sqlite3MutexEnd() +#endif /* defined(SQLITE_OMIT_MUTEX) */ + +/************** End of mutex.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + + +/* +** Each database file to be accessed by the system is an instance +** of the following structure. There are normally two of these structures +** in the sqlite.aDb[] array. aDb[0] is the main database file and +** aDb[1] is the database file used to hold temporary tables. Additional +** databases may be attached. +*/ +struct Db { + char *zName; /* Name of this database */ + Btree *pBt; /* The B*Tree structure for this database file */ + u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */ + u8 safety_level; /* How aggressive at syncing data to disk */ + Schema *pSchema; /* Pointer to database schema (possibly shared) */ +}; + +/* +** An instance of the following structure stores a database schema. +** +** If there are no virtual tables configured in this schema, the +** Schema.db variable is set to NULL. After the first virtual table +** has been added, it is set to point to the database connection +** used to create the connection. Once a virtual table has been +** added to the Schema structure and the Schema.db variable populated, +** only that database connection may use the Schema to prepare +** statements. +*/ +struct Schema { + int schema_cookie; /* Database schema version number for this file */ + Hash tblHash; /* All tables indexed by name */ + Hash idxHash; /* All (named) indices indexed by name */ + Hash trigHash; /* All triggers indexed by name */ + Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ + u8 file_format; /* Schema format version for this file */ + u8 enc; /* Text encoding used by this database */ + u16 flags; /* Flags associated with this schema */ + int cache_size; /* Number of pages to use in the cache */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3 *db; /* "Owner" connection. See comment above */ +#endif +}; + +/* +** These macros can be used to test, set, or clear bits in the +** Db.flags field. +*/ +#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->flags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->flags&=~(P) + +/* +** Allowed values for the DB.flags field. +** +** The DB_SchemaLoaded flag is set after the database schema has been +** read into internal hash tables. +** +** DB_UnresetViews means that one or more views have column names that +** have been filled out. If the schema changes, these column names might +** changes and so the view will need to be reset. +*/ +#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ +#define DB_UnresetViews 0x0002 /* Some views have defined column names */ +#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */ + +/* +** The number of different kinds of things that can be limited +** using the sqlite3_limit() interface. +*/ +#define SQLITE_N_LIMIT (SQLITE_LIMIT_VARIABLE_NUMBER+1) + +/* +** Lookaside malloc is a set of fixed-size buffers that can be used +** to satisfy small transient memory allocation requests for objects +** associated with a particular database connection. The use of +** lookaside malloc provides a significant performance enhancement +** (approx 10%) by avoiding numerous malloc/free requests while parsing +** SQL statements. +** +** The Lookaside structure holds configuration information about the +** lookaside malloc subsystem. Each available memory allocation in +** the lookaside subsystem is stored on a linked list of LookasideSlot +** objects. +** +** Lookaside allocations are only allowed for objects that are associated +** with a particular database connection. Hence, schema information cannot +** be stored in lookaside because in shared cache mode the schema information +** is shared by multiple database connections. Therefore, while parsing +** schema information, the Lookaside.bEnabled flag is cleared so that +** lookaside allocations are not used to construct the schema objects. +*/ +struct Lookaside { + u16 sz; /* Size of each buffer in bytes */ + u8 bEnabled; /* False to disable new lookaside allocations */ + u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ + int nOut; /* Number of buffers currently checked out */ + int mxOut; /* Highwater mark for nOut */ + LookasideSlot *pFree; /* List of available buffers */ + void *pStart; /* First byte of available memory space */ + void *pEnd; /* First byte past end of available space */ +}; +struct LookasideSlot { + LookasideSlot *pNext; /* Next buffer in the list of free buffers */ +}; + +/* +** A hash table for function definitions. +** +** Hash each FuncDef structure into one of the FuncDefHash.a[] slots. +** Collisions are on the FuncDef.pHash chain. +*/ +struct FuncDefHash { + FuncDef *a[23]; /* Hash table for functions */ +}; + +/* +** Each database is an instance of the following structure. +** +** The sqlite.lastRowid records the last insert rowid generated by an +** insert statement. Inserts on views do not affect its value. Each +** trigger has its own context, so that lastRowid can be updated inside +** triggers as usual. The previous value will be restored once the trigger +** exits. Upon entering a before or instead of trigger, lastRowid is no +** longer (since after version 2.8.12) reset to -1. +** +** The sqlite.nChange does not count changes within triggers and keeps no +** context. It is reset at start of sqlite3_exec. +** The sqlite.lsChange represents the number of changes made by the last +** insert, update, or delete statement. It remains constant throughout the +** length of a statement and is then updated by OP_SetCounts. It keeps a +** context stack just like lastRowid so that the count of changes +** within a trigger is not seen outside the trigger. Changes to views do not +** affect the value of lsChange. +** The sqlite.csChange keeps track of the number of current changes (since +** the last statement) and is used to update sqlite_lsChange. +** +** The member variables sqlite.errCode, sqlite.zErrMsg and sqlite.zErrMsg16 +** store the most recent error code and, if applicable, string. The +** internal function sqlite3Error() is used to set these variables +** consistently. +*/ +struct sqlite3 { + sqlite3_vfs *pVfs; /* OS Interface */ + int nDb; /* Number of backends currently in use */ + Db *aDb; /* All backends */ + int flags; /* Miscellaneous flags. See below */ + int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ + int errCode; /* Most recent error code (SQLITE_*) */ + int errMask; /* & result codes with this before returning */ + u8 autoCommit; /* The auto-commit flag. */ + u8 temp_store; /* 1: file 2: memory 0: default */ + u8 mallocFailed; /* True if we have seen a malloc failure */ + u8 dfltLockMode; /* Default locking-mode for attached dbs */ + u8 dfltJournalMode; /* Default journal mode for attached dbs */ + signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ + int nextPagesize; /* Pagesize after VACUUM if >0 */ + int nTable; /* Number of tables in the database */ + CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ + i64 lastRowid; /* ROWID of most recent insert (see above) */ + u32 magic; /* Magic number for detect library misuse */ + int nChange; /* Value returned by sqlite3_changes() */ + int nTotalChange; /* Value returned by sqlite3_total_changes() */ + sqlite3_mutex *mutex; /* Connection mutex */ + int aLimit[SQLITE_N_LIMIT]; /* Limits */ + struct sqlite3InitInfo { /* Information used during initialization */ + int iDb; /* When back is being initialized */ + int newTnum; /* Rootpage of table being initialized */ + u8 busy; /* TRUE if currently initializing */ + } init; + int nExtension; /* Number of loaded extensions */ + void **aExtension; /* Array of shared library handles */ + struct Vdbe *pVdbe; /* List of active virtual machines */ + int activeVdbeCnt; /* Number of VDBEs currently executing */ + int writeVdbeCnt; /* Number of active VDBEs that are writing */ + void (*xTrace)(void*,const char*); /* Trace function */ + void *pTraceArg; /* Argument to the trace function */ + void (*xProfile)(void*,const char*,u64); /* Profiling function */ + void *pProfileArg; /* Argument to profile function */ + void *pCommitArg; /* Argument to xCommitCallback() */ + int (*xCommitCallback)(void*); /* Invoked at every commit. */ + void *pRollbackArg; /* Argument to xRollbackCallback() */ + void (*xRollbackCallback)(void*); /* Invoked at every commit. */ + void *pUpdateArg; + void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); + void *pCollNeededArg; + sqlite3_value *pErr; /* Most recent error message */ + char *zErrMsg; /* Most recent error message (UTF-8 encoded) */ + char *zErrMsg16; /* Most recent error message (UTF-16 encoded) */ + union { + volatile int isInterrupted; /* True if sqlite3_interrupt has been called */ + double notUsed1; /* Spacer */ + } u1; + Lookaside lookaside; /* Lookaside malloc configuration */ +#ifndef SQLITE_OMIT_AUTHORIZATION + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + /* Access authorization function */ + void *pAuthArg; /* 1st argument to the access auth function */ +#endif +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + int (*xProgress)(void *); /* The progress callback */ + void *pProgressArg; /* Argument to the progress callback */ + int nProgressOps; /* Number of opcodes for progress callback */ +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + Hash aModule; /* populated by sqlite3_create_module() */ + Table *pVTab; /* vtab with active Connect/Create method */ + sqlite3_vtab **aVTrans; /* Virtual tables with open transactions */ + int nVTrans; /* Allocated size of aVTrans */ +#endif + FuncDefHash aFunc; /* Hash table of connection functions */ + Hash aCollSeq; /* All collating sequences */ + BusyHandler busyHandler; /* Busy callback */ + int busyTimeout; /* Busy handler timeout, in msec */ + Db aDbStatic[2]; /* Static space for the 2 default backends */ + Savepoint *pSavepoint; /* List of active savepoints */ + int nSavepoint; /* Number of non-transaction savepoints */ + int nStatement; /* Number of nested statement-transactions */ + u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ + +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + /* The following variables are all protected by the STATIC_MASTER + ** mutex, not by sqlite3.mutex. They are used by code in notify.c. + ** + ** When X.pUnlockConnection==Y, that means that X is waiting for Y to + ** unlock so that it can proceed. + ** + ** When X.pBlockingConnection==Y, that means that something that X tried + ** tried to do recently failed with an SQLITE_LOCKED error due to locks + ** held by Y. + */ + sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */ + sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ + void *pUnlockArg; /* Argument to xUnlockNotify */ + void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ + sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ +#endif +}; + +/* +** A macro to discover the encoding of a database. +*/ +#define ENC(db) ((db)->aDb[0].pSchema->enc) + +/* +** Possible values for the sqlite.flags and or Db.flags fields. +** +** On sqlite.flags, the SQLITE_InTrans value means that we have +** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement +** transaction is active on that particular database file. +*/ +#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ +#define SQLITE_InTrans 0x00000008 /* True if in a transaction */ +#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */ +#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */ +#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ +#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ + /* result set is empty */ +#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */ +#define SQLITE_WriteSchema 0x00000800 /* OK to update SQLITE_MASTER */ +#define SQLITE_NoReadlock 0x00001000 /* Readlocks are omitted when + ** accessing read-only databases */ +#define SQLITE_IgnoreChecks 0x00002000 /* Do not enforce check constraints */ +#define SQLITE_ReadUncommitted 0x00004000 /* For shared-cache mode */ +#define SQLITE_LegacyFileFmt 0x00008000 /* Create new databases in format 1 */ +#define SQLITE_FullFSync 0x00010000 /* Use full fsync on the backend */ +#define SQLITE_LoadExtension 0x00020000 /* Enable load_extension */ + +#define SQLITE_RecoveryMode 0x00040000 /* Ignore schema errors */ +#define SQLITE_SharedCache 0x00080000 /* Cache sharing is enabled */ +#define SQLITE_CommitBusy 0x00200000 /* In the process of committing */ +#define SQLITE_ReverseOrder 0x00400000 /* Reverse unordered SELECTs */ + +/* +** Possible values for the sqlite.magic field. +** The numbers are obtained at random and have no special meaning, other +** than being distinct from one another. +*/ +#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ +#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ +#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ +#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ +#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ + +/* +** Each SQL function is defined by an instance of the following +** structure. A pointer to this structure is stored in the sqlite.aFunc +** hash table. When multiple functions have the same name, the hash table +** points to a linked list of these structures. +*/ +struct FuncDef { + i16 nArg; /* Number of arguments. -1 means unlimited */ + u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */ + u8 flags; /* Some combination of SQLITE_FUNC_* */ + void *pUserData; /* User data parameter */ + FuncDef *pNext; /* Next function with same name */ + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ + void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */ + void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */ + char *zName; /* SQL name of the function. */ + FuncDef *pHash; /* Next with a different name but the same hash */ +}; + +/* +** Possible values for FuncDef.flags +*/ +#define SQLITE_FUNC_LIKE 0x01 /* Candidate for the LIKE optimization */ +#define SQLITE_FUNC_CASE 0x02 /* Case-sensitive LIKE-type function */ +#define SQLITE_FUNC_EPHEM 0x04 /* Ephemeral. Delete with VDBE */ +#define SQLITE_FUNC_NEEDCOLL 0x08 /* sqlite3GetFuncCollSeq() might be called */ +#define SQLITE_FUNC_PRIVATE 0x10 /* Allowed for internal use only */ +#define SQLITE_FUNC_COUNT 0x20 /* Built-in count(*) aggregate */ + +/* +** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are +** used to create the initializers for the FuncDef structures. +** +** FUNCTION(zName, nArg, iArg, bNC, xFunc) +** Used to create a scalar function definition of a function zName +** implemented by C function xFunc that accepts nArg arguments. The +** value passed as iArg is cast to a (void*) and made available +** as the user-data (sqlite3_user_data()) for the function. If +** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. +** +** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) +** Used to create an aggregate function definition implemented by +** the C functions xStep and xFinal. The first four parameters +** are interpreted in the same way as the first 4 parameters to +** FUNCTION(). +** +** LIKEFUNC(zName, nArg, pArg, flags) +** Used to create a scalar function definition of a function zName +** that accepts nArg arguments and is implemented by a call to C +** function likeFunc. Argument pArg is cast to a (void *) and made +** available as the function user-data (sqlite3_user_data()). The +** FuncDef.flags variable is set to the value passed as the flags +** parameter. +*/ +#define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0} +#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ + {nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \ + pArg, 0, xFunc, 0, 0, #zName, 0} +#define LIKEFUNC(zName, nArg, arg, flags) \ + {nArg, SQLITE_UTF8, flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0} +#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \ + {nArg, SQLITE_UTF8, nc*SQLITE_FUNC_NEEDCOLL, \ + SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0} + +/* +** All current savepoints are stored in a linked list starting at +** sqlite3.pSavepoint. The first element in the list is the most recently +** opened savepoint. Savepoints are added to the list by the vdbe +** OP_Savepoint instruction. +*/ +struct Savepoint { + char *zName; /* Savepoint name (nul-terminated) */ + Savepoint *pNext; /* Parent savepoint (if any) */ +}; + +/* +** The following are used as the second parameter to sqlite3Savepoint(), +** and as the P1 argument to the OP_Savepoint instruction. +*/ +#define SAVEPOINT_BEGIN 0 +#define SAVEPOINT_RELEASE 1 +#define SAVEPOINT_ROLLBACK 2 + + +/* +** Each SQLite module (virtual table definition) is defined by an +** instance of the following structure, stored in the sqlite3.aModule +** hash table. +*/ +struct Module { + const sqlite3_module *pModule; /* Callback pointers */ + const char *zName; /* Name passed to create_module() */ + void *pAux; /* pAux passed to create_module() */ + void (*xDestroy)(void *); /* Module destructor function */ +}; + +/* +** information about each column of an SQL table is held in an instance +** of this structure. +*/ +struct Column { + char *zName; /* Name of this column */ + Expr *pDflt; /* Default value of this column */ + char *zDflt; /* Original text of the default value */ + char *zType; /* Data type for this column */ + char *zColl; /* Collating sequence. If NULL, use the default */ + u8 notNull; /* True if there is a NOT NULL constraint */ + u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */ + char affinity; /* One of the SQLITE_AFF_... values */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + u8 isHidden; /* True if this column is 'hidden' */ +#endif +}; + +/* +** A "Collating Sequence" is defined by an instance of the following +** structure. Conceptually, a collating sequence consists of a name and +** a comparison routine that defines the order of that sequence. +** +** There may two separate implementations of the collation function, one +** that processes text in UTF-8 encoding (CollSeq.xCmp) and another that +** processes text encoded in UTF-16 (CollSeq.xCmp16), using the machine +** native byte order. When a collation sequence is invoked, SQLite selects +** the version that will require the least expensive encoding +** translations, if any. +** +** The CollSeq.pUser member variable is an extra parameter that passed in +** as the first argument to the UTF-8 comparison function, xCmp. +** CollSeq.pUser16 is the equivalent for the UTF-16 comparison function, +** xCmp16. +** +** If both CollSeq.xCmp and CollSeq.xCmp16 are NULL, it means that the +** collating sequence is undefined. Indices built on an undefined +** collating sequence may not be read or written. +*/ +struct CollSeq { + char *zName; /* Name of the collating sequence, UTF-8 encoded */ + u8 enc; /* Text encoding handled by xCmp() */ + u8 type; /* One of the SQLITE_COLL_... values below */ + void *pUser; /* First argument to xCmp() */ + int (*xCmp)(void*,int, const void*, int, const void*); + void (*xDel)(void*); /* Destructor for pUser */ +}; + +/* +** Allowed values of CollSeq.type: +*/ +#define SQLITE_COLL_BINARY 1 /* The default memcmp() collating sequence */ +#define SQLITE_COLL_NOCASE 2 /* The built-in NOCASE collating sequence */ +#define SQLITE_COLL_REVERSE 3 /* The built-in REVERSE collating sequence */ +#define SQLITE_COLL_USER 0 /* Any other user-defined collating sequence */ + +/* +** A sort order can be either ASC or DESC. +*/ +#define SQLITE_SO_ASC 0 /* Sort in ascending order */ +#define SQLITE_SO_DESC 1 /* Sort in ascending order */ + +/* +** Column affinity types. +** +** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and +** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve +** the speed a little by numbering the values consecutively. +** +** But rather than start with 0 or 1, we begin with 'a'. That way, +** when multiple affinity types are concatenated into a string and +** used as the P4 operand, they will be more readable. +** +** Note also that the numeric types are grouped together so that testing +** for a numeric type is a single comparison. +*/ +#define SQLITE_AFF_TEXT 'a' +#define SQLITE_AFF_NONE 'b' +#define SQLITE_AFF_NUMERIC 'c' +#define SQLITE_AFF_INTEGER 'd' +#define SQLITE_AFF_REAL 'e' + +#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) + +/* +** The SQLITE_AFF_MASK values masks off the significant bits of an +** affinity value. +*/ +#define SQLITE_AFF_MASK 0x67 + +/* +** Additional bit values that can be ORed with an affinity without +** changing the affinity. +*/ +#define SQLITE_JUMPIFNULL 0x08 /* jumps if either operand is NULL */ +#define SQLITE_STOREP2 0x10 /* Store result in reg[P2] rather than jump */ + +/* +** Each SQL table is represented in memory by an instance of the +** following structure. +** +** Table.zName is the name of the table. The case of the original +** CREATE TABLE statement is stored, but case is not significant for +** comparisons. +** +** Table.nCol is the number of columns in this table. Table.aCol is a +** pointer to an array of Column structures, one for each column. +** +** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of +** the column that is that key. Otherwise Table.iPKey is negative. Note +** that the datatype of the PRIMARY KEY must be INTEGER for this field to +** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of +** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid +** is generated for each row of the table. TF_HasPrimaryKey is set if +** the table has any PRIMARY KEY, INTEGER or otherwise. +** +** Table.tnum is the page number for the root BTree page of the table in the +** database file. If Table.iDb is the index of the database table backend +** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that +** holds temporary tables and indices. If TF_Ephemeral is set +** then the table is stored in a file that is automatically deleted +** when the VDBE cursor to the table is closed. In this case Table.tnum +** refers VDBE cursor number that holds the table open, not to the root +** page number. Transient tables are used to hold the results of a +** sub-query that appears instead of a real table name in the FROM clause +** of a SELECT statement. +*/ +struct Table { + sqlite3 *dbMem; /* DB connection used for lookaside allocations. */ + char *zName; /* Name of the table or view */ + int iPKey; /* If not negative, use aCol[iPKey] as the primary key */ + int nCol; /* Number of columns in this table */ + Column *aCol; /* Information about each column */ + Index *pIndex; /* List of SQL indexes on this table. */ + int tnum; /* Root BTree node for this table (see note above) */ + Select *pSelect; /* NULL for tables. Points to definition if a view. */ + u16 nRef; /* Number of pointers to this Table */ + u8 tabFlags; /* Mask of TF_* values */ + u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + char *zColAff; /* String defining the affinity of each column */ +#ifndef SQLITE_OMIT_CHECK + Expr *pCheck; /* The AND of all CHECK constraints */ +#endif +#ifndef SQLITE_OMIT_ALTERTABLE + int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + Module *pMod; /* Pointer to the implementation of the module */ + sqlite3_vtab *pVtab; /* Pointer to the module instance */ + int nModuleArg; /* Number of arguments to the module */ + char **azModuleArg; /* Text of all module args. [0] is module name */ +#endif + Trigger *pTrigger; /* List of triggers stored in pSchema */ + Schema *pSchema; /* Schema that contains this table */ + Table *pNextZombie; /* Next on the Parse.pZombieTab list */ +}; + +/* +** Allowed values for Tabe.tabFlags. +*/ +#define TF_Readonly 0x01 /* Read-only system table */ +#define TF_Ephemeral 0x02 /* An ephemeral table */ +#define TF_HasPrimaryKey 0x04 /* Table has a primary key */ +#define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ +#define TF_Virtual 0x10 /* Is a virtual table */ +#define TF_NeedMetadata 0x20 /* aCol[].zType and aCol[].pColl missing */ + + + +/* +** Test to see whether or not a table is a virtual table. This is +** done as a macro so that it will be optimized out when virtual +** table support is omitted from the build. +*/ +#ifndef SQLITE_OMIT_VIRTUALTABLE +# define IsVirtual(X) (((X)->tabFlags & TF_Virtual)!=0) +# define IsHiddenColumn(X) ((X)->isHidden) +#else +# define IsVirtual(X) 0 +# define IsHiddenColumn(X) 0 +#endif + +/* +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existence of the to-table is not checked. +*/ +struct FKey { + Table *pFrom; /* The table that contains the REFERENCES clause */ + FKey *pNextFrom; /* Next foreign key in pFrom */ + char *zTo; /* Name of table that the key points to */ + int nCol; /* Number of columns in this key */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 updateConf; /* How to resolve conflicts that occur on UPDATE */ + u8 deleteConf; /* How to resolve conflicts that occur on DELETE */ + u8 insertConf; /* How to resolve conflicts that occur on INSERT */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ + } aCol[1]; /* One entry for each of nCol column s */ +}; + +/* +** SQLite supports many different ways to resolve a constraint +** error. ROLLBACK processing means that a constraint violation +** causes the operation in process to fail and for the current transaction +** to be rolled back. ABORT processing means the operation in process +** fails and any prior changes from that one operation are backed out, +** but the transaction is not rolled back. FAIL processing means that +** the operation in progress stops and returns an error code. But prior +** changes due to the same operation are not backed out and no rollback +** occurs. IGNORE means that the particular row that caused the constraint +** error is not inserted or updated. Processing continues and no error +** is returned. REPLACE means that preexisting database rows that caused +** a UNIQUE constraint violation are removed so that the new insert or +** update can proceed. Processing continues and no error is reported. +** +** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. +** +** The following symbolic values are used to record which type +** of action to take. +*/ +#define OE_None 0 /* There is no constraint to check */ +#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ +#define OE_Abort 2 /* Back out changes but do no rollback transaction */ +#define OE_Fail 3 /* Stop the operation but leave all prior changes */ +#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ +#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ + +#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 7 /* Set the foreign key value to NULL */ +#define OE_SetDflt 8 /* Set the foreign key value to its default */ +#define OE_Cascade 9 /* Cascade the changes */ + +#define OE_Default 99 /* Do whatever the default action is */ + + +/* +** An instance of the following structure is passed as the first +** argument to sqlite3VdbeKeyCompare and is used to control the +** comparison of the two index keys. +*/ +struct KeyInfo { + sqlite3 *db; /* The database connection */ + u8 enc; /* Text encoding - one of the TEXT_Utf* values */ + u16 nField; /* Number of entries in aColl[] */ + u8 *aSortOrder; /* If defined an aSortOrder[i] is true, sort DESC */ + CollSeq *aColl[1]; /* Collating sequence for each term of the key */ +}; + +/* +** An instance of the following structure holds information about a +** single index record that has already been parsed out into individual +** values. +** +** A record is an object that contains one or more fields of data. +** Records are used to store the content of a table row and to store +** the key of an index. A blob encoding of a record is created by +** the OP_MakeRecord opcode of the VDBE and is disassembled by the +** OP_Column opcode. +** +** This structure holds a record that has already been disassembled +** into its constituent fields. +*/ +struct UnpackedRecord { + KeyInfo *pKeyInfo; /* Collation and sort-order information */ + u16 nField; /* Number of entries in apMem[] */ + u16 flags; /* Boolean settings. UNPACKED_... below */ + i64 rowid; /* Used by UNPACKED_PREFIX_SEARCH */ + Mem *aMem; /* Values */ +}; + +/* +** Allowed values of UnpackedRecord.flags +*/ +#define UNPACKED_NEED_FREE 0x0001 /* Memory is from sqlite3Malloc() */ +#define UNPACKED_NEED_DESTROY 0x0002 /* apMem[]s should all be destroyed */ +#define UNPACKED_IGNORE_ROWID 0x0004 /* Ignore trailing rowid on key1 */ +#define UNPACKED_INCRKEY 0x0008 /* Make this key an epsilon larger */ +#define UNPACKED_PREFIX_MATCH 0x0010 /* A prefix match is considered OK */ +#define UNPACKED_PREFIX_SEARCH 0x0020 /* A prefix match is considered OK */ + +/* +** Each SQL index is represented in memory by an +** instance of the following structure. +** +** The columns of the table that are to be indexed are described +** by the aiColumn[] field of this structure. For example, suppose +** we have the following table and index: +** +** CREATE TABLE Ex1(c1 int, c2 int, c3 text); +** CREATE INDEX Ex2 ON Ex1(c3,c1); +** +** In the Table structure describing Ex1, nCol==3 because there are +** three columns in the table. In the Index structure describing +** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. +** The second column to be indexed (c1) has an index of 0 in +** Ex1.aCol[], hence Ex2.aiColumn[1]==0. +** +** The Index.onError field determines whether or not the indexed columns +** must be unique and what to do if they are not. When Index.onError=OE_None, +** it means this is not a unique index. Otherwise it is a unique index +** and the value of Index.onError indicate the which conflict resolution +** algorithm to employ whenever an attempt is made to insert a non-unique +** element. +*/ +struct Index { + char *zName; /* Name of this index */ + int nColumn; /* Number of columns in the table used by this index */ + int *aiColumn; /* Which columns are used by this index. 1st is 0 */ + unsigned *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ + Table *pTable; /* The SQL table being indexed */ + int tnum; /* Page containing root of this index in database file */ + u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ + char *zColAff; /* String defining the affinity of each column */ + Index *pNext; /* The next index associated with the same table */ + Schema *pSchema; /* Schema containing this index */ + u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */ + char **azColl; /* Array of collation sequence names for index */ +}; + +/* +** Each token coming out of the lexer is an instance of +** this structure. Tokens are also used as part of an expression. +** +** Note if Token.z==0 then Token.dyn and Token.n are undefined and +** may contain random values. Do not make any assumptions about Token.dyn +** and Token.n when Token.z==0. +*/ +struct Token { + const char *z; /* Text of the token. Not NULL-terminated! */ + unsigned int n; /* Number of characters in this token */ +}; + +/* +** An instance of this structure contains information needed to generate +** code for a SELECT that contains aggregate functions. +** +** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a +** pointer to this structure. The Expr.iColumn field is the index in +** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate +** code for that node. +** +** AggInfo.pGroupBy and AggInfo.aFunc.pExpr point to fields within the +** original Select structure that describes the SELECT statement. These +** fields do not need to be freed when deallocating the AggInfo structure. +*/ +struct AggInfo { + u8 directMode; /* Direct rendering mode means take data directly + ** from source tables rather than from accumulators */ + u8 useSortingIdx; /* In direct mode, reference the sorting index rather + ** than the source table */ + int sortingIdx; /* Cursor number of the sorting index */ + ExprList *pGroupBy; /* The group by clause */ + int nSortingColumn; /* Number of columns in the sorting index */ + struct AggInfo_col { /* For each column used in source tables */ + Table *pTab; /* Source table */ + int iTable; /* Cursor number of the source table */ + int iColumn; /* Column number within the source table */ + int iSorterColumn; /* Column number in the sorting index */ + int iMem; /* Memory location that acts as accumulator */ + Expr *pExpr; /* The original expression */ + } *aCol; + int nColumn; /* Number of used entries in aCol[] */ + int nColumnAlloc; /* Number of slots allocated for aCol[] */ + int nAccumulator; /* Number of columns that show through to the output. + ** Additional columns are used only as parameters to + ** aggregate functions */ + struct AggInfo_func { /* For each aggregate function */ + Expr *pExpr; /* Expression encoding the function */ + FuncDef *pFunc; /* The aggregate function implementation */ + int iMem; /* Memory location that acts as accumulator */ + int iDistinct; /* Ephemeral table used to enforce DISTINCT */ + } *aFunc; + int nFunc; /* Number of entries in aFunc[] */ + int nFuncAlloc; /* Number of slots allocated for aFunc[] */ +}; + +/* +** Each node of an expression in the parse tree is an instance +** of this structure. +** +** Expr.op is the opcode. The integer parser token codes are reused +** as opcodes here. For example, the parser defines TK_GE to be an integer +** code representing the ">=" operator. This same integer code is reused +** to represent the greater-than-or-equal-to operator in the expression +** tree. +** +** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, +** or TK_STRING), then Expr.token contains the text of the SQL literal. If +** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), +** then Expr.token contains the name of the function. +** +** Expr.pRight and Expr.pLeft are the left and right subexpressions of a +** binary operator. Either or both may be NULL. +** +** Expr.x.pList is a list of arguments if the expression is an SQL function, +** a CASE expression or an IN expression of the form " IN (, ...)". +** Expr.x.pSelect is used if the expression is a sub-select or an expression of +** the form " IN (SELECT ...)". If the EP_xIsSelect bit is set in the +** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is +** valid. +** +** An expression of the form ID or ID.ID refers to a column in a table. +** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is +** the integer cursor number of a VDBE cursor pointing to that table and +** Expr.iColumn is the column number for the specific column. If the +** expression is used as a result in an aggregate SELECT, then the +** value is also stored in the Expr.iAgg column in the aggregate so that +** it can be accessed after all aggregates are computed. +** +** If the expression is an unbound variable marker (a question mark +** character '?' in the original SQL) then the Expr.iTable holds the index +** number for that variable. +** +** If the expression is a subquery then Expr.iColumn holds an integer +** register number containing the result of the subquery. If the +** subquery gives a constant result, then iTable is -1. If the subquery +** gives a different answer at different times during statement processing +** then iTable is the address of a subroutine that computes the subquery. +** +** If the Expr is of type OP_Column, and the table it is selecting from +** is a disk table or the "old.*" pseudo-table, then pTab points to the +** corresponding table definition. +** +** ALLOCATION NOTES: +** +** Expr objects can use a lot of memory space in database schema. To +** help reduce memory requirements, sometimes an Expr object will be +** truncated. And to reduce the number of memory allocations, sometimes +** two or more Expr objects will be stored in a single memory allocation, +** together with Expr.zToken strings. +** +** If the EP_Reduced and EP_TokenOnly flags are set when +** an Expr object is truncated. When EP_Reduced is set, then all +** the child Expr objects in the Expr.pLeft and Expr.pRight subtrees +** are contained within the same memory allocation. Note, however, that +** the subtrees in Expr.x.pList or Expr.x.pSelect are always separately +** allocated, regardless of whether or not EP_Reduced is set. +*/ +struct Expr { + u8 op; /* Operation performed by this node */ + char affinity; /* The affinity of the column or 0 if not a column */ + u16 flags; /* Various flags. EP_* See below */ + union { + char *zToken; /* Token value. Zero terminated and dequoted */ + int iValue; /* Integer value if EP_IntValue */ + } u; + + /* If the EP_TokenOnly flag is set in the Expr.flags mask, then no + ** space is allocated for the fields below this point. An attempt to + ** access them will result in a segfault or malfunction. + *********************************************************************/ + + Expr *pLeft; /* Left subnode */ + Expr *pRight; /* Right subnode */ + union { + ExprList *pList; /* Function arguments or in " IN ( IN (