src/network/newgrf_download.cpp
author terom@terom-desktop
Thu, 24 Jul 2008 16:18:29 +0300
changeset 11185 5036deb1fcaf
parent 11181 403550141f43
permissions -rw-r--r--
fix check_available_write_cb buffer-growing logic
/* $Id$ */

/** @file newgrf_download.cpp GUI to download NewGRFs. */
#include "../stdafx.h"
#include "../openttd.h"
#include "../strings_func.h"
#include "../date_func.h"
#include "../gui.h"
#include "../window_gui.h"
#include "../textbuf_gui.h"
#include "../variables.h"
#include "../newgrf.h"
#include "newgrf_download.h"
#include "core/config.h"
#include "core/game.h"
#include "../functions.h"
#include "../debug.h"
#include "../window_func.h"
#include "../core/alloc_func.hpp"
#include "../string_func.h"
#include "../gfx_func.h"
#include "../querystring_gui.h"
#include "../sortlist_type.h"
#include "../fileio.h"
#include "../rev.h"

// XXX: ...
#include <curl/curl.h>

#include "table/strings.h"
#include "../table/sprites.h"

static int _has_curl_init = 0;

static CURLcode do_init_curl () {
    CURLcode err;

    if (!_has_curl_init) {
        // XXX: be smarter about what to initialize
        if ((err = curl_global_init(CURL_GLOBAL_ALL)))
            return err;

        _has_curl_init = 1;
    }

    return CURLE_OK;
}

struct check_write_ctx {
    char *buf;
    size_t len;
    size_t offset;
};

#define QUERY_ITEM_SIZE (                   \
            4   /* the "grf=" prefix */     \
          + 8   /* the GRFID, in hex */     \
          + 1   /* the ":" */               \
          + 32  /* the md5sum, in hex */    \
          + 1   /* the "&" or '\0' */       \
        )

static size_t check_available_write_cb (void *ptr, size_t size, size_t nmemb, void *arg) {
    struct check_write_ctx *ctx = (struct check_write_ctx *) arg;
    
    // total number of bytes
    size_t len = size * nmemb, oldlen = ctx->len;
    
    // increase the size until the new data fits
    while (ctx->offset + len + 1 > ctx->len) {
        ctx->len *= 2;
    }
    
    // realloc if needed
    if (ctx->len != oldlen)
        ctx->buf = ReallocT<char>(ctx->buf, ctx->len);
    
    // thankfully we only have a limited amount of data..
    memcpy(ctx->buf + ctx->offset, ptr, len);
    ctx->offset += len;
    *(ctx->buf + ctx->offset) = '\0';

    return len;
}

/** Ask the master database about the availablity of the given NewGRFs, and update
 * the GRFConfigs that are avilable to GCS_AVAILABLE
 * @param config pointer to a linked-list of grfconfig's needed */
