don't click here

ASM Spinball disassembly

Discussion in 'Engineering & Reverse Engineering' started by Andlabs, Oct 1, 2010.

  1. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    Level Tile Format solved

    I have been able to document the tiling mechanism for levels and additional data that supplements the tile data, or is miscellaneous level info.

    The following pointer tables exist:

    [0x0BFBE0 & 0x0BFBF0] Foreground (FG) and Background (BG) Tilesets (SSC Compressed 16-bit XBGR data)
    [0x0BFC00 & 0x0BFC10] FG and BG "Brushes" - These are large tiles made up of 4x4 standard 8px x 8px VDP tiles.
    [0x0BFC20 & 0x0BFC30] FG and BG Layouts - These are grids of tile indices that reference the Brushes defined earlier, with additional flags such as flipping the brushes on the X/Y axis.

    For the tile Brushes and Layouts, they follow the same bit masking as a VDP tile, which includes X/Y flipping, priority flagging, and palette swapping. This documentation on Sega Retro was invaluable for figuring this out, in particular around the 10-bit mask for indexing tiles. https://segaretro.org/Sega_Mega_Drive/Planes#Format

    [0x0BFC40] Level Collision Layouts - This part will be worked on next, as this ties in closely with the physics code. But on the surface these appear to use a similar approach
    [0x0BFC50] Level Palette set - The 4 palette lines to load for this level. All levels will load the player palette onto Line 3.
    [0x0BFC70] Level Dimensions - a pair of unsigned shorts that specify how large each level is, in terms of pixels
    [0x0BFC80] Camera Start Position - The X/Y coordinates that specify the start position for the top-left coordinate of the camera, when the level starts.
    [0x0BFC90] Music ID - The GEMS audio bank ID to play when the level starts
    [0x0BFC98] Timer for Game Over - there is a table of timer values for a timer that is set when the player has 0 lives remaining. It is longer on levels that are farther along for some reason.

    Using this information, I have been able to use the Spintool to export background and foreground tile maps for all levels (see attached). I have neglected to support chroma keying/alpha channels in the tool, so composite layouts will have to come later lol.

    (Showdown appears to have some kind of offset hacked into code, which is why it appears misaligned)

    spinball_level_00_BG.gif spinball_level_00_FG.gif spinball_level_01_BG.gif spinball_level_01_FG.gif spinball_level_02_BG.gif spinball_level_02_FG.gif spinball_level_03_BG.gif spinball_level_03_FG.gif spinball_level_OPTIONS.gif
    (The renders are 100% scale, so open them in a new tab to zoom in and such)

    EDIT:
    Once the layers are being rendered together, this will trivial to do. I still have work to do on decoding the format used for adding dynamic objects like animated scenery and enemies - but I have a lot of threads that have led me to the data that I need to eventually flesh out the content for the entire levels.
     
    Last edited: Dec 27, 2024
    • Like Like x 11
    • Informative Informative x 3
    • List
  2. Bobblen

    Bobblen

    Member
    452
    232
    43
    Looks awesome, well done. Alas, I don't see any out of bounds areas on first glance, always unlikely for a game like this I suppose!
     
  3. Does Showdown have any BG scrolling data? Curious since it seems to be the only stage that has a true background layer in the vein of the other Genesis Sonic games…
     
  4. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    There are a couple of code hacks that are unique to the final level. The following is figured out using code snippets from my own Ghidra decompilation:

    Firstly there is an initial camera offset tweak. Likely because the starting flippers are located much higher vs the death zones vs other levels. The code hack confuses me a little since there is level data that can specify a starting camera offset already... yet it is not utilised to offset the camera than more than something like 20px...
    Code (Text):
    1. // Located at 0x000D42A6
    2.   if (s_current_level == LEVEL_SHOWDOWN) {
    3.     camera_start_y = 0x168;
    4.   }
    5.   else {
    6.     camera_start_y = 0xe8;
    7.   }
    8.  

    The main bit that you're likely interested in is the following code:
    Code (Text):
    1. // This code is branched to from 0x000D4F82. This function is located at 0x000D4EFE
    2. short Showdown_BG_Tile_Layer_Parallax(void)
    3. {
    4.   short new_y_offset;
    5.  
    6.   if (s_current_level_camera_y < 1440) {
    7.     new_y_offset = CalculateParallaxOffsetForPos(s_current_level_camera_y, 29, 45);
    8.   }
    9.   else {
    10.     new_y_offset = s_current_level_camera_y + -512;
    11.   }
    12.   return new_y_offset;
    13. }
    14.  
    EDIT: Fixed the function name since it had a nonsensical name which I changed mid-way through this post!

    This will apply parallax scrolling when the camera is above a certain position (1440), otherwise it will keep the BG tile plane at a fixed 512px offset. If you measure my BG tile layer image export, the space below the visible tiles (ignoring the arbitrary data mess at the bottom-left) is exactly 512. So this checks out.

    The final bolt in this machine is the actual parallax calculation. It uses a fixed x position, but that hard value is passed as a parameter, which would seem to imply that it can implement X scrolling parallax if we wanted it to.
    Code (Text):
    1. // Located at 0x000D67DA
    2. uint CalculateParallaxOffsetForPos(short y_pos,short x_pos,short tile_grid_width)
    3. {
    4.   return ((int)y_pos * (int)x_pos) % (int)tile_grid_width << 16 |
    5.          ((int)y_pos * (int)x_pos) / (int)tile_grid_width & 0xffffU;
    6. }
    7.  

    The disassembly is much simpler, if you find that easier to follow:
    Code (Text):
    1.  
    2. 000d67da 30 2f 00 06      move.w                              (y_pos,SP),D0w
    3. 000d67de c1 ef 00 0a      muls.w                              (x_pos,SP),D0
    4. 000d67e2 81 ef 00 0e      divs.w                              (tile_grid_width,SP),D0
    5. 000d67e6 4e 75            rts
    6.  
     
    Last edited: Dec 29, 2024
    • Informative Informative x 2
    • List
  5. Not me over here just waiting for the eventual Casino Night table :eng99:
     
  6. Bobblen

    Bobblen

    Member
    452
    232
    43
    or the ultimate monstrosity, a level with no pinball sections and only platforming!
     
    • Like Like x 4
    • Agree Agree x 1
    • List
  7. Bobblen

    Bobblen

    Member
    452
    232
    43
    Shamelessly compiling SpinTool to get a sneaky peak (in full knowledge that it's unfinished and with zero expectations), those map layouts are really starting to come together nicely!
     
  8. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    Thank you. I appreciate you taking a look (and I'm happy that you were able to get up and running OK too).

    Right now I do have a significant amount of the level data in my sights, including definitions for placing all of the objects. Flippers and Rings are done using bespoke code, so were easier to extract (and I could solve some bits using other means that aren't by reading the real data).

    I still need to decipher the following types of data:
    • Animation data - Whilst I have located and understand some basics around the animations, I have not yet been able to grasp the relationship between the "asset indices" supplied in a timeline vs the actual sprite ROM locations. (The player sprite locations that I've showed off in the Spintool are partly based on collated research and making a big assumption that all of the uncompressed sprite data is sequential).
      The object definitions I have located do contain ptrs to what I presume to be animations, and likely use these indices in the animations to locate the actual sprites at some point.

    • Level Collision - The visual tiles and collision are separate things. I am somewhat sure I've located where this collision data is, but I am not having a good time trying to understand the collision algorithms. I'm pretty sure I've found what looks like lookup tables for sine/cosine functions though. I believe I know where the data for this is, but I don't currently understand it. I would hazard a guess that each collision tile supplies an angle or vector of some kind.

    • Main Menu, Cutscene and Bonus stage asset formats - So far I know for sure that these large sprites are built using a similar method to those sprites used in-game. However I've been unable to deserialise any of the data. I have a few locations in the ROM where I've found what appears to be the tile map data, but the actual tileset format has eluded me thus far. I'm sure I read somewhere that there is likely 2 different compression formats being used in Spinball - but I've not been able to prove or disprove this.
      I've tried to prod these areas of memory using SSC Decompression, but no such luck thus far. The amount of data that it claims to decompress is miniscule, and it also doesn't account for the actual sprite data being used around here, this is just the tile map data. The tile maps do look very similar in the ROM to actual level tile maps.

      upload_2025-1-9_22-53-19.png upload_2025-1-9_22-53-43.png
    It was suggested to me that the tools that may have been used in Spinball may be similar/the same as the ones used here: https://github.com/OldSkoolCode/Sega-Monster-Hunter/tree/master/SEGA . It's an avenue worth exploring for anyone with better skill in navigating these SOA toolchains.

    Additionally, the Bonus Stages will also need research into the object formats. I believe I've found a few bits around them, but nothing conclusive (the bonus stage is also a gigantic mega-function so the decompiler absolutely hates it when I try to research it :D).

    On the subject of the Bonus Stages though, I did manage to solve a reliable way of loading into them directly from the main menu. This culminated in what I've been internally coining "Sonic Pinball", which is just a game mode that will play all bonus stages sequentially.



    EDIT: As a silly bit of speculation, it has also occurred to me that the grid squares on the floor of each bonus level represents the level that you have completed in order to play it. Note how:

    1) The first stage has Lava Powerhouse colours even though you come from Toxic Caves.
    2) The second stage has Toxic Caves colours despite coming from Lava Powerhouse
    3) The third stage correctly has The Machine colours
    4) The Multiball stage has very water-focused colours. Makes me think the pallette would have been for Underground Caves perhaps?

    Probably not anything, but I find it interesting as this same detail was present in Sonic 3 & Knuckles, which showed evidence of how the levels had moved around vs their originally intended order.

    EDIT2:

    I have attached the current combined maps for your enjoyment:

    spinball_level_002_FullLayout.gif spinball_level_102_FullLayout.gif spinball_level_202_FullLayout.gif spinball_level_302_FullLayout.gif
     
    Last edited: Jan 9, 2025
  9. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    A short but hopefully fun post today.

    Since I started exploring Spinball, I have been sitting on the potential idea that the game would be much less annoying if the entire table didn't reset every time you died.

    Well I've implemented just that. When you die, the camera will now smoothly transition directly back to the starting location and you will respawn like you're starting the level fresh. The difference being that none of your progress is lost as a result of there being no level transition!

    [​IMG]

    There are still some comical issues to solve, such as Rexxon no longer bothering to attack you if you died by drowning; or a copy of Sonic's prior corpse quivering in mid-air at an arbitrary location on the level! But this was a fun milestone to hit in terms of understanding the level initialisation and the camera system.

    Video shows this working on all levels!
     
  10. Xkeeper

    Xkeeper

    lgkdfvlbjepasvdjzcvpaaaaaaaaaaaaaaaaaaaaaaaa Researcher
    1,524
    16
    18
    the bone zone
    current-project-fart
    ooh, that's neat. if you want any suggestions, mine'd be to speed up the transition by 2-3x; it feels a bit slow, especially when the death point is close to the respawn point

    loving the screaming sonic during the machine table example, what was up with that (edit: 4m18s in the video)
     
    • Agree Agree x 3
    • Like Like x 2
    • List
  11. nineko

    nineko

    I am the Holy Cat Tech Member
    6,377
    531
    93
    italy
    I like how the video around the one minute mark says "greets to [names] and draining slime" as if it all was part of the same sentence.

    Seriously though, that's a nice idea.
     
  12. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    I understand what you mean, but the transition will always take the same amount of time regardless of how far away it is (the camera transitions are done with a linear interpolation using a timer). I think it's akin to an actual pin all machine having the cooldown period between balls, regardless of how fast it happened. Maybe I could add a Free Ball or something if you die within a couple of seconds, to save on BS deaths.

    The timing I chose is a bit shorter than the amount of time the "P1 TOTAL" score is displayed for - which is unchanged. This way you only have about half a second of 0 motion before the P1 START text flashes.

    EDIT: To answer your question, the reason the dead sonic appears is because I have cheesed resetting the player object atm. The function that initialises the player will also create a new animated object instance. What happens then is the dead player anim obj is orphaned (although I'm not 100% sure yet why it opts for the location that it does currently)
     
    Last edited: Jan 12, 2025
  13. Black Squirrel

    Black Squirrel

    let's hurl a bwiki mart Wiki Sysop
    9,225
    3,089
    93
    Northumberland, UK
    the kwiki mart is real d'oh
    This is really nifty

    Question: the rings in Sonic Spinball don't spin because they didn't flip one of the animation frames. Does it look like they intended to?
     
  14. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    To answer your question maybe a bit too thoroughly:

    The rings appear to use a lot of bespoke code, so it's hard to know for sure what the actual intent was. However from what I can tell, the rings were either hacked in due to time constraints, or they were implemented the way they were as an optimisation due to there being so many instances of them within a level.

    There are 4 main types of logical object in Spinball levels:
    • Player
    • Flippers
    • Rings
    • Game Objects
    This can be seen time and time again in clusters of init/update functions
    (I won't pretend that the de-compilation is complete, so please excuse the naming inconsistencies)

    upload_2025-1-13_7-51-7.png

    There is an additional object type (an Animated Object) that the Player, Flippers and Game Objects all use. These rely on an underlying comprehensive animation system, with proper timelines etc (Animated objects can exist even without a logical Game Object too i.e. animated scenery).

    The rings however have numerous bespoke data structures that nothing else uses (I've collated this information at the end of this post). That, combined with how several places hardcode references to the sprite data and the level data, I'm pretty sure that these were added late in the day. My suspicion is that either no-one noticed that the rings look wrong, or they didn't have time. There is only an attempt to load 3 frames of sprite data, although the frame index sequence does contain 4 frames of data. It's seems possible that whomever was implementing the ring sprites just simply didn't have the time to add code to manage flipping the sprite.

    The weirdest part for me is the hack that fixes up the xoffsets of the rings. It would have been completely plausible to just make each ring sprite the same dimensions, or to use the main sprite renderer that will account for the offsets. If you look at the sprite bounds in an emulator with a debugger that will do it (like Exodus), you can see the sprite is constantly wobbling around because of this. And below you can see the sprite data in Spintool:

    The sprites have the origin offsets set reasonably to the bottom-centre of the ring sprite, but the custom ring renderer does not use this data. In fact, the memory offsets used in the ring sprite loading code uses an offset that is the starting address of these rings + 0x8 bytes. They opt to just send the pixel data and dimensions directly to the VDP.
    upload_2025-1-13_9-2-22.png upload_2025-1-13_8-59-35.png
    Ring data on ROM
    Code (Text):
    1. 0x0C151C - Level ring counts [byte[4]]               ; Array for the ring count for each level. This doesn't get used for creating or updating ring instances, only for tracking the threshold for triggering the bonus stage. The numbers for these match however
    2. ; Ring definitions arrays for each level. There is no ptr table for these, these locations are hardcoded within the level-specific initialisation functions.
    3. 0x0C3854 - Toxic Caves ring definitions
    4. 0x0C1D70 - Lava Powerhouse ring definitions
    5. 0x0C60B2 - The Machine ring definitions
    6. 0x0C49CC - Showdown ring definitions
    7.  
    8. ; Ring definition structure
    9. :00 - Object Instance ID [ushort]
    10. :02 - x_pos [ushort]
    11. :04 - y_pos [ushort]
    12.  
    13. 0x0D3B2A - Ring animation frames [short[4]]         ; Simplified animation timeline for the ring. Correctly uses 4 frames
    14. 0x0D3B44 - Ring anim frame XOffsets [short[4]]      ; The ring sprites haveset up offsets in the sprite data, but they are ignored dur to having bespoke code to load and render them.
    Ring data in RAM
    Code (Text):
    1. 0xFF3CC8 - Ring instance IDs [short[]]                ; IDs to reference the physical ring objects with collision
    2. 0xFF547E - Ring animation frame timer [short]       ; Timer to count delay between each animation frame
    3. 0xFF9984 - Number of rings in the level [short]
    4. 0xFFEE74 - Ring animation sprite ids [short[]]      ; This references the sprite allocation pool, so the ring sprite can be referenced
    5. 0xFFEE7E - Ring object instance array [short[]]     ; The list of ring objects in the level. This is separate from the main object array. The data structure for the rings is much smaller and bespoke.
    6. 0xFFF200 - Current Ring animation frame [short]     ; Frame index to determine which frame to display
     
    • Informative Informative x 3
    • List
  15. Bobblen

    Bobblen

    Member
    452
    232
    43
    The rings do seem to be a bit of an afterthought. Plenty of people simply never discovered that collecting them all triggers anything (see the comments on youtube under the 100% tool assisted speedrun for example), which makes sense as you really have to go out of your way to do it. It wouldn't surprise me if they were a last minute deal, although one wonders what the original plan for accessing the multiball stages was.
     
  16. Scrambled Eggman

    Scrambled Eggman

    Worm Bagged Tech Member
    29
    142
    28
    Picking at Sonic Spinball Disassembly
    Based on my own speculative thoughts when putting together the "Bonus Stage mode", there's a possibility that the Multiball Stage was originally the bonus stage for Underground Caves => Toxic Caves. The Multiball bonus stage is actually the first one you encounter, if you access in order them by index, plus it also has water-themed colours.

    Its inclusion is quite possibly that someone wanted to not waste the work that went into that extra bonus stage, so added the rings system at the last minute as a way to access it and to give the levels more depth.

    It would be worth looking at any screenshots of the pre-release versions to see if the rings are present, to add weight to these speculations.
     
    • Like Like x 1
    • Useful Useful x 1
    • List
  17. Kilo

    Kilo

    Starting new projects every week Tech Member
    1,231
    1,177
    93
    Canada
    Changes with the weather
    So there's no rings in the Amiga demo animation. That's kind of to be expected I suppose. But the best evidence I got is this prototype:

    There are no rings in Toxic Caves, very evident by the helix railcart section where none of the doors have been opened, but there are some in Lava Powerhouse, however that can also be chalked up to it being footage of 2 different builds I suppose.
     
    • Informative Informative x 4
    • Like Like x 1
    • List
  18. Blastfrog

    Blastfrog

    See ya starside. Member
    Also worth noting that the rings animate twice as fast as they do in the normal Sonic games. However, I've never noticed that they weren't flipped, damn, now I can't unsee that.