/*
 * tngp/files.c
 *
 * Copyright (C) 2022 bzt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @brief Platform dependent file functions
 *
 */

#include "tngp.h"

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#ifdef __WIN32__
/* only include these when necessary, it conflicts with SDL_mixer... */
#include <windows.h>
#include <winnls.h>
#include <fileapi.h>
#include <shlobj.h>
#include <shellapi.h>
wchar_t szFull[PATH_MAX + FILENAME_MAX + 1];
char wdname[FILENAME_MAX + 1];
struct _stat64 filest;
#define dname wdname
#else
struct stat filest;
#define dname ent->d_name
#endif

char *files_games = NULL, *files_cache = NULL, *files_config = NULL, *files_saves = NULL;

/**
 * Helper, natural comparition
 */
#define  pr_isdigit( x )   ( ( (unsigned)(x) - '0' ) < 10U )
int strnatcmp(const void *a, const void *b)
{
    const char *ca, *cb;
    int result = 0;
    for(ca = *((const char**)a), cb = *((const char**)b); *ca && *cb; ca++, cb++) {
        if(pr_isdigit(*ca) && pr_isdigit(*cb)) {
            for(result = 0; pr_isdigit(*ca) && pr_isdigit(*cb); ca++, cb++)
                if(!result) result = *ca - *cb;
            if(!pr_isdigit(*ca) && pr_isdigit(*cb)) return -1;
            if(!pr_isdigit(*cb) && pr_isdigit(*ca)) return +1;
            if(result || (!*ca && !*cb)) return result;
        }
        result = strncasecmp(ca, cb, 1);
        if(result) return result;
    }
    return *ca - *cb;
}
int files_gamecmp(const void *a, const void *b) {
    return strnatcmp(&((files_game_t*)a)->name, &((files_game_t*)b)->name);
}

/**
 * Get directories and config
 */
void files_init()
{
    char *fn, *path = (char*)main_alloc(PATH_MAX + FILENAME_MAX + FILENAME_MAX);
#ifdef __WIN32__
    int wlen;
    wchar_t *tmp = (wchar_t*)main_alloc((PATH_MAX + FILENAME_MAX) * sizeof(wchar_t));
    if(!SHGetFolderPathW(HWND_DESKTOP, CSIDL_PROFILE, NULL, 0, tmp)) {
        wlen = WideCharToMultiByte(CP_UTF8, 0, tmp, wcslen(tmp), path, PATH_MAX, NULL, NULL);
        if(wlen) {
            fn = path + strlen(path);
            strcat(fn, "\\TirNanoG");
            files_games = (char*)main_alloc(strlen(path) + 1);
            strcpy(files_games, path);
            strcpy(fn, "\\AppData"); files_mkdir(path);
            strcat(fn, "\\TirNanoG"); files_mkdir(path);
            files_config = (char*)main_alloc(strlen(path) + 1);
            strcpy(files_config, path);
            strcat(fn, "\\cache"); files_mkdir(path);
            files_cache = (char*)main_alloc(strlen(path) + 1);
            strcpy(files_cache, path);
            strcpy(fn, "\\Saved Games"); files_mkdir(path);
            strcat(fn, "\\TirNanoG"); files_mkdir(path);
            files_saves = (char*)main_alloc(strlen(path) + 1);
            strcpy(files_saves, path);
        }
    }
    free(tmp);
#elif defined(__ANDROID__)
    strcpy(path, SDL_AndroidGetExternalStoragePath());
    fn = path + strlen(path);
    files_config = (char*)main_alloc(strlen(path) + 1);
    strcpy(files_config, path);
    strcpy(fn, "/saves"); files_mkdir(path);
    files_saves = (char*)main_alloc(strlen(path) + 1);
    strcpy(files_saves, path);
    strcpy(fn, "/../cache"); files_mkdir(path);
    files_cache = (char*)main_alloc(strlen(path) + 1);
    strcpy(files_cache, path);
    strcpy(fn, "/../../../../Download"); files_mkdir(path);
    files_games = (char*)main_alloc(strlen(path) + 1);
    strcpy(files_games, path);
#elif !defined(__EMSCRIPTEN__)
    char *tmp = getenv("HOME");
    if(tmp) {
        strcpy(path, tmp);
        fn = path + strlen(path);
        strcat(fn, "/TirNanoG");
        files_games = (char*)main_alloc(strlen(path) + 1);
        strcpy(files_games, path);
        strcpy(fn, "/.cache"); files_mkdir(path);
        strcat(fn, "/TirNanoG"); files_mkdir(path);
        files_cache = (char*)main_alloc(strlen(path) + 1);
        strcpy(files_cache, path);
        strcpy(fn, "/.config"); files_mkdir(path);
        strcat(fn, "/TirNanoG"); files_mkdir(path);
        files_config = (char*)main_alloc(strlen(path) + 1);
        strcpy(files_config, path);
        strcat(fn, "/saves"); files_mkdir(path);
        files_saves = (char*)main_alloc(strlen(path) + 1);
        strcpy(files_saves, path);
    }
#endif
    free(path);
    if(verbose > 1) {
        printf("%s: GAMESDIR     '%s'\r\n", main_me, GAMESDIR);
        printf("%s: files_games  '%s'\r\n", main_me, files_games);
        printf("%s: files_cache  '%s'\r\n", main_me, files_cache);
        printf("%s: files_config '%s'\r\n", main_me, files_config);
        printf("%s: files_saves  '%s'\r\n", main_me, files_saves);
    }
}