static int CheckAvailableNewGRFs(GRFConfig *list)
{
    GRFConfig *c;
    int i;
    char md5buf[64];
    struct check_write_ctx write_ctx;
    CURL *curl;
    char api_url[512], useragent[512];
    char curl_errbuf[CURL_ERROR_SIZE];
    long http_code;
    char *buf_ptr;
    CURLcode err;
    int ret = -1;
    int found = 0;

    // initialize to a safe state for error handling
    curl = write_ctx.buf = NULL;
    
    // count how many NewGRFs in our list
    for (c = list, i = 0; c != NULL; c = c->next, i++) {}
    
    // how many bytes these take up...
    int query_size = i * QUERY_ITEM_SIZE;
    
    // allocate the buffer for the query data
    char *query_buf = MallocT<char>(query_size), *query_ptr = query_buf;
    
    // build the query
    for (c = list; c != NULL; c = c->next) {
        
        // the "&", except not for the first entry
        if (c != list)
            *(query_ptr++) = '&';
        
        // format the md5sum
        md5sumToString(md5buf, lastof(md5buf), c->md5sum);

        // snprintf the entry into the query buffer
        assert(snprintf(query_ptr, QUERY_ITEM_SIZE, "grf=%08X:%32s", BSWAP32(c->grfid), md5buf) == QUERY_ITEM_SIZE -1);
        
        // advance the query_ptr to point at the nul byte
        query_ptr += QUERY_ITEM_SIZE - 1;
    }

    // buil the target URL by formatting NETWORK_NEWGRF_MASTER_API with the method as the only parameter
    assert(snprintf(api_url, sizeof(api_url), NETWORK_NEWGRF_MASTER_API, "search") < 512);
    
    // our useragent
    assert(snprintf(useragent, sizeof(useragent), NETWORK_HTTP_USER_AGENT, _openttd_revision) < 512);

    // receiving is so damn hard...
    write_ctx.buf = MallocT<char>(512);
    write_ctx.len = 512;
    write_ctx.offset = 0;

    // set up curl globally if needed
    if ((err = do_init_curl())) {
        DEBUG(grfdl, 0, "curl_global_init failed: %s", curl_easy_strerror(err));
        goto error;
    }
    
    // allocate a new curl_easy
    if ((curl = curl_easy_init()) == NULL) {
        DEBUG(grfdl, 0, "curl_easy_init failed");
        goto error;
    }

    // let's set some options...
    if (
            (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1)))        // XXX: tie this in with OpenTTD's debug stuff
    /*            
            curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, XXX)
    */            
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1)))             // no progress meter on stdout, thx
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf)))  // XXX: human-readable error messages
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_URL, api_url)))              // the URL to retrieve
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent)))      // the User-agent to use
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, query_buf)))     // the POST data
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &check_available_write_cb)))
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_ctx)))     // the write buffer
    ) {
        DEBUG(grfdl, 0, "curl_easy_setopt failed: %s", curl_easy_strerror(err));
        goto error;
    }

    // XXX: currently this freezes the UI..
    if ((err = curl_easy_perform(curl))) {
        DEBUG(grfdl, 0, "the curl request failed: %s", curl_easy_strerror(err));
        goto error;
    }
    
    // get the HTTP return code
    if ((err = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code))) {
        DEBUG(grfdl, 0, "curl_easy_getinfo failed: %s", curl_easy_strerror(err));
        goto error;
    }

    if (http_code != 200) {
        DEBUG(grfdl, 0, "Bad HTTP response code: %d", http_code);
        goto error;
    }

    // start parsing...
    buf_ptr = write_ctx.buf;

    while (buf_ptr < write_ctx.buf + write_ctx.offset) {
        // replace the \n with a \0
        char *nl = strchr(buf_ptr, '\n');

        if (!nl) {
            DEBUG(grfdl, 0, "Incomplete line in search response: %s", buf_ptr);
            goto error;
        }

        *nl = '\0';
        
        // check the format
        if (buf_ptr[8] != ':' || buf_ptr[8 + 1 + 32] != ':') {
            DEBUG(grfdl, 0, "Invalid line in search response: %s", buf_ptr);
            goto error;
        }

        // separate out the fields
        buf_ptr[8] = buf_ptr[8 + 1 + 32] = '\0';
        
        // parse the GRFID...
        char *invalid;
        uint32 grfid = strtol(buf_ptr + 0, &invalid, 16);

        if (*invalid) {
            DEBUG(grfdl, 0, "Invalid GRFID in search response: %s", buf_ptr);
            goto error;
        }
        
        // parse the md5sum
        uint8 md5sum[16]; 

        if (StringToMd5sum(md5sum, buf_ptr + 8 + 1)) {
            DEBUG(grfdl, 0, "Invalid md5sum in search response: %s", buf_ptr);
            goto error;
        }

        // copy the URL
        char *url = strdup(buf_ptr + 8 + 1 + 32 + 1);

        // look for the GRF
        for (c = list; c != NULL; c = c->next) {
            if (BSWAP32(c->grfid) == grfid && memcmp(c->md5sum, md5sum, 16) == 0) {
                DEBUG(grfdl, 0, "Found %08X:%s at %s", buf_ptr + 0, buf_ptr + 8 + 1, url);

                c->filename = url;
                c->status = GCS_AVAILABLE;
                ClrBit(c->status, GCF_COMPATIBLE);

                found++;

                break;
            } 
        }
        
        // free the allocated memory if it wasn't stored in a GRFConfig
        if (c == NULL) {
            free(url);
        }

        // move on to the next line
        buf_ptr = nl + 1;
    }
    
    // return number of NewGRFs found, defaults to -1 for errors
    ret = found;    

