/*
 * tngp/launcher.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 Game launcher, world chooser functions
 *
 */

#ifdef __EMSCRIPTEN__
void launcher_init() { }
void launcher_free() { }
int  launcher_ctrl() { return 1; }
void launcher_view() { }
#else
#include "tngp.h"

#include <errno.h>
#ifdef __WIN32__
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#endif

#define LAUNCHER_LIST "https://tirnanog.codeberg.page/games.json"

SDL_Texture *launcher_tabs = NULL, *launcher_err = NULL;
files_game_t *launcher_list[3] = { 0 };
int launcher_num[3] = { 0 }, launcher_sel, launcher_y, launcher_oy, launcher_clk, launcher_p = -1, launcher_tab = 0;
int launcher_udpsock = 0;

/**
 * Draw a progress bar
 */
void launcher_progress(files_t *f, int *last, SDL_Texture *progress)
{
    SDL_Rect rect;
    uint32_t *dst;
    int i, j, p;

    if(f && last && progress) {
        j = win_w * f->pos / (f->filesize + 1); if(j > win_w) j = win_w;
        if(*last != j) {
            *last = j;
            SDL_LockTexture(progress, NULL, (void**)&dst, &p);
            for(i = 0; i < j ; i++)
                dst[i] = dst[p/4 + i] = dst[2*(p/4) + i] = dst[3*(p/4) + i] = dst[4*(p/4) + i] =
                dst[5*(p/4) + i] = dst[6*(p/4) + i] = dst[7*(p/4) + i] = 0xff600000;
            SDL_UnlockTexture(progress);
            rect.x = 0; rect.y = win_h - 8; rect.w = win_w; rect.h = 8;
            SDL_RenderCopy(renderer, progress, NULL, &rect);
            SDL_RenderPresent(renderer);
        }
    }
}

/**
 * Download game from official server
 */
