How to add 6-button pad and SEGA Team Player support to Sonic 1

Discussion in 'Tutorials' started by Natsumi, Jan 15, 2017.

  1. Natsumi

    Natsumi Markey's Member

    Joined:
    Oct 7, 2011
    Messages:
    516
    Location:
    Otter's lap
    Yes, you read that right: you can have 4-player multiplayer support in Sonic 1! Or not actually. But you can add support for 8 total controllers for your hack! Why? I don't know... Go do something interesting with it. All I can offer you is a tutorial on how to add it.

    Now, I am well aware there has been a 6-button tutorial before, but honestly speaking, it is terrible. I wanted to add 6-button pad support for something I am working on and... It did work, but I was embarrassed sticking it into my game. Not to mention, it never actually checked if player 2 had 6-button pad, and it was just excessively slow and hacky solution.

    So, I set out to write my own code. The end result? Way faster 3-button and 6-button read codes, and also full multitap support! So, why would you want to use the 6-button controller or multitap? Here are a few reasons;
    • You can use the extra X, Y, Z and MODE buttons for debugging.
    • You can use the extra X, Y, Z and MODE buttons for new content, such as moves, special stages, or minigames.
    • You can use the extra X, Y, Z and MODE buttons for a custom game or homebrew.
    • You can use the multitap for adding support for more players at once.
    • You can use the multitap for adding extra features for other people to use (for example, the second player in Super Mario Galaxy).
    There are also a couple of issues you have to consider before adding the support;
    • More RAM is required. For 6-button alone, total RAM usage is 0xC bytes, and for multitap it is 0x26 bytes(!).
    • It is more time consuming to read 6-button pads.
    • It is more time consuming to read EA 4 way play (aka EXTRA mode in multitap devices).
    • Multitap protocol is the ultimate troll (Takes ~8 scanlines for 4 controllers with one Team Player inserted, as opposed to 2 scanlines for EA or singles).
    • SEGA engineers are terrible people and they probably hate you too.
    • The user can switch between EXTRA, MULTI, and A/B/C/D modes with multitap while the game is running.
    • You can poll this change, but it lowers the responsivity slightly.
    Teh tutorialz
    Before we get to the fun part, first we have to do something as a preparation. Since we will be using a word instead of a byte to store each button press to fit in the 6-button pad buttons too, we will have to replace every single instance of button press checking to suit this change. What you need to do is quite long-winded, and to make it easier for you, I've put them into a nice list. Remember to read it carefully first!
    • In Hivebrain 2005 disassembly;
      • Change each $FFFFF602 to P1Held
      • Change each $FFFFF603 to P1Press
      • Change each $FFFFF604 to Ctrl1Held
      • Change each $FFFFF605 to Ctrl1Press
    • In Github Sonic 1 disassembly;
      • Change each v_jpadhold2 to P1Held
      • Change each v_jpadpress2 to P1Press
      • Change each v_jpadhold1 to Ctrl1Held
      • Change each v_jpadpress1 to Ctrl1Press
    • On each line with either of the above, also do the following things (when it applies);
      • If that line starts with btst, stick a +1 right after P1Held, P1Press, Ctrl1Held or Ctrl1Press.
      • If that line contains .w at the beginning (e.g. 'move.w'), change it to .l
      • Same is true if that line contains .b at the beginning, but you change it to .w instead.
      • If the line starts with andi, and contains P1Held, P1Press, Ctrl1Held or Ctrl1Press, stick +1 right after them.

    There is also a few lines we need to manually edit. Right before loc_EC70, replace this (or similar) line:
    Code:
    		move.l	#$800,P1Held.w ; make Sonic run to	the right
    with:
    Code:
    		move.l	#(J_R)<<16,P1Held.w ; make Sonic run to	the right
    And above locret_1AC60, change:
    Code:
    		move.l	#$800,P1Held.w ; make Sonic run to	the right
    to this:
    Code:
    		move.l	#(J_R)<<16,P1Held.w ; make Sonic run to	the right
    Now, one more thing to set up; demos. They still assume that we are using only a byte for our button presses. Lets fix this. Go to loc_4056, and before this line:
    Code:
    		move.b	d1,(a0)+
    just add this line:
    Code:
     		clr.b	(a0)+
    Then go to MainGameLoop, and a little above it, remove this line:
    Code:
            bsr.w    JoypadInit
    Now, onto more interesting things. At the top of your main asm file, add the following:
    Code:
    CTRL_ENABLE_MULTI =	1; enable multitap (Team Player) and EA 4-way play.
    	rsset 0
    JbU		rs.b 1	; bit Up
    JbD		rs.b 1	; bit Down
    JbL		rs.b 1	; bit Left
    JbR		rs.b 1	; bit Right
    JbB		rs.b 1	; bit B
    JbC		rs.b 1	; bit C
    JbA		rs.b 1	; bit A
    JbS		rs.b 1	; bit Start
    JbZ		rs.b 1	; bit Z
    JbY		rs.b 1	; bit Y
    JbX		rs.b 1	; bit Z
    JbM		rs.b 1	; bit Mode
    
    J_U =	(1<<JbU)	; Up
    J_D =	(1<<JbD)	; Down
    J_L =	(1<<JbL)	; Left
    J_R =	(1<<JbR)	; Right
    J_P =	J_U|J_D|J_L|J_R	; UDLR
    J_B =	(1<<JbB)	; B
    J_C =	(1<<JbC)	; C
    J_A =	(1<<JbA)	; A
    J_ABC =	J_A|J_B|J_C	; ABC
    J_S =	(1<<JbS)	; Start
    J_Z =	(1<<JbZ)	; Z
    J_Y =	(1<<JbY)	; Y
    J_X =	(1<<JbX)	; X
    J_XYZ =	J_X|J_Y|J_Z	; XYZ
    J_M =	(1<<JbM)	; Mode
    
    PollChgCTRL =	$FFFFF601	; nonzero if we want to poll CTRL changes
    P1Held =	$FFFFF602	; held buttons for player 1
    P1Press =	$FFFFF604	; pressed buttons for player 1
    P2Held =	$FFFFF606	; held buttons for player 2
    P2Press =	$FFFFF608	; pressed buttons for player 2
    
    	rsset $FFFFFF86
    	ifne CTRL_ENABLE_MULTI
    CTRL_STORE_REGS REG d0-d4/a0-a2
    Ctrl1Held	rs.w 1		; held buttons for controller 1
    Ctrl1Press	rs.w 1		; pressed buttons for controller 1
    Ctrl1BHeld	rs.w 1		; held buttons for multitap 1B
    Ctrl1BPress	rs.w 1		; pressed buttons for multitap 1B
    Ctrl1CHeld	rs.w 1		; hel§d buttons for multitap 1C
    Ctrl1CPress	rs.w 1		; pressed buttons for multitap 1C
    Ctrl1DHeld	rs.w 1		; held buttons for multitap 1D
    Ctrl1DPress	rs.w 1		; pressed buttons for multitap 1D
    Ctrl2Held	rs.w 1		; held buttons for controller 2
    Ctrl2Press	rs.w 1		; pressed buttons for controller 2
    Ctrl2BHeld	rs.w 1		; held buttons for multitap 2B
    Ctrl2BPress	rs.w 1		; pressed buttons for multitap 2B
    Ctrl2CHeld	rs.w 1		; held buttons for multitap 2C
    Ctrl2CPress	rs.w 1		; pressed buttons for multitap 2C
    Ctrl2DHeld	rs.w 1		; held buttons for multitap 2D
    Ctrl2DPress	rs.w 1		; pressed buttons for multitap 2D
    Ctrl1State	rs.b 2		; ctrl 1 and 2 state (0 = 3-button, $FF = 6-button, $FE - multitap, $EA = EA 4-way play)
    Ctrl1MTypes	rs.b 2		; multitap ctrl types (2 bits per ctrl, 00 = 3-btn, 01 = 6-btn, 11 = none)
    	else
    CTRL_STORE_REGS REG d0-d3/a0-a2
    Ctrl1Held	rs.w 1		; held buttons for controller 1
    Ctrl1Press	rs.w 1		; pressed buttons for controller 1
    Ctrl2Held	rs.w 1		; held buttons for controller 2
    Ctrl2Press	rs.w 1		; pressed buttons for controller 2
    Ctrl1State	rs.b 2		; ctrl 1 and 2 state (0 = 3-button, $FF = 6-button)
    	endif
    
    CTRLbTH =	6		; TH pin bit
    CTRLbTR =	5		; TR pin bit
    CTRLbTL =	4		; TL pin bit
    CTRL_TH =	1<<CTRLbTH	; TH pin
    CTRL_TR =	1<<CTRLbTR	; TR pin
    CTRL_TL =	1<<CTRLbTL	; TL pin
    
    ; this instruction is basically 2 nops, except it affects cc too and is 2 bytes shorter
    ctrl_delay	macro
    	or.l	d0,d0
    	endm
    Now, we will edit the subroutines that handle button presses. Replace everything between JoypadInit and VDPSetupGame. with this:
    Code:
    InitPads:
    		moveq	#0,d2
    		lea	Ctrl1State.w,a0
    
    	ifne CTRL_ENABLE_MULTI
    		lea	Ctrl1MTypes.w,a2; get multitap button list to a2
    		jsr	CheckEA(pc)
    	endif
    
    		clr.w	(a0)
    		lea	$A10003,a1
    		bsr.s	.ctrl
    		addq.w	#2,a1
    	ifne CTRL_ENABLE_MULTI
    		addq.w	#1,a2
    
    .ctrl		move.b	#CTRL_TH|CTRL_TR,6(a1)
    		jsr	CheckTeamPlay(pc)
    
    		move.b	#CTRL_TH,6(a1)
    		moveq	#0,d2		; th lo
    	else
    .ctrl
    	endif
    
    		move.b	d2,(a1)		; Pull TH line low
    		nop
    		moveq	#CTRL_TH,d3	; th hi
    		move.b	d3,(a1)		; Pull TH line high
    	ctrl_delay			; delay
    		move.b	d2,(a1)		; Pull TH line low
    	ctrl_delay			; delay
    		move.b	d3,(a1)		; Pull TH line high
    	ctrl_delay			; delay
    		move.b	d2,(a1)		; Pull TH line low
    	ctrl_delay			; delay
    
    		move.b	(a1),d0		; 6-BUTTON
    		move.b	d3,(a1)		; Pull TH line high
    		and.b	#$f,d0
    		seq	d1		; if 6-button, set d1
    		add.b	d1,(a0)+	; (a0) = $FF if 6-button
    		rts
    
    	ifne CTRL_ENABLE_MULTI
    CheckEA:
    		lea	$A10005,a1
    		moveq	#CTRL_TH,d1
    
    		move.b	d1,4(a1)	; TH is output line on port 1
    	ctrl_delay
    		move.b	#$7F,6(a1)	; all lines are output on port 2
    	ctrl_delay
    		move.b	#$7C,(a1)	; get response from port 1
    
    		moveq	#$F,d0		; all bits will be 1, but the 2 lowest ones (broken?)
    		and.b	-2(a1),d0	; and the pad value
    		bne.s	.notEA		; if those bits were not 0, branch
    
    	; found EA 4-way play
    		move.b	#$C,(a1)	; reset latch
    		addq.l	#4,sp		; do not continue normally
    		move.w	#$EAEA,(a0)	; enable EA mode
    		rts
    
    .notEA		move.b	d1,6(a1)
    		rts
    
    CheckTeamPlay:
    		move.b	#CTRL_TH|CTRL_TR,(a1)
    		moveq	#CTRL_TR,d2
    		moveq	#3-1,d4		; do 3 loops later on
    
    		moveq	#$F,d0		; prepare and value
    		and.b	(a1),d0		; and d0 with the value we got (saves 8 cycles this way :P)
    		move.b	d2,(a1)
    
    .readdat	moveq	#$F,d1		; prepare and value
    		lsl.w	#4,d0		; make room for new data
    		jsr	MTapWaitHandShake(pc)
    
    		and.b	(a1),d1		; and d1 with the value we got
    		move.b	d2,(a1)
    		or.b	d1,d0		; and or the new value in too
    		dbf	d4,.readdat	; loop til all ctrls are done
    
    		cmp.w	#$3F00,d0	; check if this is a multitap
    		bne.s	.end		; if not, branch
    
    		move.b	#-2,(a0)+	; set to be a multitap ctrl
    		clr.b	(a2)		; ensure we wont break this
    		moveq	#0,d1		; rol 0 times
    		moveq	#4-1,d4		; 4 ctrls
    
    .mulloop	jsr	MTapWaitHandShake(pc)
    		move.b	(a1),d0		; get button data
    		move.b	d2,(a1)
    
    		andi.b	#3,d0		; get 2 lowest bits
    		rol.b	d1,d0		; rotate x bits
    		or.b	d0,(a2)		; then set ctrl type
    
    		addq.w	#2,d1
    		dbf	d4,.mulloop	; loop til all ctrls are done
    
    		move.b	#CTRL_TR|CTRL_TH,(a1)
    		addq.l	#4,sp		; do not return normally
    .end		rts
    	endif
    
    ReadJoypads:
    		tst.b	PollChgCTRL.w		; are we polling for controller changes?
    		beq.s	.noinit			; if we aren't, skip this code
    		moveq	#7,d0			; we want to run once in 8 frames
    		and.b	$FFFFFE0C+3.w,d0	; and the low byte of VBlank global timer (in Github, it is v_vbla_count+3)
    		beq.w	InitPads		; if counter&7 = 0, re-initialize controllers
    
    .noinit		lea	$A10003,a1
    		lea	Ctrl1Held.w,a0
    		lea	Ctrl1State.w,a2
    
    	ifne CTRL_ENABLE_MULTI
    		cmp.w	#$EAEA,(a2)		; special: Check if EA is active
    		beq.w	ReadEA			; if so, branch
    	endif
    
    		moveq	#CTRL_TH,d3		; TH hi
    		moveq	#0,d2			; TH lo
    		bsr.s	.ctrl3
    		addq.w	#2,a1
    
    .ctrl3	ifne CTRL_ENABLE_MULTI
    		move.b	(a2)+,d0		; check if is 3 or 6-button pad or multitap
    		bmi.s	 .ctrl6			; if not 3-button pad, branch
    	else
    		tst.b	(a2)+			; check if is 3 or 6-button pad
    		bmi.s	 .ctrl6			; if 6, branch
    	endif
    
    		move.b	d2,(a1)			; set TH low
    		moveq	#CTRL_TL|CTRL_TR,d0	; prepare d0
    		nop				; delay
    		and.b	(a1),d0			; and controller port data (start/A)
    		move.b	d3,(a1)			; set TH high
    		lsl.b	#2,d0
    
    		moveq	#CTRL_TL|CTRL_TR|$F,d1
    		and.b	(a1),d1			; and controller port data (B/C/Dpad)
    		or.b	d1,d0			; Fuse together into one controller bit array
    		not.b	d0
    
    		clr.b	(a0)+			; clear high byte
    		move.b	(a0),d1			; get pressed button data
    		eor.b	d0,d1			; toggle off inputs that are being held
    		move.b	d0,(a0)+		; put held buttons to a0
    		and.b	d0,d1			; only activate buttons that were pressed this frame (but not held)
    
    		clr.b	(a0)+			; clear high byte
    		move.b	d1,(a0)+		; and then save pressed buttons
    
    	ifne CTRL_ENABLE_MULTI
    		clr.l	(a0)+			; clear buttons for multitap ctrls
    		clr.l	(a0)+
    		clr.l	(a0)+
    	endif
    		rts
    
    .ctrl6	ifne CTRL_ENABLE_MULTI
    		addq.b	#1,d0			; check if multitap
    		bmi.s	.multi			; if is, branch
    	endif
    
    		move.b	d3,(a1)			; set TH high
    		moveq	#0,d0
    		moveq	#0,d1
    
    		move.b	(a1),d1			; Reading first 6 buttons
    		move.b	d2,(a1)			; set TH low
    		andi.b	#CTRL_TL|CTRL_TR|$F,d1
    
    		move.b	(a1),d0			; Read second 2 buttons
    		move.b	d3,(a1)			; set TH high
    		andi.b	#CTRL_TL|CTRL_TR,d0
    		move.b	d2,(a1)			; set TH low
    		lsl.b	#2,d0
    		move.b	d3,(a1)			; set TH high
    		or.l	d0,d1			; Combine basic 8 buttons and store it to d1
    
    		move.b	d2,(a1)			; set TH low
    	ctrl_delay				; delay
    		move.b	d3,(a1)			; set TH high
    
    		moveq	#$F,d0			; prepare d0
    		nop
    		and.b	(a1),d0			; Read extra buttons
    		move.b	d3,(a1)			; set TH high
    		lsl.w	#8,d0			; Shift it by 8 bits
    		or.w	d1,d0			; Combine it with basic buttons
    		not.w	d0			; Invert basic buttons
    
    		move.w	(a0),d1			; get pressed button data
    		eor.w	d0,d1			; toggle off inputs that are being held
    		move.w	d0,(a0)+		; put held buttons to a0
    		and.w	d0,d1			; only activate buttons that were pressed this frame (but not held)
    		move.w	d1,(a0)+		; and then save pressed buttons
    
    	ifne CTRL_ENABLE_MULTI
    		clr.l	(a0)+			; clear buttons for multitap ctrls
    		clr.l	(a0)+
    		clr.l	(a0)+
    	endif
    		rts
    
    	ifne CTRL_ENABLE_MULTI
    .multi		moveq	#CTRL_TR,d2		; TR hi
    		move.b	2-1(a2),d4		; get the status of connected ctrls
    
    	rept 7
    		move.b	d2,(a1)
    		jsr	MTapWaitHandShake(pc)
    	endr
    
    		move.b	d2,(a1)
    		bsr.s	.mctrldo		; get button presses
    		bsr.s	.mctrldo		; get button presses
    		bsr.s	.mctrldo		; get button presses
    		bsr.s	.mctrldo		; get button presses
    
    		move.b	#CTRL_TR|CTRL_TH,(a1)
    		moveq	#CTRL_TH,d3		; TH hi
    		moveq	#0,d2			; TH lo
    		rts
    
    .mctrldo	moveq	#3,d0			; prepare and value
    		and.b	d4,d0			; and with ctrl value
    		lsr.b	#2,d4			; shift out this ctrl dat
    		add.b	d0,d0			; double d0
    		jsr	.mxof(pc,d0.w)		; jump to code
    
    		cmp.b	#J_S|J_A|J_R|J_L,-1(a0)	; check if this specific code is met (glitch when switching from mtap)
    		bne.s	.tok			; if is not, skip
    
    		clr.l	-4(a0)			; ignore user input for now
    .tok		rts
    
    .mxof		bra.s	.m3
    		bra.s	.m6
    
    		nop				; fall through
    		clr.l	(a0)+			; clear next buttons
    		rts
    
    .m6		bsr.s	.get3
    		jsr	MTapWaitHandShake(pc)
    		move.b	(a1),d1			; get SABC
    		move.b	d2,(a1)
    
    		andi.w	#$f,d1			;
    		lsl.w	#8,d1			; to high byte
    		or.w	d1,d0			; or back
    		not.w	d0			; negate
    
    		move.w	(a0),d1			; get pressed button data
    		eor.w	d0,d1			; toggle off inputs that are being held
    		move.w	d0,(a0)+		; put held buttons to a0
    		and.w	d0,d1			; only activate buttons that were pressed this frame (but not held)
    		move.w	d1,(a0)+		; and then save pressed buttons
    		rts
    
    .m3		bsr.s	.get3
    		not.b	d0
    
    		clr.b	(a0)+			; clear high byte
    		move.b	(a0),d1			; get pressed button data
    		eor.b	d0,d1			; toggle off inputs that are being held
    		move.b	d0,(a0)+		; put held buttons to a0
    		and.b	d0,d1			; only activate buttons that were pressed this frame (but not held)
    		clr.b	(a0)+			; clear high byte
    		move.b	d1,(a0)+		; and then save pressed buttons
    		rts
    
    .get3		bsr.s	MTapWaitHandShake
    		move.b	(a1),d0			; get UDLR
    		move.b	d2,(a1)
    
    		andi.w	#$F,d0
    		bsr.s	MTapWaitHandShake
    		move.b	(a1),d1			; get SABC
    		move.b	d2,(a1)
    
    		lsl.b	#4,d1
    		or.b	d1,d0
    		rts
    
    MTapWaitHandShake:
    		moveq	#5,d3
    		eor.b	#CTRL_TR,d2		; switch TR level
    		beq.s	.tl_lo
    
    .wait		btst	#CTRLbTL,(a1)
    		dbeq	d3,.wait		; wait for 5 attempts or for TL to be high
    		rts
    
    .tl_lo		btst	#CTRLbTL,(a1)
    		dbne	d3,.tl_lo		; wait for 5 attempts or for TL to be low
    		rts
    
    ReadEA:
    		lea	$A10005,a2
    		moveq	#4-1,d4
    		moveq	#$C,d3
    		moveq	#$10,d2
    
    .read		move.b	d3,(a2)
    	ctrl_delay
    		move.b	#$00,(a1)		; lower TH
    
    		move.w	#$FF00|CTRL_TR|CTRL_TL,d0; prepare for and (note: $FF00 is added, so that 3-button pads have high byte being 0!)
    		and.b	(a1),d0			; then and the value
    		move.b	#$40,(a1)		; raise TH
    		lsl.b	#2,d0			; shift in place
    
    		moveq	#CTRL_TR|CTRL_TL|$F,d1	; prepare for and
    		and.b	(a1),d1			; then and the value
    		or.l	d1,d0			; or together
    
    	; now check for 6-button controllers!
    		move.b	#$00,(a1)		; lower TH
    	ctrl_delay
    		move.b	#$40,(a1)		; raise TH
    	ctrl_delay
    		move.b	#$00,(a1)		; lower TH
    		nop
    
    		moveq	#$F,d1			; prepare for and
    		and.b	(a1),d1			; and the next value
    		bne.s	.not6			; apparently this means no 6-button pads
    		move.b	#$40,(a1)		; raise TH
    		nop
    
    		moveq	#$F,d1			; prepare for and
    		and.b	(a1),d1			; and the next value
    		lsl.w	#8,d1			; shift to upper byte
    		and.w	#$FF,d0			; clear out high bits
    		or.w	d1,d0			; and then or together
    
    .not6		not.w	d0			; Invert basic buttons
    		move.b	#$40,(a1)		; raise TH just in case
    
    		move.w	(a0),d1			; get pressed button data
    		eor.w	d0,d1			; toggle off inputs that are being held
    		move.w	d0,(a0)+		; put held buttons to a0
    		and.w	d0,d1			; only activate buttons that were pressed this frame (but not held)
    		move.w	d1,(a0)+		; and then save pressed buttons
    
    		add.b	d2,d3			; next port read
    		dbf	d4,.read		; continue onwards
    		rts
    	endif
    And finally, as a small touch, we need to selectively enable controller polling on certain screen modes to allow the user to switch even after game boot. We will be placing this line until further notice:
    Code:
    		st	PollChgCTRL.w	; enable control polling
    So, let's go to Sega_WaitPallet, and right above it, place the code. Next in Title_LoadText, insert our line right before this:
    Code:
    		move.b	#0,($FFFFFE30).w ; clear lamppost counter
    Finally, we must also disable this polling at some points. We will be using the following code until further notice:
    Code:
    		clr.b	PollChgCTRL.w	; disable control polling
    Right below Level_LoadPal, place our code. Next in SS_ClrNemRam, insert our line right before this:
    Code:
    		clr.b	($FFFFF64E).w
    Near Cont_MainLoop, insert the line right above this:
    Code:
    		bsr.w	Pal_FadeTo
    Near End_LoadData, insert the line above this:
    Code:
    		move.w	#$1E,($FFFFFE14).w
    Go to loc_5862, and put the line in. At loc_478, put in the code.

    Finally, open your build script (e.g. build.bat), and stick /k right after asm68k. Now build it and see if it works! If it does not, make sure you followed all the instructions correctly. If you still have issues, here are the files for the Hivebrain 2005 disassembly with the changes applied.

    How to use the 6-button pads.
    The 6-button pads sport a few extra buttons, X, Y, Z and M. Using these is simple enough; You can use the equates provided by me. All Jb? (where ? can be anything) are for use with bit testing, and all J_? (where ? can be anything) are for pretty much any other operation.

    Here is an example where I've chained together some button checks:
    Code:
    		btst	#JbB,Ctrl1Held+1.w		; check if B button is held on controller 1A
    		btst	#JbM,Ctrl1BPress.w		; check if MODE button is pressed on controller 1B
    		cmp.w	#J_U|J_D|J_A|J_Y|J_M,Ctrl2DHeld.w ; check if Up, Down, A, Y and MODE are held on controller 2D
    		and.w	#J_ABC|J_XYZ,Ctrl2Press.w	; check if either A, B, C, X, Y or Z are pressed on controller 2A
    		or.w	#J_S,Ctrl2CHeld.w		; force Start to be held on controller 2C
    As you can see, we can do some pretty interesting things here. Of course, you can still interface with it like any normal 3-button controller, but keep in mind all the button data is in low byte. Also, you can easily check if 6-button pad is in use, if the high bit is set (bit 15). In 6-button pads, the upper 4 bits are always being set to 1

    How to use the multitap.
    The multitap and EA 4-way play are slightly different from normal controllers. In order to enable these multitap features, you must put 1 in the line with CTRL_ENABLE_MULTI (or put a 0 if you want to disable it).

    Let's start with SEGA's multitap. Since multitap can have up to 4 controllers attached, there is 4 entries for controllers in a row (e.g. Ctrl1Held to Ctrl1DPress). These entries can be used to read controller data for all the ports of a multitap (A-D) in MULTI setting, but only the selected port in A-D setting (EXTRA setting is EA compatibility mode, see below). To check if multitap is connected to a port, check Ctrl1State & Ctrl1State+1 (for port 2). If they are -2, multitap is connected and is in MULTI mode.

    To check what controllers are inserted, check value in Ctrl1MTypes & Ctrl1MTypes+1 (for port 2). Each controller is represented by 2 bits, from A to D. Here are the button values:
    • 00 - 3-button pad
    • 01 - 6-button pad
    • 10 - none/unknown
    • 11 - none
    Remember that, a multitap can be connected to either port 1, port 2 or both.

    How to use the EA 4-way play.
    The EA 4-way play, or the EXTRA mode on multitap devices works slightly differently. Because of this, you can not know what control pads are in use (At least with my code design, if you can find a way, please share it). The information in Ctrl1MTypes is not valid. To check if 4-way play is connected, or multitap is in EXTRA mode, Ctrl1State and Ctrl1State+1 should contain $EA.

    Also, it is not possible any other controllers are inserted with 4-way play or multitap in EXTRA mode. You can still read controller input just like in multitap with the MULTI mode, but you must only use 1A-1D.

    Credits, etc.
    • Multitap documentation.
    • Genesis Plus GX source code.
    • RedHotSonic - Testing help
    • MarkeyJester - Getting me a multitap to do hardware tests on! (Thanks a lot!)
    • Regen - Being stupid bitch and not supporting EA 4-way play correctly.
    • SEGA - For being idiots and making multitap be inferior to EA 4-way play.
     
    Last edited: Feb 14, 2017
    Calvin, SomeRDude, BinBowie and 9 others like this.
  2. Clownacy

    Clownacy UP - ON - CPU Staff

    Joined:
    Aug 15, 2014
    Messages:
    726
    Location:
    Englandland
    Oh thank goodness, I don't have to clean up and release my multitap code. Yours is way easier to migrate a hack to.
     
    FireRat likes this.
  3. Crash

    Crash Well-Known Member Member

    Joined:
    Jul 15, 2010
    Messages:
    294
    Location:
    Australia
    Shouldn't that instruction to disable polling be after Level_LoadPal rather than before it? Having polling enabled during gameplay seems to cause erroneous presses of X,Y,Z and Mode while having the dpad held on the frames where it polls.
     
  4. Natsumi

    Natsumi Markey's Member

    Joined:
    Oct 7, 2011
    Messages:
    516
    Location:
    Otter's lap
    Well that's stupid and awkward oversight... I did mean below it. However, it should not cause any glitches with what buttons are active, I do not know what could cause that.

    EDIT: Dicks, I can't seem to edit the post without the tabs breaking... Sigh...
     
  5. Crash

    Crash Well-Known Member Member

    Joined:
    Jul 15, 2010
    Messages:
    294
    Location:
    Australia
    I can reproduce the wrong inputs problem on a clean disassembly with your example sonic1.asm by just changing the PauseGame routine to check for "mode" instead of "start", and holding right in game for a few seconds (with polling still enabled). Right is on the same bit in the low byte as mode is in the high byte so I'm guessing that's something to do with the problem, but since I don't know much about how all the "pull TH high/low" stuff works I can't really see why it's doing it.

    A few other things i noticed that don't seem right:
    - doesn't assemble when the multitap code is disabled cos the ".ctrl" label is inside the disabled code
    - polling only occurs every 256 frames rather than every 8 like the comments suggest
    - the rts at the end of the InitPads routine seems to prevent it from returning to the ReadJoypads routine so the cotroller inputs aren't updated on that frame
     
  6. Natsumi

    Natsumi Markey's Member

    Joined:
    Oct 7, 2011
    Messages:
    516
    Location:
    Otter's lap
    OK, so I was able to find many of these bugs, and fix most of them...

    The first problem which would mess up 6-button controller input whenever polled, was because of me forgetting to pull TH high after we were done with detection. Few lines above CheckEA, there is move.b (a1),d0, add below this; move.b d3,(a1)

    Next, the issue with polling was my derp. As the comments suggests, move.b $FFFFFE0C+3.w,d0 was supposed to be and.b $FFFFFE0C+3.w,d0.

    It is completely intentional that you can not read ctrl input during the frames the controllers are polled. This is because you can not get 100% accurate output from all devices until the next frame (ish, since the reset actually occurs in less time, but not enough to do it during v-int). Also the used CPU time is a concern, because detection takes a fair bit of time. I think ignoring input every 8 frames is good enough compromise.

    Finally, there seems to be an issue with the EA 4-way play protocol, if you hold Up and Down on the dpad at the same time, you can't really tell whether it was a controller or the multitap sending the signal. I was able to check UDLR instead, however there is no way for me to avoid this bug further that I could find. As hotfix you could check during your game's code if UDLR was pressed or held, and ignore any button input if done so. However, I do not consider this major enough to dive deep into the protocol to fix this fully in every case, because you really cant hold all the directions at once in actual hardware. Alas, this will be an issue you have to deal with if you so choose. few lines into CheckEA, you will see moveq #3,d0, just change it to moveq #$F,d0. I am not exactly sure why this works however (as the comment suggests, this should NOT work). Unfortunately EA 4-way play is not really documented anywhere, so I had hard time checking if the code works right.

    Since I can't seem to edit the posts still, these changes will not yet be reflected in the actual tutorial, however I have updated the download with the changes intact.
     
    FireRat likes this.
  7. redhotsonic

    redhotsonic Also known as RHS Staff

    Joined:
    Aug 10, 2007
    Messages:
    2,941
    Location:
    England
    Why can't you edit your first post? Is it something I missed?

    Also, good work on getting 4 controllers working. Now if only I could be arsed (and had the time) to continue working on Sonic Bash =P
     
    FireRat and Natsumi like this.
  8. nineko

    nineko I am the Holy Cat Member

    Joined:
    Mar 24, 2008
    Messages:
    1,689
    Location:
    italy
     
  9. オルドスアルファ

    オルドスアルファ RIGHT! Naebody move! Administrator

    Joined:
    Aug 5, 2007
    Messages:
    1,759
    Location:
    Glasgow, Scotland
    While this was an interesting read and no doubt usable by many, it's sadly of no use to me.
     
  10. Natsumi

    Natsumi Markey's Member

    Joined:
    Oct 7, 2011
    Messages:
    516
    Location:
    Otter's lap
    The post has been edited to reflect these changes, finally. It looks like disabling using rich text boxes fixed the issue, though its rather silly thing to have to do...
     
    MarkeyJester likes this.