mtsedit/src/blocks.c

415 lines
17 KiB
C

/*
* mtsedit/blocks.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 Dealing with the data directory and blocks.csv
*
*/
#include "main.h"
char *path = NULL, *fn = NULL, *remapfile = NULL, noblocknametranslate = 0;
#ifndef __WIN32__
char *home = NULL;
#endif
/**
* Get blocks.csv's absolute path
*/
void blocks_getdir(char **argv, char **envp)
{
FILE *f;
int i;
char **env = envp, *p;
/* find our data directory. Environment variable takes preference */
for(env = envp; env && *env; env++) {
if(!memcmp(*env, "MTSDATA=", 8)) {
path = (char*)malloc(strlen(*env)+256);
if(!path) error(lang[ERR_MEM]);
i = strlen(*env)-9;
memcpy(path, *env + 8, i+1);
if(path[i] != DIRSEP) { path[++i] = DIRSEP; i++; }
strcpy(path + i, "blocks.csv");
f = fopen(path, "r");
if(f) { path[i] = 0; fclose(f); }
else { free(path); path = NULL; }
}
#ifndef __WIN32__
if(!memcmp(*env, "HOME=", 5))
home = *env + 5;
#endif
}
#ifndef __WIN32__
/* in user's home */
if(!path && home) {
path = (char*)malloc(strlen(home)+256);
if(!path) error(lang[ERR_MEM]);
i = strlen(home)-1;
memcpy(path, home, i + 1);
if(path[i] != DIRSEP) { path[++i] = DIRSEP; i++; }
/* Linux and BSDs */
strcpy(path + i, ".config/mtsedit/blocks.csv");
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else {
strcpy(path + i, "Library/Application Support/MTSEdit/blocks.csv");
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else { free(path); path = NULL; }
}
}
/* system wide */
if(!path) {
path = (char*)malloc(26+256);
if(!path) error(lang[ERR_MEM]);
strcpy(path, "/usr/share/mtsedit/blocks.csv");
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else {
strcpy(path, "/usr/local/share/mtsedit/blocks.csv");
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else {
strcpy(path, "/Applications/MTSEdit.app/Contents/Resources/data/blocks.csv");
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else { free(path); path = NULL; }
}
}
}
#endif
/* same directory as the executable */
if(!path) {
path = (char*)malloc(strlen(argv[0])+256);
if(!path) error(lang[ERR_MEM]);
strcpy(path, argv[0]);
p = strrchr(path, DIRSEP);
if(p) p++; else { sprintf(path, ".%c", DIRSEP); p = path + 2; }
sprintf(p, "data%cblocks.csv", DIRSEP);
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else {
/* last resort, development repository or /opt/mtsedit/bin, data in parent directory */
sprintf(p, "..%cdata%cblocks.csv", DIRSEP, DIRSEP);
f = fopen(path, "r");
if(f) { path[strlen(path)-10] = 0; fclose(f); }
else { free(path); path = NULL; }
}
}
if(!path) error(lang[ERR_DATADIR]);
fn = path + strlen(path);
}
/**
* Parse the blocks.csv
*/
void blocks_parse()
{
stbi__context sc;
stbi__result_info ri;
int w, h, l, r, g, b, n;
unsigned int numfiles = 0, numbids = 0, i, j, k, m, *tmp;
char **files = NULL, *used, c, *s, *e;
unsigned int size;
unsigned char *img;
char *data = (char*)readfile("blocks.csv", &size);
char *tr = NULL, trname[16], *t, **trl = NULL, *bid = NULL, **bids = NULL;
FILE *f;
if(!data || !size) error(lang[ERR_CSV]);
/* get block images */
*fn = 0;
numfiles = listdir(path, &files, 0);
used = (char*)malloc(numfiles);
if(!used) error(lang[ERR_MEM]);
memset(used, 0, numfiles);
/* read block name dictionary if exists */
if(!noblocknametranslate) {
sprintf(trname, "blocks_%c%c.txt", lang[-1][0], lang[-1][1]);
tr = (char*)readfile(trname, &size);
if(tr) {
for(e = tr, i = 0; *e; e++) {
while(*e == '\r' || *e == '\n' || *e == ' ' || *e == '\t') e++;
trl = (char**)realloc(trl, (i+2) * sizeof(char*));
if(!trl) error(lang[ERR_MEM]);
trl[i++] = e;
while(*e && *e != '\r' && *e != '\n') e++;
if(!*e) break;
*e = 0;
}
if(trl && i) trl[i] = NULL;
}
}
/* read numeric Block ID remapping if specified */
if(remapfile) {
/* don't use readfile, this file might not be in the data directory */
f = fopen(remapfile, "rb");
if(f) {
fseek(f, 0L, SEEK_END);
size = (unsigned int)ftell(f);
fseek(f, 0L, SEEK_SET);
bid = (char*)malloc(size + 1);
if(!bid) error(lang[ERR_MEM]);
fread(bid, size, 1, f);
bid[size] = 0;
fclose(f);
for(e = bid, numbids = 0; *e; e++) {
while(*e == '\r' || *e == '\n' || *e == ' ' || *e == '\t') e++;
bids = (char**)realloc(bids, (numbids+1) * sizeof(char*));
if(!bids) error(lang[ERR_MEM]);
bids[numbids++] = e;
while(*e && *e != '\r' && *e != '\n') e++;
if(!*e) break;
*e = 0;
}
}
}
/* parse header */
for(e = data, i = j = 0; *e && *e != '\r' && *e != '\n' && i < 5; e++) {
if(*e == '\"') j ^= 1;
if(!j && (*e == ',' || *e == ';' || *e == '\t')) i++;
}
if(i != 5 || !*e || *e == '\r' || *e == '\n') errorcsv(1);
while(*e && *e != '\r' && *e != '\n') {
while(*e == ',' || *e == ';' || *e == '\t' || *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--;
j = numpalettes++;
palettes = (char**)realloc(palettes, numpalettes * sizeof(char*));
if(!palettes) error(lang[ERR_MEM]);
palettes[j] = (char*)malloc(e - s + 1);
if(!palettes[j]) error(lang[ERR_MEM]);
memcpy(palettes[j], s, e - s + 1);
palettes[j][e - s] = 0;
if((int)(e - s) > lenpalettes) lenpalettes = e - s;
while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
}
/* add Air as first block */
numblocks++;
blocks = (mtsblock_t*)realloc(blocks, numblocks * sizeof(mtsblock_t));
if(!blocks) error(lang[ERR_MEM]);
memset(&blocks[0], 0, sizeof(mtsblock_t));
for(m = 0, t = NULL; trl && trl[m]; m++)
if(!memcmp(trl[m], "Air=", 4)) { t = trl[m] + 4; break; }
if(!t) t = lang[AIR];
blocks[0].name = (char*)malloc(strlen(t)+1);
if(!blocks[0].name) error(lang[ERR_MEM]);
strcpy(blocks[0].name, t);
blocks[0].blocknames = (char**)malloc((numpalettes + 3) * sizeof(char*));
if(!blocks[0].blocknames) error(lang[ERR_MEM]);
for(i = 0; i < (unsigned int)numpalettes + 3; i++) {
blocks[0].blocknames[i] = (char*)malloc(!i ? 33 : 4);
if(!blocks[0].blocknames[i]) error(lang[ERR_MEM]);
if(!i)
memcpy(blocks[0].blocknames[i], "minecraft:air/minecraft:cave_air", 33);
else
memcpy(blocks[0].blocknames[i], "air", 4);
}
for(i = 0; i < numbids; i++)
if(!strcmp(bids[i], "Air")) { blocks[0].blockids[0] = i; break; }
/* parse rows */
while(*e) {
if(*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((numpalettes + 3) * sizeof(char*));
if(!blocks[j].blocknames) error(lang[ERR_MEM]);
memset(blocks[j].blocknames, 0, (numpalettes + 3) * sizeof(char*));
for(k = 0; s + k < e; k++)
if(s[k] == ' ' || s[k] == '\'') s[k] = '_';
/* translate block name */
for(m = 0, t = s; trl && trl[m]; m++)
if(!memcmp(trl[m], s, k) && trl[m][k] == '=') { t = trl[m] + k + 1; k = strlen(t); break; }
blocks[j].name = (char*)malloc(k + 1);
if(!blocks[j].name) error(lang[ERR_MEM]);
for(i = 0; i < k; i++)
blocks[j].name[i] = t[i] == '_' ? ' ' : t[i];
blocks[j].name[i] = 0;
/* get Block ID remapping if exists */
for(i = 0; i < numbids; i++)
if(!bids[i][k] && !memcmp(bids[i], s, k)) { blocks[j].blockids[0] = i; break; }
/* get block images and possible param2 values */
for(i = 0, l = 0; i < numfiles; i++) {
if((int)strlen(files[i]) == (int)(e - s + 4) && !memcmp(files[i], s, e - s)) {
used[i] = 1;
img = readfile(files[i], &size);
if(img && size) {
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;
blocks[j].img = (unsigned char*)stbi__png_load(&sc, &w, &h, &r, 0, &ri);
free(img);
if(blocks[j].img) {
blocks[j].numpar2 = (h >> 5) & PARAM2_MAX;
if(r != 4) {
tmp = (unsigned int *)malloc(w * h * 4);
if(!tmp) error(lang[ERR_MEM]);
for(m = 0; (int)m < w * h; m++)
switch(r) {
case 1:
tmp[m] = 0xFF000000 | (blocks[j].img[m] << 16) | (blocks[j].img[m] << 8)
| (blocks[j].img[m]);
break;
case 2:
tmp[m] = (blocks[j].img[(m<<1)+1] << 24) | (blocks[j].img[m<<1] << 16) |
(blocks[j].img[m<<1] << 8) | (blocks[j].img[m<<1]);
break;
case 3:
tmp[m] = 0xFF000000 | (blocks[j].img[m*3+2] << 16) | (blocks[j].img[m*3+1] << 8)
| (blocks[j].img[m*3]);
break;
}
free(blocks[j].img);
blocks[j].img = (unsigned char*)tmp;
}
for(m = r = g = b = n = 0; (int)m < w * h * 4; m += 4) {
if(blocks[j].img[m+3] >= 127) {
r += blocks[j].img[m+0];
g += blocks[j].img[m+1];
b += blocks[j].img[m+2];
n++;
}
}
if(n) {
blocks[j].color = 0xFF000000 | (((b/n) & 0xFF) << 16) | (((g/n) & 0xFF) << 8) | ((r/n) & 0xFF);
while(!is_color_unique(blocks[j].color)) {
img = (unsigned char*)&blocks[j].color;
if(img[1] < 255) img[1]++; /* increase green channel first if we can */
else if(img[0] < 255) img[0]++;
else if(img[2] < 255) img[2]++;
else img[3]--; /* for full white, decrease opacity as last resort */
}
} else blocks[j].color = 0xFFFF0000;
l++;
if(!dx || !dz || (!memcmp(s, "Cobblestone", 11) && s[11] != '_' && s[11] != ' ')) {
detcube(32, 32, blocks[j].img);
dx = x1 - x0;
dz = _y1 - _y0;
}
}
}
}
}
while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
if(!l && verbose) { *e = 0; fprintf(stderr, "mtsedit: blocks.csv(%d): %s '%s'\r\n", j+1, lang[ERR_IMG], s); }
while(*e == ' ') e++;
if(*e != ',' && *e != ';' && *e != '\t') errorcsv(j+1);
e++;
while(*e == ' ') e++;
/* get Block IDs */
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++;
for(i = 0; !numbids && s < e && i < 4;) {
blocks[j].blockids[i] = atoi(s);
if(blocks[j].blockids[i]) i++;
while(*s >= '0' && *s <= '9') s++;
if(*s == '/') s++;
}
while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
k = 0;
}
/* get palette specific names */
while(*e == ' ') e++;
if(*e != ',' && *e != ';' && *e != '\t') errorcsv(j+1);
e++;
while(*e == ' ') e++;
if(!*e) errorcsv(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--;
if(e != s) {
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;
}
k++; if(k > (unsigned int)numpalettes + 3) errorcsv(j+2);
while(*e && *e != ',' && *e != ';' && *e != '\t' && *e != '\r' && *e != '\n') e++;
}
if(!numblocks || numblocks > 65535) error(lang[ERR_CSV]);
/* free temp resources */
for(i = 0; i < numfiles; i++) {
if(verbose && !used[i])
fprintf(stderr, "mtsedit: '%s'?\r\n", files[i]);
free(files[i]);
}
if(tr) free(tr);
if(trl) free(trl);
if(bid) free(bid);
if(bids) free(bids);
free(files);
free(used);
free(data);
}
/**
* Free resources
*/
void blocks_free()
{
int i, j;
for(i = 0; i < numpalettes; i++) free(palettes[i]);
free(palettes);
for(i = 0; i < numblocks; i++) {
free(blocks[i].name);
if(blocks[i].blocknames) {
for(j = 0; j < numpalettes + 3; j++)
if(blocks[i].blocknames[j]) free(blocks[i].blocknames[j]);
free(blocks[i].blocknames);
}
if(blocks[i].img) free(blocks[i].img);
if(blocks[i].dr) free(blocks[i].dr);
if(blocks[i].tr) free(blocks[i].tr);
}
free(results);
free(blocks);
}