; 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.VduGrafJ
;
; ARTHUR OPERATING SYSTEM - Vdu Drivers
; =======================
;
; Vdu driver code - Sprite stuff
;
; Author R C Manby
; Date   10.11.86
;

; *****************************************************************************
;
;       GetSpriteUserCoords - Pick up area of screen as sprite using
;                             given external coordinates
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite name
;       R3 = 0 => exclude palette data
;            1 => include palette data
;       R4,R5 = (X,Y) EXTERNAL coordinates of one corner of box
;       R6,R7 = (X,Y) EXTERNAL coordinates of opposite corner of box
;

GetSpriteUserCoords ROUT
        Push    "R1-R3, R14"
        ADD     R8, WsPtr, #GCsX
        LDMIA   R8, {R9,R10}            ; preserve GCsX,GCsY around EIG

        MOV     R0, R4
        MOV     R1, R5
        MOV     R2, #4                  ; indicate absolute coord
        BL      EIG
        MOV     R4, R0
        MOV     R5, R1

        MOV     R0, R6
        MOV     R1, R7
        BL      EIG
        MOV     R6, R0
        MOV     R7, R1

        STMIA   R8, {R9,R10}            ; restore GcsX,GCsY
        Pull    "R1-R3, R14"
        B       GetSpr05

; *****************************************************************************
;
;       GetSprite - Pick up area of screen bounded by OldCs and GCsI as sprite
;
;       External routine + GetSpr05 called by GetSpriteUserCoords
;        (also external)
;
; in:   R1 -> sprite area
;       R2 -> sprite name
;       R3 = 0 => exclude palette data
;            1 => include palette data
;       OldCsX,OldCsY = (X,Y) INTERNAL coordinates of one corner of box
;       GCsIX, GCsIY  = (X,Y) INTERNAL coordinates of opposite corner of box
;

GetSprite ROUT
        ADD     R4, WsPtr, #OldCsX      ; pickup area given by OldCs
        LDMIA   R4, {R4-R7}             ; and GCsIX
