/*
 * mtsedit/util.c
 *
 * Copyright (C) 2019 bzt (bztsrc@gitlab)
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * @brief Utilities
 *
 */

#include "main.h"
#include "bzip2.h"
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#define TMPSIZE 80

/* cube coordinates */
int x0,x1,x2, _y0,_y1,y2,y3,y4;
int verbose = 0;

int fncmp(const void *a, const void *b) { return strcmp(*((const char**)a), *((const char**)b)); }

/**
 * Read a file into memory from the data directory
 */
unsigned char *readfile(char *file, unsigned int *size)
{
    FILE *f;
    unsigned char *ret = NULL;

    *size = 0;
    if(!path || !fn) return NULL;
    strcpy(fn, file);
    f = fopen(path, "rb");
    if(f) {
        fseek(f, 0L, SEEK_END);
        *size = (unsigned int)ftell(f);
        fseek(f, 0L, SEEK_SET);
        ret = (unsigned char*)malloc(*size + 1);
        if(ret) {
            fread(ret, *size, 1, f);
            ret[*size] = 0;
        } else
            *size = 0;
        fclose(f);
    }
    return ret;
}

/**
 * Read schematics from file
 */
void readschem()
{
    FILE *f;
    bz_stream bz;
    unsigned char *data = NULL, *buff = NULL;
    unsigned int size = 0;
    int x, y, z;

    status = NULL;
    if(isdir(mtsfile)) return;

    bound_valid = 0;
    mts_x = mts_y = mts_z = 0;
    memset(layerprob, 127, sizeof(layerprob));
    memset(nodes, 0, sizeof(nodes));
    for(x = 0; x < numblocks; x++) {
        blocks[x].dobiome = 0;
        blocks[x].numref = 0;
    }
    blocks[0].numref = 256 * 256 * 256;
    savepal = savemapgen = savebiome = 0;
    currlayer = gndlayer = cx = cz = 127;

    status = lang[ERR_LOAD];
    f = fopen(mtsfile, "rb");
    if(f) {
        fseek(f, 0L, SEEK_END);
        size = (unsigned int)ftell(f);
        fseek(f, 0L, SEEK_SET);
        data = (unsigned char*)malloc(size);
        if(!data) error(lang[ERR_MEM]);
        fread(data, 1, size, f);
        fclose(f);
        status = lang[ERR_BADFILE];

        /* if it's gzip compressed, uncompress it first */
        if(data[0] == 0x1f && data[1] == 0x8b) {
            /* skip over gzip header */
            buff = data + 3;
            x = *buff++; buff += 6;
            if(x & 4) { y = *buff++; y += (*buff++ << 8); buff += y; }
            if(x & 8) { while(*buff++ != 0); }
            if(x & 16) { while(*buff++ != 0); }
            if(x & 2) buff += 2;
            size -= (int)(buff - data);
            buff = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)buff, size, 4096, &x, 0);
            if(buff) { free(data); data = buff; buff = NULL; size = (unsigned int)x; }
        } else
        /* if it's bzip2 compressed */
        if(data[0] == 'B' && data[1] == 'Z' && data[2] == 'h') {
            memset(&bz, 0, sizeof(bz_stream));
            if(BZ2_bzDecompressInit(&bz, 0, 0) != BZ_OK) return;
            bz.next_in = (char*)data;
            bz.avail_in = size;
            buff = NULL;
            size = 0;
            do {
                bz.avail_out = 65536;
                size += bz.avail_out;
                bz.next_out -= (uintptr_t)buff;
                buff = realloc(buff, size);
                if(!buff) return;
                bz.next_out += (uintptr_t)buff;
                x = BZ2_bzDecompress(&bz);
            } while(x == BZ_OK && !bz.avail_out);
            if(x != BZ_STREAM_END) return;
            if(buff) { free(data); data = buff; buff = NULL; size -= bz.avail_out; }
            BZ2_bzDecompressEnd(&bz);
        } else buff = data;

        /* if it's an MTS file */
        if(!memcmp(data, "MTSM", 4)) mts_load(data, size); else
        /* Could be a Minecraft NBT */
        if(!memcmp(data + 3, "Schematic", 9) || !memcmp(data + 6, "Schematic", 9)) schem_load(data, size); else
        /* Blueprint PNG */
        if(!memcmp(data, "\x89PNG", 4)) bprint_load(data, size); else
        /* Model 3D */
        if(!memcmp(data, "3DMO", 4)) m3d_load(data, size); else
        /* Tiled TMX XML */
        if(!memcmp(data, "<?xml", 5)) tmx_load(data, size); else
        /* Goxel GOX file */
        if(!memcmp(data, "GOX ", 4)) gox_load(data, size); else
        /* Magicavoxel VOX file */
        if(!memcmp(data, "VOX ", 4)) vox_load(data, size); else
        /* It is a shame, but Qubicle QB files has no real magic */
        if(!memcmp(data, "\001\001\000\000", 4)) qb_load(data, size); else
        /* WorldEdit Lua table */
        if(!memcmp(data + 1, ":return", 7)) we_load(data, size);

        /* make sure that all non-air blocks have their probability set */
        for(y = 0; y < 256; y++)
            for(z = 0; z < 256; z++)
                for(x = 0; x < 256; x++)
                    if(nodes[y][z][x].param0) {
                        if(!(nodes[y][z][x].param1 & 0x7F)) nodes[y][z][x].param1 |= 127;
                    }
        currlayer = gndlayer;
    }
    if(data) free(data);
}

/**
 * Common error handler
 */
void error(char *msg)
{
#ifdef __WIN32__
    MessageBox(NULL, msg, "MTSEdit", MB_ICONEXCLAMATION | MB_OK);
#else
    fprintf(stderr, "mtsedit: %s\r\n", msg);
#endif
    exit(1);
}

/**
 * CSV error
 */
void errorcsv(int line)
{
    char msg[1024];
    sprintf(msg, "blocks.csv(%d): %s", line, lang[ERR_CSV]);
    error(msg);
}

/**
 * IMGCSV error
 */
void errorimgcsv(int line)
{
    char msg[1024];
    sprintf(msg, "blockimgs.csv(%d): %s", line, lang[ERR_IMGCSV]);
}

/**
 * UTF-8 strlen
 */
int mbstrlen(char *s)
{
    int r = 0;
    for(; *s; s++, r++) {
        if((*s & 128) != 0) {
            if(!(*s & 32)) s++; else
            if(!(*s & 16)) s += 2; else
            if(!(*s & 8)) *s += 3;
        }
    }
    return r;
}

/**
 * Returns true if argument is a directory
 */
int isdir(char *path)
{
    DIR *dir;
    dir = opendir(path);
    if(dir) { closedir(dir); return 1; }
    return 0;
}

/**
 * Get contents of a directory
 */
