Task 119: .CUBE File Format

Task 119: .CUBE File Format

.CUB File Format Specifications

The .CUB file format, as specified in the Naviter documentation, is designed for storing airspaces and NOTAMs (Notices to Airmen). It comprises a header followed by item structures and point data. The format supports optional encryption for data after the header, but the specification assumes unencrypted data (IsSecured = 0) for standard processing. All multi-byte integers are in little-endian byte order unless PcByteOrder indicates otherwise.

1. List of Properties Intrinsic to the File Format

The properties are derived from the file's structural components: the CubHeader (fixed at 210 bytes), multiple CubItem structures (each 42 bytes, padded if necessary), and associated CubPoint data. These properties define the file's metadata, bounding boxes, item details, and geometric points. Below is a comprehensive list of all properties, including data types, sizes, and descriptions:

CubHeader Properties (Offset 0, Size: 210 bytes)

  • Ident: UINT32 (4 bytes) - Identifier; must be 0x425543C2.
  • Title: CHAR[122] (122 bytes) - Copyright notice or title string.
  • AllowedSerials: UINT16[8] (16 bytes) - Array of up to 8 device serial numbers allowed to read the file (used only if IsSecured = 2; 0 otherwise).
  • PcByteOrder: UINT8 (1 byte) - Byte order flag; 0 indicates multi-byte integers need reversal.
  • IsSecured: UINT8 (1 byte) - Encryption type for data after header; 0 for unencrypted.
  • Crc32: UINT32 (4 bytes) - CRC32 checksum (ignored in processing).
  • Key: UINT8[16] (16 bytes) - Encryption key (used if IsSecured > 0).
  • SizeOfItem: INT32 (4 bytes) - Size of each CubItem structure.
  • SizeOfPoint: INT32 (4 bytes) - Size of each CubPoint structure.
  • HdrItems: INT32 (4 bytes) - Number of CubItem structures in the file.
  • MaxPts: INT32 (4 bytes) - Maximum number of points across all items.
  • Left: FLOAT (4 bytes) - Left boundary of the overall data bounding box (in radians).
  • Top: FLOAT (4 bytes) - Top boundary of the overall data bounding box (in radians).
  • Right: FLOAT (4 bytes) - Right boundary of the overall data bounding box (in radians).
  • Bottom: FLOAT (4 bytes) - Bottom boundary of the overall data bounding box (in radians).
  • MaxWidth: FLOAT (4 bytes) - Maximum width of any CubItem (in radians).
  • MaxHeight: FLOAT (4 bytes) - Maximum height of any CubItem (in radians).
  • LoLaScale: FLOAT (4 bytes) - Scaling factor used in shape construction.
  • HeaderOffset: INT32 (4 bytes) - Byte offset to the first CubItem.
  • DataOffset: INT32 (4 bytes) - Byte offset to the first CubPoint.
  • Alignment: INT32 (4 bytes) - Alignment value (ignored in processing).

CubItem Properties (Variable number, each SizeOfItem bytes; starts at HeaderOffset)

  • Left: FLOAT (4 bytes) - Left boundary of the item's bounding box (in radians).
  • Top: FLOAT (4 bytes) - Top boundary of the item's bounding box (in radians).
  • Right: FLOAT (4 bytes) - Right boundary of the item's bounding box (in radians).
  • Bottom: FLOAT (4 bytes) - Bottom boundary of the item's bounding box (in radians).
  • Style: UINT8 (1 byte) - Airspace style; combines type (lowest 4 bits + highest bit) and class (bits 5-7).
  • AltStyle: UINT8 (1 byte) - Altitude style; lowest 4 bits for MinAlt, highest 4 bits for MaxAlt.
  • MinAlt: INT16 (2 bytes) - Bottom altitude bound of the airspace.
  • MaxAlt: INT16 (2 bytes) - Top altitude bound of the airspace.
  • Data: INT32 (4 bytes) - Relative byte offset from the first CubPoint.
  • TimeOut: INT32 (4 bytes) - Timeout value for the item (purpose not fully detailed in specification).
  • Name: CHAR[8] (8 bytes) - Name or identifier for the item.
  • Radio: CHAR[8] (8 bytes) - Radio frequency or related string.

CubPoint Properties (Variable number, each SizeOfPoint bytes; starts at DataOffset)

  • Longitude: FLOAT (4 bytes) - Longitude coordinate (in radians).
  • Latitude: FLOAT (4 bytes) - Latitude coordinate (in radians).

These properties collectively define the file's intrinsic characteristics, including metadata, spatial bounds, and geometric data.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CUB File Dumping

The following is a self-contained HTML page with embedded JavaScript that allows drag-and-drop of a .CUB file. It parses the file (assuming unencrypted data), extracts all properties, and displays them on the screen.

.CUB File Property Dumper
Drag and drop a .CUB file here

4. Python Class for .CUB File Handling

The following Python class can open, decode, read, write, and print all properties of a .CUB file to the console. It uses the struct module for binary parsing and assumes little-endian byte order with no encryption.

import struct
import sys

