Skip to content

File formats

As of 2026-10-19 (vulture-ios 3176c4cc4, vulture-android 85fd9cc6).

IGC is the only flight format. Waypoints and tasks use SeeYou CUP. Airspace comes as OpenAir text, SeeYou CUB (binary) or openAIP XML. There is no Vulture file format of its own: no .vulture type exists.

FormatExtensionContentiOS iniOS outAndroid inAndroid out
IGC flight log.igctext, A/H/I/B/C/E/K/L recordsYesYes (share, recording, backup)YesYes (share, export)
SeeYou waypoints and tasks.cupCSV-like textYesYes (waypoint share, task export, backup)Decoder only, no import screen foundTasks in the export ZIP
SeeYou airspace.cubbinaryYesYes (share)YesYes (export ZIP)
OpenAir airspace.txttextYesYes (share)YesYes (export ZIP)
openAIP airspace.aip (an openAIP _asp.xml renamed by vulture-web)XMLYesYes (share)YesYes (export ZIP)
XCTrack taskQR code, XCTSK: or XCTSKZ:JSON, deflated for XCTSKZ:Yes (task scanner)NoDecoder onlyNo
LXNAV Nano declarationfile named decltextNoYes (task export)NoNo
FLARM declarationflarmcfg.txttextNoYes (task export, also sent over Wi-Fi)NoRemoved
Backup / export all data.zipfolder tree, see belowNoYesYes (any ZIP of flights, including the iOS backup)Yes
Map image.pngimageNoYesNoYes
FormatiOSAndroid
IGCvulture-ios/Packages/Flights/Sources/Flights/Decorder/FlightDecoder.swift (A, H, I, B, E, C and K records, I-record extensions such as ENL, MOP and FXA)vulture-android/domain/flight/src/main/kotlin/be/fousa/vulture/domain/flight/igc/IgcParser.kt
CUPvulture-ios/Packages/Waypoints/Sources/Waypoints/WaypointsDecoder.swift, WaypointsEncoder.swiftvulture-android/domain/waypoints/.../WaypointsDecoder.kt, WaypointsEncoder.kt
CUBvulture-ios/Packages/Airspace/Sources/Airspace/Decoder/AirspaceCUBDecoder.swiftvulture-android/domain/airspace decoders
OpenAir.../Airspace/Decoder/AirspaceOpenAirDecoder.swiftvulture-android/domain/airspace decoders
openAIP.../Airspace/Decoder/AirspaceOpenAipDecoder.swiftvulture-android/domain/airspace decoders
XCTrack QRvulture-ios/Packages/Waypoints/Sources/Waypoints/XCTrackTaskDecoder.swift, used by TaskScreens/.../TaskScannerViewController.swiftvulture-android/domain/waypoints/.../XCTrackTaskDecoder.kt
Task exportsvulture-ios/Packages/Formatting/Sources/Formatting/TaskExportType.swift (cup, nano, flarm)CUP inside the export ZIP only

The parsers are held to the same output by the golden data. See Golden data and Flight analysis.

Both platforms decide the airspace type the same way. First they look at the extension (txt, aip or cub, in any case). If the extension does not say, they look at the content. The CUB format has a binary signature.

  • iOS: AirspaceFileType.swift, File+Airspace.swift
  • Android: vulture-android/domain/airspace/.../model/AirspaceFileType.kt (fromExtension, detect)

On iOS the extension switch is repeated in several places: SceneDelegate.swift, SplitViewModel.swift, DroppedFileLoader.swift, the QuickLook and Thumbnail view models, and AirspaceFileType.swift. A new format has to be added to all of them.

The iOS recorder (iPhone and Watch) writes the file in vulture-ios/Packages/FlightRecording/Sources/FlightRecording/Location/Extensions/FixRecord+IGC.swift. Lines are joined with \n. Android does not record flights in v1. When it does, it must write the same records, so that the backend, the services and the analysis treat both recordings alike.

RecordValueNotes
AAVUL<identifierForVendor>The vendor UUID of the device, or 000
HFDTEthe date of the first fix, DDMMYY
HFPLTPILOTdeclared pilotFalls back to a localised “unknown pilot” text
HFCM2CREW2declared passengerOnly when set
HFGTYGLIDERTYPEdeclared glider typeFalls back to a localised “unknown glider” text
HFGIDGLIDERIDregistrationOnly when set
HFCIDCOMPETITIONIDcompetition idOnly when set
HFCCLCOMPETITIONCLASScompetition classOnly when set
HFRFWFIRMWAREVERSIONthe app’s marketing version
HFFTYFRTYPEVulture, or Vulture Watch on the Watch
I013638FXAFXA in bytes 36 to 38Every B record writes 007 there
C…the task declaration, when a task is setDate, time, 000000, 0001, turnpoint count, then a C0000000N00000000E line, the waypoints, and another C0000000N00000000E line
LNAVOZN=<n>,Style=…,R1=…m,R2=…m,A1=…,A2=…,A12=…,Line=1,AAT=1one per zoneLXNAV observation-zone lines; values that are zero are left out
E<time>PEVpilot event
LLXVSPO2<SpO2×10>HR<bpm>oxygen saturation and heart rateWhen health data is recorded (Watch)
Btime, lat, lon, A, GPS altitude, then GPS altitude again in the pressure column, 007No barometric altitude is written

