Actions

SCHG How-to

Difference between revisions of "Set up a Sonic 1 split disassembly"

From Sonic Retro

m
m (Text replacement - "\[\[Category:SCHG How-tos.*" to "")
 
(18 intermediate revisions by 8 users not shown)
Line 1: Line 1:
{{GuideBy|FraGag}}
+
''(Original guide by [[User:Qjimbo|Qjimbo]] / [[The Glowing Bridge]])''
''(Updated by [[andlabs]] 24-25 July 2009 to the SVN disassembly.)''
+
==Introduction and Overview==
 +
So, you want to modify Sonic 1? The first step for most people might be to download SonED1, or ESE II, and edit the ROM directly. However there is another way of ROM editing, called using a '''split [[disassembly]]'''. Here we are going to cover the basics of using a disassembly, and allow you the user to understand how they work, so you can get modifying Sonic 1!
  
In ''[[Sonic the Hedgehog (16-bit)|Sonic 1]]'', there are technically 7 zones (not counting the special stage):
+
==Files and Useful Tools==
 +
* [https://github.com/sonicretro/s1disasm_git/archive/master.zip Sonic 1 Git Disassembly] Standard ASM68K version
 +
: For rebuilding the ROM from its split form.
 +
* [[:Image:SonED2_v11-05-19.rar|Stealth's SonED2]]
 +
: For creating and editing the levels (a guide for SonLVL, another level editor, is available [[SCHG How-to:Set Up SonLVL|here]]).
  
* 00: [[Green Hill Zone]]
+
==Setting up the Project==
* 01: [[Labyrinth Zone]]
 
* 02: [[Marble Zone]]
 
* 03: [[Star Light Zone]]
 
* 04: [[Spring Yard Zone]]
 
* 05: [[Scrap Brain Zone]]
 
* 06: Ending sequence
 
  
This guide will help you to add a new zone (which I'll name Alpha Beta Zone (ABZ) in this guide) as zone 07 using [http://svn.sonicretro.org/wsvn/CommunityDisassemblies/Sonic%201%20Disassembly/?op=dl&rev=0&isdir=1 the Sonic Retro SVN disassembly]. Remember to change ABZ to the name of your zone(s) in the given examples. Additional zones could be added by following these steps again. Some steps only need to be done once to support up to 256 zones; those steps are clearly identified as such.
+
:1. Open the SonED2 rar file and extract it into a folder. I'll use '''''C:\SonED2-1\''''' (For this you need [http://www.rarsoft.com/ WinRAR])
  
Note that it is important to follow all these steps before attempting to try running the game, unless you entirely removed the feature related to the step you're about to follow. Otherwise, the game could try to load the wrong data or run invalid code.
+
:2. Now open the zip file containing the Git Sonic 1 disassembly and extract it to a subdirectory inside your chosen folder. In my case, this will be  '''''C:\SonED2-1\s1h\''''', and with that you're ready to go! The SonED2 project files can be found under '''''C:\SonED2-1\s1h\SonED2 Projects'''''.
  
== Palette cycling routine ==
+
==Advantages over hex editing==
:''File: _inc\PaletteCycle.asm''
+
* One of the most common problems you run into with hex editing is when something doesn't fit, and has to be put at the end of the ROM, and all the pointers changed. This is incredibly wasteful. With a split disassembly everything is kept organized and pointers automatically change during the assembling process, so you can, for example, replace art with a larger file, assemble the ROM and everything will be adjusted accordingly.
The PaletteCycle routine is used to dynamically replace some colors in the palette at predefined intervals. The routine uses an offset table PCycle_Index to run the code corresponding to the zone the player is currently playing. You must add a routine (although it can be empty) for each level you add to the game, otherwise strange things will happen. Here is what PCycle_Index should look like after adding one zone:
 
<asm>PCycle_Index: dc.w PCycle_GHZ-PCycle_Index
 
  
dc.w PCycle_LZ-PCycle_Index
+
* As everything is in separate files, there is no extracting -> decompressing -> editing -> recompressing -> inserting into the ROM. All you have to do are the middle three steps, sometimes only the editing if files are compressed during the build.
  
dc.w PCycle_MZ-PCycle_Index
+
* Editing the code. While this might seem awkward, it's amazing how far a little bit of patience can go in terms of teaching yourself the basics of assembly. Editing in hex makes code editing a lot harder.
  
dc.w PalCycle_SLZ-PCycle_Index
+
==Possibilities==
 +
The possibilities are only as limited as the hardware and your imagination. You can edit the code of the game, the artwork, the level layouts, the music, anything! All these parts of the game are labelled and in separate files. This is why I believe more people should be aware of the advantages of using a split disassembly, even if they plan not to even touch the assembly code.
  
dc.w PalCycle_SYZ-PCycle_Index
+
{{S1Howtos}}
 
+
|{{PAGENAME}}]]
dc.w PalCycle_SBZ-PCycle_Index
 
 
 
dc.w PCycle_GHZ-PCycle_Index
 
dc.w PCycle_ABZ-PCycle_Index</asm>
 
You will probably want to replace ABZ with what corresponds to the name of your new zone.
 
 
 
Once you've added the zone to the offset table, we must define the routine. If you want no cycling palette, you can use this routine:
 
<asm>; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
 
 
 
 
 
PalCycle_ABZ: ; XREF: PalCycle
 
rts
 
; End of function PalCycle_ABZ</asm>
 
Put this code close to the offset table, either right after the table, or after the end of PalCycle_SBZ (at the end of the file).
 
 
 
== Collision pointers ==
 
:''File: sonic.asm''
 
The new zone needs collision data. You may already have this data if you used SonED2. To do so, just add an entry to ColPointers. If this is the first zone you add, you will also need to define collision for the ending sequence "zone", as the collision is usually loaded elsewhere for the ending sequence. For the ending sequence, the collision data from Green Hill Zone will do. If you used collision data from another zone for your own, you can point to that zone's collision data directly; otherwise, you must point to your own data and incbin that data.
 
 
 
Here is what the collision array should look like with one additional zone, assuming the zone uses independent collision data:
 
<asm>; ---------------------------------------------------------------------------
 
 
 
; Collision index pointers
 
 
 
; ---------------------------------------------------------------------------
 
 
 
ColPointers: dc.l Col_GHZ
 
 
 
dc.l Col_LZ
 
 
 
dc.l Col_MZ
 
 
 
dc.l Col_SLZ
 
 
 
dc.l Col_SYZ
 
 
 
dc.l Col_SBZ
 
dc.l Col_ABZ</asm>
 
If you used independant collision data, you must also include it in your ROM. If want to keep your disassembly organized, you would include it where the rest of the collision data is defined. Find Col_SBZ, and after the even under it, add something like the following:
 
<asm>Col_ABZ: incbin "collide\abz.bin" ; ABZ index
 
even</asm>
 
 
 
== Level size array ==
 
The level size array is used to set the level boundaries and to limit the camera movement when initially loading a level. The data is located in '''misc\Level Size Array.bin'''. Each act has 12 ($C) bytes and since the game reserves 4 acts for each zone for simplicity's sake, you will need to add 48 ($30) bytes for a zone. Look at the values used for the other zones to determine what works, and make sure to test it in the game.
 
 
 
== Start location array ==
 
This array is used to position Sonic when starting a level, or restarting a level when a lamppost has not been hit yet. The data is located in '''misc\Start Location Array - Levels.bin'''. Each act has 4 bytes: 2 bytes for the X position and 2 bytes for the Y position.
 
 
 
== Unknown data related to the level size ==
 
:''Files: _inc\LevelSizeLoad & BgScrollSpeed.asm and _inc\LevelSizeLoad & BgScrollSpeed (JP1).asm''
 
There is some unknown data that is placed in RAM, which seems to be mostly unused. For the sake of completeness, we're going to add data for each new zone. The data is located at dword_61B4. By looking at the data itself, we can see that every zone has the same data, except for Green Hill Zone and the ending (which uses GHZ art). Let's use the one that appears the most often:
 
<asm>dword_61B4: dc.l $700100, $1000100
 
dc.l $8000100, $1000000
 
dc.l $8000100, $1000000
 
dc.l $8000100, $1000000
 
dc.l $8000100, $1000000
 
dc.l $8000100, $1000000
 
dc.l $700100, $1000100
 
dc.l $8000100, $1000000</asm>
 
 
 
== Background scrolling speed ==
 
:''Files: _inc\LevelSizeLoad & BgScrollSpeed.asm and _inc\LevelSizeLoad & BgScrollSpeed (JP1).asm''
 
This subroutine is used to set the scroll speed of some backgrounds. If your background isn't too fancy, you can just use an empty routine. First, edit the offset table like this:
 
<asm>BgScroll_Index: dc.w BgScroll_GHZ-BgScroll_Index, BgScroll_LZ-BgScroll_Index
 
 
 
dc.w BgScroll_MZ-BgScroll_Index, BgScroll_SLZ-BgScroll_Index
 
 
 
dc.w BgScroll_SYZ-BgScroll_Index, BgScroll_SBZ-BgScroll_Index
 
 
 
dc.w BgScroll_End-BgScroll_Index, BgScroll_ABZ-BgScroll_Index</asm>
 
Then, add an empty routine after the routine for the ending.
 
<asm>; ===========================================================================
 
 
 
BgScroll_ABZ: ; XREF: BgScroll_Index
 
rts </asm>
 
 
 
== Background layer deformation ==
 
:''Files: _inc\DeformLayers.asm and _inc\DeformLayers (JP1).asm''
 
When moving in the level, the background lines sometimes scroll at different speeds. This is controlled by the background layer deformation routines. If you just want a plain scrolling background, you can use the routine for Labyrinth Zone. Otherwise, you'll have to program your own routine.
 
 
 
Here's what Deform_Index should look like:
 
<asm>Deform_Index: dc.w Deform_GHZ-Deform_Index, Deform_LZ-Deform_Index
 
dc.w Deform_MZ-Deform_Index, Deform_SLZ-Deform_Index
 
dc.w Deform_SYZ-Deform_Index, Deform_SBZ-Deform_Index
 
dc.w Deform_GHZ-Deform_Index, Deform_ABZ-Deform_Index</asm>
 
 
 
== Dynamic screen resizing (and other level events) ==
 
:''File: _inc\DynamicLevelEvents.asm''
 
This set of routines controls the camera and level boundaries when moving around in the level. They also create the boss objects in act 3. Add an entry for your new zone:
 
<asm>; ---------------------------------------------------------------------------
 
 
 
; Offset index for dynamic level events
 
 
 
; ---------------------------------------------------------------------------
 
 
 
DLE_Index: dc.w DLE_GHZ-DLE_Index, DLE_LZ-DLE_Index
 
 
 
dc.w DLE_MZ-DLE_Index, DLE_SLZ-DLE_Index
 
 
 
dc.w DLE_SYZ-DLE_Index, DLE_SBZ-DLE_Index
 
 
 
dc.w DLE_Ending-DLE_Index, dc.w DLE_ABZ-DLE_Index</asm>
 
Then add the routine itself. If you have absolutely no need for dynamic screen resizing, you can simply create an empty routine:
 
<asm>; ===========================================================================
 
; ---------------------------------------------------------------------------
 
; Alpha Beta Zone sequence dynamic screen resizing (empty)
 
; ---------------------------------------------------------------------------
 
 
 
DLE_ABZ: ; XREF: DLE_Index
 
rts</asm>
 
If you need dynamic screen resizing (or think you might eventually), you can take this as a base to have separate code for each act:
 
<asm>; ===========================================================================
 
; ---------------------------------------------------------------------------
 
; Alpha Beta Zone sequence dynamic screen resizing (empty)
 
; ---------------------------------------------------------------------------
 
 
 
DLE_ABZ: ; XREF: DLEIndex
 
moveq #0,d0
 
move.b ($FFFFFE11).w,d0
 
add.w d0,d0
 
move.w Resize_ABZx(pc,d0.w),d0
 
jmp Resize_ABZx(pc,d0.w)
 
; ===========================================================================
 
Resize_ABZx: dc.w Resize_ABZ1-Resize_SBZx
 
dc.w Resize_ABZ2-Resize_SBZx
 
dc.w Resize_ABZ3-Resize_SBZx
 
; ===========================================================================
 
Resize_ABZ1:
 
rts
 
; ===========================================================================
 
Resize_ABZ2:
 
rts
 
; ===========================================================================
 
Resize_ABZ3:
 
rts
 
; ===========================================================================</asm>
 
 
 
== Zone title card ==
 
=== First-time fix ===
 
:''File: _incObj\34 Title Cards.asm''
 
You probably want to give your zone a cool name too, huh? Before you add new zones, you'll need to edit the title card object a little, because it's made to support only 6 zones. This only needs to be done once, and will support any number of zones (up to 256).
 
 
 
First, go to Card_CheckFZ and replace this:
 
<asm> Card_CheckFZ:
 
 
 
move.w d0,d2
 
 
 
cmpi.w #(id_SBZ<<8)+2,(v_zone).w ; check if level is FZ
 
 
 
bne.s Card_LoadConfig
 
 
 
moveq #6,d0 ; load title card number 6 (FZ)
 
 
 
moveq #$B,d2 ; use "FINAL" mappings</asm>
 
with this:
 
<asm> Card_CheckFZ:
 
 
 
move.w d0,d2
 
cmpi.w #$502,(v_zone).w ; check if level is FZ
 
bne.s Card_CheckNew
 
moveq #6,d0 ; load title card number 6 (FZ)
 
moveq #$B,d2 ; use "FINAL" mappings
 
 
 
Card_CheckNew:
 
cmpi.b #7,(v_zone).w ; check if level is in the new zones
 
blo.s Card_LoadConfig
 
addq.b #$C-7,d2 ; use correct mappings</asm>
 
=== Zone-specific data ===
 
:''File: sonic.asm''
 
When that's done, you need to add the actual data for your new zone. This section might be useful to edit the title card: [[SCHG:Sonic_the_Hedgehog/Text Editing#Level Title Cards]]. You should put the data for your new zone at the end of the mappings, i.e. after byte_CB8A, just before the even. You'll also need to add a pointer to your new mapping at Map_Card:
 
<asm>Map_Card: dc.w M_Card_GHZ-Map_Card
 
 
 
dc.w M_Card_LZ-Map_Card
 
 
 
dc.w M_Card_MZ-Map_Card
 
 
 
dc.w M_Card_SLZ-Map_Card
 
 
 
dc.w M_Card_SYZ-Map_Card
 
 
 
dc.w M_Card_SBZ-Map_Card
 
 
 
dc.w M_Card_Zone-Map_Card
 
 
 
dc.w M_Card_Act1-Map_Card
 
 
 
dc.w M_Card_Act2-Map_Card
 
 
 
dc.w M_Card_Act3-Map_Card
 
 
 
dc.w M_Card_Oval-Map_Card
 
 
 
dc.w M_Card_FZ-Map_Card
 
dc.w M_Card_ABZ-Map_Card</asm>
 
This is what the mapping for Alpha Beta Zone looks like:
 
<asm>M_Card_ABZ: dc.b 9
 
dc.b $F8, 5, 0, 0, $B0 ; A
 
dc.b $F8, 5, 0, $26, $C0 ; L
 
dc.b $F8, 5, 0, $36, $D0 ; P
 
dc.b $F8, 5, 0, $1C, $E0 ; H
 
dc.b $F8, 5, 0, 0, $F0 ; A
 
dc.b $F8, 5, 0, 4, $10 ; B
 
dc.b $F8, 5, 0, $10, $20 ; E
 
dc.b $F8, 5, 0, $42, $30 ; T
 
dc.b $F8, 5, 0, 0, $40 ; A</asm>
 
:''File: _incObj\34 Title Cards.asm''
 
You will also need to position the parts of the title card manually in Card_ConData. Add a line like this at the end of the table
 
<asm> dc.w 0, $120, $FF04, $144, $41C, $15C, $21C, $15C ; ABZ</asm>
 
 
 
== Level order ==
 
The level order tells which level to load when an act has been completed. The data is stored in '''misc\Level Order.bin'''. Each act takes 2 bytes (the zone and the act), so each zone takes 8 bytes. Again, there is no data for the ending zone, so add 8 "00" bytes at the end of the file, then add the zone and act the game should bring the player to when the level is complete.
 
 
 
Don't understand how it works? Let's take an example: let's say you just completed Marble Zone act 2. The game needs to know what level it will load next. Since Marble Zone is zone 02, skip 2 * 8 bytes, i.e. 16 or $10 bytes. Then, skip 2 more bytes for act 1. At 00000012, there's "02 02", which means that zone 02 (Marble Zone) act 02 (act 3 — remember that the zone and act numbers are zero-based) will be loaded.
 
 
 
If you want to incorporate your new zone in the level flow, you'll need to edit at least one of the values to point to your new zone, then at the end of your zone, point back to one of the existing zones (or yet another new zone).
 
 
 
== Music playlists ==
 
:''(Mostly rewritten by andlabs.) File: sonic.asm''
 
Sonic 1 has 2 music playlists: one to play music when loading the level and one to resume it when invincibility wears off. For the first list, just place the pointer to the music — we'll add that later — at the end of the list:
 
<asm>; ---------------------------------------------------------------------------
 
 
 
; Music playlist
 
 
 
; ---------------------------------------------------------------------------
 
 
 
MusicList: dc.b bgm_GHZ, bgm_LZ, bgm_MZ, bgm_SLZ, bgm_SYZ, bgm_SBZ, bgm_FZ, bgm_ABS
 
 
 
even</asm>
 
Do the same for the second list:
 
<asm>; ---------------------------------------------------------------------------
 
 
 
; Music to play after invincibility wears off
 
 
 
; ---------------------------------------------------------------------------
 
 
 
MusicList2: dc.b bgm_GHZ, bgm_LZ, bgm_MZ, bgm_SLZ, bgm_SYZ, bgm_SBZ, bgm_ABZ
 
 
 
even</asm>
 
 
 
== Animated level graphics ==
 
:''File: _inc\AnimateLevelGfx.asm''
 
Some zones have animated art in the foreground or in the background. This is handled by AnimateLevelGfx. The zones that don't have any animated art use AniArt_none, and to keep things simple, this is what I will do for ABZ:
 
<asm>AniArt_Index: dc.w AniArt_GHZ-AniArt_Index, AniArt_none-AniArt_Index
 
 
 
dc.w AniArt_MZ-AniArt_Index, AniArt_none-AniArt_Index
 
 
 
dc.w AniArt_none-AniArt_Index, AniArt_SBZ-AniArt_Index
 
 
 
dc.w AniArt_Ending-AniArt_Index, AniArt_none-AniArt_Index</asm>
 
 
 
== Object debug list ==
 
:''File: _inc\DebugList.asm''
 
If you didn't remove debug mode from your hack, you must add an object debug list to your zone. First, add a pointer to the new list for your zone:
 
<asm>DebugList:
 
 
 
dc.w @GHZ-DebugList
 
 
 
dc.w @LZ-DebugList
 
 
 
dc.w @MZ-DebugList
 
 
 
dc.w @SLZ-DebugList
 
 
 
dc.w @SYZ-DebugList
 
 
 
dc.w @SBZ-DebugList
 
 
 
dc.w @Ending-DebugList
 
dc.w @ABZ-DebugList</asm>
 
Then, add the new list to the ASM file:
 
<asm>@ABC:
 
 
 
dc.w (@ABZend-@ABZ-2)/8
 
 
 
 
 
dbug Map_Ring, id_Rings, 0, 0, $27$B2
 
dbug Map_Monitor, id_Monitor, 0, 0, $680
 
 
 
dbug Map_Lamp, id_Lamppost, 1, 0, $7A0
 
@ABCend:</asm>
 
 
 
== Pattern load cues (zone and animals) ==
 
=== Zone-specific data ===
 
 
 
:''File: _inc\Pattern Load Cues.asm''
 
 
 
The pattern load cues are used to load art to be displayed on the screen. The way the PLCs are arranged in Sonic 1, there will be some problems with the animal PLCs, but the zone PLCs are retrieved from the main level load blocks, so no special treatment is required for those.
 
 
 
First, add the PLCs for your new zone at the end of the table at the beginning:
 
<asm>ptr_PLC_ABZ: dc.w PLC_ABZ-ArtLodCues</asm>
 
Then, add the ID of the PLC into the index list at the end of the file:
 
<asm>plcid_ABZ: equ (ptr_PLC_ABZ-ArtLodCues)/2 ; $20</asm>
 
 
 
If you have added other PLCs before this, you may need to adjust some indices in the code below. If you declare your PLCs in another way, the code in this section won't work. The rest of this section only needs to be followed once.
 
=== First-time fix ===
 
:''File: sonic.asm''
 
Now, since the animals PLCs are scattered across the table, we are going to use this new subroutine to load the correct PLC. Put it just before AddPLC:
 
<asm>; ---------------------------------------------------------------------------
 
; Subroutine to load the art for the animals for the current zone
 
; ---------------------------------------------------------------------------
 
 
 
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
 
 
 
 
 
LoadAnimalPLC:
 
moveq #0,d0
 
move.b (v_level).w,d0
 
cmpi.w #7,d0
 
bhs.s LoadAnimalPLC_New
 
addi.w #$15,d0
 
bra.s AddPLC
 
; ---------------------------------------------------------------------------
 
 
 
LoadAnimalPLC_New:
 
subi.w #7,d0
 
; multiply d0 by 3
 
move.w d0,d1
 
add.w d0,d0
 
add.w d1,d0
 
; add $22 (this is the index of the animal PLC for the first added zone)
 
addi.w #$22,d0
 
; bra.s LoadPLC
 
; End of function LoadAnimalPLC</asm>
 
Now that we have this subroutine, we can replace the two instances in the code that load the animal graphics. Go to Level_ClrCardArt and replace this:
 
<asm>Level_ClrCardArt:
 
 
 
moveq #plcid_Explode,d0
 
 
 
jsr (AddPLC).l ; load explosion gfx
 
 
 
moveq #0,d0
 
 
 
move.b (v_zone).w,d0
 
 
 
addi.w #plcid_GHZAnimals,d0
 
 
 
jsr (AddPLC).l ; load animal gfx (level no. + $15)</asm>
 
with this:
 
<asm>Level_ClrCardArt:
 
moveq #plcid_Explode,d0
 
jsr (AddPLC).l ; load explosion patterns
 
jsr (LoadAnimalPLC).l ; load animal patterns</asm>
 
:''File: _incObj\34 Title Cards.asm''
 
Then, go to Card_ChangeArt and replace this:
 
<asm>Card_ChangeArt: ; XREF: Card_ChkPos2
 
 
 
cmpi.b #4,obRoutine(a0)
 
 
 
bne.s Card_Delete
 
 
 
moveq #plcid_Explode,d0
 
 
 
jsr (AddPLC).l ; load explosion patterns
 
 
 
moveq #0,d0
 
 
 
move.b (v_zone).w,d0
 
 
 
addi.w #plcid_GHZAnimals,d0
 
 
 
jsr (AddPLC).l ; load animal patterns</asm>
 
with this:
 
<asm>Card_ChangeArt: ; XREF: Obj34_ChkPos2
 
cmpi.b #4,obRoutine(a0)
 
bne.s Card_Delete
 
moveq #plcid_Explode,d0
 
jsr (AddPLC).l ; load explosion patterns
 
jsr (LoadAnimalPLC).l ; load animal patterns</asm>
 
 
 
== Zone palette ==
 
:''File: _inc\Palette Pointers.asm''
 
Your new level needs a palette, otherwise it will be all black or full of random colors. Add this line at the end of the PalPointers array but before the "even":
 
<asm>ptr_Pal_ABZ: palp Pal_ABZ,$FB20,$17 ; $14 (20) - ABZ</asm>
 
Add this line to the end of the file:
 
<asm>palid_ABZ: equ (ptr_Pal_ABZ-PalPointers)/8</asm>
 
:''File: sonic.asm''
 
Finally, add this line after Pal_Ending:
 
<asm>Pal_ABZ: incbin "palette\ABZ.bin"</asm>
 
 
 
== Main level load blocks ==
 
:''File: _inc\LevelHeaders.asm"
 
The main level load blocks tell the game what to load for a specific zone. They point to the level patterns, to the 16x16 blocks, to the 256x256 chunks, the 2 pattern load cues used to load the art for the level and the palette used in the level.
 
 
 
By looking at how this data is defined for the existing levels and reading the comments at the beginning of the file, you should be able to figure out how to configure your zone. Here is how ABZ is defined (this goes just before the '''even''' directive):
 
<asm> lhead plcid_ABZ, Nem_ABZ, plcid_ABZ, Blk16_ABZ, Blk256_ABZ, bgm_ABZ, palid_ABZ ; ABZ</asm>
 
:''File: sonic.asm''
 
You must also incbin the data for the zone. Add these lines before Nem_Eggman:
 
<asm>Blk16_ABZ: incbin "map16\ABZ.bin"
 
even
 
Nem_ABZ: incbin "artnem\8x8 - ABZ.bin" ; ABZ primary patterns
 
even
 
Blk256_ABZ: incbin "map256\ABZ.bin"
 
even</asm>
 
 
 
== Level layout index ==
 
:''File: sonic.asm''
 
This is probably one of the first things you'd have thought of, yet we're doing it only now! Still, it must be done, so go to Level_Index. This huge array defines the foreground layout, the background layout and another layout that appears to be unused (most of the time, it is empty). For simplicity, I will assume that you have 3 acts, and that all your acts use the same background (look at Scrap Brain Zone for an example of a zone with different backgrounds).
 
 
 
First, add entries at the end of the table for your new zone, like this (byte_6A320 points to an empty layout):
 
<asm> dc.w Level_ABZ1-Level_Index, Level_ABZbg-Level_Index, byte_6A320-Level_Index
 
dc.w Level_ABZ2-Level_Index, Level_ABZbg-Level_Index, byte_6A320-Level_Index
 
dc.w Level_ABZ3-Level_Index, Level_ABZbg-Level_Index, byte_6A320-Level_Index
 
dc.w byte_6A320-Level_Index, byte_6A320-Level_Index, byte_6A320-Level_Index</asm>
 
Then, incbin the layouts. Add this after byte_6A320:
 
<asm>Level_ABZ1: incbin levels\abz1.bin
 
even
 
Level_ABZ2: incbin levels\abz2.bin
 
even
 
Level_ABZ3: incbin levels\abz3.bin
 
even
 
Level_ABZbg: incbin levels\abzbg.bin
 
even</asm>
 
 
 
== Objects positions index ==
 
=== First-time fix ===
 
:''File: sonic.asm''
 
Your levels would be boring if they didn't have objects, wouldn't they? However, there's a small problem with how the object layouts are organized in the ROM: the platforms on conveyor belts from Labyrinth Zone and Scrap Brain Zone use data that is put in the same data table as the rest of the object layouts, so we can't add data for new zones without breaking them. Fortunately, it's not too hard to work around this issue by editing the way they are referenced. This only needs to be done once.
 
 
 
First, we will add 2 labels to effectively break the table in 3. We will then use those labels in their respective tables to compute the offsets. The end of the table should look like this:
 
<asm>; ...
 
dc.w ObjPos_End-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_End-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_End-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_End-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
 
 
ObjPos_LZxpf_Index:
 
dc.w ObjPos_LZ1pf1-ObjPos_LZxpf_Index, ObjPos_LZ1pf2-ObjPos_LZxpf_Index
 
dc.w ObjPos_LZ2pf1-ObjPos_LZxpf_Index, ObjPos_LZ2pf2-ObjPos_LZxpf_Index
 
dc.w ObjPos_LZ3pf1-ObjPos_LZxpf_Index, ObjPos_LZ3pf2-ObjPos_LZxpf_Index
 
dc.w ObjPos_LZ1pf1-ObjPos_LZxpf_Index, ObjPos_LZ1pf2-ObjPos_LZxpf_Index
 
 
 
ObjPos_SBZ1pf_Index:
 
dc.w ObjPos_SBZ1pf1-ObjPos_SBZ1pf_Index, ObjPos_SBZ1pf2-ObjPos_SBZ1pf_Index
 
dc.w ObjPos_SBZ1pf3-ObjPos_SBZ1pf_Index, ObjPos_SBZ1pf4-ObjPos_SBZ1pf_Index
 
dc.w ObjPos_SBZ1pf5-ObjPos_SBZ1pf_Index, ObjPos_SBZ1pf6-ObjPos_SBZ1pf_Index
 
dc.w ObjPos_SBZ1pf1-ObjPos_SBZ1pf_Index, ObjPos_SBZ1pf2-ObjPos_SBZ1pf_Index</asm>
 
:''File: _incObj\63 LZ Conveyor.asm''
 
Then, we will edit the platform objects to reference the data by using these new labels. Go to loc_12460. Replace these two lines:
 
<asm> addi.w #$70,d0
 
lea (ObjPos_Index).l,a2</asm>
 
with this single line:
 
<asm> lea (ObjPos_LZxpf_Index).l,a2</asm>
 
:''File: _incObj\6F SBZ Spin Platform Conveyor.asm''
 
Now, go to loc_1639A. Similarly, replace these two lines:
 
<asm> addi.w #$80,d0
 
lea (ObjPos_Index).l,a2</asm>
 
with this line:
 
<asm> lea (ObjPos_SBZ1pf_Index).l,a2</asm>
 
=== Zone-specific data ===
 
:''File: sonic.asm''
 
Now that this is done, we can add object layouts for new zones right before ObjPos_LZxpf_Index (without making the platforms behave incorrectly), like this:
 
<asm> dc.w ObjPos_ABZ1-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_ABZ2-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_ABZ3-ObjPos_Index, ObjPos_Null-ObjPos_Index
 
dc.w ObjPos_ABZ1-ObjPos_Index, ObjPos_Null-ObjPos_Index</asm>
 
Then, incbin the object positions:
 
<asm>ObjPos_ABZ1: incbin objpos\abz1.bin
 
even
 
ObjPos_ABZ2: incbin objpos\abz2.bin
 
even
 
ObjPos_ABZ3: incbin objpos\abz3.bin
 
even</asm>
 
 
 
== Build errors ==
 
After applying these steps on a clean Sonic 1 disassembly, I got the following build errors:
 
* Error : Branch (32784 bytes) is out of range (near ESth_Move)
 
* Error : Illegal value (136)
 
:''File: _incObj\89 Ending Sequence STH.asm''
 
The first error points to this line:
 
<asm> bra.w DisplaySprite</asm>
 
Replace '''bra.w''' with '''jmp''' to fix this error. If you've further altered your hack, you may encounter similar "out of range" errors. If that happens, replace '''bra.s''' with '''bra.w''' or '''bra.w''' with '''jmp'''.
 
:''File: _inc\LevelSizeLoad & BgScrollSpeed.asm''
 
The second errors occurs on this line:
 
<asm> move.l LoopTileNums(pc,d0.w),(v_256loop1).w</asm>
 
Since we extended the start location array, the loop tile numbers are a bit too far away. You can fix this quickly by moving these 4 lines just after LevSz_Unk:
 
<asm> moveq #0,d0
 
 
 
move.b (v_zone).w,d0
 
 
 
lsl.b #2,d0
 
 
 
move.l LoopTileNums(pc,d0.w),(v_256loop1).w</asm>
 
 
 
== Conclusion ==
 
[[Image:Additional level example.png|frame|right|Alpha Beta Zone]]
 
If you've applied these steps correctly, you should be able to build your ROM and enjoy your new level. Here's a list of the binary files you need to add to be able to build your ROM:
 
* pallet\abz.bin
 
* map16\abz.bin
 
* artnem\8x8abz.bin
 
* map256\abz.bin
 
* collide\abz.bin
 
* levels\abz1.bin
 
* levels\abz2.bin
 
* levels\abz3.bin
 
* levels\abzbg.bin
 
* objpos\abz1.bin
 
* objpos\abz2.bin
 
* objpos\abz3.bin
 
If you didn't do so yet, you may want to follow Puto's guide to [[SCHG How-to:Fix the SEGA Sound|fix the SEGA sound]].
 
 
 
[[Category:SCHG How-tos|Add a new zone in Sonic 1]]
 

Latest revision as of 10:51, 25 August 2018

(Original guide by Qjimbo / The Glowing Bridge)

Introduction and Overview

So, you want to modify Sonic 1? The first step for most people might be to download SonED1, or ESE II, and edit the ROM directly. However there is another way of ROM editing, called using a split disassembly. Here we are going to cover the basics of using a disassembly, and allow you the user to understand how they work, so you can get modifying Sonic 1!

Files and Useful Tools

For rebuilding the ROM from its split form.
For creating and editing the levels (a guide for SonLVL, another level editor, is available here).

Setting up the Project

1. Open the SonED2 rar file and extract it into a folder. I'll use C:\SonED2-1\ (For this you need WinRAR)
2. Now open the zip file containing the Git Sonic 1 disassembly and extract it to a subdirectory inside your chosen folder. In my case, this will be C:\SonED2-1\s1h\, and with that you're ready to go! The SonED2 project files can be found under C:\SonED2-1\s1h\SonED2 Projects.

Advantages over hex editing

  • One of the most common problems you run into with hex editing is when something doesn't fit, and has to be put at the end of the ROM, and all the pointers changed. This is incredibly wasteful. With a split disassembly everything is kept organized and pointers automatically change during the assembling process, so you can, for example, replace art with a larger file, assemble the ROM and everything will be adjusted accordingly.
  • As everything is in separate files, there is no extracting -> decompressing -> editing -> recompressing -> inserting into the ROM. All you have to do are the middle three steps, sometimes only the editing if files are compressed during the build.
  • Editing the code. While this might seem awkward, it's amazing how far a little bit of patience can go in terms of teaching yourself the basics of assembly. Editing in hex makes code editing a lot harder.

Possibilities

The possibilities are only as limited as the hardware and your imagination. You can edit the code of the game, the artwork, the level layouts, the music, anything! All these parts of the game are labelled and in separate files. This is why I believe more people should be aware of the advantages of using a split disassembly, even if they plan not to even touch the assembly code.

SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug | Fix a remember sprite related bug
Changing Design Choices
Change Spike Behavior | Collide with Water After Being Hurt | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines | Port Sonic 2 Level Select
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add the Air Roll
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Change The SEGA Sound
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Make Ending Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits | Use Dynamic Palettes
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)

|Set up a Sonic 1 split disassembly]]