int listdir(char *path, char ***files, int type)
{
    DIR *dir;
    struct dirent *de;
    int i, j, numfiles = 0;
    char tmp[MAXPATHLEN], *fn, c;

    strcpy(tmp, path);
    if(tmp[strlen(tmp)-1] == DIRSEP) tmp[strlen(tmp)-1] = 0;
    dir = opendir(path);
    if(!dir) {
        fn = strrchr(tmp, DIRSEP);
        if(!fn) fn = tmp; else fn++;
        *fn = 0;
        dir = opendir(tmp);
    } else
        fn = tmp + strlen(tmp);
    if(fn[-1] != DIRSEP) { *fn++ = DIRSEP; *fn = 0; }
    if(dir) {
        while((de = readdir(dir))) {
            i = strlen(de->d_name);
            if(i == 1 && de->d_name[0] == '.') continue;
            c = 0;
            switch(type) {
                case 0:
                    if(!(i > 5 && !strcmp(de->d_name + i - 4, ".png"))) continue;
                break;
                case 1:
                    strcpy(fn, de->d_name);
                    if(isdir(tmp)) c = '/';
                    else
                    if( (i < 5 || strcmp(de->d_name + i - 4, ".mts")) &&
                        (i < 5 || strcmp(de->d_name + i - 4, ".png")) &&
                        (i < 7 || strcmp(de->d_name + i - 6, ".schem")) &&
                        (i < 11 || strcmp(de->d_name + i - 10, ".schematic")) &&
                        (i < 4 || strcmp(de->d_name + i - 3, ".we")) &&
                        (i < 7 || strcmp(de->d_name + i - 6, ".we.gz")) &&
                        (i < 8 || strcmp(de->d_name + i - 7, ".we.bz2")) &&
                        (i < 5 || strcmp(de->d_name + i - 4, ".m3d")) &&
                        (i < 5 || strcmp(de->d_name + i - 4, ".tmx")) &&
                        (i < 5 || strcmp(de->d_name + i - 4, ".gox")) &&
                        (i < 5 || strcmp(de->d_name + i - 4, ".vox")) &&
                        (i < 4 || strcmp(de->d_name + i - 3, ".qb")))
                        continue;
                break;
            }
            j = numfiles++;
            *files = (char**)realloc(*files, numfiles * sizeof(char*));
            if(!*files) error(lang[ERR_MEM]);
            (*files)[j] = (char*)malloc(i + 2);
            if(!(*files)[j]) error(lang[ERR_MEM]);
            if(c) {
                (*files)[j][0] = c;
                memcpy((*files)[j] + 1, de->d_name, i + 1);
            } else
                memcpy((*files)[j], de->d_name, i + 1);
        }
        closedir(dir);
    }
    if(numfiles)
        qsort(*files, numfiles, sizeof(char*), fncmp);
    return numfiles;
}

/**
 * Free directory list
 */
void freedir(int n, char ***files)
{
    int i;

    for(i = 0; i < n; i++)
        free((*files)[i]);
    free(*files);
    *files = NULL;
}

/**
 * Deetect cube edges
 */
void detcube(int w, int h, unsigned char *block)
{
    unsigned char *d;
    int i, j;

    for(d = block; d[3] < 128; d += 4);
    x1 = ((int)(d - block) % (w * 4)) / 4;
    _y0 = (int)(d - block) / (w * 4);
    for(i = 0, d = block; i < w/2 && d[3] < 128; i++)
        for(j = 0, d = block + i*4; j < h/2 && d[3] < 128; j++, d += w*4);
    x0 = ((int)(d - block) % (w * 4)) / 4 - 1;
    _y1 = (int)(d - block) / (w * 4);
    x2 = x1 + (x1 - x0) + 1;
    y2 = _y1 + (_y1 - _y0);
    for(; d[3] > 128; d += w*4);
    d -= w*4;
    y3 = (int)(d - block) / (w * 4);
    y4 = y3 + (_y1 - _y0);
}

/**
 * Install Minetest mod
 */
int instmod(unsigned char *data, int size)
{
    FILE *f;
    char tmp[MAXPATHLEN], msg[MAXPATHLEN + 128], *fn, *d;
    unsigned char *un, *b, *end;
    int i, j, s;

    /* get the data */
    b = un = (unsigned char*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)data, size, 32768, &s, 1);
    if(!un) error(lang[ERR_MEM]);
    end = b + s;

#ifndef __WIN32__
    if(mtsfile[0] == '~') {
        strcpy(tmp, home);
        strcat(tmp, mtsfile + 1);
    } else
#endif
        strcpy(tmp, mtsfile);
    fn = tmp + strlen(tmp);
    if(fn[-1] != DIRSEP) { *fn++ = DIRSEP; *fn = 0; }
    strcpy(fn, "mtsedit");
    fn += 7; *fn++ = DIRSEP; *fn = 0;
    mkdir(tmp
#ifndef __WIN32__
    , 0755
#endif
    );
    if(!isdir(tmp)) {
        sprintf(msg, "%s: %s", lang[ERR_MOD], tmp);
        error(msg);
    }
    while(b < end) {
#ifndef __WIN32__
        i = strlen((char*)b) + 1;
#else
        for(i = 0; b[i]; i++)
            if(b[i] == '/') b[i] = '\\';
        i++;
#endif
        j = (b[i + 1] << 8) | b[i];
        strcpy(fn, (char*)b);
        d = strrchr(fn, DIRSEP);
        if(d > fn) {
            *d = 0;
            mkdir(tmp
#ifndef __WIN32__
            , 0755
#endif
            );
            *d = DIRSEP;
        }
        f = fopen(tmp, "wb");
        if(f) {
            fwrite(b + i + 2, j, 1, f);
            fclose(f);
        } else {
            sprintf(msg, "%s: %s", lang[ERR_MOD], tmp);
            error(msg);
        }
        b += i + 2 + j;
    }
    free(un);
    return 0;
}

/**
 * Generate slab and stair pictures from block image
 */