The declared values come from the synced declared* settings. See persistence and settings §3.

The Nano (decl) and FLARM (flarmcfg.txt) exports start with their own header lines (vulture-ios/Packages/Formatting/Sources/Formatting/String+Nano.swift, String+Flarm.swift). For example, the Nano export writes HFDTE, HFFXA999, HFDTMDTM100GPSDATUM:WGS-1984, HFFTYFRTYPE:Vulture and the declared pilot.

Both platforms write the same layout:

  • iOS: vulture-ios/Modules/App/Sources/Services/System/Backup.swift
  • Android: vulture-android/feature/settings/src/main/kotlin/be/fousa/vulture/feature/settings/export/AllDataArchive.kt
<yyyy-MM-dd>.zip
└─ <yyyy-MM-dd>/ the day of the export, device time zone
├─ <year>/<flight>.igc each logbook flight, in its section (takeoff year), under its file name
├─ <task>.cup every task in one SeeYou file, if there are any
├─ Waypoints/<file> waypoint files (folder name translated: file.export.directory.waypoints)
└─ Airspace/<file> airspace files (folder name translated: file.export.directory.airspace)

The two platforms differ in three ways:

  • Missing name or takeoff time. iOS skips a flight without a file name or section. Android names it as the share sheet does and files it under the year it was imported.
  • IGC bytes. iOS writes its stored text re-encoded as UTF-8. Android writes the bytes as stored.
  • Import. Only Android imports a ZIP or a folder (decision §6 in vulture-android/docs/decisions.md), so an iOS backup can be read on Android.

These are declared in vulture-ios/Modules/App/Resources/Info.plist. Every imported type conforms to public.content and public.data.

UTI (UTImportedTypeDeclarations)ExtensionDescriptionDocument type (CFBundleDocumentTypes)
public.igcigcIGC FileIGC File, rank Default
public.cupcupCUP FileCUP File, rank Default
public.cubcubCUB FileCUB File, rank Default
public.aipaipOpenAip FileOpenAIP File, rank Default
public.plain-text (system type)txtn/aOpenAir File, rank Default

public.igc, public.cup, public.cub and public.aip are imported declarations in Apple’s reserved public. namespace, not types that Vulture exports. Opening a file goes through SceneDelegate (switch firstURL.pathExtension.lowercased()), which reports openFlightInVulture, openWaypointsInVulture or openAirspaceInVulture (see Analytics events). Quick Look previews and thumbnails exist for IGC, CUP, CUB and AIP files (vulture-ios/Modules/QuickLook, Modules/Thumbnail).

These are on MainActivity in vulture-android/app/src/main/AndroidManifest.xml:

FilterActionDataPurpose
Files by MIME typeVIEWschemes content and file; application/vnd.fai.igc, application/x-igc, application/octet-stream, text/plain, application/zip, application/x-zip-compressed“Open with” for IGC, airspace text and ZIP files
Files by extensionVIEWschemes content and file, host *, */*, pathPattern .*\.igc, .*\..*\.igc, .*\..*\..*\.igc, .*\.IGC, .*\..*\.IGCFiles whose provider gives no useful MIME type
Share sheetSEND, SEND_MULTIPLEapplication/vnd.fai.igc, application/x-igc, application/octet-stream, application/zip, application/x-zip-compressedShare into Vulture. text/plain goes to ShareLinkActivity instead (see Deep links)

The MIME type does not decide the format. Content is sniffed after the file is copied in (ExternalFileIntents, :data:db import, AirspaceFileType.detect). A raw import reads at most 64 MiB per file or ZIP entry (FlightImporter.MAX_FILE_BYTES). Without the several flights Pro feature, a batch opened from another app keeps only its first flight.

Outgoing files are shared through the FileProvider in :core:share.

The sync schema keeps the IGC gzipped in Cloud Storage and caps each stored object at 32 MB. IGC compresses about 5×, so this sits above the 64 MiB raw import cap. See Sync schema, “Storage objects” in §2.