char *launcher_download(files_game_t *game)
{
#define FBUFSIZE 1024 * 1024
    zip_local_t *loc = NULL;
    z_stream zstrm;
    SDL_Texture *progress = NULL;
    uint8_t *src, *s, *e, *buf, *in = NULL, *out = NULL;
    uint16_t method;
    uint32_t *dst;
    uint64_t orig, comp, len, o;
    int i, p, last = -1, ulen;
    char *path = NULL, *fn, *bn;
    files_t *f;
    FILE *g = NULL;

    if(!game || !game->gameid || !*game->gameid || !game->url || !*game->url) return NULL;
    f = files_ssl_open(game->url, "GET", NULL, NULL, NULL);
    if(f) {
        i = strlen(GAMESDIR); p = files_games && *files_games ? strlen(files_games) : 0;
        path = (char*)main_alloc((i > p ? i : p) + 18 + FILENAME_MAX);
        fn = path + sprintf(path, "%s" SEP "%s", GAMESDIR, game->gameid);
        files_mkdir(path);
        if(!files_exists(path) && files_games && *files_games) {
            fn = path + sprintf(path, "%s" SEP "%s", files_games, game->gameid);
            files_mkdir(files_games);
            files_mkdir(path);
        }
        if(!files_exists(path)) {
            if(verbose) printf("tngp: unable to create directory '%s'\r\n", path);
            files_ssl_close(f);
            return NULL;
        }
        *fn++ = SEP[0];
        if(verbose) printf("tngp: downloading '%s' to '%s'\r\n", game->url, path);
        if(game->png && game->s > 0) {
            strcpy(fn, "preview.png");
            g = files_open(path, "wb");
            if(g) {
                if(fwrite(game->png, 1, game->s, g) != (size_t)game->s && verbose) printf("tngp: unable to save '%s'\r\n", path);
                fclose(g);
                g = NULL;
            }
        }
        progress = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, 8);
        if(progress) {
            SDL_LockTexture(progress, NULL, (void**)&dst, &p);
            for(i = 0; i < p/4*8; i++) dst[i] = 0xff000000;
            SDL_UnlockTexture(progress);
        }
        out = (uint8_t*)main_alloc(FBUFSIZE);
        in = (uint8_t*)main_alloc(2 * FBUFSIZE);
        while(files_ssl_read(f)) {
            if(!f->buf) { files_ssl_close(f); goto err; }
            launcher_progress(f, &last, progress);
            for(src = f->buf; src < f->buf + f->olen && SDL_SwapLE32(((zip_local_t*)src)->magic) == ZIP_LOCAL_MAGIC;) {
                loc = (zip_local_t*)src;
                if(src + sizeof(zip_local_t) >= f->buf + f->olen ||
                  src + sizeof(zip_local_t) + SDL_SwapLE16(loc->fnlen) + SDL_SwapLE16(loc->extlen) >= f->buf + f->olen) {
                    f->olen -= (int)((uintptr_t)src - (uintptr_t)f->buf);
                    memmove(f->buf, src, f->olen);
                    if(!files_ssl_read(f)) break;
                    launcher_progress(f, &last, progress);
                    src = f->buf; loc = (zip_local_t*)src;
                }
                orig = SDL_SwapLE32(loc->orig); comp = SDL_SwapLE32(loc->comp); method = SDL_SwapLE16(loc->method);
                for(s = src + sizeof(zip_local_t) + SDL_SwapLE16(loc->fnlen), e = s + SDL_SwapLE16(loc->extlen); s < e;
                  s += ((s[3] << 8) | s[2]) + 4)
                    if(((s[1] << 8) | s[0]) == ZIP_EXTZIP64_MAGIC) {
                        comp = SDL_SwapLE64(((zip_ext64_t *)s)->comp);
                        orig = SDL_SwapLE64(((zip_ext64_t *)s)->orig); break;
                    }
                i = sizeof(zip_local_t) + SDL_SwapLE16(loc->fnlen);
                if((SDL_SwapLE16(loc->fnlen) > 4 && !memcmp(src + i - 4, ".tng", 4)) ||
                  (SDL_SwapLE16(loc->fnlen) > 4 && !memcmp(src + i - 4, ".lnk", 4)) ||
                  (SDL_SwapLE16(loc->fnlen) > 8 && !memcmp(src + i - 8, ".desktop", 4))) {
                    for(bn = (char*)src + i - 1; bn > (char*)src + sizeof(zip_local_t) && *bn != '/'; bn--);
                    if(*bn == '/') bn++;
                    i = (int)((uintptr_t)src + i - (uintptr_t)bn);
                    if(i > 0) {
                        memcpy(fn, bn, i); fn[i] = 0;
                        g = files_open(path, "wb");
                        if(g) {
                            if(verbose)
                                printf("tngp: extracting '%s' (compressed %lu, uncompressed %lu)\r\n", fn,
                                    (unsigned long)comp, (unsigned long)orig);
                        } else {
                            if(verbose)
                                printf("tngp: unable to save '%s'\r\n", path);
                        }
                    }
                }
                memset(&zstrm, 0, sizeof(zstrm));
                if(inflateInit2(&zstrm, -MAX_WBITS) != Z_OK) {
                    if(verbose) printf("tngp: zlib init error\r\n");
                    if(g) { fclose(g); g = NULL; }
                }
                zstrm.next_out = out; zstrm.avail_out = FBUFSIZE; o = 0;
                buf = src = src + sizeof(zip_local_t) + SDL_SwapLE16(loc->fnlen) + SDL_SwapLE16(loc->extlen);
                while(comp > 0 && f->olen > 0) {
                    len = f->olen - (int)((uintptr_t)buf - (uintptr_t)f->buf);
                    if(len > comp) len = comp;
                    if(g) {
                        switch(method) {
                            case ZIP_DATA_STORE:
                                if(fwrite(buf, 1, len, g) != len && verbose)
                                    printf("tngp: error writing file\r\n");
                            break;
                            case ZIP_DATA_DEFLATE:
                                if(zstrm.avail_in > 0) {
                                    memcpy(in + zstrm.avail_in, buf, len);
                                    zstrm.next_in = in;
                                    zstrm.avail_in += len;
                                } else {
                                    zstrm.next_in = buf;
                                    zstrm.avail_in = len;
                                }
                                i = inflate(&zstrm, comp > len ? Z_NO_FLUSH : Z_FINISH);
                                if(verbose && i != Z_OK && i != Z_STREAM_END)
                                    printf("tngp: zlib inflate error %d\r\n", i);
                                if(zstrm.avail_in > 0)
                                    memcpy(in, zstrm.next_in, zstrm.avail_in);
                                ulen = FBUFSIZE - zstrm.avail_out;
                                if(ulen > 0) {
                                    o += ulen;
                                    if(fwrite(out, 1, ulen, g) != (size_t)ulen && verbose)
                                        printf("tngp: error writing file\r\n");
                                    zstrm.next_out = out; zstrm.avail_out = FBUFSIZE;
                                }
                            break;
                            default:
                                if(verbose) printf("tngp: unknown compression method %04X\r\n", method);
                            break;
                        }
                    }
                    comp -= len;
                    src = buf + len;
                    if(buf + len >= f->buf + f->olen) {
                        f->olen = 0;
                        buf = f->buf;
                        if(!files_ssl_read(f)) break;
                        launcher_progress(f, &last, progress);
                    }
                }
                if(g) {
                    fclose(g); g = NULL;
                    if(verbose && o != orig)
                        printf("tngp: zlib inflate error, uncompressed sizes don't match %lu != %lu\r\n",
                            (unsigned long)o, (unsigned long)orig);
                }
                inflateEnd(&zstrm);
            }
            if(src >= f->buf + f->olen || SDL_SwapLE32(((zip_local_t*)src)->magic) != ZIP_LOCAL_MAGIC) break;
            f->olen = 0;
        }
        files_ssl_close(f);
        strcpy(fn, "game.tng");
        if(files_size(path) > 0) {
            if(in) free(in);
            if(out) free(out);
            if(progress) SDL_DestroyTexture(progress);
            return path;
        }
    } else
    if(verbose) printf("tngp: unable to download '%s'\r\n", game->url);
