415 lines
17 KiB
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);
|
|
}
|