; 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.VduGrafH
;
; ARTHUR OPERATING SYSTEM - Vdu Drivers
; =======================
;
; Vdu driver code - Sprite stuff
;
; Authors   RManby, TDobson
; Started   10.11.86

; ************************************************************
; ***    C h a n g e   L i s t  (better late than never!)  ***
; ************************************************************

; Date       Description
; ----       -----------
; 17-Feb-88  Created change list
;            Fixed bug in SLOAD (errors weren't reported)
; 08-Apr-88  Changed LoadFile to use open with nodir + mustopen
; 20-May-88  Changed NoRoomToLoad to NotEnoughRoom

; *****************************************************************************
;
;       MergeSpriteFile - Merge sprite file from filing system
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> filename
;

MergeSpriteFile ROUT
        Push    R14
        KillSpChoosePtr
        LDR     R3, [R1, #saFree]
        ADD     R3, R3, R1
        BL      LoadFile        ; R1 -> sprite area, R2 -> filename
        Pull    PC, VS          ; R3 -> free space
        SUB     R2, R3, #4
        BL      MergeSpriteAreas ; in: R1 -> destination, R2 -> source, V=0
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       LoadSpriteFile - Load sprite file from filing system
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> filename
;

LoadSpriteFile ROUT
        KillSpChoosePtr
        ADD     R3, R1, #saNumber       ; R3 = load address

; and drop thru to LoadFile (V will be set/cleared by that routine)

; *****************************************************************************
;
;       LoadFile - Load sprite file to particular address
;
;       Internal routine, called by LoadSpriteFile, MergeSpriteFile
;
; in:   R1 -> sprite area
;       R2 -> filename
;       R3 = load address
;
; out:  VS => error during load, R0 -> error
;       VC => loaded OK, R0 undefined
;       R1-R11 preserved
;

LoadFile ROUT
        Push    "R1-R6, R14"

 ; new version (07-Dec-89) to help broadcast loader a bit

        Push    "R2,R3"                 ; filename ptr, load address
        LDR     R6, [R1, #saEnd]
        ADD     R6, R6, R1
        SUB     R6, R6, R3              ; free space = saEnd-LoadAddress

        MOV     R0, #OSFile_ReadInfo
        MOV     R1, R2                  ; filename ptr
        SWI     XOS_File                ; OSFILE(5), returns R0 type, R4 length
        BVS     %FT10                   ; R0 file type, R4 length

        TEQ     R0, #object_file        ; if not a file
        BNE     %FT08                   ; then make into suitable error

        CMP     R6, R4                  ; will file fit in available space ?
        ADRCCL  R0, SpriteErr_NotEnoughRoom
      [ International
        BLCC    TranslateError
      ]
        BCC     %FT10

; There is room to load file, so load the lot

        MOV     R0, #OSFile_Load
        Pull    "R1, R2"                ; filename ptr, load address
        MOV     R3, #0                  ; use given address
        SWI     XOS_File
        BVS     %FT20

; TMD 07-Oct-91 (G-RO-9262)
; New code inserted here
; Check validity of sprite file
; R4 = file length from XOS_File above

; TMD 06-Mar-92 (RP-0589)
; Validity check weakened to only check that
; offset to first sprite is < length of file

        LDR     R3, [sp, #2*4]          ; R3 = load address
        LDR     R1, [R3, #saFirst-4]    ; offset to first sprite must be < length of file
        CMP     R1, R4
        Pull    "R1-R6, PC",CC          ; R0 is corrupt, R1-R11 preserved, V=0

; it was a bad file, so make it look like an empty sprite area before erroring
; so that in SLoad case we don't get a naff sprite area.

05
        MOV     R0, #0
        STR     R0, [R3, #saNumber-4]
        MOV     R0, #SpriteAreaCBsize
        STR     R0, [R3, #saFirst-4]
        STR     R0, [R3, #saFree-4]

        ADRL    R0, SpriteErr_BadSpriteFile
      [ International
        BL      TranslateError
      ]
        B       %FT20

08
        MOV     R2, R0
        MOV     R0, #OSFile_MakeError   ; then make into suitable error
        SWI     XOS_File
10
        ADD     R13, R13, #2*4          ; balance stack
20
        Pull    "R1-R6, R14"            ; return with error
        STR     R0, [WsPtr, #RetnReg0]
        RETURNVS                        ; R1-R11 preserved

; *****************************************************************************
;
;       SaveSpriteFile - Save sprite area to filing system
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> filename
;

SaveSpriteFile ROUT
        LDR     R3, [R1, #saNumber]             ; if no sprites
        TEQ     R3, #0
        BEQ     %FT10                           ; then quit with error

        Push    R14
        ADD     R4, R1, #saNumber               ; save sprite area, excluding
        LDR     R5, [R1, #saFree]               ; free space or saEnd
        ADD     R5, R5, R1

        [ AssemblingArthur :LOR: Module
        MOV     R0, #OSFile_SaveStamp
        MOV     R1, R2                          ; filename ptr
        LDR     R2, =&FF9                       ; type=SpriteFile
        MOV     R3, #0                          ; junk
        |
        MOV     R0, #0                          ; normal save
        MOV     R1, R2                          ; filename ptr
        MOV     R2, #0
        MOV     R3, #0
        ]

        SWI     XOS_File                        ; save file
        STRVS   R0, [WsPtr, #RetnReg0]
        Pull    PC

10
        ADRL    R0, SpriteErr_NoSprites
      [ International
        Push    "lr"
        BL      TranslateError
        Pull    "lr"
      ]
        STR     R0, [WsPtr, #RetnReg0]
        RETURNVS

        LTORG

; *****************************************************************************
;
;       MergeSpriteAreas - Merge two sprite areas
;
;       Internal routine, called by MergeSpriteFile
;
; in:   R1 -> destination sprite area
;       R2 -> source sprite area
;       V=0
;
; out:  All registers preserved
;

MergeSpriteAreas ROUT
        TEQ     R2, #0                  ; validate R2 (R1 already checked)
        BEQ     %FT30
        Push    "R1-R4, R14"

        LDR     R4, [R2, #saNumber]     ; number of sprites to merge
        TEQ     R4, #0
        BEQ     %FT20                   ; nothing to merge

        LDR     R3, [R2, #saFirst]
        ADD     R2, R2, R3
10                                      ; R1 -> dest area CB,
        BL      MergeSprite             ; R2 -> to sprite CB
                                        ; return R2 -> next sprite
        BVS     %FT20                   ; error 'NoRoomToMerge'
        SUBS    R4, R4, #1              ; (V:=0 if becoming zero)
        BNE     %BT10
20
        Pull    "R1-R4, PC"             ; exit with V already set up

30
        ADRL    R0, SpriteErr_Bad2ndPtr
      [ International
        Push    "lr"
        BL      TranslateError
        Pull    "lr"
      ]
        STR     R0, [WsPtr, #RetnReg0]
        RETURNVS

; *****************************************************************************
;
;       MergeSprite - Merge sprite into sprite area
;
;       Internal routine, called by MergeSpriteAreas
;
; in:   R1 -> destination area
;       R2 -> source sprite
;
; out:  R2 -> past end of source sprite (ie next sprite)
;

MergeSprite ROUT
        Push    "R1, R3-R5, R14"
        ADD     R2, R2, #spName
        BL      DeleteSpriteByName      ; in: R1-R2; out: all preserved
        SUB     R2, R2, #spName
AddSprite
        LDR     R3, [R1, #saFree]
        LDR     R5, [R1, #saEnd]
        SUB     R5, R5, R3              ; amount of free ram in dest area

        LDR     R4, [R2, #spNext]       ; space needed for sprite
        CMP     R5, R4
        BCC     %FT10

        ADD     R3, R3, R1              ; first free locn in dest. area
        CopyDown R3,R2,R4,R5,R14        ; NB CopyDown will exit with R3 -> end
                                        ; of dest area
        LDR     R4, [R1, #saNumber]     ; update number of sprites
        ADD     R4, R4, #1
        STR     R4, [R1, #saNumber]

        SUB     R3, R3, R1              ; and free space offset
        STR     R3, [R1, #saFree]

        Pull    "R1, R3-R5, R14"
        RETURNVC                        ; ignore 'not found' from DeleteSprite

10
        Pull    "R1, R3-R5, R14"
        ADRL    R0, SpriteErr_NoRoomToMerge
      [ International
        Push    "lr"
        BL      TranslateError
        Pull    "lr"
      ]
        STR     R0, [WsPtr, #RetnReg0]
        RETURNVS

; *****************************************************************************
;
;       AppendSprite - Append sprite to sprite area
;
;       Internal routine, called by CopySprite
;
; in:   R1 -> destination area
;       R2 -> source sprite
;
; out:  R2 -> past end of source sprite (ie next sprite)
;

AppendSprite ROUT
        Push    "R1, R3-R5, R14"
        B       AddSprite

; *****************************************************************************
;
;       ExtendHorizontally - Add one column to RHS of the sprite
;
;       Internal routine, called by InsertCol
;
; in:   R1 -> sprite area
;       R2 -> sprite
;

ExtendHorizontally ROUT
        Push    "R3-R6, R9-R11, R14"
        LDR     R3, [R2, #spRBit]
        LDR     R4, [WsPtr, #SprBytesPerChar]
        ADD     R3, R3, R4              ; alter spRBit to include next pixel
        CMP     R3, #32
        BCC     %FT20
                                        ; >=32 means add 1 word to each row
        LDR     R3, [R2, #spHeight]
        ADD     R3, R3, #1              ; extend by (spHeight+1) words
        BL      ExtendSprite            ; (space doubled if mask present)
        Pull    "R3-R6, R9-R11, PC", VS ; no room, then give error

        MOV     R4, #0
        BL      MaskOffset              ; use spHeight+1 as row counter,
        MOVNE   R3, R3, LSL #1          ; doubled if mask present

        LDRNE   R5, [R2, #spTrans]      ; correct mask ptr, if mask present
        ADDNE   R5, R5, R3, LSL #1      ; R3 is words*2, hence LSL
        STRNE   R5, [R2, #spTrans]

        BL      InsertWords             ; Insert R3 words at position R4 in
                                        ; sprite, ie at beginning
        LDR     R5, [R2, #spWidth]
        ADD     R5, R5, #1              ; new spWidth
        STR     R5, [R2, #spWidth]

        LDR     R9, [R2, #spImage]
        ADD     R9, R9, R2              ; to
        ADD     R10, R9, R3, LSL #2     ; from

10
        MOV     R11, R5, LSL #2
                                        ; move one row
        CopyDown R9,R10,R11,R14,R6      ; To,From,Size, Temp

        STR     R4, [R9], #4            ; add 1 word at RH end of row
        SUBS    R3, R3, #1
        BHI     %BT10                   ; next row

        LDR     R3, [WsPtr, #SprBytesPerChar]
        SUBS    R3, R3, #1
20
        STR     R3, [R2, #spRBit]
        Pull    "R3-R6, R9-R11, R14"
        RETURNVC

; *****************************************************************************
;
;       ReduceHorizontally - Remove some bits from the RHS of the sprite
;
;       Internal routine, called by DeleteCol
;
; in:   R0 = number of bits to delete off right hand side
;       R1 -> sprite area
;       R2 -> sprite
;

ReduceHorizontally ROUT
        Push    "R3-R6, R9-R11, R14"
        LDR     R3, [R2, #spRBit]
        SUBS    R3, R3, R0              ; alter spRBit to exclude those bits
        ADDCC   R3, R3, #32             ; if underflow then extract whole word
        STR     R3, [R2, #spRBit]
        BCS     %FT20
                                        ; < 0 means remove 1 word per row
        LDR     R3, [R2, #spHeight]
        ADD     R3, R3, #1              ; remove (spHeight+1) words
        MOV     R4, #0
        BL      MaskOffset
        MOVNE   R3, R3, LSL #1          ; doubled if mask present

        LDRNE   R5, [R2, #spTrans]      ; correct mask ptr, if mask present
        SUBNE   R5, R5, R3, LSL #1      ; R3 is words*2, hence LSL
        STRNE   R5, [R2, #spTrans]

        LDR     R9, [R2, #spImage]
        ADD     R9, R9, R2              ; to
        MOV     R10, R9                 ; from
        LDR     R5, [R2, #spWidth]
10
        MOV     R11, R5, LSL #2
                                        ; move one row
        CopyDown R9,R10,R11,R14,R6      ; To,From,Size, Temp


        ADD     R10, R10, #4            ; skip unwanted word
        SUBS    R3, R3, #1
        BHI     %BT10                   ; next row

; R9 -> past end of this sprite
; R10 -> next sprite

        SUB     R3, R10, R9
        MOV     R3, R3, LSR #2          ; no. of words to remove
        SUB     R4, R9, R2
        LDR     R9, [R2, #spImage]
        SUB     R4, R4, R9              ; byte offset within image

        BL      RemoveWords

        LDR     R5, [R2, #spWidth]
        SUB     R5, R5, #1              ; new spWidth
        STR     R5, [R2, #spWidth]
20
        Pull    "R3-R6, R9-R11, R14"
        RETURNVC

; *****************************************************************************
;
;       InsertRow - Insert blank row into sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = row number to insert below (0 => bottom, spHeight+1 => above top)
;

InsertRow ROUT
        Push    "R0,R4,R14"
        MOV     R0, #57                 ; SpriteOp reason code for insert/delete rows
        ORR     R0, R0, #512            ;Set it to use pointers to user area & sprite
        MOV     R4, #1                  ; We're only inserting one row! (put 1 in)
        SWI     XOS_SpriteOp
        BVS     %FT20

        Pull    "R0,R4,R14"
        RETURNVC                        ; exit OK

20
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0,R4,R14"
        RETURNVS                        ; exit with error

; *****************************************************************************
;
;       DeleteRow - Delete row from sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = row number to remove (0=bottom, spHeight=top)
;

DeleteRow ROUT
        Push    "R0,R4,R14"
        MOV     R0, #57                 ; SpriteOp reason code for insert/delete rows
        ORR     R0, R0, #512            ;Set it to use pointers to user area & sprite
        MVN     R4, #0                  ; We're only removing one row! (put -1 in)
        SWI     XOS_SpriteOp
        BVS     %FT20

        Pull    "R0,R4,R14"
        RETURNVC                        ; exit OK

20
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0,R4,R14"
        RETURNVS                        ; exit with error

; *****************************************************************************
;
;       InsertCol - Insert blank column into sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = column to insert at (0 => before left..width => after right)
;

InsertCol ROUT
        Push    "R0,R4,R14"
        MOV     R0, #58                 ; SpriteOp reason code for insert/delete cols
        ORR     R0, R0, #512            ;Set it to use pointers to user area & sprite
        MOV     R4, #1                  ; We're only inserting one column! (put 1 in)
        SWI     XOS_SpriteOp
        BVS     %FT20

        Pull    "R0,R4,R14"
        RETURNVC                        ; exit OK

20
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0,R4,R14"
        RETURNVS                        ; exit with error

; *****************************************************************************
;
;       DeleteCol - Delete column from sprite
;
;       External routine, and LHWastageEntry called from RemoveLeftHandWastage
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = column number to remove (0 => left, width-1 => right)
;

DeleteCol ROUT
        Push    "R0,R4,R14"
        MOV     R0, #58                 ; SpriteOp reason code for insert/delete cols
        ORR     R0, R0, #512            ;Set it to use pointers to user area & sprite
        MVN     R4, #0                  ; We're only removing one row! (put -1 in)
        SWI     XOS_SpriteOp
        BVS     %FT20

        Pull    "R0,R4,R14"
        RETURNVC                        ; exit OK

20
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R0,R4,R14"
        RETURNVS                        ; exit with error

; *****************************************************************************
;
;       ExtendSprite - Add R3 words to the end of the sprite (R3*2 if mask)
;
;       Internal routine, called by ExtendHorizontally, InsertRow, CreateMask,
;        and ExtendSpriteByR3 called by GetSprite
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = no. of words to insert (gets doubled if mask present)
;

ExtendSpriteByR3 ROUT
        Push    "R3, R8-R11, R14"
        B       ExtendSprite10

ExtendSprite ROUT
        Push    "R3, R8-R11, R14"
        BL      MaskOffset
        MOVNE   R3, R3, LSL #1          ; double no. of words if mask present
ExtendSprite10
        LDR     R10, [R1, #saEnd]
        LDR     R11, [R1, #saFree]
        SUB     R10, R10, R3, LSL #2
        CMP     R10, R11
        BCC     %FT10

        LDR     R10, [R2, #spNext]
        ADD     R10, R10, R2            ; copy source
        ADD     R9, R10, R3, LSL #2     ; copy destination

        LDR     R11, [R1, #saFree]
        ADD     R11, R11, R1
        SUB     R11, R11, R10           ; size (bytes) to copy
        CopyUp  R9,R10,R11, R14, R8     ; To,From,Size,Temp, Temp2

        LDR     R9, [R1, #saFree]
        ADD     R9, R9, R3, LSL #2
        STR     R9, [R1, #saFree]       ; update saFree

        LDR     R9, [R2, #spNext]
        ADD     R9, R9, R3, LSL #2
        STR     R9, [R2, #spNext]       ; update spNext

        Pull    "R3, R8-R11, R14"
        RETURNVC

10
        ADRL    R0, SpriteErr_NoRoomToInsert
      [ International
        BL      TranslateError
      ]
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R3, R8-R11, R14"
        RETURNVS

; *****************************************************************************
;
;       InsertWords - Insert R3 words into given sprite at specified position
;
;       Internal routine, called by ExtendHorizontally, InsertRow
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = number of words to insert
;       R4 = insertion point (byte offset within sprite image)
;
;       NB Assumes ExtendSprite has been called to leave R3 extra words
;       at the end of the sprite
;
; out:  All registers preserved
;

InsertWords ROUT
        Push    "R8-R11, R14"
        LDR     R10, [R2, #spImage]
        ADD     R10, R10, R2
        ADD     R10, R10, R4            ; copy source
        ADD     R9, R10, R3, LSL #2     ; copy destination
        LDR     R11, [R2, #spNext]
        ADD     R11, R11, R2
        SUB     R11, R11, R9            ; size (bytes) to copy
        CopyUp  R9,R10,R11, R14,R8      ; To,From,Size,Temp, Temp2
        Pull    "R8-R11, R14"
        RETURNVC

; *****************************************************************************
;
;       ClearWords - Clear R3 words in sprite
;
;       Internal routine, called by InsertRow, CreateSprite
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = number of words to clear
;       R4 = byte offset within sprite image to clear from
;
; out:  All registers preserved
;

ClearWords ROUT
        Push    "R9-R11, R14"
        LDR     R10, [R2, #spImage]
        ADD     R10, R10, R2
        ADD     R10, R10, R4            ; clear from
        MOVS    R9, R3
        MOVNE   R11, #0
10
        STRNE   R11, [R10], #4
        SUBNES  R9, R9, #1
        BNE     %BT10
        Pull    "R9-R11, R14"
        RETURNVC

; *****************************************************************************
;
;       RemoveWords - Delete R3 words from given sprite
;
;       Internal routine, called by ReduceHorizontally, DeleteRow, RemoveMask,
;        GetSprite
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = number of words to remove
;       R4 = removal point (byte offset within sprite image)
;
; out:  All registers preserved
;       spNext (in sprite) and spFree (in sprite area) updated
;

RemoveWords ROUT
        Push    "R8-R11, R14"
        LDR     R9, [R2, #spImage]
        ADD     R9, R9, R2
        ADD     R9, R9, R4              ; copy destination
        ADD     R10, R9, R3, LSL #2     ; copy source
        LDR     R11, [R1, #saFree]
        ADD     R11, R11, R1
        SUB     R11, R11, R10           ; size (bytes) to copy
        CopyDown R9,R10,R11, R14,R8     ; To,From,Size,Temp, temp2
        SUB     R9, R9, R1
        STR     R9, [R1, #saFree]       ; update saFree
        LDR     R9, [R2, #spNext]
        SUB     R9, R9, R3, LSL #2
        STR     R9, [R2, #spNext]       ; update spNext
        Pull    "R8-R11, PC"

; *****************************************************************************
;
;       MaskOffset - Read mask size (0 if absent)
;
;       Internal routine, called by ExtendHorizontally, ReduceHorizontally,
;        InsertRow, DeleteRow, ExtendSprite, FlipAboutXAxis
;
; in:   R2 -> sprite
;
; out:  R0 = 0 if no mask, otherwise mask size
;       EQ if no mask, NE if mask present
;

MaskOffset ROUT
        Push    R14
        LDR     R0, [R2, #spImage]
        LDR     R14, [R2, #spTrans]
        SUBS    R0, R14, R0             ; offset from Image to Trans mask
                                        ; =0 if no mask
        Pull    PC                      ; return EQ/NE for nomask/mask

; *****************************************************************************
;
;       ReadPixelColour - Read colour of a pixel in a given sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = X coordinate of pixel (0 = left)
;       R4 = Y coordinate of pixel (0 = bottom)
;
; out:  RetnReg5 = colour  (0..NColour) or  0..63
;       RetnReg6 = tint     0           or  0/64/128/192
;

ReadPixelColour ROUT
        Push    R14
        BL      SpriteGenAddr           ; returns R6,      R7
        Pull    PC, VS                  ;         Address, Bit position
        LDR     R5, [R6]                ; word from sprite
        LDR     R6, [WsPtr, #SprReadNColour]

        CMP     R6, #63                 ; check for 256 colour
        MOVEQ   R6, #255

        AND     R0, R6, R5, LSR R7      ; extract one pixel (bit0..)

        CMP     R6, #255

        MOVNE   R2, R0                  ; colour = pixel
        MOVNE   R3, #0                  ; tint   = 0
        BNE     %FT10

        ;now check for size of palette
        ADD     R2, R2, #spImage        ; point to image/mask start
        LDMIA   R2, {R2, R3}
        CMP     R3, R3
        MOVGT   R3, R3
        SUB     R2, R2, #spPalette
        CMP     R2, #&0800
        BEQ     %FT05

        ;see comment below - for this call to work we have to temporarily
        ;set NColour to SprReadNColour

        LDR     R8,[WsPtr,#NColour]
        STR     R6,[WsPtr,#NColour]

        BL      ExtractTintAndColour    ; else extract colour & tint from pixel

        STR     R8,[WsPtr,#NColour]
        B       %FT10
05
        MOV     R2, R0
        MOV     R3, #0
10
        STR     R2, [WsPtr, #RetnReg5]  ; pass colour in R5
        STR     R3, [WsPtr, #RetnReg6]  ; and tint in R6
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       WritePixelColour - Write a pixel in a given sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = X coordinate of pixel (0 = left)
;       R4 = Y coordinate of pixel (0 = bottom)
;       R5 = pixel colour
;       R6 = tint
;
; out:  R1-R11 preserved
;

; amg: note. need to handle full palettes differently here - since the GCOL and TINT
; model should not apply. Check for a full palette on an 8bpp sprite and deal with
; it accordingly.
; amg: bug fix to MED-01885. Although ReadPixel and WritePixel use SprReadNColour,
; then call AddTintToColour which uses NColour - being the screen's value not the
; sprites. Therefore, the safest fix this near freeze is to simply temporarily change
; the NColour value for the call, and then restore it.

WritePixelColour ROUT
        Push    "R1-R4, R14"
        LDR     R8, [WsPtr, #SprReadNColour]

        AND     R0, R5, R8              ; limit colour to 0..NColour

        CMP     R8, #63                 ; check for 256 colours
        CMPNE   R8, #255
        BNE     %FT08                   ; despatch non 8bpp

        ;now need to determine size of 8bpp sprite's palette
        ADD     R3, R2, #spImage        ; point to image/mask start
        LDMIA   R3, {R2, R3}            ; fetch them
        CMP     R2, R3                  ; which is higher
        MOVGT   R2, R3                  ; use the lesser
        SUB     R2, R2, #spPalette      ; subtract start offset
        CMP     R2, #&800

        MOVNE   R3, R6                  ; then combine

        ;see comment above - for this call to work we have to temporarily
        ;set NColour to SprReadNColour

        LDRNE   R7,[WsPtr,#NColour]
        STRNE   R8,[WsPtr,#NColour]

        BLNE    AddTintToColour         ; colour & tint

        STRNE   R7,[WsPtr,#NColour]

        B       %FT05                   ; 8 bpp take this branch
08
        ADDCC   R0, R0, R8              ; else index into full colour table
        ADRCCL  R5, TBFullCol
        LDRCCB  R0, [R5, R0]            ; N.B. a table of bytes
        BCC     %FT05                   ; 1,2,4 bpp take this branch

        ; if 16bpp only need to shift round once, if 32bpp not at all
        LDR     LR, [R2, #spMode]
        MOV     LR, LR, LSR #27
        CMP     LR, #6
        MOV     R5, R0
        BCS     %FT06                   ; 32 bpp takes this branch

        B       %FT07                   ; and 16 bpp takes this one
05
        ORR     R5, R0, R0, LSL #8      ; expand byte value into a word
07
        ORR     R5, R5, R5, LSL #16
06
        Pull    "R1-R4"
        BL      SpriteGenAddr           ; returns R6,      R7
        Pull    PC, VS                  ;         Address, Bit position
        LDR     R8, [WsPtr, #SprWriteNColour]
        AND     R5, R5, R8              ; limit colour to pixel width
        LDR     R0, [R6]                ; word from sprite
        BIC     R0, R0, R8, LSL R7
        ORR     R0, R0, R5, LSL R7
        STR     R0, [R6]
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       ReadPixelMask - Read mask state for a pixel in a given sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = X coordinate of pixel (0 = left)
;       R4 = Y coordinate of pixel (0 = bottom)
;
; out:  RetnReg5 = 0/1 (transparent/solid)
;

ReadPixelMask ROUT
        Push    R14
        LDR     R5, [R2, #spImage]
        LDR     R6, [R2, #spTrans]
        SUBS    R5, R6, R5              ; offset from Image to Trans mask
        MOVEQ   R5, #1                  ; if =0, no mask so pixel is solid
        BEQ     %FT10
        BL      SpriteMaskAddr          ; returns R6,      R7
        Pull    PC, VS                  ;         Address, Bit position
        LDR     R5, [R6, R5]            ; word from mask

        LDR     LR, [R2, #spMode]       ; check for 1bpp masks
        MOVS    LR, LR, LSR #27
        MOVNE   R6, #1
        LDREQ   R6, [WsPtr, #SprReadNColour]

        ANDS    R5, R6, R5, LSR R7      ; extract one mask pixel (bit0..)
        MOVNE   R5, #1
10
        STR     R5, [WsPtr, #RetnReg5]
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       WritePixelMask - Write a pixel in the mask for a given sprite
;
;       External routine
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = X coordinate of pixel (0 = left)
;       R4 = Y coordinate of pixel (0 = bottom)
;       R5 = pixel mask 0/1 (transparent/solid)
;

WritePixelMask ROUT
        Push    R14
        LDR     R8, [R2, #spImage]
        LDR     R9, [R2, #spTrans]
        SUBS    R9, R9, R8              ; offset from Image to Trans mask
        BEQ     %FT10                   ; if =0, no mask so quit
        BL      SpriteMaskAddr          ; returns R6,      R7
        Pull    PC, VS                  ;         Address, Bit position

        LDR     LR, [R2, #spMode]
        MOVS    LR, LR, LSR #27
        LDREQ   R8, [WsPtr, #SprWriteNColour]
        MOVNE   R8, #1                  ; adjust for new format sprites

        TEQ     R5, #0
        MOVNE   R5, R8
        LDR     R0, [R6, R9]            ; word from mask
        BIC     R0, R0, R8, LSL R7
        ORR     R0, R0, R5, LSL R7
        STR     R0, [R6, R9]
10
        Pull    R14
        RETURNVC

; *****************************************************************************
;
;       SpriteGenAddr - Generate address for a given (X,Y) position
;
;       SpriteMaskAddr - For use on mask (copes with old/new masks)
;
;       Internal routine, called by InsertCol, DeleteCol, ReadPixelColour,
;        WritePixelColour, ReadPixelMask, WritePixelMask
;
;       Note that InsertCol and DeleteCol are *not* being altered for the
;       present round of 1bpp mask work.
;
;
; in:   R1 -> sprite area
;       R2 -> sprite
;       R3 = X coordinate of pixel (0 = left)
;       R4 = Y coordinate of pixel (0 = bottom)
;
; out:  R6 = address
;       R7 = bit position of pixel
;       R1-R5, R8-R11 preserved
;       V=1 => outside sprite, R0 -> error
;

SpriteMaskAddr ROUT
        Push    "R1-R5, R8-R11, LR"

        LDR     LR, [R2, #spMode]
        MOVS    LR, LR, LSR #27         ; get the sprite type from the mode word
        BEQ     %FT10                   ; branch: old format, use old routine as is

        ADD     R5, R2, #spWidth
        LDMIA   R5, {R5-R8}             ; R5     ,R6      ,R7    ,R8
                                        ; spWidth,spHeight,spLBit,spRBit
        SUBS    R4, R6, R4              ; invert Y coord
        BCC     %FT90                   ; must be in range 0..spHeight

        BL      GetMaskspWidth          ; change R5 to suit the mask width
                                        ; and R8 to new last bit used

        MLA     R0, R5, R4, R4          ; word offset to row = Y*(width+1)

        ;for new format masks the depth is fixed, so...

        MOV     R9, #5                  ; XShftFactor
        MOV     R10, #31                ; SprNPix
        MOV     R11, #0                 ; Log2BPC

        B       %FT20                   ; and continue in the old code

SpriteGenAddr
        Push    "R1-R5, R8-R11, LR"
10
        ADD     R5, R2, #spWidth
        LDMIA   R5, {R5-R8}             ; R5     ,R6      ,R7    ,R8
                                        ; spWidth,spHeight,spLBit,spRBit
        SUBS    R4, R6, R4              ; invert Y coord
        BCC     %FT90                   ; must be in range 0..spHeight

        MLA     R0, R5, R4, R4          ; word offset to row = Y*(width+1)

        LDR     R9, [WsPtr, #SprXShftFactor]
        LDR     R10, [WsPtr, #SprNPix]
        LDR     R11, [WsPtr, #SprLog2BPC]
20
        BitLOffset R6,R3, R9,R10,R11
        WordOffset R3,R3, R9,R10,R11

; sprite starts LBit bits into word
; so add LBit to bit offset

        ADD     R7, R6, R7
        ADD     R3, R3, R7, LSR #5      ; if offset>=32 then inc word address
        AND     R7, R7, #31             ; force offset into range 0..31

        CMP     R3, R5                  ; R3 should now be in range 0..spWidth
        CMPEQ   R7, R8                  ; if R3=spWidth, then check bit posn
        BHI     %FT90                   ; is within sprite

        ADD     R6, R0, R3              ; word offset into sprite
        ADD     R6, R2, R6, LSL #2
        LDR     R8, [R2, #spImage]
        ADD     R6, R6, R8              ; byte address of word in sprite

        Pull    "R1-R5, R8-R11, LR"
        RETURNVC

90
        ADRL    R0, SpriteErr_InvalidRowOrCol
      [ International
        Push    "lr"
        BL      TranslateError
        Pull    "lr"
      ]
        STR     R0, [WsPtr, #RetnReg0]
        Pull    "R1-R5, R8-R11, LR"
        RETURNVS


; *****************************************************************************
;
;       GetMaskspWidth - convert spWidth for data to spWidth for mask (1bpp masks)
;
;       Internal routine, called from spritemaskaddr & switchouputtomask
;
; in:   R5 = spWidth (ie width in words-1)
;       (expects R2->sprite)
;
; out:  R5 = spWidth (words -1) for mask data
;       R8 modified for new last bit used in mask data

; should only be called for new format sprites, but will cope with old too

;NOTE: If any changes are made to this routine, please look at the SpriteExtend
;source too, as there is a very similiar routine there too (in SprAdjSize). WT

GetMaskspWidth ROUT
        Push    "R0,LR"

        LDR     LR, [R2, #spMode]       ; fetch the sprite mode
        MOVS    LR, LR, LSR #27         ; isolate the sprite type and test for =0

        Pull    "R0,PC",EQ              ; if an old format sprite, return R5 unchanged

        ; treat any T>max sprites as 32bpp
        CMP     LR, #SpriteType_MAX
        MOVCS   LR, #SpriteType_Substitute

        ; bugfix 9/8/93: get log2bpp this way
        ADRL    R0, NSM_bpptable-4
        LDR     LR, [R0, LR, LSL #2]    ; get the log2bpp to LR

        RSB     LR, LR, #5              ; and change to 5-log2bpp

        MOV     R5, R5, LSL LR          ; number of pixels for full words

        RSB     LR, LR, #5              ; now switch back to log2bpp
        LDR     R0, [R2, #spRBit]
        ADD     R0, R0, #1
        ADD     R5, R5, R0, LSR LR

        ANDS    LR, R5, #&1F            ; fit exactly in a number of words ?
        SUB     R8, LR, #1              ; alter the last bit used for the mask data
                                        ; fix bug MED-01130....
        AND     R8, R8, #&1F            ; ....bring back into range 00-1F (may be -1 here)
        MOVNE   LR, #1                  ; if not, add an extra word
        ADD     R5, LR, R5, LSR #5      ; add the whole number of words
        SUB     R5, R5, #1              ; returns as words-1

        Pull    "R0,PC"


; *****************************************************************************
;
;       RemoveLeftHandWastage - Remove left-hand wastage from a sprite
;
;       Internal routine, but made external for testing
;
; in:   R1 -> sprite area
;       R2 -> sprite
;

RemoveLeftHandWastage ROUT
        LDR     R0, [R2, #spLBit]
        CMP     R0, #0                  ; is there any wastage ?
        MOVEQ   PC, R14                 ; no, then exit straight away

        Push    "R1, R2, R14"           ; get stack the same as in DeleteCol
        LDR     R11, [R2, #spImage]
        ADD     R11, R11, R2            ; R11 := address of first word
        MOV     R7, #0                  ; bit position of first pixel
        STR     R7, [R2, #spLBit]       ; pretend LBit = 0
        MOV     R10, #0                 ; byte offset from LH end to delete pt.

        LDR     R9, [R2, #spWidth]
        MOV     R9, R9, LSL #2          ; byte offset from delete pt to LH end
                                        ; of next row -4
        LDR     R8, [R2, #spNext]
        ADD     R8, R8, R2              ; first byte after sprite

        MOV     R2, R0                  ; number of bits to delete
LHWastageEntry
        RSB     R3, R2, #32                     ; LShft := 32-RShft

        MOV     R4, #1
        RSB     R4, R4, R4, LSL R7      ; mask for pixels left of deletion pt.
        MVN     R5, R4                  ; inclusive & right of extractn. pt.

; R0,   R1,   R2   ,R3   ,R4   ,R5   ,R6   ,R7     ,R8    ,R9     ,R10   .R11
;   ,     ,   RShft,LShft,LMask,RMask,     ,WordCnt,EndAdr,WordOff,RowOff,Adr

; R11 -> LH end of row

10
        ADD     R11, R11, R10           ; step to deletion point
        LDR     R0, [R11]
        AND     R1, R5, R0, LSR R2      ; extract & shift rightmost pixels
                                        ; (ie MSBits)
        AND     R0, R4, R0              ; extract leftmost pixels (ie LSBits)
        ORR     R0, R0, R1              ; recombine (unwanted pixel removed)
        LDR     R1, [R11, #4]           ; shift leftmost pixel of next word
        ORR     R0, R0, R1, LSL R3      ; in at rightmost end of this word
        STR     R0, [R11], #4           ; NB #4 to cope with naff rowoff (R10)
        CMP     R9, #0
        BEQ     %FT30
        MOV     R7, R9
20
        LDMIA   R11,{R0,R1}             ; now do a 1 pixel shift left
        MOV     R0, R0, LSR R2          ; of the rest of the row
        ORR     R0, R0, R1, LSL R3
        STR     R0, [R11], #4
        SUBS    R7, R7, #4
        BGT     %BT20

; R11 -> LH end of next row

30
        CMP     R8, R11
        BHI     %BT10                   ; if EndAdr>Adr, do next row

        MOV     R0, R2                  ; R0 = number of bits to delete
        LDMFD   R13, {R1,R2}
        BL      ReduceHorizontally
        Pull    "R1-R2, R14"
        RETURNVC

60
        STR     R0, [WsPtr, #RetnReg0]
70
        Pull    "R1-R2, R14"
        RETURNVS


; ******************************************************************************
;
;        bounce_new_format_masks - object to masks on new format sprites
;
;        enter with R2->sprite
;        either returns with all registers preserved, or VS and R0->error

bounce_new_format_masks ROUT
        STMFD   R13!,{R0,R14}
        LDR     LR, [R2, #spMode]       ; fetch the sprites mode
        MOVS    LR, LR, LSR #27         ; set NE if new format
        LDMEQFD R13!,{R0,R15}           ; out now if old format
        BL      MaskOffset              ; returns R0=mask size, EQ if no mask, NE if mask
        LDMEQFD R13!,{R0,R15}           ; out now if no mask
        ADRL    R0, SpriteErr_NoMaskOrPaletteAllowedInThisDepth
      [ International
        BL      TranslateError
      |
        SETV
      ]
        STR     R0,[R13]
        STR     R0,[WsPtr, #RetnReg0]
        LDMFD   R13!,{R0,R15}

        END