Last active
August 29, 2015 13:56
-
-
Save facelessuser/8967138 to your computer and use it in GitHub Desktop.
hexviewer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# !/usr/bin/python | |
""" | |
Hex Viewer | |
Licensed under MIT | |
Copyright (c) 2011 Isaac Muse <[email protected]> | |
""" | |
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