#include "crsm.h" #include #include #include #include #include #include #include #define CMD_SIGN "!" CRSM::CRSM(QObject *parent) : QObject(parent) { qout = new QTextStream(stdout,QIODevice::WriteOnly); qin = new QTextStream(stdin, QIODevice::ReadOnly); qout->setCodec(QTextCodec::codecForName("UTF-8")); serverprocess=new QProcess; args << "/fullscreen" << "/config:config" << "/lobby:60" << "/nosignup"; current = 0; finish = false; readConfig(); cleanUp(); listC4Folders(); readScenarios(); autoHost = settings["AutoHost"] == "true"; //QString fifopath(settings["ClonkDirectory"]+"Clonk.log"); //mkfifo(fifopath.toUtf8(),0666); connect(serverprocess, SIGNAL(readyReadStandardOutput()), this, SLOT(readServerOutput())); connect(serverprocess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError())); connect(serverprocess, SIGNAL(finished(int)), this, SLOT(scenarioFinished())); //int fp=open(fifopath.toUtf8(),O_RDONLY|O_NONBLOCK); //logfile=new QFile(); //logfile->open(fp,QIODevice::ReadOnly); //QSocketNotifier *logNotifier = new QSocketNotifier(logfile->handle(),QSocketNotifier::Read,this); QSocketNotifier *inNotifier = new QSocketNotifier(STDIN_FILENO,QSocketNotifier::Read,this); connect(inNotifier, SIGNAL(activated(int)), this, SLOT(readInput())); //connect(logNotifier,SIGNAL(activated(int)),this, SLOT(readLog())); QFile *reallog = new QFile(settings["ClonkDirectory"]+"CRSM.log"); reallog->open(QIODevice::WriteOnly|QIODevice::Text); logstream = new QTextStream(reallog); connection = new IrcConnection("irc.rbx.fr.euirc.net"); connection->setUserName(settings["IrcNick"]); connection->setNickName(settings["IrcNick"]); connection->setRealName(settings["IrcRealName"]); connection->sendCommand(IrcCommand::createJoin(settings["IrcChannel"])); connection->setPassword(settings["IrcPassword"]); //connection->sendCommand(IrcCommand::createMessage(settings["IrcChannel"], "Hi, kthxbye!")); connection->open(); connect(connection, SIGNAL(messageReceived(IrcMessage*)), this, SLOT(ircMessageReceived(IrcMessage*))); } CRSM::~CRSM() { } void CRSM::start() { if(autoHost) nextScen(); } void CRSM::readServerOutput() { QRegExp timeRemover("^>?\\s*\\[\\d\\d:\\d\\d:\\d\\d\\]\\s+(.*)$"); //QString what(QTextCodec::codecForName("Windows-1250")->toUnicode(serverprocess->readAll())); QString what(serverprocess->readAll()); *qout << what.trimmed() << endl; if(!timeRemover.exactMatch(what)) return; what=timeRemover.cap(1).trimmed(); //*qout << what << endl; QRegExp userexp("^<([^>]*)>\\s+(.*)"); if(userexp.exactMatch(what)) { QString user = userexp.cap(1).trimmed(); if(user!=settings["ServerNick"]) { QString msg = userexp.cap(2).trimmed(); QRegExp commandExp("^\\" CMD_SIGN "([^ ]+)(\\s+(.*)\\s*)?$"); if(commandExp.exactMatch(msg)) { QString command=commandExp.cap(1).trimmed(); QString commandArgs=commandExp.cap(2).trimmed(); if(command=="help") serverprocess->write(CMD_SIGN"host <[Rundenordner.c4f/]Szenarioname.c4s> - Startet das gewaehlte Szenario, solange die Warteliste nicht zu gross ist.\n" CMD_SIGN"queue - Zeigt die naechsten 3 Szenarien auf der Warteliste.\n" "[Sachen in eckigen Klammern sind optional]\n" "\n" "Weitere Befehle folgen.\n"); if(command=="host") { if(userlist.length()>=settings["UserListLength"].toInt()) serverprocess->write(QString("Maximale Warteschlangenlänge von "+settings["UserListLength"]+" erreicht!\n").toLatin1()); else { if(commandArgs=="") serverprocess->write("Bitte gib einen Szenarionamen an!\n"); else { //extractC4Folders(); if(scenExists(commandArgs)) { userlist << commandArgs; serverprocess->write(QString("Szenario "+commandArgs+" wurde der Warteschlange hinzugefügt.\n").toLatin1()); if(userlist.length() == 1 && session["userwish"] != "true" && session["running"] != "true") serverprocess->write(QString("Überrede alle Spieler zu leaven und dein Wunsch wird sofort gehostet ;-)\n").toLatin1()); } else { serverprocess->write(QString("Szenario "+commandArgs+" wurde nicht gefunden!\n").toLatin1()); } } } } if(command=="admin") { if(commandArgs!="") { if(session["admin"]==""||user==session["admin"] || lists["Moderators"].contains(user)) { session["admin"]=commandArgs.trimmed(); serverprocess->write(QString("Spieler "+commandArgs+" wurde als Rundenadmin eingetragen.\n").toLatin1()); } else { serverprocess->write(QString(session["admin"]+" ist bereits Rundenadmin!\n").toLatin1()); } } else { if(session["admin"]=="") serverprocess->write(QString("Es gibt noch keinen Rundenadmin.\n").toLatin1()); else serverprocess->write(QString(session["admin"]+" ist Rundenadmin!\n").toLatin1()); } } if(command=="chatadmin") { if(commandArgs!="") { if(session["chatadmin"]==""||user==session["chatadmin"] || lists["Moderators"].contains(user)) { session["chatadmin"]=commandArgs.trimmed(); serverprocess->write(QString("Spieler "+commandArgs+" wurde als Chat-Rundenadmin eingetragen.\n").toLatin1()); } else { serverprocess->write(QString(session["chatadmin"]+" ist bereits Chat-Rundenadmin!\n").toLatin1()); } } else { if(session["chatadmin"]=="") { serverprocess->write(QString(user + " wurde als Chat-Rundenadmin eingetragen.\n").toLatin1()); } else serverprocess->write(QString(session["admin"]+" ist Chat-Rundenadmin!\n").toLatin1()); } } if((command=="observer"||command=="deactivate"||command=="activate"||command=="kick"||command=="set"||command=="script"||command=="asyncctrl"||command=="centralctrl"||command=="decentralctrl"||command=="start"||command=="nodebug"||command=="stop"||command=="pause"||command=="unpause")&&(user==session["admin"] || user==session["chatadmin"] || lists["Moderators"].contains(user))) { if(!(command=="set"&&(commandArgs.simplified()=="faircrew on"||commandArgs.simplified()=="faircrew off")&&session["running"]!="true")) serverprocess->write(QString("/"+command+" "+commandArgs+"\n").toLatin1()); } if(command=="queue") { serverprocess->write("Folgende Szenarien befinden sich in der Warteschlange:\n"); for(int i=0;iwrite(QString("\t"+QString::number(i+1)+". "+(userlist.length()>i?userlist.at(i):scenlist.at((i-userlist.length() + current)%scenlist.length()) + " (auto)")+"\n").toLatin1()); } } if(command=="list") { serverprocess->write(listScenarios(commandArgs).toLatin1()); } } if(msg.trimmed().toLower() == "hi" && !greeted.contains(user)) { serverprocess->write("Hallo " + user .toLatin1()+ "!\n"); greeted.push_back(user); } } } QRegExp joinExp("^Client (.+) (?:verbunden|connected)\\.\\s*$"); joinExp.setMinimal(true); if(joinExp.exactMatch(what)) { //waitinggreets << joinExp.cap(1); ++clientcount; } /*joinExp = QRegExp("^Client (.+) (?:aktiviert|activated)\\.\\s*$"); joinExp.setMinimal(true); if(joinExp.exactMatch(what)) if(waitinggreets.contains(joinExp.cap(1))) { serverprocess->write(QString("Hallo "+joinExp.cap(1) +"!\n").toLatin1()); waitinggreets.removeAll(joinExp.cap(1)); }*/ QRegExp startExp("^Start!\\s*$"); if(startExp.exactMatch(what)) { serverprocess->write(QString("/set maxplayer 0\n").toLatin1()); session["running"]="true"; } QRegExp leaveExp("^Client (.+) (?:entfernt|removed)(.*)"); //leaveExp.setMinimal(true); if(leaveExp.exactMatch(what)) { serverprocess->write(QString(leaveExp.cap(1) +" ist ein L34V0R!\n").toLatin1()); if(--clientcount == 0 && userlist.length() > 0 && session["userwish"] != "true") { serverprocess->closeWriteChannel(); } } } void CRSM::processError() { *qout << serverprocess->errorString() << endl; } void CRSM::readInput() { QString what(qin->readLine()); if(what=="/exit") { serverprocess->closeWriteChannel(); finish=true; cleanUp(); if(session["hosting"] != "true") scenarioFinished(); return; } if(what=="/exitafter") { finish=true; return; } if(what=="/next") { serverprocess->closeWriteChannel(); return; } if(what=="/help") printAdditionalHelp(); serverprocess->write(what.toLatin1()+"\n"); } void CRSM::nextScen() { session["hosting"] = "true"; if(userlist.length()>0) { startScen(userlist.at(0), args); userlist.removeFirst(); session["userwish"] = "true"; } else { startScen(scenlist.at(current), args); if(++current>=scenlist.length()) current = 0; } } void CRSM::printAdditionalHelp() { *qout << "/exit stops the actual Clonk Server and exits the Server Manager.\n" "/next stops the actual Clonk Server and starts a new one.\n" "Clonk commands following..." << endl; } void CRSM::readLog() { QString temp(logfile->readLine().trimmed()); if(temp!="") *logstream << temp << endl; } void CRSM::scenarioFinished() { if(finish) { connection->quit(settings["IrcQuitMessage"]); connect(connection, SIGNAL(disconnected()), QCoreApplication::instance(), SLOT(quit())); QTimer::singleShot(500, QCoreApplication::instance(), SLOT(quit())); return; } session.clear(); //waitinggreets.clear(); greeted.clear(); clientcount = 0; if((autoHost || userlist.length() > 0) && !finish) nextScen(); } void CRSM::ircMessageReceived(IrcMessage *message) { if(message->isOwn()) return; if(message->type() == IrcMessage::Private) { QString target = message->parameters().at(0); QString mess = message->parameters().at(1).trimmed(); if(mess.left(QString(CMD_SIGN).length()) == CMD_SIGN) { QStringList args = mess.right(mess.length() - QString(CMD_SIGN).length()).split(" ", QString::SkipEmptyParts); QString cmd = args.first().trimmed(); args.removeFirst(); QString arg = args.join(" ").trimmed(); if(cmd == "list") { /*foreach(const QString &line, listScenarios(arg).split('\n')) connection->sendCommand(IrcCommand::createMessage(message->nick(), line));*/ connection->sendCommand(IrcCommand::createMessage(target, "Szenarioliste ist zurzeit nur in der Lobby und im laufenden Spiel verfügbar.")); } else if(cmd == "autohost") { autoHost = true; connection->sendCommand(IrcCommand::createMessage(target, "Automatisches Hosting aktiviert.")); if(session["hosting"] != "true") { nextScen(); } } else if(cmd == "noautohost") { autoHost = false; connection->sendCommand(IrcCommand::createMessage(target, "Automatisches Hosting deaktiviert.")); } else if(cmd=="host") { if(userlist.length()>=settings["UserListLength"].toInt()) connection->sendCommand(IrcCommand::createMessage(target, "Maximale Warteschlangenlänge von "+settings["UserListLength"]+" erreicht!")); else { if(arg=="") connection->sendCommand(IrcCommand::createMessage(target, "Bitte gib einen Szenarionamen an!")); else { if(scenExists(arg)) { bool skipCurrent = false; if(userlist.isEmpty() && session["userwish"] != "true") skipCurrent = true; userlist << arg; connection->sendCommand(IrcCommand::createMessage(target, "Szenario " + arg + " wurde der Warteschlange hinzugefügt.")); if(session["hosting"] != "true") nextScen(); else if(clientcount == 0 && skipCurrent) { serverprocess->closeWriteChannel(); } } else { connection->sendCommand(IrcCommand::createMessage(target, "Szenario " + arg + " wurde nicht gefunden!")); } } } } } } else if(message->type() == IrcMessage::Join) { connection->sendCommand(IrcCommand::createMessage(settings["IrcChannel"], "Hallo " + message->nick() + "!")); } else if(message->type() == IrcMessage::Kick) { if(message->parameters().at(1) == connection->nickName()) { connection->sendCommand(IrcCommand::createJoin(settings["IrcChannel"])); } } } void CRSM::startScen(QString scen, QStringList argList) { serverprocess->setWorkingDirectory(QDir::currentPath()); serverprocess->start(settings["ServerExecutable"], argList << scen); } void CRSM::readConfig() { QFile config("CrServerManager.conf"); if(!config.exists()||!config.open(QIODevice::ReadOnly | QIODevice::Text)) { config.open(QIODevice::WriteOnly | QIODevice::Text); settings = defaultSettings(); foreach(const QString &key, settings.keys()) { config.write(QString(key+" = "+settings.value(key)+"\n").toUtf8()); } } else { QRegExp confExp("^([^=]+)=(.*)$"); QRegExp confPlusExp("^([^=]+)\\+=(.*)$"); for(QString line=config.readLine().trimmed(); !config.atEnd(); line=config.readLine().trimmed()) { if(confPlusExp.exactMatch(line)) { lists[confPlusExp.cap(1).trimmed()].push_back(confPlusExp.cap(2).trimmed()); } else if(confExp.exactMatch(line)) { settings.insert(confExp.cap(1).trimmed(),confExp.cap(2).trimmed()); } } *qout << "config:" << endl; foreach(const QString &key, settings.keys()) { *qout << key << " = " << settings.value(key) << endl; } *qout << endl; foreach(const QString &key, lists.keys()) { *qout << key << ":" << endl; foreach(const QString &val, lists.value(key)) { *qout << "\t" << val << endl; } } args=settings["Arguments"].split(" "); args << "/config:"+settings["ClonkConfig"]; } //settings["ClonkDirectory"]= QDir().absoluteFilePath(settings["ServerExecutable"]).replace(QRegExp("^(.*)"+QFileInfo(settings["ServerExecutable"]).fileName()+"$"),"\\1"); settings["ClonkDirectory"] = QFileInfo(settings["ServerExecutable"]).absoluteDir().absolutePath()+QDir::separator(); QFile clonkconfig(settings["ClonkConfig"]); if(!clonkconfig.exists()) *qout << "WARNING: Clonk's config file is not existing!"; else { clonkconfig.open(QFile::ReadOnly); QRegExp nickexp("^\\s*Nick=\"(.*)\"\\s*$"); foreach(const QString &line, QString(clonkconfig.readAll().trimmed()).split("\n")) { //*qout << line << endl; if(nickexp.exactMatch(line)) { settings["ServerNick"]=nickexp.cap(1); break; } } *qout << "ClonkDirectory" << " = " << settings.value("ClonkDirectory") << endl; *qout << "ServerNick" << " = " << settings.value("ServerNick") << endl; } *qout << endl; } void CRSM::readScenarios() { QFile scenfile("scenarios.lst"); if(!scenfile.exists()) { *qout << "No scenarios.lst found!"; scenfile.open(QFile::WriteOnly); scenfile.write("Worlds.c4f/Goldmine.c4s"); scenfile.close(); } scenfile.open(QFile::ReadOnly); scenlist = QString(scenfile.readAll()).trimmed().split("\n",QString::SkipEmptyParts); *qout << "Scenarios in list:" << endl; foreach(const QString &scen, scenlist) { if(scenExists(scen)) *qout << scen << endl; else *qout << "WARNING: Scenario " << scen << " not found!" << endl; } *qout << endl; scenfile.close(); } QMap CRSM::defaultSettings() { QMap temp; temp.insert("ServerExecutable","clonk-server"); temp.insert("Arguments","/fullscreen /lobby:60 /nosignup"); temp.insert("ClonkConfig","config"); temp.insert("UserListLength","5"); return temp; } void CRSM::listC4Folders() { /*// *qout << "Executable Setting: " << settings["ServerExecutable"] << endl; // *qout << "Path: " << clonkdir << endl; QDirIterator it(settings["ClonkDirectory"], QDirIterator::FollowSymlinks | QDirIterator::Subdirectories); bool extracting=false; for(;it.hasNext();it.next()) { if(it.fileInfo().suffix()=="c4f"&&!it.fileInfo().isDir()&&!it.fileInfo().absoluteFilePath().contains(settings["ClonkDirectory"]+"Network/")) { if(!extracting) { extracting=true; *qout << endl << "Extracting scenario folders for hosting through Lobby..." << endl; } *qout << it.fileInfo().absoluteFilePath().replace(settings["ClonkDirectory"],"") << "..." << endl; QProcess c4group(this); QStringList c4gargs; c4gargs << it.fileInfo().absoluteFilePath() << "-u"; c4group.start(settings["ClonkDirectory"]+"c4group",c4gargs); c4group.waitForFinished(); } } if(extracting) *qout << endl;*/ //Old extracting is now replaced by listing the scenarios with c4group *qout << "Listing Contents of C4Folders..."; QDirIterator it(settings["ClonkDirectory"], QDirIterator::FollowSymlinks); for(;it.hasNext();it.next()) { if((it.fileInfo().suffix() == "c4f" || (it.fileInfo().isDir() && !QDir(it.fileInfo().absoluteFilePath()).entryList(QStringList() << "*.c4f" << "*.c4s").isEmpty())) && it.fileName() != "." && it.fileName() != ".." && !lists["IgnoreFolders"].contains(it.fileInfo().baseName())) { #ifdef Q_OS_LINUX64 QString executable = "c4group64"; #else QString executable = "c4group"; #endif QProcess c4group; c4group.start(settings["ClonkDirectory"]+executable, QStringList() << it.filePath() << "-l", QProcess::ReadOnly); c4group.waitForFinished(); c4group.readLine(); QRegExp finishExp("^\\d+ Entries, \\d+ Bytes$"); QRegExp entryExp("^(.*\\.c4s)\\s+\\d+ Bytes\\s.*$"); QFile listFile(it.filePath()+".lst"); listFile.open(QFile::WriteOnly); QString line; while(!c4group.atEnd()) { line = c4group.readLine().trimmed(); if(line.isEmpty()) continue; if(finishExp.exactMatch(line)) break; if(entryExp.exactMatch(line)) listFile.write(entryExp.cap(1).toUtf8() + '\n'); } listFile.close(); } } *qout << QString(3, '\b') << ": Finished" << endl; } void CRSM::cleanUp() { *qout << endl << "Cleaning up Clonk Folder..." << endl; QDirIterator it(settings["ClonkDirectory"]+"Network/", QDirIterator::FollowSymlinks | QDirIterator::Subdirectories); for(;it.hasNext();it.next()) if(it.fileInfo().exists()) QFile(it.fileInfo().absoluteFilePath()).remove(); /*QDirIterator lstIt(settings["ClonkDirectory"], QStringList() << "*.lst", QDir::NoFilter, QDirIterator::FollowSymlinks); for(;lstIt.hasNext();lstIt.next()) if(!lists["IgnoreFolders"].contains(lstIt.fileInfo().baseName())) QFile(lstIt.fileInfo().absoluteFilePath()).remove();*/ *qout << endl; } bool CRSM::scenExists(QString filePath) { bool exists = QFile(settings["ClonkDirectory"]+filePath).exists(); if(!exists) { QStringList split = filePath.split('/'); if(split.length() == 2) { if(split.first().right(4) == ".c4f") { QFile lstFile(settings["ClonkDirectory"] + split.first() + ".lst"); if(lstFile.exists()) { lstFile.open(QFile::ReadOnly); while(!lstFile.atEnd()) { QString line = lstFile.readLine().trimmed(); if(line == split.last()) { exists = true; break; } } } lstFile.close(); } } } return exists; } QString CRSM::listScenarios(QString commandArgs) { QString ret; if(commandArgs.isEmpty()) { ret += "Folgende Szenarien stehen zur Auswahl:\n"; QDirIterator it(settings["ClonkDirectory"], QDirIterator::FollowSymlinks); for(;it.hasNext();it.next()) { if(it.fileInfo().suffix()=="c4s"&&!it.fileInfo().absoluteFilePath().contains(settings["ClonkDirectory"]+"Network/")) ret += QString(" "+it.fileInfo().absoluteFilePath().replace(settings["ClonkDirectory"],"")+"\n"); } ret += "-----------------------------------------------------------------\nFolgende Ordner stehen zur Auswahl:\n"; QDirIterator folderIt(settings["ClonkDirectory"], QDirIterator::FollowSymlinks); for(;folderIt.hasNext();folderIt.next()) { if(folderIt.fileInfo().suffix()=="lst"&&!folderIt.fileInfo().absoluteFilePath().contains(settings["ClonkDirectory"]+"Network/") && !lists["IgnoreFolders"].contains(folderIt.fileInfo().baseName())) ret += QString(" "+folderIt.fileInfo().absoluteFilePath().left(folderIt.fileInfo().absoluteFilePath().length() - 4).replace(settings["ClonkDirectory"],"")+"\n"); } } else { QFile file(settings["ClonkDirectory"] + commandArgs + ".lst"); if(file.exists()) { ret += "Der Ordner \"" + commandArgs + QString("\" enthält folgende Szenarien:\n"); file.open(QFile::ReadOnly); while(!file.atEnd()) ret += " " + QString::fromUtf8(file.readLine()).toLatin1().trimmed() + "\n"; } else ret += "Der Ordner \"" + commandArgs + "\" wurde nicht gefunden!\n"; } return ret; }