; *****************************************************************************
; *****************************************************************************
;
;             Nintendo Entertainment System in Assembler (NESA)
;             =================================================
;
;  0.00 06-Sep-96       Started work on main body of the emulator
;       07-Sep-96       Writing to PPU Memory,Sprite Memory and Registers
;                       Got millipede title screen up ! this is great !
;       08-Sep-96       Got 'Popeye','Circus' and 'Millepede' title screens
;                       up,fixed an irritating bug in NESA.
;       09-Sep-96       Did a little bit of work on NESA (Read from PPU Memory)
;                       Got Mario 1 screen up.
;       10-Sep-96       Horizontal Scroll now works ok. Wrote sprite code
;                       Wrote Joystick Code.
; 0.10                  I CAN PLAY SUPER MARIO !!!!!!
; 0.11  11-Sep-96       Fixed interrupt bug (Z register update !)
;                       Fixed bug (fetch bl meant code in RAM didn't work)
; 0.12                  Did 8 x 16 sprites according to NES Architecture doc.
;                       Did Background sprites
;                       Had a go at the split screen code.
;                       Z Key as alternative to Left Alt (probs under Windows)
;                       Line Table -T switch
;                       Added support for type 3 cartridges
; 0.13                  Moved code pointers out of iNES file into a space
;                       which is copied to.
;                       Can write to VRAM (reqd. No VROM Banks)
;                       Added battery back-up RAM (not loaded/saved !)
;                       Added support for type 1/2 cartridges.
;                       Added scroll scan line override.
; 0.14                  Added toggle mirror switch.
; 0.15                  Screen mask count can be any value
;                       All sprites down one line !
;                       Added support for the HIT flag
; 0.16                  Fixed the colour palette
; 0.17                  Tweaked VGA mode
;
;                       Battle City Problems... Writing $C0 to $4017
;
; *****************************************************************************
; *****************************************************************************

LineSize equ 256                        ; bytes per VGA display line
ScrHeight equ 224

        jmp     start

        org     120h                    ; for joysticks....
        dw      0,0,0,0
        db      'NESJoyCal'
        even

; *****************************************************************************

D_TraceCPU      equ     0               ; if 1 trace the CPU
D_TraceRW       equ     0               ; if 1 trace W to PPU Ram
D_TraceReg      equ     0               ; if 1 trace Registers
D_TracePalette  equ     0               ; if 1 trace palette
D_Debugger      equ     0               ; if 1 remove gfx/int for debugger
D_Fast          equ     0               ; if 1 go fast

CHorzScan:      dw      90              ; CPU Cycles/Horizontal Scan (visible)
CHorzBlank:     dw      10              ; CPU Cycles/Horizontal Scan (blank)
StartScan:      dw      8               ; Scan line where screen starts
EndScan:        dw      232             ; Scan line where screen ends
EndScreen:      dw      262             ; Scan line,end of screen
ScanLine:       dw      0               ; Current Scanline
ScrDrawMask:    dw      3               ; Screen draw mask
NoSplit:        dw      0               ; if 1 no split screen.

PPUCtrl1:       db      0               ; PPU Control Register 1 (R/W)
PPUCtrl2:       db      0               ; PPU Control Register 2 (R/W)
PPUStatus:      db      0               ; PPU Status (Read Only)
PPUSpriteAddr:  db      0               ; Sprite Memory Address
PPUVScroll:     db      0               ; Vertical Scroll
PPUHScroll:     db      0               ; Horizontal Scroll
PPUMemoryAddr:  dw      0               ; PPU Memory Address
_HMirror:       db      0               ; 1 if horiz mirror,0 if vert mirror
MToggle:        db      0               ; mirror toggle.
NESPalette:     db      32 dup 0        ; NES Palette Registers (3f00-3f1f)
LineTable:      dw      LineAddress     ; Line table
CartType:       db      0               ; cartridge type
OverRide:       db      0               ; line to override scroll pos at

VSSegment:      dw      0               ; Virtual Screen Segment
ESSegment:      dw      0               ; 64k code space
     
; *****************************************************************************

MSDOS = 33                              ; MSDOS Function Number

start:  mov     sp,Stack86+StackSize-2  ; initialise the 8086 stack.
        call    ClearRAM
#if D_Debugger=0
        call    ShowGraphic
#if LineSize=256
        call    TweakVideo
#endif
        call    SetNewInt9
        call    WrBanner
#endif

        mov     ax,cs                   ; Calculate where VScreen goes
        add     ax,256 * 1024 / 16      ; well out of the way !
        mov     [VSSegment],ax
        add     ax,68*1024/16
        mov     [ESSegment],ax          ; code space
        call    ClearVS

        call    Initialise
        CPUReset

main1:  call    Clock                   ; read and save CPU Clock

        push    ax
        call    DoScreen                ; do three screens
        call    DoScreen
        call    DoScreen
        pop     bx

#if D_Fast=0
wait1:  call    Clock                   ; wait until clock changed
        cmp     ax,bx                   ; frame rate about 55fps
        je      wait1
#endif
        mov     al,[KeyTable+1]
        cmp     al,0
        jz      main1

        call    ShowText
        jmp     DExit

; *****************************************************************************
;
;                            Do one CPU Screen
;
; *****************************************************************************

_TopSection:db  0                       ; top of current section
OldScroll:  dw   0                       ; previous VScroll/HScroll value

DoScreen:
        mov     ax,word [_ScrCount]     ; decrement screen mask count
        cmp     ax,0                    ; if its zero,reset the counter.
        jnz     _NoScrC1
        mov     ax,[ScrDrawMask]
_NoScrC1:
        dec     ax
        mov     word [_ScrCount],ax

        mov     word [ScanLine],0       ; back to the top of the screen
        mov     si,0                    ; reset the cycle counter to zero

TopVBlank:
        mov     ax,[StartScan]          ; do the top few scanlines.
        cmp     ax,[ScanLine]
        je      CompletedTopVBlank
        call    DoScanLine              ; do a scanline
        jmp     TopVBlank

CompletedTopVBlank:
        mov     al,[ScanLine]
        mov     [_TopSection],al
        and     byte [PPUStatus],63     ; reset the vblank+hblank flag
VisibleScreen:
        mov     ax,[PPUVScroll]         ; save scroll registers
        mov     [OldScroll],ax
        call    DoScanLine              ; do the visible screen
        mov     ax,[NoSplit]
        cmp     ax,0
        jne     VSNoChange
        mov     ax,[OldScroll]
        cmp     ax,[PPUVScroll]         ; reload scroll registers
        je      VSNoChange              ; if changed

        push    cx

        mov     ax,[PPUVScroll]
        push    ax
        mov     ax,[OldScroll]
        mov     [PPUVScroll],ax

        mov     cl,[_TopSection]

        mov     ch,[ScanLine]
        add     ch,8

        test    byte [OverRide],255
        jz      NoOvr
        mov     ch,[OverRide]
NoOvr:
        mov     [_TopSection],ch
        dec     ch
        call    Screen

        pop     ax
        mov     [PPUVScroll],ax

        pop     cx

VSNoChange:
        mov     ax,[ScanLine]
        cmp     ax,[EndScan]
        jne     VisibleScreen

        push    cx
        mov     cl,[_TopSection]
        mov     ch,[EndScan]
        call    Screen                  ; redraw the screen
        pop     cx

        or      byte [PPUStatus],128    ; set the vblank flag on.

        test    byte [PPUCtrl1],128     ; if interrupt enabled
        jz      BottomVBlank

        CPUNMI                          ; do it !

BottomVBlank:
        call    DoScanLine              ; do the bottom scanlines
        mov     ax,[ScanLine]
        cmp     ax,[EndScreen]
        jl      BottomVBlank
        ret

ExecuteDiCycles macro                   ; do at least Di Cycles
        add     di,si
M1:     call    Execute
#if D_TraceCPU=1
        cmp     bp,08C31h
        jne     >M3
        mov     byte [_Trace],1
M3:
        test    byte [_Trace],255
        jz      >M2
        call    Dump
M2:
#endif
        cmp     si,di
        jb      M1
#em

_Trace: db      0
      
DoScanLine:                             ; do a scan line (in VBlank)
        mov     ax,[ScanLine]           ; does scanline = Sprite 0 ????
        mov     di,SpriteRAM
        cmp     al,[di]
        jne     _DSL1
        or      byte [PPUStatus],64     ; set the hit bit
_DSL1:
        mov     di,[CHorzScan]          ; do HorzScan+HorzBlank cycles
        add     di,[CHorzBlank]         ; not strictly necessary now !
        ExecuteDiCycles
        inc     word [ScanLine]
        ret

; *****************************************************************************
;
;                              Return to MSDOS
;
; *****************************************************************************

DExit:
#if D_Debugger=0
        call    SetOldInt9
#endif
        mov     ah,04Ch
        int     MSDOS

; *****************************************************************************
;
;               Read file (name at ds:bx) into Memory
;
; *****************************************************************************

LoadROM:
        push    ax,bx,cx,dx,es,ds

        mov     dx,bx                   ; open the file.
        mov     al,0
        mov     ah,03Dh
        int     MSDOS
        jc      LoadFail
        push    ax                      ; save handle for close

        mov     bx,ax                   ; read the file bx = handle
        mov     ax,cs                   ; es = NESHeader
        add     ax,NESHeader/16
        mov     ds,ax
_LDLoop:
        xor     dx,dx                   ; load to ES:0
        mov     cx,8192                 ; bytes to read.
        mov     ah,03Fh
        int     MSDOS
        cmp     ax,8192
        jne     _ELoad
        mov     ax,ds
        add     ax,8192/16
        mov     ds,ax
        jmp     _LdLoop

_ELoad:
        pop     bx                      ; Close the file

        mov     ah,03Eh
        int     MSDOS

        pop     ds,es,dx,cx,bx,ax
        ret

LoadFail:
        mov     al,7
        call    ConOut
        call    ShowText
        jmp     DExit

; *****************************************************************************
;
;          Copy 8k from ROMBank + AX * 1024 into VRAM Workspace
;
; *****************************************************************************

CopyVROM:
        push    ds,es,si,di,cx,bx,ax

        mov     di,VRom                 ; es:di points to the VROM space
        mov     es,ds

        mov     bx,cs                   ; ROMBank segment in DS now.
        add     bx,(ROMBank/16)
        shl     ax,10 - 4               ; left 10 (k# -> addr) right 4 (to seg)
        add     bx,ax
        mov     ds,bx
        mov     si,0                    ; ds:si now points to the VROM to copy
        mov     cx,8192/2               ; no of words to copy (4k)
        rep     movsw                   ; and copy it

        pop     ax,bx,cx,di,si,es,ds
        ret

; *****************************************************************************
;
;          Copy 16k from ROMBank BX to CPU WorkSpace @ AX
;
; *****************************************************************************

CopyROM:
        push    ds,es,si,di,cx,bx,ax

        mov     di,ax                   ; es:di points to CPU target space
        mov     es,[ESSegment]

        shl     bx,(14-4)               ; convert ROM# to segment
        mov     ax,cs                   ; ROMBank segment in DS now.
        add     ax,(ROMBank/16)
        add     ax,bx
        mov     ds,ax
        mov     si,0                    ; ds:si now points to the VROM to copy
        mov     cx,16384/2              ; no of words to copy (16k)
        rep     movsw                   ; and copy it

        pop     ax,bx,cx,di,si,es,ds
        ret

; *****************************************************************************
;
;                       Clear NESRam -> NESHeader
;
; *****************************************************************************

ClearRAM:
        push    ax,bx
        mov     bx,NESRam
        xor     ax,ax
CLR1:   mov     [bx],ax
        inc     bx
        inc     bx
        cmp     bx,NESHeader
        jb      CLR1
        pop     bx,ax
        ret

; *****************************************************************************
; *****************************************************************************
;
;       Alternative INT9 Handler,allows us to check keyboard state.
;
; *****************************************************************************
; *****************************************************************************

KeyTable:db     128 dup 0               ; Key pressed table
OldInt9:dw      0,0                     ; old vector for int9

SetNewInt9:
        push    es,si,bx,ax
        cli
        mov     ax,3509h                ; get old INT 9
        int     21h
        mov     si,oldint9
        mov     [OldInt9],bx            ; save offset
        mov     [OldInt9+2],es          ; save segment
        mov     ax,2509h                ; set new INT 9
        mov     dx,NewInt9              ; ds:dx new interrupt vector
        int     21h
        sti
        pop     ax,bx,si,es
        ret

SetOldInt9:
        push    ds
        cli
        mov     ax,2509h                ; set new INT 9
        mov     dx,[OldInt9]            ; load offset
        mov     ds,[OldInt9+2]          ; load segment
        int     21h
        sti
        pop     ds
        ret


NewInt9:push    ax,bx
        in      al,60h                  ; get the scan code

        mov     bl,al                   ; bx = offset into the table
        and     bl,07Fh
        xor     bh,bh
        and     al,080h                 ; isolate the on off bit
        xor     al,080h                 ; bit 7 0 if key pressed,reverse it
        mov     cs:KeyTable[bx],al      ; write it into the table

        mov     al,20h                  ; reset something or other....
        out     20h,al
                                        ; (bit 7 set if pressed)
        pop     bx,ax
        sti
        iret

; *****************************************************************************
;
;                              Initialisation
;
; *****************************************************************************

Initialise:
        mov     bx,081h                 ; Make Command Line ASCIIZ String
INext:  mov     al,[bx]
        inc     bx
        cmp     al,32
        je      INext
        cmp     al,'-'                  ; is it -????
        je      InitCtrl
        dec     bx

        push    bx
Init1:  mov     al,[bx]
        cmp     al,32
        jbe     Init2
        inc     bx
        jmp     Init1

Init2:  mov     byte [bx],00
        pop     bx
        call    LoadROM

        mov     al,[NESHeader+6]        ; get the cartridge type
        shr     al,4
        and     al,15
        mov     [CartType],al
        cmp     al,1                    ; type 1 and 2 load last page into
        je      _ZeldaCart              ; $C000 and page 0 into $8000
        cmp     al,2
        je      _ZeldaCart

        cmp     byte [NESHeader+4],1    ; if 2 16k ROM banks
        jne     Is32kROM

        mov     ax,16                   ; copy VROM @ 16k
        call    CopyVROM                ; space

        mov     ax,0C000h               ; copy Bank 0 to C000h
        mov     bx,0
        call    CopyROM

        mov     ax,[ESSegment]
        jmp     LoadCont

Is32kROM:
        mov     ax,32                   ; copy VROM @ 32k
        call    CopyVROM
        mov     ax,08000h
        mov     bx,0
        call    CopyROM
        mov     ax,0C000h
        mov     bx,1
        call    CopyRom
        mov     ax,[ESSegment]

LoadCont:
        mov     es,ax                   ; set the register

        mov     al,[NESHeader+6]
        and     al,1
        xor     al,1
        xor     al,[MToggle]
        mov     [_HMirror],al
_Init1:
        ret


InitCtrl:                               ; -<command> <integer>
        mov     al,[bx]
        cmp     al,'a'                  ; convert to lower case
        jb      InNotLC
        cmp     al,'z'
        ja      InNotLC
        sub     al,32
InNotLC:xor     cx,cx                   ; strip out the number
        inc     bx
InInt:  mov     dl,[bx]
        inc     bx
        cmp     dl,'0'
        jb      InInt2
        cmp     dl,'9'
        ja      InInt2
        push    ax                      ; multiply cx by 10
        add     cx,cx
        mov     ax,cx
        shl     cx,2
        add     cx,ax
        pop     ax
        and     dx,15
        add     cx,dx
        jmp     InInt

_ZeldaCart:                             ; load a zelda type cart
        mov     ax,0C000h               ; load last ROM Page to $C000
        mov     bl,[NESHeader+4]        ; bx = last page #
        xor     bh,bh
        dec     bx                      ; pages numbered 0 .. n-1
        call    CopyRom
        mov     ax,08000h               ; load page 0 to $8000
        xor     bx,bx
        call    CopyRom
        mov     ax,[ESSegment]
        jmp     LoadCont


InInt2:                                 ; al = command cx = integer
        cmp     al,'H'
        je      ISetHVis
        cmp     al,'B'
        je      ISetHBlk
        cmp     al,'E'
        je      ISetEScrn
        cmp     al,'M'
        je      ISetSMask
        cmp     al,'S'
        je      ISetNoSplit
        cmp     al,'T'
        je      AltLineAddress
        cmp     al,'O'
        je      SLOverride
        cmp     al,'Q'
        je      SetMToggle
        jmp     INext

SetMToggle:
        mov     byte [MToggle],1
        jmp     INext

SLOverRide:
        mov     [OverRide],cl
        jmp     INext

ISetNoSplit:
        mov     word [NoSplit],-1
        jmp     INext
ISetHVis:
        mov     [CHorzScan],cx
        jmp     INext
ISetHBlk:
        mov     [CHorzBlank],cx
        jmp     INext
ISetEScrn:
        mov     [EndScreen],cx
        jmp     INext
ISetSMask:
        mov     [ScrDrawMask],cx
        jmp     INext


AltLineAddress:
        push    ax,bx,cx,dx,si
        mov     dx,256                  ; lines to generate
        mov     si,LineGenTable         ; pointer
        mov     [LineTable],si
Alt1:   cmp     cx,0                    ; while cx > 0
        jz      Alt2
        mov     word [si],0             ; generate 00 lines.
        add     si,2
        dec     cx
        jmp     Alt1
Alt2:   mov     bx,32
        mov     cx,200
Alt3:   mov     word [si],bx
        add     bx,LineSize
        add     si,2
        dec     cx
        jnz     Alt3
Alt4:   mov     word [si],0
        add     si,2
        cmp     si,LineGenTable+512
        jb      Alt4
        pop     si,dx,cx,bx,ax
        jmp     INext

; *****************************************************************************
;
;                               Read DOS Clock
;
; *****************************************************************************
Clock:  push    bx,cx,dx
        mov     ah,0                    ; look at the clock tick
        int     01Ah
        mov     ax,dx
        pop     dx,cx,bx
        ret

StackF0:
        push    ax,bx
        mov     bx,01F0h
S1:     mov     al,NESRam[bx]
        call    ConHex
        mov     al,32
        call    ConOut
        inc     bx
        cmp     bx,0200h
        jne     S1
        mov     al,13
        call    ConOut
        pop     bx,ax
        ret

; *****************************************************************************
;
;                           New VGA Banner
;
; *****************************************************************************

WrBanner:
        push    ax,bx,si,di,es
        mov     es,0A000h
        mov     bx,LineSize-1
        mov     di,LineSize*ScrHeight-1
        mov     al,14
W1:     mov     es:[bx],al
        mov     es:[di],al
        dec     di
        dec     bx
        jns     W1
        mov     bx,(ScrHeight-1)*LineSize
W2:     mov     es:[bx],al
        mov     es:LineSize-1[bx],al
        sub     bx,LineSize
        jnz     W2
        mov     bx,(ScrHeight/2 - 46)*LineSize+(LineSize/2)-84
        mov     di,bx
        mov     si,Banner
WrBanner1:
        mov     al,[si]
        call    VGAChar
        inc     si
        mov     al,[si]
        cmp     al,-2
        jne     WrBanner1

WrWait1:test    byte [KeyTable+039h],128
        jz      WrWait1
WrWait2:test    byte [KeyTable+039h],128
        jnz     WrWait2
        xor     bx,bx
        mov     ax,bx
WrClr:  mov     es:[bx],ax
        dec     bx
        dec     bx
        jnz     WrClr
        pop     es,di,si,bx,ax
        ret

Banner: db      '         nesa v0.17 (beta version)',-1,-1
        db      'nintendo entertainment system in assembler',-1,-1
        db      '     written by paul robson [c] 1996',-1,-1
        db      '     dedicated to jane and elizabeth',-1,-1,
        db      ' thanks to everyone who has contributed',-1,-1
        db      '        especially marat fayzullin ',-1,-1,-1
        db      '         press [space] to start',-2

VGAChar:test    al,128
        jnz     VGANL
        push    ax,cx,dx,si,di
        push    bx
        mov     si,0
VGAFind:mov     ah,ChTable[si]
        cmp     al,ah
        je      VGAFound
        inc     si
        cmp     ah,0
        jnz     VGAFind
        jmp     VCNext

VGAFound:
        add     si,si
        mov     cl,5
        mov     ax,ChData[si]
VGADraw:test    ax,04000h
        jz      VS1
        mov     byte es:[bx],11
VS1:    test    ax,02000h
        jz      VS2
        mov     byte es:1[bx],11
VS2:    test    ax,01000h
        jz      VS3
        mov     byte es:2[bx],11
VS3:    shl     ax,3
        add     bx,LineSize
        dec     cl
        jnz     VGADraw

VCNext: pop     bx
        add     bx,4

VCExit: pop     di,si,dx,cx,ax
        ret

VGANL:  add     di,LineSize*6
        mov     bx,di
        ret

