1 /** 2 Copyright: Copyright (c) 2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module mc_region; 7 8 import std.algorithm; 9 import std.bitmanip; 10 import std.file; 11 import std.getopt; 12 import std.path; 13 import std.stdio; 14 15 import voxelman.math; 16 import voxelman.core.config; 17 import voxelman.math.box; 18 19 enum regionExt = ".mca"; 20 enum MC_REGION_WIDTH = 32; 21 enum MC_REGION_WIDTH_SQR = MC_REGION_WIDTH * MC_REGION_WIDTH; 22 enum MC_CHUNK_WIDTH = 16; 23 enum MC_CHUNK_WIDTH_SQR = MC_CHUNK_WIDTH * MC_CHUNK_WIDTH; 24 enum MC_CHUNK_WIDTH_CUBE = MC_CHUNK_WIDTH * MC_CHUNK_WIDTH * MC_CHUNK_WIDTH; 25 enum MC_CHUNK_HEIGHT = 256; 26 enum CHUNKS_PER_MC_REGION_WIDTH = MC_CHUNK_WIDTH * MC_REGION_WIDTH / CHUNK_SIZE; 27 enum CHUNKS_PER_MC_REGION_HEIGHT = MC_CHUNK_HEIGHT / CHUNK_SIZE; 28 29 enum SECTOR_SIZE = 4096; 30 enum McChunkCompression : ubyte { 31 gzip = 1, 32 zlib 33 } 34 35 Box calcRegionBox(int rx, int rz) 36 { 37 int x = rx * CHUNKS_PER_MC_REGION_WIDTH; 38 int y = 0; 39 int z = rz * CHUNKS_PER_MC_REGION_WIDTH; 40 int sx = CHUNKS_PER_MC_REGION_WIDTH; 41 int sy = CHUNKS_PER_MC_REGION_HEIGHT; 42 int sz = CHUNKS_PER_MC_REGION_WIDTH; 43 return Box(ivec3(x, y, z), ivec3(sx, sy, sz)); 44 } 45 46 auto regionIterator(string regionDir) 47 { 48 return dirEntries(regionDir, SpanMode.shallow) 49 .filter!(entry => entry.isFile && extension(entry.name) == regionExt); 50 } 51 52 struct McRegion 53 { 54 ubyte[] buffer; 55 uint[MC_REGION_WIDTH_SQR] offsets; 56 uint[MC_REGION_WIDTH_SQR] timestamps; 57 string path; 58 int x, z; 59 60 /// iterates all chunks in region. 61 int opApply(scope int delegate(McChunkInfo) del) 62 { 63 File file; 64 file.open(path); 65 readHeader(file); 66 67 foreach(chunkIndex; 0..MC_REGION_WIDTH_SQR) 68 { 69 if (offsets[chunkIndex] == 0) 70 continue; 71 72 int x = chunkIndex % MC_REGION_WIDTH; 73 int z = chunkIndex / MC_REGION_WIDTH; 74 75 auto offset = offsets[chunkIndex]; 76 auto sectorNumber = offset >> 8; 77 auto numSectors = offset & 0xFF; 78 79 file.seek(sectorNumber * SECTOR_SIZE); 80 ubyte[4] uintBuffer; 81 file.rawRead(uintBuffer[]); 82 auto dataLength = bigEndianToNative!uint(uintBuffer); 83 ubyte[1] compressionType; 84 file.rawRead(compressionType); 85 86 if (dataLength > numSectors * SECTOR_SIZE) { 87 writefln("Invalid data length (%s, %s), data length (%s) > num sectors (%s) * %s", 88 x, z, dataLength, numSectors, SECTOR_SIZE); 89 continue; 90 } 91 92 ubyte[] data = file.rawRead(buffer[0..dataLength-1]); 93 94 auto chunkInfo = McChunkInfo(x, z, data); 95 96 if (compressionType[0] == McChunkCompression.gzip) { 97 writefln("gzip, skipping"); 98 continue; 99 } 100 else if (compressionType[0] == McChunkCompression.zlib) 101 { 102 import std.zlib; 103 chunkInfo.data = cast(ubyte[])uncompress(data); 104 } 105 106 if (auto ret = del(chunkInfo)) 107 return ret; 108 } 109 return 0; 110 } 111 112 void parseRegionFilename(string regionFile) 113 { 114 import std.regex : matchFirst, regex; 115 import std.conv : to; 116 enum regionPattern = `r\.([-]?[0-9]+)\.([-]?[0-9]+)`; 117 118 path = regionFile; 119 string name = regionFile.baseName.stripExtension; 120 auto c = matchFirst(name, regex(regionPattern, "m")); 121 x = to!int(c[1]); 122 z = to!int(c[2]); 123 } 124 125 void readHeader(ref File file) 126 { 127 file.rawRead(offsets[]); 128 file.rawRead(timestamps[]); 129 130 version(LittleEndian) 131 foreach(i; 0..MC_REGION_WIDTH_SQR) 132 { 133 offsets[i] = bigEndianToNative!uint(*cast(ubyte[4]*)&offsets[i]); 134 timestamps[i] = bigEndianToNative!uint(*cast(ubyte[4]*)×tamps[i]); 135 } 136 } 137 } 138 139 struct McChunkInfo 140 { 141 int x, z; 142 ubyte[] data; 143 }