Advertisement
Guest User

Untitled

a guest
Jan 21st, 2017
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.58 KB | None | 0 0
  1. Source code for nbtparse.minecraft.terrain.region
  2. from collections import abc as cabc
  3. import datetime as dt
  4. import io
  5. import itertools
  6. import logging
  7. import struct
  8. import time
  9. import zlib
  10.  
  11. from .. import entity_ids
  12. from . import chunk
  13.  
  14. logger = logging.getLogger(__name__)
  15.  
  16. OFFSET_LENGTH = 4 # bytes
  17. TIMESTAMP_LENGTH = 4
  18.  
  19. SECTOR_SIZE = 4096 # 4 KiB
  20.  
  21. CHUNK_HEADER = 5 # bytes
  22.  
  23. GZIP_COMPRESSION = 1
  24. ZLIB_COMPRESSION = 2
  25.  
  26. SIDE_LENGTH = 32 # chunks
  27. CHUNK_LENGTH = 16 # blocks
  28. SECTION_HEIGHT = 16
  29. SIDE_LENGTH_BLOCKS = SIDE_LENGTH * CHUNK_LENGTH
  30.  
  31.  
  32. def _fix_index(index):
  33. x, z = index
  34. x = int(x)
  35. z = int(z)
  36. x %= SIDE_LENGTH
  37. z %= SIDE_LENGTH
  38. return (x, z)
  39.  
  40.  
  41. [docs]class Region(cabc.MutableMapping):
  42. """A Minecraft region file.
  43.  
  44. :obj:`region_x` and :obj:`region_z` should be the region coordinates (as
  45. they appear in the filename). Standard constructor creates an empty
  46. region. :meth:`load` loads a region from a file.
  47.  
  48. For both loading and saving, files generally need to be seekable. This
  49. may pose a problem if you want to send a region over the network or
  50. through a pipe. Depending on your memory and disk resources, you may be
  51. able to use :class:`io.BytesIO` or :mod:`tempfile` as temporary buffers.
  52.  
  53. Contains individual chunks. Access chunk (1, 2), in chunk coordinates,
  54. as follows::
  55.  
  56. r = Region.load(...)
  57. chunk = r[1, 2]
  58.  
  59. No slicing. A :exc:`KeyError` is raised if the chunk does not exist. You
  60. may use absolute or relative coordinates as you please; they will be
  61. translated into positive relative coordinates via modulus. You may also
  62. replace one chunk with another.
  63.  
  64. """
  65. def __init__(self, region_x: int, region_z: int,
  66. namespace=entity_ids.VANILLA):
  67. filename = 'r.{}.{}.mca'.format(region_x, region_z)
  68. self.chunks = {}
  69. self.timestamps = {}
  70. self.coords = (region_x, region_z)
  71. self._namespace = namespace
  72.  
  73. @classmethod
  74. [docs] def load(cls, region_x: int, region_z: int, src: io.BufferedReader,
  75. namespace=entity_ids.VANILLA):
  76. """Load a region from disk (or another I/O-like object).
  77.  
  78. :obj:`src` must support seeking in both directions.
  79.  
  80. """
  81. if not src.seekable():
  82. raise ValueError('src must be seekable')
  83. result = cls(region_x, region_z)
  84. result._namespace = namespace
  85. not_present = set()
  86. offsets = {}
  87. lengths = {}
  88. timestamps = {}
  89. for z, x in itertools.product(range(SIDE_LENGTH), repeat=2):
  90. logger.debug('Reading offset data for chunk %r, %r', x, z)
  91. raw = src.read(OFFSET_LENGTH)
  92. if len(raw) < OFFSET_LENGTH:
  93. raise RuntimeError('Incomplete region file.')
  94. high_word, low_byte, length = struct.unpack('>HBB', raw)
  95. offset = (high_word << 8) | low_byte
  96. if offset == 0:
  97. logger.debug('Chunk %r, %r is absent', x, z)
  98. not_present.add((x, z))
  99. else:
  100. offsets[x, z] = offset*SECTOR_SIZE
  101. lengths[x, z] = length*SECTOR_SIZE
  102. for z, x in itertools.product(range(SIDE_LENGTH), repeat=2):
  103. if (x, z) in not_present:
  104. logger.debug('Skipping timestamp for absent chunk '
  105. '%r, %r', x, z)
  106. src.seek(TIMESTAMP_LENGTH, io.SEEK_CUR)
  107. continue
  108. logger.debug('Reading timestamp for chunk %r, %r', x, z)
  109. raw = src.read(TIMESTAMP_LENGTH)
  110. if len(raw) < TIMESTAMP_LENGTH:
  111. raise RuntimeError('Incomplete region file.')
  112. (posix_time,) = struct.unpack('>i', raw)
  113. timestamp = dt.datetime.fromtimestamp(posix_time,
  114. dt.timezone.utc)
  115. logger.debug('Timestamp is %s', timestamp)
  116. result.timestamps[x, z] = timestamp
  117. for (x, z), offset in offsets.items():
  118. result._load_chunk(x, z, src, offset)
  119. return result
  120.  
  121. def _after_load_chunk(self, x: int, z: int) -> None:
  122. """Hook called right after chunk (x, z) is loaded."""
  123. self._insert_tiles(x, z)
  124.  
  125. def _load_chunk(self, x: int, z: int, src: io.BufferedReader,
  126. offset: int):
  127. logger.debug('Loading chunk at %r, %r from %r', x, z, src)
  128. src.seek(offset)
  129. header = src.read(CHUNK_HEADER)
  130. length, compression = struct.unpack('>IB', header)
  131. compressed = src.read(length - 1)
  132. logger.debug('Compressed: %r bytes', len(compressed))
  133. if compression != ZLIB_COMPRESSION:
  134. raise RuntimeError('Cannot work with compression method {!r}'
  135. .format(compression))
  136. if len(compressed) != length - 1:
  137. raise RuntimeError('Incomplete chunk')
  138. raw_chunk = zlib.decompress(compressed)
  139. logger.debug('Uncompressed: %r bytes', len(raw_chunk))
  140. loaded = chunk.Chunk.from_bytes(raw_chunk, namespace=self._namespace)
  141. self.chunks[x, z] = loaded
  142. self._after_load_chunk(x, z)
  143.  
  144. def __repr__(self):
  145. x, z = self.coords
  146. return '<Region at ({!r}, {!r})>'.format(x, z)
  147.  
  148. def _extract_tiles(self, c_x: int, c_z: int):
  149. """Extract tile entities and prepare to save them to disk."""
  150. r_x, r_z = self.coords
  151. chnk = self[c_x, c_z]
  152. logger.debug('Extracting tile entities from VoxelBuffers in chunk'
  153. ' (%i, %i)', c_x, c_z)
  154. # Calculate origin of the chunk
  155. base_x = r_x*SIDE_LENGTH_BLOCKS + c_x*CHUNK_LENGTH
  156. base_z = r_z*SIDE_LENGTH_BLOCKS + c_z*CHUNK_LENGTH
  157. chnk.tiles = []
  158. for sec_y, section in chnk.sections.items():
  159. base_y = sec_y*SECTION_HEIGHT
  160. # Now (base_x, base_y, base_z) is the origin of section.blocks
  161. tilemap = section.blocks.tilemap
  162. for (x, y, z), t in tilemap.items():
  163. block_x = x + base_x
  164. block_y = y + base_y
  165. block_z = z + base_z
  166. t.coords = (block_x, block_y, block_z)
  167. logger.debug('Extracted tile entity at (%i, %i, %i), '
  168. 'chunk coordinates (%i, %i, %i), from '
  169. 'section %i', block_x, block_y, block_z, x,
  170. y, z, sec_y)
  171. chnk.tiles.append(t)
  172.  
  173. def _remove_empty_sections(self, x: int, z: int) -> None:
  174. chnk = self[x, z]
  175. for sec_y, section in list(chnk.items()):
  176. if section.blocks.empty():
  177. del chnk[sec_y]
  178.  
  179. def _before_save(self, x: int, z: int) -> None:
  180. """Hook called right before chunk (x, z) is saved."""
  181. self._remove_empty_sections(x, z)
  182. self._extract_tiles(x, z)
  183.  
  184. def _insert_tiles(self, x, z):
  185. """Insert tile entities into VoxelBuffers."""
  186. logger.debug('Inserting tile entities into VoxelBuffers in chunk (%i,'
  187. ' %i)', x, z)
  188. result = self[x, z]
  189. for t in result.tiles:
  190. block_x, block_y, block_z = t.coords
  191. chunk_x = block_x % 16
  192. chunk_y = block_y % 16
  193. chunk_z = block_z % 16
  194. section = block_y // 16
  195. logger.debug('Inserting tile entity at (%i, %i, %i), chunk '
  196. ' coordinates (%i, %i, %i), into section %i',
  197. block_x, block_y, block_z, chunk_x, chunk_y, chunk_z,
  198. section)
  199. vb = result.sections[section].blocks
  200. vb.tilemap[chunk_x, chunk_y, chunk_z] = t
  201.  
  202. [docs] def clear(self):
  203. """Mark this region as empty.
  204.  
  205. Equivalent to deleting every chunk from the region, but quite a bit
  206. faster.
  207.  
  208. """
  209. self.chunks.clear()
  210. self.timestamps.clear()
  211.  
  212. [docs] def save(self, dest: io.BufferedWriter):
  213. """Save the state of this region.
  214.  
  215. :obj:`dest` must support seeking in both directions.
  216.  
  217. """
  218. if not dest.seekable():
  219. raise ValueError('dest must be seekable')
  220. offset_table = {}
  221. length_table = {}
  222. total_offset = 2*SECTOR_SIZE
  223. # Write the chunks:
  224. logger.info('Saving chunks')
  225. for (x, z), chnk in self.items():
  226. logger.debug('Saving chunk %r, %r at offset %r', x, z,
  227. total_offset)
  228. # Loop invariant: total_offset % SECTOR_SIZE == 0
  229. assert total_offset % SECTOR_SIZE == 0
  230. dest.seek(total_offset)
  231. offset_table[x, z] = total_offset // SECTOR_SIZE
  232. self._before_save(x, z)
  233. raw_chunk = chnk.to_bytes()
  234. logger.debug('Uncompressed: %r bytes', len(raw_chunk))
  235. compressed = zlib.compress(raw_chunk)
  236. logger.debug('Compressed: %r bytes', len(compressed))
  237. length = len(compressed) + 1
  238. # length_table[x, z] = ceil(length / SECTOR_SIZE)
  239. # But that would create floating-point errors, so:
  240. length_table[x, z] = (length // SECTOR_SIZE)
  241. if length % SECTOR_SIZE != 0:
  242. length_table[x, z] += 1
  243. header = struct.pack('>IB', length, ZLIB_COMPRESSION)
  244. total_offset += dest.write(header)
  245. total_offset += dest.write(compressed)
  246. if total_offset % SECTOR_SIZE != 0:
  247. total_offset += SECTOR_SIZE - (total_offset % SECTOR_SIZE)
  248. dest.seek(0)
  249. # Now write the header:
  250. logger.info('Saving header')
  251. for z, x in itertools.product(range(SIDE_LENGTH), repeat=2):
  252. if (x, z) not in offset_table:
  253. logger.debug('Skipping missing chunk %r, %r', x, z)
  254. dest.write(b'\x00\x00\x00\x00')
  255. continue
  256. logger.debug('Writing header for chunk %r, %r', x, z)
  257. sector_offset = offset_table[x, z]
  258. sector_length = length_table[x, z]
  259. high_word = (sector_offset & 0xFFFF00) >> 8
  260. low_byte = sector_offset & 0xFF
  261. item = struct.pack('>HBB', high_word, low_byte, sector_length)
  262. dest.write(item)
  263. now = int(time.time()) & 0x7FFFFFFF
  264. # XXX: This will cause a Y2k38 bug, but it's Mojang's fault for
  265. # using 4-byte timestamps in the first place.
  266. logger.info('Saving timestamps (as %r)', now)
  267. for z, x in itertools.product(range(SIDE_LENGTH), repeat=2):
  268. if (x, z) not in offset_table:
  269. logger.debug('Skipping missing chunk %r, %r', x, z)
  270. dest.write(b'\x00\x00\x00\x00')
  271. continue
  272. logger.debug('Writing timestamp for chunk %r, %r', x, z)
  273. item = struct.pack('>i', now)
  274. dest.write(item)
  275.  
  276. def __getitem__(self, index: (int, int)) -> chunk.Chunk:
  277. x, z = _fix_index(index)
  278. return self.chunks[x, z]
  279.  
  280. def __setitem__(self, index: (int, int), value: chunk.Chunk):
  281. x, z = _fix_index(index)
  282. self.chunks[x, z] = value
  283.  
  284. def __delitem__(self, index: (int, int)):
  285. x, z = _fix_index(index)
  286. del self.chunks[x, z]
  287. del self.timestamps[x, z]
  288.  
  289. def __iter__(self):
  290. for key in self.chunks.keys():
  291. yield key
  292.  
  293. def __len__(self):
  294. return len(self.chunks)
  295.  
  296. Quick search
  297.  
  298. Go
  299. Enter search terms or a module, class or function name.
  300.  
  301.  
  302.  
  303. ++++++++++++ CREATED AND CODED BY COLBIY +++++++++++++++++
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement