FD1094 Game Conversion

Disclaimer

  • I will not give any assistance beyond what is presented here.
  • You'll have to write the tools and do the work yourself.
  • You assume any and all risks resulting from PCB modification.
  • If you want to buy decrypted ROM kits, check Sega Resurrection.

BEFORE You Start!

When converting an existing Sega game to a new game using decrypted ROMs, please check to see if the FD1094 or ROMs have been dumped. If you can dump your own ROMs, verify if that particular set is supported in MAME or not.

If you have an undumped game, by all means get in contact with myself, Guru, or the MAME™ team. It would be a shame to have any undumped Sega games converted to another type and be lost forever in the process.

Introduction

The FD1094 is a custom 68000 made by Hitachi for Sega. Many games use it, notably those on the System 16B, System 18, and System 24 hardware amongst others. It decrypts program code based on information stored in internal battery-backed SRAM. When the battery dies, the table contents are lost and the FD1094 will no longer decrypt correctly.

Using tables of data extracted from the FD1094 CPUs used in various games, it has been possible to create what is thought to be a close (or identical) representation of the SRAM contents. This is used in MAME to decrypt FD1094 games allowing them to be emulated. Even better for arcade game collectors and operators, boards with dead FD1094's can be restored to full working order using decrypted ROM images.

Due to the way the FD1094 works, creating a decrypted ROM image for a particular game is not easy. Program code and data are stored in a interleaved fashion that is game-dependant, complicating the process of decrypting just the encrypted code and leaving the data unmodified.

The program code and data can be combined, through either manual inspection (tracing of the game program), collecting reference data through an emulator to make a 'map' of which addresses are executed from and which are read from, or using an intelligent disassembly tool to automatically recurse through code paths to determine what is code and what is data.

One technique used in bootlegs of FD1089 games is to store the decrypted program code and data in two parts of a double-sized ROM. The high-order address line of the ROM is modified depending on when the 68000 accesses program code or data. The two do not need to be combined and it takes the guesswork out of determining which addresses are code or data.

This same technique can be applied to the FD1094, with one additional complication. Sections of the program code are encrypted differently through a state selection mechanism. It would not be (easily) possible to detect when states are selected during execution, nor have enough ROM storage for multiple copies of the decrypted program code in each of the states that are used. The solution is to manually decrypt and merge the sections of program code back together.

There are two situations that don't have to be bothered with; FD1094 games don't expect to use data at one address in two different states (this would expose information about how the encryption works) nor do they have a random dispersal of state switches - most all games go through several states during initialization and stick with one for the the main game loop.

Hardware details

When the 68000 access memory, it outputs the address space type on the function code pins, FC2-FC0. These pins are only valid when /AS is low. Here is a list of all possible settings from the 68000 user's manual:

FC2 FC1 FC0 Type
0 0 0 Undefined
0 0 1 User data
0 1 0 User program
0 1 1 Undefined
1 0 0 Undefined
1 0 1 Supervisor data
1 1 0 Supervisor program
1 1 1 CPU space

The only type of address space we want to differentiate from the others is a program space access. Both supervisor and user accesses will be treated the same, so we can ignore FC2. Beacuse the original data and decrypted code will be split into two parts, we will select the decrypted code half for a program space access and select the data half for everything else. This means the EPROM high-order address input will be set when FC1 is high, FC0 is low, and /AS is low.

We can detect this condition using a simple circuit consisting of a 74HC02 quad NOR gate:

The circuit is small enough that it could be assembled on a small piece of veroboard, or have direct connections between the IC pins themselves, with the IC and related wiring covered in heat-shrink tubing to prevent shorts. Here's another diagram showing the connections with more detail:

I would recommend adding a 0.1uF ceramic disc decoupling capacitor between +5V and GND on the 74HC02. It may be necessary to buffer /AS, FC1, and FC0 (using a 74LS244 for example) to avoid fanout limitations depending on how they are used elsewhere on the PCB.

Creating a decrypted ROM image

For this example, I will go through the steps used to decrypt the Sega System 18 game "Alien Storm". It stores it's program code in two 27C020 (256Kx8) EPROMs. Two 27C040's will be used to hold the original data and decrypted program code.

With the bytes interleaved from both ROMs, the vector table can be examined:

    Offset: +00      +04      +08      +0C
    000000  B297F23F 55334533 00000408 00000408
    000010  00000408 00000408 0000040C 0000040C
    000020  00000408 0000040C 0000040C 0000040C
    000030  0000040C 0000040C 0000040C 0000040C
    000040  0000040C 0000040C 0000040C 0000040C
    000050  0000040C 0000040C 0000040C 0000040C
    000060  0000040C 0000040C 0000040C 0000040C
    000070  00000404 0000040C 0000040C 0000040C
    :
    0003E0  0000040C 0000040C 0000040C 0000040C
    0003F0  FA60FA60 FA60FA60 FFFFFFFF 0000040C

