(svn r15126) -Feature: downloading content from a central server (content.openttd.org) where authors can upload they NewGRFS/AI etc. This should make joining servers that use only NewGRFs that are distributed via this system easier as the players can download the NewGRFs from in the game. It should also make it easier to see whether there are updates for NewGRFs and make the necessary updates.

This commit is contained in:
rubidium 2009-01-17 16:53:32 +00:00
parent 2850bf9e00
commit 3a13b75e37
23 changed files with 2150 additions and 20 deletions

View File

@ -631,6 +631,10 @@
RelativePath=".\..\src\network\network_command.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_content.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_gamelist.cpp"
>
@ -1123,6 +1127,10 @@
RelativePath=".\..\src\network\network_client.h"
>
</File>
<File
RelativePath=".\..\src\network\network_content.h"
>
</File>
<File
RelativePath=".\..\src\network\network_func.h"
>
@ -1779,6 +1787,10 @@
RelativePath=".\..\src\network\network_chat_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_content_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_gui.cpp"
>
@ -3231,6 +3243,14 @@
RelativePath=".\..\src\network\core\tcp.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_content.cpp"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_content.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_game.cpp"
>

View File

@ -628,6 +628,10 @@
RelativePath=".\..\src\network\network_command.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_content.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_gamelist.cpp"
>
@ -1120,6 +1124,10 @@
RelativePath=".\..\src\network\network_client.h"
>
</File>
<File
RelativePath=".\..\src\network\network_content.h"
>
</File>
<File
RelativePath=".\..\src\network\network_func.h"
>
@ -1776,6 +1784,10 @@
RelativePath=".\..\src\network\network_chat_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_content_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\network\network_gui.cpp"
>
@ -3228,6 +3240,14 @@
RelativePath=".\..\src\network\core\tcp.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_content.cpp"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_content.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_game.cpp"
>

View File

@ -45,6 +45,7 @@ namegen.cpp
network/network.cpp
network/network_client.cpp
network/network_command.cpp
network/network_content.cpp
network/network_gamelist.cpp
network/network_server.cpp
network/network_udp.cpp
@ -211,6 +212,7 @@ namegen_func.h
network/network.h
network/network_base.h
network/network_client.h
network/network_content.h
network/network_func.h
network/network_gamelist.h
network/network_gui.h
@ -391,6 +393,7 @@ main_gui.cpp
misc_gui.cpp
music_gui.cpp
network/network_chat_gui.cpp
network/network_content_gui.cpp
network/network_gui.cpp
newgrf_gui.cpp
news_gui.cpp
@ -776,6 +779,8 @@ network/core/packet.cpp
network/core/packet.h
network/core/tcp.cpp
network/core/tcp.h
network/core/tcp_content.cpp
network/core/tcp_content.h
network/core/tcp_game.cpp
network/core/tcp_game.h
network/core/udp.cpp

View File

@ -111,7 +111,9 @@ public:
static AIInfo *FindInfo(const char *name, int version);
static bool ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm);
static void Rescan();
#if defined(ENABLE_NETWORK)
static bool HasAI(const struct ContentInfo *ci, bool md5sum);
#endif
private:
static uint frame_counter;
static class AIScanner *ai_scanner;

View File

@ -416,3 +416,141 @@ char *AIScanner::GetAIConsoleList(char *p, const char *last)
return p;
}
#if defined(ENABLE_NETWORK)
#include "../network/network_content.h"
#include "../md5.h"
#include "../tar_type.h"
/** Helper for creating a MD5sum of all files within of an AI. */
struct AIFileChecksumCreator : FileScanner {
byte md5sum[16]; ///< The final md5sum
/**
* Initialise the md5sum to be all zeroes,
* so we can easily xor the data.
*/
AIFileChecksumCreator()
{
memset(this->md5sum, 0, sizeof(this->md5sum));
}
/* Add the file and calculate the md5 sum. */
virtual bool AddFile(const char *filename, size_t basepath_length)
{
Md5 checksum;
uint8 buffer[1024];
size_t len, size;
byte tmp_md5sum[16];
/* Open the file ... */
FILE *f = FioFOpenFile(filename, "rb", DATA_DIR, &size);
if (f == NULL) return false;
/* ... calculate md5sum... */
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
size -= len;
checksum.Append(buffer, len);
}
checksum.Finish(tmp_md5sum);
FioFCloseFile(f);
/* ... and xor it to the overall md5sum. */
for (uint i = 0; i < sizeof(md5sum); i++) this->md5sum[i] ^= tmp_md5sum[i];
return true;
}
};
/**
* Check whether the AI given in info is the same as in ci based
* on the shortname and md5 sum.
* @param ci the information to compare to
* @param md5sum whether to check the MD5 checksum
* @param info the AI to get the shortname and md5 sum from
* @return true iff they're the same
*/
static bool IsSameAI(const ContentInfo *ci, bool md5sum, AIFileInfo *info)
{
uint32 id = 0;
const char *str = info->GetShortName();
for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
if (id != ci->unique_id) return false;
if (!md5sum) return true;
AIFileChecksumCreator checksum;
char path[MAX_PATH];
strecpy(path, info->GetMainScript(), lastof(path));
/* There'll always be at least 2 path separator characters in an AI's
* main script name as the search algorithm requires the main script to
* be in a subdirectory of the AI directory; so ai/<path>/main.nut. */
*strrchr(path, PATHSEPCHAR) = '\0';
*strrchr(path, PATHSEPCHAR) = '\0';
TarList::iterator iter = _tar_list.find(path);
if (iter != _tar_list.end()) {
/* The main script is in a tar file, so find all files that
* are in the same tar and add them to the MD5 checksumming. */
TarFileList::iterator tar;
FOR_ALL_TARS(tar) {
/* Not in the same tar. */
if (tar->second.tar_filename != iter->first) continue;
/* Check the extension. */
const char *ext = strrchr(tar->first.c_str(), '.');
if (ext == NULL || strcasecmp(ext, ".nut") != 0) continue;
/* Create the full path name, */
seprintf(path, lastof(path), "%s%c%s", tar->second.tar_filename, PATHSEPCHAR, tar->first.c_str());
checksum.AddFile(path, 0);
}
} else {
/* Add the path sep char back when searching a directory, so we are
* in the actual directory. */
path[strlen(path)] = PATHSEPCHAR;
checksum.Scan(".nut", path);
}
return memcmp(ci->md5sum, checksum.md5sum, sizeof(ci->md5sum)) == 0;
}
/**
* Check whether we have an AI (library) with the exact characteristics as ci.
* @param ci the characteristics to search on (shortname and md5sum)
* @param md5sum whether to check the MD5 checksum
* @return true iff we have an AI (library) matching.
*/
bool AIScanner::HasAI(const ContentInfo *ci, bool md5sum)
{
switch (ci->type) {
case CONTENT_TYPE_AI:
for (AIInfoList::iterator it = this->info_list.begin(); it != this->info_list.end(); it++) {
if (IsSameAI(ci, md5sum, (*it).second)) return true;
}
return false;
case CONTENT_TYPE_AI_LIBRARY:
for (AILibraryList::iterator it = this->library_list.begin(); it != this->library_list.end(); it++) {
if (IsSameAI(ci, md5sum, (*it).second)) return true;
}
return false;
default:
NOT_REACHED();
}
}
/**
* Check whether we have an AI (library) with the exact characteristics as ci.
* @param ci the characteristics to search on (shortname and md5sum)
* @param md5sum whether to check the MD5 checksum
* @return true iff we have an AI (library) matching.
*/
/*static */ bool AI::HasAI(const ContentInfo *ci, bool md5sum)
{
return AI::ai_scanner->HasAI(ci, md5sum);
}
#endif /* ENABLE_NETWORK */

View File

@ -64,6 +64,9 @@ public:
*/
void RescanAIDir();
#if defined(ENABLE_NETWORK)
bool HasAI(const struct ContentInfo *ci, bool md5sum);
#endif
private:
typedef std::map<const char *, class AILibrary *, ltstr> AILibraryList;

View File

@ -119,6 +119,17 @@ public:
return this->Find(item) != this->End();
}
/** Removes given item from this map
* @param item item to remove
* @return true iff key was found
* @note it has to be pointer to item in this map. It is overwritten by the last item.
*/
FORCEINLINE void Erase(T *item)
{
assert(item >= this->Begin() && item < this->End());
*item = this->data[--this->items];
}
/**
* Tests whether a item is present in the vector, and appends it to the end if not.
* The '!=' operator of T is used for comparison.

View File

@ -518,7 +518,7 @@ static void SimplifyFileName(char *name)
#endif
}
static bool TarListAddFile(const char *filename)
bool TarListAddFile(const char *filename)
{
/* The TAR-header, repeated for every file */
typedef struct TarHeader {
@ -951,6 +951,27 @@ void DeterminePaths(const char *exe)
free(save_dir);
free(autosave_dir);
/* If we have network we make a directory for the autodownloading of content */
_searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
#ifdef ENABLE_NETWORK
FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
/* Create the directory for each of the types of content */
const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, DATA_DIR, AI_DIR, AI_LIBRARY_DIR };
for (uint i = 0; i < lengthof(dirs); i++) {
char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], FioGetSubdirectory(dirs[i]));
FioCreateDirectory(tmp);
free(tmp);
}
#else /* ENABLE_NETWORK */
/* If we don't have networking, we don't need to make the directory. But
* if it exists we keep it, otherwise remove it from the search paths. */
if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
free((void*)_searchpaths[SP_AUTODOWNLOAD_DIR]);
_searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
}
#endif /* ENABLE_NETWORK */
ScanForTarFiles();
}
@ -1097,3 +1118,18 @@ uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars)
return num;
}
/**
* Scan for files with the given extention in the given search path.
* @param extension the extension of files to search for.
* @param directory the sub directory to search in.
* @return the number of found files, i.e. the number of times that
* AddFile returned true.
*/
uint FileScanner::Scan(const char *extension, const char *directory)
{
char path[MAX_PATH];
strecpy(path, directory, lastof(path));
AppendPathSeparator(path, lengthof(path));
return ScanPath(this, extension, path, strlen(path));
}

View File

