/*
 * tngp/tngd.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 Main function for the TirNanoG Dumper
 *
 */

#define STB_IMAGE_IMPLEMENTATION
#define SSFN_IMPLEMENTATION
#define SSFN_CONSOLEBITMAP_TRUECOLOR

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <time.h>                   /* for strftime */
#ifdef __WIN32__                    /* for inet_ntop */
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include <SDL.h>
#include "tng.h"
#include "world.h"
#include "theora/theoradec.h"
#include "vorbis/codec.h"
#include "theora.h"
#include "zlib.h"
#include "lang.h"
#include "data.h"
#include "ssfn.h"
#define STBI_NO_STDIO
#define STBI_NO_LINEAR
#define STBI_NO_HDR
#define STBI_ONLY_PNG
#include "stb_image.h"
#include "version.h"
#include "ssfn.h"

/* minimum screen size */
#define SCR_MINW    784
#define SCR_MINH    512

/* theme colors */
enum {
    THEME_BG,   THEME_FG,    THEME_LIGHT,  THEME_DARK,  THEME_LIGHTER, THEME_DARKER, THEME_INPBG, THEME_ERR,
    THEME_BTNB, THEME_BTN0L, THEME_BTN0BL, THEME_BTN0BD,THEME_BTN0D,   THEME_BTN1L,  THEME_BTN1BL,THEME_BTN1BD,THEME_BTN1D,
    THEME_SELBG,THEME_SELFG, THEME_CURSOR, THEME_INA,   THEME_SELBX,   THEME_PATH,   THEME_GRID1, THEME_GRID2,
    THEME_CMNT, THEME_LOOP,  THEME_SWITCH, THEME_LET,   THEME_FUNC,

    THEME_LAST
};
uint32_t theme[] = {
    0xFF444644, 0xFFBEBEBE, 0xFF5D5D5A, 0xFF313331, 0xFF626562, 0xFF2E312E, 0xFF3A3D3A, 0xFF24244C,
    0xFF101010, 0xFF686868, 0xFF515151, 0xFF484848, 0xFF404040, 0xFF744C4C, 0xFF5D3535, 0xFF542C2C, 0xFF4C2424,
    0xFF5C5C5C, 0xFFF0F0F0, 0xFF909090, 0xFF3E413E, 0xFFF0F000, 0xFF2020AF, 0xFF3F413F, 0xFF3B3D3B,
    0xFF606060, 0xFF20406F, 0xFF606020, 0xFF206020, 0xFF602020
};
typedef struct {
    char *label;                        /* label */
    int width;                          /* non-zero for fixed coloumns */
    int order;                          /* 1 if label is clickable for changing order */
    int w;                              /* calculated width */
} ui_tablehdr_t;

typedef void (*ui_drawcell_t)(
    SDL_Texture *dst,
    int idx,                            /* index of cell */
    int sel,                            /* index of selected cell */
    int x, int y, int w, int h,         /* where to draw */
    ui_tablehdr_t *hdr,                 /* header specification (for a row, cell widths in hdr[i].w) */
    void *data);                        /* pointer to current data entry */

typedef struct {
    ui_tablehdr_t *hdr;                 /* header list */
    int row, x, y;
    int first;                          /* has a special first element */
    int fld, h, val, scr, page;         /* calculated values, initialize as zero */
    int w;                              /* calculated value, initialize as -1 */
    int num;                            /* number of data entries */
    int entsize;                        /* size of one data entry */
    ui_drawcell_t drawcell;             /* callback to draw a table row (if col 0) or cell (if col non-zero) */
    void *data;                         /* list of data entries, each 'entsize' in length, 'num' records */
} ui_table_t;

typedef struct {
    int w, h;
    SDL_Texture *txt;
} atlas_t;

typedef struct {
    char cat;
    int name;
} crd_t;

/**
 * Translations
 */
char **lang = NULL;
char *dict[1][NUMTEXTS + 1] = {{
#include "lang/en.h"
}};

/* provide workarounds for dynamically linked shared libraries */
#ifdef _DYNSDLFIX_
char *strtok_r(char *s1, const char *s2, char **ptr);
char *SDL_strtokr(char *s1, const char *s2, char **ptr) { return strtok_r(s1, s2, ptr); }
#endif

/* this is only needed for the theora interface */
world_asset_t wasset = { 0 };
world_tng_t world_tng = { 0 };
world_t world;

SDL_Window *window;
SDL_Renderer *renderer;
SDL_Event event;
SDL_Texture *screen = NULL, *back = NULL, *preview = NULL;
SDL_Rect ui_clip;
uint32_t *scr_data = NULL;
volatile uint32_t main_tick;
int scr_w, scr_h, scr_p, verbose = 1, tilew, tileh, main_musvol = MIX_MAX_VOLUME, cutscn_inloop = 0;
unsigned int sec_size = 0, sec_unc = 0, sec_type = 0;
int sec_tab = 0, sec_hastab, sec_playx = 0, sec_playy = 0, back_w, back_h, tabx, taby, tabn, tabc = 0;
char ws[0x110000], badstr[32];
FILE *f = NULL;
tng_hdr_t hdr = { 0 };
tng_section_t *sec = NULL;
int freqs[] = { TNG_FREQS }, numstrings = 0, numtranslation = 0, numtransl = 0, numatlas = 0, numsprites = 0, numcrd = 0;
int numopts = 0, zoom = 1, strmax = 0, *strings = NULL, *translation = NULL, numspr[5] = { 0 }, numattrs = 0, abclen = 0;
int nummne = 0, *mne = NULL, plen = 0, tilesx = 0, tilesy = 0, origx = -1, origy = -1;
char *translations = NULL, *transls = NULL, **transl = NULL, *mnes = NULL;
atlas_t *atlas = NULL;
uint32_t chkcrc = 0;
tng_sprite_t **sprites = NULL;
tng_sprmap_desc_t *currframe = NULL;
crd_t *crds = NULL;
uint8_t **attrs = NULL, *abc = NULL, *prvw = NULL, *sbuf = NULL, **opts = NULL, *optsbuf = NULL;
uint16_t *tiles = NULL;

const char *main_me = "tngd";
const char *hdr_types[] = { "TNG_TYPE_ORTHO", "TNG_TYPE_ISO", "TNG_TYPE_HEXV", "TNG_TYPE_HEXH", "TNG_TYPE_3D" };
const char *section_types[] = { "Strings", "Translation", "Fonts", "Music", "Sounds", "Movies", "Atlases", "UI Spites",
    "Tile Sprites", "Character Sprites", "Portrait Sprites", "Backgrounds", "#12 unused", "#13 unused", "#14 unused", "#15 unused",
    "Interface", "Main Menu", "HUD", "Equipmentslots", "Alerts", "Credits", "Choosers", "Cutscenes", "Startup", "Attributes",
    "Actions", "Characters", "NPCs", "Spawners", "Dialogs", "Objects", "Craftings", "Tiles Meta", "Quests", "Maps" };
const char *dir_types[] = { "so", "sw", "we", "nw", "no", "ne", "ea", "se" };
const char *anim_types[] = { "once", "fobk", "loop" };
const char *atlop_types[] = { "copy", "vflp", "hflp", "rcw", "rot", "rccw" };
const char *ui_types[] = { "cursor pointer", "cursor click", "cursor not allowed", "cursor action", "cursor loading",
    "checkbox 0", "checkbox 1", "option prev", "option prev pressed", "option bg", "option next", "option next pressed",
    "slider left", "slider button", "slider bg", "slider right", "scrollbar left", "scrollbar h bg", "scrollbar right",
    "scrollbar up", "scrollbar v bg", "scrollbar down", "scrollbar button", "window tab normal left", "window tab normal bg",
    "window tab normal right", "window tab inactive left", "window tab inactive bg", "window tab inactive right",
    "window top left", "window top middle", "window top right", "window middle left", "window bg", "window middle right",
    "window bottom left", "window bottom middle", "window bottom right", "dialog tab left", "dialog tab bg", "dialog tab right",
    "dialog option left", "dialog option bg", "dialog option right", "dialog top left", "dialog top middle", "dialog top right",
    "dialog middle left", "dialog bg", "dialog middle right", "dialog bottom left", "dialog bottom middle", "dialog bottom right",
    "button normal left", "button normal bg", "button normal right", "button selected left", "button selected bg",
    "button selected right", "button pressed left", "button pressed bg", "button pressed right", "button inactive left",
    "button inactive bg", "button inactive right", "chat input left", "chat input bg", "chat input right", "application icon"
};
const char *pbar_types[] = { "l2r", "b2t", "alc", "enm" };
const char *crdcat = "gcdafmsvtp";
const char *attr_types[] = { "prim", "char", "glob", "ccal", "gcal" };
const char *cmd_types[] = { "", "", "", "", "", "exit", "delay", "music", "sound", "speak", "chooser", "cutscene", "dialog",
    "location", "quest", "canim", "oanim", "npc", "actor", "behave", "market", "south", "southwest", "west", "northwest",
    "north", "northeast", "east", "southeast", "remove", "drop", "delete", "add", "replace", "give", "take", "event",
    "transport", "scene", "waitnpc", "craft", "match", "alert", "altitude"
};
const char *rel_types[] = { "=", "!=", ">=", ">", "<=", "<", NULL };
const char *spr_types[] = { "idle", "walk", "climb", "jump", "run", "swim", "fly", "block", "hurt", "die" };
const char *evt_types[] = { "onclick", "ontouch", "onleave", "onapproach", "onaction1", "onaction2", "onaction3",
    "onaction4", "onaction5", "onaction6", "onaction7", "onaction8", "onaction9", "onusing1", "onusing2", "onusing3",
    "ontimer", "onsell", "onbuy", NULL };
const char *tmr_types[] = { "1/10s", "1s", "10s", "20s", "30s", "1m", "2m", "3m", "5m", "10m", "20m", "30m", "1h", NULL };
const char *beh_types[] = { "attack", "neutral", "flee" };
const char *trn_types[] = { "walk", "swim", "fly", "drive" };

void draw_sections(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data);

ui_tablehdr_t sections_hdr[] = {
    { "", 0, 0, 24 },
    { "Section", 0, 0, 198 - 24 },
    { 0 }
};
ui_table_t sections_tbl = { sections_hdr, 18, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, sizeof(tng_section_t), draw_sections, NULL };
ui_tablehdr_t assets_hdr[16] = { 0 };
ui_table_t assets_tbl = { assets_hdr, 18, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, NULL, NULL };
ui_tablehdr_t dirs_hdr[16] = { 0 };
ui_table_t dirs_tbl = { dirs_hdr, 18, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, NULL, NULL };
ui_tablehdr_t frames_hdr[16] = { 0 };
ui_table_t frames_tbl = { frames_hdr, 18, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, NULL, NULL };
ui_tablehdr_t events_hdr[16] = { 0 };
ui_table_t events_tbl = { events_hdr, 18, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, NULL, NULL };

/* do NOT include SDL_mixer, it won't compile on mingw */
#define MIX_INIT_OGG 0x00000010
extern DECLSPEC int SDLCALL Mix_Init(int flags);
extern DECLSPEC void SDLCALL Mix_Quit(void);
extern DECLSPEC int SDLCALL Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);
extern DECLSPEC void SDLCALL Mix_CloseAudio(void);

/**
 * Windows workaround
 */
#ifdef __WIN32__
#define CLIFLAG '/'
/* only include these when necessary, some of their defines conflict with SDL_mixer's... */
#include <windows.h>
#include <winnls.h>
#include <fileapi.h>
#include <shellapi.h>
wchar_t szFull[PATH_MAX + FILENAME_MAX + 1];

/* these two functions were borrowed from sdl_windows_main.c */
static void UnEscapeQuotes(char *arg)
{
    char *last = NULL, *c_curr, *c_last;

    while (*arg) {
        if (*arg == '"' && (last != NULL && *last == '\\')) {
            c_curr = arg;
            c_last = last;

            while (*c_curr) {
                *c_last = *c_curr;
                c_last = c_curr;
                c_curr++;
            }
            *c_last = '\0';
        }
        last = arg;
        arg++;
    }
}

/* Parse a command line buffer into arguments */
static int ParseCommandLine(char *cmdline, char **argv)
{
    char *bufp;
    char *lastp = NULL;
    int argc, last_argc;

    argc = last_argc = 0;
    for (bufp = cmdline; *bufp;) {
        /* Skip leading whitespace */
        while (SDL_isspace(*bufp)) {
            ++bufp;
        }
        /* Skip over argument */
        if (*bufp == '"') {
            ++bufp;
            if (*bufp) {
                if (argv) {
                    argv[argc] = bufp;
                }
                ++argc;
            }
            /* Skip over word */
            lastp = bufp;
            while (*bufp && (*bufp != '"' || *lastp == '\\')) {
                lastp = bufp;
                ++bufp;
            }
        } else {
            if (*bufp) {
                if (argv) {
                    argv[argc] = bufp;
                }
                ++argc;
            }
            /* Skip over word */
            while (*bufp && !SDL_isspace(*bufp)) {
                ++bufp;
            }
        }
        if (*bufp) {
            if (argv) {
                *bufp = '\0';
            }
            ++bufp;
        }

        /* Strip out \ from \" sequences */
        if (argv && last_argc != argc) {
            UnEscapeQuotes(argv[last_argc]);
        }
        last_argc = argc;
    }
    if (argv) {
        argv[argc] = NULL;
    }
    return (argc);
}

/* Windows entry point */
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    char *cmdline = GetCommandLineA();
    int ret, argc = ParseCommandLine(cmdline, NULL);
    char **argv = SDL_stack_alloc(char*, argc+2);
    (void)hInstance;
    (void)hPrevInstance;
    (void)lpCmdLine;
    (void)nCmdShow;
    ParseCommandLine(cmdline, argv);
    SDL_SetMainReady();
    ret = main(argc, argv);
    SDL_stack_free(argv);
    exit(ret);
    return ret;
}
#else
#define CLIFLAG '-'
#endif

void main_quit();

/**
 * String
 */
char *str(int name)
{
    int i;

    name &= 0xffffff;
    if(!strings || !sec) {
        sprintf(badstr, "str%06X", name);
        return badstr;
    }
    if(!name) {
        sprintf(badstr, "(unset)");
        return badstr;
    }
    for(i = 0; i < numstrings; i++)
        if(strings[i] == name) return (char*)sec + sec[0].offs + name;
    ssfn_dst.bg = theme[THEME_ERR];
    sprintf(badstr, "(INVALID)");
    return badstr;
}

/**
 * Display a hex number with extra small glyphs (4 * 4x5)
 */
void ui_hex(SDL_Texture *dst, int x, int y, uint16_t val, uint32_t c)
{
    int i, j, k, l, d = 0, s, p, pitch, x2 = ui_clip.x + ui_clip.w, y2 = ui_clip.y + ui_clip.h;
    uint32_t *data, n[] = {0x4aaa4,0x4c444,0xc248e,0xc2c2c,0x24ae2,0xe8c2c,0x68eac,0xe2444,0x4a4a4,0xea62c,
        0x4aeaa,0xcacac,0x68886,0xcaaac,0xe8e8e,0xe8e88};

    if(x + 20 < ui_clip.x || y + 5 < ui_clip.y || x >= x2 || y >= y2) return;

    if(dst == screen) { data = scr_data; pitch = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&data, &pitch);
    pitch /= 4; p = pitch * y;
    for(s = 12; s >= 0 && x < x2; x += 4, s -= 4) {
        d = (val >> s) & 0xf;
        for(k = 1<<19, j = l = 0; j < 5 && y + j < y2; j++, l += pitch)
            for(i = 0; i < 4 && x + i < x2; i++, k >>= 1)
                if((n[d] & k) && y + j >= ui_clip.y && x + i >= ui_clip.x) data[p + l + x + i] = c;
    }
    if(dst != screen) SDL_UnlockTexture(dst);
}

/**
 * Recalculate size (sw, sh) to fit into (mw, mh) keeping aspect ratio
 */
void ui_fit(int mw, int mh, int sw, int sh, int *w, int *h)
{
    if(!w || !h) return;
    if(mw < 1 || mh < 1 || sw < 1 || sh < 1) { *w = *h = 0; return; }
    *w = mw; *h = sh * mw / sw;
    if(*h > mh) { *h = mh; *w = sw * mh / sh; }
    if(*w < 1) *w = 1;
    if(*h < 1) *h = 1;
}

/**
 * Return text width in pixels
 */
int ui_textwidth(char *str)
{
    int ret = 0, i = 0, u;

    if(!str || !*str) return 0;
    while(*str) {
        u = ssfn_utf8(&str);
        if(u == '\n') { if(ret < i) ret = i; i = 0; }
        else i += (int)ws[u];
    }
    if(ret < i) ret = i;
    return ret;
}

/**
 * Display text
 */
void ui_text(SDL_Texture *dst, int x, int y, char *str)
{
    char *s = str;
    int p, u;

    ssfn_dst.w = ui_clip.x + ui_clip.w;
    ssfn_dst.h = ui_clip.y + ui_clip.h;
    if(y + 16 <= 0 || x > scr_w || y > scr_h) return;
    if(dst == screen) { ssfn_dst.ptr = (unsigned char *)scr_data; p = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&ssfn_dst.ptr, &p);
    ssfn_dst.p = p;
    ssfn_dst.x = x;
    ssfn_dst.y = y;
    while(s && *s) {
        u = ssfn_utf8(&s);
        if(u == '\n') { ssfn_dst.x = x; ssfn_dst.y += 16; } else
        if(ssfn_dst.x >= 0 && ssfn_dst.x < scr_w)
            ssfn_putc(u);
        else
            ssfn_dst.x += ws[u];
    }
    if(dst != screen) SDL_UnlockTexture(dst);
}

/**
 * Display text with shadow
 */
void ui_text2(SDL_Texture *dst, int x, int y, char *str, uint32_t c, uint32_t s)
{
    uint32_t f = ssfn_dst.fg, b = ssfn_dst.bg;
    ssfn_dst.fg = s;
    ui_text(dst, x + 1, y + 1, str);
    ssfn_dst.bg = 0;
    ssfn_dst.fg = c;
    ui_text(dst, x, y, str);
    ssfn_dst.bg = b;
    ssfn_dst.fg = f;
}

/**
 * Display a rectangle
 */
void ui_rect(SDL_Texture *dst, int x, int y, int w, int h, uint32_t l, uint32_t d)
{
    int i, j, x2 = ui_clip.x + ui_clip.w, y2 = ui_clip.y + ui_clip.h, p, p2, pitch;
    uint32_t *data;

    if(x >= x2 || x + w < ui_clip.x || y >= y2 || y + h < ui_clip.y || w < 1 || h < 1 || !dst) return;

    if(dst == screen) { data = scr_data; pitch = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&data, &pitch);
    p = y * pitch/4 + x;
    p2 = (y + h - 1) * pitch/4 + x;
    for(i=0; i + 1 < w && x + i + 1 < x2; i++) {
        if(y >= ui_clip.y && x + i >= ui_clip.x) data[p + i] = l;
        if(y + h + 1 >= ui_clip.y && y + h - 1 < y2 && x + i >= ui_clip.x) data[p2 + i + 1] = d;
    }
    p += pitch/4;
    for(j=1; j + 1 < h && y + j < y2; j++, p += pitch/4)
        if(y + j >= ui_clip.y) {
            if(x >= ui_clip.x) data[p] = l;
            if(x + w - 1 < x2) data[p + w - 1] = d;
        }
    if(dst != screen) SDL_UnlockTexture(dst);
}

/**
 * Display a filled box
 */
void ui_box(SDL_Texture *dst, int x, int y, int w, int h, uint32_t l, uint32_t b, uint32_t d)
{
    int i, j, x2 = ui_clip.x + ui_clip.w, y2 = ui_clip.y + ui_clip.h, p, p2, pitch;
    uint32_t *data;

    if(x >= x2 || x + w < ui_clip.x || y >= y2 || y < ui_clip.y || w < 1 || h < 1 || !dst) return;

    if(dst == screen) { data = scr_data; pitch = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&data, &pitch);
    p = y * pitch/4 + x;
    p2 = (y + h - 1) * pitch/4 + x;
    for(i=0; i + 1 < w && x + i + 1 < x2; i++) {
        if(y >= ui_clip.y && x + i >= ui_clip.x) data[p + i] = l;
        if(y + h + 1 >= ui_clip.y && y + h - 1 < y2 && x + i >= ui_clip.x) data[p2 + i + 1] = d;
    }
    p += pitch/4;
    for(j=1; j + 1 < h && y + j < y2; j++, p += pitch/4)
        if(y + j >= ui_clip.y) {
            if(x >= ui_clip.x) data[p] = l;
            if(x + w - 1 < x2) data[p + w - 1] = d;
            for(i = 1; i + 1 < w && x + i + 1 < x2; i++)
                if(x + i >= ui_clip.x) data[p + i] = b;
        }
    if(dst != screen) SDL_UnlockTexture(dst);
}

/**
 * Display a filled box with altering background
 * t - pixels to skip at top (header)
 * s - scroll position
 * r - size of a row in pixels
 * c - size of a coloumn in pixels or zero is there are only rows
 */
void ui_box2(SDL_Texture *dst, int x, int y, int w, int h, uint32_t l, uint32_t b, uint32_t d, int t, int s, int r, int c)
{
    int i, j, k, x2 = ui_clip.x + ui_clip.w, y2 = ui_clip.y + ui_clip.h, p, p2, pitch;
    uint32_t *data, B, a;

    if(x >= x2 || x + w < ui_clip.x || y >= y2 || y < ui_clip.y || w < 1 || h < 1 || !dst) return;

    if(dst == screen) { data = scr_data; pitch = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&data, &pitch);
    p = y * pitch/4 + x;
    p2 = (y + h - 1) * pitch/4 + x;
    for(i=0; i + 1 < w && x + i + 1 < x2; i++) {
        if(y >= ui_clip.y && x + i >= ui_clip.x) data[p + i] = l;
        if(y + h + 1 >= ui_clip.y && y + h - 1 < y2 && x + i >= ui_clip.x) data[p2 + i + 1] = d;
    }
    p += pitch/4; p2 = p;
    for(j=1; j + 1 < h && y + j < y2; j++, p += pitch/4)
        if(y + j >= ui_clip.y) {
            if(x >= ui_clip.x) data[p] = l;
            if(x + w - 1 < x2) data[p + w - 1] = d;
        }
    y += t; p = p2 + t * pitch/4; s &= 1;
    B = (b & 0xFF000000) | ((b + 0x040404) & 0x7F7F7F);
    if(c) {
        for(j=0; j + t + 2 < h && y + j < y2; j++, p += pitch/4)
            for(i=1; i + 1 < w && x + i < x2; ) {
                a = s ^ ((j / r) & 1) ^ ((i / c) & 1) ? B : b;
                for(k = 0; k < c && i + 1 < w && x + i < x2; i++, k++) data[p + i] = a;
            }
    } else {
        for(j=0; j + t + 2 < h && y + j < y2; j++, p += pitch/4) {
            a = s ^ ((j / r) & 1) ? B : b;
            for(i=1; i + 1 < w && x + i < x2; i++) data[p + i] = a;
        }
    }
    if(dst != screen) SDL_UnlockTexture(dst);
}

/**
 * Display a submit button (both string and icon)
 * ts = theme selector, 0 = default, 1 = blueish
 */
void ui_button(SDL_Texture *dst, int x, int y, int w, char *str, int pressed)
{
    int i, j, p, p2, p3, pitch, x2 = ui_clip.x + ui_clip.w, y2 = ui_clip.y + ui_clip.h;
    uint32_t l, b, B, d, t = theme[THEME_FG], c = theme[THEME_BTNB], P;
    uint32_t *data;

    if(x < 1 || y < 1 || x >= x2 || y >= y2 || w < 1) return;

    l = theme[THEME_BTN0L]; b = theme[THEME_BTN0BL]; B = theme[THEME_BTN0BD]; d = theme[THEME_BTN0D];
    if(pressed == -1) {
        l = d = b = B = theme[THEME_INA];
        t = theme[THEME_LIGHTER]; c = theme[THEME_DARKER];
        pressed = 0;
    }

    if(dst == screen) { data = scr_data; pitch = scr_p; }
    else SDL_LockTexture(dst, NULL, (void**)&data, &pitch);
    P = pitch/4; p = y * P + x; p2 = (y + 20 - 1) * P + x;
    for(i=0; i < w && x + i < x2; i++) {
        if(i && i < w - 1) data[p + i - P] = c;
        data[p + i] = !i || i == w - 1 ? c : (!pressed ? l : d);
        if(y + 20 < y2) {
            data[p2 + i] = !i || i == w - 1 ? c : (!pressed ? d : l);
            if(i && i < w - 1) data[p2 + i + P] = c;
        }
    }
    p += P;
    for(j=1, p3 = p; j+1 < 20 && y + j < y2; j++, p3 += P) {
        data[p3 - 1] = c;
        data[p3] = (pressed & 1 ? d : l);
        if(x + w < x2) {
            data[p3 + w - 1] = (!pressed ? d : l);
            data[p3 + w] = c;
        }
    }
    for(j=1; j + 1 < 20 && y + j < y2; j++, p += P)
        for(i=1; i + 1 < w && x + i < x2; i++)
            data[p + i] = !pressed ? (j < 12 ? b : B) : (j < 8 ? B : b);
    if(dst != screen) SDL_UnlockTexture(dst);
    ui_clip.w = x + w - ui_clip.x;
    if(str && *str) {
        ssfn_dst.bg = 0; l = ssfn_dst.fg;
        x += (w - ui_textwidth(str)) / 2;
        if(t != theme[THEME_LIGHTER]) {
            ssfn_dst.fg = theme[THEME_BTNB];
            ui_text(dst, x - 1, y + (!pressed ? 0 : 1), str);
        }
        ssfn_dst.fg = t;
        ui_text(dst, x, y + (!pressed ? 1 : 2), str);
        ssfn_dst.fg = l;
    }
    ui_clip.w = x2 - ui_clip.x;
}

/**
 * Vertical scrollbar
 */
void ui_vscrbar(SDL_Texture *dst, int x, int y, int w, int h, int scroll, int page, int num, int pressed)
{
    if(!num || page > num) {
        ui_box(dst, x, y, w, h, theme[pressed ? THEME_DARK : THEME_LIGHT], theme[THEME_BG],
            theme[pressed ? THEME_LIGHT : THEME_DARK]);
        return;
    }
    if(scroll + page > num) scroll = num - page;
    if(scroll < 0) scroll = 0;
    ui_box(dst, x, y, w, h, theme[THEME_DARKER], theme[THEME_DARKER], theme[THEME_DARKER]);
    ui_box(dst, x, y + (h - 20) * scroll / num, w, 20 + (h - 20) * page / num,
        theme[pressed ? THEME_DARK : THEME_LIGHT], theme[THEME_BG], theme[pressed ? THEME_LIGHT : THEME_DARK]);
}

/**
 * Horizontal scrollbar
 */
void ui_hscrbar(SDL_Texture *dst, int x, int y, int w, int h, int scroll, int page, int num, int pressed)
{
    if(!num || page > num) {
        ui_box(dst, x, y, w, h, theme[pressed ? THEME_DARK : THEME_LIGHT], theme[THEME_BG],
            theme[pressed ? THEME_LIGHT : THEME_DARK]);
        return;
    }
    if(scroll + page > num) scroll = num - page;
    if(scroll < 0) scroll = 0;
    ui_box(dst, x, y, w, h, theme[THEME_DARKER], theme[THEME_DARKER], theme[THEME_DARKER]);
    ui_box(dst, x + (w - 20) * scroll / num, y, 20 + (w - 20) * page / num, h,
        theme[pressed ? THEME_DARK : THEME_LIGHT], theme[THEME_BG], theme[pressed ? THEME_LIGHT : THEME_DARK]);
}

