; *****************************************************************************
; *****************************************************************************
;
;                         NES I/O Stuff
;
; *****************************************************************************
; *****************************************************************************

_Transfer:      db      0               ; Value read/written
PPUReadInvalid: db      0               ; if non-zero,next PPU Read is invalid
_ScrollToggle:  dw      1               ; 0 if next->VScroll,1 if next->HScroll
_AddrToggle:    dw      1               ; 1 if next->high,0 if next->low
_Last4016:      db      0               ; last value written to $4016
_JOffset:       db      -1              ; next key function to be read.

; *****************************************************************************
;
;                       Read joystick port ($4016)
;
; *****************************************************************************
Read4016:
        mov     byte [_Transfer],0h     ; return zero,by default
        mov     bl,[_JOffset]           ; get next read into bx
        cmp     bl,0
        js      Ret
        xor     bh,bh
        cmp     bl,1                    ; special case for left-alt
        jne     R4016c
        test    byte [KeyTable+02Ch],128 ; z has same function
        jnz     R4016d
R4016c:
        mov     al,JoyKeyTable[bx]      ; get the key number to read
        mov     bl,al
        xor     bh,bh
        test    byte KeyTable[bx],128   ; get whether its on or not !
        jz      R4016a
R4016d: mov     byte [_Transfer],41h    ; its pressed,return 1.
R4016a: inc     byte [_JOffset]         ; next time,read next key.
        mov     al,[_JOffset]
        cmp     al,8
        jb      ret
        mov     byte [_JOffset],-1
        ret

JoyKeyTable:
        db      039h                    ; Space (A)
        db      038h                    ; Left-Alt (B)
        db      00Fh                    ; TAB (Select)
        db      01Ch                    ; Enter (Start)
        db      048h                    ; Up
        db      050h                    ; Down
        db      04Bh                    ; Left
        db      04Dh                    ; Right

; *****************************************************************************
;
;                      Read from RAM at 6000-7FFF
;
; *****************************************************************************

ReadBatteryRAM:
        and     bx,01FFFh
        mov     al,BatteryRAM[bx]       ; get byte from battery RAM
        mov     [_Transfer],al
        ret

; *****************************************************************************
;
;                         Read from PPU Register
;
; *****************************************************************************

NesRead:mov     byte [_Transfer],0
        cmp     bh,60h
        jge     ReadBatteryRAM
        cmp     bx,04016h
        je      Read4016
        cmp     bh,020h
        jne     ret

        cmp     bl,00h                  ; read Ctrl1
        je      NRCtrl1
        cmp     bl,01h                  ; read Ctrl2
        je      NRCtrl2
        cmp     bl,02h                  ; read Status
        je      NRStatus
        cmp     bl,04h                  ; read sprite memory
        je      NRSpDat
        cmp     bl,07h                  ; read PPU Memory
        je      NRPPUMemory
        xor     al,al
NR1:    mov     byte [_Transfer],al
        ret

NRCtrl1:                                ; read Ctrl 1
        mov     al,[PPUCtrl1]
        jmp     NR1
NRCtrl2:                                ; read Ctrl 2
        mov     al,[PPUCtrl2]
        jmp     NR1
NRStatus:
        mov     al,[PPUStatus]          ; read status
        and     byte [PPUStatus],63     ; clear Hit and VBlank bit
        jmp     NR1

NRSpDat:mov     bl,[PPUSpriteAddr]      ; bx = address
        mov     bh,SpriteRam / 256      ; Must be on a 256byte boundary !
        mov     al,[bx]                 ; read the data
        inc     byte [PPUSpriteAddr]    ; inc sprite address register
        jmp     NR1

NRPPUMemory:
        mov     al,[PPUReadInvalid]     ; invalid read ????
        cmp     al,0
        jnz     NRPPInv                 ; yes,its an invalid read
        mov     bx,[PPUMemoryAddr]      ; look at the address
        inc     word [PPUMemoryAddr]    ; increment the memory pointer

        cmp     bx,02000h               ; < 2000h
        jb      NRPPVRom                ; read VROM.
        cmp     bx,03000h               ; < 3000h
        jb      NRPPURam                ; read PPU Ram
        cmp     bx,03F00h               ; otherwise if not 3F00-3F10 invalid
        jb      NRPPInv
        cmp     bx,03F20h
        jae     NRPPInv

        and     bx,01Fh                 ; read the palette
        mov     al,NESPalette[bx]       ; 3f00-3f1f
        jmp     NR1