/**
 * Free resources
 */
void files_free()
{
    if(files_games) { free(files_games); files_games = NULL; }
    if(files_cache) { free(files_cache); files_cache = NULL; }
    if(files_config) { free(files_config); files_config = NULL; }
    if(files_saves) { free(files_saves); files_saves = NULL; }
}

/**
 * Check if file exists
 */
int files_exists(char *path)
{
#ifdef __WIN32__
    MultiByteToWideChar(CP_UTF8, 0, path, -1, szFull, PATH_MAX);
    return !_wstat64(szFull, &filest);
#else
    return !stat(path, &filest);
#endif
}

/**
 * Get file size
 */
uint64_t files_size(char *path)
{
    filest.st_size = 0;
    files_exists(path);
    return S_ISREG(filest.st_mode) ? (uint64_t)filest.st_size : 0;
}

/**
 * Make a directory
 */
void files_mkdir(char *path)
{
#ifdef __WIN32__
    MultiByteToWideChar(CP_UTF8, 0, path, -1, szFull, PATH_MAX);
    _wmkdir(szFull);
#else
    mkdir(path, 0755);
#endif
}

/**
 * Open a file
 */
FILE *files_open(const char *fn, const char *mode)
{
    FILE *f = NULL;
#ifdef __EMSCRIPTEN__
    (void)fn; (void)mode;
    EM_ASM({tngoffset = 0;});
#else
#ifdef __WIN32__
    int i;
    wchar_t wmode[8];

    memset(wmode, 0, sizeof(wmode));
    MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, 8);
    MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFull, PATH_MAX);
    for(i = 0; szFull[i]; i++)
        if(szFull[i] == L'/') szFull[i] = L'\\';
    f = _wfopen(szFull, wmode);
#else
    f = fopen(fn, mode);
#endif
#endif
    return f;
}

/**
 * Seek in a file
 */
void files_seek(FILE *f, int64_t offs)
{
#ifdef __EMSCRIPTEN__
    (void)f;
    EM_ASM_({tngoffset = $1 * 2147483648 + $0;}, (int)(offs & 0x7fffffff), (int)((offs >> 31) & 0x7fffffff));
#else
# ifdef __WIN32__
    fpos_t pos = (fpos_t)offs;
# else
    fpos_t pos = {0};
    pos.__pos = offs;
# endif
    if(f) fsetpos(f, &pos);
#endif
}

#ifdef __EMSCRIPTEN__
EM_ASYNC_JS(int, emscriptmadness, (void *buf, int size), {
    if(tngfile == undefined) return 0;
    /* today youngsters are not aware how terrible and awefully inefficient this is... */
    var blob = tngfile.slice(tngoffset, tngoffset + size);
    /* copy #1: from resolved promise to ret */
    var ret = await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsArrayBuffer(blob);
    });
    if(ret == undefined || ret.byteLength < 1) return 0;
    /* copy #2: from arraybuffer to uint8array */
    var u8data = new Uint8Array(ret, 0, ret.byteLength);
    /* copy #3: from uint8array to wasm heap... */
    Module.HEAPU8.set(u8data, buf);
    tngoffset += ret.byteLength;
    return ret.byteLength;
});
#endif

/**
 * Read data from file
 */
int files_read(FILE *f, void *buf, int size)
{
    if(!buf || size < 1) return 0;
#ifdef __EMSCRIPTEN__
    (void)f;
    return emscriptmadness(buf, size);
#else
    return f ? fread(buf, 1, size, f) : 0;
#endif
}

/**
 * Read directories for projects
 */
char **files_getdir(char *path, char *ext, int *num)
{
#ifdef __EMSCRIPTEN__
    printf("WebAssembly has no clue what a directory is! Do not call files_getdir()\n");
    return NULL;
#else
#ifdef __WIN32__
    WIN32_FIND_DATAW ffd;
    HANDLE h;
#else
    DIR *dir;
    struct dirent *ent;
#endif
    FILE *f;
    int n = 0, l, d, e = ext ? strlen(ext) : 0;
    char **ret, *fn, *fn2, data[16];

    if(num) *num = 0;
    if(!path || !*path) return NULL;
    ret = (char**)malloc(sizeof(char*));
    if(!ret) return NULL;
    ret[0] = NULL;
    l = strlen(path);
    fn = (char*)malloc(l + 2 + FILENAME_MAX);
    if(!fn) { free(ret); return NULL; }
    memcpy(fn, path, l); fn2 = fn + l;
    if(fn2[-1] != SEP[0]) *fn2++ = SEP[0];
#ifdef __WIN32__
    strcpy(fn2, "*.*");
    MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFull, PATH_MAX);
    h = FindFirstFileW(szFull, &ffd);
    if(h != INVALID_HANDLE_VALUE) {
        do {
            if(ffd.cFileName[0] == L'.') continue;
            wdname[WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, wcslen(ffd.cFileName), wdname, FILENAME_MAX, NULL, NULL)] = 0;
            strcpy(fn2, wdname);
            MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFull, PATH_MAX);
            if(_wstat64(szFull, &filest)) continue;
#else
    if((dir = opendir(path)) != NULL) {
        while ((ent = readdir(dir)) != NULL) {
            if(ent->d_name[0] == '.') continue;
            strcpy(fn2, ent->d_name);
            if(stat(fn, &filest)) continue;
#endif
            if((!ext && !S_ISDIR(filest.st_mode))) continue;
            l = strlen(dname);
            if(ext) {
                if(l < e || strcmp(dname + l - e, ext)) continue;
                if(l == 8 && e == 3 && !strcmp(ext, "tng") && !strcmp(dname, "game.tng")) continue;
            } else {
                strcpy(fn2 + l, SEP "game.tng");
                memset(data, 0, sizeof(data)); d = 0;
                f = files_open(fn, "rb");
                if(f) {
                    d = fread(data, 1, 16, f);
                    fclose(f);
                }
                if(d != 16 || memcmp(data, TNG_MAGIC, 16)) continue;
            }
            ret = (char**)realloc(ret, (n+2)*sizeof(char*));
            if(!ret) {
#ifdef __WIN32__
                FindClose(h);
#else
                closedir(dir);
#endif
                free(fn); return NULL;
            }
            ret[n] = ret[n + 1] = NULL;
            ret[n] = malloc(l + 1);
            if(ret[n]) { strcpy(ret[n], dname); ret[++n] = NULL; }
#ifdef __WIN32__
        } while(FindNextFileW(h, &ffd) != 0);
        FindClose(h);
#else
        }
        closedir(dir);