/**
 * Display a data table
 */
void ui_table(SDL_Texture *dst, int x, int y, int w, int h, ui_table_t *table, int pressed)
{
    uint8_t *data;
    uint32_t f = ssfn_dst.fg;
    int i, k, dy, dh, hx, y2, page;
    SDL_Rect save;

    if(!dst || !table || w < 1 || h < 1) return;
    if(pressed == -1) {
        ui_box(dst, x, y, w, h, theme[THEME_INA], theme[THEME_INA], theme[THEME_INA]);
        return;
    }
    table->x = x;
    table->y = y;
    table->w = w;
    table->h = h;
    dy = y + 1 + (table->hdr ? 20 : 0);
    dh = h - 2 - (table->hdr ? 20 : 0);
    ui_box2(dst, x, y, w, h, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER], table->hdr ? 20 : 0, table->scr * 18,
        table->row, 0);

    if(table->hdr) {
        for(i = 0, hx = x + 1; table->hdr[i].label && hx < x + w; i++) {
            ui_box(dst, hx, y + 1, table->hdr[i].w, 20, theme[THEME_LIGHTER], theme[THEME_BG], theme[THEME_DARKER]);
            if(table->hdr[i].label)
                ui_text2(dst, hx + 4, y + 3, table->hdr[i].label, theme[THEME_DARKER], theme[THEME_LIGHTER]);
            hx += table->hdr[i].w;
        }
    }
    if(table->val >= table->num) table->val = table->num - 1;

    if(table->entsize) {
        if(table->first || (table->data && table->num > 0)) {
            page = dh / (table->row ? table->row : 1);
            table->page = page;
            if(!table->data || !table->num) table->val = table->scr = -table->first;
            if(table->val < table->scr) table->scr = table->val;
            if(table->val > table->scr + table->page - 1) table->scr = table->val - table->page + 1;
            if(table->scr < -table->first) table->scr = -table->first;
            data = (uint8_t*)table->data + table->entsize * table->scr;
            ui_vscrbar(dst, x + w - 13, dy, 12, dh, table->scr, table->page, table->num + table->first - 1, pressed &&
                table->fld == -1);
            memcpy(&save, &ui_clip, sizeof(ui_clip)); ui_clip.x = x + 1; ui_clip.y = dy; ui_clip.w = w - 14; ui_clip.h = dh;
            if(ui_clip.x + ui_clip.w > scr_w - 11) ui_clip.w = scr_w - 11 - ui_clip.x;
            if(ui_clip.y + ui_clip.h > scr_h - 5) ui_clip.h = scr_h - 5 - ui_clip.y;
            y2 = dy + dh;
            for(i = table->scr; i < table->num && dy < y2; i++, dy += table->row, data += table->entsize) {
                k = i == table->val;
                ssfn_dst.fg = theme[k ? THEME_SELFG : THEME_FG];
                if(k && pressed)
                    ui_box(dst, x + 1, dy, w - 14, table->row, theme[THEME_SELBG], theme[THEME_SELBG], theme[THEME_SELBG]);
                if(table->drawcell)
                    (*table->drawcell)(dst, i, table->val, x, dy, w - 14, table->row, table->hdr, i < 0 ? NULL : data);
            }
            memcpy(&ui_clip, &save, sizeof(ui_clip));
        }
    }
    ssfn_dst.fg = f;
}

/**
 * Display a navigation tab
 */
void ui_tab(SDL_Texture *dst, int x, int y, char **tabs)
{
    if(!dst || !tabs) return;
    tabx = x; taby = y;
    for(tabn = 0; tabs[tabn]; tabn++, x += 100) {
        ui_box(dst, x, y, 80, 20, theme[tabn == tabc ? THEME_DARK : THEME_LIGHT], theme[THEME_BG],
            theme[tabn == tabc ? THEME_LIGHT : THEME_DARK]);
        ui_text(dst, x + (80 - ui_textwidth(tabs[tabn])) / 2, y + (tabn != tabc ? 2 : 3), tabs[tabn]);
    }
}

/**
 * Common error handler
 */
void main_error(int msg)
{
    char *mess = lang ? lang[msg] : dict[0][msg];
#ifdef __WIN32__
    wchar_t buf[256];
    int wlen;

    wlen = MultiByteToWideChar(CP_UTF8, 0, mess, strlen(mess), NULL, 0);
    if(wlen > 255) wlen = 255;
    if(!wlen) {
        /* we don't have stderr, only stdout */
        fprintf(stdout, "tngd: %s\r\n", mess);
        MessageBoxA(NULL, mess, "TirNanoG Dumper", MB_ICONEXCLAMATION | MB_OK);
    } else {
        MultiByteToWideChar(CP_UTF8, 0, mess, strlen(mess), buf, wlen);
        buf[wlen] = 0;
        fwprintf(stdout, L"tngd: %s\r\n", buf);
        MessageBoxW(NULL, buf, L"TirNanoG Dumper", MB_ICONEXCLAMATION | MB_OK);
    }
#else
    SDL_DisplayMode dm;
    SDL_Window *window = NULL;
    SDL_Surface *surface = NULL;
    char *s = mess;
    int w;

    fprintf(stderr, "tngd: %s\r\n", mess);
    fflush(stdout); fflush(stderr);
    /* create a dummy MessageBox on Linux */
    if(msg != ERR_DISPLAY && ssfn_src && SDL_WasInit(SDL_INIT_VIDEO)) {
        SDL_ShowCursor(SDL_ENABLE);
        w = ui_textwidth(mess) + 48;
        SDL_GetDesktopDisplayMode(0, &dm);
        window = SDL_CreateWindow("TirNanoG Dumper", (dm.w - w) / 2, (dm.h - 64) / 2, w, 64, SDL_WINDOW_ALWAYS_ON_TOP |
            SDL_WINDOW_SKIP_TASKBAR);
        if(window) {
            surface = SDL_GetWindowSurface(window);
            if(surface) {
                memset(surface->pixels, 0x88, 64 * surface->pitch);
                ssfn_dst.ptr = (uint8_t*)surface->pixels + 24 * surface->pitch + 96;
                ssfn_dst.p = surface->pitch;
                ssfn_dst.x = ssfn_dst.y = ssfn_dst.w = ssfn_dst.h = 0; ssfn_dst.bg = 0; ssfn_dst.fg = 0xff000000;
                while(s && *s) ssfn_putc(ssfn_utf8(&s));
                while(1) {
                    SDL_UpdateWindowSurface(window);
                    SDL_RaiseWindow(window);
                    SDL_WaitEvent(&event);
                    if(event.type == SDL_QUIT || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_KEYUP ||
                        (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE)) break;
                }
            }
            SDL_DestroyWindow(window);
        }
    }
#endif
    main_quit();
    exit(1);
}

/**
 * Allocate memory with error handler
 */
void *main_alloc(size_t size)
{
    void *tmp = malloc(size);
    if(!tmp) main_error(ERR_MEM);
    memset(tmp, 0, size);
    return tmp;
}

/**
 * Reallocate memory with error handler
 */
void *main_realloc(void *ptr, size_t size)
{
    void *tmp = realloc(ptr, size);
    if(!tmp) main_error(ERR_MEM);
    return tmp;
}

/**
 * Seek in one of the tng files
 */
void world_seektng(int idx, int strm, uint64_t offs)
{
# ifdef __WIN32__
    fpos_t pos = (fpos_t)offs;
# else
    fpos_t pos = {0};
    pos.__pos = offs;
# endif
    if(idx < 0 || idx >= world.numtng || strm < 0 || strm > 2) return;
    world.tng[idx].offs[strm] = offs;
    fsetpos(world.tng[idx].f[strm], &pos);
}

/**
 * Read data from one of the tng files
 */
int world_readtng(int idx, int strm, void *buff, int len)
{
    if(idx < 0 || idx >= world.numtng || !buff || len < 1 || strm < 0 || strm > 2) return 0;
    if((len = fread(buff, 1, len, world.tng[idx].f[strm])) < 1) return 0;
    world.tng[idx].offs[strm] += len;
    return len;
}

/**
 * Return true on asset eof
 */
int world_eofasset(int strm, world_asset_t *asset)
{
    if(!asset || asset->tng < 0 || asset->tng >= world.numtng || strm < 0 || strm > 2) return 1;
    return world.tng[asset->tng].offs[strm] >= asset->offs + asset->size;
}

/**
 * Get stream duration
 */
# define TH_VERSION_CHECK(_info,_maj,_min,_sub) \
 ((_info)->version_major>(_maj)||((_info)->version_major==(_maj)&& \
 (((_info)->version_minor>(_min)||((_info)->version_minor==(_min)&& \
 (_info)->version_subminor>=(_sub))))))
int theora_getduration(tng_asset_desc_t *asset)
{
    uint64_t end;
    uint32_t size;
    uint8_t *buff;
    char *buffer;
    int hv, ha, s, sv = 0, sa = 0, dur = 0;
    ogg_int64_t      granulepos, iframe, pframe;
    ogg_packet       op;
    ogg_sync_state   oy;
    ogg_page         og;
    ogg_stream_state vo;
    ogg_stream_state to;
    ogg_stream_state test;
    th_info          ti;
    th_comment       tc;
    th_setup_info    *ts;
    vorbis_info      vi;
    vorbis_dsp_state vd;
    vorbis_comment   vc;

    if(!f || !asset) return 0;
    end = asset->offs + sizeof(tng_hdr_t) + 4 + sec_size;
    world_seektng(0, 0, end);
    end += asset->size;
    ogg_sync_init(&oy);
    vorbis_info_init(&vi);
    vorbis_comment_init(&vc);
    th_comment_init(&tc);
    th_info_init(&ti);
    ts = NULL;

    /* Ogg file open; parse the headers */
    /* Only interested in Vorbis/Theora streams */
    s = hv = ha = 0;
    while (!s) {
        buffer = ogg_sync_buffer(&oy, 4096);
        s = world.tng[0].offs[0] + 4096 < end ? 4096 : end - world.tng[0].offs[0];
        s = world_readtng(0, 0, buffer, s);
        if(s <= 0 || ogg_sync_wrote(&oy, s)) break;
        s = 0;
        while(ogg_sync_pageout(&oy, &og) > 0) {
            if(!ogg_page_bos(&og)) {
                if(hv) ogg_stream_pagein(&to, &og);
                if(ha) ogg_stream_pagein(&vo, &og);
                s = 1;
                break;
            }
            ogg_stream_init(&test, ogg_page_serialno(&og));
            ogg_stream_pagein(&test, &og);
            ogg_stream_packetout(&test, &op);
            if(!hv && th_decode_headerin(&ti, &tc, &ts, &op) >= 0) {
                memcpy(&to, &test, sizeof(test));
                sv = ogg_page_serialno(&og);
                hv = 1;
            } else
            if(!ha && vorbis_synthesis_headerin(&vi, &vc, &op) >= 0) {
                memcpy(&vo, &test, sizeof(test));
                sa = ogg_page_serialno(&og);
                ha = 1;
            } else
                ogg_stream_clear(&test);
        }
    }
    if(ha || hv) {
        /* we're expecting more header packets. */
        while(world.tng[0].offs[0] < end && ((hv && hv < 3) || (ha && ha < 3))) {
            while(hv && (hv < 3)) {
                if(ogg_stream_packetout(&to, &op) != 1) break;
                if(!th_decode_headerin(&ti, &tc, &ts, &op)) { hv = 0; break; }
                hv++;
            }
            while(ha && (ha < 3)) {
                if(ogg_stream_packetout(&vo, &op) != 1) break;
                if(vorbis_synthesis_headerin(&vi, &vc, &op)) { ha = 0; break; }
                ha++;
            }
            if(ogg_sync_pageout(&oy, &og) > 0) {
                if(hv) ogg_stream_pagein(&to, &og);
                if(ha) ogg_stream_pagein(&vo, &og);
            } else {
                buffer = ogg_sync_buffer(&oy, 4096);
                s = world.tng[0].offs[0] + 4096 < end ? 4096 : end - world.tng[0].offs[0];
                s = world_readtng(0, 0, buffer, s);
                if(s <= 0 || ogg_sync_wrote(&oy, s)) break;
            }
        }
        /* and now we have it all.  initialize ti and vi structures */
        if(!(hv && ti.pixel_fmt == TH_PF_420 && ti.pic_width > 0 && ti.pic_height > 0 &&
          ti.pic_width < 16384 && ti.pic_height < 16384)) {
            /* tear down the partial theora setup */
            th_info_clear(&ti);
            th_comment_clear(&tc);
            hv = 0;
        }
        if(ts) th_setup_free(ts);

        if(ha){
            vorbis_synthesis_init(&vd, &vi);
        } else {
            vorbis_info_clear(&vi);
            vorbis_comment_clear(&vc);
        }
        /* read in the last 128K of the file. Forget tht horrible ogg_sync API from now on */
        if(asset->size > 128 * 1024) {
            size = 128 * 1024;
            world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size + asset->size - size);
        } else {
            size = asset->size;
            world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        }
        buff = (uint8_t*)malloc(size);
        if(buff) {
            s = world_readtng(0, 0, buff, size);
            /* locate the last ogg packet */
            s = (hv ? sv : sa);
            for(og.header = buff + size - 19; og.header > buff && (memcmp(og.header, "OggS", 4) || ogg_page_serialno(&og) != s);
                og.header--);
            if(og.header > buff) {
                granulepos = ogg_page_granulepos(&og);
                /* if we have found the last packet of the audio stream */
                if(s == sa) {
                    dur = (int)((granulepos * 100 + vi.rate - 1) / vi.rate);
                } else
                /* if we have found the last packet of the video stream */
                if(s == sv) {
                    /* this is from th_granule_time() */
                    iframe = granulepos >> ti.keyframe_granule_shift;
                    pframe = granulepos - (iframe << ti.keyframe_granule_shift);
                    granulepos = iframe + pframe - TH_VERSION_CHECK(&ti,3,2,1);
                    if(ti.fps_numerator < 1) ti.fps_numerator = 1;
                    dur = (int)(((granulepos + 1) * 100 * ti.fps_denominator + ti.fps_numerator - 1) / ti.fps_numerator);
                }
            }
            free(buff);
        }
    }
    if(ha) {
        ogg_stream_clear(&vo);
        vorbis_dsp_clear(&vd);
    }
    vorbis_comment_clear(&vc);
    vorbis_info_clear(&vi);

    if(hv) ogg_stream_clear(&to);
    th_comment_clear(&tc);
    th_info_clear(&ti);
    ogg_sync_clear(&oy);

    return dur;
}

/**
 * Free temporary asset buffers
 */
void assets_free()
{
    dirs_tbl.scr = dirs_tbl.val = frames_tbl.scr = frames_tbl.val = 0;
    if(sprites) {
        if(sprites[0]) free(sprites[0]);
        free(sprites); sprites = NULL; numsprites = 0;
    }
    if(back) {
        SDL_DestroyTexture(back); back = NULL; back_w = back_h = 0;
    }
    if(mnes) {
        free(mnes); mnes = NULL; free(mne); mne = NULL; nummne = 0;
    }
    if(abc) {
        free(abc); abc = NULL; abclen = 0;
    }
    if(optsbuf) {
        free(optsbuf); optsbuf = NULL;
    }
    if(opts) {
        free(opts); opts = NULL; numopts = 0;
    }
    if(tiles) {
        free(tiles); tiles = NULL;
    }
}

/**
 * Free temporary section buffers
 */
void section_free()
{
    assets_free();
    sec_tab = assets_tbl.scr = assets_tbl.val = numtranslation = numcrd = numattrs = tabc = tilesx = tilesy = 0; zoom = 1;
    origx = origy = -1;
    if(translation) { free(translation); translation = NULL; }
    if(translations) { free(translations); translations = NULL; }
    if(crds) { free(crds); crds = NULL; }
    if(attrs) { free(attrs); attrs = NULL; }
}

/**
 * Disassembly bytecode
 */
void bc_disasm(uint8_t *bc, int len)
{
    uint8_t *ptr = bc, *end = bc + len;
    uint32_t c;
    int i, t = 1, o = 0, n, m;
    char *s;

    if(!ptr || len < 1) return;
    nummne = 0;
    while(ptr < end && t) {
        mne = (int*)realloc(mne, (nummne + 1) * sizeof(int));
        if(!mne) {
            if(mnes) { free(mnes); mnes = NULL; }
            nummne = 0;
            return;
        }
        i = ptr - bc; t = ptr[0]; m = 128 + strmax;
        if(t == TNG_BC_SWITCH) { n = ptr[1]; m = 48 + n * 7; }
        if(t == TNG_BC_PUSHTXT && transls && transl) {
            c = 0; memcpy(&c, ptr + 1, 3);
            if(c < (uint32_t)numtransl) m = 48 + strlen(transl[c]);
        }
        mnes = (char*)realloc(mnes, o + m);
        if(!mnes) {
            free(mne); mne = NULL; nummne = 0;
            return;
        }
        memset(mnes + o, 0, m);
        mne[nummne++] = o;
        s = mnes + o; s += sprintf(s, "%06X: ", i);
        switch(t) {
            case TNG_BC_FNC0: case TNG_BC_FNC1: case TNG_BC_FNC2: case TNG_BC_FNC3:
            case TNG_BC_MIN: case TNG_BC_MAX: case TNG_BC_POP: case TNG_BC_PUSH: case TNG_BC_PUSH8: i = 2; break;
            case TNG_BC_CNT1: case TNG_BC_CNTO1: case TNG_BC_PUSH16: i = 3; break;
            case TNG_BC_JMP: case TNG_BC_JZ: case TNG_BC_SUM: case TNG_BC_SUMO: case TNG_BC_POPA: case TNG_BC_POPO:
            case TNG_BC_PUSHA: case TNG_BC_PUSHO: case TNG_BC_PUSH24: case TNG_BC_PUSHSPR: case TNG_BC_PUSHMUS:
            case TNG_BC_PUSHSND: case TNG_BC_PUSHSPC: case TNG_BC_PUSHCHR: case TNG_BC_PUSHCUT: case TNG_BC_PUSHDLG:
            case TNG_BC_PUSHCFT: case TNG_BC_PUSHQST: case TNG_BC_PUSHTXT: i = 4; break;
            case TNG_BC_SWITCH: case TNG_BC_RND: case TNG_BC_PUSH32: i = 5; break;
            case TNG_BC_PUSHMAP: i = 8; break;
            default: i = 1; break;
        }
        for(m = 0; m < 8; m++)
            s += sprintf(s, m < i ? "%02X " : (t == TNG_BC_SWITCH && m == i && n > 1 ? "..." : "   "), ptr[m]);
        ptr++; c = 0; memcpy(&c, ptr, 3);
        switch(t) {
            case TNG_BC_END:    s += sprintf(s, "END"); break;
            case TNG_BC_SWITCH:
                s += sprintf(s, "SWITCH "); ptr++;
                for(i = 0; i < n; i++, ptr += 3) s += sprintf(s, " %02X%02X%02X", ptr[2], ptr[1], ptr[0]);
            break;
            case TNG_BC_JMP:    s += sprintf(s, "JMP     %02X%02X%02X", ptr[2], ptr[1], ptr[0]); ptr += 3; break;
            case TNG_BC_JZ:     s += sprintf(s, "JZ      %02X%02X%02X", ptr[2], ptr[1], ptr[0]); ptr += 3; break;
            case TNG_BC_FNC0:
            case TNG_BC_FNC1:
            case TNG_BC_FNC2:
            case TNG_BC_FNC3:   s += sprintf(s, "CALL    %s(%d)", ptr[0] < 5 || ptr[0] > TNG_BC_FUNC_MAX ? "???" : cmd_types[ptr[0]], t - TNG_BC_FNC0); ptr++; break;
            case TNG_BC_CNT0:   s += sprintf(s, "CNT0"); break;
            case TNG_BC_CNT1:   s += sprintf(s, "CNT1    1.%02X%02X", ptr[1], ptr[0]); ptr += 2; break;
            case TNG_BC_SUM:    s += sprintf(s, "SUM     %s", str(c)); ptr += 3; break;
            case TNG_BC_CNTO0:  s += sprintf(s, "CNTO0"); break;
            case TNG_BC_CNTO1:  s += sprintf(s, "CNTO1   1.%02X%02X", ptr[1], ptr[0]); ptr += 2; break;
            case TNG_BC_SUMO:   s += sprintf(s, "SUMO    %s", str(c)); ptr += 3; break;
            case TNG_BC_RND:    s += sprintf(s, "RND     %u", *((uint32_t*)ptr)); ptr += 4; break;
            case TNG_BC_MIN:    s += sprintf(s, "MIN     (%u)", ptr[0]); ptr++; break;
            case TNG_BC_MAX:    s += sprintf(s, "MAX     (%u)", ptr[0]); ptr++; break;
            case TNG_BC_ADD:    s += sprintf(s, "ADD     (2)"); break;
            case TNG_BC_SUB:    s += sprintf(s, "SUB     (2)"); break;
            case TNG_BC_MUL:    s += sprintf(s, "MUL     (2)"); break;
            case TNG_BC_DIV:    s += sprintf(s, "DIV     (2)"); break;
            case TNG_BC_MOD:    s += sprintf(s, "MOD     (2)"); break;
            case TNG_BC_EQ:     s += sprintf(s, "EQ      (2)"); break;
            case TNG_BC_NE:     s += sprintf(s, "NE      (2)"); break;
            case TNG_BC_GE:     s += sprintf(s, "GE      (2)"); break;
            case TNG_BC_GT:     s += sprintf(s, "GT      (2)"); break;
            case TNG_BC_LE:     s += sprintf(s, "LE      (2)"); break;
            case TNG_BC_LT:     s += sprintf(s, "LT      (2)"); break;
            case TNG_BC_NOT:    s += sprintf(s, "NOT     (1)"); break;
            case TNG_BC_OR:     s += sprintf(s, "OR      (2)"); break;
            case TNG_BC_AND:    s += sprintf(s, "AND     (2)"); break;
            case TNG_BC_POP:    s += sprintf(s, "POP     %c", ptr[0] < 26 ? ptr[0] + 'a' : '?'); ptr++; break;
            case TNG_BC_POPA:   s += sprintf(s, "POPA    %s", str(c)); ptr += 3; break;
            case TNG_BC_POPO:   s += sprintf(s, "POPO    %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSH:   s += sprintf(s, "PUSH    %c", ptr[0] < 26 ? ptr[0] + 'a' : '?'); ptr++; break;
            case TNG_BC_PUSHA:  s += sprintf(s, "PUSHA   %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHO:  s += sprintf(s, "PUSHO   %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSH8:  s += sprintf(s, "PUSH8   %u", ptr[0]); ptr++; break;
            case TNG_BC_PUSH16: s += sprintf(s, "PUSH16  %u", *((uint16_t*)ptr)); ptr += 2; break;
            case TNG_BC_PUSH24: s += sprintf(s, "PUSH24  %u", c); ptr += 3; break;
            case TNG_BC_PUSH32: s += sprintf(s, "PUSH32  %u", *((uint32_t*)ptr)); ptr += 4; break;
            case TNG_BC_PUSHMAP:s += sprintf(s, "PUSHMAP %s", str(c)); ptr += 3;
                                s += sprintf(s, ",%u,%u", (ptr[1] << 8) | ptr[0], (ptr[3] << 8) | ptr[2]); ptr += 4; break;
            case TNG_BC_PUSHSPR:s += sprintf(s, "PUSHSPR %u.%02X%02X", ptr[0], ptr[2], ptr[1]); ptr += 3; break;
            case TNG_BC_PUSHMUS:s += sprintf(s, "PUSHMUS %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHSND:s += sprintf(s, "PUSHSND %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHSPC:s += sprintf(s, "PUSHSPC %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHCHR:s += sprintf(s, "PUSHCHR %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHCUT:s += sprintf(s, "PUSHCUT %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHDLG:s += sprintf(s, "PUSHDLG %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHCFT:s += sprintf(s, "PUSHCFT %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHQST:s += sprintf(s, "PUSHQST %s", str(c)); ptr += 3; break;
            case TNG_BC_PUSHTXT:s += sprintf(s, "PUSHTXT %u \"%s\"", c, transls && transl && c < (uint32_t)numtransl ? transl[c] : "???"); ptr += 3; break;
            default: s += sprintf(s, "INVALID aborting disassembly"); t = 0; break;
        }
        o = (int)((uintptr_t)s - (uintptr_t)mnes) + 1;
    }
}

/**
 * Header view
 */
void view_header()
{
    int i, j, k;
    char tmp[128], *s;
    uint8_t *c;

    sprintf(tmp, "TirNanoG Dumper v%s (build %u) by bzt", tngpver, BUILD);
    ui_text2(screen, 220, 4, tmp, theme[THEME_DARKER], theme[THEME_LIGHTER]);
    ssfn_dst.fg = theme[THEME_FG];

    j = 328;
    if(hdr.magic[0] == '#' || hdr.magic[0] == 'T') {
        for(i = 0, s = tmp; i < 16; i++)
            s += sprintf(s, "%02X ", hdr.magic[i]);
    } else {
        for(i = 0, s = tmp; i < 8; i++)
            s += sprintf(s, "%02X ", hdr.magic[i]);
        sprintf(s, " proto %02X%02X flags %02X%02X%02X%02X%02X%02X", hdr.magic[9], hdr.magic[8],
            hdr.magic[15], hdr.magic[14], hdr.magic[13], hdr.magic[12], hdr.magic[11], hdr.magic[10]);
    }
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Magic:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, j, 32, tmp);

    memcpy(tmp, hdr.id, 16); tmp[16] = 0;
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 48, "GameID:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, j, 48, tmp);

    if(hdr.magic[0] == 'T') {
        sprintf(tmp, "%u bytes (uncompressed %u bytes)", sec_size, sec_unc);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 64, "Chunks:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 64, tmp);
        if(prvw) {
            ssfn_dst.fg = theme[THEME_LIGHTER];
            ui_text(screen, 220, 80, "Preview data:");
            for(i = 0, s = tmp; i < 16; i++)
                s += sprintf(s, "%02X ", prvw[i]);
            ssfn_dst.fg = theme[THEME_FG];
            ui_text(screen, j, 80, tmp);
            for(s = tmp; i < 32; i++)
                s += sprintf(s, "%02X ", prvw[i]);
            ui_text(screen, j, 96, tmp);
            ssfn_dst.fg = theme[THEME_LIGHTER];
            ui_text(screen, 220, 112, "Time played:");
            memcpy(&k, prvw + 8, 4);
            sprintf(tmp, "%u secs (%u days, %u:%02u:%02u)", k, k / 24 / 3600, (k / 3600) % 23, (k / 60) % 60, k % 60);
            ssfn_dst.fg = theme[THEME_FG];
            ui_text(screen, j, 112, tmp);
            ssfn_dst.bg = 0;
            ssfn_dst.fg = theme[THEME_LIGHTER];
            ui_text(screen, 220, 128, "Image:");
            k = (prvw[31] << 8) | prvw[30];
            sprintf(tmp, "%u bytes (%u x %u pixels)", plen - 32, k, k);
            ssfn_dst.fg = theme[THEME_FG];
            if(k < 16) ssfn_dst.bg = theme[THEME_ERR];
            if(k > 1024) { k = 1024; ssfn_dst.bg = theme[THEME_ERR]; }
            ui_text(screen, j, 128, tmp);
            ssfn_dst.bg = 0;
            ssfn_dst.fg = theme[THEME_FG];
            ui_box2(screen, 220, 150, k + 2, k + 2, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER], 0, 0, 8, 8);
            if(!preview) {
                ssfn_dst.bg = theme[THEME_ERR];
                ui_text(screen, 224, 154, " IMAGE ERROR ");
                ssfn_dst.bg = 0;
            }
        }
    } else {
        sprintf(tmp, "%02X, %u", hdr.revision, hdr.revision);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 64, "Revision:");
        ssfn_dst.fg = theme[THEME_FG];
        if(hdr.revision) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 64, tmp);
        if(hdr.revision) {
            ui_text(screen, ssfn_dst.x + 8, 64, " (should be 0) ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 64, "OK");

        sprintf(tmp, "%02X, %s", hdr.type, hdr_types[hdr.type & 0x0F]);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 80, "Type:");
        ssfn_dst.fg = theme[THEME_FG];
        if((hdr.type & 0x0F) > TNG_TYPE_ISO) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 80, tmp);
        if((hdr.type & 0x0F) > TNG_TYPE_ISO) {
            ui_text(screen, ssfn_dst.x + 8, 80, " (should be 0 or 1) ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 80, "OK");

        sprintf(tmp, "%02X %02X, %u x %u pixels", hdr.tilew, hdr.tileh, tilew, tileh);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 96, "Tiles:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 96, tmp);

        sprintf(tmp, "%02X, %u x %u tiles", hdr.mapsize, (1 << hdr.mapsize), (1 << hdr.mapsize));
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 112, "Map:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 112, tmp);

        sprintf(tmp, "%02X, %u x %u pixels", hdr.atlaswh, (1 << hdr.atlaswh), (1 << hdr.atlaswh));
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 128, "Atlas:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 128, tmp);

        sprintf(tmp, "%02X, %u Hz", hdr.freq, hdr.freq < (int)(sizeof(freqs)/sizeof(freqs[0])) ? freqs[hdr.freq] : 0);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 144, "Audio:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 144, tmp);

        sprintf(tmp, "%02X, %3u", hdr.fps, hdr.fps);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 160, "FPS:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 160, tmp);

        sprintf(tmp, "%02X, %3u", hdr.numact, hdr.numact);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 176, "Actions:");
        ssfn_dst.fg = theme[THEME_FG];
        if(hdr.numact > 9) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 176, tmp);
        if(hdr.numact > 9) {
            ui_text(screen, ssfn_dst.x + 8, 176, " (should be <= 9) ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 176, "OK");

        sprintf(tmp, "%02X, %3u", hdr.numlyr, hdr.numlyr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 192, "Layers:");
        ssfn_dst.fg = theme[THEME_FG];
        if(hdr.numlyr != 11) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 192, tmp);
        if(hdr.numlyr != 11) {
            ui_text(screen, ssfn_dst.x + 8, 192, " (should be 11) ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 192, "OK");

        sprintf(tmp, "%02X, %3u methods", hdr.numtrn, hdr.numtrn);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 208, "Transports:");
        ssfn_dst.fg = theme[THEME_FG];
        if(hdr.numtrn != 3) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 208, tmp);
        if(hdr.numtrn != 3) {
            ui_text(screen, ssfn_dst.x + 8, 208, " (should be 3) ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 208, "OK");

        sprintf(tmp, "%02X, %3u (highest function code)", hdr.maxbc, hdr.maxbc);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 224, "Max command:");
        ssfn_dst.fg = theme[THEME_FG];
        if(hdr.maxbc > TNG_BC_FUNC_MAX) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, j, 224, tmp);
        if(hdr.maxbc > TNG_BC_FUNC_MAX) {
            sprintf(tmp, " (should be <= %u) ", TNG_BC_FUNC_MAX);
            ui_text(screen, ssfn_dst.x + 8, 224, tmp);
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, ssfn_dst.x + 8, 224, "OK");

        sprintf(tmp, "%02X, %s%3u pixels (character base line)", hdr.deltay, hdr.deltay > 0 ? "+" : "", hdr.deltay);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 240, "Delta Y:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 240, tmp);

        sprintf(tmp, "%02X, %3u sprites", hdr.sprperlyr, hdr.sprperlyr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 256, "Spr/Layer:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 256, tmp);

        for(i = 0, s = tmp, c = (uint8_t*)&hdr.unique; i < 8; i++)
            s += sprintf(s, "%02X ", c[i]);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 272, "UniqueID:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 272, tmp);

        if(hdr.magic[0] == '#') {
            sprintf(tmp, "%08X", hdr.crc);
            ssfn_dst.fg = theme[THEME_LIGHTER];
            ui_text(screen, 220, 288, "CRC:");
            ssfn_dst.fg = theme[THEME_FG];
            if(hdr.crc != chkcrc) ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, j, 288, tmp);
            if(hdr.crc != chkcrc) {
                sprintf(tmp, " BAD CHECKSUM (should be %08X) ", chkcrc);
            } else {
                sprintf(tmp, "OK");
            }
            ui_text(screen, j + 72, 288, tmp);
            ssfn_dst.bg = 0;
        } else {
            sprintf(tmp, "%08X%08X", hdr.enc, hdr.crc);
            ssfn_dst.fg = theme[THEME_LIGHTER];
            ui_text(screen, 220, 288, "Boundle size:");
            ssfn_dst.fg = theme[THEME_FG];
            ui_text(screen, j, 288, tmp);
        }

        sprintf(tmp, "%u bytes (uncompressed %u bytes)", sec_size, sec_unc);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 304, "Sections:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 304, tmp);

        sprintf(tmp, "%u bytes", sec->offs);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 320, "Sectiontable:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 320, tmp);

        if(!tng_compat(&hdr)) {
            ssfn_dst.fg = theme[THEME_SELFG];
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, 220, 352, " NOT COMPATIBLE with latest player ");
            ssfn_dst.bg = 0;
        } else
            ui_text(screen, 220, 352, "Compatible with latest player");
    }
}

/**
 * Draw table cell
 */
void draw_texts(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    ui_text(dst, x + 4, y + 1, *((char**)data));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_assets(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 140, y + 1, tmp);
    sprintf(tmp, "%06X", asset->name & 0xffffff);
    ui_text(dst, x + 228, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(asset->name));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_obj(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    uint32_t o = ssfn_dst.bg;
    char tmp[32];
    int k;

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 140, y + 1, tmp);
    k = asset->name & 0xffff;
    if(k >= numspr[1]) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "1.%04X", k);
    ui_text(dst, x + 228, y + 1, tmp);
    ssfn_dst.bg = o;
}

/**
 * Draw table cell
 */
void draw_backs(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%04X", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 44, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 180, y + 1, tmp);
    if(idx == sel) {
        sprintf(tmp, "%u x %u", back_w, back_h);
        ui_text(dst, x + 268, y + 1, tmp);
    }
}

/**
 * Draw table cell
 */
void draw_atlas(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)sel; (void)hdr; (void)w; (void)h;
    if(!data || idx >= numatlas) return;
    sprintf(tmp, "%03X", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 44, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 180, y + 1, tmp);
    sprintf(tmp, "%u x %u", atlas[idx].w, atlas[idx].h);
    ui_text(dst, x + 276, y + 1, tmp);
}