NRPPInv:mov     byte [PPUReadInvalid],0 ; clear the invalid flag
        xor     al,al
        jmp     NR1

NRPPVRom:                               ; read from VROM
        mov     al,VRom[bx]             ; 0000-1fff
        jmp     NR1

NRPPURam:                               ; read PPU Memory (2000-2fff)
        call    MapPPUAddress           ; map address to PPU Ram
        mov     al,PPURam[bx]           ; read it
        jmp     NR1

; *****************************************************************************
;
;                       Write to joystick strobe (4016h)
;
; *****************************************************************************

WRStrobe:
        mov     al,[_Transfer]          ; has value changed from last strobe
        and     al,1                    ; write ?
        cmp     al,[_Last4016]
        je      WRSExit
        cmp     al,0                    ; yes. has it gone from 1 to 0 ?
        jnz     WRSExit

        mov     byte [_JOffset],0       ; reset the joystick read counter

WRSExit:mov     al,[_Transfer]          ; Update the last value
        and     al,1
        mov     [_Last4016],al
        ret

; *****************************************************************************
;
;                        DMA Sprite Transfer
;
; *****************************************************************************

DMASprite:
        push    ax,bx,cx,di,es

        mov     bh,[_Transfer]          ; get the transfer byte
        xor     bl,bl                   ; so BX = Copy Address
        CPUAddress                      ; do an address conversion.
                                        ; ES:BX points to the memory to copy
        mov     di,SpriteRAM            ; DS:DI = target
        mov     cl,128                  ; words to copy
DMALoop:mov     ax,es:[bx]              ; copy a word
        mov     [di],ax
        add     di,2
        add     bx,2
        dec     cl                      ; copy 128 words !!
        jnz     DMALoop

        pop     es,di,cx,bx,ax
        ret

; *****************************************************************************
;
;                       Write to battery backup RAM
;
; *****************************************************************************

WriteBatteryRam:
        mov     al,[_Transfer]
        and     bx,01FFFh
        mov     BatteryRam[bx],al
        ret

; *****************************************************************************
;
;                           Write to PPU Register
;
; *****************************************************************************
NesWrite:
        cmp     bh,060h
        jge     WriteBatteryRam
        cmp     bx,04014h               ; is it DMA Sprite Transfer
        je      DMASprite
        cmp     bx,04016h               ; is it strobe write ?????
        je      WRStrobe
        cmp     bx,02007h               ; is it PPU Data write ???
        jne     NotDataWrite

        mov     bx,[PPUMemoryAddr]      ; get the PPU memory address
        cmp     bx,02000h               ; if < $2000 writing to VRAM ?
        jl      WriteVRAM
        cmp     bx,03000h               ; if < $3000h its the tile table
        jae     CheckPaletteWrite

        call    MapPPUAddress           ; convert PPU Address to PPU Offset
        add     bx,PPURam               ; now its an address....
WriteToBX:
        mov     al,[_Transfer]          ; byte to write
        mov     [bx],al                 ; and write it.
#if D_TraceRW=1
        call    ConHex                  ; this echoes the write
        mov     al,'>'
        call    ConOut
        mov     ax,[PPUMemoryAddr]
        call    ConWord
        mov     al,' '
        call    ConOut
        mov     ax,bp
        call    ConWord
        mov     al,13
        call    ConOut
#endif

NoWrite:
        mov     bx,[PPUMemoryAddr]      ; increment the memory address
        inc     bx
        test    byte [PPUCtrl1],4       ; if bit 2 of PPUCtrl1 set
        jz      NoVInc
        add     bx,31                   ; its a vertical increment
NoVInc: mov     [PPUMemoryAddr],bx      ; write the address back
        ret

WriteVRAM:                              ; writing to VRAM.
        mov     al,[NESHeader+5]
        cmp     al,0
        jnz     NoWrite                 ; sorry can't do it if there is VROM

        mov     al,[_Transfer]          ; yes , we can write to VRAM
        mov     VROM[bx],al             ; so lets do it !
        jmp     NoWrite

CheckPaletteWrite:                      ; writing to palette
        cmp     bx,03F00h               ; must be 3F00-3F1F
        jb      NoWrite
        cmp     bx,03F20h
        jae     NoWrite
        and     bx,01Fh                 ; its now a value 00-1F
        jz      IsPalette0
        cmp     bx,010h
        jz      IsPalette0
        add     bx,NESPalette           ; add it to the palette
        jmp     WriteToBX