err:if(path) free(path);
    if(in) free(in);
    if(out) free(out);
    if(progress) SDL_DestroyTexture(progress);
    launcher_err = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, 40);
    if(launcher_err) {
        SDL_LockTexture(launcher_err, NULL, (void**)&dst, &p);
        for(i = 0; i < p/4*40; i++) dst[i] = 0xff000040;
        SDL_UnlockTexture(launcher_err);
        ssfn_dst.fg = 0xffffffff; i = scr_w; scr_w = win_w;
        main_text(launcher_err, (win_w - main_textwidth(lang[ERR_DOWNLOAD])) / 2, 13, lang[ERR_DOWNLOAD]);
        scr_w = i;
    }
    return NULL;
}

/**
 * Redraw
 */
void launcher_redraw(int selonly)
{
    char *s;
    int i, j, k, p, q, u, w;
    uint32_t *dst, *src;

    if(scroll.y + scroll.h > scr_h) scroll.y = scr_h - scroll.h;
    if(scroll.y < 0) scroll.y = 0;
    if(launcher_tabs) {
        SDL_SetTextureBlendMode(launcher_tabs, SDL_BLENDMODE_BLEND);
        SDL_LockTexture(launcher_tabs, NULL, (void**)&dst, &p);
        SDL_LockTexture(logo, NULL, (void**)&src, &q);
        memset(dst, 0, p * 42);
        j = (win_w - 4 * 16) / 3; k = 16;
        for(i = 0; i < 3; i++, k += j + 16) {
            ui_keybox(dst, p, k, 0, j, 40, " ", launcher_p == i ? 1 : (launcher_tab == i ? 2 : 0), launcher_p == i);
            s = lang[LAUNCHER_LOCAL + i];
            ssfn_dst.ptr = (uint8_t*)dst;
            ssfn_dst.w = win_w;
            ssfn_dst.h = 40;
            ssfn_dst.p = p;
            ssfn_dst.x = k + (j - main_textwidth(s) - 24) / 2;
            ssfn_dst.y = 12;
            ui_blit(dst, 16, 16, p, ssfn_dst.x, 12, src, 352 + i * 16, 160, 16, 16, q, 0);
            ssfn_dst.x += 24;
            while(s && *s) {
                u = ssfn_utf8(&s);
                if(ssfn_dst.x >= 0 && ssfn_dst.x < k + j)
                    ssfn_putc(u);
            }
        }
        SDL_UnlockTexture(logo);
        SDL_UnlockTexture(launcher_tabs);
    }
    if(!screen) return;
    if(launcher_list[launcher_tab] && launcher_num[launcher_tab] > 0) {
        SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
        memset(scr_data, 0, scr_p * scr_h);
        for(j = selonly ? launcher_sel : 0; j < (selonly ? launcher_sel + 1 : launcher_num[launcher_tab]); j++) {
            dst = scr_data + j * 70 * scr_p / 4;
            for(i = 0; i < 66 * scr_p / 4; i++) dst[i] = j == launcher_sel ? 0xff200000 : 0xff040404;
            w = launcher_list[launcher_tab][j].w > 128 ? 128 : launcher_list[launcher_tab][j].w;
            k = main_textwidth(launcher_list[launcher_tab][j].name) + (launcher_list[launcher_tab][j].img ? w + 8 : 0);
            k = (scr_w - k) / 2;
            if(launcher_list[launcher_tab][j].img) {
                u = launcher_list[launcher_tab][j].h; if(u > 64) u = 64;
                ui_blit(scr_data, w, u, scr_p, k, j * 70 + 1 + selonly + (64 - u) / 2, (uint32_t*)launcher_list[launcher_tab][j].img,
                    0, 0, w, u, launcher_list[launcher_tab][j].w * 4, launcher_sel != j);
                k += w + 8;
            }
            if(!selonly) {
                ssfn_dst.fg = 0xff010101;
                main_text(screen, k + 1, j * 70 + 24 + 1, launcher_list[launcher_tab][j].name);
            }
            ssfn_dst.fg = j == launcher_sel ? 0xffffffff : 0xffa0a0a0;
            main_text(screen, k, j * 70 + 24 + selonly, launcher_list[launcher_tab][j].name);
        }
        SDL_UnlockTexture(screen);
    }
}