@ -74,6 +74,7 @@ public:
virtual ~FileScanner() {}
uint Scan(const char *extension, Subdirectory sd, bool tars = true);
uint Scan(const char *extension, const char *directory);
/**
* Add a file with the given filename.

View File

@ -36,6 +36,7 @@ enum Searchpath {
SP_BINARY_DIR, ///< Search in the directory where the binary resides
SP_INSTALLATION_DIR, ///< Search in the installation directory
SP_APPLICATION_BUNDLE_DIR, ///< Search within the application bundle
SP_AUTODOWNLOAD_DIR, ///< Search within the autodownload directory
NUM_SEARCHPATHS
};

View File

@ -77,7 +77,7 @@ void UndrawMouseCursor();
enum {
/* Size of the buffer used for drawing strings. */
DRAW_STRING_BUFFER = 1024,
DRAW_STRING_BUFFER = 2048,
};
void RedrawScreenRect(int left, int top, int right, int bottom);

View File

@ -543,3 +543,36 @@ char *GetGraphicsSetsList(char *p, const char *last)
return p;
}
#if defined(ENABLE_NETWORK)
#include "network/network_content.h"
/**
* Check whether we have an graphics with the exact characteristics as ci.
* @param ci the characteristics to search on (shortname and md5sum)
* @param md5sum whether to check the MD5 checksum
* @return true iff we have an graphics set matching.
*/
bool HasGraphicsSet(const ContentInfo *ci, bool md5sum)
{
assert(ci->type == CONTENT_TYPE_BASE_GRAPHICS);
for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
if (g->found_grfs <= 1) continue;
if (g->shortname != ci->unique_id) continue;
if (!md5sum) return true;
byte md5[16];
memset(md5, 0, sizeof(md5));
for (uint i = 0; i < MAX_GFT; i++) {
for (uint j = 0; j < sizeof(md5); j++) {
md5[j] ^= g->files[i].hash[j];
}
}
if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return true;
}
return false;
}
#endif /* ENABLE_NETWORK */

View File

@ -12,6 +12,7 @@
#include "heightmap.h"
#include "genworld.h"
#include "network/network_gui.h"
#include "network/network_content.h"
#include "newgrf.h"
#include "strings_func.h"
#include "window_func.h"
@ -24,8 +25,8 @@
#include "table/sprites.h"
static const Widget _select_game_widgets[] = {
{ WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN, 0, 335, 0, 13, STR_0307_OPENTTD, STR_NULL},
{ WWT_PANEL, RESIZE_NONE, COLOUR_BROWN, 0, 335, 14, 194, 0x0, STR_NULL},
{ WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN, 0, 335, 0, 13, STR_0307_OPENTTD, STR_NULL},
{ WWT_PANEL, RESIZE_NONE, COLOUR_BROWN, 0, 335, 14, 194, 0x0, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 22, 33, STR_0140_NEW_GAME, STR_02FB_START_A_NEW_GAME},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 22, 33, STR_0141_LOAD_GAME, STR_02FC_LOAD_A_SAVED_GAME},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 40, 51, STR_029A_PLAY_SCENARIO, STR_0303_START_A_NEW_GAME_USING},
@ -43,7 +44,8 @@ static const Widget _select_game_widgets[] = {
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 157, 168, STR_CONFIG_PATCHES, STR_CONFIG_PATCHES_TIP},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 157, 168, STR_NEWGRF_SETTINGS_BUTTON, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 104, 231, 175, 186, STR_0304_QUIT, STR_0305_QUIT_OPENTTD},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 175, 186, STR_CONTENT_INTRO_BUTTON, STR_CONTENT_INTRO_BUTTON_TIP},
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 175, 186, STR_0304_QUIT, STR_0305_QUIT_OPENTTD},
{ WIDGETS_END},
};
@ -70,6 +72,7 @@ private:
SGI_DIFFICULTIES,
SGI_PATCHES_OPTIONS,
SGI_GRF_SETTINGS,
SGI_CONTENT_DOWNLOAD,
SGI_EXIT,
};
@ -123,6 +126,14 @@ public:
case SGI_DIFFICULTIES: ShowGameDifficulty(); break;
case SGI_PATCHES_OPTIONS: ShowPatchesSelection(); break;
case SGI_GRF_SETTINGS: ShowNewGRFSettings(true, true, false, &_grfconfig_newgame); break;
case SGI_CONTENT_DOWNLOAD:
if (!_network_available) {
ShowErrorMessage(INVALID_STRING_ID, STR_NETWORK_ERR_NOTAVAILABLE, 0, 0);
} else {
ShowNetworkContentListWindow();
}
break;
case SGI_EXIT: HandleExitGameRequest(); break;
}
}

View File

@ -3687,3 +3687,60 @@ STR_CONFIG_PATCHES_NOISE_LEVEL :{LTBLUE}Allow t
STR_NOISE_IN_TOWN :{BLACK}Noise limit in town: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_STATION_NOISE :{BLACK}Noise generated: {GOLD}{COMMA}
########
############ Downloading of content from the central server
STR_CONTENT_NO_ZLIB :{WHITE}OpenTTD is build without "zlib" support...
STR_CONTENT_NO_ZLIB_SUB :{WHITE}... downloading content is not possible!
STR_CONTENT_TYPE_BASE_GRAPHICS :Base graphics
STR_CONTENT_TYPE_NEWGRF :NewGRF
STR_CONTENT_TYPE_AI :AI
STR_CONTENT_TYPE_AI_LIBRARY :AI library
STR_CONTENT_TYPE_SCENARIO :Scenario
STR_CONTENT_TYPE_HEIGHTMAP :Heightmap
STR_CONTENT_TITLE :{WHITE}Content downloading
STR_CONTENT_TYPE_CAPTION :{BLACK}Type
STR_CONTENT_TYPE_CAPTION_TIP :{BLACK}Type of the content
STR_CONTENT_NAME_CAPTION :{BLACK}Name
STR_CONTENT_NAME_CAPTION_TIP :{BLACK}Name of the content
STR_CONTENT_MATRIX_TIP :{BLACK}Click on a line to see the details{}Click on the checkbox to select it for downloading
STR_CONTENT_SELECT_ALL_CAPTION :{BLACK}Select all
STR_CONTENT_SELECT_ALL_CAPTION_TIP :{BLACK}Mark all content to be downloaded
STR_CONTENT_SELECT_UPDATES_CAPTION :{BLACK}Select updates
STR_CONTENT_SELECT_UPDATES_CAPTION_TIP :{BLACK}Mark all content that is an update for existing content to be downloaded
STR_CONTENT_UNSELECT_ALL_CAPTION :{BLACK}Unselect all
STR_CONTENT_UNSELECT_ALL_CAPTION_TIP :{BLACK}Mark all content to be not downloaded
STR_CONTENT_DOWNLOAD_CAPTION :{BLACK}Download
STR_CONTENT_DOWNLOAD_CAPTION_TIP :{BLACK}Start downloading the selected content
STR_CONTENT_TOTAL_DOWNLOAD_SIZE :{SILVER}Total download size: {WHITE}{BYTES}
STR_CONTENT_DETAIL_TITLE :{SILVER}CONTENT INFO
STR_CONTENT_DETAIL_SUBTITLE_UNSELECTED :{SILVER}You have not selected this to be downloaded
STR_CONTENT_DETAIL_SUBTITLE_SELECTED :{SILVER}You have selected this to be downloaded
STR_CONTENT_DETAIL_SUBTITLE_AUTOSELECTED :{SILVER}This dependency has been selected to be downloaded
STR_CONTENT_DETAIL_SUBTITLE_ALREADY_HERE :{SILVER}You already have this
STR_CONTENT_DETAIL_SUBTITLE_DOES_NOT_EXIST :{SILVER}This content is unknown and cannot be downloaded in OpenTTD
STR_CONTENT_DETAIL_UPDATE :{SILVER}This is a replacement for an exising {STRING}
STR_CONTENT_DETAIL_NAME :{SILVER}Name: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_VERSION :{SILVER}Version: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_DESCRIPTION :{SILVER}Description: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_URL :{SILVER}URL: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_TYPE :{SILVER}Type: {WHITE}{STRING}
STR_CONTENT_DETAIL_FILESIZE :{SILVER}Download size: {WHITE}{BYTES}
STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF :{SILVER}Selected because of: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_DEPENDENCIES :{SILVER}Dependencies: {WHITE}{RAW_STRING}
STR_CONTENT_DETAIL_TAGS :{SILVER}Tags: {WHITE}{RAW_STRING}
STR_CONTENT_DOWNLOAD_TITLE :{WHITE}Downloading content...
STR_CONTENT_DOWNLOAD_INITIALISE :{WHITE}Requesting files...
STR_CONTENT_DOWNLOAD_FILE :{WHITE}Currently downloading {RAW_STRING} ({NUM} of {NUM})
STR_CONTENT_DOWNLOAD_COMPLETE :{WHITE}Download complete
STR_CONTENT_DOWNLOAD_PROGRESS_SIZE :{WHITE}{BYTES} of {BYTES} downloaded ({NUM} %)
STR_CONTENT_ERROR_COULD_NOT_CONNECT :{WHITE}Could not connect to the content server...
STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD :{WHITE}Downloading failed...
STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_CONNECTION_LOST :{WHITE}... connection lost
STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE :{WHITE}... file not writable
STR_CONTENT_ERROR_COULD_NOT_EXTRACT :{WHITE}Could not decompress the downloaded file
STR_CONTENT_INTRO_BUTTON :{BLACK}Check online content
STR_CONTENT_INTRO_BUTTON_TIP :{BLACK}Check for new and updated content to download
########

View File

