Streets of Rage 1 Hacking

Discussion in 'Discussion and Q&A Archive' started by DanielHall, Aug 3, 2015.

  1. DanielHall

    DanielHall Well-Known Member Member

    Jan 18, 2010
    North Wales
    Okay, lessons learned. Back up my stuff.


    Basically, some of you may know that I have been working on a disassembly of the game 'Streets of Rage' for the past 6 months. Well today, my hard drive decided it didn't want anything else to do with it and decided to pack itself in. So while I'm foaming at the mouth, trying to salvage what I can to see if I haven't wasted 6 months of my life, I thought I might as well post what I've learned about Streets of Rage, while I can still remember some of the specifics.


    RAM Addresses:


    $FFB800 - Start of object RAM. Each entry is $80 bytes in size, and the space is $1000 bytes in size, which gives you $20 (32) objects to use at a time, excluding collectibles. During regular gameplay, $FFB800 is generally reserved for the first player, while $FFB880 is reserved for the second, meaning that the start of object RAM for everything else is $FFB900. The SSTs that I can remember are 00 (object ID, see below), 04 is the mappings offset, $10 is the X-axis coordinates (word) and $14 is the Y-axis coordinates (word). $18 is an artificial third axis which measures your height. Used when jumping. Setting this too low or high can cause the character to die, except for Round 8. $30 is the object's routine counter, $32 is the object's health, where the object is killed if it goes below 0 ($FFFF), and $34 is the object's attack power (i.e. how much damage it does to you).


    $FFC980 - Seperate object RAM space that only seems to store bats and knives.

    $FFCD00 - Seperate object RAM space that stores items you can pick up (presumably due to different SST handlers needed), with exception of what was listed above. $400 bytes in size, which means you can only have 8 collectible objects on the screen at the same time.

    $FFF00A - Updates the current track playing.

    $FFF400 - Normal palette buffer. DMAed to CRAM during VBlank, depending on whether $FFFA01 is set or not. $80 bytes in size.

    $FFF480 - Target palette RAM space. Used by fading routines to fade the palettes at $FFF400, until the data matches the target palette. $80 bytes in size.

    $FFFA01 - CRAM DMA flag. When set to 1, whatever's stored in the normal palette buffer will be DMAed to CRAM during the next Vint.

    $FFFF02 - Level flag.

    $FFFF1B - Player 1's continues.

    $FFFF1D - Player 2's continues.
    $FFFF20 - Player $1's lives.

    $FFFF21 - Player 1's police car amount.

    $FFFF23 - Player 2's lives.

    $FFFF24 - Player 2's police car amount.

    $FFFF46 - VDP Register 1 value, enables display.

    $FFFF48 - VDP Register 1 value, disables display.

    $FFFFC7 - Difficulty

    00 - Easy

    01 - Normal

    02 - Hard

    03 - Hardest (No mania options as rumoured ;V)


    Music Hacking:


    Whatever's written to $FFF00A will play. There is a subroutine that handles playing tracks when the IDs are stored to d7, located at $01069E.


    Music IDs:


    $00-$7F - Stops the music.

    $80 - /

    $81 - Round 1

    $82 - Game Over

    $83 - Title

    $84 - Round 3

    $85 - Round 5

    $86 - Round 7

    $87 - Boss

    $88 - Round 8

    $89 - Round 6

    $8A - Select

    $8B - Round 2

    $8C - Round 4

    $8D - Name Entry

    $8E - Round Clear

    $8F - Bad Ending

    $90 - Last Boss

    $91 - Good Ending

    $92-$9F - Blank

    ; Start of Sound Effects

    $A0 - SE 1

    $A1 - SE 2

    $A2 - SE 3

    $A3 - SE 4

    $A4 - SE 5

    $A5 - SE 6

    $A6 - SE 7

    $A7 - SE 8

    $A8 - SE 9

    $A9 - SE 10

    $AA - SE 11

    $AB - SE 12

    $AC - SE 13

    $AD - SE 14

    $AE - SE 15

    $AF - SE 16

    $B0 - SE 17

    $B1 - SE 18

    $B2 - SE 19

    $B3 - SE 20

    $B4 - SE 21

    $B5 - SE 22

    $B6 - SE 23

    $B7 - SE 24

    $B8 - SE 25

    $B9 - SE 26

    $BA - SE 27

    $BB - SE 28

    $BC - SE 29

    $BD - Pause Jingle (Not in sound test)

    $BE - SE 30

    $BF - SE 31

    $C0 - SE 32

    $C1 - SE 33

    $C2 - SE 34

    $C3 - SE 35

    $C4 - SE 36

    $C5 - SE 37

    $C6 - SE 38

    $C7 - SE 39

    $C8 - SE 40

    $C9 - SE 41

    $CA - SE 42

    $CB - SE 43

    $CC - SE 44

    $CD - SE 45

    $CE - SE 46

    $CF - 'GO' Buzzing (Not in sound test)

    ; Start of Voices

    $D0 - Adam's voice (Voice 1)

    $D1 - Unused, higher pitch variant of Adam's voice (Not in sound test).

    $D2 - Voice 3

    $D3 - Voice 4

    $D4 - Voice 5

    $D5 - Voice 6

    $D6 - Voice 7

    $D7 - Voice 8

    $D8 - Voice 9

    $D9 - Voice 10

    $DA - Boss defeated SE (Not in sound test)

    $DB - Axel's voice (Voice 2)

    $DC-$DF - Blank

    ; Special Flags

    $E0 - Fade out music.

    $E1 - Stop Music

    $E2 - Stop Sound Effects

    $E3 - Stop sound.

    $E4 - ''


    Palette Editing:


    Streets of Rage uses a different method compared to the usual 0BGR method. It is encoded so that the subroutine it is loaded to knows exactly what palette line to load it to, which entry in that palette line to load it to (to where it is then positioned appropriately in the palette buffer) and the length of the data string. Rather than the typical bitfield:


    0000 BBB0 GGG0 RRR0


    ...It uses:




    For instance, take the value $6EFE for instance:



    0110 1110   1111    1110


    The XXXX in this case is represented by the number 6. Palette entries are $0-$F, so this one is loaded to palette entry $6 in this case. Next, the first N is 0, while the second N is 1. Group them together, and you get 01. Now look at this chart:


    00 - Palette line 0

    01 - Palette line 1

    10 - Palette line 2

    11 - Palette line 3


    This shows which palette line the palette will end up in. Since this is 01, it will end up in the "1st" one. You can usually tell if an N bit is checked by it having an odd number in the number it's representing in hex. So for example, the second digit 'E' isn't odd, so the N bit isn't checked, while the third digit is, so the N bit is checked. You can't have odd numbers for palettes, so the game makes use of it before it is written to the palette buffer. In this case, T isn't checked, so the game will treat the following word as a palette, and will try to decode it. If it's the end of the palette data, set T to 1 (or put an odd number into the final digit). Afterwards, the original data is anded by $0EEE (White, as you can't get a higher value than that), and the value is positioned appropriately in the palette buffer. $6EFE anded by $0EEE is $0EEE, so white will be moved to palette line 1, entry 6 during the next CRAM DMA in VBlank (Setting $FFFA01 to 1 activates it, otherwise CRAM DMA is skipped).




    $010538 - Loads the target palette into a2, sets the CRAM DMA flag and then decodes the palettes into the buffer at $010548.

    $01053E - Loads the palette buffer into a2, sets the CRAM DMA flag and then decodes the palettes into the buffer at $010548.

    $010548 - Palettes are decoded here into the appropriate position relative to a2.




    The object list is found at $00B238 and each entry is a word in size. The entries go in the following order:


    Object Lists:


    01 - Main character

    02 -

    03 -

    04 -

    05 - Police car

    06 - Character select cursor controller

    07 - Main characters on the select screen

    08 - Knife

    09 - Bottle


    0B - Pipe

    0C - Pepper

    0D -

    0E -

    0F -

    10 -

    11 - Phone box

    12 -

    13 -

    14 -

    15 - Statue

    16 - Can

    17 -

    18 - Stacks of tyres

    19 - Barrel / Oil Drum

    1A -

    1B -

    1C - Luminous Stand

    1D - Roadblock

    1E -

    1F -

    20 - Galsia holding knife

    21 - Passive Galsia

    22 - Galsia

    23 - Galsia holding long weapon

    24 - Signal

    25 - Hakuyo

    26 - Nora

    27 - Jack

    28 - Jack's Axes/Fire sticks

    29 -

    2A - Group of 3 Hakuyos

    2B -

    2C -

    2D -

    2E -

    2F -

    30 - Abadede

    31 -

    32 -

    33 - Mr X's body sitting down

    34 - Mr X's legs sitting down

    35 -

    36 -

    37 - Mr X defeated?

    38 -

    39 -

    3A -

    3B -

    3C -

    3D -

    3E -

    3F - Money

    40 - Gold bars

    41 - Wooden crates

    42 - Industrial crushers

    43 -

    44 -

    45 - Dinner table

    46 -

    47 - Meat

    48 -

    49 -

    4A - Baseball bat

    4B - Apple

    4C - Extra life

    4D -

    4E - Round 3's trees

    4F - Mini police car

    50 -

    51 - Door and window (Round 7)

    52 -

    53 -

    54 - Garage door

    55 - Souther

    56 - Antonio

    57 - Bongo

    58 - Yasha & Onihime (separate objects, 2 are loaded at a time)

    ; There's more, but this is all I recognise, ATM.



    This stuff is pretty bare-bones, but it's all I can remember at the moment. Expect it to be updated should I ever get hold of my source again. I do have a lot of cool stuff that I would like to post (song locations, more on music hacking, screen modes, art locations, etc.), so fingers crossed that my HDD isn't too badly damaged.
    Last edited by a moderator: Aug 3, 2015
  2. redhotsonic

    redhotsonic Also known as RHS Member

    Aug 10, 2007
    Good work, shame the disassembly work has gone.  Tut tut, you should know by now to back-up regularly!  Were you working on a SoR hack at all?
  3. DanielHall

    DanielHall Well-Known Member Member

    Jan 18, 2010
    North Wales
    Cheers. That would've been something I would like to pursue, but I wanted to finish the whole thing before I got my hands properly dirty. That being said, I did release a mini hack of it in the mini projects section on Retro. Dunno if I released it here.
  4. StephenUK

    StephenUK Working on a Quackshot disassembly Member

    Aug 5, 2007
    Nice work, I really hope you recover the disassembly as it's still one of my favourite games. This reminds me, I should really finish off my RKA disassembly soon as the majority of it is converted to the relevant data types and it was just a matter of identifying the code and art. Need to get my hands on some utilities to finalise things though. Seeing as I'm on the point of utilities, does SoR use its own compression formats or does it rely on stock formats provided by a dev kit? I seem to remember reading somewhere that the dev kits came with stock routines for compression, although most developers made their own purpose built routines. This could have been misinformation though for all I know
  5. DanielHall

    DanielHall Well-Known Member Member

    Jan 18, 2010
    North Wales
    Stock compression formats, typical Nemesis, Kosinski and Enigma. There were a few other similar routines such as the LoadPLC and the plane mapping-writing subroutines.
    Last edited by a moderator: Sep 10, 2015
  6. DanielHall

    DanielHall Well-Known Member Member

    Jan 18, 2010
    North Wales
    Update with some stuff that I missed/need to cover:

    It's extremely unlikely that I'm going to be able to recover this. I had a look at the HDD and if you reflect it against the light, it has little ring-shaped bending around it, and what looks to be a dent. Whatever, it can be started again. Anyway, this might be my last update until I start working on this again, since I forgot to post these, but didn't have a computer to do it on.

    The following decompression routines (or just routines in general) use the same decompression routines as Sonic 1, word for word, which leads me to believe that they were supplied by a devkit.

    $000003BA - Screen mode pointers.
    $00008138 - Controller-reading subroutine.
    $00008192 - Nemesis decompression subroutine.
    $000082D6 - Enigma decompression subroutine.
    $00008454 - LoadPLC
    $00008484 - LoadPLC2/AddPLC routine from Sonic 1, except ClearPLC is built into it, rather than having it branch out.
    $000084BA - RunPLC_RAM (word for word, except with the RAM addresses being different).
    $000085A2 - Kosinski decompression subroutine.
    $00008642 - RunPLC_ROM (word for word, with the exception of the starting lea being pc-relative).
    $00008672 - ArtLoadCues table.
    $0000A63A - Nemesis pointer table. Before branching, d0 must contain 4 separate bytes: d0 - ZZYYXXWW, which is a pointer. The subroutine
                has a table that has a pre-written VRAM address and a Nemesis-compressed source. The subroutine reads the first byte,
                rotates it to the right (XX read next) until they've all been read. If it contains a 00, it is skipped. The byte is used as
                an ID for the table, where the VRAM address is loaded to the control port of the VDP and the Nemesis source is ran through
    $0000B748 - Similar to the above table, but uses Kosinski pointers, RAM addresses instead of VRAM and doesn't check the other byte entries.
    *NOTE* There is a table for Enigma similar to the three above, but I can't remember the address.
    $00010502 - WaitforVBlank/DelayProgram routine.
    $00010514 - Seperate WaitforVBlank/DelayProgram routine that IIRC, branches to a different mode during VBlank. Bit vague, sorry.
    $00010576 - Palette fade out routine.
    $000105CC - Palette fade in routine.
    $00019D1A - VBlank routine.
    $0001A166 - Start of HUGE DMA table (I think it's sprite table data).

    $FFDCD0 - PLC address queue.
    $FFF600 - Nemesis decompression buffer.
    $FFFB0C - Palette-fading variable.
    $FFFC04/08 - Controller variables.
    $FFFF00 - Screen mode.

    It's probably worth mentioning that there are a few variations of PlaneMaptoVRAM/ShowVDPGraphics, but I can't seem to remember their locations. I do remember that some were modified to load the amount of X-tiles and Y-tiles as a header from the mappings, though. Anyway, I think that's all I have now. If I do happen to remember anything, I will post it here.