error :
    // cleanup
    free(write_ctx.buf);

    if (curl)
        curl_easy_cleanup(curl);
    
    // return as defined by ret
    return ret;
}

/** Download the NewGRFs that are listed as GCS_AVAILABLE
 * @param config pointer to a linked-list of grfconfig's to be downloaded */
static int DownloadAvailableNewGRFs(GRFConfig *list)
{
    GRFConfig *c;
    CURL *curl = NULL;
    CURLcode err;
    FILE *fh = NULL;
    char md5buf[64], filename[256], useragent[512];
    long http_code;
    int ret = -1;
    int found = 0;

    // set up curl
    if ((err = do_init_curl())) {
        DEBUG(grfdl, 0, "curl_global_init failed: %s", curl_easy_strerror(err));
        goto error;
    }
    
    // allocate a new curl_easy
    if ((curl = curl_easy_init()) == NULL) {
        DEBUG(grfdl, 0, "curl_easy_init failed");
        goto error;
    }
    
    // our useragent
    assert(snprintf(useragent, sizeof(useragent), NETWORK_HTTP_USER_AGENT, _openttd_revision) < 512);

    // let's set some options...
    if (
            (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1)))        // XXX: tie this in with OpenTTD's debug stuff
    /*            
            curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, XXX)
    */            
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1)))             // no progress meter on stdout, thx
//        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf)))  // human-readable error messages

        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent)))      // the User-agent to use

/*       
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, query_buf)))     // the POST data
        ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &check_available_write_cb)))
*/        
    ) {
        DEBUG(grfdl, 0, "curl_easy_setopt failed: %s", curl_easy_strerror(err));
        goto error;
    }
    
    // then start looping through the NewGRFs to download
    for (c = list; c != NULL; c = c->next) {
        // skip the ones that are not available
        if (c->status != GCS_AVAILABLE) 
            continue;
        
        // format the md5sum
        md5sumToString(md5buf, lastof(md5buf), c->md5sum);

        // snprintf the entry into the query buffer
        assert(snprintf(filename, sizeof(filename), "%08X-%32s.tar", BSWAP32(c->grfid), md5buf) < 256);

        // open the file
        if ((fh = FioFOpenFile(filename, "wb", CACHE_DIR)) == NULL) {
            DEBUG(grfdl, 0, "Opening local GRF file failed: %s", filename);
            goto error;
        }
        
        // set up curl
        if (
                (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_URL, c->filename)))              // the URL to retrieve
            ||  (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEDATA, fh)))                 // XXX: need to fwrite() ourself
        ) {
            DEBUG(grfdl, 0, "curl_easy_setopt failed: %s", curl_easy_strerror(err));
            goto error;
        }
        
        // XXX: currently this freezes the UI..
        if ((err = curl_easy_perform(curl))) {
            DEBUG(grfdl, 0, "the curl request failed: %s", curl_easy_strerror(err));
            goto error;
        }
        
        // get the HTTP return code
        if ((err = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code))) {
            DEBUG(grfdl, 0, "curl_easy_getinfo failed: %s", curl_easy_strerror(err));
            goto error;
        }

        if (http_code != 200) {
            DEBUG(grfdl, 0, "Bad HTTP response code: %d", http_code);
            goto error;
        }

        // OK, great
        c->status = GCS_UNKNOWN;
        ClrBit(c->status, GCF_COMPATIBLE);

        found++;
    }

    // success
    ret = found;

error:
    if (fh)
        fclose(fh);
    
    if (curl)
        curl_easy_cleanup(curl);

    return ret;    
}