/**
 * Switch launcher tab
 */
void launcher_switch(int tab)
{
    stbi__context sc;
    stbi__result_info ri;
    struct sockaddr_in addr;
    files_t *f, *g;
    char *s, *e, *gameid, *name, *url, *img, key[] = "name_xx";
    char tmp[16] = { 'H', 'i', 0, 0, 0, 0, 0x46, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
    uint32_t *dst;
    int i, j, p;
#ifdef __WIN32__
    DWORD nb;
#endif

    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    if(tab < 0) tab = 2;
    if(tab > 2) tab = 0;
    files_freegames(&launcher_list[2], &launcher_num[2]);
    if(launcher_udpsock) { close(launcher_udpsock); launcher_udpsock = 0; }
    launcher_tab = tab;
    launcher_sel = launcher_y = launcher_clk = 0;
    memset(&scroll, 0, sizeof(scroll));
    switch(launcher_tab) {
        case 0:
            files_freegames(&launcher_list[0], &launcher_num[0]);
            launcher_list[0] = files_getgames(&launcher_num[0]);
        break;
        case 1:
            launcher_p = 1; launcher_redraw(0); launcher_p = -1;
            SDL_RenderClear(renderer);
            launcher_view();
            SDL_RenderPresent(renderer);
            if(!launcher_list[1]) {
                key[5] = detlng[0]; key[6] = detlng[1];
                f = files_ssl_open(LAUNCHER_LIST, "GET", NULL, NULL, NULL);
                if(f) {
                    while(files_ssl_read(f)) {
                        for(s = (char*)f->buf; *s && s < (char*)f->buf + f->olen; s++) {
                            if(*s == '{') {
                                for(e = s; *e && e < (char*)f->buf + f->olen && *e != '}'; e++);
                                if(e >= (char*)f->buf + f->olen) {
                                    f->olen -= (int)((uintptr_t)s - (uintptr_t)f->buf);
                                    memmove(f->buf, s, f->olen);
                                    files_ssl_read(f);
                                    for(e = s = (char*)f->buf; *e && e < (char*)f->buf + f->olen && *e != '}'; e++);
                                }
                                if(*e == '}') {
                                    *e = 0;
                                    gameid = json_get(s, "gameid");
                                    if(gameid) {
                                        for(i = 0; i < launcher_num[0] && strcmp(gameid, launcher_list[0][i].gameid); i++);
                                        if(i < launcher_num[0]) { free(gameid); s = e; continue; }
                                    }
                                    url = json_get(s, "url");
                                    name = json_get(s, key); if(!name) name = json_get(s, "name_en");
                                    if(gameid && url && name) {
                                        i = launcher_num[1]++;
                                        launcher_list[1] = (files_game_t*)main_realloc(launcher_list[1], launcher_num[1] * sizeof(files_game_t));
                                        launcher_list[1][i].gameid = gameid;
                                        launcher_list[1][i].url = url;
                                        launcher_list[1][i].name = name;
                                        launcher_list[1][i].w = launcher_list[1][i].h = launcher_list[1][i].s = 0;
                                        launcher_list[1][i].img = launcher_list[1][i].png = NULL;
                                        img = json_get(s, "img");
                                        if(img) {
                                            g = files_ssl_open(img, "GET", NULL, NULL, NULL);
                                            if(g) {
                                                if(files_ssl_read(g)) {
                                                    launcher_list[1][i].png = (uint8_t*)main_alloc(g->olen);
                                                    memcpy(launcher_list[1][i].png, g->buf, g->olen);
                                                    launcher_list[1][i].s = g->olen;
                                                    memset(&sc, 0, sizeof(sc));
                                                    memset(&ri, 0, sizeof(ri));
                                                    sc.img_buffer = sc.img_buffer_original = g->buf;
                                                    sc.img_buffer_end = sc.img_buffer_original_end = g->buf + g->olen;
                                                    ri.bits_per_channel = 8;
                                                    launcher_list[1][i].img = (uint8_t*)stbi__png_load(&sc,
                                                        &launcher_list[1][i].w, &launcher_list[1][i].h, &j, 4, &ri);
                                                }
                                                files_ssl_close(g);
                                            }
                                            free(img);
                                        }
                                    } else {
                                        if(gameid) free(gameid);
                                        if(url) free(url);
                                        if(name) free(name);
                                    }
                                    s = e;
                                }
                            }
                        }
                        f->olen = 0;
                    }
                    files_ssl_close(f);
                }
                if(!launcher_list[1]) {
                    launcher_err = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, 40);
                    if(launcher_err) {
                        SDL_LockTexture(launcher_err, NULL, (void**)&dst, &p);
                        for(i = 0; i < p/4*40; i++) dst[i] = 0xff000040;
                        SDL_UnlockTexture(launcher_err);
                        ssfn_dst.fg = 0xffffffff; i = scr_w; scr_w = win_w;
                        main_text(launcher_err, (win_w - main_textwidth(lang[ERR_CONNECT])) / 2, 13, lang[ERR_CONNECT]);
                        scr_w = i;
                    }
                }
            }
        break;
        case 2:
            launcher_p = 2; launcher_redraw(0); launcher_p = -1;
            SDL_RenderClear(renderer);
            launcher_view();
            SDL_RenderPresent(renderer);
            memset(&addr, 0, sizeof(addr));
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = INADDR_ANY;
            addr.sin_port = htons(4433);
            launcher_udpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if(launcher_udpsock <= 0) {
                if(verbose) printf("tngp: socket failed %d errno %d\r\n", launcher_udpsock, errno);
                launcher_udpsock = 0; break;
            }
            i = 1;
            if(setsockopt(launcher_udpsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&i, sizeof(i)) ||
#ifdef __WIN32__
              ioctlsocket(launcher_udpsock, FIONBIO, &nb)
#else
              fcntl(launcher_udpsock, F_SETFL, O_NONBLOCK, 1)
#endif
              ) {
                if(verbose) printf("tngp: setsockopt failed errno %d\r\n", errno);
                close(launcher_udpsock); launcher_udpsock = 0; break;
            }
            sendto(launcher_udpsock, (const char*)tmp, 16, 0, (struct sockaddr*)&addr, sizeof(addr));
        break;
    }
    if(launcher_list[launcher_tab] && launcher_num[launcher_tab] > 0) {
        scr_h = launcher_num[launcher_tab] * 70;
        for(scr_w = i = 0; i < launcher_num[launcher_tab]; i++) {
            j = main_textwidth(launcher_list[launcher_tab][i].name);
            if(j > scr_w) scr_w = j;
        }
    } else {
        scr_w = main_textwidth(lang[ERR_NOGAMES]);
        scr_h = 40;
    }
    if(scr_w < win_w) scr_w = win_w;
    screen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_w, scr_h);
    if(screen) {
        scroll.w = scr_w > win_w ? win_w : scr_w;
        scroll.h = scr_h > win_h - 96 - 50 ? win_h - 96 - 50 : scr_h;
        SDL_SetTextureBlendMode(screen, SDL_BLENDMODE_BLEND);
        SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
        memset(scr_data, 0, scr_p * scr_h);
        if(!launcher_tab && launcher_num[launcher_tab] < 1) {
            for(i = 0; i < scr_w * scr_h; i++) scr_data[i] = 0xff000040;
            ssfn_dst.fg = 0xffffffff;
            main_text(screen, (scr_w - main_textwidth(lang[ERR_NOGAMES])) / 2, 12, lang[ERR_NOGAMES]);
        }
        SDL_UnlockTexture(screen);
        launcher_redraw(0);
    }
}

