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
    958
    581
    93
    According to the wiki, all the data in S&KC is byteswapped. Maybe the compressed data is an exception? Because the x86 ASM does read like the C code you wrote for a big endian value.

    I think you're looking in mapdevr.c, which has the Engima decompression code. The Nemesis decompression code is in bitdevr.c.

    But corrections for mapdevr are welcome, too, so, thanks! :)

    In the meantime I fixed another bug in the bitdevr codetable generation. Looking at the output in a hex editor has it looking even more off compared to the reference, but in-game it looks a little better.
     
  2. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    I went for mapdevr, because of this. I didn't know you were talking about Nemesis specifically.
     
  3. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    Sorry, my bad. I meant Nemesis.

    Maybe I should compare Enigma output next because at the moment I don't seem to be getting anywhere with Nemesis. The codetable generation should be correct now, yet the first repeat count retrieved during decompression is still wrong.
     
  4. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    Try this:
    Code (Text):
    1. static unsigned char* bitdevr_codetable(unsigned char* p_source, unsigned short* p_bitdevwk) {
    2.   unsigned short data = *p_source++;
    3.   while (data != 255) {
    4.     unsigned short byte1 = data;
    5.     while (1) {
    6.       unsigned char byte2 = *p_source++;
    7.       if (byte2 & 0x80) {
    8.         data = byte2;
    9.         break;
    10.       }
    11.       unsigned char palette_index = byte1 & 0xF;
    12.       unsigned char indexrepeat_cnt = byte2 & 0x70;
    13.       unsigned char codebits_cnt = byte2 & 0xF;
    14.       unsigned short codetable_entry = codebits_cnt << 8 | indexrepeat_cnt | palette_index;
    15.       if (codebits_cnt == 8) {
    16.         unsigned short code = *p_source++;
    17.         p_bitdevwk[code] = codetable_entry;
    18.       }
    19.       else {
    20.         unsigned short code = *p_source++ << 8 - codebits_cnt;
    21.         unsigned short cnt = (1 << 8 - codebits_cnt) - 1;
    22.         do {
    23.           p_bitdevwk[code++] = codetable_entry;
    24.         } while (cnt-- != 0);
    25.       }
    26.     }
    27.   }
    28.  
    29.   return p_source;
    30. }

    EDIT: Fixed a thing

    Also, this
    Code (Text):
    1.       if (codebits_cnt == 8) {
    2.         unsigned short code = *p_source++;
    3.         p_bitdevwk[code] = codetable_entry;
    4.       }
    5.       else {
    6.         unsigned short code = *p_source++ << 8 - codebits_cnt;
    7.         unsigned short cnt = (1 << 8 - codebits_cnt) - 1;
    8.         do {
    9.           p_bitdevwk[code++] = codetable_entry;
    10.         } while (cnt-- != 0);
    11.       }

    Can be shortened down to the following, since the logic is basically the same (codebits_cnt being 8 will nullify the shifts, and only run the code in the loop once). Not sure if you wanna implement this, for the sake of accuracy, but the option is here if you like.
    Code (Text):
    1.       unsigned short code = *p_source++ << 8 - codebits_cnt;
    2.       unsigned short cnt = (1 << 8 - codebits_cnt) - 1;
    3.       do {
    4.         p_bitdevwk[code++] = codetable_entry;
    5.       } while (cnt-- != 0);
     
    Last edited: Aug 21, 2024
  5. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    That causes a segmentation fault because the variable data no longer has the last read value, which it needs at the top of the outer loop.

    I just reverted a bugfix because I now remember why I didn't subtract 1 at the end of this line:
    Code (C):
    1. unsigned short cnt = 1 << 8 - codebits_cnt;
    The subtraction was there specifically so the developer could satisfy his obsession with the dbf instruction. Without it, I can count down to zero.
     
    • Informative Informative x 1
    • List
  6. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    I did notice I misread a line and perhaps was following the original 68000 code too closely, leading to the "break" stuff that I added. Reverting it just circled me back to the original code you wrote. My apologies.
     
    Last edited: Aug 21, 2024
  7. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    That's okay; you were only trying to help.

    Any ideas for how I could test the Enigma decompression? I can't find an x86 implementation to compare my output with.
     
  8. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    Have you taken a look at flamewing's mdcomp? It does come with binaries for Windows in the releases tab, but I dunno what OS you are using (though, it also is designed for the original big-endian versions of data). If that's not what you're looking for, I could perhaps hack something together.
     
  9. MainMemory

    MainMemory

    Has-Been Modder Tech Member
    4,820
    410
    63
    Myself
    Enigma-compressed data in S&KC is little endian, as is the initial word of Moduled Kosinski data. KENSSharp has the ability to handle data in big endian and little endian mode for this exact reason, as I requested it so SonLVL could edit S&KC's data.
     
  10. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    This just keeps getting frustratingly weirder.

    I installed KENSSharp and tried to decompress the map. It threw up an error as it tried to read past the end of the data. I looked at my input data, and it's 459 bytes, even though the array I wrote it from has only 428 characters!

    EDIT: Found it. I have to write in binary mode. Evidently, it's not because I've decompiled tons of C code that I have a lot of experience writing it.

    ...But KENS still tries to read past the stream.
    ...Except if you pick Big Endian. Looking more closely, only the header seems to be little endian.

    EDIT2: The output, however, is little endian. That's going to make it harder to compare outputs.

    I went as far as to replicate the x86 ASM. The first values of Enigma data remain wrong when compared to KENS's (byte-swapped) output. I'm out of ideas.

    Expected output is
    Code (Text):
    1. 00 01 00 02 00 03 00 04 00 05 00 00 00 01 00 02 00
    Actual output:
    Code (Text):
    1. 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09
    The code:
    Code (C):
    1. void mapdevr(unsigned short* p_destination, unsigned char* p_source, unsigned short offset) {
    2.   mapdevrwk wk = { p_source };
    3.   unsigned short a5, d0;
    4.   a5 = d0 = *p_source++;
    5.   //unsigned char inline_copy_bit_cnt = *p_source++;
    6.   unsigned char d4 = *p_source++ << 3;
    7.   //unsigned char bitfield = *p_source++ << 3;
    8.   unsigned short a2 = *(unsigned short*)p_source++ + offset;
    9.   //unsigned short incremental_copy = *(unsigned short*)p_source++ + offset;
    10.   unsigned short a4 = *(unsigned short*)p_source++ + offset;
    11.   //unsigned short literal_copy = *(unsigned short*)p_source++ + offset;
    12.   wk.data = *p_source++ << 8; // d5
    13.   wk.data |= *p_source++;
    14.   wk.shift = 16; // d6
    15.   bool done = false;
    16.  
    17.   do {
    18.     d0 = 7;
    19.     wk.entry_bit_cnt = 7;
    20.     unsigned char d7 = wk.shift; // d7 = d6
    21.     d7 -= d0;
    22.     unsigned short d1 = wk.data;
    23.     d1 >>= d7;
    24.     d1 &= 0x7F;
    25.     // unsigned char entry = (wk.data >> wk.shift - wk.entry_bit_cnt) & 0x7F;
    26.     unsigned short d2 = d1;
    27.     // unsigned char type = entry >> 4; (combination with later operation)
    28.     if (d1 < 64) {
    29.     //if (entry < 64) {
    30.     //if (!(entry & 0x40)) { // entry < 64 ?
    31.       d0 = 6;
    32.       wk.entry_bit_cnt = 6;
    33.       d2 >>= 1;
    34.       //entry >>= 1;
    35.     }
    36.     mapdevr_nextbyte(&wk);
    37.     d2 &= 0xF;
    38.     //unsigned char repeat_cnt = entry & 0xF;
    39.     d1 >>= 4;
    40.  
    41.     switch (d1) {
    42.     //switch (type) {
    43.       case 0:
    44.       case 1:
    45.         do {
    46.           *p_destination++ = a2;
    47.           ++a2;
    48.           //*p_destination++ = incremental_copy++;
    49.         } while (d2-- != 0);
    50.         //} while (repeat_cnt-- != 0);
    51.         break;
    52.       case 2:
    53.       case 3:
    54.         do {
    55.           *p_destination++ = a4;
    56.           //*p_destination++ = literal_copy;
    57.         } while (d2-- != 0);
    58.         //} while (repeat_cnt-- != 0);
    59.         break;
    60.       case 4: {
    61.         unsigned short inline_copy = mapdevr_getinline(&wk, offset, d4, a5);
    62.         //unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    63.  
    64.         do {
    65.           *p_destination++ = inline_copy;
    66.         } while (d2-- != 0);
    67.         //} while (repeat_cnt-- != 0);
    68.         break;
    69.       }
    70.       case 5: {
    71.         unsigned short inline_copy = mapdevr_getinline(&wk, offset, d4, a5);
    72.         //unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    73.  
    74.         do {
    75.           *p_destination++ = inline_copy++;
    76.         } while (d2-- != 0);
    77.         //} while (repeat_cnt-- != 0);
    78.         break;
    79.       }
    80.       case 6: {
    81.         unsigned short inline_copy = mapdevr_getinline(&wk, offset, d4, a5);
    82.         //unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    83.  
    84.         do {
    85.           *p_destination++ = inline_copy--;
    86.         } while (d2-- != 0);
    87.         //} while (repeat_cnt-- != 0);
    88.         break;
    89.       }
    90.       case 7:
    91.         if (d2 == 0xF) {
    92.         //if (repeat_cnt == 0xF) {
    93.           done = true;
    94.         }
    95.         else {
    96.           unsigned short inline_copy = mapdevr_getinline(&wk, offset, d4, a5);
    97.           //unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    98.  
    99.           do {
    100.             *p_destination++ = inline_copy;
    101.           } while (d2-- != 0);
    102.           //} while (repeat_cnt-- != 0);
    103.         }
    104.         break;
    105.     }
    106.   } while (!done);
    107. }
    I'm going to cry in a corner now.
     
    Last edited by a moderator: Aug 21, 2024
  11. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    I've tested your code for myself, and I noticed a couple things.

    One glaring issue is that in the beginning of the function, you load "p_source" into "wk", but then proceed to use "p_source" directly for getting the header, which means "wk.p_source" ends up being incorrect when it actually starts decompressing the data.

    Another thing:
    Code (Text):
    1.     *(unsigned short*)p_source++ + offset;

    Only advances p_source by 1 byte, not 2, at least for me, because the "++" doesn't account for the type casting with the way it's set up.

    Try this, as it both accounts for the pointer issue and the increment issue (solved by wrapping the type cast itself into its own parenthesis, and putting the "++" after:
    Code (Text):
    1.     mapdevrwk wk = { p_source };
    2.     unsigned char inline_copy_bit_cnt = *wk.p_source++;
    3.     unsigned char bitfield = *wk.p_source++ << 3;
    4.     unsigned short incremental_copy = *((unsigned short*)wk.p_source)++ + offset;
    5.     unsigned short literal_copy = *((unsigned short*)wk.p_source)++ + offset;
    6.     wk.data = *wk.p_source++ << 8;
    7.     wk.data |= *wk.p_source++;

    Another issue I noticed is how BIT_IS_SET is set up:
    Code (Text):
    1. #define BIT_IS_SET(value, bit) (value &= 1 << (bit))

    It overwrites "value" with the "&=". This is not ideal for this function, because it directly checks "p_wk->data" in "mapdevr_getinline", which needs to be retained in order for the decompression to continue properly.

    Another issue I saw is when "shiftwk" in "mapdevr_getinline" is > 0. In the original 68000 code, the inline bit count is used for "mapdevr_nextbyte", but that doesn't happen in your version. A quick fix is to just set "p_wk->entry_bit_cnt" to "inline_copy_bit_cnt":
    Code (Text):
    1.     else if (shiftwk > 0) {
    2.         result >>= shiftwk;
    3.         result &= mapdevr_masks[inline_copy_bit_cnt - 1];
    4.         result += offset;
    5.         p_wk->entry_bit_cnt = inline_copy_bit_cnt;
    6.         mapdevr_nextbyte(p_wk);
    7.     }

    There's also this stuff that I edited in yesterday that you seem to have missed (I blame that more on myself, I have a really really bad habit of constantly editing my posts to include more or change information):
    Another issue, with the bit rotation:
    Code (Text):
    1.     p_wk->data = p_wk->data & 0xFF00 | p_wk->data << shiftwk & 0x00FF | p_wk->data >> 8 - shiftwk & 0x00FF;

    "p_wk->data >> 8 - shiftwk" will end up shifting the upper byte bits down where they should not be. "p_wk->data" should be masked BEFORE shifting:
    Code (Text):
    1.     p_wk->data = p_wk->data & 0xFF00 | p_wk->data << shiftwk & 0x00FF | (p_wk->data & 0x00FF) >> 8 - shiftwk;

    Another thing:
    Code (Text):
    1.         case 7:
    2.             if (repeat_cnt == 0xF) {
    3.                 done = true;
    4.             }
    5.             else {
    6.                 unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    7.  
    8.                 do {
    9.                     *p_destination++ = inline_copy;
    10.                 } while (repeat_cnt-- != 0);
    11.             }
    12.             break;

    The call to "mapdevr_getinline" should actually be in the do/while loop, as per this 68000 code:
    Code (ASM):
    1. EniDec_SubE:
    2.     cmpi.w   #$F,d2
    3.     beq.s    EniDec_End
    4.  
    5. .loop:
    6.     bsr.w    EniDec_GetInlineCopyVal
    7.     move.w   d1,(a1)+
    8.     dbf      d2,.loop

    So, it should be:
    Code (Text):
    1.             if (repeat_cnt == 0xF) {
    2.                 done = true;
    3.             }
    4.             else {
    5.                 do {
    6.                     unsigned short inline_copy = mapdevr_getinline(&wk, offset, bitfield, inline_copy_bit_cnt);
    7.                     *p_destination++ = inline_copy;
    8.                 } while (repeat_cnt-- != 0);
    9.             }
    10.             break;

    That should be all the issues.
     
  12. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    I noticed the same mistake yesterday in bitdevr. At least I'm consistent?
    C's precedence rules strike again.

    Unfortunately, while adding parentheses makes sense to us, the compiler complains that now I'm trying to increment something that's not an "lvalue". I've moved the incrementing to the next line.
    Good catch. I admittedly wouldn't have looked.
    That's not just a quick fix; it's the correct fix, because the x86 ASM does the same thing. :)
    I've been doing lots of editing myself the past few days to make sure I don't get scolded by Overlord.
    As bit rotations are frequently used in the pixelbuffer code, I've since made a macro for that. It doesn't have the issue you describe, so I'll replace that code with it. Here it is, for reference:
    Code (C):
    1. #define ROTATE_LEFT(value, bits) ((value = value << bits | value >> 32 - bits) & 0xFFFFFFFF)
    Ah, I guess I missed it because the other calls weren't inside the loop.

    Thank you so much for all of these corrections! :D The outputs now seem to match, save for the fact that SK&C's version is byte-swapped. Behold:

    screenshot.jpg

    Some things to note:
    • bitdevr isn't fixed yet, so I turned KENS's valid output into an inline byte array. Couldn't do the same trick to circumvent mapdevr, as it takes an offset that stand-alone tools don't account for.
    • Yes, the colours look weird. The original program actually exhibits the same behaviour in full screen at the level select and in select zones. I don't know why. I tried switching the RGB values around when the palette is being copied (like redirecting the green channel to the blue channel, and vice-versa), but that had no effect.

    This afternoon I looked into why the graphics don't display in window mode, without much luck. DirectDraw really doesn't like to be called while you're stepping through code with a debugger, making me think that calls were failing. The only call that actually fails is SetPalette because there's no 8-bit palette support. It then falls back to GDI. I continued following the code, but nothing looks wrong to me. But I have little experience with graphics programming, so I must be missing something.
     
  13. Devon

    Devon

    pfp by @litchui on Twitter Tech Member
    1,582
    1,959
    93
    your mom
    It actually worked for me, granted I was using modern MSVC's C compiler. It didn't work if I used the C++ compiler. Regardless, I'm glad that bit's worked out :)

    However...
    The problem with this is that the original 68000 code does an 8-bit rotation, not a 32-bit rotation:
    Code (ASM):
    1.     rol.b    d7,d5           ; and rotate the required bits into the lowest positions

    Also the fact that "p_wk->data" is a 16-bit value, so a 32-bit rotation wouldn't even work properly to begin with.

    Anyways... so, how about some bitdevr corrections?

    So first off:
    Code (C):
    1.     unsigned short code = p_wk->data >> p_wk->shift - 8;

    In the original 68000 code, it checks the lower byte:
    Code (ASM):
    1.     cmpi.b   #%11111100,d1        ; are the high 6 bits set?
    2.     bhs.s    NemPCD_InlineData    ; if they are, it signifies inline data

    The above shift doesn't guarantee that the upper bits are cleared, which throws off the check if it's >= 252. Obviously, the solution is to mask it with 0xFF when performing the check:
    Code (C):
    1.     if ((code & 0xFF) >= 252) {

    With the functions that write the pixel data to memory, I noticed that it stores the non-XOR'd row of pixels, which is incorrect:
    Code (C):
    1.     p_wk->prev_pixels ^= pixels;
    2.     BYTESWAP(pixels);
    3.     *p_wk->p_destination++ = pixels;

    The original 68000 code writes the XOR'd pixels:
    Code (ASM):
    1.     eor.l   d4,d2      ; XOR the previous row by the current row
    2.     move.l  d2,(a4)    ; and write the result

    A quick fix would be to just overwrite pixels with the XOR'd pixel data after performing the XOR, since it needs to be byteswapped:
    Code (C):
    1.     p_wk->prev_pixels ^= pixels;
    2.     pixels = p_wk->prev_pixels;
    3.     BYTESWAP(pixels);
    4.     *p_wk->p_destination++ = pixels;

    Also, I noticed that "pixels" gets cleared every time a new code is retrieved:
    Code (C):
    1.     unsigned long pixels = 0;

    It should only be cleared after a row of pixels is written. This should be moved to the top of "bitdevr_decompress", right before the loop. This is because it is possible for a packet of pixels to be read, but not fully fill up the pixel buffer, which is to be filled with the packets after. In the original 68000 code, it clears the pixel buffer before building the code table:
    Code (ASM):
    1.     moveq    #0,d4
    2.     bsr.w    NemDec_BuildCodeTable

    And when it prepares a new row of pixels, of course, it clears the buffer:
    Code (ASM):
    1. NemPCD_NewRow:
    2.     moveq    #0,d4    ; reset row
    3.     moveq    #8,d3    ; reset nybble counter

    And that should be it!
     
    Last edited: Aug 22, 2024
    • Informative Informative x 2
    • Useful Useful x 1
    • List
  14. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    You're right, I was too quick to jump for my macro. I reverted my fix and implemented your suggestion instead.
    Fixed.
    I don't like that solution, as that's not what the original ASM does. I modified BYTESWAP so it acts like function instead of a statement so I get to decide where to store the result:
    Code (C):
    1. *p_wk->p_destination++ = BYTESWAP(p_wk->prev_pixels);
    Done.

    I've verified the outputs, and they match! Thank you so much!

    In the meantime, I've fixed some input bugs and some level select bugs. All that's left is the background animating too fast. The speed at which the current selection updates is the same as the original, so thankfully it's not an issue with the timing code. It's probably my port of the animation script code.

    Speaking of animation scripts, they have a format that, when translated to a C type, can't be part of an array:
    Code (C):
    1. typedef struct animscript {
    2.   unsigned char dummy1;
    3.   unsigned char dummy2;
    4.   unsigned char dummy3;
    5.   signed char tim;
    6.   unsigned char* p_rom;
    7.   unsigned short vram_adr;
    8.   unsigned char cnt;
    9.   unsigned char size;
    10.   signed char data[];
    11. }
    12. animscript;
    The dummy variables are exclusive to S&KC. I suspect they're padding to place p_rom on a 4-byte boundary.

    Anyway, right at the end there's a variable amount of data, dependent on the number of animation frames and whether there's frame-specific durations or not. This can be expressed in C99 as a flexible array that's at the end of a struct, which in this case also fits the original data. You cannot, however, make an array of this struct type because the size of an array's members needs to be known at compile time.

    The level select has only one animation script, so it's not a problem there. But when there are several, I need a way to loop over them. I can't just place the structs next to each other in code because there's no guarantee that they'll also be next to each other in memory. I can't keep them as byte data, either, because there's a pointer to tile data in there. I suspect the original program hard-coded addresses to assets.
     
  15. Bobblen

    Bobblen

    Member
    470
    242
    43
    Really nice to see this coming together. It'll be interesting to see if any more differences from the Mega Drive version are spotted along the way.

    One thing I found when researching cheat codes for the game was that you start Doomsday with 500 rings in debug mode. Another fun one was that if you start debug mode for Sonic & Knuckles (no lock on), the game will start in Sonic 3 & Knuckles mode anyway, but if you then soft reset, it reverts back to Sonic & Knuckles. A programming oversight I assume.
     
    • Informative Informative x 1
    • List
  16. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    From what you tell me, this is your start-up context:
    • g_startGameMode is set to 2 because debug mode is set in the INI file
    • g_sonicGameMode is set to 1 because you chose Sonic & Knuckles in the launcher
    After initialisation, the game decides on the start-up routine based on g_startGameMode. There are three of them:
    • Zero launches one of the three games based on g_sonicGameMode
    • 1 launches Special Stage Mode (aka Blue Spheres)
    • 2 launches debug mode
    The start-up code for debug mode doesn't care about the game you've selected, and sets g_mdRam.cartridge to zero, which is the value for Sonic 3 & Knuckles.

    Now, when you soft reset the game, g_startGameMode is set to zero, restoring normal operation (read: no more debug mode). The normal start-up code does care about the value of g_sonicGameMode, which is still set to 1, so Sonic & Knuckles is started.

    Hope that clears it up! :)

    In other news, I've fixed the animation timing! I had to deviate a little bit from the original ASM because copying it literally would've involved casting an unsigned char to a signed char, and that's not portable.

    Now I just need to fix window mode.
     
    • Informative Informative x 1
    • List
  17. MainMemory

    MainMemory

    Has-Been Modder Tech Member
    4,820
    410
    63
    Myself
    I'm fairly certain the original data was written as assembly code, thus not bound to any size or format constraints like C/C++ are. I'm assuming that on MD the tim field was packed into the first byte of p_rom, since it has a 24-bit address bus and the high byte of all addresses are ignored.
     
    • Agree Agree x 1
    • Informative Informative x 1
    • List
  18. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    Today I solved as many compiler warnings as possible. When that didn't lead anywhere, I checked return values. Those are all fine.

    Then I got the idea of overriding the palette values by assinging 128 to all channels. The display portion of the window became gray, so at least I knew there was an active surface there and that it was tied to a palette. Next I tried viewing memory, but that didn't lead anywhere.

    Hey, why not try hard-coding the palette to a diverse set of colours? That would allow me to check if any pixels are actually written to it instead of it being blank.

    upload_2024-8-22_23-33-13.png

    Finally, I'm getting somewhere. Now I have to find out why the palette doesn't make its way to the surface.
     
  19. ndiddy

    ndiddy

    Member
    54
    68
    18
    Are you able to make "data" into a pointer and declare the animation data before the struct? This would also help pre-populate the size member, as you can do a macro like
    Code (Text):
    1. #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
    and then set size to ARRAY_LEN(data_arr).
     
  20. BenoitRen

    BenoitRen

    Tech Member
    958
    581
    93
    Of course I can do that! That's a good idea. :)

    As for my palette hunt...

    If I zero the palette data when the program starts (which seems to have been the plan at one point, as I found an unreferenced function doing exactly that), the palette data is never updated because it never changes. Yet full-screen mode still seems to have colour data. I'm starting to think that it having colour is a glitch and that the palette it's using is uninitialised data.

    @MainMemory do you have any idea where the palette data is loaded into the virtual Mega Drive's RAM? The function at 0x00426c05 loads the palette data for both level selects from colortbl (006704a6), and stores it at 0x9000000 + 0x80 (using the pointer at 008549d4). That thing has more than 1000 references. I went through a lot of them, but have yet to find where is retrieved.