Okay, so, I have been trying to make a change to my hack that may be a bit of an arbitrary idea, but something I wanted to figure out, mainly for the sake of learning. So far, my efforts have been... Less than successful. To put it simply, I'm trying to make the level layouts in my Sonic 2 hack uncompressed for now, and I've been having some issues figuring everything out. Here is the best result I've gotten so far: The original code looked like this: Code (Text): loadLevelLayout: moveq #0,d0 move.w (Current_ZoneAndAct).w,d0 ror.b #1,d0 lsr.w #6,d0 lea (Off_Level).l,a0 move.w (a0,d0.w),d0 lea (a0,d0.l),a0 lea (Level_Layout).w,a1 bra.w JmpTo_KosDec As far as I can figure out, all I need to do is get rid of the jump to Kosdec, move a0 to a1, and add an RTS But that didn't do it. Any idea what I'm doing wrong?
It's not that you're doing anyting "wrong", it's just that you're missing something major out, so, let's observe, we'll change your above original code to the way that you specified, but with a slight difference: Code (ASM): loadLevelLayout: moveq #0,d0 move.w (Current_ZoneAndAct).w,d0 ror.b #1,d0 lsr.w #6,d0 lea (Off_Level).l,a0 move.w (a0,d0.w),d0 lea (a0,d0.l),a0 move.l a0,(Level_Layout).w ; save the "address" of the FG layout lea $80(a0),a0 ; advance the layout address to the BG area move.l a0,(Level_Layout+$04).w ; save the "address" of the BG layout rts ; return So now, the address/location of the FG layout is stored from "Level_Layout" to "Level_Layout+$03" as a long-word address, and the address/location of the BG layout is stored from "Level_Layout+$04" to "Level_Layout+$07" as a long-word address. The loading/setting up aspect of the layout is covered, but what isn't covered is the reading of the layout, as far as the draw code and collision codes are concerned, the entire layout has been decompressed to RAM, it doesn't know yet that you've changed the way you've loaded the layout. In several areas of the source code, you will have instances such as "lea (Level_Layout).w,a4", which is used to load the RAM address as the location of the layout, you'll want to change them to something along the lines of "move.l (Level_Layout).w,a4", the same would go for the BG, I know for a fact that the old disassemblies have sections such as "lea ($FFFF8080).w,a4" (I don't think they had an equate for that RAM address), for that example, you would change to "move.l (Level_Layout+$04).w,a4" to load the ROM address of the BG layout. A side warning though, would be that some areas might do things differently, for example: Code (ASM): lea (Level_Layout).w,a0 ; load FG layout bsr ReadFG ; read FG layout lea $80(a0),a0 ; load BG layout This is not in Sonic 2, I just made it up as an example, just in case something like this does exist somewhere, if it does, then you would want to change it to: Code (ASM): move.l (Level_Layout).w,a0 ; load FG layout bsr ReadFG ; read FG layout move.l (Level_Layout+$04).w,a0 ; load BG layout
The only thing have to add to add to MarkeyJester's post is that some levels perform level layout edits. Off the top of my head, Sonic 2 does this in WFZ (and I can't remember anywhere else). You will have to either add a workaround for it or remove the outside->inside swap that happens when you bust that thing near the boss.
LZ3 does something very similar, as I noticed when porting Sonic 128 to the HG disasm... which uses the coding methods that Markey is talking about. Here is the code for how Sonic 128 handles this layout change. I think you can apply something similar to the instances in Sonic 2. This is from Sonic 128's DynamicLevelEvent.asm file: Code (Text): DLE_LZ3: tst.b (f_switch+$F).w ; has switch $F been pressed? beq.s loc_6F28 ; if not, branch move.l (v_lvllayout).w,d0 ; MJ: load layout being read currently cmp.l #Level_LZ3_WALL,d0 ; MJ: is it already set to wall version? beq.s loc_6F28 ; MJ: if so, branch to skip move.l #Level_LZ3_WALL,(v_lvllayout).w ; MJ: Set wall version of act 3's layout to be read move.w #sfx_Rumbling,d0 bsr.w PlaySound_Special ; play rumbling sound Basically you would have to have another layout file, nearly identical aside from the change that occurs... this is due to it being read from the ROM, and no longer from RAM. So just bear in mind that you'll be making your ROM a little bit larger.
I personally used an unrolled loop of movem instructions to copy the uncompressed layouts to RAM as quickly as possible, which still allows for dynamic layout changes, such as in CNZ and WFZ's boss arenas.
I have read your post entirely, and while it's an interesting idea, unless I'm missing something from what you said, It didn't seem to work. As you instructed, I did change all of the needed lea's to move.l. An "unrolled" loop? I have a little bit of an understanding with loops, but I'm not familiar with that terminology. I would like to hear a more about your method, it sounds closer to what I'm hoping to achieve.
"Loop unrolling" is a technique that trades off space (code size) for speed. It consists on reducing the loop counter by a constant factor and replicating the code in the loop by the same factor, thus reducing the loop overhead. Let me give an example: consider this code: Code (Text): move.w #$E0-1,d1 loop: move.l d0,(a1)+ dbra d1,loop An unrolled version of it might look like this: Code (Text): move.w #($E0/8)-1,d1 loop: move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ move.l d0,(a1)+ dbra d1,loop with eight iterations unrolled. The number of "move.l" instructions executed is still the same, but there are 8 times less "dbra" instructions; this translates into less time to execute this loop. For this particular loop, unrolling 32 iterations results in gains of about 2000 cycles. Unrolling is made easier by use of macros; for instance, the above code could be written as Code (Text): move.w #($E0/8)-1,d1 loop: rept 8 move.l d0,(a1)+ endm dbra d1,loop which allows easier tweaking of the number of repetitions unrolled. You could even take it to the extreme and unroll all iterations (doing away with the "move.w" and "dbra" instructions entirely), but it is usually not worth it to unroll that much (too much additional code). Edit: this is an extract from the LZ background scrolling code; there are a few instructions omitted for clarity. I unrolled it by a factor of 32 in my hack, which is why I remember the ballpark value of the number of cycles saved.
Here I thought more code just took longer to read. Shows how little I know. I like this idea, so I'm trying to do that with my code now. I do understand what I'm doing mostly now, except for some reason I'm getting incorrect 128x128 chunks again. In fact, it seems like that's been the problem the whole time; when I try getting the level layout uncompressed, using whatever means, I think it's just not using the offset the same way it does when decompressed or something. I tried using both the Kosinski and Nemesis decompression routines (KosDec and NemDecToRAM), and they get the proper level layout information. With that said, here's what I've done for the moment, perhaps it will shed some light on what still needs to be done. Code (Text): loadLevelLayout: moveq #0,d0 move.w (Current_ZoneAndAct).w,d0 ror.b #1,d0 lsr.w #6,d0 lea (Off_Level).l,a0 move.w (a0,d0.w),d0 lea (a0,d0.l),a0 lea (Level_Layout).w,a4 div.w #$20,d1 LevelLayoutLoop: move.l (a0)+,a4 ;1 move.l (a0)+,a4 ;2 move.l (a0)+,a4 ;3 move.l (a0)+,a4 ;4 move.l (a0)+,a4 ;5 move.l (a0)+,a4 ;6 move.l (a0)+,a4 ;7 move.l (a0)+,a4 ;8 dbra d1,LevelLayoutLoop rts
Normal loops have a register containing the number of iterations minus one and a dbf instruction that decrements the register and branches back to the beginning of the loop until the register's value is -1. For an unrolled loop, you can use rept count and endm, and AS will assemble the instructions between them count times. My code is like this: Code (ASM): loadLevelLayout: moveq #0,d0 move.w (Current_ZoneAndAct).w,d0 ror.b #1,d0 lsr.w #6,d0 lsl.l d0 lea (Off_Level).l,a0 move.l (a0,d0.w),d0 lea (a0,d0.l),a0 lea (Level_Layout).w,a1 ;bra.w JmpTo_KosDec rept 78 movem.l (a0)+,d0-d7/a2-a6 movem.l d0-d7/a2-a6,(a1) lea 13*4(a1),a1 endm movem.l (a0),d0-d7/a2-a3 movem.l d0-d7/a2-a3,(a1) rts Each loop uses 13 4-byte registers to copy 52 bytes per loop, since the loop is repeated 78 times, that's a total of 4056 bytes. Since level layouts in Sonic 2 are all 4KB (4096 bytes), you need an extra movem using 10 registers for the last 40 bytes.
That did it! Okay, now it seems like the only thing not working is the 4-act zone expansion. I tried changing Code (Text): ror.b #1,d0 lsr.w #6,d0 to Code (Text): ror.b #2,d0 lsr.w #5,d0 like we did before, but it didn't help. What else needs to be changed?
Oh, right. My level table uses longword offsets because word offsets didn't have enough range to fit all the uncompressed level data. If you're using word-sized offsets, you can just replace everything up to the commented out KosDec branch with the original code.
Unfortunately, my table is also a long. Using the unmodified vode you gave me, every act 1 and 2 seems to work (Though I haven't checked with every act and zone, obviously), but act 3 is using act 1's level layout. I can't say about act 4 since I don't really have any zones that use the fourth act currently.
As far as I can tell that should work... you could also change the lsr to 4 and remove the lsl to shave off an instruction.
I tried doing that, I'm still getting weird results... No idea why. Here's the code I have right now: Code (Text): loadLevelLayout: moveq #0,d0 move.w (Current_ZoneAndAct).w,d0 ror.b #2,d0 lsr.w #4,d0 ;lsl.l d0 lea (Off_Level).l,a0 move.l (a0,d0.w),d0 lea (a0,d0.l),a0 lea (Level_Layout).w,a1 ;bra.w JmpTo_KosDec rept 78 movem.l (a0)+,d0-d7/a2-a6 movem.l d0-d7/a2-a6,(a1) lea 13*4(a1),a1 endm movem.l (a0),d0-d7/a2-a3 movem.l d0-d7/a2-a3,(a1) rts What have I done wrong? EDIT: I figured it out! It was the offset table, not your code. I changed it to longword offsets, but I didn't change the input for the zoneOrderedOffsetTable macro. I didn't think about it at first, but that seemed to quite fix it. Code (Text): Off_Level: zoneOrderedOffsetTable 2,4 I should have changed that to Code (Text): Off_Level: zoneOrderedOffsetTable 4,4 It's working now! Thanks so much for your help!
I.. Have been scratching my head a lot over this, and I finally decided I need some help. I feel like part of me knows what's wrong, but I can't quite put my finger on it. I imported the Sonic 1 Title Cards (Or, the Sonic 2NA Title cards without the RTS's before DisplaySprite branches) into Sonic 2, and while I got everthing working okay in terms of functionality, I have two problems, but I'm going to tackle the one that, in my eyes, is more important ATM: Whenever I start a zone, this happens: If I were to take a guess, it would be that the game is preparing Plane A to be deformed by the title card, but since the title card isn't there in the for it wants, it just stays like that. When you start moving, all the titles that were offscreen start displaying properly. I looked at (and spent hours trying to tweak) the routine DrawInitialBG:, which I also have renamed LoadTilesFromStart, mainly for consistency, and while I think that's part of the problem, "fixing" it to the best of my understanding to be consistent with the Sonic 2 NA version hasn't really resolved the issue. Below is my code for said Routine as I have it now, hopefully I just overlooked something simple: Spoiler Code (Text): LoadTilesFromStart: lea (VDP_control_port).l,a5 lea (VDP_data_port).l,a6 tst.w (Two_player_mode).w beq.s loc_711E lea (Camera_X_pos_P2).w,a3 lea (Level_Layout).w,a4 move.w #$6000,d2 bsr.s LoadTilesFromStart_2p loc_711E: lea (Camera_BG_X_pos).w,a3 lea (Level_Layout).w,a4 ; foreground move.w #$4000,d2 bsr.s LoadTilesFromStart2 lea (Camera_BG_X_pos).w,a3 lea (Level_Layout+$80).w,a4 ; background move.w #$6000,d2 ; This selects a VRAM write and moves to PNT B cmpi.b #casino_night_zone,(Current_Zone).w beq.w + tst.w (Two_player_mode).w beq.w LoadTilesFromStart2 cmpi.b #mystic_cave_zone,(Current_Zone).w beq.w loc_E396 LoadTilesFromStart2: moveq #-$10,d4 + moveq #$F,d6 - movem.l d4-d6,-(sp) moveq #0,d5 move.w d4,d1 bsr.w CalcBlockVRAMPos move.w d1,d4 moveq #0,d5 moveq #$1F,d6 move #$2700,sr bsr.w DrawBlockRow move #$2300,sr movem.l (sp)+,d4-d6 addi.w #$10,d4 dbf d6,- rts As usual, I'd really appreciate any help.
Yes. Basically, title card share the same Plane A with the foreground. When the card starts moving out, the 16x16 blocks of FG are rendered instead of solid blue blocks of the title card, line by line. This happens fast enough to create a smooth motion and simulate that the blue area is scrolling up, showing us the level, whereas no scrolling is used. As for the code, try this: Code (ASM): ; =========================================================================== LoadTilesFromStart: lea VDP_control_port,a5 lea VDP_data_port,a6 lea Camera_X_pos,a3 ; foreground camera lea Level_Layout,a4 ; foreground layout moveq #$4000,d2 ; set VRAM location to Plane A bsr.s $$DrawPlane lea Camera_BG_X_pos,a3 ; background camera lea Level_Layout+$80,a4 ; background layout move.w #$6000,d2 ; set VRAM location to Plane B $$DrawPlane: moveq #0,d4 ; sets D4 to $00 or -$10 depending on the following conditions... cmpi.b #casino_night_zone,Current_Zone beq.w ++ tst.w Two_player_mode beq.w + cmpi.b #mystic_cave_zone,Current_Zone beq.w loc_E396 + moveq #-$10,d4 + moveq #$F,d6 ; repeat for $10 horizontal lines of 16x16 blocks - movem.l d4-d6,-(sp) moveq #0,d5 move.w d4,d1 bsr.w CalcBlockVRAMPos ; calculate horizontal line start offset in VRAM move.w d1,d4 moveq #0,d5 moveq #$1F,d6 ; draw $20 16x16 blocks on a line move #$2700,sr ; disable interrupts, as we're going to access VRAM bsr.w DrawBlockRow ; ... move #$2300,sr ; re-enable interrupts movem.l (sp)+,d4-d6 addi.w #$10,d4 dbf d6,- rts This basically copies Sonic 1 routine plus Sonic 2-specific tweaks. I'm not sure if the code that sets D4 should work for both Planes, or Plane B exclusively -- I've no idea about all the Sonic 2 quirks, as this is not the game I'm experienced in. If you encounter issues in CNZ/MCZ/2P mode, try placing $$DrawPlane directly above moveq #-$10,d4 line.
Your code did help, though I didn't end up using it. It did however, show me what I did wrong, so thank you for that. As it turns out, I overlooked this line here: Code (Text): lea (Camera_BG_X_pos).w,a3 lea (Level_Layout).w,a4 ; foreground > Camera_BG_X_pos I derped. Should've been (Camera_X_pos).w Also, something I see you did, just for future advice, is you lea referenced a RAM address, like this: lea Level_Layout,a4. From my past experience, Sonic 2 disassemblies (at least, the hg one) don't like referencing memory addresses that way, and it would trow errors at me if I tried to use it. Sorry if I sound naggy or something, just thought I'd let you know. Sonic 1 and 2 are quite a different bag of worms in terms of assembly standards. Now, there's a single other issue, dealing with the fade in transition from the title card, which looks like this: Code (Text): Level_DelayLoop: move.b #8,(Vint_routine).w bsr.w WaitForVint dbf d1,Level_DelayLoop move.w #$202F,(Palette_fade_range).w bsr.w Pal_FadeTo2 This area of code I completely changed pretty much from Sonic 2; I edited it to be consistent with the Sonic 2 NA prototype. If I comment out the the move.w and bsr.w, then the level loads the right palette, but it just kind of... Snaps into existence, and is quite jarring. However, if I use the fade-in routine, this happens: Does anyone know what's wrong? For reference, this is my (barely changed) Pal_FadeTo: routine: Code (Text): Pal_FadeTo: move.w #$3F,(Palette_fade_range).w Pal_FadeTo2: moveq #0,d0 lea (Normal_palette).w,a0 move.b (Palette_fade_start).w,d0 adda.w d0,a0 moveq #0,d1 move.b (Palette_fade_length).w,d0 ; loc_23DE: Pal_ToBlack: move.w d1,(a0)+ dbf d0,Pal_ToBlack ; fill palette with $000 (black) move.w #$15,d4 - move.b #$12,(Vint_routine).w bsr.w WaitForVint bsr.s Pal_FadeIn bsr.w RunPLC_RAM dbf d4,- rts ; End of function Pal_FadeTo
It is because Sonic 1 and Sonic 2 use different assemblers. AS ( the assembler used in S2 disasm) has a syntax closer to Motorolla's than asm68k (the assembler of the S1 disasm) does, and it requires absolute addresses to be enclosed in parenthesis and with a size modifier (".w" for absolute short or ".l" for absolute long) appended: Code (Text): lea (XXX).w,aN ; absolute short lea (XXX).l,aN ; absolute long (the reason for the strikethroughs in in vladikcompiler's post and my own after that)
May I ask, what is the difference between using one or the other? and how does it cause such errors? I wonder if something like this is giving me the headaches I have in my hack.