1use crate::chunk::{ChunkKey, ChunkPos, ChunkRecordTag, Dimension, LEGACY_TERRAIN_VALUE_LEN};
9use crate::error::{BedrockWorldError, Result};
10use crate::level_dat::read_level_dat_document;
11use crate::nbt::NbtTag;
12use bytes::Bytes;
13use std::collections::BTreeMap;
14use std::fs;
15use std::path::Path;
16use std::sync::{
17 Arc, RwLock,
18 atomic::{AtomicBool, Ordering},
19};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct StorageEntry {
24 pub key: Bytes,
26 pub value: Bytes,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct StorageEntryRef<'a> {
33 pub key: &'a [u8],
35 pub value: &'a [u8],
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum StorageOp {
42 Put {
44 key: Bytes,
46 value: Bytes,
48 },
49 Delete {
51 key: Bytes,
53 },
54}
55
56#[derive(Debug, Clone)]
57pub struct StorageReadOptions {
59 pub threading: StorageThreadingOptions,
61 pub scan_mode: StorageScanMode,
63 pub cache_policy: StorageCachePolicy,
65 pub pipeline: StoragePipelineOptions,
67 pub cancel: Option<StorageCancelFlag>,
69 pub progress: Option<StorageProgressSink>,
71}
72
73impl Default for StorageReadOptions {
74 fn default() -> Self {
75 Self {
76 threading: StorageThreadingOptions::Auto,
77 scan_mode: StorageScanMode::ParallelTables,
78 cache_policy: StorageCachePolicy::Bypass,
79 pipeline: StoragePipelineOptions::default(),
80 cancel: None,
81 progress: None,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
87pub enum StorageCachePolicy {
89 #[default]
90 Bypass,
92 Use,
94}
95
96#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
97pub struct StoragePipelineOptions {
99 pub queue_depth: usize,
101 pub table_batch_size: usize,
103 pub progress_interval: usize,
105}
106
107#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
108pub enum StorageThreadingOptions {
110 #[default]
111 Auto,
113 Fixed(usize),
115 Single,
117}
118
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
120pub enum StorageScanMode {
122 #[default]
123 Sequential,
125 ParallelTables,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum StorageVisitorControl {
132 Continue,
134 Stop,
136}
137
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
139pub struct StorageScanOutcome {
141 pub visited: usize,
143 pub bytes_read: usize,
145 pub stopped: bool,
147 pub tables_scanned: usize,
149 pub worker_threads: usize,
151 pub queue_wait_ms: u128,
153 pub cancel_checks: usize,
155}
156
157impl StorageScanOutcome {
158 #[must_use]
159 pub const fn empty() -> Self {
161 Self {
162 visited: 0,
163 bytes_read: 0,
164 stopped: false,
165 tables_scanned: 0,
166 worker_threads: 0,
167 queue_wait_ms: 0,
168 cancel_checks: 0,
169 }
170 }
171
172 pub fn record(&mut self, value_len: usize) {
174 self.visited = self.visited.saturating_add(1);
175 self.bytes_read = self.bytes_read.saturating_add(value_len);
176 }
177
178 pub fn merge(&mut self, other: Self) {
180 self.visited = self.visited.saturating_add(other.visited);
181 self.bytes_read = self.bytes_read.saturating_add(other.bytes_read);
182 self.stopped |= other.stopped;
183 self.tables_scanned = self.tables_scanned.saturating_add(other.tables_scanned);
184 self.worker_threads = self.worker_threads.max(other.worker_threads);
185 self.queue_wait_ms = self.queue_wait_ms.saturating_add(other.queue_wait_ms);
186 self.cancel_checks = self.cancel_checks.saturating_add(other.cancel_checks);
187 }
188}
189
190#[derive(Debug, Clone, Default)]
191pub struct StorageCancelFlag(Arc<AtomicBool>);
193
194impl StorageCancelFlag {
195 #[must_use]
196 pub fn new() -> Self {
198 Self::default()
199 }
200
201 pub fn cancel(&self) {
203 self.0.store(true, Ordering::Relaxed);
204 }
205
206 #[must_use]
207 pub fn from_shared(cancelled: Arc<AtomicBool>) -> Self {
209 Self(cancelled)
210 }
211
212 #[must_use]
213 pub fn is_cancelled(&self) -> bool {
215 self.0.load(Ordering::Relaxed)
216 }
217}
218
219#[derive(Clone)]
220pub struct StorageProgressSink {
222 inner: Arc<dyn Fn(StorageScanProgress) + Send + Sync>,
223}
224
225impl std::fmt::Debug for StorageProgressSink {
226 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 formatter
228 .debug_struct("StorageProgressSink")
229 .finish_non_exhaustive()
230 }
231}
232
233impl StorageProgressSink {
234 #[must_use]
235 pub fn new(callback: impl Fn(StorageScanProgress) + Send + Sync + 'static) -> Self {
237 Self {
238 inner: Arc::new(callback),
239 }
240 }
241
242 pub fn emit(&self, progress: StorageScanProgress) {
244 (self.inner)(progress);
245 }
246}
247
248#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
249pub struct StorageScanProgress {
251 pub entries_seen: usize,
253 pub bytes_read: usize,
255}
256
257#[derive(Debug, Clone, Default, PartialEq, Eq)]
258pub struct StorageBatch {
260 ops: Vec<StorageOp>,
261}
262
263impl StorageBatch {
264 #[must_use]
265 pub const fn new() -> Self {
267 Self { ops: Vec::new() }
268 }
269
270 pub fn put(&mut self, key: impl Into<Bytes>, value: impl Into<Bytes>) {
272 self.ops.push(StorageOp::Put {
273 key: key.into(),
274 value: value.into(),
275 });
276 }
277
278 pub fn delete(&mut self, key: impl Into<Bytes>) {
280 self.ops.push(StorageOp::Delete { key: key.into() });
281 }
282
283 #[must_use]
284 pub fn is_empty(&self) -> bool {
286 self.ops.is_empty()
287 }
288
289 #[must_use]
290 pub fn ops(&self) -> &[StorageOp] {
292 &self.ops
293 }
294}
295
296pub trait WorldStorage: Send + Sync {
298 fn get(&self, key: &[u8]) -> Result<Option<Bytes>>;
300 fn get_many(&self, keys: &[Bytes]) -> Result<Vec<Option<Bytes>>> {
302 keys.iter().map(|key| self.get(key)).collect()
303 }
304 fn get_many_ordered_with_control(
306 &self,
307 keys: &[Bytes],
308 options: StorageReadOptions,
309 ) -> Result<Vec<Option<Bytes>>> {
310 check_cancelled(&options)?;
311 let mut values = Vec::with_capacity(keys.len());
312 for key in keys {
313 check_cancelled(&options)?;
314 values.push(self.get(key)?);
315 }
316 Ok(values)
317 }
318 fn put(&self, key: &[u8], value: &[u8]) -> Result<()>;
320 fn delete(&self, key: &[u8]) -> Result<()>;
322 fn for_each_key(
325 &self,
326 options: StorageReadOptions,
327 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
328 ) -> Result<StorageScanOutcome>;
329 fn for_each_prefix(
331 &self,
332 prefix: &[u8],
333 options: StorageReadOptions,
334 visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
335 ) -> Result<StorageScanOutcome>;
336 fn for_each_prefix_ref(
338 &self,
339 prefix: &[u8],
340 options: StorageReadOptions,
341 visitor: &mut (dyn FnMut(StorageEntryRef<'_>) -> Result<StorageVisitorControl> + Send),
342 ) -> Result<StorageScanOutcome> {
343 self.for_each_prefix(prefix, options, &mut |key, value| {
344 visitor(StorageEntryRef {
345 key,
346 value: value.as_ref(),
347 })
348 })
349 }
350 fn for_each_prefix_key(
353 &self,
354 prefix: &[u8],
355 options: StorageReadOptions,
356 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
357 ) -> Result<StorageScanOutcome> {
358 self.for_each_prefix(prefix, options, &mut |key, _value| visitor(key))
359 }
360 fn for_each_entry(
362 &self,
363 options: StorageReadOptions,
364 visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
365 ) -> Result<StorageScanOutcome> {
366 self.for_each_prefix(b"", options, visitor)
367 }
368 fn write_batch(&self, batch: &StorageBatch) -> Result<()>;
370 fn flush(&self) -> Result<()>;
372 fn compact(&self) -> Result<()> {
374 self.flush()
375 }
376}
377
378#[derive(Debug, Clone, Default)]
379pub struct MemoryStorage {
381 values: Arc<RwLock<BTreeMap<Vec<u8>, Bytes>>>,
382}
383
384impl MemoryStorage {
385 #[must_use]
386 pub fn new() -> Self {
388 Self::default()
389 }
390}
391
392impl WorldStorage for MemoryStorage {
393 fn get(&self, key: &[u8]) -> Result<Option<Bytes>> {
394 let values = self.values.read().map_err(|_| {
395 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
396 })?;
397 Ok(values.get(key).cloned())
398 }
399
400 fn get_many(&self, keys: &[Bytes]) -> Result<Vec<Option<Bytes>>> {
401 let values = self.values.read().map_err(|_| {
402 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
403 })?;
404 Ok(keys
405 .iter()
406 .map(|key| values.get(key.as_ref()).cloned())
407 .collect())
408 }
409
410 fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
411 let mut values = self.values.write().map_err(|_| {
412 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
413 })?;
414 values.insert(key.to_vec(), Bytes::copy_from_slice(value));
415 Ok(())
416 }
417
418 fn delete(&self, key: &[u8]) -> Result<()> {
419 let mut values = self.values.write().map_err(|_| {
420 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
421 })?;
422 values.remove(key);
423 Ok(())
424 }
425
426 fn for_each_key(
427 &self,
428 options: StorageReadOptions,
429 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
430 ) -> Result<StorageScanOutcome> {
431 let values = self.values.read().map_err(|_| {
432 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
433 })?;
434 let mut outcome = StorageScanOutcome::empty();
435 for (key, value) in values.iter() {
436 check_cancelled(&options)?;
437 outcome.record(value.len());
438 if visitor(key)? == StorageVisitorControl::Stop {
439 outcome.stopped = true;
440 return Ok(outcome);
441 }
442 emit_progress(&options, outcome);
443 }
444 Ok(outcome)
445 }
446
447 fn for_each_prefix(
448 &self,
449 prefix: &[u8],
450 options: StorageReadOptions,
451 visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
452 ) -> Result<StorageScanOutcome> {
453 let values = self.values.read().map_err(|_| {
454 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
455 })?;
456 let mut outcome = StorageScanOutcome::empty();
457 for (key, value) in values
458 .range(prefix.to_vec()..)
459 .take_while(|(key, _)| key.starts_with(prefix))
460 {
461 check_cancelled(&options)?;
462 outcome.record(value.len());
463 if visitor(key, value)? == StorageVisitorControl::Stop {
464 outcome.stopped = true;
465 return Ok(outcome);
466 }
467 emit_progress(&options, outcome);
468 }
469 Ok(outcome)
470 }
471
472 fn for_each_prefix_key(
473 &self,
474 prefix: &[u8],
475 options: StorageReadOptions,
476 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
477 ) -> Result<StorageScanOutcome> {
478 let values = self.values.read().map_err(|_| {
479 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
480 })?;
481 let mut outcome = StorageScanOutcome::empty();
482 for (key, value) in values
483 .range(prefix.to_vec()..)
484 .take_while(|(key, _)| key.starts_with(prefix))
485 {
486 check_cancelled(&options)?;
487 outcome.record(value.len());
488 if visitor(key)? == StorageVisitorControl::Stop {
489 outcome.stopped = true;
490 return Ok(outcome);
491 }
492 emit_progress(&options, outcome);
493 }
494 Ok(outcome)
495 }
496
497 fn write_batch(&self, batch: &StorageBatch) -> Result<()> {
498 let mut values = self.values.write().map_err(|_| {
499 BedrockWorldError::ConcurrentWrite("memory storage poisoned".to_string())
500 })?;
501 for op in batch.ops() {
502 match op {
503 StorageOp::Put { key, value } => {
504 values.insert(key.to_vec(), value.clone());
505 }
506 StorageOp::Delete { key } => {
507 values.remove(key.as_ref());
508 }
509 }
510 }
511 Ok(())
512 }
513
514 fn flush(&self) -> Result<()> {
515 Ok(())
516 }
517
518 fn compact(&self) -> Result<()> {
519 Ok(())
520 }
521}
522
523pub const POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN: usize = 82_176;
527const POCKET_CHUNKS_DAT_LOCATION_TABLE_LEN: usize = 4 * 32 * 32;
528const POCKET_CHUNKS_DAT_SECTOR_BYTES: usize = 4096;
529const DEFAULT_LEGACY_BIOME_SAMPLE: [u8; 4] = [1, 0x7f, 0xb2, 0x38];
530
531#[derive(Debug, Clone)]
532pub struct PocketChunksDatStorage {
534 values: Arc<BTreeMap<Vec<u8>, Bytes>>,
535 origin_chunk_x: i32,
536 origin_chunk_z: i32,
537}
538
539impl PocketChunksDatStorage {
540 pub fn open(world_path: impl AsRef<Path>) -> Result<Self> {
542 let world_path = world_path.as_ref();
543 let chunks_path = world_path.join("chunks.dat");
544 let bytes = fs::read(&chunks_path)?;
545 let (origin_chunk_x, origin_chunk_z) = read_limited_world_origin(world_path);
546 let values = parse_pocket_chunks_dat(&bytes, origin_chunk_x, origin_chunk_z)?;
547 if world_path.join("entities.dat").is_file() {
548 match fs::read(world_path.join("entities.dat")) {
549 Ok(bytes) => log::debug!(
550 "legacy entities.dat present (bytes={}, parser=best_effort_skip)",
551 bytes.len()
552 ),
553 Err(error) => log::warn!("failed to read legacy entities.dat: {error}"),
554 }
555 }
556 log::debug!(
557 "opened Pocket chunks.dat storage (chunks={}, origin=({}, {}), path={})",
558 values.len(),
559 origin_chunk_x,
560 origin_chunk_z,
561 chunks_path.display()
562 );
563 Ok(Self {
564 values: Arc::new(values),
565 origin_chunk_x,
566 origin_chunk_z,
567 })
568 }
569
570 #[must_use]
571 pub const fn origin_chunk_x(&self) -> i32 {
573 self.origin_chunk_x
574 }
575
576 #[must_use]
577 pub const fn origin_chunk_z(&self) -> i32 {
579 self.origin_chunk_z
580 }
581}
582
583impl WorldStorage for PocketChunksDatStorage {
584 fn get(&self, key: &[u8]) -> Result<Option<Bytes>> {
585 Ok(self.values.get(key).cloned())
586 }
587
588 fn get_many(&self, keys: &[Bytes]) -> Result<Vec<Option<Bytes>>> {
589 Ok(keys
590 .iter()
591 .map(|key| self.values.get(key.as_ref()).cloned())
592 .collect())
593 }
594
595 fn put(&self, _key: &[u8], _value: &[u8]) -> Result<()> {
596 Err(pocket_chunks_dat_read_only_error())
597 }
598
599 fn delete(&self, _key: &[u8]) -> Result<()> {
600 Err(pocket_chunks_dat_read_only_error())
601 }
602
603 fn for_each_key(
604 &self,
605 options: StorageReadOptions,
606 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
607 ) -> Result<StorageScanOutcome> {
608 let mut outcome = StorageScanOutcome::empty();
609 for (key, value) in self.values.iter() {
610 check_cancelled(&options)?;
611 outcome.record(value.len());
612 if visitor(key)? == StorageVisitorControl::Stop {
613 outcome.stopped = true;
614 return Ok(outcome);
615 }
616 emit_progress(&options, outcome);
617 }
618 Ok(outcome)
619 }
620
621 fn for_each_prefix(
622 &self,
623 prefix: &[u8],
624 options: StorageReadOptions,
625 visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
626 ) -> Result<StorageScanOutcome> {
627 let mut outcome = StorageScanOutcome::empty();
628 for (key, value) in self
629 .values
630 .range(prefix.to_vec()..)
631 .take_while(|(key, _)| key.starts_with(prefix))
632 {
633 check_cancelled(&options)?;
634 outcome.record(value.len());
635 if visitor(key, value)? == StorageVisitorControl::Stop {
636 outcome.stopped = true;
637 return Ok(outcome);
638 }
639 emit_progress(&options, outcome);
640 }
641 Ok(outcome)
642 }
643
644 fn for_each_prefix_key(
645 &self,
646 prefix: &[u8],
647 options: StorageReadOptions,
648 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
649 ) -> Result<StorageScanOutcome> {
650 let mut outcome = StorageScanOutcome::empty();
651 for (key, value) in self
652 .values
653 .range(prefix.to_vec()..)
654 .take_while(|(key, _)| key.starts_with(prefix))
655 {
656 check_cancelled(&options)?;
657 outcome.record(value.len());
658 if visitor(key)? == StorageVisitorControl::Stop {
659 outcome.stopped = true;
660 return Ok(outcome);
661 }
662 emit_progress(&options, outcome);
663 }
664 Ok(outcome)
665 }
666
667 fn write_batch(&self, _batch: &StorageBatch) -> Result<()> {
668 Err(pocket_chunks_dat_read_only_error())
669 }
670
671 fn flush(&self) -> Result<()> {
672 Ok(())
673 }
674
675 fn compact(&self) -> Result<()> {
676 Ok(())
677 }
678}
679
680fn parse_pocket_chunks_dat(
681 bytes: &[u8],
682 origin_chunk_x: i32,
683 origin_chunk_z: i32,
684) -> Result<BTreeMap<Vec<u8>, Bytes>> {
685 if bytes.len() < POCKET_CHUNKS_DAT_LOCATION_TABLE_LEN {
686 return Err(BedrockWorldError::CorruptWorld(format!(
687 "chunks.dat is too small for its location table: {} bytes",
688 bytes.len()
689 )));
690 }
691 let mut values = BTreeMap::new();
692 for index in 0..(32 * 32) {
693 let entry_offset = index * 4;
694 let entry = &bytes[entry_offset..entry_offset + 4];
695 if entry == [0, 0, 0, 0] {
696 continue;
697 }
698 let sector_count = usize::from(entry[0]);
699 let sector_offset =
700 usize::from(entry[1]) | (usize::from(entry[2]) << 8) | (usize::from(entry[3]) << 16);
701 if sector_count == 0 || sector_offset == 0 {
702 continue;
703 }
704 let Some(byte_offset) = sector_offset.checked_mul(POCKET_CHUNKS_DAT_SECTOR_BYTES) else {
705 continue;
706 };
707 let Some(payload) = pocket_chunk_payload(bytes, byte_offset, sector_count) else {
708 log::warn!(
709 "skipping invalid chunks.dat entry (index={index}, sector_offset={sector_offset}, sector_count={sector_count})"
710 );
711 continue;
712 };
713 let local_x = i32::try_from(index % 32).unwrap_or(0);
714 let local_z = i32::try_from(index / 32).unwrap_or(0);
715 let pos = ChunkPos {
716 x: origin_chunk_x.saturating_add(local_x),
717 z: origin_chunk_z.saturating_add(local_z),
718 dimension: Dimension::Overworld,
719 };
720 values.insert(
721 ChunkKey::new(pos, ChunkRecordTag::LegacyTerrain)
722 .encode()
723 .to_vec(),
724 convert_pocket_terrain_to_legacy(payload),
725 );
726 }
727 Ok(values)
728}
729
730fn pocket_chunk_payload(bytes: &[u8], byte_offset: usize, sector_count: usize) -> Option<&[u8]> {
731 let sector_bytes = sector_count.checked_mul(POCKET_CHUNKS_DAT_SECTOR_BYTES)?;
732 let max_end = byte_offset.checked_add(sector_bytes)?.min(bytes.len());
733 if byte_offset >= bytes.len() || byte_offset >= max_end {
734 return None;
735 }
736 let available = &bytes[byte_offset..max_end];
737 if available.len() >= 4 {
738 let declared_len = u32::from_le_bytes(available[0..4].try_into().ok()?) as usize;
739 if declared_len == POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN
740 && available.len() >= 4 + declared_len
741 {
742 return Some(&available[4..4 + declared_len]);
743 }
744 if declared_len == LEGACY_TERRAIN_VALUE_LEN && available.len() >= 4 + declared_len {
745 return Some(&available[4..4 + POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN]);
746 }
747 }
748 if available.len() >= POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN {
749 return Some(&available[..POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN]);
750 }
751 None
752}
753
754fn convert_pocket_terrain_to_legacy(payload: &[u8]) -> Bytes {
755 if payload.len() == LEGACY_TERRAIN_VALUE_LEN {
756 return Bytes::copy_from_slice(payload);
757 }
758 let mut legacy = Vec::with_capacity(LEGACY_TERRAIN_VALUE_LEN);
759 legacy.extend_from_slice(&payload[..POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN]);
760 for _ in 0..256 {
761 legacy.extend_from_slice(&DEFAULT_LEGACY_BIOME_SAMPLE);
762 }
763 Bytes::from(legacy)
764}
765
766fn read_limited_world_origin(world_path: &Path) -> (i32, i32) {
767 let Ok(document) = read_level_dat_document(&world_path.join("level.dat")) else {
768 return (0, 0);
769 };
770 let NbtTag::Compound(root) = document.root else {
771 return (0, 0);
772 };
773 (
774 nbt_i32(root.get("LimitedWorldOriginX")).unwrap_or(0),
775 nbt_i32(root.get("LimitedWorldOriginZ")).unwrap_or(0),
776 )
777}
778
779fn nbt_i32(tag: Option<&NbtTag>) -> Option<i32> {
780 match tag {
781 Some(NbtTag::Byte(value)) => Some(i32::from(*value)),
782 Some(NbtTag::Short(value)) => Some(i32::from(*value)),
783 Some(NbtTag::Int(value)) => Some(*value),
784 Some(NbtTag::Long(value)) => i32::try_from(*value).ok(),
785 _ => None,
786 }
787}
788
789fn pocket_chunks_dat_read_only_error() -> BedrockWorldError {
790 BedrockWorldError::UnsupportedChunkFormat("Pocket chunks.dat storage is read-only".to_string())
791}
792
793pub mod backend {
795 use super::*;
796
797 #[cfg(feature = "backend-bedrock-leveldb")]
798 #[derive(Clone)]
799 pub struct BedrockLevelDbStorage {
801 db: Arc<bedrock_leveldb::Db>,
802 }
803
804 #[cfg(feature = "backend-bedrock-leveldb")]
805 impl BedrockLevelDbStorage {
806 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
808 Self::open_inner(path, false, true)
809 }
810
811 pub fn open_read_only(path: impl AsRef<Path>) -> Result<Self> {
813 Self::open_inner(path, true, true)
814 }
815
816 pub fn open_read_only_best_effort(path: impl AsRef<Path>) -> Result<Self> {
821 Self::open_inner(path, true, false)
822 }
823
824 fn open_inner(
825 path: impl AsRef<Path>,
826 read_only: bool,
827 paranoid_checks: bool,
828 ) -> Result<Self> {
829 let path = path.as_ref().to_path_buf();
830 if !path.exists() {
831 return Err(BedrockWorldError::Io(std::io::Error::new(
832 std::io::ErrorKind::NotFound,
833 format!("LevelDB path not found: {}", path.display()),
834 )));
835 }
836 let options = bedrock_leveldb::OpenOptions {
837 read_only,
838 create_if_missing: false,
839 error_if_exists: false,
840 paranoid_checks,
841 compression_policy: bedrock_leveldb::CompressionPolicy::Zlib,
842 cache_size: 64 * 1024 * 1024,
843 write_buffer_size: 0,
844 };
845 let db = bedrock_leveldb::Db::open(path, options).map_err(map_leveldb_error)?;
846 Ok(Self { db: Arc::new(db) })
847 }
848 }
849
850 #[cfg(feature = "backend-bedrock-leveldb")]
851 impl WorldStorage for BedrockLevelDbStorage {
852 fn get(&self, key: &[u8]) -> Result<Option<Bytes>> {
853 self.db.get(key).map_err(map_leveldb_error)
854 }
855
856 fn get_many(&self, keys: &[Bytes]) -> Result<Vec<Option<Bytes>>> {
857 self.db
858 .get_many_owned(
859 keys.iter().cloned(),
860 bedrock_leveldb::ReadOptions::default(),
861 )
862 .map_err(map_leveldb_error)
863 }
864
865 fn get_many_ordered_with_control(
866 &self,
867 keys: &[Bytes],
868 options: StorageReadOptions,
869 ) -> Result<Vec<Option<Bytes>>> {
870 check_cancelled(&options)?;
871 self.db
872 .get_many_owned(keys.iter().cloned(), to_leveldb_read_options(options))
873 .map_err(map_leveldb_error)
874 }
875
876 fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
877 self.db
878 .put(
879 Bytes::copy_from_slice(key),
880 Bytes::copy_from_slice(value),
881 write_options(),
882 )
883 .map_err(map_leveldb_error)
884 }
885
886 fn delete(&self, key: &[u8]) -> Result<()> {
887 self.db
888 .delete(Bytes::copy_from_slice(key), write_options())
889 .map_err(map_leveldb_error)
890 }
891
892 fn for_each_key(
893 &self,
894 options: StorageReadOptions,
895 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
896 ) -> Result<StorageScanOutcome> {
897 let read_options = to_leveldb_read_options(options);
898 let mut visitor_error = None;
899 let scan_result = self
900 .db
901 .for_each_key(read_options, |key| match visitor(key) {
902 Ok(StorageVisitorControl::Continue) => {
903 Ok(bedrock_leveldb::VisitorControl::Continue)
904 }
905 Ok(StorageVisitorControl::Stop) => Ok(bedrock_leveldb::VisitorControl::Stop),
906 Err(error) => {
907 visitor_error = Some(error);
908 Ok(bedrock_leveldb::VisitorControl::Stop)
909 }
910 });
911 match (scan_result, visitor_error) {
912 (_, Some(error)) => Err(error),
913 (Ok(outcome), None) => Ok(to_storage_outcome(outcome)),
914 (Err(error), None) => Err(map_leveldb_error(error)),
915 }
916 }
917
918 fn for_each_prefix(
919 &self,
920 prefix: &[u8],
921 options: StorageReadOptions,
922 visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
923 ) -> Result<StorageScanOutcome> {
924 let read_options = to_leveldb_read_options(options);
925 let mut visitor_error = None;
926 let scan_result = self.db.for_each_prefix(prefix, read_options, |key, value| {
927 match visitor(key, value) {
928 Ok(StorageVisitorControl::Continue) => {
929 Ok(bedrock_leveldb::VisitorControl::Continue)
930 }
931 Ok(StorageVisitorControl::Stop) => Ok(bedrock_leveldb::VisitorControl::Stop),
932 Err(error) => {
933 visitor_error = Some(error);
934 Ok(bedrock_leveldb::VisitorControl::Stop)
935 }
936 }
937 });
938 match (scan_result, visitor_error) {
939 (_, Some(error)) => Err(error),
940 (Ok(outcome), None) => Ok(to_storage_outcome(outcome)),
941 (Err(error), None) => Err(map_leveldb_error(error)),
942 }
943 }
944
945 fn for_each_prefix_ref(
946 &self,
947 prefix: &[u8],
948 options: StorageReadOptions,
949 visitor: &mut (dyn FnMut(StorageEntryRef<'_>) -> Result<StorageVisitorControl> + Send),
950 ) -> Result<StorageScanOutcome> {
951 let mut read_options = to_leveldb_read_options(options);
952 read_options.read_strategy = bedrock_leveldb::ReadStrategy::Borrowed;
953 let mut visitor_error = None;
954 let scan_result = self.db.for_each_prefix_ref(prefix, read_options, |entry| {
955 match visitor(StorageEntryRef {
956 key: entry.key.as_bytes(),
957 value: entry.value.as_bytes(),
958 }) {
959 Ok(StorageVisitorControl::Continue) => {
960 Ok(bedrock_leveldb::VisitorControl::Continue)
961 }
962 Ok(StorageVisitorControl::Stop) => Ok(bedrock_leveldb::VisitorControl::Stop),
963 Err(error) => {
964 visitor_error = Some(error);
965 Ok(bedrock_leveldb::VisitorControl::Stop)
966 }
967 }
968 });
969 match (scan_result, visitor_error) {
970 (_, Some(error)) => Err(error),
971 (Ok(outcome), None) => Ok(to_storage_outcome(outcome)),
972 (Err(error), None) => Err(map_leveldb_error(error)),
973 }
974 }
975
976 fn for_each_prefix_key(
977 &self,
978 prefix: &[u8],
979 options: StorageReadOptions,
980 visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
981 ) -> Result<StorageScanOutcome> {
982 let read_options = to_leveldb_read_options(options);
983 let mut visitor_error = None;
984 let scan_result =
985 self.db
986 .for_each_prefix_key(prefix, read_options, |key| match visitor(key) {
987 Ok(StorageVisitorControl::Continue) => {
988 Ok(bedrock_leveldb::VisitorControl::Continue)
989 }
990 Ok(StorageVisitorControl::Stop) => {
991 Ok(bedrock_leveldb::VisitorControl::Stop)
992 }
993 Err(error) => {
994 visitor_error = Some(error);
995 Ok(bedrock_leveldb::VisitorControl::Stop)
996 }
997 });
998 match (scan_result, visitor_error) {
999 (_, Some(error)) => Err(error),
1000 (Ok(outcome), None) => Ok(to_storage_outcome(outcome)),
1001 (Err(error), None) => Err(map_leveldb_error(error)),
1002 }
1003 }
1004
1005 fn write_batch(&self, batch: &StorageBatch) -> Result<()> {
1006 let mut db_batch = bedrock_leveldb::WriteBatch::new();
1007 for op in batch.ops() {
1008 match op {
1009 StorageOp::Put { key, value } => db_batch.put(key.clone(), value.clone()),
1010 StorageOp::Delete { key } => db_batch.delete(key.clone()),
1011 }
1012 }
1013 self.db
1014 .write(db_batch, write_options())
1015 .map_err(map_leveldb_error)
1016 }
1017
1018 fn flush(&self) -> Result<()> {
1019 Ok(())
1020 }
1021
1022 fn compact(&self) -> Result<()> {
1023 Ok(())
1024 }
1025 }
1026
1027 #[cfg(feature = "backend-bedrock-leveldb")]
1028 const fn write_options() -> bedrock_leveldb::WriteOptions {
1029 bedrock_leveldb::WriteOptions { sync: true }
1030 }
1031
1032 #[cfg(feature = "backend-bedrock-leveldb")]
1033 fn map_leveldb_error(error: bedrock_leveldb::LevelDbError) -> BedrockWorldError {
1034 match error.kind() {
1035 bedrock_leveldb::ErrorKind::Cancelled => BedrockWorldError::Cancelled {
1036 operation: "LevelDB scan",
1037 },
1038 bedrock_leveldb::ErrorKind::ReadOnly => BedrockWorldError::ReadOnly,
1039 _ => BedrockWorldError::LevelDb(error.to_string()),
1040 }
1041 }
1042
1043 #[cfg(feature = "backend-bedrock-leveldb")]
1044 fn to_leveldb_read_options(options: StorageReadOptions) -> bedrock_leveldb::ReadOptions {
1045 bedrock_leveldb::ReadOptions {
1046 checksum: bedrock_leveldb::ChecksumMode::Inherit,
1047 cache_policy: match options.cache_policy {
1048 StorageCachePolicy::Bypass => bedrock_leveldb::CachePolicy::Bypass,
1049 StorageCachePolicy::Use => bedrock_leveldb::CachePolicy::Use,
1050 },
1051 read_strategy: bedrock_leveldb::ReadStrategy::Shared,
1052 threading: match options.threading {
1053 StorageThreadingOptions::Auto => bedrock_leveldb::ThreadingOptions::Auto,
1054 StorageThreadingOptions::Fixed(threads) => {
1055 bedrock_leveldb::ThreadingOptions::Fixed(threads)
1056 }
1057 StorageThreadingOptions::Single => bedrock_leveldb::ThreadingOptions::Single,
1058 },
1059 scan_mode: match options.scan_mode {
1060 StorageScanMode::Sequential => bedrock_leveldb::ScanMode::Sequential,
1061 StorageScanMode::ParallelTables => bedrock_leveldb::ScanMode::ParallelTables,
1062 },
1063 pipeline: bedrock_leveldb::ScanPipelineOptions {
1064 queue_depth: options.pipeline.queue_depth,
1065 table_batch_size: options.pipeline.table_batch_size,
1066 progress_interval: options.pipeline.progress_interval,
1067 },
1068 cancel: options
1069 .cancel
1070 .map(|cancel| bedrock_leveldb::ScanCancelFlag::from_shared(cancel.0)),
1071 progress: options.progress.map(|progress| {
1072 bedrock_leveldb::ScanProgressSink::new(move |db_progress| {
1073 progress.emit(StorageScanProgress {
1074 entries_seen: db_progress.visited,
1075 bytes_read: db_progress.bytes_read,
1076 });
1077 })
1078 }),
1079 }
1080 }
1081
1082 #[cfg(feature = "backend-bedrock-leveldb")]
1083 const fn to_storage_outcome(outcome: bedrock_leveldb::ScanOutcome) -> StorageScanOutcome {
1084 StorageScanOutcome {
1085 visited: outcome.visited,
1086 bytes_read: outcome.bytes_read,
1087 stopped: outcome.stopped,
1088 tables_scanned: outcome.tables_scanned,
1089 worker_threads: outcome.worker_threads,
1090 queue_wait_ms: outcome.queue_wait_ms,
1091 cancel_checks: outcome.cancel_checks,
1092 }
1093 }
1094
1095 #[cfg(not(feature = "backend-bedrock-leveldb"))]
1096 #[derive(Debug, Clone, Copy)]
1097 pub struct BedrockLevelDbStorage;
1099
1100 #[cfg(not(feature = "backend-bedrock-leveldb"))]
1101 impl BedrockLevelDbStorage {
1102 pub fn open(_path: impl AsRef<Path>) -> Result<Self> {
1104 Err(BedrockWorldError::LevelDb(
1105 "backend-bedrock-leveldb feature is disabled".to_string(),
1106 ))
1107 }
1108
1109 pub fn open_read_only(_path: impl AsRef<Path>) -> Result<Self> {
1111 Err(BedrockWorldError::LevelDb(
1112 "backend-bedrock-leveldb feature is disabled".to_string(),
1113 ))
1114 }
1115
1116 pub fn open_read_only_best_effort(_path: impl AsRef<Path>) -> Result<Self> {
1118 Err(BedrockWorldError::LevelDb(
1119 "backend-bedrock-leveldb feature is disabled".to_string(),
1120 ))
1121 }
1122 }
1123
1124 #[cfg(not(feature = "backend-bedrock-leveldb"))]
1125 impl WorldStorage for BedrockLevelDbStorage {
1126 fn get(&self, _key: &[u8]) -> Result<Option<Bytes>> {
1127 Err(BedrockWorldError::LevelDb(
1128 "backend-bedrock-leveldb feature is disabled".to_string(),
1129 ))
1130 }
1131
1132 fn get_many(&self, _keys: &[Bytes]) -> Result<Vec<Option<Bytes>>> {
1133 Err(BedrockWorldError::LevelDb(
1134 "backend-bedrock-leveldb feature is disabled".to_string(),
1135 ))
1136 }
1137
1138 fn put(&self, _key: &[u8], _value: &[u8]) -> Result<()> {
1139 Err(BedrockWorldError::LevelDb(
1140 "backend-bedrock-leveldb feature is disabled".to_string(),
1141 ))
1142 }
1143
1144 fn delete(&self, _key: &[u8]) -> Result<()> {
1145 Err(BedrockWorldError::LevelDb(
1146 "backend-bedrock-leveldb feature is disabled".to_string(),
1147 ))
1148 }
1149
1150 fn for_each_key(
1151 &self,
1152 _options: StorageReadOptions,
1153 _visitor: &mut (dyn FnMut(&[u8]) -> Result<StorageVisitorControl> + Send),
1154 ) -> Result<StorageScanOutcome> {
1155 Err(BedrockWorldError::LevelDb(
1156 "backend-bedrock-leveldb feature is disabled".to_string(),
1157 ))
1158 }
1159
1160 fn for_each_prefix(
1161 &self,
1162 _prefix: &[u8],
1163 _options: StorageReadOptions,
1164 _visitor: &mut (dyn FnMut(&[u8], &Bytes) -> Result<StorageVisitorControl> + Send),
1165 ) -> Result<StorageScanOutcome> {
1166 Err(BedrockWorldError::LevelDb(
1167 "backend-bedrock-leveldb feature is disabled".to_string(),
1168 ))
1169 }
1170
1171 fn write_batch(&self, _batch: &StorageBatch) -> Result<()> {
1172 Err(BedrockWorldError::LevelDb(
1173 "backend-bedrock-leveldb feature is disabled".to_string(),
1174 ))
1175 }
1176
1177 fn flush(&self) -> Result<()> {
1178 Err(BedrockWorldError::LevelDb(
1179 "backend-bedrock-leveldb feature is disabled".to_string(),
1180 ))
1181 }
1182
1183 fn compact(&self) -> Result<()> {
1184 Err(BedrockWorldError::LevelDb(
1185 "backend-bedrock-leveldb feature is disabled".to_string(),
1186 ))
1187 }
1188 }
1189}
1190
1191fn check_cancelled(options: &StorageReadOptions) -> Result<()> {
1192 if options
1193 .cancel
1194 .as_ref()
1195 .is_some_and(StorageCancelFlag::is_cancelled)
1196 {
1197 return Err(BedrockWorldError::Cancelled {
1198 operation: "storage scan",
1199 });
1200 }
1201 Ok(())
1202}
1203
1204fn emit_progress(options: &StorageReadOptions, outcome: StorageScanOutcome) {
1205 if let Some(progress) = &options.progress {
1206 progress.emit(StorageScanProgress {
1207 entries_seen: outcome.visited,
1208 bytes_read: outcome.bytes_read,
1209 });
1210 }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use super::*;
1216 #[cfg(feature = "backend-bedrock-leveldb")]
1217 use std::time::{SystemTime, UNIX_EPOCH};
1218
1219 #[test]
1220 fn memory_storage_scans_prefix_without_copying_values() {
1221 let storage = MemoryStorage::new();
1222 storage.put(b"abc1", b"one").expect("put");
1223 storage.put(b"abc2", b"two").expect("put");
1224 storage.put(b"abd", b"three").expect("put");
1225
1226 let mut entries = Vec::new();
1227 storage
1228 .for_each_prefix(b"abc", StorageReadOptions::default(), &mut |key, value| {
1229 entries.push(StorageEntry {
1230 key: Bytes::copy_from_slice(key),
1231 value: value.clone(),
1232 });
1233 Ok(StorageVisitorControl::Continue)
1234 })
1235 .expect("scan");
1236 assert_eq!(entries.len(), 2);
1237 assert_eq!(entries[0].key, Bytes::from_static(b"abc1"));
1238 assert_eq!(entries[1].value, Bytes::from_static(b"two"));
1239 }
1240
1241 #[cfg(feature = "backend-bedrock-leveldb")]
1242 #[test]
1243 fn bedrock_leveldb_storage_roundtrips_raw_records() {
1244 let path = std::env::temp_dir().join(format!(
1245 "bedrock-world-storage-{}",
1246 SystemTime::now()
1247 .duration_since(UNIX_EPOCH)
1248 .expect("time")
1249 .as_nanos()
1250 ));
1251 std::fs::create_dir_all(&path).expect("create");
1252 drop(
1253 bedrock_leveldb::Db::open(&path, bedrock_leveldb::OpenOptions::default())
1254 .expect("initialize"),
1255 );
1256
1257 let storage = backend::BedrockLevelDbStorage::open(&path).expect("open");
1258 storage.put(b"player_1", b"one").expect("put");
1259 storage.put(b"player_2", b"two").expect("put");
1260 storage.flush().expect("flush");
1261 let table_count = std::fs::read_dir(&path)
1262 .expect("read dir")
1263 .filter_map(std::result::Result::ok)
1264 .filter(|entry| {
1265 entry
1266 .path()
1267 .extension()
1268 .is_some_and(|extension| extension == "ldb")
1269 })
1270 .count();
1271 assert_eq!(table_count, 0);
1272
1273 let reopened = backend::BedrockLevelDbStorage::open(&path).expect("reopen");
1274 assert_eq!(
1275 reopened.get(b"player_1").expect("get"),
1276 Some(Bytes::from_static(b"one"))
1277 );
1278 let mut player_count = 0;
1279 reopened
1280 .for_each_prefix(
1281 b"player_",
1282 StorageReadOptions::default(),
1283 &mut |_key, _value| {
1284 player_count += 1;
1285 Ok(StorageVisitorControl::Continue)
1286 },
1287 )
1288 .expect("scan");
1289 assert_eq!(player_count, 2);
1290
1291 std::fs::remove_dir_all(path).expect("cleanup");
1292 }
1293
1294 #[test]
1295 fn pocket_chunks_dat_exposes_virtual_legacy_terrain_records() {
1296 let path = std::env::temp_dir().join(format!(
1297 "bedrock-world-pocket-chunks-{}",
1298 std::time::SystemTime::now()
1299 .duration_since(std::time::UNIX_EPOCH)
1300 .expect("time")
1301 .as_nanos()
1302 ));
1303 std::fs::create_dir_all(&path).expect("create world dir");
1304 let mut terrain = vec![0_u8; POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN];
1305 let block_index = (1_usize << 11) | (3_usize << 7) | 2_usize;
1306 let column_index = 3_usize * 16 + 1_usize;
1307 terrain[block_index] = 42;
1308 terrain[crate::LEGACY_TERRAIN_BLOCK_COUNT
1309 + (crate::LEGACY_TERRAIN_BLOCK_COUNT / 2) * 3
1310 + column_index] = 99;
1311 let mut chunks = vec![0_u8; POCKET_CHUNKS_DAT_SECTOR_BYTES];
1312 chunks[0] = 21;
1313 chunks[1] = 1;
1314 let mut payload = Vec::new();
1315 payload.extend_from_slice(&(POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN as u32).to_le_bytes());
1316 payload.extend_from_slice(&terrain);
1317 chunks.extend_from_slice(&payload);
1318 let padded_len = POCKET_CHUNKS_DAT_SECTOR_BYTES * 22;
1319 chunks.resize(padded_len, 0);
1320 std::fs::write(path.join("chunks.dat"), chunks).expect("write chunks.dat");
1321
1322 let storage = PocketChunksDatStorage::open(&path).expect("open pocket chunks");
1323 let pos = ChunkPos {
1324 x: 0,
1325 z: 0,
1326 dimension: Dimension::Overworld,
1327 };
1328 let legacy_key = ChunkKey::new(pos, ChunkRecordTag::LegacyTerrain).encode();
1329 let missing_key = ChunkKey::new(
1330 ChunkPos {
1331 x: 1,
1332 z: 0,
1333 dimension: Dimension::Overworld,
1334 },
1335 ChunkRecordTag::LegacyTerrain,
1336 )
1337 .encode();
1338
1339 let values = storage
1340 .get_many(&[missing_key.clone(), legacy_key.clone()])
1341 .expect("get many");
1342 assert!(values[0].is_none());
1343 let Some(value) = &values[1] else {
1344 panic!("legacy terrain should be present");
1345 };
1346 assert_eq!(value.len(), LEGACY_TERRAIN_VALUE_LEN);
1347 assert_eq!(
1348 &value[..POCKET_CHUNKS_DAT_TERRAIN_VALUE_LEN],
1349 terrain.as_slice()
1350 );
1351 let terrain = crate::LegacyTerrain::parse(value.clone()).expect("legacy terrain");
1352 assert_eq!(terrain.block_id_at(1, 2, 3), Some(42));
1353 assert_eq!(terrain.height_at(1, 3), Some(99));
1354
1355 let mut keys = Vec::new();
1356 storage
1357 .for_each_key(StorageReadOptions::default(), &mut |key| {
1358 keys.push(Bytes::copy_from_slice(key));
1359 Ok(StorageVisitorControl::Continue)
1360 })
1361 .expect("scan keys");
1362 assert_eq!(keys, vec![legacy_key]);
1363 assert!(storage.put(b"x", b"y").is_err());
1364
1365 std::fs::remove_dir_all(path).expect("cleanup");
1366 }
1367}