IsPalette0:
        mov     al,[_Transfer]          ; if writing to 3f00,3f10 do it to
        mov     [NESPalette],al         ; both of them !
        mov     [NESPalette+16],al
        add     bx,NESPalette
        jmp     WriteToBX

NotDataWrite:
        test    bh,128                  ; writing to ROM Space ?
        jnz     WriteROM

        cmp     bh,020h                 ; write to $20xx registers ?
        jne     ret

        push    ax,bx
        mov     al,[_Transfer]          ; get the byte to write

        cmp     bl,03h                  ; write to sprite address register
        je      NWSpAdr
        cmp     bl,04h                  ; write to sprite data register
        je      NWSpDat
        cmp     bl,05h                  ; write to Background Scroll
        je      NWScroll
        cmp     bl,06h                  ; write to PPU memory address
        je      NWAddr
        cmp     bl,00h                  ; write to Control Reg 1
        je      NWCtrl1
        cmp     bl,01h                  ; write to Control Reg 2
        je      NWCtrl2

NESWExit:                               ; exit through here
#if D_TraceReg=1
        call    PPURegisterDump
#endif
        pop     bx,ax
        ret

NWCtrl1:mov     [PPUCtrl1],al           ; write to PPU Control Reg 1
        jmp     NESWExit

NWCtrl2:mov     [PPUCtrl2],al           ; write to PPU Control Reg 2
        jmp     NESWExit

NWSpAdr:mov     [PPUSpriteAddr],al      ; write to sprite address register
        jmp     NESWExit

NWSpDat:mov     bl,[PPUSpriteAddr]      ; bx = address
        mov     bh,SpriteRam / 256      ; Must be on a 256byte boundary !
        mov     [bx],al                 ; write the data
        inc     byte [PPUSpriteAddr]    ; inc sprite address register
        jmp     NESWExit
        
NWScroll:
        mov     bx,[_ScrollToggle]      ; get the scroll toggle
        mov     PPUVScroll[bx],al       ; write value to register
        xor     bx,1                    ; toggle the scroll toggle
        mov     [_ScrollToggle],bx      ; and write it back
        jmp     NESWExit

NWAddr:
        mov     bx,[_AddrToggle]        ; get the address toggle
        mov     PPUMemoryAddr[bx],al    ; write value to register
        xor     bx,1                    ; toggle the address toggle
        mov     [_AddrToggle],bx        ; and write it back
        mov     byte [PPUReadInvalid],1 ; next PPU Read will be invalid
        jmp     NESWExit

WriteROM:                               ; switch ROM Banks
        mov     al,[CartType]
        cmp     al,1
        je      CartType1
        cmp     al,2
        je      CartType2
        mov     al,[_Transfer]
        and     ax,3
        call    SwitchROM
        jmp     NESWExit

_RomPage:db     0                       ; Current ROM Page

CartType1:
        call    Type1Write
        jmp     NESWExit

; *****************************************************************************
;
;                       Handle type 2 cartridges
;
; *****************************************************************************

CartType2:
        mov     bl,[_Transfer]
        cmp     bl,[_RomPage]
        je      NESWExit
        mov     [_RomPage],bl
        xor     bh,bh
        mov     ax,08000h
        call    CopyRom
        jmp     NESWExit

; *****************************************************************************
;
;             MAP PPU Address in BX to Address in Data Segment
;
; *****************************************************************************

MapPPUAddress:
        sub     bh,020h                 ; PPU Addresses are 2000-2FFFF
        cmp     bh,04h                  ; 20xx-23xx map onto 0,always.
        jl      ret
        test    byte [_HMirror],1       ; if horizontal mirroring...
        jnz     MapPPUHorizontal
        and     bh,0F7h                 ; vertical mirroring,drop bit 11
        ret                             ; so 2800,2C00 -> 2000,2400

MapPPUHorizontal:
        and     bh,0FBh                 ; drop bit 10. 2400->2000,2C00->2800
        test    bh,8                    ; if its in the 28/2C Map
        jz      ret                     ; (e.g. if bit 11 is set)
        xor     bh,0Ch                  ; toggle bits 11 and 10
        ret

; *****************************************************************************
;
;       Set Palette item bx (0..31) according to value in _Transfer
;
;       Note: in NESA - Tile palette = cols 0..15,Sprite = cols 16..31
;
; *****************************************************************************
SetPalette:
        push    es,ax,bx,cx,dx
        mov     cl,040h
        mov     bx,NewPaletteTable