#endif
        qsort(ret, n, sizeof(char*), strnatcmp);
        if(num) *num = n;
    }
    free(fn);
    return ret;
#endif
}

/**
 * Free a directory list
 */
void files_freedir(char ***list, int *num)
{
    int i;

    if(list && *list) {
        for(i = 0; (!num || i < *num) && (*list)[i]; i++) free((*list)[i]);
        free(*list);
        *list = NULL;
    }
    if(num) *num = 0;
}

/**
 * Load an entire file into memory, mostly used for configs
 */
char *files_readfile(char *fn, int *size)
{
    FILE *f;
    char *ret = NULL;
    int s = 0;

    if(!fn || !*fn) return NULL;
    f = files_open(fn, "rb");
    if(f) {
        fseek(f, 0L, SEEK_END);
        s = (int)ftell(f);
        fseek(f, 0L, SEEK_SET);
        ret = (char*)malloc(s + 1);
        if(ret) {
            /* do not use files_read here */
            s = (int)fread(ret, 1, s, f);
            ret[s] = 0;
        } else
            s = 0;
        fclose(f);
    }
    if(size) *size = s;
    return ret;
}

/**
 * Requred by mbedtls
 */
int mbedtls_hardware_poll( void *data, unsigned char *output, size_t len, size_t *olen )
{
    size_t i;
    unsigned char t = (unsigned char)time(NULL);
    (void) data;
    for( i = 0; i < len; ++i )
        output[i] = rand() ^ t;
    *olen = len;
    return( 0 );
}

/**
 * File abstraction for opening files over SSL
 */
