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

Discussion in 'Tutorials Archive' started by AURORA☆FIELDS, Jan 15, 2017.

  1. AURORA☆FIELDS

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    759
    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
        endif
    
    .ctrl    ifne CTRL_ENABLE_MULTI
            move.b    #CTRL_TH|CTRL_TR,6(a1)
            jsr    CheckTeamPlay(pc)
            beq.w    .end
            move.b    #CTRL_TH,6(a1)
            moveq    #0,d2        ; th lo
            nop
        else
            move.b    #CTRL_TH,6(a1)
        ctrl_delay
        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)
            moveq    #0,d0        ; for beq
    .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: Mar 24, 2018
  2. Clownacy

    Clownacy Retired Staff lolololo Member

    Joined:
    Aug 15, 2014
    Messages:
    1,016
    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:
    302
    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. AURORA☆FIELDS

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    759
    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:
    302
    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. AURORA☆FIELDS

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    759
    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 Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    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 AURORA☆FIELDS like this.
  8. nineko

    nineko I am the Holy Cat Member

    Joined:
    Mar 24, 2008
    Messages:
    1,902
    Location:
    italy
     
  9. OrdosAlpha

    OrdosAlpha RIGHT! Naebody move! Root Admin

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

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    759
    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.