Task 553: .PKA File Format

Task 553: .PKA File Format

.PKA File Format Specifications

The .PKA file format is used by Cisco Packet Tracer for activity files. These files contain network simulation activities, including topology, configurations, instructions, and grading criteria. The format is a binary file that is encoded, encrypted, and compressed to protect the content. The underlying data is an XML document that describes the activity. The format has evolved over versions; the description below is based on newer versions (e.g., Packet Tracer 7.x), which include additional security layers compared to older versions (e.g., 5.x).

The file is processed in four stages for decoding: reverse XOR, TwoFish decryption, forward XOR, and zlib decompression. The resulting XML has a root element of <PACKETTRACER5_ACTIVITY> (the "5" may vary with version, but it's retained for compatibility). The XML contains sections for the network topology, activity instructions, and answer key for grading.

List of all the properties of this file format intrinsic to its file system:

  • Encoding layer 1: Reverse XOR using a key derived from file length and index (specific formula: k = length % 256, a = (k * (i % 256)) % 256, c = length - a % 256, then c ^= original byte from reverse position).
  • Encryption layer: TwoFish cipher in CBC mode with key b'\x89' * 16 and IV b'\x10' * 16.
  • Encoding layer 2: Forward XOR with key (file length - index) % 256 for each byte.
  • Compression: Zlib compression (default level, header bytes 0x78 0x9C).
  • Header: 4-byte big-endian unsigned integer indicating the uncompressed XML size.
  • XML structure root: <PACKETTRACER5_ACTIVITY> (or similar versioned root).
  • XML property: VERSION (string, e.g., "7.2.1").
  • XML property: NETWORK (contains the simulated network topology).
  • XML property: DEVICES (list of devices in the network).
  • XML property: DEVICE (individual device details, including ENGINE with TYPE, model, NAME, POWER state, DESCRIPTION, and MODULES).
  • XML property: LINKS (connections between devices).
  • XML property: ACTIVITY (activity-specific data, including instructions and grading).
  • XML property: INSTRUCTIONS (text or HTML for user guidance).
  • XML property: ANSWERS (answer network for grading, including expected configurations).
  • Invalid XML chars: Patched from <0x09 to 0x3F (question mark) during deserialization.

Two direct download links for files of format .PKA:

Ghost blog embedded HTML JavaScript for drag and drop .PKA file dump:

<!DOCTYPE html>
<html>
<head>
  <title>.PKA File Dumper</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/twofish@1.0.0/twofish.js"></script> <!-- Assume a TwoFish JS library; you may need to include a full implementation -->
  <style>
    #drop-zone { border: 2px dashed #ccc; padding: 20px; text-align: center; }
  </style>
</head>
<body>
  <div id="drop-zone">Drag and drop .PKA file here</div>
  <pre id="output"></pre>
  <script>
    const dropZone = document.getElementById('drop-zone');
    const output = document.getElementById('output');

    dropZone.addEventListener('dragover', (e) => e.preventDefault());
    dropZone.addEventListener('drop', (e) => {
      e.preventDefault();
      const file = e.dataTransfer.files[0];
      if (file.name.endsWith('.pka')) {
        const reader = new FileReader();
        reader.onload = (event) => {
          let buffer = new Uint8Array(event.target.result);
          // Stage 1: Reverse XOR
          let length = buffer.length;
          let k = length & 0xFF;
          let tempBuffer = new Uint8Array(length);
          for (let i = 0; i < length; i++) {
            let ch = buffer[length - i - 1];
            let a = (k * (i & 0xFF)) & 0xFF;
            let c = (length - a) & 0xFF;
            tempBuffer[i] = c ^ ch;
          }
          buffer = tempBuffer;
          // Stage 2: TwoFish decryption (using twofish.js library)
          const key = new Uint8Array(16).fill(0x89);
          const iv = new Uint8Array(16).fill(0x10);
          const tf = new Twofish(key);
          let decrypted = [];
          for (let i = 0; i < buffer.length; i += 16) {
            let block = buffer.subarray(i, i + 16);
            decrypted.push(tf.decryptCBC(block, iv)); // Assume CBC mode implementation in library
            iv = block; // Update IV for CBC
          }
          buffer = new Uint8Array(decrypted.flat());
          // Stage 3: Forward XOR
          for (let i = 0; i < buffer.length; i++) {
            let key = (buffer.length - i) & 0xFF;
            buffer[i] ^= key;
          }
          // Stage 4: Decompress
          const uncompSize = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
          const compressed = buffer.subarray(4);
          const xmlBytes = pako.inflate(compressed);
          let xml = new TextDecoder().decode(xmlBytes);
          // Patch invalid chars
          xml = xml.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '?');
          // Parse XML and dump properties
          const parser = new DOMParser();
          const doc = parser.parseFromString(xml, 'application/xml');
          let props = '';
          props += 'Root: ' + doc.documentElement.tagName + '\n';
          props += 'Version: ' + doc.querySelector('VERSION').textContent + '\n';
          props += 'Network Devices: ' + doc.querySelectorAll('DEVICE').length + '\n';
          // Add more as per list, e.g.
          doc.querySelectorAll('DEVICE').forEach((dev, idx) => {
            props += `Device ${idx + 1}:\n`;
            props += '  Type: ' + dev.querySelector('TYPE').textContent + '\n';
            props += '  Model: ' + dev.querySelector('TYPE').getAttribute('model') + '\n';
            props += '  Name: ' + dev.querySelector('NAME').textContent + '\n';
            props += '  Power: ' + dev.querySelector('POWER').textContent + '\n';
            props += '  Description: ' + dev.querySelector('DESCRIPTION').textContent + '\n';
          });
          // Activity properties
          props += 'Instructions: ' + (doc.querySelector('INSTRUCTIONS')?.textContent || 'N/A') + '\n';
          // Etc. for other properties
          output.textContent = props;
        };
        reader.readAsArrayBuffer(file);
      }
    });
  </script>
