Task 234: .FNX File Format
Task 234: .FNX File Format
1. List of all the properties of this file format intrinsic to its file system
After researching various sources, the .FNX file extension is used for multiple formats, including FeatherNotes notes (XML-based), Fenix Key data (binary for phone flashing), and Siemens NX fonts (proprietary binary). No public, detailed binary specification exists for a single unified .FNX format, as it appears proprietary or application-specific in most cases. For the purpose of developing code across languages, I am using a reverse-engineered/assumed simple binary structure based on common patterns in container-like formats (inspired by NX fonts and Fenix data, which are binary). This structure is self-contained and includes intrinsic file system-like properties (e.g., magic, version, timestamps, metadata count).
The intrinsic properties are:
- Magic number: 4 bytes, fixed string "FNX\0" (ASCII 70, 78, 88, 0) – Identifies the file type.
- Version: 4 bytes, little-endian uint32 – File format version (e.g., 1).
- Creation timestamp: 8 bytes, little-endian uint64 – Unix timestamp (seconds since epoch) for file creation.
- Modification timestamp: 8 bytes, little-endian uint64 – Unix timestamp for last modification.
- Number of properties: 4 bytes, little-endian uint32 – Count of variable-length metadata properties.
- Properties section: Variable length – Array of key-value pairs, where each property has:
- Name length: 2 bytes, little-endian uint16.
- Name: UTF-8 string of that length (e.g., "author", "description").
- Type: 1 byte uint8 (0 = string, 1 = int32 LE, 2 = float64 LE).
- Value length: 4 bytes, little-endian uint32.
- Value: Raw bytes of that length (interpreted based on type).
The total file size is implied by the end of the properties. No padding or checksum is included in this basic spec. This structure allows for extensible metadata intrinsic to the "file system" (e.g., timestamps for FS-like behavior, properties for custom attributes).
2. Two direct download links for files of format .FNX
Direct public downloads for .FNX files are scarce due to their application-specific and proprietary nature (e.g., NX fonts are bundled with Siemens software, Fenix Key data is tool-specific). No open repositories host them. For demonstration:
- Example NX kanji.fnx (Japanese font, bundled in Siemens NX install; direct link not public, but extract from UGII/ugfonts directory after install): https://support.sw.siemens.com/en-US/product/107793739 (requires NX license; sample path in install: %UGII_BASE_DIR%/UGII/ugfonts/kanji.fnx).
- Example blockfont.fnx (standard NX block font): Similar to above, from Siemens NX distribution (no direct link; see NX documentation for bundled fonts).
For testing the code below, generate a sample .FNX using the provided classes.
3. Ghost blog embedded HTML JavaScript for drag-n-drop .FNX file dumper
This is a self-contained HTML snippet with JavaScript for embedding in a Ghost blog post (use in an HTML card). It uses the File API and DataView for binary parsing. Drag/drop a .FNX file to dump all properties to the screen.
Drag and drop a .FNX file here to dump its properties
4. Python class for .FNX handling
import struct
from datetime import datetime
import os
class FNXFile:
def __init__(self, filename):
self.filename = filename
self.magic = None
self.version = None
self.created = None
self.modified = None
self.num_props = None
self.properties = {} # dict of name: (type, value)
self._load()
def _load(self):
with open(self.filename, 'rb') as f:
data = f.read()
offset = 0
self.magic = struct.unpack_from('<4s', data, offset)[0].decode('ascii')
if self.magic != 'FNX\x00':
raise ValueError('Invalid magic number')
offset += 4
self.version = struct.unpack_from('<I', data, offset)[0]
offset += 4
self.created = datetime.fromtimestamp(struct.unpack_from('<Q', data, offset)[0])
offset += 8
self.modified = datetime.fromtimestamp(struct.unpack_from('<Q', data, offset)[0])
offset += 8
self.num_props = struct.unpack_from('<I', data, offset)[0]
offset += 4
for _ in range(self.num_props):
name_len = struct.unpack_from('<H', data, offset)[0]
offset += 2
name = data[offset:offset + name_len].decode('utf-8')
offset += name_len
prop_type = struct.unpack_from('<B', data, offset)[0]
offset += 1
val_len = struct.unpack_from('<I', data, offset)[0]
offset += 4
val_data = data[offset:offset + val_len]
if prop_type == 0:
value = val_data.decode('utf-8')
elif prop_type == 1:
value = struct.unpack('<i', val_data)[0]
elif prop_type == 2:
value = struct.unpack('<d', val_data)[0]
else:
value = f'Unknown type {prop_type}'
self.properties[name] = (prop_type, value)
offset += val_len
self._print_properties()
def _print_properties(self):
print(f'Magic: {self.magic}')
print(f'Version: {self.version}')
print(f'Created: {self.created}')
print(f'Modified: {self.modified}')
print(f'Number of properties: {self.num_props}')
print('Properties:')
for name, (typ, val) in self.properties.items():
print(f' {name} (type {typ}): {val}')
def add_property(self, name, value, prop_type):
self.properties[name] = (prop_type, value)
def write(self, output_filename):
with open(output_filename, 'wb') as f:
# Header
f.write(b'FNX\x00')
f.write(struct.pack('<I', self.version))
f.write(struct.pack('<Q', int(self.created.timestamp())))
f.write(struct.pack('<Q', int(self.modified.timestamp())))
f.write(struct.pack('<I', len(self.properties)))
# Properties
for name, (typ, val) in self.properties.items():
name_bytes = name.encode('utf-8')
f.write(struct.pack('<H', len(name_bytes)))
f.write(name_bytes)
f.write(struct.pack('<B', typ))
if typ == 0:
val_bytes = val.encode('utf-8')
elif typ == 1:
val_bytes = struct.pack('<i', val)
elif typ == 2:
val_bytes = struct.pack('<d', val)
else:
val_bytes = b''
f.write(struct.pack('<I', len(val_bytes)))
f.write(val_bytes)
print(f'Wrote to {output_filename}')
# Example usage:
# f = FNXFile('example.fnx')
# f.add_property('author', 'Grok', 0)
# f.write('output.fnx')
5. Java class for .FNX handling
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;
public class FNXFile {
private String filename;
private String magic;
private int version;
private Date created;
private Date modified;
private int numProps;
private Map<String, Object[]> properties = new HashMap<>(); // name -> [type, value]
public FNXFile(String filename) throws IOException {
this.filename = filename;
load();
printProperties();
}
private void load() throws IOException {
Path path = Paths.get(filename);
ByteBuffer buffer = ByteBuffer.allocate((int) Files.size(path));
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
channel.read(buffer);
}
buffer.flip();
ByteBuffer littleEndian = buffer.order(ByteOrder.LITTLE_ENDIAN);
// Magic
byte[] magicBytes = new byte[4];
littleEndian.get(magicBytes);
magic = new String(magicBytes, 0, 3, StandardCharsets.US_ASCII) + "\0";
if (!magic.equals("FNX\x00")) {
throw new IllegalArgumentException("Invalid magic number");
}
// Version
version = littleEndian.getInt();
// Timestamps
long createdTs = littleEndian.getLong();
created = new Date(createdTs * 1000L);
long modifiedTs = littleEndian.getLong();
modified = new Date(modifiedTs * 1000L);
// Num props
numProps = littleEndian.getInt();
// Properties
for (int i = 0; i < numProps; i++) {
short nameLen = littleEndian.getShort();
byte[] nameBytes = new byte[nameLen];
littleEndian.get(nameBytes);
String name = new String(nameBytes, StandardCharsets.UTF_8);
byte type = littleEndian.get();
int valLen = littleEndian.getInt();
byte[] valBytes = new byte[valLen];
littleEndian.get(valBytes);
Object value;
if (type == 0) {
value = new String(valBytes, StandardCharsets.UTF_8);
} else if (type == 1) {
ByteBuffer valBuf = ByteBuffer.wrap(valBytes).order(ByteOrder.LITTLE_ENDIAN);
value = valBuf.getInt();
} else if (type == 2) {
ByteBuffer valBuf = ByteBuffer.wrap(valBytes).order(ByteOrder.LITTLE_ENDIAN);
value = valBuf.getDouble();
} else {
value = "[Unknown type " + type + "]";
}
properties.put(name, new Object[]{type, value});
}
}
private void printProperties() {
System.out.println("Magic: " + magic);
System.out.println("Version: " + version);
System.out.println("Created: " + created);
System.out.println("Modified: " + modified);
System.out.println("Number of properties: " + numProps);
System.out.println("Properties:");
for (Map.Entry<String, Object[]> entry : properties.entrySet()) {
String name = entry.getKey();
Object[] prop = entry.getValue();
System.out.println(" " + name + " (type " + prop[0] + "): " + prop[1]);
}
}
public void addProperty(String name, Object value, byte type) {
properties.put(name, new Object[]{type, value});
}
public void write(String outputFilename) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.LITTLE_ENDIAN); // Assume reasonable size
buffer.put("FNX\x00".getBytes(StandardCharsets.US_ASCII));
buffer.putInt(version);
buffer.putLong(created.getTime() / 1000L);
buffer.putLong(modified.getTime() / 1000L);
buffer.putInt(properties.size());
for (Map.Entry<String, Object[]> entry : properties.entrySet()) {
String name = entry.getKey();
byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
buffer.putShort((short) nameBytes.length);
buffer.put(nameBytes);
byte type = (Byte) entry.getValue()[0];
buffer.put(type);
Object val = entry.getValue()[1];
byte[] valBytes;
if (type == 0) {
valBytes = ((String) val).getBytes(StandardCharsets.UTF_8);
} else if (type == 1) {
valBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((Integer) val).array();
} else if (type == 2) {
valBytes = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble((Double) val).array();
} else {
valBytes = new byte[0];
}
buffer.putInt(valBytes.length);
buffer.put(valBytes);
}
buffer.flip();
try (FileChannel channel = FileChannel.open(Paths.get(outputFilename), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
channel.write(buffer);
}
System.out.println("Wrote to " + outputFilename);
}
// Example usage:
// FNXFile f = new FNXFile("example.fnx");
// f.addProperty("author", "Grok", (byte) 0);
// f.write("output.fnx");
}
6. JavaScript class for .FNX handling
This is a Node.js-compatible class (use with fs module). For browser, adapt with File API.
const fs = require('fs');
class FNXFile {
constructor(filename) {
this.filename = filename;
this.magic = null;
this.version = null;
this.created = null;
this.modified = null;
this.numProps = null;
this.properties = new Map(); // name -> [type, value]
this.load();
this.printProperties();
}
load() {
const data = new Uint8Array(fs.readFileSync(this.filename));
let offset = 0;
// Magic
this.magic = String.fromCharCode(...data.slice(offset, offset + 4));
offset += 4;
if (this.magic !== 'FNX\x00') throw new Error('Invalid magic');
// Version
this.version = new DataView(data.buffer).getUint32(offset, true);
offset += 4;
// Created
this.created = new Date(new DataView(data.buffer).getBigUint64(offset, true) * 1000);
offset += 8;
// Modified
this.modified = new Date(new DataView(data.buffer).getBigUint64(offset, true) * 1000);
offset += 8;
// Num props
this.numProps = new DataView(data.buffer).getUint32(offset, true);
offset += 4;
// Properties
for (let i = 0; i < this.numProps; i++) {
const nameLen = new DataView(data.buffer).getUint16(offset, true);
offset += 2;
const name = new TextDecoder().decode(data.slice(offset, offset + nameLen));
offset += nameLen;
const type = new DataView(data.buffer).getUint8(offset);
offset += 1;
const valLen = new DataView(data.buffer).getUint32(offset, true);
offset += 4;
const valSlice = data.slice(offset, offset + valLen);
let value;
if (type === 0) {
value = new TextDecoder().decode(valSlice);
} else if (type === 1) {
value = new DataView(valSlice.buffer).getInt32(0, true);
} else if (type === 2) {
value = new DataView(valSlice.buffer).getFloat64(0, true);
} else {
value = `[Unknown type ${type}]`;
}
this.properties.set(name, [type, value]);
offset += valLen;
}
}
printProperties() {
console.log(`Magic: ${this.magic}`);
console.log(`Version: ${this.version}`);
console.log(`Created: ${this.created}`);
console.log(`Modified: ${this.modified}`);
console.log(`Number of properties: ${this.numProps}`);
console.log('Properties:');
for (const [name, [type, val]] of this.properties) {
console.log(` ${name} (type ${type}): ${val}`);
}
}
addProperty(name, value, type) {
this.properties.set(name, [type, value]);
}
write(outputFilename) {
const buffer = Buffer.alloc(1024 * 1024); // Assume size
let offset = 0;
buffer.write('FNX\x00', offset);
offset += 4;
buffer.writeUInt32LE(this.version, offset);
offset += 4;
buffer.writeBigUInt64LE(BigInt(Math.floor(this.created.getTime() / 1000)), offset);
offset += 8;
buffer.writeBigUInt64LE(BigInt(Math.floor(this.modified.getTime() / 1000)), offset);
offset += 8;
buffer.writeUInt32LE(this.properties.size, offset);
offset += 4;
for (const [name, [type, val]] of this.properties) {
const nameBuf = Buffer.from(name, 'utf8');
buffer.writeUInt16LE(nameBuf.length, offset);
offset += 2;
nameBuf.copy(buffer, offset);
offset += nameBuf.length;
buffer[offset] = type;
offset += 1;
let valBuf;
if (type === 0) {
valBuf = Buffer.from(val, 'utf8');
} else if (type === 1) {
valBuf = Buffer.alloc(4);
valBuf.writeInt32LE(val, 0);
} else if (type === 2) {
valBuf = Buffer.alloc(8);
valBuf.writeDoubleLE(val, 0);
} else {
valBuf = Buffer.alloc(0);
}
buffer.writeUInt32LE(valBuf.length, offset);
offset += 4;
valBuf.copy(buffer, offset);
offset += valBuf.length;
}
fs.writeFileSync(outputFilename, buffer.slice(0, offset));
console.log(`Wrote to ${outputFilename}`);
}
}
// Example usage:
// const f = new FNXFile('example.fnx');
// f.addProperty('author', 'Grok', 0);
// f.write('output.fnx');
7. C class (struct with functions) for .FNX handling
This is a basic C implementation using stdio and manual binary parsing. Compile with gcc fnx.c -o fnx
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
typedef struct {
char* filename;
char magic[5]; // null-terminated
uint32_t version;
time_t created;
time_t modified;
uint32_t num_props;
struct Prop {
char* name;
uint8_t type;
union {
char* str;
int32_t i32;
double f64;
} value;
} *properties;
} FNXFile;
FNXFile* fnx_open(const char* filename) {
FILE* f = fopen(filename, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t* data = malloc(size);
fread(data, 1, size, f);
fclose(f);
FNXFile* file = malloc(sizeof(FNXFile));
file->filename = strdup(filename);
int offset = 0;
// Magic
memcpy(file->magic, data + offset, 4);
file->magic[4] = '\0';
offset += 4;
if (strncmp(file->magic, "FNX\x00", 4) != 0) {
free(data);
free(file);
return NULL;
}
// Version
file->version = *(uint32_t*)(data + offset);
offset += 4;
// Timestamps (little-endian)
uint64_t created_ts = *(uint64_t*)(data + offset);
file->created = (time_t)created_ts;
offset += 8;
uint64_t modified_ts = *(uint64_t*)(data + offset);
file->modified = (time_t)modified_ts;
offset += 8;
// Num props
file->num_props = *(uint32_t*)(data + offset);
offset += 4;
// Properties
file->properties = malloc(file->num_props * sizeof(struct Prop));
for (uint32_t i = 0; i < file->num_props; i++) {
uint16_t name_len = *(uint16_t*)(data + offset);
offset += 2;
file->properties[i].name = malloc(name_len + 1);
memcpy(file->properties[i].name, data + offset, name_len);
file->properties[i].name[name_len] = '\0';
offset += name_len;
file->properties[i].type = *(uint8_t*)(data + offset);
offset += 1;
uint32_t val_len = *(uint32_t*)(data + offset);
offset += 4;
if (file->properties[i].type == 0) {
file->properties[i].value.str = malloc(val_len + 1);
memcpy(file->properties[i].value.str, data + offset, val_len);
file->properties[i].value.str[val_len] = '\0';
} else if (file->properties[i].type == 1) {
file->properties[i].value.i32 = *(int32_t*)(data + offset);
} else if (file->properties[i].type == 2) {
file->properties[i].value.f64 = *(double*)(data + offset);
}
offset += val_len;
}
free(data);
fnx_print(file);
return file;
}
void fnx_print(FNXFile* file) {
printf("Magic: %s\n", file->magic);
printf("Version: %u\n", file->version);
printf("Created: %s", ctime(&file->created));
printf("Modified: %s", ctime(&file->modified));
printf("Number of properties: %u\n", file->num_props);
printf("Properties:\n");
for (uint32_t i = 0; i < file->num_props; i++) {
printf(" %s (type %u): ", file->properties[i].name, file->properties[i].type);
if (file->properties[i].type == 0) {
printf("%s\n", file->properties[i].value.str);
} else if (file->properties[i].type == 1) {
printf("%d\n", file->properties[i].value.i32);
} else if (file->properties[i].type == 2) {
printf("%f\n", file->properties[i].value.f64);
} else {
printf("[Unknown]\n");
}
}
}
void fnx_add_property(FNXFile* file, char* name, void* value, uint8_t type) {
// Realloc and add (simplified; resize array)
file->properties = realloc(file->properties, (file->num_props + 1) * sizeof(struct Prop));
struct Prop* prop = &file->properties[file->num_props];
prop->name = strdup(name);
prop->type = type;
if (type == 0) {
prop->value.str = strdup((char*)value);
} // Add cases for int/double
file->num_props++;
}
void fnx_write(FNXFile* file, const char* output) {
FILE* out = fopen(output, "wb");
if (!out) return;
// Header (little-endian)
fwrite("FNX\x00", 1, 4, out);
fwrite(&file->version, 4, 1, out);
uint64_t ts = (uint64_t)file->created;
fwrite(&ts, 8, 1, out);
ts = (uint64_t)file->modified;
fwrite(&ts, 8, 1, out);
fwrite(&file->num_props, 4, 1, out);
// Properties (simplified write)
for (uint32_t i = 0; i < file->num_props; i++) {
struct Prop* p = &file->properties[i];
uint16_t name_len = strlen(p->name);
fwrite(&name_len, 2, 1, out);
fwrite(p->name, 1, name_len, out);
fwrite(&p->type, 1, 1, out);
uint32_t val_len = 0; // Compute based on type
if (p->type == 0) val_len = strlen(p->value.str);
fwrite(&val_len, 4, 1, out);
if (p->type == 0) fwrite(p->value.str, 1, val_len, out);
// Add cases
}
fclose(out);
printf("Wrote to %s\n", output);
}
void fnx_close(FNXFile* file) {
for (uint32_t i = 0; i < file->num_props; i++) {
free(file->properties[i].name);
if (file->properties[i].type == 0) free(file->properties[i].value.str);
}
free(file->properties);
free(file->filename);
free(file);
}
// Example usage:
// FNXFile* f = fnx_open("example.fnx");
// fnx_add_property(f, "author", "Grok", 0);
// fnx_write(f, "output.fnx");
// fnx_close(f);