egametang/ET · error · IOException

Invalid magic {header.magic}

Error message

Invalid magic {header.magic}

What it means

Thrown by DtMeshSetReader.ReadHeader when the first int is not the navmesh-set magic (NAVMESHSET_MAGIC) even after an endianness swap. The set magic marks a multi-tile navmesh set file; its absence means the input is not a set file (e.g., it is a single tile mesh, an OBJ, or corrupt).

Source

Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Detour/Io/DtMeshSetReader.cs:83

                throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
            }

            bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
            DtNavMesh mesh = new DtNavMesh(header.option, header.maxVertsPerPoly);
            ReadTiles(bb, is32Bit, header, cCompatibility, mesh);
            return mesh;
        }

        private NavMeshSetHeader ReadHeader(RcByteBuffer bb, int maxVertsPerPoly)
        {
            NavMeshSetHeader header = new NavMeshSetHeader();
            header.magic = bb.GetInt();
            if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
            {
                header.magic = IOUtils.SwapEndianness(header.magic);
                if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
                {
                    throw new IOException("Invalid magic " + header.magic);
                }

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

            header.version = bb.GetInt();
            if (header.version != NavMeshSetHeader.NAVMESHSET_VERSION && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J_1
                                                                      && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
            {
                throw new IOException("Invalid version " + header.version);
            }

            header.numTiles = bb.GetInt();
            header.option = paramReader.Read(bb);
            header.maxVertsPerPoly = maxVertsPerPoly;
            if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
            {
                header.maxVertsPerPoly = bb.GetInt();

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Use DtMeshDataReader for single-tile blobs and DtMeshSetReader for set files.
  2. Verify the file header bytes match NAVMESHSET_MAGIC before reading.
  3. Re-export the set file from a supported tool and confirm round-trip loading.

Example fix

// before
var mesh = setReader.Read(bb); // file is actually a single tile

// after
var data = meshDataReader.Read(bb, nvp, is32Bit);
var mesh = new DtNavMesh();
mesh.AddTile(data, 0, 0);
Defensive patterns

Strategy: validation

Validate before calling

int magic = BitConverter.ToInt32(chunk, 0);
if (magic != NavMeshSetHeader.NAVMESHSET_MAGIC &&
    IOUtils.SwapEndianness(magic) != NavMeshSetHeader.NAVMESHSET_MAGIC)
    throw new InvalidDataException("Not a navmesh set file");

Type guard

static bool IsSetMagic(int m)
    => m == NavMeshSetHeader.NAVMESHSET_MAGIC || IOUtils.SwapEndianness(m) == NavMeshSetHeader.NAVMESHSET_MAGIC;

Try / catch

try { var mesh = setReader.Read(bb); }
catch (IOException e) when (e.Message.Contains("Invalid magic"))
{ /* fall back to single-tile reader or reject */ }

Prevention

When it happens

Trigger: Loading a single-tile DtMeshData blob through the set reader; pointing at a non-navmesh file; a truncated or re-encoded asset.

Common situations: Wrong reader selected for the file kind; corrupt asset; wrong file extension mapping.

Related errors


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