Skip to content

Instantly share code, notes, and snippets.

@endolith
Created August 2, 2011 03:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save endolith/1119561 to your computer and use it in GitHub Desktop.
Save endolith/1119561 to your computer and use it in GitHub Desktop.
Launch ambiguous files in the appropriate program

This script lets you launch a file with an ambiguous file extension, like .sch, which is used to represent many different formats for many different programs. Windows only lets you associate files based on their extension. This script looks into the file itself and determines what type it is, then launches the correct app, similar to the way Linux looks at magic numbers.

Windows 7 doesn't let you associate with a script directly, but you can do it in the registry.
Associate it with something, then regedit, go to HKEY_CLASSES_ROOT\SCH_auto_file\shell\open\command, and add the script like "C:\..python" "..script.py" "%1"

You can put in any bytes you want to make this work with any ambiguous file extension. None of the magic numbers can be a substring of another, or the wrong one might be recognized. Use a hex editor like HxD to open a bunch of files from the same app, and cycle through them looking for the first byte that changes. If you add more formats, please leave them in a comment here or fork it or whatever.

The paths aren't necessarily right. I don't actually have all of these installed right now.

Config file

This is in YAML format. The first indentation level is just a name for the program, then the path to the executable, and then the list of file signatures that it should open

Enclose paths and plain text signatures in single quotes 'like this' to avoid problems with slashes and colons

Enclosing text dumps in double quotes "like this" allows use of C escapes like \r and \n

Hex dumps are automatically recognized as long as each byte is separated by a space, like this: 49 49 02 04. (10 by itself does not get recognized even though it fits the regex because YAML interprets it as an integer before it gets a chance to be regexed. So I had to use the !hexdump tag for that one.)

Some of these file dumps are from personal testing, others from File Extension Seeker

To do

  • I got this compiling with cc_freeze, and moved the configuration into a .yml file, so post that version of the code.
    • Try to make a single executable with bbfreeze instead
  • Handle .pcb and .brd files, too? Then it snowballs into handling any ambiguous extension with one program, though.
  • Use python-magic/libmagic instead?
    • More complex, but more powerful format
    • Have to invent mime types for these schematic/PCB files
    • Could be expanded to many file types, could become a Mini-Magic for Windows for any ambiguous file type? (But same icon for all those files? :/)
  • Use balloon pop-ups instead of opening the Command Prompt window
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 01 22:16:44 2011
@author: endolith@gmail.com
"""
import sys
import subprocess
import os.path
import yaml
import binascii
import re
from time import sleep
# TODO: This needs to be an absolute path? Should fit into whatever scheme Windows uses for that stuff.
# Different on my different computers
# Or there's probably a way to say "in the same directory as this file, not the working directory"
# os.path.dirname(os.path.abspath(__file__)) doesn't work after cc_freeze
signatures_file = 'magic_numbers.yml'
# Automatically recognize hexadecimal sequences like 'cf 12 f2 d0 23'
def hex_constructor(loader, node):
""" Converts hex like '88 ce cf c2' into '\x88\xce\xcf\xc2' """
value = loader.construct_scalar(node)
stripped = ''.join(value.split())
return binascii.unhexlify(stripped)
def main():
yaml.add_constructor(u'!hexdump', hex_constructor)
pattern = re.compile(r'^[0-9A-Fa-f]{2}( [0-9A-Fa-f]{2})*$')
yaml.add_implicit_resolver(u'!hexdump', pattern)
try:
magic_numbers = yaml.load(open(signatures_file))
except Exception, e:
print ('error reading ' + signatures_file)
sys.exit(e)
# TODO: Check whether definitions conflict. Check every signature against every other, minimum of the two sizes
try:
filename = sys.argv[1]
f = open(filename, 'rb')
except IndexError:
sys.exit('Needs a filename to launch')
except IOError:
sys.exit("File not found: " + filename)
# Read just enough bytes to match the signatures
max_len = 0
for value in magic_numbers.itervalues():
max_len = max(max_len, len(max(value['signatures'])))
magic_n = f.read(max_len)
f.close()
program = None
# Compare file with signatures
for file_type, value in magic_numbers.iteritems():
for sig in value['signatures']:
if sig == magic_n[:len(sig)]:
# File signature found
print("File type detected: " + file_type)
program = os.path.expandvars(value['path'])
if program:
print("Opening with: " + program)
else:
sys.exit("No executable listed in " + signatures_file + " for " + file_type)
break
if program:
break
else:
# File signature not found
print('Unknown file format, starts with:')
# Hex dump
hex_dump = binascii.hexlify(magic_n)
print('\n ' + ' '.join([hex_dump[x:x+2] for x in xrange(0, len(hex_dump), 2)]))
# Plain text
print(repr(magic_n))
# With special characters stripped
#print('\n ' + ''.join(c for c in magic_n if ord(c) >= 32))
try:
subprocess.Popen([program, filename])
sleep(3) # So the message is readable
except Exception, e:
print("Error running program: ")
exit(e)
if __name__ == "__main__":
try:
main()
except SystemExit, e:
print e
raw_input('(Press <Enter> to close)')
except Exception, e:
print('Error:')
print(e)
raw_input('(Press <Enter> to close)') # Otherwise Windows closes the window too quickly to read
raise
# Hopefully this config file is pretty self-explanatory. See readme.md if you're confused.
TINA:
path: '%PROGRAMFILES(X86)%\DesignSoft\Tina 9 - TI\TINA.EXE'
signatures:
- 'OBSS'
PSpice Schematics:
path: '%PROGRAMFILES%\OrCAD_Demo\PSpice\psched.exe'
signatures:
- '*version' # OrCad Capture can't open these, only PSpice Schematics can
OrCad Capture:
path: 'C:\OrCAD\OrCAD_16.5_Lite\tools\capture\Capture.exe'
signatures:
- "Schematic FILE\r\n" # OrCad 16.5 says "Translating SDT Schematic to Capture"
- 78 31 30 30 88 CE CF CF 4F 72 43 41 44 # "OrCAD 32bit Schematic" OrCad 16.5 says "Translating SDT Schematic to Capture."
- D0 CF 11 E0 A1 B1 1A E1 # PSpice says "Capture 7.2 data format"
Protel:
path: '%PROGRAMFILES(X86)%\Altium Designer S09 Viewer\dxp.exe'
signatures:
- 'DProtel for Windows'
PADS Logic:
path: 'C:\MentorGraphics\9.3PADS\SDD_HOME\Programs\powerlogic.exe'
signatures:
- 00 FE
- ff a3
Eagle:
path: '%PROGRAMFILES(X86)%\EAGLE-6.1.0\bin\eagle.exe'
signatures:
- !hexdump 10 # Eagle 5?
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE eagle" # Eagle 6
CIRCAD:
path: '%PROGRAMFILES(X86)%\OmniGlyph\OmniGlyph.exe'
signatures:
- 'CIRCAD Version 3' # v3.x
- 'CIRCAD Version 4' # v4.x
- 'Contents: CIRCAD' # v5.x
Qucs:
path: '%PROGRAMFILES(X86)%\Qucs\bin\qucs.exe'
signatures:
- '<Qucs Schematic ' # v0.0.x
P-CAD:
path: # Can be opened by Altium?
signatures:
- 'Personal CAD Sys' # PC-CAPS Database
- 49 49 02 04 20 B4 # P-CAD schematic
# A very simple setup script to create a single executable
#
# hello.py is a very simple "Hello, world" type script which also displays the
# environment in which the script runs
#
# Run the build process by running the command 'python setup.py build'
#
# If everything works well you should find a subdirectory in the build
# subdirectory that contains the files needed to run the script without Python
from cx_Freeze import setup, Executable
Target_1 = Executable(
# what to build
script = "magic_launcher.py",
#initScript = None,
#base = 'Win32GUI',
#targetDir = r"dist",
#targetName = "hello.exe",
compress = False,
copyDependentFiles = True,
appendScriptToExe = True,
appendScriptToLibrary = False,
icon = r'geda win7.ico',
)
setup(
name = "Magic number launcher",
version = "0.1",
description = "SCH file launcher",
author = "endolith@gmail.com",
executables = [Target_1],
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment