Skip to content
Apple IIgs reverse engineer tools
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
pascal
.gitignore
2mg.c
65816.h
LICENSE
Makefile
README.md
addresses.h
disasm.c
disasm.h
handle.h
map.c
map.h
omf.c
parser.c
parser.h
prodos16.h
prodos8.h
prodos_types.h
regs.c
scan.c
scan.h
smartport.h
tools.h

README.md

What is this?

This is a set of command-line tools designed specifically to reverse engineer Apple IIgs software. It is comprised of 3 separate tools; 2mg, omf, and regs.

2mg

2mg extracts .2mg and .po prodos disk images. You can also just list the contents of the disk image with the -l or --list command line argument. Otherwise, it will create a folder with the name of the disk and extract all the files into that folder.

Listing out the files will also give you the metadata associated with each file. In particular, it will tell you the type and auxiliary type for the files.

omf

omf is a rather complicated tool which is designed to extract relocatable segments from OMF files. Apple IIgs executables (.s16 files) and system tools (ex. SYSTEM/TOOLS/TOOL025) are in OMF format.

You first run this tool and pass it an OMF file and it will generate a .map file. This map file is a simple text file that you may edit. Each line is in the format:

segment:memory location

The segmentis the segment number from the OMF file, and memory location is where in memory to relocate that segment.

omf does its best to automatically pack all the relocatable segments into the smallest memory possible, starting at $2/0000. You can change the starting memory address with the -o or --org argument. If you wish to manually specify where each segment should go in memory, feel free to edit the .map file however you wish.

The next step is to run omf again, this time specifying the map file with -m or --map. This will apply the .map to the OMF and output each segment along with a corresponding .map file. omf will throw an error if the segments cannot be mapped as the .map file dictates (for example, you modified the map file so that the segments accidentally overlap).

omf will modify each segment, applying the proper relocations before outputting the segment. If you wish to change where a segment is in memory, you should modify the original .map file and re-run omf. The resulting output files will be hardcoded for those specific memory locations.

The segment outputs will be named segX and segX.map, where X is the segment number, in hex. If you wish to change the prefix from seg use the -p or --prefix argument.

At this point, you can use the resulting segment files and map files as input to the regs tool.

regs

regsis my 65816 tracing disassembler. It can be used in conjunction with the .map files created by omf or on its own. Since it is a tracing disassembler, it will start disassembly at a given location, and keep disassembling, following all possible paths. Everything not disassembled will be assumed to be data and shown in a hex view.

regs by itself

You can call regs and simply pass it a binary file and it will attempt to disassemble it. You can specify where in memory it should load the file before disassembly with the -o or --org argument. You can also control whether or not the disassembler is in emulation mode or 16-bit mode with various command-line arguments. Emulation mode is also useful for disassembling 8-bit Apple II software. Use -e to start in emulation mode (default native mode), -m to start with an 8-bit accumulator (default 16-bit), and -x to start with 8-bit index registers (default 16-bit).

regs with .map files

regs with .map files is where the disassembler really shines. You can call regs and pass it a .map file generated by omf and have real control over the disassembly. The .map file is designed to be edited by hand. The format is as follows:

gMAP "seg1"
sORG $30000
$30000:
$30053:mx
$31066:e
$35440:d <myTable>

The first line specifies the segment file that this map applies to. The filename in the quotes should be relative to the current directory.

The next line specifies where the segment belongs in memory. Do not edit this if the segment was created by omf, since it has also been hardcoded in the binary.

The next lines are a list of entry points to begin disassembly at. If, when analyzing the disassembly you find a switch case encoded as an indirect jump, you can take that list of jumps and add them to the map file and re-run regs to disassemble the previously un-disassembled data. As you work through a disassembly, you may end up with a map file with hundreds of entry points, that's normal.

The flags after the colon are optional, and specify whether emulation mode should be enabled, or 16-bit or 8-bit accumulator and index registers should be used. It defaults to native mode with 16-bit registers.

After the flags, you may optionally give the address a symbol name. Whenever this memory location is referenced in the code, the symbol name will appear as a comment.

You can also use bank-separators if you wish. $3/1066 is the same as $31066.

The d flag is unique in that it identifies the address as a data location and not an entry point. This is used to give variables symbol names.

Pascal folder

