diff --git a/API.md b/API.md
old mode 100755
new mode 100644
diff --git a/INSTALL.md b/INSTALL.md
index 4bfbf09..87cfaaf 100755
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -31,6 +31,18 @@ Although all binwalk run-time dependencies are optional, the `python-lzma` modul
 $ sudo apt-get install python-lzma
 ```
 
+Binwalk uses the `pycrypto` library to decrypt some known encrypted firmware images:
+
+```bash
+# Python2.7
+$ sudo apt-get install python-crypto
+```
+
+```bash
+# Python3.x
+$ sudo apt-get install python3-crypto
+```
+
 Binwalk uses [pyqtgraph](http://www.pyqtgraph.org) to generate graphs and visualizations, which requires the following: 
 
 ```bash
@@ -70,7 +82,7 @@ $ sudo apt-get install mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabex
 # Install sasquatch to extract non-standard SquashFS images
 $ sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev
 $ git clone https://github.com/devttys0/sasquatch
-$ (cd sasquatch && make && sudo make install)
+$ (cd sasquatch && ./build.sh)
 ```
 
 ```bash
@@ -81,6 +93,19 @@ $ (cd jefferson && sudo python setup.py install)
 ```
 
 ```bash
+# Install ubi_reader to extract UBIFS file systems
+$ sudo apt-get install liblzo2-dev python-lzo
+$ git clone https://github.com/jrspruitt/ubi_reader
+$ (cd ubi_reader && sudo python setup.py install)
+```
+
+```bash
+# Install yaffshiv to extract YAFFS file systems
+$ git clone https://github.com/devttys0/yaffshiv
+$ (cd yaffshiv && sudo python setup.py install)
+```
+
+```bash
 # Install unstuff (closed source) to extract StuffIt archive files
 $ wget -O - http://my.smithmicro.com/downloads/files/stuffit520.611linux-i386.tar.gz | tar -zxv
 $ sudo cp bin/unstuff /usr/local/bin/
diff --git a/deps.sh b/deps.sh
index 887a0c7..6a5fa2c 100755
--- a/deps.sh
+++ b/deps.sh
@@ -1,11 +1,12 @@
 #!/bin/bash
+set -o nounset
 
 REQUIRED_UTILS="wget tar python"
 APTCMD="apt-get"
 YUMCMD="yum"
-APT_CANDIDATES="git build-essential libqt4-opengl mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabextract cramfsprogs cramfsswap squashfs-tools zlib1g-dev liblzma-dev liblzo2-dev"
-PYTHON2_APT_CANDIDATES="python-lzma python-pip python-opengl python-qt4 python-qt4-gl python-numpy python-scipy"
-PYTHON3_APT_CANDIDATES="python3-pip python3-opengl python3-pyqt4 python3-pyqt4.qtopengl python3-numpy python3-scipy"
+APT_CANDIDATES="git build-essential libqt4-opengl mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabextract cramfsprogs cramfsswap squashfs-tools zlib1g-dev liblzma-dev liblzo2-dev sleuthkit openjdk-7-jdk"
+PYTHON2_APT_CANDIDATES="python-crypto python-lzo python-lzma python-pip python-opengl python-qt4 python-qt4-gl python-numpy python-scipy"
+PYTHON3_APT_CANDIDATES="python3-crypto python3-pip python3-opengl python3-pyqt4 python3-pyqt4.qtopengl python3-numpy python3-scipy"
 PYTHON3_YUM_CANDIDATES=""
 YUM_CANDIDATES="git gcc gcc-c++ make openssl-devel qtwebkit-devel qt-devel gzip bzip2 tar arj p7zip p7zip-plugins cabextract squashfs-tools zlib zlib-devel lzo lzo-devel xz xz-compat-libs xz-libs xz-devel xz-lzma-compat python-backports-lzma lzip pyliblzma perl-Compress-Raw-Lzma"
 PYTHON2_YUM_CANDIDATES="python-pip python-opengl python-qt4 numpy python-numdisplay numpy-2f python-Bottleneck scipy"
@@ -25,13 +26,13 @@ fi
 function install_sasquatch
 {
     git clone https://github.com/devttys0/sasquatch
-    (cd sasquatch && make && $SUDO make install)
+    (cd sasquatch && $SUDO ./build.sh)
     $SUDO rm -rf sasquatch
 }
 
 function install_jefferson
 {
-    $SUDO pip install cstruct
+    install_pip_package cstruct
     git clone https://github.com/sviehb/jefferson
     (cd jefferson && $SUDO python2 setup.py install)
     $SUDO rm -rf jefferson
@@ -47,6 +48,13 @@ function install_unstuff
     rm -rf /tmp/unstuff
 }
 
