PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
mcregion/RegionLoader.php
1 <?php
2 
3 /*
4  *
5  * ____ _ _ __ __ _ __ __ ____
6  * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7  * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8  * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9  * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * @author PocketMine Team
17  * @link http://www.pocketmine.net/
18  *
19  *
20 */
21 
22 namespace pocketmine\level\format\mcregion;
23 
36 
38  const VERSION = 1;
39  const COMPRESSION_GZIP = 1;
40  const COMPRESSION_ZLIB = 2;
41  const MAX_SECTOR_LENGTH = 32 << 12; //32 sectors
42  public static $COMPRESSION_LEVEL = 7;
43 
44  protected $x;
45  protected $z;
46  protected $filePath;
47  protected $filePointer;
48  protected $lastSector;
50  protected $levelProvider;
51  protected $locationTable = [];
52 
53  public function __construct(LevelProvider $level, $regionX, $regionZ){
54  $this->x = $regionX;
55  $this->z = $regionZ;
56  $this->levelProvider = $level;
57  $this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.mcr";
58  $exists = file_exists($this->filePath);
59  touch($this->filePath);
60  $this->filePointer = fopen($this->filePath, "r+b");
61  stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB
62  stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB
63  if(!$exists){
64  $this->createBlank();
65  }else{
66  $this->loadLocationTable();
67  }
68  }
69 
70  public function __destruct(){
71  if(is_resource($this->filePointer)){
72  $this->writeLocationTable();
73  fclose($this->filePointer);
74  }
75  }
76 
77  protected function isChunkGenerated($index){
78  return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0);
79  }
80 
81  public function readChunk($x, $z, $generate = true, $forward = false){
82  $index = self::getChunkOffset($x, $z);
83  if($index < 0 or $index >= 4096){
84  //Regenerate chunk due to corruption
85  $this->locationTable[$index][0] = 0;
86  $this->locationTable[$index][1] = 1;
87  }
88 
89  if(!$this->isChunkGenerated($index)){
90  if($generate === true){
91  //Allocate space
92  $this->locationTable[$index][0] = ++$this->lastSector;
93  $this->locationTable[$index][1] = 1;
94  fseek($this->filePointer, $this->locationTable[$index][0] << 12);
95  fwrite($this->filePointer, str_pad(Binary::writeInt(-1) . chr(self::COMPRESSION_ZLIB), 4096, "\x00", STR_PAD_RIGHT));
96  $this->writeLocationIndex($index);
97  }else{
98  return false;
99  }
100  }
101 
102  fseek($this->filePointer, $this->locationTable[$index][0] << 12);
103  $length = Binary::readInt(fread($this->filePointer, 4));
104  $compression = ord(fgetc($this->filePointer));
105 
106  if($length <= 0){ //Not yet generated
107  $this->generateChunk($x, $z);
108  fseek($this->filePointer, $this->locationTable[$index][0] << 12);
109  $length = Binary::readInt(fread($this->filePointer, 4));
110  $compression = ord(fgetc($this->filePointer));
111  }
112 
113  if($length >= self::MAX_SECTOR_LENGTH){
114  MainLogger::getLogger()->error("Corrupted chunk header detected");
115 
116  return false;
117  }
118 
119  if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
120  MainLogger::getLogger()->error("Corrupted bigger chunk detected");
121  $this->locationTable[$index][1] = $length >> 12;
122  $this->writeLocationIndex($index);
123  }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
124  MainLogger::getLogger()->error("Invalid compression type");
125 
126  return false;
127  }
128 
129  $chunk = Chunk::fromBinary(fread($this->filePointer, $length - 1), $this->levelProvider);
130  if($chunk instanceof Chunk){
131  return $chunk;
132  }elseif($forward === false){
133  MainLogger::getLogger()->error("Corrupted chunk detected");
134  $this->generateChunk($x, $z);
135 
136  return $this->readChunk($x, $z, $generate, true);
137  }else{
138  return null;
139  }
140  }
141 
142  public function chunkExists($x, $z){
143  return $this->isChunkGenerated(self::getChunkOffset($x, $z));
144  }
145 
146  public function generateChunk($x, $z){
147  $nbt = new Compound("Level", []);
148  $nbt->xPos = new Int("xPos", ($this->getX() * 32) + $x);
149  $nbt->zPos = new Int("zPos", ($this->getZ() * 32) + $z);
150  $nbt->LastUpdate = new Long("LastUpdate", 0);
151  $nbt->LightPopulated = new Byte("LightPopulated", 0);
152  $nbt->TerrainPopulated = new Byte("TerrainPopulated", 0);
153  $nbt->V = new Byte("V", self::VERSION);
154  $nbt->InhabitedTime = new Long("InhabitedTime", 0);
155  $nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256));
156  $nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127));
157  $nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")));
158 
159  $nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768));
160  $nbt->Data = new ByteArray("Data", $half = str_repeat("\x00", 16384));
161  $nbt->SkyLight = new ByteArray("SkyLight", $half);
162  $nbt->BlockLight = new ByteArray("BlockLight", $half);
163 
164  $nbt->Entities = new Enum("Entities", []);
165  $nbt->Entities->setTagType(NBT::TAG_Compound);
166  $nbt->TileEntities = new Enum("TileEntities", []);
167  $nbt->TileEntities->setTagType(NBT::TAG_Compound);
168  $nbt->TileTicks = new Enum("TileTicks", []);
169  $nbt->TileTicks->setTagType(NBT::TAG_Compound);
170  $writer = new NBT(NBT::BIG_ENDIAN);
171  $nbt->setName("Level");
172  $writer->setData(new Compound("", ["Level" => $nbt]));
173  $chunkData = $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, self::$COMPRESSION_LEVEL);
174 
175  $this->saveChunk($x, $z, $chunkData);
176  }
177 
178  protected function saveChunk($x, $z, $chunkData){
179  $length = strlen($chunkData) + 1;
180  $sectors = (int) ceil(($length + 4) / 4096);
181  $index = self::getChunkOffset($x, $z);
182  if($this->locationTable[$index][1] < $sectors){
183  $this->locationTable[$index][0] = $this->lastSector += $sectors; //The GC will clean this shift later
184  }
185  $this->locationTable[$index][1] = $sectors;
186 
187  fseek($this->filePointer, $this->locationTable[$index][0] << 12);
188  fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT));
189  $this->writeLocationIndex($index);
190  }
191 
192  public function removeChunk($x, $z){
193  $index = self::getChunkOffset($x, $z);
194  $this->locationTable[$index][0] = 0;
195  $this->locationTable[$index][1] = 0;
196  }
197 
198  public function writeChunk(FullChunk $chunk){
199  $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunk->toBinary());
200  }
201 
202  protected static function getChunkOffset($x, $z){
203  return $x + ($z << 5);
204  }
205 
206  public function close(){
207  $this->writeLocationTable();
208  fclose($this->filePointer);
209  $this->levelProvider = null;
210  }
211 
212  public function doSlowCleanUp(){
213  for($i = 0; $i < 1024; ++$i){
214  if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){
215  continue;
216  }
217  fseek($this->filePointer, $this->locationTable[$i][0] << 12);
218  $chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12);
219  $length = Binary::readInt(substr($chunk, 0, 4));
220  if($length <= 1){
221  $this->locationTable[$i] = [0, 0, 0]; //Non-generated chunk, remove it from index
222  }
223 
224  try{
225  $chunk = zlib_decode(substr($chunk, 5));
226  }catch(\Exception $e){
227  $this->locationTable[$i] = [0, 0, 0]; //Corrupted chunk, remove it
228  continue;
229  }
230 
231  $chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, 15, 9);
232  $chunk = Binary::writeInt(strlen($chunk)) . $chunk;
233  $sectors = (int) ceil(strlen($chunk) / 4096);
234  if($sectors > $this->locationTable[$i][1]){
235  $this->locationTable[$i][0] = $this->lastSector += $sectors;
236  }
237  fseek($this->filePointer, $this->locationTable[$i][0] << 12);
238  fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT));
239  }
240  $this->writeLocationTable();
241  $n = $this->cleanGarbage();
242  $this->writeLocationTable();
243 
244  return $n;
245  }
246 
247  private function cleanGarbage(){
248  $sectors = [];
249  foreach($this->locationTable as $index => $data){ //Calculate file usage
250  if($data[0] === 0 or $data[1] === 0){
251  $this->locationTable[$index] = [0, 0, 0];
252  continue;
253  }
254  for($i = 0; $i < $data[1]; ++$i){
255  $sectors[$data[0]] = $index;
256  }
257  }
258 
259  if(count($sectors) === ($this->lastSector - 2)){ //No collection needed
260  return 0;
261  }
262 
263  ksort($sectors);
264  $shift = 0;
265  $lastSector = 1; //First chunk - 1
266 
267  fseek($this->filePointer, 8192);
268  $sector = 2;
269  foreach($sectors as $sector => $index){
270  if(($sector - $lastSector) > 1){
271  $shift += $sector - $lastSector - 1;
272  }
273  if($shift > 0){
274  fseek($this->filePointer, $sector << 12);
275  $old = fread($this->filePointer, 4096);
276  fseek($this->filePointer, ($sector - $shift) << 12);
277  fwrite($this->filePointer, $old, 4096);
278  }
279  $this->locationTable[$index][0] -= $shift;
280  $lastSector = $sector;
281  }
282  ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written
283  return $shift;
284  }
285 
286  protected function loadLocationTable(){
287  fseek($this->filePointer, 0);
288  $this->lastSector = 1;
289  $table = fread($this->filePointer, 4 * 1024 * 2);
290  for($i = 0; $i < 1024; ++$i){
291  $index = unpack("N", substr($table, $i << 2, 4))[1];
292  $this->locationTable[$i] = [($index & ~0xff) >> 8, $index & 0xff, unpack("N", substr($table, 4096 + ($i << 2), 4))[1]];
293  if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){
294  $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1;
295  }
296  }
297  }
298 
299  private function writeLocationTable(){
300  $write = [];
301 
302  for($i = 0; $i < 1024; ++$i){
303  $write[] = ($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1];
304  }
305  for($i = 0; $i < 1024; ++$i){
306  $write[] = $this->locationTable[$i][2];
307  }
308  fseek($this->filePointer, 0);
309  fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2);
310  }
311 
312  protected function writeLocationIndex($index){
313  fseek($this->filePointer, $index << 2);
314  fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4);
315  fseek($this->filePointer, 4096 + ($index << 2));
316  fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4);
317  }
318 
319  protected function createBlank(){
320  fseek($this->filePointer, 0);
321  ftruncate($this->filePointer, 0);
322  $this->lastSector = 1;
323  $table = "";
324  for($i = 0; $i < 1024; ++$i){
325  $this->locationTable[$i] = [0, 0];
326  $table .= Binary::writeInt(0);
327  }
328 
329  $time = time();
330  for($i = 0; $i < 1024; ++$i){
331  $this->locationTable[$i][2] = $time;
332  $table .= Binary::writeInt($time);
333  }
334 
335  fwrite($this->filePointer, $table, 4096 * 2);
336  }
337 
338  public function getX(){
339  return $this->x;
340  }
341 
342  public function getZ(){
343  return $this->z;
344  }
345 
346 }
static writeByte($c)
Definition: Binary.php:248
static fromBinary($data, LevelProvider $provider=null)