@ -9,11 +9,14 @@
/** DNS hostname of the masterserver */
#define NETWORK_MASTER_SERVER_HOST "master.openttd.org"
/** DNS hostname of the content server */
#define NETWORK_CONTENT_SERVER_HOST "content.openttd.org"
/** Message sent to the masterserver to 'identify' this client as OpenTTD */
#define NETWORK_MASTER_SERVER_WELCOME_MESSAGE "OpenTTDRegister"
enum {
NETWORK_MASTER_SERVER_PORT = 3978, ///< The default port of the master server (UDP)
NETWORK_CONTENT_SERVER_PORT = 3978, ///< The default port of the content server (TCP)
NETWORK_DEFAULT_PORT = 3979, ///< The default port of the game server (TCP & UDP)
NETWORK_DEFAULT_DEBUGLOG_PORT = 3982, ///< The default port debug-log is sent too (TCP)

View File

@ -0,0 +1,136 @@
/* $Id$ */
/**
* @file tcp_content.cpp Basic functions to receive and send Content packets.
*/
#ifdef ENABLE_NETWORK
#include "../../stdafx.h"
#include "../../debug.h"
#include "tcp_content.h"
ContentInfo::ContentInfo()
{
memset(this, 0, sizeof(*this));
}
ContentInfo::~ContentInfo()
{
free(this->dependencies);
free(this->tags);
}
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;
}
bool ContentInfo::IsSelected() const
{
switch (this->state) {
case ContentInfo::SELECTED:
case ContentInfo::AUTOSELECTED:
return true;
default:
return false;
}
}
bool ContentInfo::IsValid() const
{
return this->state < ContentInfo::INVALID && this->type >= CONTENT_TYPE_BEGIN && this->type < CONTENT_TYPE_END;
}
NetworkRecvStatus NetworkContentSocketHandler::CloseConnection()
{
this->has_quit = true;
return NETWORK_RECV_STATUS_OKAY;
}
void NetworkContentSocketHandler::Close()
{
CloseConnection();
if (this->sock == INVALID_SOCKET) return;
closesocket(this->sock);
this->sock = INVALID_SOCKET;
}
/**
* Defines a simple (switch) case for each network packet
* @param type the packet type to create the case for
*/
#define CONTENT_COMMAND(type) case type: return this->NetworkPacketReceive_ ## type ## _command(p); break;
/**
* Handle an incoming packets by sending it to the correct function.
* @param p the received packet
*/
bool NetworkContentSocketHandler::HandlePacket(Packet *p)
{
PacketContentType type = (PacketContentType)p->Recv_uint8();
switch (this->HasClientQuit() ? PACKET_CONTENT_END : type) {
CONTENT_COMMAND(PACKET_CONTENT_CLIENT_INFO_LIST);
CONTENT_COMMAND(PACKET_CONTENT_CLIENT_INFO_ID);
CONTENT_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID);
CONTENT_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID_MD5);
CONTENT_COMMAND(PACKET_CONTENT_SERVER_INFO);
CONTENT_COMMAND(PACKET_CONTENT_CLIENT_CONTENT);
CONTENT_COMMAND(PACKET_CONTENT_SERVER_CONTENT);
default:
if (this->HasClientQuit()) {
DEBUG(net, 0, "[tcp/content] received invalid packet type %d from %s:%d", type, inet_ntoa(this->client_addr.sin_addr), ntohs(this->client_addr.sin_port));
} else {
DEBUG(net, 0, "[tcp/content] received illegal packet from %s:%d", inet_ntoa(this->client_addr.sin_addr), ntohs(this->client_addr.sin_port));
}
return false;
}
}
/**
* Receive a packet at UDP level
*/
void NetworkContentSocketHandler::Recv_Packets()
{
Packet *p;
NetworkRecvStatus res;
while ((p = this->Recv_Packet(&res)) != NULL) {
bool cont = HandlePacket(p);
delete p;
if (!cont) return;
}
}
/**
* Create stub implementations for all receive commands that only
* show a warning that the given command is not available for the
* socket where the packet came from.
* @param type the packet type to create the stub for
*/
#define DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(type) \
bool NetworkContentSocketHandler::NetworkPacketReceive_## type ##_command(Packet *p) { \
DEBUG(net, 0, "[tcp/content] received illegal packet type %d from %s:%d", \
type, inet_ntoa(this->client_addr.sin_addr), ntohs(this->client_addr.sin_port)); \
return false; \
}
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_LIST);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_ID);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID_MD5);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_INFO);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_CONTENT);
DEFINE_UNAVAILABLE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_CONTENT);
#endif /* ENABLE_NETWORK */

View File

@ -0,0 +1,208 @@
/* $Id$ */
/**
* @file tcp_content.h Basic functions to receive and send TCP packets to/from the content server.
*/
#ifndef NETWORK_CORE_CONTENT_H
#define NETWORK_CORE_CONTENT_H
#ifdef ENABLE_NETWORK
#include "os_abstraction.h"
#include "tcp.h"
#include "packet.h"
#include "../../debug.h"
/** The values in the enum are important; they are used as database 'keys' */
enum ContentType {
CONTENT_TYPE_BEGIN = 1, ///< Helper to mark the begin of the types
CONTENT_TYPE_BASE_GRAPHICS = 1, ///< The content consists of base graphics
CONTENT_TYPE_NEWGRF = 2, ///< The content consists of a NewGRF
CONTENT_TYPE_AI = 3, ///< The content consists of an AI
CONTENT_TYPE_AI_LIBRARY = 4, ///< The content consists of an AI library
CONTENT_TYPE_SCENARIO = 5, ///< The content consists of a scenario
CONTENT_TYPE_HEIGHTMAP = 6, ///< The content consists of a heightmap
CONTENT_TYPE_END, ///< Helper to mark the end of the types
};
/** Enum with all types of TCP content packets. The order MUST not be changed **/
enum PacketContentType {
PACKET_CONTENT_CLIENT_INFO_LIST, ///< Queries the content server for a list of info of a given content type
PACKET_CONTENT_CLIENT_INFO_ID, ///< Queries the content server for information about a list of internal IDs
PACKET_CONTENT_CLIENT_INFO_EXTID, ///< Queries the content server for information about a list of external IDs
PACKET_CONTENT_CLIENT_INFO_EXTID_MD5, ///< Queries the content server for information about a list of external IDs and MD5
PACKET_CONTENT_SERVER_INFO, ///< Reply of content server with information about content
PACKET_CONTENT_CLIENT_CONTENT, ///< Request a content file given an internal ID
PACKET_CONTENT_SERVER_CONTENT, ///< Reply with the content of the given ID
PACKET_CONTENT_END ///< Must ALWAYS be on the end of this list!! (period)
};
#define DECLARE_CONTENT_RECEIVE_COMMAND(type) virtual bool NetworkPacketReceive_## type ##_command(Packet *p)
#define DEF_CONTENT_RECEIVE_COMMAND(cls, type) bool cls ##NetworkContentSocketHandler::NetworkPacketReceive_ ## type ## _command(Packet *p)
enum ContentID {
INVALID_CONTENT_ID = UINT32_MAX
};
/** Container for all important information about a piece of content. */
struct ContentInfo {
enum State {
UNSELECTED, ///< The content has not been selected
SELECTED, ///< The content has been manually selected
AUTOSELECTED, ///< The content has been selected as dependency
ALREADY_HERE, ///< The content is already at the client side
DOES_NOT_EXIST, ///< The content does not exist in the content system
INVALID ///< The content's invalid
};
ContentType type; ///< Type of content
ContentID id; ///< Unique (server side) ID for the content
uint32 filesize; ///< Size of the file
char filename[48]; ///< Filename (for the .tar.gz; only valid on download)
char name[32]; ///< Name of the content
char version[16]; ///< Version of the content
char url[64]; ///< URL related to the content
char description[512]; ///< Description of the content
uint32 unique_id; ///< Unique ID; either GRF ID or shortname
byte md5sum[16]; ///< The MD5 checksum
uint8 dependency_count; ///< Number of dependencies
ContentID *dependencies; ///< Malloced array of dependencies (unique server side ids)
uint8 tag_count; ///< Number of tags
char (*tags)[32]; ///< Malloced array of tags (strings)
State state; ///< Whether the content info is selected (for download)
bool update; ///< This item is an update
/** Clear everything in the struct */
ContentInfo();
/** Free everything allocated */
~ContentInfo();
/**
* Get the size of the data as send over the network.
* @return the size.
*/
size_t Size() const;
/**
* Is the state either selected or autoselected?
* @return true iff that's the case
*/
bool IsSelected() const;
/**
* Is the information from this content info valid?
* @return true iff it's valid
*/
bool IsValid() const;
};
/** Base socket handler for all Content TCP sockets */
class NetworkContentSocketHandler : public NetworkTCPSocketHandler {
protected:
struct sockaddr_in client_addr; ///< The address we're connected to.
NetworkRecvStatus CloseConnection();
void Close();
/**
* Client requesting a list of content info:
* byte type
* uint32 openttd version
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_LIST);
/**
* Client requesting a list of content info:
* uint16 count of ids
* uint32 id (count times)
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_ID);
/**
* Client requesting a list of content info based on an external
* 'unique' id; GRF ID for NewGRFS, shortname and for base
* graphics and AIs.
* Scenarios and AI libraries are not supported
* uint8 count of requests
* for each request:
* uint8 type
* unique id (uint32)
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID);
/**
* Client requesting a list of content info based on an external
* 'unique' id; GRF ID + MD5 checksum for NewGRFS, shortname and
* xor-ed MD5 checsums for base graphics and AIs.
* Scenarios and AI libraries are not supported
* uint8 count of requests
* for each request:
* uint8 type
* unique id (uint32)
* md5 (16 bytes)
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_INFO_EXTID_MD5);
/**
* Server sending list of content info:
* byte type (invalid ID == does not exist)
* uint32 id
* uint32 file_size
* string name (max 32 characters)
* string version (max 16 characters)
* uint32 unique id
* uint8 md5sum (16 bytes)
* uint8 dependency count
* uint32 unique id of dependency (dependency count times)
* uint8 tag count
* string tag (max 32 characters for tag count times)
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_INFO);
/**
* Client requesting the actual content:
* uint16 count of unique ids
* uint32 unique id (count times)
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_CLIENT_CONTENT);
/**
* Server sending list of content info:
* uint32 unique id
* uint32 file size (0 == does not exist)
* string file name (max 48 characters)
* After this initial packet, packets with the actual data are send using
* the same packet type.
*/
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_CONTENT);
/**
* 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 immediatelly handle further packets, false otherwise
*/
bool HandlePacket(Packet *p);
public:
/**
* Create a new cs socket handler for a given cs
* @param s the socket we are connected with
* @param sin IP etc. of the client
*/
NetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin) :
NetworkTCPSocketHandler(s),
client_addr(*sin)
{
}
/** On destructing of this class, the socket needs to be closed */
virtual ~NetworkContentSocketHandler() { this->Close(); }
/** Do the actual receiving of packets. */
void Recv_Packets();
};
#endif /* ENABLE_NETWORK */
#endif /* NETWORK_CORE_CONTENT_H */

