VOXELFORMAT: replaced enkimi again with my own (non-working) implementation for minecraft region files

master
Martin Gerhardy 2022-02-12 13:45:32 +01:00
parent 9e64cc8500
commit cb3eb86d03
8 changed files with 723 additions and 2875 deletions

View File

@ -213,12 +213,6 @@ update-ogt_vox:
cp $(UPDATEDIR)/ogl_vox.sync/src/ogt_vox.h src/modules/voxelformat/external
sed -i 's/[ \t]*$$//' src/modules/voxelformat/external/ogt_vox.h
update-enkimi:
$(call UPDATE_GIT,enkimi,https://github.com/dougbinks/enkiMI.git)
cp $(UPDATEDIR)/enkimi.sync/src/enkimi.[ch] src/modules/voxelformat/external
sed -i '/miniz.h/d' src/modules/voxelformat/external/enkimi.h
sed -i 's/[ \t]*$$//' src/modules/voxelformat/external/enkimi.[ch]
update-tinyobjloader:
$(call UPDATE_GIT,tinyobjloader,https://github.com/tinyobjloader/tinyobjloader.git)
cp $(UPDATEDIR)/tinyobjloader.sync/tiny_obj_loader.h src/modules/voxelformat/external

1
debian/changelog vendored
View File

@ -1,6 +1,7 @@
vengi (0.0.19.0-1) UNRELEASED; urgency=low
* General:
* Removed minecraft support via external lib enkiMI
-- Martin Gerhardy <martin.gerhardy@gmail.com> Sat, 12 Feb 2022 09:32:14 +0100

View File

@ -10,6 +10,10 @@ Known [issues](https://github.com/mgerhardy/vengi/issues?q=is%3Aissue+is%3Aopen+
## 0.0.19 (2022-XX-XX)
General:
- Removed minecraft support via external lib enkiMI
## 0.0.18 (2022-02-12)
> renamed the github project to **vengi** - the url changed to [https://github.com/mgerhardy/vengi](https://github.com/mgerhardy/vengi).

View File

@ -10,7 +10,7 @@
| CubeWorld | cub | X | X | X | |
| Goxel | gox | X | X | X | |
| MagicaVoxel | vox | X | X | X | X |
| Minecraft Region | mcr | X | | X | X |
| Minecraft Region | mcr | | | X | X |
| Nick's Voxel Model | nvm | X | | X | |
| Qubicle Exchange | qef | X | X | X | |
| Qubicle Binary Tree | qbt | X | X | X | |

View File

@ -3,131 +3,697 @@
*/
#include "MCRFormat.h"
#include "core/Color.h"
#include "SDL_endian.h"
#include "core/Common.h"
#include "core/Log.h"
#include "voxel/RawVolume.h"
#include "voxelutil/VolumeMerger.h"
#include "core/StringUtil.h"
#include "core/Zip.h"
#include "core/collection/DynamicArray.h"
#include "io/File.h"
#include "io/MemoryReadStream.h"
#define MINIZ_NO_STDIO
// Note: we do not need miniz stdio functions so can define MINIZ_NO_STDIO in project to remove them
#include "core/miniz.h"
#include "external/enkimi.c"
#include <glm/common.hpp>
namespace voxel {
namespace _priv {
static const uint32_t mcpalette[] = {
0xff000000, 0xff7d7d7d, 0xff4cb376, 0xff436086, 0xff7a7a7a, 0xff4e7f9c, 0xff256647, 0xff535353, 0xffdcaf70,
0xffdcaf70, 0xff135bcf, 0xff125ad4, 0xffa0d3db, 0xff7a7c7e, 0xff7c8b8f, 0xff7e8287, 0xff737373, 0xff315166,
0xff31b245, 0xff54c3c2, 0xfff4f0da, 0xff867066, 0xff894326, 0xff838383, 0xff9fd3dc, 0xff324364, 0xff3634b4,
0xff23c7f6, 0xff7c7c7c, 0xff77bf8e, 0xffdcdcdc, 0xff296595, 0xff194f7b, 0xff538ba5, 0xff5e96bd, 0xffdddddd,
0xffe5e5e5, 0xff00ffff, 0xff0d00da, 0xff415778, 0xff0d0fe1, 0xff4eecf9, 0xffdbdbdb, 0xffa1a1a1, 0xffa6a6a6,
0xff0630bc, 0xff0026af, 0xff39586b, 0xff658765, 0xff1d1214, 0xff00ffff, 0xff005fde, 0xff31271a, 0xff4e87a6,
0xff2a74a4, 0xff0000ff, 0xff8f8c81, 0xffd5db61, 0xff2e5088, 0xff17593c, 0xff335682, 0xff676767, 0xff00b9ff,
0xff5b9ab8, 0xff387394, 0xff345f79, 0xff5190b6, 0xff6a6a6a, 0xff5b9ab8, 0xff40596a, 0xff7a7a7a, 0xffc2c2c2,
0xff65a0c9, 0xff6b6b84, 0xff2d2ddd, 0xff000066, 0xff0061ff, 0xff848484, 0xfff1f1df, 0xffffad7d, 0xfffbfbef,
0xff1d830f, 0xffb0a49e, 0xff65c094, 0xff3b5985, 0xff42748d, 0xff1b8ce3, 0xff34366f, 0xff334054, 0xff45768f,
0xffbf0a57, 0xff2198f1, 0xffffffec, 0xffb2b2b2, 0xffb2b2b2, 0xffffffff, 0xff2d5d7e, 0xff7c7c7c, 0xff7a7a7a,
0xff7cafcf, 0xff78aaca, 0xff6a6c6d, 0xfff4efd3, 0xff28bdc4, 0xff69dd92, 0xff53ae73, 0xff0c5120, 0xff5287a5,
0xff2a4094, 0xff7a7a7a, 0xff75718a, 0xff767676, 0xff1a162c, 0xff1a162c, 0xff1a162c, 0xff2d28a6, 0xffb1c454,
0xff51677c, 0xff494949, 0xff343434, 0xffd18934, 0xffa5dfdd, 0xff0f090c, 0xff316397, 0xff42a0e3, 0xff4d84a1,
0xff49859e, 0xff1f71dd, 0xffa8e2e7, 0xff74806d, 0xff3c3a2a, 0xff7c7c7c, 0xff5a5a5a, 0xff75d951, 0xff345e81,
0xff84c0ce, 0xff455f88, 0xff868b8e, 0xffd7dd74, 0xff595959, 0xff334176, 0xff008c0a, 0xff17a404, 0xff5992b3,
0xffb0b0b0, 0xff434347, 0xff1d6b9e, 0xff70fdfe, 0xffe5e5e5, 0xff4c4a4b, 0xffbdc6bf, 0xffddedfb, 0xff091bab,
0xff4f547d, 0xff717171, 0xffdfe6ea, 0xffe3e8eb, 0xff41819b, 0xff747474, 0xffa1b2d1, 0xfff6f6f6, 0xff878787,
0xff395ab0, 0xff325cac, 0xff152c47, 0xff65c878, 0xff3534df, 0xffc7c7c7, 0xffa5af72, 0xffbec7ac, 0xff9fd3dc,
0xffcacaca, 0xff425c96, 0xff121212, 0xfff4bfa2, 0xff1474cf, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xff1d56ac,
#define wrap(expression) \
do { \
if ((expression) != 0) { \
Log::error("Could not load file: Not enough data in stream " CORE_STRINGIFY(#expression) " at " SDL_FILE \
":%i", \
SDL_LINE); \
return false; \
} \
} while (0)
0xff1d57ae, 0xff1d57ae, 0xff1d57ae, 0xff243c50, 0xff8dcddd, 0xff4d7aaf, 0xff0e2034, 0xff366bcf, 0xff355d7e,
0xff7bb8c7, 0xff5f86bb, 0xff1e2e3f, 0xff3a6bc5, 0xff30536e, 0xffe0f3f7, 0xff5077a9, 0xff2955aa, 0xff21374e,
0xffcdc5dc, 0xff603b60, 0xff856785, 0xffa679a6, 0xffaa7eaa, 0xffa879a8, 0xffa879a8, 0xffa879a8, 0xffaae6e1,
0xffaae6e1, 0xff457d98, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0,
0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0,
0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0,
0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0,
0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0,
0xfff0f0f0, 0xfff0f0f0, 0xfff0f0f0, 0xff242132
};
}
#define wrapBool(expression) \
do { \
if (!(expression)) { \
Log::error("Could not load file: Not enough data in stream " CORE_STRINGIFY(#expression) " at " SDL_FILE \
":%i", \
SDL_LINE); \
return false; \
} \
} while (0)
size_t MCRFormat::loadPalette(const core::String &filename, io::SeekableReadStream& file, core::Array<uint32_t, 256> &palette) {
for (int i = 0; i < lengthof(_priv::mcpalette); ++i) {
palette[i] = _priv::mcpalette[i];
}
return lengthof(_priv::mcpalette);
void MCRFormat::reset() {
core_memset(_chunkTimestamps, 0, sizeof(_chunkTimestamps));
}
bool MCRFormat::loadGroups(const core::String &filename, io::SeekableReadStream &stream, SceneGraph &sceneGraph) {
enkiRegionFile regionFile;
enkiRegionFileInit(&regionFile);
regionFile.regionDataSize = stream.size();
uint8_t *buffer = (uint8_t *)core_malloc(stream.size());
if (stream.read(buffer, stream.size()) == -1) {
core_free(buffer);
reset();
const int64_t length = stream.size();
if (length < SECTOR_BYTES) {
Log::error("File does not contain enough data");
return false;
}
regionFile.pRegionData = buffer;
core::DynamicArray<voxel::RawVolume *> rawVolumes;
_paletteSize = lengthof(_priv::mcpalette);
for (size_t i = 0; i < _paletteSize; ++i) {
_palette[i] = findClosestIndex(core::Color::fromRGBA(_priv::mcpalette[i]));
core::String name = filename.toLower();
int chunkX = 0;
int chunkZ = 0;
char type = 'a';
if (SDL_sscanf(name.c_str(), "r.%i.%i.mc%c", &chunkX, &chunkZ, &type) != 3) {
Log::warn("Failed to parse the region chunk boundaries from filename %s", name.c_str());
const core::String &extension = core::string::extractExtension(filename);
type = extension.last();
chunkX = 0;
chunkZ = 0;
}
for (int i = 0; i < ENKI_MI_REGION_CHUNKS_NUMBER; i++) {
enkiNBTDataStream stream;
enkiInitNBTDataStreamForChunk(regionFile, i, &stream);
if (stream.dataLength) {
enkiChunkBlockData aChunk = enkiNBTReadChunk(&stream);
// TODO: use aChunk.palette
enkiMICoordinate chunkOriginPos = enkiGetChunkOrigin(&aChunk); // y always 0
Log::debug("chunk at %d:%d:%d: Number of sections: %d", chunkOriginPos.x, chunkOriginPos.y,
chunkOriginPos.z, aChunk.countOfSections);
// iterate through chunk and count non 0 voxels as a demo
int64_t numVoxels = 0;
for (int section = 0; section < ENKI_MI_NUM_SECTIONS_PER_CHUNK; ++section) {
if (aChunk.sections[section]) {
enkiMICoordinate sectionOrigin = enkiGetChunkSectionOrigin(&aChunk, section);
Log::debug(" non empty section at %d:%d:%d", sectionOrigin.x, sectionOrigin.y,
sectionOrigin.z);
enkiMICoordinate sPos;
const glm::ivec3 mins(sectionOrigin.x, sectionOrigin.y, sectionOrigin.z);
const glm::ivec3 size(ENKI_MI_SIZE_SECTIONS - 1, ENKI_MI_SIZE_SECTIONS - 1,
ENKI_MI_SIZE_SECTIONS - 1);
const voxel::Region region(mins, mins + size);
voxel::RawVolume *v = new voxel::RawVolume(region);
for (sPos.y = 0; sPos.y < ENKI_MI_SIZE_SECTIONS; ++sPos.y) {
for (sPos.z = 0; sPos.z < ENKI_MI_SIZE_SECTIONS; ++sPos.z) {
for (sPos.x = 0; sPos.x < ENKI_MI_SIZE_SECTIONS; ++sPos.x) {
const uint8_t color = enkiGetChunkSectionVoxel(&aChunk, section, sPos);
if (color) {
const uint8_t index = convertPaletteIndex(color);
const voxel::Voxel voxel = voxel::createVoxel(voxel::VoxelType::Generic, index);
v->setVoxel(mins.x + sPos.x, mins.y + sPos.y, mins.z + sPos.z, voxel);
}
}
}
}
rawVolumes.push_back(v);
}
}
Log::debug(" chunk has %g non zero voxels\n", (float)numVoxels);
enkiNBTRewind(&stream);
uint8_t *buffer = new uint8_t[length];
if (stream.read(buffer, length) != 0) {
Log::error("Could not read the file into target buffer");
return false;
}
switch (type) {
case 'r':
case 'a': {
const int64_t fileSize = stream.remaining();
if (fileSize <= 2l * SECTOR_BYTES) {
Log::error("This region file has not enough data for the 8kb header");
return false;
}
for (int i = 0; i < SECTOR_INTS; ++i) {
union {
uint8_t raw[4];
struct Offset {
uint32_t offset : 24;
uint32_t sectors : 8;
} offset;
} data;
for (int j = 0; j < 4; ++j) {
wrap(stream.readUInt8(data.raw[j]));
}
_offsets[i].sectorCount = data.offset.sectors;
const uint32_t value = data.offset.offset;
_offsets[i].offset = SDL_SwapBE32(value) * SECTOR_BYTES;
}
for (int i = 0; i < SECTOR_INTS; ++i) {
uint32_t lastModValue;
wrap(stream.readUInt32BE(lastModValue));
_chunkTimestamps[i] = lastModValue;
}
const bool success = loadMinecraftRegion(sceneGraph, buffer, (int)length, stream, chunkX, chunkZ);
delete[] buffer;
return success;
}
}
delete[] buffer;
Log::error("Unkown file type given: %c", type);
return false;
}
bool MCRFormat::loadMinecraftRegion(SceneGraph &sceneGraph, const uint8_t *buffer, int length, io::SeekableReadStream &stream,
int chunkX, int chunkZ) {
for (int i = 0; i < SECTOR_INTS; ++i) {
if (_offsets[i].sectorCount == 0u || _offsets[i].offset == 0u) {
continue;
}
if (_offsets[i].offset >= (uint32_t)length) {
Log::error("Exceeded stream boundaries: %u, %i", (int)_offsets[i].offset, length);
return false;
}
wrap(stream.seek(_offsets[i].offset));
if (!readCompressedNBT(sceneGraph, buffer, length, stream)) {
Log::error("Failed to load minecraft chunk section %i for offset %u", i, (int)_offsets[i].offset);
return false;
}
enkiNBTFreeAllocations(&stream);
}
SceneGraphNode node(SceneGraphNodeType::Model);
node.setVolume(::voxel::merge(rawVolumes), true);
sceneGraph.emplace(core::move(node));
for (voxel::RawVolume *v : rawVolumes) {
delete v;
}
core_free(buffer);
return true;
}
bool MCRFormat::readCompressedNBT(SceneGraph &sceneGraph, const uint8_t *buffer, int length, io::SeekableReadStream &stream) {
uint32_t nbtSize;
wrap(stream.readUInt32BE(nbtSize));
if (nbtSize == 0) {
Log::debug("Empty nbt chunk found");
return true;
}
if (nbtSize > 0x1FFFFFF) {
Log::error("Size of nbt data exceeds the max allowed value: %u", nbtSize);
return false;
}
uint8_t version;
wrap(stream.readUInt8(version));
if (version != VERSION_GZIP && version != VERSION_DEFLATE) {
Log::error("Unsupported version found: %u", version);
return false;
}
// the version is included in the length
--nbtSize;
const uint32_t sizeHint = nbtSize * 50; // TODO: improve this
uint8_t *nbtData = new uint8_t[sizeHint];
size_t finalBufSize;
if (!core::zip::uncompress(buffer + stream.pos(), nbtSize, nbtData, sizeHint, &finalBufSize)) {
delete[] nbtData;
Log::error("Failed to uncompress nbt data of compressed size %u", nbtSize);
return false;
}
const bool success = parseNBTChunk(sceneGraph, nbtData, (int)finalBufSize);
delete[] nbtData;
return success;
}
// this list was found in enkiMI by Doug Binks
static const struct {
const core::String name;
const uint8_t id;
} TYPES[] = {{"minecraft:air", 0},
{"minecraft:stone", 1},
{"minecraft:andesite", 1},
{"minecraft:seagrass", 2},
{"minecraft:grass", 2},
{"minecraft:grass_block", 2},
{"minecraft:dirt", 3},
{"minecraft:cobblestone", 4},
{"minecraft:oak_planks", 5},
{"minecraft:oak_sapling", 6},
{"minecraft:bedrock", 7},
{"minecraft:flowing_water", 8},
{"minecraft:water", 9},
{"minecraft:flowing_lava", 10},
{"minecraft:lava", 11},
{"minecraft:sand", 12},
{"minecraft:gravel", 13},
{"minecraft:gold_ore", 14},
{"minecraft:iron_ore", 15},
{"minecraft:coal_ore", 16},
{"minecraft:oak_log", 17},
{"minecraft:acacia_leaves", 18},
{"minecraft:spruce_leaves", 18},
{"minecraft:oak_leaves", 18},
{"minecraft:jungle_leaves", 18},
{"minecraft:birch_leaves", 18},
{"minecraft:sponge", 19},
{"minecraft:glass", 20},
{"minecraft:lapis_ore", 21},
{"minecraft:lapis_block", 22},
{"minecraft:dispenser", 23},
{"minecraft:sandstone", 24},
{"minecraft:note_block", 25},
{"minecraft:red_bed", 26},
{"minecraft:powered_rail", 27},
{"minecraft:detector_rail", 28},
{"minecraft:sticky_piston", 29},
{"minecraft:cobweb", 30},
{"minecraft:tall_grass", 31},
{"minecraft:tall_seagrass", 31},
{"minecraft:dead_bush", 32},
{"minecraft:piston", 33},
{"minecraft:piston_head", 34},
{"minecraft:white_concrete", 35},
{"minecraft:dandelion", 37},
{"minecraft:poppy", 38},
{"minecraft:brown_mushroom", 39},
{"minecraft:red_mushroom", 40},
{"minecraft:gold_block", 41},
{"minecraft:iron_block", 42},
{"minecraft:smooth_stone_slab", 43},
{"minecraft:stone_slab", 44},
{"minecraft:brick_wall", 45},
{"minecraft:bricks", 45},
{"minecraft:tnt", 46},
{"minecraft:bookshelf", 47},
{"minecraft:mossy_cobblestone", 48},
{"minecraft:obsidian", 49},
{"minecraft:torch", 50},
{"minecraft:fire", 51},
{"minecraft:spawner", 52},
{"minecraft:oak_stairs", 53},
{"minecraft:chest", 54},
{"minecraft:redstone_wire", 55},
{"minecraft:diamond_ore", 56},
{"minecraft:diamond_block", 57},
{"minecraft:crafting_table", 58},
{"minecraft:wheat", 59},
{"minecraft:farmland", 60},
{"minecraft:furnace", 61},
{"minecraft:campfire", 62},
{"minecraft:oak_sign", 63},
{"minecraft:oak_door", 64},
{"minecraft:ladder", 65},
{"minecraft:rail", 66},
{"minecraft:stone_stairs", 67},
{"minecraft:oak_wall_sign", 68},
{"minecraft:lever", 69},
{"minecraft:stone_pressure_plate", 70},
{"minecraft:iron_door", 71},
{"minecraft:oak_pressure_plate", 72},
{"minecraft:redstone_ore", 73},
{"minecraft:red_concrete", 74},
{"minecraft:redstone_wall_torch", 75},
{"minecraft:redstone_torch", 76},
{"minecraft:stone_button", 77},
{"minecraft:snow_block", 78},
{"minecraft:ice", 79},
{"minecraft:snow", 80},
{"minecraft:cactus", 81},
{"minecraft:clay", 82},
{"minecraft:bamboo", 83},
{"minecraft:jukebox", 84},
{"minecraft:oak_fence", 85},
{"minecraft:pumpkin", 86},
{"minecraft:netherrack", 87},
{"minecraft:soul_sand", 88},
{"minecraft:glowstone", 89},
{"minecraft:portal", 90},
{"minecraft:carved_pumpkin", 91},
{"minecraft:cake", 92},
{"minecraft:repeater", 93},
{"minecraft:skeleton_skull", 94},
{"minecraft:white_stained_glass", 95},
{"minecraft:oak_trapdoor", 96},
{"minecraft:turtle_egg", 97},
{"minecraft:stone_bricks", 98},
{"minecraft:brown_mushroom_block", 99},
{"minecraft:red_mushroom_block", 100},
{"minecraft:iron_bars", 101},
{"minecraft:light_blue_stained_glass_pane", 102},
{"minecraft:melon", 103},
{"minecraft:pumpkin_stem", 104},
{"minecraft:melon_stem", 105},
{"minecraft:vine", 106},
{"minecraft:oak_fence_gate", 107},
{"minecraft:brick_stairs", 108},
{"minecraft:stone_brick_stairs", 109},
{"minecraft:mycelium", 110},
{"minecraft:light_gray_concrete", 111},
{"minecraft:nether_brick", 112},
{"minecraft:nether_brick_fence", 113},
{"minecraft:nether_brick_stairs", 114},
{"minecraft:nether_wart", 115},
{"minecraft:enchanting_table", 116},
{"minecraft:brewing_stand", 117},
{"minecraft:cauldron", 118},
{"minecraft:end_portal", 119},
{"minecraft:end_portal_frame", 120},
{"minecraft:end_stone", 121},
{"minecraft:dragon_egg", 122},
{"minecraft:redstone_lamp", 123},
{"minecraft:shroomlight", 124},
{"minecraft:oak_wood", 125},
{"minecraft:oak_slab", 126},
{"minecraft:cocoa", 127},
{"minecraft:sandstone_stairs", 128},
{"minecraft:emerald_ore", 129},
{"minecraft:ender_chest", 130},
{"minecraft:tripwire_hook", 131},
{"minecraft:tripwire", 132},
{"minecraft:emerald_block", 133},
{"minecraft:spruce_stairs", 134},
{"minecraft:birch_stairs", 135},
{"minecraft:jungle_stairs", 136},
{"minecraft:command_block", 137},
{"minecraft:beacon", 138},
{"minecraft:cobblestone_wall", 139},
{"minecraft:flower_pot", 140},
{"minecraft:carrots", 141},
{"minecraft:potatoes", 142},
{"minecraft:oak_button", 143},
{"minecraft:skeleton_wall_skull", 144},
{"minecraft:anvil", 145},
{"minecraft:trapped_chest", 146},
{"minecraft:light_weighted_pressure_plate", 147},
{"minecraft:heavy_weighted_pressure_plate", 148},
{"minecraft:comparator", 149},
{"minecraft:chain", 150},
{"minecraft:daylight_detector", 151},
{"minecraft:redstone_block", 152},
{"minecraft:nether_quartz_ore", 153},
{"minecraft:hopper", 154},
{"minecraft:quartz_block", 155},
{"minecraft:quartz_stairs", 156},
{"minecraft:activator_rail", 157},
{"minecraft:dropper", 158},
{"minecraft:pink_stained_glass", 159},
{"minecraft:white_stained_glass_pane", 160},
{"minecraft:dead_brain_coral", 161},
{"minecraft:acacia_planks", 162},
{"minecraft:acacia_stairs", 163},
{"minecraft:dark_oak_stairs", 164},
{"minecraft:slime_block", 165},
{"minecraft:barrier", 166},
{"minecraft:iron_trapdoor", 167},
{"minecraft:prismarine", 168},
{"minecraft:sea_lantern", 169},
{"minecraft:hay_block", 170},
{"minecraft:white_carpet", 171},
{"minecraft:coarse_dirt", 172},
{"minecraft:coal_block", 173},
{"minecraft:packed_ice", 174},
{"minecraft:orange_concrete", 175},
{"minecraft:white_banner", 176},
{"minecraft:white_wall_banner", 177},
{"minecraft:white_concrete_powder", 178},
{"minecraft:red_sandstone", 179},
{"minecraft:red_sandstone_stairs", 180},
{"minecraft:red_sandstone_wall", 181},
{"minecraft:red_sandstone_slab", 182},
{"minecraft:spruce_fence_gate", 183},
{"minecraft:birch_fence_gate", 184},
{"minecraft:jungle_fence_gate", 185},
{"minecraft:dark_oak_fence_gate", 186},
{"minecraft:acacia_fence_gate", 187},
{"minecraft:spruce_fence", 188},
{"minecraft:birch_fence", 189},
{"minecraft:jungle_fence", 190},
{"minecraft:dark_oak_fence", 191},
{"minecraft:acacia_fence", 192},
{"minecraft:spruce_door", 193},
{"minecraft:birch_door", 194},
{"minecraft:jungle_door", 195},
{"minecraft:acacia_door", 196},
{"minecraft:dark_oak_door", 197},
{"minecraft:end_rod", 198},
{"minecraft:chorus_plant", 199},
{"minecraft:chorus_flower", 200},
{"minecraft:purpur_block", 201},
{"minecraft:purpur_pillar", 202},
{"minecraft:purpur_stairs", 203},
{"minecraft:purple_stained_glass", 204},
{"minecraft:purpur_slab", 205},
{"minecraft:end_stone_bricks", 206},
{"minecraft:beetroots", 207},
{"minecraft:grass_path", 208},
{"minecraft:end_gateway", 209},
{"minecraft:repeating_command_block", 210},
{"minecraft:chain_command_block", 211},
{"minecraft:frosted_ice", 212},
{"minecraft:magma_block", 213},
{"minecraft:nether_wart_block", 214},
{"minecraft:red_nether_bricks", 215},
{"minecraft:bone_block", 216},
{"minecraft:structure_void", 217},
{"minecraft:observer", 218},
{"minecraft:white_shulker_box", 219},
{"minecraft:orange_shulker_box", 220},
{"minecraft:magenta_shulker_box", 221},
{"minecraft:light_blue_shulker_box", 222},
{"minecraft:yellow_shulker_box", 223},
{"minecraft:lime_shulker_box", 224},
{"minecraft:pink_shulker_box", 225},
{"minecraft:gray_shulker_box", 226},
{"minecraft:light_gray_shulker_box", 227},
{"minecraft:cyan_shulker_box", 228},
{"minecraft:purple_shulker_box", 229},
{"minecraft:blue_shulker_box", 230},
{"minecraft:brown_shulker_box", 231},
{"minecraft:green_shulker_box", 232},
{"minecraft:red_shulker_box", 233},
{"minecraft:black_shulker_box", 234},
{"minecraft:white_glazed_terracotta", 235},
{"minecraft:orange_glazed_terracotta", 236},
{"minecraft:magenta_glazed_terracotta", 237},
{"minecraft:light_blue_glazed_terracotta", 238},
{"minecraft:yellow_glazed_terracotta", 239},
{"minecraft:lime_glazed_terracotta", 240},
{"minecraft:pink_glazed_terracotta", 241},
{"minecraft:gray_glazed_terracotta", 242},
{"minecraft:light_gray_glazed_terracotta", 243},
{"minecraft:cyan_glazed_terracotta", 244},
{"minecraft:purple_glazed_terracotta", 245},
{"minecraft:blue_glazed_terracotta", 246},
{"minecraft:brown_glazed_terracotta", 247},
{"minecraft:green_glazed_terracotta", 248},
{"minecraft:red_glazed_terracotta", 249},
{"minecraft:black_glazed_terracotta", 250},
{"minecraft:gray_concrete", 251},
{"minecraft:gray_concrete_powder", 252},
{"minecraft:structure_block", 255}};
bool MCRFormat::parseNBTChunk(SceneGraph &sceneGraph, const uint8_t *buffer, int length) {
io::MemoryReadStream stream(buffer, length);
MCRFormat::NamedBinaryTag root;
wrapBool(getNext(stream, root));
if (root.id != TagId::COMPOUND) {
Log::error("Failed to read root compound");
return false;
}
int32_t xPos = 0;
int32_t zPos = 0;
const uint8_t *sectionPointers[16];
MCRFormat::NamedBinaryTag nbt;
while (!stream.eos()) {
wrapBool(getNext(stream, nbt));
Log::debug("Name: %s", nbt.name.c_str());
if (nbt.name == "Level") {
while (!stream.eos()) {
wrapBool(getNext(stream, nbt));
if (nbt.name == "xPos" && nbt.id == TagId::INT) {
wrap(stream.readUInt32BE((uint32_t &)xPos));
} else if (nbt.name == "zPos" && nbt.id == TagId::INT) {
wrap(stream.readUInt32BE((uint32_t &)zPos));
} else if (nbt.name == "Sections" && nbt.id == TagId::LIST) {
TagId listId;
uint32_t sections;
wrap(stream.read(&listId, sizeof(listId)));
if (listId != TagId::COMPOUND) {
Log::error("Unexpected section tag id: %i", (int)listId);
return false;
}
wrap(stream.readUInt32BE(sections));
Log::debug("Found %u Sections (type: %i)", sections, (int)listId);
for (uint32_t i = 0; i < sections; ++i) {
int expectedLevel = nbt.level - 1;
uint8_t y = 0;
const uint8_t *blockStates = nullptr;
const uint8_t *blocks = nullptr;
while (!stream.eos()) {
wrapBool(getNext(stream, nbt));
if (nbt.id == TagId::END) {
if (nbt.level == expectedLevel) {
Log::debug("Found section end");
break;
}
Log::debug("Found sub compound, continue the loop at level %i", nbt.level);
}
if (nbt.name == "Y" && nbt.id == TagId::BYTE) {
wrap(stream.read(&y, sizeof(y)));
if (y >= lengthof(sectionPointers)) {
Log::error("section y value exceeds the max allowed value of 15: %u", y);
return false;
}
Log::debug("Section y: %u", y);
} else if (nbt.name == "BlockStates" && nbt.id == TagId::LONG_ARRAY) {
uint32_t arrayLength;
wrap(stream.readUInt32BE(arrayLength));
blockStates = buffer + stream.pos();
Log::debug("Found %u blockstates, %u", arrayLength, (uint32_t)stream.remaining());
wrapBool(stream.skip(arrayLength * 8));
} else if (nbt.name == "Palette" && nbt.id == TagId::LIST) {
TagId paletteListId;
uint32_t palettes;
wrap(stream.read(&paletteListId, sizeof(paletteListId)));
if (paletteListId != TagId::COMPOUND) {
Log::error("Unexpected palette tag id: %i", (int)paletteListId);
return false;
}
wrap(stream.readUInt32BE(palettes));
Log::debug("Found %u palettes (type: %i)", palettes, (int)paletteListId);
core::DynamicArray<uint32_t> idMapping(palettes);
uint32_t numBlockIDs = lengthof(TYPES);
uint32_t paletteNum = 0;
for (uint32_t p = 0; p < palettes; ++p) {
wrapBool(getNext(stream, nbt));
if (nbt.name == "Name" && nbt.id == TagId::STRING) {
uint16_t nameLength;
wrap(stream.readUInt16BE(nameLength));
Log::debug("Load palette name of length %u", nameLength);
core::String name;
for (uint16_t i = 0u; i < nameLength; ++i) {
uint8_t chr;
wrap(stream.readUInt8(chr));
name += (char)chr;
}
Log::debug("Palette name: %s", name.c_str());
idMapping[paletteNum] = 0;
for (uint32_t id = 0; id < numBlockIDs; ++id) {
if (TYPES[id].name != name) {
continue;
}
idMapping[paletteNum] = id;
break;
}
++paletteNum;
} else {
wrapBool(skip(stream, nbt.id));
}
}
} else if (nbt.name == "Blocks" && nbt.id == TagId::LONG_ARRAY) {
uint32_t arrayLength;
wrap(stream.readUInt32BE(arrayLength));
Log::debug("Found %u Blocks, %u", arrayLength, (uint32_t)stream.remaining());
blocks = buffer + stream.pos();
Log::debug("Found %u blockstates, %u", arrayLength, (uint32_t)stream.remaining());
wrapBool(stream.skip(arrayLength * 8));
} else {
Log::debug("Skip %s", nbt.name.c_str());
wrapBool(skip(stream, nbt.id));
}
}
if (blocks != nullptr) {
sectionPointers[y] = blocks;
}
if (blockStates) {
sectionPointers[y] = blockStates;
}
}
} else if (nbt.name == "Heightmaps" && nbt.id == TagId::COMPOUND) {
int expectedLevel = nbt.level - 1;
while (getNext(stream, nbt)) {
if (nbt.id == TagId::END && nbt.level == expectedLevel) {
break;
}
wrapBool(skip(stream, nbt.id));
}
} else if ((nbt.name == "TileTicks" || nbt.name == "ToBeTicked" || nbt.name == "TileEntities" ||
nbt.name == "Entities") &&
nbt.id == TagId::LIST) {
wrapBool(skip(stream, nbt.id));
} else if (nbt.name == "Status" && nbt.id == TagId::STRING) {
wrapBool(skip(stream, nbt.id));
} else if (nbt.name == "LastUpdate" && nbt.id == TagId::LONG) {
wrapBool(skip(stream, nbt.id));
} else if (nbt.name == "InhabitedTime" && nbt.id == TagId::LONG) {
wrapBool(skip(stream, nbt.id));
} else if (nbt.name == "Biomes" && nbt.id == TagId::INT_ARRAY) {
wrapBool(skip(stream, nbt.id));
} else {
Log::trace("skip %s: %u", nbt.name.c_str(), (uint32_t)stream.remaining());
wrapBool(skip(stream, nbt.id));
Log::trace("after skip %s: %u", nbt.name.c_str(), (uint32_t)stream.remaining());
}
}
} else {
Log::trace("skip %s: %u", nbt.name.c_str(), (uint32_t)stream.remaining());
wrapBool(skip(stream, nbt.id));
Log::trace("after skip %s: %u", nbt.name.c_str(), (uint32_t)stream.remaining());
}
}
return true;
}
bool MCRFormat::skip(io::SeekableReadStream &stream, TagId id) {
switch (id) {
case TagId::BYTE:
Log::debug("skip 1 byte");
wrapBool(stream.skip(1));
break;
case TagId::SHORT:
Log::debug("skip 2 bytes");
wrapBool(stream.skip(2));
break;
case TagId::INT:
case TagId::FLOAT:
Log::debug("skip 4 bytes");
wrapBool(stream.skip(4));
break;
case TagId::LONG:
case TagId::DOUBLE:
Log::debug("skip 8 bytes");
wrapBool(stream.skip(8));
break;
case TagId::BYTE_ARRAY: {
uint32_t length;
wrap(stream.readUInt32BE(length));
Log::debug("skip %u bytes", length + 4);
wrapBool(stream.skip(length));
break;
}
case TagId::STRING: {
uint16_t length;
wrap(stream.readUInt16BE(length));
Log::debug("skip %u bytes", length + 2);
wrapBool(stream.skip(length));
break;
}
case TagId::INT_ARRAY: {
uint32_t length;
wrap(stream.readUInt32BE(length));
Log::debug("skip %u bytes", length * 4 + 4);
wrapBool(stream.skip(length * 4));
break;
}
case TagId::LONG_ARRAY: {
uint32_t length;
wrap(stream.readUInt32BE(length));
Log::debug("skip %u bytes", length * 8 + 4);
wrapBool(stream.skip(length * 8));
break;
}
case TagId::LIST: {
TagId listId;
uint32_t length;
wrap(stream.read(&listId, sizeof(listId)));
wrap(stream.readUInt32BE(length));
Log::debug("skip 5 bytes + %u list elements following", length);
for (uint32_t i = 0; i < length; ++i) {
skip(stream, listId);
}
break;
}
case TagId::COMPOUND: {
MCRFormat::NamedBinaryTag compound;
compound.level = 1;
Log::debug("skip compound");
while (compound.level > 0) {
wrapBool(getNext(stream, compound));
wrapBool(skip(stream, compound.id));
}
break;
}
case TagId::END:
break;
default:
Log::warn("Unknown tag %i", (int)id);
break;
}
return true;
}
bool MCRFormat::getNext(io::SeekableReadStream &stream, MCRFormat::NamedBinaryTag &nbt) {
wrap(stream.read(&nbt.id, sizeof(nbt.id)));
if (nbt.id == TagId::END) {
Log::debug("Found compound end at level %i", nbt.level);
--nbt.level;
return true;
}
uint16_t nameLength;
wrap(stream.readUInt16BE(nameLength));
Log::debug("Load string of length %u", nameLength);
nbt.name.clear();
for (uint16_t i = 0u; i < nameLength; ++i) {
uint8_t chr;
wrap(stream.readUInt8(chr));
nbt.name += (char)chr;
}
Log::debug("Found tag %s at level %i", nbt.name.c_str(), nbt.level);
if (nbt.id == TagId::COMPOUND) {
++nbt.level;
}
return true;
}
#undef wrap
#undef wrapBool
} // namespace voxel

View File

@ -43,15 +43,58 @@ namespace voxel {
* @endcode
*
* @note https://github.com/Voxtric/Minecraft-Level-Ripper/blob/master/WorldConverterV2/Processor.cs
* @note https://github.com/dougbinks/enkiMI/
*/
class MCRFormat : public Format {
private:
static constexpr int VERSION_GZIP = 1;
static constexpr int VERSION_DEFLATE = 2;
static constexpr int SECTOR_BYTES = 4096;
static constexpr int SECTOR_INTS = SECTOR_BYTES / 4;
static constexpr int CHUNK_HEADER_SIZE = 5;
static constexpr int MAX_LEVELS = 512;
enum class TagId : uint8_t {
END = 0,
BYTE = 1,
SHORT = 2,
INT = 3,
LONG = 4,
FLOAT = 5,
DOUBLE = 6,
BYTE_ARRAY = 7,
STRING = 8,
LIST = 9,
COMPOUND = 10,
INT_ARRAY = 11,
LONG_ARRAY = 12
};
struct NamedBinaryTag {
core::String name;
TagId id = TagId::END;
int level = 0;
};
struct Offsets {
uint32_t offset : 24;
uint32_t sectorCount : 8;
} _offsets[SECTOR_INTS];
uint32_t _chunkTimestamps[SECTOR_INTS];
void reset();
bool skip(io::SeekableReadStream &stream, TagId id);
bool getNext(io::SeekableReadStream &stream, NamedBinaryTag& nbt);
bool parseNBTChunk(SceneGraph& sceneGraph, const uint8_t* buffer, int length);
bool readCompressedNBT(SceneGraph& sceneGraph, const uint8_t* buffer, int length, io::SeekableReadStream &stream);
bool loadMinecraftRegion(SceneGraph& sceneGraph, const uint8_t* buffer, int length, io::SeekableReadStream &stream, int chunkX, int chunkZ);
public:
bool loadGroups(const core::String &filename, io::SeekableReadStream& stream, SceneGraph& sceneGraph) override;
bool saveGroups(const SceneGraph& sceneGraph, const core::String &filename, io::SeekableWriteStream& stream) override {
reset();
return false;
}
size_t loadPalette(const core::String &filename, io::SeekableReadStream& file, core::Array<uint32_t, 256> &palette) override;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,281 +0,0 @@
/*
* Copyright (c) 2017 Juliette Foucaut & Doug Binks
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgement in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
// Note: we do not need miniz stdio functions so can define MINIZ_NO_STDIO in project to remove them
#ifdef __cplusplus
extern "C" {
#endif
#define ENKI_MI_REGION_CHUNKS_NUMBER 1024
// http://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt
typedef enum
{
enkiNBTTAG_End = 0,
enkiNBTTAG_Byte = 1,
enkiNBTTAG_Short = 2,
enkiNBTTAG_Int = 3,
enkiNBTTAG_Long = 4,
enkiNBTTAG_Float = 5,
enkiNBTTAG_Double = 6,
enkiNBTTAG_Byte_Array = 7,
enkiNBTTAG_String = 8,
enkiNBTTAG_List = 9,
enkiNBTTAG_Compound = 10,
enkiNBTTAG_Int_Array = 11,
enkiNBTTAG_Long_Array = 12,
enkiNBTTAG_SIZE,
} enkiNBTTAG_ID;
typedef struct enkiNBTTagHeader_s
{
char* pName;
// if the tag is a list, we need the following variables
int32_t listNumItems;
int32_t listCurrItem;
uint8_t listItemTagId;
// the tagId of type enkiNBTTAG_ID
uint8_t tagId;
} enkiNBTTagHeader;
// Get enkiNBTTAG_ID as string
const char* enkiGetNBTTagIDAsString( uint8_t tagID_ );
// Shorthand for enkiGetNBTTagIDAsString( tagID_.tagId );
const char* enkiGetNBTTagHeaderIDAsString( enkiNBTTagHeader tagID_ );
typedef struct enkiNBTAllocation_s
{
void* pAllocation;
struct enkiNBTAllocation_s* pNext;
} enkiNBTAllocation;
typedef struct enkiNBTDataStream_s
{
enkiNBTTagHeader parentTags[ 512 ];
enkiNBTTagHeader currentTag;
uint8_t* pCurrPos;
uint8_t* pDataEnd;
uint8_t* pData;
uint8_t* pNextTag;
enkiNBTAllocation* pAllocations;
uint32_t dataLength;
int32_t level;
} enkiNBTDataStream;
// Initialize stream from memory pointer.
// pData_ and it's contents should remain valid until
// after enkiNBTDataStream no longer needed.
// Contents of buffer will be modified for easier reading,
// namely tag name strings will moved down a byte, null terminated,
// and prefixed with 0xFF instead of int16_t string length.
// Make a copy if you need to use the buffer in another lib.
// Other strings in file will not be altered.
// pUnCompressedData_ should be freed by caller.
// FreeMemoryAllocated() should still be called to free any internal allocations.
void enkiNBTInitFromMemoryUncompressed( enkiNBTDataStream* pStream_, uint8_t* pUnCompressedData_, uint32_t dataSize_ );
// Initialize stream from memory pointer to compressed content.
// This function will allocate space for uncompressed stream and decompress it with zlib.
// If uncompressedSizeHint_ > compressedDataSize_ it will be used as the starting hint size for allocating
// the uncompressed size.
// returns 1 if successfull, 0 if not.
int enkiNBTInitFromMemoryCompressed( enkiNBTDataStream* pStream_, uint8_t* pCompressedData_,
uint32_t compressedDataSize_, uint32_t uncompressedSizeHint_ );
// returns 0 if no next tag, 1 if there was
int enkiNBTReadNextTag( enkiNBTDataStream* pStream_ );
// Rewind stream so it can be read again from beginning
void enkiNBTRewind( enkiNBTDataStream* pStream_ );
// Frees any internally allocated memory.
void enkiNBTFreeAllocations( enkiNBTDataStream* pStream_ );
int8_t enkiNBTReadInt8( enkiNBTDataStream* pStream_ );
int8_t enkiNBTReadByte( enkiNBTDataStream* pStream_ );
int16_t enkiNBTReadInt16( enkiNBTDataStream* pStream_ );
int16_t enkiNBTReadShort( enkiNBTDataStream* pStream_ );
int32_t enkiNBTReadInt32( enkiNBTDataStream* pStream_ );
int32_t enkiNBTReadInt( enkiNBTDataStream* pStream_ );
float enkiNBTReadFloat( enkiNBTDataStream* pStream_ );
int64_t enkiNBTReadInt64( enkiNBTDataStream* pStream_ );
int64_t enkiNBTReadlong( enkiNBTDataStream* pStream_ );
double enkiNBTReadDouble( enkiNBTDataStream* pStream_ );
typedef struct enkiNBTString_s
{
uint16_t size;
const char* pStrNotNullTerminated;
} enkiNBTString;
enkiNBTString enkiNBTReadString( enkiNBTDataStream* pStream_ );
typedef struct enkiRegionFile_s
{
uint8_t* pRegionData;
uint32_t regionDataSize;
} enkiRegionFile;
// enkiRegionFileInit simply zeros data
void enkiRegionFileInit( enkiRegionFile* pRegionFile_ );
enkiRegionFile enkiRegionFileLoad( FILE* fp_ );
// 1 for a chunk exists, 0 for does not.
uint8_t enkiHasChunk( enkiRegionFile regionFile_, int32_t chunkNr_ );
void enkiInitNBTDataStreamForChunk( enkiRegionFile regionFile_, int32_t chunkNr_, enkiNBTDataStream* pStream_ );
int32_t enkiGetTimestampForChunk( enkiRegionFile regionFile_, int32_t chunkNr_ );
// enkiFreeRegionFileData frees data allocated in enkiRegionFile
void enkiRegionFileFreeAllocations( enkiRegionFile* pRegionFile_ );
// Check if lhs_ and rhs_ are equal, return 1 if so, 0 if not.
// Safe to pass in NULL for either
// Note that both NULL gives 0.
int enkiAreStringsEqual( const char* lhs_, const char* rhs_ );
// World height changes (1.17 21w06a) increase num sections to a potential 256 (-128 to 127 as Y uses signed byte)
#define ENKI_MI_NUM_SECTIONS_PER_CHUNK 256
#define ENKI_MI_SECTIONS_Y_OFFSET 128
#define ENKI_MI_SIZE_SECTIONS 16
typedef struct enkiMICoordinate_s
{
int32_t x;
int32_t y; // height
int32_t z;
} enkiMICoordinate;
typedef struct enkiMINamespaceAndBlockID_s
{
const char* pNamespaceID; // e.g. "minecraft:stone"
uint8_t blockID; // block ID returned by enkiGetChunkSectionVoxel and enkiGetChunkSectionVoxelData
uint8_t dataValue; // dataValue returned by enkiGetChunkSectionVoxelData
} enkiMINamespaceAndBlockID;
typedef struct enkiMIProperty_s {
char* pName;
enkiNBTString value;
} enkiMIProperty;
// ENKI_MI_MAX_PROPERTIES can be modified but 6 appears to be the maximum
#ifndef ENKI_MI_MAX_PROPERTIES
#define ENKI_MI_MAX_PROPERTIES 6
#endif
typedef struct enkiMIProperties_s {
uint32_t size; // capped to ENKI_MI_MAX_PROPERTIES
enkiMIProperty properties[ ENKI_MI_MAX_PROPERTIES ];
} enkiMIProperties;
typedef struct enkiChunkSectionPalette_s
{
uint32_t size;
uint32_t numBitsPerBlock;
uint32_t blockArraySize;
int32_t* pDefaultBlockIndex; // lookup index into the default enkiMINamespaceAndBlockIDTable - these values may change with versions of enkiMI, <0 means not found
enkiNBTString* pNamespaceIDStrings; // e.g. "minecraft:stone"
enkiMIProperties* pBlockStateProperties; // pointer to start of stream properties
} enkiChunkSectionPalette;
typedef struct enkiChunkBlockData_s
{
uint8_t* sections[ ENKI_MI_NUM_SECTIONS_PER_CHUNK ];
uint8_t* dataValues[ ENKI_MI_NUM_SECTIONS_PER_CHUNK ];
enkiChunkSectionPalette palette[ ENKI_MI_NUM_SECTIONS_PER_CHUNK ]; // if there is a palette[k].size, then sections[k] represents BlockStates
int32_t xPos; // section coordinates
int32_t zPos; // section coordinates
int32_t countOfSections;
int32_t dataVersion;
} enkiChunkBlockData;
// enkiChunkInit simply zeros data
void enkiChunkInit( enkiChunkBlockData* pChunk_ );
// enkiNBTReadChunk gets a chunk from an enkiNBTDataStream
// pStream_ mush be kept valid whilst chunk is in use.
enkiChunkBlockData enkiNBTReadChunk( enkiNBTDataStream* pStream_ );
// Extended parameters for enkiNBTReadChunkEx:
typedef enum
{
enkiNBTReadChunkExFlags_None = 0,
enkiNBTReadChunkExFlags_NoPaletteTranslation = 1 << 0, // when loading palette do not translate namespace strings to blockID & dataValue - faster if you want to do your own translation / conversion to internal data
} enkiNBTReadChunkExFlags;
typedef struct enkiNBTReadChunkExParams_s
{
int32_t flags; // enkiNBTReadChunkExFlags defaults to enkiNBTReadChunkExFlags_None
} enkiNBTReadChunkExParams;
// call enkiGetDefaultNBTReadChunkExParams to set up default parameters - essential to maintain forwards compatibilty if new members are added to enkiNBTReadChunkExParams
enkiNBTReadChunkExParams enkiGetDefaultNBTReadChunkExParams();
// enkiNBTReadChunkEx is as enkiNBTReadChunk but with extended parameters.
enkiChunkBlockData enkiNBTReadChunkEx( enkiNBTDataStream* pStream_, enkiNBTReadChunkExParams params_ );
enkiMICoordinate enkiGetChunkOrigin( enkiChunkBlockData* pChunk_ );
// get the origin of a section (0 <-> ENKI_MI_NUM_SECTIONS_PER_CHUNK).
enkiMICoordinate enkiGetChunkSectionOrigin( enkiChunkBlockData* pChunk_, int32_t section_ );
// sectionOffset_ is the position from enkiGetChunkSectionOrigin
// Performs no safety checks.
// check pChunk_->sections[ section_ ] for NULL first in your code.
// and ensure sectionOffset_ coords with 0 to ENKI_MI_SIZE_SECTIONS
uint8_t enkiGetChunkSectionVoxel( enkiChunkBlockData* pChunk_, int32_t section_, enkiMICoordinate sectionOffset_ );
uint32_t* enkiGetMineCraftPalette(); //returns a 256 array of uint32_t's in uint8_t rgba order.
typedef struct enkiMIVoxelData_s {
uint8_t blockID; // pre-flattening blockIDs values, as returned by enkiGetChunkSectionVoxel(), can use to index into enkiGetMineCraftPalette
uint8_t dataValue; // pre-flattening data values, blockId::dataValue identifies block varients
int32_t paletteIndex; // if >=0 index into enkiChunkBlockData.palette[section].pDefaultBlockIndex and enkiChunkBlockData.palette[section].pNamespaceIDStrings
} enkiMIVoxelData;
enkiMIVoxelData enkiGetChunkSectionVoxelData( enkiChunkBlockData* pChunk_, int32_t section_, enkiMICoordinate sectionOffset_ );
typedef struct enkiMINamespaceAndBlockIDTable_s {
uint32_t size;
enkiMINamespaceAndBlockID* namespaceAndBlockIDs;
} enkiMINamespaceAndBlockIDTable;
enkiMINamespaceAndBlockIDTable enkiGetNamespaceAndBlockIDTable();
#ifdef __cplusplus
};
#endif