readFsceneb function

SceneDocument readFsceneb(
  1. Uint8List bytes
)

Parses a .fsceneb container from bytes into a SceneDocument with each embedded payload's PayloadSpec.bytes attached.

Tolerates the same JSONC superset and runs the same version migration as readFscene for the manifest. Throws a FscenebFormatException on a bad magic, an unsupported container version, or a missing manifest.

Implementation

SceneDocument readFsceneb(Uint8List bytes) {
  if (bytes.length < _headerByteLength) {
    throw const FscenebFormatException('Truncated container (no header)');
  }
  for (var i = 0; i < 4; i++) {
    if (bytes[i] != _magic[i]) {
      throw const FscenebFormatException(
        'Not a .fsceneb container (bad magic)',
      );
    }
  }
  final view = ByteData.sublistView(bytes);
  final version = view.getUint32(4, Endian.little);
  if (version > kFscenebVersion) {
    throw FscenebFormatException(
      'Container version $version is newer than supported $kFscenebVersion',
    );
  }
  final total = view.getUint32(8, Endian.little);
  if (total > bytes.length) {
    throw const FscenebFormatException('Container length exceeds the data');
  }

  String? manifest;
  final blobs = <LocalId, Uint8List>{};
  var offset = _headerByteLength;
  while (offset + 8 <= total) {
    final dataLength = view.getUint32(offset, Endian.little);
    final type = ascii.decode(
      Uint8List.sublistView(bytes, offset + 4, offset + 8),
    );
    final dataStart = offset + 8;
    final dataEnd = dataStart + dataLength;
    if (dataEnd > total) {
      throw const FscenebFormatException('Chunk extends past the container');
    }
    final data = Uint8List.sublistView(bytes, dataStart, dataEnd);
    switch (type) {
      case _chunkJson:
        manifest = utf8.decode(data);
      case _chunkBlob:
        final (id, payload) = _decodeBlob(data);
        blobs[id] = payload;
      default:
        break; // Skip unrecognized chunk types.
    }
    final padded = dataLength + ((-dataLength) & (_alignment - 1));
    offset = dataStart + padded;
  }

  if (manifest == null) {
    throw const FscenebFormatException('Container has no JSON manifest chunk');
  }
  final document = readFscene(manifest);
  blobs.forEach((id, payload) {
    document.payload(id)?.bytes = payload;
  });
  return document;
}