GetSpr05
        Push    R14
      [ {TRUE}
        GraphicsMode R0
        BNE     %FT70
      |
        LDR     R0, [WsPtr, #NPix]
        TEQ     R0, #0
        BEQ     %FT70                   ; quit with error if not graphics mode
      ]

        KillSpChoosePtr

        SortT   R4, R6, R8              ; R4 ,R5, R6 ,R7 N.B. BotL &
        SortT   R5, R7, R8              ; sL ,sB, sR ,sT      TopR

        LDR     R8, [WsPtr, #YWindLimit]
        SUB     R8, R8, R7              ; use inverted sT as index
        AND     R8, R8, #7              ; into EcfPatternTable
        STR     R8, [WsPtr, #SGetEcfIndx]

        LDR     R0, [WsPtr, #ModeNo]
        STR     R0, [WsPtr, #SGetMode]  ; needs setting up before CreateHeader

        Push    R2
        BL      SpriteCtrlBlk
        BVC     %FT90           ; sprite already exists, so be clever
        Pull    R2              ; restore name pointer

                                ;      R1     ,R2     ,R3       ,R4,R5,R6,R7
        BL      CreateHeader    ; In : AreaPtr,NamePtr,Palette  ,sl,sb,sr,st
                                ; Out:                 ImageSize,lx,ty,
        BVS     %FT80           ; Error, (no room/not a graphics mode)

        BL      GetSpriteData

; R1 -> sprite area, R2 -> sprite
; now add the sprite to the sprite area

        LDR     R3, [R2, #spNext]       ; total size of new sprite
        LDMIA   R1, {R4-R7}             ; saEnd,saNumber,saFirst,saFree
        ADD     R5, R5, #1
        ADD     R7, R7, R3
        STMIA   R1, {R4-R7}

; have we made a new format sprite ? if so no left hand wastage is allowed.

        LDR     R3, [R2, #spMode]
        CMP     R3, #256

        BLCS    RemoveLeftHandWastage

        BL      SelectSprite
        SWI     XOS_RestoreCursors
        Pull    R14
        RETURNVC

70
        ADRL    R0, SpriteErr_NotGraphics
      [ International
        BL      TranslateError
      ]
75
        STR     R0, [WsPtr, #RetnReg0]
80                                      ; return point after an error
        Pull    R14
        RETURNVS

; come here if sprite already exists
; we want to extend or reduce existing sprite as necessary

90
        ADD     R13, R13, #4            ; throw away stacked name ptr
        LDR     R14, [WsPtr, #VduSprite]
        TEQ     R14, R2                 ; if same as vdu output sprite
        ADREQL  R0, SpriteErr_SpriteIsCurrentDest
      [ International
        BLEQ    TranslateError
      ]
        BEQ     %BT75                   ; then error

 [ No26bitCode
        ADR     R14, %FT95
 |
        ADR     R14, %FT95+SVC_mode ; Enable interrupts on return from PostCreateHeader
 ]
        Push    "R1, R14"
        ADD     R8, WsPtr, #NameBuf
        LDMIA   R8, {R9-R11}            ; load 3 words of name
        ADD     R8, WsPtr, #SGetName
        STMIA   R8, {R9-R11}            ; and store in SGetName

        Push    "R1, R2, R3"            ; save sprite area, sprite, palflag
        BL      PreCreateHeader
        Pull    "R1, R2"                ; restore sprite area ptr + sprite ptr
        BVS     %FT93
                                        ; R4 = total size of sprite
        Push    R4                      ; save new size of sprite
        LDR     R0, [R2, #spNext]
        SUBS    R3, R4, R0              ; compare required size with existing
        MOV     R3, R3, ASR #2          ; no. of words to extend/reduce by
        BEQ     %FT94                   ; [is exactly right already]
        BHI     %FT92                   ; need to extend sprite

; need to reduce sprite

        RSB     R3, R3, #0              ; no. of words to reduce by
        LDR     R4, [R2, #spImage]
        SUB     R4, R0, R4              ; offset from Image to Next
        SUB     R4, R4, R3, LSL #2      ; dest. start as offset from spImage
        BL      RemoveWords
        RSB     R3, R3, #0              ; put R3 back to no. of words to extend
        B       %FT94

; need to extend sprite

92
        BL      ExtendSpriteByR3
93
        ADDVS   R13, R13, #4*4          ; junk size, palflag,
                                        ; sprite area, fake return address
        BVS     %BT80                   ; no room to extend sprite
94
        Pull    R4                      ; restore new size of sprite
        B       PostCreateHeader

; come back to here after PostCreateHeader exits
; R1 -> sprite area, R2 -> sprite, R3 = no. of words to extend by

95
 [ No26bitCode
; Enable interrupts here for 32bit machines
        WritePSRc SVC_mode, R14
 ]
        BL      GetSpriteData
        BL      SelectSprite

; have we made a new format sprite ? if so no left hand wastage is allowed.

        LDR     R3, [R2, #spMode]
        CMP     R3, #256
        BLCS    RemoveLeftHandWastage

        SWI     XOS_RestoreCursors
        Pull    R14
        RETURNVC

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

GetSpriteData ROUT
        Push    "R1-R3, R14"
        SWI     XOS_RemoveCursors
        MOV     R0, R4
        MOV     R1, R5
        BL      ScreenAddr
        MOV     R0, R2                          ; screen addr of TopL of area
        LDMIA   R13, {R4,R5}                    ; R4->sprite area, R5->sprite
        LDR     R1, [WsPtr, #SGetImage]
        ADD     R1, R1, R5                      ; memory address
        LDR     R2, [WsPtr, #SGetWidth]
        ADD     R2, R2, #1                      ; sprite width (words)
        LDR     R3, [WsPtr, #SGetHeight]        ; height
        ADD     R3, R3, #1

        LDR     R8, [WsPtr, #SGetTopMargin]     ; gap above window (rows)
        LDR     R9, [WsPtr, #SGetBotMargin]     ;     below        (rows)
        LDR     R10, [WsPtr, #SGetLWrdMargin]   ;     left of      (words)
        LDR     R11, [WsPtr, #SGetRWrdMargin]   ;     right of     (words)

        SUB     R2, R2, R10
        SUBS    R2, R2, R11             ; number words in window per scanline
        BLEQ    PaintSprite             ; Left or Right of window
        BEQ     %FT60

        SUB     R3, R3, R8
        SUBS    R3, R3, R9              ; number of rows in window
        BLEQ    PaintSprite             ; above or below window
        BEQ     %FT60

        LDR     R14, [WsPtr, #LineLength] ; offset from RHend of row to LHend
        SUB     R14, R14, R2, LSL #2    ; of next row
        STR     R14, [WsPtr, #SGetRowOfst]

        LDR     R11, [WsPtr, #SGetLBitMargin]
        MOV     R5, #&FFFFFFFF
        MOV     R5, R5, LSL R11         ; LmarginMask

        LDR     R11, [WsPtr, #SGetRBitMargin]
        MOV     R6, #&FFFFFFFE
        MVN     R6, R6, LSL R11         ; RmarginMask

        SUBS    R2, R2, #1
        STR     R2, [WsPtr, #SGetColWCnt] ; if only one word in window per row
        ANDEQ   R5, R5, R6              ; then combine L&R masks
        MOVEQ   R6, R5
        STR     R5, [WsPtr, #SGetLBitMargin]
        STR     R6, [WsPtr, #SGetRBitMargin]

        LDR     R5, [WsPtr, #SGetTopMargin]     ; paint TopMargin (if any)
        CMP     R5, #0
        BLNE    PaintBlock

; R0    ,R1    ,R2     ,R3    ,R4      ..       R11
; ScrAdr,MemAdr,ColWCnt,RowCnt,{8 words from screen},

10
        LDR     R4, [WsPtr, #SGetLWrdMargin] ; paint 1 row of LHmargin (if any)
        CMP     R4, #0
        BLNE    PaintRow        ; on exit R6 holds word of BgEcf, if not called
                                ; R6 is corrupt, but it doesn't matter
        LDR     R2, [WsPtr, #SGetColWCnt] ; on screen word count ( >0 in words)
        LDR     R5, [WsPtr, #SGetLBitMargin]
        LDR     R4, [R0], #4    ; get first on screen word
        AND     R4, R4, R5
        BIC     R6, R6, R5      ; Write BgEcf (or nonsense) to out of window
        ORR     R4, R4, R6      ; pixels
        STR     R4, [R1], #4
        SUBS    R2, R2, #1
        BLT     %FT50           ; if all plotted

        SUBS    R2, R2, #8      ; try for 8 words
20
        LDMCSIA R0!, {R4-R11}   ; copy 8 words from screen to memory
        STMCSIA R1!, {R4-R11}
        SUBCSS  R2, R2, #8
        BCS     %BT20
30
        ADDS    R2, R2, #8
        LDR     R6, [WsPtr,#SGetEcfIndx]
        ADD     R6, WsPtr, R6, LSL #2
        LDR     R6, [R6, #BgEcf]        ; BgEcf for this scanline
        LDR     R5, [WsPtr,#SGetRBitMargin]
        BIC     R6, R6, R5
40
        LDR     R4, [R0], #4
        ANDEQ   R4, R4, R5
        ORREQ   R4, R4, R6
        STR     R4, [R1], #4
        SUBS    R2, R2, #1
        BCS     %BT40
50
        LDR     R4, [WsPtr, #SGetRWrdMargin]
        CMP     R4, #0
        BLNE    PaintRow

        LDR     R2, [WsPtr, #SGetColWCnt]
        LDR     R4, [WsPtr, #SGetRowOfst]
        LDR     R5, [WsPtr,#SGetEcfIndx]
        ADD     R0, R0, R4                      ; offset ScrAdr to next row
        ADD     R5, R5, #1
        AND     R5, R5, #7
        STR     R5, [WsPtr, #SGetEcfIndx]       ; update EcfIndx to next row
        SUBS    R3, R3, #1
        BGT     %BT10                           ; do next screen line

        LDR     R5, [WsPtr, #SGetBotMargin]     ; paint bottom margin (if any)
        CMP     R5, #0
        BLNE    PaintBlock
60
        Pull    "R1-R3, PC"


; *****************************************************************************
;
;       PaintSprite - Paint the whole of the sprite in background colour
;
;       Internal routine, called by GetSprite when all area is outside window
;
; in:   R1 -> first byte in sprite
;

PaintSprite ROUT
        LDR     R5, [WsPtr, #SGetHeight]
        ADD     R5, R5, #1              ; R5 = number of rows in sprite

; and drop thru to ...

; *****************************************************************************
;
;       PaintBlock - Paint a number of rows of the sprite in background colour
;
;       Internal routine, called by GetSprite to do area above and below window
;        and dropped thru to by PaintSprite
;
; in:   R1 -> start of first row to paint
;       R5 = number of rows to do
;
; out:  Flags preserved

PaintBlock ROUT
 [ No26bitCode
        MRS     R4, CPSR
        Push    "R4,R14"
 |
        Push    R14
 ]
        LDR     R4, [WsPtr, #SGetWidth]
        ADD     R4, R4, #1
10
        BL      PaintRow
        LDR     R6, [WsPtr, #SGetEcfIndx]
        ADD     R6, R6, #1
        AND     R6, R6, #7
        STR     R6, [WsPtr, #SGetEcfIndx]
        SUBS    R5, R5, #1
        BNE     %BT10

 [ No26bitCode
        Pull    "R4,R14"
        MSR     CPSR_f, R4
        MOV     PC,R14
 |
        Pull    PC,,^                 ; we must preserve the flags
 ]

; *****************************************************************************
;
;       PaintRow - Paint part of a row in sprite with background colour
;
;       Internal routine, called by GetSprite to do areas left+right of window
;        and by PaintBlock
;
; in:   R1 -> first word to paint
;       R4 = number of words to paint
;
; out:  R4 preserved
;

PaintRow ROUT
        Push    R4
        LDR     R6, [WsPtr, #SGetEcfIndx]
        ADD     R6, WsPtr, R6, LSL #2
        LDR     R6, [R6, #BgEcf]        ; BgEcf for this scanline
10
        STR     R6, [R1], #4
        SUBS    R4, R4, #1
        BNE     %BT10
        Pull    R4
        MOV     PC, R14

; *****************************************************************************
;
;       CreateSprite - Create a sprite with given attributes
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite name
;       R3 = 0/1 => exclude/include palette data
;       R4 = width in pixels
;       R5 = height in pixels
;       R6 = mode number of sprite
;

CreateSprite ROUT
        Push    R14
        KillSpChoosePtr
        BL      DeleteSpriteByName      ; delete any existing sprite
        STR     R6, [WsPtr, #SGetMode]  ; needs setting up before CreateHeader
        SUB     R6, R4, #1              ; width in pixels-1
        SUB     R7, R5, #1              ; height-1
        MOV     R4, #0
        MOV     R5, #0
                                        ;      R3     ,R4,R5,R6,R7
        BL      CreateHeader            ; In : Palette,sl,sb,sr,st
                                        ; Out: ImageSize
        Pull    PC, VS                  ; if error, then bomb out

        MOV     R4, #0                  ; clear R3 words at offset 0 in sprite
        BL      ClearWords              ; ie clear image to 0

 ; Now add the sprite to the sprite area

        LDR     R3, [R2, #spNext]       ; total size of new sprite
        LDMIA   R1, {R4-R7}             ; saEnd,saNumber,saFirst,saFree
        ADD     R5, R5, #1
        ADD     R7, R7, R3
        STMIA   R1, {R4-R7}

        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       CreateHeader - Create a header and info for a sprite
;
;       Internal routine, called by GetSprite, CreateSprite, ScreenSave
;
; in:   R1 -> sprite area
;       R2 -> sprite name
;       R3 = 0/1 => exclude/include palette data
;       R4,R5 = (X,Y) INTERNAL coordinate of bottom left
;       R6,R7 = (X,Y) INTERNAL coordinate of top right
;
; out:  R1 preserved
;       R2 -> new sprite
;       R3 = size of image in words
;       R4,R5 = (X,Y) INTERNAL coordinate of top left of on screen area
;       R0, R6-R11 corrupted
;

CreateHeader ROUT
        Push    "R1, R14"
        Push    R3

        BL      GetName                 ; name returned in R9-R11
        ADD     R8, WsPtr, #SGetName
        STMIA   R8, {R9-R11}            ; save the name away

        BL      PreCreateHeader

        Pull    "R0, R1, PC", VS

; now the updating the sprite area bit

        LDR     R1, [R13, #1*4]         ; reload sprite area ptr off stack
        LDR     R2, [R1, #saFree]
        LDR     R5, [R1, #saEnd]
        SUB     R5, R5, R2
        CMP     R5, R4
        BCC     %FT10

        ADD     R2, R2, R1              ; address of new sprite

PostCreateHeader
        ADD     R5, WsPtr, #SGetName
        LDMIA   R5, {R5-R11}

; R4 spNext, R5-R7 spName(0..2),
; R8 spWidth, R9 spHeight, R10 spLBit, R11 spRBit

        STMIA   R2, {R4-R11}            ; write control block for sprite
        LDR     R11, [WsPtr, #SGetImage]
        STR     R11, [R2, #spImage]
        STR     R11, [R2, #spTrans]     ; spImage=spTrans ie no mask

        LDR     R11, [WsPtr, #SGetMode]

        STR     R11, [R2, #spMode]
        ANDS    LR, R11, #15<<27        ; do we have an old or new sprite ?
        BEQ     %FT09
        TEQ     LR, #SpriteType_RISCOS5<<27 ; RISC OS 5 type?
      [ NoARMT2
        ANDEQ   LR, R11, #127<<20
        MOVEQ   LR, LR, LSR #20
      |
        UBFXEQ  LR, R11, #20, #7
      ]
        MOVNE   LR, LR, LSR #27

09
        ADD     R4, WsPtr, #SGetTopLeft
        LDMIA   R4, {R4, R5}            ; (R4,R5) = TopLeft of 'on screen' area
        Pull    R11                     ; R11 = 0/1 for (ex/in)clude palette

;amg 25th May 1994. We now allow palettes on new format sprites in 8bpp and below

        CMP     LR,#SpriteType_New16bpp
        BCS     %FT11                   ; check for new 16/32 bpp

        TEQ     R11,#0                  ; was a palette wanted in the first place?
        BLNE    WritePaletteToSprite    ; do it if so

;        ;only allow palette data to be written if EQ and R11<>0
;
;        BNE     %FT11
;
;        TEQ     R11, #0
;        BLNE    WritePaletteToSprite

11
        Pull    "R1, R14"
        RETURNVC

10
        ADRL    R0, SpriteErr_NoRoom
      [ International
        BL      TranslateError
      ]
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0, R1, R14"           ; junk palflag, sprite area ptr
        RETURNVS

; *****************************************************************************
;
;       SanitizeSGetMode - Convert SGetMode into a new format sprite word if necessary
;
;       If SGetMode is either   a) a mode selector pointer, or
;                               b) a mode number which has more than 8bpp
;       then SGetMode is replaced by a suitable sprite mode word
;
; amg: 15/10/93: changed to be more keen to generate old format mode numbers. It is
;                now also called from createsprite, so it will pass through a new
;                sprite mode word unchanged. Mode numbers will be unchanged. Mode
;                selectors will be changed to a mode number if one of suitable
;                eigs and depth exists --- size of screen is *not* taken into
;                account here.


; in:   WsPtr -> VDU workspace
;
; out:  If OK, then
;         V=0
;         All registers preserved
;       else
;         V=1
;         r0 -> error
;         RetnReg0 -> error
;       endif
;

SanitizeSGetMode Entry "r0-r4,r11"
        LDR     r11, [WsPtr, #SGetMode]

        CMP     r11, #&100
        BCC     %FT20                   ; [not a mode selector or new format sprite word]

        TST     r11, #1                 ; is it already a new format sprite word?
        EXIT    NE
10
        MOV     r0, r11                 ; r0 -> mode selector
 [ ModeSelectors
        BL      ValidateModeSelector
        STRVS   r0, [sp]
        STRVS   r0, [WsPtr, #RetnReg0]
        EXIT    VS
 ]

15
; convert to new format sprite word

        MOV     r4, r11                 ; preserve the mode for later

        ;amg: add check for log2bpp=log2bpc

        MOV     r0, r4
        MOV     r1, #VduExt_Log2BPC
        SWI     XOS_ReadModeVariable
        MOV     R3,R2

        MOV     r0, r4
        MOV     r1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable

        CMP     R3, R2
        BNE     %FT90

        MOV     r0, r4
        MOV     r1, #VduExt_ModeFlags
        SWI     XOS_ReadModeVariable
        MOV     r3, r2

        MOV     r0, r4
        MOV     r1, #VduExt_NColour
        SWI     XOS_ReadModeVariable

; work out the sprite type. Note: Only dealing with RGB colour space here!
        ADDS    r2, r2, #1
        MOVEQ   r11, #SpriteType_New32bpp
        BEQ     %FT16
        CMP     r2, #1<<24
        MOVEQ   r11, #SpriteType_New24bpp
        BEQ     %FT16
        CMP     r2, #1<<12
        MOVEQ   r11, #SpriteType_New4K
        BEQ     %FT16
        CMP     r2, #1<<1
        MOVEQ   r11, #SpriteType_New1bpp
        BEQ     %FT16
        CMP     r2, #1<<2
        MOVEQ   r11, #SpriteType_New2bpp
        BEQ     %FT16
        CMP     r2, #1<<4
        MOVEQ   r11, #SpriteType_New4bpp
        BEQ     %FT16
        CMP     r2, #1<<8
        CMPNE   r2, #1<<6
        MOVEQ   r11, #SpriteType_New8bpp
        BEQ     %FT16
        CMP     r2, #1<<16
        BNE     %FT90
        ; Could be 565 or 1555
        TST     r3, #ModeFlag_64k
        MOVEQ   r11, #SpriteType_New16bpp
        MOVNE   r11, #SpriteType_New64K
16
; work out whether we need a RISC OS 5 mode word or not
        ANDS    r3, r3, #ModeFlag_DataFormat_Mask ; Any relevant modeflags set?
        TSTEQ   r11, #&F0                         ; Type too big for 4 bits?
        BEQ     %FT17

        ; RISC OS 5 style sprite mode word
        ORR     r11, r3, r11, LSL #20   ; sprite type plus mode flags
        ORR     r11, r11, #1 + (SpriteType_RISCOS5<<27)

        MOV     r1, #VduExt_XEigFactor
        SWI     XOS_ReadModeVariable
        ORR     r11, r11, r2, LSL #4    ; put xdpi into position

        MOV     r1, #VduExt_YEigFactor
        SWI     XOS_ReadModeVariable
        ORR     r11, r11, r2, LSL #6    ; put ydpi into position

        STR     r11, [WsPtr, #SGetMode] ; store new value
        EXIT

17
        ; "New"-style sprite mode word
        MOV     r11, r11, LSL #27
        ORR     r11, r11, #1

        MOV     r1, #VduExt_XEigFactor
        SWI     XOS_ReadModeVariable

        MOV     lr, #180
        MOV     lr, lr, LSR r2          ; cope with 45, 90, 180 dpi

        ORR     r11, r11, lr, LSL #1    ; put into xdpi position

        MOV     r1, #VduExt_YEigFactor
        SWI     XOS_ReadModeVariable

        MOV     lr, #180
        MOV     lr, lr, LSR r2
        ORR     r11, r11, lr, LSL #14   ; put into ydpi position

        STR     r11, [WsPtr, #SGetMode] ; store new value

        ;now check if we can force it back to a mode number

        ;if the bpp is > 8 the answer is no
        AND     r2, r11, #15<<27
        CMP     r2, #SpriteType_New16bpp
        EXIT    CS

        BIC     r0, r11, #&F8000000     ; take off the type information
        ADR     r1, substitute_list
        ADD     r2, r1, #12             ; end of list
27
        LDR     r3, [r1], #4
        TEQ     r3, r0
        BEQ     %FT28
        TEQ     r1, r2
        EXIT    EQ                      ; can't do anything with it
        BNE     %BT27
28
        ADD     r1, r1, #8              ; point at modes word, allowing for post inc
        ADD     r1, r1, r11, LSR #27    ; add in the sprite's type
        SUB     r1, r1, #1              ; and reduce it by one
        LDRB    r1, [r1]                ; fetch the right mode number

        ;if we got 255, we can't save the day
        CMP     r1,#255
        STRNE   r1, [WsPtr, #SGetMode]  ; and store it

        EXIT

substitute_list
        DCD     &001680B5               ;90 X 90 DPI, X/Y EIG 1 1
        DCD     &000B40B5               ;90 X 45 DPI, X/Y EIG 1 2
        DCD     &000B405B               ;45 X 45 DPI, X/Y EIG 2 2

        ;amg: used to use mode 4 for 2 colour eig 2 x 2 - now doesn't because of
        ;confusion about double pixels

        DCD     &1C1B1A19               ;modes  25, 26, 27, 28 for 90 x 90
        DCD     &0F0C0800               ;modes   0,  8, 12, 15 for 90 x 45
        DCD     &0D0901FF               ;modes n/a,  1,  9, 13 for 45 x 45
20
        MOV     r0, r11                 ; check if bpp for mode is > 8
        MOV     r1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        CMP     r2, #4
        BCS     %BT15                   ; if so then convert to new format sprite as well
        EXIT

90
        ADRL    R0, ErrorBlock_BadMODE
      [ International
        BL      TranslateError
      ]
        STR     r0, [sp]
        STR     r0, [WsPtr, #RetnReg0]
        SETV
        EXIT



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

PreCreateHeader ROUT
        Push    R14
        BL      SanitizeSGetMode        ; convert SGetMode to new format sprite if nec.
        Pull    PC, VS                  ; duff mode selector

        ;amg 25th May 1994
        ;We now allow palettes on new format sprites of 8bpp and below

;        ;force to no palette space if a new format sprite
;        LDR     LR, [WsPtr, #SGetMode]  ; get the mode
;        MOVS    LR, LR, LSR #27         ; set NE if new
;        MOVNE   R3, #0                  ; turn off palette

        LDR     LR, [WsPtr, #SGetMode]  ; get the sprite mode word
        BIC     LR, LR, #&80000000      ; ignore alpha mask flag
        CMP     LR, #SpriteType_RISCOS5<<27
        MOVLO   LR, LR, LSR #7          ; shift sprite type to its RISC OS 5 location
        ANDHS   LR, LR, #127<<20        ; or get RISC OS 5 type as-is
        CMP     LR, #SpriteType_New16bpp<<20 ; check for true colour
        MOVHS   R3, #0                  ; turn off the palette request

        TEQ     R3, #0                  ; convert R3 into mask to (ex/in)clude
        MOVNE   R3, #&FF                ; space for palette data

; amg need more palette space in case it's going to be a full palette
        ORRNE   R3, R3, #&300

        Push    "R6, R7"                ; preserve R6,R7 over the call
        ADD     R2, WsPtr, #SGetNext
        BL      SetupSprModeData

;      R6    ,R7    ,R8    ,R9   ,R10 ,R11
; Out: RdNCol,WrNCol,BytePC,XShft,NPix,Log2BPC

; amg 26th October 1993 - kill another bit of Arthur compatibility in favour
; of full palette 8bpp sprites
;        AND     R6, R6, #63     ; make 64 palette entries like MOS 1.2

        LDR     R7,[WsPtr,#ModeFlags]
        TST     R7, #ModeFlag_FullPalette
        ANDEQ   R6, R6, #63

        ADD     R6, R6, #1      ; number of palette entries in this mode
        AND     R3, R3, R6      ; if (palette not wanted) OR (256 colour mode)
                                ; then R3=0 else R3=number of palette entries
                                ; N.B. in 256 colour modes we end up ignoring
                                ;      the palette
        Pull    "R6,R7"
        Pull    PC, VS          ; error, not a graphics mode

        MOV     R3, R3, LSL #3  ; two words per palette entry
        ADD     R3, R3, #SpriteCBsize
        STR     R3, [WsPtr, #SGetImage] ; R0-R3 now free for use
                                        ; R4 ,R5, R6 ,R7
                                        ; sL ,sB, sR ,sT
        SUB     R0, R7, R5              ; height-1
        STR     R0, [WsPtr, #SGetHeight]
        ADD     R0, R0, #1              ; actual height in rows

        LDR     R1, [WsPtr, #GWTRow]    ; if SpriteTopRow > GWTopRow
        SUBS    R2, R7, R1
        MOVGT   R7, R1                  ; then clip for ScreenAddr's benefit
        MOVLE   R2, #0
        Least   R2, R2, R0
        STR     R2, [WsPtr, #SGetTopMargin] ; number of blank rows at top

        LDR     R1, [WsPtr, #GWBRow]
        SUBS    R2, R1, R5
        MOVLT   R2, #0
        Least   R2, R2, R0
        STR     R2, [WsPtr, #SGetBotMargin] ; number of blank rows at bottom

        WordOffset R0,R4, R9,R10,R11    ; offset to sL
        WordOffset R1,R6, R9,R10,R11    ;       to sR
        SUB     R2, R1, R0              ; width-1
        STR     R2, [WsPtr, #SGetWidth]
        ADD     R2, R2, #1              ; actual width in words

        BitLOffset R3,R4, R9,R10,R11    ; LBit
        STR     R3, [WsPtr, #SGetLBit]
        BitROffset R3,R6, R9,R10,R11    ; RBit
        STR     R3, [WsPtr, #SGetRBit]

        LDR     R8, [WsPtr, #GWLCol]
        Greatest R4,R4,R8
        WordOffset R3,R4, R9,R10,R11    ; offset to clipL
        SUB     R3, R3, R0
        Least   R3,R3,R2
        STR     R3, [WsPtr, #SGetLWrdMargin]    ; no. of blank words at left
        BitLOffset R3,R4, R9,R10,R11
        STR     R3, [WsPtr, #SGetLBitMargin]    ; no. of blank words at right

        LDR     R8, [WsPtr, #GWRCol]
        Least   R6, R6, R8
        WordOffset R3,R6, R9,R10,R11    ; offset to clipR
        SUB     R3, R1, R3
        Least   R3, R3, R2
        STR     R3, [WsPtr, #SGetRWrdMargin]
        BitROffset R3,R6, R9,R10,R11
        STR     R3, [WsPtr, #SGetRBitMargin]

        ADD     R0, WsPtr, #SGetTopLeft
        STMIA   R0, {R4, R7}            ; store top & left of 'on screen' area
        LDR     R0, [WsPtr, #SGetWidth]
        LDR     R1, [WsPtr, #SGetHeight]
        ADD     R0, R0, #1              ; width in words
        ADD     R1, R1, #1              ; height in words

        MUL     R3, R1, R0              ; image size in words
        LDR     R4, [WsPtr, #SGetImage]
        ADD     R4, R4, R3, LSL #2      ; total size in bytes

        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       Decide mask size
;
;       Internal routine called from CreateMask
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 =  size of image data (bytes)
;
; out:  R3 = size of mask data (words)

DecideMaskSize ROUT
        Entry   "R5,R8"

        LDR     LR, [R2, #spMode]       ; get the sprite mode
        MOVS    LR, LR, LSR #27         ; isolate the type

        MOVEQ   R3,R3,LSR #2            ; if T=0 then return the same size as
        EXIT    EQ                      ; the image (but returns in words not
                                        ; bytes)

        LDR     R5, [R2, #spWidth]
        BL      GetMaskspWidth          ; get mask width information
        LDR     LR, [R2, #spHeight]     ; number of rows (minus 1)
        ADD     R5, R5, #1              ; width in words
        ADD     LR, LR, #1              ; height in rows
        MUL     R3, LR, R5              ; number of words for the mask

        EXIT

; *****************************************************************************
;
;       CreateMask - Add mask to sprite or set existing mask to 'solid'
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;

CreateMask ROUT
        Push    R14
        KillSpChoosePtr
        LDR     R4, [R2, #spNext]
        LDR     R5, [R2, #spImage]      ; NB Image=Trans if NO mask
        LDR     R6, [R2, #spTrans]
        SUB     R3, R4, R6

        BL      DecideMaskSize          ; returns R3=size of mask (words)

        TEQ     R5, R6
        BNE     %FT10                   ; mask exists

        MOV     R6, R4
        BL      ExtendSprite
        ADRVSL  R0, SpriteErr_NotEnoughRoom ; only error is NoRoomToInsert
      [ International
        BLVS    TranslateError
      ]
        STRVS   R0, [WsPtr, #RetnReg0]  ; correct this to 'Not enough room'
        Pull    PC, VS

        STR     R6, [R2, #spTrans]      ; new spTrans := old spNext
10                                      ; R3 mask size (words), R6 spTrans
        ADD     R6, R6, R2
        MOV     R4, #&FFFFFFFF
20
        STR     R4, [R6], #4
        SUBS    R3, R3, #1
        BNE     %BT20

        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       RemoveMask - Remove mask from sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;

RemoveMask ROUT
        Push    R14
        KillSpChoosePtr
        LDR     R4, [R2, #spNext]
        LDR     R5, [R2, #spImage]      ; NB spTrans = spImage, if NO mask
        LDR     R6, [R2, #spTrans]
        TEQ     R5, R6
        BEQ     %FT10                   ; no mask so ignore

        SUB     R3, R4, R6

        BL      DecideMaskSize          ; returns R3=size in words

        SUB     R4, R6, R5
        BL      RemoveWords
        LDR     R5, [R2, #spImage]      ; spTrans := spImage, ie NO mask
        STR     R5, [R2, #spTrans]
10
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       WritePaletteToSprite - Write palette information into sprite CB
;
;       Internal routine, called by CreateHeader
;
; in:   R2 -> sprite
;
; out:  All registers preserved
;

WritePaletteToSprite ROUT
        Push    "R0-R4, R14"
        LDR     R0, [WsPtr, #SprReadNColour]    ; highest palette entry

; amg 26th October 1993 - this bit of Arthur compatibility bites the
; dust to make screensaving full palette sprites work properly
;        AND     R0, R0, #63             ; make 63 if 255 like MOS 1.20
        LDR     R4, [WsPtr,#ModeFlags]
        TST     R4, #ModeFlag_FullPalette
        ANDEQ   R0, R0, #63

        ADD     R4, R2, R0, LSL #3
        ADD     R4, R4, #spPalette      ; ptr to last pal pos in spPalette
10
        MOV     R1, #16                 ; read 'normal' colour
        SWI     XOS_ReadPalette
        STMIA   R4, {R2,R3}
        SUB     R4, R4, #8
        SUBS    R0, R0, #1
        BCS     %BT10
        Pull    "R0-R4,PC"

; *****************************************************************************
;
;       WritePaletteFromSprite - Write palette from information in sprite CB
;
;       Internal routine, called by ScreenLoad
;
; in:   R2 -> sprite
;
; out:  All registers preserved
;

WritePaletteFromSprite ROUT
        Push    "R0-R6, R14"
        LDR     R0, [WsPtr, #ModeNo]
        LDR     R1, [R2, #spMode]

        [ {TRUE} :LAND: ModeSelectors

        ;logic for this routine
        ;
        ;[WsPtr, #ModeNo] is the current mode/ptr to mode selector
        ;R2 points at sprite data
        ;[WsPtr, #SloadModeSel] is 36 bytes for building a mode selector for the sprite
        ;
        ;sprite mode < 256 ?
        ;yes: equal to current mode ?
        ;     yes: already in correct mode. done.
        ;     no:  change to mode sprite wants, done.
        ;no:  build a mode selector for the sprite
        ;     check pixel depth, xres, yres, xdpi and ydpi
        ;     all identical ?
        ;     yes: already in suitable mode. done.
        ;     no:  change mode. done.
        ;if we do a mode change, remember to re-remove cursors

        ;amg 15 Oct '93 Screensave is about to be changed to use a representative
        ;mode number of the eigs and depth (only), so screenload no longer believes
        ;the screen mode number in the file.

        ;amg 21 Dec '93 Slight modification - if the screen mode change failed, and
        ;we have an old screen mode number, use that as a last gasp

;        CMP     R1, #256
;        BCS     %FT30                   ;branch if a new format sprite mode word

;        CMP     R1, R0                  ;are we in the right (old style) mode ?
;        BEQ     %FT10

;        MOV     R0,#ScreenModeReason_SelectMode
;        SWI     XOS_ScreenMode
;        STRVS   R0, [WsPtr, #RetnReg0]  ;exit on error
;        Pull    "R0-R6,PC", VS
;        B       %FT40                   ;otherwise get on with it

30      ; new format sprite mode word
        ; build the mode selector at SLoadModeSel

        MOV     R5, R1                  ;keep the mode number/sprite mode word safe

        ;do the absolutes first
        MOV     R3, #-1
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_FrameRate]
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+16] ;list terminator after two pairs
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+32] ;list terminator after four pairs
        MOV     R3, #128
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+20]  ;modeflags value, if needed
        MOV     R3, #VduExt_NColour
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+24]
        MOV     R3, #255
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+28]


        MOV     R3, #1
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_Flags]
        MOV     R3, #VduExt_XEigFactor
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars]    ; modevar 1 = xeig
        MOV     R3, #VduExt_YEigFactor
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+8]  ; modevar 2 = Yeig

        ;now the things from the sprite
;        MOV     R3, R1, LSR #27         ;sprite type
;        ADRL    R4, NSM_bpptable-4      ;readmodevar's table
;        LDR     R3, [R4, R3, LSL #2]    ;word index
;        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_PixelDepth]

        ;change to calling read mode variable to cope with mode number or sprite mode word
        MOV     R4, R2                  ;save the sprite pointer
        MOV     R0, R5                  ;sprite mode word/mode number
        MOV     R1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        STR     R2, [WsPtr, #SloadModeSel+ModeSelector_PixelDepth]
        MOV     R3, R2

        ;if log2bpp=3, and size of palette data indicates full palette, we need to force
        ;a suitable mode
        CMP     R3, #3
        BNE     %FT40

        ADD     LR, R4, #spImage        ;point to image/mask start
        LDMIA   LR,{R2,LR}              ;fetch them
        CMP     R2,LR                   ;which is bigger ?
        MOVGT   R2,LR                   ;use the least
        SUB     R2,R2,#spPalette        ;and the palette size is...

        CMP     R2,#&800                ;full entry 256 colour

        ;change the mode selector so it includes a modeflags word
        ;(following two words already set up)

        MOVEQ   R2, #0
        ;amg 28/4/94 bugfix - following inst wasn't conditional
        STREQ   R2, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+16]

40
        MOV     R2, R4                  ;restore the sprite pointer

        LDR     R4, [R2, #spWidth]      ;number of words
        MOV     R4, R4, LSL #5          ;convert to a number of bits
        LDR     LR, [R2, #spRBit]       ;last bit used
        ADD     LR, LR, #1              ;convert to number of bits
        ADD     R4, R4, LR              ;combine
        MOV     R4, R4, LSR R3          ;and convert to pixels
        STR     R4, [WsPtr, #SloadModeSel+ModeSelector_XRes]

        LDR     R3, [R2, #spHeight]
        ADD     R3, R3, #1
        STR     R3, [WsPtr, #SloadModeSel+ModeSelector_YRes]
        MOV     R6, R2                  ;save the sprite pointer for later

        ;that leaves the x and y eig factors, which are derived
        ;from the dpi

        MOV     R0, R5                  ;R0 = sprite mode word
        MOV     R1, #VduExt_XEigFactor
        SWI     XOS_ReadModeVariable
        STRCC   R2, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+4]
        MOVCC   R1, #VduExt_YEigFactor
        SWICC   XOS_ReadModeVariable
        STRCC   R2, [WsPtr, #SloadModeSel+ModeSelector_ModeVars+12]
        BCS     %FT90                   ;we canna take it captain....

        ;do the comparison which involve the mode selectors first

        ;depth
        LDR     LR, [WsPtr, #ModeNo]

        LDR     R3, [LR, #ModeSelector_PixelDepth]
        LDR     R4, [WsPtr, #SloadModeSel+ModeSelector_PixelDepth]
        TEQ     R3, R4
        BNE     %FT80                   ;need to change mode to new mode selr

        LDR     R3, [LR, #ModeSelector_XRes]
        LDR     R4, [WsPtr, #SloadModeSel+ModeSelector_XRes]
        TEQ     R3, R4
        BNE     %FT80                   ;need to change mode to new mode selr

        LDR     R3, [LR, #ModeSelector_YRes]
        LDR     R4, [WsPtr, #SloadModeSel+ModeSelector_YRes]
        TEQ     R3, R4
        BNE     %FT80                   ;need to change mode to new mode selr

        ;now the eigs
        LDR     R3, [WsPtr, #XEigFactor]
        LDR     R4, [WsPtr, #SloadModeSel+ModeSelector_Flags+4]
        TEQ     R3, R4
        BNE     %FT80                   ;need to change mode to new mode selr

        LDR     R3, [WsPtr, #YEigFactor]
        LDR     R4, [WsPtr, #SloadModeSel+ModeSelector_Flags+12]
        TEQ     R3, R4

        BEQ     %FT10                   ;this mode is suitable

80
        MOV     R0,#ScreenModeReason_SelectMode
        ADD     R1,WsPtr,#SloadModeSel
        SWI     XOS_ScreenMode


        [ {TRUE}
        ;ensure we preserve the error pointer in situations where we can't try to
        ;fall back to the mode number in the sprite header

        ;if it errored try again if there's a mode number available
        BVC     %FT40

        LDR     R1, [R6, #spMode]
        BICS    R14, R1, #&FF           ; EQ if sprite mode is a number (< 256), (V still set afterwards)
        MOVEQ   R0, #ScreenModeReason_SelectMode
        SWIEQ   XOS_ScreenMode          ; if called, will set V appropriately
        |
        ;if it errored try again if there's a mode number available
        BVC     %FT40

        MOV     R0, #ScreenModeReason_SelectMode
        LDR     R1, [R6, #spMode]
        BICS    R14, R1, #&FF           ; EQ if sprite mode is a number (< 256), (V still set afterwards)
        SWIEQ   XOS_ScreenMode          ; if called, will set V appropriately
        ]

        STRVS   R0, [WsPtr, #RetnReg0]  ;exit on error
        Pull    "R0-R6,PC", VS
        B       %FT40                   ;otherwise get on with it
90
        ADRL    R0,SpriteErr_InvalidSpriteMode
      [ International
        BL      TranslateError
      |
        SETV
      ]
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0-R6,PC"
        |

        ;as originally done this code tended to compare mode specifiers against new
        ;sprite mode words, and worse still tried to select a mode from a new sprite
        ;mode word. the rewrite above takes a more logical approach

        CMP     R0, R1                  ; if already in correct mode
        BEQ     %FT10                   ; then skip

 [ ModeSelectors
        MOV     r0, #ScreenModeReason_SelectMode
        SWI     XOS_ScreenMode
 |
        MOV     R0, #22
        SWI     XOS_WriteC
        MOVVC   R0, R1
        SWIVC   XOS_WriteC
 ]
        STRVS   R0, [WsPtr, #RetnReg0]
        Pull    "R0-R6,PC", VS
        ]
40
        SWI     XOS_RemoveCursors       ; remove cursors again
10
        MOV     R2, R6
        LDR     R3, [R2, #spImage]
        CMP     R3, #spPalette          ; will clear V if EQ
        Pull    "R0-R6, PC", EQ         ; no palette data

        LDR     R4, [WsPtr, #NColour]

        ADD     R3, R2, #spPalette
        ADD     R3, R3, R4, LSL #3
20
        LDMIA   R3, {R1,R2}
        MOV     R0, R4

        BL      SendPalettePair
        Pull    "R0-R6, PC", VS

        SUB     R3, R3, #8
        SUBS    R4, R4, #1              ; (V will be cleared by this)
        BCS     %BT20
        Pull    "R0-R6, PC"

; *****************************************************************************
;
;       SendPalettePair - Program palette with flash pair
;
;       Internal routine, called by WritePaletteFromSprite
;
; in:   R0 = logical colour
;       R1 = first flash colour
;       R2 = second flash colour
;
; out:  R1 corrupted
;

SendPalettePair ROUT
        Push    "R0-R3, R14"
        TEQ     R1, R2                  ; are colours the same ?
        BNE     %FT10                   ; if not then do in two halves

        MOV     R3, #16
        BL      SendPaletteEntry        ; then send with 16
        Pull    "R0-R3, PC"
10
        MOV     R3, #17                 ; else send 1st flash with 17
        BL      SendPaletteEntry
        MOVVC   R1, R2
        MOVVC   R3, #18                 ; then 2nd flash with 18
        BLVC    SendPaletteEntry
        Pull    "R0-R3, PC"

; *****************************************************************************
;
;       SendPaletteEntry - Program one palette entry
;
;       Internal routine, called by SendPalettePair
;
; in:   R0 = logical colour
;       R1 = physical colour BGRx
;       R3 = PP field to use
;
; out:  All registers preserved
;

SendPaletteEntry ROUT
        Push    "R0,R1, R14"
        BIC     R1, R1, #&7F            ; clear all bits except sup. bit
        ORR     R1, R1, R3              ; or in new bits
        MOV     R0, R0, LSL #24         ; move log. col. up to top 8 bits
        Push    "R0, R1"                ; create an OSWORD block at R13+3

        MOV     R0, #12
        ADD     R1, R13, #3             ; R1 -> block
        SWI     XOS_Word
        STRVS   R0, [WsPtr, #RetnReg0]
        ADD     R13, R13, #8
        Pull    "R0,R1, PC"

        END