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