Reading Unity, Unreal and Godot files by hand
Nobody switches tools if it means rebuilding their world. So before Awaken could render anything interesting, it had to read other engines' native files directly - not "please export a glTF first," the actual .unitypackage, .uasset and Godot project you already have. That turned into a lot of staring at hex, and it's some of my favourite code in the repo. Every importer lands in one shared shape:
flowchart LR U[".unitypackage
(gzip tar + FBX)"] --> IR E[".uasset / .umap
(UE package)"] --> IR G["Godot project
(.tscn / .res)"] --> IR L["glTF / GLB"] --> IR IR["SceneImport IR
(right-handed, Y-up, metres)"] --> W["World + assets"]
The recurring theme across all four: there is no offset table for the geometry. glTF hands it to you; the others make you hunt. Here's how each hides it.
glTF - the one that plays fair
GLB is honest: [u32 magic 'glTF'][version][size] then chunks - a JSON chunk (the scene graph) and a BIN chunk (one buffer). Everything is bufferView → accessor: an offset, a component type (5126 = f32, 5123 = u16…), a count, a stride. You slice and upcast. This is the shape every other format wishes it had, and the yardstick for how much work the others are.
Unity .unitypackage - the geometry is in another file entirely
A .unitypackage is a gzip'd tar, and the first surprise is lovely: each tar folder is named by the asset's GUID, and inside it sits pathname, asset, asset.meta. The GUID is the cross-reference key of the whole format. The .prefab/.unity/.mat files are YAML, delimited by --- !u!<classID> &<fileID> - so you learn Unity's class-ID table by heart (!u!1 GameObject, !u!4 Transform, !u!23 MeshRenderer, !u!43 Mesh, !u!1001 PrefabInstance…).
But the meshes aren't in the YAML. A renderer just says m_Mesh: {fileID, guid}, and that guid points at an FBX file elsewhere in the package. So Unity import is really: parse YAML to build the scene, then decode FBX for geometry, then stitch them by id. Three real scars from doing that:
fileIDs are 64-bit - keep them as strings. Parse one as a JSNumberand the low digits corrupt; every collider then silently resolves to the first mesh in the file. You slice the YAML by the literal string--- !u!43 &<fileId>.- Which submesh does material slot 3 draw? The FBX
.metacarries afileIDToRecycleNametable - fileID → mesh name. You parse it so slot s lands on submesh s of the exact named mesh, no guessing. (The collision-mesh submesh index is even derivable:fileID = 4300000 + 2*index.) - Prefab placement hides behind overrides. A scene
PrefabInstancestores its transform asm_Modifications-{target, propertyPath, value}entries. Group them by target, or a nested child'sm_LocalPosition.x = 0clobbers the root's real position and the whole building collapses to the origin. The true placement is the modification targeting the prefab's root transform (the one withm_Father: {fileID: 0}).
And the detail I still enjoy: Synty trees pack trunk and leaf into one mesh, using the green vertex-colour channel as a 0/1 mask for an in-shader blend. Awaken can't run that shader, so it detects the bimodal green channel and physically splits the triangles into an opaque trunk and an alpha-cut leaf half - and refuses to split if green isn't a clean two-population mask, so it never guesses wrong.
FBX binary - polygons that end in negative numbers
The FBX inside is Kaydara binary: "Kaydara FBX Binary", version as a u32 at byte 23, node records from byte 27. Each node is [endOffset][numProps][propListLen][nameLen][name][props][children], and arrays can be zlib-deflated inline (which is why the whole importer is async). Two details worth carrying:
- Polygon ends are bit-encoded as negative indices.
PolygonVertexIndexis a flat list where the last corner of each face is stored as~index(bitwise NOT). So you walk until a value goes negative, un-negate it, and fan-triangulate that run. Miss it and your triangles smear across polygon boundaries. - Animation time is
1/46186158000-second units (KTIME_PER_SEC), and you sample clips by normalised timeu ∈ [0,1], never by key index - different bones have different key counts, and indexing by key number reads bones at mismatched times, so the pose spasms.
Unreal .uasset - hunting float arrays with no map
Unreal was the detective story. StaticMesh geometry is a MeshDescription serialised inside a UE block-compressed bulk. You scan for the tag 0xc1 0x83 0x2a 0x9e, then read a block table - [blockSize][totalComp][totalUncomp] then per-block [comp][uncomp] sizes. The real lesson is in a code comment: assuming a single block (data right after the tag) works until a mesh exceeds ~128 KB uncompressed, then it spans multiple blocks and you must read the table.
Inside, there's no offset table for the Position and UV arrays. So you find the attribute name string, then anchor on a TArray length: an int32 == count immediately before a run of count × comp plausible floats. "Plausible" needs a validity oracle - a clean() check that rejects true denormals (|v| < 1e-30, what header/string bytes look like as floats) but accepts legitimately tiny coordinates, then clamps positions to ±10,000 cm and rejects the run if the mesh is degenerate. A decoy run can't win because it can't pass the oracle.
Two more that will save you a day each: UE5 Large World Coordinates means an FVector is f64 when its serialised struct size is ≥ 24 bytes, else f32 - you infer the element width purely from the size field, because nothing else in the stream tells you. And Unreal source textures are BGRA, so you swap R and B or your castle comes out sunburnt.
Godot - two containers, and it winds the other way
Godot ships text .tscn/.tres (INI-ish: [node ...] + key = value, with ExtResource("3") / SubResource("2") refs) and binary .res/.scn (magic "RSRC", a header with 11 reserved u32s that will absolutely bite you if you skip them, then string/ext-resource/internal-resource tables). Geometry is an ArrayMesh whose 4.2+ surface is an interleaved buffer - [positions float3][normal+tangent 8 bytes] - with UVs in a separate attribute buffer whose layout you read from ARRAY_FORMAT flags (colour, if present, precedes UV). And the punchline: Godot winds triangles opposite to Awaken's front-face, so an unflipped mesh renders inside-out. Every triangle gets reversed and normals recomputed - verified on an egg mesh, 420/420 normals pointing inward before the fix.
The custom Awaken formats
Awaken's own files come in two shapes. Portable single assets are JSON with a tag:
.awakenscript-{ "forge": "script", "version": 1, id, name, source, kind? }(kind: "editor"marks a tool that never ships)..awakenprefab-{ "forge": "prefab", version, name, scene }; references scripts/materials by id, not embedded, so import prompts you to locate missing dependencies..awakenmat-{ version, id, name, hooks, params }(validates on shape, not a tag).
Whole projects/games are glTF-glb-flavoured binary - [u32 magic][version][jsonLen][JSON metadata][raw blob]:
.awakenproject - magic"FRGE"(0x45475246),PROJECT_VERSION 2(meshes quantised), every buffer 4-byte aligned so typed-array views are valid on decode..fgship container - magic"SFRN", geometry stored raw+quantised in the blob (not base64), so the compressor sees real byte patterns; textures stay base64 since they're already compressed. The whole thing is designed to never materialise a giant JSON string and trip V8's ~512 MB string cap.
The one trick
Strip away the specifics and every non-glTF importer solves the same problem: the geometry has no offset table. Unity finds it via a separate GUID-keyed FBX plus a recycle-name table; Unreal scans for a magic tag and anchors arrays on their TArray length; Godot seeks resources by offset but decodes an untyped Variant stream; FBX bit-encodes polygon ends as negatives. The common defence is a validity oracle - clean-float checks, bounding-box sanity, degenerate-mesh rejection - so a heuristic scan fails safe (a visible placeholder) instead of emitting garbage. Build the oracle first; the scanning is easy once wrong answers can't survive.
📷 Screenshot needed: a Unity or Synty pack freshly imported into Awaken (the fantasy castle is ideal) - "dropped in a native package, and it just opened."