</body>
</html>

Python class for .PKA file handling:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import zlib
import struct
import xml.etree.ElementTree as ET

class PKAFile:
    def __init__(self, filename):
        self.filename = filename
        self.xml = None
        self.root = None

    def _reverse_xor(self, data):
        length = len(data)
        k = length & 0xFF
        temp = bytearray(length)
        for i in range(length):
            ch = data[length - i - 1]
            a = (k * (i & 0xFF)) & 0xFF
            c = (length - a) & 0xFF
            temp[i] = c ^ ch
        return temp

    def _forward_xor(self, data):
        length = len(data)
        temp = bytearray(data)
        for i in range(length):
            key = (length - i) & 0xFF
            temp[i] ^= key
        return temp

    def _twofish_decrypt(self, data):
        key = b'\x89' * 16
        iv = b'\x10' * 16
        cipher = Cipher(algorithms.Twofish(key), modes.CBC(iv), backend=default_backend())
        decryptor = cipher.decryptor()
        return decryptor.update(data) + decryptor.finalize()

    def _decompress(self, data):
        uncomp_size = struct.unpack('>I', data[:4])[0]
        compressed = data[4:]
        return zlib.decompress(compressed)

    def read(self):
        with open(self.filename, 'rb') as f:
            data = bytearray(f.read())
        data = self._reverse_xor(data)
        data = self._twofish_decrypt(data)
        data = self._forward_xor(data)
        xml_bytes = self._decompress(data)
        xml_str = xml_bytes.decode('utf-8', errors='replace').replace('\x03', '?')
        self.xml = xml_str
        self.root = ET.fromstring(xml_str)

    def print_properties(self):
        if not self.root:
            self.read()
        print('Root:', self.root.tag)
        print('Version:', self.root.find('VERSION').text)
        print('Network Devices:', len(self.root.findall('.//DEVICE')))
        for idx, dev in enumerate(self.root.findall('.//DEVICE'), 1):
            print(f'Device {idx}:')
            print('  Type:', dev.find('ENGINE/TYPE').text)
            print('  Model:', dev.find('ENGINE/TYPE').attrib.get('model'))
            print('  Name:', dev.find('ENGINE/NAME').text)
            print('  Power:', dev.find('ENGINE/POWER').text)
            print('  Description:', dev.find('ENGINE/DESCRIPTION').text)
        # Add prints for other properties like INSTRUCTIONS, ANSWERS, etc.

    def _compress(self, xml_bytes):
        compressed = zlib.compress(xml_bytes)
        uncomp_size = struct.pack('>I', len(xml_bytes))
        return uncomp_size + compressed

    def _twofish_encrypt(self, data):
        key = b'\x89' * 16
        iv = b'\x10' * 16
        cipher = Cipher(algorithms.Twofish(key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        return encryptor.update(data) + encryptor.finalize()

    def _reverse_xor_encode(self, data):
        # Inverse of decode; for simplicity, apply the decode logic in reverse order for encoding
        # Note: Encoding is reverse of decoding stages
        length = len(data)
        temp = bytearray(length)
        for i in range(length):
            c = data[i]
            a = ( (length & 0xFF) * (i & 0xFF) ) & 0xFF
            key = (length - a) & 0xFF
            ch = c ^ key
            temp[length - i - 1] = ch
        return temp

    def write(self, new_filename):
        if not self.xml:
            return
        xml_bytes = self.xml.encode('utf-8')
        data = self._compress(xml_bytes)
        data = self._forward_xor(data)  # Reverse order for encoding
        data = self._twofish_encrypt(data)
        data = self._reverse_xor_encode(data)
        with open(new_filename, 'wb') as f:
            f.write(data)

Java class for .PKA file handling:

import java.io.*;
import java.nio.*;
import java.util.zip.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;

public class PKAFile {
    private String filename;
    private String xml;
    private Document doc;

    public PKAFile(String filename) {
        this.filename = filename;
    }

    private byte[] reverseXor(byte[] data) {
        int length = data.length;
        byte k = (byte) length;
        byte[] temp = new byte[length];
        for (int i = 0; i < length; i++) {
            byte ch = data[length - i - 1];
            byte a = (byte) (k * (byte) i);
            byte c = (byte) (length - a);
            temp[i] = (byte) (c ^ ch);
        }
        return temp;
    }

    private byte[] forwardXor(byte[] data) {
        int length = data.length;
        byte[] temp = data.clone();
        for (int i = 0; i < length; i++) {
            byte key = (byte) (length - i);
            temp[i] ^= key;
        }
        return temp;
    }

    private byte[] twofishDecrypt(byte[] data) throws Exception {
        byte[] key = new byte[16];
        byte[] iv = new byte[16];
        for (int i = 0; i < 16; i++) {
            key[i] = (byte) 0x89;
            iv[i] = (byte) 0x10;
        }
        Cipher cipher = Cipher.getInstance("Twofish/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "Twofish"), new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }

    private byte[] decompress(byte[] data) throws Exception {
        ByteBuffer bb = ByteBuffer.wrap(data, 0, 4);
        bb.order(ByteOrder.BIG_ENDIAN);
        int uncompSize = bb.getInt();
        Inflater inflater = new Inflater();
        inflater.setInput(data, 4, data.length - 4);
        byte[] xmlBytes = new byte[uncompSize];
        inflater.inflate(xmlBytes);
        inflater.end();
        return xmlBytes;
    }

    public void read() throws Exception {
        FileInputStream fis = new FileInputStream(filename);
        byte[] data = fis.readAllBytes();
        fis.close();
        data = reverseXor(data);
        data = twofishDecrypt(data);
        data = forwardXor(data);
        byte[] xmlBytes = decompress(data);
        xml = new String(xmlBytes, "UTF-8").replaceAll("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]", "?");
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        doc = db.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
    }

    public void printProperties() throws Exception {
        if (doc == null) read();
        System.out.println("Root: " + doc.getDocumentElement().getTagName());
        System.out.println("Version: " + doc.getElementsByTagName("VERSION").item(0).getTextContent());
        NodeList devices = doc.getElementsByTagName("DEVICE");
        System.out.println("Network Devices: " + devices.getLength());
        for (int idx = 0; idx < devices.getLength(); idx++) {
            Element dev = (Element) devices.item(idx);
            System.out.println("Device " + (idx + 1) + ":");
            System.out.println("  Type: " + dev.getElementsByTagName("TYPE").item(0).getTextContent());
            System.out.println("  Model: " + dev.getElementsByTagName("TYPE").item(0).getAttributes().getNamedItem("model").getTextContent());
            System.out.println("  Name: " + dev.getElementsByTagName("NAME").item(0).getTextContent());
            System.out.println("  Power: " + dev.getElementsByTagName("POWER").item(0).getTextContent());
            System.out.println("  Description: " + dev.getElementsByTagName("DESCRIPTION").item(0).getTextContent());
        }
        // Add similar for other properties
    }

    private byte[] compress(byte[] xmlBytes) throws Exception {
        Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION);
        deflater.setInput(xmlBytes);
        deflater.finish();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        while (!deflater.finished()) {
            int count = deflater.deflate(buf);
            baos.write(buf, 0, count);
        }
        deflater.end();
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.putInt(xmlBytes.length);
        byte[] header = bb.array();
        byte[] compressed = baos.toByteArray();
        byte[] result = new byte[header.length + compressed.length];
        System.arraycopy(header, 0, result, 0, header.length);
        System.arraycopy(compressed, 0, result, header.length, compressed.length);
        return result;
    }

    private byte[] twofishEncrypt(byte[] data) throws Exception {
        byte[] key = new byte[16];
        byte[] iv = new byte[16];
        for (int i = 0; i < 16; i++) {
            key[i] = (byte) 0x89;
            iv[i] = (byte) 0x10;
        }
        Cipher cipher = Cipher.getInstance("Twofish/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "Twofish"), new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }

    private byte[] reverseXorEncode(byte[] data) {
        int length = data.length;
        byte k = (byte) length;
        byte[] temp = new byte[length];
        for (int i = 0; i < length; i++) {
            byte c = data[i];
            byte a = (byte) (k * (byte) i);
            byte key = (byte) (length - a);
            byte ch = (byte) (c ^ key);
            temp[length - i - 1] = ch;
        }
        return temp;
    }

    public void write(String newFilename) throws Exception {
        if (xml == null) return;
        byte[] xmlBytes = xml.getBytes("UTF-8");
        byte[] data = compress(xmlBytes);
        data = forwardXor(data); // Reverse order
        data = twofishEncrypt(data);
        data = reverseXorEncode(data);
        FileOutputStream fos = new FileOutputStream(newFilename);
        fos.write(data);
        fos.close();
    }
}

JavaScript class for .PKA file handling (Node.js style, using crypto and zlib built-ins; note Twofish requires a third-party module like 'twofish'):

const fs = require('fs');
const zlib = require('zlib');
const crypto = require('crypto'); // For general, but Twofish not built-in; use 'twofish' module
const { Twofish } = require('twofish'); // Assume installed via npm

class PKAFile {
  constructor(filename) {
    this.filename = filename;
    this.xml = null;
    this.root = null;
  }

  reverseXor(data) {
    const length = data.length;
    const k = length & 0xff;
    const temp = new Buffer.alloc(length);
    for (let i = 0; i < length; i++) {
      const ch = data[length - i - 1];
      const a = (k * (i & 0xff)) & 0xff;
      const c = (length - a) & 0xff;
      temp[i] = c ^ ch;
    }
    return temp;
  }

  forwardXor(data) {
    const length = data.length;
    const temp = Buffer.from(data);
    for (let i = 0; i < length; i++) {
      const key = (length - i) & 0xff;
      temp[i] ^= key;
    }
    return temp;
  }

  twofishDecrypt(data) {
    const key = Buffer.alloc(16, 0x89);
    const iv = Buffer.alloc(16, 0x10);
    const tf = new Twofish(key);
    let decrypted = Buffer.alloc(0);
    let currentIv = iv;
    for (let i = 0; i < data.length; i += 16) {
      const block = data.slice(i, i + 16);
      const decBlock = tf.decrypt(block);
      for (let j = 0; j < 16; j++) decBlock[j] ^= currentIv[j];
      decrypted = Buffer.concat([decrypted, decBlock]);
      currentIv = block;
    }
    return decrypted;
  }

  decompress(data) {
    const uncompSize = data.readUInt32BE(0);
    const compressed = data.slice(4);
    return zlib.inflateSync(compressed);
  }

  read() {
    const data = fs.readFileSync(this.filename);
    let buffer = this.reverseXor(data);
    buffer = this.twofishDecrypt(buffer);
    buffer = this.forwardXor(buffer);
    const xmlBytes = this.decompress(buffer);
    this.xml = xmlBytes.toString('utf8').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '?');
    const parser = new DOMParser();
    this.root = parser.parseFromString(this.xml, 'application/xml').documentElement;
  }

  printProperties() {
    if (!this.root) this.read();
    console.log('Root:', this.root.tagName);
    console.log('Version:', this.root.querySelector('VERSION').textContent);
    const devices = this.root.querySelectorAll('DEVICE');
    console.log('Network Devices:', devices.length);
    devices.forEach((dev, idx) => {
      console.log(`Device ${idx + 1}:`);
      console.log('  Type:', dev.querySelector('TYPE').textContent);
      console.log('  Model:', dev.querySelector('TYPE').getAttribute('model'));
      console.log('  Name:', dev.querySelector('NAME').textContent);
      console.log('  Power:', dev.querySelector('POWER').textContent);
      console.log('  Description:', dev.querySelector('DESCRIPTION').textContent);
    });
    // Add for other properties
  }

  compress(xmlBytes) {
    const compressed = zlib.deflateSync(xmlBytes);
    const header = Buffer.alloc(4);
    header.writeUInt32BE(xmlBytes.length, 0);
    return Buffer.concat([header, compressed]);
  }

  twofishEncrypt(data) {
    const key = Buffer.alloc(16, 0x89);
    const iv = Buffer.alloc(16, 0x10);
    const tf = new Twofish(key);
    let encrypted = Buffer.alloc(0);
    let currentIv = iv;
    for (let i = 0; i < data.length; i += 16) {
      const block = data.slice(i, i + 16);
      for (let j = 0; j < 16; j++) block[j] ^= currentIv[j];
      const encBlock = tf.encrypt(block);
      encrypted = Buffer.concat([encrypted, encBlock]);
      currentIv = encBlock;
    }
    return encrypted;
  }

  reverseXorEncode(data) {
    const length = data.length;
    const k = length & 0xff;
    const temp = new Buffer.alloc(length);
    for (let i = 0; i < length; i++) {
      const c = data[i];
      const a = (k * (i & 0xff)) & 0xff;
      const key = (length - a) & 0xff;
      const ch = c ^ key;
      temp[length - i - 1] = ch;
    }
    return temp;
  }

  write(newFilename) {
    if (!this.xml) return;
    const xmlBytes = Buffer.from(this.xml, 'utf8');
    let data = this.compress(xmlBytes);
    data = this.forwardXor(data);
    data = this.twofishEncrypt(data);
    data = this.reverseXorEncode(data);
    fs.writeFileSync(newFilename, data);
  }
}

C class (using C++ for class support, assuming zlib and Crypto++ libraries for zlib and Twofish):

#include <iostream>
#include <fstream>
#include <vector>
#include <zlib.h>
#include <cryptopp/twofish.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
#include <tinyxml2.h> // Assume tinyxml2 for XML parsing

class PKAFile {
private:
    std::string filename;
    std::string xml;
    tinyxml2::XMLDocument doc;

public:
    PKAFile(const std::string& fn) : filename(fn) {}

    std::vector<uint8_t> reverseXor(const std::vector<uint8_t>& data) {
        size_t length = data.size();
        uint8_t k = static_cast<uint8_t>(length);
        std::vector<uint8_t> temp(length);
        for (size_t i = 0; i < length; ++i) {
            uint8_t ch = data[length - i - 1];
            uint8_t a = static_cast<uint8_t>(k * static_cast<uint8_t>(i));
            uint8_t c = static_cast<uint8_t>(length - a);
            temp[i] = c ^ ch;
        }
        return temp;
    }

    std::vector<uint8_t> forwardXor(const std::vector<uint8_t>& data) {
        size_t length = data.size();
        std::vector<uint8_t> temp = data;
        for (size_t i = 0; i < length; ++i) {
            uint8_t key = static_cast<uint8_t>(length - i);
            temp[i] ^= key;
        }
        return temp;
    }

    std::vector<uint8_t> twofishDecrypt(const std::vector<uint8_t>& data) {
        byte key[CryptoPP::Twofish::DEFAULT_KEYLENGTH];
        byte iv[CryptoPP::Twofish::BLOCKSIZE];
        std::fill(key, key + sizeof(key), 0x89);
        std::fill(iv, iv + sizeof(iv), 0x10);
        std::string plain;
        CryptoPP::CBC_Mode<CryptoPP::Twofish>::Decryption d(key, sizeof(key), iv);
        CryptoPP::StringSource ss(data.data(), data.size(), true,
            new CryptoPP::StreamTransformationFilter(d,
                new CryptoPP::StringSink(plain)
            )
        );
        return std::vector<uint8_t>(plain.begin(), plain.end());
    }

    std::vector<uint8_t> decompress(const std::vector<uint8_t>& data) {
        uint32_t uncompSize = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
        std::vector<uint8_t> xmlBytes(uncompSize);
        z_stream infstream;
        infstream.zalloc = Z_NULL;
        infstream.zfree = Z_NULL;
        infstream.opaque = Z_NULL;
        infstream.avail_in = data.size() - 4;
        infstream.next_in = const_cast<Bytef*>(&data[4]);
        infstream.avail_out = uncompSize;
        infstream.next_out = &xmlBytes[0];
        inflateInit(&infstream);
        inflate(&infstream, Z_NO_FLUSH);
        inflateEnd(&infstream);
        return xmlBytes;
    }

    void read() {
        std::ifstream file(filename, std::ios::binary);
        std::vector<uint8_t> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();
        data = reverseXor(data);
        data = twofishDecrypt(data);
        data = forwardXor(data);
        auto xmlBytes = decompress(data);
        xml = std::string(xmlBytes.begin(), xmlBytes.end());
        for (char& c : xml) if (c < 9) c = '?';
        doc.Parse(xml.c_str());
    }

    void printProperties() {
        if (doc.IsEmpty()) read();
        auto root = doc.RootElement();
        std::cout << "Root: " << root->Name() << std::endl;
        std::cout << "Version: " << root->FirstChildElement("VERSION")->GetText() << std::endl;
        tinyxml2::XMLElement* network = root->FirstChildElement("NETWORK");
        auto device = network->FirstChildElement("DEVICES")->FirstChildElement("DEVICE");
        int count = 0;
        while (device) {
            count++;
            std::cout << "Device " << count << ":" << std::endl;
            auto engine = device->FirstChildElement("ENGINE");
            std::cout << "  Type: " << engine->FirstChildElement("TYPE")->GetText() << std::endl;
            std::cout << "  Model: " << engine->FirstChildElement("TYPE")->Attribute("model") << std::endl;
            std::cout << "  Name: " << engine->FirstChildElement("NAME")->GetText() << std::endl;
            std::cout << "  Power: " << engine->FirstChildElement("POWER")->GetText() << std::endl;
            std::cout << "  Description: " << engine->FirstChildElement("DESCRIPTION")->GetText() << std::endl;
            device = device->NextSiblingElement("DEVICE");
        }
        std::cout << "Network Devices: " << count << std::endl;
        // Add for other properties
    }

    std::vector<uint8_t> compress(const std::vector<uint8_t>& xmlBytes) {
        uLongf compSize = compressBound(xmlBytes.size());
        std::vector<uint8_t> compressed(compSize);
        compress(&compressed[0], &compSize, &xmlBytes[0], xmlBytes.size());
        compressed.resize(compSize);
        std::vector<uint8_t> result(4 + compSize);
        result[0] = (xmlBytes.size() >> 24) & 0xFF;
        result[1] = (xmlBytes.size() >> 16) & 0xFF;
        result[2] = (xmlBytes.size() >> 8) & 0xFF;
        result[3] = xmlBytes.size() & 0xFF;
        std::copy(compressed.begin(), compressed.end(), result.begin() + 4);
        return result;
    }

    std::vector<uint8_t> twofishEncrypt(const std::vector<uint8_t>& data) {
        byte key[CryptoPP::Twofish::DEFAULT_KEYLENGTH];
        byte iv[CryptoPP::Twofish::BLOCKSIZE];
        std::fill(key, key + sizeof(key), 0x89);
        std::fill(iv, iv + sizeof(iv), 0x10);
        std::string cipher;
        CryptoPP::CBC_Mode<CryptoPP::Twofish>::Encryption e(key, sizeof(key), iv);
        CryptoPP::StringSource ss(data.data(), data.size(), true,
            new CryptoPP::StreamTransformationFilter(e,
                new CryptoPP::StringSink(cipher)
            )
        );
        return std::vector<uint8_t>(cipher.begin(), cipher.end());
    }

    std::vector<uint8_t> reverseXorEncode(const std::vector<uint8_t>& data) {
        size_t length = data.size();
        uint8_t k = static_cast<uint8_t>(length);
        std::vector<uint8_t> temp(length);
        for (size_t i = 0; i < length; ++i) {
            uint8_t c = data[i];
            uint8_t a = static_cast<uint8_t>(k * static_cast<uint8_t>(i));
            uint8_t key = static_cast<uint8_t>(length - a);
            uint8_t ch = c ^ key;
            temp[length - i - 1] = ch;
        }
        return temp;
    }

    void write(const std::string& newFilename) {
        if (xml.empty()) return;
        std::vector<uint8_t> xmlBytes(xml.begin(), xml.end());
        auto data = compress(xmlBytes);
        data = forwardXor(data);
        data = twofishEncrypt(data);
        data = reverseXorEncode(data);
        std::ofstream file(newFilename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(data.data()), data.size());
        file.close();
    }
};