amulet-core 2.0a7__cp312-cp312-win_amd64.whl → 2.0.1.0.1297307203.19.43.34808.0a0__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/core/__init__.py +36 -0
- amulet/core/__pyinstaller/hook-amulet.core.py +4 -0
- amulet/core/_amulet_core.cp312-win_amd64.pyd +0 -0
- amulet/core/_amulet_core.pyi +7 -0
- amulet/{_version.py → core/_version.py} +3 -3
- amulet/core/amulet_core.dll +0 -0
- amulet/core/amulet_core.lib +0 -0
- amulet/core/amulet_coreConfig.cmake +18 -0
- amulet/{biome.pyi → core/biome/__init__.pyi} +3 -3
- amulet/core/biome/biome.hpp +53 -0
- amulet/{block.pyi → core/block/__init__.pyi} +25 -26
- amulet/core/block/block.hpp +156 -0
- amulet/{block_entity.pyi → core/block_entity/__init__.pyi} +7 -7
- amulet/core/block_entity/block_entity.hpp +84 -0
- amulet/{errors.py → core/chunk/__init__.pyi} +37 -33
- amulet/core/chunk/chunk.hpp +126 -0
- amulet/core/chunk/component/__init__.pyi +18 -0
- amulet/core/chunk/component/biome_3d_component.hpp +96 -0
- amulet/core/chunk/component/block_component.hpp +101 -0
- amulet/core/chunk/component/block_component.pyi +28 -0
- amulet/core/chunk/component/block_entity_component.hpp +119 -0
- amulet/core/chunk/component/section_array_map.hpp +129 -0
- amulet/{chunk_components.pyi → core/chunk/component/section_array_map.pyi} +4 -24
- amulet/core/dll.hpp +21 -0
- amulet/core/entity/__init__.pyi +105 -0
- amulet/core/entity/entity.hpp +100 -0
- amulet/{palette → core/palette}/__init__.pyi +2 -2
- amulet/core/palette/biome_palette.hpp +65 -0
- amulet/{palette → core/palette}/biome_palette.pyi +8 -8
- amulet/core/palette/block_palette.hpp +71 -0
- amulet/{palette → core/palette}/block_palette.pyi +12 -10
- amulet/core/selection/__init__.pyi +8 -0
- amulet/core/selection/box.hpp +86 -0
- amulet/core/selection/box.pyi +215 -0
- amulet/core/selection/group.hpp +80 -0
- amulet/core/selection/group.pyi +213 -0
- amulet/{version.pyi → core/version/__init__.pyi} +58 -10
- amulet/core/version/version.hpp +204 -0
- {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/METADATA +25 -20
- amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/RECORD +45 -0
- {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/WHEEL +1 -1
- amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/entry_points.txt +2 -0
- amulet/__init__.cp312-win_amd64.pyd +0 -0
- amulet/__init__.py.cpp +0 -43
- amulet/__init__.pyi +0 -28
- amulet/__pyinstaller/hook-amulet.py +0 -4
- amulet/_init.py +0 -26
- amulet/biome.cpp +0 -36
- amulet/biome.hpp +0 -43
- amulet/biome.py.cpp +0 -122
- amulet/block.cpp +0 -435
- amulet/block.hpp +0 -119
- amulet/block.py.cpp +0 -377
- amulet/block_entity.cpp +0 -12
- amulet/block_entity.hpp +0 -56
- amulet/block_entity.py.cpp +0 -115
- amulet/chunk.cpp +0 -16
- amulet/chunk.hpp +0 -99
- amulet/chunk.py.cpp +0 -80
- amulet/chunk.pyi +0 -28
- amulet/chunk_components/biome_3d_component.cpp +0 -5
- amulet/chunk_components/biome_3d_component.hpp +0 -79
- amulet/chunk_components/block_component.cpp +0 -41
- amulet/chunk_components/block_component.hpp +0 -88
- amulet/chunk_components/block_entity_component.cpp +0 -5
- amulet/chunk_components/block_entity_component.hpp +0 -147
- amulet/chunk_components/section_array_map.cpp +0 -129
- amulet/chunk_components/section_array_map.hpp +0 -147
- amulet/collections/eq.py.hpp +0 -37
- amulet/collections/hash.py.hpp +0 -27
- amulet/collections/holder.py.hpp +0 -37
- amulet/collections/iterator.py.hpp +0 -80
- amulet/collections/mapping.py.hpp +0 -199
- amulet/collections/mutable_mapping.py.hpp +0 -226
- amulet/collections/sequence.py.hpp +0 -163
- amulet/collections.pyi +0 -40
- amulet/data_types.py +0 -29
- amulet/entity.py +0 -182
- amulet/game/__init__.py +0 -7
- amulet/game/_game.py +0 -152
- amulet/game/_universal/__init__.py +0 -1
- amulet/game/_universal/_biome.py +0 -17
- amulet/game/_universal/_block.py +0 -47
- amulet/game/_universal/_version.py +0 -68
- amulet/game/abc/__init__.py +0 -22
- amulet/game/abc/_block_specification.py +0 -150
- amulet/game/abc/biome.py +0 -213
- amulet/game/abc/block.py +0 -331
- amulet/game/abc/game_version_container.py +0 -25
- amulet/game/abc/json_interface.py +0 -27
- amulet/game/abc/version.py +0 -44
- amulet/game/bedrock/__init__.py +0 -1
- amulet/game/bedrock/_biome.py +0 -35
- amulet/game/bedrock/_block.py +0 -42
- amulet/game/bedrock/_version.py +0 -165
- amulet/game/java/__init__.py +0 -2
- amulet/game/java/_biome.py +0 -35
- amulet/game/java/_block.py +0 -60
- amulet/game/java/_version.py +0 -176
- amulet/game/translate/__init__.py +0 -12
- amulet/game/translate/_functions/__init__.py +0 -15
- amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
- amulet/game/translate/_functions/_code_functions/_text.py +0 -553
- amulet/game/translate/_functions/_code_functions/banner_pattern.py +0 -67
- amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +0 -152
- amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +0 -88
- amulet/game/translate/_functions/_code_functions/bedrock_sign.py +0 -152
- amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +0 -16
- amulet/game/translate/_functions/_code_functions/custom_name.py +0 -146
- amulet/game/translate/_functions/_frozen.py +0 -66
- amulet/game/translate/_functions/_state.py +0 -54
- amulet/game/translate/_functions/_typing.py +0 -98
- amulet/game/translate/_functions/abc.py +0 -123
- amulet/game/translate/_functions/carry_nbt.py +0 -160
- amulet/game/translate/_functions/carry_properties.py +0 -80
- amulet/game/translate/_functions/code.py +0 -143
- amulet/game/translate/_functions/map_block_name.py +0 -66
- amulet/game/translate/_functions/map_nbt.py +0 -111
- amulet/game/translate/_functions/map_properties.py +0 -93
- amulet/game/translate/_functions/multiblock.py +0 -112
- amulet/game/translate/_functions/new_block.py +0 -42
- amulet/game/translate/_functions/new_entity.py +0 -43
- amulet/game/translate/_functions/new_nbt.py +0 -206
- amulet/game/translate/_functions/new_properties.py +0 -64
- amulet/game/translate/_functions/sequence.py +0 -51
- amulet/game/translate/_functions/walk_input_nbt.py +0 -331
- amulet/game/translate/_translator.py +0 -433
- amulet/img/__init__.py +0 -10
- amulet/img/missing_no.png +0 -0
- amulet/img/missing_pack.png +0 -0
- amulet/img/missing_world.png +0 -0
- amulet/io/binary_reader.hpp +0 -45
- amulet/io/binary_writer.hpp +0 -30
- amulet/item.py +0 -75
- amulet/level/__init__.pyi +0 -23
- amulet/level/_load.py +0 -100
- amulet/level/abc/__init__.py +0 -12
- amulet/level/abc/_chunk_handle.py +0 -335
- amulet/level/abc/_dimension.py +0 -86
- amulet/level/abc/_history/__init__.py +0 -1
- amulet/level/abc/_history/_cache.py +0 -224
- amulet/level/abc/_history/_history_manager.py +0 -291
- amulet/level/abc/_level/__init__.py +0 -5
- amulet/level/abc/_level/_compactable_level.py +0 -10
- amulet/level/abc/_level/_creatable_level.py +0 -28
- amulet/level/abc/_level/_disk_level.py +0 -17
- amulet/level/abc/_level/_level.py +0 -449
- amulet/level/abc/_level/_loadable_level.py +0 -42
- amulet/level/abc/_player_storage.py +0 -7
- amulet/level/abc/_raw_level.py +0 -187
- amulet/level/abc/_registry.py +0 -40
- amulet/level/java/__init__.pyi +0 -16
- amulet/level/java/_chunk_handle.py +0 -17
- amulet/level/java/_dimension.py +0 -20
- amulet/level/java/_level.py +0 -184
- amulet/level/java/_raw/__init__.pyi +0 -15
- amulet/level/java/_raw/_chunk.pyi +0 -23
- amulet/level/java/_raw/_constant.py +0 -9
- amulet/level/java/_raw/_data_pack/__init__.py +0 -2
- amulet/level/java/_raw/_data_pack/data_pack.py +0 -241
- amulet/level/java/_raw/_data_pack/data_pack_manager.py +0 -77
- amulet/level/java/_raw/_dimension.py +0 -86
- amulet/level/java/_raw/_level.py +0 -507
- amulet/level/java/_raw/_typing.py +0 -3
- amulet/level/java/_raw/java_chunk_decode.cpp +0 -531
- amulet/level/java/_raw/java_chunk_decode.hpp +0 -23
- amulet/level/java/_raw/java_chunk_encode.cpp +0 -25
- amulet/level/java/_raw/java_chunk_encode.hpp +0 -23
- amulet/level/java/anvil/__init__.py +0 -2
- amulet/level/java/anvil/_dimension.py +0 -170
- amulet/level/java/anvil/_region.py +0 -421
- amulet/level/java/anvil/_sector_manager.py +0 -223
- amulet/level/java/chunk.pyi +0 -81
- amulet/level/java/chunk_/_chunk.py +0 -260
- amulet/level/java/chunk_/components/inhabited_time.py +0 -12
- amulet/level/java/chunk_/components/last_update.py +0 -12
- amulet/level/java/chunk_/components/legacy_version.py +0 -12
- amulet/level/java/chunk_/components/light_populated.py +0 -12
- amulet/level/java/chunk_/components/named_height_2d.py +0 -37
- amulet/level/java/chunk_/components/status.py +0 -11
- amulet/level/java/chunk_/components/terrain_populated.py +0 -12
- amulet/level/java/chunk_components/data_version_component.cpp +0 -32
- amulet/level/java/chunk_components/data_version_component.hpp +0 -31
- amulet/level/java/chunk_components/java_raw_chunk_component.cpp +0 -56
- amulet/level/java/chunk_components/java_raw_chunk_component.hpp +0 -45
- amulet/level/java/chunk_components.pyi +0 -22
- amulet/level/java/java_chunk.cpp +0 -170
- amulet/level/java/java_chunk.hpp +0 -141
- amulet/level/java/long_array.hpp +0 -175
- amulet/level/java/long_array.pyi +0 -39
- amulet/level/temporary_level/__init__.py +0 -1
- amulet/level/temporary_level/_level.py +0 -16
- amulet/mesh/__init__.py +0 -0
- amulet/mesh/block/__init__.py +0 -1
- amulet/mesh/block/block_mesh.py +0 -369
- amulet/mesh/block/cube.py +0 -149
- amulet/mesh/block/missing_block.py +0 -20
- amulet/mesh/util.py +0 -17
- amulet/palette/biome_palette.hpp +0 -85
- amulet/palette/block_palette.cpp +0 -32
- amulet/palette/block_palette.hpp +0 -93
- amulet/player.py +0 -62
- amulet/pybind11/collections.hpp +0 -118
- amulet/pybind11/numpy.hpp +0 -26
- amulet/pybind11/py_module.hpp +0 -34
- amulet/pybind11/type_hints.hpp +0 -51
- amulet/pybind11/types.hpp +0 -25
- amulet/pybind11/typing.hpp +0 -7
- amulet/resource_pack/__init__.py +0 -62
- amulet/resource_pack/abc/__init__.py +0 -2
- amulet/resource_pack/abc/resource_pack.py +0 -38
- amulet/resource_pack/abc/resource_pack_manager.py +0 -87
- amulet/resource_pack/bedrock/__init__.py +0 -2
- amulet/resource_pack/bedrock/bedrock_vanilla_fix/pack_icon.png +0 -0
- amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/grass_carried.png +0 -0
- amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/grass_side_carried.png +0 -0
- amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/water.png +0 -0
- amulet/resource_pack/bedrock/blockshapes/__init__.py +0 -31
- amulet/resource_pack/bedrock/blockshapes/air.py +0 -35
- amulet/resource_pack/bedrock/blockshapes/base_blockshape.py +0 -29
- amulet/resource_pack/bedrock/blockshapes/bubble_column.py +0 -29
- amulet/resource_pack/bedrock/blockshapes/cake.py +0 -46
- amulet/resource_pack/bedrock/blockshapes/chest.py +0 -54
- amulet/resource_pack/bedrock/blockshapes/comparator.py +0 -51
- amulet/resource_pack/bedrock/blockshapes/cross_texture.py +0 -186
- amulet/resource_pack/bedrock/blockshapes/cross_texture0.py +0 -17
- amulet/resource_pack/bedrock/blockshapes/cross_texture_green.py +0 -16
- amulet/resource_pack/bedrock/blockshapes/cube.py +0 -38
- amulet/resource_pack/bedrock/blockshapes/default.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door.py +0 -38
- amulet/resource_pack/bedrock/blockshapes/door1.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door2.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door3.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door4.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door5.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/door6.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/double_plant.py +0 -40
- amulet/resource_pack/bedrock/blockshapes/enchanting_table.py +0 -22
- amulet/resource_pack/bedrock/blockshapes/farmland.py +0 -22
- amulet/resource_pack/bedrock/blockshapes/fence.py +0 -22
- amulet/resource_pack/bedrock/blockshapes/flat.py +0 -55
- amulet/resource_pack/bedrock/blockshapes/flat_wall.py +0 -55
- amulet/resource_pack/bedrock/blockshapes/furnace.py +0 -44
- amulet/resource_pack/bedrock/blockshapes/furnace_lit.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/green_cube.py +0 -39
- amulet/resource_pack/bedrock/blockshapes/ladder.py +0 -36
- amulet/resource_pack/bedrock/blockshapes/lilypad.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/partial_block.py +0 -57
- amulet/resource_pack/bedrock/blockshapes/piston.py +0 -44
- amulet/resource_pack/bedrock/blockshapes/piston_arm.py +0 -72
- amulet/resource_pack/bedrock/blockshapes/portal_frame.py +0 -22
- amulet/resource_pack/bedrock/blockshapes/pressure_plate.py +0 -29
- amulet/resource_pack/bedrock/blockshapes/pumpkin.py +0 -36
- amulet/resource_pack/bedrock/blockshapes/pumpkin_carved.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/pumpkin_lit.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/red_dust.py +0 -14
- amulet/resource_pack/bedrock/blockshapes/repeater.py +0 -53
- amulet/resource_pack/bedrock/blockshapes/slab.py +0 -33
- amulet/resource_pack/bedrock/blockshapes/slab_double.py +0 -15
- amulet/resource_pack/bedrock/blockshapes/tree.py +0 -41
- amulet/resource_pack/bedrock/blockshapes/turtle_egg.py +0 -15
- amulet/resource_pack/bedrock/blockshapes/vine.py +0 -52
- amulet/resource_pack/bedrock/blockshapes/wall.py +0 -22
- amulet/resource_pack/bedrock/blockshapes/water.py +0 -38
- amulet/resource_pack/bedrock/download_resources.py +0 -147
- amulet/resource_pack/bedrock/resource_pack.py +0 -40
- amulet/resource_pack/bedrock/resource_pack_manager.py +0 -361
- amulet/resource_pack/bedrock/sort_blockshapes.py +0 -15
- amulet/resource_pack/java/__init__.py +0 -2
- amulet/resource_pack/java/download_resources.py +0 -212
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_black.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_brown.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_cyan.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_green.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_lime.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_magenta.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_orange.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_pink.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_purple.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_red.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_white.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_yellow.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/barrier.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/end_portal.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/grass.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/lava.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/structure_void.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/water.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/pack.png +0 -0
- amulet/resource_pack/java/resource_pack.py +0 -44
- amulet/resource_pack/java/resource_pack_manager.py +0 -551
- amulet/resource_pack/unknown_resource_pack.py +0 -10
- amulet/selection/__init__.py +0 -2
- amulet/selection/abstract_selection.py +0 -342
- amulet/selection/box.py +0 -852
- amulet/selection/group.py +0 -481
- amulet/utils/__init__.pyi +0 -23
- amulet/utils/call_spec/__init__.py +0 -24
- amulet/utils/call_spec/_call_spec.py +0 -257
- amulet/utils/comment_json.py +0 -188
- amulet/utils/format_utils.py +0 -41
- amulet/utils/generator.py +0 -18
- amulet/utils/matrix.py +0 -243
- amulet/utils/numpy.hpp +0 -36
- amulet/utils/numpy.pyi +0 -11
- amulet/utils/numpy_helpers.py +0 -19
- amulet/utils/shareable_lock.py +0 -335
- amulet/utils/signal/__init__.py +0 -10
- amulet/utils/signal/_signal.py +0 -228
- amulet/utils/task_manager.py +0 -235
- amulet/utils/typed_property.py +0 -111
- amulet/utils/weakref.py +0 -70
- amulet/utils/world_utils.py +0 -102
- amulet/version.cpp +0 -136
- amulet/version.hpp +0 -142
- amulet/version.py.cpp +0 -281
- amulet_core-2.0a7.dist-info/RECORD +0 -295
- amulet_core-2.0a7.dist-info/entry_points.txt +0 -2
- /amulet/{__pyinstaller → core/__pyinstaller}/__init__.py +0 -0
- /amulet/{py.typed → core/py.typed} +0 -0
- {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/top_level.txt +0 -0
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TypeAlias
|
|
4
|
-
import os
|
|
5
|
-
from collections.abc import Iterator, Sequence
|
|
6
|
-
import re
|
|
7
|
-
import threading
|
|
8
|
-
|
|
9
|
-
from amulet_nbt import NamedTag
|
|
10
|
-
|
|
11
|
-
from amulet.utils import world_utils
|
|
12
|
-
from amulet.errors import ChunkDoesNotExist
|
|
13
|
-
from amulet.data_types import (
|
|
14
|
-
ChunkCoordinates,
|
|
15
|
-
RegionCoordinates,
|
|
16
|
-
)
|
|
17
|
-
from ._region import AnvilRegion
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
RawChunkType: TypeAlias = dict[str, NamedTag]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class AnvilDimensionLayer:
|
|
24
|
-
"""A class to manage a directory of region files."""
|
|
25
|
-
|
|
26
|
-
def __init__(self, directory: str, *, mcc: bool = False):
|
|
27
|
-
self._directory = directory
|
|
28
|
-
self._regions: dict[RegionCoordinates, AnvilRegion] = {}
|
|
29
|
-
self._mcc = mcc
|
|
30
|
-
self._lock = threading.RLock()
|
|
31
|
-
|
|
32
|
-
def _region_path(self, rx: int, rz: int) -> str:
|
|
33
|
-
"""Get the file path for a region file."""
|
|
34
|
-
return os.path.join(self._directory, f"r.{rx}.{rz}.mca")
|
|
35
|
-
|
|
36
|
-
def _has_region(self, rx: int, rz: int) -> bool:
|
|
37
|
-
"""Does a region file exist."""
|
|
38
|
-
return os.path.isfile(self._region_path(rx, rz))
|
|
39
|
-
|
|
40
|
-
def _get_region(self, rx: int, rz: int, create: bool = False) -> AnvilRegion:
|
|
41
|
-
with self._lock:
|
|
42
|
-
if (rx, rz) in self._regions:
|
|
43
|
-
return self._regions[(rx, rz)]
|
|
44
|
-
elif create or self._has_region(rx, rz):
|
|
45
|
-
region = self._regions[(rx, rz)] = AnvilRegion(
|
|
46
|
-
self._region_path(rx, rz), mcc=self._mcc
|
|
47
|
-
)
|
|
48
|
-
return region
|
|
49
|
-
else:
|
|
50
|
-
raise ChunkDoesNotExist
|
|
51
|
-
|
|
52
|
-
def _iter_regions(self) -> Iterator[AnvilRegion]:
|
|
53
|
-
if os.path.isdir(self._directory):
|
|
54
|
-
for region_file_name in os.listdir(self._directory):
|
|
55
|
-
try:
|
|
56
|
-
rx, rz = AnvilRegion.get_coords(region_file_name)
|
|
57
|
-
except ValueError:
|
|
58
|
-
continue
|
|
59
|
-
else:
|
|
60
|
-
yield self._get_region(rx, rz)
|
|
61
|
-
|
|
62
|
-
def all_chunk_coords(self) -> Iterator[ChunkCoordinates]:
|
|
63
|
-
for region in self._iter_regions():
|
|
64
|
-
yield from region.all_coords()
|
|
65
|
-
|
|
66
|
-
def has_chunk(self, cx: int, cz: int) -> bool:
|
|
67
|
-
try:
|
|
68
|
-
region = self._get_region(
|
|
69
|
-
*world_utils.chunk_coords_to_region_coords(cx, cz)
|
|
70
|
-
)
|
|
71
|
-
except ChunkDoesNotExist:
|
|
72
|
-
return False
|
|
73
|
-
else:
|
|
74
|
-
return region.has_data(cx, cz)
|
|
75
|
-
|
|
76
|
-
def get_chunk_data(self, cx: int, cz: int) -> NamedTag:
|
|
77
|
-
"""
|
|
78
|
-
Get a NamedTag of a chunk from the database.
|
|
79
|
-
Will raise ChunkDoesNotExist if the region or chunk does not exist
|
|
80
|
-
"""
|
|
81
|
-
# get the region key
|
|
82
|
-
return self._get_region(
|
|
83
|
-
*world_utils.chunk_coords_to_region_coords(cx, cz)
|
|
84
|
-
).get_data(cx, cz)
|
|
85
|
-
|
|
86
|
-
def put_chunk_data(self, cx: int, cz: int, data: NamedTag) -> None:
|
|
87
|
-
"""pass data to the region file class"""
|
|
88
|
-
self._get_region(
|
|
89
|
-
*world_utils.chunk_coords_to_region_coords(cx, cz), create=True
|
|
90
|
-
).set_data(cx, cz, data)
|
|
91
|
-
|
|
92
|
-
def delete_chunk(self, cx: int, cz: int) -> None:
|
|
93
|
-
try:
|
|
94
|
-
region = self._get_region(
|
|
95
|
-
*world_utils.chunk_coords_to_region_coords(cx, cz)
|
|
96
|
-
)
|
|
97
|
-
except ChunkDoesNotExist:
|
|
98
|
-
pass
|
|
99
|
-
else:
|
|
100
|
-
region.delete_data(cx, cz)
|
|
101
|
-
|
|
102
|
-
def compact(self) -> None:
|
|
103
|
-
"""Compact all region files in this layer"""
|
|
104
|
-
for region in self._iter_regions():
|
|
105
|
-
region.compact()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class AnvilDimension:
|
|
109
|
-
"""
|
|
110
|
-
A class to manage the data for a dimension.
|
|
111
|
-
This can consist of multiple layers. Eg the region layer which contains chunk data and the entities layer which contains entities.
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
level_regex = re.compile(r"DIM(?P<level>-?\d+)")
|
|
115
|
-
|
|
116
|
-
def __init__(
|
|
117
|
-
self, directory: str, *, mcc: bool = False, layers: Sequence[str] = ("region",)
|
|
118
|
-
) -> None:
|
|
119
|
-
self._directory = directory
|
|
120
|
-
self._mcc = mcc
|
|
121
|
-
self.__layers: dict[str, AnvilDimensionLayer] = {
|
|
122
|
-
layer: AnvilDimensionLayer(
|
|
123
|
-
os.path.join(self._directory, layer), mcc=self._mcc
|
|
124
|
-
)
|
|
125
|
-
for layer in layers
|
|
126
|
-
}
|
|
127
|
-
self.__default_layer = self.__layers[layers[0]]
|
|
128
|
-
|
|
129
|
-
def all_chunk_coords(self) -> Iterator[ChunkCoordinates]:
|
|
130
|
-
yield from self.__default_layer.all_chunk_coords()
|
|
131
|
-
|
|
132
|
-
def has_chunk(self, cx: int, cz: int) -> bool:
|
|
133
|
-
return self.__default_layer.has_chunk(cx, cz)
|
|
134
|
-
|
|
135
|
-
def get_chunk_data(self, cx: int, cz: int) -> RawChunkType:
|
|
136
|
-
"""Get the chunk data for each layer"""
|
|
137
|
-
chunk_data = {}
|
|
138
|
-
for layer_name, layer in self.__layers.items():
|
|
139
|
-
try:
|
|
140
|
-
chunk_data[layer_name] = layer.get_chunk_data(cx, cz)
|
|
141
|
-
except ChunkDoesNotExist:
|
|
142
|
-
pass
|
|
143
|
-
|
|
144
|
-
if chunk_data:
|
|
145
|
-
return chunk_data
|
|
146
|
-
else:
|
|
147
|
-
raise ChunkDoesNotExist
|
|
148
|
-
|
|
149
|
-
def put_chunk_data(self, cx: int, cz: int, data_layers: RawChunkType) -> None:
|
|
150
|
-
"""Put one or more layers of data"""
|
|
151
|
-
for layer_name, data in data_layers.items():
|
|
152
|
-
if (
|
|
153
|
-
layer_name not in self.__layers
|
|
154
|
-
and layer_name.isalpha()
|
|
155
|
-
and layer_name.islower()
|
|
156
|
-
):
|
|
157
|
-
self.__layers[layer_name] = AnvilDimensionLayer(
|
|
158
|
-
os.path.join(self._directory, layer_name), mcc=self._mcc
|
|
159
|
-
)
|
|
160
|
-
if layer_name in self.__layers:
|
|
161
|
-
self.__layers[layer_name].put_chunk_data(cx, cz, data)
|
|
162
|
-
|
|
163
|
-
def delete_chunk(self, cx: int, cz: int) -> None:
|
|
164
|
-
for layer in self.__layers.values():
|
|
165
|
-
layer.delete_chunk(cx, cz)
|
|
166
|
-
|
|
167
|
-
def compact(self) -> None:
|
|
168
|
-
"""Compact all region files in this dimension"""
|
|
169
|
-
for layer in self.__layers.values():
|
|
170
|
-
layer.compact()
|
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import struct
|
|
5
|
-
import zlib
|
|
6
|
-
import gzip
|
|
7
|
-
from typing import BinaryIO
|
|
8
|
-
from collections.abc import Iterator
|
|
9
|
-
import numpy
|
|
10
|
-
import time
|
|
11
|
-
import re
|
|
12
|
-
import threading
|
|
13
|
-
import logging
|
|
14
|
-
from enum import IntEnum
|
|
15
|
-
|
|
16
|
-
import lz4.block as lz4_block # type: ignore
|
|
17
|
-
from amulet_nbt import NamedTag, read_nbt
|
|
18
|
-
|
|
19
|
-
from amulet.errors import ChunkDoesNotExist, ChunkLoadError
|
|
20
|
-
from amulet.data_types import ChunkCoordinates
|
|
21
|
-
from ._sector_manager import SectorManager, Sector
|
|
22
|
-
|
|
23
|
-
SectorSize = 0x1000
|
|
24
|
-
MaxRegionSize = 255 * SectorSize # the maximum size data in the region file can be
|
|
25
|
-
HeaderSector = Sector(0, 0x2000)
|
|
26
|
-
|
|
27
|
-
log = logging.getLogger(__name__)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class RegionFileVersion(IntEnum):
|
|
31
|
-
VERSION_GZIP = 1
|
|
32
|
-
VERSION_DEFLATE = 2
|
|
33
|
-
VERSION_NONE = 3
|
|
34
|
-
VERSION_LZ4 = 4
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
LZ4_HEADER = struct.Struct("<8sBiii")
|
|
38
|
-
LZ4_MAGIC = b"LZ4Block"
|
|
39
|
-
COMPRESSION_METHOD_RAW = 0x10
|
|
40
|
-
COMPRESSION_METHOD_LZ4 = 0x20
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _decompress_lz4(data: bytes) -> bytes:
|
|
44
|
-
"""The LZ4 compression format is a sequence of LZ4 blocks with some header data."""
|
|
45
|
-
# https://github.com/lz4/lz4-java/blob/7c931bef32d179ec3d3286ee71638b23ebde3459/src/java/net/jpountz/lz4/LZ4BlockInputStream.java#L200
|
|
46
|
-
decompressed: list[bytes] = []
|
|
47
|
-
index = 0
|
|
48
|
-
while index < len(data):
|
|
49
|
-
magic, token, compressed_length, original_length, checksum = LZ4_HEADER.unpack(
|
|
50
|
-
data[index : index + LZ4_HEADER.size]
|
|
51
|
-
)
|
|
52
|
-
index += LZ4_HEADER.size
|
|
53
|
-
compression_method = token & 0xF0
|
|
54
|
-
if (
|
|
55
|
-
magic != LZ4_MAGIC
|
|
56
|
-
or original_length < 0
|
|
57
|
-
or compressed_length < 0
|
|
58
|
-
or (original_length == 0 and compressed_length != 0)
|
|
59
|
-
or (original_length != 0 and compressed_length == 0)
|
|
60
|
-
or (
|
|
61
|
-
compression_method == COMPRESSION_METHOD_RAW
|
|
62
|
-
and original_length != compressed_length
|
|
63
|
-
)
|
|
64
|
-
):
|
|
65
|
-
raise ValueError("LZ4 compressed block is corrupted.")
|
|
66
|
-
if compression_method == COMPRESSION_METHOD_RAW:
|
|
67
|
-
decompressed.append(data[index : index + original_length])
|
|
68
|
-
index += original_length
|
|
69
|
-
elif compression_method == COMPRESSION_METHOD_LZ4:
|
|
70
|
-
decompressed.append(
|
|
71
|
-
lz4_block.decompress(
|
|
72
|
-
data[index : index + compressed_length], original_length
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
index += compressed_length
|
|
76
|
-
else:
|
|
77
|
-
raise ValueError("LZ4 compressed block is corrupted.")
|
|
78
|
-
return b"".join(decompressed)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _compress(tag: NamedTag) -> bytes:
|
|
82
|
-
"""Convert an NBTFile into a compressed bytes object"""
|
|
83
|
-
data = tag.save_to(compressed=False)
|
|
84
|
-
return b"\x02" + zlib.compress(data)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _decompress(data: bytes) -> NamedTag:
|
|
88
|
-
"""Convert a bytes object into an NBTFile"""
|
|
89
|
-
compress_type, data = data[0], data[1:]
|
|
90
|
-
if compress_type == RegionFileVersion.VERSION_GZIP:
|
|
91
|
-
return read_nbt(gzip.decompress(data), compressed=False)
|
|
92
|
-
elif compress_type == RegionFileVersion.VERSION_DEFLATE:
|
|
93
|
-
return read_nbt(zlib.decompress(data), compressed=False)
|
|
94
|
-
elif compress_type == RegionFileVersion.VERSION_NONE:
|
|
95
|
-
return read_nbt(data, compressed=False)
|
|
96
|
-
elif compress_type == RegionFileVersion.VERSION_LZ4:
|
|
97
|
-
return read_nbt(_decompress_lz4(data), compressed=False)
|
|
98
|
-
raise ChunkLoadError(f"Invalid compression type {compress_type}")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _sanitise_file(handler: BinaryIO) -> None:
|
|
102
|
-
handler.seek(0, os.SEEK_END)
|
|
103
|
-
file_size = handler.tell()
|
|
104
|
-
if file_size & 0xFFF:
|
|
105
|
-
# ensure the file is a multiple of 4096 bytes
|
|
106
|
-
file_size = (file_size | 0xFFF) + 1
|
|
107
|
-
handler.truncate(file_size)
|
|
108
|
-
|
|
109
|
-
# if the length of the region file is less than 8KiB extend it to 8KiB
|
|
110
|
-
if file_size < SectorSize * 2:
|
|
111
|
-
file_size = SectorSize * 2
|
|
112
|
-
handler.truncate(file_size)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class AnvilRegion:
|
|
116
|
-
"""
|
|
117
|
-
A class to read and write Minecraft Java Edition Region files.
|
|
118
|
-
Only one class should exist per region file at any given time otherwise bad things may happen.
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
region_regex = re.compile(r"r\.(?P<rx>-?\d+)\.(?P<rz>-?\d+)\.mca")
|
|
122
|
-
|
|
123
|
-
__slots__ = (
|
|
124
|
-
"_path",
|
|
125
|
-
"_rx",
|
|
126
|
-
"_rz",
|
|
127
|
-
"_mcc",
|
|
128
|
-
"_sector_manager",
|
|
129
|
-
"_chunk_locations",
|
|
130
|
-
"_lock",
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
# The path to the region file
|
|
134
|
-
_path: str
|
|
135
|
-
|
|
136
|
-
# The region coordinates
|
|
137
|
-
_rx: int
|
|
138
|
-
_rz: int
|
|
139
|
-
|
|
140
|
-
# Is support for .mcc files enabled
|
|
141
|
-
_mcc: bool
|
|
142
|
-
|
|
143
|
-
# A class to track which sectors are reserved
|
|
144
|
-
_sector_manager: SectorManager | None
|
|
145
|
-
|
|
146
|
-
# A dictionary mapping the chunk coordinate to the location on disk
|
|
147
|
-
_chunk_locations: dict[ChunkCoordinates, Sector]
|
|
148
|
-
|
|
149
|
-
# A lock to limit access to multiple threads
|
|
150
|
-
_lock: threading.RLock
|
|
151
|
-
|
|
152
|
-
@classmethod
|
|
153
|
-
def get_coords(cls, file_path: str) -> tuple[int, int]:
|
|
154
|
-
"""Parse a region file path to get the region coordinates."""
|
|
155
|
-
file_path = os.path.basename(file_path)
|
|
156
|
-
match = cls.region_regex.fullmatch(file_path)
|
|
157
|
-
if match is None:
|
|
158
|
-
raise ValueError(f"{file_path} is not a valid region file path.")
|
|
159
|
-
return int(match.group("rx")), int(match.group("rz"))
|
|
160
|
-
|
|
161
|
-
def __init__(self, file_path: str, *, mcc: bool = False) -> None:
|
|
162
|
-
"""
|
|
163
|
-
A class wrapper for a region file
|
|
164
|
-
:param file_path: The file path of the region file
|
|
165
|
-
:param create: bool - if true will create the region from scratch. If false will try loading from disk
|
|
166
|
-
"""
|
|
167
|
-
self._path = file_path
|
|
168
|
-
self._rx, self._rz = self.get_coords(file_path)
|
|
169
|
-
self._mcc = mcc # create mcc file if the chunk is greater than 1MiB
|
|
170
|
-
self._sector_manager = None
|
|
171
|
-
self._chunk_locations = {}
|
|
172
|
-
self._lock = threading.RLock()
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def path(self) -> str:
|
|
176
|
-
"""The file path to the region file."""
|
|
177
|
-
return self._path
|
|
178
|
-
|
|
179
|
-
@property
|
|
180
|
-
def rx(self) -> int:
|
|
181
|
-
"""The region x coordinate."""
|
|
182
|
-
return self._rx
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def rz(self) -> int:
|
|
186
|
-
"""The region z coordinate."""
|
|
187
|
-
return self._rz
|
|
188
|
-
|
|
189
|
-
def get_mcc_path(self, cx: int, cz: int) -> str:
|
|
190
|
-
"""Get the mcc path. Coordinates are world chunk coordinates."""
|
|
191
|
-
return os.path.join(
|
|
192
|
-
os.path.dirname(self._path),
|
|
193
|
-
f"c.{cx}.{cz}.mcc",
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
def _load(self) -> None:
|
|
197
|
-
"""Load region metadata. The lock must be acquired when calling this."""
|
|
198
|
-
if self._sector_manager is not None:
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
# Create the sector manager and ensure the header is not reservable
|
|
202
|
-
self._sector_manager = SectorManager(0, 0x2000)
|
|
203
|
-
self._sector_manager.reserve(HeaderSector)
|
|
204
|
-
|
|
205
|
-
if os.path.isfile(self._path):
|
|
206
|
-
# Load the file and populate the sector manager
|
|
207
|
-
with open(self._path, "rb+") as handler:
|
|
208
|
-
_sanitise_file(handler)
|
|
209
|
-
handler.seek(0)
|
|
210
|
-
location_table = numpy.fromfile(
|
|
211
|
-
handler, dtype=">u4", count=1024
|
|
212
|
-
).reshape(32, 32)
|
|
213
|
-
for (cz, cx), sector_data in numpy.ndenumerate(location_table):
|
|
214
|
-
if sector_data:
|
|
215
|
-
sector_offset = (sector_data >> 8) * 0x1000
|
|
216
|
-
sector_size = (sector_data & 0xFF) * 0x1000
|
|
217
|
-
sector = Sector(sector_offset, sector_offset + sector_size)
|
|
218
|
-
self._sector_manager.reserve(sector)
|
|
219
|
-
self._chunk_locations[
|
|
220
|
-
(cx + self.rx * 32, cz + self.rz * 32)
|
|
221
|
-
] = sector
|
|
222
|
-
|
|
223
|
-
def all_coords(self) -> Iterator[ChunkCoordinates]:
|
|
224
|
-
"""An iterable of chunk coordinates in world space."""
|
|
225
|
-
with self._lock:
|
|
226
|
-
self._load()
|
|
227
|
-
coords = list(self._chunk_locations)
|
|
228
|
-
yield from coords
|
|
229
|
-
|
|
230
|
-
def has_data(self, cx: int, cz: int) -> bool:
|
|
231
|
-
"""Does the chunk exists. Coords are in world space."""
|
|
232
|
-
with self._lock:
|
|
233
|
-
self._load()
|
|
234
|
-
return (cx, cz) in self._chunk_locations
|
|
235
|
-
|
|
236
|
-
def get_data(self, cx: int, cz: int) -> NamedTag:
|
|
237
|
-
with self._lock:
|
|
238
|
-
self._load()
|
|
239
|
-
sector = self._chunk_locations.get((cx, cz))
|
|
240
|
-
if sector is None:
|
|
241
|
-
raise ChunkDoesNotExist
|
|
242
|
-
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
|
243
|
-
with open(self._path, "rb+") as handler:
|
|
244
|
-
_sanitise_file(handler)
|
|
245
|
-
handler.seek(0, os.SEEK_END)
|
|
246
|
-
if handler.tell() < sector.stop:
|
|
247
|
-
# if the sector is beyond the end of the file
|
|
248
|
-
raise ChunkDoesNotExist
|
|
249
|
-
|
|
250
|
-
handler.seek(sector.start)
|
|
251
|
-
buffer_size_bytes: bytes = handler.read(4)
|
|
252
|
-
buffer_size = struct.unpack(">I", buffer_size_bytes)[0]
|
|
253
|
-
buffer: bytes = handler.read(buffer_size)
|
|
254
|
-
|
|
255
|
-
if buffer:
|
|
256
|
-
if buffer[0] & 128: # if the "external" bit is set
|
|
257
|
-
if self._mcc:
|
|
258
|
-
mcc_path = self.get_mcc_path(cx, cz)
|
|
259
|
-
if os.path.isfile(mcc_path):
|
|
260
|
-
with open(mcc_path, "rb") as f:
|
|
261
|
-
return _decompress(
|
|
262
|
-
bytes([buffer[0] & 127]) + f.read()
|
|
263
|
-
)
|
|
264
|
-
else:
|
|
265
|
-
return _decompress(buffer)
|
|
266
|
-
raise ChunkDoesNotExist
|
|
267
|
-
|
|
268
|
-
def _write_data(self, cx: int, cz: int, data: bytes | None) -> None:
|
|
269
|
-
assert (
|
|
270
|
-
self.rx * 32 <= cx < (self.rx + 1) * 32
|
|
271
|
-
and self.rz * 32 <= cz < (self.rz + 1) * 32
|
|
272
|
-
)
|
|
273
|
-
if isinstance(data, bytes) and len(data) + 4 > MaxRegionSize and not self._mcc:
|
|
274
|
-
# if the data is too large and mcc files are not supported then do nothing
|
|
275
|
-
log.error(
|
|
276
|
-
f"Could not save data {cx},{cz} in region file {self._path} because it was too large."
|
|
277
|
-
)
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
with self._lock:
|
|
281
|
-
self._load()
|
|
282
|
-
sector_manager = self._sector_manager
|
|
283
|
-
assert sector_manager is not None
|
|
284
|
-
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
|
285
|
-
handler: BinaryIO
|
|
286
|
-
with open(
|
|
287
|
-
self._path, "rb+" if os.path.isfile(self._path) else "wb+"
|
|
288
|
-
) as handler:
|
|
289
|
-
_sanitise_file(handler)
|
|
290
|
-
|
|
291
|
-
old_sector = self._chunk_locations.pop((cx, cz), None)
|
|
292
|
-
if old_sector is not None:
|
|
293
|
-
# the chunk used to exist
|
|
294
|
-
handler.seek(old_sector.start + 4)
|
|
295
|
-
if self._mcc and handler.read(1)[0] & 127:
|
|
296
|
-
# if the file is stored externally delete the file
|
|
297
|
-
mcc_path = self.get_mcc_path(cx, cz)
|
|
298
|
-
if os.path.isfile(mcc_path):
|
|
299
|
-
os.remove(mcc_path)
|
|
300
|
-
sector_manager.free(old_sector)
|
|
301
|
-
|
|
302
|
-
location = b"\x00\x00\x00\x00"
|
|
303
|
-
|
|
304
|
-
if isinstance(data, bytes):
|
|
305
|
-
# find a memory location large enough to fit the data
|
|
306
|
-
if len(data) + 4 > MaxRegionSize:
|
|
307
|
-
# save externally (if mcc files are not supported the check at the top will filter large files out)
|
|
308
|
-
with open(self.get_mcc_path(cx, cz), "wb") as mcc:
|
|
309
|
-
mcc.write(data[1:])
|
|
310
|
-
data = bytes([data[0] | 128])
|
|
311
|
-
data = struct.pack(">I", len(data)) + data
|
|
312
|
-
sector_length = len(data)
|
|
313
|
-
if sector_length & 0xFFF:
|
|
314
|
-
sector_length = (sector_length | 0xFFF) + 1
|
|
315
|
-
sector = sector_manager.reserve_space(sector_length)
|
|
316
|
-
assert sector.start & 0xFFF == 0
|
|
317
|
-
self._chunk_locations[(cx, cz)] = sector
|
|
318
|
-
location = struct.pack(
|
|
319
|
-
">I", (sector.start >> 4) + (sector_length >> 12)
|
|
320
|
-
)
|
|
321
|
-
handler.seek(sector.start)
|
|
322
|
-
handler.write(data)
|
|
323
|
-
_sanitise_file(handler)
|
|
324
|
-
|
|
325
|
-
# write the header data
|
|
326
|
-
handler.seek(4 * (cx - self.rx * 32 + (cz - self.rz * 32) * 32))
|
|
327
|
-
handler.write(location)
|
|
328
|
-
handler.seek(SectorSize - 4, os.SEEK_CUR)
|
|
329
|
-
handler.write(struct.pack(">I", int(time.time())))
|
|
330
|
-
|
|
331
|
-
def set_data(self, cx: int, cz: int, data: NamedTag) -> None:
|
|
332
|
-
"""Write the data to the region file."""
|
|
333
|
-
bytes_data = _compress(data)
|
|
334
|
-
self._write_data(cx, cz, bytes_data)
|
|
335
|
-
|
|
336
|
-
def delete_data(self, cx: int, cz: int) -> None:
|
|
337
|
-
"""Delete the data from the region file."""
|
|
338
|
-
self._write_data(cx, cz, None)
|
|
339
|
-
|
|
340
|
-
def compact(self) -> None:
|
|
341
|
-
"""Compact the region file.
|
|
342
|
-
This moves all entries to the front of the file and deletes any unused space."""
|
|
343
|
-
with self._lock:
|
|
344
|
-
if not os.path.isfile(self._path):
|
|
345
|
-
# Do nothing if there is no file.
|
|
346
|
-
return
|
|
347
|
-
|
|
348
|
-
# All chunks in the region must be valid at all times.
|
|
349
|
-
# Load metadata
|
|
350
|
-
self._load()
|
|
351
|
-
sector_manager = self._sector_manager
|
|
352
|
-
assert sector_manager is not None
|
|
353
|
-
|
|
354
|
-
# Generate a list of sectors in sequential order
|
|
355
|
-
# location header index, chunk coordinate, sector
|
|
356
|
-
chunk_sectors: list[tuple[int, tuple[int, int], Sector]] = [
|
|
357
|
-
(4 * (cx - self.rx * 32 + (cz - self.rz * 32) * 32), (cx, cz), sector)
|
|
358
|
-
for (cx, cz), sector in sorted(
|
|
359
|
-
self._chunk_locations.items(), key=lambda item: item[1].start
|
|
360
|
-
)
|
|
361
|
-
]
|
|
362
|
-
|
|
363
|
-
# Set the position to the end of the header
|
|
364
|
-
file_position = HeaderSector.stop
|
|
365
|
-
# The end of the last chunk or the end of the header if no chunks exist.
|
|
366
|
-
if chunk_sectors:
|
|
367
|
-
file_end = chunk_sectors[-1][2].stop
|
|
368
|
-
else:
|
|
369
|
-
file_end = HeaderSector.stop
|
|
370
|
-
|
|
371
|
-
with open(self._path, "rb+") as handler:
|
|
372
|
-
while chunk_sectors:
|
|
373
|
-
# While there are remaining sectors
|
|
374
|
-
# Get the first sector
|
|
375
|
-
header_index, chunk_coordinate, sector = chunk_sectors.pop(0)
|
|
376
|
-
|
|
377
|
-
if file_position == sector.start:
|
|
378
|
-
# There isn't any space before the sector. Do nothing.
|
|
379
|
-
file_position = sector.stop
|
|
380
|
-
else:
|
|
381
|
-
# There is space before the sector
|
|
382
|
-
if file_position + sector.length <= sector.start:
|
|
383
|
-
# There is enough space before the sector to fit the whole sector.
|
|
384
|
-
# Copy it to the new location
|
|
385
|
-
new_sector = Sector(
|
|
386
|
-
file_position, file_position + sector.length
|
|
387
|
-
)
|
|
388
|
-
file_position = new_sector.stop
|
|
389
|
-
else:
|
|
390
|
-
# There is space before the sector but not enough to fit the sector.
|
|
391
|
-
# Move it to the end for processing later.
|
|
392
|
-
new_sector = Sector(file_end, file_end + sector.length)
|
|
393
|
-
file_end = new_sector.stop
|
|
394
|
-
chunk_sectors.append(
|
|
395
|
-
(header_index, chunk_coordinate, new_sector)
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
# Read in the data
|
|
399
|
-
handler.seek(sector.start)
|
|
400
|
-
data = handler.read(sector.length)
|
|
401
|
-
|
|
402
|
-
# Reserve and write the data to the new sector
|
|
403
|
-
sector_manager.reserve(new_sector)
|
|
404
|
-
handler.seek(new_sector.start)
|
|
405
|
-
handler.write(data)
|
|
406
|
-
|
|
407
|
-
# Update the index
|
|
408
|
-
handler.seek(header_index)
|
|
409
|
-
handler.write(
|
|
410
|
-
struct.pack(
|
|
411
|
-
">I",
|
|
412
|
-
(new_sector.start >> 4) + (new_sector.length >> 12),
|
|
413
|
-
)
|
|
414
|
-
)
|
|
415
|
-
self._chunk_locations[chunk_coordinate] = new_sector
|
|
416
|
-
|
|
417
|
-
# Free the old sector
|
|
418
|
-
sector_manager.free(sector)
|
|
419
|
-
|
|
420
|
-
# Delete any unused data at the end.
|
|
421
|
-
handler.truncate(file_position)
|