View File

@ -18,6 +18,7 @@
#include "network_internal.h"
#include "network_client.h"
#include "network_server.h"
#include "network_content.h"
#include "network_udp.h"
#include "network_gamelist.h"
#include "core/udp.h"
@ -964,6 +965,8 @@ static bool NetworkDoClientLoop()
// We have to do some UDP checking
void NetworkUDPGameLoop()
{
NetworkContentLoop();
if (_network_udp_server) {
_udp_server_socket->ReceivePackets();
_udp_master_socket->ReceivePackets();

View File

@ -0,0 +1,445 @@
/* $Id$ */
/** @file network_content.cpp Content sending/receiving part of the network protocol. */
#if defined(ENABLE_NETWORK)
#include "../stdafx.h"
#include "../debug.h"
#include "../rev.h"
#include "../core/alloc_func.hpp"
#include "../core/math_func.hpp"
#include "../newgrf_config.h"
#include "../fileio_func.h"
#include "../string_func.h"
#include "../ai/ai.hpp"
#include "../window_func.h"
#include "../gui.h"
#include "core/host.h"
#include "network_content.h"
#include "table/strings.h"
#if defined(WITH_ZLIB)
#include <zlib.h>
#endif
extern bool TarListAddFile(const char *filename);
extern bool HasGraphicsSet(const ContentInfo *ci, bool md5sum);
static ClientNetworkContentSocketHandler *_network_content_client;
/** Wrapper function for the HasProc */
static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
{
return FindGRFConfig(BSWAP32(ci->unique_id), md5sum ? ci->md5sum : NULL) != NULL;
}
/**
* Check whether a function piece of content is locally known.
* Matches on the unique ID and possibly the MD5 checksum.
* @param ci the content info to search for
* @param md5sum also match the MD5 checksum?
* @return true iff it's known
*/
typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_INFO)
{
ContentInfo *ci = new ContentInfo();
ci->type = (ContentType)p->Recv_uint8();
ci->id = (ContentID)p->Recv_uint32();
ci->filesize = p->Recv_uint32();
p->Recv_string(ci->name, lengthof(ci->name));
p->Recv_string(ci->version, lengthof(ci->name));
p->Recv_string(ci->url, lengthof(ci->url));
p->Recv_string(ci->description, lengthof(ci->description));
ci->unique_id = p->Recv_uint32();
for (uint j = 0; j < sizeof(ci->md5sum); j++) {
ci->md5sum[j] = p->Recv_uint8();
}
ci->dependency_count = p->Recv_uint8();
ci->dependencies = MallocT<ContentID>(ci->dependency_count);
for (uint i = 0; i < ci->dependency_count; i++) ci->dependencies[i] = (ContentID)p->Recv_uint32();
ci->tag_count = p->Recv_uint8();
ci->tags = MallocT<char[32]>(ci->tag_count);
for (uint i = 0; i < ci->tag_count; i++) p->Recv_string(ci->tags[i], lengthof(*ci->tags));
if (!ci->IsValid()) {
delete ci;
this->Close();
return false;
}
/* Find the appropriate check function */
HasProc proc = NULL;
switch (ci->type) {
case CONTENT_TYPE_NEWGRF:
proc = HasGRFConfig;
break;
case CONTENT_TYPE_BASE_GRAPHICS:
proc = HasGraphicsSet;
break;
case CONTENT_TYPE_AI:
case CONTENT_TYPE_AI_LIBRARY:
proc = AI::HasAI; break;
break;
default:
break;
}
if (proc != NULL) {
if (proc(ci, true)) {
ci->state = ContentInfo::ALREADY_HERE;
} else {
ci->state = ContentInfo::UNSELECTED;
if (proc(ci, false)) ci->update = true;
}
} else {
ci->state = ContentInfo::UNSELECTED;
}
/* Something we don't have and has filesize 0 does not exist in te system */
if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST;
for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) {
(*iter)->OnReceiveContentInfo(ci);
}
return true;
}
void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
{
Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST);
p->Send_uint8 ((byte)type);
p->Send_uint32(_openttd_newgrf_version);
this->Send_Packet(p);
}
void ClientNetworkContentSocketHandler::RequestContentList(uint count, const ContentID *content_ids)
{
while (count > 0) {
/* We can "only" send a limited number of IDs in a single packet.
* A packet begins with the packet size and a byte for the type.
* Then this packet adds a byte for the content type and a uint16
* for the count in this packet. The rest of the packet can be
* used for the IDs. */
uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID);
p->Send_uint16(p_count);
for (uint i = 0; i < p_count; i++) {
p->Send_uint32(content_ids[i]);
}
this->Send_Packet(p);
count -= p_count;
content_ids += count;
}
}
void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bool send_md5sum)
{
if (cv == NULL) return;
/* 20 is sizeof(uint32) + sizeof(md5sum (byte[16])) */
assert(cv->Length() < 255);
assert(cv->Length() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) / (send_md5sum ? 20 : sizeof(uint32)));
Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID);
p->Send_uint8(cv->Length());
for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
const ContentInfo *ci = *iter;
p->Send_uint8((byte)ci->type);
p->Send_uint32(ci->unique_id);
if (!send_md5sum) continue;
for (uint j = 0; j < sizeof(ci->md5sum); j++) {
p->Send_uint8(ci->md5sum[j]);
}
}
this->Send_Packet(p);
}
void ClientNetworkContentSocketHandler::RequestContent(uint count, const uint32 *content_ids)
{
while (count > 0) {
/* We can "only" send a limited number of IDs in a single packet.
* A packet begins with the packet size and a byte for the type.
* Then this packet adds a uint16 for the count in this packet.
* The rest of the packet can be used for the IDs. */
uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT);
p->Send_uint16(p_count);
for (uint i = 0; i < p_count; i++) {
p->Send_uint32(content_ids[i]);
}
this->Send_Packet(p);
count -= p_count;
content_ids += count;
}
}
/**
* Determine the full filename of a piece of content information
* @param ci the information to get the filename from
* @param compressed should the filename end with .gz?
* @return a statically allocated buffer with the filename or
* NULL when no filename could be made.
*/
static char *GetFullFilename(const ContentInfo *ci, bool compressed)
{
Subdirectory dir;
switch (ci->type) {
default: return NULL;
case CONTENT_TYPE_BASE_GRAPHICS: dir = DATA_DIR; break;
case CONTENT_TYPE_NEWGRF: dir = DATA_DIR; break;
case CONTENT_TYPE_AI: dir = AI_DIR; break;
case CONTENT_TYPE_AI_LIBRARY: dir = AI_LIBRARY_DIR; break;
case CONTENT_TYPE_SCENARIO: dir = SCENARIO_DIR; break;
case CONTENT_TYPE_HEIGHTMAP: dir = HEIGHTMAP_DIR; break;
}
static char buf[MAX_PATH];
FioGetFullPath(buf, lengthof(buf), SP_AUTODOWNLOAD_DIR, dir, ci->filename);
strecat(buf, compressed ? ".tar.gz" : ".tar", lastof(buf));
return buf;
}
/**
* Gunzip a given file and remove the .gz if successful.
* @param ci container with filename
* @return true if the gunzip completed
*/
static bool GunzipFile(const ContentInfo *ci)
{
#if defined(WITH_ZLIB)
bool ret = true;
gzFile fin = gzopen(GetFullFilename(ci, true), "rb");
FILE *fout = fopen(GetFullFilename(ci, false), "wb");
if (fin == NULL || fout == NULL) {
ret = false;
goto exit;
}
byte buff[8192];
while (!gzeof(fin)) {
int read = gzread(fin, buff, sizeof(buff));
if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
ret = false;
break;
}
}
exit:
if (fin != NULL) gzclose(fin);
if (fout != NULL) fclose(fout);
return ret;
#else
NOT_REACHED();
#endif /* defined(WITH_ZLIB) */
}
DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT)
{
if (this->curFile == NULL) {
/* When we haven't opened a file this must be our first packet with metadata. */
this->curInfo = new ContentInfo;
this->curInfo->type = (ContentType)p->Recv_uint8();
this->curInfo->id = (ContentID)p->Recv_uint32();
this->curInfo->filesize = p->Recv_uint32();
p->Recv_string(this->curInfo->filename, lengthof(this->curInfo->filename));
if (!this->curInfo->IsValid()) {
delete this->curInfo;
this->curInfo = NULL;
this->Close();
return false;
}
if (this->curInfo->filesize != 0) {
/* The filesize is > 0, so we are going to download it */
const char *filename = GetFullFilename(this->curInfo, true);
if (filename == NULL) {
/* Unless that fails ofcourse... */
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, 0, 0);
this->Close();
return false;
}
this->curFile = fopen(filename, "wb");
}
} else {
/* We have a file opened, thus are downloading internal content */
size_t toRead = (size_t)(p->size - p->pos);
if (fwrite(p->buffer + p->pos, 1, toRead, this->curFile) != toRead) {
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, 0, 0);
this->Close();
fclose(this->curFile);
this->curFile = NULL;
return false;
}
/* Tell the rest we downloaded some bytes */
for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) {
(*iter)->OnDownloadProgress(this->curInfo, toRead);
}
if (toRead == 0) {
/* We read nothing; that's our marker for end-of-stream.
* Now gunzip the tar and make it known. */
fclose(this->curFile);
this->curFile = NULL;
if (GunzipFile(this->curInfo)) {
unlink(GetFullFilename(this->curInfo, true));
TarListAddFile(GetFullFilename(this->curInfo, false));
for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) {
(*iter)->OnDownloadComplete(this->curInfo->id);
}
} else {
ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_EXTRACT, 0, 0);
}
}
}
/* We ended this file, so clean up the mess */
if (this->curFile == NULL) {
delete this->curInfo;
this->curInfo = NULL;
}
return true;
}
/**
* Create a socket handler with the given socket and (server) address.
* @param s the socket to communicate over
* @param sin the IP/port of the server
*/
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin) :
NetworkContentSocketHandler(s, sin),
curFile(NULL),
curInfo(NULL)
{
}
/** Clear up the mess ;) */
ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
{
delete this->curInfo;
if (this->curFile != NULL) fclose(this->curFile);
}
/**
* Connect to the content server if needed, return the socket handler and
* add the callback to the socket handler.
* @param cb the callback to add
* @return the socket handler or NULL is connecting failed
*/
ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb)
{
/* If there's no connection or when it's broken, we try to reconnect.
* Otherwise we just add ourselves and return immediatelly */
if (_network_content_client != NULL && !_network_content_client->has_quit) {
*_network_content_client->callbacks.Append() = cb;
return _network_content_client;
}
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) return NULL;
if (!SetNoDelay(s)) DEBUG(net, 1, "Setting TCP_NODELAY failed");
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = NetworkResolveHost(NETWORK_CONTENT_SERVER_HOST);
sin.sin_port = htons(NETWORK_CONTENT_SERVER_PORT);
/* We failed to connect for which reason what so ever */
if (connect(s, (struct sockaddr*)&sin, sizeof(sin)) != 0) return NULL;
if (!SetNonBlocking(s)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error?
if (_network_content_client != NULL) {
if (_network_content_client->sock != INVALID_SOCKET) closesocket(_network_content_client->sock);
_network_content_client->sock = s;
/* Clean up the mess that could've been left behind */
_network_content_client->has_quit = false;
delete _network_content_client->curInfo;
_network_content_client->curInfo = NULL;
if (_network_content_client->curFile != NULL) fclose(_network_content_client->curFile);
_network_content_client->curFile = NULL;
} else {
_network_content_client = new ClientNetworkContentSocketHandler(s, &sin);
}
*_network_content_client->callbacks.Append() = cb;
return _network_content_client;
}
/**
* Remove yourself from the network content callback list and when
* that list is empty the connection will be closed.
*/
void NetworkContent_Disconnect(ContentCallback *cb)
{
_network_content_client->callbacks.Erase(_network_content_client->callbacks.Find(cb));
if (_network_content_client->callbacks.Length() == 0) {
delete _network_content_client;
_network_content_client = NULL;
}
}
/**
* Check whether we received/can send some data from/to the content server and
* when that's the case handle it appropriately
*/
void NetworkContentLoop()
{
if (_network_content_client == NULL) return;
fd_set read_fd, write_fd;
struct timeval tv;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FD_SET(_network_content_client->sock, &read_fd);
FD_SET(_network_content_client->sock, &write_fd);
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv);
#else
WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL);
#endif
if (FD_ISSET(_network_content_client->sock, &read_fd)) _network_content_client->Recv_Packets();
_network_content_client->writable = !!FD_ISSET(_network_content_client->sock, &write_fd);
_network_content_client->Send_Packets();
}
#endif /* ENABLE_NETWORK */

