#!/usr/bin/env python3 """ Convert Sedona .sab binary file to .sax XML format Final working version based on hex dump analysis """ import sys import os import struct from pathlib import Path def read_null_string(data, offset): """Read a null-terminated string""" start = offset while offset < len(data) and data[offset] != 0: offset += 1 if offset >= len(data): return None, offset string = data[start:offset].decode('ascii') offset += 1 # Skip null return string, offset def sab_to_sax(input_file): """Convert .sab file to .sax XML format""" # Read binary file with open(input_file, 'rb') as f: sab_data = f.read() offset = 0 # Check magic number magic = sab_data[0:4].decode('ascii') if magic != "sapp": print(f"Error: Not a valid Sedona binary file") return offset += 4 print(f"āœ“ Valid Sedona app file detected") # Version (2 bytes) version = struct.unpack_from('>H', sab_data, offset)[0] offset += 2 # Kit count kit_count = struct.unpack_from('>H', sab_data, offset)[0] offset += 2 # Read kit names and checksums kits = [] for i in range(kit_count): kit_name, offset = read_null_string(sab_data, offset) checksum = struct.unpack_from('>I', sab_data, offset)[0] offset += 4 kits.append((kit_name, checksum)) print(f" Found kit: {kit_name}") # Start XML output sax_xml = '\n' sax_xml += f'\n\n' sax_xml += ' \n' # Group types by kit (based on prefixes in the names) kit_types = {kit[0]: [] for kit in kits} # Read all type names and their data type_id = 1 while offset < len(sab_data): try: # Read type name type_name, new_offset = read_null_string(sab_data, offset) if type_name is None: break # Determine which kit this type belongs to assigned_kit = None for kit_name in kit_types.keys(): if type_name.startswith(kit_name) or kit_name in type_name: assigned_kit = kit_name break if not assigned_kit: assigned_kit = kits[0][0] # Default to first kit # Read the 4 bytes after the name (type data) if new_offset + 4 <= len(sab_data): type_data = struct.unpack_from('>I', sab_data, new_offset)[0] new_offset += 4 # Store type info kit_types[assigned_kit].append({ 'id': type_id, 'name': type_name, 'data': type_data }) type_id += 1 offset = new_offset except Exception as e: print(f" Warning: Stopped parsing at offset {offset}: {e}") break # Write XML for each kit for kit_name, checksum in kits: types = kit_types.get(kit_name, []) sax_xml += f' \n' for t in types: sax_xml += f' \n' sax_xml += ' \n' sax_xml += ' \n\n' # Add placeholder for app and links sax_xml += ' \n' sax_xml += ' \n' sax_xml += ' \n\n' sax_xml += ' \n' sax_xml += ' \n' sax_xml += ' \n' sax_xml += '' # Write output file output_file = str(Path(input_file).with_suffix('.sax')) with open(output_file, 'w', encoding='utf-8') as f: f.write(sax_xml) print(f"\nāœ“ Successfully converted: {input_file} -> {output_file}") print(f" Found {type_id-1} total types across {len(kits)} kits") def main(): if len(sys.argv) != 2: print("Usage: python sab_to_sax.py ") sys.exit(1) input_file = sys.argv[1] if not os.path.exists(input_file): print(f"Error: File not found: {input_file}") sys.exit(1) try: sab_to_sax(input_file) except Exception as e: print(f"Error converting file: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()