Skip to content

Instantly share code, notes, and snippets.

@facelessuser
Last active August 29, 2015 13:56
Show Gist options
  • Save facelessuser/8967138 to your computer and use it in GitHub Desktop.
Save facelessuser/8967138 to your computer and use it in GitHub Desktop.
hexviewer.py
# !/usr/bin/python
"""
Hex Viewer
Licensed under MIT
Copyright (c) 2011 Isaac Muse <isaacmuse@gmail.com>
"""
import struct
from os.path import getsize, abspath, normpath
from glob import glob
import sys
import traceback
from time import time
PY3 = sys.version_info >= (3, 0)
DEFAULT_BIT_GROUP = 16
DEFAULT_BYTES_WIDE = 24
VALID_BITS = [8, 16, 32, 64, 128]
VALID_BYTES = [8, 10, 16, 24, 32, 48, 64, 128, 256, 512]
BITS_PER_BYTE = 8
__app__ = "hexviewer"
__version__ = "1.0.1"
class HexViewer(object):
thread = None
def __init__(self, silent=False):
"""
Setup
"""
self.silent = silent
def file_init(self, file_name, bit_group, bytes_width):
"""
Initialize file info and desired formatting of data.
"""
# File info
self.file_name = file_name
self.file_size = getsize(file_name)
self.read_count = 0
# Set hex format
self.set_format(bit_group, bytes_width)
# Prepare translation tables
if PY3:
self.translate_table = str.maketrans("".join([chr(c) for c in range(0, 256)]), "".join(["."] * 32 + [chr(c) for c in range(32, 127)] + ["."] * 129))
else:
self.translate_table = ("." * 32) + "".join(chr(c) for c in range(32, 127)) + ("." * 129)
self.def_struct = struct.Struct("=" + ("B" * self.bytes_wide))
self.def_template = (("%02x" * self.group_size) + " ") * int(self.bytes_wide / self.group_size)
def set_format(self, bit_group, byte_width):
"""
Setup the hex output format.
"""
self.bits = bit_group if bit_group in VALID_BITS else DEFAULT_BIT_GROUP
self.bytes = byte_width if byte_width in VALID_BYTES else DEFAULT_BYTES_WIDE
# Set grouping
self.group_size = int(self.bits / BITS_PER_BYTE)
# Set bytes per line
self.bytes_wide = self.bytes
# Check if grouping and bytes per line do not align
# Round to nearest bytes
offset = self.bytes_wide % self.group_size
if offset == self.bytes_wide:
self.bytes_wide = int(self.bits / BITS_PER_BYTE)
elif offset != 0:
self.bytes_wide -= offset
def iterfile(self, maxblocksize=4096):
"""
Chunk file
"""
with open(self.file_name, "rb") as f:
# Ensure read block is a multiple of groupsize
bytes_wide = self.bytes_wide
blocksize = maxblocksize - (maxblocksize % bytes_wide)
start = 0
byte_array = f.read(blocksize)
while byte_array:
outbytes = byte_array[start:start + bytes_wide]
while outbytes:
yield outbytes
start += bytes_wide
outbytes = byte_array[start:start + bytes_wide]
start = 0
byte_array = f.read(blocksize)
def to_hex(self):
"""
Parse file and convert to hex.
"""
self.abort = False
with open(self.file_name + ".hex", "w") as f:
line = 0
read_count = 0
if not self.silent:
last_time = time()
for byte_array in self.iterfile():
l_buffer = []
read_count += self.bytes_wide
self.read_count = read_count if read_count < self.file_size else self.file_size
# Add line number
l_buffer.append("%08x: " % (line * self.bytes_wide))
if len(byte_array) == self.bytes_wide:
# Complete line
# Convert to decimal value
values = self.def_struct.unpack(byte_array)
# Add hex value
l_buffer.append(self.def_template % values)
else:
# Incomplete line
# Convert to decimal value
values = struct.unpack("=" + ("B" * len(byte_array)), byte_array)
# Add hex value
remain_group = int(len(byte_array) / self.group_size)
remain_extra = len(byte_array) % self.group_size
l_buffer.append(((("%02x" * self.group_size) + " ") * (remain_group) + ("%02x" * remain_extra)) % values)
# Calculate extra space
delta = self.bytes_wide - len(byte_array)
group_space = int(delta / self.group_size)
extra_space = (1 if delta % self.group_size else 0)
l_buffer.append(" " * (group_space + extra_space + delta * 2))
# Append printable chars
if PY3:
l_buffer.append(" :" + "".join([chr(self.translate_table[b]) for b in byte_array]))
else:
l_buffer.append(" :" + byte_array.translate(self.translate_table))
# Write to file
f.write("".join(l_buffer) + '\n')
line += 1
# Log status
if not self.silent:
if time() - last_time > 0.5:
self.status()
last_time = time()
# Update status for final time
if not self.silent:
self.status()
def status(self):
"""
Display conversion status.
"""
ratio = float(self.read_count) / float(self.file_size)
percent = int(ratio * 10)
leftover = 10 - percent
message = " [" + "-" * percent + ">" + "-" * leftover + ("] %3d%%" % int(ratio * 100)) + " converted to hex\r"
sys.stdout.write(message)
sys.stdout.flush()
def convert(self, file_name, bit_group=DEFAULT_BIT_GROUP, bytes_width=DEFAULT_BYTES_WIDE):
"""
Convert file to hex with the specified format.
"""
self.file_init(file_name, bit_group, bytes_width)
if not self.silent:
print("Converting %s..." % self.file_name)
# Read bin
self.to_hex()
if not self.silent:
print('\n')
def get_files(file_patterns):
"""
Find and return files matching the given patterns.
"""
files = []
all_files = []
if len(file_patterns):
for pattern in file_patterns:
files += glob(pattern)
for f in files:
all_files.append(abspath(normpath(f)))
return all_files
def main():
"""
Main
"""
import argparse
status = 0
# Parse arguments
parser = argparse.ArgumentParser(prog=__app__, description='Convert file to hex.')
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
parser.add_argument('--debug', '-d', action='store_true', default=False, help=argparse.SUPPRESS)
parser.add_argument('--bits', '-b', nargs=1, default=False, help="Bits per group")
parser.add_argument('--bytes', '-B', nargs=1, default=False, help="Bytes per line")
parser.add_argument('--silent', '-s', action='store_true', default=False, help="No output.")
parser.add_argument('files', nargs='+', default=[], help='file(s) or file pattern(s) to convert')
args = parser.parse_args()
hexviewer = HexViewer(silent=args.silent)
files = get_files(args.files)
total = len(files)
converted = 0
for f in files:
try:
hexviewer.convert(f, int(args.bits), int(args.bytes))
converted += 1
except:
print(traceback.format_exc())
status = 1
if not args.silent:
print("ERROR: Could not convert %s!\n" % f)
if not args.silent:
print("%d/%d files converted!" % (converted, total))
return status
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment