icone facebook icone facebook



The principle

The idea came to me during one of my internet breaks during which I wander between stupid videos for fun and to destroy the remains of my brain. I came across a pretty popular video that showed a guy trying one of these training collars for dogs on his neck.

For those unfamiliar, these collars are able to detect (more or less accurately) barking and emit electric shocks which are harmless but unpleasant enough for the dog to eventually associate barking with pain.
Needless to say, the video made me laugh and quickly inspired me the idea.

In principle, the idea is almost the same: to associate an unpleasant event with an unpleasant sensation. Except that the event is unpleasant for those who suffer the punishment! To still be interesting apart from the purely masochistic side, I thought it would be fun if these events depended on a quality (agility, speed, memory ...) or the chance of the subject. The best choice to my liking was the video game console!

In practice, this gives video games capable of controling these collars to inflict high voltage shocks to players when they lose (or for example, when the opponent wins, there can be many possibilities !).

 

Why the Sega Genesis ?

At first I thought about doing the mod on the NES, because it's still relatively popular today, the game library is quite extensive, and the architecture wasn't too complex for me (although I'm known by some for hating the 6502).

I quickly recalled that the NES, in addition to having a crappy processor, was known to have games with an innumerable number of mappers. These chips are used to map the ROMs (memory chips in the cartridges) in banks in order to have "big" games. Since the flash cartridge I made a while back was made on a SLROM circuit, we could only have played the games released on the same circuit, which would have really limited our choices (in addition to having to flash the games with a programmer).

The fact that my flash cartridge was not recognized anymore definitely convinced me to change platform. The next "logic" choice for me was the Master System (you know, the one with Alex Kidd embedded ?).
The choice of games was roughly comparable to the NES, the hardware capacities were also comparable, I knew the processor (Z80) much better, but unfortunately, although the cartridges used two or three different types of mappers, these were integrated into ROM, making the task still quite complex and we would still have to re-flash the cartridge each time we wanted to play a different game

Should we return to Nintendo ? The SNES? No thanks, I want to have fun, not having to learn to code on a pseudo-16bit patchwork. This is the exact moment where I remembered that Realmyop gave me a Everdrive MD to write him some VJ software (which I still didn't finish), and that this tool was just laying in front of me.

It did not take me more time to take a decision: it will be the Genesis. All the key elements were present: The choice of games, the simplicity of dropping the ROMs on a SD card, and the availability of the hardware in case of an accident (you never know what can go wrong).

 

 

The hardware: joypads

Firstly I would like to reassure some. For those who would like to do this mod but who are too protective to touch their Genesis, fear not: No need to modify your console to discover the joys of high voltage, we will only be using the joypads! (Which will still function normally, by the way).

We want to command the collars on or off (shock or no shock). For this, we just need a digital output from the console. Knowing a bit about the Genesis already, I knew that this was available on the joypad ports. At first glance, without knowing anything about the console, one might say that the joypad ports are only inputs that correspond to each button. But how does it work with the 11 buttons and the 7 data pins ? (9 really, but 2 are for power).

The 7 pins of the ports are actually configurable as inputs or outputs. Typically, games configure them as six inputs and one output. There is an integrated circuit in the joypads that is wired so as to "listen" to this output, and which answers back the status of one out of two groups of buttons. When the game wishes to read the state of all buttons, it will first output a 0 to read the Top, Bottom, Left, Right, B and C buttons, then will set the output to 1 to get the Start and A buttons.
It seems not very logical or easy like that, but it was matter of cost reduction in electronics.
From now on, I will call this output "SELECT" (no, no relation with a button of the same name in other gaming systems).

Most games base their timing and reading of these buttons on the frequency of the picture rendering to the screen, at 50 or 60Hz. So we'll see pulses on SELECT 50 or 60 times per second. However, the polarity of the signal can vary, we're never sure if the game will put the signal high first, then low, or the opposite. There is no standart and I have seen both cases in commercial games (from the same developers!).

I then thought of using this output to convey additional information to joypads and make them a bit more intelligent so they are able to understand this new information. Initially I was thinking of using a conventional counter circuit, which would count the number of pulses received on SELECT and which would reset to zero when the signal was stable (to avoid counting the pulses 50 / 60 times per second).

Since I couldn't find a simple solution to decode the signal and ignore the signal's polarity, I opted for a Atmel ATtiny25 microcontroller. They are small, have 5 I/O pins, operate at 5V, have an internal oscillator, and are inexpensive. They offer everything we need: understanding a defined signal as an input and drive an output to enable or disable the collar. Everything can simply be modified in software. As I doubt some of you don't have AVR programmers, I can help you out on request by email .

Let's move on to the modification itself. It's likely that you have an official Sega joypad, if this is not the case, you must find which wire corresponds to the SELECT line yourself. In Sega joypads, it's simple, it is on pin 1 of the single integrated circuit (a 74HC157, quad 2-to-1 multiplexer).

We'll connect it to pin 2 of our ATtiny25. We will also give him power, by getting the 5V as shown in red, and ground as indicated in gray. Optionally, you can add a LED after a few hundred ohms resistor on pin 7 of the microcontroller, which may be useful to know if the signal is well received without having a collar to test. Finally, the pin 3 will serve as a control signal for the collar (named SIG).

All this can easily fit on a small piece of stripboard and placed in a hollow area of the joypad. A hole may be made near the cable to place the LED if you chose to useone. It's also a good idea to keep the microcontroller on a socket, the height is not a problem and it will be practical to remove it if improperly flashed.

You can also take advantage of this opportunity to clean the button contacts with alcohol, scrub the conductive membranes with a pencil, and put a few layers of paper behind the disk of the directional pad to harden the touch.

For connection to the collar, I used a 3-way male Molex connector (Power + signal), its look isn't great but it's very sturdy and simple to install. I cut 3mm from the edge of the plastic on the bottom of the joypad to place the connector, with the soldering pins bent outward to prevent movement (see picture).

The picture shows that the pinout I chose for the connector is as follows, from left to right: +5V, GND, SIG (output signal to the collar). Of course the order of the pins doesn't matter but you must remember it in order to not fry the collar.

 

The hardware: collars.

The collar I bought on eBay was sold at a surprisingly low price, especially when you know the price tag of the branded ones available in pet supplies shops. Numerous patents such as this one and that one clearly describe how these collars deliver electric shocks. A simple transformer and one or two transistors are used to convert low voltage pulses from the battery to high voltage, low current pulses.
These collars can modulate the output power by either spacing the pulses temporally, either by shorting the transformer's primary coil to reduce their duration and maximum value.

The seller was kind enough to offer the 6V battery (which isn't always included and not the most common) and a small neon bulb to test the collar's electrodes.

This kind of bulbs can still be found in some old appliances or low end devices as indicators of the presence of a relatively high voltage (hundreds of volts) without the use of resistors and diodes. A LED here would certainly not survive the high voltage pulses, although very short and of low intensity. In contrast, a conventional bulb would also be inappropriate because it would light up with a low voltage and so would be a poor indicator of the proper operation of the collar.

As it was impossible to find voltage and current values ​​on eBay and in the user manual ("13V", yeah right !), I had to make some basic measurments to at least know what to expect (if I would suffer greatly or a lot).

To get meaningful values, we must simulate the actual operating conditions by feeding the collar with the 5V that will be given by the Megadrive rather than the 6V from the battery, and also simulate the resistance of the skin with a conventional carbon resistor.

I got about 1Mohm per centimeter of skin, so I just took a 5.6Mohms resistor to place between the collar's electrodes, and measured the voltage across it with an oscilloscope. Know that the skin resistance varies greatly depending on its moisture. By wetting my finger and pressing the probe tips as much as possible, the value lowered to about 800kOhms.

The accuracy of the signal's trace was unfortunately limited because my probes were only capable of dividing by 10 (and not by 100). I didn't make a divider circuit with a follower just because of pure laziness... At least we now know that the positive voltage peak exceeds 200V (one can imagine the plot going up to about 400V), and that it lasts about 100 microseconds.

If we assume the worst value will be 500V, we will get a 500/5, 6.10e6 = 90μA current value, nothing really dangerous in fact (that doesn't mean it won't hurt!).

I also observed the signal on the base pin of the transistor that switches the battery voltage on the transformer's primary, just to see what it looked like. It turns out that the manufacturer of the collar chose 50μs pulses, 300μs apart by bursts of 50, bursts that are spaced 100 ms apart and repeated 30 times (for a total of approximately 3.5 seconds).

The collar has its own microcontroller but I won't describe its operation (bark detection, timeout, "motion" detection...) because we already know everything we need to know.

We'll just completely bypass the brain of the collar by cutting the connection between the microcontroller and the transistor. On the schematic on the left, you can see that we will only use a small part of the components of the collar, just what's used to provide the high voltage pulses.

This means that the microcontroller we will add in the joypads produce needs to produce a control signal similar to the original one. Nothing complicated to do with a few loops and a timer. This will even enable us to have zaps of different lengths !

To connect the joypads, I used an old USB cable with data lines (not only power). As we only need 3 wires, one wire won't be connected.

Connections to the 5V supply will be made on the two large welds from the spring contacts of the battery, while the SIG wire will go on the resistor just before the transistor. Note that the value of this resistance may vary from model to model. Given the short duration of the control pulses, it may be omitted to get voltage peaks a tad higher.

As it is likely that the cable will suffer some pulling during the torture session, it is better not to trust the welds and make a knot in the cable before it leaves the housing of the collar. That way, the plastic housing will hold the cable (common practice in low-end hardware where molden knots are too expensive).

The plug is made of the female complement of the connector previously installed on the joypad. No need to use a crimp pliers here, just solder the wires to the metal pins and insert them into the plastic.

For mechanical strength reasons as well, these connectors have a self-locking clip. I added a few inches of heat-shrink sleeve on the connector base, which I filled with hot glue to inflict the effort of pulling the plug to the plastic rather than the welds.

 

The collars connects to each joypad. I admit that the white connector is not the best, but nothing prevents you from painting it !

Also note the red LED on the top right.

 

 

 

 

 

The microcontroler's firmware

The microcontroller's code was written in C for AVR-GCC. Here are the available downloads:

The code source (v1). The complete AVRStudio project. The .hex binary (v1).

The code isn't that complicated at all. All we want to do is to detect a particular signal from the console, and in the case it's detected, provide a control signal to the collar.
To simplify the modification of the Genesis games, I decided to delegate the timing of the shocks to the microcontroller. The games will then have to simply send a signal to initiate a shock, and it will automatically stop X seconds later.

As written above, we want to detect a special signal that the game will generate and you want to ignore all other signals (in order to avoid false triggering, that wouldn't be fair).

We know that normally, the SELECT signal received by the joypad changes state 50 or 60 times per second. There is however a case I didn't speak of, which is when the game is compatible with 6-button joypads. I will not describe how they work here because it doesn't matter much for what we want to do, but be aware that these games can cause up to 8 state changes per frame !
So to prevent these special readings from trigger the collars, our code will have to ignore any signal having 8 or less edges. I chose to put the threshold at 10 edges.
To count those edges, I used the Pin Change interrupt of the ATtiny25. This interrupt is triggered when the state of the chosen pin changes. No need to know if the pin has changed from 1 to 0 or from 0 to 1, we only want to know when it changes. That way, the polarity of the signal won't disturb us.

It's also required that the counting of the edges are reset to zero after a period of inactivity on SELECT, so as not to increment the counter each frame. For this, we use the Timer0 Overflow interrupt. The Timer0 is a 8 bit counter which is incremented automatically at short, regular intervals. When its maximum value is reached (255), it will trigger the Overflow Interrupt and will put the edge counter to zero.
When an edge is detected by the Pin Change interrupti, we will reset the Timer0 counter to reset the timeout.
Care must be taken to choose the increment frequency of Timer0 so that the Overflow Interrupt is triggered at intervals shorter than 1/60 = 17ms (worst case when the console is in 60Hz mode), but longer than the pulse lengths on SELECT (otherwise the counter would never exceed 1).

We will set the Timer0's prescaler to 3, which according to the datasheet, indicates a division of the main frequency by 64. If we run our microcontroller at 8MHz, we will have our Timer0 counting at 8.10e6/64 = 125kHz. With this value we can know how long the Timer0 will take to reach its maximum value and trigger the overflow interruption: 1/125.10e3 * 256 = 2ms. This is well below 17ms and well beyond the duration between the edges that we will receive with a 7.6MHz Genesis: 7,6.10e6 / 4 cycles per instruction = 530ns (or 0.5 ms). That's good.

We will need two interrupt handlers, first for the Pin Change:

ISR(PCINT0_vect) {
edge++; // Increment the edge counter
TCNT0 = 0; // Reset Timer0
}

And a second for the Overflow Interrupt, which will also hold the code to generate the control signal for the collar. To have 2 shock durations, I chose to differentiate between two numbers of edges: between 10 and 15 edges for a short zap, and 16 or more for a long zap.

ISR(TIMER0_OVF_vect) {
	uint8_t pulse,zap;
	if (edge >= 16) {                        // If edges >= 16: big zap
      PORTB = _BV(LED);
      for (pulse=0;pulse<20;pulse++) {	     // 20 bursts of 10 spikes
      	 for (zap=0;zap<10;zap++) {
      	   PORTB = _BV(SIG) | _BV(LED);      // SIG at 1
      	   _delay_us(300);
      	   PORTB = _BV(LED);                 // SIG at 0
      	   _delay_ms(4);
        }
        _delay_ms(50);
      }
      PORTB = 0;
   } else if ((edge < 16) && (edge > 10)) { // If edges > 10 et < 16: short zap
      PORTB = _BV(LED);
      for (pulse=0;pulse<2;pulse++) {		 // 2 bursts of 10 spikes
        for (zap=0;zap<10;zap++) {
          PORTB = _BV(SIG) | _BV(LED);      // SIG at 1
          _delay_us(300);
          PORTB = _BV(LED);                 // SIG at 0
          _delay_ms(4);
        }
        _delay_ms(50);
      }
      PORTB = 0;
   }
   edge = 0;	// Reset edge counter
}

By putting the validation of the signal on the Timer0 overflow, we know implicitly that the response to the signal will be late 2ms after receiving the last front, which is completely negligible in our case. The remaining code is only the main() with the necessary initializations and code to flash the LED to indicate that everything's going well.
Refer to the bit definitions of the ATTiny25 datasheet for more details.

PORTB = 0b00000000;            // Be sure that outputs are low
DDRB = _BV(SIG) | _BV(LED);    // SIG and LED as outputs
TCCR0A = 0b00000000;			// Timer0 in normal mode
TCCR0B = 0b00000011; // Timer0 prescaler = /64
TIMSK = 0b00000010; // Overflow interrupt enabled GIMSK = 0b00100000; // Pin change interrupt enabled
PCMSK = _BV(SELECT); // Pin change interrupt on SELECT pin only _delay_ms(100); for (blink=0;blink<10;blink++) {
PORTB = _BV(LED); // Blink the LED 10 times
_delay_ms(10);
PORTB = 0;
_delay_ms(100);
} sei(); // Enable interrupts

 

The debug software

You'll probably agree that having to start a game and lose intentionally to test the proper functioning of the collar would be quite tedious, so why not create a small debug ROM from scratch. This is the first part in 68000 assembly.

Even in assembly the code is only 350 lines long, and it can test both kinds of shocks on each of the collars very easily with a small menu.

Operation is simple, the software displays a menu with 6 choices: Small shock on player 1, 2, both players, long shock on player 1, 2, both. The choice is naturally made with the up and down buttons, and validated with the A button
When a choice is made, a countdown starts from 3 to 0 before zapping the chosen player(s). It's visible in the source that the countdown is based on excatly one second (50-frame counter), if your Genesis is in PAL mode. If it's in NTSC, the counting will be slightly faster.

Nothing special in the code, the bare minimum to display text: cleaning RAM, VRAM, loading of the alphabet, the palette, a routine to write null-terminated strings, and magic routines that give orders to the freshly installed microcontrollers in the joypads.

Such routine looks like this (example for a short discharge for player 1):

zap1s:
     move.b #$40,$A10009     ; Configure SELECT of port 1 as an output
     move.l #6-1,d0          ; We want 6 transitions (12 edges)
.z1s:
     move.b #$40,$A10003     ; SELECT at 5V
     nop
     move.b #$00,$A10003     ; SELECT at 0V
     dbra   d0,.z1s          ; Loop back
     rts

For those who are used to assembly, comments should speak for themselves but here's the details anyways:

  • We put the hex value $40 (01000000 in binary, ie bit 6 = 1) in the register $A10009. Sega's documentation specifies that this register is used to configure the I/Os of the player1's joypad port. By putting $40, we tell the hardware that we want the pin corresponding to the bit 6 as an output (SELECT).
  • Then, as described in the section about the firmware, we want more than 10 transitions on SELECT but no more than 16, I chose 12. We put 6 in the register D0 (-1 because there's one iteration more with DBRA), to use it as a loop counter.
  • Next we place a label ("z1s") for our loop, we will put inside SELECT to 1 by placing $40 in register $A10003 (output status of the joypad port 1).
  • We wait a bit by doing nothing with the NOP instruction, so we're sure that the microcontroller detects the state change.
  • Then we set SELECT to 0 by placing 0 in the same register.
  • Thanks to the DBRA instruction, D0 is decremented and the branch to "Z1S" is taken if we have not reached 0 yet. No need for a second NOP instruction here because DBRA is already slow.
  • 6 times 1 and 0 gives 12 transitions on SELECT and the microcontroller will detect it.

For the second player, we will simply use the same code but this time with the $A1000B and $A10005 registers instead of $A10009 and $A10003.
For the longer shock, we'll need at least 16 transitions on SELECT, so we will load D0 with 8-1.

This is exactly the same routines that will be used to patch the gamse so they can control the collars.

The assembly source code for ASW (v1). The ROM in bin format (v1).

 

Patching the games

This is the part which is likely to roll back more than one, for it is we'll have to tackle the 68000 assembler we have not written ourselves.

The principle of patching here is to modify the game ROMs to add our collar-control code at specific locations in the game. We must therefore first find what code is executed when the event we want to hook occurs (dying, taking a hit...), insert a jump to an empty space of the ROM, replace that empty space with our code, and return to the game's code. We have to be careful of not disrupting the original code's behaviour with the execution of ours.
Note that it is impossible to insert code at any random place because it would shift all of the following addresses and the game code wouldn't mean anything anymore (the game will just crash and freeze).

As those who don't code will certainly not go through the pain of making patches for games, I'll first propose my ready-to-play and toroughly tested patches. I will not explain how to code in 68000 assembly as this would take -a lot- of time, but only how to patch Sonic, other games will be covered up more quickly. Those who are familiar with debuggers and emulators should quickly grasp the idea.

Here are the Ninja patches I made so far:

Of course if you modded your joypads and collars but you like none of these games, don't hesitate to write me and ask to make a patch for your favorite game, I'll see what I can do ;)

To apply those patches you'll need the original ROM (I'll let you find them on the net, won't distribute them as they're copyrighted), and the Ouinja utility for Windows that will apply the changes to the ROM. To avoid corruption, the patch won't work if the ROM you downloaded isn't exactly the same as the one I used (CRC check).

In this section, I use IDA to disassemble the games, the Fusion Genesis emulator to find values ​​in RAM, MESS for further investigation and debug, Tile Layer Pro to look for empty zones and finally ASW to assemble the code. We may use a variety of techniques to find and patch the game:

  • Most often I'm using the "cheat finder" in Fusion. This is a feature found among others in the Action Replay devices for finding the address of variables in RAM according to their value and their evolution during the game
  • GameGenie codes are also very useful for finding interesting spots. Such a code is in fact an address and the opcode of an instruction to replace in the game. GameGenie devices are actually real-time ROM patchers.
  • Another technique is to place watchpoints in large areas of RAM in MESS in order to refine little by little the address we wish to find.

Little reminder or summary to use MESS's debugger: To place a breakpoint just write "bpset address", to remove it, write "bpclear number". For watchpoints it's "wpset address, r / w size", and to remove it, "wpclear number".
For example, if we want to break on word (16bits) writes at $FF5A18, we will write "wpset ff5a18,w,2".

 

Patching Sonic 1

Sonic was one of the easiest games to patch. All the events I wanted to hook caused changes in values ​​that were displayed on the screen (number of rings and lives). The only small issue I had was the checksum verification done by the game at startup, displaying a red screen if the ROM was modified.Nothing too complicated to bypass thanks to MESS, however.

Small shock when rings lost (hit):

  • Cheat search in count mode with Fusion on the value of the rings, quickly gives a unique result at $FFFE21.
  • Byte watchpoint on $FFFE21 with MESS, skip initializations.
  • When the player takes a hit, MESS breaks on the move.w #0 at $9D72, the rings variable is actually a 16-bit word at $FFFE20.
  • Search for available space with Tile Layer, blank area at $71380.
  • Replace the move.w by a jmp to $71380 (6 bytes match, no need to move instructions except the move.w).
  • At $71380, we put the move.w we just erased back, we push D0 on the stack (we're using the D0 register to send the collar commands, remember the loop ?), we put our collar control code, and pop D0 back.
  • Back in the game's code with a jmp to $9D78.

We get a red screen at startup, so there's some checksum verification going on.

  • MESS shows that we're stuck in an infinite loop at $3D4.
  • Word watchpoint at $200 (the checksum routine recommended by Sega does the sum of words starting at $200 until the end of the ROM)
  • Breaks on the add.w (a0) a $32C, it's in a addition loop.A little further down the cmp.w compares the final result with the correct checksum and below there's a bne which means "branch on not equal".
  • We replace the bneby two nops (4 bytes) to ignore the difference (we could also skip the whole checksum routine to go quicker but... too lazy to find where it actually starts).The game now starts.

Big shock when losing a life:

  • Cheat search in count mode again with Fusion on the number of lives, gives quickly an unique result at $FFFE12.
  • Watchpoint on $FFFE12 with MESS, skip initializations.
  • When we die, MESS breaks on the subq.b at $138A0, the variable is a byte.
  • There's still some empty space at $ 71400, no need to look elsewhere.
  • The subq.b is only 4 bytes therefore we'll also replace the following addq.b by a jmp to $71400 and a nop (6 + 2 bytes) as padding.
  • At $71400, we place our deleted addq and subq, we push D0, put the code for a long shock, and pop D0
  • Back in the game's code with a jmp to $138A4.

 

Patching Battletoads

Battletoads was a bit more complicated because we wanted a small shock when we lost energy (which was displayed as a life bar with 6 squares), no numerical value so I had to open my eyes a bit more. I started by the easiest: hooking the loss of a life.

Big shock when losing a life:

  • Cheat search in count mode with Fusion on the number of lives in one player mode, a gives a single result at $FFE046.
  • Watchpoint on $FFE046 with MESS, the value is set at 5 when starting a game, which matches the starting number of lives.
  • When we die, MESS breaks on the subq.b at $4360, it's indexed with register A6. A6 is the index address for the structure of the player ($FFE000 when the player 1 dies, $FFE080 for the player 2). Multiplayer games typically have the same code for both players, but called with different index for each one.
  • Search for empty space with TLP, free zone at $7FE90.
  • Replaced the subq.b and the following bpl at $4378 by a jmp to $7FE90 and a nop (6 + 2 bytes).
  • At $7FE90, we must make the distinction between player 1 and player 2, so we compare A6 with $FFE000, if it's equal, we zap player 1, otherwise it's for player 2. No need to take care of checking other values as the game can only be played at 2.
  • At the end of our patched routine, we put back the subq which is used to decrement the lives (deleted to place our jmp), and the bpl back to the game at $4378. If the branch isn't taken, jmp to $4366.

After this patch was done, the game started right up so there wasn't any checksum verification (or we were very lucky!).

Now for the short shock when we lose energy (several hits).

  • The GameGenie invincibility code was made of 3 codes used to patch 3 words at $A3D8. The corresponding values were for a btst instruction used to jump over the hit code if the player was invincible (flashing sprite). Not really useful here because we want to detect only the hits which decrements the energy, not each and everyone of them.
  • Knowing where the data structures for the players were ($FFE000 for player 1), with the memory viewer MESS at the right spot I could see that the value at $FFE006 changed when I lost energy.
  • Watchpoint at $FFE006 in MESS, init at $17 when a game is started (23decimal), so we apparently have 4 life points per lifebar box (?).
  • Break on the move.w d1 at $3BE8 when the player loses energy, always with indexing with register A6.
  • There is still some room at $7FF20 for the patch code.
  • Replaced the move.w and the following move.b by a jmp to $7FF20 and a nop (6+2 bytes).
  • At $7FF20, push D0, put the move.w and move.b back, compare A6 with $FFE000 again to distinguish between player 1 and player 2, pop D0 and jmp back to $3BEE.
Patching Mega Bomberman (still translating)

Bomberman was a little more complicated because I couldn't search for values in RAM, only particular events: getting killed, catching a disease (those skull blocks), and blowing up our kangaroo. Hoping it would facilitate the task, I first searched for GameGenie codes for this game. There was some, and the first one was a "master code", it's a code you must put with the others you want to use or else the game won't work. This is typically a code which patches the checksum verification, so we will first deal with that.

  • The "master code" is CV7T-AN9T. Thanks to this page on Segakore, we can decrypt it to obtain the address and the replacement data. The code means: Replace the word at address $7BF0 by $6614.
  • If we look at the game code in IDA at that address, we fall right on the same checksum routine that we met in Sonic. At $7BF0, we find the beq instruction, which jumps to the start of the game only if the checksum is correct. The GameGenie code replaces the beq (opcode $6714) with $6614, which means bne instead. That's the opposite condition for the jump, so if the checksum's bad, the branch is taken.
  • For the sake of proper logic, I decided to replace the beq by an unconditionnal jump (bra). If we're unlucky, we can fall on the same checksum even after patching, here nothing bad will ever happen. So I replaced the $6714 at $7BF0 by $6014 (bra).

The game starts fine, the checksum will no longer bother us.
First step, find the address of the executed code when the player picks up a disease. No GameGenie code for that unfortunately.

  • Cheat search in state mode with Fusion: there are several results but less when playing with the second player. We see that two consecutive addresses are modified when dying: $FF9E97 for player 1 and $FF9E98 for player 2.
  • Back on player 1 and with a watchpoint on $FF9E97 in MESS. The value is initialized to zero early in the game.
  • When we catch a disease, MESS breaks on the move.b A0 at $ 6AB. Moreover we see that A0 is loaded just before with $FF9E97+D4. So D4 is our index for the player (which is zero for player 1, as expected). We also note that D4 is loaded before with the value at $FFA93F, so that's our player number variable.
  • Search for space with TLP, free zone at $FF930.
  • To avoid replacing two instructions, I decided instead to replace the jsr $6AD8 at $1CFA (just a little earlier in the same routine). Replaced it with a jmp to $FF930 and a nop (6+2 bytes).
  • At $FF930, we must still distinguish between player 1 and player 2. As D4 is not yet loaded with the player number, we need to read $FFA93F directly, value which is compared with 0 and 1 to see which player gets the zap (we need to check both vaues, as there can be up to 4 players !).
  • Just before returning we put back the jsr $1CFA and a return with a jmp to $6ADE.

Second step, giving a big shock when the player dies. Here again no luck, the GameGenie codes for invincibility were incorrect (at least those I found), they were patching graphics data... All was left was Fusion again.

  • Cheat search in state mode with Fusion between normal play and just when the victory sound is played, tries several different games and levels to finally get a difference at $FFA432 (set to zero when the player dies with player 1).
  • Watchpoint on $FFA432 with MESS, initialized to 1 early in the game. Obviously this is the flag that indicates whether the player is dead or not.
  • A few frames after the player explodes, MESS breaks on the clr (a4) at $317A. A4 is our index, at $FFA432 for player 1 and $FFA472 for player 2.
  • There's still some empty space after our previous patch, at $FF9C0.
  • To avoid crushing two instructions again, I decided not to replace the clr (a4), but the move.b at $3174 (which is 6 bytes) with a jmp to $FF9C0.
  • A $FF9C0, we must distinguish again between player 1 and player 2, so we compare A4 with $FFA432 and $FFA472 to know which player gets the big zap.
  • We place the move.b back and return to the game code with a jmp to $317A.


Liste
creative commons
date
CC:BY,NC,SA - Engine V2.0
21ms
3098 *1
symbol symbol symbol symbol symbol