|
|
#include <assert.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#define FAST_CHUNK // disabling this enables the old, slower path that deblocks into a regular form
#include "cave_parse.h"
#include "stb_image.h"
#include "stb.h"
#define NUM_CHUNKS_PER_REGION 32 // only on one axis
#define NUM_CHUNKS_PER_REGION_LOG2 5
#define NUM_COLUMNS_PER_CHUNK 16
#define NUM_COLUMNS_PER_CHUNK_LOG2 4
uint32 read_uint32_be(FILE *f) { unsigned char data[4]; fread(data, 1, 4, f); return (data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3]; }
typedef struct { uint8 *data; size_t len; int x,z; // chunk index
int refcount; // for multi-threading
} compressed_chunk;
typedef struct { int x,z; uint32 sector_data[NUM_CHUNKS_PER_REGION][NUM_CHUNKS_PER_REGION]; } region;
size_t cached_compressed=0;
FILE *last_region; int last_region_x; int last_region_z; int opened=0;
static void open_file(int reg_x, int reg_z) { if (!opened || last_region_x != reg_x || last_region_z != reg_z) { char filename[256]; if (last_region != NULL) fclose(last_region); sprintf(filename, "r.%d.%d.mca", reg_x, reg_z); last_region = fopen(filename, "rb"); last_region_x = reg_x; last_region_z = reg_z; opened = 1; } }
static region *load_region(int reg_x, int reg_z) { region *r; int x,z;
open_file(reg_x, reg_z);
r = malloc(sizeof(*r));
if (last_region == NULL) { memset(r, 0, sizeof(*r)); } else { fseek(last_region, 0, SEEK_SET); for (z=0; z < NUM_CHUNKS_PER_REGION; ++z) for (x=0; x < NUM_CHUNKS_PER_REGION; ++x) r->sector_data[z][x] = read_uint32_be(last_region); } r->x = reg_x; r->z = reg_z;
return r; }
void free_region(region *r) { free(r); }
#define MAX_MAP_REGIONS 64 // in one axis: 64 regions * 32 chunk/region * 16 columns/chunk = 16384 columns
region *regions[MAX_MAP_REGIONS][MAX_MAP_REGIONS];
static region *get_region(int reg_x, int reg_z) { int slot_x = reg_x & (MAX_MAP_REGIONS-1); int slot_z = reg_z & (MAX_MAP_REGIONS-1); region *r;
r = regions[slot_z][slot_x];
if (r) { if (r->x == reg_x && r->z == reg_z) return r; free_region(r); }
r = load_region(reg_x, reg_z); regions[slot_z][slot_x] = r;
return r; }
// about one region, so size should be ok
#define NUM_CACHED_X 64
#define NUM_CACHED_Z 64
// @TODO: is it really worth caching these? we probably can just
// pull them from the disk cache nearly as efficiently.
// Can test that by setting to 1x1?
compressed_chunk *cached_chunk[NUM_CACHED_Z][NUM_CACHED_X];
static void deref_compressed_chunk(compressed_chunk *cc) { assert(cc->refcount > 0); --cc->refcount; if (cc->refcount == 0) { if (cc->data) free(cc->data); free(cc); } }
static compressed_chunk *get_compressed_chunk(int chunk_x, int chunk_z) { int slot_x = chunk_x & (NUM_CACHED_X-1); int slot_z = chunk_z & (NUM_CACHED_Z-1); compressed_chunk *cc = cached_chunk[slot_z][slot_x];
if (cc && cc->x == chunk_x && cc->z == chunk_z) return cc; else { int reg_x = chunk_x >> NUM_CHUNKS_PER_REGION_LOG2; int reg_z = chunk_z >> NUM_CHUNKS_PER_REGION_LOG2; region *r = get_region(reg_x, reg_z); if (cc) { deref_compressed_chunk(cc); cached_chunk[slot_z][slot_x] = NULL; } cc = malloc(sizeof(*cc)); cc->x = chunk_x; cc->z = chunk_z; { int subchunk_x = chunk_x & (NUM_CHUNKS_PER_REGION-1); int subchunk_z = chunk_z & (NUM_CHUNKS_PER_REGION-1); uint32 code = r->sector_data[subchunk_z][subchunk_x];
if (code & 255) { open_file(reg_x, reg_z); fseek(last_region, (code>>8)*4096, SEEK_SET); cc->len = (code&255)*4096; cc->data = malloc(cc->len); fread(cc->data, 1, cc->len, last_region); } else { cc->len = 0; cc->data = 0; } } cc->refcount = 1; cached_chunk[slot_z][slot_x] = cc; return cc; } }
// NBT parser -- can automatically parse stuff we don't
// have definitions for, but want to explicitly parse
// stuff we do have definitions for.
//
// option 1: auto-parse everything into data structures,
// then read those
//
// option 2: have a "parse next object" which
// doesn't resolve whether it expands its children
// yet, and then the user either says "expand" or
// "skip" after looking at the name. Anything with
// "children" without names can't go through this
// interface.
//
// Let's try option 2.
typedef struct { unsigned char *buffer_start; unsigned char *buffer_end; unsigned char *cur; int nesting; char temp_buffer[256]; } nbt;
enum { TAG_End=0, TAG_Byte=1, TAG_Short=2, TAG_Int=3, TAG_Long=4, TAG_Float=5, TAG_Double=6, TAG_Byte_Array=7, TAG_String=8, TAG_List=9, TAG_Compound=10, TAG_Int_Array=11 };
static void nbt_get_string_data(unsigned char *data, char *buffer, size_t bufsize) { int len = data[0]*256 + data[1]; int i; for (i=0; i < len && i+1 < (int) bufsize; ++i) buffer[i] = (char) data[i+2]; buffer[i] = 0; }
static char *nbt_peek(nbt *n) { unsigned char type = *n->cur; if (type == TAG_End) return NULL; nbt_get_string_data(n->cur+1, n->temp_buffer, sizeof(n->temp_buffer)); return n->temp_buffer; }
static uint32 nbt_parse_uint32(unsigned char *buffer) { return (buffer[0] << 24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; }
static void nbt_skip(nbt *n);
// skip an item that doesn't have an id or name prefix (usable in lists)
static void nbt_skip_raw(nbt *n, unsigned char type) { switch (type) { case TAG_Byte : n->cur += 1; break; case TAG_Short : n->cur += 2; break; case TAG_Int : n->cur += 4; break; case TAG_Long : n->cur += 8; break; case TAG_Float : n->cur += 4; break; case TAG_Double: n->cur += 8; break; case TAG_Byte_Array: n->cur += 4 + 1*nbt_parse_uint32(n->cur); break; case TAG_Int_Array : n->cur += 4 + 4*nbt_parse_uint32(n->cur); break; case TAG_String : n->cur += 2 + (n->cur[0]*256 + n->cur[1]); break; case TAG_List : { unsigned char list_type = *n->cur++; unsigned int list_len = nbt_parse_uint32(n->cur); unsigned int i; n->cur += 4; // list_len
for (i=0; i < list_len; ++i) nbt_skip_raw(n, list_type); break; } case TAG_Compound : { while (*n->cur != TAG_End) nbt_skip(n); nbt_skip(n); // skip the TAG_end
break; } } assert(n->cur <= n->buffer_end); }
static void nbt_skip(nbt *n) { unsigned char type = *n->cur++; if (type == TAG_End) return; // skip name
n->cur += (n->cur[0]*256 + n->cur[1]) + 2; nbt_skip_raw(n, type); }
// byteswap
static void nbt_swap(unsigned char *ptr, int len) { int i; for (i=0; i < (len>>1); ++i) { unsigned char t = ptr[i]; ptr[i] = ptr[len-1-i]; ptr[len-1-i] = t; } }
// pass in the expected type, fail if doesn't match
// returns a pointer to the data, byteswapped if appropriate
static void *nbt_get_fromlist(nbt *n, unsigned char type, int *len) { unsigned char *ptr; assert(type != TAG_Compound); assert(type != TAG_List); // we could support getting lists of primitives as if they were arrays, but eh
if (len) *len = 1; ptr = n->cur; switch (type) { case TAG_Byte : break;
case TAG_Short : nbt_swap(ptr, 2); break; case TAG_Int : nbt_swap(ptr, 4); break; case TAG_Long : nbt_swap(ptr, 8); break; case TAG_Float : nbt_swap(ptr, 4); break; case TAG_Double: nbt_swap(ptr, 8); break;
case TAG_Byte_Array: *len = nbt_parse_uint32(ptr); ptr += 4; break; case TAG_Int_Array: { int i; *len = nbt_parse_uint32(ptr); ptr += 4; for (i=0; i < *len; ++i) nbt_swap(ptr + 4*i, 4); break; }
default: assert(0); // unhandled case
} nbt_skip_raw(n, type); return ptr; }
static void *nbt_get(nbt *n, unsigned char type, int *len) { assert(n->cur[0] == type); n->cur += 3 + (n->cur[1]*256+n->cur[2]); return nbt_get_fromlist(n, type, len); }
static void nbt_begin_compound(nbt *n) // start a compound
{ assert(*n->cur == TAG_Compound); // skip header
n->cur += 3 + (n->cur[1]*256 + n->cur[2]); ++n->nesting; }
static void nbt_begin_compound_in_list(nbt *n) // start a compound
{ ++n->nesting; }
static void nbt_end_compound(nbt *n) // end a compound
{ assert(*n->cur == TAG_End); assert(n->nesting != 0); ++n->cur; --n->nesting; }
// @TODO no interface to get lists from lists
static int nbt_begin_list(nbt *n, unsigned char type) { uint32 len; unsigned char *ptr;
ptr = n->cur + 3 + (n->cur[1]*256 + n->cur[2]); if (ptr[0] != type) return -1; n->cur = ptr; len = nbt_parse_uint32(n->cur+1); assert(n->cur[0] == type); // @TODO keep a stack with the count to make sure they do it right
++n->nesting; n->cur += 5; return (int) len; }
static void nbt_end_list(nbt *n) { --n->nesting; }
// raw_block chunk is 16x256x16x4 = 2^(4+8+4+2) = 256KB
//
// if we want to process 64x64x256 at a time, that will be:
// 4*4*256KB => 4MB per area in raw_block
//
// (plus we maybe need to decode adjacent regions)
#ifdef FAST_CHUNK
typedef fast_chunk parse_chunk; #else
typedef chunk parse_chunk; #endif
static parse_chunk *minecraft_chunk_parse(unsigned char *data, size_t len) { char *s; parse_chunk *c = NULL;
nbt n_store, *n = &n_store; n->buffer_start = data; n->buffer_end = data + len; n->cur = n->buffer_start; n->nesting = 0;
nbt_begin_compound(n); while ((s = nbt_peek(n)) != NULL) { if (!strcmp(s, "Level")) { int *height; c = malloc(sizeof(*c)); #ifdef FAST_CHUNK
memset(c, 0, sizeof(*c)); c->pointer_to_free = data; #else
c->rb[15][15][255].block = 0; #endif
c->max_y = 0;
nbt_begin_compound(n); while ((s = nbt_peek(n)) != NULL) { if (!strcmp(s, "xPos")) c->xpos = *(int *) nbt_get(n, TAG_Int, 0); else if (!strcmp(s, "zPos")) c->zpos = *(int *) nbt_get(n, TAG_Int, 0); else if (!strcmp(s, "Sections")) { int count = nbt_begin_list(n, TAG_Compound), i; if (count == -1) { // this not-a-list case happens in The End and I'm not sure
// what it means... possibly one of those silly encodings
// where it's not encoded as a list if there's only one?
// not worth figuring out
nbt_skip(n); count = -1; } for (i=0; i < count; ++i) { int yi, len; uint8 *light = NULL, *blocks = NULL, *data = NULL, *skylight = NULL; nbt_begin_compound_in_list(n); while ((s = nbt_peek(n)) != NULL) { if (!strcmp(s, "Y")) yi = * (uint8 *) nbt_get(n, TAG_Byte, 0); else if (!strcmp(s, "BlockLight")) { light = nbt_get(n, TAG_Byte_Array, &len); assert(len == 2048); } else if (!strcmp(s, "Blocks")) { blocks = nbt_get(n, TAG_Byte_Array, &len); assert(len == 4096); } else if (!strcmp(s, "Data")) { data = nbt_get(n, TAG_Byte_Array, &len); assert(len == 2048); } else if (!strcmp(s, "SkyLight")) { skylight = nbt_get(n, TAG_Byte_Array, &len); assert(len == 2048); } } nbt_end_compound(n);
assert(yi < 16);
#ifndef FAST_CHUNK
// clear data below current max_y
{ int x,z; while (c->max_y < yi*16) { for (x=0; x < 16; ++x) for (z=0; z < 16; ++z) c->rb[z][x][c->max_y].block = 0; ++c->max_y; } }
// now assemble the data
{ int x,y,z, o2=0,o4=0; for (y=0; y < 16; ++y) { for (z=0; z < 16; ++z) { for (x=0; x < 16; x += 2) { raw_block *rb = &c->rb[15-z][x][y + yi*16]; // 15-z because switching to z-up will require flipping an axis
rb[0].block = blocks[o4]; rb[0].light = light[o2] & 15; rb[0].data = data[o2] & 15; rb[0].skylight = skylight[o2] & 15;
rb[256].block = blocks[o4+1]; rb[256].light = light[o2] >> 4; rb[256].data = data[o2] >> 4; rb[256].skylight = skylight[o2] >> 4;
o2 += 1; o4 += 2; } } } c->max_y += 16; } #else
c->blockdata[yi] = blocks; c->data [yi] = data; c->light [yi] = light; c->skylight [yi] = skylight; #endif
} //nbt_end_list(n);
} else if (!strcmp(s, "HeightMap")) { height = nbt_get(n, TAG_Int_Array, &len); assert(len == 256); } else nbt_skip(n); } nbt_end_compound(n);
} else nbt_skip(n); } nbt_end_compound(n); assert(n->cur == n->buffer_end); return c; }
#define MAX_DECODED_CHUNK_X 64
#define MAX_DECODED_CHUNK_Z 64
typedef struct { int cx,cz; fast_chunk *fc; int valid; } decoded_buffer;
static decoded_buffer decoded_buffers[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; void lock_chunk_get_mutex(void); void unlock_chunk_get_mutex(void);
#ifdef FAST_CHUNK
fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z) { unsigned char *decoded; compressed_chunk *cc; int inlen; int len; fast_chunk *fc;
lock_chunk_get_mutex(); cc = get_compressed_chunk(chunk_x, chunk_z); if (cc->len != 0) ++cc->refcount; unlock_chunk_get_mutex();
if (cc->len == 0) return NULL;
assert(cc != NULL);
assert(cc->data[4] == 2);
inlen = nbt_parse_uint32(cc->data); decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); assert(decoded != NULL); assert(len != 0);
lock_chunk_get_mutex(); deref_compressed_chunk(cc); unlock_chunk_get_mutex();
#ifdef FAST_CHUNK
fc = minecraft_chunk_parse(decoded, len); #else
fc = NULL; #endif
if (fc == NULL) free(decoded); return fc; }
decoded_buffer *get_decoded_buffer(int chunk_x, int chunk_z) { decoded_buffer *db = &decoded_buffers[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; if (db->valid) { if (db->cx == chunk_x && db->cz == chunk_z) return db; if (db->fc) { free(db->fc->pointer_to_free); free(db->fc); } }
db->cx = chunk_x; db->cz = chunk_z; db->valid = 1; db->fc = 0;
{ db->fc = get_decoded_fastchunk_uncached(chunk_x, chunk_z); return db; } }
fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z) { decoded_buffer *db = get_decoded_buffer(chunk_x, chunk_z); return db->fc; } #endif
#ifndef FAST_CHUNK
chunk *get_decoded_chunk_raw(int chunk_x, int chunk_z) { unsigned char *decoded; compressed_chunk *cc = get_compressed_chunk(chunk_x, chunk_z); assert(cc != NULL); if (cc->len == 0) return NULL; else { chunk *ch; int inlen = nbt_parse_uint32(cc->data); int len; assert(cc->data[4] == 2); decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); assert(decoded != NULL); #ifdef FAST_CHUNK
ch = NULL; #else
ch = minecraft_chunk_parse(decoded, len); #endif
free(decoded); return ch; } }
static chunk *decoded_chunks[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; chunk *get_decoded_chunk(int chunk_x, int chunk_z) { chunk *c = decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; if (c && c->xpos == chunk_x && c->zpos == chunk_z) return c; if (c) free(c); c = get_decoded_chunk_raw(chunk_x, chunk_z); decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)] = c; return c; } #endif
|