#include "crsm.h" #include #include #include #include #include #include #include #define CMD_SIGN "!" CRSM::CRSM(QObject *parent) : QObject(parent) { codec = QTextCodec::codecForName("Windows-1252"); 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(); connect(&greetMapper, SIGNAL(mapped(QString)), this, SLOT(greet(QString))); 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::Append|QIODevice::Text); logstream.setDevice(reallog); if(settings["UseIrc"] == "true") { connection = new IrcConnection(settings["IrcServer"]); connection->setUserName(settings["IrcNick"]); connection->setNickName(settings["IrcNick"]); connection->setRealName(settings["IrcRealName"]); connection->sendCommand(IrcCommand::createMode(settings["IrcNick"], "+B")); connection->sendCommand(IrcCommand::createJoin(settings["IrcChannel"])); if(settings["IrcUseIngameChat"] == "true" && !settings["IrcIngameChannel"].isEmpty()) { connection->sendCommand(IrcCommand::createJoin(settings["IrcIngameChannel"])); connection->sendCommand(IrcCommand::createTopic(settings["IrcIngameChannel"], "Kein laufendes Spiel.")); } else { settings["IrcUseIngameChat"] = "false"; } connection->setPassword(settings["IrcPassword"]); connect(connection, SIGNAL(messageReceived(IrcMessage*)), this, SLOT(ircMessageReceived(IrcMessage*))); connection->open(); } else { settings["IrcUseIngameChat"] = "false"; } } CRSM::~CRSM() { } void CRSM::start() { if(autoHost) nextScen(); } void CRSM::readServerOutput() { QRegExp timeRemover("^>?\\s*\\[\\d\\d:\\d\\d:\\d\\d\\]\\s+(.*)$"); //QString what(codec->toUnicode(serverprocess->readAll())); //QString what(QTextCodec::codecForName("Windows-1250")->toUnicode(serverprocess->readAll())); //QString what = QString::fromLatin1(serverprocess->readAll()); QString what(serverprocess->readAll()); if(settings["ServerUsesReadline"] == "true") { while(writtenToServer.length() > 0 && what.length() > 0 && writtenToServer.at(0) == what.at(0)) { writtenToServer.remove(0, 1); what.remove(0, 1); } if(what.at(0) == '>') what.remove(0, 1); } if(what.isEmpty()) return; logstream << what.trimmed() << endl; if(settings["UseIrc"] == "true") { foreach(const QString &mess, what.split("\n", QString::SkipEmptyParts)) foreach(const QString &mod, ircModIOList) { connection->sendCommand(IrcCommand::createNotice(mod, mess)); } } *qout << what.trimmed() << endl; if(!timeRemover.exactMatch(what)) return; what=timeRemover.cap(1).trimmed(); //*qout << what << endl; //QRegExp userexp("^<([^>]*)>\\s+(.*)"); QRegExp userexp("^>?\\s*<(.*)\\|(\\d+)\\|([^>]*)>\\s+(.*)$"); if(userexp.exactMatch(what)) { QString user = userexp.cap(3); QString pcName = userexp.cap(1); QString cuid = userexp.cap(2); ClientInfo& info = clients[pcName]; if(user!=settings["ServerNick"]) { QString msg = userexp.cap(4).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") writeToServer(QString(CMD_SIGN "host <[Rundenordner.c4f/]Szenarioname.c4s> - Startet das gewählte Szenario, solange die Warteliste nicht zu groß ist.\n" CMD_SIGN "queue - Zeigt die nächsten %1 Szenarien auf der Warteliste.\n" CMD_SIGN "list [Aliase¦Rundenordner[.c4f]] - Listet alle definierten Aliase oder alle möglichen Szenarien und Ordner auf, bzw. alle Szenarien im Ordner oder Rundenordner\n" CMD_SIGN "admin [Chatnick oder PC-Name] - Ohne Name trägt es den Autor der Nachricht als Rundenadmin ein, bzw. mit Name den Spieler mit entsprechendem Namen, insofern nicht bereits ein Rundenadmin feststeht.\n" CMD_SIGN "teamdist - Ändert die Teamverteilung.\n" CMD_SIGN "plrteam - Verschiebt den angegebenen Spieler ins angegebene Team.\n" CMD_SIGN "aliaswish = - Deponiert den Wunsch, als Alias für einzutragen. Ein Moderator entscheidet darüber.\n" CMD_SIGN "help - Zeigt diese Hilfe an.\n" + (settings["IrcUseIngameChat"] == "true" ? QString(CMD_SIGN "ircchat - Schaltet den Irc-Ingame-Chat ein bzw. aus.\n") : QString("")) + "Szenarionamen sind Case-Sensitive (Groß- Kleinschreibung beachten)!\n" "Der Rundenadmin kann alle Clonk-Befehle mit " CMD_SIGN " statt / ausführen. Bspw.: " CMD_SIGN "kick Client-123\n" "Zusätzlich zu den bekannten Clonk-Befehlen gibt es noch: stop, pause, unpause, teamcolorson, teamcolorsoff.\n" "Ein Rundenadmin kann gegebenfalls einen anderen Spieler als Rundenadmin eintragen.\n" "[Sachen in eckigen Klammern sind optional]\n" "\n" "Von ¦ getrennten Werten ist einer auszuwählen.\n" "Weitere Befehle folgen.\n").arg(settings["UserListLength"])); if(command=="host") { if(userlist.length()>=settings["UserListLength"].toInt()) writeToServer(QString("Maximale Warteschlangenlänge von "+settings["UserListLength"]+" erreicht!\n")); else { if(commandArgs=="") writeToServer("Bitte gib einen Szenarionamen an!\n"); else { //extractC4Folders(); if(scenExists(commandArgs)) { userlist << commandArgs; writeToServer("Szenario "+commandArgs+" wurde der Warteschlange hinzugefügt.\n"); if(userlist.length() == 1 && session["userwish"] != "true" && session["running"] != "true") writeToServer("Überrede alle Spieler zu leaven und dein Wunsch wird sofort gehostet ;-)\n"); } else { writeToServer(QString("Szenario \"" + commandArgs + "\" wurde nicht gefunden!\n")); } } } } if(command=="admin") { if(commandArgs!="") { if(commandArgs == settings["ServerNick"] || commandArgs == settings["ServerPCName"]) { writeToServer("Der Server kann nicht als Rundenadmin eingetragen werden!\n"); return; } if(commandArgs == sessionAdmin.pcName) { writeToServer(QString(sessionAdmin.nick + " (" + sessionAdmin.pcName + ") ist bereits Rundenadmin!\n")); return; } if(sessionAdmin == ClientInfo() || info == sessionAdmin || lists["Moderators"].contains(cuid)) { if(clients.contains(commandArgs)) { sessionAdmin = clients.value(commandArgs); } else { bool notFound = true, ambigous = false; ClientInfo foundClient; foreach(const ClientInfo& client, clients) { if(client.nick == commandArgs) { if(!notFound) { ambigous = true; break; } foundClient = client; notFound = false; } } if(ambigous) { writeToServer(QString("Chat-Nickname " + commandArgs + " ist nicht eindeutig. Bitte PC-Namen verwenden.\n")); return; } else if(notFound) { writeToServer(QString("Spieler " + commandArgs + " wurde nicht gefunden!\n")); return; } else if(sessionAdmin == foundClient) { writeToServer(QString(sessionAdmin.nick + " (" + sessionAdmin.pcName + ") ist bereits Rundenadmin!\n")); return; } else { sessionAdmin = foundClient; } } writeToServer(QString("Spieler " + sessionAdmin.nick + " (" + sessionAdmin.pcName + ") wurde als Rundenadmin eingetragen.\n")); } else { writeToServer(QString(sessionAdmin.nick + " (" + sessionAdmin.pcName + ") ist bereits Rundenadmin!\n")); } } else { if(sessionAdmin == info) { writeToServer(QString("Du bist bereits Rundenadmin!\n")); } else if(sessionAdmin == ClientInfo() || lists["Moderators"].contains(cuid)) { sessionAdmin = info; writeToServer(QString("Spieler " + info.nick + " (" + info.pcName + ") wurde als Rundenadmin eingetragen!\n")); } else writeToServer(QString(sessionAdmin.nick + " (" + sessionAdmin.pcName + ") ist bereits Rundenadmin!\n")); } } /*if(command=="chatadmin") { if(commandArgs!="") { if(session["chatadmin"]==""||user==session["chatadmin"] || lists["Moderators"].contains(user)) { session["chatadmin"]=commandArgs.trimmed(); writeToServer(QString("Spieler "+commandArgs+" wurde als Chat-Rundenadmin eingetragen.\n")); } else { writeToServer(QString(session["chatadmin"]+" ist bereits Chat-Rundenadmin!\n")); } } else { if(session["chatadmin"]=="") { writeToServer(QString(user + " wurde als Chat-Rundenadmin eingetragen.\n")); } else writeToServer(QString(session["admin"]+" ist Chat-Rundenadmin!\n")); } }*/ else 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"||command=="teamcolorsoff"||command=="teamcolorson"||command=="teamdist"||command=="plrteam")&&(sessionAdmin == info || lists["Moderators"].contains(cuid))) { if(command=="kick") { if(clients.contains(commandArgs)) { if(lists["Moderators"].contains(QString::number(clients.value(commandArgs).CUID)) && !lists["Moderators"].contains(cuid)) { writeToServer(QString("Moderatoren können nicht gekickt werden!\n")); return; } } } if(!(command=="set"&&(commandArgs.simplified()=="faircrew on"||commandArgs.simplified()=="faircrew off")&&session["running"]!="true")) writeToServer(QString("/"+command+" "+commandArgs+"\n")); } else if(command=="queue") { writeToServer(printQueue()); } else if(command=="list") { writeToServer(listScenarios(commandArgs)); } else if(command=="aliaswish") { writeToServer(addAliasWish(commandArgs) + "\n"); } else if(command=="ircchat"&&(sessionAdmin == info || lists["Moderators"].contains(cuid))) { writeToServer(ircActivateIngameChat(commandArgs.toLower() == "on") + "\n"); } } else if(session["IrcUseIngameChat"] == "true") { connection->sendCommand(IrcCommand::createMessage(settings["IrcIngameChannel"], "[Clonk]<" + user + "> " + msg)); } } } QRegExp joinExp("^Client (.+) (?:verbunden|connected)\\.\\s*$"); //joinExp.setMinimal(true); if(joinExp.exactMatch(what)) { //waitinggreets << joinExp.cap(1); QRegExp infoMatch("^<(.*)\\|(\\d+)\\|(.*)>$"); if(infoMatch.exactMatch(joinExp.cap(1))) { ClientInfo info; info.CUID = infoMatch.cap(2).toInt(); info.nick = infoMatch.cap(3); info.pcName = infoMatch.cap(1); clients.insert(info.pcName, info); QTimer *timer = new QTimer; connect(timer, SIGNAL(timeout()), &greetMapper, SLOT(map())); connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater())); greetMapper.setMapping(timer, info.pcName); timer->start(500); } ++clientcount; } joinExp = QRegExp("^Client (.+) (?:aktiviert|activated)\\.\\s*$"); { QRegExp infoMatch("^<(.*)\\|(\\d+)\\|(.*)>$"); if(infoMatch.exactMatch(joinExp.cap(1))) { ClientInfo &info = clients[infoMatch.cap(1)]; info.activated = true; } } joinExp = QRegExp("^Client (.+) (?:deaktiviert|deactivated)\\.\\s*$"); { QRegExp infoMatch("^<(.*)\\|(\\d+)\\|(.*)>$"); if(infoMatch.exactMatch(joinExp.cap(1))) { ClientInfo &info = clients[infoMatch.cap(1)]; info.activated = false; } } QRegExp startExp("^Start!\\s*$"); if(startExp.exactMatch(what)) { writeToServer(QString("/set maxplayer 0\n")); session["running"]="true"; } QRegExp leaveExp("^Client (.+) (?:entfernt|removed)(.*)"); //leaveExp.setMinimal(true); if(leaveExp.exactMatch(what)) { QRegExp infoMatch("^<(.*)\\|(\\d+)\\|(.*)>$"); if(infoMatch.exactMatch(leaveExp.cap(1))) { ClientInfo &info = clients[infoMatch.cap(1)]; writeToServer(QString(info.nick +" ist ein L34V0R!\n")); clients.remove(info.pcName); } 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; *qout << "Will exit after this round." << endl; return; } if(what=="/next") { skipCurrent(); return; } if(what=="/skip") { QString skipped; if((skipped = skipScen()) != "") { *qout << "Skipped \"" << skipped << "\"." << endl; } else *qout << "User list is empty!" << endl; return; } if(what=="/reload") { *qout << "Reloading config..." << endl; lists.clear(); settings.clear(); ircModChecks.clear(); ircMods.clear(); ircModIOList.clear(); readConfig(); return; } if(what=="/help") printAdditionalHelp(); writeToServer(what+"\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" "/exitafter exits the Server Manager after the current round.\n" "/next stops the actual Clonk Server and starts a new one.\n" "/reload rereads the config file.\n" "/skip removes the first scenario in user wish list.\n" "Clonk commands following..." << endl; } void CRSM::readLog() { QString temp(logfile->readLine().trimmed()); if(temp!="") logstream << temp << endl; } void CRSM::scenarioFinished() { if(finish) { writeConfig(); if(connection != 0) { connection->quit(settings["IrcQuitMessage"]); connect(connection, SIGNAL(disconnected()), QCoreApplication::instance(), SLOT(quit())); QTimer::singleShot(500, QCoreApplication::instance(), SLOT(quit())); } else QCoreApplication::quit(); return; } session.clear(); sessionAdmin = ClientInfo(); clients.clear(); greeted.clear(); clientcount = 0; if((autoHost || userlist.length() > 0) && !finish) nextScen(); else if(!settings["IrcIngameChannel"].isEmpty()) { connection->sendCommand(IrcCommand::createTopic(settings["IrcIngameChannel"], "Kein laufendes Spiel.")); QFile lastScenFile(LAST_SCEN_FILE_NAME); QFile curScenFile(CUR_SCEN_FILE_NAME); curScenFile.open(QFile::ReadOnly); lastScenFile.open(QFile::WriteOnly); lastScenFile.write(curScenFile.readAll()); lastScenFile.close(); curScenFile.close(); curScenFile.open(QFile::WriteOnly); curScenFile.close(); } } void CRSM::ircMessageReceived(IrcMessage *message) { if(message->isOwn()) return; if(message->type() == IrcMessage::Notice) { if(message->nick() == "NickServ") { QStringList split = message->parameters().at(1).split(' ', QString::SkipEmptyParts); if(split.first() == "STATUS") { QString statusNick = split.at(1); int status = split.at(2).toInt(); if(ircModChecks.contains(statusNick)) { ircModChecks.removeAll(statusNick); if(status == 3 && lists["IrcModerators"].contains(statusNick)) { ircMods.append(statusNick); ircModJoined(statusNick); } else { ircModFifos.remove(statusNick); } } } } } else if(message->type() == IrcMessage::Private) { QString target = message->parameters().at(0); if(message->parameters().at(0) == connection->nickName()) target = message->nick(); QString mess = message->parameters().at(1).trimmed(); if(target == settings["IrcIngameChannel"]) { writeToServer("[IRC]<" + message->nick() + "> " + mess + "\n"); } else if(mess.left(QString(CMD_SIGN).length()) == CMD_SIGN && mess.length() > QString(CMD_SIGN).length()) { 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, settings["IrcScenListMessage"]/*"Szenarioliste ist zurzeit nur in der Lobby und im laufenden Spiel verfügbar."*/)); } else if(cmd == "autohost") { ircCheckModCmd(message->nick(), IrcModOperations::Autohost); } else if(cmd == "noautohost") { ircCheckModCmd(message->nick(), IrcModOperations::NoAutohost); } 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(cmd=="queue") { foreach(const QString &line, printQueue().split('\n')) connection->sendCommand(IrcCommand::createNotice(target, line)); } else if(cmd=="help") { connection->sendCommand(IrcCommand::createNotice(target, CMD_SIGN "list - Zeigt Möglichkeiten zur Betrachtung einer Szenarienliste an.")); connection->sendCommand(IrcCommand::createNotice(target, CMD_SIGN "host <[Rundenordner.c4f/]Szenarioname.c4s> - Startet das gewählte Szenario, solange die Warteliste nicht zu groß ist.")); connection->sendCommand(IrcCommand::createNotice(target, QString(CMD_SIGN "queue - Zeigt die nächsten %1 Szenarien auf der Warteliste.").arg(settings["UserListLength"]))); connection->sendCommand(IrcCommand::createNotice(target, CMD_SIGN "aliaswish = - Deponiert den Wunsch, als Alias für einzutragen. Ein Moderator entscheidet darüber.")); connection->sendCommand(IrcCommand::createNotice(target, CMD_SIGN "help - Zeigt diese Hilfe an.")); ircCheckModCmd(message->nick(), ModHelp); } else if(cmd=="moderatorcheck") { ircCheckModCmd(message->nick(), IrcModOperations::ModCheck); } else if(cmd=="modinfo") { ircCheckModCmd(message->nick(), IrcModOperations::ModInfo); } else if(cmd=="skip") { ircCheckModCmd(message->nick(), IrcModOperations::SkipScen); } else if(cmd=="clear") { ircCheckModCmd(message->nick(), IrcModOperations::ClearUserList); } else if(cmd=="next") { ircCheckModCmd(message->nick(), IrcModOperations::SkipCurrentScen); } else if(cmd=="kill") { ircCheckModCmd(message->nick(), IrcModOperations::Kill); } else if(cmd=="io") { ircCheckModCmd(message->nick(), IrcModOperations::IO); } else if(cmd=="newalias") { ircCheckModCmd(message->nick(), IrcModOperations::NewAlias, arg); } else if(cmd=="aliaswish") { connection->sendCommand(IrcCommand::createMessage(target, addAliasWish(arg))); } else if(cmd=="aliaswishes") { ircCheckModCmd(message->nick(), IrcModOperations::AliasWishes); } else if(cmd=="ingamechat") { ircCheckModCmd(message->nick(), IrcModOperations::IngameChat, arg); } } else if(message->nick() == target && ircMods.contains(target)) { if(aliasWishEditor == target) { editAliasWishes(mess); } else if(ircModIOList.contains(message->nick())) { QString writeMessage; if(mess.at(0) == '\\') { mess[0] = '/'; } else { writeMessage = "[IRC]<" + message->nick() + "> "; } writeMessage += mess; writeToServer(writeMessage + "\n"); } } } else if(message->type() == IrcMessage::Join) { QString joinChannel = message->parameters().at(0); connection->sendCommand(IrcCommand::createMessage(joinChannel, "Hallo " + message->nick() + "!")); if(joinChannel == settings["IrcIngameChannel"]) { writeToServer("[IRC] " + message->nick() + " hat den Channel betreten." + "\n"); } else ircCheckModCmd(message->nick(), IrcModOperations::CheckOnly); } else if(message->type() == IrcMessage::Quit) { ircMods.removeAll(message->nick()); ircModChecks.removeAll(message->nick()); ircModIOList.removeAll(message->nick()); if(aliasWishEditor == message->nick()) { stopAliasWishEditing(); } } else if(message->type() == IrcMessage::Kick) { if(message->parameters().at(1) == connection->nickName()) { connection->sendCommand(IrcCommand::createJoin(message->parameters().at(0))); } } else if(message->type() == IrcMessage::Part) { QString leaveChannel = message->parameters().at(0); if(leaveChannel == settings["IrcIngameChannel"]) { writeToServer("[IRC] " + message->nick() + " hat den Channel verlassen." + "\n"); } } else if(message->type() == IrcMessage::Mode) { QRegExp modeExp("^\\+[a-zA-Z]*(a|o)"); if(message->parameters().size() >= 3 && message->parameters().at(0) == settings["IrcIngameChannel"] && modeExp.exactMatch(message->parameters().at(1)) && message->parameters().at(2) == settings["IrcNick"]) { if(settings["IrcUseIngameChat"] == "true" && !settings["IrcIngameChannel"].isEmpty()) { connection->sendCommand(IrcCommand::createTopic(settings["IrcIngameChannel"], "Kein laufendes Spiel.")); } } } } void CRSM::greet(QString pcName) { if(!clients.contains(pcName)) return; ClientInfo &info = clients[pcName]; writeToServer(QString("Hallo " + info.nick + "!\n")); } void CRSM::startScen(QString scen, QStringList argList) { QFile lastScenFile(LAST_SCEN_FILE_NAME); QFile curScenFile(CUR_SCEN_FILE_NAME); curScenFile.open(QFile::ReadOnly); lastScenFile.open(QFile::WriteOnly); lastScenFile.write(curScenFile.readAll()); lastScenFile.close(); curScenFile.close(); curScenFile.open(QFile::WriteOnly); curScenFile.write(scen.toUtf8()); curScenFile.close(); QFile scoreboardFile(SCOREBOARD_FILE_NAME); scoreboardFile.open(QFile::WriteOnly); scoreboardFile.close(); session["scenname"] = scen; session["IrcUseIngameChat"] = settings["IrcUseIngameChat"]; if(settings["IrcUseIngameChat"] == "true" && !settings["IrcIngameChannel"].isEmpty()) { connection->sendCommand(IrcCommand::createTopic(settings["IrcIngameChannel"], "Aktuelles Szenario: " + session["scenname"] + " | Ingamechat ist " + (session["IrcUseIngameChat"] == "true" ? "" : "de") + "aktviert.")); } while(maps["Alias"].contains(scen)) scen = maps["Alias"].value(scen); serverprocess->setWorkingDirectory(QDir::currentPath()); serverprocess->start(settings["ServerExecutable"], argList << scen); } void CRSM::readConfig() { QFile config(CONFIG_FILE_NAME); 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("^([^=]+)\\+=(.*)$"); QRegExp confMapExp("^([^\\[]+)\\[([^\\]]+)\\]\\s*=\\s*(.*)$"); QString line; for(;;) { line=config.readLine().trimmed(); if(confPlusExp.exactMatch(line)) { lists[confPlusExp.cap(1).trimmed()].push_back(confPlusExp.cap(2).trimmed()); } else if(confMapExp.exactMatch(line)) { maps[confMapExp.cap(1).trimmed()][confMapExp.cap(2).trimmed()] = confMapExp.cap(3).trimmed(); } else if(confExp.exactMatch(line)) { settings.insert(confExp.cap(1).trimmed(),confExp.cap(2).trimmed()); } if(config.atEnd()) break; } *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; } } *qout << endl; foreach(const QString &key, maps.keys()) { *qout << key << ":" << endl; foreach(const QString &mapkey, maps.value(key).keys()) { *qout << "\t[" << mapkey << "]" << " = " << maps.value(key).value(mapkey) << endl; } } *qout << 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*$"); QRegExp pcNameExp("^\\s*LocalName=\"(.*)\"\\s*$"); foreach(const QString &line, QString(clonkconfig.readAll().trimmed()).split("\n")) { //*qout << line << endl; if(nickExp.exactMatch(line)) { settings["ServerNick"]=nickExp.cap(1); break; } else if(pcNameExp.exactMatch(line)) { settings["ServerPCName"]=pcNameExp.cap(1); break; } } *qout << endl; *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.fileName() == ".." || it.fileName() == ".") continue; 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())) { const QStringList& list = listC4Folder(it.filePath()); if(!list.isEmpty()) { QFile listFile(it.filePath()+".lst"); listFile.open(QFile::WriteOnly); foreach(const QString& scen, list) { listFile.write(scen.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) { while(maps["Alias"].contains(filePath)) filePath = maps["Alias"].value(filePath); QFileInfo fileInfo(settings["ClonkDirectory"]+filePath); if(fileInfo.suffix() != "c4s") return false; bool exists = fileInfo.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 if(commandArgs.toLower() == "aliase") { ret += "Vorhandene Aliase:\n"; foreach(const QString &alias, maps["Alias"].keys()) { ret += QString(" " + alias + " = " + maps["Alias"].value(alias) + "\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()).trimmed() + "\n"; } else ret += "Der Ordner \"" + commandArgs + "\" wurde nicht gefunden!\n"; } return ret; } QString CRSM::printQueue() { QString ret("Folgende Szenarien befinden sich in der Warteschlange:\n"); for(int i=0;ii?userlist.at(i):scenlist.at((i-userlist.length() + current)%scenlist.length()) + " (auto)")+"\n"; } return ret; } /*QByteArray CRSM::toClonkFormat(const QString &str) { QByteArray ret; foreach(const QChar& c, str) { if(c == QString("™")) ret.append(c); else ret.append(c.toLatin1()); } return ret; }*/ void CRSM::ircCheckModCmd(const QString &nick, CRSM::IrcModOperations operation, QString arg) { if(!lists["IrcModerators"].contains(nick)) return; if(ircMods.contains(nick)) ircModOperation(nick, operation, arg); else { if(!ircModChecks.contains(nick)) { ircModChecks.append(nick); connection->sendCommand(IrcCommand::createMessage("NickServ", "STATUS " + nick)); } ircModFifos[nick].append(qMakePair(operation, arg)); } } void CRSM::ircModOperation(const QString &nick, CRSM::IrcModOperations operation, QString arg) { QString answer, skipped; QRegExp aliasExp("^([^=]+)=(.*)$"); switch(operation) { case IrcModOperations::ModCheck: connection->sendCommand(IrcCommand::createMessage(nick, "Du bist Moderator.")); break; case IrcModOperations::ModInfo: connection->sendCommand(IrcCommand::createMessage(nick, "Moderatoren sind (* ist aktiv, + verwendet IO):")); foreach(const QString &mod, lists["IrcModerators"]) { connection->sendCommand(IrcCommand::createMessage(nick, (ircMods.contains(mod) ? QString("*") : QString(" ")) + (ircModIOList.contains(mod) ? QString("+") : QString(" ")) + " " + mod)); } break; case IrcModOperations::SkipScen: if((skipped = skipScen()) != "") { answer = "\"" + skipped + "\" übersprungen."; } else answer = "Userliste ist leer!"; connection->sendCommand(IrcCommand::createMessage(nick, answer)); break; case IrcModOperations::ClearUserList: if(userlist.length() > 0) { userlist.clear(); answer = "Userliste geleert."; } else { answer = "Userliste ist leer!"; } connection->sendCommand(IrcCommand::createMessage(nick, answer)); break; case IrcModOperations::SkipCurrentScen: skipCurrent(); connection->sendCommand(IrcCommand::createMessage(nick, "Versuche zu überspringen...")); break; case IrcModOperations::Autohost: autoHost = true; connection->sendCommand(IrcCommand::createMessage(nick, "Automatisches Hosting aktiviert.")); if(session["hosting"] != "true") { nextScen(); } break; case IrcModOperations::NoAutohost: autoHost = false; connection->sendCommand(IrcCommand::createMessage(nick, "Automatisches Hosting deaktiviert.")); break; case IrcModOperations::ModHelp: connection->sendCommand(IrcCommand::createMessage(nick, "Moderatorbefehle:")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "next - Schließt stdin vom Clonk-Server, vorausgesetzt es läuft einer. Wenn dieser ordnungsgemäß läuft beendet er sich.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "kill - Tötet den Clonk-Server, vorausgesetzt es läuft einer. Bitte nur verwenden wenn " CMD_SIGN "next nicht funktioniert.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "skip - Entfernt das nächste Szenario aus der Wunschliste.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "clear - Löscht die Wunschliste.")); if(settings["IrcUseIngameChat"] == "true") connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "ingamechat - Schaltet den Irc-Ingame-Chat ein bzw. aus.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "modinfo - Listet alle Moderatoren auf.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "io - Schaltet den IO-Modus ein bzw. aus. Im IO-Modus wird die Ausgabe vom Clonk-Server an den Privat-Chat weitergeleitet und der Privat-Chat wird an den Clonk-Server weitergeleitet. Clonk-Befehle mit \\ statt /.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "newalias = - Trägt als Alias für ein.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "aliaswishes - Zum geführten Bearbeiten der Aliaswünsche.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "autohost - Schaltet Autohosting ein.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "noautohost - Schaltet Autohosting aus.")); connection->sendCommand(IrcCommand::createMessage(nick, CMD_SIGN "aliaswishes - Zum geführten Bearbeiten der Aliaswünsche.")); break; case IrcModOperations::Kill: serverprocess->kill(); connection->sendCommand(IrcCommand::createMessage(nick, "Clonk-Server wurde gekillt.")); break; case IrcModOperations::IO: if(ircModIOList.contains(nick)) ircModIOList.removeAll(nick); else ircModIOList.append(nick); case IrcModOperations::CheckOnly: break; case IrcModOperations::NewAlias: if(aliasExp.exactMatch(arg)) { const QString &scen = aliasExp.cap(2).trimmed(); const QString &alias = aliasExp.cap(1).trimmed(); if(scenExists(scen)) { if(maps["AliasWishes"].contains(alias)) { connection->sendCommand(IrcCommand::createMessage(nick, "Aliaswunsch für \"" + maps["AliasWishes"].value(alias) + "\" entfernt und Alias hinzugefügt!")); maps["AliasWishes"].remove(alias); } else if(maps["Alias"].contains(alias)) { connection->sendCommand(IrcCommand::createMessage(nick, "Alias für \"" + maps["Alias"][alias] + "\" überschrieben!")); } else { connection->sendCommand(IrcCommand::createMessage(nick, "Alias hinzugefügt!")); } maps["Alias"][alias] = scen; } else connection->sendCommand(IrcCommand::createMessage(nick, "Szenario \"" + scen + "\" wurde nicht gefunden!")); } else connection->sendCommand(IrcCommand::createMessage(nick, "Eingabefehler! Siehe !help für mehr Informationen.")); break; case IrcModOperations::AliasWishes: if(aliasWishEditor == "") { aliasWishEditor = nick; if(ircModIOList.contains(nick)) connection->sendCommand(IrcCommand::createMessage(nick, "IO-Eingabe ist während dem Aliaswünsche-Bearbeiten deaktiviert.")); editAliasWishes(); } else connection->sendCommand(IrcCommand::createMessage(nick, aliasWishEditor + " bearbeitet bereits die Aliase. Danke für die Bemühung.")); break; case IrcModOperations::IngameChat: connection->sendCommand(IrcCommand::createMessage(nick, ircActivateIngameChat(arg.toLower() == "on"))); break; } } void CRSM::ircModOperation(const QString &nick, QPair operationArg) { ircModOperation(nick, operationArg.first, operationArg.second); } QString CRSM::skipScen() { if(userlist.length() > 0) { QString skipped = userlist.first(); userlist.removeFirst(); return skipped; } else return ""; } void CRSM::skipCurrent() { serverprocess->closeWriteChannel(); } void CRSM::writeToServer(const QString &message) { if(!serverprocess->isWritable()) return; serverprocess->write(codec->fromUnicode(message)); if(settings["ServerUsesReadline"] == "true") { if(writtenToServer.length() > settings["ReadlineRereadLimit"].toInt()) writtenToServer.clear(); writtenToServer += codec->fromUnicode(message); } } void CRSM::writeConfig() { QFile config(CONFIG_FILE_NAME); config.open(QFile::WriteOnly); QTextStream configStream(&config); foreach(const QString &key, settings.keys()) { configStream << key << " = " << settings.value(key) << endl; } configStream << endl; foreach(const QString &key, lists.keys()) { foreach(const QString &val, lists.value(key)) { configStream << key << " += " << val << endl; } } configStream << endl; foreach(const QString &key, maps.keys()) { foreach(const QString &mapkey, maps.value(key).keys()) { configStream << key << "[" << mapkey << "]" << " = " << maps.value(key).value(mapkey) << endl; } } configStream << endl; config.close(); } QString CRSM::addAliasWish(const QString ¶m) { QRegExp aliasExp("^([^=]+)=(.*)$"); if(aliasExp.exactMatch(param)) { const QString &alias = aliasExp.cap(1).trimmed(); const QString &scen = aliasExp.cap(2).trimmed(); if(maps["Alias"].contains(alias)) return "Alias ist bereits vergeben!"; else if(maps["AliasWishes"].contains(alias)) return "Alias ist bereits als Wunsch vergeben!"; else if(scenExists(scen)) { maps["AliasWishes"].insert(alias, scen); informModsAboutAliasWish(); return "Aliaswunsch ist hinterlegt!"; } else return "Szenario \"" + scen + "\" wurde nicht gefunden!"; } else return "Eingabefehler! Siehe !help für mehr Informationen."; } void CRSM::ircModJoined(const QString &nick) { while(ircModFifos[nick].length() > 0) { ircModOperation(nick, ircModFifos[nick].first()); ircModFifos[nick].removeFirst(); } if(!maps["AliasWishes"].isEmpty()) { connection->sendCommand(IrcCommand::createNotice(nick, QString::number(maps["AliasWishes"].size()) + " neue" + (maps["AliasWishes"].size() > 1 ? QString("") : QString("r")) + " Aliasw" + (maps["AliasWishes"].size() > 1 ? QString("ü") : QString("u")) + "nsch" + (maps["AliasWishes"].size() > 1 ? QString("e") : QString("")) + " " + (maps["AliasWishes"].size() > 1 ? QString("sind") : QString("ist")) + " verfügbar.")); connection->sendCommand(IrcCommand::createNotice(nick, "Bitte bei nächster Gelegenheit mit " CMD_SIGN "aliaswishes bearbeiten.")); } } void CRSM::informModsAboutAliasWish() { foreach(const QString& mod, ircMods) { connection->sendCommand(IrcCommand::createNotice(mod, "Ein neuer Aliaswunsch ist verfügbar. Insgesamt verfügbar: " + QString::number(maps["AliasWishes"].size()))); } } void CRSM::editAliasWishes() { if(maps["AliasWishes"].isEmpty()) { connection->sendCommand(IrcCommand::createMessage(aliasWishEditor, "Keine Aliaswünsche " + (currentAliasWish == "" ? QString("") : QString("mehr ")) + "vorhanden.")); stopAliasWishEditing(); } else { currentAliasWish = maps["AliasWishes"].firstKey(); connection->sendCommand(IrcCommand::createMessage(aliasWishEditor, currentAliasWish + " = " + maps["AliasWishes"][currentAliasWish] + " [Ja|Nein|Stop]")); } } void CRSM::editAliasWishes(const QString &message) { if(message.toLower() == "j" || message.toLower() == "ja") { maps["Alias"][currentAliasWish] = maps["AliasWishes"][currentAliasWish]; maps["AliasWishes"].remove(currentAliasWish); } else if(message.toLower() == "n" || message.toLower() == "nein") { maps["AliasWishes"].remove(currentAliasWish); } else if(message.toLower() == "s" || message.toLower() == "stop") { connection->sendCommand(IrcCommand::createMessage(aliasWishEditor, "Aliaswunsch-Bearbeitung gestoppt.")); stopAliasWishEditing(); return; } else { connection->sendCommand(IrcCommand::createMessage(aliasWishEditor, "\"" + message + "\" ist keine Antwortmöglichkeit. Antwortmöglichkeiten: [Ja|Nein|Stop]")); } editAliasWishes(); } void CRSM::stopAliasWishEditing() { aliasWishEditor = ""; currentAliasWish = ""; } QString CRSM::ircActivateIngameChat(bool activated) { if(settings["IrcUseIngameChat"] == "true" && !settings["IrcIngameChannel"].isEmpty()) { session["IrcUseIngameChat"] = activated ? "true" : "false"; connection->sendCommand(IrcCommand::createTopic(settings["IrcIngameChannel"], "Aktuelles Szenario: " + session["scenname"] + " | Ingamechat ist " + (session["IrcUseIngameChat"] == "true" ? "" : "de") + "aktviert.")); return "Ingamechat wurde " + (session["IrcUseIngameChat"] == "true" ? QString("") : QString("de")) + "aktviert."; } else return "Ingamechat ist administrativ deaktiviert!"; } QStringList CRSM::listC4Folder(const QString &path) { QStringList ret; QFileInfo fileInfo(path); if(fileInfo.isDir()) { QDir dir(path); const QStringList folderList = dir.entryList(QStringList() << "*.c4f", QDir::NoFilter, QDir::Name | QDir::IgnoreCase); ret.append(dir.entryList(QStringList() << "*.c4s", QDir::NoFilter, QDir::Name | QDir::IgnoreCase)); foreach(const QString &folder, folderList) { const QStringList &folderList = listC4Folder(path + '/' + folder); foreach (const QString &scen, folderList) { ret.append(folder + '/' + scen); } } } else { QProcess c4group; c4group.start(settings["ClonkDirectory"]+C4GROUP_EXECUTABLE, QStringList() << path << "-l", QProcess::ReadOnly); c4group.waitForFinished(); c4group.readLine(); QRegExp finishExp("^\\d+ Entries, \\d+ Bytes$"); QRegExp scenarioExp("^(.*\\.c4s)\\s+\\d+ Bytes\\s.*$"); QRegExp folderExp("^(.*\\.c4f)\\s+\\d+ Bytes\\s.*$"); QString line; while(!c4group.atEnd()) { line = codec->toUnicode(c4group.readLine().trimmed()); if(line.isEmpty()) continue; if(finishExp.exactMatch(line)) break; if(scenarioExp.exactMatch(line)) { ret.append(scenarioExp.cap(1)); } else if(folderExp.exactMatch(line)) { const QStringList &folderList = listC4Folder(path + '/' + folderExp.cap(1)); foreach (const QString &scen, folderList) { ret.append(folderExp.cap(1) + '/' + scen); } } } } return ret; }