// XXX: copy-pasted from newgrf_gui.cpp
static void ShowDownloadNewGRFInfo(const GRFConfig *c, uint x, uint y, uint w, uint bottom, bool show_params)
{
	char buff[256];

	if (c->error != NULL) {
		char message[512];
		if (c->error->custom_message == NULL) {
			SetDParamStr(0, c->filename);
			SetDParamStr(1, c->error->data);
			for (uint i = 0; i < c->error->num_params; i++) {
				uint32 param = 0;
				byte param_number = c->error->param_number[i];

				if (param_number < c->num_params) param = c->param[param_number];

				SetDParam(2 + i, param);
			}

			GetString(message, c->error->message, lastof(message));
		} else {
			SetDParamStr(0, c->error->custom_message);
			GetString(message, STR_JUST_RAW_STRING, lastof(message));
		}

		SetDParamStr(0, message);
		y += DrawStringMultiLine(x, y, c->error->severity, w, bottom - y);
	}

	/* Draw filename or not if it is not known (GRF sent over internet) */
	if (c->filename != NULL) {
		SetDParamStr(0, c->filename);
		y += DrawStringMultiLine(x, y, STR_NEWGRF_FILENAME, w, bottom - y);
	}

	/* Prepare and draw GRF ID */
	snprintf(buff, lengthof(buff), "%08X", BSWAP32(c->grfid));
	SetDParamStr(0, buff);
	y += DrawStringMultiLine(x, y, STR_NEWGRF_GRF_ID, w, bottom - y);

	/* Prepare and draw MD5 sum */
	md5sumToString(buff, lastof(buff), c->md5sum);
	SetDParamStr(0, buff);
	y += DrawStringMultiLine(x, y, STR_NEWGRF_MD5SUM, w, bottom - y);

	/* Show GRF parameter list */
	if (show_params) {
		if (c->num_params > 0) {
			GRFBuildParamList(buff, c, lastof(buff));
			SetDParam(0, STR_JUST_RAW_STRING);
			SetDParamStr(1, buff);
		} else {
			SetDParam(0, STR_01A9_NONE);
		}
		y += DrawStringMultiLine(x, y, STR_NEWGRF_PARAMETER, w, bottom - y);
	}

	/* Show flags */
	if (c->status == GCS_NOT_FOUND)        y += DrawStringMultiLine(x, y, STR_NEWGRF_NOT_FOUND, w, bottom - y);
	if (c->status == GCS_DISABLED)         y += DrawStringMultiLine(x, y, STR_NEWGRF_DISABLED, w, bottom - y);
	if (HasBit(c->flags, GCF_COMPATIBLE)) y += DrawStringMultiLine(x, y, STR_NEWGRF_COMPATIBLE_LOADED, w, bottom - y);

	/* Draw GRF info if it exists */
	if (c->info != NULL && !StrEmpty(c->info)) {
		SetDParam(0, STR_JUST_RAW_STRING);
		SetDParamStr(1, c->info);
		y += DrawStringMultiLine(x, y, STR_02BD, w, bottom - y);
	} else {
		y += DrawStringMultiLine(x, y, STR_NEWGRF_NO_INFO, w, bottom - y);
	}
}

/**
 * Window for downloading NewGRF files
 */
struct NewGRFDownloadWindow : public Window {
	/* Names of the add a newgrf window widgets */
	enum DownloadNewGRFWindowWidgets {
        DNGRFS_CLOSEBOX = 0,
		DNGRFS_CAPTION,
		DNGRFS_BACKGROUND,
		DNGRFS_FILE_LIST,
		DNGRFS_SCROLLBAR,
		DNGRFS_NEWGRF_INFO,
		DNGRFS_CHECK_AVAILABLE,
		DNGRFS_DOWNLOAD_AVAILABLE,
		DNGRFS_RESIZE,
	};
    
    struct NetworkGameInfo *info;   ///< the server we are downloading NewGRFs for
	GRFConfig *list;        ///< temporary grf list to which changes are made
	const GRFConfig *sel;

	NewGRFDownloadWindow(const WindowDesc *desc, struct NetworkGameInfo *info, GRFConfig *list) : Window(desc, 0)
	{
        // resize magic
		this->resize.step_height = 14;
        
        // the server info
        this->info = info;

        // the list of GRFs that we need
        this->list = list;
        
        // display window?
		this->FindWindowPlacementAndResize(desc);

        // set up our own state
		this->SetupDownloadNewGRFWindow();
	}

