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]*)&timestamps[i]);
135 		}
136 	}
137 }
138 
139 struct McChunkInfo
140 {
141 	int x, z;
142 	ubyte[] data;
143 }