int stairgen()
{
    FILE *f;
    stbi__context sc;
    stbi__result_info ri;
    int i, j, w, h, l;
    unsigned int size;
    unsigned char *data, *block, *slab, *stair;
    char *c, *d, *e, fn[MAXPATHLEN];

    f = fopen(mtsfile, "rb");
    if(f) {
        fseek(f, 0L, SEEK_END);
        size = (unsigned int)ftell(f);
        fseek(f, 0L, SEEK_SET);
        data = (unsigned char*)malloc(size);
        if(!data) error(lang[ERR_MEM]);
        fread(data, size, 1, f);
        fclose(f);
        c = strrchr(mtsfile, '_');
        if(!c) c = strrchr(mtsfile, '.');
        if(!c) c = &mtsfile[strlen(mtsfile)];
        sc.read_from_callbacks = 0;
        sc.img_buffer = sc.img_buffer_original = data;
        sc.img_buffer_end = sc.img_buffer_original_end = data + size;
        ri.bits_per_channel = 8;
        block = (unsigned char*)stbi__png_load(&sc, &w, &h, &l, 0, &ri);
        free(data);
        if(!block) return 1;
        slab = (unsigned char *)malloc(w * w * 4);
        if(!slab) error(lang[ERR_MEM]);
        memset(slab, 0, w * w * 4);
        stair = (unsigned char *)malloc(4 * w * w * 4);
        if(!stair) error(lang[ERR_MEM]);
        /* detect cube edges */
        detcube(w, w, block);
#if DEBUG
        *((uint32_t*)(slab + _y0*w*4 + x1*4)) = 0xFF0000FF;
        *((uint32_t*)(slab + _y1*w*4 + x0*4)) = 0xFF0000FF;
        *((uint32_t*)(slab + _y1*w*4 + x2*4)) = 0xFF00FF00;
        *((uint32_t*)(slab + y2*w*4 + x1*4)) = 0xFF00FF00;
        *((uint32_t*)(slab + y3*w*4 + x0*4)) = 0xFF0000FF;
        *((uint32_t*)(slab + y3*w*4 + x2*4)) = 0xFF00FF00;
        *((uint32_t*)(slab + y4*w*4 + x1*4)) = 0xFF00FF00;
#endif

        /* add rotated block to the bottom of the image */
        if(w == h) {
            block = (unsigned char*)realloc(block, 2 * w * w * 4);
            if(!block) return 1;
            memset(block + w * w * 4, 0, w * w * 4);
            for(i = x0; i <= x2+1; i++) {
                for(j = _y0; j <= y4; j++)
                    *((uint32_t*)(block + (32+j)*w*4 + (x2 - i + x0 + (x0 & 1))*4)) = *((uint32_t*)(block + j*w*4 + i*4));
            }
            data = (unsigned char *)stbi_write_png_to_mem(block, w*4, w, 2*w, 4, &l);
            if(data) {
                f = fopen(mtsfile,"wb");
                if(f) { fwrite(data, l, 1, f); fclose(f); }
                free(data);
            }
        }

        /* slab */
        memset(slab, 0, w * w * 4);
        for(i = x0; i <= x1; i++) {
            l = (_y1 - _y0) * (i - x0) / (x1 - x0);
            for(j = _y0; j < _y1 + (y3 - _y1)/2 + l; j++)
                *((uint32_t*)(slab + (j+(y3 - _y1)/2+1)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
            for(j = y3 + l; j < w; j++)
                *((uint32_t*)(slab + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x1+1; i <= x2; i++) {
            l = (_y1 - _y0) * ((x2-i+1) - x1) / (x2 - x1);
            for(j = _y0; j < y2 + (y3 - _y1)/2 + l; j++)
                *((uint32_t*)(slab + (j+(y3 - _y1)/2+1)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
            for(j = y4 + l; j < w; j++)
                *((uint32_t*)(slab + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        data = (unsigned char *)stbi_write_png_to_mem(slab, w*4, w, w, 4, &l);
        if(data) {
            strcpy(c, "_Slab.png");
            f = fopen(mtsfile,"wb");
            if(f) { fwrite(data, l, 1, f); fclose(f); }
            free(data);
        }

        /* double slab */
        memset(stair, 0, w * w * 4);
        for(i = x0; i <= x1; i++) {
            l = (_y1 - _y0) * (i - x0) / (x1 - x0);
            for(j = _y0; j < _y1 + (y3 - _y1)*3/4 + l; j++)
                *((uint32_t*)(stair + (j+(y3 - _y1)/4+1)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
            for(j = y3 + l; j < w; j++)
                *((uint32_t*)(stair + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x1+1; i <= x2; i++) {
            l = (_y1 - _y0) * ((x2-i+1) - x1) / (x2 - x1);
            for(j = _y0; j < y2 + (y3 - _y1)*3/4 + l; j++)
                *((uint32_t*)(stair + (j+(y3 - _y1)/4+1)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
            for(j = y4 + l; j < w; j++)
                *((uint32_t*)(stair + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        data = (unsigned char *)stbi_write_png_to_mem(stair, w*4, w, w, 4, &l);
        if(data) {
            strcpy(fn, mtsfile);
            d = strrchr(mtsfile, DIRSEP);
            if(d) d++; else d = mtsfile;
            memcpy(fn, mtsfile, (int)(d - mtsfile));
            e = fn + (int)(d - mtsfile);
            strcpy(e, "Double_"); e += 7;
            memcpy(e, d, (int)(c - d)); e += (int)(c - d);
            strcpy(e, "_Slab.png");
            f = fopen(fn,"wb");
            if(f) { fwrite(data, l, 1, f); fclose(f); }
            free(data);
        }

        /* rotated stairs */
        memcpy(stair, slab, w * w * 4);
        for(i = x0 + (x1 - x0)/2; i <= x1 + (x2 - x1)/2; i++) {
            l = (_y1 - _y0) * (i - x0) / (x1 - x0);
            for(j = _y0; j <= _y0 + l; j++)
                *((uint32_t*)(stair + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i--; i <= x2; i++) {
            for(j = _y0; j <= y4; j++)
                *((uint32_t*)(stair + j*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x0; i <= x1; i++) {
            l = (_y1 - _y0) * (i - x0) / (x1 - x0);
            for(j = _y1 + l - 1; j <= _y1 + (y3 - _y1)/2 + l; j++)
                *((uint32_t*)(stair + (j-(_y1-_y0)/2)*w*4 + (i + (x1 - x0)/2)*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }

        memcpy(stair + w*w*4, slab, w * w * 4);
        for(i = x0 + (x1 - x0)/2; i <= x2; i++) {
            l = (_y1 - _y0) * ((x2-i+1) - x1) / (x2 - x1);
            for(j = _y1 + l; j < y4; j++)
                *((uint32_t*)(stair + (w+j)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x0; i <= x1; i++) {
            l = (_y1 - _y0) * ((x1-i+1) - x1) / (x1 - x0);
            for(j = _y1 + l; j <= y2 + l; j++)
                *((uint32_t*)(stair + (w+j+(_y1-_y0)/2)*w*4 + (i+(x1 - x0)/2)*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }

        memcpy(stair + 2*w*w*4, slab, w * w * 4);
        for(i = x0; i <= x1 + (x2 - x1)/2; i++) {
            l = (_y1 - _y0) * (i - x0) / (x1 - x0);
            for(j = _y0 + l; j < y4; j++)
                *((uint32_t*)(stair + (2*w+j)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x1; i <= x2; i++) {
            l = (_y1 - _y0) * (i - x1) / (x1 - x0);
            for(j = _y0 + l; j <= _y0 + l + 1; j++)
                *((uint32_t*)(stair + (2*w+(j+(_y1-_y0)/2))*w*4 + (i - (x1 - x0)/2)*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }

        memcpy(stair + 3*w*w*4, slab, w * w * 4);
        for(i = x0; i <= x0 + (x1 - x0)/2 +1; i++) {
            for(j = _y0; j <= y4; j++)
                *((uint32_t*)(stair + (3*w+j)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(; i <= x2; i++) {
            l = (_y1 - _y0) * (i - (x0 + (x1 - x0)/2)) / (x2 - x1);
            for(j = _y0; j <= _y1 + (y2 - _y1)/2 - l; j++)
                *((uint32_t*)(stair + (3*w+j)*w*4 + i*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        for(i = x1+1; i <= x2; i++) {
            l = (_y1 - _y0) * ((x2-i+1) - x1) / (x2 - x1);
            for(j = y2 + l - 1; j <= y2 + (y3 - _y1)/2 + l; j++)
                *((uint32_t*)(stair + (3*w+(j-(_y1-_y0)/2))*w*4 + (i-(x1-x0)/2)*4)) = *((uint32_t*)(block + j*w*4 + i*4));
        }
        data = (unsigned char *)stbi_write_png_to_mem(stair, w*4, w, 4*w, 4, &l);
        if(data) {
            strcpy(c, "_Stair.png");
            f = fopen(mtsfile,"wb");
            if(f) { fwrite(data, l, 1, f); fclose(f); }
            free(data);
        }

        fprintf(stderr, "mtsedit: %s\r\n", lang[IMGSGEN]);
        return 0;
    }
    return 1;
}

/**
 * Resample and save block image
 */
typedef struct tColorRGBA {
    Uint8 r;
    Uint8 g;
    Uint8 b;
    Uint8 a;
} tColorRGBA;
int savepng(char *name, int siz, SDL_Surface *src)
{
    int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep, lx, ly, dgap, l;
    tColorRGBA *c00, *c01, *c10, *c11, *csp, *dp;
    unsigned char *img;
    char path[MAXPATHLEN];
    FILE *f;
    SDL_Surface *dst = SDL_CreateRGBSurface(0, 32, 32 * siz, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);

    /* resample */
    src->h = siz*(TMPSIZE+8);
    /**
     * This is from SDL_gfx, but I don't wanted to introduce a new dependency just because
     * of a single function. Heavily optimized, all non-used code-paths removed, plus I hate
     * that I can't give exact destination dimensions to zoomSurfaceRGBA()
     */
    sx = sy = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w);

    if ((sax = (int *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) error(lang[ERR_MEM]);
    if ((say = (int *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) {
        free(sax);
        error(lang[ERR_MEM]);
    }

    csp = (tColorRGBA *) src->pixels;
    dp = (tColorRGBA *) dst->pixels;

    csx = 0;
    csax = sax;
    for (x = 0; x <= dst->w; x++) {
        *csax = csx;
        csax++;
        csx &= 0xffff;
        csx += sx;
    }
    csy = 0;
    csay = say;
    for (y = 0; y <= dst->h; y++) {
        if(!(y & 0x1F)) csy = 0;
        *csay = csy;
        csay++;
        csy &= 0xffff;
        csy += sy;
    }

    dgap = dst->pitch - dst->w * 4;

    csay = say;
    ly = 0;
    for (y = l = 0; y < dst->h; y++) {
        if(!(y & 0x1F)) {
            csay = say;
            csp = (tColorRGBA *) ((uint8_t*)src->pixels + l*(TMPSIZE+8)*src->pitch);
            l++;
        }
        c00 = csp;
        c01 = csp;
        c01++;
        c10 = csp;
        c10 += src->pitch/4;
        c11 = c10;
        c11++;

        csax = sax;
        lx = 0;
        for (x = 0; x < dst->w; x++) {
            ex = (*csax & 0xffff);
            ey = (*csay & 0xffff);
            t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff;
            t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff;
            dp->r = (((t2 - t1) * ey) >> 16) + t1;
            t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff;
            t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff;
            dp->g = (((t2 - t1) * ey) >> 16) + t1;
            t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff;
            t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff;
            dp->b = (((t2 - t1) * ey) >> 16) + t1;
            t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff;
            t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff;
            dp->a = (((t2 - t1) * ey) >> 16) + t1;

            csax++;
            if (*csax > 0)
            {
                sstep = (*csax >> 16);
                lx += sstep;
                if (lx <= src->w)
                {
                    c00 += sstep;
                    c01 += sstep;
                    c10 += sstep;
                    c11 += sstep;
                }
            }

            dp++;
        }

        csay++;
        if (*csay > 0)
        {
            sstep = (*csay >> 16);
            ly += sstep;
            if (ly < src->h)
            {
                csp += (sstep * (src->pitch/4));
            }
        }

        dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
    }
    free(sax);
    free(say);

    /* save png */
#if 0
    img = (unsigned char *)stbi_write_png_to_mem(src->pixels, src->pitch, src->w, src->h, 4, &l);
#else
    img = (unsigned char *)stbi_write_png_to_mem(dst->pixels, dst->pitch, dst->w, dst->h, 4, &l);
#endif
    SDL_FreeSurface(dst);
    if(img) {
        sprintf(path, "%s.png",name);
        if(verbose)
            printf("mtsedit: %s\r\n", path);
        f = fopen(path,"wb");
        if(f) { fwrite(img, l, 1, f); fclose(f); }
        free(img);
        return 1;
    }
    return 0;
}

/**
 * Mix two pixels
 */
void mixpixel(unsigned char *a, unsigned char *b, unsigned int i)
{
    unsigned int c[4];
    c[0] = (unsigned int)b[0] * i / 255; if(c[0] > 255) c[0] = 255;
    c[1] = (unsigned int)b[1] * i / 255; if(c[1] > 255) c[1] = 255;
    c[2] = (unsigned int)b[2] * i / 255; if(c[2] > 255) c[2] = 255;
    a[0] = (c[0]*b[3] + (256 - a[3])*a[0])>>8;
    a[1] = (c[1]*b[3] + (256 - a[3])*a[1])>>8;
    a[2] = (c[2]*b[3] + (256 - a[3])*a[2])>>8;
    a[3] = (b[3]*b[3] + (256 - a[3])*a[3])>>8;
}

/**
 * Isometric blit
 */
void blitiso(unsigned char *from, int quadrant, unsigned char *to, int rotation, int x, int y, int blk)
{
    int i, j, ox, oy, sx, sy;

    x += 8; y += 4 + blk*(TMPSIZE+8);
    switch(quadrant) {
        /* XX
         * XX (full) */
         case 0: sx = sy = 32; ox = oy = 0; break;
        /* X.
         * .. */
         case 1: sx = sy = 16; ox = oy = 0; break;
        /* .X
         * .. */
        case 2: sx = sy = 16; ox = 16; oy = 0; break;
        /* ..
         * X. */
        case 3: sx = sy = 16; oy = 16; ox = 0; break;
        /* ..
         * .X */
        case 4: sx = sy = 16; ox = oy = 16; break;
        /* ..
         * __ (slabs) */
        case 5: sx = 32; sy = 8; ox = 0; oy = 24; break;
    }

    switch(rotation) {
        /* .N.
         * XXN
         * .X. */
        case 0:
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j-1)*4, from + (oy+j-i)*32*4 + (ox+i)*4, 287);
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j)*4, from + (oy+j-i)*32*4 + (ox+i)*4, 287);
                }
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4+(x+2*sx-(2*i+sx-j)-1)*4, from + (oy+sy-j-1+i)*32*4+(ox+sx-i-1)*4, 287);
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4+(x+2*sx-(2*i+sx-j))*4, from + (oy+sy-j-1+i)*32*4+(ox+sx-i-1)*4, 287);
                }
        break;
        /* .X.
         * XXN
         * .N. */
        case 1:
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j-1)*4, from + (oy+sy-i-1)*32*4 + (ox+j-i)*4, 287);
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j)*4, from + (oy+sy-i-1)*32*4 + (ox+j-i)*4, 287);
                }
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j)-1)*4, from + (oy+i)*32*4 + (ox+sx-(j-i)-1)*4, 287);
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j))*4, from + (oy+i)*32*4 + (ox+sx-(j-i)-1)*4, 287);
                }
        break;
        /* .X.
         * NXX
         * .N. */
        case 2:
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j-1)*4, from + (oy+sy-j+i-1)*32*4 + (ox+sx-i-1)*4, 287);
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j)*4, from + (oy+sy-j+i-1)*32*4 + (ox+sx-i-1)*4, 287);
                }
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j)-1)*4, from + (oy+j-i)*32*4 + (ox+i)*4, 287);
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j))*4, from + (oy+j-i)*32*4 + (ox+i)*4, 287);
                }
        break;
        /* .N.
         * NXX
         * .X. */
        case 3:
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j-1)*4, from + (oy+i)*32*4 + (ox+sx-(j-i)-1)*4, 287);
                    mixpixel(to + (y+j/2)*TMPSIZE*4 + (x+2*i+sx-j)*4, from + (oy+i)*32*4 + (ox+sx-(j-i)-1)*4, 287);
                }
            for(j = 0; j < sy; j++)
                for(i = 0; i <= j; i++) {
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j)-1)*4, from + (oy+sy-i-1)*32*4 + (ox+j-i)*4, 287);
                    mixpixel(to + (y+sy-j/2-1)*TMPSIZE*4 + (x+2*sx-(2*i+sx-j))*4, from + (oy+sy-i-1)*32*4 + (ox+j-i)*4, 287);
                }
        break;
        /* X.
         * XX
         * .X */
        case 4:
            for(j = 0; j < sy*2; j++)
                for(i = 0; i < sx; i++)
                    mixpixel(to + (y+j*5/8+i/2)*TMPSIZE*4 + (x+i)*4, from + (oy+j/2)*32*4 + (ox+i)*4, 255);
        break;
        /* .X
         * XX
         * X. */
        case 5:
            for(j = 0; j < sy*2; j++)
                for(i = 0; i < sx; i++)
                    mixpixel(to + (y+j*5/8+sy/2-i/2-1)*TMPSIZE*4 + (x+i)*4, from + (oy+j/2)*32*4 + (ox+i)*4, 128);
        break;
        /* XX
         * XX */
        default:
            for(j = 0; j < sy; j++)
                for(i = 0; i < sx; i++) {
                    mixpixel(to + (y+2*j)*TMPSIZE*4 + (x+2*i)*4, from + (oy+j)*32*4 + (ox+i)*4, 255);
                    mixpixel(to + (y+2*j)*TMPSIZE*4 + (x+2*i+1)*4, from + (oy+j)*32*4 + (ox+i)*4, 255);
                    mixpixel(to + (y+2*j+1)*TMPSIZE*4 + (x+2*i)*4, from + (oy+j)*32*4 + (ox+i)*4, 255);
                    mixpixel(to + (y+2*j+1)*TMPSIZE*4 + (x+2*i+1)*4, from + (oy+j)*32*4 + (ox+i)*4, 255);
                }
        break;
    }
}

/**
 * Generate block, slab and stair pictures from textures
 */
int blockgen(char *blockid)
{
    stbi__context sc;
    stbi__result_info ri;
    FILE *f;
    unsigned char *img, *raw, *top, *bottom, *north, *south, *west, *east, out[32 * TMPSIZE * (TMPSIZE+8) * 4];
    unsigned int size, *tmp;
    char *data, c, *s, *e, **paths = NULL, path[MAXPATHLEN];
    int w, h, l, r, p = 0, np = 0, i, j = 0, k = 0, o = 0, sl, ds, st;
    SDL_Rect src, dst;

    fg = SDL_CreateRGBSurfaceFrom(NULL, 32, 32, 32, 32*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    src.x = src.y = dst.x = dst.y = 0; dst.w = dst.h = 32;

    f = fopen(mtsfile, "rb");
    if(f) {
        fseek(f, 0L, SEEK_END);
        size = (unsigned int)ftell(f);
        fseek(f, 0L, SEEK_SET);
        data = (char*)malloc(size + 1);
        if(!data) error(lang[ERR_MEM]);
        fread(data, size, 1, f);
        data[size] = 0;
        fclose(f);
    } else {
        fprintf(stderr, "mtsedit: %s: %s\r\n", lang[ERR_IMGCSV], mtsfile);
        return 1;
    }

    for(s = blockid; s && *s; s++)
        if(*s == ' ' || *s == '\'') *s = '_';
    e = data;
    while(*e) {
        if(e == data || *e == '\r' || *e == '\n') {
            while(*e == '\r' || *e == '\n') e++;
            if(!*e) break;
            /* get canonical name */
            if(*e == '\"') { e++; c = '\"'; } else c = ',';
            for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
                if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
            while(*s <= ' ') s++;
            while(*(e-1) <= ' ') e--;
            j = numblocks++;
            blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
            if(!blocks) error(lang[ERR_MEM]);
            memset(&blocks[j], 0, sizeof(mtsblock_t));
            blocks[j].blocknames = (char**)malloc(16 * sizeof(char*));
            if(!blocks[j].blocknames) error(lang[ERR_MEM]);
            memset(blocks[j].blocknames, 0, 16 * sizeof(char*));
            blocks[j].name = (char*)malloc((int)(e - s) + 1);
            if(!blocks[j].name) error(lang[ERR_MEM]);
            for(i = 0; i < (int)(e - s); i++)
                blocks[j].name[i] = s[i] == ' ' || s[i] == '\'' ? '_' : s[i];
            while(i > 0 && (blocks[j].name[i-1] == '(' || blocks[j].name[i-1] == '_')) i--;
            blocks[j].name[i] = 0;
            /* get technical name, drawtype, paramtype2, node_box type */
            for(k = 0; k < 4; k++) {
                while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
                if(*e != ',' && *e != ';' && *e != '\t') errorimgcsv(j+1);
                e++;
                while(*e == ' ') e++;
                if(*e == '\"') { e++; c = '\"'; } else c = ',';
                for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
                    if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
                while(*s <= ' ') s++;
                while(*(e-1) <= ' ') e--;
                blocks[j].blocknames[k] = (char*)malloc(e - s + 1);
                if(!blocks[j].blocknames[k]) error(lang[ERR_MEM]);
                memcpy(blocks[j].blocknames[k], s, e - s + 1);
                blocks[j].blocknames[k][e - s] = 0;
            }
            /* get directory */
            while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
            if(*e != ',' && *e != ';' && *e != '\t') errorimgcsv(j+1);
            e++;
            while(*e == ' ') e++;
            if(*e == '\"') { e++; c = '\"'; } else c = ',';
            for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
                if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
            while(*s <= ' ') s++;
            while(*(e-1) <= ' ') e--;
            memcpy(path, s, e - s + 1);
            k = e - s;
            if(path[k-1] != DIRSEP) { path[k++] = DIRSEP; path[k] = 0; }
            strcpy(path + k, "textures"); k += 8;
            path[k++] = DIRSEP; path[k] = 0;
            for(k = 0, o = -1; k < np; k++)
                if(!strcmp(paths[k], path)) { o = k; break; }
            if(o == -1) {
                paths = (char**)realloc(paths, (np + 1) * sizeof(char*));
                if(!paths) error(lang[ERR_MEM]);
                paths[np] = (char*)malloc(strlen(path) + 1);
                if(!paths[np]) error(lang[ERR_MEM]);
                strcpy(paths[np], path);
                np++;
            }
            while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
            k = 0; o = 0;
        }
        /* read textures */
        while(*e == ' ') e++;
        if(*e != ',' && *e != ';' && *e != '\t') errorimgcsv(j+1);
        e++;
        while(*e == ' ') e++;
        if(!*e) errorimgcsv(j+1);
        if(*e == '\"') { e++; c = '\"'; } else c = ',';
        for(s = e; *e && *e != '\r' && *e != '\n' && *e != c; e++)
            if(*e == '\\' || (*e == '\"' && e[1] == '\"')) { e++; continue; }
        while(*s <= ' ') s++;
        while(*(e-1) <= ' ') e--;
        /* switch to overlay textures */
        if(s[0] == '|') { k = 10; continue; }
        /* read in the texture filenames */
        if(e != s) {
            for(i = 0; i < e - s && s[i] != '^'; i++);
            blocks[j].blocknames[k + 4] = (char*)malloc(i + 1);
            if(!blocks[j].blocknames[k + 4]) error(lang[ERR_MEM]);
            memcpy(blocks[j].blocknames[k + 4], s, i);
            blocks[j].blocknames[k + 4][i] = 0;
        }
        k++; if(k > 15) errorimgcsv(j+1);
        while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
    }
    /* we have all possible paths, read in the textures */
    for(i = 0; i < j; i++) {
        /* filter out what we can't or don't want to handle */
        if(blockid && strcasecmp(blockid, blocks[i].name)) continue;
        s = strrchr(blocks[i].name, '_');
        if(s && (!strcmp(s, "_Slab") || !strcmp(s, "_Stair"))) continue;
        if(!strcmp(blocks[i].blocknames[1], "nodebox") || !strcmp(blocks[i].blocknames[1], "mesh")) continue;
        /* get tiles */
        for(k = 0; k < 6; k++) {
            if(blocks[i].blocknames[k + 4] && blocks[i].blocknames[k + 4][0])
                for(l = r = 0; l < np && !r; l++) {
                    strcpy(path, paths[l]);
                    strcat(path, blocks[i].blocknames[k + 4]);
                    f = fopen(path, "rb");
                    if(f) {
                        if(verbose && blockid) printf("mtsedit: texture %s\n",path);
                        fseek(f, 0L, SEEK_END);
                        size = (unsigned int)ftell(f);
                        fseek(f, 0L, SEEK_SET);
                        img = (unsigned char*)malloc(size);
                        if(!img) error(lang[ERR_MEM]);
                        fread(img, size, 1, f);
                        fclose(f);
                        sc.read_from_callbacks = 0;
                        sc.img_buffer = sc.img_buffer_original = img;
                        sc.img_buffer_end = sc.img_buffer_original_end = img + size;
                        ri.bits_per_channel = 8;
                        raw = (unsigned char*)stbi__png_load(&sc, &w, &h, &r, 0, &ri);
                        if(raw) {
                            /* convert grayscale, alpha grayscale and RGB to RGBA */
                            if(r != 4) {
                                tmp = (unsigned int *)malloc(w * h * 4);
                                if(!tmp) error(lang[ERR_MEM]);
                                for(l = 0; l < w * h; l++)
                                    switch(r) {
                                        case 1: tmp[l] = 0xFF000000 | (raw[l] << 16) | (raw[l] << 8) | (raw[l]); break;
                                        case 2: tmp[l] = (raw[(l<<1)+1]<<24) | (raw[l<<1]<<16) | (raw[l<<1]<<8) | (raw[l<<1]); break;
                                        case 3: tmp[l] = 0xFF000000 | (raw[l*3+2] << 16) | (raw[l*3+1] << 8) | (raw[l*3]); break;
                                    }
                                free(raw);
                                raw = (unsigned char*)tmp;
                            }
                            /* rescale to 32x32 */
                            if(!blocks[i].img) {
                                blocks[i].img = (unsigned char *)malloc(6 * 32 * 32 * 4);
                                if(!blocks[i].img) error(lang[ERR_MEM]);
                                memset(blocks[i].img, 0, 6 * 32 * 32 * 4);
                            }
                            fg->pixels = blocks[i].img + k * 32 * 32 * 4;
                            blk = SDL_CreateRGBSurfaceFrom(raw, w, h, 32, w*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
                            src.w = w; src.h = h;
                            SDL_BlitScaled(blk, &src, fg, &dst);
                            SDL_FreeSurface(blk);
                            free(raw);
                            blocks[i].numpar2++;
                        } else r = 0;
                        free(img);
                    }
                }
            /* get overlays */
            if(blocks[i].img && blocks[i].blocknames[k + 10] && blocks[i].blocknames[k + 10][0])
                for(l = r = 0; l < np && !r; l++) {
                    strcpy(path, paths[l]);
                    strcat(path, blocks[i].blocknames[k + 10]);
                    f = fopen(path, "rb");
                    if(f) {
                        fseek(f, 0L, SEEK_END);
                        size = (unsigned int)ftell(f);
                        fseek(f, 0L, SEEK_SET);
                        img = (unsigned char*)malloc(size);
                        if(!img) error(lang[ERR_MEM]);
                        fread(img, size, 1, f);
                        fclose(f);
                        sc.read_from_callbacks = 0;
                        sc.img_buffer = sc.img_buffer_original = img;
                        sc.img_buffer_end = sc.img_buffer_original_end = img + size;
                        ri.bits_per_channel = 8;
                        raw = (unsigned char*)stbi__png_load(&sc, &w, &h, &r, 0, &ri);
                        if(raw) {
                            /* convert grayscale, alpha grayscale and RGB to RGBA */
                            if(r != 4) {
                                tmp = (unsigned int *)malloc(w * h * 4);
                                if(!tmp) error(lang[ERR_MEM]);
                                for(l = 0; l < w * h; l++)
                                    switch(r) {
                                        case 1: tmp[l] = 0xFF000000 | (raw[l] << 16) | (raw[l] << 8) | (raw[l]); break;
                                        case 2: tmp[l] = (raw[(l<<1)+1]<<24) | (raw[l<<1]<<16) | (raw[l<<1]<<8) | (raw[l<<1]); break;
                                        case 3: tmp[l] = 0xFF000000 | (raw[l*3+2] << 16) | (raw[l*3+1] << 8) | (raw[l*3]); break;
                                    }
                                free(raw);
                                raw = (unsigned char*)tmp;
                            }
                            /* rescale to 32x32 */
                            fg->pixels = blocks[i].img + k * 32 * 32 * 4;
                            blk = SDL_CreateRGBSurfaceFrom(raw, w, h, 32, w*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
                            src.w = w; src.h = h;
                            /* blit overlay to original texture */
                            SDL_BlitScaled(blk, &src, fg, &dst);
                            SDL_FreeSurface(blk);
                            free(raw);
                        } else r = 0;
                        free(img);
                    }
                }
        }
        if(blocks[i].img) p++;
    }
    SDL_FreeSurface(fg);
    blk = SDL_CreateRGBSurfaceFrom(out, TMPSIZE, 32*TMPSIZE, 32, TMPSIZE*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
    /* ok, now we have all the textures loaded. The blocks array looks like this:
     *   name = canonical block id name (like "Acacia_Bark_Stair")
     *   blocknames[0] = block's technical name (like "mcl_core:bedrock")
     *   blocknames[1] = drawtype
     *   blocknames[2] = paramtype2
     *   blocknames[3] = node_box type
     *   blocknames[4] - [9] = tile names (like "acacia.png^[transformR90")
     *   blocknames[10] - [15] = overlay tile names
     *   img[0] - img[5] = uncompressed tiles, scaled to 32x32 RGBA, overlay images applied
     *   numpar2 = number of textures */
    for(i = k = o = 0; i < j; i++) {
        if(!blocks[i].img) continue;
        if(!verbose) {
            k++;
            printf("%3d%% %s                         \r", k*100/p, blocks[i].name);
        }
        /* construct block images */
        memset(out, 0, sizeof(out));
        if(!strcmp(blocks[i].blocknames[1], "plantlike") || !strcmp(blocks[i].blocknames[1], "firelike")) {
            blitiso(blocks[i].img, 0, out, 6, 0,0, 0);
            o += savepng(blocks[i].name,1,blk);
        } else
        if(!strcmp(blocks[i].blocknames[1], "raillike")) {
            blitiso(blocks[i].img, 0, out, 0, 0,40, 0);
            blitiso(blocks[i].img, 0, out, 1, 0,40, 1);
            o += savepng(blocks[i].name,2,blk);
        } else
        if(!strcmp(blocks[i].blocknames[1], "normal") || !strcmp(blocks[i].blocknames[1], "glasslike")) {
            /* get side textures */
            top = blocks[i].img;
            bottom = blocks[i].numpar2 > 1 ? blocks[i].img + 32 * 32 * 4 : blocks[i].img;
            west = blocks[i].numpar2 > 2 ? blocks[i].img + 2 * 32 * 32 * 4 : blocks[i].img;
            east = blocks[i].numpar2 > 3 ? blocks[i].img + 3 * 32 * 32 * 4 : west;
            north = blocks[i].numpar2 > 4 ? blocks[i].img + 4 * 32 * 32 * 4 : west;
            south = blocks[i].numpar2 > 5 ? blocks[i].img + 5 * 32 * 32 * 4 : west;
            /* faces south */
            blitiso(bottom, 0, out, 0, 0,39, 0);
            blitiso(west, 0, out, 5, 0,0, 0);
            blitiso(north, 0, out, 4, 32,0, 0);
            blitiso(east, 0, out, 5, 32,16, 0);
            blitiso(south, 0, out, 4, 0,16, 0);
            blitiso(top, 0, out, 0, 0,0, 0);
            l = 1;
            if(blocks[i].numpar2 > 1) {
                /* faces east */
                blitiso(bottom, 0, out, 1, 0,39, 1);
                blitiso(south, 0, out, 5, 0,0, 1);
                blitiso(west, 0, out, 4, 32,0, 1);
                blitiso(north, 0, out, 5, 32,16, 1);
                blitiso(east, 0, out, 4, 0,16, 1);
                blitiso(top, 0, out, 1, 0,0, 1);
                l++;
                /* faces north */
                blitiso(bottom, 0, out, 2, 0,39, 2);
                blitiso(east, 0, out, 5, 0,0, 2);
                blitiso(south, 0, out, 4, 32,0, 2);
                blitiso(west, 0, out, 5, 32,16, 2);
                blitiso(north, 0, out, 4, 0,16, 2);
                blitiso(top, 0, out, 2, 0,0, 2);
                l++;
                /* faces west */
                blitiso(bottom, 0, out, 3, 0,39, 3);
                blitiso(north, 0, out, 5, 0,0, 3);
                blitiso(east, 0, out, 4, 32,0, 3);
                blitiso(south, 0, out, 5, 32,16, 3);
                blitiso(west, 0, out, 4, 0,16, 3);
                blitiso(top, 0, out, 3, 0,0, 3);
                l++;
            }
            o += savepng(blocks[i].name,l,blk);
            /* check if we need to generate slabs and stairs for this block */
            /* FIXME: this assumes no block ids are longer than 255 bytes, hopefully true */
            sprintf(path, "%s_Slab", blocks[i].name);
            sprintf(path+256, "Double_%s_Slab", blocks[i].name);
            sprintf(path+512, "%s_Stair", blocks[i].name);
            for(sl = ds = st = l = 0; l < j; l++) {
                if(!strcmp(blocks[l].name, path)) sl = 1;
                if(!strcmp(blocks[l].name, path+256)) ds = 1;
                if(!strcmp(blocks[l].name, path+512)) st = 1;
            }
            if(sl) {
                /* slab */
                memset(out, 0, sizeof(out));
                blitiso(bottom, 0, out, 0, 0,39, 0);
                blitiso(west, 5, out, 5, 0,41, 0);
                blitiso(north, 5, out, 4, 32,30, 0);
                blitiso(east, 5, out, 5, 32,58, 0);
                blitiso(south, 5, out, 4, 0,46, 0);
                blitiso(top, 0, out, 0, 0,29, 0);
                o += savepng(path,1,blk);
            }
            if(ds) {
                /* double slab */
                memset(out, 0, sizeof(out));
                blitiso(bottom, 0, out, 0, 0,39, 0);
                blitiso(west, 3, out, 5, 0,28, 0);
                blitiso(west, 4, out, 5, 16,20, 0);
                blitiso(north, 3, out, 4, 32,20, 0);
                blitiso(north, 4, out, 4, 48,28, 0);
                blitiso(east, 4, out, 5, 48,36, 0);
                blitiso(east, 3, out, 5, 32,44, 0);
                blitiso(south, 4, out, 4, 16,44, 0);
                blitiso(south, 3, out, 4, 0,36, 0);
                blitiso(top, 0, out, 0, 0,20, 0);
                o += savepng(path+256,1,blk);
            }
            if(st) {
                /* stairs */
                memset(out, 0, sizeof(out));
                /* north */
                blitiso(bottom, 0, out, 0, 0,39, 0);
                blitiso(west, 3, out, 5, 0,28, 0);
                blitiso(west, 4, out, 5, 16,20, 0);
                blitiso(north, 3, out, 4, 32,20, 0);
                blitiso(north, 4, out, 4, 48,28, 0);
                blitiso(east, 4, out, 5, 48,36, 0);
                blitiso(east, 3, out, 5, 32,44, 0);
                blitiso(south, 4, out, 4, 16,44, 0);
                blitiso(south, 3, out, 4, 0,36, 0);
                blitiso(top, 1, out, 0, 16,36, 0);
                blitiso(top, 2, out, 0, 0,28, 0);
                blitiso(west, 2, out, 5, 16,1, 0);
                blitiso(north, 1, out, 4, 32,1, 0);
                blitiso(north, 2, out, 4, 48,9, 0);
                blitiso(east, 2, out, 5, 48,17, 0);
                blitiso(south, 2, out, 4, 32,17, 0);
                blitiso(south, 1, out, 4, 16,9, 0);
                blitiso(top, 4, out, 0, 16,0, 0);
                blitiso(top, 3, out, 0, 32,8, 0);
                /* east */
                blitiso(bottom, 0, out, 1, 0,39, 1);
                blitiso(south, 3, out, 5, 0,28, 1);
                blitiso(south, 4, out, 5, 16,20, 1);
                blitiso(west, 3, out, 4, 32,20, 1);
                blitiso(west, 4, out, 4, 48,28, 1);
                blitiso(north, 4, out, 5, 48,36, 1);
                blitiso(north, 3, out, 5, 32,44, 1);
                blitiso(east, 4, out, 4, 16,44, 1);
                blitiso(east, 3, out, 4, 0,36, 1);
                blitiso(top, 3, out, 1, 16,20, 1);
                blitiso(top, 4, out, 1, 0,28, 1);
                blitiso(south, 1, out, 5, 32,9, 1);
                blitiso(south, 2, out, 5, 16,17, 1);
                blitiso(west, 2, out, 4, 48,9, 1);
                blitiso(north, 2, out, 5, 48,18, 1);
                blitiso(north, 1, out, 5, 32,26, 1);
                blitiso(east, 2, out, 4, 16,26, 1);
                blitiso(top, 1, out, 1, 32,8, 1);
                blitiso(top, 2, out, 1, 16,16, 1);
                /* south */
                blitiso(bottom, 0, out, 2, 0,39, 2);
                blitiso(east, 3, out, 5, 0,28, 2);
                blitiso(east, 4, out, 5, 16,20, 2);
                blitiso(south, 3, out, 4, 32,20, 2);
                blitiso(south, 4, out, 4, 48,28, 2);
                blitiso(west, 4, out, 5, 48,36, 2);
                blitiso(west, 3, out, 5, 32,44, 2);
                blitiso(north, 4, out, 4, 16,44, 2);
                blitiso(north, 3, out, 4, 0,36, 2);
                blitiso(top, 1, out, 2, 16,20, 2);
                blitiso(top, 2, out, 2, 32,28, 2);
                blitiso(east, 1, out, 5, 0,9, 2);
                blitiso(south, 1, out, 4, 16,9, 2);
                blitiso(south, 2, out, 4, 32,17, 2);
                blitiso(west, 1, out, 5, 32,25, 2);
                blitiso(north, 2, out, 4, 16,25, 2);
                blitiso(north, 1, out, 4, 0,17, 2);
                blitiso(top, 3, out, 2, 0,8, 2);
                blitiso(top, 4, out, 2, 16,16, 2);
                /* west */
                blitiso(bottom, 0, out, 3, 0,39, 3);
                blitiso(north, 3, out, 5, 0,28, 3);
                blitiso(north, 4, out, 5, 16,20, 3);
                blitiso(east, 3, out, 4, 32,20, 3);
                blitiso(east, 4, out, 4, 48,28, 3);
                blitiso(south, 4, out, 5, 48,36, 3);
                blitiso(south, 3, out, 5, 32,44, 3);
                blitiso(west, 4, out, 4, 16,44, 3);
                blitiso(west, 3, out, 4, 0,36, 3);
                blitiso(top, 1, out, 3, 32,28, 3);
                blitiso(top, 2, out, 3, 16,36, 3);
                blitiso(north, 1, out, 5, 0,9, 3);
                blitiso(north, 2, out, 5, 16,1, 3);
                blitiso(east, 1, out, 4, 32,1, 3);
                blitiso(south, 2, out, 5, 32,9, 3);
                blitiso(south, 1, out, 5, 16,17, 3);
                blitiso(west, 1, out, 4, 0,17, 3);
                blitiso(top, 3, out, 3, 0,8, 3);
                blitiso(top, 4, out, 3, 16,0, 3);
                o += savepng(path+512,4,blk);
            }
        }
    }
    if(!verbose)
        printf("mtsedit: %d PNGs, OK.                                                            \r\n", o);
    return 0;
}

/**
 * Returns true if color is not used by any blocks
 */
int is_color_unique(uint32_t color)
{
    int i;
    if(color == 0xFF000000 || color == 0xFFFF0000 || color == theme[THEME_BP_BG]) return 0;
    for(i = 1; i < numblocks - 1; i++)
        if(blocks[i].color == color) return 0;
    return 1;
}

/**
 * Returns block id for the closest color match
 */
int color2blockid(uint32_t color)
{
    unsigned char *c = (unsigned char*)&color, *d;
    int i, j, k, ld, diff = 255;

    /* unknown and air blocks */
    if(color == 0xFFFF0000 || color == theme[THEME_BP_BG] || c[3] < 128 || (!c[0] && !c[1] && !c[2])) return 0;

    for(i = 1, j = 0; i < numblocks; i++) {
        /* get local maximum */
        ld = 0; d = (unsigned char*)&blocks[i].color;
        k = d[0] > c[0] ? d[0] - c[0] : c[0] - d[0]; if(k > ld) ld = k;
        k = d[1] > c[1] ? d[1] - c[1] : c[1] - d[1]; if(k > ld) ld = k;
        k = d[2] > c[2] ? d[2] - c[2] : c[2] - d[2]; if(k > ld) ld = k;
        k = d[3] > c[3] ? d[3] - c[3] : c[3] - d[3]; if(k > ld) ld = k;
        /* is it smaller than global minimum? */
        if(ld < diff) { diff = ld; j = i; }
    }
    return j;
}

/**
 * Generate GIMP palette for blocks with unique colors
 */
void gimppal()
{
    int i;
    unsigned char *c;
    char *s;

    blocks_parse();
    if(savepal < 0 || savepal >= numpalettes) savepal = 0;

    printf("GIMP Palette\r\nName: %s\r\n# Generated by MTSEdit with current blocks.csv\r\n", palettes[savepal]);
    for(i = 0; i < numblocks; i++) {
        c = (unsigned char*)&blocks[i].color;
        s = blocks[i].blocknames[savepal+3] && blocks[i].blocknames[savepal+3][0] ?
                blocks[i].blocknames[savepal+3] : blocks[i].blocknames[3];
        if(c[3]) {
            if(s && *s)
                printf("%3d %3d %3d\t%s\r\n", c[2], c[1], c[0], s);
            else
                printf("%3d %3d %3d\t#%06x\r\n", c[2], c[1], c[0], blocks[i].color & 0xFFFFFF);
        }
    }
}

/**
 * Generate colors.txt for Minetestmapper
 */
void mapperpal()
{
    int i;
    unsigned char *c;

    if(savepal < 0 || savepal >= numpalettes) savepal = 0;

    printf("# Generated by MTSEdit for Minetestmapper colors.txt\r\n# using node palette %s\r\n\r\n", palettes[savepal]);
    for(i = 0; i < numblocks; i++) {
        c = (unsigned char*)&blocks[i].color;
        if(c[3] && blocks[i].blocknames[3])
            printf("%s %3d %3d %3d\r\n", blocks[i].blocknames[savepal+3] && blocks[i].blocknames[savepal+3][0] ?
                blocks[i].blocknames[savepal+3] : blocks[i].blocknames[3], c[0], c[1], c[2]);
    }
}

/**
 * Generate tile remapping list
 */
void tileremap()
{
    int i, j, k;

    /* make sure no translations are loaded and no remapping used */
    noblocknametranslate = 1;
    remapfile = NULL;
    blocks_parse();
    for(j = 0; j < 256; j++)
        for(i = 0; i < numblocks; i++)
            if(blocks[i].blockids[0] == j || (j && blocks[i].blockids[1] == j)) {
                for(k = 0; k < (int)strlen(blocks[i].name); k++)
                    if(blocks[i].name[k] == ' ') blocks[i].name[k] = '_';
                printf("%s\r\n", blocks[i].name);
/*                printf("0x%08x\n",blocks[i].color);*/
                break;
            }
}