/**
 * Draw table cell
 */
void draw_sprites(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%04X", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%011lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 44, y + 1, tmp);
    sprintf(tmp, "%4u", asset->size);
    ui_text(dst, x + 140, y + 1, tmp);
    sprintf(tmp, "%2u", asset->name >> 24);
    ui_text(dst, x + 180, y + 1, tmp);
}

/**
 * Draw table cell
 */
void draw_frames(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_sprmap_desc_t *frame = (tng_sprmap_desc_t*)data;
    char tmp[32];
    int i;

    (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%03X", frame->atlasid & 0xfff);
    ui_text(dst, x + 12, y + 1, tmp);
    i = frame->atlasid >> 12;
    sprintf(tmp, "%3u?", i);
    ui_text(dst, x + 52, y + 1, i < (int)(sizeof(atlop_types)/sizeof(atlop_types[0])) ? (char*)atlop_types[i] : tmp);
    sprintf(tmp, "%4u", frame->x);
    ui_text(dst, x + 90, y + 1, tmp);
    sprintf(tmp, "%4u", frame->y);
    ui_text(dst, x + 128, y + 1, tmp);
    sprintf(tmp, "%4u", frame->w);
    ui_text(dst, x + 166, y + 1, tmp);
    sprintf(tmp, "%4u", frame->h);
    ui_text(dst, x + 204, y + 1, tmp);
    sprintf(tmp, "%4u", frame->l);
    ui_text(dst, x + 242, y + 1, tmp);
    sprintf(tmp, "%4u", frame->t);
    ui_text(dst, x + 280, y + 1, tmp);
    if(idx == sel) currframe = frame;
}

/**
 * Draw table cell
 */
void draw_dirs(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_sprite_t *spr = *((tng_sprite_t**)data);
    char tmp[32];
    int i;

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%5u", spr->w);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%5u", spr->h);
    ui_text(dst, x + 60, y + 1, tmp);
    i = TNG_SPRITE_DIR(spr);
    ui_text(dst, x + 116, y + 1, i < 8 ? (char*)dir_types[i] : "??");
    i = TNG_SPRITE_ANIMTYPE(spr);
    ui_text(dst, x + 156, y + 1, i < 3 ? (char*)anim_types[i] : "??");
    sprintf(tmp, "%3u", spr->nframe);
    ui_text(dst, x + 196, y + 1, tmp);
}

/**
 * Display a font reference
 */
uint8_t *view_font(uint8_t *ptr, int x, int y)
{
    char tmp[16];
    uint32_t c = 0;

    sprintf(tmp, "%02X%c%c %3u", ptr[0], ptr[0] & 1 ? 'B' : '.', ptr[0] & 2 ? 'I' : '.', ptr[1]); ptr += 2;
    ui_text(screen, x, y, tmp);
    memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    ui_text(screen, ssfn_dst.x + 8, y, !c ? "(default)" : str(c));
    ssfn_dst.bg = 0;
    return ptr;
}

/**
 * Draw table cell
 */
