egametang/ET · error · IOException

Invalid magic

Error message

Invalid magic

What it means

DtTileCacheReader.Read parses a tile-cache *set* file. The first int must be TILECACHESET_MAGIC ('T','S','E','T' = 0x54534554); if it fails the reader tries byte-swapping once and re-checks, throwing only if both fail. So this error means the bytes are not a tile-cache set in either endianness.

Source

Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/Io/DtTileCacheReader.cs:53

            _compFactory = compFactory;
        }

        public DtTileCache Read(BinaryReader @is, int maxVertPerPoly, IDtTileCacheMeshProcess meshProcessor)
        {
            RcByteBuffer bb = IOUtils.ToByteBuffer(@is);
            return Read(bb, maxVertPerPoly, meshProcessor);
        }

        public DtTileCache Read(RcByteBuffer bb, int maxVertPerPoly, IDtTileCacheMeshProcess meshProcessor)
        {
            DtTileCacheSetHeader header = new DtTileCacheSetHeader();
            header.magic = bb.GetInt();
            if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC)
            {
                header.magic = IOUtils.SwapEndianness(header.magic);
                if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC)
                {
                    throw new IOException("Invalid magic");
                }

                bb.Order(bb.Order() == RcByteOrder.BIG_ENDIAN ? RcByteOrder.LITTLE_ENDIAN : RcByteOrder.BIG_ENDIAN);
            }

            header.version = bb.GetInt();
            if (header.version != DtTileCacheSetHeader.TILECACHESET_VERSION)
            {
                if (header.version != DtTileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
                {
                    throw new IOException("Invalid version");
                }
            }

            bool cCompatibility = header.version == DtTileCacheSetHeader.TILECACHESET_VERSION;
            header.numTiles = bb.GetInt();
            header.meshParams = paramReader.Read(bb);
            header.cacheParams = ReadCacheParams(bb, cCompatibility);

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Verify the file is a tile-cache set produced by DtTileCacheWriter (magic 'TSET').
  2. Check the file is not truncated or corrupted (compare length/hash against the baked asset).
  3. Ensure you are calling the set reader (DtTileCacheReader) only on full tile-cache set files, not individual layers or navmesh tiles.

Example fix

// before: feeding the wrong blob
using var fs = File.OpenRead(path); // might be a navmesh, not a tilecache set
var tc = reader.Read(bb, maxVertPerPoly, proc);

// after: confirm magic before handing off
int magic = bb.GetInt();
if (magic != DtTileCacheSetHeader.TILECACHESET_MAGIC
 && IOUtils.SwapEndianness(magic) != DtTileCacheSetHeader.TILECACHESET_MAGIC)
    throw new InvalidDataException("not a tile-cache set file");
bb.RewindInt();
var tc = reader.Read(bb, maxVertPerPoly, proc);
Defensive patterns

Strategy: validation

Validate before calling

int magic = bb.GetInt();
if (magic != DtTileCacheSetHeader.TILECACHESET_MAGIC
 && IOUtils.SwapEndianness(magic) != DtTileCacheSetHeader.TILECACHESET_MAGIC)
    throw new InvalidDataException("not a tile-cache set file (bad magic)");

Try / catch

try { return reader.Read(bb, maxVertPerPoly, proc); }
catch (IOException e) when (e.Message == "Invalid magic")
{ /* file is not a tile-cache set; do not retry unchanged */ }

Prevention

When it happens

Trigger: Calling DtTileCacheReader.Read on a file that isn't a tile-cache set (e.g. a raw navmesh, a single layer, or unrelated bytes); a truncated/corrupt header where even the swapped int isn't the magic.

Common situations: Loading the wrong asset (navmesh .bin mistaken for a tile-cache set); corrupt download; feeding a single layer blob to the set reader.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/f9e2c0fbdde433f0. Report an issue: GitHub.