egametang/ET · error · IOException

Invalid version

Error message

Invalid version

What it means

After the set magic passes, DtTileCacheReader.Read checks the version int. It accepts the current TILECACHESET_VERSION (1) or the legacy recast4j version (0x8801); any other value throws 'Invalid version'. The cCompatibility flag is then derived from which version matched, so a truly unknown version cannot be safely parsed.

Source

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

            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);
            DtNavMesh mesh = new DtNavMesh(header.meshParams, maxVertPerPoly);
            IRcCompressor comp = _compFactory.Create(cCompatibility ? 0 : 1);
            DtTileCacheStorageParams storageParams = new DtTileCacheStorageParams(bb.Order(), cCompatibility);
            DtTileCache tc = new DtTileCache(header.cacheParams, storageParams, mesh, comp, meshProcessor);
            // Read tiles.
            for (int i = 0; i < header.numTiles; ++i)
            {
                long tileRef = bb.GetInt();
                int dataSize = bb.GetInt();
                if (tileRef == 0 || dataSize == 0)
                {

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Re-export the tile-cache set with the consuming DotRecast version.
  2. Keep producer and consumer library versions aligned in CI/asset baking.
  3. If migrating from recast4j, confirm the file carries version 0x8801 so the legacy path is used.

Example fix

// before: set baked on an incompatible version
var tc = reader.Read(bb, maxVertPerPoly, proc);

// after: re-bake with current version, or verify version up front
int v = bb.GetInt();
if (v != DtTileCacheSetHeader.TILECACHESET_VERSION
 && v != DtTileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
    throw new InvalidDataException($"unsupported tilecache set version {v}");
Defensive patterns

Strategy: try-catch

Validate before calling

int v = bb.GetInt(); // after magic
if (v != DtTileCacheSetHeader.TILECACHESET_VERSION
 && v != DtTileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
    throw new InvalidDataException($"unsupported tile-cache set version {v}");

Try / catch

try { return reader.Read(bb, maxVertPerPoly, proc); }
catch (IOException e) when (e.Message == "Invalid version")
{ /* re-export the set with the current version */ }

Prevention

When it happens

Trigger: Loading a tile-cache set written by a DotRecast/recast4j version whose set version is neither 1 nor 0x8801; bytes whose version int is corrupt after a valid magic.

Common situations: Cross-version asset loading after a format change; hand-edited or partially overwritten files; producer and consumer on mismatched library versions.

Related errors


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