void draw_fonts(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32], *s = "Lorem Ipsum Dolor Sic Amet Igitur";
    uint8_t *buf;
    uint32_t unicode;
    ssfn_t ctx;
    int ret;

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 140, y + 1, tmp);
    sprintf(tmp, "%06X", asset->name & 0xffffff);
    ui_text(dst, x + 228, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(asset->name));
    ssfn_dst.bg = 0;
    if(idx == sel) {
        ui_clip.h = scr_h - 5 - ui_clip.y; ui_clip.w = scr_w - 10 - ui_clip.x;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size)) {
            memset(&ctx, 0, sizeof(ctx));
            ssfn_load(&ctx, buf);
            ssfn_select(&ctx, SSFN_FAMILY_ANY, NULL, SSFN_STYLE_NOCACHE, 14 + 2 * zoom);
            ssfn_dst.x = x; ssfn_dst.y = scr_h - 90 + 24 + 14 + 2 * zoom;
            ssfn_dst.w = scr_w - 10; ssfn_dst.h = scr_h - 5;
            ssfn_dst.fg = theme[THEME_FG];
            for(; (ret = ssfn_render(&ctx, &ssfn_dst, s)) > 0 || ret == SSFN_ERR_NOGLYPH; s += (ret < 1 ? 1 : ret));
            if(ctx.f) {
                ssfn_dst.fg = theme[THEME_LIGHTER];
                ui_text(dst, x, scr_h - 90, "Glyphs:");
                ssfn_dst.fg = theme[THEME_FG];
                if(_ssfn_c(ctx.f, "0", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Numbers");
                if(_ssfn_c(ctx.f, "A", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Latin");
                if(_ssfn_c(ctx.f, "Α", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Greek");
                if(_ssfn_c(ctx.f, "А", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Cyrillic");
                if(_ssfn_c(ctx.f, "Ա", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Armenian");
                if(_ssfn_c(ctx.f, "א", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Hebrew");
                if(_ssfn_c(ctx.f, "ؠ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Arabic");
                if(_ssfn_c(ctx.f, "ܐ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Syriac");
                if(_ssfn_c(ctx.f, "ऐ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Devanagari");
                if(_ssfn_c(ctx.f, "ᄀ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Hangul");
                if(_ssfn_c(ctx.f, "ᚠ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Runic");
                if(_ssfn_c(ctx.f, "⠀", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Braille");
                if(_ssfn_c(ctx.f, "ぁ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Hiragana");
                if(_ssfn_c(ctx.f, "ァ", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "Katakana");
                if(_ssfn_c(ctx.f, "㌀", &ret, &unicode))
                    ui_text(dst, ssfn_dst.x + 8, scr_h - 90, "CJK");
            }
            ssfn_free(&ctx);
        }
        free(buf);
    }
}

/**
 * Draw table cell
 */
void draw_fontrefs(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint32_t c = 0;
    uint8_t *ptr = (uint8_t*)data;
    char tmp[32];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%02X%c%c", ptr[0], ptr[0] & 1 ? 'B' : '.', ptr[0] & 2 ? 'I' : '.'); ptr++;
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%3u", *ptr++);
    ui_text(dst, x + 52, y + 1, tmp);
    memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%3u", (ptr[1] << 8) | ptr[0]);
    ui_text(dst, x + 92, y + 1, tmp);
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 132, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "(default)" : str(c));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_uisprite(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c = ssfn_dst.bg;
    int k;
    char tmp[64];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    k = (ptr[2] << 8) | ptr[1];
    if(ptr[0] > 4 || (k != 0xffff && k >= numspr[(int)ptr[0]])) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%2u", *ptr++);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%04X %s%s", k, idx < (int)(sizeof(ui_types)/sizeof(ui_types[0])) ? ui_types[idx] : "???",
        k == 0xffff ? " (unset)" : "");
    ui_text(dst, x + 36, y + 1, tmp);
    ssfn_dst.bg = c;
}

/**
 * Draw sprite reference
 */
uint8_t *draw_sprref(SDL_Texture *dst, int x, int y, uint8_t *ptr)
{
    char tmp[32];
    uint32_t c = ssfn_dst.bg;
    int k = (ptr[2] << 8) | ptr[1];

    if(ptr[0] > 4 || (k != 0xffff && k >= numspr[(int)ptr[0]])) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%u.%04X%s", ptr[0], k, k == 0xffff ? " (unset)" : "");
    ui_text(dst, x, y, tmp);
    ssfn_dst.bg = c;
    return ptr + 3;
}

/**
 * Draw table cell
 */
void draw_menuprx(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[16];
    int k;

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%4d", (int16_t)((ptr[1] << 8) | ptr[0])); ptr += 2;
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%4d", (int16_t)((ptr[1] << 8) | ptr[0])); ptr += 2;
    ui_text(dst, x + 44, y + 1, tmp);
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u.%02u", k / 100, k % 100);
    ui_text(dst, x + 100, y + 1, tmp);
    draw_sprref(dst, x + 140, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_menubtn(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    memcpy(&c, ptr, 4); ptr += 4;
    ui_box(screen, x + 4, y + 5, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
    sprintf(tmp, "%08X", c);
    ui_text(dst, x + 14, y + 1, tmp);

    ptr = draw_sprref(dst, x + 92, y + 1, ptr);
    ptr = draw_sprref(dst, x + 220, y + 1, ptr);
    ptr = draw_sprref(dst, x + 348, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_pbars(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c = 0;
    int i = (scr_w - 140 - 160 - 8 - 224) / 2;
    char tmp[64];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%02X %s", ptr[0], ptr[0] < (uint8_t)(sizeof(pbar_types)/sizeof(pbar_types[0])) ? pbar_types[ptr[0]] : "??");
    ptr++;
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%4d", (ptr[1] << 8) | ptr[0]); ptr += 2;
    ui_text(dst, x + 64, y + 1, tmp);
    sprintf(tmp, "%4d", (ptr[1] << 8) | ptr[0]); ptr += 2;
    ui_text(dst, x + 104, y + 1, tmp);
    memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 144, y + 1, tmp);
    if(!c) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "(err)" : str(c));
    ssfn_dst.bg = 0;
    memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 144 + i, y + 1, tmp);
    if(!c) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "(err)" : str(c));
    ssfn_dst.bg = 0;
    ptr = draw_sprref(dst, x + 144 + i + i, y + 1, ptr);
    ptr = draw_sprref(dst, x + 224 + i + i, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_equips(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%4d", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%4d", (ptr[1] << 8) | ptr[0]); ptr += 2;
    ui_text(dst, x + 44, y + 1, tmp);
    sprintf(tmp, "%4d", (ptr[1] << 8) | ptr[0]); ptr += 2;
    ui_text(dst, x + 84, y + 1, tmp);
    sprintf(tmp, "%08X", 1 << idx);
    ui_text(dst, x + 124, y + 1, tmp);
}

/**
 * Draw translated text
 */
uint8_t *draw_tr(SDL_Texture *dst, int x, int y, uint8_t *ptr)
{
    char tmp[16];
    int c = 0;
    uint32_t o = ssfn_dst.bg;

    memcpy(&c, ptr, 3); if(c == 0xffffff) c = -1;
    sprintf(tmp, "%d", c);
    if(c >= numtransl) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, x, y, tmp);
    if(c == -1)
        ui_text(dst, ssfn_dst.x + 8, y, "(unset)");
    else
        ui_text(dst, ssfn_dst.x + 8, y, transls && transl && c < numtransl ? transl[c] : "???");
    ssfn_dst.bg = o;
    return ptr + 3;
}

/**
 * Draw time
 */
uint8_t *draw_time(SDL_Texture *dst, int x, int y, uint8_t *ptr)
{
    char tmp[16];
    int c = 0;

    memcpy(&c, ptr, 3);
    sprintf(tmp, "%3u.%02u", c / 100, c % 100);
    ui_text(dst, x, y, tmp);
    return ptr + 3;
}

/**
 * Draw table cell
 */
void draw_alerts(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    memcpy(&c, ptr, 4); ptr += 4;
    ui_box(screen, x + 4, y + 5, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
    sprintf(tmp, "%08X", c);
    ui_text(dst, x + 14, y + 1, tmp);

    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 92, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
    ssfn_dst.bg = 0;

    ptr = draw_tr(dst, x + 92 + (scr_w - 88 - 8 - 224) / 2, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_credits(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    crd_t *crd = (crd_t*)data;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    if(!strchr(crdcat, crd->cat)) ssfn_dst.bg = theme[THEME_ERR];
    tmp[0] = crd->cat; tmp[1] = 0;
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;

    sprintf(tmp, "%06X", crd->name);
    ui_text(dst, x + 36, y + 1, tmp);
    if(!crd->name) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, ssfn_dst.x + 8, y + 1, !crd->name ? "(err)" : str(crd->name));
    ssfn_dst.bg = 0;
}

/**
 * Draw attribute
 */
uint8_t *draw_attr(SDL_Texture *dst, int x, int y, uint8_t *ptr)
{
    char tmp[16];
    int c = 0;
    uint32_t o = ssfn_dst.bg;

    if(ptr[0] == 1) { ui_text(dst, x, y, "@"); x += 8; } else
    if(ptr[0] >= 28) { ssfn_dst.bg = theme[THEME_ERR]; ui_text(dst, x, y, "(err)"); ssfn_dst.bg = o; return ptr + 4; } else
    if(ptr[0] > 1) { tmp[0] = ptr[0] - 2 + 'a'; tmp[1] = 0; ui_text(dst, x, y, tmp); return ptr + 4; }
    ptr++;
    memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x, y, tmp);
    ui_text(dst, ssfn_dst.x + 8, y, str(c));
    return ptr;
}

/**
 * Draw table cell
 */
void draw_chooser(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[64];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    if(ptr[0] & 0x80)
        ui_text(dst, x + 4, y + 1, "yes");
    c = ptr[0] & 0x7F; ptr++;
    if(c > 13) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%3u", c);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
    ssfn_dst.bg = 0;
    if(c < 13)
        sprintf(tmp, "0.%02X%02X 0.%02X%02X 0.%02X%02X", ptr[1], ptr[0], ptr[3], ptr[2], ptr[5], ptr[4]);
    else
        sprintf(tmp, "(%u%%,%u%%) - (%u%%,%u%%)", ptr[0], ptr[1], ptr[2], ptr[3]);
    ptr += 6;
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, tmp);
    ssfn_dst.bg = 0;
    if(c < 13) {
        ptr = draw_attr(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w, y + 1, ptr);
        memcpy(&c, ptr, 4); ptr += 4;
        sprintf(tmp, "==%d &&", c);
        ui_text(dst, ssfn_dst.x, y + 1, tmp);
        ptr = draw_attr(dst, ssfn_dst.x + 8, y + 1, ptr);
        ssfn_dst.bg = 0;
        memcpy(&c, ptr, 4); ptr += 4;
        sprintf(tmp, ">%d", c);
        ui_text(dst, ssfn_dst.x, y + 1, tmp);
    } else ptr += 16;
    ptr = draw_attr(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w + hdr[5].w, y + 1, ptr);
    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, ":=%d", c);
    ui_text(dst, ssfn_dst.x, y + 1, tmp);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_cutimg(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    ptr = draw_time(dst, x + 4, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w, y + 1, ptr);
    memcpy(&c, ptr, 4); ptr += 4;
    if(c) {
        ui_box(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w, y + 5, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + 10, y + 1, tmp);
    } else
        ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w, y + 1, "(unset)");
    ptr = draw_sprref(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w, y + 1, ptr);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_cutspc(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    ptr = draw_time(dst, x + 4, y + 1, ptr);

    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
    if(!c) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "(err)" : str(c));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_cutsub(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    ptr = draw_time(dst, x + 4, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, ptr);
    ptr = draw_time(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w, y + 1, ptr);
    memcpy(&c, ptr, 4); ptr += 4;
    if(c) {
        ui_box(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w, y + 5, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + 10, y + 1, tmp);
    } else
        ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w, y + 1, "(unset)");
    sprintf(tmp, "%3u %3u %c", ptr[0], ptr[1], ptr[2] == 0 ? 'l' : (ptr[2] == 1 ? 'c' : (ptr[2] == 2 ? 'r' : '?'))); ptr += 3;
    ui_text(screen, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w, y + 1, tmp);
    ptr = view_font(ptr, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w + hdr[5].w, y + 1);
    ssfn_dst.bg = 0;
    ptr = draw_tr(dst, x + 4, y + 16, ptr);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_cron(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 140, y + 1, tmp);
    if(!asset->name)
        ui_text(dst, x + 228, y + 1, "once");
    else {
        sprintf(tmp, "every %u.%02u sec", asset->name / 100, asset->name % 100);
        ui_text(dst, x + 228, y + 1, tmp);
    }
}

/**
 * Draw table cell
 */
void draw_attrs(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = *((uint8_t**)data);
    int c, k, i = (scr_w - 80 - 104 - 8 - 224) / 2;
    char tmp[16];

    (void)hdr; (void)w; (void)h;
    if(!data) return;
    k = *ptr++;
    if(k > 4) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%02X %s", k, k < 5 ? attr_types[k] : "???");
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;

    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 84, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
    ssfn_dst.bg = 0;

    ptr = draw_tr(dst, x + 84 + i, y + 1, ptr);

    if(k == 1 || k == 3) {
        memcpy(&c, ptr, 4); ptr += 4;
        sprintf(tmp, "%10d", c);
        ui_text(dst, x + 84 + i + i, y + 1, tmp);
    }

    if(idx == sel)
        switch(k) {
            case 1: case 3:
                abclen = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; ptr += 3;
                abc = (uint8_t*)main_alloc(abclen);
                memcpy(abc, ptr, abclen);
            break;
            case 2: case 4:
                abclen = (ptr[1] << 8) | ptr[0]; ptr += 2;
                abc = (uint8_t*)main_alloc(abclen);
                memcpy(abc, ptr, abclen);
            break;
        }
}

/**
 * Draw table cell
 */
void draw_actions(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    tng_asset_desc_t *asset = (tng_asset_desc_t*)data;
    char tmp[32];

    (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%3u", idx + 1);
    ui_text(dst, x + 1, y + 1, tmp);
    sprintf(tmp, "%016lX", (unsigned long int)(asset->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(dst, x + 36, y + 1, tmp);
    sprintf(tmp, "%10u", asset->size);
    ui_text(dst, x + 172, y + 1, tmp);
    sprintf(tmp, "%06X", asset->name & 0xffffff);
    ui_text(dst, x + 260, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(asset->name));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_opts(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = *((uint8_t**)data);

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    ptr = draw_tr(dst, x + 1, y + 1, ptr);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_attrlist(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    int c, k;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    k = *ptr++;
    if(k > 5 && k != 0xff) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%02X %s", k, k < 6 ? rel_types[k] : (k == 0xff ? ":=" : "???"));
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;

    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 68, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
    ssfn_dst.bg = 0;

    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, "%10d", c);
    ui_text(dst, x + 4 + scr_w - 440 - 2 - 96, y + 1, tmp);
}

/**
 * Draw table cell
 */
void draw_objlist(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;

    if(!ptr[0]) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%3u", *ptr++);
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;

    ptr = draw_sprref(dst, x + 36, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_sprlist(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[16];

    (void)sel; (void)w; (void)h;
    if(!data) return;

    sprintf(tmp, "action%u", idx - 9);
    ui_text(dst, x + 4, y + 1, ptr[0] == 3 ? "portrait" : (idx < 10 ? (char*)spr_types[idx] : (idx < 19 ? tmp : "???")));

    ptr = draw_sprref(dst, x + 4 + hdr[0].w, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_dialog(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[64];
    int k;

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    ptr = draw_attr(dst, x + 4, y + 1, ptr);
    k = *ptr++;
    if(k > 5 && k != 0xff) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%02X %s", k, k < 6 ? rel_types[k] : (k == 0xff ? ":=" : "???"));
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
    ssfn_dst.bg = 0;
    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, "%6d", c);
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);

    ptr = draw_attr(dst, x + hdr[0].w + 4, y + 1, ptr);
    ssfn_dst.bg = 0;
    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, ":=%6d", c);
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + hdr[0].w + hdr[1].w + 4, y + 1, tmp);
    if(!c) ssfn_dst.bg = theme[THEME_ERR];
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "(err)" : str(c));
    ssfn_dst.bg = 0;
    ptr = draw_tr(dst, x + hdr[0].w + hdr[1].w + hdr[2].w + 4, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_craft(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint16_t *ptr = (uint16_t*)data, qty, prod;
    char tmp[256];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    qty = *ptr++; prod = *ptr++;
    sprintf(tmp, "%2u %04X, %2u %04X, %2u %04X, %2u %04X,", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); ptr += 8;
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%2u %04X, %2u %04X, %2u %04X, %2u %04X,", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); ptr += 8;
    ui_text(dst, x + 4, y + 17, tmp);
    sprintf(tmp, "%2u %04X, %2u %04X, %2u %04X, %2u %04X,", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); ptr += 8;
    ui_text(dst, x + 4, y + 33, tmp);
    sprintf(tmp, "%2u %04X, %2u %04X, %2u %04X, %2u %04X,", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); ptr += 8;
    ui_text(dst, x + 4, y + 49, tmp);
    sprintf(tmp, "%2u %04X", qty, prod);
    ui_text(dst, x + hdr[0].w + 4, y + 25, tmp);
}

/**
 * Draw table cell
 */
void draw_coll(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[16];
    int i;

    (void)idx; (void)sel; (void)w; (void)h; (void)hdr;
    if(!data) return;
    ssfn_dst.x = x;
    for(i = 0; i < frames_tbl.entsize; i++) {
        sprintf(tmp, "%u", *ptr++);
        ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
    }
}

/**
 * Draw table cell
 */
void draw_sound(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 4, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_sprite(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    ptr = draw_sprref(dst, x + 4, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_attrmod(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    int c, k, l;
    char tmp[16];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;
    k = *ptr++;
    ui_text(dst, x + 4, y + 1, k & 1 ? "yes" : "no");

    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, !c ? "transport" : str(c));
    ssfn_dst.bg = 0;

    memcpy(&l, ptr, 4); ptr += 4;
    if(!c) {
        if(l > 3) ssfn_dst.bg = theme[THEME_ERR];
        sprintf(tmp, "%02X %s", l, l <= 3 ? trn_types[l] : "???");
    } else
        sprintf(tmp, "%10d%c", l, k & 2 ? '%' : ' ');
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, tmp);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_charopt(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    int i;

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;
    ptr = draw_tr(dst, x + 4, y + 9, ptr);
    for(i = 0; i < 20; i++)
        ptr = draw_sprref(dst, x + hdr[0].w + i * 7 * 8 + 4, y + 1, ptr);
    for(i = 0; i < 20; i++)
        ptr = draw_sprref(dst, x + hdr[0].w + i * 7 * 8 + 4, y + 17, ptr);
}

/**
 * Draw table cell
 */
void draw_evt(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)frames_tbl.data;
    int i, l, t;
    char tmp[32];

    (void)w; (void)h; (void)data;
    if(!ptr) return;
    for(i = 0; i < idx; i++) {
        ptr += 4; l = 0; memcpy(&l, ptr, 3); ptr += 3 + l;
    }
    t = *ptr++;
    if(t > frames_tbl.entsize) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%02X %s", t, t <= frames_tbl.entsize ? evt_types[t] : "(err)");
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;
    i = 0; memcpy(&i, ptr, 3); ptr += 3;
    switch(t) {
        case 2: case 3:
            sprintf(tmp, "%10d", i);
            ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
        break;
        case 13: case 14: case 15:
            draw_sprref(dst, x + 4 + hdr[0].w, y + 1, ptr - 3);
        break;
        case 16:
            if(i > 12) ssfn_dst.bg = theme[THEME_ERR];
            sprintf(tmp, "%02X %s", i, i <= 12 ? tmr_types[i] : "(err)");
            ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
            ssfn_dst.bg = 0;
        break;
    }
    i = 0; memcpy(&i, ptr, 3); ptr += 3;
    if(idx == sel && !mnes)
        bc_disasm(ptr, i);
}

/**
 * Draw table cell
 */
void draw_invlist(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    int i;
    char tmp[32];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    sprintf(tmp, "%u%%", *ptr++);
    ui_text(dst, x + 4, y + 1, tmp);
    i = 0; memcpy(&i, ptr, 3); ptr += 3;
    sprintf(tmp, "%10d", i);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
    i = 0; memcpy(&i, ptr, 3); ptr += 3;
    sprintf(tmp, "%9dm", i);
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, tmp);
    ptr = draw_sprref(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[1].w, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_spawner(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c;
    char tmp[64];
    int k;

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;

    ptr = draw_attr(dst, x + 4, y + 1, ptr);
    k = *ptr++;
    if(k > 5 && k != 0xff) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%02X %s", k, k < 6 ? rel_types[k] : (k == 0xff ? ":=" : "???"));
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
    ssfn_dst.bg = 0;
    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, "%6d", c);
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);

    ptr = draw_attr(dst, x + hdr[0].w + 4, y + 1, ptr);
    ssfn_dst.bg = 0;
    memcpy(&c, ptr, 4); ptr += 4;
    sprintf(tmp, ":=%6d", c);
    ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
    ptr = draw_sprref(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, ptr);
    ptr = draw_sprref(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w, y + 1, ptr);
}

/**
 * Draw table cell
 */
void draw_mnemonics(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    int offs = *((int*)data);

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    if(mnes) {
        if(!memcmp(mnes + offs + 32, "INV", 3) || mnes[offs + 32 + 8] == '?') ssfn_dst.bg = theme[THEME_ERR];
        ui_text(dst, x + 4, y + 1, mnes + offs);
        ssfn_dst.bg = 0;
    }
}

/**
 * Draw table cell
 */
void draw_translation(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    char tmp[16];

    (void)sel; (void)hdr; (void)w; (void)h; (void)data;
    if(!data || idx < 0 || idx >= numtranslation) return;
    sprintf(tmp, "%8u", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    if(translations)
        ui_text(dst, x + 76, y + 1, translations + translation[idx]);
}

/**
 * Draw table cell
 */
void draw_tiles(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hd, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    int c, k, i;
    char tmp[16];

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    if(k > numspr[1]) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "1.%04X", k);
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;

    for(i = 0, x += 4; i < hdr.numtrn; i++) {
        x += hd[i].w; ssfn_dst.w = x + hd[i + 1].w - 8;
        sprintf(tmp, "%4d", (int)(((int)ptr[1] << 8) | ptr[0])); ptr += 2;
        ui_text(dst, x, y + 1, tmp);
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(dst, ssfn_dst.x + 8, y + 1, tmp);
        ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
        ssfn_dst.bg = 0;
    }
    ssfn_dst.w = scr_w;
}

/**
 * Draw table cell
 */
void draw_neight(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hd, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c = 0;
    char tmp[16], *dir, *ortho[] = { "so", "we", "no", "ea" };
    char *hexv[] = { "sw", "we", "nw", "ne", "ea", "se" }, *hexh[] = { "so", "sw", "nw", "no", "ne", "se" };

    (void)sel; (void)w; (void)h;
    if(!data) return;
    switch(hdr.type) {
        case TNG_TYPE_ORTHO: case TNG_TYPE_ISO: if(idx > 4) { return; } dir = ortho[idx]; break;
        case TNG_TYPE_HEXV: dir = hexv[idx]; break;
        case TNG_TYPE_HEXH: dir = hexh[idx]; break;
        default: dir = "???"; break;
    }
    sprintf(tmp, "%u", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, dir);
    c = 0; memcpy(&c, ptr, 3); ptr += 3;
    sprintf(tmp, "%06X", c);
    ui_text(dst, x + 4 + hd[0].w, y + 1, tmp);
    ui_text(dst, ssfn_dst.x + 8, y + 1, str(c));
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_mapprx(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    uint32_t c = 0;
    char tmp[16], *lyr[] = { "fg", "bg" };

    (void)idx; (void)sel; (void)w; (void)h;
    if(!data) return;
    if(ptr[0] > 1) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%x %s", ptr[0], ptr[0] < 2 ? lyr[(int)ptr[0]] : "??"); ptr++;
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;
    sprintf(tmp, "%6d", (int)ptr[0]); ptr++;
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
    sprintf(tmp, "%6u", (int)ptr[0]); ptr++;
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w, y + 1, tmp);
    sprintf(tmp, "%6d", (int)ptr[0]); ptr++;
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w, y + 1, tmp);
    sprintf(tmp, "%6u", (int)ptr[0]); ptr++;
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w, y + 1, tmp);
    c = 0; memcpy(&c, ptr, 2); ptr += 2;
    sprintf(tmp, "%4u.%02u", c / 100, c % 100);
    ui_text(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w, y + 1, tmp);
    ptr = draw_sprref(dst, x + 4 + hdr[0].w + hdr[1].w + hdr[2].w + hdr[3].w + hdr[4].w + hdr[5].w, y + 1, ptr);
    ssfn_dst.bg = 0;
}

/**
 * Draw table cell
 */
void draw_mappath(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hd, void *data)
{
    uint8_t *ptr = (uint8_t*)frames_tbl.data;
    int i, l, X, Y;
    char tmp[16];

    (void)sel; (void)h; (void)data;
    if(!ptr) return;
    for(i = 0; i < idx; i++)
        ptr += 3 + ((ptr[2] << 8) | ptr[1]) * 4;
    if(ptr[0] > 98) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "%2u", ptr[0]); ptr++;
    ui_text(dst, x + 4, y + 1, tmp);
    ssfn_dst.bg = 0;
    l = ((ptr[1] << 8) | ptr[0]); ptr += 2;
    sprintf(tmp, "%6u", l);
    ui_text(dst, x + 4 + hd[0].w, y + 1, tmp);
    ssfn_dst.x = x + 4 + hd[0].w + hd[1].w;
    for(i = 0; i < l && ssfn_dst.x < x + w; i++, ssfn_dst.x += 8) {
        X = ((ptr[1] << 8) | ptr[0]); Y = ((ptr[3] << 8) | ptr[2]); ptr += 4;
        if(X >= (1 << hdr.mapsize) || Y >= (1 << hdr.mapsize)) ssfn_dst.bg = theme[THEME_ERR];
        sprintf(tmp, "(%u,%u)", X, Y);
        ui_text(dst, ssfn_dst.x, y + 1, tmp);
        ssfn_dst.bg = 0;
    }
}

/**
 * Draw table cell
 */
void draw_strings(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    char tmp[16];

    (void)sel; (void)hdr; (void)w; (void)h; (void)data;
    if(!data) return;
    sprintf(tmp, "%06X", strings[idx]);
    ui_text(dst, x + 4, y + 1, tmp);
    ui_text(dst, x + 60, y + 1, !idx ? "(empty)" : (char*)sec + sec[0].offs + strings[idx]);
}

/**
 * View Strings section
 */
void view_strings(tng_section_t *section)
{
    (void)section;
    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 56;
    assets_hdr[1].label = "String"; assets_hdr[1].w = scr_w - 56 - 8 - 224;
    assets_hdr[2].label = NULL;
    assets_tbl.entsize = sizeof(int*);
    assets_tbl.num = numstrings;
    assets_tbl.data = strings;
    assets_tbl.drawcell = draw_strings;
    ui_table(screen, 220, 32, scr_w - 230, scr_h - 36, &assets_tbl, sec_tab == 1);
}

/**
 * View Translation section
 */
void view_translations(tng_section_t *section)
{
    char tmp[32], *code = (char*)sec + section->offs, *buf, *ptr;
    tng_asset_desc_t *text = (tng_asset_desc_t*)((uint8_t*)sec + section->offs + 2);
    int i = 0;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Language:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 32, str(text->name));
    ssfn_dst.bg = 0;
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 8, 32, "(");
    sprintf(tmp, "%c%c", code[0], code[1]);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x, 32, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, 32, ")  Texts offset:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%016lX", (unsigned long int)(text->offs + sizeof(tng_hdr_t) + 4 + sec_size));
    ui_text(screen, ssfn_dst.x + 8, 32, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 8, 32, "size:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", text->size);
    ui_text(screen, ssfn_dst.x + 8, 32, tmp);

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Voice"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = (TNG_SECTION_SIZE(section) - 2 - sizeof(tng_asset_desc_t)) / sizeof(tng_asset_desc_t);
    assets_tbl.data = text + 1;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 48, scr_w - 230, 168, &assets_tbl, sec_tab == 1);
    ui_box(screen, 220, 228, scr_w - 328, 6, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER]);
    ui_button(screen, scr_w - 100, 220, 80, "Play", assets_tbl.num < 1 || sec_tab != 1 ? -1 : 0);
    sec_playx = scr_w - 100; sec_playy = 220;

    dirs_hdr[0].label = "Index"; dirs_hdr[0].w = 72;
    dirs_hdr[1].label = "String"; dirs_hdr[1].w = scr_w - 304;
    dirs_hdr[2].label = NULL;
    if(!translation) {
        buf = (char*)main_alloc(text->size);
        world_seektng(0, 0, text->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, text->size)) {
            translations = (char*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, text->size, 4096, &i, 1);
            if(translations && i > 0)
                for(ptr = translations; ptr < translations + i;) {
                    translation = (int*)main_realloc(translation, (numtranslation + 1) * sizeof(int));
                    translation[numtranslation++] = (int)(ptr - translations);
                    while(ptr < translations + i && *ptr) ptr++;
                    if(ptr < translations + i && !*ptr) ptr++;
                }
        }
        free(buf);
    }
    dirs_tbl.entsize = sizeof(int*);
    dirs_tbl.num = numtranslation;
    dirs_tbl.data = translation;
    dirs_tbl.drawcell = draw_translation;
    ui_table(screen, 220, 256, scr_w - 230, scr_h - 256 - 4, &dirs_tbl, sec_tab == 2);
}

/**
 * View Fonts section
 */
void view_fonts(tng_section_t *section)
{
    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Font's Name"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_fonts;
    ui_table(screen, 220, 32, scr_w - 230, scr_h - 128, &assets_tbl, sec_tab == 1);
}

/**
 * View Music / Sounds / Movies section
 */
void view_media(tng_section_t *section)
{
    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Media"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, scr_h - 32 - 24 - 5, &assets_tbl, sec_tab == 1);
    ui_box(screen, 220, scr_h - 17, scr_w - 328, 6, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER]);
    ui_button(screen, scr_w - 100, scr_h - 25, 80, "Play", assets_tbl.num < 1 || sec_tab != 1 ? -1 : 0);
    sec_playx = scr_w - 100; sec_playy = scr_h - 25;
}

/**
 * View Atlas section
 */
void view_atlas(tng_section_t *section)
{
    SDL_Rect rect;

    assets_hdr[0].label = "Idx"; assets_hdr[0].w = 40;
    assets_hdr[1].label = "Offset"; assets_hdr[1].w = 136;
    assets_hdr[2].label = "Size"; assets_hdr[2].w = 88;
    assets_hdr[3].label = "Resolution"; assets_hdr[3].w = scr_w - 136 - 128 - 8 - 224;
    assets_hdr[4].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_atlas;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);
    if(assets_tbl.val < numatlas) {
        if(!atlas[assets_tbl.val].txt) {
            ssfn_dst.fg = theme[THEME_SELFG];
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, 220, 128 + 32 + 4, " PNG ERROR ");
            ssfn_dst.bg = 0;
        } else {
            rect.x = 221; rect.y = 128 + 32 + 5;
            ui_fit(scr_w - rect.x - 11, scr_h - rect.y - 6, atlas[assets_tbl.val].w, atlas[assets_tbl.val].h, &rect.w, &rect.h);
            ui_box2(screen, 220, 128 + 32 + 4, rect.w + 2, rect.h + 2, theme[THEME_DARKER], theme[THEME_BG],
                theme[THEME_LIGHTER], 0, 0, 8, 8);
        }
    }
}

/**
 * View a sprite map section
 */
void view_sprites(tng_section_t *section)
{
    uint8_t *buf, *ptr, *end;
    tng_asset_desc_t *asset;
    char tmp[128];
    int i;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Category:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", TNG_SECTION_TYPE(section) - 7);
    ui_text(screen, 300, 32, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 8, 32, (char*)section_types[TNG_SECTION_TYPE(section)]);
    ssfn_dst.fg = theme[THEME_FG];

    assets_hdr[0].label = "Idx"; assets_hdr[0].w = 40;
    assets_hdr[1].label = "Offset"; assets_hdr[1].w = 96;
    assets_hdr[2].label = "Size"; assets_hdr[2].w = 40;
    assets_hdr[3].label = "Dir"; assets_hdr[3].w = 40;
    assets_hdr[4].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_sprites;
    ui_table(screen, 220, 52, 136 + 40 + 40 + 2, scr_h - 52 - 4, &assets_tbl, sec_tab == 1);

    dirs_hdr[0].label = "Width"; dirs_hdr[0].w = 56;
    dirs_hdr[1].label = "Height"; dirs_hdr[1].w = 56;
    dirs_hdr[2].label = "Dir"; dirs_hdr[2].w = 40;
    dirs_hdr[3].label = "Type"; dirs_hdr[3].w = 40;
    dirs_hdr[4].label = "NumFrames"; dirs_hdr[4].w = scr_w - 456 - 96 - 96 - 2;
    dirs_hdr[5].label = NULL;
    if(!sprites && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size)) {
            ptr = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
            if(ptr && i > 0)
                for(end = ptr + i; ptr < end;) {
                    sprites = (tng_sprite_t**)main_realloc(sprites, (numsprites + 1) * sizeof(tng_sprite_t*));
                    sprites[numsprites++] = (tng_sprite_t*)ptr;
                    ptr += sizeof(tng_sprite_t) + ((tng_sprite_t*)ptr)[0].nframe * sizeof(tng_sprmap_desc_t);
                }
        }
        free(buf);
    }
    dirs_tbl.num = numsprites;
    dirs_tbl.data = sprites;
    dirs_tbl.entsize = sizeof(tng_sprite_t*);
    dirs_tbl.drawcell = draw_dirs;
    ui_table(screen, 446, 52, scr_w - 456, 128, &dirs_tbl, sec_tab == 2);


    frames_hdr[0].label = "Atlas"; frames_hdr[0].w = 48;
    frames_hdr[1].label = "Oper"; frames_hdr[1].w = 38;
    frames_hdr[2].label = "X"; frames_hdr[2].w = 38;
    frames_hdr[3].label = "Y"; frames_hdr[3].w = 38;
    frames_hdr[4].label = "W"; frames_hdr[4].w = 38;
    frames_hdr[5].label = "H"; frames_hdr[5].w = 38;
    frames_hdr[6].label = "Left"; frames_hdr[6].w = 38;
    frames_hdr[7].label = "Top"; frames_hdr[7].w = scr_w - 458 - 48 - 6 * 38;
    frames_hdr[8].label = NULL;
    frames_tbl.num = 0;
    frames_tbl.data = NULL;
    if(numsprites && dirs_tbl.val < numsprites) {
        frames_tbl.num = sprites[dirs_tbl.val]->nframe;
        frames_tbl.data = (uint8_t*)sprites[dirs_tbl.val] + sizeof(tng_sprite_t);
        ui_box2(screen, 446, 316, zoom * sprites[dirs_tbl.val]->w + 2, zoom * sprites[dirs_tbl.val]->h + 2,
            theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER], 0, 0, 8, 8);
    }
    frames_tbl.entsize = sizeof(tng_sprmap_desc_t);
    frames_tbl.drawcell = draw_frames;
    ui_table(screen, 446, 52 + 128 + 4, scr_w - 456, 128, &frames_tbl, sec_tab == 3);
}

/**
 * View Backgrounds section
 */
void view_backs(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    stbi__context s;
    stbi__result_info ri;
    SDL_Rect rect;
    uint8_t *buf, *ptr, *data;
    char tmp[128];
    int k;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Category:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%02X %s", TNG_SECTION_TYPE(section) - 7, section_types[TNG_SECTION_TYPE(section)]);
    ui_text(screen, 300, 32, tmp);

    assets_hdr[0].label = "Idx"; assets_hdr[0].w = 40;
    assets_hdr[1].label = "Offset"; assets_hdr[1].w = 136;
    assets_hdr[2].label = "Size"; assets_hdr[2].w = 88;
    assets_hdr[3].label = "Resolution"; assets_hdr[3].w = scr_w - 136 - 128 - 8 - 224;
    assets_hdr[4].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_backs;
    if(!back && assets_tbl.num && assets_tbl.val < assets_tbl.num) {
        asset = &((tng_asset_desc_t*)assets_tbl.data)[assets_tbl.val];
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size)) {
            memset(&s, 0, sizeof(s));
            memset(&ri, 0, sizeof(ri));
            s.img_buffer = s.img_buffer_original = buf;
            s.img_buffer_end = s.img_buffer_original_end = buf + asset->size;
            ri.bits_per_channel = 8;
            ptr = (uint8_t*)stbi__png_load(&s, (int*)&back_w, (int*)&back_h, &k, 4, &ri);
            if(ptr) {
                back = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, back_w, back_h);
                if(back) {
                    SDL_SetTextureBlendMode(back, SDL_BLENDMODE_BLEND);
                    SDL_LockTexture(back, NULL, (void**)&data, &k);
                    memcpy(data, ptr, back_w * back_h * 4);
                    SDL_UnlockTexture(back);
                }
                free(ptr);
            }
        }
        free(buf);
    }
    ui_table(screen, 220, 52, scr_w - 230, 128, &assets_tbl, sec_tab == 1);
    if(back) {
        rect.x = 221; rect.y = 128 + 52 + 5;
        ui_fit(scr_w - rect.x - 11, scr_h - rect.y - 6, back_w, back_h, &rect.w, &rect.h);
        ui_box2(screen, 220, 128 + 52 + 4, rect.w + 2, rect.h + 2, theme[THEME_DARKER], theme[THEME_BG],
            theme[THEME_LIGHTER], 0, 0, 8, 8);
    } else
    if(assets_tbl.num && assets_tbl.val < assets_tbl.num) {
        ssfn_dst.fg = theme[THEME_SELFG];
        ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, 220, 128 + 52 + 4, " PNG ERROR ");
        ssfn_dst.bg = 0;
    }
}

/**
 * View User Interface elements section
 */
void view_ui(tng_section_t *section)
{
    char tmp[16];
    uint8_t *ptr = (uint8_t*)sec + section->offs, *end = ptr + TNG_SECTION_SIZE(section);
    uint32_t c;
    int i, k;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(ptr[0] != 10) ssfn_dst.bg = theme[THEME_ERR];
    k = *ptr++;
    sprintf(tmp, "Colors: (%u)", k);
    ui_text(screen, 220, 32, tmp);
    ssfn_dst.bg = 0;
    ssfn_dst.fg = theme[THEME_FG];
    for(i = 0; i < k; i++) {
        memcpy(&c, ptr, 4); ptr += 4;
        ui_box(screen, ssfn_dst.x + 8, 36, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(screen, ssfn_dst.x + 18, 32, tmp);
    }

    assets_hdr[0].label = "Style"; assets_hdr[0].w = 48;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 40;
    assets_hdr[2].label = "Top"; assets_hdr[2].w = 40;
    assets_hdr[3].label = "Font"; assets_hdr[3].w = scr_w - 48 - 80 - 8 - 224;
    assets_hdr[4].label = NULL;
    assets_tbl.num = *ptr++;
    assets_tbl.data = ptr;
    assets_tbl.entsize = 7;
    assets_tbl.drawcell = draw_fontrefs;
    ui_table(screen, 220, 68, scr_w - 230, 128, &assets_tbl, sec_tab == 1);
    ptr += assets_tbl.num * assets_tbl.entsize;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(ptr[0] != 3) ssfn_dst.bg = theme[THEME_ERR];
    k = *ptr++;
    sprintf(tmp, "Sounds: (%u)", k);
    ui_text(screen, 220, 48, tmp);
    ssfn_dst.bg = 0;
    ssfn_dst.fg = theme[THEME_FG];
    c = 0;
    for(i = 0; i < k; i++) {
        memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, ssfn_dst.x + 16, 48, tmp);
        ui_text(screen, ssfn_dst.x + 8, 48, str(c));
        ssfn_dst.bg = 0;
    }

    dirs_hdr[0].label = "Cat"; dirs_hdr[0].w = 32;
    dirs_hdr[1].label = "Sprite index"; dirs_hdr[1].w = scr_w - 32 - 8 - 224;
    dirs_hdr[2].label = NULL;
    dirs_tbl.num = (end - ptr) / 3;
    dirs_tbl.data = ptr;
    dirs_tbl.entsize = 3;
    dirs_tbl.drawcell = draw_uisprite;
    ui_table(screen, 220, 200, scr_w - 230, scr_h - 204, &dirs_tbl, sec_tab == 2);
}

/**
 * View Main menu section
 */
void view_mainmenu(tng_section_t *section)
{
    char tmp[64];
    uint8_t *ptr = (uint8_t*)sec + section->offs;
    uint32_t c = 0;
    int j = 328;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    memcpy(&c, ptr, 3); ptr += 3;
    ui_text(screen, 220, 32, "Intro:");
    sprintf(tmp, "%06X", c);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, j, 32, tmp);
    ui_text(screen, ssfn_dst.x + 8, 32, str(c));
    ssfn_dst.bg = 0;

    if(TNG_SECTION_SIZE(section) < 4) {
        sec_hastab = 0;
        ui_text(screen, 220, 64, "Main menu disabled");
    } else {
        sec_hastab = 2;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 48, "Bg Music:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 48, tmp);
        ui_text(screen, ssfn_dst.x + 8, 48, str(c));
        ssfn_dst.bg = 0;

        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 64, "Bg Movie:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 64, tmp);
        ui_text(screen, ssfn_dst.x + 8, 64, str(c));
        ssfn_dst.bg = 0;

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 80, "Bg Image:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, j, 80, ptr);

        assets_hdr[0].label = "dX"; assets_hdr[0].w = 40;
        assets_hdr[1].label = "dY"; assets_hdr[1].w = 40;
        assets_hdr[2].label = "Time"; assets_hdr[2].w = 56;
        assets_hdr[3].label = "Parallax Sprite"; assets_hdr[3].w = scr_w - 136 - 8 - 224;
        assets_hdr[4].label = NULL;
        assets_tbl.num = *ptr++;
        assets_tbl.data = ptr;
        assets_tbl.entsize = 9;
        assets_tbl.drawcell = draw_menuprx;
        ui_table(screen, 220, 100, scr_w - 230, 76, &assets_tbl, sec_tab == 1);
        ptr += assets_tbl.num * assets_tbl.entsize;

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 180, "Title Image:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, j, 180, ptr);

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 196, "Title Text:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02X %s %s", ptr[0], ptr[0] & 1 ? "(visible)" : "(hidden)",
            ptr[0] & 2 ? "(no cropping)" : "(crop background)"); ptr++;
        ui_text(screen, j, 196, tmp);

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 212, "Title Color:");
        ssfn_dst.fg = theme[THEME_FG];
        memcpy(&c, ptr, 4); ptr += 4;
        ui_box(screen, j, 216, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(screen, j + 10, 212, tmp);
        memcpy(&c, ptr, 4); ptr += 4;
        ui_box(screen, ssfn_dst.x + 8, 216, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(screen, ssfn_dst.x + 18, 212, tmp);

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 228, "Title Font:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = view_font(ptr, j, 228);

        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 244, "Title URL:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, j, 244, tmp);
        ui_text(screen, ssfn_dst.x + 8, 244, str(c));
        ssfn_dst.bg = 0;



        sprintf(tmp, "%3u", (ptr[1] << 8) | ptr[0]); ptr += 2;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 260, "Menu Font:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = view_font(ptr, j, 260);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x + 8, 260, "top:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, ssfn_dst.x + 8, 260, tmp);

        dirs_hdr[0].label = "Color"; dirs_hdr[0].w = 88;
        dirs_hdr[1].label = "Left"; dirs_hdr[1].w = 128;
        dirs_hdr[2].label = "Middle"; dirs_hdr[2].w = 128;
        dirs_hdr[3].label = "Right Sprite"; dirs_hdr[3].w = scr_w - 248 - 64 - 32 - 8 - 224;
        dirs_hdr[4].label = NULL;
        dirs_tbl.num = *ptr++;
        dirs_tbl.data = ptr;
        dirs_tbl.entsize = 13;
        dirs_tbl.drawcell = draw_menubtn;
        ui_table(screen, 220, 280, scr_w - 230, 128, &dirs_tbl, sec_tab == 2);
        ptr += dirs_tbl.num * dirs_tbl.entsize;

        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 412, "Graph Color:");
        ssfn_dst.fg = theme[THEME_FG];
        memcpy(&c, ptr, 4); ptr += 4;
        ui_box(screen, j, 416, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(screen, j + 10, 412, tmp);
        memcpy(&c, ptr, 4); ptr += 4;
        ui_box(screen, ssfn_dst.x + 8, 416, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
        sprintf(tmp, "%08X", c);
        ui_text(screen, ssfn_dst.x + 18, 412, tmp);
    }
}

/**
 * View Heads-Up Display section
 */
void view_hud(tng_section_t *section)
{
    char tmp[16];
    uint8_t *ptr = (uint8_t*)sec + section->offs;
    uint32_t c = 0;
    int j = 328;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Navigation:");
    ui_text(screen, j, 32, "circ:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 32, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 32, "btns:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 32, ptr);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 48, "Itembar:");
    ui_text(screen, j, 48, "back:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 48, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 48, "fore:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 48, ptr);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, j, 64, "numitems");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "w");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "h");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "l");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "t");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "gap");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 64, "primary");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 64, tmp);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 80, "Statusbar:");
    ui_text(screen, j, 80, "back:");
    c = 0; c = *ptr++;
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 80, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 80, "fore:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 80, ptr);
    sprintf(tmp, "%02X %s", c, c ? "(combined)" : "(separate)");
    ui_text(screen, ssfn_dst.x + 8, 80, tmp);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 96, "Inventory:");
    ui_text(screen, j, 96, "icon:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 96, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 96, "pressed:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 96, ptr);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, j, 112, "w");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 112, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 112, "h");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 112, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 112, "l");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 112, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 112, "t");
    sprintf(tmp, "%u", *ptr++);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 112, tmp);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 128, "Items:");
    ui_text(screen, j, 128, "font:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = view_font(ptr, ssfn_dst.x + 8, 128);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, j, 144, "slot:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 144, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 144, "selected:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 144, ptr);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 144, "equip:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, ssfn_dst.x + 8, 144, ptr);

    assets_hdr[0].label = "Type"; assets_hdr[0].w = 60;
    assets_hdr[1].label = "Left"; assets_hdr[1].w = 40;
    assets_hdr[2].label = "Top"; assets_hdr[2].w = 40;
    assets_hdr[3].label = "Value"; assets_hdr[3].w = assets_hdr[4].w = (scr_w - 140 - 160 - 8 - 224) / 2;
    assets_hdr[4].label = "Max";
    assets_hdr[5].label = "Sprite"; assets_hdr[5].w = 80;
    assets_hdr[6].label = "Back"; assets_hdr[6].w = 80;
    assets_hdr[7].label = NULL;
    assets_tbl.num = *ptr++;
    assets_tbl.data = ptr;
    assets_tbl.entsize = 17;
    assets_tbl.drawcell = draw_pbars;
    ui_table(screen, 220, 164, scr_w - 230, scr_h - 168, &assets_tbl, sec_tab == 1);
    ptr += assets_tbl.num * assets_tbl.entsize;
}

