예제 #1
0
/**
 * Adds a participant to a convo, if that conversation dont exist, the tab and convo is created.
 * @brief MainWindow::addToConvo
 * @param fromIp ip of user that added someone to a conversation, empty if originating user is the current user.
 * @param fromName name of user that added someone to a conversation, empty if originating user is the current user.
 * @param member new participant to be added: "{name}/{ip}"
 * @param cid Conversation id.
 * @return false if member already exists in the conversation, true otherwise.
 */
bool MainWindow::addToConvo(QString fromIp, QString fromName, QString member, QString cid)
{
    Conversation* lst = 0;
    QStringList tmp = member.split("/", QString::SkipEmptyParts);
    int i = 0;
    for (; i < convos->count(); ++i)
    {
        if(convos->at(i)->getCid().compare(cid) == 0)
        {
            lst = convos->at(i);
            break;
        }
    }

    if(lst == 0)
    {
        createTab(cid, fromIp, fromName);
        lst = convos->at(convos->count()-1);
    }
    else
    {
        for (int i = 0; i < lst->count(); ++i)
        {
            if(tmp.at(1).compare(lst->at(i)->getIp()) == 0) return false;
        }
    }

    lst->add(tmp.at(1), tmp.at(0));

    if(ui->tabgrpConversations->currentIndex() == (i+1))
        ui->lstInConvo->addItem(member);

    return true;
}
예제 #2
0
void ConversationManager::handleMessageReceived(Swift::Message::ref message) {
// 	std::string name = message->getTo().getUnescapedNode();
// 	if (name.find_last_of("%") != std::string::npos) { // OK when commented
// 		name.replace(name.find_last_of("%"), 1, "@"); // OK when commented
// 	}
	std::string name = Buddy::JIDToLegacyName(message->getTo());
	if (name.empty()) {
		LOG4CXX_WARN(logger, m_user->getJID().toString() << ": Tried to create empty conversation");
		return;
	}

	// create conversation if it does not exist.
	if (!m_convs[name]) {
		Conversation *conv = m_component->getFactory()->createConversation(this, name);
		addConversation(conv);
	}
	// if it exists and it's MUC, but this message is PM, get PM conversation or create new one.
	else if (m_convs[name]->isMUC() && message->getType() != Swift::Message::Groupchat) {
		std::string room_name = name;
		name = room_name + "/" + message->getTo().getResource();
		if (m_convs.find(name) == m_convs.end()) {
			Conversation *conv = m_component->getFactory()->createConversation(this, message->getTo().getResource());
			conv->setRoom(room_name);
			conv->setNickname(name);
			addConversation(conv);
		}
	}

	// update resource and send the message
	m_convs[name]->setJID(message->getFrom());
	m_convs[name]->sendMessage(message);
}
예제 #3
0
Conversation LoadConversation()
{
	Conversation conversation;
	DataFile file(cin);
	for(const DataNode &node : file)
		if(node.Token(0) == "conversation")
		{
			conversation.Load(node);
			break;
		}
	
	const map<string, string> subs = {
		{"<bunks>", "[N]"},
		{"<cargo>", "[N tons of Commodity]"},
		{"<commodity>", "[Commodity]"},
		{"<date>", "[Day Mon Year]"},
		{"<day>", "[The Nth of Month]"},
		{"<destination>", "[Planet in the Star system]"},
		{"<fare>", "[N passengers]"},
		{"<first>", "[First]"},
		{"<last>", "[Last]"},
		{"<origin>", "[Origin Planet]"},
		{"<passengers>", "[your passengers]"},
		{"<planet>", "[Planet]"},
		{"<ship>", "[Ship]"},
		{"<system>", "[Star]"},
		{"<tons>", "[N tons]"}
	};
	return conversation.Substitute(subs);
}
예제 #4
0
/**
 * Adds selected participant to a conversation, and notifies participants.
 * @brief MainWindow::contextAddToConvo
 * @param sel Member to add, formatted: "{name}/{ip}"
 */
void MainWindow::contextAddToConvo(QString sel)
{
    Conversation* convo = convos->at(ui->tabgrpConversations->currentIndex()-1);
    if(addToConvo("","",sel, convo->getCid()))
    {
        controller->notifyMembersChanged(convo, sel);
    }
}
예제 #5
0
/**
 * Called when user has been added to an existing conversation
 * between two or more participants. Creates a new tab, and
 * adds all participants to the corresponding conversation list.
 * @brief MainWindow::createExistingConvo
 * @param data "{convo_id}|{ip}|{name}|{ip}|{name}|..."
 */
