VOXELFORMAT: replaced enkimi again with my own (non-working) implementation for minecraft region files
parent
9e64cc8500
commit
cb3eb86d03
6
Makefile
6
Makefile
|
@ -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,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
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 | |
|
||||
|
|
|
@ -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(®ionFile);
|
||||
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
|
||||
|
|
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue