121 lines
3.0 KiB
Python
121 lines
3.0 KiB
Python
# Copyright (c) 2026 Dominic Masters
|
|
#
|
|
# This software is released under the MIT License.
|
|
# https://opensource.org/licenses/MIT
|
|
|
|
"""
|
|
Converts DCF chunk files from version 1 to version 2.
|
|
|
|
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]
|
|
|
|
Usage:
|
|
python3 -m tools.asset.chunk <input.dcf> [output.dcf]
|
|
If output is omitted the input file is updated in place.
|
|
"""
|
|
|
|
import struct
|
|
import sys
|
|
import os
|
|
|
|
# Must match src/dusk/rpg/overworld/chunk.h
|
|
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
|
|
|
|
# C enum (int) = 4 bytes; meshvertex_t = uv[2]+pos[3] floats = 20 bytes
|
|
TILE_SIZE = 4
|
|
VERTEX_SIZE = 20 # 2 floats UV + 3 floats pos, MESH_ENABLE_COLOR=0
|
|
|
|
FILE_MAGIC = b'DCF'
|
|
VERSION_IN = 1
|
|
VERSION_OUT = 2
|
|
|
|
|
|
def read_v1(path):
|
|
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('<I', data, 4)[0]
|
|
if version != VERSION_IN:
|
|
raise ValueError(f"{path}: expected version {VERSION_IN}, got {version}")
|
|
|
|
offset = 8
|
|
tiles_bytes = CHUNK_TILE_COUNT * TILE_SIZE
|
|
tiles = data[offset:offset + tiles_bytes]
|
|
offset += tiles_bytes
|
|
|
|
vert_count = struct.unpack_from('<I', data, offset)[0]
|
|
offset += 4
|
|
|
|
verts = data[offset:offset + vert_count * VERTEX_SIZE]
|
|
if len(verts) != vert_count * VERTEX_SIZE:
|
|
raise ValueError(f"{path}: truncated vertex data")
|
|
|
|
return tiles, vert_count, verts
|
|
|
|
|
|
def write_v2(path, tiles, vert_count, verts):
|
|
if vert_count > CHUNK_VERTEX_COUNT:
|
|
print(
|
|
f" Warning: {vert_count} vertices exceeds pool "
|
|
f"({CHUNK_VERTEX_COUNT}); truncating."
|
|
)
|
|
vert_count = CHUNK_VERTEX_COUNT
|
|
verts = verts[:vert_count * VERTEX_SIZE]
|
|
|
|
mesh_count = 1 if vert_count > 0 else 0
|
|
|
|
buf = bytearray()
|
|
buf += FILE_MAGIC
|
|
buf += b'\x00'
|
|
buf += struct.pack('<I', VERSION_OUT)
|
|
buf += tiles
|
|
buf += struct.pack('<B', mesh_count)
|
|
if mesh_count > 0:
|
|
buf += struct.pack('<I', vert_count)
|
|
buf += verts
|
|
|
|
with open(path, 'wb') as f:
|
|
f.write(buf)
|
|
|
|
print(
|
|
f" Wrote {path}: version {VERSION_OUT}, "
|
|
f"{mesh_count} mesh(es), {vert_count} vertices."
|
|
)
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
if not args:
|
|
print("Usage: python3 -m tools.asset.chunk <input.dcf> [output.dcf]")
|
|
sys.exit(1)
|
|
|
|
src = args[0]
|
|
dst = args[1] if len(args) > 1 else src
|
|
|
|
print(f"Reading {src} ...")
|
|
tiles, vert_count, verts = read_v1(src)
|
|
print(f" tiles={CHUNK_TILE_COUNT}, vertices={vert_count}")
|
|
|
|
print(f"Writing {dst} ...")
|
|
write_v2(dst, tiles, vert_count, verts)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|