SPLoop: call    SetVGAPalette
        add     bx,4
        inc     cl
        cmp     cl,080h
        jne     SPLoop
        pop     dx,cx,bx,ax,es
        ret
; ****************************************************************************
;
;               Set VGA Colour CL to be [BX],[BX+1],[BX+2]
;
; ****************************************************************************

SetVGAPalette:
        mov     al,255
        mov     dx,03c6h
        out     dx,al
        inc     dx,2
        mov     al,cl
        out     dx,al
        inc     dx
        mov     al,0[bx]
        shr     al,2
        out     dx,al
        mov     al,1[bx]
        shr     al,2
        out     dx,al
        mov     al,2[bx]
        shr     al,2
        out     dx,al
        ret

; *****************************************************************************
;
;                       Keyboard test routine
;
; *****************************************************************************

KeyTest:mov     bx,0
Key1:   test    byte KeyTable[bx],128
        jz      Key2
        mov     al,bl
        call    ConHex
        mov     al,' '
        call    ConOut
Key2:   inc     bx
        cmp     bx,128
        jne     Key1
        mov     al,[KeyTable+1]
        cmp     al,0
        je      KeyTest
        ret

; *****************************************************************************
;
;                             PPU Register Dump
;
; *****************************************************************************
PPURegisterDump:
        push    ax,bx,cx,dx,si,di
        mov     si,PPURDName
        mov     bx,PPUCtrl1
        mov     cl,8
PPURD1: mov     al,[si]
        call    ConOut
        mov     al,1[si]
        call    ConOut
        add     si,2
        mov     al,'='
        call    ConOut
        mov     al,[bx]
        call    ConHex
        inc     bx
        mov     al,' '
        call    ConOut
        dec     cl
        jnz     PPURd1
        mov     al,13
        call    ConOut
        pop     di,si,dx,cx,bx,ax
        ret
PPURDName:
        db      'C1','C2','St','Sp','Vs','Hs','Lo','Hi'

; *****************************************************************************
;
;                 Switch to ROM Bank in AL (Type 3 carts)
;
; *****************************************************************************

SwitchROM:
        push    ax,bx
        xor     ah,ah                   ; ax = bank#
        shl     ax,3                    ; multiply it by 8
        add     ax,16                   ; Copy ROMSpace+16k (VROM) into VRAM
        cmp     byte [NESHeader+4],1    ; if 2 16k ROM banks
        je      SWR1
        add     ax,16                   ; ROM Space is at 32k
SWR1:
        call    CopyVROM                ; space
        pop     bx,ax
        ret

; *****************************************************************************
;
;                  Write to $8000..$FFFF (Type 1 carts)
;
; *****************************************************************************

Type1Write:
        and     bx,7FFFh                ; drop bit 7
        shr     bx,13                   ; this makes ax 0,1,2, or 3
        mov     al,[_Transfer]
        test    al,128
        jnz     T1Reset                 ; if bit 7 data set its a reset
        test    al,1                    ; if bit 0 is set or mask & data
        jz      T1NoOr
        mov     al,T1Mask[bx]
        or      T1Data[bx],al
T1NoOr: mov     al,T1Mask[bx]           ; shift left mask
        add     al,al
        mov     T1Mask[bx],al
        cmp     al,32                   ; done 5 bits ? if not,exit
        jne     T1Exit

        mov     ah,bl                   ; ah = register#
        mov     al,T1Data[bx]           ; al = data
        call    Type1RegWrite

T1Reset:mov     byte T1Mask[bx],1       ; send in bit 0 next time.
        mov     byte T1Data[bx],0
T1Exit: ret

T1Data: db      0,0,0,0                 ; type 1 data
T1Mask: db      1,1,1,1                 ; next bit being set in data

; *****************************************************************************
;
;             Write  - Type 1 Register AH set to Data in AL
;
; *****************************************************************************

Type1RegWrite:
        push    ax,bx

        mov     bl,ah                   ; save the register data
        xor     bh,bh
        mov     T1Reg[bx],al

        cmp     ah,3
        je      SetRomPage1

        mov     al,[T1Reg+0]            ; update the horizontal mirroring.
        and     al,1
        mov     [_HMirror],al

        jmp     Type1Exit


T1Reg:  db      0,0,0,0                 ; registers

; *****************************************************************************
;
;                   ROM Change on Cartridge type 1
;
; *****************************************************************************

