From: merlyn@digibd.com (Merlyn LeRoy)
Subject: Game Genie for Genesis decoder
Organization: DigiBoard Incorporated, Eden Prairie, MN
Date: Thu, 07 Jan 1993 16:31:27 GMT


I've figured out the Genesis Game Genie 8-letter codes; all the codes are
for a 24-bit address and 16-bit data for the 68000.  The Game Genie
lies to the CPU about those addresses, substituting the 16-bit instruction
at that address.  For example, the codes for Sonic 1 to make each ring
worth 2 rings, SCRA-BJX0, decodes to 009c76: 5478, which is an addq #2,...
instruction (instead of adding 1 to the total, add 2).  Since the 68000
has a regular instruction set for addq 1..8, you can make rings worth
1 to 8 each.  The instruction 5478 disassembles into addq.w #2, <word addr>,
so if we instead change the instruction to bra.s +2 (branch over the
word address), rings are worth nothing.  bra.s +2 is 6002, so putting
009c76: 6002 into the decoder gives us ALRA-AA50.  Using this code makes
all loose rings worthless; only monitors give you rings, and picking up
rings after getting hit does nothing.

Unfortunately, the Game Genie seems to erase all of RAM when it resets;
if it didn't, it would be possible to use the GG to write a small program
in RAM and execute it to dump a game cartridge ROM.  Another possibility
is using the battery-backup RAM of a game to hold the program (assuming
such games use normal-looking RAM, and has enough to hold a program).
I'll play around with this possibility, but it doesn't look promising.

In any case, it's still possible to dump the ROM of a game veeeeery
slowly.  What you do is find a section of code that you can clobber
with 4 or 5 words of code, and insert code that does a btst #n, addr
followed by a beq.s -2 (if the bit tested is zero, branch to itself,
otherwise, continue).  This will hang if you find a 0 and continue if
you find a 1.  The btst instruction can have a 1 or 2 word address.
btst.b #3, 1111 would be 0838 0003 1111 and btst.b #4, 12345 would
be 0839 0004 0001 2345; beq.s -2 is 67fe.  Addresses greater than
7fff need the 2-word form.

In Sonic 1, you can test this with the following:
00039a: 0838 0000 0007 67fe
Which becomes:
HABT-ATE4 AABT-AAE6 A6BT-AAE8 92BT-AR7A

This tests the LSB of byte 7, the LS byte of the initial PC value on reset.
This has to be zero (even addresses only), so the game will hang after the
SEGA banner (39a is just after the banner call).  If you change the bit to
test to 1 by changing AABT-AAE6 to AEBT-AAE6, the program will NOT hang (but
it will crash later) and you know bit 1 of byte 7 is 1.  And so on...

You can also find out things faster if you can have the game help you.
For example, Sonic 1 has the exception vectors display data, so you
can load a word in memory into a register, optionally add 1 to it,
then jump to it.  If the value is odd, you will get an address exception
with the data you loaded displayed (if it isn't odd, change your add 1
instruction and try again).  Here's that code:

move	addr, a1   2279 0000 0004
addq.l	#1, a1     5289   ; if you don't get addr error at 3a4, change this
jmp	(a1)       4ed1   ;    to addq.l #1, a0  (5288)

Remember to unsubtract the 1 if you are adding 1 to it to make it odd.
Here it is to read the longword at 4, the initial PC value, which we know
must be even (so we'll add 1 to make it odd, and remember to subtract 1).

39a: 2279 0000 0004 5289 4ed1

Here's the full format of what the decoder prints:
SEBT-AEN4       = 00039a: 2279
AABT-AAE6 [  0] = 00039c: 0000
ATBT-AAE8 [  4] = 00039e: 0004
VEBT-BEZA +1    = 0003a0: 5289
4EBT-A6ZC       = 0003a2: 4ed1

The number in brackets are the decimal values of the bottom 8 bits; this is
shown if the 68000 instruction is a move immediate, or if all the high bits
are zero.  This is for GG codes like "starting number of lives"; you can
type the genie code with your own number in brackets to make it whatever
value you want.  Similarly, the "+1" indicates an addq instruction; you can
add a +1..+8 or -1..-8 to change it.  To not add 1 to a1, change VEBT-BEZA
to VABT-BEZA, which adds one to a0.

Anyway, if you plug in these codes, you'll get an address exception after
the Sega banner.  You should see, in a very unreadable italic font:

ADDRESS ERROR     $000003A4$00000207

Which means the value at 4: is 00000206

Too bad Sonic 2 doesn't have the exception code anymore.

Anyway, if anyone out there knows either 1) RAM that the GG doesn't touch, or
2) battery-backed-up RAM addresses for a Genesis game, please send email.