void MainWindow::createExistingConvo(QString data)
{
    QStringList stuff = data.split('|', QString::SkipEmptyParts);

    createTab(stuff.at(0), stuff.at(1), stuff.at(2));
    indicateChange(convos->count());
    Conversation* newConvo = convos->at(convos->count()-1);

    for (int i = 3; i < stuff.count(); i++)
    {
        QString ip = stuff.at(i++); //Separated to avoid possibly undefined behavior.
        newConvo->add(ip, stuff.at(i));
    }
}
예제 #6
0
bool Manager::saveMessage(string json){
	MessageFactory messageFactory;
	Message* message = messageFactory.createWithJsonString(json);
	User* sender = message->getSender();
	User* receptor = message->getReceptor();
	Conversation* conv = this->db->getConversation(sender ->getUsername(), receptor->getUsername() );

	int messageID = conv->getNumberMessages();
	message->setId(to_string(messageID));
	conv->addOneMessage(messageID);
	this->db->saveConversation(conv);
	LoggerManager::getInstance()->log(LoggerManager::logInfo, "New Message created");
	return this->db->saveMessage(message);
}
예제 #7
0
/**
 * Called when tab is switched, empties the participant list,
 * and populates it with the participants in the newly selected
 * tab.
 * @brief MainWindow::on_tabgrpConversations_currentChanged
 */
void MainWindow::on_tabgrpConversations_currentChanged(int index)
{
    ui->lstInConvo->clear();

    indicateChange(index, Qt::black);
    if(index == 0) return;

    Conversation* lst = convos->at(index-1);

    for (int i = 0; i < lst->count() ; ++i)
    {
        Peer* p = lst->at(i);
        ui->lstInConvo->addItem(p->getName() + "/" + p->getIp());
    }
}
예제 #8
0
Message::ptr_t MessageManager::receivedMessage(Conversation &conversation, MessageData data)
{
    auto message = make_shared<Message>(*this, data, Message::INCOMING, conversation.getId());

    assert(conversation.getIdentity());
    assert(conversation.getFirstParticipant());

    auto cert = conversation.getFirstParticipant()->getCert();
    if (!message->validate(*cert)) {
        LFLOG_WARN << "Incoming message from " << conversation.getFirstParticipant()->getName()
                   << " to " << conversation.getIdentity()->getName()
                   << " failed validation. Rejecting.";
        return {};
    }

    // See if we already have received this message
    if (auto existing = getMessage(data.messageId, conversation.getId())) {
        return existing;
    }

    message->addToDb();
    registry_.add(message->getId(), message);
    touch(message);

    emit messageAdded(message);
    return message;
}
예제 #9
0
void Encounter::OnMainMenuButtonClicked(int id)
{
    int originalId = id;

    // No buttons are in the list for conversations that aren't available,
    // so we want to bump up the ID to account for these.
    for (int i = 0; i <= id; i++)
    {
        Conversation *pConversation = allConversationList[i];

        if (!pConversation->GetIsEnabled() || (pConversation->GetDisappearsAfterCompleted() && pConversation->GetHasBeenCompleted()))
        {
            id++;
        }
    }

    PrepNextConversation(allConversationList[id], originalId);
}
예제 #10
0
Message::ptr_t MessageManager::sendMessage(Conversation &conversation, MessageData data)
{
    auto message = make_shared<Message>(*this, data, Message::OUTGOING, conversation.getId());

    assert(conversation.getIdentity());

    auto cert = conversation.getIdentity()->getCert();
    message->sign(*cert);
    message->addToDb();
    registry_.add(message->getId(), message);
    touch(message);

    emit messageAdded(message);

    if (auto contact = conversation.getFirstParticipant()) {
        contact->queueMessage(message);
    }

    return message;
}
예제 #11
0
/**
 * Creates a new tab, and initializes a new conversation with given ip and name.
 * @brief MainWindow::createTab
 * @param cid Conversation id.
 * @param ip IPv4 address
 * @param name Nickname
 */
