How BedrockWriter Saves Minecraft Worlds in Bedrock Edition Format: Implementation Deep Dive
BedrockWriter converts the in-memory WorldToModify representation into a standards-compliant Minecraft Bedrock Edition .mcworld archive by orchestrating LevelDB chunk serialization, NBT metadata generation, and Deflate-compressed zip packaging.
The BedrockWriter struct in src/world_editor/bedrock.rs serves as the final export stage in the Arnis terrain generator. It transforms the internal world model into a playable Bedrock world through a 10-stage pipeline that handles everything from block palette deduplication to temporary directory cleanup.
Architecture Overview
BedrockWriter implements a strict pipeline orchestrated by the write_world method. The process moves from directory initialization through LevelDB population to final archive packaging, with each stage handling a specific Bedrock format requirement.
The implementation relies on several specialized crates declared in Cargo.toml:
bedrockrs_levelandfastnbtfor NBT serializationrusty_leveldbfor chunk database interfacezipfor archive creation
Initialization and Directory Preparation
Constructing the Writer
The BedrockWriter::new method (lines 135-151) initializes the export context:
pub fn new(
output_path: PathBuf,
world_name: String,
spawn: Option<(i32, i32)>,
ground: Option<Arc<Ground>>,
) -> Self
When the output_path ends with .mcworld, the extension is automatically stripped to create a temporary working directory for the LevelDB files and metadata.
Preparing the Workspace
The prepare_output_dir method (lines 185-199) ensures a clean export environment by removing any stale temporary folders or existing archives, then creates a fresh directory structure that will hold the db/ folder, level.dat, and supporting files.
Writing Level Metadata
Level Name and Version Information
The write_level_name method creates levelname.txt (lines 200-210), a plain text file containing the world name that Bedrock Edition displays in the world selection screen.
The write_level_dat method (lines 212-242) generates the binary NBT file containing all world settings. Using the BedrockLevelDat struct, it serializes:
- Storage version header (4 bytes)
- World name and generator settings
- Spawn coordinates (with optional ground elevation adjustment from the
Groundreference) - Game rules and version numbers
The NBT payload is written with a 4-byte storage-version header followed by the payload length, ensuring full compatibility with Bedrock's level.dat format expectations.
Serializing Chunks to LevelDB
Database Population Pipeline
The write_chunks_to_db method (lines 250-285) opens a RustyDBInterface on the temporary db/ directory and processes the world hierarchy:
- Iterates through every region in
WorldToModify - Processes every chunk within each region
- Handles every sub-chunk (16x16x16 section) within each chunk
For each sub-chunk, it writes:
- A version marker (
ChunkKey::chunk_marker) - A minimal
Data3Drecord containing heightmap and biome placeholder data - Encoded sub-chunk data under the appropriate sub-chunk key
Progress updates are emitted via emit_gui_progress_update to maintain the GUI's "Saving Bedrock world..." animation.
Sub-Chunk Encoding Algorithm
The encode_subchunk method (lines 673-735) implements Bedrock sub-chunk format version 9:
fn encode_subchunk(subchunk: &ChunkToModify, y_index: i8) -> Vec<u8>
The encoding process:
- Writes header byte
9, layer count1, and the Y-index - Builds a palette via
build_palette_and_indicesto deduplicate block states - Selects bits-per-block from Bedrock-allowed values (1, 2, 3, 4, 5, 6, 8, or 16) using
bedrock_bits_per_block - Packs block indices into 32-bit words matching Bedrock's
Chunkeralgorithm - Writes palette entries as NBT
BedrockBlockStatestructs
Palette Construction and Block Translation
The build_palette_and_indices method (lines 638-690) creates the block palette essential for Bedrock's compressed chunk format:
- Starts with the mandatory air entry
- Traverses blocks in XZY order (Bedrock's expected layout)
- Converts internal blocks to
BedrockBlockusingto_bedrock_block_with_propertiesfromsrc/bedrock_block_map.rs - Uses
StdHashMapto deduplicate identical block+property combinations - Generates a compact palette and 4096-element index map
During encoding, the writer calls:
let block = section.get_block_at_index(internal_idx);
let properties = section.properties.get(&internal_idx);
let bedrock_block = to_bedrock_block_with_properties(block, properties);
This ensures that block states like stair orientation or color variants are preserved correctly in the exported world.
Entity and Block Entity Handling
The write_chunk_entities method (lines 290-320) handles persistent entities and block entities (chests, signs, etc.):
- Re-opens the LevelDB using
rusty_leveldb::DBfor direct access - Writes compound-list NBT records for block entities and entities
- Deduplicates entries via
dedup_compound_listto avoid duplicate entity IDs
This ensures that any generated structures containing chests or other tile entities remain functional when imported into Bedrock Edition.
Packaging the .mcworld Archive
Archive Structure
The package_mcworld method (lines 727-754) creates the final .mcworld zip archive containing:
levelname.txt- World name for the UImetadata.json- Geographic extents and chunk countlevel.dat- Binary NBT world settingsworld_icon.jpeg- Built-in thumbnail imagedb/- LevelDB directory containing all chunk data
The zip uses Deflate compression method for full compatibility with Minecraft Bedrock Edition's import expectations.
Metadata and Cleanup
The write_metadata method (lines 693-724) creates metadata.json recording the world's geographic bounds and total chunk count, used by the Arnis GUI and external tools for world management.
After successful archive creation, cleanup_temp_dir (lines 758-764) removes the temporary working directory to ensure no orphaned files remain on the host system.
Code Example: Creating a Bedrock World
use arnis::world_editor::bedrock::BedrockWriter;
use arnis::world_editor::common::WorldToModify;
use arnis::coordinate_system::cartesian::XZBBox;
use arnis::coordinate_system::geographic::LLBBox;
// Temporary directory for the world files
let out_dir = std::env::temp_dir().join("my_bedrock_world");
// Empty world representation
let world = WorldToModify::default();
let xz = XZBBox::rect_from_xz_lengths(15.0, 15.0).unwrap();
let ll = LLBBox::new(0.0, 0.0, 1.0, 1.0).unwrap();
BedrockWriter::new(out_dir, "MyWorld".into(), None, None)
.write_world(&world, &xz, &ll)
.expect("Failed to write Bedrock world");
This creates MyWorld.mcworld in the temporary directory. To specify a custom spawn point:
let spawn = Some((100, 64)); // X=100, Z=64
BedrockWriter::new(out_dir, "SpawnWorld".into(), spawn, None)
.write_world(&world, &xz, &ll)
.unwrap();
The spawn coordinates are injected in write_level_dat (lines 212-224). When ground elevation data is provided, write_level_dat queries it to raise the spawn Y by three blocks (lines 218-229).
Summary
- BedrockWriter orchestrates a 10-stage pipeline to convert
WorldToModifyinto a.mcworldarchive - Key stages include LevelDB chunk serialization, NBT metadata generation, and Deflate-compressed zip packaging
- The implementation uses Bedrock sub-chunk format version 9 with XZY-ordered palette deduplication
- Block state translation occurs via
to_bedrock_block_with_propertiesinsrc/bedrock_block_map.rs - The writer reports progress to the GUI and cleans up temporary files after archiving
Frequently Asked Questions
What file format does BedrockWriter produce?
BedrockWriter generates a .mcworld file, which is a standard ZIP archive containing a level.dat NBT file, a levelname.txt file, a db/ directory with LevelDB chunk data, and metadata files. This format is directly importable into Minecraft Bedrock Edition on Windows, iOS, Android, and consoles.
How does BedrockWriter handle block property conversion?
The writer delegates block translation to to_bedrock_block_with_properties in src/bedrock_block_map.rs. This function maps internal block identifiers to Bedrock-compatible block names and extracts property maps (such as stair orientation or color variants) from the SectionToModify storage. During palette construction in build_palette_and_indices, these translated blocks are deduplicated while preserving their NBT property states.
What is the sub-chunk format version used by BedrockWriter?
The implementation uses Bedrock sub-chunk format version 9, as implemented in the encode_subchunk method. This format includes a header byte (9), layer count, Y-index, a deduplicated block palette built via build_palette_and_indices, and densely packed block indices using Bedrock's specific bits-per-block values (1, 2, 3, 4, 5, 6, 8, or 16 bits). The packing algorithm matches Bedrock's Chunker 32-bit word alignment requirements.
How does BedrockWriter report progress during world generation?
The writer emits progress updates through emit_gui_progress_update calls within write_chunks_to_db and other stages. This allows the Arnis GUI to display a "Saving Bedrock world..." animation with granular progress indicators as chunks are serialized and written to the LevelDB database, providing real-time feedback during large world exports.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →