617 lines
14 KiB
C++
617 lines
14 KiB
C++
// _________ __ __
|
|
// / _____// |_____________ _/ |______ ____ __ __ ______
|
|
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
|
|
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
|
|
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
|
|
// \/ \/ \//_____/ \/
|
|
// ______________________ ______________________
|
|
// T H E W A R B E G I N S
|
|
// Stratagus - A free fantasy real time strategy game engine
|
|
//
|
|
/**@name cmd.cpp - Client/Server Command Interpreter. */
|
|
//
|
|
// (c) Copyright 2005 by Edward Haase and Jimmy Salmon
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; only version 2 of the License.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
// 02111-1307, USA.
|
|
//
|
|
|
|
//@{
|
|
|
|
/*----------------------------------------------------------------------------
|
|
-- Includes
|
|
----------------------------------------------------------------------------*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "stratagus.h"
|
|
#include "cmd.h"
|
|
#include "netdriver.h"
|
|
#include "db.h"
|
|
#include "games.h"
|
|
|
|
/*----------------------------------------------------------------------------
|
|
-- Functions
|
|
----------------------------------------------------------------------------*/
|
|
|
|
#define GetNextArg(buf, arg) \
|
|
do { \
|
|
if (*buf == '\"') { \
|
|
*arg = ++buf; \
|
|
while (*buf != '\"' && *buf) ++buf; \
|
|
if (*buf != '\"') return 1; \
|
|
*buf++ = '\0'; \
|
|
if (**arg == '\0') return 1; \
|
|
if (*buf != ' ') return 1; \
|
|
} else { \
|
|
*arg = buf; \
|
|
while (*buf != ' ' && *buf) ++buf; \
|
|
if (!*buf) return 1; \
|
|
} \
|
|
*buf++ = '\0'; \
|
|
} while (0)
|
|
|
|
#define GetLastArg(buf, arg) \
|
|
do { \
|
|
if (*buf == '\"') { \
|
|
*arg = ++buf; \
|
|
while (*buf != '\"' && *buf) ++buf; \
|
|
if (*buf != '\"') return 1; \
|
|
*buf++ = '\0'; \
|
|
if (**arg == '\0') return 1; \
|
|
if (*buf != ' ' && *buf) return 1; \
|
|
} else { \
|
|
*arg = buf; \
|
|
while (*buf != ' ' && *buf) ++buf; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define GetNextAndOptionalArg(buf, arg1, arg2) \
|
|
do { \
|
|
if (*buf == '\"') { \
|
|
*arg1 = ++buf; \
|
|
while (*buf != '\"' && *buf) ++buf; \
|
|
if (*buf != '\"') return 1; \
|
|
*buf++ = '\0'; \
|
|
if (**arg1 == '\0') return 1; \
|
|
if (*buf != ' ' && *buf) return 1; \
|
|
} else { \
|
|
*arg1 = buf; \
|
|
while (*buf != ' ' && *buf) ++buf; \
|
|
} \
|
|
if (*buf == ' ') *buf++ = '\0'; \
|
|
\
|
|
*arg2 = NULL; \
|
|
while (*buf == ' ') ++buf; \
|
|
if (!*buf) return 0; \
|
|
\
|
|
GetLastArg(buf, arg2); \
|
|
} while (0)
|
|
|
|
#define SkipSpaces(buf) \
|
|
do { \
|
|
while (*buf == ' ') ++buf; \
|
|
if (!*buf) return 1; \
|
|
} while (0)
|
|
|
|
#define CheckExtraParameter(buf) \
|
|
do { \
|
|
if (*buf) { \
|
|
while (*buf == ' ') ++buf; \
|
|
if (*buf) return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int Parse1Arg(char *buf, char **arg1)
|
|
{
|
|
SkipSpaces(buf);
|
|
|
|
GetLastArg(buf, arg1);
|
|
CheckExtraParameter(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int Parse1or2Args(char *buf, char **arg1, char **arg2)
|
|
{
|
|
SkipSpaces(buf);
|
|
|
|
GetNextAndOptionalArg(buf, arg1, arg2);
|
|
CheckExtraParameter(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int Parse4Args(char *buf, char **arg1, char **arg2, char **arg3,
|
|
char **arg4)
|
|
{
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg1);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg2);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg3);
|
|
SkipSpaces(buf);
|
|
|
|
GetLastArg(buf, arg4);
|
|
CheckExtraParameter(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0 // not used
|
|
static int Parse5Args(char *buf, char **arg1, char **arg2, char **arg3,
|
|
char **arg4, char **arg5)
|
|
{
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg1);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg2);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg3);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg4);
|
|
SkipSpaces(buf);
|
|
|
|
GetLastArg(buf, arg5);
|
|
CheckExtraParameter(buf);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int Parse5or6Args(char *buf, char **arg1, char **arg2, char **arg3,
|
|
char **arg4, char **arg5, char **arg6)
|
|
{
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg1);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg2);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg3);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextArg(buf, arg4);
|
|
SkipSpaces(buf);
|
|
|
|
GetNextAndOptionalArg(buf, arg5, arg6);
|
|
CheckExtraParameter(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
** Parse PING
|
|
*/
|
|
static void ParsePing(Session *session)
|
|
{
|
|
Send(session, "PING_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse USER
|
|
*/
|
|
static void ParseUser(Session *session, char *buf)
|
|
{
|
|
char *username;
|
|
char *password;
|
|
char *gamename;
|
|
char *gamever;
|
|
char pw[MAX_PASSWORD_LENGTH + 1];
|
|
|
|
if (Parse4Args(buf, &username, &password, &gamename, &gamever)) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (strlen(username) > MAX_USERNAME_LENGTH ||
|
|
strlen(password) > MAX_PASSWORD_LENGTH ||
|
|
strlen(gamename) > MAX_GAMENAME_LENGTH ||
|
|
strlen(gamever) > MAX_VERSION_LENGTH) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (!DBFindUser(username, pw)) {
|
|
DebugPrint("Username doesn't exist: %s\n" _C_ username);
|
|
Send(session, "ERR_NOUSER\n");
|
|
return;
|
|
}
|
|
if (strcmp(pw, password)) {
|
|
DebugPrint("Bad password for user %s\n" _C_ username);
|
|
Send(session, "ERR_BADPASSWORD\n");
|
|
return;
|
|
}
|
|
|
|
DebugPrint("User logged in: %s\n" _C_ username);
|
|
strcpy(session->UserData.Name, username);
|
|
strcpy(session->UserData.GameName, gamename);
|
|
strcpy(session->UserData.Version, gamever);
|
|
session->UserData.LoggedIn = 1;
|
|
DBUpdateLoginDate(username);
|
|
Send(session, "USER_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse REGISTER
|
|
*/
|
|
static void ParseRegister(Session *session, char *buf)
|
|
{
|
|
char *username;
|
|
char *password;
|
|
char *gamename;
|
|
char *gamever;
|
|
char pw[MAX_PASSWORD_LENGTH + 1];
|
|
|
|
if (Parse4Args(buf, &username, &password, &gamename, &gamever)) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (strlen(username) > MAX_USERNAME_LENGTH ||
|
|
strlen(password) > MAX_PASSWORD_LENGTH ||
|
|
strlen(gamename) > MAX_GAMENAME_LENGTH ||
|
|
strlen(gamever) > MAX_VERSION_LENGTH) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (DBFindUser(username, pw)) {
|
|
DebugPrint("Tried to register existing user: %s\n" _C_ username);
|
|
Send(session, "ERR_USEREXISTS\n");
|
|
return;
|
|
}
|
|
|
|
DebugPrint("New user registered: %s\n" _C_ username);
|
|
session->UserData.LoggedIn = 1;
|
|
strcpy(session->UserData.Name, username);
|
|
strcpy(session->UserData.GameName, gamename);
|
|
strcpy(session->UserData.Version, gamever);
|
|
DBAddUser(username, password); // FIXME: if this fails?
|
|
Send(session, "REGISTER_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse CREATEGAME
|
|
*/
|
|
static void ParseCreateGame(Session *session, char *buf)
|
|
{
|
|
char *description;
|
|
char *map;
|
|
char *players;
|
|
char *ip;
|
|
char *port;
|
|
char *password;
|
|
int players_int;
|
|
int port_int;
|
|
|
|
|
|
if (Parse5or6Args(buf, &description, &map, &players, &ip, &port, &password)) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
players_int = atoi(players);
|
|
port_int = atoi(port);
|
|
// FIXME: check ip
|
|
if (strlen(description) > MAX_DESCRIPTION_LENGTH ||
|
|
strlen(map) > MAX_MAP_LENGTH ||
|
|
players_int < 1 || players_int > 16 ||
|
|
port_int < 1 || port_int > 66535 ||
|
|
(password && strlen(password) > MAX_GAME_PASSWORD_LENGTH)) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
CreateGame(session, description, map, players, ip, port, password);
|
|
|
|
DebugPrint("%s created a game\n" _C_ session->UserData.Name);
|
|
DBAddGame(session->Game->ID, description, map, players_int);
|
|
Send(session, "CREATEGAME_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse CANCELGAME
|
|
*/
|
|
static void ParseCancelGame(Session *session, char *buf)
|
|
{
|
|
// No args
|
|
while (*buf == ' ') ++buf;
|
|
if (*buf) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (CancelGame(session)) {
|
|
Send(session, "ERR_NOGAMECREATED\n");
|
|
return;
|
|
}
|
|
|
|
DebugPrint("%s canceled a game\n" _C_ session->UserData.Name);
|
|
Send(session, "CANCELGAME_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse STARTGAME
|
|
*/
|
|
static void ParseStartGame(Session *session, char *buf)
|
|
{
|
|
// No args
|
|
while (*buf == ' ') ++buf;
|
|
if (*buf) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
if (StartGame(session)) {
|
|
Send(session, "ERR_NOGAMECREATED\n");
|
|
return;
|
|
}
|
|
|
|
DebugPrint("%s started a game\n" _C_ session->UserData.Name);
|
|
Send(session, "STARTGAME_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse LISTGAMES
|
|
*/
|
|
static void ParseListGames(Session *session, char *buf)
|
|
{
|
|
// No args
|
|
while (*buf == ' ') ++buf;
|
|
if (*buf) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
ListGames(session);
|
|
|
|
Send(session, "LISTGAMES_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse JOINGAME
|
|
*/
|
|
static void ParseJoinGame(Session *session, char *buf)
|
|
{
|
|
char *id;
|
|
char *password;
|
|
int ret;
|
|
unsigned long udphost;
|
|
int udpport;
|
|
|
|
if (Parse1or2Args(buf, &id, &password)) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
ret = JoinGame(session, atoi(id), password, &udphost, &udpport);
|
|
if (ret == -1) {
|
|
Send(session, "ERR_ALREADYINGAME\n");
|
|
return;
|
|
} else if (ret == -2) { // ID not found
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
} else if (ret == -3) {
|
|
if (!password) {
|
|
Send(session, "ERR_NEEDPASSWORD\n");
|
|
} else {
|
|
Send(session, "ERR_BADPASSWORD\n");
|
|
}
|
|
return;
|
|
} else if (ret == -4) {
|
|
Send(session, "ERR_GAMEFULL\n");
|
|
return;
|
|
} else if (ret == -5) {
|
|
Send(session, "ERR_SERVERNOTREADY\n");
|
|
return;
|
|
}
|
|
|
|
char* reply = (char*)calloc(sizeof(char), strlen("JOINGAME_OK 255.255.255.255 66535\n") + 1);
|
|
sprintf(reply, "JOINGAME_OK %d.%d.%d.%d %d\n", NIPQUAD(ntohl(UDPHost)), udpport);
|
|
DebugPrint("%s joined game %d with %s\n" _C_ session->UserData.Name _C_ atoi(id) _C_ reply);
|
|
Send(session, reply);
|
|
free(reply);
|
|
}
|
|
|
|
/**
|
|
** Parse PARTGAME
|
|
*/
|
|
static void ParsePartGame(Session *session, char *buf)
|
|
{
|
|
int ret;
|
|
|
|
// No args
|
|
while (*buf == ' ') ++buf;
|
|
if (*buf) {
|
|
Send(session, "ERR_BADPARAMETER\n");
|
|
return;
|
|
}
|
|
|
|
ret = PartGame(session);
|
|
if (ret == -1) {
|
|
Send(session, "ERR_NOTINGAME\n");
|
|
return;
|
|
} else if (ret == -2) {
|
|
Send(session, "ERR_GAMESTARTED\n");
|
|
return;
|
|
}
|
|
|
|
DebugPrint("%s left a game\n" _C_ session->UserData.Name);
|
|
Send(session, "PARTGAME_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse ENDGAME
|
|
*/
|
|
static void ParseEndGame(Session *session, char *buf)
|
|
{
|
|
char *result;
|
|
|
|
Parse1Arg(buf, &result);
|
|
|
|
Send(session, "ENDGAME_OK\n");
|
|
}
|
|
|
|
|
|
/**
|
|
** Parse STATS
|
|
*/
|
|
static void ParseStats(Session *session, char *buf)
|
|
{
|
|
char *result;
|
|
int start_time = 0;
|
|
char resultbuf[20] = {'\0'};
|
|
|
|
while (*buf == ' ') ++buf;
|
|
if (*buf) {
|
|
Parse1Arg(buf, &result);
|
|
start_time = atoi(result);
|
|
}
|
|
|
|
DebugPrint("%s requested stats\n" _C_ session->UserData.Name);
|
|
char* reply = (char*)calloc(sizeof(char), strlen("GAMES SINCE 12345678901234567890: 12345678901234567890\n") + 1);
|
|
DBStats(resultbuf, start_time);
|
|
sprintf(reply, "GAMES SINCE %d: %s\n", start_time, resultbuf);
|
|
Send(session, reply);
|
|
Send(session, "STATS_OK\n");
|
|
}
|
|
|
|
/**
|
|
** Parse MSG
|
|
*/
|
|
static void ParseMsg(Session *session, char *buf)
|
|
{
|
|
}
|
|
|
|
/**
|
|
** ParseBuffer: Handler client/server interaction.
|
|
**
|
|
** @param session Current session.
|
|
*/
|
|
static void ParseBuffer(Session *session)
|
|
{
|
|
char *buf;
|
|
|
|
if (!session || session->Buffer[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
buf = session->Buffer;
|
|
if (!strncmp(buf, "PING", 4)) {
|
|
ParsePing(session);
|
|
} else {
|
|
if (!session->UserData.LoggedIn) {
|
|
if (!strncmp(buf, "USER ", 5)) {
|
|
ParseUser(session, buf + 5);
|
|
} else if (!strncmp(buf, "REGISTER ", 9)) {
|
|
ParseRegister(session, buf + 9);
|
|
}
|
|
} else {
|
|
if (!strncmp(buf, "USER ", 5) || !strncmp(buf, "REGISTER ", 9)) {
|
|
Send(session, "ERR_ALREADYLOGGEDIN\n");
|
|
}
|
|
}
|
|
if (!strncmp(buf, "CREATEGAME ", 11)) {
|
|
ParseCreateGame(session, buf + 11);
|
|
} else if (!strcmp(buf, "CANCELGAME") || !strncmp(buf, "CANCELGAME ", 11)) {
|
|
ParseCancelGame(session, buf + 10);
|
|
} else if (!strcmp(buf, "STARTGAME") || !strncmp(buf, "STARTGAME ", 10)) {
|
|
ParseStartGame(session, buf + 9);
|
|
} else if (!strcmp(buf, "LISTGAMES") || !strncmp(buf, "LISTGAMES ", 10)) {
|
|
ParseListGames(session, buf + 9);
|
|
} else if (!strncmp(buf, "JOINGAME ", 9)) {
|
|
ParseJoinGame(session, buf + 9);
|
|
} else if (!strcmp(buf, "PARTGAME") || !strncmp(buf, "PARTGAME ", 9)) {
|
|
ParsePartGame(session, buf + 8);
|
|
} else if (!strncmp(buf, "ENDGAME ", 8)) {
|
|
ParseEndGame(session, buf + 8);
|
|
} else if (!strncmp(buf, "STATS ", 6)) {
|
|
ParseStats(session, buf + 6);
|
|
} else if (!strncmp(buf, "MSG ", 4)) {
|
|
ParseMsg(session, buf + 4);
|
|
} else {
|
|
fprintf(stderr, "Unknown command: %s\n", session->Buffer);
|
|
Send(session, "ERR_BADCOMMAND\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Parse all session buffers
|
|
*/
|
|
int UpdateParser(void)
|
|
{
|
|
Session *session;
|
|
int len;
|
|
char *next;
|
|
|
|
if (!Pool || !Pool->First) {
|
|
// No connections
|
|
return 0;
|
|
}
|
|
|
|
for (session = Pool->First; session; session = session->Next) {
|
|
// Confirm full message.
|
|
while ((next = strpbrk(session->Buffer, "\r\n"))) {
|
|
*next++ = '\0';
|
|
if (*next == '\r' || *next == '\n') {
|
|
++next;
|
|
}
|
|
|
|
ParseBuffer(session);
|
|
|
|
// Remove parsed message
|
|
len = next - session->Buffer;
|
|
memmove(session->Buffer, next, sizeof(session->Buffer) - len);
|
|
session->Buffer[sizeof(session->Buffer) - len] = '\0';
|
|
}
|
|
|
|
}
|
|
|
|
if (strlen(UDPBuffer)) {
|
|
// If this is a server, we'll note its external data. When clients join,
|
|
// they'll receive this as part of the TCP response that they
|
|
// successfully joined. This is a simplification of the full UDP hole
|
|
// punching algorithm, which unneccessarily might go through the NAT
|
|
// even for clients inside the same NAT. This will also not work if in
|
|
// that case the NAT does not support hairpin translation. But we'll see
|
|
// how common that is...
|
|
char ip[128] = {'\0'};
|
|
char port[128] = {'\0'};
|
|
sscanf(UDPBuffer, "%s %s", (char*)&ip, (char*)&port);
|
|
DebugPrint("Filling in UDP info for %s:%s\n" _C_ ip _C_ port);
|
|
if (FillinUDPInfo(UDPHost, UDPPort, ip, port)) {
|
|
fprintf(stderr, "Error filling in UDP info for %s:%s with %d.%d.%d.%d:%d",
|
|
ip, port, NIPQUAD(ntohl(UDPHost)), UDPPort);
|
|
}
|
|
UDPBuffer[0] = '\0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//@}
|