177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
import nbtlib
|
|
import nbtlib.tag
|
|
from nbtlib.tag import Int
|
|
import gzip
|
|
import os
|
|
import sys # Import sys to print interpreter info
|
|
|
|
NBTFormatError = getattr(nbtlib, 'NBTFormatError', None)
|
|
if NBTFormatError is None:
|
|
# Fallback for older nbtlib where it might have been in nbtlib.exceptions
|
|
try:
|
|
import nbtlib.exceptions
|
|
NBTFormatError = nbtlib.exceptions.NBTFormatError
|
|
except ImportError:
|
|
NBTFormatError = Exception # Generic fallback if not found
|
|
|
|
def parse_schem_file(filepath):
|
|
"""
|
|
Parses a .schem file and extracts its main components.
|
|
|
|
Args:
|
|
filepath (str): The path to the .schem file.
|
|
|
|
Returns:
|
|
dict: A dictionary containing schematic dimensions, block palette,
|
|
block data, and potentially block entities and entities.
|
|
Returns None if parsing fails.
|
|
"""
|
|
if not os.path.exists(filepath):
|
|
print(f"Error: File not found at {filepath}")
|
|
return None
|
|
|
|
try:
|
|
# nbtlib can directly load GZipped NBT files
|
|
schematic = nbtlib.load(filepath)
|
|
|
|
# Access the root compound tag
|
|
root = schematic
|
|
|
|
# --- Extract Header Information ---
|
|
version = root["Version"]
|
|
if version != Int(2):
|
|
print(f"Warning: Schematic version is {version}. Expected 2 for .schem files. May not parse correctly.")
|
|
|
|
width = root["Width"].value
|
|
height = root["Height"].value
|
|
length = root["Length"].value
|
|
|
|
print(f"Schematic Dimensions: Width={width}, Height={height}, Length={length}")
|
|
print(f"Schematic Version: {version}")
|
|
|
|
# --- Extract Block Palette ---
|
|
block_palette_nbt = root["BlockPalette"]
|
|
block_palette = {}
|
|
# The BlockPalette in .schem is a compound where keys are block string IDs
|
|
# and values are integer IDs. We reverse this to map int ID -> string ID.
|
|
for block_id_str, int_id_tag in block_palette_nbt.items():
|
|
block_palette[int_id_tag.value] = block_id_str
|
|
|
|
print(f"\nBlock Palette ({len(block_palette)} entries):")
|
|
# print(block_palette) # Uncomment to see the full palette
|
|
|
|
# --- Extract Block Data ---
|
|
# BlockData is typically a byte array or sometimes an int array
|
|
block_data_raw = root["BlockData"].value # This is a bytes object
|
|
|
|
# BlockData in .schem is often variable-length encoded (VLQ)
|
|
# We need to decode it to get the actual block IDs
|
|
# This part requires a custom VLQ decoder. nbtlib doesn't do this automatically.
|
|
|
|
# A simple VLQ decoder (from WorldEdit docs / common implementations)
|
|
# This function reads integers that are variable-length encoded.
|
|
# Each byte, if its 7th bit is 1, indicates more bytes follow.
|
|
# The actual value is in the lower 7 bits.
|
|
|
|
decoded_block_ids = []
|
|
i = 0
|
|
while i < len(block_data_raw):
|
|
value = 0
|
|
j = 0
|
|
while True:
|
|
byte = block_data_raw[i]
|
|
value |= (byte & 0x7F) << (7 * j)
|
|
i += 1
|
|
if not (byte & 0x80): # If the 7th bit is 0, this is the last byte
|
|
break
|
|
j += 1
|
|
if j > 5: # Prevent infinite loops for corrupted data
|
|
raise Exception("Corrupted VLQ data: too many bytes in integer")
|
|
decoded_block_ids.append(value)
|
|
|
|
print(f"\nDecoded Block Data Length: {len(decoded_block_ids)}")
|
|
# print(decoded_block_ids[:10]) # Print first 10 decoded IDs
|
|
|
|
# --- Reconstruct 3D block array (optional, but useful for processing) ---
|
|
# Order is typically (Y * Length + Z) * Width + X
|
|
blocks_3d = [[[None for _ in range(width)] for _ in range(length)] for _ in range(height)]
|
|
|
|
for y in range(height):
|
|
for z in range(length):
|
|
for x in range(width):
|
|
# Calculate the 1D index
|
|
idx_1d = (y * length + z) * width + x
|
|
|
|
if idx_1d < len(decoded_block_ids):
|
|
palette_id = decoded_block_ids[idx_1d]
|
|
block_string_id = block_palette.get(palette_id, "minecraft:unknown")
|
|
blocks_3d[y][z][x] = block_string_id
|
|
else:
|
|
blocks_3d[y][z][x] = "minecraft:missing"
|
|
|
|
|
|
# --- Extract Block Entities and Entities (if present) ---
|
|
block_entities = root.get("BlockEntities", nbtlib.tag.List()) # List of Compound Tags
|
|
entities = root.get("Entities", nbtlib.tag.List()) # List of Compound Tags
|
|
|
|
print(f"\nNumber of Block Entities: {len(block_entities)}")
|
|
if len(block_entities) > 0:
|
|
# print("First Block Entity:", block_entities[0].pretty_tree()) # Pretty print first one
|
|
pass # Or process them further
|
|
|
|
print(f"Number of Entities: {len(entities)}")
|
|
if len(entities) > 0:
|
|
# print("First Entity:", entities[0].pretty_tree()) # Pretty print first one
|
|
pass # Or process them further
|
|
|
|
return {
|
|
"version": version,
|
|
"width": width,
|
|
"height": height,
|
|
"length": length,
|
|
"block_palette": block_palette,
|
|
"decoded_block_ids": decoded_block_ids,
|
|
"blocks_3d": blocks_3d,
|
|
"block_entities": block_entities,
|
|
"entities": entities
|
|
}
|
|
|
|
except NBTFormatError as e:
|
|
print(f"Error: Not a valid NBT or schematic file. {e}")
|
|
except KeyError as e:
|
|
print(f"Error: Missing expected NBT tag in schematic. Likely not a valid .schem file or corrupted. Missing tag: {e}")
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred: {e}")
|
|
|
|
return None
|
|
|
|
# def import_schem_file(filepath):
|
|
# nbtfile = nbtlib.load(filepath)
|
|
# input()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# inputfile = "18805.schem"
|
|
# import_schem_file(inputfile)
|
|
schem_file_path = "18805.schem"
|
|
|
|
parsed_data = parse_schem_file(schem_file_path)
|
|
if parsed_data:
|
|
print("\nSuccessfully parsed schematic data.")
|
|
# You can now access and process the data:
|
|
# print(parsed_data["blocks_3d"][0][0][0]) # Block at Y=0, Z=0, X=0
|
|
# print(parsed_data["block_palette"])
|
|
|
|
# Example: Print a top-down view of the first layer (Y=0)
|
|
print("\n--- Top-Down View of Layer 0 (Y=0) ---")
|
|
for z in range(parsed_data["length"]):
|
|
row_blocks = []
|
|
for x in range(parsed_data["width"]):
|
|
# Get the block string ID and truncate for display
|
|
block_id = parsed_data["blocks_3d"][0][z][x]
|
|
row_blocks.append(block_id.replace("minecraft:", "")[:6].ljust(6)) # Shorten for display
|
|
print(" ".join(row_blocks))
|
|
print("---------------------------------------")
|
|
|
|
|