	~NewGRFDownloadWindow()
	{
        // Apply changes from list to the config
        GRFConfig *config, *list;

        // default to true, set back to false if we find a still-missing NewGRF
        this->info->compatible = true;
        
        for (config = this->info->grfconfig, list = this->list; config != NULL && list != NULL; config = config->next, list = list->next) {
            // skip those entries that aren't in list
            if (config->grfid == list->grfid) {
                // changed?
                if (config->status != list->status && list->status != GCS_AVAILABLE) {
                    config->status = list->status;
                    ClrBit(config->flags, GCF_COMPATIBLE);
                }
            }
            
            // is this still NOT_FOUND?
			if (config->status == GCS_NOT_FOUND) 
                this->info->compatible = false;

        }            

		/* Remove the temporary copy of grf-list used in window */
		ClearGRFConfigList(&this->list);
    }

    void SetupDownloadNewGRFWindow()
	{
		const GRFConfig *c;
		int i;
        
        // count how many NewGRFs in our list
		for (c = this->list, i = 0; c != NULL; c = c->next, i++) {}
        
        // something to do with vertical scrolling...
		this->vscroll.cap = (this->widget[DNGRFS_FILE_LIST].bottom - this->widget[DNGRFS_FILE_LIST].top) / 14 + 1;
		SetVScrollCount(this, i);
        
        // disable the "Check Available" button if there are no incompatible NewGRFs
		this->SetWidgetDisabledState(DNGRFS_CHECK_AVAILABLE, (i == 0));

        // disable the "Download Available" button until we've checked for available NewGRFs
		this->SetWidgetDisabledState(DNGRFS_DOWNLOAD_AVAILABLE, 1);
	}

    virtual void OnPaint()
	{
        // superclass widget-drawing
		this->DrawWidgets();

		/* Draw the NewGRF file list */

		int y = this->widget[DNGRFS_FILE_LIST].top;
		int i = 0;

        // for each GRF...
		for (const GRFConfig *c = this->list; c != NULL; c = c->next, i++) {

            // is scrolled into view
			if (i >= this->vscroll.pos && i < this->vscroll.pos + this->vscroll.cap) {

				SpriteID pal;
				byte txtoffset;

                // the NewGRF name
				const char *text = (c->name != NULL && !StrEmpty(c->name)) ? c->name : c->filename;

				/* 
                 * Pick a colour 
                 *
                 *  orange  - server has it, we don't, haven't checked availablity yet
                 *  red     - not available
                 *  green   - available
                 *  blue    - downloaded
                 */
				switch (c->status) {
					case GCS_NOT_FOUND:
                        pal = PALETTE_TO_ORANGE;
                        break;

					case GCS_DISABLED:
						pal = PALETTE_TO_RED;
						break;

					case GCS_AVAILABLE:
						pal = PALETTE_TO_GREEN;
						break;

					default:    // GCS_INITIALIZED or GCS_ACTIVATED
						pal = PALETTE_TO_BLUE;
						break;
				}

            /*
                 XXX: flags?
				if (pal != PALETTE_TO_RED) {
					if (HasBit(c->flags, GCF_STATIC)) {
						pal = PALETTE_TO_GREY;

					} else if (HasBit(c->flags, GCF_COMPATIBLE)) {
						pal = PALETTE_TO_ORANGE;

					}
				}
            */

                // draw the square in the chosen colour
				DrawSprite(SPR_SQUARE, pal, 5, y + 2);

                // XXX: hmm... use for download errors?
				if (c->error != NULL) DrawSprite(SPR_WARNING_SIGN, 0, 20, y + 2);

                // where to draw the text
				txtoffset = c->error != NULL ? 35 : 25;

                // draw the chosen text (GRF name/filename)
				DoDrawStringTruncated(text, txtoffset, y + 3, this->sel == c ? TC_WHITE : TC_BLACK, this->width - txtoffset - 10);

                // advance on to the next row
				y += 14;
			}
		}

		if (this->sel != NULL) {
			/* Draw NewGRF file info */
			const Widget *wi = &this->widget[DNGRFS_NEWGRF_INFO];
    
			ShowDownloadNewGRFInfo(this->sel, wi->left + 2, wi->top + 2, wi->right - wi->left - 2, wi->bottom, false);
		}
	}

	virtual void OnClick(Point pt, int widget)
	{
        int found;

		switch (widget) {
            case DNGRFS_CHECK_AVAILABLE:
                // do the HTTP request etc.
                found = CheckAvailableNewGRFs(this->list);

                if (found < 0) {
                    // failed
                    ShowErrorMessage(INVALID_STRING_ID, STR_NEWGRF_DOWNLOAD_CHECK_FAILED, 0, 0);

                } else if (found == 0) {
                    // no NewGRFs found
                    ShowErrorMessage(INVALID_STRING_ID, STR_NEWGRF_DOWNLOAD_CHECK_NONE, 0, 0);

                } else {
                    // success, flip the button enabled states
                    this->SetWidgetDisabledState(DNGRFS_CHECK_AVAILABLE, 1);
                    this->SetWidgetDisabledState(DNGRFS_DOWNLOAD_AVAILABLE, 0);
                    
                    // redraw with new status
                    this->SetDirty();
                }
				

                break;

            case DNGRFS_DOWNLOAD_AVAILABLE:
                // do a bunch of HTTP requests
                found = DownloadAvailableNewGRFs(this->list);

                if (found < 0) {
                    // failed
                    ShowErrorMessage(INVALID_STRING_ID, STR_NEWGRF_DOWNLOAD_FAILED, 0, 0);

                } else {
                    if (found == 0) {
                        // ???
                        // this shouldn't really happen, as we check for this in CHECK_AVAILABLE
                        ShowErrorMessage(INVALID_STRING_ID, STR_NEWGRF_DOWNLOAD_CHECK_NONE, 0, 0);

                    } else {
                        /* XXX: not exported, so prototype here */
                        void ScanForTarFiles ();

                        // load the newly downloaded NewGRF files
                        ScanForTarFiles();
                        ScanNewGRFFiles();
                    } 

                    // disable the button, not useful anymore
                    this->SetWidgetDisabledState(DNGRFS_DOWNLOAD_AVAILABLE, 1);
                    
                    // redraw with new status
                    this->SetDirty();
                }   
               
                break;

			case DNGRFS_FILE_LIST: { // Select a GRF
				GRFConfig *c;
				uint i = (pt.y - this->widget[DNGRFS_FILE_LIST].top) / 14 + this->vscroll.pos;

				for (c = this->list; c != NULL && i > 0; c = c->next, i--) {}
				this->sel = c;

				this->SetDirty();
				break;
			}
		}
	}

    virtual void OnResize(Point new_size, Point delta)
	{
		if (delta.x != 0) {
			ResizeButtons(this, DNGRFS_CHECK_AVAILABLE, DNGRFS_DOWNLOAD_AVAILABLE);
		}

		this->vscroll.cap += delta.y / 14;
		this->widget[DNGRFS_FILE_LIST].data = (this->vscroll.cap << 8) + 1;

		this->SetupDownloadNewGRFWindow();
	}

	virtual void OnInvalidateData(int data = 0)
	{
		this->SetupDownloadNewGRFWindow();
	}
};

