don't click here

How to port Sonic 2's layout loader to Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by Clownacy, Jun 13, 2014.

  1. Clownacy

    Clownacy

    Tech Member
    1,061
    607
    93
    You getting bored of better performance at the cost of space? Let's try something different this time.

    Here, we'll be porting Sonic 2's layout format and loader to Sonic 1. This doesn't save a whole lot of space (my own port reduced the total size of all REV01 level layouts from 5722 bytes to 3721), but it's a good way to familiarise yourself with the layout format and the differences between Sonic 1's and Sonic 2's. This guide will break compatibility with whatever level editors use pre-defined level layout formats. To my knowledge, the only workaround exists for SonLVL.


    The theory behind it
    The standard level layout setup, if you don't already know, is a simple list of chunks, arranged into rows, from left to right, following that is another row, then another, etc. This is consistent between Sonic 1 and Sonic 2.

    So what is the difference between Sonic 1 and Sonic 2's layouts?

    Sonic 1:
    1. Layouts are in two separate files for each act
    2. Files are uncompressed
    3. Designed for the 256x256 chunk system
    4. Maximum row size and number: 64x8
    5. Each layout file has a two-byte header

    Sonic 2:
    1. Layouts are in one file for each act
    2. Files are Kosinski-compressed
    3. Designed for 128x128 chunk system
    4. Maximum row size and number: 128x16
    5. Layout files do not have headers

    What does this mean, and why?

    1.
    In Sonic 1, there is a file for the foreground layout, and another file for the background layout. When a level is loading, these files are aligned and interlaced in RAM. While each act has its own FG layout file, there is usually only one BG layout per zone, one exception being SBZ, which has a unique BG layout for act 1. This is a space saving measure. Why waste room on duplicate BG layout data? This isn't the only instance of Sonic 1 ridding itself of redundant data, another instance will be seen in section 5.

    In Sonic 2, the files are already interlaced. Of course, this creates duplicate data, so why do it? It isn't space efficient! Read on...

    2.
    The files are uncompressed in Sonic 1, because compressing would require RAM, and there's already a large portion of RAM dedicated to the complete layout, so another section just for the 'building blocks' of the layout may have seemed unnecessary. The point being that there'd essentially exist two areas in RAM for the layout, with one being used to create the other. In this sense, the Sonic 1 layout files are comparable to tables, and you don't see all of those being compressed! I'll explain in greater detail later, but the individual files in Sonic 1 aren't raw layouts, as they lack the 'padding' (the numerous 'air' chunks that make up the bulk of the undefined areas of a level). This is why the layouts aren't read from ROM, they aren't 'truly' raw, and they aren't interlaced, that is all handled during loading, with the suitable layouts pieced together in RAM. Being in RAM does have its benefits, such as allowing for dynamic modification: in Sonic 1, LZ act 3 takes advantage of this by making a wall chunk change into a different chunk. The Good Ending also does this.

    In Sonic 2, on the other hand, the interlacing has already been done, and the 'padding' already exists, so loading to RAM is a simple copy-and-paste. Just stream the entire file with no fuss. But why stop there? These files can be compressed with no issues, saving space. They're not being read from ROM anyway!

    3 & 4.
    Sonic 2 really needs the compression: Between Sonic 1 and Sonic 2, the levels are the same size, 16384x2048 pixels, but the layouts themselves quadruple in size. This is because of the transition from the 256x256 to 128x128 chunk size, requiring four '128 chunks' to occupy the space of one '256 chunk'. While this means there'd be more redundant layout data to be shaved off with the S1 method, there's also more needed data, data that the S1 method can't do a thing about.

    5.
    The headers are the first two bytes in a Sonic 1 level layout, and are an element that play into the S1 method: they define the size and number of the rows in the layout file, allowing one to get away with:

    Code (ASM):
    1. 01  ; Size of rows-1
    2. 01  ; Number of rows-1
    3.  
    4. ; Row 1
    5. 0102    ; 2 bytes
    6.  
    7. ; Row 2
    8. 0304    ; 2 bytes

    Instead of:

    Code (ASM):
    1. ; No header
    2.  
    3. ; Row 1
    4. 01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    5.  
    6. ; Row 2
    7. 03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    8.  
    9. ; Row 3
    10. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    11.  
    12. ; Row 4
    13. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    14.  
    15. ; Row 5
    16. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    17.  
    18. ; Row 6
    19. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    20.  
    21. ; Row 7
    22. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes
    23.  
    24. ; Row 8
    25. 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $40 bytes

    Which, by the way, with Sonic 2's chunk system, would look like this:

    Code (ASM):
    1. ; No header
    2.  
    3. ; Row 1
    4. 0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    5.  
    6. ; Row 2
    7. 0506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    8.  
    9. ; Row 3
    10. 090A0B0C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    11.  
    12. ; Row 4
    13. 0D0E0F1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    14.  
    15. ; Row 5
    16. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    17.  
    18. ; Row 6
    19. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    20.  
    21. ; Row 7
    22. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    23.  
    24. ; Row 8
    25. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    26.  
    27. ; Row 9
    28. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    29.  
    30. ; Row 10
    31. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    32.  
    33. ; Row 11
    34. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    35.  
    36. ; Row 12
    37. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    38.  
    39. ; Row 13
    40. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    41.  
    42. ; Row 14
    43. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    44.  
    45. ; Row 15
    46. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes
    47.  
    48. ; Row 16
    49. 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    ; $80 bytes

    Scary, huh?

    Thanks to the joys of compression, these relatively insanely large files can be shrunk down to a size comparable to their S1 counterparts. Even better, in the event that all of those 00's are actual chunks that the S1 method can't eliminate, Kosinski will see to compressing that down, also, resulting in a smaller file.

    That's the goal here, folks. We're going to free up some ROM space by employing the Sonic 2 layout loading system in Sonic 1, and creating an S1-S2 hybrid level layout format!

    Please note that this does not involve modifying Sonic 1 to use the 128x128 chunks system. This involves only the level layouts. Also note that Project Sonic 1: Two-Eight and any derivatives already use the Sonic 2 level layout format and loader.


    Converting the layouts
    We have two ways to go about this: either we simply copy the processed layouts from RAM, or we convert them manually in a hex editor. The former is significantly easier than the latter, it being the one I recommend.

    From RAM:
    Enter an act of your hack, once the title card is over and the level layout is visibly loaded, perform a RAM dump. If you can, just dump the RAM from $FFFFA400 to $FFFFA800, as that will dump only the layout. If that is not an option, open the dump in a hex editor and go to offset $A400, select $400 bytes of data from that point and place it in its own file. Name it after the FG layout file it's based off of. Make a new folder, "levels_u", and place the extracted layouts in there. Do this for every act of your hack.

    Be careful of exactly when you dump the RAM: you don't want to dump the RAM if the layout has been modified by a level event, such as LZ's disappearing wall, or the Good Ending's flowers and magical moving trees, so be sure to dump the RAM before such events occur.

    By hand
    Here we'll have to process the layouts just as the loader would. This is quite time consuming, and I'd recommend the above option (I followed this method my first time, and believe me, it ain't fun).

    For this example, I'll be using Green Hill Zone act 1:

    In your 'levels' folder, open ghz1.bin and ghzbg.bin in your hex editor of choice. Create a new file, $400 bytes big. If possible, arrange this file into chunks of $40 bytes.

    So far we have this:
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    This will be our 'canvas' (and will be henceforth referred to as such). Understand that the method of converting will be copying each row of chunks from ghz1.bin to every other $40 bytes of the Canvas, then each row of chunks from ghzbg.bin to every other other $40 bytes.

    Go back to ghz1.bin. Read the header, as said before, the header defines the size of the rows and the number of rows. ghz1.bin's is "2F 04", meaning that there are 5 rows that are $30 bytes in size. Of course, rows are $40 bytes in size in Sonic 1, so the last $10 will have to be occupied by 00's. Good thing we just created that blank layout file, huh? It does all of the work for us!

    In ghz1.bin, after the two-byte header, select $30 bytes of data and copy it. In the Canvas, select the first $30 bytes of the first $40 byte block and replace it with your copied values from ghz1.bin. Go back to ghz1.bin and copy another $30 bytes after the ones you previously did, and paste it over the first $30 bytes of the third $40 byte block. Note that we're skipping a block, that block is for the first row of ghzbg.bin. Continue to do this until you have copied a total of 5 rows from ghz1.bin, by that point, there should be no more data in ghz1.bin to copy. You have completed the conversion of the foreground layout! Now to tackle ghzbg.bin.

    But first, here's what we have so far:
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 38 01 01 01 24 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 38 24 00 00 21 26 11 11 1F 0F 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 2D 31 24 14
    38 04 23 25 2D B5 26 11 1F 1E 20 0F 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    2D 2D 03 31 24 10 02 07 04 05 2B 0E 1E 11 25 1A
    26 11 08 09 0A 17 1E 1E 20 11 1F 0F 00 10 05 2B
    16 02 03 37 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 1E 1E 1E 20 25 07 22 0C 0D
    15 19 11 25 2D 2D 2D 2D 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    We're just about doing the same thing as with ghz1.bin. ghzbg.bin's header is "1F 00", meaning that there is a single row that is $20 bytes in size. So, go to ghzbg.bin and skip the first two bytes (again, that's the header) and copy $20 bytes of data, and paste it over the first $20 bytes of the second $40 byte block of the Canvas. Done. Since there was only one row, we're finished. With that, that's GHZ act 1 wrapped up. Make a new folder, "levels_u" and save the Canvas in it as "ghz1.bin".

    Here's what you should have:
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 38 01 01 01 24 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    3D 3E 3A 3F 30 39 3B 3D 3A 30 3E 39 3D 3B 3B 3D
    3E 3A 3F 3E 3A 3F 30 39 3B 3D 3A 30 3E 39 3D 3B
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 38 24 00 00 21 26 11 11 1F 0F 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 2D 31 24 14
    38 04 23 25 2D B5 26 11 1F 1E 20 0F 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    2D 2D 03 31 24 10 02 07 04 05 2B 0E 1E 11 25 1A
    26 11 08 09 0A 17 1E 1E 20 11 1F 0F 00 10 05 2B
    16 02 03 37 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 1E 1E 1E 20 25 07 22 0C 0D
    15 19 11 25 2D 2D 2D 2D 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    Repeat this for every act of your hack.

    Note that the ending has its own layout, be sure to convert that also.

    With all of your layouts converted, compress them in Kosinski, and replace the contents of the 'levels' folder with them. Make sure that each and every one of the original files are replaced.

    Porting the layout loader
    The loader was totally rewritten for Sonic 2, so we'll have to port the routine over instead of tweaking Sonic 1's current one to use the new format.

    The loader is known as "LevelLayoutLoad" in Sonic 1, and "loadLevelLayout" in Sonic 2. Looking at Sonic 1's you can see that it clears $FFFFA400-$FFFFA800, and then uses the FG and BG layouts to construct the real layout. Sonic 2's, on the other hand, is far more simple, being little more than an arrangement for a branch to KosDec. Humorously, you can find an S1-style loader dummied out below the used code. Said dummied out code is modified to conform to Sonic 2's rules, showing that Sonic 1's loader was still in use even after the jump to a 128x128 chunk system was made. Don't be as careless as Sonic Team with your precious ROM space, and delete (or comment out) all code from the label

    Code (ASM):
    1. LevelLayoutLoad:

    to the line

    Code (ASM):
    1. ; End of function LevelLayoutLoad2

    and, in its place, paste this:

    Code (ASM):
    1. LevelLayoutLoad:
    2.         move.w  (v_zone).w,d0   ; Load Zone and Act into d0
    3.         ror.b   #2,d0
    4.         lsr.w   #5,d0       ; Convert d0's Zone & Act value into an offset
    5.         lea (Level_Index).l,a0
    6.         move.w  (a0,d0.w),d0    ; Offset Level_Index by d0 to reach the appropriate layout pointer
    7.         lea (a0,d0.w),a0        ; Load layout ROM pointer into a0
    8.         lea (v_lvllayout).w,a1  ; Load layout RAM address into a1
    9.         bra.w   KosDec          ; Decompress layout to RAM
    10. ; End of function LevelLayoutLoad

    Converting the level layout index
    Go to Level_Index. In Sonic 1, each act in the index has three entries, word-sized pointers. The first is the FG layout, the second is the BG layout, the third... you got me. It doesn't seem to be used and normally points to null data, on three exceptions does it share its pointer with the second entry. Sonic 2 has a single word-sized pointer per act, this pointer leading to the combined FG and BG. We'll convert the index to Sonic 2's format, getting rid of the always- (and recently-made-) redundant data.

    Remove the second and third entry of each act's section of the index, leaving only the FG entry. You can change whatever entries that still point to null data, and have them point to a used layout, or just blank out the pointer altogether, though in the latter's case, loading that entry will lead to a crash. You are free to delete the many 'Level_XXXbg's and null 'byte_XXXXX's, saving even more room in ROM.

    With that, you're done. Save and build.

    But wait!


    Fixing compatibility with SonLVL (credit to MainMemory for fix)
    Because our layout format is not exactly Sonic 1 or Sonic 2, no level editors support it! Here are the directions for regaining compatibility with SonLVL, the only editor I'm aware of that's capable of this.

    Inside your 'SonLVL INI Files' folder, create a blank file with the name "CustomLayout.cs", to keep things simple, and open it in a text editor. Inside, paste this, and save the file:

    Code (Text):
    1. namespace SonicRetro.SonLVL.API.S2
    2. {
    3.     public class CustomLayout : SonicRetro.SonLVL.API.S2.Layout
    4.     {
    5.         public override void ReadLayout(byte[] rawdata, LayoutData layout)
    6.         {
    7.             layout.FGLayout = new byte[DefaultSize.Width, DefaultSize.Height];
    8.             layout.BGLayout = new byte[DefaultSize.Width, DefaultSize.Height];
    9.         layout.FGLoop = new bool[DefaultSize.Width, DefaultSize.Height];
    10.         layout.BGLoop = new bool[DefaultSize.Width, DefaultSize.Height];
    11.             int c = 0;
    12.             for (int lr = 0; lr < DefaultSize.Height; lr++)
    13.             {
    14.                 for (int lc = 0; lc < DefaultSize.Width; lc++)
    15.         {
    16.                     layout.FGLayout[lc, lr] = (byte)(rawdata[c] & 0x7F);
    17.             layout.FGLoop[lc, lr] = (rawdata[c++] & 0x80) == 0x80;
    18.         }
    19.                 for (int lc = 0; lc < DefaultSize.Width; lc++)
    20.         {
    21.                     layout.BGLayout[lc, lr] = (byte)(rawdata[c] & 0x7F);
    22.             layout.BGLoop[lc, lr] = (rawdata[c++] & 0x80) == 0x80;
    23.         }
    24.             }
    25.     }
    26.  
    27.         public override void WriteLayout(LayoutData layout, out byte[] rawdata)
    28.         {
    29.             rawdata = new byte[(DefaultSize.Width * DefaultSize.Height) * 2];
    30.             int c = 0;
    31.             for (int lr = 0; lr < DefaultSize.Height; lr++)
    32.             {
    33.                 for (int lc = 0; lc < DefaultSize.Width; lc++)
    34.                     rawdata[c++] = (byte)(layout.FGLayout[lc, lr] | (layout.FGLoop[lc, lr] ? 0x80 : 0));
    35.                 for (int lc = 0; lc < DefaultSize.Width; lc++)
    36.                     rawdata[c++] = (byte)(layout.BGLayout[lc, lr] | (layout.BGLoop[lc, lr] ? 0x80 : 0));
    37.             }
    38.         }
    39.  
    40.     public override bool HasLoopFlag { get { return true; } }
    41.  
    42.         public override System.Drawing.Size MaxSize { get { return new System.Drawing.Size(64, 8); } }
    43.     }
    44. }
    This goes all the way back to Section 4; our custom layout is almost one-for-one identical to Sonic 2's layout format, safe for three features: any editors that support Sonic 2's layout format expect the rows to be 128 bytes in length and for there to be 16 of them. This is not the case with Sonic 1's system, which has a row length of 64 and a row total of 8. Also, we're still using S1's 'looping chunk' system, which Sonic 2 lacks. Here, these parameters are changed to reflect this, while otherwise treating the layouts as Sonic 2's.

    With CustomLayout.cs done, create a new .ini file with the name SonLVL.user.ini or S1LVL.rev01.user.ini, depending on which .ini you use. Inside, paste these global setting:

    Code (Text):
    1. layoutfmt=Custom
    2. layoutcodefile=CustomLayout.cs
    3. layoutcodetype=CustomLayout.CustomLayout
    But that's not all, each act is still configured to use two layout files! Open your SonLVL.ini or S1LVL.rev01.ini and copy across all act definitions to the end of your user.ini, and replace the data inside each with this single line:

    Code (Text):
    1. layout=../levels/XXXX.bin
    With the XXXX being the layout of the act. You should have something like this:

    Code (Text):
    1. layoutcodetype=CustomLayout.CustomLayout
    2.  
    3. [Green Hill Zone Act 1]
    4. layout=../levels/ghz1.bin
    5. [Green Hill Zone Act 2]
    6. layout=../levels/ghz2.bin
    7. [Green Hill Zone Act 3]
    8. layout=../levels/ghz3.bin
    9. [Marble Zone Act 1]
    10. layout=../levels/mz1.bin
    11. ...
    When using SonLVL, don't open the user.ini, use the normal INI.
     
  2. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Specifying layoutcmp is unnecessary, as the SonicRetro.SonLVL.API.S2.Layout class your CustomLayout class inherits from has Kosinski set as the default.

    Furthermore, I recommend making a new INI file SonLVL.user.ini/S1LVL.rev01.user.ini with all the changed lines, like:
    Code (Text):
    1. layoutfmt=Custom
    2. layoutcodefile=CustomLayout.cs
    3. layoutcodetype=CustomLayout.CustomLayout
    4. [Green Hill Zone Act 1]
    5. layout=../levels/ghz1.bin
    6. [Green Hill Zone Act 2]
    7. layout=../levels/ghz2.bin
    And so on for the other levels. That way you can safely download and install new versions of the INI files while retaining all your customizations.
     
  3. The SonLVL layout .cs file isn't working, something about a compile error?
     
  4. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Can you post what the error is?
     
  5. Code (Text):
    1. Program: SonLVL
    2. Build Date: 08/29/2021 14:59:10
    3. OS Version: Microsoft Windows NT 6.2.9200.0
    4. Log:
    5. Operating system: Microsoft Windows NT 6.2.9200.0
    6. Opening INI file "C:\Users\alex.field4\Desktop\_BACKUP\s1\SonLVL INI Files\SonLVL.ini"...
    7. Game type is S1.
    8. Loading Green Hill Zone Act 1...
    9. Loading 8x8 tiles from file "../artnem/8x8 - GHZ1.bin", using compression Nemesis...
    10. Loading 16x16 blocks from file "../map16/GHZ.bin", using compression Enigma...
    11. Loading 256x256 chunks from file "../map256/GHZ.bin", using compression Kosinski...
    12. Loading type CustomLayout.CustomLayout from "CustomLayout.cs"...
    13. Compiling code file...
    14. Loading 8x8 tiles from file "../artnem/8x8 - GHZ2.bin", using compression Nemesis...
    15. Loading palette file "../palette/Sonic.bin"...
    16. Source: 0 Destination: 0 Length: 16
    17. Loading palette file "../palette/Green Hill Zone.bin"...
    18. Source: 0 Destination: 16 Length: 48
    19. Compile failed.
    20. Errors:
    21. error CS0042: Unexpected error creating debug information file 'c:\Users\alex.field4\Desktop\_BACKUP\s1\SonLVL INI Files\dllcache\CustomLayout.CustomLayout.PDB' -- 'c:\Users\alex.field4\Desktop\_BACKUP\s1\SonLVL INI Files\dllcache\CustomLayout.CustomLayout.pdb: The system cannot find the path specified.
    22.  
    23. System.AggregateException: One or more errors occurred. ---> System.Exception: Failed compiling file.
    24.    at SonicRetro.SonLVL.API.LevelData.CompileCodeFile[T](String codefile, String typename) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 933
    25.    at SonicRetro.SonLVL.API.LevelData.LoadLevelLayout(String levelname) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 619
    26.    at SonicRetro.SonLVL.API.LevelData.<>c__DisplayClass70_0.<LoadLevel>b__0() in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 179
    27.    at System.Threading.Tasks.Task.InnerInvoke()
    28.    at System.Threading.Tasks.Task.Execute()
    29.    --- End of inner exception stack trace ---
    30.    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
    31.    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout)
    32.    at System.Threading.Tasks.Parallel.Invoke(ParallelOptions parallelOptions, Action[] actions)
    33.    at System.Threading.Tasks.Parallel.Invoke(Action[] actions)
    34.    at SonicRetro.SonLVL.API.LevelData.LoadLevel(String levelname, Boolean loadGraphics) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 179
    35.    at SonicRetro.SonLVL.GUI.MainForm.backgroundLevelLoader_DoWork(Object sender, DoWorkEventArgs e) in D:\Programs\SonLVL\SonLVL\MainForm.cs:line 574
    36. ---> (Inner Exception #0) System.Exception: Failed compiling file.
    37.    at SonicRetro.SonLVL.API.LevelData.CompileCodeFile[T](String codefile, String typename) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 933
    38.    at SonicRetro.SonLVL.API.LevelData.LoadLevelLayout(String levelname) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 619
    39.    at SonicRetro.SonLVL.API.LevelData.<>c__DisplayClass70_0.<LoadLevel>b__0() in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 179
    40.    at System.Threading.Tasks.Task.InnerInvoke()
    41.    at System.Threading.Tasks.Task.Execute()<---
    42.  
     
  6. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Right, I just put out an update to fix that exact problem.
     
  7. Pretty sure the .cs file Clownacy provided is using an outdated format, since it's still giving me a compiler error:
    Code (Text):
    1. Program: SonLVL
    2. Build Date: 10/12/2021 18:23:18
    3. OS Version: Microsoft Windows NT 6.2.9200.0
    4. Log:
    5. Operating system: Microsoft Windows NT 6.2.9200.0
    6. Opening INI file "C:\Users\alex.field4\Desktop\_BACKUP\s1\SonLVL INI Files\SonLVL.ini"...
    7. Game type is S1.
    8. Loading Green Hill Zone Act 1...
    9. Loading 8x8 tiles from file "../artnem/8x8 - GHZ1.bin", using compression Nemesis...
    10. Loading 16x16 blocks from file "../map16/GHZ.bin", using compression Enigma...
    11. Loading 256x256 chunks from file "../map256/GHZ.bin", using compression Kosinski...
    12. Loading 8x8 tiles from file "../artnem/8x8 - GHZ2.bin", using compression Nemesis...
    13. Loading type CustomLayout.CustomLayout from "CustomLayout.cs"...
    14. Loading palette file "../palette/Sonic.bin"...
    15. Source: 0 Destination: 0 Length: 16
    16. Compiling code file...
    17. Loading palette file "../palette/Green Hill Zone.bin"...
    18. Source: 0 Destination: 16 Length: 48
    19. Compile failed.
    20. Errors:
    21. c:\...\SonLVL INI Files\CustomLayout.cs(7,31) : error CS0029: Cannot implicitly convert type 'byte[*,*]' to 'ushort[*,*]'
    22. c:\...\SonLVL INI Files\CustomLayout.cs(8,31) : error CS0029: Cannot implicitly convert type 'byte[*,*]' to 'ushort[*,*]'
    23.  
    24. System.AggregateException: One or more errors occurred. ---> System.Exception: Failed compiling file.
    25.    at SonicRetro.SonLVL.API.LevelData.CompileCodeFile[T](String codefile, String typename) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 933
    26.    at SonicRetro.SonLVL.API.LevelData.LoadLevelLayout(String levelname) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 619
    27.    at SonicRetro.SonLVL.API.LevelData.<>c__DisplayClass70_0.<LoadLevel>b__0() in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 184
    28.    at System.Threading.Tasks.Task.InnerInvoke()
    29.    at System.Threading.Tasks.Task.Execute()
    30.    --- End of inner exception stack trace ---
    31.    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
    32.    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout)
    33.    at System.Threading.Tasks.Parallel.Invoke(ParallelOptions parallelOptions, Action[] actions)
    34.    at System.Threading.Tasks.Parallel.Invoke(Action[] actions)
    35.    at SonicRetro.SonLVL.API.LevelData.LoadLevel(String levelname, Boolean loadGraphics) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 184
    36.    at SonicRetro.SonLVL.GUI.MainForm.backgroundLevelLoader_DoWork(Object sender, DoWorkEventArgs e) in D:\Programs\SonLVL\SonLVL\MainForm.cs:line 574
    37. ---> (Inner Exception #0) System.Exception: Failed compiling file.
    38.    at SonicRetro.SonLVL.API.LevelData.CompileCodeFile[T](String codefile, String typename) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 933
    39.    at SonicRetro.SonLVL.API.LevelData.LoadLevelLayout(String levelname) in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 619
    40.    at SonicRetro.SonLVL.API.LevelData.<>c__DisplayClass70_0.<LoadLevel>b__0() in D:\Programs\SonLVL\SonLVLAPI\LevelData.cs:line 184
    41.    at System.Threading.Tasks.Task.InnerInvoke()
    42.    at System.Threading.Tasks.Task.Execute()<---
     
  8. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Change "byte" to "ushort" in the first two lines of ReadLayout, like so:
    Code (Text):
    1.             layout.FGLayout = new ushort[DefaultSize.Width, DefaultSize.Height];
    2.             layout.BGLayout = new ushort[DefaultSize.Width, DefaultSize.Height];