/**
 * Initialize
 */
void launcher_init()
{
#ifdef __WIN32__
    WSADATA WsaData;
    WSAStartup(MAKEWORD(2,2), &WsaData);
#endif
    launcher_tabs = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, 42);
    launcher_list[0] = files_getgames(&launcher_num[0]);
    launcher_list[1] = launcher_list[2] = NULL;
    launcher_num[1] = launcher_num[2] = 0;
    launcher_switch(0);
    SDL_ShowCursor(SDL_ENABLE);
}

/**
 * Free resources
 */
void launcher_free()
{
    files_freegames(&launcher_list[0], &launcher_num[0]);
    files_freegames(&launcher_list[1], &launcher_num[1]);
    files_freegames(&launcher_list[2], &launcher_num[2]);
    if(launcher_udpsock) { close(launcher_udpsock); launcher_udpsock = 0; }
    if(launcher_tabs) { SDL_DestroyTexture(launcher_tabs); launcher_tabs = NULL; }
    if(launcher_err) { SDL_DestroyTexture(launcher_err); launcher_err = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    SDL_ShowCursor(SDL_DISABLE);
#ifdef __WIN32__
    WSACleanup();
#endif
}

/**
 * Controller
 */
int launcher_ctrl()
{
    char *fn;
    int i, y = 96 + 50 + (win_h - 96 - 50 - scroll.h) / 2;

    if(launcher_err && event.type != SDL_MOUSEMOTION && event.type != SDL_MOUSEBUTTONUP && event.type != SDL_KEYUP &&
        event.type != SDL_CONTROLLERBUTTONUP) { SDL_DestroyTexture(launcher_err); launcher_err = NULL; }
    switch(event.type) {
        case SDL_KEYDOWN:
            switch(event.key.keysym.sym) {
                case SDLK_UP:
up:                 if(launcher_num[launcher_tab] > 0 && launcher_sel > 0) {
                        launcher_sel--;
                        if(launcher_sel * 70 < scroll.y) scroll.y = launcher_sel * 70;
                        if(launcher_sel * 70 > scroll.y + win_h - 174) scroll.y = launcher_sel * 70 - win_h + 174;
                        launcher_redraw(0);
                    }
                break;
                case SDLK_DOWN:
down:               if(launcher_num[launcher_tab] > 0 && launcher_sel < launcher_num[launcher_tab] - 1) {
                        launcher_sel++;
                        if(launcher_sel * 70 < scroll.y) scroll.y = launcher_sel * 70;
                        if(launcher_sel * 70 > scroll.y + win_h - 216) scroll.y = launcher_sel * 70 - win_h + 216;
                        launcher_redraw(0);
                    }
                break;
                case SDLK_TAB:
                case SDLK_RIGHT:
right:              launcher_switch(launcher_tab + 1);
                break;
                case SDLK_LEFT:
left:               launcher_switch(launcher_tab - 1);
                break;
                case SDLK_RETURN: launcher_redraw(1); break;
            }
        break;
        case SDL_KEYUP:
            switch(event.key.keysym.sym) {
                case SDLK_AC_BACK:
                case SDLK_F10:
                case SDLK_BACKSPACE:
                case SDLK_ESCAPE: return 0;
                case SDLK_RETURN:
selectworld:        if(launcher_num[launcher_tab] < 1 || launcher_sel >= launcher_num[launcher_tab]) return 0;
                    if(launcher_tab == 1) {
                        fn = launcher_download(&launcher_list[launcher_tab][launcher_sel]);
                        if(fn) {
                            main_init(fn, NULL);
                            free(fn);
                        } else
                            launcher_redraw(0);
                    } else
                        main_init(launcher_list[launcher_tab][launcher_sel].url, NULL);
                    return 1;
                break;
            }
        break;
        case SDL_MOUSEBUTTONDOWN:
            if(event.button.y >= 96 && event.button.y < 96 + 50) {
                launcher_p = event.button.x / (win_w / 3);
                launcher_redraw(0);
            }
            if(launcher_num[launcher_tab] > 0 && event.button.y > y && event.button.y < y + scroll.h) {
                launcher_clk = 1;
                launcher_oy = scroll.y;
                if(launcher_y == event.button.y) {
                    launcher_y = -1;
                    launcher_redraw(1);
                } else
                    launcher_y = event.button.y;
            }
        break;
        case SDL_MOUSEBUTTONUP:
            launcher_clk = 0;
            if(launcher_p != -1) {
                if(event.button.y >= 96 && event.button.y < 96 + 50) {
                    i = event.button.x / (win_w / 3);
                    if(i == launcher_p) launcher_switch(i);
                }
                launcher_p = -1;
                launcher_redraw(0);
            }
            if(launcher_y == -1) goto selectworld;
            if(event.button.y == launcher_y) {
                launcher_sel = (event.button.y - y + scroll.y) / 70;
                launcher_redraw(0);
            } else
                launcher_y = 0;
        break;
        case SDL_MOUSEMOTION:
            if(launcher_clk && win_h - 96 - 42 < scr_h) {
                scroll.y = launcher_y - event.motion.y + launcher_oy;
                if(scroll.y + scroll.h > scr_h) scroll.y = scr_h - scroll.h;
                if(scroll.y < 0) scroll.y = 0;
            }
        break;
        case SDL_MOUSEWHEEL:
            if(event.wheel.y > 0) goto up;
            if(event.wheel.y < 0) goto down;
        break;
        case SDL_CONTROLLERBUTTONDOWN:
            switch(event.cbutton.button) {
                case SDL_CONTROLLER_BUTTON_START:
                case SDL_CONTROLLER_BUTTON_A:
                case SDL_CONTROLLER_BUTTON_B:
                case SDL_CONTROLLER_BUTTON_X:
                case SDL_CONTROLLER_BUTTON_Y: launcher_redraw(1); break;
                case SDL_CONTROLLER_BUTTON_DPAD_UP: goto up;
                case SDL_CONTROLLER_BUTTON_DPAD_DOWN: goto down;
                case SDL_CONTROLLER_BUTTON_DPAD_LEFT: goto left;
                case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: goto right;
            }
        break;
        case SDL_CONTROLLERBUTTONUP:
            switch(event.cbutton.button) {
                case SDL_CONTROLLER_BUTTON_BACK: return 0;
                case SDL_CONTROLLER_BUTTON_START:
                case SDL_CONTROLLER_BUTTON_A:
                case SDL_CONTROLLER_BUTTON_B:
                case SDL_CONTROLLER_BUTTON_X:
                case SDL_CONTROLLER_BUTTON_Y: goto selectworld;
            }
        break;
        case SDL_CONTROLLERAXISMOTION:
            if(event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY || event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) {
                if(event.caxis.value < -main_gptres) goto up;
                if(event.caxis.value > main_gptres) goto down;
            }
            if(event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX) {
                if(event.caxis.value < -main_gptres) goto left;
                if(event.caxis.value > main_gptres) goto right;
            }
        break;
    }
    return 1;
}

/**
 * View
 */
void launcher_view()
{
    int i;
    char ipstr[INET6_ADDRSTRLEN + 8], buf[33] = { 0 };
    struct sockaddr_in peer;
    socklen_t addrlen;
    SDL_Rect rect;

    if(launcher_tab == 2 && launcher_udpsock) {
        addrlen = sizeof(peer);
        if(recvfrom(launcher_udpsock, (char*)buf, 32, 0, (struct sockaddr*)&peer, &addrlen) == 32 &&
          buf[4] == 'T' && buf[5] == 'N' && buf[6] == 'G' && buf[7] == 'S') {
            memset(&ipstr, 0, sizeof(ipstr)); strcpy(ipstr, "tng://");
            inet_ntop(peer.sin_family, (void*)&peer.sin_addr, ipstr + 6, sizeof(ipstr) - 6);
            for(i = 0; launcher_list[2] && i < launcher_num[2] && strcmp(ipstr, launcher_list[2][i].url); i++);
            if(i >= launcher_num[2]) {
                i = launcher_num[2]++;
                launcher_list[2] = (files_game_t*)main_realloc(launcher_list[2], launcher_num[2] * sizeof(files_game_t));
                launcher_list[2][i].gameid = (char*)main_alloc(17); strcpy(launcher_list[2][i].gameid, buf + 16);
                launcher_list[2][i].url = (char*)main_alloc(strlen(ipstr)); strcpy(launcher_list[2][i].url, ipstr);
                launcher_list[2][i].name = (char*)main_alloc(17); strcpy(launcher_list[2][i].name, buf + 16);
                launcher_list[2][i].w = launcher_list[2][i].h = launcher_list[2][i].s = 0;
                launcher_list[2][i].img = launcher_list[2][i].png = NULL;
                launcher_redraw(0);
            }
        }
    }
    if(!main_draw) return;
    rect.x = (win_w - 440) / 2; rect.y = 16; rect.w = lgopos.w; rect.h = lgopos.h;
    SDL_RenderCopy(renderer, logo, &lgopos, &rect);
    if(launcher_tabs) {
        rect.x = 0; rect.y = 96; rect.w = win_w; rect.h = 42;
        SDL_RenderCopy(renderer, launcher_tabs, NULL, &rect);
    }
    rect.x = scr_w > win_w ? 0 : (win_w - scr_w) / 2; rect.w = scroll.w;
    rect.y = 96 + 50 + (win_h - 96 - 50 - scroll.h) / 2; rect.h = scroll.h;
    if(screen) SDL_RenderCopy(renderer, screen, &scroll, &rect);
    if(launcher_err) {
        rect.x = 0; rect.y = win_h - 40; rect.w = win_w; rect.h = 40;
        SDL_RenderCopy(renderer, launcher_err, NULL, &rect);
    }
}
#endif
