aboutsummaryrefslogtreecommitdiff
path: root/tests/runrom/joypad.s
blob: 18394dd8eacf238383005baca335ebe58947ec71 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
.segment "HEADER"
    .byte 'N', 'E', 'S', $1A
    .byte $02, $01
    .byte $00
    .byte $00

.segment "CHARS"
.byte 0

.segment "CODE"
;; asan:stack full

;; NOTE: adapted from git.mssola.com/nes/code.nes
.scope Joypad
    ;; Button masks.
    BUTTON_A      = 1 << 7
    BUTTON_B      = 1 << 6
    BUTTON_UP     = 1 << 3
    BUTTON_DOWN   = 1 << 2

    ;; Port addresses for controllers.
    m_joypad1 = $4016

    ;; After running a `joypad_read_*` function these two variables will contain
    ;; the given result.
    zp_buttons1 = $22

    ;;;
    ;; Safely read via a re-read algorithm the joypad as indexed by the X register
    ;; (0 for controller 1; 1 for controller 2).
    .proc read_x
        jsr Joypad::unsafe_read_x

        ;; The main idea around a re-read algorithm is that you read the
        ;; controller "unsafely" once, then you do it again and compare both
        ;; reads. If they were the same then we are on the safe side. Otherwise
        ;; we would need to loop until we get two identical reads. This sounds
        ;; bad but in practice it's not so much (and hey, if it worked for Super
        ;; Mario Bros. 3, it should work for us too :P). Otherwise there is the
        ;; algorithm via OAM DMA, but it sure is tricky.
    @reread:
        lda Joypad::zp_buttons1, x
        tay
        jsr Joypad::unsafe_read_x
        tya
        cmp Joypad::zp_buttons1, x
        bne @reread

        rts
    .endproc

    ;;;
    ;; Read the joypad as indexed by the X register (0 for controller 1; 1 for
    ;; controller 2). This method is fast but it might be vulnerable to the DPCM
    ;; bug (see: https://www.nesdev.org/wiki/Controller_reading_code).
    .proc unsafe_read_x
        ;; Start the latch process.
        lda #$01
        sta Joypad::m_joypad1
        sta Joypad::zp_buttons1, x   ; Bit as a guard for the loop below.
        lsr
        sta Joypad::m_joypad1

        ;; Now the joypad is ready to accept reads.
    @loop:
        lda Joypad::m_joypad1, x
        and #%00000011              ; Ignore bits other than controller.
        cmp #$01                    ; Set carry if and only if nonzero.
        rol Joypad::zp_buttons1, x   ; Carry -> bit 0; bit 7 -> Carry
        bcc @loop
        rts
    .endproc
.endscope

;; Shortcut for reading the joypad from the first player safely.
.macro READ_JOYPAD1
    ldx #$00
    jsr Joypad::read_x
.endmacro

zp_count = $00
zp_up    = $01
zp_down  = $02
zp_a     = $03
zp_b     = $04

.proc reset
    lda #0
    sta zp_count
    sta zp_up
    sta zp_down
    sta zp_a
    sta zp_b

    READ_JOYPAD1
    lda #(Joypad::BUTTON_UP | Joypad::BUTTON_A)
    and Joypad::zp_buttons1
    beq :+
    inc zp_up
    inc zp_a
    inc zp_count
    inc zp_count

:
    READ_JOYPAD1
    lda #(Joypad::BUTTON_DOWN | Joypad::BUTTON_B)
    and Joypad::zp_buttons1
    beq :+
    inc zp_down
    inc zp_b
    inc zp_count
    inc zp_count

:
    rts
.endproc

.segment "VECTORS"
    .addr reset, reset, reset