don't click here

Sonic & Knuckles Collection C port

Discussion in 'Engineering & Reverse Engineering' started by BenoitRen, Jan 11, 2024.

  1. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    Is there any more information on which memory regions are used for what by the Special Stage? Here's what I have so far:
    • A400-A4FF - collision response list
    • A500-???? - temporary data while checking if we tagged a group of spheres?
    • F100-F500 - layout of the current stage
    I'm scratching my head as what's happening in the equivalent of the disassembly's sub_9EBC label.
     
  2. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    I might have bitten off more than I can chew by trying to port the Special Stage. I'm a good decompiler/porter, but as I'm not that good at reverse engineering I depend on existing documentation. There's too little for the Special Stage.

    After some refactoring, I took a look at the code for Sonic 3's title screen. Sonic & Knuckles Collection has its code entirely separate from Sonic 3 & Knuckles's, but as expected they're very similar. They even went as far as to duplicate mapping and animation data, which I have verified are identical.

    Not wanting to repeat this unnecessary duplication, I decided to merge Sonic 3's title screen code with Sonic 3 & Knuckles.

    Some observations:
    • Unlike the other title screens, Sonic 3's doesn't bother hiding the trademark (TM) symbol if the game is running on a domestic system.
    • Sonic 3 & Knuckles polls both controllers for the Start button. Sonic 3 only polls the first controller.

    upload_2024-9-21_21-40-30.png
     
    • Informative Informative x 1
    • List
  3. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    Sonic 3's level select has also been ported! This time I didn't merge all the code, but only that of the subroutines. Merging the main routines would have been a bit too messy.

    I had never taken a close look at this level select before due to never having succeeded in entering the cheat code for it, but the zone icons are those of Sonic 2!

    Another curiosity is that the cheat code to get all emeralds has two possible results depending on the value of register d2. If d2 is zero, you get 15 continues. If it's non-zero, you get all emeralds. But d2 is always 1.

    upload_2024-9-23_22-57-11.png
     
    • Informative Informative x 1
    • List
  4. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    I'm still stumped by Sonic & Knuckles's title screen.

    @Devon could there still be something wrong with my mapdevr port? Or maybe my port of moduled unlze?
     
  5. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,524
    1,878
    93
    your mom
    @BenoitRen You forgot to increment p_source after reading the size in unlze_moduled:
    Code (C):
    1.     unsigned short size = *((unsigned short*)p_source);
    2.     if (size == 0) return;

    Should be:
    Code (C):
    1.     unsigned short size = *((unsigned short*)p_source);
    2.     p_source += 2;
    3.     if (size == 0) return;

    That seems to be it. mapdevr works perfectly.
     
    Last edited: Sep 25, 2024
    • Agree Agree x 1
    • Useful Useful x 1
    • List
  6. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    Looking much better!

    upload_2024-9-25_17-20-44.png

    Something must be off with the code handling the animation, because Sonic's left hand doesn't display properly, and Knuckles's hands don't animate at all. Still, progress!
     
  7. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    I've finished most of Sonic & Knuckles's title screen!



    I say "most" because the phaseout effect on the logo doesn't work, and part of the bottom of the background shows at the top when Sonic lands.

    The character selection roulette works, but I'm not sure about the implementation. To decide whether the Knuckles name tag has to be at the front, the variable that keeps track of the rotation is decremented with 160, and it checks if the result is negative. This seems to rely on the underflow behaviour of a signed char. So I tried to check if the variable is higher than 32 instead, thinking that decrementing 0 by 128 would cause an underflow, making the number positive again.

    However, during testing I saw that when the rotation has stopped at Knuckles's name tag, the rotation variable is -128. So I added an extra check for that.

    After this, I wasn't satisfied, because it didn't seem that the two name tags were equally highlighted while at the front during the rotation, so I changed the value of 32 by 64.

    Any ideas or corrections for this logic are welcome.
     
  8. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    Many pages back, I was implementing a common function that did DPLC. I'm looking at the Special Stage code again, and this part at the end of the player code looks similar to it:
    Code (ASM):
    1. SStage_PLCLoad_91A2:
    2.        moveq    #0,d0
    3.        move.b    mapping_frame(a0),d0
    4.        cmp.b    $3A(a0),d0
    5.        beq.s    locret_91E6
    6.        move.b    d0,$3A(a0)
    7.        add.w    d0,d0
    8.        adda.w    (a2,d0.w),a2
    9.        move.w    (a2)+,d5
    10.        subq.w    #1,d5
    11.        bmi.s    locret_91E6
    12.  
    13. loc_91BE:
    14.        moveq    #0,d1
    15.        move.w    (a2)+,d1
    16.        move.w    d1,d3
    17.        lsr.w    #8,d3
    18.        andi.w    #$F0,d3
    19.        addi.w    #$10,d3
    20.        andi.w    #$FFF,d1
    21.        lsl.l    #5,d1
    22.        add.l    d6,d1
    23.        move.w    d4,d2
    24.        add.w    d3,d4
    25.        add.w    d3,d4
    26.        jsr    (Add_To_DMA_Queue).l
    27.        dbf    d5,loc_91BE
    However, it seems to do things a bit differently. The first two bytes still contain the count, and I think the instruction follows. But in contrast to the function I ported, the instruction also needs to be byte-swapped in the PC version. After that, I don't know what it's doing. The left shifting by 5 bits makes me think that's where a vram address is extracted.
     
  9. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,524
    1,878
    93
    your mom
    DPLCs are designed to load only a specific set of tiles in VRAM for a sprite frame. For example, you cannot just load all of Sonic's sprites into VRAM, because there isn't enough space to hold all of the tiles. Each sprite frame gets a list of definitions that define which tiles from a bank of tiles to load into VRAM.

    Each entry simple defines the tile offset in the bank, and how many tiles to load (minus 1), and the tiles get loaded in sequence within a given space in VRAM. This means the first set of tiles are loaded at the beginning of the designated space, and then the next entry's tiles are loaded right after, etc.

    Add_To_DMA_Queue's parameters are the following:
    • d1 = Address in 68000 space to load tiles from
    • d2 = VRAM address to load into
    • d3 = Number of 16-bit words to load

    With the function you are demonstrating, an entry is formatted like this in binary:

    LLLLLLLL TTTTTTTT TTTTTTTT TTTTTTTT

    T = Tile offset. Because every tile is 32 bytes in size, it performs that multiplication to get the byte offset.
    L = Tile count, subtracted by 1. The subtraction gets undone and then it effectively gets multiplied by 16, with the way it's extracted (not 32, because of Add_To_DMA_Queue).

    This function takes d4 as the starting VRAM address of the space to load tiles into, and d6 is the address of the tile bank to load files from. For each DPLC entry, the source address for Add_To_DMA_Queue is calculated from source address + (entry & 0xFFF << 5), and the copy length is calculated from ((entry >> 8) & 0xF0) + 0x10. The VRAM address is then advanced by the copy length in bytes (hence, why it does 2 additions instead of 1).
     
    Last edited: Oct 5, 2024
  10. Brainulator

    Brainulator

    Regular garden-variety member Member
    I wonder if the problem is that player objects (and things like shields/barriers) use the same DPLC format as seen in Sonic 2, but many other objects use a different system. More on that on the wiki and this blog.
     
  11. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,799
    385
    63
    SonLVL
    I think I was the first person to actually document the different DPLC format. I was certainly the first to support it in tools (SonLVL etc).
     
  12. BenoitRen

    BenoitRen

    Tech Member
    911
    555
    93
    Thanks! I've translated it to the following C code:
    Code (C):
    1.  
    2.   if (p_actwk->patno != SPRITE_STATUS_UCHAR(p_actwk, 58)) {
    3.     SPRITE_STATUS_UCHAR(p_actwk, 58) = p_actwk->patno;
    4.     unsigned short* p_wpat = p_wrtpat[p_actwk->patno];
    5.     unsigned short cnt = *p_wpat++;
    6.    
    7.     for (int i = 0; i < cnt; ++i) {
    8.       unsigned short instruction = *p_wpat++;
    9.       unsigned short words = ((instruction >> 8) & 0xF0) + 16;
    10.       unsigned char* p_source = p_sourcebase + (instruction & 0xFFF) * 32;
    11.       copy_data_vram(p_source, vram_adr, words);
    12.       vram_adr += words * 2;
    13.     }
    14.   }
    By the way, the reason I got back to Special Stage code is because I found some documentation on its inner workings on Sonic 3 Unlocked.

    Still not confident I can get this working, but we'll see how far I get.
     
  13. Kilo

    Kilo

    Stupid Little Baby Man Tech Member
    1,269
    1,202
    93
    Canada
    Sonic 1 Source Code Recration
    I heard the code behind calculating when to turn outlined spheres into rings is total aids, so godspeed Benoit.