/**
 * View Equipment slots section
 */
void view_equip(tng_section_t *section)
{
    assets_hdr[0].label = "Idx"; assets_hdr[0].w = 40;
    assets_hdr[1].label = "Left"; assets_hdr[1].w = 40;
    assets_hdr[2].label = "Top"; assets_hdr[2].w = 40;
    assets_hdr[3].label = "Equipment Mask"; assets_hdr[3].w = scr_w - 120 - 8 - 224;
    assets_hdr[4].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / 4;
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = 4;
    assets_tbl.drawcell = draw_equips;
    ui_table(screen, 220, 32, scr_w - 230, scr_h - 36, &assets_tbl, sec_tab == 1);
}

/**
 * View Alerts section
 */
void view_alerts(tng_section_t *section)
{
    char tmp[16];
    uint8_t *ptr = (uint8_t*)sec + section->offs;
    int j = 328, k;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Alert Font:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = view_font(ptr, j, 32);

    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u.%02u sec", k / 100, k % 100);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 48, "Alert Time:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, j, 48, tmp);
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u.%02u sec", k / 100, k % 100);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 48, "Fade In:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 48, tmp);
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u.%02u sec", k / 100, k % 100);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 48, "Fade Out:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, ssfn_dst.x + 8, 48, tmp);

    assets_hdr[0].label = "Color"; assets_hdr[0].w = 88;
    assets_hdr[1].label = "Sound"; assets_hdr[1].w = assets_hdr[2].w = (scr_w - 88 - 8 - 224) / 2;
    assets_hdr[2].label = "Text";
    assets_hdr[3].label = NULL;
    assets_tbl.num = *ptr++;
    assets_tbl.data = ptr;
    assets_tbl.entsize = 10;
    assets_tbl.drawcell = draw_alerts;
    ui_table(screen, 220, 68, scr_w - 230, 128, &assets_tbl, sec_tab == 1);
}

/**
 * View Credits section
 */
void view_credits(tng_section_t *section)
{
    char tmp[32], cat;
    uint8_t *ptr = (uint8_t*)sec + section->offs, *end = ptr + TNG_SECTION_SIZE(section);
    uint32_t c = 0;
    int j = 328, k;

    memcpy(&c, ptr, 3); ptr += 3;
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Bg Music:");
    sprintf(tmp, "%06X", c);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, j, 32, tmp);
    ui_text(screen, ssfn_dst.x + 8, 32, str(c));
    ssfn_dst.bg = 0;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 48, "Bg Image:");
    ssfn_dst.fg = theme[THEME_FG];
    ptr = draw_sprref(screen, j, 48, ptr);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 64, "Header:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&c, ptr, 4); ptr += 4;
    ui_box(screen, j, 68, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
    sprintf(tmp, "%08X", c);
    ui_text(screen, j + 10, 64, tmp);
    ptr = view_font(ptr, ssfn_dst.x + 16, 64);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 80, "Names:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&c, ptr, 4); ptr += 4;
    ui_box(screen, j, 84, 8, 8, theme[THEME_DARKER], c, theme[THEME_LIGHTER]);
    sprintf(tmp, "%08X", c);
    ui_text(screen, j + 10, 80, tmp);
    ptr = view_font(ptr, ssfn_dst.x + 16, 80);

    if(!crds) {
        while(ptr < end) {
            cat = *ptr++;
            k = (ptr[1] << 8) | ptr[0]; ptr += 2;
            crds = (crd_t*)realloc(crds, (numcrd + k) * sizeof(crd_t));
            if(crds)
                for(j = 0; j < k; j++, numcrd++, ptr += 3) {
                    crds[numcrd].cat = cat;
                    crds[numcrd].name = (ptr[1] << 16) | (ptr[1] << 8) | ptr[0];
                }
            else
                numcrd = 0;
        }
    }
    assets_hdr[0].label = "Cat"; assets_hdr[0].w = 32;
    assets_hdr[1].label = "Name"; assets_hdr[1].w = scr_w - 32 - 8 - 224;
    assets_hdr[2].label = NULL;
    assets_tbl.num = numcrd;
    assets_tbl.data = crds;
    assets_tbl.entsize = sizeof(crd_t);
    assets_tbl.drawcell = draw_credits;
    ui_table(screen, 220, 100, scr_w - 230, scr_h - 104, &assets_tbl, sec_tab == 1);
}

/**
 * View Choosers section
 */
void view_choosers(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c;
    int i;
    char tmp[16];

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Chooser"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    dirs_hdr[0].label = "Keep"; dirs_hdr[0].w = 40;
    dirs_hdr[1].label = "Pos"; dirs_hdr[1].w = 32;
    dirs_hdr[2].label = "Normal"; dirs_hdr[2].w = 56;
    dirs_hdr[3].label = "Select"; dirs_hdr[3].w = 56;
    dirs_hdr[4].label = "Press"; dirs_hdr[4].w = 56;
    dirs_hdr[5].label = "Required"; dirs_hdr[5].w = (scr_w - 40 - 32 - 3*56 - 8 - 224) * 2 / 3;
    dirs_hdr[6].label = "Provided"; dirs_hdr[6].w = scr_w - 40 - 32 - 3*56 - dirs_hdr[5].w - 8 - 224;
    dirs_hdr[7].label = NULL;
    dirs_tbl.num = 0;
    dirs_tbl.data = NULL;
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 36 + 128, "Margins:");
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        sprintf(tmp, "%u", c);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 36 + 128, "left:");
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, ssfn_dst.x + 8, 36 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 36 + 128, ", top:");
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        sprintf(tmp, "%u", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, ssfn_dst.x + 8, 36 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 36 + 128, ", horizontal:");
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        sprintf(tmp, "%u", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, ssfn_dst.x + 8, 36 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 36 + 128, ", vertical:");
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        sprintf(tmp, "%u", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, ssfn_dst.x + 8, 36 + 128, tmp);
        ssfn_dst.bg = 0;
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        dirs_tbl.num = c;
        dirs_tbl.data = ptr;
        dirs_tbl.entsize = 31;
        dirs_tbl.drawcell = draw_chooser;
    }
    ui_table(screen, 220, 54 + 128, scr_w - 230, scr_h - 4 - (54 + 128), &dirs_tbl, sec_tab == 2);
}

/**
 * View Cutscenes section
 */
void view_cutscenes(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c = 0, nimg = 0, nspc = 0, nsub = 0, nbc = 0;
    char tmp[32], *channels[] = { "Slides", "Speech", "Subtitle", "Script", NULL };
    int i;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Cutscene"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    dirs_tbl.hdr = NULL;
    dirs_tbl.num = 4;
    dirs_tbl.data = channels;
    dirs_tbl.entsize = sizeof(char*);
    dirs_tbl.drawcell = draw_texts;
    ui_table(screen, 220, 36 + 128, 100, 6 + 4 * 18, &dirs_tbl, sec_tab == 2);
    frames_tbl.num = 0;
    frames_tbl.data = NULL;

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    if(optsbuf) {
        ptr = optsbuf;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 36 + 128, "Once Movie:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 36 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 36 + 128, str(c));
        ssfn_dst.bg = 0;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 52 + 128, "Once Music:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 52 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 52 + 128, str(c));
        ssfn_dst.bg = 0;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 68 + 128, "Loop Movie:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 68 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 68 + 128, str(c));
        ssfn_dst.bg = 0;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 84 + 128, "Loop Music:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 84 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 84 + 128, str(c));
        ssfn_dst.bg = 0;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 100 + 128, "ChannelNums:");
        memcpy(&nimg, ptr, 2); ptr += 2;
        sprintf(tmp, "%04X (%u)", nimg, nimg);
        ui_text(screen, 436, 100 + 128, tmp);
        memcpy(&nspc, ptr, 2); ptr += 2;
        sprintf(tmp, "%04X (%u)", nspc, nspc);
        ui_text(screen, ssfn_dst.x + 8, 100 + 128, tmp);
        memcpy(&nsub, ptr, 2); ptr += 2;
        sprintf(tmp, "%04X (%u)", nsub, nsub);
        ui_text(screen, ssfn_dst.x + 8, 100 + 128, tmp);
        memcpy(&nbc, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X (%u)", nbc, nbc);
        ui_text(screen, ssfn_dst.x + 8, 100 + 128, tmp);
        if(dirs_tbl.val == 0) {
            frames_hdr[0].label = "Start"; frames_hdr[0].w = 56;
            frames_hdr[1].label = "FdIn"; frames_hdr[1].w = 56;
            frames_hdr[2].label = "FdOut"; frames_hdr[2].w = 56;
            frames_hdr[3].label = "End"; frames_hdr[3].w = 56;
            frames_hdr[4].label = "Color"; frames_hdr[4].w = 80;
            frames_hdr[5].label = "Slide"; frames_hdr[5].w = scr_w - 4*56 - 80 - 8 - 224;
            frames_hdr[6].label = NULL;
            frames_tbl.num = nimg;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 19;
            frames_tbl.drawcell = draw_cutimg;
        }
        ptr += nimg * 19;
        if(dirs_tbl.val == 1) {
            frames_hdr[0].label = "Start"; frames_hdr[0].w = 56;
            frames_hdr[1].label = "Speech"; frames_hdr[1].w = scr_w - 56 - 8 - 224;
            frames_hdr[2].label = NULL;
            frames_tbl.num = nspc;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 6;
            frames_tbl.drawcell = draw_cutspc;
        }
        ptr += nspc * 6;
        if(dirs_tbl.val == 2) {
            frames_hdr[0].label = "Start"; frames_hdr[0].w = 56;
            frames_hdr[1].label = "FdIn"; frames_hdr[1].w = 56;
            frames_hdr[2].label = "FdOut"; frames_hdr[2].w = 56;
            frames_hdr[3].label = "End"; frames_hdr[3].w = 56;
            frames_hdr[4].label = "Color"; frames_hdr[4].w = 80;
            frames_hdr[5].label = "Position"; frames_hdr[5].w = 80;
            frames_hdr[6].label = "Font"; frames_hdr[6].w = scr_w - 4*56 - 80 - 80 - 8 - 224;
            frames_hdr[7].label = NULL;
            frames_tbl.num = nsub;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 27;
            frames_tbl.row = 34;
            frames_tbl.drawcell = draw_cutsub;
        }
        ptr += nsub * 27;
        if(dirs_tbl.val == 3) {
            if(!mnes)
                bc_disasm(ptr, nbc);
            frames_hdr[0].label = "Address"; frames_hdr[0].w = 64;
            frames_hdr[1].label = "Bytecode"; frames_hdr[1].w = 192;
            frames_hdr[2].label = "Mnemonic"; frames_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
            frames_hdr[3].label = NULL;
            frames_tbl.num = nummne;
            frames_tbl.data = mne;
            frames_tbl.entsize = sizeof(int);
            frames_tbl.drawcell = draw_mnemonics;
        }
    }
    ui_table(screen, 220, 246, scr_w - 230, scr_h - 4 - 246, &frames_tbl, sec_tab == 3);
    frames_tbl.row = 18;
}

/**
 * View Startup section
 */
void view_startup(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *buf;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Interval"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_cron;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!abc && assets_tbl.num && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            abc = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &abclen, 1);
        free(buf);
    }
    if(abc) {
        sec_hastab = 2;
        if(!mnes)
            bc_disasm(abc, abclen);
        dirs_hdr[0].label = "Address"; dirs_hdr[0].w = 64;
        dirs_hdr[1].label = "Bytecode"; dirs_hdr[1].w = 192;
        dirs_hdr[2].label = "Mnemonic"; dirs_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
        dirs_hdr[3].label = NULL;
        dirs_tbl.num = nummne;
        dirs_tbl.data = mne;
        dirs_tbl.entsize = sizeof(int);
        dirs_tbl.drawcell = draw_mnemonics;
        ui_table(screen, 220, 36 + 128, scr_w - 230, scr_h - 40 - 128, &dirs_tbl, sec_tab == 2);
    }
}

/**
 * View Attributes section
 */
void view_attrs(tng_section_t *section)
{
    char tmp[32];
    uint8_t *ptr = (uint8_t*)sec + section->offs, *end = ptr + TNG_SECTION_SIZE(section);
    uint32_t c;
    int i, k;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 32, "Free-mode total points:");
    ssfn_dst.fg = theme[THEME_FG];
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u", k);
    ui_text(screen, ssfn_dst.x + 8, 32, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x + 16, 32, "per attribute max points:");
    ssfn_dst.fg = theme[THEME_FG];
    k = (ptr[1] << 8) | ptr[0]; ptr += 2;
    sprintf(tmp, "%u", k);
    ui_text(screen, ssfn_dst.x + 8, 32, tmp);

    ssfn_dst.fg = theme[THEME_LIGHTER];
    k = *ptr++;
    if(k != 4) ssfn_dst.bg = theme[THEME_ERR];
    sprintf(tmp, "Engine: (%u)", k);
    ui_text(screen, 220, 48, tmp);
    ssfn_dst.bg = 0;
    ssfn_dst.fg = theme[THEME_FG];
    ssfn_dst.x -= 8;
    for(i = 0; i < k; i++) {
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, ssfn_dst.x + 16, 48, tmp);
        ui_text(screen, ssfn_dst.x + 8, 48, str(c));
        ssfn_dst.bg = 0;
    }

    if(!attrs) {
        numattrs = (ptr[1] << 8) | ptr[0]; ptr += 2;
        attrs = (uint8_t**)main_alloc(numattrs * sizeof(uint8_t*));
        for(i = 0; i < numattrs && ptr < end; i++) {
            attrs[i] = ptr;
            k = *ptr++; ptr += 6;
            switch(k) {
                case 1: case 3: ptr += 4; k = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; ptr += 3 + k; break;
                case 2: case 4: k = (ptr[1] << 8) | ptr[0]; ptr += 2 + k; break;
            }
        }
    }
    assets_hdr[0].label = "Type"; assets_hdr[0].w = 80;
    assets_hdr[1].label = "Name"; assets_hdr[1].w = assets_hdr[2].w = (scr_w - 80 - 104 - 8 - 224) / 2;
    assets_hdr[2].label = "Translated";
    assets_hdr[3].label = "Default"; assets_hdr[3].w = 104;
    assets_hdr[4].label = NULL;
    assets_tbl.num = numattrs;
    assets_tbl.data = attrs;
    assets_tbl.entsize = sizeof(uint8_t*);
    assets_tbl.drawcell = draw_attrs;
    abclen = 0; abc = NULL;
    ui_table(screen, 220, 68, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(abc) {
        sec_hastab = 2;
        if(!mnes)
            bc_disasm(abc, abclen);
        dirs_hdr[0].label = "Address"; dirs_hdr[0].w = 64;
        dirs_hdr[1].label = "Bytecode"; dirs_hdr[1].w = 192;
        dirs_hdr[2].label = "Mnemonic"; dirs_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
        dirs_hdr[3].label = NULL;
        dirs_tbl.num = nummne;
        dirs_tbl.data = mne;
        dirs_tbl.entsize = sizeof(int);
        dirs_tbl.drawcell = draw_mnemonics;
        ui_table(screen, 220, 72 + 128, scr_w - 230, scr_h - 76 - 128, &dirs_tbl, sec_tab == 2);
    }
}

/**
 * View Actions section
 */
void view_actions(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *buf;

    assets_hdr[0].label = "Idx"; assets_hdr[0].w = 32;
    assets_hdr[1].label = "Offset"; assets_hdr[1].w = 136;
    assets_hdr[2].label = "Size"; assets_hdr[2].w = 88;
    assets_hdr[3].label = "Range Attribute"; assets_hdr[3].w = scr_w - 32 - 136 - 88 - 8 - 224;
    assets_hdr[4].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_actions;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!abc && assets_tbl.num && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            abc = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &abclen, 1);
        free(buf);
    }
    if(abc) {
        sec_hastab = 2;
        if(!mnes)
            bc_disasm(abc, abclen);
        dirs_hdr[0].label = "Address"; dirs_hdr[0].w = 64;
        dirs_hdr[1].label = "Bytecode"; dirs_hdr[1].w = 192;
        dirs_hdr[2].label = "Mnemonic"; dirs_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
        dirs_hdr[3].label = NULL;
        dirs_tbl.num = nummne;
        dirs_tbl.data = mne;
        dirs_tbl.entsize = sizeof(int);
        dirs_tbl.drawcell = draw_mnemonics;
        ui_table(screen, 220, 36 + 128, scr_w - 230, scr_h - 40 - 128, &dirs_tbl, sec_tab == 2);
    }
}

/**
 * View Character Options section
 */
void view_chars(tng_section_t *section)
{
    char *tabs[] = { "Attrs", "Items", "Sprites", NULL }, tmp[64];
    int i, l;
    uint8_t *buf, *ptr, *end;
    tng_asset_desc_t *asset;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Character Option"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    dirs_hdr[0].label = "Options"; dirs_hdr[0].w = 198;
    dirs_hdr[1].label = NULL;
    if(!opts && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size)) {
            ptr = optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
            if(ptr && i > 0) {
                end = ptr + i; ptr += 6; l = *ptr++;
                for(i = 0; i < l && ptr < end; i++) {
                    opts = (uint8_t**)main_realloc(opts, (numopts + 1) * sizeof(uint8_t*));
                    opts[numopts++] = ptr;
                    ptr += 9 + ptr[6] * 8 + ptr[7] * 4 + ptr[8] * 3;
                }
            }
        }
        free(buf);
    }
    dirs_tbl.num = numopts;
    dirs_tbl.data = opts;
    dirs_tbl.entsize = sizeof(uint8_t*);
    dirs_tbl.drawcell = draw_opts;
    ui_table(screen, 220, 190, 200, scr_h - 194, &dirs_tbl, sec_tab == 2);
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 170, "Name:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, ssfn_dst.x + 8, 170, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, scr_w - 200, 170, "Palette:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, ssfn_dst.x + 8, 170, ptr);
    }
    if(opts && dirs_tbl.val >= 0 && dirs_tbl.val < dirs_tbl.num) {
        ptr = opts[dirs_tbl.val] + 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 430, 190, "Desc:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, ssfn_dst.x + 8, 190, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        sprintf(tmp, "Nums: %02X (%u attrs), %02X (%u items), %02X (%u sprites)", ptr[0], ptr[0], ptr[1], ptr[1], ptr[2], ptr[2]);
        ui_text(screen, 430, 206, tmp);
        ssfn_dst.fg = theme[THEME_FG];
        ui_tab(screen, 430, 226, tabs);
        frames_tbl.num = 0;
        frames_tbl.data = NULL;
        switch(tabc) {
            case 0:
                frames_hdr[0].label = "Type"; frames_hdr[0].w = 64;
                frames_hdr[1].label = "Attribute"; frames_hdr[1].w = scr_w - 440 - 2 - 64 - 96;
                frames_hdr[2].label = "Value"; frames_hdr[2].w = 96;
                frames_hdr[3].label = NULL;
                frames_tbl.num = ptr[0];
                frames_tbl.data = ptr + 3;
                frames_tbl.entsize = 8;
                frames_tbl.drawcell = draw_attrlist;
            break;
            case 1:
                frames_hdr[0].label = "Qty"; frames_hdr[0].w = 32;
                frames_hdr[1].label = "Item Object"; frames_hdr[1].w = scr_w - 440 - 2 - 32;
                frames_hdr[2].label = NULL;
                frames_tbl.num = ptr[1];
                frames_tbl.data = ptr + 3 + ptr[0] * 8;
                frames_tbl.entsize = 4;
                frames_tbl.drawcell = draw_objlist;
            break;
            case 2:
                frames_hdr[0].label = "Type"; frames_hdr[0].w = 80;
                frames_hdr[1].label = "Sprite"; frames_hdr[1].w = scr_w - 440 - 2 - 80;
                frames_hdr[2].label = NULL;
                frames_tbl.num = ptr[2];
                frames_tbl.data = ptr + 3 + ptr[0] * 8 + ptr[1] * 4;
                frames_tbl.entsize = 3;
                frames_tbl.drawcell = draw_sprlist;
            break;
        }
        ui_table(screen, 430, 250, scr_w - 440, scr_h - 250 - 4, &frames_tbl, sec_tab == 3);
    }
}

/**
 * View Objects section
 */
void view_objects(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    char tmp[32], *channels[] = { "Collision", "Sounds", "Equipted", "Required", "Modifiers", "Charsprites", "Evthandlers", NULL };
    uint32_t c;
    uint8_t *ptr, *buf;
    int i, w, h, nums, numr, numm, numc, nume;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Meta for Object"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_obj;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    dirs_tbl.hdr = NULL;
    dirs_tbl.num = 7;
    dirs_tbl.data = channels;
    dirs_tbl.entsize = sizeof(char*);
    dirs_tbl.drawcell = draw_texts;
    ui_table(screen, 220, 36 + 128, 128, 6 + 5 * 18, &dirs_tbl, sec_tab == 2);
    frames_tbl.num = 0;
    frames_tbl.data = NULL;
    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 164, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 456, 164, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 180, "Collmask:");
        ssfn_dst.fg = theme[THEME_FG];
        w = (ptr[1] << 8) | ptr[0];  ptr += 2;
        sprintf(tmp, "%u", w);
        ui_text(screen, 456, 180, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x + 8, 180, "x");
        ssfn_dst.fg = theme[THEME_FG];
        h = (ptr[1] << 8) | ptr[0];  ptr += 2;
        sprintf(tmp, "%u", h);
        ui_text(screen, ssfn_dst.x + 8, 180, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 180, ", action group:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02x", ptr[0]); ptr++;
        ui_text(screen, ssfn_dst.x + 8, 180, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 196, "Inventory:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, 456, 196, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 196, ", sfx:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, ssfn_dst.x + 8, 196, tmp);
        ui_text(screen, ssfn_dst.x + 8, 196, str(c));
        ssfn_dst.bg = 0;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 212, "Price:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%d 1.%04X", c, (ptr[1] << 8) | ptr[0]); ptr += 2;
        ui_text(screen, 456, 212, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 212, ", cat:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02x", ptr[0]); ptr++;
        ui_text(screen, ssfn_dst.x + 8, 212, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 228, "Projectile:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, 456, 228, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 228, ", dur:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%u.%02u", c/100, c%100);
        ui_text(screen, ssfn_dst.x + 8, 228, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 228, ", ammo:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, ssfn_dst.x + 8, 228, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 244, "Layer order:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%u", ptr[0]); ptr++;
        ui_text(screen, 456, 244, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 244, ", equipment mask:");
        ssfn_dst.fg = theme[THEME_FG];
        memcpy(&c, ptr, 4); ptr += 4;
        if(c == 0xffffffff) sprintf(tmp, "skill"); else sprintf(tmp, "%08X", c);
        ui_text(screen, ssfn_dst.x + 8, 244, tmp);
        nums = *ptr++; numr = *ptr++; numm = *ptr++; numc = *ptr++; nume = *ptr++;
        if(dirs_tbl.val==0) {
            frames_hdr[0].label = "Collision Mask"; frames_hdr[0].w = scr_w - 8 - 224;
            frames_hdr[1].label = NULL;
            frames_tbl.entsize = w;
            frames_tbl.num = h;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_coll;
        }
        ptr += w * h;
        if(dirs_tbl.val==1) {
            frames_hdr[0].label = "Sound Effects"; frames_hdr[0].w = scr_w - 8 - 224;
            frames_hdr[1].label = NULL;
            frames_tbl.entsize = 3;
            frames_tbl.num = nums;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_sound;
        }
        ptr += nums * 3;
        if(dirs_tbl.val==2) {
            frames_hdr[0].label = "Equipted Sprites"; frames_hdr[0].w = scr_w - 8 - 224;
            frames_hdr[1].label = NULL;
            frames_tbl.entsize = 3;
            frames_tbl.num = 32;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_sprite;
        }
        ptr += 32 * 3;
        if(dirs_tbl.val==3) {
            frames_hdr[0].label = "Type"; frames_hdr[0].w = 64;
            frames_hdr[1].label = "Attribute"; frames_hdr[1].w = scr_w - 64 - 96 - 8 - 224;
            frames_hdr[2].label = "Value"; frames_hdr[2].w = 96;
            frames_hdr[3].label = NULL;
            frames_tbl.entsize = 8;
            frames_tbl.num = numr;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_attrlist;
        }
        ptr += numr * 8;
        if(dirs_tbl.val==4) {
            frames_hdr[0].label = "Equipted"; frames_hdr[0].w = 72;
            frames_hdr[1].label = "Attribute"; frames_hdr[1].w = scr_w - 72 - 96 - 8 - 224;
            frames_hdr[2].label = "Value"; frames_hdr[2].w = 96;
            frames_hdr[3].label = NULL;
            frames_tbl.entsize = 8;
            frames_tbl.num = numm;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_attrmod;
        }
        ptr += numm * 8;
        if(dirs_tbl.val==5) {
            frames_hdr[0].label = "Charopt"; frames_hdr[0].w = 128;
            frames_hdr[1].label = "Sprites"; frames_hdr[1].w = scr_w - 128 - 8 - 224;
            frames_hdr[2].label = NULL;
            frames_tbl.entsize = 123;
            frames_tbl.num = numc;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_charopt;
            frames_tbl.row = 34;
        }
        ptr += numc * 123;
        if(dirs_tbl.val==6) {
            frames_hdr[0].label = "Type"; frames_hdr[0].w = 128;
            frames_hdr[1].label = "Parameter"; frames_hdr[1].w = scr_w - 8 - 224 - 128;
            frames_hdr[2].label = NULL;
            frames_tbl.entsize = 16;
            frames_tbl.num = nume;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_evt;
        }
    }
    if(dirs_tbl.val < 6) {
        ui_table(screen, 220, 264, scr_w - 230, scr_h - 4 - 264, &frames_tbl, sec_tab == 3);
    } else {
        sec_hastab = 4;
        if(mnes) {
            free(mnes); mnes = NULL; free(mne); mne = NULL; nummne = 0;
        }
        ui_table(screen, 220, 264, scr_w - 230, 75, &frames_tbl, sec_tab == 3);
        events_hdr[0].label = "Address"; events_hdr[0].w = 64;
        events_hdr[1].label = "Bytecode"; events_hdr[1].w = 192;
        events_hdr[2].label = "Mnemonic"; events_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
        events_hdr[3].label = NULL;
        events_tbl.num = 0;
        events_tbl.data = NULL;
        if(mnes) {
            events_tbl.num = nummne;
            events_tbl.data = mne;
            events_tbl.entsize = sizeof(int);
            events_tbl.drawcell = draw_mnemonics;
        }
        ui_table(screen, 220, 264 + 79, scr_w - 230, scr_h - 4 - 264 - 79, &events_tbl, sec_tab == 4);
    }
    frames_tbl.row = 18;
}

