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
    392
    178
    43
    It's been a week, so let's take a look at some more labels!

    elec03: Something electric in Carnival Night Zone. elec03_shot suggests it also shoots something. I'm guessing this is the Sparkle enemy, but there's not enough to go on to know for sure.

    gkn209: At first it looks like a plane name (GKN-209), but it's likely something in Lava Reef Zone. But what? Looking at the other labels, gkn209_doriru caught my eye. It looks like "doriru" means "drill". Ahh, the driller robot you encounter that digs a new passage for you before falling to its doom! Now it makes sense why it has the labels gkn209_fall0 and gkn209_die0.

    bs01b0: Hydro City's end boss, the Screw Mobile.

    bs0cb0: Doomsday Zone's boss, Final Weapon.

    snow05: Ice Cap Zone snow? I expect snow to fall, per fallsnow, but not to break, per snow05_break.

    bs00m0_body and bs00m1_body: Angel Island Zone's mid-boss, Fire Breath. Version 0 is for the cutscene, while version 1 is the one you actually fight. bs00m0_napalm suggests it uses napalm.

    bs02b0, bs02b1, bs02b2: Marble Garden Zone's end boss, the Drill Mobile. I expected two versions, as Knuckles' fight is very different, but here we have three. As the first has limited logic associated to it, I'm guessing it was used exclusively for cutscenes.

    bs0ab0: A Sky Sanctuary boss. bs0ab0_chain and embl_jet hint at it being the Mecha Sonic-controlled Egg Wrecker. I do wonder if this boss didn't have a name before the Encyclo-speed-ia, which seems to want to use Sonic Adventure's naming scheme for all of Eggman's piloted creations.

    pilr05: An Ice Cap Zone pillar? Is this one of the background objects? A related label, pilr05_sita, doesn't tell me much, as "sita" seems to mean "down".

    bs0ab1: Sky Sanctuary's second boss. The labels refer to a laser and a balloon. This must be the Mecha Sonic version of Flying Eggman. Curiously, most labels don't use a bs0ab1 prefix, but a bboss7 prefix. Speculation time: Metropolis Zone is the eighth zone of Sonic 2. If we start counting from zero, that makes it zone 7, which matches the number used in the labels. Maybe this code was copy-pasted from Sonic 2? Both games were developed at Sega Technical Institute, after all.
    Now that I got this far, I can tell you that you were right on the money. CreateChild1_Normal is indeed set_act00.
    It is the norm for computer data structures, like arrays, but I don't expect human-made sequences to start at zero.
     
  2. muteKi

    muteKi

    Fuck it Member
    7,851
    131
    43
    Some of the pillars in the stage are stationary while others move when stepped on, others move in accordance with a timer (the first one you see is like this), and yet another kind moves when Sonic moves past them (I think this is only the one that blocks access back after the Act 2 transition begins). My guess is that shita (probably being used for 'underneath' more than 'down') refers to the ones that move when stepped on.

    You'll encounter the first timed one right after the infinite corridor that has you land on a platform to smash an ice pillar, and immediately after is the first one that moves up from under you when stepped on.
     
  3. Brainulator

    Brainulator

    Regular garden-variety member Member
    Oh, definitely. This just confirms it.
     
  4. Tiberious

    Tiberious

    Yeah, I'm furry. Got a problem? Oldbie
    778
    15
    18
    Your 'snow05' sounds like the snowdrift pile objects that slow Sonic down if he walks through them, and can be 'blown away' by spindashing in it or rolling fast enough through them.
     
  5. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,741
    338
    63
    SonLVL
    The code in S&KC is nearly identical to the MD version, down to the order of the subroutines. If you aren't sure what something is, the disassembly of the MD version may have a plain English label that's helpful.
     
  6. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    Looking something up in the disassembly is a pain because many labels are just the memory addresses, and I've already noticed that the order is only partially the same.

    After Obj_SOZGhosts (gost08) is Obj_Fireworm, which is much, much later in Sonic & Knuckles Collection (where it's snak09). In S&KC there are object-related routines instead.

    Next is Obj_Iwamodoki (iwa09), which does match. But then it goes off the rails again with Obj_Toxomister. In SK&C there's the code for rolldai instead.

    Then we have Obj_LRZRockCrusher, and only after that do we have the code for when you enter Hidden Palace through a big ring, known in the disassembly as Obj_HPZMasterEmerald, and in S&KC as caos09.
    I thought so, too, but I don't remember it falling, and it's weird that they call it going away "break". Maybe the snow falling is a related object mixed in.
     
  7. Mookey

    Mookey

    Member
    150
    92
    28
    Doesn't it fall on top of Sonic as he falls into the starting cavern? Or are you all talking about a different snow object? I'm thinking of the one he hops out of after he crashes the snowboard.
     
  8. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    Another week, another batch of labels.

    daru05: The related label daruma_chk suggests that "daru" is short for "daruma". Looking up what this means gets me results about hollow round Japanese dolls. Other related labels are daru05_base and daru05_ice. My guess is that these are hollow ice sculptures found in Ice Cap Zone that you can find around switches and monitors.

    bs0ab2: Sky Sanctuary's third boss, Mecha Sonic. It not only has Sonic's fight, but the fights with Knuckles as well. It going "super" using the Master Emerald is also how the developers saw it, as attested by the bs0ab2_super label.

    bs09b1: Lava Reef Zone's end boss, Hot Mobile. Astute readers will note that the label is for the zone's second boss. That's because the sequence where Sonic is ambushed by the Death Egg counts as the first boss.

    mous04: the mouse enemy from Flying Battery Zone, Chu-Chu, known in English as Technosqueek.

    bs03b0: Carnival Night Zone's end boss, Graviton Mobile.

    bs0bb0: Death Egg Zone's end boss, Death Ball. What confirms this are the labels bs0bb0_bobin (bobin = bumper) and bs0bb0_cnsl (the console Eggman uses).

    rpillar and rpillar02: rotating pillars. The second is definitely used in Marble Garden Zone, as the label rpillar02_rideon attests that you can ride it.

    gady0b: an enemy in Death Egg Zone, Guardy, known in English as Spikebonker. I initially wasn't sure if it was that one or Crow/Chainspike, but "gady" is similar to "Guardy", and the label gady0b_iball refers to the spiked ball it has.

    hdai01: a platform in Hydro City Zone. The label hdai01_shot0 suggests it shoots. I'm not sure what this is.

    bs04b0: Flying Battery Zone's end boss, Hang Mobile. I know it's not the previous boss with the spiked platforms and laser because the arms are referenced with labels like bs04b0_arm.
    We're referring to the snow lumps in the second act that slow Sonic down if he isn't moving fast enough.
     
  9. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    I've done it! I've annotated and extracted all 15213+ ASM labels with their memory address! It has been attached to the first post as labels.txt. Counting the indexes, they cover 202 source files.
     
    • Like Like x 5
    • Informative Informative x 1
    • List
  10. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    The linked lists are from two builds before Sonic 2 Nick Arcade. Is there a list somewhere of the labels in S2NA? Or are its labels identical to S2NA's?

    I'm trying to see if there's a label for the function currently labelled as Add_Object_To_Collision_Response_List. We do have the label for the list itself thanks to Sonic Jam: colibuffer.

    One clue I found is that a routine labelled as chk_actsubc has a call to Add_Object_To_Collision_Response_List and a jump to actionsub, after which it returns.
     
    Last edited: Mar 8, 2024
  11. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,241
    1,410
    93
    your mom
    I don't see why the labels would change. They aren't *that* out of date, and also the labels highlighted by Brainulator can also be found in Sonic 1 and CD.

    Regarding Add_Object_To_Collision_Response_List, the Sonic 2 Nick Arcade symbols won't be of any use, because that function nor the collision response list wasn't in Sonic 2; it was introduced in Sonic 3. Sonic 1, 2, and CD just iterated through every object slot in the dynamic object slot pool when checking for collision.
     
    Last edited: Mar 8, 2024
  12. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    Yeah, I was afraid of it being a new feature. Oh well.

    Relatedly, is it documented anywhere what known routines do? By now I know that actionsub puts an object on screen, that frameout is the object deletion routine, etc. because of these discussions, but is there documentation?
     
  13. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,241
    1,410
    93
    your mom
    • Like Like x 1
    • Useful Useful x 1
    • List
  14. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    Now there's a Git repository! So far, it has the result of yesterday and today's work, as I started over.

    I've chosen to write it in C99. (A loud sigh could be heard in the audience.) Why?
    • Can declare variables anywhere.
    • Standard bool type.
    • Single-line comments.
    • Still old enough to support a wide variety of consumer platforms.

    What files are there now?
    • 002.c - This has code based on the second block of labelled ASM. It contains functions related to player and enemy objects.
    • 004xxxxx.c - This has code based on the 004xxxxx address range of the original .EXE. It contains functions related to sprites.
    • 091.c - This has code based on the 91st block of labelled ASM. It also seems to contain functions related to player objects. Be sure to look at this one, as I've found that chk_pl_attack() has dead code for Tails.
    • constants.h - Constants for character IDs and sprite status objects.
    • globals.c - Global variables that reside in Mega Drive RAM. Lowercase names are from Sonic CD and Sonic Jam. Those that I made up start with an uppercase letter so I can keep track of them.
    • gost08.c - Code for Sandopolis ghost enemy Hyudoro.
    • types.h - Contains all of the types I've had to define so far.

    This project is still in its infancy, and I've never done a project like this before, so any constructive criticism is welcome!

    One thing I've been thinking of is to put all data of an object in its separate header. For example, all those arrays at the start of gost08.c could be moved to gost08_data.h. Is this a good idea?
     
  15. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,741
    338
    63
    SonLVL
    Technically the intended purpose of header files is to declare things (structs, variables, functions, etc) that are shared across multiple files. Data declarations are meant to be contained in .c files. You could move the arrays into a separate .c file, then make a header for that that you include in the main gost08.c (or just declare the variables again in gost08.c with extern linkage).
     
  16. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    I mentioned that chk_pl_attack() has dead code for Tails. It seems to be more complicated than that.

    This is the original 68000 ASM from skdisasm:
    Code (Text):
    1.  
    2. Check_TailsAttack:
    3. tst.b   double_jump_flag(a1)
    4. beq.s   Check_SonicAttack       ; If Tails is not flying, branch
    5. btst   #6,status(a1)
    6. bne.s   Check_SonicAttack       ; If Tails is underwater, branch
    It was translated to the following x86 ASM:
    Code (Text):
    1. MOV AL,byte ptr [EDI + 0x2f]
    2. TEST AL,AL
    3. JZ chk_pl_attack@@z_ret
    4. BT word ptr [EDI + 0x2a],0x6
    5. JNZ chk_pl_attack@@z_ret
    JNZ branches if the zero flag is not set. The problem is that BT does not affect the flag. The last operation that affected the flag is "TEST AL,AL".

    Which is why it translates to the following C code:
    Code (Text):
    1. if (((unsigned char*)pPlayerwk)[47] == 0) return true;
    2. pPlayerwk->cddat1 & 64;
    3. if (((unsigned char*)pPlayerwk)[47] != 0) return true;
    I may have found a S&K Collection-exclusive bug.
     
  17. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    I want to talk a bit about the collision response list, called colibuffer in the original source code. It's basically an array of 64 words (128 bytes) where the first word is used for bookkeeping the next 63 slots. This first word doubles as a counter (when divided by 2) and an offset to last occupied slot. It's quite an elegant design, if you ask me, which is why I implemented it as-is instead of converting it into a C structure, for now.

    The code to add an object to it is quite simple.
    Code (Text):
    1. if (colibuffer[0] >= 126) return;
    If the first word is 126, that means it's full, because 63 * 2 = 126.
    Code (Text):
    1. colibuffer[0] += 2;
    Add 2 so the offset points to the next (free) slot.
    Code (Text):
    1. colibuffer[colibuffer[0] / 2] = actwk - pActwk;
    As we're not manipulating a memory value in bytes, but an array of words, the first word has to be divided by 2 when indexing the array. As for the slot's value, just like in Sonic CD, we use the index of the sprite status object in the actwk array instead of saving the memory location.

    By the way, this data structure is also used for what's called the "sprite table input", which has 8 instances of it, used to sort sprite status objects by priority.

    EDIT: As this doesn't warrant a new post so soon: I've added three missing labels to my extract and updated the file in the first post.
     
    Last edited: Mar 12, 2024
  18. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    @MainMemory Do you know what the data at run-time memory address 008549d4 is for?

    If I look at the Mega Drive games disassembly, once it's referred to as "ros_addr" (Remove_From_TrackingSlot). But in other routines, like Check_PlayerCollision (chk_emy_col in S&KC), I don't see an equivalent at all.
     
  19. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,741
    338
    63
    SonLVL
    I have it defined as m68kRAMEndPtr, it's initialized to 0x9000000 (the end of the static RAM buffer) in AllocateRAMAndVRAM (4043B0), and it's used anywhere a sign-extended word is used to store a RAM address in the original code. On a Megadrive, you can store addresses $000000-$007FFF and $FF8000-$FFFFFF as a word (16 bits), which gets sign-extended to the full 24-bit address (technically it's extended to 32 bits but the top 8 bits are ignored by the address bus) as needed. On 32-bit x86, of course, all pointers are full 32-bit, so instead it reads the 16-bit address as a signed short and adds it to the pointer pointing to the end of the RAM block. For example, a pointer to the ring counter on MD could be stored as $FE20, which would be sign extended to $(FF)FFFE20; on PC, 0xFE20 would be read as signed and added to 0x9000000, resulting in 0x8FFFE20. As to why they didn't just implement this behavior using an OR instead, or why they even have this variable instead of having it be a constant in the code, who knows.
     
    • Agree Agree x 1
    • Informative Informative x 1
    • List
  20. BenoitRen

    BenoitRen

    Tech Member
    392
    178
    43
    I've finished the C port of the gost08 code (as well as most of the code it calls)!

    Now's a good time for everyone to take a look at the work that's been done and give some constructive criticism (please?). In particular, I'd like feedback on the code style and organisation.

    Having worked on the Sonic CD decompilation, I based my style on that. In general, function and variable names use snake case, with one exception: pointers. For pointers, camel case is used, prefixed with the letter p. This admittedly looks weird at times.

    There's one function I'm not happy with at the moment:
    Code (Text):
    1. void set_emy_wchg(sprite_status* pActwk, wrt_data* pData) {
    2.   if (pActwk->patno == SPRITE_STATUS_UCHAR(pActwk, 58)) return;
    3.   SPRITE_STATUS_UCHAR(pActwk, 58) = pActwk->patno;
    4.   unsigned long* a3 = pData->unknown;
    5.   unsigned short vram_adr = (pActwk->sproffset & 0x7FF) << 5;
    6.   unsigned short* tbl = pData->tbl[pActwk->patno];
    7.   short cnt = *tbl++; // this conversion from unsigned to signed should be safe
    8.   cnt = cnt >> 8 | cnt << 8; // rotate lower and upper 8 bits
    9.   unsigned short d3 = 0;
    10.  
    11.   do {
    12.     d3 = *tbl++;
    13.     unsigned short d1 = d3;
    14.     d1 &= 0xFFF0;
    15.     d1 += d1;
    16.     d1 += *a3;
    17.     unsigned short d2 = vram_adr;
    18.     d3 &= 0xF;
    19.     ++d3;
    20.     d3 <<= 4;
    21.     vram_adr += d3;
    22.     vram_adr += d3;
    23.     Copy_Data_To_VRAM(d1, d2, d3);
    24.     --cnt;
    25.   } while (cnt != -1);
    26. }
    I do my best to give variables meaningful names, but for this function, which is named Perform_DPLC in the disassembly, my understanding is lacking.

    Another thing is that I use bit-wise operators to rotate a signed 16-bit value, which isn't portable. But why does it need to be rotated in the first place? Other count-like variables can be read as-is. In fact, the other 16-bit values read from the same array don't need to be rotated, either.