A packer for .bfres.mc model files from The Legend of Zelda: Tears of the Kingdom.
Existing tools only go one way. MeshCodec
and the GameBanana decompressors unpack these files; Switch Toolbox reads them
too, and its CompressMeshCodec writes a plain zstd stream with no FMSH
section — which the game rejects by hanging on the boot logo.
This library writes files the game actually loads, including the FMSH geometry section. As far as I can tell, no other public tool does.
📄 Full format specification — every field, verified against retail files.
Three findings decided the whole thing, and each cost a failed attempt on real hardware:
The game requires an FMSH section. A file without one never loads, even
when its decompressed buffer is byte-identical to the original. Bit 4 of the
MCPK flags field announces the section — set it without shipping one and the
game hangs looking for geometry that is not there.
The zstd stream has to look like Nintendo's. A 128 KiB window
(windowLog = 17), because the game streams the frame through
ZSTD_decompressContinue with a matching buffer. And few blocks — every
shipped asset is a single block, while compression levels above 15 split the
stream into dozens.
Large buffers must be split across frames. NullCodec::Decompress copies
at most min(remaining, 0x40000) bytes per call and runs once per frame. Miss
this and the tail of your geometry is never copied: the model comes out
shredded into spikes, or the game hangs outright.
git clone https://github.com/UltimateBomb/totk-meshcodec
cd totk-meshcodec
pip install -e .Requires Python 3.8+ and zstandard.
# inspect a file
mcpack info Link.Link_RaulSkin.bfres.mc
# repack a model, reusing the geometry of the original
mcpack pack model.bfres out.bfres.mc --fmsh-from original.bfres.mc
# pull the BFRES body back out
mcpack unpack Link.Link_RaulSkin.bfres.mc model.bfresLink.Link_RaulSkin.bfres.mc (76123 bytes)
MCPK version : 0.1.1
flags : 0x0000079c
buffer size : 245760
zstd stream : 15012 bytes
FMSH @0x3ab0 : 61099 bytes
indexOut=35632 vertexOut=139768
from mcpack import pack, build_fmsh, split_geometry
buffer = open("model.bfres", "rb").read()
# geometry lives outside the BFRES body
indices, vertices = split_geometry(buffer, geom_offset=0x107d0,
index_size=35632, vertex_size=139768)
fmsh = build_fmsh(indices, vertices) # frames are split automatically
data, info = pack(buffer, fmsh)
open("out.bfres.mc", "wb").write(data)
print(info["buffer_size"]) # 245760Repacking a retail asset and decoding it with the original MeshCodec decompressor reproduces the source byte for byte:
Link.Link_RaulSkin 245760 bytes sha256 b24acc3e4ba5cb1a identical
Link.Link_RaulSkin_RightHand 557056 bytes sha256 b2a7676caa2aba4a identical
The MCPK header comes out identical to Nintendo's, down to the flags field.
Custom geometry built with this library loads on a real Switch.
pytest tests/
ruff check mcpack tests examplesA worked example lives in examples/repack_model.py:
it locates the geometry in a decompressed buffer, encodes an FMSH section and
writes a loadable file.
Replacing a model needs RESTBL updated too — it stores the buffer size the
game allocates per resource, and an oversized asset silently fails to load.
Paths there are hashed without the .mc suffix. The structure is documented in
docs/FORMAT.md.
- dt-12345/MeshCodec — the decompressor this work builds on. The format could not have been read without it.
- Watertoon and Lord Bubbles — earlier decompression tools for the community.
MIT — see LICENSE.
Ships no game assets. You need your own legally obtained copy of the game.