View File

@ -0,0 +1,79 @@
/* $Id$ */
/** @file network_content.h Part of the network protocol handling content distribution. */
#ifndef NETWORK_CONTENT_H
#define NETWORK_CONTENT_H
#if defined(ENABLE_NETWORK)
#include "core/tcp_content.h"
#include "../core/smallvec_type.hpp"
/** Vector with content info */
typedef SmallVector<ContentInfo *, 16> ContentVector;
/** Iterator for the content vector */
typedef ContentInfo **ContentIterator;
/** Callbacks for notifying others about incoming data */
struct ContentCallback {
/**
* We received a content info.
* @param ci the content info
*/
virtual void OnReceiveContentInfo(ContentInfo *ci) {}
/**
* We have progress in the download of a file
* @param ci the content info of the file
* @param bytes the number of bytes downloaded since the previous call
*/
virtual void OnDownloadProgress(ContentInfo *ci, uint bytes) {}
/**
* We have finished downloading a file
* @param cid the ContentID of the downloaded file
*/
virtual void OnDownloadComplete(ContentID cid) {}
/** Silentium */
virtual ~ContentCallback() {}
};
/**
* Socket handler for the content server connection
*/
class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler {
protected:
SmallVector<ContentCallback *, 2> callbacks; ///< Callbacks to notify "the world"
FILE *curFile; ///< Currently downloaded file
ContentInfo *curInfo; ///< Information about the currently downloaded file
friend ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb);
friend void NetworkContent_Disconnect(ContentCallback *cb);
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_INFO);
DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_CONTENT);
ClientNetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin);
~ClientNetworkContentSocketHandler();
public:
void RequestContentList(ContentType type);
void RequestContentList(uint count, const ContentID *content_ids);
void RequestContentList(ContentVector *cv, bool send_md5sum = true);
void RequestContent(uint count, const uint32 *content_ids);
};
ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb);
void NetworkContent_Disconnect(ContentCallback *cb);
void NetworkContentLoop();
void ShowNetworkContentListWindow(ContentVector *cv = NULL, ContentType type = CONTENT_TYPE_END);
#else
static inline void ShowNetworkContentListWindow() {}
#endif /* ENABLE_NETWORK */
#endif /* NETWORK_CONTENT_H */

View File

@ -0,0 +1,841 @@
/* $Id$ */
/** @file network_gui.cpp Implementation of the Network related GUIs. */
#if defined(ENABLE_NETWORK)
#include "../stdafx.h"
#include "../string_func.h"
#include "../strings_func.h"
#include "../gfx_func.h"
#include "../window_func.h"
#include "../window_gui.h"
#include "../gui.h"
#include "../core/smallvec_type.hpp"
#include "../ai/ai.hpp"
#include "../gfxinit.h"
#include "network_content.h"
#include "table/strings.h"
#include "../table/sprites.h"
/** Widgets for the download window */
static const Widget _network_content_download_status_window_widget[] = {
{ WWT_CAPTION, RESIZE_NONE, COLOUR_GREY, 0, 349, 0, 13, STR_CONTENT_DOWNLOAD_TITLE, STR_018C_WINDOW_TITLE_DRAG_THIS}, // NCDSWW_CAPTION
{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 0, 349, 14, 84, 0x0, STR_NULL}, // NCDSWW_BACKGROUND
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_WHITE, 125, 225, 69, 80, STR_012E_CANCEL, STR_NULL}, // NCDSWW_CANCELOK
{ WIDGETS_END},
};
/** Window description for the download window */
static const WindowDesc _network_content_download_status_window_desc = {
WDP_CENTER, WDP_CENTER, 350, 85, 350, 85,
WC_NETWORK_STATUS_WINDOW, WC_NONE,
WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL,
_network_content_download_status_window_widget,
};
/** Window for showing the download status of content */
struct NetworkContentDownloadStatusWindow : public Window, ContentCallback {
/** Widgets used by this window */
enum Widgets {
NCDSWW_CAPTION, ///< Caption of the window
NCDSWW_BACKGROUND, ///< Background
NCDSWW_CANCELOK, ///< Cancel/OK button
};
private:
ClientNetworkContentSocketHandler *connection; ///< Our connection with the content server
SmallVector<ContentType, 4> receivedTypes; ///< Types we received so we can update their cache
uint total_files; ///< Number of files to download
uint downloaded_files; ///< Number of files downloaded
uint total_bytes; ///< Number of bytes to download
uint downloaded_bytes; ///< Number of bytes downloaded
uint32 cur_id; ///< The current ID of the downloaded file
char name[32]; ///< The current name of the downloaded file
public:
/**
* Create a new download window based on a list of content information
* with flags whether to download them or not.
* @param infos the list to search in
*/
NetworkContentDownloadStatusWindow(ContentVector &infos) :
Window(&_network_content_download_status_window_desc),
cur_id(UINT32_MAX)
{
this->parent = FindWindowById(WC_NETWORK_WINDOW, 1);
this->connection = NetworkContent_Connect(this);
if (this->connection == NULL) {
/* When the connection got broken and we can't rebuild it we can't do much :( */
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_CONNECTION_LOST, 0, 0);
delete this;
return;
}
/** Make the list of items to download */
uint32 *ids = MallocT<uint32>(infos.Length());
for (ContentIterator iter = infos.Begin(); iter != infos.End(); iter++) {
const ContentInfo *ci = *iter;
if (!ci->IsSelected()) continue;
ids[this->total_files++] = ci->id;
this->total_bytes += ci->filesize;
}
/** And request them for download */
this->connection->RequestContent(this->total_files, ids);
free(ids);
this->FindWindowPlacementAndResize(&_network_content_download_status_window_desc);
}
/** Free whatever we've allocated */
~NetworkContentDownloadStatusWindow()
{
/* Tell all the backends about what we've downloaded */
for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
switch (*iter) {
case CONTENT_TYPE_AI:
case CONTENT_TYPE_AI_LIBRARY:
AI::Rescan();
InvalidateWindowClasses(WC_AI_DEBUG);
break;
case CONTENT_TYPE_BASE_GRAPHICS:
FindGraphicsSets();
break;
case CONTENT_TYPE_NEWGRF:
ScanNewGRFFiles();
/* Yes... these are the NewGRF windows */
InvalidateWindowClasses(WC_SAVELOAD);
InvalidateWindowData(WC_GAME_OPTIONS, 0, 1);
InvalidateWindowData(WC_NETWORK_WINDOW, 0, 2);
break;
default:
break;
}
}
NetworkContent_Disconnect(this);
}
virtual void OnPaint()
{
/* When downloading is finished change cancel in ok */
if (this->downloaded_bytes == this->total_bytes) {
this->widget[NCDSWW_CANCELOK].data = STR_012F_OK;
}
this->DrawWidgets();
/* Draw nice progress bar :) */
DrawFrameRect(20, 18, (int)((this->width - 20) * this->downloaded_bytes / this->total_bytes), 28, COLOUR_MAUVE, FR_NONE);
SetDParam(0, this->downloaded_bytes);
SetDParam(1, this->total_bytes);
SetDParam(2, this->downloaded_bytes * 100 / this->total_bytes);
DrawStringCentered(this->width / 2, 35, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, TC_GREY);
if (this->downloaded_bytes == this->total_bytes) {
DrawStringCentered(this->width / 2, 46, STR_CONTENT_DOWNLOAD_COMPLETE, TC_GREY);
} else if (!StrEmpty(this->name)) {
SetDParamStr(0, this->name);
SetDParam(1, this->downloaded_files);
SetDParam(2, this->total_files);
DrawStringCentered(this->width / 2, 46, STR_CONTENT_DOWNLOAD_FILE, TC_GREY);
} else {
DrawStringCentered(this->width / 2, 46, STR_CONTENT_DOWNLOAD_INITIALISE, TC_GREY);
}
}
virtual void OnClick(Point pt, int widget)
{
if (widget == NCDSWW_CANCELOK) delete this;
}
virtual void OnDownloadProgress(ContentInfo *ci, uint bytes)
{
if (ci->id != this->cur_id) {
strecpy(this->name, ci->filename, lastof(this->name));
this->cur_id = ci->id;
this->downloaded_files++;
this->receivedTypes.Include(ci->type);
}
this->downloaded_bytes += bytes;
this->SetDirty();
}
};
/** Window that lists the content that's at the content server */
class NetworkContentListWindow : public Window, ContentCallback {
/** All widgets used */
enum Widgets {
NCLWW_CLOSE, ///< Close 'X' button
NCLWW_CAPTION, ///< Caption of the window
NCLWW_BACKGROUND, ///< Resize button
NCLWW_CHECKBOX, ///< Button above checkboxes
NCLWW_TYPE, ///< 'Type' button
NCLWW_NAME, ///< 'Name' button
NCLWW_MATRIX, ///< Panel with list of content
NCLWW_SCROLLBAR, ///< Scrollbar of matrix
NCLWW_DETAILS, ///< Panel with content details
NCLWW_SELECT_ALL, ///< 'Select all' button
NCLWW_SELECT_UPDATE, ///< 'Select updates' button
NCLWW_UNSELECT, ///< 'Unselect all' button
NCLWW_CANCEL, ///< 'Cancel' button
NCLWW_DOWNLOAD, ///< 'Download' button
NCLWW_RESIZE, ///< Resize button
};
ClientNetworkContentSocketHandler *connection; ///< Connection with the content server
SmallVector<ContentID, 4> requested; ///< ContentIDs we already requested (so we don't do it again)
ContentVector infos; ///< All content info we received
ContentInfo *selected; ///< The selected content info
int list_pos; ///< Our position in the list
/** Make sure that the currently selected content info is within the visible part of the matrix */
void ScrollToSelected()
{
if (this->selected == NULL) return;
if (this->list_pos < this->vscroll.pos) {
/* scroll up to the server */
this->vscroll.pos = this->list_pos;
} else if (this->list_pos >= this->vscroll.pos + this->vscroll.cap) {
/* scroll down so that the server is at the bottom */
this->vscroll.pos = this->list_pos - this->vscroll.cap + 1;
}
}
/**
* Download information of a given Content ID if not already tried
* @param cid the ID to try
*/
void DownloadContentInfo(ContentID cid)
{
/* When we tried to download it already, don't try again */
if (this->requested.Contains(cid)) return;
*this->requested.Append() = cid;
assert(this->requested.Contains(cid));
this->connection->RequestContentList(1, &cid);
}
/**
* Get the content info based on a ContentID
* @param cid the ContentID to search for
* @return the ContentInfo or NULL if not found
*/
ContentInfo *GetContent(ContentID cid)
{
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
ContentInfo *ci = *iter;
if (ci->id == cid) return ci;
}
return NULL;
}
/**
* Reverse lookup the dependencies of (direct) parents over a given child.
* @param parents list to store all parents in (is not cleared)
* @param child the child to search the parents' dependencies for
*/
void ReverseLookupDependency(ContentVector &parents, ContentInfo *child)
{
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
ContentInfo *ci = *iter;
if (ci == child) continue;
for (uint i = 0; i < ci->dependency_count; i++) {
if (ci->dependencies[i] == child->id) {
*parents.Append() = ci;
break;
}
}
}
}
/**
* Reverse lookup the dependencies of all parents over a given child.
* @param tree list to store all parents in (is not cleared)
* @param child the child to search the parents' dependencies for
*/
void ReverseLookupTreeDependency(ContentVector &tree, ContentInfo *child)
{
*tree.Append() = child;
/* First find all direct parents */
for (ContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
ContentVector parents;
ReverseLookupDependency(parents, *iter);
for (ContentIterator piter = parents.Begin(); piter != parents.End(); piter++) {
tree.Include(*piter);
}
}
}
/**
* Check the dependencies (recursively) of this content info
* @param ci the content info to check the dependencies of
*/
void CheckDependencyState(ContentInfo *ci)
{
if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
/* Selection is easy; just walk all children and set the
* autoselected state. That way we can see what we automatically
* selected and thus can unselect when a dependency is removed. */
for (uint i = 0; i < ci->dependency_count; i++) {
ContentInfo *c = this->GetContent(ci->dependencies[i]);
if (c == NULL) {
DownloadContentInfo(ci->dependencies[i]);
} else if (c->state == ContentInfo::UNSELECTED) {
c->state = ContentInfo::AUTOSELECTED;
this->CheckDependencyState(c);
}
}
return;
}
if (ci->state != ContentInfo::UNSELECTED) return;
/* For unselection we need to find the parents of us. We need to
* unselect them. After that we unselect all children that we
* depend on and are not used as dependency for us, but only when
* we automatically selected them. */
ContentVector parents;
ReverseLookupDependency(parents, ci);
for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
ContentInfo *c = *iter;
if (!c->IsSelected()) continue;
c->state = ContentInfo::UNSELECTED;
CheckDependencyState(c);
}
for (uint i = 0; i < ci->dependency_count; i++) {
ContentInfo *c = this->GetContent(ci->dependencies[i]);
if (c == NULL) {
DownloadContentInfo(ci->dependencies[i]);
continue;
}
if (c->state != ContentInfo::AUTOSELECTED) continue;
/* Only unselect when WE are the only parent. */
parents.Clear();
ReverseLookupDependency(parents, c);
/* First check whether anything depends on us */
int sel_count = 0;
bool force_selection = false;
for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
if ((*iter)->IsSelected()) sel_count++;
if ((*iter)->state == ContentInfo::SELECTED) force_selection = true;
}
if (sel_count == 0) {
/* Nothing depends on us */
c->state = ContentInfo::UNSELECTED;
this->CheckDependencyState(c);
continue;
}
/* Something manually selected depends directly on us */
if (force_selection) continue;
/* "Flood" search to find all items in the dependency graph*/
parents.Clear();
ReverseLookupTreeDependency(parents, c);
/* Is there anything that is "force" selected?, if so... we're done. */
for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
if ((*iter)->state != ContentInfo::SELECTED) continue;
force_selection = true;
break;
}
/* So something depended directly on us */
if (force_selection) continue;
/* Nothing depends on us, mark the whole graph as unselected.
* After that's done run over them once again to test their children
* to unselect. Don't do it immediatelly because it'll do exactly what
* we're doing now. */
for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
ContentInfo *c = *iter;
if (c->state == ContentInfo::AUTOSELECTED) c->state = ContentInfo::UNSELECTED;
}
for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
this->CheckDependencyState(*iter);
}
}
}
/** Toggle the state of a content info and check it's dependencies */
void ToggleSelectedState()
{
switch (this->selected->state) {
case ContentInfo::SELECTED:
case ContentInfo::AUTOSELECTED:
this->selected->state = ContentInfo::UNSELECTED;
break;
case ContentInfo::UNSELECTED:
this->selected->state = ContentInfo::SELECTED;
break;
default:
return;
}
this->CheckDependencyState(this->selected);
}
public:
/**
* Create the content list window.
* @param desc the window description to pass to Window's constructor.
* @param cv the list with content to show; if NULL find content ourselves
*/
NetworkContentListWindow(const WindowDesc *desc, ContentVector *cv, ContentType type) : Window(desc, 1), selected(NULL), list_pos(0)
{
this->connection = NetworkContent_Connect(this);
this->vscroll.cap = 14;
this->resize.step_height = 14;
this->resize.step_width = 2;
if (cv == NULL) {
if (type == CONTENT_TYPE_END) {
this->connection->RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
this->connection->RequestContentList(CONTENT_TYPE_NEWGRF);
this->connection->RequestContentList(CONTENT_TYPE_AI);
} else {
this->connection->RequestContentList(type);
}
this->HideWidget(NCLWW_SELECT_ALL);
} else {
this->connection->RequestContentList(cv, true);
for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
*this->infos.Append() = *iter;
}
this->HideWidget(NCLWW_SELECT_UPDATE);
}
this->FindWindowPlacementAndResize(desc);
}
/** Free everything we allocated */
~NetworkContentListWindow()
{
NetworkContent_Disconnect(this);
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
}
virtual void OnPaint()
{
/* To sum all the bytes we intend to download */
uint filesize = 0;
bool show_select_all = false;
bool show_select_update = false;
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
const ContentInfo *ci = *iter;
switch (ci->state) {
case ContentInfo::SELECTED:
case ContentInfo::AUTOSELECTED:
filesize += ci->filesize;
break;
case ContentInfo::UNSELECTED:
show_select_all = true;
show_select_update |= ci->update;
break;
default:
break;
}
}
this->SetWidgetDisabledState(NCLWW_DOWNLOAD, filesize == 0);
this->SetWidgetDisabledState(NCLWW_UNSELECT, filesize == 0);
this->SetWidgetDisabledState(NCLWW_SELECT_ALL, !show_select_all);
this->SetWidgetDisabledState(NCLWW_SELECT_UPDATE, !show_select_update);
this->DrawWidgets();
/* Fill the matrix with the information */
uint y = this->widget[NCLWW_MATRIX].top + 3;
int cnt = 0;
for (ContentIterator iter = this->infos.Get(this->vscroll.pos); iter != this->infos.End() && cnt < this->vscroll.cap; iter++, cnt++) {
const ContentInfo *ci = *iter;
if (ci == this->selected) GfxFillRect(this->widget[NCLWW_CHECKBOX].left + 1, y - 2, this->widget[NCLWW_NAME].right - 1, y + 9, 10);
SpriteID sprite;
SpriteID pal = PAL_NONE;
switch (ci->state) {
case ContentInfo::UNSELECTED: sprite = SPR_BOX_EMPTY; break;
case ContentInfo::SELECTED: sprite = SPR_BOX_CHECKED; break;
case ContentInfo::AUTOSELECTED: sprite = SPR_BOX_CHECKED; break;
case ContentInfo::ALREADY_HERE: sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED; break;
default: NOT_REACHED();
}
DrawSprite(sprite, pal, this->widget[NCLWW_CHECKBOX].left + (pal == PAL_NONE ? 3 : 4), y + (pal == PAL_NONE ? 1 : 0));
StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
DrawStringCenteredTruncated(this->widget[NCLWW_TYPE].left, this->widget[NCLWW_TYPE].right, y, str, TC_BLACK);
SetDParamStr(0, ci->name);
DrawStringTruncated(this->widget[NCLWW_NAME].left + 5, y, STR_JUST_RAW_STRING, TC_BLACK, this->widget[NCLWW_NAME].right - this->widget[NCLWW_NAME].left - 5);
y += this->resize.step_height;
}
/* Create the nice grayish rectangle at the details top */
GfxFillRect(this->widget[NCLWW_DETAILS].left + 1, this->widget[NCLWW_DETAILS].top + 1, this->widget[NCLWW_DETAILS].right - 1, this->widget[NCLWW_DETAILS].top + 50, 157);
DrawStringCentered((this->widget[NCLWW_DETAILS].left + this->widget[NCLWW_DETAILS].right) / 2, this->widget[NCLWW_DETAILS].top + 11, STR_CONTENT_DETAIL_TITLE, TC_FROMSTRING);
if (this->selected == NULL) return;
/* And fill the rest of the details when there's information to place there */
DrawStringMultiCenter((this->widget[NCLWW_DETAILS].left + this->widget[NCLWW_DETAILS].right) / 2, this->widget[NCLWW_DETAILS].top + 32, STR_CONTENT_DETAIL_SUBTITLE_UNSELECTED + this->selected->state, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 10);
/* Also show the total download size, so keep some space from the bottom */
const uint max_y = this->widget[NCLWW_DETAILS].bottom - 15;
y = this->widget[NCLWW_DETAILS].top + 55;
if (this->selected->update) {
SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_UPDATE, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
y += 11;
}
SetDParamStr(0, this->selected->name);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_NAME, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
if (!StrEmpty(this->selected->version)) {
SetDParamStr(0, this->selected->version);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_VERSION, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
if (!StrEmpty(this->selected->description)) {
SetDParamStr(0, this->selected->description);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_DESCRIPTION, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
if (!StrEmpty(this->selected->url)) {
SetDParamStr(0, this->selected->url);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_URL, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_TYPE, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
y += 11;
SetDParam(0, this->selected->filesize);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_FILESIZE, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
if (this->selected->dependency_count != 0) {
/* List dependencies */
char buf[8192] = "";
char *p = buf;
for (uint i = 0; i < this->selected->dependency_count; i++) {
ContentID cid = this->selected->dependencies[i];
/* Try to find the dependency */
ContentIterator iter = this->infos.Begin();
for (; iter != this->infos.End(); iter++) {
const ContentInfo *ci = *iter;
if (ci->id != cid) continue;
p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
break;
}
/* We miss the dependency, but we'll only request it if not done before. */
if (iter == this->infos.End()) DownloadContentInfo(cid);
}
SetDParamStr(0, buf);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_DEPENDENCIES, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
if (this->selected->tag_count != 0) {
/* List all tags */
char buf[8192] = "";
char *p = buf;
for (uint i = 0; i < this->selected->tag_count; i++) {
p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
}
SetDParamStr(0, buf);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_TAGS, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
if (this->selected->IsSelected()) {
/* When selected show all manually selected content that depends on this */
ContentVector tree;
ReverseLookupTreeDependency(tree, this->selected);
char buf[8192] = "";
char *p = buf;
for (ContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
ContentInfo *ci = *iter;
if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
}
if (p != buf) {
SetDParamStr(0, buf);
y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y);
}
}
/* Draw the total download size */
SetDParam(0, filesize);
DrawString(this->widget[NCLWW_DETAILS].left + 5, this->widget[NCLWW_DETAILS].bottom - 12, STR_CONTENT_TOTAL_DOWNLOAD_SIZE, TC_BLACK);
}
virtual void OnDoubleClick(Point pt, int widget)
{
/* Double clicking on a line in the matrix toggles the state of the checkbox */
if (widget != NCLWW_MATRIX) return;
pt.x = this->widget[NCLWW_CHECKBOX].left;
this->OnClick(pt, widget);
}
virtual void OnClick(Point pt, int widget)
{
switch (widget) {
case NCLWW_MATRIX: {
uint32 id_v = (pt.y - this->widget[NCLWW_MATRIX].top) / this->resize.step_height;
if (id_v >= this->vscroll.cap) return; // click out of bounds
id_v += this->vscroll.pos;
if (id_v >= this->infos.Length()) return; // click out of bounds
this->selected = this->infos[id_v];
this->list_pos = id_v;
if (pt.x <= this->widget[NCLWW_CHECKBOX].right) this->ToggleSelectedState();
this->SetDirty();
} break;
case NCLWW_SELECT_ALL:
case NCLWW_SELECT_UPDATE:
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
ContentInfo *ci = *iter;
if (ci->state == ContentInfo::UNSELECTED && (widget == NCLWW_SELECT_ALL || ci->update)) ci->state = ContentInfo::SELECTED;
}
this->SetDirty();
break;
case NCLWW_UNSELECT:
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
ContentInfo *ci = *iter;
if (ci->IsSelected()) ci->state = ContentInfo::UNSELECTED;
}
this->SetDirty();
break;
case NCLWW_CANCEL:
delete this;
break;
case NCLWW_DOWNLOAD:
new NetworkContentDownloadStatusWindow(this->infos);
break;
}
}
virtual EventState OnKeyPress(uint16 key, uint16 keycode)
{
if (this->infos.Length() == 0) return ES_HANDLED;
switch (keycode) {
case WKC_UP:
/* scroll up by one */
if (this->list_pos > 0) this->list_pos--;
break;
case WKC_DOWN:
/* scroll down by one */
if (this->list_pos < (int)this->infos.Length() - 1) this->list_pos++;
break;
case WKC_PAGEUP:
/* scroll up a page */
this->list_pos = (this->list_pos < this->vscroll.cap) ? 0 : this->list_pos - this->vscroll.cap;
break;
case WKC_PAGEDOWN:
/* scroll down a page */
this->list_pos = min(this->list_pos + this->vscroll.cap, (int)this->infos.Length() - 1);
break;
case WKC_HOME:
/* jump to beginning */
this->list_pos = 0;
break;
case WKC_END:
/* jump to end */
this->list_pos = this->infos.Length() - 1;
break;
case WKC_SPACE:
this->ToggleSelectedState();
this->SetDirty();
return ES_HANDLED;
default: return ES_NOT_HANDLED;
}
this->selected = this->infos[this->list_pos];
/* scroll to the new server if it is outside the current range */
this->ScrollToSelected();
/* redraw window */
this->SetDirty();
return ES_HANDLED;
}
virtual void OnResize(Point new_size, Point delta)
{
this->vscroll.cap += delta.y / (int)this->resize.step_height;
this->widget[NCLWW_MATRIX].data = (this->vscroll.cap << 8) + 1;
SetVScrollCount(this, this->infos.Length());
/* Make the matrix and details section grow both bigger (or smaller) */
delta.x /= 2;
this->widget[NCLWW_NAME].right -= delta.x;
this->widget[NCLWW_MATRIX].right -= delta.x;
this->widget[NCLWW_SCROLLBAR].left -= delta.x;
this->widget[NCLWW_SCROLLBAR].right -= delta.x;
this->widget[NCLWW_DETAILS].left -= delta.x;
}
virtual void OnReceiveContentInfo(ContentInfo *rci)
{
/* Do we already have a stub for this? */
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
ContentInfo *ci = *iter;
if (ci->type == rci->type && ci->unique_id == rci->unique_id &&
memcmp(ci->md5sum, rci->md5sum, sizeof(ci->md5sum)) == 0) {
/* Preserve the name if possible */
if (StrEmpty(rci->name)) strecpy(rci->name, ci->name, lastof(rci->name));
delete ci;
*iter = rci;
this->SetDirty();
return;
}
}
/* Missing content info? Don't list it */
if (rci->filesize == 0) {
delete rci;
return;
}
/* Nothing selected, lets select something */
if (this->selected == NULL) this->selected = rci;
*this->infos.Append() = rci;
/* Incoming data means that we might need to reconsider dependencies */
for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
CheckDependencyState(*iter);
}
this->SetDirty();
}
virtual void OnDownloadComplete(ContentID cid)
{
/* When we know something that completed downloading, we have it! */
ContentInfo *ci = this->GetContent(cid);
if (ci != NULL) {
ci->state = ContentInfo::ALREADY_HERE;
this->SetDirty();
}
}
};
/** Widgets used for the content list */
static const Widget _network_content_list_widgets[] = {
/* TOP */
{ WWT_CLOSEBOX, RESIZE_NONE, COLOUR_LIGHT_BLUE, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // NCLWW_CLOSE
{ WWT_CAPTION, RESIZE_RIGHT, COLOUR_LIGHT_BLUE, 11, 449, 0, 13, STR_CONTENT_TITLE, STR_NULL}, // NCLWW_CAPTION
{ WWT_PANEL, RESIZE_RB, COLOUR_LIGHT_BLUE, 0, 449, 14, 263, 0x0, STR_NULL}, // NCLWW_BACKGROUND
/* LEFT SIDE */
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_WHITE, 8, 20, 22, 33, STR_EMPTY, STR_NULL}, // NCLWW_CHECKBOX
{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_WHITE, 21, 110, 22, 33, STR_CONTENT_TYPE_CAPTION, STR_CONTENT_TYPE_CAPTION_TIP}, // NCLWW_TYPE
{ WWT_PUSHTXTBTN, RESIZE_RIGHT, COLOUR_WHITE, 111, 191, 22, 33, STR_CONTENT_NAME_CAPTION, STR_CONTENT_NAME_CAPTION_TIP}, // NCLWW_NAME
{ WWT_MATRIX, RESIZE_RB, COLOUR_LIGHT_BLUE, 8, 190, 34, 230, (14 << 8) | 1, STR_NETWORK_CLICK_GAME_TO_SELECT}, // NCLWW_MATRIX
{ WWT_SCROLLBAR, RESIZE_LRB, COLOUR_LIGHT_BLUE, 191, 202, 22, 230, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, // NCLWW_SCROLLBAR
/* RIGHT SIDE */
{ WWT_PANEL, RESIZE_LRB, COLOUR_LIGHT_BLUE, 210, 440, 22, 230, 0x0, STR_NULL}, // NCLWW_DETAILS
/* BOTTOM */
{ WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_WHITE, 10, 110, 238, 249, STR_CONTENT_SELECT_ALL_CAPTION, STR_CONTENT_SELECT_ALL_CAPTION_TIP}, // NCLWW_SELECT_ALL
{ WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_WHITE, 10, 110, 238, 249, STR_CONTENT_SELECT_UPDATES_CAPTION, STR_CONTENT_SELECT_UPDATES_CAPTION_TIP}, // NCLWW_SELECT_UPDATES
{ WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_WHITE, 118, 218, 238, 249, STR_CONTENT_UNSELECT_ALL_CAPTION, STR_CONTENT_UNSELECT_ALL_CAPTION_TIP}, // NCLWW_UNSELECT
{ WWT_PUSHTXTBTN, RESIZE_LRTB, COLOUR_WHITE, 226, 326, 238, 249, STR_012E_CANCEL, STR_NULL}, // NCLWW_CANCEL
{ WWT_PUSHTXTBTN, RESIZE_LRTB, COLOUR_WHITE, 334, 434, 238, 249, STR_CONTENT_DOWNLOAD_CAPTION, STR_CONTENT_DOWNLOAD_CAPTION_TIP}, // NCLWW_DOWNLOAD
{ WWT_RESIZEBOX, RESIZE_LRTB, COLOUR_LIGHT_BLUE, 438, 449, 252, 263, 0x0, STR_RESIZE_BUTTON }, // NCLWW_RESIZE
{ WIDGETS_END},
};
/** Window description of the content list */
static const WindowDesc _network_content_list_desc = {
WDP_CENTER, WDP_CENTER, 450, 264, 630, 460,
WC_NETWORK_WINDOW, WC_NONE,
WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
_network_content_list_widgets,
};
/**
* Show the content list window with a given set of content
* @param cv the content to show, or NULL when it has to search for itself
* @param type the type to (only) show
*/
void ShowNetworkContentListWindow(ContentVector *cv, ContentType type)
{
#if defined(WITH_ZLIB)
if (NetworkContent_Connect(NULL)) {
DeleteWindowById(WC_NETWORK_WINDOW, 1);
new NetworkContentListWindow(&_network_content_list_desc, cv, type);
NetworkContent_Disconnect(NULL);
} else {
ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_CONNECT, 0, 0);
#else
{
ShowErrorMessage(STR_CONTENT_NO_ZLIB_SUB, STR_CONTENT_NO_ZLIB, 0, 0);
#endif /* WITH_ZLIB */
/* Connection failed... clean up the mess */
if (cv != NULL) {
for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter;
}
}
}
#endif /* ENABLE_NETWORK */

View File

@ -11,6 +11,7 @@
#include "../fios.h"
#include "network_internal.h"
#include "network_client.h"
#include "network_content.h"
#include "network_gui.h"
#include "network_gamelist.h"
#include "../gui.h"
@ -76,7 +77,7 @@ enum {
* @param unselect unselect the currently selected item */
void UpdateNetworkGameWindow(bool unselect)
{
InvalidateWindowData(WC_NETWORK_WINDOW, 0, unselect);
InvalidateWindowData(WC_NETWORK_WINDOW, 0, unselect ? 1 : 0);
}
/** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
@ -595,9 +596,36 @@ public:
virtual void OnInvalidateData(int data)
{
if (data != 0) {
this->server = NULL;
this->list_pos = SLP_INVALID;
switch (data) {
/* Remove the selection */
case 1:
this->server = NULL;
this->list_pos = SLP_INVALID;
break;
/* Reiterate the whole server list as we downloaded some files */
case 2:
for (NetworkGameList **iter = this->servers.Begin(); iter != this->servers.End(); iter++) {
NetworkGameList *item = *iter;
bool missing_grfs = false;
for (GRFConfig *c = item->info.grfconfig; c != NULL; c = c->next) {
if (c->status != GCS_NOT_FOUND) continue;
const GRFConfig *f = FindGRFConfig(c->grfid, c->md5sum);
if (f == NULL) {
missing_grfs = true;
continue;
}
c->filename = f->filename;
c->name = f->name;
c->info = f->info;
c->status = GCS_UNKNOWN;
}
if (!missing_grfs) item->info.compatible = item->info.version_compatible;
}
break;
}
this->servers.ForceRebuild();
this->SetDirty();

View File

@ -18,6 +18,8 @@
#include "gamelog.h"
#include "settings_func.h"
#include "widgets/dropdown_type.h"
#include "network/network.h"
#include "network/network_content.h"
#include "table/strings.h"
#include "table/sprites.h"
@ -307,6 +309,7 @@ struct NewGRFWindow : public Window {
SNGRFS_SET_PARAMETERS,
SNGRFS_TOGGLE_PALETTE,
SNGRFS_APPLY_CHANGES,
SNGRFS_CONTENT_DOWNLOAD,
SNGRFS_RESIZE,
};
@ -579,13 +582,34 @@ struct NewGRFWindow : public Window {
break;
}
case SNGRFS_TOGGLE_PALETTE: {
case SNGRFS_TOGGLE_PALETTE:
if (this->sel != NULL) {
this->sel->windows_paletted ^= true;
this->SetDirty();
}
break;
}
case SNGRFS_CONTENT_DOWNLOAD:
if (!_network_available) {
ShowErrorMessage(INVALID_STRING_ID, STR_NETWORK_ERR_NOTAVAILABLE, 0, 0);
} else {
#if defined(ENABLE_NETWORK)
/* Only show the things in the current list, or everything when nothing's selected */
ContentVector cv;
for (const GRFConfig *c = this->list; c != NULL; c = c->next) {
ContentInfo *ci = new ContentInfo();
ci->type = CONTENT_TYPE_NEWGRF;
ci->state = ContentInfo::DOES_NOT_EXIST;
ttd_strlcpy(ci->name, c->name, lengthof(ci->name));
ci->unique_id = BSWAP32(c->grfid);
memcpy(ci->md5sum, c->md5sum, sizeof(ci->md5sum));
*cv.Append() = ci;
}
ShowNetworkContentListWindow(cv.Length() == 0 ? NULL : &cv, CONTENT_TYPE_NEWGRF);
#endif
}
break;
}
}
@ -658,10 +682,34 @@ struct NewGRFWindow : public Window {
this->SetupNewGRFWindow();
}
virtual void OnInvalidateData(int data = 0)
virtual void OnInvalidateData(int data)
{
this->preset = -1;
this->SetupNewGRFWindow();
switch (data) {
default: NOT_REACHED();
case 0:
this->preset = -1;
this->SetupNewGRFWindow();
break;
case 1:
/* Search the list for items that are now found and mark them as such. */
for (GRFConfig *c = this->list; c != NULL; c = c->next) {
if (c->status != GCS_NOT_FOUND) continue;
const GRFConfig *f = FindGRFConfig(c->grfid, c->md5sum);
if (f == NULL) continue;
free(c->filename);
free(c->name);
free(c->info);
c->filename = f->filename == NULL ? NULL : strdup(f->filename);
c->name = f->name == NULL ? NULL : strdup(f->name);;
c->info = f->info == NULL ? NULL : strdup(f->info);;
c->status = GCS_UNKNOWN;
}
break;
}
}
};
@ -681,16 +729,17 @@ static const Widget _newgrf_widgets[] = {
{ WWT_MATRIX, RESIZE_RB, COLOUR_MAUVE, 0, 287, 46, 115, 0x501, STR_NEWGRF_FILE_TIP }, // SNGRFS_FILE_LIST
{ WWT_SCROLLBAR, RESIZE_LRB, COLOUR_MAUVE, 288, 299, 46, 115, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST }, // SNGRFS_SCROLLBAR
{ WWT_PANEL, RESIZE_RTB, COLOUR_MAUVE, 0, 299, 116, 238, STR_NULL, STR_NULL }, // SNGRFS_NEWGRF_INFO
{ WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_MAUVE, 0, 95, 239, 250, STR_NEWGRF_SET_PARAMETERS, STR_NULL }, // SNGRFS_SET_PARAMETERS
{ WWT_PUSHTXTBTN, RESIZE_RTB, COLOUR_MAUVE, 96, 191, 239, 250, STR_NEWGRF_TOGGLE_PALETTE, STR_NEWGRF_TOGGLE_PALETTE_TIP }, // SNGRFS_TOGGLE_PALETTE
{ WWT_PUSHTXTBTN, RESIZE_RTB, COLOUR_MAUVE, 192, 287, 239, 250, STR_NEWGRF_APPLY_CHANGES, STR_NULL }, // SNGRFS_APPLY_CHANGES
{ WWT_RESIZEBOX, RESIZE_LRTB, COLOUR_MAUVE, 288, 299, 239, 250, 0x0, STR_RESIZE_BUTTON }, // SNGRFS_RESIZE
{ WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_MAUVE, 0, 99, 239, 250, STR_NEWGRF_SET_PARAMETERS, STR_NULL }, // SNGRFS_SET_PARAMETERS
{ WWT_PUSHTXTBTN, RESIZE_RTB, COLOUR_MAUVE, 100, 199, 239, 250, STR_NEWGRF_TOGGLE_PALETTE, STR_NEWGRF_TOGGLE_PALETTE_TIP }, // SNGRFS_TOGGLE_PALETTE
{ WWT_PUSHTXTBTN, RESIZE_RTB, COLOUR_MAUVE, 200, 299, 239, 250, STR_NEWGRF_APPLY_CHANGES, STR_NULL }, // SNGRFS_APPLY_CHANGES
{ WWT_PUSHTXTBTN, RESIZE_RTB, COLOUR_MAUVE, 0, 287, 251, 262, STR_CONTENT_INTRO_BUTTON, STR_CONTENT_INTRO_BUTTON_TIP }, // SNGRFS_DOWNLOAD_CONTENT
{ WWT_RESIZEBOX, RESIZE_LRTB, COLOUR_MAUVE, 288, 299, 251, 261, 0x0, STR_RESIZE_BUTTON }, // SNGRFS_RESIZE
{ WIDGETS_END },
};
/* Window definition of the manage newgrfs window */
static const WindowDesc _newgrf_desc = {
WDP_CENTER, WDP_CENTER, 300, 251, 300, 251,
WDP_CENTER, WDP_CENTER, 300, 262, 300, 262,
WC_GAME_OPTIONS, WC_NONE,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
_newgrf_widgets,