+function install_ubireader
+{
+    git clone https://github.com/jrspruitt/ubi_reader
+    (cd ubi_reader && $SUDO python setup.py install)
+    $SUDO rm -rf ubi_reader
+}
+
 function install_pip_package
 {
     PACKAGE="$1"
@@ -137,10 +145,16 @@ fi
 
 # Do the install(s)
 cd /tmp
-sudo $PKGCMD $PKGCMD_OPTS $PKG_CANDIDTES
+sudo $PKGCMD $PKGCMD_OPTS $PKG_CANDIDATES
+if [ $? -ne 0 ]
+    then
+    echo "Package installation failed: $PKG_CANDIDATES"
+    exit 1
+fi
 install_pip_package pyqtgraph
 install_pip_package capstone
 install_sasquatch
 install_jefferson
 install_unstuff
+install_ubireader
 
diff --git a/images/README.md b/images/README.md
new file mode 100644
index 0000000..3f8e8df
--- /dev/null
+++ b/images/README.md
@@ -0,0 +1 @@
+This is just a directory to store screenshots used in the github Wiki / documentation.
diff --git a/images/binwalk_ida_plugin_output.png b/images/binwalk_ida_plugin_output.png
new file mode 100644
index 0000000..18b0e7c
Binary files /dev/null and b/images/binwalk_ida_plugin_output.png differ
diff --git a/images/binwalk_ida_plugin_usage.png b/images/binwalk_ida_plugin_usage.png
new file mode 100644
index 0000000..1036628
Binary files /dev/null and b/images/binwalk_ida_plugin_usage.png differ
diff --git a/setup.py b/setup.py
index b9785cc..6e3fb21 100755
--- a/setup.py
+++ b/setup.py
@@ -8,6 +8,7 @@ from distutils.core import setup, Command
 from distutils.dir_util import remove_tree
 
 MODULE_NAME = "binwalk"
+SCRIPT_NAME = MODULE_NAME
 
 # Python2/3 compliance
 try:
@@ -200,7 +201,7 @@ for data_dir in ["magic", "config", "plugins", "modules", "core"]:
 
 # Install the module, script, and support files
 setup(name = MODULE_NAME,
-      version = "2.1.0",
+      version = "2.1.2b",
       description = "Firmware analysis tool",
       author = "Craig Heffner",
       url = "https://github.com/devttys0/%s" % MODULE_NAME,
@@ -208,7 +209,7 @@ setup(name = MODULE_NAME,
       requires = [],
       packages = [MODULE_NAME],
       package_data = {MODULE_NAME : install_data_files},
-      scripts = [os.path.join("scripts", MODULE_NAME)],
+      scripts = [os.path.join("scripts", SCRIPT_NAME)],
 
       cmdclass = {'clean' : CleanCommand, 'uninstall' : UninstallCommand, 'idainstall' : IDAInstallCommand, 'idauninstall' : IDAUnInstallCommand}
 )
diff --git a/src/binwalk/__init__.py b/src/binwalk/__init__.py
index 76bbe25..e4443cf 100755
--- a/src/binwalk/__init__.py
+++ b/src/binwalk/__init__.py
@@ -1,9 +1,11 @@
-__all__ = ['scan', 'execute', 'Modules', 'ModuleException']
+__all__ = ['scan', 'execute', 'ModuleException']
 
 from binwalk.core.module import Modules, ModuleException
 
 # Convenience functions
 def scan(*args, **kwargs):
-    return Modules(*args, **kwargs).execute()
+    with Modules(*args, **kwargs) as m:
+        objs = m.execute()
+    return objs
 def execute(*args, **kwargs):
-    return Modules(*args, **kwargs).execute()
+    return scan(*args, **kwargs)
diff --git a/src/binwalk/config/extract.conf b/src/binwalk/config/extract.conf
index 656a33c..37e972a 100755
--- a/src/binwalk/config/extract.conf
+++ b/src/binwalk/config/extract.conf
@@ -13,23 +13,23 @@
 #
 #   o zlib
 #   o cpio
-#   o jffs2
 #   o Raw LZMA/deflate streams
+#   o Hilink encrypted uImage firmware
 #
 # There are also alternative extractors for the following file formats, implemented as plugins:
 #
 #   o gzip
 #   o lzma
+#   o xz
 #
 #######################################################################################################################################
 
 # Assumes these utilities are installed in $PATH.
 ^gzip compressed data:gz:gzip -d -f '%e':0,2
 ^lzma compressed data:7z:7z e -y '%e':0,1
-^xz compressed data:tar:tar xJf '%e'
+^xz compressed data:xz:7z e -y '%e':0,1
 ^bzip2 compressed data:bz2:bzip2 -d '%e'
 ^compress'd data:Z:gzip -d '%e'
-^zip archive data:zip:7z x -y '%e' -p '':0,1
 ^posix tar archive:tar:tar xvf '%e'
 ^rar archive data:rar:unrar e '%e'
 ^rar archive data:rar:unrar -x '%e' # This is for the 'free' version
@@ -39,6 +39,17 @@
 ^microsoft cabinet archive:cab:cabextract '%e'
 ^stuffit:sit:unstuff '%e'
 
+# jar just does a better job of extracting zip files than either
+# unzip or 7z.
+#
+# unzip does not support files that are "missing end of central directory header".
+# 
+# 7z handles most zip files, but fails on some zip archives, inexplicably seeing
+# only the *last* entry in the zip archive (though 7z thinks it succeeded). See
+# StarCam firmware CH-sys-48.53.64.67.zip.
+^zip archive data:zip:jar xvf '%e':0
+^zip archive data:zip:7z x -y '%e' -p '':0,1
+
 # Try unsquashfs first, or if not installed, sasquatch
 ^squashfs filesystem:squashfs:unsquashfs -d '%%squashfs-root%%' '%e':0:False
 ^squashfs filesystem:squashfs:sasquatch -p 1 -le -d '%%squashfs-root%%' '%e':0:False
@@ -60,15 +71,21 @@
 # Use sviehb's jefferson.py tool for JFFS2 extraction
 ^jffs2 filesystem:jffs2:jefferson -d '%%jffs2-root%%' '%e':0:False
 
+# Use ubi_reader tool for UBIFS extraction
+^ubifs filesystem superblock node:ubi:ubireader_extract_files -o '%%ubifs-root%%' '%e':0:False
+^ubi erase count header:ubi:ubireader_extract_files -o '%%ubifs-root%%' '%e':0:False
+
+# Experimental yaffs extractor
+^yaffs filesystem:yaffs:yaffshiv --auto --brute-force -f '%e' -d '%%yaffs-root%%':0:False
+
 # These were extractors used from FMK that still need suitable replacements.
 #^bff volume entry:bff:/opt/firmware-mod-kit/src/bff/bffxtractor.py '%e'
 #^wdk file system:wdk:/opt/firmware-mod-kit/src/firmware-tools/unwdk.py '%e'
 
 # Extract, but don't run anything
-^ubifs filesystem:ubi
-^elf,:elf
-private key:key
-certificate:crt
-html document header
-xml document:xml
+#^elf,:elf
+#private key:key
+#certificate:crt
+#html document header
+#xml document:xml
 
diff --git a/src/binwalk/core/common.py b/src/binwalk/core/common.py
index 04cdd2a..d297dd6 100755
--- a/src/binwalk/core/common.py
+++ b/src/binwalk/core/common.py
@@ -5,11 +5,15 @@ import os
 import re
 import sys
 import ast
-import hashlib
 import platform
 import operator as op
+import binwalk.core.idb
 from binwalk.core.compat import *
 
+# Don't try to import hashlib when loaded into IDA; it doesn't work.
+if not binwalk.core.idb.LOADED_IN_IDA:
+    import hashlib
+
 # The __debug__ value is a bit backwards; by default it is set to True, but
 # then set to False if the Python interpreter is run with the -O option.
 if not __debug__:
@@ -483,3 +487,4 @@ def BlockFile(fname, mode='r', subclass=io.FileIO, **kwargs):
             return (data, dlen)
 
     return InternalBlockFile(fname, mode=mode, **kwargs)
+
diff --git a/src/binwalk/core/display.py b/src/binwalk/core/display.py
index 62dabd1..dc7de79 100755
--- a/src/binwalk/core/display.py
+++ b/src/binwalk/core/display.py
@@ -33,6 +33,31 @@ class Display(object):
             if csv:
                 self.csv = pycsv.writer(self.fp)
 
+    def _fix_unicode(self, line):
+        '''
+        This is a hack, there must be a better way to handle it.
+        In Python3, if the environment variable LANG=C is set, indicating
+        that the terminal is ASCII only, but unicode characters need to be
+        printed to the screen or to a file (e.g., file path, magic result
+        format string), then an UnicodeEncodError exception will be raised.
+
+        This converts the given line to ASCII, ignoring conversion errors,
+        and returns a str.
+        '''
+        return bytes2str(line.encode('ascii', 'ignore'))
+
+    def _fix_unicode_list(self, columns):
+        '''
+        Convenience wrapper for self.log which is passed a list of format arguments.
+        '''
+        if type(columns) in [list, tuple]:
+            for i in range(0, len(columns)):
+                try:
+                    columns[i] = self._fix_unicode(columns[i])
+                except AttributeError:
+                    pass
+        return columns
+
     def format_strings(self, header, result):
         self.result_format = result
         self.header_format = header
@@ -43,9 +68,15 @@ class Display(object):
     def log(self, fmt, columns):
         if self.fp:
             if self.csv:
-                self.csv.writerow(columns)
+                try:
+                    self.csv.writerow(columns)
+                except UnicodeEncodeError:
+                    self.csv.writerow(self._fix_unicode_list(columns))
             else:
-                self.fp.write(fmt % tuple(columns))
+                try:
+                    self.fp.write(fmt % tuple(columns))
+                except UnicodeEncodeError:
+                    self.fp.write(fmt % tuple(self._fix_unicode_list(columns)))
 
             self.fp.flush()
 
@@ -100,7 +131,11 @@ class Display(object):
 
         if not self.quiet and stdout:
             try:
-                sys.stdout.write(self._format_line(line.strip()) + "\n")
+                try:
+                    sys.stdout.write(self._format_line(line.strip()) + "\n")
+                except UnicodeEncodeError:
+                    line = self._fix_unicode(line)
+                    sys.stdout.write(self._format_line(line.strip()) + "\n")
                 sys.stdout.flush()
             except IOError as e:
                 pass
diff --git a/src/binwalk/core/magic.py b/src/binwalk/core/magic.py
index 47ec88f..0c398a4 100755
--- a/src/binwalk/core/magic.py
+++ b/src/binwalk/core/magic.py
@@ -789,6 +789,7 @@ class Magic(object):
 
         Returns None.
         '''
+        # Magic files must be ASCII, else encoding issues can arise.
         fp = open(fname, "r")
         lines = fp.readlines()
         self.parse(lines)
diff --git a/src/binwalk/core/module.py b/src/binwalk/core/module.py
index b5c0f4e..b17b1f8 100755
--- a/src/binwalk/core/module.py
+++ b/src/binwalk/core/module.py
@@ -6,12 +6,15 @@
 import io
 import os
 import sys
+import time
 import inspect
 import argparse
 import traceback
+import binwalk.core.statuserver
 import binwalk.core.common
 import binwalk.core.settings
 import binwalk.core.plugin
+from threading import Thread
 from binwalk.core.compat import *
 
 class Option(object):
@@ -216,10 +219,11 @@ class Module(object):
     # Set to False if this is not a primary module (e.g., General, Extractor modules)
     PRIMARY = True
 
-    def __init__(self, **kwargs):
+    def __init__(self, parent, **kwargs):
         self.errors = []
         self.results = []
 
+        self.parent = parent
         self.target_file_list = []
         self.status = None
         self.enabled = False
@@ -258,6 +262,13 @@ class Module(object):
         '''
         return None
 
+    def unload(self):
+        '''
+        Invoked at module load time.
+        May be overridden by the module sub-class.
+        '''
+        return None
+
     def reset(self):
         '''
         Invoked only for dependency modules immediately prior to starting a new primary module.
@@ -336,6 +347,17 @@ class Module(object):
 
         return args
 
+    def _unload_dependencies(self):
+        # Calls the unload method for all dependency modules.
+        # These modules cannot be unloaded immediately after being run, as
+        # they must persist until the module that depends on them is finished.
+        # As such, this must be done separately from the Modules.run 'unload' call.
+        for dependency in self.dependencies:
+            try:
+                getattr(self, dependency.attribute).unload()
+            except AttributeError:
+                continue
+
     def next_file(self, close_previous=True):
         '''
         Gets the next file to be scanned (including pending extracted files, if applicable).
@@ -358,7 +380,7 @@ class Module(object):
 
         # Reset all dependencies prior to continuing with another file.
         # This is particularly important for the extractor module, which must be reset
-        # in order to reset it's base output directory path for each file, and the
+        # in order to reset its base output directory path for each file, and the
         # list of pending files.
         self.reset_dependencies()
 
@@ -386,8 +408,10 @@ class Module(object):
 
         if fp is not None:
             self.current_target_file_name = fp.path
+            self.status.fp = fp
         else:
             self.current_target_file_name = None
+            self.status.fp = None
 
         self.previous_next_file_fp = fp
 
@@ -426,6 +450,12 @@ class Module(object):
         self.validate(r)
         self._plugins_result(r)
 
+        # Update the progress status automatically if it is not being done manually by the module
+        if r.offset and r.file and self.AUTO_UPDATE_STATUS:
+            self.status.total = r.file.length
+            self.status.completed = r.offset
+            self.status.fp = r.file
+
         for dependency in self.dependencies:
             try:
                 getattr(self, dependency.attribute).callback(r)
@@ -435,11 +465,6 @@ class Module(object):
         if r.valid:
             self.results.append(r)
 
-            # Update the progress status automatically if it is not being done manually by the module
-            if r.offset and r.file and self.AUTO_UPDATE_STATUS:
-                self.status.total = r.file.length
-                self.status.completed = r.offset
-
             if r.display:
                 display_args = self._build_display_args(r)
                 if display_args:
@@ -499,14 +524,14 @@ class Module(object):
             if hasattr(self, dependency.attribute):
                 getattr(self, dependency.attribute).reset()
 
-    def main(self, parent):
+    def main(self):
         '''
         Responsible for calling self.init, initializing self.config.display, and calling self.run.
 
         Returns the value returned from self.run.
         '''
-        self.status = parent.status
-        self.modules = parent.loaded_modules
+        self.status = self.parent.status
+        self.modules = self.parent.executed_modules
 
         # A special exception for the extractor module, which should be allowed to
         # override the verbose setting, e.g., if --matryoshka has been specified
@@ -584,12 +609,25 @@ class Modules(object):
         Returns None.
         '''
         self.arguments = []
-        self.loaded_modules = {}
+        self.executed_modules = {}
         self.default_dependency_modules = {}
-        self.status = Status(completed=0, total=0)
+        self.status = Status(completed=0, total=0, fp=None, running=False, shutdown=False, finished=False)
+        self.status_server_started = False
+        self.status_service = None
 
         self._set_arguments(list(argv), kargv)
 
+    def cleanup(self):
+        if self.status_service:
+            self.status_service.server.socket.shutdown(1)
+            self.status_service.server.socket.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, t, v, b):
+        self.cleanup()
+
     def _set_arguments(self, argv=[], kargv={}):
         for (k,v) in iterator(kargv):
             k = self._parse_api_opt(k)
@@ -691,7 +729,7 @@ class Modules(object):
             obj = self.run(module)
 
         # Add all loaded modules that marked themselves as enabled to the run_modules list
-        for (module, obj) in iterator(self.loaded_modules):
+        for (module, obj) in iterator(self.executed_modules):
             # Report the results if the module is enabled and if it is a primary module or if it reported any results/errors
             if obj.enabled and (obj.PRIMARY or obj.results or obj.errors):
                 run_modules.append(obj)
@@ -704,15 +742,29 @@ class Modules(object):
         '''
         Runs a specific module.
         '''
-        obj = self.load(module, kwargs)
+        try:
+            obj = self.load(module, kwargs)
 
-        if isinstance(obj, binwalk.core.module.Module) and obj.enabled:
-            obj.main(parent=self)
-            self.status.clear()
+            if isinstance(obj, binwalk.core.module.Module) and obj.enabled:
+                obj.main()
+                self.status.clear()
 
-        # If the module is not being loaded as a dependency, add it to the loaded modules dictionary
-        if not dependency:
-            self.loaded_modules[module] = obj
+            # If the module is not being loaded as a dependency, add it to the executed modules dictionary.
+            # This is used later in self.execute to determine which objects should be returned.
+            if not dependency:
+                self.executed_modules[module] = obj
+
+                # The unload method tells the module that we're done with it, and gives it a chance to do
+                # any cleanup operations that may be necessary. We still retain the object instance in self.executed_modules.
+                obj._unload_dependencies()
+                obj.unload()
+        except KeyboardInterrupt as e:
+            # Tell the status server to shut down, and give it time to clean up.
+            if self.status.running:
+                self.status.shutdown = True
+                while not self.status.finished:
+                    time.sleep(0.1)
+            raise e
 
         return obj
 
@@ -720,7 +772,7 @@ class Modules(object):
         argv = self.argv(module, argv=self.arguments)
         argv.update(kwargs)
         argv.update(self.dependencies(module, argv['enabled']))
-        return module(**argv)
+        return module(self, **argv)
 
     def dependencies(self, module, module_enabled):
         import binwalk.modules
@@ -859,6 +911,21 @@ class Modules(object):
         else:
             raise Exception("binwalk.core.module.Modules.process_kwargs: %s has no attribute 'KWARGS'" % str(obj))
 
+    def status_server(self, port):
+        '''
+        Starts the progress bar TCP service on the specified port.
+        This service will only be started once per instance, regardless of the
+        number of times this method is invoked.
+
+        Failure to start the status service is considered non-critical; that is,
+        a warning will be displayed to the user, but normal operation will proceed.
+        '''
+        if self.status_server_started == False:
+            self.status_server_started = True
+            try:
+                self.status_service = binwalk.core.statuserver.StatusServer(port, self)
+            except Exception as e:
+                binwalk.core.common.warning("Failed to start status server on port %d: %s" % (port, str(e)))
 
 def process_kwargs(obj, kwargs):
     '''
@@ -869,7 +936,9 @@ def process_kwargs(obj, kwargs):
 
     Returns None.
     '''
-    return Modules().kwargs(obj, kwargs)
+    with Modules() as m:
+        kwargs = m.kwargs(obj, kwargs)
+    return kwargs
 
 def show_help(fd=sys.stdout):
     '''
@@ -879,6 +948,7 @@ def show_help(fd=sys.stdout):
 
     Returns None.
     '''
-    fd.write(Modules().help())
+    with Modules() as m:
+        fd.write(m.help())
 
 
diff --git a/src/binwalk/core/settings.py b/src/binwalk/core/settings.py
index d76502b..d4248ca 100755
--- a/src/binwalk/core/settings.py
+++ b/src/binwalk/core/settings.py
@@ -17,7 +17,7 @@ class Settings:
         o PLUGINS             - Path to the plugins directory.
     '''
     # Release version
-    VERSION = "2.1.0"
+    VERSION = "2.1.2b"
 
     # Sub directories
     BINWALK_USER_DIR = ".binwalk"
diff --git a/src/binwalk/core/statuserver.py b/src/binwalk/core/statuserver.py
new file mode 100644
index 0000000..7fb993b
--- /dev/null
+++ b/src/binwalk/core/statuserver.py
@@ -0,0 +1,75 @@
+# Provides scan status information via a TCP socket service.
+# Currently only works for signature scans.
+
+import sys
+import time
+import errno
+import threading
+import binwalk.core.compat
+
+# Python 2/3 compatibility
+try:
+    import SocketServer
+except ImportError:
+    import socketserver as SocketServer
+
+class StatusRequestHandler(SocketServer.BaseRequestHandler):
+
+    def handle(self):
+        message_format = "%s     %3d%%     [ %d / %d ]"
+        last_status_message_len = 0
+        status_message = ''
+        message_sent = False
+
+        self.server.binwalk.status.running = True
+
+        while True:
+            time.sleep(0.1)
+
+            try:
+                self.request.send(binwalk.core.compat.str2bytes('\b' * last_status_message_len))
+                self.request.send(binwalk.core.compat.str2bytes(' ' * last_status_message_len))
+                self.request.send(binwalk.core.compat.str2bytes('\b' * last_status_message_len))
+
+                if self.server.binwalk.status.shutdown:
+                    self.server.binwalk.status.finished = True
+                    break
+
+                if self.server.binwalk.status.total != 0:
+                    percentage = ((float(self.server.binwalk.status.completed) / float(self.server.binwalk.status.total)) * 100)
+                    status_message = message_format % (self.server.binwalk.status.fp.path,
+                                                       percentage,
+                                                       self.server.binwalk.status.completed,
+                                                       self.server.binwalk.status.total)
+                elif not message_sent:
+                    status_message = "No status information available at this time!"
+                else:
+                    continue
+
+                last_status_message_len = len(status_message)
+                self.request.send(binwalk.core.compat.str2bytes(status_message))
+                message_sent = True
+            except IOError as e:
+                if e.errno == errno.EPIPE:
+                    break
+            except Exception as e:
+                binwalk.core.common.debug('StatusRequestHandler exception: ' + str(e) + '\n')
+            except KeyboardInterrupt as e:
+                raise e
+
+        self.server.binwalk.status.running = False
+        return
+
+class ThreadedStatusServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    daemon_threads = True
+    allow_reuse_address = True
+
+class StatusServer(object):
+
+    def __init__(self, port, binwalk):
+        self.server = ThreadedStatusServer(('127.0.0.1', port), StatusRequestHandler)
+        self.server.binwalk = binwalk
+
+        t = threading.Thread(target=self.server.serve_forever)
+        t.setDaemon(True)
+        t.start()
diff --git a/src/binwalk/magic/binarch b/src/binwalk/magic/binarch
index 89c2325..014bd2f 100755
--- a/src/binwalk/magic/binarch
+++ b/src/binwalk/magic/binarch
@@ -17,15 +17,15 @@
 #
 # addiu $sp, XX
 # jr $ra
-0	belong	0x03e00008	MIPS instructions, function epilogue
->4	beshort	!0x27BD		{invalid}
-0	beshort	0x27BD		MIPS instructions, function epilogue
->2	belong	!0x03e00008	{invalid}
+0	ubelong	0x03e00008	MIPS instructions, function epilogue
+>4	ubeshort	!0x27BD		{invalid}
+0	ubeshort	0x27BD		MIPS instructions, function epilogue
+>2	ubelong	!0x03e00008	{invalid}
 
-0	lelong	0x03e00008	MIPSEL instructions, function epilogue
->6	leshort	!0x27BD		{invalid}
-0	leshort	0x27BD		MIPS instructions, function epilogue
->2	lelong	!0x03e00008	{invalid}
+0	ulelong	0x03e00008	MIPSEL instructions, function epilogue
+>6	uleshort	!0x27BD		{invalid}
+0	uleshort	0x27BD		MIPS instructions, function epilogue
+>2	ulelong	!0x03e00008	{invalid}
 
 # MIPS16e
 # nop (x4)
@@ -41,63 +41,63 @@
 # move $sp, $s1
 # restore XX, XX, XX
 # jrc $ra
-0	beshort	0x65B9			MIPS16e instructions, function epilogue
->3	byte	!0x64			{invalid}
->4	beshort	!0xE8A0			{invalid}
-0	leshort	0x65B9			MIPSEL16e instructions, function epilogue
->3	byte	!0x64			{invalid}
->4	leshort	!0xE8A0			{invalid}
+0	ubeshort	0x65B9			MIPS16e instructions, function epilogue
+>3	byte	    !0x64			{invalid}
+>4	ubeshort	!0xE8A0			{invalid}
+0	uleshort	0x65B9			MIPSEL16e instructions, function epilogue
+>3	byte	    !0x64			{invalid}
+>4	uleshort	!0xE8A0			{invalid}
 
 # jrc $ra
 # nop
-0	belong	0xe8a06500		MIPS16e instructions, function epilogue
-0	lelong	0xe8a06500		MIPSEL16e instructions, function epilogue
+0	ubelong	0xe8a06500		MIPS16e instructions, function epilogue
+0	ulelong	0xe8a06500		MIPSEL16e instructions, function epilogue
 
 # PowerPC prologue
 # mflr r0
-0	belong 0x7C0802A6	PowerPC big endian instructions, function prologue
-0	lelong 0x7C0802A6	PowerPC little endian instructions, function prologue
+0	ubelong 0x7C0802A6	PowerPC big endian instructions, function prologue
+0	ulelong 0x7C0802A6	PowerPC little endian instructions, function prologue
 
 # PowerPC epilogue
 # blr
-0	belong 0x4E800020	PowerPC big endian instructions, function epilogue
-0	lelong 0x4E800020	PowerPC little endian instructions, function epilogue
+0	ubelong 0x4E800020	PowerPC big endian instructions, function epilogue
+0	ulelong 0x4E800020	PowerPC little endian instructions, function epilogue
 
 # TODO: Add ARM Thumb dectection
 # ARM prologue
 # STMFD SP!, {XX}
 # <any instruction whose opcode begins with 0xE>
-0	beshort		0xE92D	ARMEB instructions, function prologue
+0	ubeshort	0xE92D	ARMEB instructions, function prologue
 >4	byte&0xF0	!0xE0	{invalid}
 >8	byte&0xF0	!0xE0	{invalid}
-0	leshort		0xE92D	ARM instructions, function prologue{adjust:-2}
+0	uleshort	0xE92D	ARM instructions, function prologue{adjust:-2}
 >5	byte&0xF0	!0xE0	{invalid}
 >9	byte&0xF0	!0xE0	{invalid}
 
 # ARM epilogue
 # MOV R0, XX
 # LDMFD SP!, {XX}
-0	beshort		0xE1A0	ARMEB instructions, function epilogue
+0	ubeshort	0xE1A0	ARMEB instructions, function epilogue
 >4	beshort		!0xE8BD	{invalid}
-0	leshort		0xE1A0	ARM instructions, function epilogue{adjust:-2}
+0	uleshort	0xE1A0	ARM instructions, function epilogue{adjust:-2}
 >4	leshort		!0xE8BD	{invalid}
 
 
 # Ubicom32 prologue
 # move.4 -4($sp)++, $ra
-0	belong	0x02FF6125	Ubicom32 instructions, function prologue
+0	ubelong	    0x02FF6125	Ubicom32 instructions, function prologue
 
 # Ubicom32 epilogues
 # calli $ra, 0($ra)
 # ret ($sp)4++
-0	belong	0xF0A000A0	Ubicom32 instructions, function epilogue
-0	belong	0x000022E1	Ubicom32 instructions, function epilogue
+0	ubelong	    0xF0A000A0	Ubicom32 instructions, function epilogue
+0	ubelong	    0x000022E1	Ubicom32 instructions, function epilogue
 
 # AVR8 prologue
 # push r28
 # push r29
-0	belong	0x93CF93DF	AVR8 instructions, function prologue
-0	belong	0x93DF93CF	AVR8 instructions, function prologue
+0	ubelong	    0x93CF93DF	AVR8 instructions, function prologue
+0	ubelong	    0x93DF93CF	AVR8 instructions, function prologue
 
 # AVR32 prologue
 # pushm   r7,lr
diff --git a/src/binwalk/magic/compressed b/src/binwalk/magic/compressed
index f48fe43..eca5aad 100755
--- a/src/binwalk/magic/compressed
+++ b/src/binwalk/magic/compressed
@@ -115,6 +115,7 @@
 >3      byte            &0x02           \b, has header CRC
 >3      byte&0x04       0x04
 >>10    leshort         x               \b, has %d bytes of extra data
+>>10    leshort         <0              {invalid}(invalid extra data size)               
 >3      byte&0xC        =0x08           \b, has original file name
 >>10    string          x               \b: "%s"{name:%s}
 >3      byte            &0x10           \b, has comment
@@ -137,14 +138,12 @@
 >3      byte            &0x20           \b, encrypted{invalid}
 # Dates before 1992 are {invalid}, unless of course you're DD-WRT in which
 # case you don't know how to set a date in your gzip files. Brilliant.
->4      lelong          =0              \b, NULL date (1970-01-01 00:00:00)
->4      lelong          <0              {invalid}
->4      lelong          >0
->>4     lelong          <694224000      {invalid}
->>4     lelong          =694224000      {invalid}
->>4     lelong          >694224000      \b, last modified:
->>>4    ledate          x               %s
->>>4    lelong          x               {epoch:%d}
+>4      ledate          x               \b, last modified: %s
+>4      lelong          =0              (null date)
+>4      lelong          !0
+>>4     lelong          <694224000      (bogus date)
+>>4     lelong          =694224000      (bogus date)
+
 
 # Supplementary magic data for the file(1) command to support
 # rzip(1).  The format is described in magic(5).
diff --git a/src/binwalk/magic/console b/src/binwalk/magic/console
index dba0674..60db2db 100755
--- a/src/binwalk/magic/console
+++ b/src/binwalk/magic/console
@@ -75,7 +75,7 @@
 >113    string  x               (%s)
 
 #------------------------------------------------------------------------------
-## Microsoft Xbox executables .xbe (Esa Hyytiä <ehyytia@cc.hut.fi>)
+## Microsoft Xbox executables .xbe (Esa Hyyti <ehyytia@cc.hut.fi>)
 0                       string              XBEH            Microsoft Xbox executable (XBE),
 ## probabilistic checks whether signed or not
 >0x0004                 ulelong             =0
diff --git a/src/binwalk/magic/crypto b/src/binwalk/magic/crypto
index 7397b17..374aa30 100755
--- a/src/binwalk/magic/crypto
+++ b/src/binwalk/magic/crypto
@@ -6,6 +6,7 @@
 0       string  -----BEGIN\x20CERTIFICATE\x20REQ    PEM certificate request
 0       string  -----BEGIN\x20RSA\x20PRIVATE        PEM RSA private key
 0       string  -----BEGIN\x20DSA\x20PRIVATE        PEM DSA private key
+0       string  -----BEGIN\x20EC\x20PRIVATE         PEM EC private key
 
 # Type: OpenSSH key files
 # From: Nicolas Collignon <tsointsoin@gmail.com>
@@ -17,6 +18,9 @@
 
 0       string  ssh-dss\x20             OpenSSH DSA public key
 0       string  ssh-rsa\x20             OpenSSH RSA public key
+0       string  ecdsa-sha2-nistp256\x20 OpenSSH ECDSA (Curve P-256) public key
+0       string  ecdsa-sha2-nistp384\x20 OpenSSH ECDSA (Curve P-384) public key
+0       string  ecdsa-sha2-nistp521\x20 OpenSSH ECDSA (Curve P-521) public key
 
 # Type: Certificates/key files in DER format
 # From: Gert Hulselmans <hulselmansgert@gmail.com>
diff --git a/src/binwalk/magic/efi b/src/binwalk/magic/efi
new file mode 100644
index 0000000..8224e59
--- /dev/null
+++ b/src/binwalk/magic/efi
@@ -0,0 +1,89 @@
+# http://blogs.phoenix.com/phoenix_technologies_bios/\
+# 2007/02/uefi_pi_10_firm.html
+
+40      string     _FVH              UEFI PI Firmware Volume
+>32     ulequad    >0xFFFFFFFF       {invalid} unrealistic size
+>32     ulequad    x                 \b, volume size: %d
+>52     uleshort   x                 \b, header size: %d
+>58     ubyte      !0                {invalid} reserved byte set
+>59     ubyte      >1                {invalid} invalid revision
+>59     ubyte      x                 \b, revision: %d
+
+# GUID: 7A9354D9-0468-444A-81CE-0BF617D890D
+>16     string     \xd9\x54\x93\x7a\x68\x04\x4a\x44\x81\xce\x0b\xf6\x17\xd8\x90\xdf  \b, EFI Firmware File System
+
+# GUID: 8C8CE578-8A3D-4F1C-9935-896185C32DD3
+>16     string     \x78\xe5\x8c\x8c\x3d\x8a\x1c\x4f\x99\x35\x89\x61\x85\xc3\x2d\xd3  \b, EFI Firmware File System v2
+
+# GUID: 04ADEEAD-61FF-4D31-B6BA-64F8BF901F5A
+>16     string     \xad\xee\xad\x04\xff\x61\x31\x4d\xb6\xba\x64\xf8\xbf\x90\x1f\x5a  \b, Apple Boot Volume
+
+# GUID: 8C1B00BD-716A-7B48-A14F-0C2A2DCF7A5D
+>16     string     \x8c\x1b\x00\xbd\x71\x6a\x7b\x48\xa1\x4f\x0c\x2a\x2d\xcf\x7a\x5d  \b, Apple Boot Volume v2
+
+# GUID: AD3FFFFF-D28B-44C4-9F13-9EA98A97F9F0
+>16     string     \xff\xff\x3f\xad\x8b\xd2\xc4\x44\x9f\x13\x9e\xa9\x8a\x97\xf9\xf0  \b, Intel v1
+
+# GUID: D6A1CD70-4B33-4994-A6EA-375F2CCC5437
+>16     string     \x70\xcd\xa1\xd6\x33\x4b\x94\x49\xa6\xea\x37\x5f\x2c\xcc\x54\x37  \b, Intel v2
+
+# GUID: 4F494156-AED6-4D64-A537-B8A5557BCEEC
+>16     string     \x56\x41\x49\x4f\xd6\xae\x64\x4d\xa5\x37\xb8\xa5\x55\x7b\xce\xec  \b, Sony v1
+
+# GUID: FFF12B8D-7696-4C8B-85A9-2747075B4F50
+>16     string     \x8d\x2b\xf1\xff\x96\x76\x8b\x4c\xa9\x85\x27\x47\x07\x5b\x4f\x50  \b, Variable Storage
+
+>16     ulelong    x                 \b, GUID: %.8X-
+>>20    uleshort   x                 \b%.4X-
+>>22    uleshort   x                 \b%.4X-
+>>24    uleshort   x                 \b%.4X-
+>>26    ubyte      x                 \b%.2X
+>>27    ubyte      x                 \b%.2X
+>>28    ubyte      x                 \b%.2X
+>>29    ubyte      x                 \b%.2X
+>>30    ubyte      x                 \b%.2X
+>>31    ubyte      x                 \b%.2X
+
+# http://www.intel.com/content/www/us/en/architecture-and-technology/\
+# unified-extensible-firmware-interface/efi-capsule-specification.html
+# GUID: 3B6686BD-0D76-4030-B70E-B5519E2FC5A0
+0       string     \xBD\x86\x66\x3B\x76\x0D\x30\x40\xB7\x0E\xB5\x51\x9E\x2F\xC5\xA0  EFI capsule v0.9
+>16     lelong     <0                {invalid}
+>16     lelong     x                 \b, header size: %d
+>20     lelong     x                 \b, flags: 0x%.8X
+>24     lelong     <0                {invalid}
+>24     lelong     x                 \b, capsule size: %d
+
+# Intel/UEFI format
+# http://www.uefi.org/sites/default/files/resources/UEFI%202_5.pdf
+# GUID: 539182B9-ABB5-4391-B69A-E3A943F72FCC
+0       string     \xb9\x82\x91\x53\xb5\xab\x91\x43\xb6\x9a\xe3\xa9\x43\xf7\x2f\xcc  UEFI capsule
+>16     lelong     <0                {invalid}
+>16     lelong     x                 \b, header size: %d
+>20     lelong     x                 \b, flags: 0x%.8X
+>24     lelong     <0                {invalid}
+>24     lelong     x                 \b, capsule size: %d
+
+# GUID: 4A3CA68B-7723-48FB-803D-578CC1FEC44D
+0       string     \x8b\xa6\x3c\x4a\x23\x77\xfb\x48\x80\x3d\x57\x8c\xc1\xfe\xc4\x4d  AMI Aptio extended EFI capsule
+>16     lelong     <0                {invalid}
+>16     lelong     x                 \b, header size: %d
+>20     lelong     x                 \b, flags: 0x%.8X
+>24     lelong     <0                {invalid}
+>24     lelong     x                 \b, capsule size: %d
+
+# GUID: 14EEBB90-890A-43DB-AED1-5D3C4588A418
+0       string     \x90\xbb\xee\x14\x0a\x89\xdb\x43\xae\xd1\x5d\x3c\x45\x88\xa4\x18  AMI Aptio unsigned EFI capsule
+>16     lelong     <0                {invalid}
+>16     lelong     x                 \b, header size: %d
+>20     lelong     x                 \b, flags: 0x%.8X
+>24     lelong     <0                {invalid}
+>24     lelong     x                 \b, capsule size: %d
+
+# GUID: 3BE07062-1D51-45D2-2B83-F093257ED461
+0       string     \x62\x70\xe0\x3b\x51\x1d\xd2\x45\x83\x2b\xf0\x93\x25\x7e\xd4\x61  Toshiba EFI capsule
+>16     lelong     <0                {invalid}
+>16     lelong     x                 \b, header size: %d
+>20     lelong     x                 \b, flags: 0x%.8X
+>24     lelong     <0                {invalid}
+>24     lelong     x                 \b, capsule size: %d
diff --git a/src/binwalk/magic/filesystems b/src/binwalk/magic/filesystems
index 70519ca..1fbac1a 100755
--- a/src/binwalk/magic/filesystems
+++ b/src/binwalk/magic/filesystems
@@ -34,7 +34,11 @@
 #>0x1e   string      minix       \b, bootable
 
 # YAFFS
-0    string    \x03\x00\x00\x00\x01\x00\x00\x00\xFF\xFF    YAFFS filesystem
+0    string     \x03\x00\x00\x00\x01\x00\x00\x00\xFF\xFF\x00\x00    YAFFS filesystem, little endian
+# The big endian signature has to be done a bit differently to prevent it from being self-overlapping
+4    string     \x00\x00\x00\x01\xFF\xFF                            YAFFS filesystem, big endian
+>0   string     !\x00\x00\x00\x03                                   {invalid}(first object is not a directory)
+>10  string     !\x00                                               {invalid}(unexpected name in the first object entry)
 
 # EFS2 file system - jojo@utulsa.edu
 0      lelong       0x53000000       EFS2 Qualcomm filesystem super block, little endian,
@@ -68,8 +72,11 @@
 
 # MPFS file system
 0       string      MPFS     MPFS filesystem, Microchop,
+>4      byte        <0       {invalid}
+>5      byte        <0       {invalid}
 >4      byte        x        version %d.
 >5      byte        x        \b%d,
+>6      leshort     <0       {invalid}
 >6      leshort     x        %d file entries
 
 # cramfs filesystem - russell@coker.com.au
@@ -116,15 +123,17 @@
 >28     string      !\x00*12        {invalid}
 
 # http://lxr.free-electrons.com/source/fs/ubifs/ubifs-media.h
-#0       string      UBI\x23         UBI erase count header,
-#>4      ubyte       x               version: %d,
-#>5      string      !\x00*3         {invalid}
-#>8      ubequad     x               EC: 0x%lX,
-#>16     ubelong     x               VID header offset: 0x%X,
-#>20     ubelong     x               data offset: 0x%X
+0       string      UBI\x23         UBI erase count header,
+>4      ubyte       x               version: %d,
+>5      string      !\x00*3         {invalid}
+>8      ubequad     x               EC: 0x%lX,
+>16     ubelong     x               VID header offset: 0x%X,
+>20     ubelong     x               data offset: 0x%X
+# dummy jump - actual jump value is determined in UBIValidPlugin
+>20       ubyte       x               {jump:0}
 
 # http://lxr.free-electrons.com/source/fs/ubifs/ubifs-media.h
-0        lelong      0x06101831     UBIFS
+0        lelong      0x06101831     UBIFS filesystem
 >20      ubyte       <6             {invalid}
 >20      ubyte       >7             {invalid} # Only look for superblock and master nodes
 >22      leshort     !0             {invalid} # 2 bytes of padding should be filled with NULLs
diff --git a/src/binwalk/magic/firmware b/src/binwalk/magic/firmware
index b9ca772..5c3f0eb 100755
--- a/src/binwalk/magic/firmware
+++ b/src/binwalk/magic/firmware
@@ -67,6 +67,10 @@
 >31    byte      3              compression type: lzma,
 >32    string    x              image name: "%s"
 
+# Hilink encrypted uImage firmware.
+# Additional validation/processing is done by the hilink.py plugin.
+0x23   string    \x4A\x52\xCA\xDA     Encrypted Hilink uImage firmware header
+
 #IMG0 header, found in VxWorks-based Mercury router firmware
 0       string        IMG0    IMG0 (VxWorks) header,
 >4      belong        <1      {invalid}
@@ -495,8 +499,6 @@
 >35     byte        x           try decryption tool from:
 >35     byte        x           http://download.modem-help.co.uk/mfcs-A/Alcatel/Modems/Misc/
 
-16    string        \xd9\x54\x93\x7a\x68\x04\x4a\x44\x81\xce\x0b\xf6\x17\xd8\x90\xdf    UEFI PI firmware volume
-
 # http://android.stackexchange.com/questions/23357/\
 # is-there-a-way-to-look-inside-and-modify-an-adb-backup-created-file/\
 # 23608#23608
@@ -690,5 +692,13 @@
 >44 lelong &0x4 SLT
 >44 lelong &0xffffff00 {invalid}
 
+# Android bootimg
+# https://android.googlesource.com/platform/system/core.git/+/master/mkbootimg/bootimg.h
+0     string     ANDROID!      Android bootimg
+>8    ulelong    x             \b, kernel size: %d bytes
+>12   ulelong    x             \b, kernel addr: 0x%X
+>16   ulelong    x             \b, ramdisk size: %d bytes
+>20   ulelong    x             \b, ramdisk addr: 0x%X
+>48   string     x             \b, product name: "%s"
 
 
diff --git a/src/binwalk/magic/linux b/src/binwalk/magic/linux
index e802da0..f6c2a7d 100755
--- a/src/binwalk/magic/linux
+++ b/src/binwalk/magic/linux
@@ -3,7 +3,7 @@
 
 # Linux kernel boot images, from Albert Cahalan <acahalan@cs.uml.edu>
 # and others such as Axel Kohlmeyer <akohlmey@rincewind.chemie.uni-ulm.de>
-# and Nicolás Lichtmaier <nick@debian.org>
+# and Nicolas Lichtmaier <nick@debian.org>
 # All known start with: b8 c0 07 8e d8 b8 00 90 8e c0 b9 00 01 29 f6 29
 0       string      \xb8\xc0\x07\x8e\xd8\xb8\x00\x90\x8e\xc0\xb9\x00\x01\x29\xf6\x29    Linux kernel boot image
 >514    string      !HdrS                                                               {invalid}
@@ -16,14 +16,23 @@
 >>14    string      x                       "%s"
 
 # Linux ARM compressed kernel image
-# See arch/arm/boot/compressed/head.S and arch/arm/boot/compressed/vmlinux.lds.S
-0	ulelong	0x016f2818	Linux kernel ARM boot executable zImage (little-endian),
->4	ulelong x		    load address: "0x%.8X",
->8	ulelong	x		    end address: "0x%.8X"
->12 ulelong !0x04030201 {invalid}
-
-0	ubelong	0x016f2818	Linux kernel ARM boot executable zImage (big-endian),
->4	ubelong x		    load address: "0x%.8X",
->8	ubelong	x		    end address: "0x%.8X"
->12 ubelong !0x04030201 {invalid}
+# Starts with 8 NOPs, with 0x016F2818 at offset 0x24
+36  ulelong 0x016F2818                      Linux kernel ARM boot executable zImage (little-endian)
+>0  ulelong !0xE1A00000                     {invalid}(invalid)
+>4  ulelong !0xE1A00000                     {invalid}(invalid)
+>8  ulelong !0xE1A00000                     {invalid}(invalid)
+>12 ulelong !0xE1A00000                     {invalid}(invalid)
+>16 ulelong !0xE1A00000                     {invalid}(invalid)
+>20 ulelong !0xE1A00000                     {invalid}(invalid)
+>24 ulelong !0xE1A00000                     {invalid}(invalid)
+>28 ulelong !0xE1A00000                     {invalid}(invalid)
 
+36  ubelong 0x016F2818                      Linux kernel ARM boot executable zImage (big-endian)
+>0  ubelong !0xE1A00000                     {invalid}(invalid)
+>4  ubelong !0xE1A00000                     {invalid}(invalid)
+>8  ubelong !0xE1A00000                     {invalid}(invalid)
+>12 ubelong !0xE1A00000                     {invalid}(invalid)
+>16 ubelong !0xE1A00000                     {invalid}(invalid)
+>20 ubelong !0xE1A00000                     {invalid}(invalid)
+>24 ubelong !0xE1A00000                     {invalid}(invalid)
+>28 ubelong !0xE1A00000                     {invalid}(invalid)
diff --git a/src/binwalk/magic/misc b/src/binwalk/magic/misc
index 0515d2b..098d955 100755
--- a/src/binwalk/magic/misc
+++ b/src/binwalk/magic/misc
@@ -41,8 +41,18 @@
 # CodeGate 2011 http://nopsrus.blogspot.com/2013/05/codegate-ctf-2011-binary-100-points.html
 0    string    \x23\x40\x7e\x5e    Windows Script Encoded Data (screnc.exe)
 
-0   regex   /[a-zA-Z0-9\.\-_]{1,25}/[a-zA-Z0-9\.\-_]{1,25}/[a-zA-Z0-9\.\-_]{1,25}/[a-zA-Z0-9\.\-_/].*       Unix path:
->0  string  x                                                                                               %s
+0           regex   /[a-zA-Z0-9\.\-_]{1,25}/[a-zA-Z0-9\.\-_]{1,25}/[a-zA-Z0-9\.\-_/].*       Unix path:
+>0          string  x                                                                        %s
+>0          string  !/home/
+>>0         string  !/bin/
+>>>0        string  !/sbin/
+>>>>0       string  !/usr/
+>>>>>0      string  !/sys/
+>>>>>>0     string  !/var/
+>>>>>>>0    string  !/opt/
+>>>>>>>>0   string  !/etc/
+>>>>>>>>>0  string  !/lib/
+>>>>>>>>>>0 string  !/dev/                                                                   {invalid}(likely false positive)
 
 0   string      neighbor           Neighborly text,
 >0  string      x                  "%s
diff --git a/src/binwalk/magic/phones b/src/binwalk/magic/phones
new file mode 100644
index 0000000..a9aca5f
--- /dev/null
+++ b/src/binwalk/magic/phones
@@ -0,0 +1,20 @@
+# From: http://arm.ninja/2016/03/04/reverse-engineering-samsung-s6-modem/
+0           string      TOC\x00\x00\x00\x00     Samsung modem TOC index,
+>20         lelong      x                       size: 0x%X bytes,
+>24         lelong      !0                      invalid TOC CRC,{invalid}
+>28         lelong      !1                      invalid TOC index{invalid}
+>0x20       string      x                       TOC entries: %s
+>0x40       byte        !0
+>>0x40      string      x                       \b, %s
+>0x60       byte        !0
+>>0x60      string      x                       \b, %s
+>0x80       byte        !0
+>>0x80      string      x                       \b, %s
+>0xA0       byte        !0
+>>0xA0      string      x                       \b, %s
+>0xC0       byte        !0
+>>0xC0      string      x                       \b, %s
+>0xE0       byte        !0
+>>0xE0      string      x                       \b, %s
+>0x100      byte        !0
+>>0x100     string      x                       \b, %s
diff --git a/src/binwalk/modules/compression.py b/src/binwalk/modules/compression.py
index f6bfedd..e919f7e 100755
--- a/src/binwalk/modules/compression.py
+++ b/src/binwalk/modules/compression.py
@@ -2,11 +2,14 @@
 
 import os
 import zlib
-import lzma
 import struct
 import binwalk.core.compat
 import binwalk.core.common
 from binwalk.core.module import Option, Kwarg, Module
+try:
+    import lzma
+except ImportError:
+    from backports import lzma
 
 class LZMAHeader(object):
     def __init__(self, **kwargs):
@@ -159,18 +162,38 @@ class Deflate(object):
             self.module.extractor.add_rule(regex='^%s' % self.DESCRIPTION.lower(), extension="deflate", cmd=self.extractor)
 
     def extractor(self, file_name):
+        in_data = ""
+        out_data = ""
+        retval = False
         out_file = os.path.splitext(file_name)[0]
 
+        with binwalk.core.common.BlockFile(file_name, 'r') as fp_in:
+            while True:
+                (data, dlen) = fp_in.read_block()
+                if not data or dlen == 0:
+                    break
+                else:
+                    in_data += data
+
+                try:
+                    out_data = zlib.decompress(binwalk.core.compat.str2bytes(in_data), -15)
+                    with binwalk.core.common.BlockFile(out_file, 'w') as fp_out:
+                        fp_out.write(out_data)
+                    retval = True
+                    break
+                except zlib.error as e:
+                    pass
+
+        return retval
+
     def decompress(self, data):
         valid = True
         description = None
 
-        # Prepend data with a standard zlib header
-        data = "\x78\x9C" + data
-
         # Looking for either a valid decompression, or an error indicating truncated input data
         try:
-            zlib.decompress(binwalk.core.compat.str2bytes(data))
+            # Negative window size (e.g., -15) indicates that raw decompression should be performed
+            zlib.decompress(binwalk.core.compat.str2bytes(data), -15)
         except zlib.error as e:
             if not str(e).startswith("Error -5"):
                 # Bad data.
diff --git a/src/binwalk/modules/entropy.py b/src/binwalk/modules/entropy.py
index dc109ff..f9a4420 100755
--- a/src/binwalk/modules/entropy.py
+++ b/src/binwalk/modules/entropy.py
@@ -3,7 +3,6 @@
 import os
 import math
 import zlib
-import multiprocessing
 import binwalk.core.common
 from binwalk.core.compat import *
 from binwalk.core.module import Module, Option, Kwarg
@@ -113,21 +112,14 @@ class Entropy(Module):
             else:
                 self.block_size = None
 
+    def _entropy_sigterm_handler(self, *args):
+        print ("FUck it all.")
+
     def run(self):
-        # Need to invoke the pyqtgraph stuff via a separate process, as calling pg.exit
-        # is pretty much required. pg.exit calls os._exit though, and we don't want to
-        # exit out of the main process (especially if being run via the API).
-        if not binwalk.core.common.MSWindows():
-            p = multiprocessing.Process(target=self._run)
-            p.start()
-            p.join()
-        else:
-            # There seem to be all kinds of issues using the multiprocessing module in
-            # Windows, as done above.
-            #
-            # This means that when run in Windows, pg.exit will cause binwalk
-            # to exit.
-            self._run()
+        # If generating a graphical plot, this function will never return, as it invokes
+        # pg.exit. Calling pg.exit is pretty much required, but pg.exit calls os._exit in
+        # order to work around QT cleanup issues.
+        self._run()
 
     def _run(self):
         # Sanity check and warning if pyqtgraph isn't found
@@ -135,7 +127,7 @@ class Entropy(Module):
             try:
                 import pyqtgraph as pg
             except ImportError as e:
-                binwalk.core.common.warning("pyqtgraph not found, visual entropy graphing will be disabled")
+                binwalk.core.common.warning("Failed to import pyqtgraph module, visual entropy graphing will be disabled")
                 self.do_plot = False
 
         for fp in iter(self.next_file, None):
diff --git a/src/binwalk/modules/extractor.py b/src/binwalk/modules/extractor.py
index 7f22fa6..66f39ce 100755
--- a/src/binwalk/modules/extractor.py
+++ b/src/binwalk/modules/extractor.py
@@ -72,6 +72,11 @@ class Extractor(Module):
                    type=int,
                    kwargs={'max_size' : 0},
                    description='Limit the size of each extracted file'),
+            Option(short='n',
+                   long='count',
+                   type=int,
+                   kwargs={'max_count' : 0},
+                   description='Limit the number of extracted files'),
             Option(short='r',
                    long='rm',
                    kwargs={'remove_after_execute' : True},
@@ -84,6 +89,7 @@ class Extractor(Module):
 
     KWARGS = [
             Kwarg(name='max_size', default=None),
+            Kwarg(name='max_count', default=None),
             Kwarg(name='base_directory', default=None),
             Kwarg(name='remove_after_execute', default=False),
             Kwarg(name='load_default_rules', default=False),
@@ -100,6 +106,10 @@ class Extractor(Module):
         self.directory = None
         # Key value pairs of input file path and output extraction path
         self.output = {}
+        # Number of extracted files
+        self.extraction_count = 0
+        # Override the directory name used for extraction output directories
+        self.output_directory_override = None
 
         if self.load_default_rules:
             self.load_defaults()
@@ -154,7 +164,7 @@ class Extractor(Module):
         # Only extract valid results that have been marked for extraction and displayed to the user.
         # Note that r.display is still True even if --quiet has been specified; it is False if the result has been
         # explicitly excluded via the -y/-x options.
-        if r.valid and r.extract and r.display:
+        if r.valid and r.extract and r.display and (not self.max_count or self.extraction_count < self.max_count):
             # Create some extract output for this file, it it doesn't already exist
             if not binwalk.core.common.has_key(self.output, r.file.path):
                 self.output[r.file.path] = ExtractInfo()
@@ -165,6 +175,9 @@ class Extractor(Module):
 
             # If the extraction was successful, self.extract will have returned the output directory and name of the dd'd file
             if extraction_directory and dd_file:
+                # Track the number of extracted files
+                self.extraction_count += 1
+
                 # Get the full path to the dd'd file and save it in the output info for this file
                 dd_file_path = os.path.join(extraction_directory, dd_file)
                 self.output[r.file.path].carved[r.offset] = dd_file_path
@@ -190,7 +203,10 @@ class Extractor(Module):
                         self.output[r.file.path].extracted[r.offset].append(real_file_path)
 
                     # If recursion was specified, and the file is not the same one we just dd'd
-                    if self.matryoshka and file_path != dd_file_path and scan_extracted_files:
+                    if (self.matryoshka and
+                        file_path != dd_file_path and
+                        scan_extracted_files and
+                        self.directory in real_file_path):
                         # If the recursion level of this file is less than or equal to our desired recursion level
                         if len(real_file_path.split(self.directory)[1].split(os.path.sep)) <= self.matryoshka:
                             # If this is a directory and we are supposed to process directories for this extractor,
@@ -271,18 +287,19 @@ class Extractor(Module):
             if match:
                 self.append_rule(r)
 
-    def remove_rule(self, text):
+    def remove_rules(self, description):
         '''
-        Remove all rules that match a specified text.
+        Remove all rules that match a specified description.
 
-        @text - The text to match against.
+        @description - The description to match against.
 
         Returns the number of rules removed.
         '''
         rm = []
+        description = description.lower()
 
         for i in range(0, len(self.extract_rules)):
-            if self.extract_rules[i]['regex'].match(text):
+            if self.extract_rules[i]['regex'].search(description):
                 rm.append(i)
 
         for i in rm:
@@ -290,6 +307,27 @@ class Extractor(Module):
 
         return len(rm)
 
+    def edit_rules(self, description, key, value):
+        '''
+        Edit all rules that match a specified description.
+
+        @description - The description to match against.
+        @key         - The key to change for each matching rule.
+        @value       - The new key value for each matching rule.
+
+        Returns the number of rules modified.
+        '''
+        count = 0
+        description = description.lower()
+
+        for i in range(0, len(self.extract_rules)):
+            if self.extract_rules[i]['regex'].search(description):
+                if has_key(self.extract_rules[i], key):
+                    self.extract_rules[i][key] = value
+                    count += 1
+
+        return count
+
     def clear_rules(self):
         '''
         Deletes all extraction rules.
@@ -298,11 +336,25 @@ class Extractor(Module):
         '''
         self.extract_rules = []
 
-    def get_rules(self):
+    def get_rules(self, description=None):
         '''
-        Returns a list of all extraction rules.
+        Returns a list of extraction rules that match a given description.
+
+        @description - The description to match against.
+
+        Returns a list of extraction rules that match the given description.
+        If no description is provided, a list of all rules are returned.
         '''
-        return self.extract_rules
+        if description:
+            rules = []
+            description = description.lower()
+            for i in range(0, len(self.extract_rules)):
+                if self.extract_rules[i]['regex'].search(description):
+                    rules.append(self.extract_rules[i])
+        else:
+            rules = self.extract_rules
+
+        return rules
 
     def load_from_file(self, fname):
         '''
@@ -344,6 +396,23 @@ class Extractor(Module):
                     if binwalk.core.common.DEBUG:
                         raise Exception("Extractor.load_defaults failed to load file '%s': %s" % (extract_file, str(e)))
 
+    def get_output_directory_override(self):
+        '''
+        Returns the current output directory basename override value.
+        '''
+        return self.output_directory_override
+
+    def override_output_directory_basename(self, dirname):
+        '''
+        Allows the overriding of the default extraction directory basename.
+
+        @dirname - The directory base name to use.
+
+        Returns the current output directory basename override value.
+        '''
+        self.output_directory_override = dirname
+        return self.output_directory_override
+
     def build_output_directory(self, path):
         '''
         Set the output directory for extracted files.
@@ -357,31 +426,42 @@ class Extractor(Module):
             basedir = os.path.dirname(path)
             basename = os.path.basename(path)
 
-            # Make sure we put the initial extracted file in the CWD
+            # Make sure we put the initial extraction directory in the CWD
             if self.directory is None:
-                if self.base_directory is None:
-                    basedir = os.getcwd()
-                else:
-                    basedir = self.base_directory
-                    if not os.path.exists(basedir):
-                        os.mkdir(basedir)
+                self.directory = os.getcwd()
+
+            if basedir != self.directory:
+                # During recursive extraction, extracted files will be in subdirectories
+                # of the CWD. This allows us to figure out the subdirectory by simply
+                # splitting the target file's base directory on our known CWD.
+                #
+                # However, the very *first* file being scanned is not necessarily in the
+                # CWD, so this will raise an IndexError. This is easy to handle though,
+                # since the very first file being scanned needs to have its contents
+                # extracted to ${CWD}/_basename.extracted, so we just set the subdir
+                # variable to a blank string when an IndexError is encountered.
+                try:
+                    subdir = basedir.split(self.directory)[1][1:]
+                except IndexError as e:
+                    subdir = ""
+            else:
+                subdir = ""
 
-            outdir = os.path.join(basedir, '_' + basename)
-            output_directory = unique_file_name(outdir, extension='extracted')
+            if self.output_directory_override:
+                output_directory = os.path.join(self.directory, subdir, self.output_directory_override)
+            else:
+                outdir = os.path.join(self.directory, subdir, '_' + basename)
+                output_directory = unique_file_name(outdir, extension='extracted')
 
             if not os.path.exists(output_directory):
                 os.mkdir(output_directory)
 
             self.extraction_directories[path] = output_directory
+            self.output[path].directory = os.path.realpath(output_directory) + os.path.sep
         # Else, just use the already created directory
         else:
             output_directory = self.extraction_directories[path]
 
-        # Set the initial base extraction directory for later determining the level of recusion
-        if self.directory is None:
-            self.directory = os.path.realpath(output_directory) + os.path.sep
-            self.output[path].directory = self.directory
-
         return output_directory
 
     def cleanup_extracted_files(self, tf=None):
@@ -413,6 +493,7 @@ class Extractor(Module):
         Returns the name of the extracted file (blank string if nothing was extracted).
         '''
         fname = ''
+        recurse = False
         original_dir = os.getcwd()
         rules = self.match(description)
         file_path = os.path.realpath(file_name)
@@ -423,6 +504,7 @@ class Extractor(Module):
         else:
             binwalk.core.common.debug("Found %d matching extraction rules" % len(rules))
 
+        # Generate the output directory name where extracted files will be stored
         output_directory = self.build_output_directory(file_name)
 
         # Extract to end of file if no size was specified
@@ -689,7 +771,7 @@ class Extractor(Module):
         except KeyboardInterrupt as e:
             raise e
         except Exception as e:
-            binwalk.core.common.warning("Extractor.execute failed to run external extrator '%s': %s" % (str(cmd), str(e)))
+            binwalk.core.common.warning("Extractor.execute failed to run external extractor '%s': %s" % (str(cmd), str(e)))
             retval = None
 
         if tmp is not None:
diff --git a/src/binwalk/modules/general.py b/src/binwalk/modules/general.py
index 6434007..0549a97 100755
--- a/src/binwalk/modules/general.py
+++ b/src/binwalk/modules/general.py
@@ -80,6 +80,11 @@ class General(Module):
                type=str,
                kwargs={'file_name_exclude_regex' : ""},
                description='Do not scan files whose names match this regex'),
+        Option(short='s',
+               long='status',
+               type=int,
+               kwargs={'status_server_port' : 0},
+               description='Enable the status server on the specified port'),
         Option(long=None,
                short=None,
                type=binwalk.core.common.BlockFile,
@@ -96,6 +101,7 @@ class General(Module):
         Kwarg(name='offset', default=0),
         Kwarg(name='base', default=0),
         Kwarg(name='block', default=0),
+        Kwarg(name='status_server_port', default=0),
         Kwarg(name='swap_size', default=0),
         Kwarg(name='log_file', default=None),
         Kwarg(name='csv', default=False),
@@ -113,6 +119,7 @@ class General(Module):
     PRIMARY = False
 
     def load(self):
+        self.threads_active = False
         self.target_files = []
 
         # A special case for when we're loaded into IDA
@@ -141,6 +148,9 @@ class General(Module):
             if not binwalk.core.idb.LOADED_IN_IDA:
                 sys.exit(0)
 
+        if self.status_server_port > 0:
+            self.parent.status_server(self.status_server_port)
+
     def reset(self):
         pass
 
@@ -181,7 +191,13 @@ class General(Module):
         if swap is None:
             swap = self.swap_size
 
-        return binwalk.core.common.BlockFile(fname, subclass=self.subclass, length=length, offset=offset, swap=swap, block=block, peek=peek)
+        return binwalk.core.common.BlockFile(fname,
+                                             subclass=self.subclass,
+                                             length=length,
+                                             offset=offset,
+                                             swap=swap,
+                                             block=block,
+                                             peek=peek)
 
     def _open_target_files(self):
         '''
diff --git a/src/binwalk/modules/hexdiff.py b/src/binwalk/modules/hexdiff.py
index cfcb923..0bfc707 100755
--- a/src/binwalk/modules/hexdiff.py
+++ b/src/binwalk/modules/hexdiff.py
@@ -118,6 +118,13 @@ class HexDiff(Module):
         loop_count = 0
         sep_count = 0
 
+        # Figure out the maximum diff size (largest file size)
+        self.status.total = 0
+        for i in range(0, len(target_files)):
+            if target_files[i].size > self.status.total:
+                self.status.total = target_files[i].size
+                self.status.fp = target_files[i]
+
         while True:
             line = ""
             done_files = 0
@@ -168,6 +175,7 @@ class HexDiff(Module):
 
             last_line = line
             loop_count += 1
+            self.status.completed += self.block
 
     def init(self):
         # To mimic expected behavior, if all options are False, we show everything
diff --git a/src/binwalk/plugins/compressd.py b/src/binwalk/plugins/compressd.py
index f282524..3da5e83 100755
--- a/src/binwalk/plugins/compressd.py
+++ b/src/binwalk/plugins/compressd.py
@@ -1,39 +1,39 @@
-import binwalk.core.C
+#import binwalk.core.C
 import binwalk.core.plugin
-from binwalk.core.common import *
+#from binwalk.core.common import *
 
 class CompressdPlugin(binwalk.core.plugin.Plugin):
-    '''
-    Searches for and validates compress'd data.
-    '''
+#    '''
+#    Searches for and validates compress'd data.
+#    '''
 
     MODULES = ['Signature']
 
-    READ_SIZE = 64
+    #READ_SIZE = 64
 
-    COMPRESS42 = "compress42"
+    #COMPRESS42 = "compress42"
     #COMPRESS42_FUNCTIONS = [
     #    binwalk.core.C.Function(name="is_compressed", type=bool),
     #]
 
-    comp = None
+    #comp = None
 
-    def init(self):
+    #def init(self):
         #self.comp = binwalk.core.C.Library(self.COMPRESS42, self.COMPRESS42_FUNCTIONS)
         # This plugin is currently disabled due to the need to move away from supporting C
         # libraries and into a pure Python project, for cross-platform support and ease of
         # installation / package maintenance. A Python implementation will likely need to
         # be custom developed in the future, but for now, since this compression format is
         # not very common, especially in firmware, simply disable it.
-        self.comp = None
+        #self.comp = None
 
-    def scan(self, result):
-        if self.comp and result.file and result.description.lower().startswith("compress'd data"):
-            fd = self.module.config.open_file(result.file.name, offset=result.offset, length=self.READ_SIZE)
-            compressed_data = fd.read(self.READ_SIZE)
-            fd.close()
+    #def scan(self, result):
+    #    if self.comp and result.file and result.description.lower().startswith("compress'd data"):
+    #        fd = self.module.config.open_file(result.file.name, offset=result.offset, length=self.READ_SIZE)
+    #        compressed_data = fd.read(self.READ_SIZE)
+    #        fd.close()
 
-            if not self.comp.is_compressed(compressed_data, len(compressed_data)):
-                result.valid = False
+    #        if not self.comp.is_compressed(compressed_data, len(compressed_data)):
+    #            result.valid = False
 
 
diff --git a/src/binwalk/plugins/hilink.py b/src/binwalk/plugins/hilink.py
new file mode 100644
index 0000000..d193616
--- /dev/null
+++ b/src/binwalk/plugins/hilink.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+
+import struct
+import string
+import binwalk.core.plugin
+import binwalk.core.compat
+import binwalk.core.common
+try:
+    # Requires the pycrypto library
+    from Crypto.Cipher import DES
+except ImportError as e:
+    DES = None
+
+
+class HilinkDecryptor(binwalk.core.plugin.Plugin):
+    '''
+    Plugin to decrypt, validate, and extract Hilink encrypted firmware.
+    '''
+    MODULES = ["Signature"]
+
+    DES_KEY = "H@L9K*(3"
+    SIGNATURE_DESCRIPTION = "Encrypted Hilink uImage firmware".lower()
+
+    def init(self):
+        if DES is None:
+            self.enabled = False
+        else:
+            self.enabled = True
+
+        if self.enabled is True and self.module.extractor.enabled is True:
+            # Add an extraction rule for encrypted Hilink firmware signature results
+            self.module.extractor.add_rule(regex="^%s" % self.SIGNATURE_DESCRIPTION,
+                                           extension="enc",
+                                           cmd=self._decrypt_and_extract)
+
+
+    def _decrypt_and_extract(self, fname):
+        '''
+        This does the extraction (e.g., it decrypts the image and writes it to a new file on disk).
+        '''
+        with open(fname, "r") as fp_in:
+            encrypted_data = fp_in.read()
+
+            decrypted_data = self._hilink_decrypt(encrypted_data)
+
+            with open(binwalk.core.common.unique_file_name(fname[:-4], "dec"), "w") as fp_out:
+                fp_out.write(decrypted_data)
+
+    def _hilink_decrypt(self, encrypted_firmware):
+        '''
+        This does the actual decryption.
+        '''
+        cipher = DES.new(self.DES_KEY, DES.MODE_ECB)
+
+        p1 = encrypted_firmware[0:3]
+        p2 = encrypted_firmware[3:]
+        p2 += b"\x00" * (8 - (len(p2) % 8))
+
+        d1 = p1 + cipher.decrypt(p2)
+        d1 += b"\x00" * (8 - (len(d1) % 8))
+
+        return cipher.decrypt(d1)
+
+    def scan(self, result):
+        '''
+        Validate signature results.
+        '''
+        if self.enabled is True:
+            if result.valid is True:
+                if result.description.lower().startswith(self.SIGNATURE_DESCRIPTION) is True:
+                    # Read in the first 64 bytes of the suspected encrypted uImage header
+                    fd = self.module.config.open_file(result.file.name, offset=result.offset)
+                    encrypted_header_data = binwalk.core.compat.str2bytes(fd.read(64))
+                    fd.close()
+
+                    # Decrypt the header
+                    decrypted_header_data = self._hilink_decrypt(encrypted_header_data)
+
+                    # Pull out the image size and image name fields from the decrypted uImage header
+                    # and add them to the printed description.
+                    result.size = struct.unpack(b">L", decrypted_header_data[12:16])[0]
+                    result.description += ", size: %d" % (result.size)
+                    # NOTE: The description field should be 32 bytes? Hilink seems to use only 24 bytes for this field,
+                    #       even though the header size is still 64 bytes?
+                    result.description += ', image name: "%s"' % binwalk.core.compat.bytes2str(decrypted_header_data[32:56]).strip("\x00")
+
+                    # Do some basic validation on the decrypted size and image name fields
+                    if result.size > (result.file.size - result.offset):
+                        result.valid = False
+                    if not all(c in string.printable for c in result.description):
+                        result.valid = False
+
diff --git a/src/binwalk/plugins/lzmaextract.py b/src/binwalk/plugins/lzmaextract.py
index 4c6aafd..93f6240 100755
--- a/src/binwalk/plugins/lzmaextract.py
+++ b/src/binwalk/plugins/lzmaextract.py
@@ -9,7 +9,14 @@ class LZMAExtractPlugin(binwalk.core.plugin.Plugin):
 
     def init(self):
         try:
-            import lzma
+            # lzma package in Python 2.0 decompress() does not handle multiple
+            # compressed streams, only first stream is extracted.
+            # backports.lzma package could be used to keep consistent behaviour.
+            try:
+                import lzma
+            except ImportError:
+                from backports import lzma
+
             self.decompressor = lzma.decompress
 
             # If the extractor is enabled for the module we're currently loaded
@@ -19,6 +26,10 @@ class LZMAExtractPlugin(binwalk.core.plugin.Plugin):
                                                regex="^lzma compressed data",
                                                extension="7z",
                                                cmd=self.extractor)
+                self.module.extractor.add_rule(txtrule=None,
+                                               regex="^xz compressed data",
+                                               extension="xz",
+                                               cmd=self.extractor)
         except ImportError as e:
             pass
 
diff --git a/src/binwalk/plugins/lzmavalid.py b/src/binwalk/plugins/lzmavalid.py
index a343656..62e15b9 100755
--- a/src/binwalk/plugins/lzmavalid.py
+++ b/src/binwalk/plugins/lzmavalid.py
@@ -17,7 +17,10 @@ class LZMAPlugin(binwalk.core.plugin.Plugin):
 
     def init(self):
         try:
-            import lzma
+            try:
+                import lzma
+            except ImportError:
+                from backports import lzma
             self.decompressor = lzma.decompress
         except ImportError as e:
             self.decompressor = None
diff --git a/src/binwalk/plugins/ubivalid.py b/src/binwalk/plugins/ubivalid.py
new file mode 100755
index 0000000..186fb3c
--- /dev/null
+++ b/src/binwalk/plugins/ubivalid.py
@@ -0,0 +1,64 @@
+import struct
+import binascii
+import binwalk.core.plugin
+import binwalk.core.compat
+
+class UBIValidPlugin(binwalk.core.plugin.Plugin):
+    '''
+    Helps validate UBI erase count signature results.
+
+    Checks header CRC and calculates jump value
+    '''
+    MODULES = ['Signature']
+    current_file=None
+    last_ec_hdr_offset = None
+    peb_size = None
+
+    def _check_crc(self, ec_header):
+        # Get the header's reported CRC value
+        header_crc = struct.unpack(">I", ec_header[60:64])[0]
+
+        # Calculate the actual CRC
+        calculated_header_crc = ~binascii.crc32(ec_header[0:60]) & 0xffffffff
+
+        # Make sure they match
+        return header_crc == calculated_header_crc
+
+    def _process_result(self, result):
+        if self.current_file == result.file.name:
+            result.display=False
+        else:
+            # Reset everything in case new file is encountered
+            self.peb_size=None
+            self.last_ec_hdr_offset=None
+            self.peb_size=None
+
+            # Display result and trigger extraction
+            result.display=True
+
+        self.current_file = result.file.name
+
+        if not self.peb_size and self.last_ec_hdr_offset:
+            # Calculate PEB size by subtracting last EC block offset
+            self.peb_size = result.offset - self.last_ec_hdr_offset
+        else:
+            # First time plugin is called on file, save EC block offset
+            self.last_ec_hdr_offset = result.offset
+
+        if self.peb_size:
+            # If PEB size has been determined jump PEB size
+            result.jump = self.peb_size
+        else:
+            result.jump = 0
+
+    def scan(self, result):
+        if result.file and result.description.lower().startswith('ubi erase count header'):
+            # Seek to and read the suspected UBI erase count header
+            fd = self.module.config.open_file(result.file.name, offset=result.offset)
+
+            ec_header = binwalk.core.compat.str2bytes(fd.read(1024))
+            fd.close()
+
+            result.valid = self._check_crc(ec_header[0:64])
+            if result.valid:
+                self._process_result(result)
diff --git a/src/scripts/binwalk b/src/scripts/binwalk
index 1fe1a4a..e9ebdd7 100755
--- a/src/scripts/binwalk
+++ b/src/scripts/binwalk
@@ -2,7 +2,6 @@
 
 import os
 import sys
-from threading import Thread
 
 # If installed to a custom prefix directory, binwalk may not be in
 # the default module search path(s). Try to resolve the prefix module
@@ -15,7 +14,8 @@ for _module_path in [
     # from build dir: build/scripts-3.4/ -> build/lib/
     os.path.join(_parent_dir, "lib"),
     # installed in non-default path: bin/ -> lib/python3.4/site-packages/
-    os.path.join(_parent_dir, "lib",
+    os.path.join(_parent_dir,
+                 "lib",
                  "python%d.%d" % (sys.version_info[0], sys.version_info[1]),
                  "site-packages")
 ]:
@@ -24,51 +24,27 @@ for _module_path in [
 
 import binwalk
 import binwalk.modules
-from binwalk.core.compat import user_input
-
-def display_status(m):
-    # Display the current scan progress when the enter key is pressed.
-    while True:
-        try:
-            user_input()
-            percentage = ((float(m.status.completed) / float(m.status.total)) * 100)
-            sys.stderr.write("Progress: %.2f%% (%d / %d)\n\n" % (percentage,
-                                                                 m.status.completed,
-                                                                 m.status.total))
-        except KeyboardInterrupt as e:
-            raise e
-        except Exception:
-            pass
-
-def usage(modules):
-    sys.stderr.write(modules.help())
-    sys.exit(1)
 
 def main():
-    modules = binwalk.Modules()
-
-    # Start the display_status function as a daemon thread.
-    t = Thread(target=display_status, args=(modules,))
-    t.setDaemon(True)
-    t.start()
-
-    try:
-        if len(sys.argv) == 1:
-            usage(modules)
-        # If no explicit module was enabled in the command line arguments,
-        # run again with the default signature scan explicitly enabled.
-        elif not modules.execute():
-            # Make sure the Signature module is loaded before attempting 
-            # an implicit signature scan; else, the error message received
-            # by the end user is not very helpful.
-            if hasattr(binwalk.modules, "Signature"):
-                modules.execute(*sys.argv[1:], signature=True)
-            else:
-                sys.stderr.write("Error: Signature scans not supported; ")
-                sys.stderr.write("make sure you have python-lzma installed and try again.\n")
+    with binwalk.Modules() as modules:
+        try:
+            if len(sys.argv) == 1:
+                sys.stderr.write(modules.help())
                 sys.exit(1)
-    except binwalk.ModuleException as e:
-        sys.exit(1)
+            # If no explicit module was enabled in the command line arguments,
+            # run again with the default signature scan explicitly enabled.
+            elif not modules.execute():
+                # Make sure the Signature module is loaded before attempting 
+                # an implicit signature scan; else, the error message received
+                # by the end user is not very helpful.
+                if hasattr(binwalk.modules, "Signature"):
+                    modules.execute(*sys.argv[1:], signature=True)
+                else:
+                    sys.stderr.write("Error: Signature scans not supported; ")
+                    sys.stderr.write("make sure you have python-lzma installed and try again.\n")
+                    sys.exit(2)
+        except binwalk.ModuleException as e:
+            sys.exit(3)
 
 if __name__ == '__main__':
     try: