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.
2. Two Direct Download Links for .CUB Files
- https://soaringweb.org/Airspace/NA/CanAirspace315all.cub
- https://soaringweb.org/Airspace/NA/CanUSborder.cub
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.
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;
// }