files_t *files_ssl_open(char *url, char *method, char *param, char *add, char **cookie)
{
    files_t *f;
    unsigned char *dst, *end = NULL;
    char *hostname = NULL, *path = NULL, *req = NULL, portstr[16], *s, *d, *cook = NULL;
    int port = 443, r, cnt, l, redir = 8;
    mbedtls_entropy_context entropy;

    if(!url || !*url) return NULL;
    while(url && *url == ' ') url++;
    f = (files_t*)malloc(sizeof(files_t));
    if(!f) return NULL;
    memset(f, 0, sizeof(files_t));
    f->ilen = 1024 * 1024;
    if(memcmp(url, "https://", 8)) {
        f->f = files_open(url, "rb");
        if(f->f) {
            f->filesize = files_size(url);
            f->buf = (unsigned char*)realloc(f->buf, f->ilen + 1);
            return f;
        }
        free(f);
        return NULL;
    }
    if(!cookie) cookie = &cook;

again:
    mbedtls_entropy_init( &entropy );
    mbedtls_ctr_drbg_init( &f->ctr_drbg );
    mbedtls_entropy_add_source(&entropy, mbedtls_hardware_poll, NULL, 32, MBEDTLS_ENTROPY_SOURCE_STRONG);
    mbedtls_ctr_drbg_seed(&f->ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*)"salt", 5);
    mbedtls_entropy_free( &entropy );
    port = 443;
    l = strlen(url);
    hostname = realloc(hostname, l);
    if(!hostname) goto err2;
    path = realloc(path, l);
    if(!path) goto err2;
    for(s = url + 8, d = hostname; *s && *s != ':' && *s !='/'; s++)
        *d++ = *s;
    *d = 0;
    if(*s == ':') { port = atoi(s + 1); if(port > 65534) { port = 65534; } while(*s && *s != '/') s++; }
    if(*s == '/') s++;
    for(d = path; *s > ' ' && *s != '#'; )
        *d++ = *s++;
    *d = 0;
    sprintf(portstr, "%u", port);
    mbedtls_net_init( &f->server_fd );
    mbedtls_ssl_init( &f->ssl );
    mbedtls_ssl_config_init( &f->conf );

    if(mbedtls_net_connect( &f->server_fd, hostname, portstr, MBEDTLS_NET_PROTO_TCP, NULL, NULL, NULL ) != 0 ||
        mbedtls_ssl_config_defaults( &f->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM,
        MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
err:        mbedtls_net_free( &f->server_fd );
            mbedtls_ssl_free( &f->ssl );
            mbedtls_ssl_config_free( &f->conf );
err2:       if(f) {
                if(f->buf) free(f->buf);
                free(f);
            }
            if(hostname) free(hostname);
            if(path) free(path);
            if(cook) free(cook);
            return NULL;
    }
    if(verbose > 1) printf("%s: connection %s:%u ok\r\n", main_me, hostname, port);
    mbedtls_ssl_conf_authmode( &f->conf, MBEDTLS_SSL_VERIFY_OPTIONAL );
    mbedtls_ssl_conf_rng( &f->conf, mbedtls_ctr_drbg_random, &f->ctr_drbg );
    if(mbedtls_ssl_setup( &f->ssl, &f->conf ) != 0 || mbedtls_ssl_set_hostname( &f->ssl, hostname ) != 0) goto err;
    mbedtls_ssl_set_bio( &f->ssl, &f->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL );

    for(cnt = 100; ( r = mbedtls_ssl_handshake( &f->ssl ) ) != 0; cnt-- ) {
        if((r != MBEDTLS_ERR_SSL_WANT_READ && r != MBEDTLS_ERR_SSL_WANT_WRITE) || cnt < 1) { printf("r %x\n",-r); goto err; }
    }
    if(verbose > 1) printf("%s: handshake ok\r\n", main_me);

    req = malloc(256 + strlen(hostname) + strlen(path) + (param ? strlen(param) : 0) + (cookie && *cookie ? strlen(*cookie) : 0) +
        (add ? strlen(add) : 0));
    if(!req) goto err;
    l = sprintf(req, "%s /%s%s%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Unknown; rv:41.0) Gecko/41.0 Firefox/41.0\r\n"
        "Accept: */*\r\nConnection: close\r\n%s%s%s%s\r\n%s", method, path, method[0] != 'P' && param ? (strchr(path, '?') ? "&" : "?") : "",
        method[0] != 'P' && param ? param : "", hostname, cookie && *cookie ? "Cookie: " : "", cookie && *cookie ? *cookie : "",
        cookie && *cookie ? "\r\n" : "", add ? add : "", method[0] == 'P' && param ? param : "");
    free(hostname); hostname = NULL;
    free(path); path = NULL;
    for(cnt = 100; ( r = mbedtls_ssl_write(&f->ssl, (const unsigned char*)req, l) ) <= 0; cnt-- ) {
        if((r != MBEDTLS_ERR_SSL_WANT_READ && r != MBEDTLS_ERR_SSL_WANT_WRITE) || cnt < 1) { free(req); goto err; }
    }
    free(req);

    f->buf = dst = malloc(16385);
    if(!f->buf) goto err;

    end = NULL;
    do {
        *dst = 0;
        l = mbedtls_ssl_read( &f->ssl, dst, 16384 );
        if(l == MBEDTLS_ERR_SSL_WANT_READ || l == MBEDTLS_ERR_SSL_WANT_WRITE) continue;
        if(!l || l == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) break;
        if(l < 0 ) goto err;
        if(verbose > 1 && dst == f->buf) printf("%s: response '%c%c%c%c%c%c%c%c%c%c%c%c'\r\n", main_me,
            dst[0], dst[1], dst[2], dst[3], dst[4], dst[5], dst[6], dst[7], dst[8], dst[9], dst[10], dst[11]);
        (f->olen) += l;
        dst -= (uintptr_t)(f->buf);
        f->buf = realloc(f->buf, (f->olen) + 16385);
        if(!f->buf) goto err;
        dst += (uintptr_t)(f->buf) + l;
        if(!end) {
            end = (unsigned char*)strstr((const char*)(f->buf), "\r\n\r\n");
            if(end) {
                *end = 0;
                if(cookie)
                    for(s = (char*)(f->buf); s + 14 < (char*)end && *s; s++)
                        if(!memcmp(s, "\nSet-Cookie:", 12)) {
                            for(s += 12; *s == ' '; s++);
                            for(d = s; *d && *d != '\r' && *d != '\n' && *d != ';'; d++);
                            l = (cookie && *cookie) ? strlen(*cookie) : 0;
                            *cookie = (char*)realloc(*cookie, l + (d - s) + 3);
                            if(*cookie) {
                                if(l) { memcpy(*cookie + l, "; ", 2); l += 2; }
                                memcpy(*cookie + l, s, d - s);
                                (*cookie)[l + (d - s)] = 0;
                            }
                        }
                l = dst - (end + 4);
                if((dst = (unsigned char*)strstr((const char*)(f->buf), "Content-Length: ")))
                    f->filesize = (size_t)atol((char*)dst + 16);
                else if((dst = (unsigned char*)strstr((const char*)(f->buf), "Content-length: ")))
                    f->filesize = (size_t)atol((char*)dst + 16);
                if(verbose == 2) printf("%s: content length %lu\r\n", main_me, (unsigned long)f->filesize);
                if(verbose > 2) printf("%s: response headers\r\n%s\r\n", main_me, f->buf);
                dst = (f->buf);
                while(dst < end && *dst && *dst != '\n' && *dst != ' ') dst++;
                if(!*dst || memcmp(dst, " 200", 4)) { end = NULL; break; }
                memcpy(f->buf, end + 4, l);
                f->olen = l;
                break;
            }
        }
    } while(1);
    if(!end && f->buf) {
        /* handle http redirects */
        end = (unsigned char*)strstr((const char*)(f->buf), "Location: ");
        if(end && --redir) {
            for(url = (char*)end + 10; *url == ' '; url++);
            for(end = (unsigned char*)url; *end && *end != '\r' && *end != '\n'; end++);
            *end = 0;
            f->olen = f->filesize = 0;
            mbedtls_ssl_close_notify( &f->ssl );
            mbedtls_net_free( &f->server_fd );
            mbedtls_ssl_free( &f->ssl );
            mbedtls_ssl_config_free( &f->conf );
            goto again;
        }
        goto err;
    }
    f->pos = f->olen;
    f->buf = (unsigned char*)realloc(f->buf, f->ilen + 1);
    if(!f->buf) goto err;
    if(cook) free(cook);
    return f;
}