void MainWindow::createTab(QString cid, QString ip, QString name)
{
    // Inflate tablelayout from file
        QFormBuilder builder;
        QFile file(FORM_TAB);
        file.open(QFile::ReadOnly);
        QWidget *tabLayout = builder.load(&file, this);
        file.close();

    // Add layout to a new tab
        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(tabLayout);
        QWidget *newTab = new QWidget(ui->tabgrpConversations);
        newTab->setLayout(layout);
        ui->tabgrpConversations->addTab(newTab, name + "/" + ip);

    //Add eventlisteners to new tab button, message text-box and smiley-list.
    QPushButton* btnSend = tabLayout->findChild<QPushButton*>("btnTabSend");
    connect(btnSend, SIGNAL(clicked()), this, SLOT(sendConvoMessage()));

    QTextEdit* txtMsg = tabLayout->findChild<QTextEdit*>(CONVO_TAB_MSG_ID);
    txtMsg->installEventFilter(this);

    QListWidget* lstSmil = tabLayout->findChild<QListWidget*>("lstTabSmileys");
    connect(lstSmil, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_lstSmileys_doubleClicked(QModelIndex)));
    for (int i = 0; i < smileys->count(); i++)
    {
        QListWidgetItem* qlwi = new QListWidgetItem(QIcon(RES_SMILEYS + QString::number(i) + ".png"), "");
        lstSmil->addItem(qlwi);
    }

    Conversation* c = new Conversation(cid);
    if(controller->isExternal(ip)) //External ip
    {
        c->add(ip, name, true);
    }
    else c->add(ip, name);

    convos->append(c);
}
예제 #12
0
void ConversationManager::handleDatabaseJobComplete(void* ref, DatabaseResult* result)
{
    CVAsyncContainer* asyncContainer = reinterpret_cast<CVAsyncContainer*>(ref);

    switch(asyncContainer->mQuery)
    {
    case ConvQuery_Conversations:
    {
        Conversation*		conv;
        CVAsyncContainer*	asCont;
        uint32				insertId;

        DataBinding* binding = mDatabase->CreateDataBinding(1);
        binding->addField(DFT_uint32,offsetof(Conversation,mId),4,0);

        uint64 count = result->getRowCount();

        for(uint32 i = 0; i < count; i++)
        {
            conv = new Conversation();
            result->GetNextRow(binding,conv);

            insertId = conv->getId();
            mConversations.insert(insertId,conv);

            asCont = new(mDBAsyncPool.malloc()) CVAsyncContainer(ConvQuery_Pages);
            asCont->mConversation = conv;

            mDatabase->ExecuteSqlAsync(this,asCont,"SELECT * FROM conversation_pages WHERE conversation_id=%u ORDER BY page",insertId);
        }

        if(result->getRowCount())
            gLogger->logMsgLoadSuccess("ConversationManager::loading %u Conversations...",MSG_NORMAL,result->getRowCount());
        else
            gLogger->logMsgLoadFailure("ConversationManager::loading Conversations...",MSG_NORMAL);


        mDatabase->DestroyDataBinding(binding);
    }
    break;

    case ConvQuery_Pages:
    {
        ConversationPage*	page;
        CVAsyncContainer*	asCont;
        uint32				batchId;

        DataBinding*		pageBinding = mDatabase->CreateDataBinding(5);
        pageBinding->addField(DFT_uint32,offsetof(ConversationPage,mId),4,1);
        pageBinding->addField(DFT_bstring,offsetof(ConversationPage,mCustomText),512,2);
        pageBinding->addField(DFT_bstring,offsetof(ConversationPage,mStfFile),255,3);
        pageBinding->addField(DFT_bstring,offsetof(ConversationPage,mStfVariable),255,4);
        pageBinding->addField(DFT_uint32,offsetof(ConversationPage,mAnimation),4,6);

        DataBinding*	batchBinding = mDatabase->CreateDataBinding(1);
        batchBinding->addField(DFT_uint32,0,4,5);

        uint32 count = static_cast<uint32>(result->getRowCount());

        for(uint64 i = 0; i< count; i++)
        {
            page = new ConversationPage();
            result->GetNextRow(pageBinding,page);

            page->mCustomText.convert(BSTRType_Unicode16);

            result->ResetRowIndex(static_cast<int>(i));

            result->GetNextRow(batchBinding,&batchId);

            asyncContainer->mConversation->mPages.push_back(page);


            // query options
            asCont = new(mDBAsyncPool.malloc()) CVAsyncContainer(ConvQuery_Page_OptionBatch);
            asCont->mConversationPage = page;

            mDatabase->ExecuteSqlAsync(this,asCont,"SELECT conversation_options.id,conversation_options.customText,conversation_options.stf_file,"
                                       "conversation_options.stf_variable,conversation_options.event,conversation_options.pageLink "
                                       "FROM "
                                       "conversation_option_batches "
                                       "INNER JOIN conversation_options ON (conversation_option_batches.option_id = conversation_options.id) "
                                       "WHERE "
                                       "(conversation_option_batches.id = %u) ORDER BY conversation_option_batches.option_id",batchId);

        }

        mDatabase->DestroyDataBinding(pageBinding);
        mDatabase->DestroyDataBinding(batchBinding);
    }
    break;

    case ConvQuery_Page_OptionBatch:
    {
        ConversationOption*	option;
        DataBinding*		binding = mDatabase->CreateDataBinding(6);

        binding->addField(DFT_uint32,offsetof(ConversationOption,mId),4,0);
        binding->addField(DFT_bstring,offsetof(ConversationOption,mCustomText),512,1);
        binding->addField(DFT_bstring,offsetof(ConversationOption,mStfFile),255,2);
        binding->addField(DFT_bstring,offsetof(ConversationOption,mStfVariable),255,3);
        binding->addField(DFT_uint32,offsetof(ConversationOption,mEvent),4,4);
        binding->addField(DFT_uint32,offsetof(ConversationOption,mPageLinkId),4,5);

        uint64 count = result->getRowCount();

        for(uint32 i = 0; i < count; i++)
        {
            option = new ConversationOption();

            result->GetNextRow(binding,option);

            option->mCustomText.convert(BSTRType_Unicode16);

            asyncContainer->mConversationPage->mOptions.push_back(option);
        }


        mDatabase->DestroyDataBinding(binding);
    }
    break;

    default:
        break;
    }

    mDBAsyncPool.free(asyncContainer);
}
예제 #13
0
파일: msn-sb.cpp 프로젝트: licq-im/debian
void CMSN::ProcessSBPacket(const Licq::UserId& socketUserId, CMSNBuffer* packet,
                           Licq::TCPSocket* sock)
{
    int nSock = sock->Descriptor();
    CMSNPacket *pReply;
    bool bSkipPacket;

    //while (!packet->End())
    {
        pReply = 0;
        bSkipPacket = true;
        string strCmd = packet->unpackRawString(3);

        if (strCmd == "IRO")
        {
            packet->SkipParameter(); // Seq
            packet->SkipParameter(); // current user to add
            packet->SkipParameter(); // total users in conversation
            UserId userId(myOwnerId, packet->GetParameter());

            bool newUser;
            {
                Licq::UserWriteGuard u(userId, true, &newUser);
                if (newUser)
                {
                    // MSN uses UTF-8 so we need to set this for all new users automatically
                    u->SetEnableSave(false);
                    u->setUserEncoding("UTF-8");
                    u->SetEnableSave(true);
                    u->save(Licq::User::SaveLicqInfo);
                }
            }

            // Add the user to the conversation
            Conversation* convo = gConvoManager.getFromSocket(nSock);
            if (convo == NULL)
                convo = gConvoManager.add(myOwnerId, nSock);
            convo->addUser(userId);

            // Notify the plugins of the new CID
            Licq::gPluginManager.pushPluginSignal(new Licq::PluginSignal(
                    Licq::PluginSignal::SignalConversation,
                    Licq::PluginSignal::ConvoCreate, userId, 0, SocketToCID(nSock)));

            Licq::gPluginManager.pushPluginSignal(new Licq::PluginSignal(
                    Licq::PluginSignal::SignalConversation,
                    Licq::PluginSignal::ConvoJoin, userId, 0, SocketToCID(nSock)));

            gLog.info("%s joined the conversation", userId.toString().c_str());
        }
        else if (strCmd == "ANS")
        {
            // Send our capabilities
            Send_SB_Packet(Licq::UserId(), new CPS_MsnClientCaps(), sock);
        }
        else if (strCmd == "MSG")
        {
            Licq::UserId userId(myOwnerId, packet->GetParameter());
            packet->SkipParameter(); // Nick
            string strSize = packet->GetParameter(); // Size
            int nSize = atoi(strSize.c_str()) + 1; // Make up for the \n
            unsigned long nBeforeParse = packet->getDataPosRead() - packet->getDataStart();
            packet->SkipPacket(); // Skip \r\n
            packet->ParseHeaders();
            unsigned long nAfterParse = packet->getDataPosRead() - packet->getDataStart();
            int nRead = nAfterParse - nBeforeParse;
            nSize -= nRead;

            string strType = packet->GetValue("Content-Type");
            if (strType == "text/x-msmsgscontrol")
            {
                packet->SkipRN();
                packet->SkipRN();
                packet->SkipRN();
                Licq::UserWriteGuard u(userId);
                if (u.isLocked())
                    setIsTyping(*u, true, SocketToCID(nSock));
            }
            else if (strncmp(strType.c_str(), "text/plain", 10) == 0)
            {
                gLog.info("Message from %s", userId.accountId().c_str());

                bSkipPacket = false;
                string msg = Licq::gTranslator.returnToUnix(packet->unpackRawString(nSize));

                Licq::EventMsg* e = new Licq::EventMsg(msg, time(0), 0, SocketToCID(nSock));
                Licq::UserWriteGuard u(userId);
                if (u.isLocked())
                    setIsTyping(*u, false, SocketToCID(nSock));
                if (Licq::gDaemon.addUserEvent(*u, e))
                    gOnEventManager.performOnEvent(OnEventData::OnEventMessage, *u);
            }
            else if (strncmp(strType.c_str(), "text/x-msmsgsinvite", 19) == 0)
            {
                packet->SkipRN();
                packet->ParseHeaders();

                string application = packet->GetValue("Application-Name");
                string cookie = packet->GetValue("Invitation-Cookie");
                string command = packet->GetValue("Invitation-Command");

                if (command == "INVITE")
                {
                    // Invitation for unknown application, tell inviter that we don't have it
                    gLog.info("Invitation from %s for unknown application (%s)",
                              userId.accountId().c_str(), application.c_str());
                    pReply = new CPS_MSNCancelInvite(cookie, "REJECT_NOT_INSTALLED");
                }
            }
            else if (strncmp(strType.c_str(), "application/x-msnmsgrp2p", 24) == 0)
            {
                // Get the binary header
                /*
                  unsigned long nSessionID = packet->UnpackUnsignedLong();
                unsigned long nIdentifier = packet->UnpackUnsignedLong();
                unsigned long nOffset[2], nSize[2], nAckDataSize[2];
                nOffset[0] = packet->UnpackUnsignedLong();
                nOffset[1] = packet->UnpackUnsignedLong();
                nSize[0] = packet->UnpackUnsignedLong();
                nSize[1] = packet->UnpackUnsignedLong();
                unsigned long nLen = packet->UnpackUnsignedLong();
                unsigned long nFlag = packet->UnpackUnsignedLong();
                unsigned long nAckID = packet->UnpackUnsignedLong();
                unsigned long nAckUniqueID = packet->UnpackUnsignedLong();
                nAckDataSize[0] = packet->UnpackUnsignedLong();
                nAckDataSize[1] = packet->UnpackUnsignedLong();

                printf("%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
                      nSessionID, nIdentifier, nOffset[0], nOffset[1], nSize[0],
                      nSize[1], nLen, nFlag, nAckID, nAckUniqueID,
                      nAckDataSize[0], nAckDataSize[1]);
                */

                CMSNDataEvent* p = FetchDataEvent(userId, sock);
                if (p)
                {
                    if (p->ProcessPacket(packet) > 0)
                    {
                        RemoveDataEvent(p);
                    }
                }
            }
            else if (strncmp(strType.c_str(), "text/x-clientcaps", 17) == 0)
            {
                packet->SkipRN();
                packet->ParseHeaders();
                string userClient = packet->GetValue("Client-Name");
                if (!userClient.empty())
                {
                    gLog.info("Identified user client as %s", userClient.c_str());

                    Licq::UserWriteGuard u(userId);
                    u->setClientInfo(userClient);
                }
            }
            else
            {
                gLog.info("Message from %s with unknown content type (%s)",
                          userId.accountId().c_str(), strType.c_str());
            }
        }
        else if (strCmd == "ACK")
        {
            string strId = packet->GetParameter();
            unsigned long nSeq = (unsigned long)atoi(strId.c_str());
            Licq::Event* e = RetrieveEvent(nSeq);
            if (e)
            {
                e->m_eResult = Licq::Event::ResultAcked;
                if (e->m_pUserEvent)
                {
                    Conversation* convo = gConvoManager.getFromSocket(nSock);
                    if (convo != NULL)
                    {
                        Licq::ConversationUsers users;
                        convo->getUsers(users);
                        BOOST_FOREACH(const UserId& userId, users)
                        {
                            Licq::UserWriteGuard u(userId);
                            if (!u.isLocked())
                                continue;

                            e->m_pUserEvent->AddToHistory(*u, false);
                            u->SetLastSentEvent();
                            if (u->id() == e->userId())
                                gOnEventManager.performOnEvent(OnEventData::OnEventMsgSent, *u);
                        }
                    }
                    else
                    {
예제 #14
0
void Encounter::RefreshButtonArrayContents()
{
    vector<ButtonArrayLoadParameters> loadParametersList;

    for (unsigned int i = 0; i < conversationList.size(); i++)
    {
        Conversation *pConversation = conversationList[i];

        if (pConversation->GetIsEnabled())
        {
            ButtonArrayLoadParameters loadParameters;

            loadParameters.text = pConversation->GetName();

            if (pConversation->GetIsLocked())
            {
                loadParameters.lockCount = pConversation->GetLockCount();
            }

            loadParameters.unlockedLockCount = 0;

            vector<Conversation::UnlockCondition *> *pUnlockConditions = pConversation->GetUnlockConditions();

            for (unsigned int i = 0; i < pUnlockConditions->size(); i++)
            {
                Conversation::UnlockCondition *pUnlockCondition = (*pUnlockConditions)[i];

                if (pUnlockCondition->GetIsConditionMet() && !pUnlockCondition->GetHasHandledMetCondition())
                {
                    loadParameters.unlockedLockCount++;
                    pUnlockCondition->SetHasHandledMetCondition(true);
                }
            }

            loadParameters.showCheckBox = pConversation->GetHasBeenCompleted();

            loadParametersList.push_back(loadParameters);
        }
    }

    for (unsigned int i = 0; i < interrogationList.size(); i++)
    {
        Interrogation *pInterrogation = interrogationList[i];

        if (!pInterrogation->GetHasBeenCompleted())
        {
            if (pInterrogation->GetIsEnabled())
            {
                ButtonArrayLoadParameters loadParameters;

                char text[256];
                snprintf(text, 256, gpLocalizableContent->GetText("Encounter/InterrogationDesignationFormatText").c_str(), pInterrogation->GetName().c_str());

                loadParameters.text = string(text);
                loadParametersList.push_back(loadParameters);
            }
        }
    }

    for (unsigned int i = 0; i < confrontationList.size(); i++)
    {
        Confrontation *pConfrontation = confrontationList[i];

        if (!pConfrontation->GetHasBeenCompleted())
        {
            if (pConfrontation->GetIsEnabled())
            {
                ButtonArrayLoadParameters loadParameters;

                char text[256];
                snprintf(text, 256, gpLocalizableContent->GetText("Encounter/ConfrontationDesignationFormatText").c_str(), pConfrontation->GetName().c_str());

                loadParameters.text = string(text);
                loadParametersList.push_back(loadParameters);
            }
        }
    }

    pMainMenuButtonArray->Load(loadParametersList);
    pPresentEvidenceTab->SetIsEnabled(Case::GetInstance()->GetEvidenceManager()->GetHasEvidence());
}
예제 #15
0
int main(int argc, char *argv[])
{
	Conversation conversation;
	bool debugMode = false;
	for(const char *const *it = argv + 1; *it; ++it)
	{
		string arg = *it;
		if(arg == "-h" || arg == "--help")
		{
			PrintHelp();
			return 0;
		}
		else if(arg == "-v" || arg == "--version")
		{
			PrintVersion();
			return 0;
		}
		else if(arg == "-t" || arg == "--talk")
			conversation = LoadConversation();
		else if(arg == "-d" || arg == "--debug")
			debugMode = true;
	}
	PlayerInfo player;
	
	try {
		SDL_Init(SDL_INIT_VIDEO);
		
		// Begin loading the game data.
		GameData::BeginLoad(argv);
		Audio::Init(GameData::Sources());
		
		// On Windows, make sure that the sleep timer has at least 1 ms resolution
		// to avoid irregular frame rates.
#ifdef _WIN32
		timeBeginPeriod(1);
#endif
		
		player.LoadRecent();
		player.ApplyChanges();
		
		// Check how big the window can be.
		SDL_DisplayMode mode;
		if(SDL_GetCurrentDisplayMode(0, &mode))
			return DoError("Unable to query monitor resolution!");
		
		Preferences::Load();
		Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI;
		if(Preferences::Has("fullscreen"))
			flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
		
		// Make the window just slightly smaller than the monitor resolution.
		int maxWidth = mode.w;
		int maxHeight = mode.h;
		// Restore this after toggling fullscreen.
		int restoreWidth = 0;
		int restoreHeight = 0;
		if(maxWidth < 640 || maxHeight < 480)
			return DoError("Monitor resolution is too small!");
		
		if(Screen::RawWidth() && Screen::RawHeight())
		{
			// Never allow the saved screen width to be leaving less than 100
			// pixels free around the window. This avoids the problem where you
			// maximize without going full-screen, and next time the window pops
			// up you can't access the resize control because it is offscreen.
			Screen::SetRaw(
				min(Screen::RawWidth(), (maxWidth - 100)),
				min(Screen::RawHeight(), (maxHeight - 100)));
			if(flags & SDL_WINDOW_FULLSCREEN_DESKTOP)
			{
				restoreWidth = Screen::RawWidth();
				restoreHeight = Screen::RawHeight();
				Screen::SetRaw(maxWidth, maxHeight);
			}
		}
		else
			Screen::SetRaw(maxWidth - 100, maxHeight - 100);
		// Make sure the zoom factor is not set too high for the full UI to fit.
		if(Screen::Height() < 700)
			Screen::SetZoom(100);
		
		// Create the window.
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#ifdef _WIN32
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
		
		SDL_Window *window = SDL_CreateWindow("Endless Sky",
			SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
			Screen::RawWidth(), Screen::RawHeight(), flags);
		if(!window)
			return DoError("Unable to create window!");
		
		SDL_GLContext context = SDL_GL_CreateContext(window);
		if(!context)
			return DoError("Unable to create OpenGL context! Check if your system supports OpenGL 3.0.", window);
		
		if(SDL_GL_MakeCurrent(window, context))
			return DoError("Unable to set the current OpenGL context!", window, context);
		
		SDL_GL_SetSwapInterval(1);
		
		// Initialize GLEW.
#ifndef __APPLE__
		glewExperimental = GL_TRUE;
		if(glewInit() != GLEW_OK)
			return DoError("Unable to initialize GLEW!", window, context);
#endif
		
		// Check that the OpenGL version is high enough.
		const char *glVersion = reinterpret_cast<const char *>(glGetString(GL_VERSION));
		if(!glVersion || !*glVersion)
			return DoError("Unable to query the OpenGL version!", window, context);
		
		const char *glslVersion = reinterpret_cast<const char *>(glGetString(GL_SHADING_LANGUAGE_VERSION));
		if(!glslVersion || !*glslVersion)
		{
			ostringstream out;
			out << "Unable to query the GLSL version. OpenGL version is " << glVersion << ".";
			return DoError(out.str(), window, context);
		}
		
		if(*glVersion < '3')
		{
			ostringstream out;
			out << "Endless Sky requires OpenGL version 3.0 or higher." << endl;
			out << "Your OpenGL version is " << glVersion << ", GLSL version " << glslVersion << "." << endl;
			out << "Please update your graphics drivers.";
			return DoError(out.str(), window, context);
		}
		
		glClearColor(0.f, 0.f, 0.0f, 1.f);
		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		
		GameData::LoadShaders();
		
		{
			// Check whether this is a high-DPI window.
			int width = 0;
			int height = 0;
			SDL_GL_GetDrawableSize(window, &width, &height);
			Screen::SetHighDPI(width > Screen::RawWidth() && height > Screen::RawHeight());
			
			// Fix a possible race condition leading to the wrong window dimensions.
			glViewport(0, 0, width, height);
		}
		
		
		UI gamePanels;
		UI menuPanels;
		menuPanels.Push(new MenuPanel(player, gamePanels));
		if(!conversation.IsEmpty())
			menuPanels.Push(new ConversationPanel(player, conversation));
		
		string swizzleName = "_texture_swizzle";
#ifndef __APPLE__
		const char *extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
		if(!strstr(extensions, swizzleName.c_str()))
#else
		bool hasSwizzle = false;
		GLint extensionCount;
		glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount);
		for(GLint i = 0; i < extensionCount && !hasSwizzle; ++i)
		{
			const char *extension = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i));
			hasSwizzle = (extension && strstr(extension, swizzleName.c_str()));
		}
		if(!hasSwizzle)
#endif
			menuPanels.Push(new Dialog(
				"Note: your computer does not support the \"texture swizzling\" OpenGL feature, "
				"which Endless Sky uses to draw ships in different colors depending on which "
				"government they belong to. So, all human ships will be the same color, which "
				"may be confusing. Consider upgrading your graphics driver (or your OS)."));
		
		FrameTimer timer(60);
		bool isPaused = false;
		while(!menuPanels.IsDone())
		{
			// Handle any events that occurred in this frame.
			SDL_Event event;
			while(SDL_PollEvent(&event))
			{
				UI &activeUI = (menuPanels.IsEmpty() ? gamePanels : menuPanels);
				
				// The caps lock key slows the game down (to make it easier to
				// see and debug things that are happening quickly).
				if(debugMode && (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
						&& event.key.keysym.sym == SDLK_CAPSLOCK)
				{
					timer.SetFrameRate((event.key.keysym.mod & KMOD_CAPS) ? 10 : 60);
				}
				else if(debugMode && event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_BACKQUOTE)
				{
					isPaused = !isPaused;
				}
				else if(event.type == SDL_KEYDOWN && menuPanels.IsEmpty()
						&& Command(event.key.keysym.sym).Has(Command::MENU)
						&& !gamePanels.IsEmpty() && gamePanels.Top()->IsInterruptible())
				{
					menuPanels.Push(shared_ptr<Panel>(
						new MenuPanel(player, gamePanels)));
				}
				else if(event.type == SDL_QUIT)
				{
					menuPanels.Quit();
				}
				else if(event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
				{
					int width = event.window.data1 & ~1;
					int height = event.window.data2 & ~1;
					if(width != Screen::RawWidth() || height != Screen::RawHeight())
					{
						Screen::SetRaw(width, height);
						if((event.window.data1 | event.window.data2) & 1)
							SDL_SetWindowSize(window, Screen::RawWidth(), Screen::RawHeight());
						SDL_GL_GetDrawableSize(window, &width, &height);
						glViewport(0, 0, width, height);
					}
				}
				else if(event.type == SDL_KEYDOWN
						&& (Command(event.key.keysym.sym).Has(Command::FULLSCREEN)
						|| (event.key.keysym.sym == SDLK_RETURN && event.key.keysym.mod & KMOD_ALT)))
				{
					if(restoreWidth)
					{
						SDL_SetWindowFullscreen(window, 0);
						Screen::SetRaw(restoreWidth, restoreHeight);
						SDL_SetWindowSize(window, Screen::RawWidth(), Screen::RawHeight());
						restoreWidth = 0;
						restoreHeight = 0;
					}
					else
					{
						restoreWidth = Screen::RawWidth();
						restoreHeight = Screen::RawHeight();
						Screen::SetRaw(maxWidth, maxHeight);
						SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
					}
					int width, height;
					SDL_GL_GetDrawableSize(window, &width, &height);
					glViewport(0, 0, width, height);
				}
				else if(activeUI.Handle(event))
				{
					// No need to do anything more!
				}
			}
			Font::ShowUnderlines(SDL_GetModState() & KMOD_ALT);
			
			// Tell all the panels to step forward, then draw them.
			((!isPaused && menuPanels.IsEmpty()) ? gamePanels : menuPanels).StepAll();
			Audio::Step();
			// That may have cleared out the menu, in which case we should draw
			// the game panels instead:
			(menuPanels.IsEmpty() ? gamePanels : menuPanels).DrawAll();
			
			SDL_GL_SwapWindow(window);
			timer.Wait();
		}
		
		// If you quit while landed on a planet, save the game.
		if(player.GetPlanet())
			player.Save();
		
		// The Preferences class reads the screen dimensions, so update them if
		// the window is full screen:
		bool isFullscreen = (restoreWidth != 0);
		Preferences::Set("fullscreen", isFullscreen);
		if(isFullscreen)
			Screen::SetRaw(restoreWidth, restoreHeight);
		Preferences::Save();
		
		Cleanup(window, context);
	}
	catch(const runtime_error &error)
	{
		DoError(error.what());
	}
	
	return 0;
}