; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > $.Source.VduDriver
;
; ARTHUR OPERATING SYSTEM - Vdu Drivers
; =======================
;
; Vdu driver code - Vdu queue, mode, default windows etc.
;
; Author R C Manby
; Date   5.9.86
;
        GBLL NewStyleEcfs
NewStyleEcfs   SETL  {TRUE}

        GBLL    DoVdu23_0_12
DoVdu23_0_12   SETL  {FALSE}

        GBLL    BleedinDaveBell
BleedinDaveBell SETL {TRUE}

        GBLL    LCDPowerCtrl
LCDPowerCtrl    SETL {TRUE} :LAND: :LNOT: STB

        GET s.vdu.VduDecl
        GET s.vdu.VduGrafDec

        MACRO
        HostVdu
        Push    "R0,R14"
        LDR     R0, [R0, -R0]
        TEQ     R0, #0
        ADREQ   R0, %FT01
        SWIEQ   OS_CLI
        Pull    "R0,R14"
        B       %FT02
01
        =       "HOSTVDU", 0
        ALIGN
02
        MEND

        MACRO
        Print   $string
        Push    "R0,R14"
        LDR     R0, [R0, -R0]
        TEQ     R0, #0
        BNE     %FT01
        SWI     OS_WriteS
        =       "$string", 0
        ALIGN
        SWI     OS_ReadC
        Pull    "R0,R14",CS
        SWICS   OS_BreakPt
        SWI     OS_NewLine
01
        Pull    "R0,R14"
        MEND

        MACRO
        RMVT    $var, $BW
        [ "$BW"="B"
        =       (wk$var-wkstart)*2
        |
        [ "$BW"="W"
        =       (wk$var-wkstart)*2 +1
        |
        .... Invalid option on RMVT ....
        ]
        ]
        MEND

        MACRO
        RVVT    $var
        ASSERT  ($var >= 0) :LAND: ($var < &400) :LAND: (($var :AND: 3)=0)
        =       $var :SHR: 2
        MEND

        MACRO
        IssueService
        BL      Issue_Service
        MEND

        MACRO
        MALIGN  $length, $phase
        LCLA    temp
        LCLS    string
temp    SETA    .-ArthurVduDriver
        [ "$phase"=""
string  SETS    "ALIGN $length"
        |
string  SETS    "ALIGN $length, $phase"
        ]
        $string
temp    SETA    .-ArthurVduDriver-temp
        [ temp=0
        !       0, string :CC: " fitted exactly"
        |
        !       0, string :CC: " wasted " :CC: :STR: temp
        ]
        MEND


; Macro to load up video bandwidth and video memory size

        MACRO
        GetBandwidthAndSize  $bw, $size
  [ HAL
        MOV     $size, #0
        MOV     $bw, #100*1024*1024
        LDR     $size, [$size, #VideoSize]
        !       0, "Sort out GetBandwidthAndSize"
  |
        MOV     $size, #0
        LDR     $bw, [$size, #VideoBandwidth]   ; load bandwidth
        LDR     $size, [$size, #VideoSize]      ; and total amount of video RAM
  ]
        MEND

; *****************************************************************************

; Vdu status bits

Vdu2ModeBitPosn *       0
Vdu2Mode        *       1 :SHL: Vdu2ModeBitPosn
Windowing       *       1 :SHL: 3
Shadowing       *       1 :SHL: 4

; *****************************************************************************

ArthurVduDriver

; *****************************************************************************
;
; VduInit - Once only initialisation of Vdu drivers eg after Break
; =======
;
VduInit ROUT
        Push    R14
        MOV     R0, #0
        STRB    R0, [R0, #OsbyteVars + :INDEX: VDUqueueItems] ;purge queue
        STRB    R0, [WsPtr, #ScreenBlankFlag]   ; not blanked
        STR     R0, [WsPtr, #CursorCounter]
        STR     R0, [WsPtr, #CursorDesiredState]
        STR     R0, [WsPtr, #VduStatus]
        STRB    R0, [WsPtr, #PointerShapeNumber]  ; make sure pointer off
        STR     R0, [WsPtr, #PointerShapeLA]      ; no shape passed to HAL yet
        STR     R0, [WsPtr, #CursorStack]         ; 0 bits => on
        STR     R0, [WsPtr, #VduSaveAreaPtr]      ; indicate no save area yet
        STR     R0, [WsPtr, #ClipBoxEnable]       ; no clip box calculating
        STRB    R0, [WsPtr, #ExternalFramestore]

        Push    "r4, r9, r12"
        mjsAddressHAL
        MOV     r4, r12                       ; temp WsPtr
        mjsCallHAL HAL_Video_Features
        STR     r0, [r4, #HALVideoFeatures]
 [ :LNOT:UseGraphicsV
        mjsCallHAL HAL_Video_PixelFormats
        STR     r0, [r4, #HWPixelFormats]
        mjsCallHAL HAL_Video_BufferAlignment
        STR     r0, [r4, #HWBufferAlign]
 ]
        Pull    "r4, r9, r12"

        ;;; sort this out!
        ! 0, "mjsHAL not doing anything useful with HAL_Video_BufferAlignment"
        ! 0, "mjsHAL not dealing with lack of h/w pointer"

        LDR     R0, =RangeC+SpriteReason_SwitchOutputToSprite
        STR     R0, [WsPtr, #SpriteMaskSelect]

        MOV     R0, #InitialCursorFlags         ; TMD 25/9/86
        STR     R0, [WsPtr, #CursorFlags]

        ADRL    R0, NUL                 ; point to MOV PC,R14
        STR     R0, [WsPtr, #WrchNbit]  ; just in case ...

        ADRL    R0, ExportedHLine
        STR     R0, [WsPtr, #HLineAddr]
        ADD     R0, WsPtr, #FgEcfOraEor
        STR     R0, [WsPtr, #GcolOraEorAddr]

        MOV     R0, #maxmode
        STR     R0, [WsPtr, #MaxMode]   ; constant now


        LDROSB  R0, LastBREAK           ; is it a hard reset ?
        TEQ     R0, #0
        BEQ     %FT10                   ; [no, don't reset font]


; allocate buffer for Tim's whizzy text expansion.  This now lives
; in the system heap, unless the claim request fails.  If the request
; fails then the pointer points at the original buffer in the Vdu
; driver workspace.

; This allows the user to access depths upto 16 bit per pixel before
; strings things things become afoot at the Circle K.

        LDR     R3, =TextExpandArea_Size
        BL      ClaimSysHeapNode        ; allocate buffer for heap data
        ADDVS   R2, WsPtr, #TextExpand
        STR     R2, [WsPtr, #TextExpandArea]

        LDR     R3, =ModeSelector_MaxSize+4     ; get block for two overlapping copies
        BL      ClaimSysHeapNode                ; of mode selector, 1 word apart
        STR     R2, [WsPtr, #KernelModeSelector]


; now reset soft font from hard font

        MOV     R1, #1                  ; start at character 1*32
        STRB    R1, [WsPtr, #ScreenMemoryClaimed]
                                        ; can't claim memory till a mode change
        MOV     R2, #7                  ; copy 7 pages
        BL      DoResetFont

  [ :LNOT: HAL
    ;
    ; mjsHAL - temporary workspace while semi HALised code still in kernel
    ;
        MOV     r3, #mjs_thalwk_size
        BL      ClaimSysHeapNode          ; this had better succeed!
        LDR     r4, =mjs_tempHALworkspace
        STR     r2, [r4, #0]
        BL      mjs_tempHALworkspace_init
    ;
  ]

;initialise the 6 pointer shape pointers and blocks
;(shape buffers are 6 * &100 starting at CursorData)
;
        ADD     r0, WsPtr, #PointerShapes
        LDR     r2, =CursorData
        MOV     r3, #6
        ADD     r4, WsPtr, #PointerShapeBlocks
02
        STR     r4, [r0]                 ; attach pointer to block
        STR     r2, [r4, #PointerBuffLA]
        Push    "r0-r2"
        MOV     r1, #0
        STRB    r1, [r4, #PointerWidth]  ; zero width
        STRB    r1, [r4, #PointerHeight] ; zero height (no shape)
        SUB     sp, sp, #3*4             ; room for one entry of OS_Memory page block
        MOV     r1, sp
        STR     r2, [r1, #4]             ; provide logical address
        MOV     r2, #1
        MOV     r0, #&2200               ; convert logical to physical address
        SWI     XOS_Memory
        LDR     r2, [r1, #8]             ; read physical address
        STR     r2, [r4, #PointerBuffPA]
        ADD     sp, sp, #3*4
        Pull    "r0-r2"
        ADD     r4, r4, #PointerBlkSize
        ADD     r2, r2, #&100
        ADD     r0, r0, #4
        SUBS    r3, r3, #1
        BNE     %BT02

; palette space (256 normal + 1 border + 3 pointer = 260), and allowing for Gamma Correction
;   this space is: blank palette, 260 words
;                  logical and physical copies of both flash states, 260*4 words
;                  3 lookup tables for r,g,b mapping, 3*256 bytes
;
        LDR     r3, =Pal_Blocksize
        BL      ClaimSysHeapNode           ; this had better succeed!

        ASSERT  :INDEX:Pal_Blank = 0
        STR     r2, [WsPtr, #BlankPalAddr]
        ADD     r3, r2, #Pal_LogFirst
        STR     r3, [WsPtr, #FirPalAddr]
        ADD     r3, r2, #Pal_LogSecond
        STR     r3, [WsPtr, #SecPalAddr]

; initialise blank palette to all solid black

        MOV     r3, #0
        MOV     r4, #PalEntries
04      STR     r3, [r2], #4
        SUBS    r4, r4, #1
        BNE     %BT04

        ADD     r2, r2, #Pal_RTable-(Pal_Blank+4*PalEntries) ; r2 -> rgb tables

; initialise red, green and blue transfer function tables to 1-1 mapping

        MOV     r0, #0
05
        STRB    r0, [r2, #Pal_STable-Pal_RTable] ; store in supremacy table
        STRB    r0, [r2, #Pal_BTable-Pal_RTable] ; store in blue table
        STRB    r0, [r2, #Pal_GTable-Pal_RTable] ; store in green table
        STRB    r0, [r2], #1                     ; store in red table, and advance
        ADD     r0, r0, #1
        CMP     r0, #256
        BCC     %BT05
10
        BL      SpriteInit

        LDR     R14, [WsPtr, #ScreenEndAddr]
        LDR     R0, [WsPtr, #TotalScreenSize]
        RSB     R0, R0, R14
        STR     R0, [WsPtr, #DisplayStart]
        BL	SetDisplayScreenStart
        STR     R0, [WsPtr, #ScreenStart]
        STR     R0, [WsPtr, #CursorAddr]
        STR     R0, [WsPtr, #InputCursorAddr]

        Pull    PC

        LTORG

; *****************************************************************************
;
;       InitialiseMode - Select mode number given by ModeNo variable
;
;       Called by MOS once before initialisation, and then afterwards
;       before printing "RISC OS ..."
;
; in:   -
; out:  All registers may be corrupted (except R13_svc !)
;

InitialiseMode Entry
 [ SoftResets
        MOV     r0, #&FD                ; read last reset type
        MOV     r1, #0
        MOV     r2, #&FF
        SWI     XOS_Byte
        CMP     r1, #SoftReset
        LDREQ   r0, =VduDriverWorkSpace+ModeNo
        LDREQ   r0, [r0]                ; use previous mode if a soft reset
        MOVNE   r0, #1                  ; otherwise read configured mode
        SWINE   XOS_ReadSysInfo
 |
        MOV     r0, #1                  ; no need to check for soft reset,
        SWI     XOS_ReadSysInfo         ; always use configured value
 ]
        MOV     r1, r0
        MOV     r0, #ScreenModeReason_SelectMode
        SWI     XOS_ScreenMode
        EXIT    VC

        MOV     r0, #114                ; failed, so get rid of any shadow
        MOV     r1, #1
        SWI     XOS_Byte
        SWI     XOS_WriteI+22
        SWIVC   XOS_WriteI+0            ; and if we can't get mode 0, we're really fooked!!!
        EXIT

;
;------------------------------------------------------------------------------
;
; Vdu -  Main VDU driver entry point
; ===    Queue up bytes and dispatch via JVec
;
; in:   R0 = character to be printed
;
; out:  C=1 <=> send character to printer if enabled
;

Vdu     ROUT
        MOV     R11, #0                 ; NB used later in Vdu07 as well
        LDRB    R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]
        MOVS    R9, R10, LSL #24        ; move up to top byte (for sign extend)
        BEQ     Vdu07                   ; not queueing, then start Vdu sequence

; *****Comment made by DJS: Changing this to fall through if not queueing
; and branch if queueing might be a good idea - it would speed up all
; printable characters and simple cursor movements.

        LDR     R8, [WsPtr, #QOffset]
        STRB    R0, [R8, R9, ASR #24]   ; add byte to queue

        ADD     R10, R10, #1            ; move on pointer
        STRB    R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]

        CMP     R10, #&100              ; finished sequence ?
        MOVCC   PC, R14                 ; no, then return (no printing)

        Push    "R0, R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
05
 [ No26bitCode
        ADR     R14, VduPrintExit
 |
        MOV     R14, PC
        ADD     R14, R14, #(VduPrintExit-%BT05-8) ; push address of SEC exit
                                                  ; (with flags)
 ]
        Push    R14
        BL      Vdu05
        BL      PostWrchCursor
        CLC                             ; also clears V
        Pull    "R14,PC"

VduPrintExit                             ; exit used if print required
        BL      PostWrchCursor
        SEC                             ; also clears V
        Pull    PC

; *****************************************************************************

Vdu05   ROUT
        TST     R6, #VduDisabled        ; VDU enabled ?
        LDREQ   PC, [WsPtr, #JVec]      ; yes, then do it (no printing)

; Queue sequence has just completed, but VDU is disabled
; Only interesting case is VDU 1 (SOH), which must still print
; the character if the printer is enabled

        LDR     R10, [WsPtr, #JVec]
        ADR     R11, SOH
        TEQ     R10, R11                ; JVec -> SOH ?
        MOVNE   PC, R14                 ; no, then return (no printing)
SOH
        LDR     R1, [WsPtr, #VduStatus]  ; in VDU 2 mode ?
        TST     R1, #Vdu2Mode
        MOVEQ   PC, R14                 ; no, then return

        Push    R14
        BL      MOSDoPrint
        Pull    PC, VC                  ; good exit, so return

        Pull    R14                     ; bad exit, return with error
        B       VduBadExit

; *****************************************************************************

Vdu07   ROUT
        AND     R0, R0, #&FF            ; Only look at bottom byte!

        CMP     R0,#127                 ;If DELETE, change R0 to 32 and
        MOVEQ   R0,#32                  ;  drop through to control char code
        CMPNE   R0,#31                  ;Otherwise, branch to printable char
        BHI     Vdu20                   ;  code if not a control char

; *****Further desirable change: Make printable characters fall through,
; control characters take the branch - speeding up printable characters is
; important!

        ADR     R10, VduJTb             ; Address of beginning of jump table
        LDR     R9, [R10, R0, LSL #2]   ; Get routine address from VduJTB
        STR     R9, [WsPtr, #JVec]      ; save address

        ADD     R10, R10, #(VduQTb-VduJTb)

        LDRB    R9, [R10, R0]           ; Pick up QQ + length of queue
        RSBS    R10, R9, #QQ            ; -(length of queue) & test if none

        STRNEB  R10, [R11, #OsbyteVars + :INDEX: VDUqueueItems]
                                        ; yes, then set up Osbyte variable
        ADDNE   R9, R9, WsPtr
        STRNE   R9, [WsPtr, #QOffset]   ; and QOffset
        MOVNE   PC, R14

        Push    "R0,R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
10
        MOV     R14, PC
        ADD     R14, R14, #(VduPrintExit-%BT10-8) ; push address of SEC exit
                                                  ; (with flags)
        Push    R14
        BL      Vdu10
        BL      PostWrchCursor
        CLC                             ; also clears V
        Pull    "R14,PC"

;
; This is the only byte of a single byte Vdu sequence
;
;       R6 = CursorFlags
;       R11 = 0 (used to index VDUqueueItems)
;

Vdu10   ROUT
        TST     R6, #CursorsSplit       ; are we cursor editing ?
        BNE     Vdu15
Vdu10Continue

; TMD 31/7/87; bug fixed here - chars 8 to 13 should still go to printer if
; enabled, even if VDU is disabled

        CMP     R0, #8                  ; is char in range 8-13 ?
        RSBCSS  R1, R0, #13             ; if so then we want to print it
        LDRCS   R1, [WsPtr, #VduStatus]
        MOVCSS  R1, R1, LSR #(Vdu2ModeBitPosn +1) ; providing printer enabled
        Pull    R14, CS                 ; so pull old R14

        TST     R6, #VduDisabled        ; are we disabled
        LDREQ   PC, [WsPtr, #JVec]      ; enabled, so go get em floyd !

        TEQ     R0, #6                  ; disabled, so is it ACK (enable VDU)
        BICEQ   R6, R6, #VduDisabled
        STREQ   R6, [WsPtr, #CursorFlags]

NUL ;Does nothing
ESC ;Does nothing
        MOV     PC, R14                 ; return anyway

; *****************************************************************************

Vdu15
        TEQ     R0, #13                 ; and is it a carriage return ?
        BNE     Vdu10Continue

        Push    "R0, R14"
        BIC     R6, R6, #CursorsSplit   ; then stop cursor editing
        STR     R6, [WsPtr, #CursorFlags]
        MOV     R1, #1                  ; restore old Reg10Copy
        BL      CursorOnOff
        Pull    "R0, R14"
        B       Vdu10Continue

; *****************************************************************************

Vdu20   ROUT
        Push    "R0,R14"
        BL      PreWrchCursor           ; exits with R6 = CursorFlags
        Pull    "R0"
05
        MOV     R14, PC
        ADD     R14, R14, #(VduPrintExit-%BT05-8) ; push address of SEC exit
                                                  ; (with flags)
        Push    R14
        BL      TimWrch
        BL      PostWrchCursor
        CLC                             ; also clears V
        Pull    "R14,PC"

; *****************************************************************************

VduJTb                  ; Table of addresses
 & NUL                          ; Does nothing
 & SOH                          ; Next char to printer only
 & STX                          ; Enable printer
 & ETX                          ; Disable printer
 & EOT                          ; Write text at text cursor
 & ENQ                          ; Write text at graphics cursor
 & NUL                          ; Enable VDU drivers
 & BEL                          ; Beep
 & BS                           ; Cursor left
 & HT                           ; Cursor right
 & VduLF                        ; Cursor down
 & VT                           ; Cursor up
 & FF                           ; Clear text area (CLS)
 & VduCR                        ; Carriage return
 & SO                           ; Page mode on
 & SI                           ; Page mode off
 & DLE                          ; Clear graphics area (CLG)
 & DC1                          ; Define text colour (COLOUR)
 & DC2                          ; Define graphics colour and action (GCOL)
 & DC3                          ; Define logical colour
 & VDU20                        ; Restore default logical colours
 & NAK                          ; Disable VDU drivers
 & SYN                          ; Select screen mode (MODE)
 & ETB                          ; Reprogram display character (VDU23,...)
 & CAN                          ; Define graphics window
 & EM                           ; (PLOT k,x,x,y,y)
 & DefaultWindows               ; Restore default windows
 & ESC                          ; Does nothing
 & FS                           ; Define text window
 & GS                           ; Define graphics origin
 & RS                           ; Home cursor to "top left"
 & US                           ; Move text cursor (TAB x,y)
 & Delete                       ; Delete character (127)

        ASSERT  QQ+9 < 256

VduQTb          ; QQ + length of queue for each of the above
        DCB     QQ+0, QQ+1, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0
        DCB     QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0, QQ+0
        DCB     QQ+0, QQ+1, QQ+2, QQ+5, QQ+0, QQ+0, QQ+1, QQ+9
        DCB     QQ+8, QQ+5, QQ+0, QQ+0, QQ+4, QQ+4, QQ+0, QQ+2
        DCB     QQ+0 ; (delete)
        ALIGN

; *****************************************************************************

WrchNbitTab
        &       Wrch1bit-WrchNbitTab
        &       Wrch2bit-WrchNbitTab
        &       Wrch4bit-WrchNbitTab
        &       Wrch8bit-WrchNbitTab
        &       Wrch16bit-WrchNbitTab
        &       Wrch32bit-WrchNbitTab
WrchNbitDoubleTab
        &       Wrch1bitDouble-WrchNbitDoubleTab

CursorNbitTab
        &       Cursor1bit-CursorNbitTab
        &       Cursor2bit-CursorNbitTab
        &       Cursor4bit-CursorNbitTab
        &       Cursor8bit-CursorNbitTab
        &       Cursor16bit-CursorNbitTab
        &       Cursor32bit-CursorNbitTab

; table of susbstitute mode numbers to cater for hardware that might
; not support all of 1,2,4,8 bpp (bits per pixel) modes
;
; indexed by mode number (0..49), pairs of byte values:
;   bpp    = bits per pixel of this mode number
;   promo  = promoted mode number (0..49), or &FF if none
;
; promoted number is:
;  1) same resolution at next higher bpp (up to 8), if available, or
;  2) similar resolution at 8 bpp (8 bpp should be available on most h/w)
;
ModePromoTable
;
;          bpp promo       mode no.
;
      DCB    1,    8     ;  0
      DCB    2,    9     ;  1
      DCB    4,   10     ;  2
      DCB    1,   15     ;  3
      DCB    1,    1     ;  4
      DCB    2,    2     ;  5
      DCB    1,   13     ;  6
    [ TTX256
      DCB    8,  &FF     ;  7
    |
      DCB    4,   13     ;  7
    ]
      DCB    2,   12     ;  8
      DCB    4,   13     ;  9
      DCB    8,  &FF     ; 10
      DCB    2,   14     ; 11
      DCB    4,   15     ; 12
      DCB    8,  &FF     ; 13
      DCB    4,   15     ; 14
      DCB    8,  &FF     ; 15
      DCB    4,   24     ; 16
      DCB    4,   24     ; 17
      DCB    1,   19     ; 18
      DCB    2,   20     ; 19
      DCB    4,   21     ; 20
      DCB    8,  &FF     ; 21
      DCB    4,   36     ; 22
      DCB    1,   28     ; 23
      DCB    8,  &FF     ; 24
      DCB    1,   26     ; 25
      DCB    2,   27     ; 26
      DCB    4,   28     ; 27
      DCB    8,  &FF     ; 28
      DCB    1,   30     ; 29
      DCB    2,   31     ; 30
      DCB    4,   32     ; 31
      DCB    8,  &FF     ; 32
      DCB    1,   34     ; 33
      DCB    2,   35     ; 34
      DCB    4,   36     ; 35
      DCB    8,  &FF     ; 36
      DCB    1,   38     ; 37
      DCB    2,   39     ; 38
      DCB    4,   40     ; 39
      DCB    8,  &FF     ; 40
      DCB    1,   42     ; 41
      DCB    2,   43     ; 42
      DCB    4,   28     ; 43
      DCB    1,   45     ; 44
      DCB    2,   46     ; 45
      DCB    4,   15     ; 46
      DCB    8,  &FF     ; 47
      DCB    4,   49     ; 48
      DCB    8,  &FF     ; 49
      DCB    1,   51     ; 50
      DCB    2,   52     ; 51
      DCB    4,   53     ; 52
      DCB    8,  &FF     ; 53

      ASSERT (.-ModePromoTable)=(NumModes*2)
;
      ALIGN


; *****************************************************************************
;
;       SYN - Perform MODE change
;
;       External routine
;
; in:   Vdu queue contains mode number
;

SYN     ROUT                            ; Select screen mode (MODE)
        Push    lr
        LDRB    R2, [WsPtr, #QQ]        ; Read mode number from queue
        BL      ModeChangeSub
        Pull    lr
        MOVVC   pc, lr                  ; if no error, exit to Vdu
                                        ; (which calls PostWrchCursor)
; else drop thru into ...

VduBadExit                              ; jumped to if an error in VDU code
        Push    R0                      ; save error pointer
        BL      PostWrchCursor
        SETV
        Pull    "R0, R14,PC"

ModeChangeSub ROUT
        Push    lr

        ;If its a common mode number (0..49) consider a possible mode number
        ;substitution, if hardware does not support given bits per pixel.
        ;We are vaguely assuming h/w supports at least 8 bpp, otherwise we may
        ;not be able to find a usable mode number, and later code may not handle
        ;that well. This is probably ok, 8 bpp is almost universal.
        ;
        CMP     r2, #256
        BHS     mchsub_3
        AND     r1, r2, #&7F
        CMP     r1, #NumModes                ; mode number
        BHS     mchsub_3
        Push    "r3, r4"
 [ UseGraphicsV
        Push    "r0-r2"
        MOV     r4, #GraphicsV_DisplayFeatures
        BL      CallGraphicsV
        TEQ     r4, #0
        MOVEQ   r4, r1
        MOVNE   r4, #2_111111
        Pull    "r0-r2"
 |
        LDR     r4, [WsPtr, #HWPixelFormats] ; bits 0 to 3 set for 1,2,4,8 bpp supported
 ]
        ADR     lr, ModePromoTable           ; table of mode promotions
mchsub_1
        MOV     r1, r1, LSL #1
        LDRB    r3, [lr, r1]                 ; bpp for this mode number (1,2,4,8)
        TST     r3, r4                       ; supported in h/w?
        ANDNE   r2, r2, #&80                 ; if yes, take mode number that passed
        ORRNE   r2, r2, r1, LSR #1
        BNE     mchsub_2
        ADD     r1, r1, #1                   ; else look for promotion
        LDRB    r1, [lr, r1]                 ; new mode number
        CMP     r1, #&FF                     ; &FF if none
        BNE     mchsub_1
        ;alright, dont panic, just try to get a VGA-like mode of any bpp, if not tried already
        CMP     r1, #28                      ; VGA 8 bpp
        MOVNE   r1, #25                      ; VGA 1 bpp
        BNE     mchsub_1
mchsub_2
        Pull    "r3, r4"
;
mchsub_3
        MOV     R1, #Service_PreModeChange
        IssueService
        TEQ     R1, #0                  ; was service claimed ?
        BNE     %FT03                   ; no, so continue

        CMP     R0, #0                  ; service claimed; generate error ?
        Pull    PC, EQ                  ; no, just exit (V=0 from CMP)
        B       %FT07                   ; yes, then generate error
03
        MOV     R0, R2                  ; put mode (possibly changed) in R0
        MOV     R2, R0, LSR #7          ; R2 = 0/1 if bit 7 of mode clear/set
        CMP     r2, #2                  ; if mode number >= 256 then mode selector
        MOVCS   r2, #0                  ; so no shadow
        LDROSB  R1, Shadow
        TEQ     R1, #0                  ; if shadow 0 then force shadow mode
        MOVEQ   R2, #1

        BL      FindOKMode              ; out: R1 = mode we are going to use
        BVS     %FT07

        TEQ     R0, R1                  ; if substitute mode
        MOVNE   R2, #0                  ; then don't use shadow

        CMP     r1, #&100
        BICCC   r10, r1, #&80
        MOVCS   r10, r1
        MOV     R11, R10
        BL      PushModeInfo
        BVS     %FT07                   ; [probably duff mode selector]

        LDR     R11, [R13, #wkScreenSize] ; get screen size for this mode
        LDR     R9, [WsPtr, #TotalScreenSize] ; maximum allowed amount

        Push    R1                      ; save proper mode
        RSBS    R1, R9, R11, LSL R2     ; extra amount we need
        BLS     %FT08                   ; enough memory, so skip

 [ UseGraphicsV
        LDRB    R0, [WsPtr, #ExternalFramestore]
        TEQ     R0, #0
        BNE     %FT06                   ; can't grow an external framestore
 ]

; try to extend the amount of screen memory

        ! 0, "Need to fix ModeChangeSub to not leave CursorAddr, etc. pointing to unmapped pages during DA resize. Causes bad stuff should an abort occur/screen output be attempted!" ; Note that even enlarging the DA can leave the pointers in a bad state, due to the way the screen DA handler shuffles down/unmaps the lower mapping of VRAM before the higher copy is enlarged

        MOV     R0, #2                  ; expand screen memory
        SWI     XOS_ChangeDynamicArea
        BVC     %FT08
06
        ADD     R13, R13, #PushedInfoSize + 1*4 ; junk stacked info + mode no.
        ADR     R0, ErrorBlock_BadMODE
      [ International
        BL      TranslateError
      ]
07
        SETV                            ; indicate error
        Pull    PC

 [ STB
TV_Mode_string
         =       "TV_Mode", 0
        ALIGN
 ]

; valid mode and enough memory

08
        Pull    R0                      ; restore mode we are using
        CMP     r0, #&100               ; if not mode selector
        BICCC   r0, r0, #&80            ; then knock off shadow bit
        BCC     %FT12

; it's a mode selector, so copy it to our static mode selector block

        LDR     r1, [WsPtr, #KernelModeSelector] ; point at block

        SUBS    r3, r0, r1              ; if r0 -> 1st mode block position
        TEQNE   r3, #4                  ; or r0 -> 2nd mode block position
        MOVEQ   r1, r0                  ; then use it in place
        BEQ     %FT09

        LDR     r3, [WsPtr, #DisplayModeNo] ; else check if current mode is a mode selector
        SUB     r3, r3, r1              ; r3 = offset from start of block
        CMP     r3, #8                  ; if 0 or 4
        EORCC   r3, r3, #4              ; then make 4 or 0 (ie toggle between them)
        ADDCC   r1, r1, r3              ; and add on base

        ASSERT  (ModeSelector_ModeVars+4) :AND: 7 = 0
09
        MOV     r3, #0
10
        LDR     r6, [r0, r3]            ; copy 1st word - after fixed bit this will be previous var value
        STR     r6, [r1, r3]
        ADD     r3, r3, #4
        LDR     r6, [r0, r3]            ; copy 2nd word - after fixed bit this will be next var index
        STR     r6, [r1, r3]
        ADD     r3, r3, #4
        CMP     r3, #ModeSelector_ModeVars + 4  ; only exit if we've done the essential bit
        CMPCS   r6, #-1                 ; AND we've had a -1 as the var index (NOT as the value)
        CMPCC   r3, #ModeSelector_MaxSize ; OR we've gone off the end of our block
        BCC     %BT10                   ; [we haven't, so loop]

        CMP     r3, #ModeSelector_MaxSize       ; if we did go off the end
        MOVCS   r6, #-1
        STRCS   r6, [r1, #ModeSelector_MaxSize-4] ; then terminate it properly

        MOV     r0, r1                  ; point at static block
12
        STR     R0, [WsPtr, #DisplayModeNo] ; store the new display mode

; now issue Service_ModeChanging

        MOV     R1, #Service_ModeChanging
        BL      IssueModeService


        [ {FALSE} ;;; LCDPowerCtrl :LAND: :LNOT: STB
                  ;;; mjsHAL no LCD support
        ;Switch LCD off here if it is _not_ an LCD mode
        MOV     R3, #0
        LDRB    R3, [R3, #LCD_Active]
        ANDS    R0, R3, #&7F            ;Pick out the lcd mode bits, ignoring the single/dual panel bit
        Push    "r0-r1"
        MOVEQ   R0, #0
        LDREQ   R1, =:NOT:(PortableControl_LCDEnable :OR: PortableControl_BacklightEnable)
        SWIEQ   XPortable_Control
        Pull    "r0-r1"
        ]

; R13 -> mode variables

13      LDR     R3, [R13, #wkScreenSize]
        STR     R3, [WsPtr, #ScreenSize] ; store screensize BEFORE calling
                                        ; ConvertBankToAddress (was a bug!)

 [ UseGraphicsV
        Push    "r0-r4"
        MOV     r4, #GraphicsV_FramestoreAddress
        BL      CallGraphicsV
        TEQ     r4, #0
        BNE     %FT581
        MOV     r1, r1, LSR #20                 ; round size down to 1MB
        MOV     r2, r1, LSL #20
        ADD     r0, r0, #1:SHL:20               ; round addr up to 1MB
        SUB     r0, r0, #1
        MOV     r0, r0, LSR #20                 ; (because of OS_Memory 13 limits)
        MOV     r1, r0, LSL #20
        MOV     r0, #13                         ; map in permanently
        ORR     r0, r0, #1:SHL:8                ; buffered, uncached
        ORR     r0, r0, #1:SHL:16+1:SHL:17      ; doubly map, access permission specified
        SWI     XOS_Memory
        BVS     %FT581
        STR     r2, [WsPtr, #TotalScreenSize]
        ADD     r3, r3, r2
        STR     r3, [WsPtr, #ScreenEndAddr]
        MOV     r14, #1
        B       %FT582
581
        MOV     r0, #128+2
        SWI     XOS_ReadDynamicArea
        STRVC   r1, [WsPtr, #TotalScreenSize]
        ADDVC   r0, r0, r1
        STRVC   r0, [WsPtr, #ScreenEndAddr]
        MOV     r14, #0
582
        Pull    "r0-r4"
        STRB    r14, [WsPtr, #ExternalFramestore]
 ]

        TEQ     R2, #0                  ; Shadowing or not ?
        LDR     R3, [WsPtr, #VduStatus]
        BICEQ   R3, R3, #Shadowing
        ORRNE   R3, R3, #Shadowing
        STR     R3, [WsPtr, #VduStatus]

        STRB    R2, [WsPtr, #ScreenMemoryClaimed] ; only allow ADFS to claim
                                        ; if non-shadow (simplifies things!)

        BL      ConvertBankToAddress    ; R3 := default start for this bank
        STR     R3, [WsPtr, #DriverBankAddr]
        STR     R3, [WsPtr, #DisplayBankAddr]

        MOV     R6, #0
        STRB    R6, [R6, #OsbyteVars + :INDEX:MemDriver]  ; indicate default
        STRB    R6, [R6, #OsbyteVars + :INDEX:MemDisplay] ; for both of these

        LDR     R6, [R13, #wkModeFlags]
        STR     R6, [WsPtr, #DisplayModeFlags]

        MOV     R2, #wkend-wkdispstart  ; number of bytes to do
        ADD     R1, R13, #wkdispstart

        ADD     R4, WsPtr, #PalIndex    ; first display mode variable
15
        LDR     R3, [R1], #4            ; copy variables
        STR     R3, [R4], #4
        SUBS    R2, R2, #4              ; loop until all done
        BNE     %BT15

; now set up other mode variables by calling SwitchOutput

        ADD     R3, WsPtr, #VduSaveArea+InitFlag
        STR     R2, [R3]                ; indicate uninitialised (R2=0)
        TST     R6, #Flag_Teletext
        MOVNE   R3, #0                  ; if teletext, then no save area
        MOVEQ   R3, #1                  ; else MOS's save area
        MOV     R1, #0                  ; just in case
        MOV     R0, #SpriteReason_SwitchOutputToSprite
        SWI     XOS_SpriteOp

; now create other variables from simple ones

        LDR     R3, [WsPtr, #NColour]
        STR     R3, [WsPtr, #DisplayNColour]

        ASSERT  YWindLimit = XWindLimit +4
        ASSERT  DisplayYWindLimit = DisplayXWindLimit +4
        ADD     R0, WsPtr, #XWindLimit
        LDMIA   R0, {R3, R4}
        ADD     R0, WsPtr, #DisplayXWindLimit
        STMIA   R0, {R3, R4}

        ASSERT  YEigFactor = XEigFactor +4
        ASSERT  DisplayYEigFactor = DisplayXEigFactor +4
        ADD     R0, WsPtr, #XEigFactor
        LDMIA   R0, {R3, R4}
        ADD     R0, WsPtr, #DisplayXEigFactor
        STMIA   R0, {R3, R4}

        ASSERT  Log2BPP = Log2BPC +4
        ADD     R0, WsPtr, #Log2BPC
        LDMIA   R0, {R0, R1}                    ; R0 = Log2BPC; R1 = Log2BPP
        SUB     R3, R3, R0                      ; adjust XEig for double pixels
        ADD     R3, R3, R1
        STR     R3, [WsPtr, #PointerXEigFactor]

        LDR     R3, [R13, #wkModeFlags]
        STR     R3, [WsPtr, #ModeFlags]

; finished doing other variables

 [ :LNOT:UseGraphicsV
        Push    "R0-R3, R9, R12"                ; preserve registers ready to make HAL call later
        ADD     R0, R13, #wkwordsize+6*4        ; R0 -> VIDCList3 (we have just pushed 6 regs)
 |
        ADD     R0, R13, #wkwordsize            ; R0 -> VIDCList3
 ]

;adjust vertical porch parameters in VIDCList3 for TVVertical (from *TV)
;
        LDROSB  R1, TVVertical
        MOV     R1, R1, LSL #24          ; sign extend to 32 bits
        MOV     R1, R1, ASR #24-3        ; and multiply by 8
        LDR     R2, [WsPtr, #ModeFlags]
        TST     R2, #Flag_GapMode        ; gap mode ?
        ADDNE   R1, R1, R1, ASR #2       ; add on 2 rows if so
        TST     R2, #Flag_DoubleVertical ; if double vertical
        ADDNE   R1, R1, R1               ; then double it
        LDR     R2, [R0, #VIDCList3_VertiBackPorch]
        SUBS    R2, R2, R1
        MOVMI   R2, #0
        STR     R2, [R0, #VIDCList3_VertiBackPorch]   ;subtract from back porch, clamp at 0
        LDR     R2, [R0, #VIDCList3_VertiFrontPorch]
        ADDS    R2, R2, R1
        MOVMI   R2, #0
        STR     R2, [R0, #VIDCList3_VertiFrontPorch]  ;add to front porch, clamp at 0

;remember pixel rate (kHz) and border settings from VIDCList3
;
        LDR     R2, [R0, #VIDCList3_PixelRate]
        STR     R2, [WsPtr, #PixelRate]
        LDR     R2, [R0, #VIDCList3_HorizLeftBorder]
        STR     R2, [WsPtr, #BorderL]
        LDR     R2, [R0, #VIDCList3_VertiBottomBorder]
        STR     R2, [WsPtr, #BorderB]
        LDR     R2, [R0, #VIDCList3_HorizRightBorder]
        STR     R2, [WsPtr, #BorderR]
        LDR     R2, [R0, #VIDCList3_VertiTopBorder]
        STR     R2, [WsPtr, #BorderT]

;remember DPMSState (if specified) from VIDCList3
;
        MOV     R2, #0                      ; DPMSState = 0 if not specified in list
        ADD     R1, R0, #VIDCList3_ControlList
20      LDR     R3, [R1], #8                ; loop over the control parameter list
        CMP     R3, #-1
        BEQ     %FT30                       ; didn't find the DPMSState entry
        TEQ     R3, #ControlList_DPMSState
        BNE     %BT20                       ; next control parameter
        LDR     R2, [R1, #-4]               ; read DPMSState value
        AND     R2, R2, #3                  ; only bits 0,1 valid
30
        STRB    R2, [WsPtr, #ScreenBlankDPMSState]

;kernel/HAL split - call the HAL to program video controller for mode,
;
 [ UseGraphicsV
        MOV     R4, #GraphicsV_SetMode
        BL      CallGraphicsV
 |
        mjsAddressHAL
        mjsCallHAL    HAL_Video_SetMode

        Pull    "R0-R3, R9, R12"            ; restore registers after HAL call
 ]

        ADD     R13, R13, #PushedInfoSize       ; junk stacked data

        ; for backward compatibility, show that video DMA is enabled in
        ; MEMC soft copy (DON'T call OS_UpdateMEMC, which would also
        ; make redundant call to HAL)
        ;
        SavePSR R2
        MOV     R0, #0
        WritePSRc SVC_mode+I_bit+F_bit, R14
        LDR     R1, [R0, #MEMC_CR_SoftCopy]
        ORR     R1, R1, #(1 :SHL: 10)
        STR     R1, [R0, #MEMC_CR_SoftCopy]
        RestPSR R2

        BL      SetVendDefault

        LDR     R1, [WsPtr, #ScreenEndAddr]     ; need to reload cos corrupt
        LDR     R2, [WsPtr, #TotalScreenSize]
        SUB     R0, R1, R2                      ; R0 = Vstart
        BL      SetVstart
        MOV     R0, #0
        STRB    R0, [WsPtr, #PointerShapeNumber]
        STR     R0, [WsPtr, #TeletextOffset]
        STR     R0, [WsPtr, #CursorStack]       ; restore cursor on a mode

        BL      PalInit                 ; set default palette
        BL      UnblankScreen
        BL      SetMouseRectangle
        BL      FF

        [ {FALSE} ;;; LCDPowerCtrl :LAND: :LNOT: STB
                  ;;; mjsHAL no LCD support
        ;Switch the LCD on if LCD mode
        Push    "r0"
        MOV     R1, #0
        LDRB    R1, [R1, #LCD_Active]
        ANDS    R1, R1, #&7F            ;Check the LCD mode bits only, not the single/dual panel bit
        LDRNE   R0, =(PortableControl_LCDEnable :OR: PortableControl_BacklightEnable)
        LDRNE   R1, =:NOT:(PortableControl_LCDEnable :OR: PortableControl_BacklightEnable)
        SWINE   XPortable_Control
        Pull    "r0"
        ]

        MOV     R1, #Service_ModeChange
        BL      IssueModeService

        CLRV                            ; indicate no error
        Pull    PC                      ; return to caller

        MakeErrorBlock BadMODE

        LTORG

; The following symbols, apart from being used in NColourTable,
; are also used in constructing mode selectors for mode numbers in VduModes

NColour_0       *       1
NColour_1       *       3
NColour_2       *       15
NColour_3       *       63
NColour_4       *       &FFFF
NColour_5       *       &FFFFFFFF

        GET     s.vdu.VduModes

; *****************************************************************************
;
;       IssueModeService - Issue service (either ModeChanging or ModeChange)
;
; in:   R1 = service code
;
; out:  R1 corrupted
;

IssueModeService Entry "r2,r3"
        BL      ReadMonitorType
        LDR     r2, [WsPtr, #DisplayModeNo]
        IssueService
        EXIT

; *****************************************************************************
;
;       PushModeInfo - Push appropriate mode table and video controller params
;       onto stack, having generated it by possibly issuing service
;
; in:   R10 = mode to try for
;       R11 = mode to use if service not claimed
;       R10 and R11 should have bit 7 CLEAR (if mode numbers)
;
; out:  If r10 is an invalid mode selector or invalid new format sprite word then
;         V=1
;         r0 -> error
;         stack flat (no pushed info)
;       else
;         V=0
;         Stack holds a mode table (size wkwordsize) and VIDCList
;         type 3 (size VIDCList3Size) (total size PushedInfoSize)
;       endif
;       All other registers preserved
;

PushModeInfoAnyMonitor ROUT
        SUB     sp, sp, #PushedInfoSize
        Push    "r2-r4,r7-r11, lr"
        MOV     r3, #-1
        MOV     r7, #-1                 ; indicate no VIDC stuff necessary
        CMP     r10, #&100              ; is it a mode selector
        BCS     PushModeInfoCommonNoService
        BranchIfKnownMode r10, PushModeInfoCommonNoService
        B       PushModeInfoCommon

PushModeInfo ROUT
        SUB     sp, sp, #PushedInfoSize
        Push    "r2-r4,r7-r11, lr"
        MOV     r7, #0                  ; indicate VIDC stuff IS necessary
        BL      ReadMonitorType
PushModeInfoCommon
        MOV     r2, r10                 ; r2 = original mode
        BL      OfferModeExtension
        BEQ     %FT30                   ; [service claimed]

        CMP     r2, #&100               ; service not claimed - check if mode selector
        MOVCC   r10, r11                ; unrecognised mode number, so use substitute
        BCC     PushModeInfoCommonNoService

; service not claimed and it's a mode selector - return error "Screen mode not available"

        ADR     r0, ErrorBlock_ModeNotAvailable
  [ International
        BL      TranslateError
  ]
        B       %FT40

        MakeErrorBlock ModeNotAvailable

30
        TEQ     r4, #0                  ; if r4 returned zero, then a mode selector was used
                                        ; either initially or after translation from mode number
        BEQ     %FT35                   ; so no ws list
        LDR     r2, [r4, #4]!           ; else if claimed, then find ws base mode
        CMP     r2, #&100               ; if ws base mode is a mode selector, it's invalid
        ANDCC   r2, r2, #&7F            ; else knock off shadow bit
        BCC     %FT35
        MOV     r10, r11                ; invalid ws base mode, so pretend service not responded to

; and drop thru to PushModeInfoCommonNoService

PushModeInfoCommonNoService
        MOV     r2, r10                 ; else use provided mode
        MOV     r3, #0
        MOV     r4, #0
35
        ADD     r9, sp, #9*4            ; adjust for pushed registers
        CMP     r2, #&100
        BCC     %FT45
        BL      GenerateModeSelectorVars ; also copes with new sprite mode word
        BVC     %FT55

; we got an error

40
        SETV
        Pull    "r2-r4,r7-r11,lr"       ; restore registers
        ADD     sp, sp, #PushedInfoSize ; junk stack frame
        MOV     pc, lr                  ; exit VS, r0 -> error

45
        ADRL    r14, Vwstab
        LDR     r10, [r14, r2, LSL #2]
        ADD     r14, r14, r10           ; r14 -> mode table
        MOV     r10, #wkwordsize-4
50
        LDR     r2, [r14, r10]
        STR     r2, [r9, r10]
        SUBS    r10, r10, #4
        BCS     %BT50

; now change any variables specified in workspace block or mode selector overrides

55
        TEQ     r4, #0                  ; if service was claimed
        ADDNE   r4, r4, #4              ; then skip ws base mode
        BLNE    ProcessModeVarPairs

; special VIDCListType3 widgetry
; 1) Set SyncPol_Interlace (according to TVInterlace) if not already specified
; 2) check for the interlaced flag in the control parameters - if set, set the interlaced
;    flag in the mode flags

        TEQ     r3, #0                  ; do we have a VIDC list?
        BEQ     %FT58
        LDR     r2, [r3, #0]            ; is it type 3? (actually, it always should be now)
        TEQ     r2, #3
        BNE     %FT58

        LDR     r10, [r3, #VIDCList3_SyncPol]
        TST     r10, #SyncPol_InterlaceSpecified
        BNE     %FT56                              ; Interlace already specified
        LDROSB  R14, TVInterlace
        TST     R14, #1
        ORRNE   R10, R10, #SyncPol_InterlaceSpecified                     ; specify as non-interlaced
        ORREQ   R10, R10, #(SyncPol_InterlaceSpecified+SyncPol_Interlace) ; specify as interlaced
        STR     R10, [r3, #VIDCList3_SyncPol]

56
        ADD     r10, r3, #VIDCList3_ControlList
57      LDR     r14, [r10], #8          ; loop over the control parameter list
        CMP     r14, #-1
        BEQ     %FT58                   ; didn't find the interlaced entry - not interlaced

        TEQ     r14, #ControlList_Interlaced
        BNE     %BT57                   ; check the next one then

        LDR     r14, [r10, #-4]         ; read value
        TEQ     r14, #0
        BEQ     %FT58                   ; if zero, not interlaced

; it's interlaced
        LDR     r14, [r9, #wkModeFlags]
        ORR     r14, r14, #Flag_InterlacedMode
        STR     r14, [r9, #wkModeFlags]
58
 [ UseGraphicsV
        Push    "r0-r2,r4"
        MOV     r4, #GraphicsV_DisplayFeatures
        BL      CallGraphicsV
  ; claim or release falsevsync vector if vsyncs supported/not supported
        Push    "r0"
        TST     r0, #1<<4                       ; NE = VSyncs not generated?
        MOV     a1, #TickerV
        LDR     a2, =FalseVsyncIRQ
        LDR     a3, =OsbyteVars
        BEQ     %ft2
        SWI     XOS_Claim
        B       %ft1
2       SWI     XOS_Release
1
        Pull    "r0"

        TST     r0, #1                          ; bit 0 is h/w scroll support
        Pull    "r0-r2,r4"
 |
        LDR     r14, [WsPtr, #HALVideoFeatures]
        TST     r14, #1                         ; bit 0 is h/w scroll support
 ]
        LDREQ   r14, [r9, #wkModeFlags]
        ORREQ   r14, r14, #Flag_HardScrollDisabled
        STREQ   r14, [r9, #wkModeFlags]

; hopefully, R7 is still set from up there to be NZ if no VIDC stuff necessary

        CMP     r7, #0
        Pull    "r2-r4,r7-r11, pc", NE  ; if no VIDC stuff required, exit (NB V=0 from CMP)

; mjs Kernel/HAL split
; pushed video controller info is now hardware independent, it is a VIDCList type 3

        TEQ     R3, #0                  ; if no module claimed service
        MOVEQ   R2, R11                 ; then use provided mode
        BEQ     %FT62
59
        ADD     R14, R9, #wkwordsize    ; R14 -> space for VIDCList3
        MOV     R10, #VIDCList3Size     ; its a VIDCList3, R10 is max room we have
60
        LDR     R8, [R3], #4            ; copy list
        SUBS    R10, R10, #4
        MOVEQ   R8, #-1                 ; emergency terminate (source list too long)
        STR     R8, [R14], #4
        CMP     R8, #-1
        BNE     %BT60
        CLRV
        Pull    "R2-R4,R7-R11, PC"      ; done

62                                      ; arrive here if service not claimed, R2 = provided mode number
        BL      ReadMonitorType         ; get monitor type in R3
        CMP     R3, #NumMonitorTypes    ; monitor type must be in range
        CMPCC   R2, #NumModes           ; and mode must be in range
        MOVCC   R11, #NumModes
        MLACC   R11, R3, R11, R2        ; then form monitortype*numberofmodes + modenumber
        MOVCS   R11, #0                 ; if illegal then use mode 0 monitortype 0
64
        ADRL    R14, BigVIDCTable       ; point to big table
        LDR     R11, [R14, R11, LSL #2] ; and load offset
        CMP     R11, #-1                ; if table offset is valid
        ADDCC   R3, R14, R11            ; then add to table address (R3 -> VIDCList3 from table)
        BCC     %BT59                   ; copy it and exit
        MOV     R11, #0                 ; desperate again, use mode 0 monitortype 0 (this had better have valid entry)
        B       %BT64

; *****************************************************************************
;
;       GenerateModeSelectorVars - Work out mode variables from mode selector
;                                    or new format sprite mode word
;
; Note: the algorithms used to generate these variables are duplicated in
; s.vdu.vduswis in the OS_ReadModeVariable code.

; in:   r2 = new format sprite word or pointer to mode selector
;       r9 -> stack frame to store vars in
;
; out:  If valid then
;           V=0
;           r0 preserved
;       else
;           V=1
;           r0 -> error
;       endif
;       All other registers preserved
;

GenerateModeSelectorVars Entry "r0,r1,r3-r8,r10-r12"
        ASSERT  ModeSelector_Flags = 0
        ASSERT  ModeSelector_XRes = 4
        ASSERT  ModeSelector_YRes = 8
        ASSERT  ModeSelector_PixelDepth = 12
        TST     r2, #1                          ; is it a new format sprite mode word?
        BNE     %FT50                           ; [yes, so skip]
        MOV     r0, r2
        BL      ValidateModeSelector
        BVS     %FT95                           ; invalid - return error
        LDMIB   r2, {r4-r6}                     ; r4 = xres; r5 = yres; r6 = pixdepth

        STR     r6, [r9, #wkLog2BPC]            ; log2bpc = log2bpp = pixdepth
        STR     r6, [r9, #wkLog2BPP]
        ADR     lr, NColourTable
        LDR     lr, [lr, r6, LSL #2]            ; load NColour value
        STR     lr, [r9, #wkNColour]
        ADR     lr, PalIndexTable
        LDRB    lr, [lr, r6]
        STR     lr, [r9, #wkPalIndex]
        ADR     lr, ECFIndexTable
        LDRB    lr, [lr, r6]
        STR     lr, [r9, #wkECFIndex]

        MOV     lr, #0
        STR     lr, [r9, #wkYShftFactor]        ; yshftfactor = 0 (obsolete)
        STR     lr, [r9, #wkModeFlags]          ; modeflags = 0

 [ RogerEXEY
; TMD 09-Dec-93
; New algorithms for xeig, yeig from Roger:
;       xeig = 1: yeig = 1
;       if yres<xres/2 OR yres<400 then yeig = 2
;       if (xres<<xeig)<(yres<<yeig) then xeig = 2

        CMP     r5, r4, LSR #1                  ; if yres < xres/2
        CMPCS   r5, #400                        ; or yres < 400
        MOVCC   r7, #2                          ; then yeig = 2
        MOVCS   r7, #1                          ; else yeig = 1
        STR     r7, [r9, #wkYEigFactor]

        MOV     r7, r5, LSL r7                  ; r7 = yres << yeig
        CMP     r7, r4, LSL #1                  ; if (xres<<1) < (yres<<yeig)
        MOVHI   r7, #2                          ; then xeig = 2
        MOVLS   r7, #1                          ; else xeig = 1
        STR     r7, [r9, #wkXEigFactor]

        MOV     lr, #1
 |
        MOV     lr, #1
        STR     lr, [r9, #wkXEigFactor]         ; xeig = 1
        CMP     r5, r4, LSR #1                  ; if yres < xres/2
        MOVCC   r7, #2                          ; then yeig = 2
        MOVCS   r7, #1                          ; else yeig = 1
        STR     r7, [r9, #wkYEigFactor]
 ]
        RSB     r7, lr, r4, LSR #3              ; scrrcol = (xres >> 3) -1
        STR     r7, [r9, #wkScrRCol]
        RSB     r7, lr, r5, LSR #3              ; scrbrow = (yres >> 3) -1
        STR     r7, [r9, #wkScrBRow]

        SUB     r7, r4, #1
        STR     r7, [r9, #wkXWindLimit]         ; xwindlimit = xres-1
        SUB     r7, r5, #1
        STR     r7, [r9, #wkYWindLimit]         ; ywindlimit = yres-1

        MOV     r7, r4, LSL r6                  ; r7 = xres << pixdepth
        MOV     lr, r7, LSR #3
        STR     lr, [r9, #wkLineLength]         ; linelen = (xres << pixdepth) >> 3

        MUL     r7, r5, r7                      ; r7 = (xres * yres) << pixdepth
        MOV     lr, r7, LSR #3
        STR     lr, [r9, #wkScreenSize]         ; screensize = ((xres * yres) << pixdepth) >> 3

        ADD     r4, r2, #ModeSelector_ModeVars  ; now do pairs of mode variables
        BL      ProcessModeVarPairs

        CLRV
        EXIT


; store info for new format sprite word in stack frame

50
        MOV     r0, #0
        STR     r0, [r9, #wkYShftFactor]        ; yshftfactor = 0
        STR     r0, [r9, #wkModeFlags]          ; modeflags = 0

        MOV     r0, r2, LSR #27                 ; get type
        CMP     r0, #SpriteType_MAX             ; check for legality - NB type 0 is illegal because r2>=&100
        MOVCS   r0, #SpriteType_Substitute      ; substitute if unknown
        ADRL    lr, NSM_bpptable-4
        LDR     r0, [lr, r0, LSL #2]            ; get the bpp from table

        STR     r0, [r9, #wkLog2BPC]
        STR     r0, [r9, #wkLog2BPP]

        ADR     r1, NColourTable
        LDR     r1, [r1, r0, LSL #2]
        STR     r1, [r9, #wkNColour]

        ADR     r1, PalIndexTable
        LDRB    r1, [r1, r0]
        STR     r1, [r9, #wkPalIndex]

        ADR     r1, ECFIndexTable
        LDRB    r1, [r1, r0]
        STR     r1, [r9, #wkECFIndex]

        MOV     r1, r2, LSL #(31-13)
        MOV     r1, r1, LSR #(31-13)+1          ; extract xdpi (bits 1..13)

        TEQ     r1, #180                        ; 180 => xeig=0
        MOVEQ   r1, #0
        BEQ     %FT70

        TEQ     r1, #22                         ; 22/23 => xeig=3
        TEQNE   r1, #23
        MOVEQ   r1, #3
        BEQ     %FT70

        TEQ     r1, #(45 :SHL: 2), 2            ; check if 45   (EQ,CC if so)
        CMPNE   r1, #90                         ; or 90         (EQ,CS if so)
        BNE     %FT80
        MOVCC   r1, #2                          ; 45 => xeig=2
        MOVCS   r1, #1                          ; 90 => xeig=1
70
        STR     r1, [r9, #wkXEigFactor]

        MOV     r1, r2, LSL #(31-26)
        MOV     r1, r1, LSR #(31-26)+14         ; extract ydpi (bits 14..26)

        TEQ     r1, #180                        ; 180 => yeig=0
        MOVEQ   r1, #0
        BEQ     %FT71

        TEQ     r1, #22                         ; 22/23 => yeig=3
        TEQNE   r1, #23
        MOVEQ   r1, #3
        BEQ     %FT71

        TEQ     r1, #(45 :SHL: 2), 2            ; check if 45   (EQ,CC if so)
        CMPNE   r1, #90                         ; or 90         (EQ,CS if so)
        BNE     %FT80
        MOVCC   r1, #2                          ; 45 => yeig=2
        MOVCS   r1, #1                          ; 90 => yeig=1
71
        STR     r1, [r9, #wkYEigFactor]

        CLRV
        EXIT

80
        ADR     r0, ErrorBlock_Sprite_BadDPI
90
 [ International
        BL      TranslateError
 ]
95
        STR     r0, [sp]                        ; update saved r0
        SETV                                    ; indicate error
        EXIT

NColourTable    &       NColour_0, NColour_1, NColour_2
                &       NColour_3, NColour_4, NColour_5
PalIndexTable   =       0, 1, 2, 3, 6, 7
        ALIGN                                   ; makes ECFIndexTable more accessible
ECFIndexTable   =       4, 2, 3, 5, 5, 5
        ALIGN

        MakeErrorBlock  BadPixelDepth
        MakeErrorBlock  Sprite_BadDPI

; *****************************************************************************
;
;       ValidateModeSelector - Check a mode selector is valid
;
; in:   r0 -> mode selector
;
; out:  If OK, then
;         V=0
;         All registers preserved
;       else
;         V=1
;         r0 -> error
;         All other registers preserved
;       endif
;

ValidateModeSelector Entry
        LDR     lr, [r0, #ModeSelector_Flags]
        AND     lr, lr, #ModeSelectorFlags_FormatMask
        TEQ     lr, #ModeSelectorFlags_ValidFormat
        ADRNE   r0, ErrorBlock_BadMSFlags
        BNE     %FT90
        LDR     lr, [r0, #ModeSelector_PixelDepth]
        CMP     lr, #6
        ADRCS   r0, ErrorBlock_BadPixelDepth
        BCS     %FT90
        CLRV
        EXIT

90
  [ International
        BL      TranslateError
  ]
        SETV
        EXIT

        MakeErrorBlock BadMSFlags


; *****************************************************************************
;
;       ProcessModeVarPairs - Modify stacked variable info from
;                              mode variable (index, value) pairs
;
;       Internal routine used to do Service_ModeExtension workspace lists and
;        mode selectors
;
; in:   r4 -> first pair (may be none)
;       r9 -> stack frame
;
; out:  All registers preserved

ProcessModeVarPairs Entry "r4, r8, r10"
        ADRL    r14, RMVTab
10
        LDR     r10, [r4], #4           ; get next entry in ws table
        CMP     r10, #-1
        EXIT    EQ                      ; no more to do
        CMP     r10, #(SWIRVVTabModeEnd-SWIRVVTab) ; is it a mode variable ?
        BCS     %BT10                   ; no, then ignore
        LDRB    r10, [r14, r10]         ; load index out of RMVTab
        MOVS    r10, r10, LSR #1        ; shift byte/word flag into carry
        LDR     r8, [r4], #4            ; load value
        STRCCB  r8, [r9, r10]           ; either store byte
        STRCS   r8, [r9, r10]           ; or store word
        B       %BT10

; *****************************************************************************
;
;       OfferModeExtension - Issue mode extension service
;
; in:   R2 = mode specifier
;
; out:  EQ => service claimed, R3 -> VIDC list, R4 -> workspace list
;       NE => service not claimed, R3,R4 preserved
;       All other registers preserved
;

OfferModeExtensionAnyMonitor ROUT
        MOV     r3, #-1
OfferModeExtension ROUT
        Push    "r1,r2,r4,r5,r14"

; TMD 10-Jan-94 - added code here to check for erroneous passing in of a sprite mode word.
; This prevents data aborts when modules try to index off a bad address.
;
; We could have done OS_ValidateAddress, but that would be rather slow, and mode selectors
; are of indeterminate length.
;
; If we detect one of these, we pretend the service wasn't claimed. Hopefully this should
; ensure that the mode change returns an error.

; Fixes bug MED-00483.

        BICS    r14, r2, #&FF                   ; NE if not a mode number
        TSTNE   r2, #3                          ; NE if not a mode number, but invalid mode selector
        Pull    "r1,r2,r4,r5,pc", NE            ; so exit NE, pretending that service not claimed

        GetBandwidthAndSize     r4, r5
        MOV     r1, #Service_ModeExtension
        IssueService
        TEQ     r1, #0                          ; if service claimed
        CMPNE   r3, #-1                         ; or if "don't care" monitortype
        BEQ     %FT90                           ; then we can't do any more

        CMP     r2, #&100                       ; if it's a mode selector
        BCS     %FT90                           ; then we can't help them either
        BranchIfNotKnownMode r2, %FA90          ; if we don't recognise screen mode number we can't either

; it is a known numbered mode, so create a mode selector on the stack that we can pass to service

        Push    "r6,r7"
        SUB     sp, sp, #ModeSelector_ModeVars+4        ; make room for block including terminator
        MOV     r6, #ModeSelectorFlags_ValidFormat
        STR     r6, [sp, #ModeSelector_Flags]
        ADRL    r6, FrameRateTable
        LDRB    r6, [r6, r2]
        STR     r6, [sp, #ModeSelector_FrameRate]

        ADRL    r6, Vwstab
        LDR     r14, [r6, r2, LSL #2]
        ADD     r6, r6, r14
        LDR     r14, [r6, #wkLog2BPP]
        STR     r14, [sp, #ModeSelector_PixelDepth]     ; pixdepth = log2bpp

        LDR     r7, [r6, #wkLog2BPC]
        SUB     r14, r7, r14                            ; r14 = log2bpc-log2bpp

        LDR     r7, [r6, #wkXWindLimit]
        ADD     r7, r7, #1
        MOV     r7, r7, LSL r14
        STR     r7, [sp, #ModeSelector_XRes]

        LDR     r7, [r6, #wkYWindLimit]
        ADD     r7, r7, #1
        STR     r7, [sp, #ModeSelector_YRes]

        MOV     r7, #-1
        STR     r7, [sp, #ModeSelector_ModeVars]

        MOV     r2, sp
        IssueService
        TEQ     r1, #0
        BEQ     %FT10                                   ; service was claimed

; not claimed, so try again with -1 as frame rate

        MOV     r7, #-1
        STR     r7, [sp, #ModeSelector_FrameRate]
        IssueService
        TEQ     r1, #0
10
        ADD     sp, sp, #ModeSelector_ModeVars+4        ; junk mode selector
        Pull    "r6, r7"
90
        CMP     r2, #&100                               ; if we started or ended up with a mode selector
        MOVCS   r4, #0                                  ; then return r4 = 0 (if claimed)

        TEQ     r1, #0
        STREQ   r4, [sp, #2*4] ; if service claimed, then return r4 from service, else preserve it
        Pull    "r1,r2,r4,r5,pc"

 [ STB
svc_PortMan
        TEQ     R0, #0                                  ; PortManager starting
        MOVNE   PC, LR

    [ {TRUE}
;;;mjsHAL - may need sorting/HAL split
;;;         for now, switched out coz kernel doesn't have VIDCControlSoftCopy any more!
;;;
   ! 0, "mjsHAL - svc_PortMan currently broken by kernel/HAL split"
        MOV     PC, LR
    |
; When PortManager restarts, we need to put TV_Mode back. PortMan defaults to
; output high, but we don't know polarity of bit, so must set either way.
        Entry   "r0-r1, WsPtr"
        VDWS    WsPtr
        LDR     R0, [WsPtr, #VIDCControlSoftCopy]       ; get saved VIDC control register
        AND     R0, R0, #CR_VCLK :OR: CR_HCLK :OR: CR_RCLK
        TEQ     R0, #CR_HCLK                            ; are we using HCLK?
        MOVNE   R0, #1                                  ; no: TV_Mode = 0
        MOVEQ   R0, #3                                  ; yes: TV_Mode = 1
        addr    R1, TV_Mode_string
        SWI     XPortMan_AccessBit
        EXIT
    ]
 ]


; *****************************************************************************
;
; ETB - Redefine character
; ===   & other stuff
;
;       VDU 23,0,r,v,0|         Talk to 6845 !
;       VDU 23,1,n,m,r,g,b|     Program cursor
;       VDU 23,2,n1..n8         Ecf pattern 1
;       VDU 23,3,n1..n8         Ecf pattern 2
;       VDU 23,4,n1..n8         Ecf pattern 3
;       VDU 23,5,n1..n8         Ecf pattern 4
;       VDU 23,6,n1..n8         Dot dash line style
;       VDU 23,7,m,d,z|         Scroll window directly
;       VDU 23,8,t1,t2,x1,y1,x2,y2|     Clear block
;       VDU 23,9,n|             Set 1st flash time
;       VDU 23,10,n|            Set 2nd flash time
;       VDU 23,11|              Default Ecf patterns
;       VDU 23,12,n1..n8        Ecf pattern 1 (simple setting)
;       VDU 23,13,n1..n8        Ecf pattern 2 (simple setting)
;       VDU 23,14,n1..n8        Ecf pattern 3 (simple setting)
;       VDU 23,15,n1..n8        Ecf pattern 4 (simple setting)
;       VDU 23,16,x,y|          Cursor movement control
;       VDU 23,17,c,t|          Set colour tints, ECF info, char sizes
;
;
ETB
        LDRB    R0, [WsPtr, #QQ+0]
        CMP     R0, #32         ; defining a normal character ?
        BCS     DefineChar
        LDR     R2, [PC, R0, LSL #2]
        ADD     PC, PC, R2      ; enters routine with R0 => byte after 23


ETBtab
        &       Vdu23_0-ETBtab-4
        &       Vdu23_1-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       ComplexEcfPattern-ETBtab-4
        &       LineStyle-ETBtab-4
        &       Vdu23_7-ETBtab-4
        &       Vdu23_8-ETBtab-4
        &       Vdu23_9-ETBtab-4
        &       Vdu23_10-ETBtab-4
        &       DefaultEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       SimpleEcfPattern-ETBtab-4
        &       Vdu23_16-ETBtab-4
        &       Vdu23_17-ETBtab-4
        &       Vdu23_18-ETBtab-4
        &       Vdu23_19-ETBtab-4
        &       Vdu23_20-ETBtab-4
        &       Vdu23_21-ETBtab-4
        &       Vdu23_22-ETBtab-4
        &       Vdu23_23-ETBtab-4
        &       Vdu23_24-ETBtab-4
        &       Vdu23_25-ETBtab-4
        &       Vdu23_26-ETBtab-4
        &       Vdu23_27-ETBtab-4
        &       Vdu23_28-ETBtab-4
        &       Vdu23_29-ETBtab-4
        &       Vdu23_30-ETBtab-4
        &       Vdu23_31-ETBtab-4

; NB All other labels for Vdu23 are in TMD files so I don't have to pester RCM

;Vdu23_18       ; Assigned to Teletext operations
Vdu23_19
Vdu23_20
Vdu23_21
Vdu23_22
Vdu23_23
Vdu23_24
Vdu23_25
Vdu23_26
; Vdu23_27      ; Assigned to Richard (well some of it, anyway)
Vdu23_28
Vdu23_29
Vdu23_30
Vdu23_31
UnknownVdu23

; R0 already contains first parameter to VDU23

        MOV     R10, #UKVDU23V
        Push    "WsPtr, R14"            ; calling a vector corrupts R12
        BL      VduQQVec                ; so we have to preserve it
        Pull    "WsPtr, PC", VC         ; before we return to PostWrchCursor

; error in UKVDU23 vector, so go to vdu error exit

        Pull    "WsPtr, R14"
        B       VduBadExit

; *****************************************************************************
;
; DLE
; CLG - Clear graphics window
; ===
;
; On exit, R0..R11 corrupt
;
DLE
CLG     ROUT
        GraphicsMode R0
        MOVNE   PC,LR                   ; check for graphics mode (changed by DDV 15/9/92)

        ADD     R0, WsPtr, #BgEcfOraEor ; point at background colour
        STR     R0, [WsPtr, #GColAdr]
        ADD     R11, WsPtr, #GWLCol     ; load window coordinates into
        LDMIA   R11, {R0-R3}            ; RectFill's parameter space

        LDR     R6, [WsPtr, #CursorFlags] ; if clip box is not enabled
        TST     R6, #ClipBoxEnableBit   ; then goto code directly
        BEQ     RectFillA

        Push    R14                     ; else merge graphics window (in R0-R3)
        BL      MergeClipBox            ; with clip box
        Pull    R14
        B       RectFillA

; *****************************************************************************
;
; DC2
; GCol - Set Graphics action and colour
; ====
;
; On entry, R0 holds GCol action
;           R1 holds GCol colour, 0..127 means program fg
;                                 128..255 means program bg
;
; In 256-colour modes, the extra colours are accessed via TINT
;
DC2
GCol    ROUT
        LDRB    R0, [WsPtr, #QQ+0]      ; GCol action, eg store, eor etc.
        LDRB    R1, [WsPtr, #QQ+1]      ; GCol colour
        LDR     R2, [WsPtr, #NColour]   ; number of colours-1
        TST     R1, #&80
        AND     R1, R1, R2              ; limit colour to range available

        STREQ   R0, [WsPtr, #GPLFMD]    ; GCOL(a,0..127) is foreground
        STREQ   R1, [WsPtr, #GFCOL]

        STRNE   R0, [WsPtr, #GPLBMD]    ; GCOL(a,128..255) is background
        STRNE   R1, [WsPtr, #GBCOL]
                                        ; drop into SetColour to....

; SetColour - Setup FgEcf & BgEcf, used after GCOL or setting of Ecfs
; =========   or default palette (does a gcol).

SetColour
        Push    R14
        LDR     R1, [WsPtr, #GPLFMD]    ; setup FgEcf, maybe solid or Ecf
        LDR     R0, [WsPtr, #GFCOL]
        ADD     R2, WsPtr, #FgEcf
        LDR     R3, [WsPtr, #GFTint]    ; tint only used in 256 colour modes
        ADD     R4, WsPtr, #FgPattern   ; used if OS_SetColour call
        BL      SetCol10

        LDR     R1, [WsPtr, #GPLBMD]    ; and BgEcf
        LDR     R0, [WsPtr, #GBCOL]
        ADD     R2, WsPtr, #BgEcf
        LDR     R3, [WsPtr, #GBTint]
        ADD     R4, WsPtr, #BgPattern   ; used if OS_SetColour call
        BL      SetCol10

        ADD     R0, WsPtr, #FgEcf       ; setup FgEcfOraEor
        ADD     R1, WsPtr, #BgEcf
        ADD     R2, WsPtr, #FgEcfOraEor
        LDR     R3, [WsPtr, #GPLFMD]
        BL      SetCol60

        ADD     R0, WsPtr, #BgEcf       ; and BgEcfOraEor
        ADD     R1, WsPtr, #FgEcf
        ADD     R2, WsPtr, #BgEcfOraEor
        LDR     R3, [WsPtr, #GPLBMD]
        BL      SetCol60

        ADD     R0, WsPtr, #BgEcf       ; and BgEcfStore
        ADD     R1, WsPtr, #FgEcf
        ADD     R2, WsPtr, #BgEcfStore
        MOV     R3, #0
        BL      SetCol60

        Pull    PC

; SetCol10 - Internal to SetColour
;          Build up an Ecf, given action and colour numbers
;
; On entry, R0 holds colour (0..255, where 127..255 means 0..127)
;           R1 holds action (may indicate ecf)
;           R2 holds address to write data (FgEcf or BgEcf)
;           R3 holds tint information (only used in 256 colour modes)
;           R4 holds pointer to suitable pattern table for OS_SetColour call

SetCol10 ROUT
        ANDS    R1, R1, #&F0            ; actions >=16 mean Ecf
        BNE     SetCol30

        Push    R14
        LDR     R4, [WsPtr, #NColour]   ; else use given colour number
        AND     R0, R0, R4
        AND     R0, R0, #63             ; another bodge in the house of bodges
        TST     R4, #&F0                ; if 256 colour (ie 8bpp)
        BLNE    AddTintToColour         ; then combine tint with colour

; R0 contains colour number for current mode

        LDR     LR, [WsPtr, #BitsPerPix]
10
        TEQ     LR, #32
        ORRNE   R0, R0, R0, LSL LR      ; replicate again
        MOVNE   LR, LR, LSL #1          ; doubling the shift for each pass
        BNE     %BT10

        STR     R0, [R2]
        STR     R0, [R2, #4]
        STR     R0, [R2, #8]
        STR     R0, [R2, #12]
        STR     R0, [R2, #16]
        STR     R0, [R2, #20]
        STR     R0, [R2, #24]
        STR     R0, [R2, #28]

        Pull    "PC"

; R1 = ecf number as 16,32,48,64,80,96
; R2 -> destination
; R4 -> pattern block (if R1 =96!)

SetCol30
        CMP     R1, #96                 ; special internal plot?
        MOVCS   R0, R4                  ; yes, so point at pattern to be copied
        MOVCS   R3, #1                  ; col step =1
        MOVCS   R4, #0                  ; row step =0 (already there!)
        BCS     SetCol35

        CMP     R1, #80                 ; 80 => giant ecf (>80 does same)
        ADDCS   R0, WsPtr, #Ecf1        ; then point at ecf0
        MOVCS   R3, #8                  ; col step=8
        MOVCS   R4, #(1-8*4)            ; row step, back to 1st ecf on 1 byte
        BCS     SetCol35

        ADD     R0, WsPtr, #(Ecf1-8)
        ADD     R0, R0, R1, LSR #1      ; else point R0 at Ecf1,2,3 or 4

        LDR     R4, [WsPtr, #BitsPerPix] ; if BitsPerPix <> 'fudged' BitsPerPix
        LDR     R5, [WsPtr, #BytesPerChar]
        TEQ     R4, R5
        BNE     SetCol52                ; then double up the pixels
                                        ; else its a normal Ecf
        MOV     R3, #0                  ; col step=0, same byte each coloum
        MOV     R4, #1                  ; row step=1
SetCol35
        MOV     R5, #8                  ; do 8 rows
SetCol40
        MOV     R6, #4                  ; of 4 columns
SetCol50
        LDRB    R7, [R0], R3            ; read from source & move by col step
        STRB    R7, [R2], #1            ; write to dest, update dest pointer
        SUBS    R6, R6, #1
        BNE     SetCol50
        ADD     R0, R0, R4              ; step source pointer to next row
        SUBS    R5, R5, #1
        BNE     SetCol40
        MOV     PC, R14

; Double up the pixels for Mode2 etc
;
; R0 points to Ecf(n)
; R2 points to destination
;
; Uses
;
;     R3  - NColour used as PixMsk
;     R4  - BitsPerPix
;     R5  - BytesPerChar (unused)
;     R6  - byte cntr 7..0
;     R7  - source byte
;     R8  - ExtrtShftFact
;     R9  - InsrtShftFact
;     R10 - result word
;     R11 - temp

SetCol52
        LDR     R3, [WsPtr, #NColour]   ; mask for extracting pixels from ecf
        TST     R3, #&F0                ; ** if 256 colour mode
        MOVNE   R3, #&FF                ; ** then use &FF (TMD 25-Mar-87)
        LDR     R4, [WsPtr, #BitsPerPix]
        MOV     R6, #7                  ; 8 bytes/rows to do
SetCol54
        LDRB    R7, [R0, R6]            ; get byte of Ecf(n)
        RSB     R8, R4, #8
        RSB     R9, R4, #32
        MOV     R10, #0                 ; clear result word
SetCol57
        AND     R11, R3, R7, ROR R8     ; extract 1 pixel from Ecf
        ORR     R10, R10, R11, LSL R9   ; double it into result word
        SUB     R9, R9, R4
        ORR     R10, R10, R11, LSL R9

        SUB     R8, R8, R4
        AND     R8, R8, #7

        SUBS    R9, R9, R4
        BGE     SetCol57                ; process next pixel in result word

        STR     R10, [R2, R6, LSL #2]   ; write expanded word to (Fg/Bg)Ecf

        SUBS    R6, R6, #1
        BGE     SetCol54                ; process next row/byte
        MOV     PC, R14

; Tables of full colours for 2,4 & 16 colour modes (256 colour modes
; use colour number directly).
;
; N.B. these are tables of bytes

TBFullCol
        =       &FF                     ; not used - remove sometime
                                        ; (DJS comment: don't bother!)
        =       &00, &FF                ; 2 colour mode
        =       &00, &55, &AA, &FF      ; 4 colour mode

        =       &FF, &FF, &FF, &FF      ; not used but cannot be removed
        =       &FF, &FF, &FF, &FF      ; (8 colour mode!)

        =       &00, &11, &22, &33      ; 16 colour mode
        =       &44, &55, &66, &77
        =       &88, &99, &AA, &BB
        =       &CC, &DD, &EE, &FF

        ALIGN

; *****************************************************************************
;
;       SetCol60 - Build up an ecf, ORed and EORed appropriate to GCOL action
;
;       Internal to SetColour
;
; in:   R0 -> ecf colour (FgEcf/BgEcf)
;       R1 -> transparent colour (BgEcf/FgEcf)
;       R2 -> destination FgEcfOraEor/BgEcfOraEor/BgEcfStore
;       R3 = gcol action number (may indicate ecf)
;
; uses: R4 = index into ecf (7..0)
;       R5 = ecf colour word
;       R6 = transparency mask, set to &FFFFFFFF for NO transparency
;       R7 -> zgoo..zgee for gcol action
;       R8 = mask for pixel under examination
;       R9 = shift factor to move mask to next pixel (BytesPerChar)
;       R10, R11 temporary
;

SetCol60 ROUT
        MOV     R4, #7                          ; 7..0 words to process
        AND     R3, R3, #&F                     ; extract action bits
        AND     R11, R3, #7                     ; 0-7 Store etc
                                                ; 8-15 ditto with transparency
        MOV     R11, R11, LSL #2                ; 4 bits for each
        LDR     R7, =TBscrmasks
        MOV     R7, R7, ROR R11                 ; relevant bits are in top 4
        AND     R7, R7, #&F0000000              ; isolate these bits (N,Z,C,V)
SetCol70
        LDR     R5, [R0, R4, LSL #2]            ; get ecf word
        TST     R3, #8                          ; if action < 8
        MOVEQ   R6, #&FFFFFFFF
        BEQ     SetCol90                        ; then not transparent
                                                ; else build transparency mask
        LDR     R8, [WsPtr, #RAMMaskTb]         ; fetch mask for leftmost pixel
        LDR     R9, [WsPtr, #BytesPerChar]      ; shift factor for next pixel
        LDR     R6, [R1, R4, LSL #2]            ; get 'transparent' colour
        EOR     R6, R6, R5
SetCol80
        TST     R6, R8                          ; if pixels the same,
                                                ; then it's transparent
        ORRNE   R6, R6, R8                      ; else set mask to plot it
        MOVS    R8, R8, LSL R9
        BNE     SetCol80

SetCol90
        MSR     CPSR_f, R7                      ; put bits into N, Z, C, V
                                                ;              OO,EO,OE,EE

        MOVCC   R10, R5                         ; if ORing with &00000000
        MOVCS   R10, #&FFFFFFFF                 ; if ORing with &FFFFFFFF
        MVNVS   R10, R10                        ; if EORing with &FFFFFFFF

;       MOVPL   R5, R5                          ; if ORing with &00000000
        MOVMI   R5, #&FFFFFFFF                  ; if ORing with &FFFFFFFF
        MVNEQ   R5, R5                          ; if EORing with &FFFFFFFF

; now R5 = OR mask, R10 = EOR mask

        AND     R5, R5, R6                      ; then clear 'transparent'
        AND     R10, R10, R6                    ; pixels

        LDR     R11, [WsPtr, #ECFShift]
        MOV     R5, R5, ROR R11                 ; rotate OR and EOR masks
        MOV     R10, R10, ROR R11               ; to correct for ECF X origin

        LDR     R11, [WsPtr, #ECFYOffset]
        ADD     R11, R11, R4                    ; add on ECF Y offset
        AND     R11, R11, #7                    ; and wrap

        ADD     R11, R2, R11, LSL #3
        STMIA   R11, {R5, R10}                  ; write to (Fg/Bg)EcfOraEor

        SUBS    R4, R4, #1
        BCS     SetCol70

        MOV     PC, R14

; *****************************************************************************
;
;       AddTintToColour - in 256 colour modes
;
;       Internal to SetColour (derived from TMD's FudgeColour)
;
; in:   R0 = colour (0..255), where 6 LSBits are used
;       R3 = tint
;
; out:  R0 holds colour byte with tint added
;       R1-R3 preserved
;       R4 undefined
;       PSR preserved
;

        !       0,"WARNING: AddTintToColour returns > 8 bit values now, check ECF handling!"

AddTintToColour
        Push    "R3,LR"
        AND     R0, R0, #63             ; extract suitable set of bits
        AND     R3, R3, #192            ; and another set
        ORR     R0, R0, R3
        BL      ConvertGCOLToColourNumber
        Pull    "R3,PC"

; *****************************************************************************
;
;       CAN - Define graphics window
;
;       External routine
;
; in:   The window is given by bytes in the vdu queue, as follows :-
;         QQ+0 = leftLo
;         QQ+1 = leftHi
;         QQ+2 = bottomLo
;         QQ+3 = bottomHi
;         QQ+4 = rightLo
;         QQ+5 = rightHi
;         QQ+6 = topLo
;         QQ+7 = topHi
;
;       These are relative to the current graphics origin.
;       The resultant window must obey the following rules :-
;         RCol >= LCol
;         TRow >= BRow
;         LCol >= 0
;         BRow >= 0
;         YWindLimit >= TRow
;         XWindLimit >= RCol
;

CAN     ROUT
        Push    R14
        ADD     R8, WsPtr, #GCsX        ; save ECursor away, cos EIG changes it
        LDMIA   R8, {R6, R7}            ; and we don't want it to!

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+5]      ; rightHi
;        LDRB    R1, [WsPtr, #QQ+4]      ; rightLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;
;        LDRB    R1, [WsPtr, #QQ+7]      ; topHi
;        LDRB    R2, [WsPtr, #QQ+6]      ; topLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+4 ;Get top right point

; *****End of change made by DJS

        MOV     R2, #&FF                ; convert external-to-internal
        BL      EIG                     ; as absolute coordinates

        MOV     R4, R0                  ; move internal version of top right
        MOV     R5, R1                  ; out of harm's way

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+1]      ; leftHi
;        LDRB    R1, [WsPtr, #QQ+0]      ; leftLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;
;        LDRB    R1, [WsPtr, #QQ+3]      ; bottomHi
;        LDRB    R2, [WsPtr, #QQ+2]      ; bottomLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+0 ;Get bottom left point

; *****End of change made by DJS

        MOV     R2, #&FF                ; convert external-to-internal
        BL      EIG                     ; as absolute coordinates

; For a valid window, the following must be true

        CMP     R4, R0                          ;  RCol >= LCol
        CMPGE   R5, R1                          ;  TRow >= BRow
        CMPGE   R0, #0                          ;  LCol >= 0
        CMPGE   R1, #0                          ;  BRow >= 0
        LDRGE   R2, [WsPtr, #YWindLimit]        ;  YWindLimit >= TRow
        CMPGE   R2, R5
        LDRGE   R2, [WsPtr, #XWindLimit]        ;  XWindLimit >= RCol
        CMPGE   R2, R4

        ADD     R2, WsPtr, #GWLCol
        STMGEIA R2, {R0,R1, R4,R5}      ; if the new window is OK, update it

        STMIA   R8, {R6, R7}            ; restore ECursor (EIG corrupted it)
        Pull    PC

; *****************************************************************************
;
;       DefaultWindows - Restore default windows
;
;       External routine, and called by mode change + switch output to sprite
;
;       Set default text and graphics windows,
;       Clear graphics origin and both cursors
;

DefaultWindows ROUT
        Push    R14
        MOV     R0, #0
        MOV     R1, #0
        ADD     R4, WsPtr, #GWLCol

        ASSERT  YWindLimit = XWindLimit +4

        ADD     R2, WsPtr, #XWindLimit
        LDMIA   R2, {R2,R3}     ; R2 := XWindLimit; R3 := YWindLimit
        STMIA   R4, {R0-R3}     ; zero GWLCol, GWBRow
                                ; GWRCol:=XWindLimit; GWTRow:=YWindLimit
        MOV     R3, #0
        LDR     R1, [WsPtr, #ScrBRow]
        LDR     R2, [WsPtr, #ScrRCol]
        ADD     R4, WsPtr, #TWLCol      ; zero TWLCol, TWTRow
        STMIA   R4!, {R0-R3}            ; TWRCol := ScrRCol; TWBRow := ScrBRow

        MOV     R1, #0
        MOV     R2, #0
        STMIA   R4!, {R0-R3}    ; zero OrgX, OrgY, GCsX, GCsY
        STMIA   R4!, {R0-R3}    ; zero OlderCsX, OlderCsY, OldCsX, OldCsY
        STMIA   R4!, {R0-R3}    ; zero GCsIX, GCsIY, NewPtX, NewPtY

        LDR     R0, [WsPtr, #VduSprite]
        TEQ     R0, #0

        LDR     R0, [WsPtr, #VduStatus]         ; if not outputting to sprite
        BICEQ   R0, R0, #Windowing              ; then indicate no text window
        ORRNE   R0, R0, #Windowing              ; else indicate is text window
        STR     R0, [WsPtr, #VduStatus]

        BL      HomeVdu4                        ; home TEXT cursor
                                                ; (even in VDU 5 mode)
        Pull    PC

; *****************************************************************************
;
;       GS - Define graphics origin
;
;       External routine
;
; in:   The origin is given by bytes in the vdu queue, as follows :-
;         QQ+0 = xLo
;         QQ+1 = xHi
;         QQ+2 = yLo
;         QQ+3 = yHi
;
;       The coordinates are in external 16 bit form.
;       This does not move the windows, but does move the graphics cursor
;

GS      ROUT
        Push    R14

; *****Change made by DJS
; Original code was:
;        LDRB    R0, [WsPtr, #QQ+1]      ; xHi
;        LDRB    R1, [WsPtr, #QQ+0]      ; xLo
;        PackXtnd R0,R0,R1               ; pack 2 bytes and sign extend
;        LDRB    R1, [WsPtr, #QQ+3]      ; yHi
;        LDRB    R2, [WsPtr, #QQ+2]      ; yLo
;        PackXtnd R1,R1,R2               ; pack 2 bytes and sign extend

        LoadCoordPair R0, R1, WsPtr, QQ+0

; *****End of change made by DJS

        ADD     R2, WsPtr, #OrgX
        STMIA   R2, {R0,R1}             ; write the new origin
        BL      IEG                     ; update external cursor
        Pull    PC

        LTORG

;------------------------------------------------------------------------------
;
; OS_SetColour implementation
; ------------
;
; This call can be used to change the current GCOL/pattern table used for
; plotting with the VDU primitives.
;
; in    R0 = flags / logical operation
;               bit 0-3 = logical operation
;               bit 4   = set => bg, else fg flag
;               bit 5   = set => pattern block supplied
;               bit 6   = set => set text colour
;               bit 7   = set => read colour
;       R1 = colour number / -> pattern block to use
; out   -
;
;------------------------------------------------------------------------------

setcol_LogicOpMask      * &0F
setcol_FgBgFlag         * &10
setcol_PatternFlag      * &20
setcol_TextColour       * &40
setcol_ReadFlag         * &80

        ASSERT  WsPtr > 9

SWISetColour ROUT

        Push    "R0-R9,WsPtr,R14"

        VDWS    WsPtr                           ; Obtain base of the VDU driver workspace

        TST     R0, #setcol_ReadFlag            ; Are we reading?
        BNE     %FT75

        TST     R0, #setcol_TextColour          ; Are we changing the text colour?
        BNE     %FT70

        AND     R2, R0, #setcol_LogicOpMask     ; Get the logical operation
        ORR     R2, R2, #&60                    ; Mark as being a special kind of pattern

        TST     R0, #setcol_FgBgFlag
        STREQ   R2, [WsPtr, #GPLFMD]            ; Store the relevant logical operation away for fg/bg
        STRNE   R2, [WsPtr, #GPLBMD]
        ADDEQ   R3, WsPtr, #FgPattern
        ADDNE   R3, WsPtr, #BgPattern           ; Setup the pointer to a store for the pattern

        TST     R0, #setcol_PatternFlag         ; Did the caller specify a pattern block?
        BNE     %FT50                           ; Yes so don't try to expand colour value to pattern

        MOV     R2, #1
        LDR     R4, [WsPtr, #Log2BPP]           ; Get the Log2 depth of the mode
        MOV     R4, R2, ASL R4                  ; R4 = bits per pixel
        RSB     R2, R2, R2, LSL R4              ; Get a mask to extract only meaningful bits from word
        AND     R1, R1, R2                      ; Extract bits suitable for this depth of mode
10
        TEQ     R4, #32                         ; Do we need to do any more replication
        ORRNE   R1, R1, R1, LSL R4              ; Yes so or word with itself shifted
        MOVNE   R4, R4, LSL #1                  ; and double amount to shift next time
        BNE     %BT10

        MOV     R2, #8
20
        STR     R1, [R3], #4                    ; now copy word 8 times into block
        SUBS    R2, R2, #1
        BNE     %BT20
        B       %FT60

50
        LDMIA   R1,{R0,R2,R4-R9}
        STMIA   R3,{R0,R2,R4-R9}                ; Copy the pattern into the buffer (assumes word aligned)
60
        BL      SetColour                       ; And then setup the internal GCOL tables
65
        Pull    "R0-R9,WsPtr,R14"
        ExitSWIHandler
70
        TST     R0, #setcol_FgBgFlag            ; Store the foreground or background colour?
        STREQ   R1, [WsPtr, #TextFgColour]
        STRNE   R1, [WsPtr, #TextBgColour]

        LDR     R0, [WsPtr, #CursorFlags]       ; Indicate the text colour needs re-computing!
        ORR     R0, R0, #TEUpdate
        STR     R0, [WsPtr, #CursorFlags]

        B       %BT65                           ; Leave gracefully ....

75
        ; Reading the colour...
        TST     R0, #setcol_TextColour
        BEQ     %FT80

        ; Reading text colour
        TST     R0, #setcol_FgBgFlag
        LDREQ   R1, [WsPtr, #TextFgColour]
        LDRNE   R1, [WsPtr, #TextBgColour]
        BIC     R0, R0, #setcol_PatternFlag :OR: setcol_ReadFlag

77
        ; Standard exit for reading the colour
        STMIA   SP, {R0,R1}
        B       %BT65

80
        ; Reading graphics colour
        TST     R0, #setcol_FgBgFlag
        LDREQ   R2, [WsPtr, #GPLFMD]            ; Get the relevant logical operation for fg/bg
        LDRNE   R2, [WsPtr, #GPLBMD]

        ; SetColour setting - copy block
        ADDEQ   R3, WsPtr, #FgEcf
        ADDNE   R3, WsPtr, #BgEcf

        ; Copy the pattern to the user's buffer
        LDMIA   R3,{R0,R3,R4-R9}
        STMIA   R1,{R0,R3,R4-R9}

        ; Construct a suitable reason code
        AND     R0, R2, #setcol_LogicOpMask
        ORRNE   R0, R0, #setcol_FgBgFlag
        ORR     R0, R0, #setcol_PatternFlag
        B       %BT77

        END