├── requirements.txt ├── hx ├── __init__.py └── dumper.py ├── README.md ├── LICENSE ├── setup.py ├── .gitignore └── bin └── hx /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | -------------------------------------------------------------------------------- /hx/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | __author__ = 'Wannes `wapiflapi` Rombouts' 4 | __email__ = 'wapiflapi@yahoo.fr' 5 | __version__ = '0.2.0' 6 | 7 | from hx.dumper import * 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # hx 3 | 4 | This is an alternative hexdump. 5 | 6 | ![screenshot](http://i.imgur.com/ehlyBQs.png) 7 | 8 | It's possible this **might not render well on some terminal configurations**. It 9 | uses black and bold black to render null bytes and other non printable 10 | characters; if those colors are actually black on a black background with your 11 | configuration then you won't see them. Change your color configuration to 12 | something more useful or submit a PR with a smarter solution, that will be 13 | appreciated. 14 | 15 | This is a screenshot under solarized via @BestPig. 16 | ![screenshot soloarized](http://i.imgur.com/EFugRZ5.png) 17 | 18 | Thanks @Raaka for the screenshot on default OSX bellow. 19 | 20 | ![screenshot OSX](http://i.imgur.com/cEncXRG.png) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 wapiflapi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | 6 | if sys.version_info < (3, 3, 0): 7 | from datetime import datetime 8 | sys.stdout.write("It's %d. This requires Python > 3.3.\n" 9 | % datetime.now().year) 10 | sys.exit(1) 11 | 12 | try: 13 | from setuptools import setup 14 | except ImportError: 15 | from distutils.core import setup 16 | 17 | readme = open('README.md').read() 18 | requirements = open('requirements.txt').read() 19 | 20 | setup( 21 | name='hx', 22 | description='A colored hexdump.', 23 | long_description=readme, 24 | version='0.2.0', 25 | author='Wannes `wapiflapi` Rombouts', 26 | author_email='wapiflapi@yahoo.fr', 27 | url='https://github.com/wapiflapi/hx', 28 | license="MIT", 29 | zip_safe=False, 30 | keywords='hx', 31 | classifiers=[ 32 | 'License :: OSI Approved :: MIT License', 33 | 'Natural Language :: English', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.4', 36 | ], 37 | 38 | scripts=['bin/hx'], 39 | packages=['hx'], 40 | package_dir={'hx': 'hx'}, 41 | 42 | install_requires=requirements, 43 | ) 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Linux ### 2 | *~ 3 | 4 | # KDE directory preferences 5 | .directory 6 | 7 | # Linux trash folder which might appear on any partition or disk 8 | .Trash-* 9 | 10 | 11 | ### Emacs ### 12 | # -*- mode: gitignore; -*- 13 | *~ 14 | \#*\# 15 | /.emacs.desktop 16 | /.emacs.desktop.lock 17 | *.elc 18 | auto-save-list 19 | tramp 20 | .\#* 21 | 22 | # Org-mode 23 | .org-id-locations 24 | *_archive 25 | 26 | # flymake-mode 27 | *_flymake.* 28 | 29 | # eshell files 30 | /eshell/history 31 | /eshell/lastdir 32 | 33 | # elpa packages 34 | /elpa/ 35 | 36 | # reftex files 37 | *.rel 38 | 39 | # AUCTeX auto folder 40 | /auto/ 41 | 42 | # cask packages 43 | .cask/ 44 | 45 | 46 | # Byte-compiled / optimized / DLL files 47 | __pycache__/ 48 | *.py[cod] 49 | 50 | # C extensions 51 | *.so 52 | 53 | # Distribution / packaging 54 | .Python 55 | env/ 56 | build/ 57 | develop-eggs/ 58 | dist/ 59 | downloads/ 60 | eggs/ 61 | .eggs/ 62 | lib/ 63 | lib64/ 64 | parts/ 65 | sdist/ 66 | var/ 67 | *.egg-info/ 68 | .installed.cfg 69 | *.egg 70 | 71 | # PyInstaller 72 | # Usually these files are written by a python script from a template 73 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 74 | *.manifest 75 | *.spec 76 | 77 | # Installer logs 78 | pip-log.txt 79 | pip-delete-this-directory.txt 80 | 81 | # Unit test / coverage reports 82 | htmlcov/ 83 | .tox/ 84 | .coverage 85 | .coverage.* 86 | .cache 87 | nosetests.xml 88 | coverage.xml 89 | *,cover 90 | 91 | # Translations 92 | *.mo 93 | *.pot 94 | 95 | # Django stuff: 96 | *.log 97 | 98 | # Sphinx documentation 99 | docs/_build/ 100 | 101 | # PyBuilder 102 | target/ 103 | -------------------------------------------------------------------------------- /bin/hx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import argparse 5 | 6 | import hx 7 | 8 | if __name__ == '__main__': 9 | 10 | parser = argparse.ArgumentParser() 11 | 12 | parser.add_argument("file", nargs="?", type=argparse.FileType("rb"), 13 | default=sys.stdin.buffer, 14 | help="file to read instead of stdin") 15 | 16 | parser.add_argument("-d", "--debug", action='store_true', 17 | help="show debuging info when ") 18 | parser.add_argument("-c", "--colors", action='store_true', 19 | help="force colors") 20 | parser.add_argument("--no-colors", action='store_false', dest='colors', 21 | help="strip colors") 22 | parser.add_argument("--show-sigpipe", action='store_true', dest='spipe', 23 | help="ignore broken pipes") 24 | parser.add_argument("--no-columns", action='store_false', dest='columns', 25 | help="don't hilight columns") 26 | 27 | parser.add_argument("--bpg", type=int, help="bytes per group") 28 | parser.add_argument("--gpl", type=int, help="groups per line") 29 | parser.add_argument("--fmt", type=str, help="group format string") 30 | 31 | kind = parser.add_mutually_exclusive_group() 32 | kind.add_argument("-b", "--bin", action=hx.BinDflts, 33 | help="show binary") 34 | kind.add_argument("-o", "--oct", action=hx.OctDflts, 35 | help="show octal") 36 | kind.add_argument("-x", "--hex", action=hx.HexDflts, 37 | help="show hexadecimal") 38 | 39 | parser.set_defaults(colors=sys.stdout.isatty()) 40 | 41 | parser.set_defaults( 42 | bpg=hx.HexDflts.bpg, gpl=hx.HexDflts.gpl, fmt=hx.HexDflts.fmt) 43 | 44 | args = parser.parse_args() 45 | 46 | hx.init_colorama(args.colors) 47 | 48 | dumper = hx.Dumper(args.bpg, args.gpl, args.fmt, args.columns) 49 | 50 | ignored = () if args.spipe or args.debug else BrokenPipeError 51 | catched = () if args.debug else BaseException 52 | 53 | try: 54 | dumper.dump(args.file) 55 | except ignored: 56 | pass 57 | except catched as e: 58 | print(e) 59 | sys.exit(1) 60 | -------------------------------------------------------------------------------- /hx/dumper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | import string 4 | import argparse 5 | import functools 6 | 7 | import colorama 8 | 9 | 10 | def init_colorama(colors=True): 11 | colorama.init(strip=None if colors else True, 12 | convert=None if colors else False) 13 | 14 | 15 | class BaseDflts(argparse.Action): 16 | 17 | def __init__(self, **kwargs): 18 | kwargs['nargs'] = 0 19 | super().__init__(**kwargs) 20 | 21 | def __call__(self, parser, namespace, values, option_string=None): 22 | setattr(namespace, 'bpg', self.bpg) 23 | setattr(namespace, 'gpl', self.gpl) 24 | setattr(namespace, 'fmt', self.fmt) 25 | 26 | 27 | class BinDflts(BaseDflts): 28 | bpg = 1 29 | gpl = 4 30 | fmt = "{0:08b}" 31 | 32 | 33 | class OctDflts(BaseDflts): 34 | bpg = 2 35 | gpl = 4 36 | fmt = "{0:03o}" 37 | 38 | 39 | class HexDflts(BaseDflts): 40 | bpg = 4 41 | gpl = 4 42 | fmt = "{0:02x}" 43 | 44 | 45 | class Dumper(): 46 | 47 | def __init__(self, bpg=HexDflts.bpg, gpl=HexDflts.gpl, 48 | fmt=HexDflts.fmt, col=True): 49 | self.bpg = bpg 50 | self.gpl = gpl 51 | self.fmt = fmt 52 | self.col = col 53 | self.hld = ' ' * len(fmt.format(0)) 54 | 55 | def dump(self, f, addr=0): 56 | 57 | nextbyte = self.get_byte(f) 58 | 59 | while nextbyte is not None: 60 | line = self.start_line(addr) 61 | for _ in range(self.gpl): 62 | line += self.start_group() 63 | for _ in range(self.bpg): 64 | addr += 1 65 | line += self.dump_byte(nextbyte) 66 | nextbyte = None if nextbyte is None else self.get_byte(f) 67 | line += self.end_group() 68 | line += self.end_line() 69 | 70 | line.append('\n') 71 | 72 | text = ''.join(line) 73 | # sys.stdout.write(text) 74 | sys.stdout.buffer.write(text.encode("utf8")) 75 | 76 | def get_byte(self, f): 77 | byte = f.read(1) 78 | if not len(byte): 79 | return None 80 | return struct.unpack('B', byte)[0] 81 | 82 | def dump_byte(self, byte): 83 | txt = self.hld if byte is None else self.fmt.format(byte) 84 | 85 | if self.col and not len(self.line) % 2: 86 | bck = colorama.Style.BRIGHT 87 | else: 88 | bck = colorama.Style.NORMAL 89 | 90 | self.group.append(byte) 91 | self.line.append(byte) 92 | 93 | return [bck, self.color(byte), txt, colorama.Style.RESET_ALL] 94 | 95 | def color(self, byte, text=True): 96 | if not byte: 97 | return colorama.Fore.BLACK 98 | if text and 0x20 < byte <= 0x7e: 99 | return colorama.Fore.MAGENTA 100 | return colorama.Fore.WHITE 101 | 102 | def printable(self, byte): 103 | if byte is None: 104 | return ' ' 105 | c = chr(byte) 106 | if c == '\n': 107 | return '↩' 108 | if c != ' ' and c.isspace(): 109 | return '␣' 110 | if c in string.printable: 111 | return c 112 | return '·' 113 | 114 | def start_group(self): 115 | self.group = [] 116 | return [] 117 | 118 | def end_group(self): 119 | return [' '] 120 | 121 | def start_line(self, addr): 122 | self.line = [] 123 | txt = '%x ' % addr 124 | nil = '0' * max(10 - len(txt), 0) 125 | return [colorama.Style.BRIGHT, colorama.Fore.BLACK, 126 | nil, colorama.Fore.WHITE, txt] 127 | 128 | def end_line(self): 129 | 130 | output = [colorama.Fore.WHITE, ' '] 131 | 132 | for i, byte in enumerate(self.line): 133 | 134 | if self.col and not i % 2: 135 | bck = colorama.Style.BRIGHT 136 | else: 137 | bck = colorama.Style.NORMAL 138 | 139 | output.extend([bck, self.color(byte, text=False), 140 | self.printable(byte), colorama.Style.RESET_ALL]) 141 | 142 | return output 143 | --------------------------------------------------------------------------------