/**
 * File abstraction for reading files over SSL
 */
int files_ssl_read(files_t *f)
{
    unsigned char *dst;
    int l;

    if(!f || !f->buf || !f->ilen) return 0;
    /* tricky, because we might have already read a bit of file data while reading in the headers in files_ssl_open... */
    dst = f->buf + f->olen;
    do {
        *dst = 0;
        l = f->ilen - f->olen;
        if(!l) break;
        if(l > 16384) l = 16384;
        if(f->f) {
            l = fread(dst, 1, l, f->f);
        } else {
            l = mbedtls_ssl_read( &f->ssl, dst, l );
            if(l == MBEDTLS_ERR_SSL_WANT_READ || l == MBEDTLS_ERR_SSL_WANT_WRITE) continue;
        }
        if(l < 0 || l == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { f->ilen = 0; return 1; }
        if(!l) break;
        f->pos += l;
        f->olen += l;
        dst += l;
    } while(1);
    f->buf[f->olen] = 0;
    return 1;
}

/**
 * File abstraction for closing files over SSL
 */
int files_ssl_close(files_t *f)
{
    if(!f) return 0;
    if(f->buf) free(f->buf);
    if(f->f) {
        fclose(f->f);
    } else {
        mbedtls_ssl_close_notify( &f->ssl );
        mbedtls_net_free( &f->server_fd );
        mbedtls_ssl_free( &f->ssl );
        mbedtls_ssl_config_free( &f->conf );
        mbedtls_ctr_drbg_free( &f->ctr_drbg );
    }
    memset(f, 0, sizeof(files_t));
    free(f);
    return 1;
}

/**
 * Get list of locally installed games
 */
files_game_t *files_getgames(int *num)
{
    stbi__context sc;
    stbi__result_info ri;
    files_game_t *ret = NULL;
    int i, j, l1 = 0, l2 = 0;
    uint8_t *data;
    char *path;
    char **list1 = files_getdir(GAMESDIR, NULL, &l1);
    char **list2 = files_getdir(files_games, NULL, &l2);

    if(num) *num = 0;
    if(l1 + l2 < 1) return NULL;
    ret = (files_game_t*)main_alloc((l1 + l2) * sizeof(files_game_t));

    if(list1) {
        for(i = 0; i < l1; i++) {
            ret[i].gameid = list1[i];
            ret[i].name = (char*)main_alloc(strlen(list1[i]) + 1);
            strcpy(ret[i].name, list1[i]);
            ret[i].url = (char*)main_alloc(strlen(GAMESDIR) + strlen(list1[i]) + 16);
            sprintf(ret[i].url, "%s" SEP "%s" SEP "game.tng", GAMESDIR, list1[i]);
        }
        free(list1);
    }
    if(list2) {
        for(i = 0; i < l2; i++) {
            ret[i + l1].gameid = list2[i];
            ret[i + l1].name = (char*)main_alloc(strlen(list2[i]) + 1);
            strcpy(ret[i + l1].name, list2[i]);
            ret[i + l1].url = (char*)main_alloc(strlen(files_games) + strlen(list2[i]) + 16);
            sprintf(ret[i + l1].url, "%s" SEP "%s" SEP "game.tng", files_games, list2[i]);
        }
        free(list2);
    }
    if(num) *num = l1 + l2;
    i = strlen(GAMESDIR); j = strlen(files_games); if(i > j) j = i;
    path = (char*)main_alloc(j + 32);
    for(i = 0; i < l1 + l2; i++) {
        sprintf(path, "%s" SEP "%s" SEP "preview.png", i < l1 ? GAMESDIR : files_games, ret[i].gameid);
        data = (uint8_t*)files_readfile(path, &j);
        if(data) {
            memset(&sc, 0, sizeof(sc));
            memset(&ri, 0, sizeof(ri));
            sc.img_buffer = sc.img_buffer_original = data;
            sc.img_buffer_end = sc.img_buffer_original_end = data + j;
            ri.bits_per_channel = 8;
            ret[i].img = (uint8_t*)stbi__png_load(&sc, &ret[i].w, &ret[i].h, &j, 4, &ri);
            free(data);
        }
    }
    free(path);
    qsort(ret, l1 + l2, sizeof(files_game_t), files_gamecmp);
    return ret;
}

/**
 * Free games list
 */
void files_freegames(files_game_t **games, int *num)
{
    int i;

    if(!games || !*games || !num) return;
    for(i = 0; i < *num; i++) {
        if((*games)[i].gameid) free((*games)[i].gameid);
        if((*games)[i].name) free((*games)[i].name);
        if((*games)[i].url) free((*games)[i].url);
        if((*games)[i].png) free((*games)[i].png);
        if((*games)[i].img) free((*games)[i].img);
    }
    free(*games); *games = NULL; *num = 0;
}