SetRomPage1:
        mov     bl,al                   ; bx = rom page number
        and     bl,15
        xor     bh,bh
        mov     ax,08000h               ; ax = address
        call    CopyROM                 ; copy the ROM.
Type1Exit:
        pop     bx,ax
        ret

NewPaletteTable:

        db      068h,068h,068h,0
        db      000h,040h,080h,0
        db      000h,000h,080h,0
        db      040h,000h,080h,0

        db      080h,000h,080h,0
        db      080h,000h,040h,0
        db      080h,000h,000h,0
        db      055h,000h,000h,0

        db      040h,040h,000h,0
        db      033h,050h,000h,0
        db      000h,050h,000h,0
        db      000h,050h,000h,0

        db      000h,040h,040h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0

        db      098h,098h,098h,0
        db      000h,080h,0C0h,0
        db      040h,040h,0C0h,0
        db      080h,000h,0C0h,0

        db      0C0h,000h,0C0h,0
        db      0C0h,000h,080h,0
        db      0C0h,020h,020h,0
        db      0C0h,040h,000h,0

        db      080h,080h,000h,0
        db      055h,080h,000h,0
        db      000h,080h,000h,0
        db      033h,080h,033h,0

        db      000h,080h,080h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0

        db      0D0h,0D0h,0D0h,0
        db      040h,0C0h,0FFh,0
        db      080h,080h,0FFh,0
        db      0C0h,040h,0FFh,0

        db      0FFh,000h,0FFh,0
        db      0FFh,040h,0C0h,0
        db      0FFh,050h,050h,0
        db      0FFh,080h,040h,0

        db      0C0h,0C0h,000h,0
        db      080h,0C0h,000h,0
        db      000h,0C0h,000h,0
        db      055h,0C0h,055h,0

        db      000h,0C0h,0C0h,0
        db      033h,033h,033h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0

        db      0FFh,0FFh,0FFh,0
        db      080h,0FFh,0FFh,0
        db      0C0h,0C0h,0FFh,0
        db      0FFh,080h,0FFh,0

        db      0FFh,040h,0FFh,0
        db      0FFh,080h,0FFh,0
        db      0FFh,080h,080h,0
        db      0FFh,0C0h,080h,0

        db      0FFh,0FFh,040h,0
        db      0C0h,0FFh,040h,0
        db      040h,0FFh,040h,0
        db      0AAh,0FFh,0AAh,0

        db      040h,0FFh,0FFh,0
        db      099h,099h,099h,0
        db      000h,000h,000h,0
        db      000h,000h,000h,0



        db      148,148,148,0
        db     20, 84,244,0
        db     20, 52,244,0
        db     84, 52,244,0
        db      244, 20,244,0
        db      244, 20, 84,0
        db      244, 20,  4,0
        db      212, 20,  4,0
        db      180, 84,  4,0
         db    4,116,  4,0
         db    4,148,  4,0
         db    4,116, 20,0
         db    4, 84, 84,0
         db    0,  0,  0,0
         db    0,  0,  0,0
         db    0,  0,  0,0
        db      180,180,180,0
         db    4,116,244,0
         db    4, 84,244,0
        db     84, 84,244,0
        db      244, 52,244,0
        db      244, 52, 84,0
        db      244, 52,  4,0
        db      244, 84,  4,0
        db      148,148,  4,0
         db    4,180,  4,0
         db    4,212,  4,0
        db     20,212, 84,0
         db    4,180,244,0
         db    0,  0,  0,0
         db    0,  0,  0,0
         db    0,  0,  0,0
        db      212,212,212,0
         db    4,148,244,0
        db      116,116,244,0
        db      148,116,244,0
        db      244,116,244,0
        db      244, 84,148,0
        db      244,116, 84,0
        db      244,180,  4,0
        db      212,212,  4,0
        db      180,244, 20,0
        db      116,244, 52,0
        db     84,244,148,0
        db     52,212,244,0
        db      116,116,116,0
        db    0,  0,  0,0
        db    0,  0,  0,0
        db      244,244,244,0
        db      116,180,244,0
        db      180,180,244,0
        db      212,180,244,0
        db      244,148,244,0
        db      244,148,180,0
        db      244,180,116,0
        db      244,180, 52,0
        db      244,180, 84,0
        db      212,244,116,0
        db      180,244,180,0
        db      180,244,212,0
        db      148,212,244,0
        db      244,212,244,0
        db    0,  0,  0,0
        db    0,  0,  ,0