class CubFile:
    def __init__(self, filename=None):
        self.header = {}
        self.items = []
        self.points = []
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        offset = 0

        # Parse Header
        self.header['Ident'] = struct.unpack_from('<I', data, offset)[0]; offset += 4
        self.header['Title'] = data[offset:offset+122].decode('ascii', errors='ignore').rstrip('\x00'); offset += 122
        self.header['AllowedSerials'] = list(struct.unpack_from('<8H', data, offset)); offset += 16
        self.header['PcByteOrder'] = struct.unpack_from('<B', data, offset)[0]; offset += 1
        self.header['IsSecured'] = struct.unpack_from('<B', data, offset)[0]; offset += 1
        if self.header['IsSecured'] != 0:
            print("Warning: File is encrypted; parsing may fail.")
        self.header['Crc32'] = struct.unpack_from('<I', data, offset)[0]; offset += 4
        self.header['Key'] = list(struct.unpack_from('<16B', data, offset)); offset += 16
        self.header['SizeOfItem'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['SizeOfPoint'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['HdrItems'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['MaxPts'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['Left'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['Top'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['Right'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['Bottom'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['MaxWidth'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['MaxHeight'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['LoLaScale'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
        self.header['HeaderOffset'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['DataOffset'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
        self.header['Alignment'] = struct.unpack_from('<i', data, offset)[0]; offset += 4

        # Parse Items
        offset = self.header['HeaderOffset']
        for i in range(self.header['HdrItems']):
            item = {}
            item['Left'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            item['Top'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            item['Right'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            item['Bottom'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            item['Style'] = struct.unpack_from('<B', data, offset)[0]; offset += 1
            item['AltStyle'] = struct.unpack_from('<B', data, offset)[0]; offset += 1
            item['MinAlt'] = struct.unpack_from('<h', data, offset)[0]; offset += 2
            item['MaxAlt'] = struct.unpack_from('<h', data, offset)[0]; offset += 2
            item['Data'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
            item['TimeOut'] = struct.unpack_from('<i', data, offset)[0]; offset += 4
            item['Name'] = data[offset:offset+8].decode('ascii', errors='ignore').rstrip('\x00'); offset += 8
            item['Radio'] = data[offset:offset+8].decode('ascii', errors='ignore').rstrip('\x00'); offset += 8
            offset += (self.header['SizeOfItem'] - 42)  # Pad
            self.items.append(item)

        # Parse Points
        offset = self.header['DataOffset']
        point_count = 0
        while offset + self.header['SizeOfPoint'] <= len(data) and point_count < self.header['MaxPts']:
            point = {}
            point['Longitude'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            point['Latitude'] = struct.unpack_from('<f', data, offset)[0]; offset += 4
            offset += (self.header['SizeOfPoint'] - 8)  # Pad
            self.points.append(point)
            point_count += 1

    def print_properties(self):
        print("CubHeader Properties:")
        for key, value in self.header.items():
            print(f"{key}: {value}")
        print("\nCubItem Properties:")
        for i, item in enumerate(self.items):
            print(f"Item {i+1}:")
            for key, value in item.items():
                print(f"  {key}: {value}")
        print("\nCubPoint Properties:")
        for i, point in enumerate(self.points):
            print(f"Point {i+1}: Longitude={point['Longitude']}, Latitude={point['Latitude']}")

    def write(self, filename):
        with open(filename, 'wb') as f:
            # Write Header
            f.write(struct.pack('<I', self.header['Ident']))
            f.write(self.header['Title'].encode('ascii').ljust(122, b'\x00'))
            f.write(struct.pack('<8H', *self.header['AllowedSerials']))
            f.write(struct.pack('<B', self.header['PcByteOrder']))
            f.write(struct.pack('<B', self.header['IsSecured']))
            f.write(struct.pack('<I', self.header['Crc32']))
            f.write(struct.pack('<16B', *self.header['Key']))
            f.write(struct.pack('<i', self.header['SizeOfItem']))
            f.write(struct.pack('<i', self.header['SizeOfPoint']))
            f.write(struct.pack('<i', self.header['HdrItems']))
            f.write(struct.pack('<i', self.header['MaxPts']))
            f.write(struct.pack('<f', self.header['Left']))
            f.write(struct.pack('<f', self.header['Top']))
            f.write(struct.pack('<f', self.header['Right']))
            f.write(struct.pack('<f', self.header['Bottom']))
            f.write(struct.pack('<f', self.header['MaxWidth']))
            f.write(struct.pack('<f', self.header['MaxHeight']))
            f.write(struct.pack('<f', self.header['LoLaScale']))
            f.write(struct.pack('<i', self.header['HeaderOffset']))
            f.write(struct.pack('<i', self.header['DataOffset']))
            f.write(struct.pack('<i', self.header['Alignment']))

            # Write Items (assuming HeaderOffset is after header)
            for item in self.items:
                f.write(struct.pack('<f', item['Left']))
                f.write(struct.pack('<f', item['Top']))
                f.write(struct.pack('<f', item['Right']))
                f.write(struct.pack('<f', item['Bottom']))
                f.write(struct.pack('<B', item['Style']))
                f.write(struct.pack('<B', item['AltStyle']))
                f.write(struct.pack('<h', item['MinAlt']))
                f.write(struct.pack('<h', item['MaxAlt']))
                f.write(struct.pack('<i', item['Data']))
                f.write(struct.pack('<i', item['TimeOut']))
                f.write(item['Name'].encode('ascii').ljust(8, b'\x00'))
                f.write(item['Radio'].encode('ascii').ljust(8, b'\x00'))
                f.write(b'\x00' * (self.header['SizeOfItem'] - 42))  # Pad

            # Write Points (assuming DataOffset is after items)
            for point in self.points:
                f.write(struct.pack('<f', point['Longitude']))
                f.write(struct.pack('<f', point['Latitude']))
                f.write(b'\x00' * (self.header['SizeOfPoint'] - 8))  # Pad

# Example usage:
# cub = CubFile('example.cub')
# cub.print_properties()
# cub.write('output.cub')

5. Java Class for .CUB File Handling

The following Java class can open, decode, read, write, and print all properties of a .CUB file to the console. It uses DataInputStream and DataOutputStream for binary handling.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

public class CubFile {
    private static class Header {
        int ident;
        String title;
        short[] allowedSerials = new short[8];
        byte pcByteOrder;
        byte isSecured;
        int crc32;
        byte[] key = new byte[16];
        int sizeOfItem;
        int sizeOfPoint;
        int hdrItems;
        int maxPts;
        float left, top, right, bottom;
        float maxWidth, maxHeight;
        float loLaScale;
        int headerOffset;
        int dataOffset;
        int alignment;
    }

    private static class Item {
        float left, top, right, bottom;
        byte style;
        byte altStyle;
        short minAlt, maxAlt;
        int data;
        int timeOut;
        String name;
        String radio;
    }

    private static class Point {
        float longitude, latitude;
    }

    private Header header = new Header();
    private List<Item> items = new ArrayList<>();
    private List<Point> points = new ArrayList<>();

    public CubFile(String filename) throws IOException {
        read(filename);
    }

    public CubFile() {}

    public void read(String filename) throws IOException {
        try (FileInputStream fis = new FileInputStream(filename);
             DataInputStream dis = new DataInputStream(fis)) {
            byte[] buffer = new byte[(int) new File(filename).length()];
            dis.readFully(buffer);
            ByteBuffer bb = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
            int offset = 0;

            // Parse Header
            header.ident = bb.getInt(offset); offset += 4;
            byte[] titleBytes = new byte[122];
            bb.position(offset); bb.get(titleBytes); offset += 122;
            header.title = new String(titleBytes, "ASCII").trim();
            for (int i = 0; i < 8; i++) {
                header.allowedSerials[i] = bb.getShort(offset); offset += 2;
            }
            header.pcByteOrder = bb.get(offset); offset += 1;
            header.isSecured = bb.get(offset); offset += 1;
            if (header.isSecured != 0) {
                System.out.println("Warning: File is encrypted; parsing may fail.");
            }
            header.crc32 = bb.getInt(offset); offset += 4;
            bb.position(offset); bb.get(header.key); offset += 16;
            header.sizeOfItem = bb.getInt(offset); offset += 4;
            header.sizeOfPoint = bb.getInt(offset); offset += 4;
            header.hdrItems = bb.getInt(offset); offset += 4;
            header.maxPts = bb.getInt(offset); offset += 4;
            header.left = bb.getFloat(offset); offset += 4;
            header.top = bb.getFloat(offset); offset += 4;
            header.right = bb.getFloat(offset); offset += 4;
            header.bottom = bb.getFloat(offset); offset += 4;
            header.maxWidth = bb.getFloat(offset); offset += 4;
            header.maxHeight = bb.getFloat(offset); offset += 4;
            header.loLaScale = bb.getFloat(offset); offset += 4;
            header.headerOffset = bb.getInt(offset); offset += 4;
            header.dataOffset = bb.getInt(offset); offset += 4;
            header.alignment = bb.getInt(offset); offset += 4;

            // Parse Items
            offset = header.headerOffset;
            for (int i = 0; i < header.hdrItems; i++) {
                Item item = new Item();
                item.left = bb.getFloat(offset); offset += 4;
                item.top = bb.getFloat(offset); offset += 4;
                item.right = bb.getFloat(offset); offset += 4;
                item.bottom = bb.getFloat(offset); offset += 4;
                item.style = bb.get(offset); offset += 1;
                item.altStyle = bb.get(offset); offset += 1;
                item.minAlt = bb.getShort(offset); offset += 2;
                item.maxAlt = bb.getShort(offset); offset += 2;
                item.data = bb.getInt(offset); offset += 4;
                item.timeOut = bb.getInt(offset); offset += 4;
                byte[] nameBytes = new byte[8];
                bb.position(offset); bb.get(nameBytes); offset += 8;
                item.name = new String(nameBytes, "ASCII").trim();
                byte[] radioBytes = new byte[8];
                bb.position(offset); bb.get(radioBytes); offset += 8;
                item.radio = new String(radioBytes, "ASCII").trim();
                offset += (header.sizeOfItem - 42);
                items.add(item);
            }

            // Parse Points
            offset = header.dataOffset;
            int pointCount = 0;
            while (offset + header.sizeOfPoint <= buffer.length && pointCount < header.maxPts) {
                Point point = new Point();
                point.longitude = bb.getFloat(offset); offset += 4;
                point.latitude = bb.getFloat(offset); offset += 4;
                offset += (header.sizeOfPoint - 8);
                points.add(point);
                pointCount++;
            }
        }
    }

    public void printProperties() {
        System.out.println("CubHeader Properties:");
        System.out.println("Ident: " + Integer.toHexString(header.ident));
        System.out.println("Title: " + header.title);
        System.out.print("AllowedSerials: [");
        for (short s : header.allowedSerials) System.out.print(s + " ");
        System.out.println("]");
        System.out.println("PcByteOrder: " + header.pcByteOrder);
        System.out.println("IsSecured: " + header.isSecured);
        System.out.println("Crc32: " + header.crc32);
        System.out.print("Key: [");
        for (byte b : header.key) System.out.print(b + " ");
        System.out.println("]");
        System.out.println("SizeOfItem: " + header.sizeOfItem);
        System.out.println("SizeOfPoint: " + header.sizeOfPoint);
        System.out.println("HdrItems: " + header.hdrItems);
        System.out.println("MaxPts: " + header.maxPts);
        System.out.println("Left: " + header.left);
        System.out.println("Top: " + header.top);
        System.out.println("Right: " + header.right);
        System.out.println("Bottom: " + header.bottom);
        System.out.println("MaxWidth: " + header.maxWidth);
        System.out.println("MaxHeight: " + header.maxHeight);
        System.out.println("LoLaScale: " + header.loLaScale);
        System.out.println("HeaderOffset: " + header.headerOffset);
        System.out.println("DataOffset: " + header.dataOffset);
        System.out.println("Alignment: " + header.alignment);

        System.out.println("\nCubItem Properties:");
        for (int i = 0; i < items.size(); i++) {
            Item item = items.get(i);
            System.out.println("Item " + (i + 1) + ":");
            System.out.println("  Left: " + item.left);
            System.out.println("  Top: " + item.top);
            System.out.println("  Right: " + item.right);
            System.out.println("  Bottom: " + item.bottom);
            System.out.println("  Style: " + item.style);
            System.out.println("  AltStyle: " + item.altStyle);
            System.out.println("  MinAlt: " + item.minAlt);
            System.out.println("  MaxAlt: " + item.maxAlt);
            System.out.println("  Data: " + item.data);
            System.out.println("  TimeOut: " + item.timeOut);
            System.out.println("  Name: " + item.name);
            System.out.println("  Radio: " + item.radio);
        }

        System.out.println("\nCubPoint Properties:");
        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            System.out.println("Point " + (i + 1) + ": Longitude=" + point.longitude + ", Latitude=" + point.latitude);
        }
    }

    public void write(String filename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filename);
             DataOutputStream dos = new DataOutputStream(fos)) {
            // Write Header
            dos.writeInt(header.ident);
            dos.write(header.title.getBytes("ASCII"));
            dos.write(new byte[122 - header.title.length()]);  // Pad
            for (short s : header.allowedSerials) dos.writeShort(s);
            dos.writeByte(header.pcByteOrder);
            dos.writeByte(header.isSecured);
            dos.writeInt(header.crc32);
            dos.write(header.key);
            dos.writeInt(header.sizeOfItem);
            dos.writeInt(header.sizeOfPoint);
            dos.writeInt(header.hdrItems);
            dos.writeInt(header.maxPts);
            dos.writeFloat(header.left);
            dos.writeFloat(header.top);
            dos.writeFloat(header.right);
            dos.writeFloat(header.bottom);
            dos.writeFloat(header.maxWidth);
            dos.writeFloat(header.maxHeight);
            dos.writeFloat(header.loLaScale);
            dos.writeInt(header.headerOffset);
            dos.writeInt(header.dataOffset);
            dos.writeInt(header.alignment);

            // Write Items
            for (Item item : items) {
                dos.writeFloat(item.left);
                dos.writeFloat(item.top);
                dos.writeFloat(item.right);
                dos.writeFloat(item.bottom);
                dos.writeByte(item.style);
                dos.writeByte(item.altStyle);
                dos.writeShort(item.minAlt);
                dos.writeShort(item.maxAlt);
                dos.writeInt(item.data);
                dos.writeInt(item.timeOut);
                dos.write(item.name.getBytes("ASCII"));
                dos.write(new byte[8 - item.name.length()]);  // Pad
                dos.write(item.radio.getBytes("ASCII"));
                dos.write(new byte[8 - item.radio.length()]);  // Pad
                dos.write(new byte[header.sizeOfItem - 42]);  // Extra pad
            }

            // Write Points
            for (Point point : points) {
                dos.writeFloat(point.longitude);
                dos.writeFloat(point.latitude);
                dos.write(new byte[header.sizeOfPoint - 8]);  // Pad
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     CubFile cub = new CubFile("example.cub");
    //     cub.printProperties();
    //     cub.write("output.cub");
    // }
}

6. JavaScript Class for .CUB File Handling

The following JavaScript class can open (via FileReader), decode, read, write (via Blob download), and print all properties of a .CUB file to the console. It is suitable for browser environments.

class CubFile {
    constructor() {
        this.header = {};
        this.items = [];
        this.points = [];
    }

    read(file, callback) {
        const reader = new FileReader();
        reader.onload = (event) => {
            const arrayBuffer = event.target.result;
            const dataView = new DataView(arrayBuffer);
            let offset = 0;

            // Parse Header
            this.header.Ident = dataView.getUint32(offset, true); offset += 4;
            this.header.Title = this.getString(dataView, offset, 122); offset += 122;
            this.header.AllowedSerials = [];
            for (let i = 0; i < 8; i++) {
                this.header.AllowedSerials.push(dataView.getUint16(offset, true)); offset += 2;
            }
            this.header.PcByteOrder = dataView.getUint8(offset); offset += 1;
            this.header.IsSecured = dataView.getUint8(offset); offset += 1;
            if (this.header.IsSecured !== 0) {
                console.warn('File is encrypted; parsing may fail.');
            }
            this.header.Crc32 = dataView.getUint32(offset, true); offset += 4;
            this.header.Key = [];
            for (let i = 0; i < 16; i++) {
                this.header.Key.push(dataView.getUint8(offset)); offset += 1;
            }
            this.header.SizeOfItem = dataView.getInt32(offset, true); offset += 4;
            this.header.SizeOfPoint = dataView.getInt32(offset, true); offset += 4;
            this.header.HdrItems = dataView.getInt32(offset, true); offset += 4;
            this.header.MaxPts = dataView.getInt32(offset, true); offset += 4;
            this.header.Left = dataView.getFloat32(offset, true); offset += 4;
            this.header.Top = dataView.getFloat32(offset, true); offset += 4;
            this.header.Right = dataView.getFloat32(offset, true); offset += 4;
            this.header.Bottom = dataView.getFloat32(offset, true); offset += 4;
            this.header.MaxWidth = dataView.getFloat32(offset, true); offset += 4;
            this.header.MaxHeight = dataView.getFloat32(offset, true); offset += 4;
            this.header.LoLaScale = dataView.getFloat32(offset, true); offset += 4;
            this.header.HeaderOffset = dataView.getInt32(offset, true); offset += 4;
            this.header.DataOffset = dataView.getInt32(offset, true); offset += 4;
            this.header.Alignment = dataView.getInt32(offset, true); offset += 4;

            // Parse Items
            offset = this.header.HeaderOffset;
            for (let i = 0; i < this.header.HdrItems; i++) {
                const item = {};
                item.Left = dataView.getFloat32(offset, true); offset += 4;
                item.Top = dataView.getFloat32(offset, true); offset += 4;
                item.Right = dataView.getFloat32(offset, true); offset += 4;
                item.Bottom = dataView.getFloat32(offset, true); offset += 4;
                item.Style = dataView.getUint8(offset); offset += 1;
                item.AltStyle = dataView.getUint8(offset); offset += 1;
                item.MinAlt = dataView.getInt16(offset, true); offset += 2;
                item.MaxAlt = dataView.getInt16(offset, true); offset += 2;
                item.Data = dataView.getInt32(offset, true); offset += 4;
                item.TimeOut = dataView.getInt32(offset, true); offset += 4;
                item.Name = this.getString(dataView, offset, 8); offset += 8;
                item.Radio = this.getString(dataView, offset, 8); offset += 8;
                offset += (this.header.SizeOfItem - 42);
                this.items.push(item);
            }

            // Parse Points
            offset = this.header.DataOffset;
            let pointCount = 0;
            while (offset + this.header.SizeOfPoint <= arrayBuffer.byteLength && pointCount < this.header.MaxPts) {
                const point = {};
                point.Longitude = dataView.getFloat32(offset, true); offset += 4;
                point.Latitude = dataView.getFloat32(offset, true); offset += 4;
                offset += (this.header.SizeOfPoint - 8);
                this.points.push(point);
                pointCount++;
            }

            if (callback) callback();
        };
        reader.readAsArrayBuffer(file);
    }

    printProperties() {
        console.log('CubHeader Properties:');
        for (const [key, value] of Object.entries(this.header)) {
            console.log(`${key}: ${Array.isArray(value) ? '[' + value.join(', ') + ']' : value}`);
        }
        console.log('\nCubItem Properties:');
        this.items.forEach((item, i) => {
            console.log(`Item ${i + 1}:`);
            for (const [key, value] of Object.entries(item)) {
                console.log(`  ${key}: ${value}`);
            }
        });
        console.log('\nCubPoint Properties:');
        this.points.forEach((point, i) => {
            console.log(`Point ${i + 1}: Longitude=${point.Longitude}, Latitude=${point.Latitude}`);
        });
    }

    write(filename) {
        const bufferSize = 210 + this.header.HdrItems * this.header.SizeOfItem + this.points.length * this.header.SizeOfPoint;
        const arrayBuffer = new ArrayBuffer(bufferSize);
        const dataView = new DataView(arrayBuffer);
        let offset = 0;

        // Write Header
        dataView.setUint32(offset, this.header.Ident, true); offset += 4;
        this.setString(dataView, offset, this.header.Title, 122); offset += 122;
        this.header.AllowedSerials.forEach(s => { dataView.setUint16(offset, s, true); offset += 2; });
        dataView.setUint8(offset, this.header.PcByteOrder); offset += 1;
        dataView.setUint8(offset, this.header.IsSecured); offset += 1;
        dataView.setUint32(offset, this.header.Crc32, true); offset += 4;
        this.header.Key.forEach(b => { dataView.setUint8(offset, b); offset += 1; });
        dataView.setInt32(offset, this.header.SizeOfItem, true); offset += 4;
        dataView.setInt32(offset, this.header.SizeOfPoint, true); offset += 4;
        dataView.setInt32(offset, this.header.HdrItems, true); offset += 4;
        dataView.setInt32(offset, this.header.MaxPts, true); offset += 4;
        dataView.setFloat32(offset, this.header.Left, true); offset += 4;
        dataView.setFloat32(offset, this.header.Top, true); offset += 4;
        dataView.setFloat32(offset, this.header.Right, true); offset += 4;
        dataView.setFloat32(offset, this.header.Bottom, true); offset += 4;
        dataView.setFloat32(offset, this.header.MaxWidth, true); offset += 4;
        dataView.setFloat32(offset, this.header.MaxHeight, true); offset += 4;
        dataView.setFloat32(offset, this.header.LoLaScale, true); offset += 4;
        dataView.setInt32(offset, this.header.HeaderOffset, true); offset += 4;
        dataView.setInt32(offset, this.header.DataOffset, true); offset += 4;
        dataView.setInt32(offset, this.header.Alignment, true); offset += 4;

        // Write Items
        this.items.forEach(item => {
            dataView.setFloat32(offset, item.Left, true); offset += 4;
            dataView.setFloat32(offset, item.Top, true); offset += 4;
            dataView.setFloat32(offset, item.Right, true); offset += 4;
            dataView.setFloat32(offset, item.Bottom, true); offset += 4;
            dataView.setUint8(offset, item.Style); offset += 1;
            dataView.setUint8(offset, item.AltStyle); offset += 1;
            dataView.setInt16(offset, item.MinAlt, true); offset += 2;
            dataView.setInt16(offset, item.MaxAlt, true); offset += 2;
            dataView.setInt32(offset, item.Data, true); offset += 4;
            dataView.setInt32(offset, item.TimeOut, true); offset += 4;
            this.setString(dataView, offset, item.Name, 8); offset += 8;
            this.setString(dataView, offset, item.Radio, 8); offset += 8;
            offset += (this.header.SizeOfItem - 42);
        });

        // Write Points
        this.points.forEach(point => {
            dataView.setFloat32(offset, point.Longitude, true); offset += 4;
            dataView.setFloat32(offset, point.Latitude, true); offset += 4;
            offset += (this.header.SizeOfPoint - 8);
        });

        const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
    }

    getString(view, offset, length) {
        let str = '';
        for (let i = 0; i < length; i++) {
            const char = view.getUint8(offset + i);
            if (char === 0) break;
            str += String.fromCharCode(char);
        }
        return str.trim();
    }

    setString(view, offset, str, length) {
        for (let i = 0; i < length; i++) {
            view.setUint8(offset + i, i < str.length ? str.charCodeAt(i) : 0);
        }
    }
}

// Example usage:
// const cub = new CubFile();
// cub.read(fileObject, () => {
//     cub.printProperties();
//     cub.write('output.cub');
// });

7. C Implementation for .CUB File Handling

The following C code provides structures and functions to open, decode, read, write, and print all properties of a .CUB file to the console. It uses standard I/O for binary handling.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    uint32_t ident;
    char title[123];  // Null-terminated
    uint16_t allowedSerials[8];
    uint8_t pcByteOrder;
    uint8_t isSecured;
    uint32_t crc32;
    uint8_t key[16];
    int32_t sizeOfItem;
    int32_t sizeOfPoint;
    int32_t hdrItems;
    int32_t maxPts;
    float left, top, right, bottom;
    float maxWidth, maxHeight;
    float loLaScale;
    int32_t headerOffset;
    int32_t dataOffset;
    int32_t alignment;
} CubHeader;

typedef struct {
    float left, top, right, bottom;
    uint8_t style;
    uint8_t altStyle;
    int16_t minAlt, maxAlt;
    int32_t data;
    int32_t timeOut;
    char name[9];  // Null-terminated
    char radio[9];  // Null-terminated
} CubItem;

typedef struct {
    float longitude, latitude;
} CubPoint;

typedef struct {
    CubHeader header;
    CubItem* items;
    CubPoint* points;
} CubFile;

void readCubFile(CubFile* cf, const char* filename) {
    FILE* f = fopen(filename, "rb");
    if (!f) {
        perror("Failed to open file");
        return;
    }
    fseek(f, 0, SEEK_END);
    long fileSize = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t* buffer = malloc(fileSize);
    fread(buffer, 1, fileSize, f);
    fclose(f);

    uint8_t* ptr = buffer;

    // Parse Header
    memcpy(&cf->header.ident, ptr, 4); ptr += 4;
    memcpy(cf->header.title, ptr, 122); cf->header.title[122] = '\0'; ptr += 122;
    memcpy(cf->header.allowedSerials, ptr, 16); ptr += 16;
    cf->header.pcByteOrder = *ptr; ptr += 1;
    cf->header.isSecured = *ptr; ptr += 1;
    if (cf->header.isSecured != 0) {
        printf("Warning: File is encrypted; parsing may fail.\n");
    }
    memcpy(&cf->header.crc32, ptr, 4); ptr += 4;
    memcpy(cf->header.key, ptr, 16); ptr += 16;
    memcpy(&cf->header.sizeOfItem, ptr, 4); ptr += 4;
    memcpy(&cf->header.sizeOfPoint, ptr, 4); ptr += 4;
    memcpy(&cf->header.hdrItems, ptr, 4); ptr += 4;
    memcpy(&cf->header.maxPts, ptr, 4); ptr += 4;
    memcpy(&cf->header.left, ptr, 4); ptr += 4;
    memcpy(&cf->header.top, ptr, 4); ptr += 4;
    memcpy(&cf->header.right, ptr, 4); ptr += 4;
    memcpy(&cf->header.bottom, ptr, 4); ptr += 4;
    memcpy(&cf->header.maxWidth, ptr, 4); ptr += 4;
    memcpy(&cf->header.maxHeight, ptr, 4); ptr += 4;
    memcpy(&cf->header.loLaScale, ptr, 4); ptr += 4;
    memcpy(&cf->header.headerOffset, ptr, 4); ptr += 4;
    memcpy(&cf->header.dataOffset, ptr, 4); ptr += 4;
    memcpy(&cf->header.alignment, ptr, 4); ptr += 4;

    // Parse Items
    cf->items = malloc(cf->header.hdrItems * sizeof(CubItem));
    ptr = buffer + cf->header.headerOffset;
    for (int i = 0; i < cf->header.hdrItems; i++) {
        memcpy(&cf->items[i].left, ptr, 4); ptr += 4;
        memcpy(&cf->items[i].top, ptr, 4); ptr += 4;
        memcpy(&cf->items[i].right, ptr, 4); ptr += 4;
        memcpy(&cf->items[i].bottom, ptr, 4); ptr += 4;
        cf->items[i].style = *ptr; ptr += 1;
        cf->items[i].altStyle = *ptr; ptr += 1;
        memcpy(&cf->items[i].minAlt, ptr, 2); ptr += 2;
        memcpy(&cf->items[i].maxAlt, ptr, 2); ptr += 2;
        memcpy(&cf->items[i].data, ptr, 4); ptr += 4;
        memcpy(&cf->items[i].timeOut, ptr, 4); ptr += 4;
        memcpy(cf->items[i].name, ptr, 8); cf->items[i].name[8] = '\0'; ptr += 8;
        memcpy(cf->items[i].radio, ptr, 8); cf->items[i].radio[8] = '\0'; ptr += 8;
        ptr += (cf->header.sizeOfItem - 42);
    }

    // Parse Points
    cf->points = malloc(cf->header.maxPts * sizeof(CubPoint));
    ptr = buffer + cf->header.dataOffset;
    int pointCount = 0;
    while (ptr - buffer + cf->header.sizeOfPoint <= fileSize && pointCount < cf->header.maxPts) {
        memcpy(&cf->points[pointCount].longitude, ptr, 4); ptr += 4;
        memcpy(&cf->points[pointCount].latitude, ptr, 4); ptr += 4;
        ptr += (cf->header.sizeOfPoint - 8);
        pointCount++;
    }
    cf->header.maxPts = pointCount;  // Update actual count

    free(buffer);
}

void printProperties(const CubFile* cf) {
    printf("CubHeader Properties:\n");
    printf("Ident: %x\n", cf->header.ident);
    printf("Title: %s\n", cf->header.title);
    printf("AllowedSerials: [");
    for (int i = 0; i < 8; i++) printf("%u ", cf->header.allowedSerials[i]);
    printf("]\n");
    printf("PcByteOrder: %u\n", cf->header.pcByteOrder);
    printf("IsSecured: %u\n", cf->header.isSecured);
    printf("Crc32: %u\n", cf->header.crc32);
    printf("Key: [");
    for (int i = 0; i < 16; i++) printf("%u ", cf->header.key[i]);
    printf("]\n");
    printf("SizeOfItem: %d\n", cf->header.sizeOfItem);
    printf("SizeOfPoint: %d\n", cf->header.sizeOfPoint);
    printf("HdrItems: %d\n", cf->header.hdrItems);
    printf("MaxPts: %d\n", cf->header.maxPts);
    printf("Left: %f\n", cf->header.left);
    printf("Top: %f\n", cf->header.top);
    printf("Right: %f\n", cf->header.right);
    printf("Bottom: %f\n", cf->header.bottom);
    printf("MaxWidth: %f\n", cf->header.maxWidth);
    printf("MaxHeight: %f\n", cf->header.maxHeight);
    printf("LoLaScale: %f\n", cf->header.loLaScale);
    printf("HeaderOffset: %d\n", cf->header.headerOffset);
    printf("DataOffset: %d\n", cf->header.dataOffset);
    printf("Alignment: %d\n", cf->header.alignment);

    printf("\nCubItem Properties:\n");
    for (int i = 0; i < cf->header.hdrItems; i++) {
        printf("Item %d:\n", i + 1);
        printf("  Left: %f\n", cf->items[i].left);
        printf("  Top: %f\n", cf->items[i].top);
        printf("  Right: %f\n", cf->items[i].right);
        printf("  Bottom: %f\n", cf->items[i].bottom);
        printf("  Style: %u\n", cf->items[i].style);
        printf("  AltStyle: %u\n", cf->items[i].altStyle);
        printf("  MinAlt: %d\n", cf->items[i].minAlt);
        printf("  MaxAlt: %d\n", cf->items[i].maxAlt);
        printf("  Data: %d\n", cf->items[i].data);
        printf("  TimeOut: %d\n", cf->items[i].timeOut);
        printf("  Name: %s\n", cf->items[i].name);
        printf("  Radio: %s\n", cf->items[i].radio);
    }

    printf("\nCubPoint Properties:\n");
    for (int i = 0; i < cf->header.maxPts; i++) {
        printf("Point %d: Longitude=%f, Latitude=%f\n", i + 1, cf->points[i].longitude, cf->points[i].latitude);
    }
}

void writeCubFile(const CubFile* cf, const char* filename) {
    FILE* f = fopen(filename, "wb");
    if (!f) {
        perror("Failed to open file for writing");
        return;
    }

    // Write Header
    fwrite(&cf->header.ident, 4, 1, f);
    fwrite(cf->header.title, 122, 1, f);
    fwrite(cf->header.allowedSerials, 2, 8, f);
    fwrite(&cf->header.pcByteOrder, 1, 1, f);
    fwrite(&cf->header.isSecured, 1, 1, f);
    fwrite(&cf->header.crc32, 4, 1, f);
    fwrite(cf->header.key, 1, 16, f);
    fwrite(&cf->header.sizeOfItem, 4, 1, f);
    fwrite(&cf->header.sizeOfPoint, 4, 1, f);
    fwrite(&cf->header.hdrItems, 4, 1, f);
    fwrite(&cf->header.maxPts, 4, 1, f);
    fwrite(&cf->header.left, 4, 1, f);
    fwrite(&cf->header.top, 4, 1, f);
    fwrite(&cf->header.right, 4, 1, f);
    fwrite(&cf->header.bottom, 4, 1, f);
    fwrite(&cf->header.maxWidth, 4, 1, f);
    fwrite(&cf->header.maxHeight, 4, 1, f);
    fwrite(&cf->header.loLaScale, 4, 1, f);
    fwrite(&cf->header.headerOffset, 4, 1, f);
    fwrite(&cf->header.dataOffset, 4, 1, f);
    fwrite(&cf->header.alignment, 4, 1, f);

    // Write Items
    for (int i = 0; i < cf->header.hdrItems; i++) {
        fwrite(&cf->items[i].left, 4, 1, f);
        fwrite(&cf->items[i].top, 4, 1, f);
        fwrite(&cf->items[i].right, 4, 1, f);
        fwrite(&cf->items[i].bottom, 4, 1, f);
        fwrite(&cf->items[i].style, 1, 1, f);
        fwrite(&cf->items[i].altStyle, 1, 1, f);
        fwrite(&cf->items[i].minAlt, 2, 1, f);
        fwrite(&cf->items[i].maxAlt, 2, 1, f);
        fwrite(&cf->items[i].data, 4, 1, f);
        fwrite(&cf->items[i].timeOut, 4, 1, f);
        fwrite(cf->items[i].name, 8, 1, f);
        fwrite(cf->items[i].radio, 8, 1, f);
        uint8_t pad[1024] = {0};  // Assuming max pad
        fwrite(pad, 1, cf->header.sizeOfItem - 42, f);
    }

    // Write Points
    for (int i = 0; i < cf->header.maxPts; i++) {
        fwrite(&cf->points[i].longitude, 4, 1, f);
        fwrite(&cf->points[i].latitude, 4, 1, f);
        uint8_t pad[1024] = {0};
        fwrite(pad, 1, cf->header.sizeOfPoint - 8, f);
    }

    fclose(f);
}

void freeCubFile(CubFile* cf) {
    free(cf->items);
    free(cf->points);
}

// Example usage:
// int main() {
//     CubFile cf = {0};
//     readCubFile(&cf, "example.cub");
//     printProperties(&cf);
//     writeCubFile(&cf, "output.cub");
//     freeCubFile(&cf);
//     return 0;
// }