You'll notice a pascal folder in this repository. These are the original GS/OS pascal header files. This is to make it easier for you to look up the arguments and structures of various tool calls you'll come across when disassembling.

Examples

The flexibility of these tools makes their use a little complicated. So here are some examples of how to go about disassembling various things.

Disassembling an S16

I'll be using the S16 from Dream Zone as an example.

Generate a basic map of your S16:

$ omf dream.s16

This will create a file called dream.s16.map, which we could edit if we choose. We'll leave it as it is. Extract the segments of the OMF with:

$ omf --map=dream.s16.map dream.s16 --prefix=dream

This will create files dream1to dream5 as well as dream1.map to dream5.map.

The program's entry point is always the beginning of the first segment, so we'll start there.

$ regs dream1.map > dream1.s

This will disassemble the entry point. We can then modify the map to further refine the disassembly if we wish.

Disassembling a Tool

This works the same as disassembling an S16, but with an important difference.

We'll start the same, generating a map and extracting it.

$ omf TOOL025
$ omf --map=TOOL025.map TOOL025

Now, we'll remove all disassembly instructions from the map. You'll see why in a second. So we edit the map file to look like the following:

gMAP "seg1"
sORG $20000

That's it.. no disassembly instructions. Now we run the disassembler:

$ regs seg1.map > seg1.s

This will just give us a hex dump of the segment. That's actually what we want. All tools start with a tool table. The first dword specifies the number of tools in this toolset. The next dwords all contain addresses (minus 1) of the various tool entry points.

Let's say I want to disassemble NoteOn. We check the pascal folder and discover that it's tool $0b inside the $19 toolset. Which is the TOOL025 file we're working on (the tool numbers in the filenames are in decimal). So we calculate the offset to that entry point.

$0b * 4 + $20000 = $2:002c

If we look at the hex dump at that location we'll discover the entry point of NoteOn: $2:02dd. Well that's minus one, so we add the real entry point to the .map file:

gMAP "seg1"
sORG $20000
$2/02de:

And rerun the disassembler.

$ regs seg1.map > seg1.s

We have just disassembled the NoteOn function.

Disassembling a Specific Tool Call in ROM

Let's say I want to disassemble WriteRamBlock. We discover it's in the sound toolset $08. If you search, you'll discover that there isn't a TOOL008 anywhere, so we'll have to pull it from ROM. I'll be using an older 128k ROM just because it's convenient.

First thing I do, is actually hand make a rom.map file for the ROM.

gMAP "APPLE2GS.ROM"
sORG $fe0000
$fe/0000:

and disassemble it.

$ regs rom.map > rom.s

This is actually the bootstrap that initializes the $e1/0000 tool call entrypoint. I notice it copies over a block of memory from $fe/0051 into $e1/0000. So we add $fe/0051 to the disassembly list of the map file, and disassemble it again.

Following along with the disassembly, we discover that there's a toolset list starting at $fe/012f. It starts with a dword with the number of toolsets in the ROM, followed by a list of offsets to the various toolsets. We want toolset 8 for the sound toolset.

$8 * 4 + $fe012f = $fe014f

Look up the dword in that location and I find that the toolset is located at $ff/3e00. If you then jump to that location, you'll find this is in the exact same format as a tool on disk. It starts with a tool table. WriteRamBlock is tool 9.

$9 * 4 + $ff3e00 = $ff3e24

At that location, we discover the offset to the tool entry point is $ff/41a4 so we'll add $ff/41a5to the map file and rerun the disassembly.

Boom, we have just disassembled a specific tool call from ram.

Disassembling a simple ProDOS executable

ProDOS binaries aren't relocatable and don't have anything inside them that specifies where in RAM they should be loaded. However, the filesystem itself does have that information.

Using 2mg with the -l or --list argument will give a list of the files along with metadata associated with the files. Let's use BASIC.SYSTEM as an example.

You'll see that BASIC.SYSTEM has a type of $ff and auxtype of $2000, and 2mg identifies it as a "sys/ProDOS System File". This is indeed a simple executable.

The aux type specifies where in RAM to load this executable, in this case, it's $2000.

It is also important to note that these executables should start with 8-bit registers.

So we can use all of that information to disassemble this file.

$ regs --org=0x2000 -m -x BASIC.SYSTEM > basic.s

This tells regs to start with 8-bit accumulator and indices, and load the file starting at $2000 before disassembling it.

You can’t perform that action at this time.