The hex editor was designed for reverse engineering 6502 machine code, initially for the Atari 8-bit computer systems, but expanded to Apple ][ and other 6502-based processors. There is also support for most other 8-bit processors, but as the author doesn’t have experience with other 8-bit processors they have not been extensively tested.
Opening a file to edit will present the main hex edit user interface that shows many different views of the data. Editing is supported in the hex view, character view, and the disassembly. There is also a bitmap view but is presently only for viewing, not editing.
The various views can be scrolled independently, but there is only one cursor location. Clicking on a location in one view will move the other views to show the same location. Selections are analogous; see below.
Binary data is parsed using code that started life as part of Omnivore but I spun it out because it’s useful as a standalone library: atrcopy. It knows a lot about Atari 8-bit files and disk images, knows some stuff about Apple ][ files and disk images, and knows almost nothing about anything else (yet). Atrcopy thinks of binary data in terms of segments, where a segment is simply a portion of the disk image.
The interesting feature of atrcopy is due to the use of numpy, and it’s this: segments can provide views of the same data in different orders. And, changing a byte in one segment also changes the value in other segments that contain that byte because there is only one copy of the data.
This turns out to be super useful. For instance, the first segment that appears in Omnivore’s list of segments will contain all the data from the disk image, in the order that the bytes appear in the file. This may or may not mean much depending on the format of the image. As an example, the catalog of an Apple DOS 3.3 disk is stored in sectors that increment downwards, so the catalog appears backwards(-ish. It’s complicated). So atrcopy goes further and breaks this disk image segment into smaller segments depending on the type of the file. In the catalog example above, it creates another segment that displays the catalog in the correct order. Changing a byte in either of those segments will change the value in the other, because it’s really the same value. It’s just two different looks into the same data.
Hex data can be edited by:
- clicking on a cell and changing the hex data
- selecting a region (or multiple regions; see Selections below) and using one of the operations in the Bytes Menu
- cutting and pasting hex data from elsewhere in the file
- cutting and pasting hex data from another file edited in a different tab or window
- pasting in data from an external application.
Character data can be edited by clicking on a character in the character map to set the cursor and then typing. Inverse text is supported for Atari modes. Also supported are all the selection and cut/paste methods as above.
Omnivore automatically highlights changes to each segment as compared to the state of the data when first loaded.
Optionally, you can specify a baseline difference file to compare to a different set of data, like a canonical or reference image. This is useful to compare changes to some known state over several Omnivore editing sessions, as Omnivore will remember the path to the reference image and will reload it when the disk image is edited in the future.
As data is changed, the changes as compared to the baseline will be displayed in red.
By default, baseline data difference highlighting is turned on, you can change this with the Show Baseline Differences menu item.
Left clicking on a byte in any of the data views (hex, char, disassembly, bitmap, etc.) and dragging the mouse with the button held down will start a new selection, finished by releasing the mouse button. The selection will be shown in all views of the data, scrolling each view independently if necessary.
The selection may be extended by shift-left-click, extending from either the beginning or the end of the selection as appropriate.
Multiple selections are supported by holding the Control key (Command on Mac) while clicking and dragging as above. Extending a selection when using multiple selection is not currently supported.
The data is searchable in multiple ways. Starting any search will display a search bar on the bottom of the main window. The basic search bar available with the `Find`_ menu item tries to be flexible and will show matches in any data view using an appropriate conversion for that view. For instance, the text string “00” in the search bar will find values of 0 in the hex view, strings of “00” in the character view, labels that have “00” anywhere in their text, “00” as an operand in the disassembly, or anything that has “00” in a comment.
The Find Next and `Find Prev`_ menu items (or keyboard shortcuts) will traverse the list of matches.
A more complicated search can be performed using the Find Using Expression menu item that support ranges of addresses or specific data values as search parameters using arbitrary boolean expressions.
Comments are a hugely important part of reverse engineering, because by definition the original source has been lost (or was never available). As you figure things out, it’s important to write things down. Omnivore supports adding a comment to any byte in the file, and it will appear in any segment that views that byte.
In the sidebar is a big ol’ list of comments, and selecting one of the comments will move the data views to display the byte that is referenced by that comment. Because there may be multiple views of the same byte, the comment shown in the comments list is the first segment that contains that comment.
Note that segments must have a defined origin for the segment to be considered as the primary for that comment.
Omnivore started out as a reverse engineering tool for Atari 8-bit computers, which use the 6502 processor. After developing for a while, I found a python disassembler called udis that supports multiple processors. Through its usage, Omnivore can disassemble (and assemble! See below) code for:
but the 6502 is the only processor have direct knowledge of and so the only one I’ve tested thoroughly. Bug reports (and patches!) for the other processors are welcome.
The disassembly can be edited using a simple mini-assembler; clicking on an opcode provides a text entry box to change the command. The mini-assembler supports all CPU types, not just 6502.
Labels can be set on an address, and the label will be reflected in the disassembly code. Also, memory mapping files can be supplied that automatically label operating system locations.
To support reverse engineering, regions can be marked as data, code, ANTIC display lists, and other types. Regions are highlighted in a different style and changes how the disassembly is displayed.
To help identify regions, static tracing can be used. Turning on static tracing assumes that every byte is data and shows temporary highlights over the entire segment. Starting a trace at an address causes Omnivore to follow the path of execution until it hits a return, break or bad instruction, marking every byte that it traverses as code. It will also follow both code paths at any branch. This is not an emulator, however, so it is not able to tell if there is any self-modifying code. Any blocks of code that aren’t reached will require additional traces. When tracing is finished, the results can be applied to the segment to mark as data or code.
A list of supported file types that can be created by Omnivore.
Choosing an item in this list will open up a new tab in the current window showing a new blank template of the selected file format.
Available templates include:
Open a file using a file select dialog box.
This submenu contain a list of the files most recently loaded or saved.
You can limit the number of items to remember in the General tab of the Preferences dialog.
Insert binary data at the cursor
The data from the loaded file will overwrite data starting at the cursor, so it’s not inserted in the text editor sense where space is created in the existing data.
Save the file, overwriting the previously saved version
Save the file to a new filename, leaving the originally loaded unchanged on disk.
Save the current view as an image (if possible with this editor)
Reverts the file to the last saved version on disk
This throws away any edits and is not undoable.
Choose options for printing
Preview the pages to be printed
Print the current view to a printer
Create an Atari 8-bit executable from a set of segments.
Opens a dialog window providing a list of segments to be added to the new executable and a starting address at which the Atari will begin executing the program on completion of the load.
Create an Atari 8-bit boot disk from a set of segments.
Opens a dialog window providing a list of segments to be added to the boot disk and a starting address at which the Atari will begin executing the program after reading all the sectors written to disk.
This creates a smaller-than-normal ATR image with a custom bootloader. Any sectors beyond the number fo sectors required to create the image are not included in the image.
Quit the program
Undo the last action
Actions that modify data are undoable; some that modify the metadata are but movement commands are not stored in the undo list, so for example cursor moves or changes to selection regions are not undoable.
Redo the last operation that was undone. See Undo.
The commands in this menu operate on the current selection to change the byte values to:
Restore the selection to the data contained in the Baseline Data file.
Cut and remove the current selection
Copy the current selection
Copy the disassembly text of the current selection to the clipboard.
Copy the text of the comments only, using the disassembly for line breaks. Any blank lines that appear in the disassembly are included in the copy.
Copy the current selection as a text string containing a string (with escaped characters where necessary) that reproduces the bytes in the selection.
Python note: both double quotes and single quotes are escaped as hex values so the resulting string is safe to use inside either of those characters as the string delimiter.
Copy the current selection as text where each byte is converted to the C source code representation.
Paste from the clipboard
Paste and repeat clipboard data until current selection is filled
Paste text as comment lines
Select the entire document
Clear selection
Inverts the selection; that is, select everything that is currently unselected and unselect those that were selected.
Marks the selected bytes as valid code to be disassembled using the current processor definition.
Marks the selected bytes as data, not to be disassembled but shown as byte values in the disassembly listing.
Marks the selected bytes as unitialized data, skipping over those bytes
and placing an origin
directive in the disassembly that points to the
next address that contains any other type of data.
Marks the selected bytes as an ANTIC display list, which will be shown as data in the disassembly listing. The data will be grouped by ANTIC command, where all bytes that belong to a command will be on a single line. This can result in a large number of data bytes appearing on one line when displaying a graphics 8 display list, for example. Exporting the disassembly will produce listings that break up these long lines into normal amounts, defaulting to 4 bytes on a line.
Marks the selected bytes as Jumpman drawing element descriptors. This is not used much for direct editing now that the Jumpman Level Editor is available.
Marks the selected bytes as a Jumpman harvest table. This is not used much for direct editing now that the Jumpman Level Editor is available.
Find bytes or characters in the raw data or in disassembly comments
Find bytes using logical and arithmetic comparisons
Find next match
Convert all matched locations to multi-selection
Open a window to change program settings and defaults.
These are built-in machine definitions that store preset values for Processor, Memory Map, Colors, Font, Character Display and Bitmap Display.
Currently defined machine types are:
The processor type defines the opcodes displayed in the disassembly window and those understood by the mini-assembler.
Currently supported processors are:
Add the syntax characteristics for a new assembler.
Modify the list of assemblers, rearranging the order or editing the characteristics of existing assemlers.
Mark an assembler that will become the default for future editing sessions.
In the disassembly window, target addresses for jumps, branches, loads,
stores, compares, etc. can be replaced by text labels that are defined for
the particular platform. For example, on the Atari 8-bit platform,
occurrences of $E459
will be replaced by SIOV
.
Some platforms like the Atari 8-bits have hardware locations that perform
different functions depending on whether it is read from or written to. The
disassembler can handle this, so reading the location $D000
will show
the label M0PF
(missile 0 to playfield collisions) while writing will
show HPOSP0
(set the horizontal position of player 0).
Currently supported platforms are:
This list sets the color encoding standard for all bitmapped graphics of the disk image. Currently supported are:
Changes the color palette to ANTIC Powerup Colors
Open a window to choose the color palette from the available colors of the ANTIC processor.
This submenu contains a list of all available font renderers. Selecting an item in this list will change how the text is displayed in the character map window.
This submenu contains a list of all available character mappings. Most platforms use a regular ASCII mapping, but some like the Atari 8-bits have different character mappings depending on the usage: the ANTIC mapping if looking at screen memory and the ATASCII mapping for normal usage.
Set the number of bytes per row of the character display.
This submenu contains a list of all available bitmap renderers. Selecting an item in this list will change the rendering of the graphics display.
Set the number of bytes per row of the bitmap display, which in turn sets the width in pixels.
Set the zoom factor of bitmap display. This is an integer value greater than zero that scales the display size of each pixel in the bitmap.
Toggle whether differences to the Baseline Data are highlighted or not.
Open a font selection window to choose the font and size used to display the values in the hex grid and the disassembly text.
Toggles whether or not the named extra pane is shown or hidden in the current window.
Changes the view in the entire window to a new editing task. The files in the current task are not lost, it’s just a way to edit different types of files while using the same top level window on the desktop.
The commands in this menu operate on the current selection to change the byte values to:
Set to zero
Set to $ff
Set to the NOP command of the current processor
Prompts the user and sets the data to the specified value
Or with $80
And with $7f
Invert every bit in each byte
Logical OR the selected data with the user specified value
Logical AND the selected data with the user specified value
Logical XOR the selected data with the user specified value
Shift bits left (multiply by 2), inserting zeros in the low bit
Shift bits right (divide by 2), inserting zeros in the high bit
Shift bits left where the high bit wraps around to become the new low bit
Shift bits right where the low bit wraps around to become the new high bit
Reverse the bit pattern of each byte; e.g. $c0 or 11000000 in binary becomes 00000011 in binary, $03 in hex
Adds the user specified value to the data, performing a logical AND with $ff if necessary to keep all values in the 8-bit range.
Subtracts the user specified value from the data (AND with $ff if necessary). Note the difference between this and Subtract From
Subtracts the data from the user specified value (AND with $ff if necessary). Note the difference between this and Subtract
Multiply the data from the user specified value (AND with $ff if necessary).
Divides the data by the user specified value by the data, ignoring the remainder. Note the difference between this and Divide From
Divides the data from the user specified value (that is to say: dividing the user specified value by the data), ignoring the remainder. Note the difference between this and Divide By
Starting with the user specified value at the first selected byte, loops over each byte in the selection and adds one to the value of the previous byte. At $ff, it wraps around to $00.
Starting with the user specified value at the first selected byte, loops over each byte in the selection and subtracts one from the value of the previous byte. At $00, it wraps around to $ff.
Reverses the order of bytes in the selection
This submenu contains a list of all segments in the disk image. Selecting one of these items will change the view to the selected segment.
Create a new segment in the segment list using the current selection.
All the bytes in the current selection will be shown in the new segment. If there are multiple selections, the new segment will show the bytes as contiguous but they will represent the original locations in the disk image.
Create a set of segments from the current selection, given the desired length of the resulting segments. If the number of bytes in the selection is not an exact multiple of the specified length, the last segment created will contain the remaining bytes. It will not be padded with zeros.
Create new segment by interleaving segments
Sets the origin of the current segment to an address, changing the starting point for all windows displaying this segment’s data.
Add a text comment to a byte location.
A comment is associated with a single byte, so although a range can be selected, the comment is applied to only the first byte in the range.
Bytes with comments will be highlighted in all displays.
Remove any comments that are in the selected range, or if no selection from the current cursor position.
Add a label to a byte location.
Like Add Comment, a label is associated with a single byte, so although a range can be selected, the comment is applied to only the first byte in the range.
Unlike comments, labels are not highlighted and are only shown in the disassembly window.
Remove the label at the current cursor position, or if there is a selection, all labels in the selected range.
Imports a text file that defines labels and addresses.
The text file should contain the address and the label on a single line. It’s pretty generous about parsing the input; there are two major types recognized.
The first is typical assembler format:
<label> = <address>
where the address can be in decimal without a prefix or in hex with the
$
or 0x
prefix.
and the second is a line with a hex value first and the label following.
Any line without an =
character is parsed this way, such that the first
thing that lookslike a hex or decimal number is used as the address, and
the first thing after that that looks like a valid text string is used as
the label. It can be comma separated, space separated, tab separated, etc;
anything but =
.
Exports a text file containing label/address pairs.
The text file will have a format that can be included in most assemblers:
<label> = $<hex address>
for example:
SIOV = $E459
SETVBV = $E45C
Exports a text file containing label/address pairs.
The text file will have a format that can be included in most assemblers:
<label> = $<hex address>
for example:
SIOV = $E459
SETVBV = $E45C
Start a trace at the cursor or at all instructions in the selected ranges
Copy the results of the trace to the current segment
Clear the current trace
Change the parser that generates segments from the disk image.
Run the current emulator using the current emulator.
The current emulator is shown in the Emulators sub-menu.
This submenu contains a list of the known emulators and a checkbox to indicate the current emulator.
Open up a window to define a reference to an external emulator
Omnivore can run the disk image in any emulator that is capable of being started from a command line. It spawns a separate process and feeds the emulator a path to the disk image along with any necessary command line arguments that you have to specify when setting up the emulator in this window.
Make changes to the current list of emulators.
This opens a window with a list of the currently defined emulators to make changes to existing emulators or add/delete any already defined.
The currently specified emulator in the Emulators list will be set as the system default and remembered for subsequent editing sessions.
Resize the document to add extra data at the end
Open a window to select a Baseline Data file.
The absolute path to the baseline file is stored, so if the baseline file is moved to a new location you will have to use this command again to point to the new location.
Move the cursor to the next block of data that is different than the Baseline Data file.
This will wrap around to the beginning of the segment if it doesn’t find a difference before the end.
Move the cursor to the previous block of data that is different than the Baseline Data file.Data
This will wrap around to the end of the segment if it doesn’t find a difference before the beginning.
Show a text representation of the differences
Move the cursor to an address. If the address is in this segment, moves there. If not, it searches through all the segments (in segment list order) to find one that does contain that address.
If that address is not valid for any segment, it will return an error message.
This menu contains a list of all documents open in the current session, across all windows and tabs. Selecting an item in this list will switch the view to that document, using the editor that was being used the last time it was edited.
This menu will contain a list of all editors that can modify this file. Selecting an item in this list will add a new view into this file using the selected editor. For instance, you can have a map editor and a hex editor on the same file; they point to the same data and modifying data in one window will show up in the other window.
Open a new, blank Omnivore window that can be used to load new files or to provide a second view on a currently loaded file.
Display window with version number and author info
Display the user guide in a new window
Open the log directory in the desktop file manager program.
The log directory will contain debug logs (if enabled) and other files, most of which are generally only useful for developers or to get more information to send to the developers in the event of a problem.
Displays a window that can be used to turn on debugging of particular parts of the program.
The log levels shown initially are the default log levels for each logger.
Using the Filter
text entry box, you can enter a string or a comma
separated list of strings that will be used to select which loggers get
switched to DEBUG mode. Everything else gets set to its default state,
usually either INFO or WARNING.
The string is not a regular expression, but will match partial strings.
Open the wxPython Widget Inspector
Raise an exception to test the error reporter