mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
c7e7a4d7be
If shared memory allocation failed on client server will be started without shared memory option. Only downside of this is that server wouldn't be able to fallback to random port if default is busy.
693 lines
17 KiB
C++
693 lines
17 KiB
C++
#include "StdInc.h"
|
|
|
|
#include <boost/asio.hpp>
|
|
|
|
#include "../lib/filesystem/Filesystem.h"
|
|
#include "../lib/mapping/CCampaignHandler.h"
|
|
#include "../lib/CThreadHelper.h"
|
|
#include "../lib/serializer/Connection.h"
|
|
#include "../lib/CModHandler.h"
|
|
#include "../lib/CArtHandler.h"
|
|
#include "../lib/CGeneralTextHandler.h"
|
|
#include "../lib/CHeroHandler.h"
|
|
#include "../lib/CTownHandler.h"
|
|
#include "../lib/CBuildingHandler.h"
|
|
#include "../lib/spells/CSpellHandler.h"
|
|
#include "../lib/CCreatureHandler.h"
|
|
#include "zlib.h"
|
|
#include "CVCMIServer.h"
|
|
#include "../lib/StartInfo.h"
|
|
#include "../lib/mapping/CMap.h"
|
|
#include "../lib/rmg/CMapGenOptions.h"
|
|
#ifdef VCMI_ANDROID
|
|
#include "lib/CAndroidVMHelper.h"
|
|
#else
|
|
#include "../lib/Interprocess.h"
|
|
#endif
|
|
#include "../lib/VCMI_Lib.h"
|
|
#include "../lib/VCMIDirs.h"
|
|
#include "CGameHandler.h"
|
|
#include "../lib/mapping/CMapInfo.h"
|
|
#include "../lib/GameConstants.h"
|
|
#include "../lib/logging/CBasicLogConfigurator.h"
|
|
#include "../lib/CConfigHandler.h"
|
|
#include "../lib/ScopeGuard.h"
|
|
|
|
#include "../lib/UnlockGuard.h"
|
|
|
|
#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
|
|
#include <execinfo.h>
|
|
#endif
|
|
|
|
std::string NAME_AFFIX = "server";
|
|
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
|
|
#ifndef VCMI_ANDROID
|
|
namespace intpr = boost::interprocess;
|
|
#endif
|
|
bool end2 = false;
|
|
|
|
boost::program_options::variables_map cmdLineOptions;
|
|
|
|
/*
|
|
* CVCMIServer.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
|
|
static void vaccept(boost::asio::ip::tcp::acceptor *ac, boost::asio::ip::tcp::socket *s, boost::system::error_code *error)
|
|
{
|
|
ac->accept(*s,*error);
|
|
}
|
|
|
|
|
|
|
|
CPregameServer::CPregameServer(CConnection *Host, TAcceptor *Acceptor /*= nullptr*/)
|
|
: host(Host), listeningThreads(0), acceptor(Acceptor), upcomingConnection(nullptr),
|
|
curmap(nullptr), curStartInfo(nullptr), state(RUNNING)
|
|
{
|
|
initConnection(host);
|
|
}
|
|
|
|
void CPregameServer::handleConnection(CConnection *cpc)
|
|
{
|
|
setThreadName("CPregameServer::handleConnection");
|
|
try
|
|
{
|
|
while(!cpc->receivedStop)
|
|
{
|
|
CPackForSelectionScreen *cpfs = nullptr;
|
|
*cpc >> cpfs;
|
|
|
|
logNetwork->infoStream() << "Got package to announce " << typeid(*cpfs).name() << " from " << *cpc;
|
|
|
|
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
|
|
bool quitting = dynamic_ptr_cast<QuitMenuWithoutStarting>(cpfs),
|
|
startingGame = dynamic_ptr_cast<StartWithCurrentSettings>(cpfs);
|
|
if(quitting || startingGame) //host leaves main menu or wants to start game -> we end
|
|
{
|
|
cpc->receivedStop = true;
|
|
if(!cpc->sendStop)
|
|
sendPack(cpc, *cpfs);
|
|
|
|
if(cpc == host)
|
|
toAnnounce.push_back(cpfs);
|
|
}
|
|
else
|
|
toAnnounce.push_back(cpfs);
|
|
|
|
if(startingGame)
|
|
{
|
|
//wait for sending thread to announce start
|
|
auto unlock = vstd::makeUnlockGuard(mx);
|
|
while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
|
|
}
|
|
else if(quitting) // Server must be stopped if host is leaving from lobby to avoid crash
|
|
{
|
|
end2 = true;
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
|
|
logNetwork->errorStream() << *cpc << " dies... \nWhat happened: " << e.what();
|
|
}
|
|
|
|
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
|
|
if(state != ENDING_AND_STARTING_GAME)
|
|
{
|
|
connections -= cpc;
|
|
|
|
//notify other players about leaving
|
|
auto pl = new PlayerLeft();
|
|
pl->playerID = cpc->connectionID;
|
|
announceTxt(cpc->name + " left the game");
|
|
toAnnounce.push_back(pl);
|
|
|
|
if(connections.empty())
|
|
{
|
|
logNetwork->error("Last connection lost, server will close itself...");
|
|
boost::this_thread::sleep(boost::posix_time::seconds(2)); //we should never be hasty when networking
|
|
state = ENDING_WITHOUT_START;
|
|
}
|
|
}
|
|
|
|
logNetwork->infoStream() << "Thread listening for " << *cpc << " ended";
|
|
listeningThreads--;
|
|
vstd::clear_pointer(cpc->handler);
|
|
}
|
|
|
|
void CPregameServer::run()
|
|
{
|
|
startListeningThread(host);
|
|
start_async_accept();
|
|
|
|
while(state == RUNNING)
|
|
{
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> myLock(mx);
|
|
while(!toAnnounce.empty())
|
|
{
|
|
processPack(toAnnounce.front());
|
|
toAnnounce.pop_front();
|
|
}
|
|
|
|
// //we end sending thread if we ordered all our connections to stop
|
|
// ending = true;
|
|
// for(CPregameConnection *pc : connections)
|
|
// if(!pc->sendStop)
|
|
// ending = false;
|
|
|
|
if(state != RUNNING)
|
|
{
|
|
logNetwork->info("Stopping listening for connections...");
|
|
acceptor->close();
|
|
}
|
|
|
|
if(acceptor)
|
|
{
|
|
acceptor->get_io_service().reset();
|
|
acceptor->get_io_service().poll();
|
|
}
|
|
} //frees lock
|
|
|
|
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
|
|
}
|
|
|
|
logNetwork->info("Thread handling connections ended");
|
|
|
|
if(state == ENDING_AND_STARTING_GAME)
|
|
{
|
|
logNetwork->info("Waiting for listening thread to finish...");
|
|
while(listeningThreads) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
|
|
logNetwork->info("Preparing new game");
|
|
}
|
|
}
|
|
|
|
CPregameServer::~CPregameServer()
|
|
{
|
|
delete acceptor;
|
|
delete upcomingConnection;
|
|
|
|
for(CPackForSelectionScreen *pack : toAnnounce)
|
|
delete pack;
|
|
|
|
toAnnounce.clear();
|
|
|
|
//TODO pregameconnections
|
|
}
|
|
|
|
void CPregameServer::connectionAccepted(const boost::system::error_code& ec)
|
|
{
|
|
if(ec)
|
|
{
|
|
logNetwork->info("Something wrong during accepting: %s", ec.message());
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
logNetwork->info("We got a new connection! :)");
|
|
std::string name = NAME;
|
|
CConnection *pc = new CConnection(upcomingConnection, name.append(" STATE_PREGAME"));
|
|
initConnection(pc);
|
|
upcomingConnection = nullptr;
|
|
|
|
startListeningThread(pc);
|
|
|
|
*pc << (ui8)pc->connectionID << curmap;
|
|
|
|
announceTxt(pc->name + " joins the game");
|
|
auto pj = new PlayerJoined();
|
|
pj->playerName = pc->name;
|
|
pj->connectionID = pc->connectionID;
|
|
toAnnounce.push_back(pj);
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
upcomingConnection = nullptr;
|
|
logNetwork->info("I guess it was just my imagination!");
|
|
}
|
|
|
|
start_async_accept();
|
|
}
|
|
|
|
void CPregameServer::start_async_accept()
|
|
{
|
|
assert(!upcomingConnection);
|
|
assert(acceptor);
|
|
|
|
upcomingConnection = new TSocket(acceptor->get_io_service());
|
|
acceptor->async_accept(*upcomingConnection, std::bind(&CPregameServer::connectionAccepted, this, _1));
|
|
}
|
|
|
|
void CPregameServer::announceTxt(const std::string &txt, const std::string &playerName /*= "system"*/)
|
|
{
|
|
logNetwork->info("%s says: %s", playerName, txt);
|
|
ChatMessage cm;
|
|
cm.playerName = playerName;
|
|
cm.message = txt;
|
|
|
|
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
|
|
toAnnounce.push_front(new ChatMessage(cm));
|
|
}
|
|
|
|
void CPregameServer::announcePack(const CPackForSelectionScreen &pack)
|
|
{
|
|
for(CConnection *pc : connections)
|
|
sendPack(pc, pack);
|
|
}
|
|
|
|
void CPregameServer::sendPack(CConnection * pc, const CPackForSelectionScreen & pack)
|
|
{
|
|
if(!pc->sendStop)
|
|
{
|
|
logNetwork->infoStream() << "\tSending pack of type " << typeid(pack).name() << " to " << *pc;
|
|
*pc << &pack;
|
|
}
|
|
|
|
if(dynamic_ptr_cast<QuitMenuWithoutStarting>(&pack))
|
|
{
|
|
pc->sendStop = true;
|
|
}
|
|
else if(dynamic_ptr_cast<StartWithCurrentSettings>(&pack))
|
|
{
|
|
pc->sendStop = true;
|
|
}
|
|
}
|
|
|
|
void CPregameServer::processPack(CPackForSelectionScreen * pack)
|
|
{
|
|
if(dynamic_ptr_cast<CPregamePackToHost>(pack))
|
|
{
|
|
sendPack(host, *pack);
|
|
}
|
|
else if(SelectMap *sm = dynamic_ptr_cast<SelectMap>(pack))
|
|
{
|
|
vstd::clear_pointer(curmap);
|
|
curmap = sm->mapInfo;
|
|
sm->free = false;
|
|
announcePack(*pack);
|
|
}
|
|
else if(UpdateStartOptions *uso = dynamic_ptr_cast<UpdateStartOptions>(pack))
|
|
{
|
|
vstd::clear_pointer(curStartInfo);
|
|
curStartInfo = uso->options;
|
|
uso->free = false;
|
|
announcePack(*pack);
|
|
}
|
|
else if(dynamic_ptr_cast<StartWithCurrentSettings>(pack))
|
|
{
|
|
state = ENDING_AND_STARTING_GAME;
|
|
announcePack(*pack);
|
|
}
|
|
else
|
|
announcePack(*pack);
|
|
|
|
delete pack;
|
|
}
|
|
|
|
void CPregameServer::initConnection(CConnection *c)
|
|
{
|
|
*c >> c->name;
|
|
connections.insert(c);
|
|
logNetwork->info("Pregame connection with player %s established!", c->name);
|
|
}
|
|
|
|
void CPregameServer::startListeningThread(CConnection * pc)
|
|
{
|
|
listeningThreads++;
|
|
pc->enterPregameConnectionMode();
|
|
pc->handler = new boost::thread(&CPregameServer::handleConnection, this, pc);
|
|
}
|
|
|
|
CVCMIServer::CVCMIServer()
|
|
: port(3030), io(new boost::asio::io_service()), firstConnection(nullptr)
|
|
{
|
|
logNetwork->trace("CVCMIServer created!");
|
|
if(cmdLineOptions.count("port"))
|
|
port = cmdLineOptions["port"].as<ui16>();
|
|
logNetwork->info("Port %d will be used", port);
|
|
try
|
|
{
|
|
acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
|
|
}
|
|
catch(...)
|
|
{
|
|
logNetwork->info("Port %d is busy, trying to use random port instead", port);
|
|
if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("use-shm"))
|
|
{
|
|
logNetwork->error("Cant pass port number to client without shared memory!", port);
|
|
exit(0);
|
|
}
|
|
acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
|
|
port = acceptor->local_endpoint().port();
|
|
}
|
|
logNetwork->info("Listening for connections at port %d", port);
|
|
}
|
|
CVCMIServer::~CVCMIServer()
|
|
{
|
|
//delete io;
|
|
//delete acceptor;
|
|
//delete firstConnection;
|
|
}
|
|
|
|
CGameHandler * CVCMIServer::initGhFromHostingConnection(CConnection &c)
|
|
{
|
|
auto gh = new CGameHandler();
|
|
StartInfo si;
|
|
c >> si; //get start options
|
|
|
|
if(!si.createRandomMap())
|
|
{
|
|
bool mapFound = CResourceHandler::get()->existsResource(ResourceID(si.mapname, EResType::MAP));
|
|
|
|
//TODO some checking for campaigns
|
|
if(!mapFound && si.mode == StartInfo::NEW_GAME)
|
|
{
|
|
c << ui8(1); //WRONG!
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
c << ui8(0); //OK!
|
|
|
|
gh->init(&si);
|
|
gh->conns.insert(&c);
|
|
|
|
return gh;
|
|
}
|
|
|
|
void CVCMIServer::newGame()
|
|
{
|
|
CConnection &c = *firstConnection;
|
|
ui8 clients;
|
|
c >> clients; //how many clients should be connected
|
|
assert(clients == 1); //multi goes now by newPregame, TODO: custom lobbies
|
|
|
|
CGameHandler *gh = initGhFromHostingConnection(c);
|
|
|
|
auto onExit = vstd::makeScopeGuard([&]()
|
|
{
|
|
vstd::clear_pointer(gh);
|
|
});
|
|
|
|
gh->run(false);
|
|
}
|
|
|
|
void CVCMIServer::newPregame()
|
|
{
|
|
auto cps = new CPregameServer(firstConnection, acceptor);
|
|
cps->run();
|
|
if(cps->state == CPregameServer::ENDING_WITHOUT_START)
|
|
{
|
|
delete cps;
|
|
return;
|
|
}
|
|
|
|
if(cps->state == CPregameServer::ENDING_AND_STARTING_GAME)
|
|
{
|
|
CGameHandler gh;
|
|
gh.conns = cps->connections;
|
|
gh.init(cps->curStartInfo);
|
|
|
|
for(CConnection *c : gh.conns)
|
|
c->addStdVecItems(gh.gs);
|
|
|
|
gh.run(false);
|
|
}
|
|
}
|
|
|
|
void CVCMIServer::start()
|
|
{
|
|
#ifndef VCMI_ANDROID
|
|
ServerReady *sr = nullptr;
|
|
intpr::mapped_region *mr;
|
|
if(cmdLineOptions.count("use-shm"))
|
|
{
|
|
try
|
|
{
|
|
intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write);
|
|
smo.truncate(sizeof(ServerReady));
|
|
mr = new intpr::mapped_region(smo,intpr::read_write);
|
|
sr = reinterpret_cast<ServerReady*>(mr->get_address());
|
|
}
|
|
catch(...)
|
|
{
|
|
intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write);
|
|
smo.truncate(sizeof(ServerReady));
|
|
mr = new intpr::mapped_region(smo,intpr::read_write);
|
|
sr = new(mr->get_address())ServerReady();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
boost::system::error_code error;
|
|
for (;;)
|
|
{
|
|
try
|
|
{
|
|
auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service());
|
|
boost::thread acc(std::bind(vaccept,acceptor,s,&error));
|
|
#ifdef VCMI_ANDROID
|
|
{ // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm
|
|
CAndroidVMHelper envHelper;
|
|
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
|
|
logNetwork->info("Sending server ready message to client");
|
|
}
|
|
#else
|
|
if(cmdLineOptions.count("use-shm"))
|
|
{
|
|
sr->setToTrueAndNotify(port);
|
|
delete mr;
|
|
}
|
|
#endif
|
|
|
|
acc.join();
|
|
if (error)
|
|
{
|
|
logNetwork->warnStream()<<"Got connection but there is an error " << error;
|
|
return;
|
|
}
|
|
logNetwork->info("We've accepted someone... ");
|
|
std::string name = NAME;
|
|
firstConnection = new CConnection(s, name.append(" STATE_WAITING"));
|
|
logNetwork->info("Got connection!");
|
|
while(!end2)
|
|
{
|
|
ui8 mode;
|
|
*firstConnection >> mode;
|
|
switch (mode)
|
|
{
|
|
case 0:
|
|
firstConnection->close();
|
|
exit(0);
|
|
case 1:
|
|
firstConnection->close();
|
|
return;
|
|
case 2:
|
|
newGame();
|
|
break;
|
|
case 3:
|
|
loadGame();
|
|
break;
|
|
case 4:
|
|
newPregame();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
vstd::clear_pointer(firstConnection);
|
|
logNetwork->info("I guess it was just my imagination!");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVCMIServer::loadGame()
|
|
{
|
|
CConnection &c = *firstConnection;
|
|
std::string fname;
|
|
CGameHandler gh;
|
|
boost::system::error_code error;
|
|
ui8 clients;
|
|
|
|
c >> clients >> fname; //how many clients should be connected
|
|
|
|
{
|
|
CLoadFile lf(*CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME)), MINIMAL_SERIALIZATION_VERSION);
|
|
gh.loadCommonState(lf);
|
|
lf >> gh;
|
|
}
|
|
|
|
c << ui8(0);
|
|
|
|
gh.conns.insert(firstConnection);
|
|
|
|
for(int i=1; i<clients; i++)
|
|
{
|
|
auto s = make_unique<boost::asio::ip::tcp::socket>(acceptor->get_io_service());
|
|
acceptor->accept(*s,error);
|
|
if(error) //retry
|
|
{
|
|
logNetwork->warn("Cannot establish connection - retrying...");
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
gh.conns.insert(new CConnection(s.release(),NAME));
|
|
}
|
|
|
|
gh.run(true);
|
|
}
|
|
|
|
|
|
|
|
static void handleCommandOptions(int argc, char *argv[])
|
|
{
|
|
namespace po = boost::program_options;
|
|
po::options_description opts("Allowed options");
|
|
opts.add_options()
|
|
("help,h", "display help and exit")
|
|
("version,v", "display version information and exit")
|
|
("run-by-client", "indicate that server launched by client on same machine")
|
|
("use-shm", "enable usage of shared memory")
|
|
("port", po::value<ui16>(), "port at which server will listen to connections from client")
|
|
("resultsFile", po::value<std::string>()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode.");
|
|
|
|
if(argc > 1)
|
|
{
|
|
try
|
|
{
|
|
po::store(po::parse_command_line(argc, argv, opts), cmdLineOptions);
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
po::notify(cmdLineOptions);
|
|
|
|
if (cmdLineOptions.count("help"))
|
|
{
|
|
printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str());
|
|
printf("Copyright (C) 2007-2014 VCMI dev team - see AUTHORS file\n");
|
|
printf("This is free software; see the source for copying conditions. There is NO\n");
|
|
printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
|
|
printf("\n");
|
|
printf("Usage:\n");
|
|
std::cout << opts;
|
|
exit(0);
|
|
}
|
|
|
|
if (cmdLineOptions.count("version"))
|
|
{
|
|
printf("%s\n", GameConstants::VCMI_VERSION.c_str());
|
|
std::cout << VCMIDirs::get().genHelpString();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
|
|
void handleLinuxSignal(int sig)
|
|
{
|
|
const int STACKTRACE_SIZE = 100;
|
|
void * buffer[STACKTRACE_SIZE];
|
|
int ptrCount = backtrace(buffer, STACKTRACE_SIZE);
|
|
char ** strings;
|
|
|
|
logGlobal->error("Error: signal %d :", sig);
|
|
strings = backtrace_symbols(buffer, ptrCount);
|
|
if(strings == nullptr)
|
|
{
|
|
logGlobal->error("There are no symbols.");
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; i < ptrCount; ++i)
|
|
{
|
|
logGlobal->error(strings[i]);
|
|
}
|
|
free(strings);
|
|
}
|
|
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
// Installs a sig sev segmentation violation handler
|
|
// to log stacktrace
|
|
#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
|
|
signal(SIGSEGV, handleLinuxSignal);
|
|
#endif
|
|
|
|
console = new CConsoleHandler;
|
|
CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
|
|
logConfig.configureDefault();
|
|
logGlobal->info(NAME);
|
|
|
|
handleCommandOptions(argc, argv);
|
|
preinitDLL(console);
|
|
settings.init();
|
|
logConfig.configure();
|
|
|
|
loadDLLClasses();
|
|
srand ( (ui32)time(nullptr) );
|
|
try
|
|
{
|
|
boost::asio::io_service io_service;
|
|
CVCMIServer server;
|
|
|
|
try
|
|
{
|
|
while (!end2)
|
|
{
|
|
server.start();
|
|
}
|
|
io_service.run();
|
|
}
|
|
catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
|
|
{
|
|
logNetwork->error(e.what());
|
|
end2 = true;
|
|
}
|
|
catch (...)
|
|
{
|
|
handleException();
|
|
}
|
|
}
|
|
catch(boost::system::system_error &e)
|
|
{
|
|
logNetwork->error(e.what());
|
|
//catch any startup errors (e.g. can't access port) errors
|
|
//and return non-zero status so client can detect error
|
|
throw;
|
|
}
|
|
#ifdef VCMI_ANDROID
|
|
CAndroidVMHelper envHelper;
|
|
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
|
|
#endif
|
|
vstd::clear_pointer(VLC);
|
|
CResourceHandler::clear();
|
|
return 0;
|
|
}
|
|
|
|
#ifdef VCMI_ANDROID
|
|
|
|
void CVCMIServer::create()
|
|
{
|
|
const char * foo[1] = {"android-server"};
|
|
main(1, const_cast<char **>(foo));
|
|
}
|
|
|
|
#endif
|