|
|
|
/* $Id$ */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file is part of OpenTTD.
|
|
|
|
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
|
|
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file tcp_content.cpp Basic functions to receive and send Content packets.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "../../stdafx.h"
|
|
|
|
#ifndef OPENTTD_MSU
|
|
|
|
#include "../../textfile_gui.h"
|
|
|
|
#include "../../newgrf_config.h"
|
|
|
|
#include "../../base_media_base.h"
|
|
|
|
#include "../../ai/ai.hpp"
|
|
|
|
#include "../../game/game.hpp"
|
|
|
|
#include "../../fios.h"
|
|
|
|
#endif /* OPENTTD_MSU */
|
|
|
|
#include "tcp_content.h"
|
|
|
|
|
|
|
|
#include "../../safeguards.h"
|
|
|
|
|
|
|
|
/** Clear everything in the struct */
|
|
|
|
ContentInfo::ContentInfo()
|
|
|
|
{
|
|
|
|
memset(this, 0, sizeof(*this));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Free everything allocated */
|
|
|
|
ContentInfo::~ContentInfo()
|
|
|
|
{
|
|
|
|
free(this->dependencies);
|
|
|
|
free(this->tags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy data from other #ContentInfo and take ownership of allocated stuff.
|
|
|
|
* @param other Source to copy from. #dependencies and #tags will be NULLed.
|
|
|
|
*/
|
|
|
|
void ContentInfo::TransferFrom(ContentInfo *other)
|
|
|
|
{
|
|
|
|
if (other != this) {
|
|
|
|
free(this->dependencies);
|
|
|
|
free(this->tags);
|
|
|
|
memcpy(this, other, sizeof(ContentInfo));
|
|
|
|
other->dependencies = NULL;
|
|
|
|
other->tags = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the size of the data as send over the network.
|
|
|
|
* @return the size.
|
|
|
|
*/
|
|
|
|
size_t ContentInfo::Size() const
|
|
|
|
{
|
|
|
|
size_t len = 0;
|
|
|
|
for (uint i = 0; i < this->tag_count; i++) len += strlen(this->tags[i]) + 1;
|
|
|
|
|
|
|
|
/* The size is never larger than the content info size plus the size of the
|
|
|
|
* tags and dependencies */
|
|
|
|
return sizeof(*this) +
|
|
|
|
sizeof(this->dependency_count) +
|
|
|
|
sizeof(*this->dependencies) * this->dependency_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the state either selected or autoselected?
|
|
|
|
* @return true iff that's the case
|
|
|
|
*/
|
|
|
|
bool ContentInfo::IsSelected() const
|
|
|
|
{
|
|
|
|
switch (this->state) {
|
|
|
|
case ContentInfo::SELECTED:
|
|
|
|
case ContentInfo::AUTOSELECTED:
|
|
|
|
case ContentInfo::ALREADY_HERE:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the information from this content info valid?
|
|
|
|
* @return true iff it's valid
|
|
|
|
*/
|
|
|
|
bool ContentInfo::IsValid() const
|
|
|
|
{
|
|
|
|
return this->state < ContentInfo::INVALID && this->type >= CONTENT_TYPE_BEGIN && this->type < CONTENT_TYPE_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef OPENTTD_MSU
|
|
|
|
/**
|
|
|
|
* Search a textfile file next to this file in the content list.
|
|
|
|
* @param type The type of the textfile to search for.
|
|
|
|
* @return The filename for the textfile, \c NULL otherwise.
|
|
|
|
*/
|
|
|
|
const char *ContentInfo::GetTextfile(TextfileType type) const
|
|
|
|
{
|
|
|
|
if (this->state == INVALID) return NULL;
|
|
|
|
const char *tmp;
|
|
|
|
switch (this->type) {
|
|
|
|
default: NOT_REACHED();
|
|
|
|
case CONTENT_TYPE_AI:
|
|
|
|
tmp = AI::GetScannerInfo()->FindMainScript(this, true);
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_AI_LIBRARY:
|
|
|
|
tmp = AI::GetScannerLibrary()->FindMainScript(this, true);
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_GAME:
|
|
|
|
tmp = Game::GetScannerInfo()->FindMainScript(this, true);
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_GAME_LIBRARY:
|
|
|
|
tmp = Game::GetScannerLibrary()->FindMainScript(this, true);
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_NEWGRF: {
|
|
|
|
const GRFConfig *gc = FindGRFConfig(BSWAP32(this->unique_id), FGCM_EXACT, this->md5sum);
|
|
|
|
tmp = gc != NULL ? gc->filename : NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CONTENT_TYPE_BASE_GRAPHICS:
|
|
|
|
tmp = TryGetBaseSetFile(this, true, BaseGraphics::GetAvailableSets());
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_BASE_SOUNDS:
|
|
|
|
tmp = TryGetBaseSetFile(this, true, BaseSounds::GetAvailableSets());
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_BASE_MUSIC:
|
|
|
|
tmp = TryGetBaseSetFile(this, true, BaseMusic::GetAvailableSets());
|
|
|
|
break;
|
|
|
|
case CONTENT_TYPE_SCENARIO:
|
|
|
|
case CONTENT_TYPE_HEIGHTMAP:
|
|
|
|
extern const char *FindScenario(const ContentInfo *ci, bool md5sum);
|
|
|
|
tmp = FindScenario(this, true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (tmp == NULL) return NULL;
|
|
|
|
return ::GetTextfile(type, GetContentInfoSubDir(this->type), tmp);
|
|
|
|
}
|
|
|
|
#endif /* OPENTTD_MSU */
|
|
|
|
|
|
|
|
void NetworkContentSocketHandler::Close()
|
|
|
|
{
|
|
|
|
CloseConnection();
|
|
|
|
if (this->sock == INVALID_SOCKET) return;
|
|
|
|
|
|
|
|
closesocket(this->sock);
|
|
|
|
this->sock = INVALID_SOCKET;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle the given packet, i.e. pass it to the right
|
|
|
|
* parser receive command.
|
|
|
|
* @param p the packet to handle
|
|
|
|
* @return true if we should immediately handle further packets, false otherwise
|
|
|
|
*/
|
|
|
|
bool NetworkContentSocketHandler::HandlePacket(Packet *p)
|
|
|
|
{
|
|
|
|
PacketContentType type = (PacketContentType)p->Recv_uint8();
|
|
|
|
|
|
|
|
switch (this->HasClientQuit() ? PACKET_CONTENT_END : type) {
|
|
|
|
case PACKET_CONTENT_CLIENT_INFO_LIST: return this->Receive_CLIENT_INFO_LIST(p);
|
|
|
|
case PACKET_CONTENT_CLIENT_INFO_ID: return this->Receive_CLIENT_INFO_ID(p);
|
|
|
|
case PACKET_CONTENT_CLIENT_INFO_EXTID: return this->Receive_CLIENT_INFO_EXTID(p);
|
|
|
|
case PACKET_CONTENT_CLIENT_INFO_EXTID_MD5: return this->Receive_CLIENT_INFO_EXTID_MD5(p);
|
|
|
|
case PACKET_CONTENT_SERVER_INFO: return this->Receive_SERVER_INFO(p);
|
|
|
|
case PACKET_CONTENT_CLIENT_CONTENT: return this->Receive_CLIENT_CONTENT(p);
|
|
|
|
case PACKET_CONTENT_SERVER_CONTENT: return this->Receive_SERVER_CONTENT(p);
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (this->HasClientQuit()) {
|
|
|
|
DEBUG(net, 0, "[tcp/content] received invalid packet type %d from %s", type, this->client_addr.GetAddressAsString());
|
|
|
|
} else {
|
|
|
|
DEBUG(net, 0, "[tcp/content] received illegal packet from %s", this->client_addr.GetAddressAsString());
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receive a packet at TCP level
|
|
|
|
* @return Whether at least one packet was received.
|
|
|
|
*/
|
|
|
|
bool NetworkContentSocketHandler::ReceivePackets()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We read only a few of the packets. This as receiving packets can be expensive
|
|
|
|
* due to the re-resolving of the parent/child relations and checking the toggle
|
|
|
|
* state of all bits. We cannot do this all in one go, as we want to show the
|
|
|
|
* user what we already received. Otherwise, it can take very long before any
|
|
|
|
* progress is shown to the end user that something has been received.
|
|
|
|
* It is also the case that we request extra content from the content server in
|
|
|
|
* case there is an unknown (in the content list) piece of content. These will
|
|
|
|
* come in after the main lists have been requested. As a result, we won't be
|
|
|
|
* getting everything reliably in one batch. Thus, we need to make subsequent
|
|
|
|
* updates in that case as well.
|
|
|
|
*
|
|
|
|
* As a result, we simple handle an arbitrary number of packets in one cycle,
|
|
|
|
* and let the rest be handled in subsequent cycles. These are ran, almost,
|
|
|
|
* immediately after this cycle so in speed it does not matter much, except
|
|
|
|
* that the user inferface will appear better responding.
|
|
|
|
*
|
|
|
|
* What arbitrary number to choose is the ultimate question though.
|
|
|
|
*/
|
|
|
|
Packet *p;
|
|
|
|
static const int MAX_PACKETS_TO_RECEIVE = 42;
|
|
|
|
int i = MAX_PACKETS_TO_RECEIVE;
|
|
|
|
while (--i != 0 && (p = this->ReceivePacket()) != NULL) {
|
|
|
|
bool cont = this->HandlePacket(p);
|
|
|
|
delete p;
|
|
|
|
if (!cont) return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return i != MAX_PACKETS_TO_RECEIVE - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper for logging receiving invalid packets.
|
|
|
|
* @param type The received packet type.
|
|
|
|
* @return Always false, as it's an error.
|
|
|
|
*/
|
|
|
|
bool NetworkContentSocketHandler::ReceiveInvalidPacket(PacketContentType type)
|
|
|
|
{
|
|
|
|
DEBUG(net, 0, "[tcp/content] received illegal packet type %d from %s", type, this->client_addr.GetAddressAsString());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NetworkContentSocketHandler::Receive_CLIENT_INFO_LIST(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_INFO_LIST); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_CLIENT_INFO_ID(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_INFO_ID); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_CLIENT_INFO_EXTID(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_INFO_EXTID); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_CLIENT_INFO_EXTID_MD5(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_INFO_EXTID_MD5); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_SERVER_INFO); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_CLIENT_CONTENT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_CONTENT); }
|
|
|
|
bool NetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_SERVER_CONTENT); }
|
|
|
|
|
|
|
|
#ifndef OPENTTD_MSU
|
|
|
|
/**
|
|
|
|
* Helper to get the subdirectory a #ContentInfo is located in.
|
|
|
|
* @param type The type of content.
|
|
|
|
* @return The subdirectory the content is located in.
|
|
|
|
*/
|
|
|
|
Subdirectory GetContentInfoSubDir(ContentType type)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
default: return NO_DIRECTORY;
|
|
|
|
case CONTENT_TYPE_AI: return AI_DIR;
|
|
|
|
case CONTENT_TYPE_AI_LIBRARY: return AI_LIBRARY_DIR;
|
|
|
|
case CONTENT_TYPE_GAME: return GAME_DIR;
|
|
|
|
case CONTENT_TYPE_GAME_LIBRARY: return GAME_LIBRARY_DIR;
|
|
|
|
case CONTENT_TYPE_NEWGRF: return NEWGRF_DIR;
|
|
|
|
|
|
|
|
case CONTENT_TYPE_BASE_GRAPHICS:
|
|
|
|
case CONTENT_TYPE_BASE_SOUNDS:
|
|
|
|
case CONTENT_TYPE_BASE_MUSIC:
|
|
|
|
return BASESET_DIR;
|
|
|
|
|
|
|
|
case CONTENT_TYPE_SCENARIO: return SCENARIO_DIR;
|
|
|
|
case CONTENT_TYPE_HEIGHTMAP: return HEIGHTMAP_DIR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* OPENTTD_MSU */
|