/* Widget definition of the download newgrfs window */
static const Widget _newgrf_download_widgets[] = {
{   WWT_CLOSEBOX,  RESIZE_NONE, 10,   0,  10,   0,  13, STR_00C5,                    STR_018B_CLOSE_WINDOW },            // DNGRFS_CLOSEBOX
{    WWT_CAPTION, RESIZE_RIGHT, 10,  11, 299,   0,  13, STR_NEWGRF_DOWNLOAD_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS },  // DNGRFS_CAPTION
{      WWT_PANEL, RESIZE_RIGHT, 10,   0, 299,  14,  29, STR_NULL,                    STR_NULL },                         // DNGRFS_BACKGROUND

    /*
{ WWT_PUSHTXTBTN,  RESIZE_NONE,  3,  10,  79,  16,  27, STR_NEWGRF_ADD,              STR_NEWGRF_ADD_TIP },               // SNGRFS_ADD
{ WWT_PUSHTXTBTN,  RESIZE_NONE,  3,  80, 149,  16,  27, STR_NEWGRF_REMOVE,           STR_NEWGRF_REMOVE_TIP },            // SNGRFS_REMOVE
{ WWT_PUSHTXTBTN,  RESIZE_NONE,  3, 150, 219,  16,  27, STR_NEWGRF_MOVEUP,           STR_NEWGRF_MOVEUP_TIP },            // SNGRFS_MOVE_UP
{ WWT_PUSHTXTBTN, RESIZE_RIGHT,  3, 220, 289,  16,  27, STR_NEWGRF_MOVEDOWN,         STR_NEWGRF_MOVEDOWN_TIP },          // SNGRFS_MOVE_DOWN
    */

{     WWT_MATRIX,    RESIZE_RB, 10,   0, 287,  30,  99, 0x501,                       STR_NEWGRF_DOWNLOAD_LIST_TIP },     // DNGRFS_FILE_LIST
{  WWT_SCROLLBAR,   RESIZE_LRB, 10, 288, 299,  30,  99, 0x0,                         STR_0190_SCROLL_BAR_SCROLLS_LIST }, // DNGRFS_SCROLLBAR
{      WWT_PANEL,   RESIZE_RTB, 10,   0, 299, 100, 212, STR_NULL,                    STR_NULL },                         // DNGRFS_NEWGRF_INFO
{ WWT_PUSHTXTBTN,    RESIZE_TB, 10,   0, 143, 213, 224, STR_NEWGRF_CHECK_AVAILABLE,  STR_NULL },                         // DNGRFS_CHECK_AVILABLE
{ WWT_PUSHTXTBTN,   RESIZE_RTB, 10, 144, 287, 213, 224, STR_NEWGRF_DOWNLOAD_AVAILABLE,  STR_NULL },                      // DNGRFS_DOWNLOAD_AVAILABLE
{  WWT_RESIZEBOX,  RESIZE_LRTB, 10, 288, 299, 213, 224, 0x0,                         STR_RESIZE_BUTTON },                // DNGRFS_RESIZE
{ WIDGETS_END },
};

/* Window definition of the manage newgrfs window */
static const WindowDesc _newgrf_download_desc = {
	WDP_CENTER, WDP_CENTER, 300, 225, 300, 225,
	WC_GAME_OPTIONS, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
	_newgrf_download_widgets,
};


/** Setup the NewGRF download gui
 * @param config pointer to a linked-list of grfconfig's needed */
void ShowNewGRFDownload(struct NetworkGameInfo *info)
{
    // filter out those that we don't need to download
    GRFConfig *head = NULL, **item = &head;
    const GRFConfig *src;
    
    for (src = info->grfconfig; src != NULL; src = src->next) {
        if (src->status == GCS_NOT_FOUND || HasBit(src->flags, GCF_COMPATIBLE)) {
            // copy-past from newgrf_config.cpp CopyGRFConfigList
            GRFConfig *c = CallocT<GRFConfig>(1);
            *c = *src;
            c->next = NULL;

            if (src->filename  != NULL) c->filename  = strdup(src->filename);
            if (src->name      != NULL) c->name      = strdup(src->name);
            if (src->info      != NULL) c->info      = strdup(src->info);
            if (src->error     != NULL) {
                c->error = CallocT<GRFError>(1);
                memcpy(c->error, src->error, sizeof(GRFError));
                if (src->error->data != NULL) c->error->data = strdup(src->error->data);
                if (src->error->custom_message != NULL) c->error->custom_message = strdup(src->error->custom_message);
            }
            
            *item = c;
            item = &c->next;
        }
    }

	DeleteWindowByClass(WC_GAME_OPTIONS);
	new NewGRFDownloadWindow(&_newgrf_download_desc, info, head);
}