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]*)&timestamps[i]);
141 		}
142 	}
143 }
144 
145 struct McChunkInfo
146 {
147 	int x, z;
148 	ubyte[] data;
149 }