;       Flat Real/Real Big mode (v1.2)

code    segment public

        assume  cs:code

        public  FLAT_install, FLAT_destall

FLAT_install    proc                    ; Installs FLAT_tsr
        
        pushf                           ; Check for NT-flat (only on 386+)
        pushf
        pop     ax
        xor     ah,40h
        push    ax
        popf
        pushf
        pop     bx
        popf
        cmp     ah,bh
        jne     no_NT
.386p                                   ; SMSW is a priviledged instruction(?)
        smsw    ax                      ; Check for real mode
.386
        test    al,1
        jnz     short V86
        pushf                           ; Save flags & DS register
        push    ds
        cli
        xor     eax,eax                 ; Get IRQ5 vector & Set FLAT_tsr
        mov     ds,ax
        mov     ebx,ds:[34h]
        mov     cs:old_IRQ5,ebx
        mov     word ptr ds:[34h],offset FLAT_tsr
        mov     ax,cs
        mov     ds:[36h],ax
        shl     eax,4                   ; Build Global Descriptor Table
        add     dword ptr cs:GDT[2],eax
        pop     ds                      ; Restore DS register & flags
        popf
        ret

no_NT:  mov     ah,09h                  ; Write message
        mov     dx,offset no_NT_msg
        push    cs
        pop     ds
        int     21h
        mov     ax,4C01h                ; Terminate with error code 1
        int     21h

no_NT_msg       db      'This program requires at least an i386sx!',10,'$'

V86:    mov     ah,09h                  ; Write message
        mov     dx,offset V86_msg
        push    cs
        pop     ds
        int     21h
        mov     ax,4C02h                ; Terminate with error code 2
        int     21h

V86_msg         db      'Cannot run in a virtual environment!',10,'$'

FLAT_install    endp

FLAT_destall    proc                    ; Destalls FLAT_tsr

        push    ds                      ; Save DS register
        xor     ax,ax                   ; Restore old IRQ5 vector
        mov     ds,ax
        mov     eax,cs:old_IRQ5
        mov     ds:[34h],eax
        pop     ds                      ; Restore DS register
        ret

FLAT_destall    endp

        align   8
GDT             dw      15              ; Limit (16 bytes)
                dw      offset GDT,0    ; Offset within current segment...
                dw      ?               ; Unused

FLAT_desc       dw      0ffffh          ; Limit (bit 0..15)
                db      ?,?,?           ; Base (bit 0..23)
                db      92h             ; Access rights
                db      0cfh            ; Page granularity + Limit(16..19)
                db      ?               ; Base (bit 24..31)

FLAT_sel        equ     FLAT_desc - GDT


old_IRQ5        dd      0
last_Exc_13     dd      0
IRQ5_flag       db      0

FLAT_tsr proc

        test    cs:IRQ5_flag,1          ; Exception within IRQ5 handler?
        jnz     short Exc_13
        push    ax                      ; Ask PIC if IRQ5 is 'IN-SERVICE'
        mov     al,0Bh
        out     20h,al
        jmp     $+2
        in      al,20h
        test    al,20h
        pop     ax
        jz      short Exc_13

IRQ5:   mov     cs:IRQ5_flag,1          ; Call old IRQ5 handler
        pushf
        call    dword ptr cs:old_IRQ5
        mov     cs:IRQ5_flag,0
        iret

Exc_13: push    eax                     ; Save accumulator

        mov     eax,ss:esp[4]           ; Get address of SOE
        cmp     eax,cs:last_Exc_13      ; Same as last time?
        je      short SOE
        mov     cs:last_Exc_13,eax
.386p
        lgdt    qword ptr cs:GDT        ; Load GDT Register
        
        push    gs fs es ds bx          ; Save registers

        mov     eax,CR0
        or      al,1                    ; Enter Protected mode
        mov     CR0,eax

        jmp     $+2                     ; Flush instruction decode queue
        
        mov     bx,FLAT_sel             ; Load 4Gb limits
        mov     ds,bx
        mov     es,bx
        mov     fs,bx
        mov     gs,bx

        and     al,not 1                ; Back to Real mode
        mov     CR0,eax
.386        
        pop     bx ds es fs gs          ; Restore registers
        pop     eax                     ; Restore accumulator
        iret                            ; Done

SOE:    call    FLAT_destall            ; Remove FLAT_tsr
        mov     ah,0fh                  ; Clear screen
        int     10h
        mov     ah,00h
        int     10h
        mov     ah,09h                  ; Write message
        mov     dx,offset SOE_msg
        push    cs
        pop     ds
        int     21h
        mov     ax,4C0Dh                ; Terminate with error code 13
        int     21h

SOE_msg         db      'Segment Overrun Exception!',10,'$'

FLAT_tsr endp

code    ends
        end