Here's the program...

/*
 * "Eet ees not *I* who am crazy, eet ees I who am MAD!" - R. Ho"ek
 *
 * lamp.c - a program to control Game Genie for Genesis
 *
 * Translate between Game Genie for Genesis 8-character codes and
 * the 24-bit address & 16-bit value for that address.
 * The format is this:
 *
 * ijklm nopIJ KLMNO PABCD EFGHd efgha bcQRS TUVWX   decodes to...
 *
 * ABCDEFGH IJKLMNOP QRSTUVWX: abcdefgh ijklmnop
 * 24-bit address              16-bit data
 * MSB                    LSB  MSB           LSB
 *
 * ..where each group of five letters is a Genie code character
 * (ABCDEFGHJKLMNPRSTVWXYZ0123456789, each representing 5 bits 00000-11111),
 * and each 8-character Genie code is the 24-bit address and 16-bit data.
 *
 * See the usage message when run with no arguments...
 * Merlyn LeRoy (Brian Westley), merlyn@digibd.com 1/4/93
 */

#include <stdio.h>

int argc, data;
char **argv, *chars = (char *)0;
char gg[] =
"AaBbCcDdEeFfGgHhJjKkLlMmNnPpRrSsTtVvWwXxYyZz0O1I223344556677889";

int getachar()
{
    if (argc > 1) {
        if (!chars) {
            data = 1;
            chars = argv[data];
        }
        if (!*chars) {
            data++;
            if (data >= argc)
                return (EOF);
            chars = argv[data];
            return (' ');
        }
        return (*chars++);
    }
    else {
        return (getchar());
    }
}

instr2gen(addr, instr)
unsigned long addr, instr;
{
    int i;
    unsigned long bits[8];

    bits[0] =  (instr >>  3) & 0x1f;
    bits[1] = ((instr <<  2) & 0x1c) | ((addr  >> 14) & 0x03);
    bits[2] =  (addr  >>  9) & 0x1f;
    bits[3] = ((addr  >>  4) & 0x10) | ((addr  >> 20) & 0x0f);
    bits[4] = ((addr  >> 15) & 0x1e) | ((instr >> 12) & 0x01);
    bits[5] = ((instr >>  7) & 0x1e) | ((instr >> 15) & 0x01);
    bits[6] = ((instr >> 10) & 0x18) | ((addr  >>  5) & 0x07);
    bits[7] =   addr         & 0x1f;

    for (i = 0; i < 8; i++) {
        putchar(gg[bits[i]<<1]);
        if (i == 3)
            putchar('-');
    }

    /* print lowest 8-bits as decimal value if it's a moveq instr or */
    /* if the top 8 bits are zero (probably a wordsize move immediate) */
    if ((instr & 0xf100L) == 0x7000L || (instr & 0xff00L) == 0x0000L) {
        printf(" [%3ld]", instr & 0xff);
    }
    else {
	/* print +1..8 or -1..8 for addq & subq instr */
        if ((instr & 0xf000L) == 0x5000L && (instr & 0x00c0L) != 0x00c0L) {
	    printf(" %c%ld   ", ((instr & 0x0100L) ? '-' : '+'),
		((instr & 0x0e00L) ? ((instr>>9) & 0x07) : 8));
        }
        else
            printf("      ");
    }
}

