# Copyright (c) 2026 Dominic Masters # # This software is released under the MIT License. # https://opensource.org/licenses/MIT """ Converts DCF chunk files (version 1 or 2) to version 3 and generates the companion DMF (Dusk Mesh Format) files that version 3 references. Version 1 format (after 8-byte header + tiles): uint32_t vertCount meshvertex_t vertices[vertCount] Version 2 format (after 8-byte header + tiles): uint8_t meshCount for each mesh: uint32_t vertCount meshvertex_t vertices[vertCount] Version 3 format (after 8-byte header + tiles): uint8_t meshCount for each mesh: null-terminated string (path to companion .dmf asset) DMF format: Bytes 0-3: DMF\x00 Bytes 4-7: uint32_t version = 1 (little-endian) Bytes 8-11: uint32_t vertCount (little-endian) Bytes 12+: meshvertex_t vertices[vertCount] Usage: python3 -m tools.asset.chunk [output.dcf] If output is omitted the input file is updated in place. DMF files are written to assets/meshes/ beside the chunks/ directory. """ import struct import sys import os CHUNK_WIDTH = 16 CHUNK_HEIGHT = 16 CHUNK_DEPTH = 32 CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 8192 CHUNK_MESH_COUNT_MAX = 10 CHUNK_VERTEX_COUNT = 8192 CHUNK_MESH_NAME_MAX = 64 TILE_SIZE = 4 VERTEX_SIZE = 20 FILE_MAGIC = b'DCF' DMF_MAGIC = b'DMF\x00' VERSION_OUT = 3 DMF_VERSION = 1 def read_dcf(path): """Read a v1 or v2 DCF file. Returns (tiles, meshes) where meshes is a list of raw vertex byte strings, one per mesh.""" with open(path, 'rb') as f: data = f.read() if data[:3] != FILE_MAGIC: raise ValueError(f"{path}: not a DCF file") version = struct.unpack_from(' 0: meshes.append(verts) else: mesh_count = data[offset] offset += 1 for _ in range(mesh_count): vert_count = struct.unpack_from(' 0: meshes.append(verts) return tiles, meshes def write_dmf(path, vertex_bytes): vert_count = len(vertex_bytes) // VERTEX_SIZE buf = bytearray() buf += DMF_MAGIC buf += struct.pack('= CHUNK_MESH_NAME_MAX: raise ValueError( f"Mesh name too long (>= {CHUNK_MESH_NAME_MAX}): {name}" ) buf += encoded + b'\x00' buf += struct.pack('<3f', offset[0], offset[1], offset[2]) with open(dcf_path, 'wb') as f: f.write(buf) print( f' Wrote DCF {dcf_path}: ' f'version {VERSION_OUT}, {mesh_count} mesh(es)' ) def main(): args = sys.argv[1:] if not args: print( "Usage: python3 -m tools.asset.chunk " " [output.dcf]" ) sys.exit(1) src = args[0] dst = args[1] if len(args) > 1 else src print(f"Reading {src} ...") tiles, meshes = read_dcf(src) print( f" tiles={CHUNK_TILE_COUNT}, " f"meshes={len(meshes)}, " f"total_verts=" f"{sum(len(m) // VERTEX_SIZE for m in meshes)}" ) # Derive chunk base name from the DCF filename (e.g. "0_0_0" from # "0_0_0.dcf") to name DMF files "chunk_0_0_0_0.dmf" etc. base = os.path.splitext(os.path.basename(dst))[0] # assets/meshes/ sits beside assets/chunks/ (one dir up from the DCF). meshes_dir = os.path.normpath( os.path.join(os.path.dirname(os.path.abspath(dst)), '..', 'meshes') ) os.makedirs(meshes_dir, exist_ok=True) print(f"Writing DMF files to {meshes_dir} ...") mesh_names = [] for idx, verts in enumerate(meshes): dmf_filename = f'chunk_{base}_{idx}.dmf' dmf_path = os.path.join(meshes_dir, dmf_filename) write_dmf(dmf_path, verts) mesh_names.append(f'meshes/{dmf_filename}') print(f"Writing {dst} ...") write_v3(dst, tiles, mesh_names) if __name__ == '__main__': main()