/**
 * View NPCs section
 */
void view_npcs(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    char tmp[32], *channels[] = { "Attributes", "Inventory", "Sprites", "Evthandlers", NULL };
    uint8_t *ptr, *buf;
    int i, numa, numi, numc, nume;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "NPC Types"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    dirs_tbl.hdr = NULL;
    dirs_tbl.num = 4;
    dirs_tbl.data = channels;
    dirs_tbl.entsize = sizeof(char*);
    dirs_tbl.drawcell = draw_texts;
    ui_table(screen, 220, 36 + 128, 128, 6 + 4 * 18, &dirs_tbl, sec_tab == 2);
    frames_tbl.num = 0;
    frames_tbl.data = NULL;
    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 164, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 456, 164, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 180, "Behaviour:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02x", ptr[0]);
        if(ptr[0] > 2) ssfn_dst.bg = theme[THEME_ERR];
        ui_text(screen, 456, 180, tmp);
        ui_text(screen, ssfn_dst.x + 8, 180, ptr[0] <= 2 ? (char*)beh_types[(int)ptr[0]] : "(err)");
        ssfn_dst.bg = 0;
        ptr++;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 196, "Speed:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%u%%", (ptr[1] << 8) | ptr[0]); ptr += 2;
        ui_text(screen, 456, 196, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 196, ", dirchange: every");
        ssfn_dst.fg = theme[THEME_FG];
        i = (ptr[1] << 8) | ptr[0]; ptr += 2;
        sprintf(tmp, "%u.%02u", i/100, i%100);
        ui_text(screen, ssfn_dst.x + 8, 196, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x + 8, 196, "secs");
        ui_text(screen, 352, 212, "Patrol path:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02x", *ptr++);
        ui_text(screen, 456, 212, tmp);
        numa = *ptr++; numi = *ptr++; numc = *ptr++; nume = *ptr++;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 352, 228, "Nums:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%u %u %u %u", numa, numi, numc, nume);
        ui_text(screen, 456, 228, tmp);
        if(dirs_tbl.val==0) {
            frames_hdr[0].label = "Type"; frames_hdr[0].w = 64;
            frames_hdr[1].label = "Attribute"; frames_hdr[1].w = scr_w - 64 - 96 - 8 - 224;
            frames_hdr[2].label = "Value"; frames_hdr[2].w = 96;
            frames_hdr[3].label = NULL;
            frames_tbl.entsize = 8;
            frames_tbl.num = numa;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_attrlist;
        }
        ptr += numa * 8;
        if(dirs_tbl.val==1) {
            frames_hdr[0].label = "Chance"; frames_hdr[0].w = 64;
            frames_hdr[1].label = "Quantity"; frames_hdr[1].w = 96;
            frames_hdr[2].label = "Resupply"; frames_hdr[2].w = 96;
            frames_hdr[3].label = "Object"; frames_hdr[3].w = scr_w - 64 - 96 - 96 - 8 - 224;
            frames_hdr[4].label = NULL;
            frames_tbl.entsize = 10;
            frames_tbl.num = numi;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_invlist;
        }
        ptr += numi * 10;
        if(dirs_tbl.val==2) {
            frames_hdr[0].label = "Type"; frames_hdr[0].w = 96;
            frames_hdr[1].label = "Sprite"; frames_hdr[1].w = scr_w - 96 - 8 - 224;
            frames_hdr[2].label = NULL;
            frames_tbl.entsize = 3;
            frames_tbl.num = numc;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_sprlist;
        }
        ptr += numc * 3;
        if(dirs_tbl.val==3) {
            frames_hdr[0].label = "Type"; frames_hdr[0].w = 128;
            frames_hdr[1].label = "Parameter"; frames_hdr[1].w = scr_w - 8 - 224 - 128;
            frames_hdr[2].label = NULL;
            frames_tbl.entsize = 16;
            frames_tbl.num = nume;
            frames_tbl.data = ptr;
            frames_tbl.drawcell = draw_evt;
        }
    }
    if(dirs_tbl.val < 3) {
        ui_table(screen, 220, 248, scr_w - 230, scr_h - 4 - 248, &frames_tbl, sec_tab == 3);
    } else {
        sec_hastab = 4;
        if(mnes) {
            free(mnes); mnes = NULL; free(mne); mne = NULL; nummne = 0;
        }
        ui_table(screen, 220, 248, scr_w - 230, 75, &frames_tbl, sec_tab == 3);
        events_hdr[0].label = "Address"; events_hdr[0].w = 64;
        events_hdr[1].label = "Bytecode"; events_hdr[1].w = 192;
        events_hdr[2].label = "Mnemonic"; events_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
        events_hdr[3].label = NULL;
        events_tbl.num = 0;
        events_tbl.data = NULL;
        if(mnes) {
            events_tbl.num = nummne;
            events_tbl.data = mne;
            events_tbl.entsize = sizeof(int);
            events_tbl.drawcell = draw_mnemonics;
        }
        ui_table(screen, 220, 248 + 79, scr_w - 230, scr_h - 4 - 248 - 79, &events_tbl, sec_tab == 4);
    }
}

/**
 * View Spawners section
 */
void view_spawners(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c;
    int i;
    char tmp[32];

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Spawners"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    dirs_hdr[0].label = "Required"; dirs_hdr[0].w = 192;
    dirs_hdr[1].label = "Spawned"; dirs_hdr[1].w = 192;
    dirs_hdr[2].label = "Anim"; dirs_hdr[2].w = 128;
    dirs_hdr[3].label = "NPC"; dirs_hdr[3].w = scr_w - 192 - 192 - 128 - 8 - 224;
    dirs_hdr[4].label = NULL;
    dirs_tbl.num = 0;
    dirs_tbl.data = NULL;
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 36 + 128, "MaxNPC:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%u", *ptr++);
        ui_text(screen, 328, 36 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 52 + 128, "Respawn:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, 328, 52 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        sprintf(tmp, "(%u every 1/100th sec)", c);
        ui_text(screen, ssfn_dst.x + 8, 52 + 128, tmp);
        ssfn_dst.fg = theme[THEME_FG];
        dirs_tbl.num = (ptr[1] << 8) | ptr[0]; ptr += 2;
        dirs_tbl.data = ptr;
        dirs_tbl.entsize = 21;
        dirs_tbl.drawcell = draw_spawner;
    }
    ui_table(screen, 220, 70 + 128, scr_w - 230, scr_h - 4 - (70 + 128), &dirs_tbl, sec_tab == 2);
}

/**
 * View Dialogs section
 */
void view_dialogs(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c;
    int i;
    char tmp[16];

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Dialogs"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }

    dirs_hdr[0].label = "Required"; dirs_hdr[0].w = 192;
    dirs_hdr[1].label = "Provided"; dirs_hdr[1].w = 192;
    dirs_hdr[2].label = "Speech"; dirs_hdr[2].w = 128;
    dirs_hdr[3].label = "Text"; dirs_hdr[3].w = scr_w - 192 - 192 - 128 - 8 - 224;
    dirs_hdr[4].label = NULL;
    dirs_tbl.num = 0;
    dirs_tbl.data = NULL;
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 36 + 128, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 36 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 52 + 128, "Desc:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 52 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 68 + 128, "Speech:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, ssfn_dst.x + 8, 68 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 68 + 128, !c ? "(unset)" : str(c));
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 84 + 128, "Portrait:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, 328, 84 + 128, ptr);
        sprintf(tmp, "%02X", *ptr++);
        ui_text(screen, ssfn_dst.x + 8, 84 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        if(ptr[-1])
            ui_text(screen, ssfn_dst.x + 8, 84 + 128, "(portrait on left)");
        else
            ui_text(screen, ssfn_dst.x + 8, 84 + 128, "(portrait on right)");
        dirs_tbl.num = *ptr++;
        dirs_tbl.data = ptr;
        dirs_tbl.entsize = 21;
        dirs_tbl.drawcell = draw_dialog;
    }
    ui_table(screen, 220, 102 + 128, scr_w - 230, scr_h - 4 - (102 + 128), &dirs_tbl, sec_tab == 2);
}

/**
 * View Crafts section
 */
void view_crafts(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c;
    int i;
    char tmp[16];

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Crafts"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }

    dirs_hdr[0].label = "Ingredients"; dirs_hdr[0].w = 320;
    dirs_hdr[1].label = "Product"; dirs_hdr[1].w = scr_w - 320 - 8 - 224;
    dirs_hdr[2].label = NULL;
    dirs_tbl.num = 0;
    dirs_tbl.data = NULL;
    dirs_tbl.row = 66;
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 36 + 128, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 36 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 52 + 128, "Desc:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 52 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 68 + 128, "Crafted SFX:");
        ssfn_dst.fg = theme[THEME_FG];
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X", c);
        ui_text(screen, 328, 68 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 68 + 128, !c ? "(unset)" : str(c));
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 84 + 128, "Portrait:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, 328, 84 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x, 84 + 128, ", ingredient order:");
        ssfn_dst.fg = theme[THEME_FG];
        sprintf(tmp, "%02X", *ptr++);
        ui_text(screen, ssfn_dst.x + 8, 84 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        if(ptr[-1])
            ui_text(screen, ssfn_dst.x + 8, 84 + 128, "(matters)");
        else
            ui_text(screen, ssfn_dst.x + 8, 84 + 128, "(irrelevant)");
        dirs_tbl.num = *ptr++;
        dirs_tbl.data = ptr;
        dirs_tbl.entsize = 68;
        dirs_tbl.drawcell = draw_craft;
    }
    ui_table(screen, 220, 102 + 128, scr_w - 230, scr_h - 4 - (102 + 128), &dirs_tbl, sec_tab == 2);
    dirs_tbl.row = 18;
}

/**
 * View Tiles section
 */
void view_tiles(tng_section_t *section)
{
    int i = (scr_w - 48 - 8 - 224) / 5;

    assets_hdr[0].label = "Tile"; assets_hdr[0].w = 48;
    assets_hdr[1].label = "On Walk"; assets_hdr[1].w = i;
    assets_hdr[2].label = "On Swim"; assets_hdr[2].w = i;
    assets_hdr[3].label = "On Fly"; assets_hdr[3].w = i;
    assets_hdr[4].label = "On Drive"; assets_hdr[4].w = i;
    assets_hdr[5].label = "On X"; assets_hdr[5].w = scr_w - 48 - 4*i - 8 - 224;
    assets_hdr[6].label = NULL;
    assets_tbl.entsize = 2 + hdr.numtrn * 5;
    assets_tbl.num = TNG_SECTION_SIZE(section) / assets_tbl.entsize;
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.drawcell = draw_tiles;
    ui_table(screen, 220, 32, scr_w - 230, scr_h - 36, &assets_tbl, sec_tab == 1);
}

/**
 * View Quests section
 */
void view_quests(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c;
    int i;
    char tmp[16];

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Quest"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    dirs_hdr[0].label = "Address"; dirs_hdr[0].w = 64;
    dirs_hdr[1].label = "Bytecode"; dirs_hdr[1].w = 192;
    dirs_hdr[2].label = "Mnemonic"; dirs_hdr[2].w = scr_w - 64 - 192 - 8 - 224;
    dirs_hdr[3].label = NULL;
    dirs_tbl.num = 0;
    dirs_tbl.data = NULL;
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 36 + 128, "Global:");
        sprintf(tmp, "%02X %s", ptr[0], ptr[0] ? "yes" : "no"); ptr++;
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 328, 36 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 52 + 128, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 52 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 68 + 128, "Desc:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 328, 68 + 128, ptr);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 220, 86 + 128, "On completition:");
        c = 0; memcpy(&c, ptr, 3); ptr += 3;
        if(!mnes)
            bc_disasm(ptr, c);
        dirs_tbl.num = nummne;
        dirs_tbl.data = mne;
        dirs_tbl.entsize = sizeof(int);
        dirs_tbl.drawcell = draw_mnemonics;

    }
    ui_table(screen, 220, 102 + 128, scr_w - 230, scr_h - 4 - (102 + 128), &dirs_tbl, sec_tab == 2);
}

/**
 * Draw map
 */
void draw_map(SDL_Texture *dst, int x, int y, int w, int h, int dx, int dy, uint16_t *tiles)
{
    int i, j, k, m = (1 << hdr.mapsize), X;

    ui_box(dst, x, y, w, h, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER]);
    x += 2; w -= 4; y += 2; h -= 4;
    ui_clip.x = x; ui_clip.y = y; ui_clip.w = w; ui_clip.h = h;
    for(j = k = 0; j < m; j++, y += 8)
        for(i = 0, X = x; i < m; i++, X += 19, k++)
            ui_hex(dst, X - dx, y - dy, tiles[k], theme[tiles[k] == 0xffff ? THEME_LIGHTER : THEME_FG]);
    ui_clip.x = ui_clip.y = 0; ui_clip.w = scr_w; ui_clip.h = scr_h;
}

/**
 * View Maps section
 */
void view_maps(tng_section_t *section)
{
    tng_asset_desc_t *asset;
    uint8_t *ptr, *buf;
    uint32_t c, nprx = 0, npath = 0, nmap = 0;
    char tmp[64], *layers[] = { "Neightbors", "Parallaxes", "Paths", "Ground 1", "Ground 2", "Ground 3", "Ground 4", "Objects",
        "NPCs", "Spawners", "Roof 1", "Roof 2", "Roof 3", "Roof 4", NULL };
    int i;

    assets_hdr[0].label = "Offset"; assets_hdr[0].w = 136;
    assets_hdr[1].label = "Size"; assets_hdr[1].w = 88;
    assets_hdr[2].label = "Map"; assets_hdr[2].w = scr_w - 136 - 88 - 8 - 224;
    assets_hdr[3].label = NULL;
    assets_tbl.num = TNG_SECTION_SIZE(section) / sizeof(tng_asset_desc_t);
    assets_tbl.data = (uint8_t*)sec + section->offs;
    assets_tbl.entsize = sizeof(tng_asset_desc_t);
    assets_tbl.drawcell = draw_assets;
    ui_table(screen, 220, 32, scr_w - 230, 128, &assets_tbl, sec_tab == 1);

    dirs_tbl.hdr = NULL;
    dirs_tbl.num = 14;
    dirs_tbl.data = layers;
    dirs_tbl.entsize = sizeof(char*);
    dirs_tbl.drawcell = draw_texts;
    ui_table(screen, 220, 36 + 128, 100, 6 + 4 * 18, &dirs_tbl, sec_tab == 2);
    frames_tbl.num = 0;
    frames_tbl.data = NULL;

    if(!optsbuf && assets_tbl.val < assets_tbl.num) {
        asset = (tng_asset_desc_t*)((uint8_t*)sec + section->offs) + assets_tbl.val;
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size))
            optsbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &i, 1);
        free(buf);
    }
    if(optsbuf) {
        ptr = optsbuf;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 36 + 128, "Title:");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_tr(screen, 436, 36 + 128, ptr);
        if(dirs_tbl.val == 0) {
            sec_hastab = 3;
            frames_hdr[0].label = "Dir"; frames_hdr[0].w = 48;
            frames_hdr[1].label = "Neightbor"; frames_hdr[1].w = scr_w - 48 - 8 - 224;
            frames_hdr[2].label = NULL;
            frames_tbl.num = hdr.type < TNG_TYPE_HEXV ? 4 : 6;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 3;
            frames_tbl.drawcell = draw_neight;
        }
        ptr += 6 * 3;
        c = 0; memcpy(&c, ptr, 2); ptr += 2;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 52 + 128, "Daylight:");
        sprintf(tmp, "%04X %u", c, c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 52 + 128, tmp);
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x + 8, 52 + 128, "minutes,");
        ssfn_dst.fg = theme[THEME_FG];
        ptr = draw_sprref(screen, ssfn_dst.x + 8, 52 + 128, ptr);
        ssfn_dst.bg = 0;
        memcpy(&c, ptr, 3); ptr += 3;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 68 + 128, "Bg Music:");
        sprintf(tmp, "%06X", c);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 68 + 128, tmp);
        ui_text(screen, ssfn_dst.x + 8, 68 + 128, str(c));
        ssfn_dst.bg = 0;
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, 328, 84 + 128, "Nums:");
        memcpy(&nprx, ptr, 1); ptr++;
        sprintf(tmp, "%02X (%u prxs)", nprx, nprx);
        ui_text(screen, 436, 84 + 128, tmp);
        memcpy(&npath, ptr, 1); ptr++;
        sprintf(tmp, "%02X (%u paths)", npath, npath);
        ui_text(screen, ssfn_dst.x + 8, 84 + 128, tmp);
        ui_text(screen, 328, 100 + 128, "Tiles:");
        memcpy(&nmap, ptr, 3); ptr += 3;
        sprintf(tmp, "%06X %u", nmap, nmap);
        ssfn_dst.fg = theme[THEME_FG];
        ui_text(screen, 436, 100 + 128, tmp);
        sprintf(tmp, "bytes (%u uncompressed)", hdr.numlyr * 2 * (1 << hdr.mapsize) * (1 << hdr.mapsize));
        ssfn_dst.fg = theme[THEME_LIGHTER];
        ui_text(screen, ssfn_dst.x + 8, 100 + 128, tmp);
        if(dirs_tbl.val == 1) {
            sec_hastab = 3;
            frames_hdr[0].label = "Lyr"; frames_hdr[0].w = 48;
            frames_hdr[1].label = "SpeedX"; frames_hdr[1].w = 64;
            frames_hdr[2].label = "DeltaX"; frames_hdr[2].w = 64;
            frames_hdr[3].label = "SpeedY"; frames_hdr[3].w = 64;
            frames_hdr[4].label = "DeltaY"; frames_hdr[4].w = 64;
            frames_hdr[5].label = "Time"; frames_hdr[5].w = 64;
            frames_hdr[6].label = "Parallax Sprite"; frames_hdr[6].w = scr_w - 48 - 5*64 - 8 - 224;
            frames_hdr[7].label = NULL;
            frames_tbl.num = nprx;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 10;
            frames_tbl.drawcell = draw_mapprx;
        }
        ptr += nprx * 10;
        if(dirs_tbl.val == 2) {
            sec_hastab = 3;
            frames_hdr[0].label = "Id"; frames_hdr[0].w = 24;
            frames_hdr[1].label = "Length"; frames_hdr[1].w = 56;
            frames_hdr[2].label = "Patrol Path Coordinates"; frames_hdr[2].w = scr_w - 24 - 48 - 8 - 224;
            frames_hdr[3].label = NULL;
            frames_tbl.num = npath;
            frames_tbl.data = ptr;
            frames_tbl.entsize = 1;
            frames_tbl.drawcell = draw_mappath;
        }
        for(i = 0; i < (int)npath; i++)
            ptr += 3 + ((ptr[2] << 8) | ptr[1]) * 4;
        if(!tiles) {
            i = 11 * (1 << hdr.mapsize) * (1 << hdr.mapsize);
            tiles = (uint16_t*)main_alloc(i * sizeof(uint16_t));
            tng_rle_dec16(ptr, nmap, tiles, &i);
        }
    }
    if(dirs_tbl.val < 3)
        ui_table(screen, 220, 246, scr_w - 230, scr_h - 4 - 246, &frames_tbl, sec_tab == 3);
    else if(tiles && (dirs_tbl.val - 3) < hdr.numlyr)
        draw_map(screen, 220, 246, scr_w - 230, scr_h - 4 - 246, tilesx, tilesy,
            tiles + (dirs_tbl.val - 3) * (1 << hdr.mapsize) * (1 << hdr.mapsize));
}

/**
 * Draw table cell
 */
void draw_ipaddr(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint8_t *ptr = (uint8_t*)data;
    char tmp[INET6_ADDRSTRLEN+1];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%02x", ptr[0]);
    ui_text(dst, x + 4, y + 1, tmp);
    inet_ntop(ptr[0], (void*)(ptr + 1), tmp, INET6_ADDRSTRLEN);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
}

/**
 * View a game state deny chunk
 */
void view_deny(tng_section_t *section)
{
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Denied IP Addresses");
    ssfn_dst.fg = theme[THEME_FG];

    sec_hastab = 1;
    assets_hdr[0].label = "Fam"; assets_hdr[0].w = 24;
    assets_hdr[1].label = "IP Address"; assets_hdr[1].w = scr_w - 24 - 8 - 224;
    assets_hdr[2].label = NULL;
    assets_tbl.num = (section->size - 8) / 17;
    assets_tbl.data = (uint8_t*)sbuf + section->offs + 8;
    assets_tbl.entsize = 17;
    assets_tbl.drawcell = draw_ipaddr;
    ui_table(screen, 220, 48, scr_w - 230, scr_h - 4 - 48, &assets_tbl, sec_tab == 1);
}

/**
 * Draw table cell
 */
void draw_glbl(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    int32_t *ptr = (int32_t*)data;
    char tmp[16];

    (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%5u", idx);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%6d", *ptr);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
}

/**
 * View a game state global chunk
 */
void view_glbl(tng_section_t *section)
{
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Global Attributes");
    ssfn_dst.fg = theme[THEME_FG];

    sec_hastab = 1;
    assets_hdr[0].label = "Index"; assets_hdr[0].w = 48;
    assets_hdr[1].label = "Value"; assets_hdr[1].w = scr_w - 48 - 8 - 224;
    assets_hdr[2].label = NULL;
    assets_tbl.num = (section->size - 8) / 4;
    assets_tbl.data = (uint8_t*)sbuf + section->offs + 8;
    assets_tbl.entsize = 4;
    assets_tbl.drawcell = draw_glbl;
    ui_table(screen, 220, 48, scr_w - 230, scr_h - 4 - 48, &assets_tbl, sec_tab == 1);
}

/**
 * Draw table cell
 */
void draw_belt(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint16_t *item = (uint16_t*)data;
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    if(item[0] == 0xffff)
        ui_text(dst, x + 4, y + 1, "(unset)");
    else {
        sprintf(tmp, "%s %u", item[0] < 0x8000 ? "inv" : "skill", item[0] < 0x8000 ? item[0] : item[0] & 0x7fff);
        ui_text(dst, x + 4, y + 1, tmp);
    }
}

/**
 * Draw table cell
 */
void draw_opt(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint32_t opt = *((uint32_t*)data);
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "%10d", opt >> 8);
    ui_text(dst, x + 4, y + 1, tmp);
    sprintf(tmp, "%6d", opt & 0xff);
    ui_text(dst, x + 4 + hdr[0].w, y + 1, tmp);
}

/**
 * Draw table cell
 */
void draw_equip(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hdr, void *data)
{
    uint16_t equip = *((uint16_t*)data);
    char tmp[16];

    (void)idx; (void)sel; (void)hdr; (void)w; (void)h;
    if(!data) return;
    sprintf(tmp, "1.%04X%s", equip, equip == 0xffff ? " (unset)" : "");
    ui_text(dst, x + 4, y + 1, tmp);
}

/**
 * Draw an entity block
 */
int draw_entity(int y, uint8_t *ptr)
{
    char tmp[16];
    int j, k, l, a, i, s, q;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Position:");
    i = 0; memcpy(&i, ptr, 3); ptr += 3;
    ui_text(screen, 328, y, "Map Id:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", i);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, y, ", Direction:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", *ptr++);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, y, ", X:");
    ssfn_dst.fg = theme[THEME_FG];
    i = 0; memcpy(&i, ptr, 4); ptr += 4;
    sprintf(tmp, "%u", i);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, y, ", Y:");
    ssfn_dst.fg = theme[THEME_FG];
    i = 0; memcpy(&i, ptr, 4); ptr += 4;
    sprintf(tmp, "%u", i);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, y, ", Altitude:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", ptr[2]);
    ui_text(screen, ssfn_dst.x + 8, y, tmp);
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Behaviour:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", *ptr++);
    ui_text(screen, 328, y, tmp);
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Transport:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", ptr[0]);
    ui_text(screen, 328, y, tmp);
    if(ptr[0] < 4) ui_text(screen, ssfn_dst.x + 8, y, (char*)trn_types[(int)ptr[0]]);
    ptr++;
    y += 16;

    ptr++; /* skip altitude */

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Nums:");
    ssfn_dst.fg = theme[THEME_FG];
    a = i = s = q = 0;
    a = *ptr++;
    memcpy(&i, ptr, 2); ptr += 2;
    memcpy(&s, ptr, 2); ptr += 2;
    memcpy(&q, ptr, 2); ptr += 2;
    sprintf(tmp, "%u %u %u %u", a, i, s, q);
    ui_text(screen, 328, y, tmp);
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Attributes:");
    ssfn_dst.fg = theme[THEME_FG]; ssfn_dst.x = 320;
    for(j = 0; j < a; j++) {
        memcpy(&k, ptr, 4); ptr += 4;
        sprintf(tmp, "%d", k);
        ui_text(screen, ssfn_dst.x + 8, y, tmp);
    }
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Inventory:");
    ssfn_dst.fg = theme[THEME_FG]; ssfn_dst.x = 320;
    for(j = 0; j < i; j++) {
        memcpy(&k, ptr, 4); ptr += 4;
        memcpy(&l, ptr, 4); ptr += 4;
        sprintf(tmp, "(%d %u)", k, l);
        ui_text(screen, ssfn_dst.x + 8, y, tmp);
    }
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Skills:");
    ssfn_dst.fg = theme[THEME_FG]; ssfn_dst.x = 320;
    for(j = 0; j < s; j++) {
        memcpy(&k, ptr, 4); ptr += 4;
        memcpy(&l, ptr, 4); ptr += 4;
        sprintf(tmp, "(%d %u)", k, l);
        ui_text(screen, ssfn_dst.x + 8, y, tmp);
    }
    y += 16;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, y, "Quests:");
    ssfn_dst.fg = theme[THEME_FG]; ssfn_dst.x = 320;
    for(j = 0; j < q; j++) {
        memcpy(&k, ptr, 4); ptr += 4;
        sprintf(tmp, "%s%u", k >> 31 ? "√" : "", k & 0x7fffffff);
        ui_text(screen, ssfn_dst.x + 8, y, tmp);
    }
    y += 16;
    return y;
}

/**
 * View a game state user chunk
 */
void view_user(tng_section_t *section)
{
    uint64_t j;
    uint8_t *ptr = sbuf + section->offs;
    struct tm *tm;
    char tmp[128];
    int i;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Player (Server User)");
    ptr += 8;
    ui_text(screen, 220, 48, "Pass. Hash:");
    ssfn_dst.fg = theme[THEME_FG];
    for(i = 0; i < 16; i++) sprintf(tmp + i * 3, "%02X ", *ptr++);
    tmp[i * 3] = 0;
    ui_text(screen, 328, 48, tmp);
    for(i = 0; i < 16; i++) sprintf(tmp + i * 3, "%02X ", *ptr++);
    tmp[i * 3] = 0;
    ui_text(screen, 328, 64, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 80, "Create Date:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&j, ptr, 8); ptr += 8;
    sprintf(tmp, "%lu", j);
    ui_text(screen, 328, 80, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(j > 0) { tm = localtime((time_t*)&j); if(tm) { strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm); ui_text(screen, ssfn_dst.x + 8, 80, tmp); } }
    ui_text(screen, 220, 96, "Login Date:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&j, ptr, 8); ptr += 8;
    sprintf(tmp, "%lu", j);
    ui_text(screen, 328, 96, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(j > 0) { tm = localtime((time_t*)&j); if(tm) { strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm); ui_text(screen, ssfn_dst.x + 8, 96, tmp); } }
    ui_text(screen, 220, 112, "Logout Date:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&j, ptr, 8); ptr += 8;
    sprintf(tmp, "%lu", j);
    ui_text(screen, 328, 112, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(j > 0) { tm = localtime((time_t*)&j); if(tm) { strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm); ui_text(screen, ssfn_dst.x + 8, 112, tmp); } }
    ui_text(screen, 220, 128, "Last Seen:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&j, ptr, 8); ptr += 8;
    sprintf(tmp, "%lu", j);
    ui_text(screen, 328, 128, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    if(j > 0) { tm = localtime((time_t*)&j); if(tm) { strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm); ui_text(screen, ssfn_dst.x + 8, 128, tmp); } }
    ui_text(screen, 220, 144, "Language:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%c%c", ptr[0], ptr[1]); ptr += 2;
    ui_text(screen, 328, 144, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, 144, ", Flags:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%02x", *ptr++);
    ui_text(screen, ssfn_dst.x + 8, 144, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, ssfn_dst.x, 144, ", NumOpts:");
    ssfn_dst.fg = theme[THEME_FG];
    sprintf(tmp, "%u", ptr[0]); assets_tbl.num = *ptr++;
    ui_text(screen, ssfn_dst.x + 8, 144, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 160, "Username:");
    ssfn_dst.fg = theme[THEME_FG];
    memset(tmp, 0, sizeof(tmp));
    i = strlen((char*)ptr) + 1; memcpy(tmp, ptr, i > (int)sizeof(tmp) - 1 ? (int)sizeof(tmp) - 1 : i); ptr += i;
    ui_text(screen, 328, 160, tmp);
    assets_tbl.data = ptr; ptr += assets_tbl.num * 4;
    dirs_tbl.data = ptr; ptr += 64;
    frames_tbl.data = ptr; ptr += 64;
    i = draw_entity(176, ptr) + 4;
    sec_hastab = 3;
    assets_hdr[0].label = "Option Value"; assets_hdr[0].w = scr_w - 80 - 24 - 224 - 256;
    assets_hdr[1].label = "Palette"; assets_hdr[1].w = 80;
    assets_hdr[2].label = NULL;
    assets_tbl.entsize = 4;
    assets_tbl.drawcell = draw_opt;
    if(scr_h - 4 - i > 40)
        ui_table(screen, 220, i, scr_w - 238 - 256 - 8, scr_h - 4 - i, &assets_tbl, sec_tab == 1);
    dirs_hdr[0].label = "Equipted"; dirs_hdr[0].w = 130;
    dirs_hdr[1].label = NULL;
    dirs_tbl.num = 32;
    dirs_tbl.entsize = sizeof(uint16_t);
    dirs_tbl.drawcell = draw_equip;
    ui_table(screen, scr_w - 132 - 132 - 12, i, 132, scr_h - 4 - i, &dirs_tbl, sec_tab == 2);
    frames_hdr[0].label = "Belt"; frames_hdr[0].w = 130;
    frames_hdr[1].label = NULL;
    frames_tbl.entsize = sizeof(uint16_t);
    frames_tbl.num = 32;
    frames_tbl.drawcell = draw_belt;
    ui_table(screen, scr_w - 132 - 8, i, 132, scr_h - 4 - i, &frames_tbl, sec_tab == 3);
}

/**
 * View a game state npc chunk
 */
void view_npc(tng_section_t *section)
{
    uint8_t *ptr = sbuf + section->offs;
    char tmp[16];
    int i;

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Non-Player Character");
    ptr += 8;
    ui_text(screen, 220, 48, "NPC Type:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&i, ptr, 4); ptr += 4;
    sprintf(tmp, "%u", i);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, 328, 48, tmp);
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 56, "Spawner Id:");
    ssfn_dst.fg = theme[THEME_FG];
    memcpy(&i, ptr, 4); ptr += 4;
    sprintf(tmp, "%u", i);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, 328, 56, tmp);
    draw_entity(64, ptr);
}

/**
 * View a game state quests chunk
 */
void view_qsts(tng_section_t *section)
{
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Completed Global Quests");
    ssfn_dst.fg = theme[THEME_FG];

    sec_hastab = 1;
    assets_hdr[0].label = "Index"; assets_hdr[0].w = 48;
    assets_hdr[1].label = "Player Id"; assets_hdr[1].w = scr_w - 48 - 8 - 224;
    assets_hdr[2].label = NULL;
    assets_tbl.num = (section->size - 8) / 4;
    assets_tbl.data = (uint8_t*)sbuf + section->offs + 8;
    assets_tbl.entsize = 4;
    assets_tbl.drawcell = draw_glbl;
    ui_table(screen, 220, 48, scr_w - 230, scr_h - 4 - 48, &assets_tbl, sec_tab == 1);
}

/**
 * View a game state map object layer chunk
 */
void view_map(tng_section_t *section)
{
    int map = 0, size = 256 * 256, len;
    uint8_t *ptr = sbuf + section->offs;
    char tmp[64];

    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 24, "Modified Map Objects Layer");
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 48, "Map Id:");
    memcpy(&map, ptr + 8, 4);
    sprintf(tmp, "%u", map);
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, 328, 48, tmp);

    if(!tiles) {
        do {
            len = size;
            tiles = (uint16_t*)main_alloc(len * sizeof(uint16_t));
            tng_rle_dec16(ptr + 12, section->size - 12, tiles, &len);
            if(!len) { free(tiles); size <<= 1; }
        } while(!len && size < 1024 * 1024);
    }
    for(hdr.mapsize = 0; (1 << hdr.mapsize) * (1 << hdr.mapsize) < len && hdr.mapsize < 16; hdr.mapsize++);
    sprintf(tmp, "%u bytes (uncompressed %u bytes, %u x %u tiles)", section->size - 12, len, (1 << hdr.mapsize), (1 << hdr.mapsize));
    ssfn_dst.fg = theme[THEME_LIGHTER];
    ui_text(screen, 220, 64, "Layer Data:");
    ssfn_dst.fg = theme[THEME_FG];
    ui_text(screen, 328, 64, tmp);
    draw_map(screen, 220, 74, scr_w - 230, scr_h - 4 - 74, tilesx, tilesy, tiles);
}

/**
 * Draw table cell
 */
void draw_sections(SDL_Texture *dst, int idx, int sel, int x, int y, int w, int h, ui_tablehdr_t *hd, void *data)
{
    tng_section_t *section = (tng_section_t*)data;
    int i;
    char tmp[32];

    (void)sel; (void)hd; (void)w; (void)h;
    if(idx == -1) {
        ui_text(dst, x + 28, y + 1, "Header");
        return;
    }
    if(!data) return;
    if(hdr.magic[0] == 'T') {
        memcpy(tmp, (uint8_t*)sbuf + section->offs, 4); tmp[4] = 0;
        ui_text(dst, x + 28, y + 1, tmp);
        if(!memcmp(tmp, "USER", 4)) ui_text(dst, ssfn_dst.x + 8, y + 1, (char*)sbuf + section->offs + 76);
    } else {
        i = TNG_SECTION_TYPE(section);
        sprintf(tmp, "%02X", i);
        ui_text(dst, x + 4, y + 1, tmp);
        if(i == TNG_SECTION_TRANSLATION) {
            sprintf(tmp, "(%c%c) %s",
                section->offs > sec[0].offs + TNG_SECTION_SIZE(&sec[0]) && section->offs < sec_unc - 16 ? *((char*)sec + section->offs) : '?',
                section->offs > sec[0].offs + TNG_SECTION_SIZE(&sec[0]) && section->offs < sec_unc - 16 ? *((char*)sec + section->offs + 1) : '?',
                section_types[i]);
            ui_text(dst, x + 28, y + 1, tmp);
        } else
        if(i >= 0 && i < (int)(sizeof(section_types)/sizeof(section_types[0]))) {
            ui_text(dst, x + 28, y + 1, (char*)section_types[i]);
        } else {
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(dst, x + 28, y + 1, " INVALID ");
            ssfn_dst.bg = 0;
        }
    }
}

/**
 * Resize the screen backbuffer
 */
void ui_resize(int w, int h)
{
    if(screen) SDL_DestroyTexture(screen);
    screen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, w, h);
    if(!screen) main_error(ERR_DISPLAY);
    scr_w = w; scr_h = h;
}

/**
 * Redraw the screen backbuffer. It is okay if this is a bit slow.
 */
void ui_redraw()
{
    int i;
    char tmp[128];
    tng_section_t *section;

    ui_clip.x = ui_clip.y = 0; ui_clip.w = scr_w; ui_clip.h = scr_h;
    ssfn_dst.w = ui_clip.w; ssfn_dst.h = ui_clip.h;
    ssfn_dst.fg = theme[THEME_FG]; ssfn_dst.bg = 0;
    SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
    currframe = NULL;
    /* clear screen */
    for(i = 0; i < scr_p/4 * scr_h; i++) scr_data[i] = theme[THEME_BG];

    sec_hastab = tabx = taby = tabn = 0;
    dirs_tbl.hdr = dirs_hdr;
    ui_table(screen, 10, 4, 200, scr_h - 8, &sections_tbl, sec_tab == 0);
    if(sections_tbl.val == -1)
        view_header();
    else
    if(hdr.magic[0] == 'T') {
        section = &sec[sections_tbl.val];
        sprintf(tmp, "Chunk index %3u type %02X%02X%02X%02X offset %08X size %u", sections_tbl.val,
            sbuf[section->offs + 0], sbuf[section->offs + 1], sbuf[section->offs + 2], sbuf[section->offs + 3],
            section->offs, section->size);
        ui_text(screen, 220, 4, tmp);
        sec_type = -1;
        if(!memcmp(sbuf + section->offs, "DENY", 4)) view_deny(section); else
        if(!memcmp(sbuf + section->offs, "GLBL", 4)) view_glbl(section); else
        if(!memcmp(sbuf + section->offs, "USER", 4)) view_user(section); else
        if(!memcmp(sbuf + section->offs, "NPC", 4))  view_npc(section); else
        if(!memcmp(sbuf + section->offs, "QSTS", 4)) view_qsts(section); else
        if(!memcmp(sbuf + section->offs, "MAP", 4))  { view_map(section); sec_type = TNG_SECTION_MAPS; } else
        {
            ssfn_dst.fg = theme[THEME_SELFG];
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, 220, 32, " INVALID TYPE ");
            ssfn_dst.bg = 0;
        }
    } else
    if(sections_tbl.val >= 0 && sections_tbl.val < sections_tbl.num) {
        section = &sec[sections_tbl.val];
        sec_type = TNG_SECTION_TYPE(section);
        sprintf(tmp, "Section index %3u type %02X offset %08X size %u", sections_tbl.val, sec_type, section->offs,
            TNG_SECTION_SIZE(section));
        ui_text(screen, 220, 4, tmp);
        if(sec_type >= (int)(sizeof(section_types)/sizeof(section_types[0])) || section_types[sec_type][0] == '#') {
            ssfn_dst.fg = theme[THEME_SELFG];
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, 220, 32, " INVALID TYPE ");
            ssfn_dst.bg = 0;
        } else
        if(sec_type && (section->offs < sec[0].offs + TNG_SECTION_SIZE(&sec[0]) || section->offs + TNG_SECTION_SIZE(section) > sec_unc)) {
            ssfn_dst.fg = theme[THEME_SELFG];
            ssfn_dst.bg = theme[THEME_ERR];
            ui_text(screen, 220, 32, " OUT OF BOUNDS ");
            ssfn_dst.bg = 0;
        } else
            switch(sec_type) {
                case TNG_SECTION_STRINGS: sec_hastab = 1; view_strings(section); break;
                case TNG_SECTION_TRANSLATION: sec_hastab = 2; view_translations(section); break;
                case TNG_SECTION_FONTS: sec_hastab = 1; view_fonts(section); break;
                case TNG_SECTION_MUSIC: case TNG_SECTION_SOUNDS: case TNG_SECTION_MOVIES: sec_hastab = 1; view_media(section); break;
                case TNG_SECTION_SPRITEATLAS: sec_hastab = 1; view_atlas(section); break;
                case TNG_SECTION_SPRITE_UI: case TNG_SECTION_SPRITE_TILE: case TNG_SECTION_SPRITE_CHARACTER: case TNG_SECTION_SPRITE_PORTRAIT:
                    sec_hastab = 3; view_sprites(section); break;
                case TNG_SECTION_SPRITE_BACKGROUND: sec_hastab = 1; view_backs(section); break;
                case TNG_SECTION_UI: sec_hastab = 2; view_ui(section); break;
                case TNG_SECTION_MAINMENU: view_mainmenu(section); break;
                case TNG_SECTION_HUD: sec_hastab = 1; view_hud(section); break;
                case TNG_SECTION_EQUIPSLOTS: sec_hastab = 1; view_equip(section); break;
                case TNG_SECTION_ALERTS: sec_hastab = 1; view_alerts(section); break;
                case TNG_SECTION_CREDITS: sec_hastab = 1; view_credits(section); break;
                case TNG_SECTION_CHOOSERS: sec_hastab = 2; view_choosers(section); break;
                case TNG_SECTION_CUTSCENES: sec_hastab = 3; view_cutscenes(section); break;

                case TNG_SECTION_STARTUP: sec_hastab = 2; view_startup(section); break;
                case TNG_SECTION_ATTRS: sec_hastab = 2; view_attrs(section); break;
                case TNG_SECTION_ACTIONS: sec_hastab = 2; view_actions(section); break;
                case TNG_SECTION_CHARS: sec_hastab = 3; view_chars(section); break;
                case TNG_SECTION_NPCS: sec_hastab = 3; view_npcs(section); break;
                case TNG_SECTION_SPAWNERS: sec_hastab = 1; view_spawners(section); break;
                case TNG_SECTION_OBJECTS: sec_hastab = 3; view_objects(section); break;
                case TNG_SECTION_DIALOGS: sec_hastab = 1; view_dialogs(section); break;
                case TNG_SECTION_CRAFTS: sec_hastab = 1; view_crafts(section); break;
                case TNG_SECTION_TILES: sec_hastab = 1; view_tiles(section); break;
                case TNG_SECTION_MAPS: sec_hastab = 2; view_maps(section); break;
                case TNG_SECTION_QUESTS: sec_hastab = 2; view_quests(section); break;
            }
    }

    SDL_UnlockTexture(screen);
    ui_clip.x = ui_clip.y = 0; ui_clip.w = scr_w; ui_clip.h = scr_h;
}

/**
 * Exit player
 */
void main_quit()
{
    int i;

    section_free();
    assets_free();
    if(atlas) {
        for(i = 0; i < numatlas; i++)
            if(atlas[i].txt) SDL_DestroyTexture(atlas[i].txt);
        free(atlas); atlas = NULL; numatlas = 0;
    }
    if(transl) { free(transl); transl = NULL; }
    if(transls) { free(transls); transls = NULL; }
    if(ssfn_src) { free(ssfn_src); ssfn_src = NULL; }
    if(sbuf) { free(sbuf); sbuf = NULL; }
    if(prvw) { free(prvw); prvw = NULL; }
    if(preview) { SDL_DestroyTexture(preview); preview = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    if(renderer) { SDL_DestroyRenderer(renderer); renderer = NULL; }
    if(window) { SDL_DestroyWindow(window); window = NULL; }
    if(sec) { free(sec); sec = NULL; }
    if(strings) { free(strings); strings = NULL; numstrings = 0; }
    if(f) { fclose(f); f = NULL; }
    Mix_Quit();
    SDL_CloseAudio();
    SDL_Quit();
}

/**
 * Play media
 */
void media_play(int x, int y, tng_asset_desc_t *asset)
{
    uint8_t *buf;
    int o, w, i, j;
    Mix_Chunk *snd;
    SDL_Texture *texture = NULL;
    SDL_Rect dst, rect = { 0 };
    Uint32 end = 0, now, *p;

    SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
    o = (y + 9) * scr_p / 4 + 221; w = x - 230;
    ui_button(screen, x, y, 80, "Play", 1);
    SDL_UnlockTexture(screen);
    dst.x = 0; dst.y = 0; dst.w = scr_w; dst.h = scr_h;
    SDL_RenderCopy(renderer, screen, &dst, &dst);
    SDL_RenderPresent(renderer);

    j = hdr.freq < (int)(sizeof(freqs)/sizeof(freqs[0])) ? freqs[hdr.freq] : 44100;
    if(Mix_OpenAudio(j, AUDIO_S16LSB, 2, 4096) < 0) {
        printf("tngd: opening audio %uHz failed\r\n", j);
        return;
    }

    end = theora_getduration(asset) * 10;
    if(end < 1000) {
        buf = (uint8_t*)main_alloc(asset->size);
        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
        if(world_readtng(0, 0, buf, asset->size)) {
            snd = Mix_LoadWAV_RW(SDL_RWFromConstMem(buf, asset->size), 0);
            if(snd) {
                Mix_VolumeChunk(snd, MIX_MAX_VOLUME);
                Mix_PlayChannel(0, snd, 0);
                theora_ctx.baseticks = SDL_GetTicks(); now = 0;
                end += 100;
                while (now < end) {
                    now = SDL_GetTicks() - theora_ctx.baseticks;
                    SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
                    p = scr_data + o;
                    for(j = 0; j < 4; j++, p += scr_p / 4)
                        for(i = 0; i < w; i++)
                            p[i] = theme[!end || i <= (int)(w * now / end) ? THEME_SELFG : THEME_BG];
                    SDL_UnlockTexture(screen);
                    SDL_RenderCopy(renderer, screen, &dst, &dst);
                    SDL_RenderPresent(renderer);
                    SDL_Delay(end / 10);
                }
            }
        }
        Mix_HaltChannel(-1);
        Mix_CloseAudio();
        free(buf);
        return;
    }
    wasset.offs = asset->offs + sizeof(tng_hdr_t) + 4 + sec_size;
    wasset.size = asset->size;
    theora_start(&theora_ctx, &wasset, NULL);

    if(theora_ctx.hasVideo) {
        texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, theora_ctx.w, theora_ctx.h);
        if(!texture) goto cleanup;
        SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
        ui_box(screen, 220, 32, scr_w - 230, scr_h - 32 - 24 - 5, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER]);
        SDL_UnlockTexture(screen);
        ui_fit(scr_w - 232, scr_h - 32 - 24 - 7, theora_ctx.w, theora_ctx.h, &rect.w, &rect.h);
        rect.x = 221 + (scr_w - 232 - rect.w) / 2; rect.y = 33 + (scr_h - 32 - 24 - 7 - rect.h) / 2;
    }
    if(theora_ctx.hasAudio) {
        Mix_ChannelFinished(theora_callback);
        theora_callback(0);
    }
    theora_ctx.baseticks = SDL_GetTicks();
    while (theora_playing(&theora_ctx)) {
        main_tick = SDL_GetTicks();
        now = main_tick - theora_ctx.baseticks;
        theora_video(&theora_ctx, texture);
        while (SDL_PollEvent(&event)) {
            if(event.type == SDL_QUIT || (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE)) {
                SDL_PushEvent(&event); goto cleanup;
            }
            if(event.type == SDL_KEYDOWN || event.type == SDL_MOUSEBUTTONDOWN) goto cleanup;
            if(event.type == SDL_WINDOWEVENT && (event.window.event == SDL_WINDOWEVENT_RESIZED ||
              event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) {
                if(event.window.data1 < SCR_MINW || event.window.data2 < SCR_MINH) {
                    if(event.window.data1 < SCR_MINW) event.window.data1 = SCR_MINW;
                    if(event.window.data2 < SCR_MINH) event.window.data2 = SCR_MINH;
                    SDL_SetWindowSize(window, event.window.data1, event.window.data2);
                }
                ui_resize(event.window.data1, event.window.data2);
                ui_redraw();
                o = (sec_playy + 9) * scr_p / 4 + 221; w = sec_playx - 230;
                dst.x = 0; dst.y = 0; dst.w = scr_w; dst.h = scr_h;
                SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
                ui_button(screen, sec_playx, sec_playy, 80, "Play", 1);
                if(theora_ctx.hasVideo) {
                    ui_box(screen, 220, 32, scr_w - 230, scr_h - 32 - 24 - 5, theme[THEME_DARKER], theme[THEME_BG], theme[THEME_LIGHTER]);
                    ui_fit(scr_w - 232, scr_h - 32 - 24 - 7, theora_ctx.w, theora_ctx.h, &rect.w, &rect.h);
                    rect.x = 221 + (scr_w - 232 - rect.w) / 2; rect.y = 33 + (scr_h - 32 - 24 - 7 - rect.h) / 2;
                }
                SDL_UnlockTexture(screen);
            }
        }
        SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
        p = scr_data + o;
        for(j = 0; j < 4; j++, p += scr_p / 4)
            for(i = 0; i < w; i++)
                p[i] = theme[!end || i <= (int)(w * now / end) ? THEME_SELFG : THEME_BG];
        SDL_UnlockTexture(screen);
        SDL_RenderCopy(renderer, screen, &dst, &dst);
        if(texture)
            SDL_RenderCopy(renderer, texture, NULL, &rect);
        SDL_RenderPresent(renderer);
    }
cleanup:
    theora_ctx.stop = 1; /* do not allow any theora_audio calls after this point */
    Mix_ChannelFinished(NULL);
    Mix_HaltChannel(-1);
    SDL_Delay(10);
    theora_stop(&theora_ctx);
    if(texture) SDL_DestroyTexture(texture);
    Mix_CloseAudio();
}

/**
 * Main event handler loop
 */
void main_event()
{
    unsigned char tmp[18] = { 0 };
    FILE *f;
    int x, y, redraw = 1;
    SDL_Rect rect, src;
    uint8_t *data;

    while(1) {
        SDL_WaitEvent(&event);
        switch(event.type) {
            case SDL_QUIT:
                return;
            case SDL_WINDOWEVENT:
                switch(event.window.event) {
                    case SDL_WINDOWEVENT_CLOSE:
                        return;
                    case SDL_WINDOWEVENT_RESIZED:
                    case SDL_WINDOWEVENT_SIZE_CHANGED:
                        if(event.window.data1 < SCR_MINW || event.window.data2 < SCR_MINH) {
                            if(event.window.data1 < SCR_MINW) event.window.data1 = SCR_MINW;
                            if(event.window.data2 < SCR_MINH) event.window.data2 = SCR_MINH;
                            SDL_SetWindowSize(window, event.window.data1, event.window.data2);
                        }
                        ui_resize(event.window.data1, event.window.data2);
                        redraw = 1;
                        goto refresh;
                    break;
                    case SDL_WINDOWEVENT_EXPOSED:
                        redraw = 1;
                        goto refresh;
                    break;
                }
                break;
            case SDL_USEREVENT: goto refresh; break;
            case SDL_KEYUP:
                switch(event.key.keysym.sym) {
                    case SDLK_ESCAPE: case SDLK_q: return;
                }
            break;
            case SDL_KEYDOWN:
                switch(event.key.keysym.sym) {
                    case SDLK_UP:
                        switch(sec_tab) {
                            case 0:
                                if(sections_tbl.val > -1) { section_free(); sections_tbl.val--; redraw = 1; goto refresh; }
                            break;
                            case 1: if(assets_tbl.val > 0) { assets_free(); assets_tbl.val--; redraw = 1; goto refresh; } break;
                            case 2: if(dirs_tbl.val > 0) { frames_tbl.val = 0; dirs_tbl.val--; zoom = redraw = 1; goto refresh; } break;
                            case 3: if(frames_tbl.val > 0) { events_tbl.val = 0; frames_tbl.val--; zoom = redraw = 1; goto refresh; } break;
                            case 4: if(events_tbl.val > 0) { events_tbl.val--; redraw = 1; goto refresh; } break;
                        }
                    break;
                    case SDLK_DOWN:
                        switch(sec_tab) {
                            case 0:
                                if(sections_tbl.val < sections_tbl.num - 1) { section_free(); sections_tbl.val++; redraw = 1; goto refresh; }
                            break;
                            case 1:
                                if(assets_tbl.val < assets_tbl.num - 1) { assets_free(); assets_tbl.val++; redraw = 1; goto refresh; } break;
                            case 2: if(dirs_tbl.val < dirs_tbl.num - 1) { frames_tbl.val = 0; dirs_tbl.val++; redraw = 1; goto refresh; } break;
                            case 3: if(frames_tbl.val < frames_tbl.num - 1) { events_tbl.val = 0; frames_tbl.val++; redraw = 1; goto refresh; } break;
                            case 4: if(events_tbl.val < events_tbl.num - 1) { events_tbl.val++; redraw = 1; goto refresh; } break;
                        }
                    break;
                    case SDLK_PAGEUP:
                        if(tabc > 0) tabc--;
                        frames_tbl.val = 0;
                        redraw = 1; goto refresh;
                    break;
                    case SDLK_PAGEDOWN:
                        if(tabc + 1 < tabn) tabc++;
                        frames_tbl.val = 0;
                        redraw = 1; goto refresh;
                    break;
                    case SDLK_LEFT:
                        if(sec_tab > 0) {
                            sec_tab--;
                            if(sec_tab == 1 && !assets_tbl.num) sec_tab--;
                            redraw = 1; goto refresh;
                        }
                    break;
                    case SDLK_RIGHT:
                        if(sec_tab < sec_hastab) {
                            sec_tab++;
                            if(sec_tab == 1 && !assets_tbl.num) sec_tab++;
                            redraw = 1; goto refresh;
                        }
                    break;
                    case SDLK_TAB:
                        if(sec_tab < sec_hastab) sec_tab++; else sec_tab = 0;
                        if(sec_tab == 1 && !assets_tbl.num) sec_tab = sec_hastab >= 2 ? 2 : 0;
                        redraw = 1; goto refresh;
                    break;
                    case SDLK_RETURN:
                        if((sec_type == TNG_SECTION_TRANSLATION || (sec_type >= TNG_SECTION_MUSIC && sec_type <= TNG_SECTION_MOVIES)) &&
                          sec_tab == 1 && assets_tbl.num > 0) {
                            media_play(sec_playx, sec_playy, (tng_asset_desc_t*)assets_tbl.data + assets_tbl.val);
                            redraw = 1; goto refresh;
                        }
                    break;
                    case SDLK_MINUS:
                        if(zoom > 1) zoom >>= 1;
                        redraw = 1; goto refresh;
                    break;
                    case SDLK_PLUS: case SDLK_EQUALS:
                        if(zoom < 32) zoom <<= 1;
                        redraw = 1; goto refresh;
                    break;
                    case SDLK_s:
                        if(sec_type == TNG_SECTION_SPRITEATLAS && assets_tbl.val < numatlas && atlas[assets_tbl.val].txt) {
                            sprintf((char*)tmp, "atls%03x.tga", assets_tbl.val);
                            f = fopen((char*)tmp, "wb");
                            if(f) {
                                printf("tngd: saving '%s'\r\n", tmp);
                                SDL_LockTexture(atlas[assets_tbl.val].txt, NULL, (void**)&data, &x);
                                memset(tmp, 0, 18); tmp[2] = 2;
                                *((uint16_t*)(tmp + 10)) = *((uint16_t*)(tmp + 12)) = atlas[assets_tbl.val].w;
                                *((uint16_t*)(tmp + 14)) = atlas[assets_tbl.val].h; tmp[16] = 32; tmp[17] = 40;
                                fwrite(tmp, 1, 18, f);
                                x *= atlas[assets_tbl.val].h;
                                for(y = 0; y < x; y += 4) {
                                    tmp[0] = data[y+2]; tmp[1] = data[y+1]; tmp[2] = data[y+0]; tmp[3] = data[y+3];
                                    fwrite(tmp, 1, 4, f);
                                }
                                fclose(f);
                                SDL_UnlockTexture(atlas[assets_tbl.val].txt);
                            }
                        }
                    break;
                }
            break;
            case SDL_MOUSEBUTTONDOWN:
                origx = origy = -1;
                if(event.button.y > 25 && event.button.y < scr_h - 8) {
                    if(event.button.x > 10 && event.button.x < 200) {
                        section_free();
                        sections_tbl.val = sections_tbl.scr + (event.button.y - 25) / sections_tbl.row;
                        if(sections_tbl.val >= sections_tbl.num - 1) sections_tbl.val = sections_tbl.num - 1;
                        if(sections_tbl.val < -1) sections_tbl.val = -1;
                        redraw = 1; goto refresh;
                    } else
                    if(tabn > 0 && event.button.x > tabx && event.button.x < tabx + tabn * 100 &&
                      event.button.y > taby && event.button.y < taby + 20) {
                        tabc = (event.button.x - tabx) / 100;
                        frames_tbl.val = 0;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab && event.button.x > assets_tbl.x && event.button.x < assets_tbl.x + assets_tbl.w &&
                      event.button.y > assets_tbl.y && event.button.y < assets_tbl.y + assets_tbl.h) {
                        sec_tab = 1; assets_free();
                        assets_tbl.val = assets_tbl.scr + (event.button.y - 21 - assets_tbl.y) / assets_tbl.row;
                        if(assets_tbl.val >= assets_tbl.num - 1) assets_tbl.val = assets_tbl.num - 1;
                        if(assets_tbl.val < 0) assets_tbl.val = -1;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab > 1 && event.button.x > dirs_tbl.x && event.button.x < dirs_tbl.x + dirs_tbl.w &&
                      event.button.y > dirs_tbl.y && event.button.y < dirs_tbl.y + dirs_tbl.h) {
                        sec_tab = 2; frames_tbl.val = 0;
                        dirs_tbl.val = dirs_tbl.scr + (event.button.y - (dirs_tbl.hdr ? 21 : 0) - dirs_tbl.y) / dirs_tbl.row;
                        if(dirs_tbl.val >= dirs_tbl.num - 1) dirs_tbl.val = dirs_tbl.num - 1;
                        if(dirs_tbl.val < 0) dirs_tbl.val = -1;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab > 2 && event.button.x > frames_tbl.x && event.button.x < frames_tbl.x + frames_tbl.w &&
                      event.button.y > frames_tbl.y && event.button.y < frames_tbl.y + frames_tbl.h) {
                        sec_tab = 3;
                        frames_tbl.val = frames_tbl.scr + (event.button.y - 21 - frames_tbl.y) / frames_tbl.row;
                        if(frames_tbl.val >= frames_tbl.num - 1) frames_tbl.val = frames_tbl.num - 1;
                        if(frames_tbl.val < 0) frames_tbl.val = -1;
                        redraw = 1; goto refresh;
                    }
                    if(sec_hastab > 3 && event.button.x > events_tbl.x && event.button.x < events_tbl.x + events_tbl.w &&
                      event.button.y > events_tbl.y && event.button.y < events_tbl.y + events_tbl.h) {
                        sec_tab = 4;
                        events_tbl.val = events_tbl.scr + (event.button.y - 21 - events_tbl.y) / events_tbl.row;
                        if(events_tbl.val >= events_tbl.num - 1) events_tbl.val = events_tbl.num - 1;
                        if(events_tbl.val < 0) events_tbl.val = -1;
                        redraw = 1; goto refresh;
                    }
                    if((sec_type == TNG_SECTION_TRANSLATION || (sec_type >= TNG_SECTION_MUSIC && sec_type <= TNG_SECTION_MOVIES)) &&
                      sec_tab == 1 && assets_tbl.num > 0 &&
                      event.button.y > sec_playy && event.button.y < sec_playy + 20 &&
                      event.button.x > sec_playx && event.button.x < sec_playx + 80) {
                        media_play(sec_playx, sec_playy, (tng_asset_desc_t*)assets_tbl.data + assets_tbl.val);
                        redraw = 1; goto refresh;
                    }
                    if(sec_type == TNG_SECTION_MAPS && sec_hastab < 3 && event.button.x > 220 &&
                      event.button.y > (hdr.magic[0] == 'T' ? 74 : 246)) {
                        origx = event.button.x + tilesx; origy = event.button.y + tilesy;
                    }
                }
            break;
            case SDL_MOUSEBUTTONUP:
                origx = origy = -1;
            break;
            case SDL_MOUSEMOTION:
                if(origx != -1 && origy != -1) {
                    tilesx = origx - event.motion.x; tilesy = origy - event.motion.y;
                    redraw = 1;
                }
            break;
            case SDL_MOUSEWHEEL:
                SDL_GetMouseState(&x, &y);
                if(y > 32 && y < scr_h - 8) {
                    if(x > 10 && x < 200) {
                        section_free();
                        if(event.wheel.y > 0 && sections_tbl.val > -1) sections_tbl.val--;
                        if(event.wheel.y < 0 && sections_tbl.val < sections_tbl.num - 1) sections_tbl.val++;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab && x > assets_tbl.x && x < assets_tbl.x + assets_tbl.w &&
                      y > assets_tbl.y && y < assets_tbl.y + assets_tbl.h) {
                        sec_tab = 1; assets_free();
                        if(event.wheel.y > 0 && assets_tbl.val > 0) assets_tbl.val--;
                        if(event.wheel.y < 0 && assets_tbl.val < assets_tbl.num - 1) assets_tbl.val++;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab > 1 && x > dirs_tbl.x && x < dirs_tbl.x + dirs_tbl.w &&
                      y > dirs_tbl.y && y < dirs_tbl.y + dirs_tbl.h) {
                        sec_tab = 2; frames_tbl.val = 0;
                        if(event.wheel.y > 0 && dirs_tbl.val > 0) dirs_tbl.val--;
                        if(event.wheel.y < 0 && dirs_tbl.val < dirs_tbl.num - 1) dirs_tbl.val++;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab > 2 && x > frames_tbl.x && x < frames_tbl.x + frames_tbl.w &&
                      y > frames_tbl.y && y < frames_tbl.y + frames_tbl.h) {
                        sec_tab = 3;
                        if(event.wheel.y > 0 && frames_tbl.val > 0) frames_tbl.val--;
                        if(event.wheel.y < 0 && frames_tbl.val < frames_tbl.num - 1) frames_tbl.val++;
                        redraw = 1; goto refresh;
                    } else
                    if(sec_hastab > 3 && x > events_tbl.x && x < events_tbl.x + events_tbl.w &&
                      y > events_tbl.y && y < events_tbl.y + events_tbl.h) {
                        sec_tab = 4;
                        if(event.wheel.y > 0 && events_tbl.val > 0) events_tbl.val--;
                        if(event.wheel.y < 0 && events_tbl.val < events_tbl.num - 1) events_tbl.val++;
                        redraw = 1; goto refresh;
                    }
                }
            break;
        }
refresh:
        /* redraw screen if necessary */
        if(redraw) {
            ui_redraw();
            redraw = 0;
        }
        /* render screen to window */
        if(screen) {
            rect.x = 0; rect.y = 0; rect.w = scr_w; rect.h = scr_h;
            SDL_RenderCopy(renderer, screen, &rect, &rect);
        }
        if(preview && prvw && sections_tbl.val == -1) {
            rect.w = rect.h = (prvw[31] << 8) | prvw[30];
            rect.x = 221; rect.y = 151;
            SDL_RenderCopy(renderer, preview, NULL, &rect);
        }
        if(sec_type == TNG_SECTION_SPRITEATLAS && assets_tbl.val < numatlas && atlas[assets_tbl.val].txt) {
            rect.x = 221; rect.y = 128 + 32 + 5;
            ui_fit(scr_w - rect.x - 11, scr_h - rect.y - 6, atlas[assets_tbl.val].w, atlas[assets_tbl.val].h, &rect.w, &rect.h);
            SDL_RenderCopy(renderer, atlas[assets_tbl.val].txt, NULL, &rect);
        }
        if(sec_type == TNG_SECTION_SPRITE_BACKGROUND && back && back_w > 0 && back_h > 0) {
            rect.x = 221; rect.y = 128 + 52 + 5;
            ui_fit(scr_w - rect.x - 11, scr_h - rect.y - 6, back_w, back_h, &rect.w, &rect.h);
            SDL_RenderCopy(renderer, back, NULL, &rect);
        }
        if(currframe && (currframe->atlasid & 0xfff) < numatlas && atlas[currframe->atlasid & 0xfff].txt) {
            rect.x = 447 + zoom * currframe->l; rect.y = 317 + zoom * currframe->t;
            rect.w = zoom * currframe->w; rect.h = zoom * currframe->h;
            src.w = currframe->w; src.h = currframe->h;
            src.x = currframe->x; src.y = currframe->y;
            switch(currframe->atlasid >> 12) {
                case 0: SDL_RenderCopy(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect); break;
                case 1: SDL_RenderCopyEx(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect, 0, NULL, SDL_FLIP_VERTICAL); break;
                case 2: SDL_RenderCopyEx(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect, 0, NULL, SDL_FLIP_HORIZONTAL); break;
                case 3: SDL_RenderCopyEx(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect, 90, NULL, 0); break;
                case 4: SDL_RenderCopyEx(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect, 180, NULL, 0); break;
                case 5: SDL_RenderCopyEx(renderer, atlas[currframe->atlasid & 0xfff].txt, &src, &rect, 270, NULL, 0); break;
            }
        }
        SDL_RenderPresent(renderer);
    }
}

/**
 * The real main procedure
 */
int main(int argc, char **argv)
{
    SDL_Surface *srf;
    stbi__context s;
    stbi__result_info ri;
    tng_asset_desc_t *asset;
    int i, j, k, w, h;
    uint8_t *ptr, *buf, *data;
    uint16_t *pix;
    uint32_t crc, *pix2;

#ifdef __WIN32__
    FILE *f;
    /* restore stdout to console */
    AttachConsole(ATTACH_PARENT_PROCESS);
    f = _fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), 0x4000/*_O_TEXT*/), "w");
    if(f) *stdout = *f;
#endif
    lang = &dict[0][1];

    /* initialize screen and other SDL stuff */
    if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER|SDL_INIT_EVENTS)) main_error(ERR_DISPLAY);
    scr_w = SCR_MINW; scr_h = SCR_MINH;

    window = SDL_CreateWindow("TirNanoG Dumper", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, scr_w, scr_h,
        SDL_WINDOW_RESIZABLE);
    if(!window) {
        SDL_Quit();
        main_error(ERR_DISPLAY);
        return 1;
    }
    SDL_SetWindowMinimumSize(window, SCR_MINW, SCR_MINH);
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if(!renderer) {
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
        if(!renderer) {
            SDL_DestroyWindow(window); window = NULL;
            SDL_Quit();
            main_error(ERR_DISPLAY);
            return 1;
        }
    }
    ui_resize(scr_w, scr_h);
    memset(&s, 0, sizeof(s));
    memset(&ri, 0, sizeof(ri));
    s.img_buffer = s.img_buffer_original = binary_tngd_png;
    s.img_buffer_end = s.img_buffer_original_end = binary_tngd_png + sizeof(binary_tngd_png);
    ri.bits_per_channel = 8;
    ptr = (uint8_t*)stbi__png_load(&s, &w, &h, &k, 4, &ri);
    if(ptr) {
        srf = SDL_CreateRGBSurfaceFrom((Uint32 *)ptr, w, h, 32, w*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
        if(srf) {
            SDL_SetWindowIcon(window, srf);
            SDL_FreeSurface(srf);
        }
        free(ptr);
    }

    /* uncompress ui font */
    ptr = binary_default_sfn + 3;
    i = *ptr++; ptr += 6; if(i & 4) { w = *ptr++; w += (*ptr++ << 8); ptr += w; } if(i & 8) { while(*ptr++ != 0); }
    if(i & 16) { while(*ptr++ != 0); } j = sizeof(binary_default_sfn) - (size_t)(ptr - binary_default_sfn);
    w = 0; ssfn_src = (ssfn_font_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)ptr, j, 4096, (int*)&w, 0);
    if(!ssfn_src) main_error(ERR_MEM);
    memset(&ws, 0, sizeof(ws));
    ptr = (uint8_t*)ssfn_src + ssfn_src->characters_offs;
    for(ptr = (uint8_t*)ssfn_src + ssfn_src->characters_offs, w = 0; w < 0x110000; w++) {
        if(ptr[0] == 0xFF) { w += 65535; ptr++; }
        else if((ptr[0] & 0xC0) == 0xC0) { h = (((ptr[0] & 0x3F) << 8) | ptr[1]); w += h; ptr += 2; }
        else if((ptr[0] & 0xC0) == 0x80) { h = (ptr[0] & 0x3F); w += h; ptr++; }
        else {
            ws[w] = ptr[2];
            ptr += 6 + ptr[1] * (ptr[0] & 0x40 ? 6 : 5);
        }
    }

    if(screen) {
        /* clear screen */
        SDL_LockTexture(screen, NULL, (void**)&scr_data, &scr_p);
        for(i = 0; i < scr_p/4 * scr_h; i++) scr_data[i] = theme[THEME_BG];
        ui_text2(screen, (scr_w - 28*8) / 2, (scr_h - 16) / 2, "Loading file, please wait...", theme[THEME_DARKER],
            theme[THEME_LIGHTER]);
        SDL_UnlockTexture(screen);
        SDL_RenderCopy(renderer, screen, NULL, NULL);
        SDL_RenderPresent(renderer);
    }

    /* flush events */
    while(SDL_PollEvent(&event)) {}

    /* parse command line */
    if(argc < 2 || !argv[1] || !memcmp(argv[1], "--help", 6)) {
        printf("\r\nTirNanoG Dumper v%s (build %u) by bzt Copyright (C) 2021 - 2022 GPLv3\r\n\r\n", tngpver, BUILD);
        printf("  tngd <game.tng file>\r\n\r\n");
        lang[ERR_NOGAMES] = "No game file was given as argument";
        main_error(ERR_NOGAMES);
    }
    /* just skip any flags if given */
    for(i = 1; i < argc && argv[i][0] == '-'; i++)
        if(argv[i][1] == 'L') i++;

    /* open the game file */
#ifdef __WIN32__
    MultiByteToWideChar(CP_UTF8, 0, argv[i], -1, szFull, PATH_MAX);
    f = _wfopen(szFull, L"rb");
#else
    f = fopen(argv[i], "rb");
#endif
    if(!f || fread(&hdr, 1, sizeof(hdr), f) != sizeof(hdr)) {
bderr:  printf("tngd: file error or bad magic '%s'\r\n", argv[i]);
        main_error(ERR_TNG);
    }
    if(!memcmp(&hdr.magic, WORLD_STATEMAGIC, 16)) {
        plen = 0;
        if(!memcmp((uint8_t*)&hdr + 32, "PRVW", 4)) {
            plen = *((int*)((uint8_t*)&hdr + 36));
            if(plen < 32) goto bderr;
            prvw = (uint8_t*)main_alloc(plen);
            memcpy(prvw, (uint8_t*)&hdr + 32, 32);
            if(plen > 32) {
                if(fread(prvw + 32, 1, plen - 32, f) != (size_t)plen - 32) goto bderr;
                pix = (uint16_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)prvw + 32, plen - 32, 4096, &i, 1);
                if(pix) {
                    k = (prvw[31] << 8) | prvw[30];
                    if(k >= 16 && k <= 1024 && i == k * k * 2) {
                        preview = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, k, k);
                        if(preview) {
                            SDL_SetTextureBlendMode(preview, SDL_BLENDMODE_BLEND);
                            SDL_LockTexture(preview, NULL, (void**)&pix2, &w);
                            for(j = 0; j < k * k; j++)
                                pix2[j] = 0xFF000000 | (((pix[j] >> 10) & 0x1F) << 19) |
                                    (((pix[j] >> 5) & 0x1F) << 11) | ((pix[j] & 0x1F) << 3);
                            SDL_UnlockTexture(preview);
                        }
                    }
                    free(pix);
                }
           }
        }
        fseek(f, 0L, SEEK_END);
        sec_size = (int)ftell(f);
        sec_size -= 32 + plen;
        fseek(f, 32 + plen, SEEK_SET);
        sec = NULL; sbuf = NULL; sections_tbl.num = 0; sec_unc = 0;
        if(sec_size > 0) {
            ptr = (uint8_t*)main_alloc(sec_size);
            if(fread(ptr, 1, sec_size, f) != (size_t)sec_size) {
                printf("tngd: error reading game state\r\n");
                main_error(ERR_TNG);
            }
            crc = crc32(0, ptr, sec_size - 4);
            if(memcmp(ptr + sec_size - 4, &crc, 4)) goto ucerr;
            sbuf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)ptr, sec_size - 4, 4096, (int*)&sec_unc, 1);
            free(ptr);
            if(!sbuf || sec_unc < 1) goto ucerr;
            for(ptr = sbuf; ptr < sbuf + sec_unc; ) {
                crc = (ptr[7] << 24) | (ptr[6] << 16) | (ptr[5] << 8) | ptr[4];
                sec = (tng_section_t*)main_realloc(sec, (sections_tbl.num + 1) * sizeof(tng_section_t));
                sec[sections_tbl.num].offs = ptr - sbuf;
                sec[sections_tbl.num].size = crc;
                sections_tbl.num++;
                ptr += crc;
            }
        }
        sections_tbl.data = sec;
        sections_tbl.scr = sections_tbl.val = -1;
    } else {
        if((memcmp(&hdr.magic, TNG_MAGIC, 16) && memcmp(&hdr.magic[4], "TNGS", 4)) ||
          (hdr.type & 0x0F) > TNG_TYPE_3D || hdr.enc) goto bderr;
        world.tng = &world_tng;
        world.numtng = 1;
        world_tng.f[0] = world_tng.f[1] = world_tng.f[2] = f;
        world_tng.offs[0] = sizeof(hdr);
        tilew = hdr.tilew | (((hdr.type >> 4) & 3) << 8);
        tileh = hdr.tileh | (((hdr.type >> 6) & 3) << 8);
        if(hdr.magic[0] == '#') {
            crc = hdr.crc; hdr.crc = 0;
            chkcrc = crc32(0, (const uint8_t*)&hdr, sizeof(tng_hdr_t));
            ptr = (unsigned char*)main_alloc(65536);
            while(!feof(f)) {
                j = (int)fread(ptr, 1, 65536, f);
                chkcrc = crc32(chkcrc, ptr, j);
            }
            free(ptr);
            world_seektng(0, 0, sizeof(tng_hdr_t));
            hdr.crc = crc;
            if(crc != chkcrc)
                printf("tngd: bad checksum\r\n");
        }
        if(fread(&sec_size, 1, sizeof(int32_t), f) != sizeof(int32_t) || (sec_size & 0x80000000) || sec_size < 8) {
            printf("tngd: compressed section table too big\r\n");
            main_error(ERR_TNG);
        }
        ptr = (uint8_t*)main_alloc(sec_size);
        if(fread(ptr, 1, sec_size, f) != (size_t)sec_size) {
            printf("tngd: error reading section table\r\n");
            main_error(ERR_TNG);
        }
        crc = crc32(0, ptr, sec_size - 4);
        if(memcmp(ptr + sec_size - 4, &crc, 4)) goto ucerr;
        sec_unc = 0;
        buf = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)ptr, sec_size - 4, 4096, (int*)&sec_unc, 1);
        free(ptr);
        if(!buf || sec_unc < 1) {
ucerr:      printf("tngd: uncompression error\r\n");
            main_error(ERR_TNG);
        }
        sec = (tng_section_t*)buf;
        if(TNG_SECTION_SIZE(&sec[0]) < 1 || sec[0].offs + TNG_SECTION_SIZE(&sec[0]) >= sec_unc) {
            printf("tngd: invalid string table offset\r\n");
            main_error(ERR_TNG);
        }
        buf += sec[0].offs; ptr = buf + TNG_SECTION_SIZE(&sec[0]);
        while(buf < ptr) {
            strings = (int*)main_realloc(strings, (numstrings + 1) * sizeof(int));
            strings[numstrings++] = (int)(buf - (uint8_t*)sec - sec[0].offs);
            for(i = 0; buf < ptr && *buf; buf++, i++);
            if(buf < ptr && !*buf) buf++;
            if(i > strmax) strmax = i;
        }
        if(numstrings < 2 || strings[1] != 1) {
            printf("tngd: invalid string table\r\n");
            main_error(ERR_TNG);
        }
        sections_tbl.num = sec[0].offs / sizeof(tng_section_t);
        sections_tbl.data = sec;
        sections_tbl.scr = sections_tbl.val = -1;
        for(i = 0; i < sections_tbl.num; i++) {
            for(j = 0; j < sections_tbl.num; j++)
                if(j != i && ((sec[i].offs >= sec[j].offs && sec[i].offs < sec[j].offs + TNG_SECTION_SIZE(&sec[j])) ||
                  (sec[i].offs + TNG_SECTION_SIZE(&sec[i]) > sec[j].offs &&
                  sec[i].offs + TNG_SECTION_SIZE(&sec[i]) <= sec[j].offs + TNG_SECTION_SIZE(&sec[j])))) {
                    printf("tngd: overlaping sections #%u (%X - %X) and #%u (%X - %X)\r\n",
                        i, sec[i].offs, sec[i].offs + TNG_SECTION_SIZE(&sec[i]),
                        j, sec[j].offs, sec[j].offs + TNG_SECTION_SIZE(&sec[j]));
                    main_error(ERR_TNG);
                }
            if(TNG_SECTION_TYPE(&sec[i]) == TNG_SECTION_SPRITEATLAS) {
                if(atlas) {
                    printf("tngd: multiple atlas sections\r\n");
                    main_error(ERR_TNG);
                }
                numatlas = TNG_SECTION_SIZE(&sec[i]) / sizeof(tng_asset_desc_t);
                if(numatlas) {
                    atlas = (atlas_t*)main_alloc(numatlas * sizeof(atlas_t));
                    for(j = 0, asset = (tng_asset_desc_t*)((uint8_t*)sec + sec[i].offs); j < numatlas; j++, asset++) {
                        world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
                        buf = (unsigned char*)main_alloc(asset->size);
                        if(fread(buf, 1, asset->size, f) == (size_t)asset->size) {
                            memset(&s, 0, sizeof(s));
                            memset(&ri, 0, sizeof(ri));
                            s.img_buffer = s.img_buffer_original = buf;
                            s.img_buffer_end = s.img_buffer_original_end = buf + asset->size;
                            ri.bits_per_channel = 8;
                            ptr = (uint8_t*)stbi__png_load(&s, (int*)&atlas[j].w, (int*)&atlas[j].h, &k, 4, &ri);
                            if(ptr) {
                                atlas[j].txt = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING,
                                    atlas[j].w, atlas[j].h);
                                if(atlas[j].txt) {
                                    SDL_SetTextureBlendMode(atlas[j].txt, SDL_BLENDMODE_BLEND);
                                    SDL_LockTexture(atlas[j].txt, NULL, (void**)&data, &k);
                                    memcpy(data, ptr, atlas[j].w * atlas[j].h * 4);
                                    SDL_UnlockTexture(atlas[j].txt);
                                }
                                free(ptr);
                            }
                        }
                        free(buf);
                    }
                }
            }
            if(TNG_SECTION_TYPE(&sec[i]) == TNG_SECTION_TRANSLATION && !transl) {
                asset = (tng_asset_desc_t*)((uint8_t*)sec + sec[i].offs + 2);
                buf = (uint8_t*)main_alloc(asset->size);
                world_seektng(0, 0, asset->offs + sizeof(tng_hdr_t) + 4 + sec_size);
                if(world_readtng(0, 0, buf, asset->size)) {
                    transls = (char*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buf, asset->size, 4096, &k, 1);
                    if(transls && k > 0)
                        for(ptr = (uint8_t*)transls; ptr < (uint8_t*)transls + k;) {
                            transl = (char**)main_realloc(transl, (numtransl + 1) * sizeof(char*));
                            transl[numtransl++] = (char*)ptr;
                            while(ptr < (uint8_t*)transls + k && *ptr) ptr++;
                            if(ptr < (uint8_t*)transls + k && !*ptr) ptr++;
                        }
                }
                free(buf);
            }
            if(TNG_SECTION_TYPE(&sec[i]) >= TNG_SECTION_SPRITE_UI && TNG_SECTION_TYPE(&sec[i]) <= TNG_SECTION_SPRITE_BACKGROUND)
                numspr[TNG_SECTION_TYPE(&sec[i]) - TNG_SECTION_SPRITE_UI] = TNG_SECTION_SIZE(&sec[i]) / sizeof(tng_asset_desc_t);
        }
    }
    Mix_Init(MIX_INIT_OGG);

    event.type = SDL_USEREVENT;
    SDL_PushEvent(&event);
    main_event();

    main_quit();
    return 0;
}