hex2gen(line)
unsigned char *line;
{
    unsigned char *old_line, *hp;
    static unsigned long addr;
    unsigned long instr;
    static char hex[] = "00112233445566778899AaBbCcDdEeFf";

    do {
        old_line = line;
        instr = 0;

        while (*line && (*line == ':' || *line == ' '))
            line++;
        if (!*line)
            return;

        while (*line && *line != ' ' && *line != ':') {
	    hp = hex;
	    while (*hp && *hp != *line)
		hp++;
	    if (*hp) {
		instr = 0x10L * instr + ((hp - hex)>>1);
	    }
            else {
                fprintf(stderr, "Bad hex code '%s'\n", old_line);
		instr = 0;
		while (*line && *line != ' ' && *line != ':')
		    line++;
		break;
            }
            line++;
        }
        if (*line == ':') {
	    addr = instr;
	    if (addr & 0xff000000L) {
		fprintf(stderr, "Warning: address too large\n");
	    }
            if (addr & 0x00000001L)
                fprintf(stderr, "Warning: odd address\n");
            addr -= 2;
            continue;
        }
        else {
	    if (instr & 0xffff0000L) {
		fprintf(stderr, "Warning: instruction too large\n");
		instr &= 0x0000ffffL;
	    }
            addr += 2;
        }

        while (*line && *line == ' ')
            line++;

        instr2gen(addr, instr);
        printf(" = %06lx: %04lx\n", addr & 0x00ffffffL, instr);
    } while (*line);
}

gen2hex(line)
unsigned char *line;
{
    int i;
    unsigned char *g, *old_line;
    unsigned long bits[8];
    unsigned long addr, instr;

    old_line = line;
    for (i = 0; i < 8; i++) {
        while (*line) {
            g = gg;
            while (*g && *g != *line)
                g++;
            if (*g == *line)
                break;
            line++;
        }
        if (*line)
            bits[i] = (g - gg)>>1;
        else {
            fprintf(stderr, "Bad Genie code '%s'\n", old_line);
            return;
        }
        line++;
    }

    addr =
        ((bits[3] & 0x0f) << 20) |
        ((bits[4] & 0x1e) << 15) |
        ((bits[1] & 0x03) << 14) |
        ((bits[2] & 0x1f) <<  9) |
        ((bits[3] & 0x10) <<  4) |
        ((bits[6] & 0x07) <<  5) |
        ((bits[7] & 0x1f) <<  0);
    instr =
        ((bits[5] & 0x01) << 15) |
        ((bits[6] & 0x18) << 10) |
        ((bits[4] & 0x01) << 12) |
        ((bits[5] & 0x1e) <<  7) |
        ((bits[0] & 0x1f) <<  3) |
        ((bits[1] & 0x1c) >>  2);

    while (*line && *line == ' ')
        line++;
    if (*line == '[') {
        line++;
        i = 0;
	while ('0' <= *line && *line <= '9') {
            i = i * 10 + *line - '0';
            line++;
        }
        if (*line == ']')
            line++;
        instr = (instr & 0xff00L) | (i & 0xff);
    }
    else if (*line == '+' || *line == '-') {
        if (*line == '-') {
            instr |= 0x0100L;
        }
        else
            instr &= ~0x0100L;
        line++;
        i = *line - '0';
        instr = (instr & 0xf1ffL) | ((i<<9) & 0x0e00L);
    }

    if (addr & 0x00000001L)
        fprintf(stderr, "Warning: odd address\n");
    instr2gen(addr, instr);
    printf(" = %06lx: %04lx\n", addr, instr);
}

main(ac, av)
int ac;
unsigned char **av;
{
    int ch, i, flag;
    unsigned char line[128];

    argc = ac;
    argv = av;

    if (argc == 1) {
puts("This program will translate between Game Genie for Genesis codes");
puts("and the 24-bit address and 16-bit data that the code represents.");
puts("Enter the Game Genie code(s) as usual, e.g. SCRA-BJX0 and you will");
puts("get the address and 16-bit data as 009c76: 5478");
puts("You may then disassemble the 16-bit data as a 68000 instruction.\n");
puts("This also works in reverse; you can enter the address & data and");
puts("get the 8-letter Genie code.  In addition, entering a number in");
puts("square brackets after a genie code will change the lower 8 bits");
puts("to match (for number of starting lives, for example).");
puts("Entering a number as +N or -N (N=1..8) after a genie code will");
puts("change the various increment/decrement instructions.\n");

    }

    do {
        i = 0;
        flag = 0;
        while ((ch=getachar()) != EOF && ch != '\n' && i < sizeof(line)-1) {
            if (ch == '-')
                flag++;
            line[i++] = ch;
        }
        line[i] = 0;

        if (i) {
        	if (flag)
                gen2hex(line);
            else
                hex2gen(line);
        }
    } while (ch != EOF);
}

Newsgroups: rec.games.video