I somewhat feel like the Co-op experience in the Classic Sonic games is kind of flawed. In this guide, you will find three changes that can provide a slight improvement to it. 1) Make pausing player dependent Let's imagine a simple scenario. You're playing Sonic 2's 2PVS mode with a friend, or a sibling. You decide to pause the game for a second to get some water, and you come back to find the game unpaused, despite the fact that your opponent didn't even touch your controller! That's right, the cheeky bastard unpaused the game with their own controller! In Sonic 3 & Knuckles, this was somewhat fixed... by only letting player 1 pause the game. Which means that if you were playing, say, Sonic 3's Competition mode, and you, player 2, wanted to take a break, you had to trust in your opponent's kindness to pause the game. This modified code makes pausing player dependent, so that if player 1 pauses, only player 1 can unpause. Same for player 2. Before you continue, it is assumed you're using the AS disassembly. If you are not, remember to replace all of the dots with "@"s. Also, replace the "+" signs with new labels, also starting in "@". Spoiler: Sonic 2 (Github) When the game gets paused by either player pressing the Start button, the game sets a flag. We can turn this flag into a variable that sets itself differently based on which player pressed the Start button. Just a few lines above Pause_Loop, you'll find this: Code: move.w #1,(Game_paused).w ; freeze time Under this line, place this: Code: btst #button_start,(Ctrl_2_Press).w ; was it player 2 who paused? beq.s + move.w #2,(Game_paused).w + If it's player 2 that presses the Pause button, the game gets marked accordingly. If both players pause at the same time, player 2 gets port priority. Now, we need to edit the Pause_ChkStart routine. The original Pause_ChkStart routine checks for either player pressing the Start button. We don't want that: rather, we want to check if the player that presses the Start button is actually the one who paused. Replace everything inbetween the labels Pause_ChkStart and Pause_Resume with these lines: Code: btst #button_start,(Ctrl_1_Press).w ; is the Start button pressed? beq.s + ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? beq.s + ; if yes, branch bra.s Pause_Resume ; resume game + btst #button_start,(Ctrl_2_Press).w ; is the Start button pressed? beq.s Pause_Loop ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? bne.s Pause_Loop ; if not, branch ; below this line should be Pause_Resume: Save, build, and try it out! Spoiler: Sonic 3 & Knuckles (Github) When the game gets paused, the game sets a flag. We can turn this flag into a variable that sets itself differently based on which player pressed the Start button. But first, we have to allow the second player to pause, too. In Pause_Main, below this line... Code: move.b (Ctrl_1_pressed).w,d0 ...add this line: Code: or.b (Ctrl_2_pressed).w,d0 This line allows player 2 to pause the game, just like in Sonic 2. Now, close below, you'll find this line: Code: move.w #1,(Game_paused).w Under this line, place this: Code: move.w #1,(Game_paused).w btst #7,(Ctrl_2_pressed).w ; was it player 2 who paused? beq.s + move.w #2,(Game_paused).w + If it's player 2 that presses the Pause button, the game gets marked accordingly. If both players pause at the same time, player 2 gets port priority. Now, we need to edit the Pause_ChkStart routine. The original Pause_ChkStart routine accounts only for player 1's actions. We don't want that: rather, we want to check if the player that presses the Start button is actually the one who paused. Replace everything inbetween the labels Pause_ChkStart and Pause_ResumeMusic with these lines: Code: btst #7,(Ctrl_1_pressed).w ; is the Start button pressed? beq.s + ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? beq.s + ; if yes, branch bra.s Pause_ResumeMusic ; resume game + btst #7,(Ctrl_2_pressed).w ; is the Start button pressed? beq.s Pause_Loop ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? bne.s Pause_Loop ; if not, branch ; under this line, there should be Pause_ResumeMusic: Save, build, and try it out! 2) Make score chains player dependent Is your opponent, by chance, racking up a lot of points in Sonic 2's 2PVS? Surely, that wouldn't sit well with you. Well, you can stop their awesome score chain by just landing on the floor. Congratulations! You just earned yourself a massive beating by one of the 3 other people in the world who actively go for score in Sonic games! In Sonic 3 & Knuckles, this is not as critical, given there's no 2P mode like Sonic 2's, but your score chain can still be hindered by your partner's meddling! So, let's fix this. You'll need RAM space for a new word sized variable, which we'll call Chain_Bonus_Counter_2P. Spoiler: Sonic 2 First change that needs to be made is in Touch_KillEnemy. In between these two lines... Code: moveq #0,d0 move.w (Chain_Bonus_counter).w,d0 ...place this code: Code: cmpi.w #MainCharacter,a0 ; is caller the main character? beq.s + ; if not, branch move.w (Chain_Bonus_counter_2P).w,d0 addq.w #2,(Chain_Bonus_counter_2P).w ; add 2 to chain bonus counter bra.s ++ + And, add a "+" label after this line: Code: addq.w #2,(Chain_Bonus_counter).w ; add 2 to chain bonus counter Now, in loc_3F802, in between these lines... Code: move.w Enemy_Points(pc,d0.w),d0 cmpi.w #$20,(Chain_Bonus_counter).w ; have 16 enemies been destroyed? ...add this: Code: cmpi.w #MainCharacter,a0 ; is caller the main character? beq.s + ; if not, branch cmpi.w #$20,(Chain_Bonus_counter_2P).w ; have 16 enemies been destroyed? blo.s loc_3F81C ; if not, branch bra.s ++ + then, add another "+" label under this line: Code: blo.s loc_3F81C ; if not, branch If you're confused, the code from Touch_KillEnemy until loc_3F81C (label not included) should look like this: Code: Touch_KillEnemy: bset #7,status(a1) moveq #0,d0 cmpi.w #MainCharacter,a0 ; is caller the main character? beq.s + ; if not, branch move.w (Chain_Bonus_counter_2P).w,d0 addq.w #2,(Chain_Bonus_counter_2P).w ; add 2 to chain bonus counter bra.s ++ + move.w (Chain_Bonus_counter).w,d0 addq.w #2,(Chain_Bonus_counter).w ; add 2 to chain bonus counter + cmpi.w #6,d0 blo.s loc_3F802 moveq #6,d0 loc_3F802: move.w d0,objoff_3E(a1) move.w Enemy_Points(pc,d0.w),d0 cmpi.w #MainCharacter,a0 ; is caller the main character? beq.s + ; if not, branch cmpi.w #$20,(Chain_Bonus_counter_2P).w ; have 16 enemies been destroyed? blo.s loc_3F81C ; if not, branch bra.s ++ + cmpi.w #$20,(Chain_Bonus_counter).w ; have 16 enemies been destroyed? blo.s loc_3F81C ; if not, branch + move.w #1000,d0 ; fix bonus to 10000 points move.w #$A,objoff_3E(a1) Now, you want to go under the code of any character that can be a sidekick or an opponent. If you've made core changes to the game, you know which characters to edit. Otherwise, it can only be Tails. So, go under his code, at Tails_ResetOnFloor_Part3. Replace this line... Code: move.w #0,(Chain_Bonus_counter).w ...with this: Code: cmpi.w #MainCharacter,a0 beq.s + move.w #0,(Chain_Bonus_counter_2P).w bra.s ++ + move.w #0,(Chain_Bonus_counter).w + Save, build, and try it out! Now, both players should have their own score chains. Spoiler: Sonic 3 & Knuckles First change that needs to be made is in .dontremember, in Touch_EnemyNormal. Replace these two lines... Code: move.w (Chain_bonus_counter).w,d0 ; Get copy of chain bonus counter addq.w #2,(Chain_bonus_counter).w ; Add 2 to chain bonus counter with this: Code: cmpi.w #Player_1,a0 ; is caller the main character? beq.s + ; if not, branch move.w (Chain_Bonus_counter_2P).w,d0 addq.w #2,(Chain_Bonus_counter_2P).w ; add 2 to chain bonus counter bra.s ++ + move.w (Chain_Bonus_counter).w,d0 addq.w #2,(Chain_Bonus_counter).w ; add 2 to chain bonus counter + Then, in .notreachedlimit, replace this: Code: cmpi.w #16*2,(Chain_bonus_counter).w ; Have 16 enemies been destroyed? blo.s .notreachedlimit2 ; If not, branch move.w #1000,d0 ; Fix bonus to 10000 points move.w #$A,objoff_3E(a1) .notreachedlimit2: With this: Code: cmpi.w #Player_1,a0 ; is caller the main character? beq.s + ; if not, branch cmpi.w #$20,(Chain_Bonus_counter_2P).w ; have 16 enemies been destroyed? blo.s +++ ; if not, branch bra.s ++ + cmpi.w #$20,(Chain_Bonus_counter).w ; have 16 enemies been destroyed? blo.s ++ ; if not, branch + move.w #1000,d0 ; Fix bonus to 10000 points move.w #$A,objoff_3E(a1) + Now, you want to go under the code of any character that can be a sidekick or an opponent. If you've made core changes to the game, you know which characters to edit. Otherwise, it can only be Tails. So, go under his code, at loc_1565E. Change this: Code: move.w #0,(Chain_bonus_counter).w To this: Code: cmpi.w #Player_1,a0 beq.s + move.w #0,(Chain_Bonus_counter_2P).w bra.s ++ + move.w #0,(Chain_Bonus_counter).w + Save, build, and test it out! Do note, however, that this code opens up an exploit in Sonic 3 & Knuckles' 2 player co-op! In fact, since Tails landing no longer resets Sonic's score chain, a series of well timed jumps, as well as a pair of synchronized players, can lead to a never ending score chain! You're free to fix this by clearing player 1's score chain in player 2's code, but in my honest opinion, this actually sounds like a fun exploit, so feel free to keep it. 3) In-game controller swap This one especially applies to emulators with netplay capabilities. One thing I absolutely HATE is the fact that not a single emulator for the SEGA Mega Drive/Genesis with netplay capabilities allows to remap controller ports like emulators for other systems (e.g. Dolphin). However, we can compensate for this by adding a way to swap controllers from within the game itself. This part assumes you've followed part 1 of the guide. You'll need a new byte sized variable, one which we'll call "Invert_Joypads_Flag". Make sure you place this one someplace that doesn't get reset! Spoiler: Sonic 2 Go to ReadJoypads, and under this line... Code: lea (Ctrl_1).w,a0 ; address where joypad states are written Place these two lines: Code: tst.b (Invert_Joypads_Flag).w bne.s JR_Alternateread Then, right before the comment that marks the end of ReadJoypads, add these lines: Code: JR_Alternateread: lea (HW_Port_2_Data).l,a1 ; second joypad port bsr.s Joypad_Read ; do the second joypad subq.w #2,a1 bra.s Joypad_Read ; do the first joypad Now, we're going back to the pause feature. Change this: Code: btst #button_start,(Ctrl_2_Press).w ; is the Start button pressed? beq.s Pause_Loop ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? bne.s Pause_Loop ; if not, branch Into this: Code: btst #button_start,(Ctrl_2_Press).w ; is Start button pressed? beq.s .checkplayerswap cmpi.w #2,(Game_paused).w bne.s .checkplayerswap bra.s Pause_Resume .checkplayerswap: tst.w (Two_player_mode).w bne.s Pause_Loop move.b (Ctrl_1_Held).w,d0 and.b (Ctrl_2_Held).w,d0 cmpi.b #button_A_mask,d0 bne.s Pause_Loop not.b (Invert_Joypads_Flag).w You may end up having to fix a few branch distances in the Pause code, so make sure to do that. Spoiler: Sonic 3 & Knuckles Go to Poll_Controllers, and under this line... Code: lea (Ctrl_1).w,a0 ...place this: Code: tst.b (Invert_Joypads_Flag).w bne.s PC_Alternateread Then, right before the comment that marks the end of Poll_Controller, add these lines: Code: PC_Alternateread: lea (HW_Port_2_Data).l,a1 ; second joypad port bsr.s Poll_Controller ; do the second joypad subq.w #2,a1 bra.s Poll_Controller ; do the first joypad Now, we're going back to the pause feature. Change this: Code: btst #7,(Ctrl_2_pressed).w ; is the Start button pressed? beq.s Pause_Loop ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? bne.s Pause_Loop Into this: Code: btst #7,(Ctrl_2_pressed).w ; is the Start button pressed? beq.s + ; if not, branch cmpi.w #2,(Game_paused).w ; was it player 2 who paused? bne.s + ; if not, branch bra.s Pause_ResumeMusic + tst.w (Competition_mode).w bne.w Pause_Loop move.b (Ctrl_1).w,d0 and.b (Ctrl_2).w,d0 cmpi.b #$40,d0 bne.w Pause_Loop not.b (Invert_Joypads_Flag).w You may end up having to fix a few branch distances in the Pause code, so make sure to do that. Here's what the above code does: If both players press A while the game is paused, the game gets unpaused, but the two player controllers get swapped in such a way that reads from Player 1's controller on hardware get sent to Player 2's input RAM, and Player 2's reads go to Player 1's input RAM. The two players can swap at any time, with the exception of when they're in 2PVS gamemodes. With these three changes together, you'll be able to provide some small, but still meaningful improvements to the 2 player experience in Sonic! Why don't you try these changes out with a friend?