The vector table is $400 bytes in size. Some games store junk data or checksum information at offsets $3F0 onwards, which is the case for Alien Storm.

The first two vectors for SP and PC are encrypted, because the 68000 counts them as being located in the program space, even though they are not opcodes. The remainder of the vector table is not encrypted. We can get the real SP and PC values by decrypting the ROM in state $0000, the reset state.

In addition, the vector table only references three unique ISRs, at $404, $408, and $40C. Now we know the following:

    SP:          $FFFFFF00 (decrypted stack top, offset $3F00 in work RAM)
    PC:          $00000400 (decrypted initial PC, program starts at $400)
    ISR #1:      $00000408 (handler for critical errors)
    ISR #2:      $0000040C (handlers for everything else)
    Level 4 ISR: $00000404 (handler for V-Blank interrupt)

Let's examine the disassembled code from the ROM, when decrypted using state $0000:

    BRA      *+$000E     [0000040E]            ; 000400 60 00 00 0C
    ST .B    D4                                ; 000404 50 C4
    MOVE.L   -(A1),D3                          ; 000406 26 21
    EOR.B    D3,D2                             ; 000408 B7 02
    SUBX.B   D0,D3                             ; 00040A 97 00
    RTE                                        ; 00040C 4E 73
    LEA      $FF00,A7                          ; 00040E 4F F8 FF 00
    MOVE     #$2700,SR                         ; 000412 46 FC 27 00
    CMPI.L   #$00DFFFFF,D0                     ; 000416 0C 80 00 DF FF FF
At offset $400 (initial PC) we have a branch to $40E where the rest of the startup code resumes. Notice that the two locations of the generic ISR's, $408 and $40C seem like unusual instructions. That's because they are actually encrypted in a different state, the interrupt state ($0200) so in state $0000 they are not decrypted correctly. Then we have a state change to $00DF. We decrypt the ROM using this state.
    MOVEQ    #$00,D0                           ; 00041C 70 00 
    MOVE.L   D0,D1                             ; 00041E 22 00 
    MOVE.L   D0,D2                             ; 000420 24 00 
    MOVE.L   D0,D3                             ; 000422 26 00 
    MOVE.L   D0,D4                             ; 000424 28 00 
    MOVE.L   D0,D5                             ; 000426 2A 00 
    MOVE.L   D0,D6                             ; 000428 2C 00 
    MOVE.L   D0,D7                             ; 00042A 2E 00 
    MOVEA.L  D0,A0                             ; 00042C 20 40 
    MOVEA.L  D0,A1                             ; 00042E 22 40 
    MOVEA.L  D0,A2                             ; 000430 24 40 
    MOVEA.L  D0,A3                             ; 000432 26 40 
    MOVEA.L  D0,A4                             ; 000434 28 40 
    MOVEA.L  D0,A5                             ; 000436 2A 40 
    MOVEA.L  D0,A6                             ; 000438 2C 40 
    CMPI.L   #$0019FFFF,D0                     ; 00043A 0C 80 00 19 FF FF 

Now state $0019 is selected. We decrypt the ROM again, using this state.

    LEA      $000027D6,A0                      ; 000440 41 F9 00 00 27 D6 
    MOVEA.L  #$00FE0020,A1                     ; 000446 22 7C 00 FE 00 20 
    MOVE.W   #$000F,D1                         ; 00044C 32 3C 00 0F 
    MOVE.B   (A0)+,D0                          ; 000450 10 18 
    MOVE.W   D0,(A1)+                          ; 000452 32 C0 
    DBF      D1,*-$0004     [00000450]         ; 000454 51 C9 FF FA 
    CMPI.L   #$0046FFFF,D0                     ; 000458 0C 80 00 46 FF FF 
The memory mapping registers are set up. As before, switch to state $0046.
    MOVE.B   #$00,$00A00007                    ; 00045E 13 FC 00 00 00 A0 00 07 
    MOVE.B   #$88,$00A0001F                    ; 000466 13 FC 00 88 00 A0 00 1F 
    MOVE.B   #$00,$00A0001D                    ; 00046E 13 FC 00 00 00 A0 00 1D 
    MOVE.B   #$C0,$00A00007                    ; 000476 13 FC 00 C0 00 A0 00 07 
    CMPI.L   #$0058FFFF,D0                     ; 00047E 0C 80 00 58 FF FF 
Here we have I/O chip setup, then a switch to state $0058.
    LEA      $C000,A0                          ; 000484 41 F8 C0 00 
    MOVE.W   #$0EFF,D0                         ; 000488 30 3C 0E FF 
    MOVE.L   $EC00,D1                          ; 00048C 22 38 EC 00 
    MOVE.L   $EC24,D4                          ; 000490 28 38 EC 24 
    CLR.L    (A0)+                             ; 000494 42 98 
    DBF      D0,*-$0002     [00000494]         ; 000496 51 C8 FF FC 
    MOVE.L   D1,$EC00                          ; 00049A 21 C1 EC 00 
    NOP                                        ; 00049E 4E 71 
    NOP                                        ; 0004A0 4E 71 
    NOP                                        ; 0004A2 4E 71 
    LEA      $FF00,A7                          ; 0004A4 4F F8 FF 00 
    MOVE     #$2700,SR                         ; 0004A8 46 FC 27 00 
    JSR      $00030EEE                         ; 0004AC 4E B9 00 03 0E EE 
The RAM is cleared, the stack top and operating mode is set again, and the rest of the game code continues. There are no more state-switching instructions from here onwards, so the remainder of the ROM contains code that will decrypt correctly using state $0058, except for the interrupt handlers.

Let's look at our interrupt handlers now. The three addresses of interest were $404, $408, $40C. Let's look at these when they are decrypted in state $0200:

    BRA      *+$2946     [00002D4A]            ; 000404 60 00 29 44 
    BRA      *+$0004     [0000040C]            ; 000408 60 00 00 02 
    RTE                                        ; 00040C 4E 73 
The ISR at $40C simply terminates in a RTE, and the ISR for $408 does the same. This leaves the level 4 interrupt (V-Blank) ISR which starts at address $2D4A. Here's data from that address, still using state $0200.
    NOP                                        ; 002D46 4E 71 
    NOP                                        ; 002D48 4E 71 
    TST.B    $ECB1                             ; 002D4A 4A 38 EC B1 
    BEQ.S    *+$10       [00002D5E]            ; 002D4E 67 0E 
    MOVEQ    #$18,D1                           ; 002D50 72 18 
    DBF      D1,*-$0000     [00002D52]         ; 002D52 51 C9 FF FE 
    MOVE.B   #$01,$EC2C                        ; 002D56 11 FC 00 01 EC 2C 
    RTE                                        ; 002D5C 4E 73 
    MOVEM.L  D0-D7/A0-A6,-(A7)                 ; 002D5E 48 E7 FF FE 
    MOVE.B   $00A00009,D0                      ; 002D62 10 39 00 A0 00 09
    :
    MOVE.L   (A0)+,(A1)+                       ; 003842 22 D8 
    LEA      ($0008,A5),A5                     ; 003844 4B ED 00 08 
    BRA.S    *-$4E       [000037FA]            ; 003848 60 B0 
    RTS                                        ; 00384A 4E 75 
    NOP                                        ; 00384C 4E 71 
    NOP                                        ; 00384E 4E 71 
    NOP                                        ; 003850 4E 71 
    NOP                                        ; 003852 4E 71 
    NOP                                        ; 003854 4E 71 
    NOP                                        ; 003856 4E 71 
    NOP                                        ; 003858 4E 71 
    NOP                                        ; 00385A 4E 71 
    NOP                                        ; 00385C 4E 71 
    NOP                                        ; 00385E 4E 71 
    NOP                                        ; 003860 4E 71 
One common feature in most FD1094 games is that the interrupt service routine and all related subroutines are grouped together, and padded with many NOPs. This is helpful to look for when trying to identifying the end of the interrupt related code.

I typically scan through the main ISR looking for the highest and lowest addresses referenced by branches and jumps to get an idea of how much code is involved. This part always requires some manual tracing of the code to figure out just how big the interrupt code section is.

Now that the various sections of code in their respective states have been determined, these pieces can be joined together to form a single, unified ROM image containing decrypted program code in each state that is used.

System 18 modifications

To replace the two 27C020's with 27C040's, we need to free up pin 31 which is the A18 input. Sockets A5 and A6 have this pin connected to pin 32 (+5V) by a thin trace. This should be cut (remember to check continuity with a multimeter) and pin 31 of both sockets should be tied together, then connected to the output of the 74HC02 circuit described earlier.

Results

I've implemented this method for my Alien Storm PCB, and it works perfectly. I used a breadboard for prototyping the circuit and then constructed the finalized, more compact assembly once it was determined that the game worked as expected.

Other Platforms

This technique will work for any FD1094 based game that stores program code in ROMs. System 24 disk-based games do not apply.


Return