; Copyright 2010 Castle Technology 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.
;
; > Sources.SprOp

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; OS_SpriteOp decoding entry point
; Entry: R0 = reason code
;        R12 --> private word
; Exit : R0-R7 may be used to contain results
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

My_SpriteOp ROUT
        Debug   so, "Sprite Extend sprite op called...",r0
        Push    "R1"
        AND     r1, r0, #&FF
        CMP     r1, #SpriteReason_CheckSpriteArea
        CMPNE   r1, #myminreason
        Pull    "R1"
        ASSERT  SpriteReason_CheckSpriteArea < myminreason 
        MOVCC   pc, lr

        Push    "R10-R11,LR"
        ; Stash the entry reason code including area flags
        STR     R0,spritecode

        STR     R13,stackframe          ; for calling OS_SpriteOp

        MOV     R14,#VduDriverWorkSpace + BgEcfOraEor
        STR     R14,save_ecflimit       ; mask plotting uses bg ecf pattern

        JumpAddress LR,My_SpriteExit,forward
        AND     R10, R0, #&FF

        ; First look for CheckSpriteArea which would otherwise make the jump table huge
        CMP     r10, #SpriteReason_CheckSpriteArea
        BEQ     Go_CheckSpriteArea

        ; Do the rest...
        SUB     r10, r10, #myminreason
        CMP     R10,#mymaxreason-myminreason
        ADDCC   PC,PC,R10,LSL #2
        Pull    "R10-R11,PC"

mymaxreason     *       SpriteReason_ReadSaveAreaSize + 1
myminreason     *       SpriteReason_AppendSprite
myjptable
        B       Go_AppendSprite         ; 35 SpriteReason_AppendSprite
        B       Go_SetPointerShape      ; 36 SpriteReason_SetPointerShape
        B       Go_CreateRemovePalette  ; 37 SpriteReason_CreateRemovePalette
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        B       Go_PlotMaskScaled       ; 50 SpriteReason_PlotMaskScaled
        B       Go_PaintCharScaled      ; 51 SpriteReason_PaintCharScaled
        B       Go_PutSpriteScaled      ; 52 SpriteReason_PutSpriteScaled
        B       Go_PutSpriteGreyScaled  ; 53 SpriteReason_PutSpriteGreyScaled
        Pull    "R10-R11, PC"
        B       Go_PlotMaskTransformed  ; 55 SpriteReason_PlotMaskTransformed
        B       Go_PutSpriteTransformed ; 56 SpriteReason_PutSpriteTransformed
        B       Go_InsertDeleteRows     ; 57 SpriteReason_InsertDeleteRows
        B       Go_InsertDeleteColumns  ; 58 SpriteReason_InsertDeleteColumns
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
        Pull    "R10-R11, PC"
      [ {FALSE}
        Pull    "R10-R11, PC"           ; 63 Unsupported SpriteReason_PutSpriteScaledCalibrated
        Pull    "R10-R11, PC"           ; 64 Unsupported SpriteReason_PutSpriteTransformedCalibrated
        Pull    "R10-R11, PC"           ; 65 Unsupported SpriteReason_TileSpriteScaled
      ]
        ASSERT  (.-myjptable) = (mymaxreason-myminreason) * 4

My_SpriteExit
        LDRVC   R0,spritecode
01
        Pull    "R10-R11,LR,PC"


Go_SpriteOp
        Push    "R10-R12,LR"
01
        LDR     R10,stackframe
        Push    "PC"                    ; set up return address
        LDMIA   R10,{R10-R11,PC}        ; call rest of vector owners
        NOP                             ; returns here or next instruction
        Pull    "R10-R12,PC"

TestFor16or32bpp ROUT ; return V=1 if R2 points at a 16/32bpp sprite
        Push    "R0-R3,R11,LR"
        MOV     R11, #0
        B       %FT40

TestForMaskAnyDepth ; fault a new format sprite with mask
        Push    "R0-R3,R11,LR"
        LDR     R0,[R2,#spMode]
        MOVS    R0,R0,LSR #27
        LDMEQFD R13!,{R0-R3,R11,PC}
        B       %FT32

TestForMaskAtDepth ; fault a mask at 16/32bpp
        Push    "R0-R3,R11,LR"
        ;reject any T=5 or T=6 sprites which have a mask, and also (later) any
        ;attempts to add a palette to one
        MOV     R11,#1
40
        ;should be called after findsprite has been called

        LDR     R0,[R2,#spMode]
        CMP     R0,#256
        BCC     %FT10                   ; go if a screen mode number
        MOV     R0,R0,LSR #27           ; isolate the type alone
        CMP     R0,#SpriteType_New16bpp
        BCC     %FT20                   ; under 16bpp, so don't care
30
        CMP     R11,#0
        BEQ     %FT50
32
        LDR     R0,[R2,#spImage]
        LDR     R1,[R2,#spTrans]
        TEQ     R0,R1
        BEQ     %FT20                   ; no mask, so no complaints
50
        ADR     R0, ErrorBlock_BadDepth
        addr    R1, Title
        BL      copy_error_one          ; returns V set
        STR     R0,[R13]
        STR     R1,[R13,#4]
        Pull    "R0-R3,R11,PC"

        MakeSpriteErrorBlock BadDepth,,BadDepth
10
        ;it's a mode number
        Push    "R2"
        MOV     R1,#VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        BCS     %BT50
        CMP     R2,#4
        Pull    "R2"
        BCS     %BT30                   ; 16 or 32 bpp so check for a mask
20
        Pull    "R0-R3,R11,PC"

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteReason_CreateRemovePalette
; --------------------------------
; Entry: r0  = 37 (SpriteReason_CreateRemovePalette) (+0 / 256 / 512)
;        r1 -> sprite control block
;        r2 -> sprite name / sprite
;        r3  = -1: read palette size
;            =  0: remove palette
;            <> 0: add palette (bit 31=1, add extended palette)
;
; Exit : V=1 => r0 -> error block
;
;        r3 =-1 on entry then r3  = size of palette block, =0 if none
;                             r4 -> palette block, =0 if none
;                             r5  = mode
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_CreateRemovePalette
        Push    "r1-r11, lr"

        BL      findsprite              ; r2 -> sprite block
        Pull    "r1-r11, PC",VS

        CMP     r3, #-1
        BNE     addremoveit             ; skip read palette size

        ADD     r3, r2, #spImage
        LDMIA   r3, {r3, r4}            ; r3,r4 offsets to mask/image
        CMP     r3, r4
        MOVGT   r3, r4                  ; r3 -> top of palette block
        SUB     r3, r3, #spPalette      ; r3  = size of palette block
        MOVS    r3, r3, ASR #3          ; r3  = number of colours in palette block

        ADDNE   r4, r2, #spPalette      ; r4 -> palette block to return
        MOVEQ   r4, #0                  ; or  =0 if no palette
        LDR     r5, [r2, #spMode]       ; r5  = mode number

        ADD     lr, sp, #4*2            ; lr -> r3 in return frame
        STMIA   lr, {r3, r4, r5}

        CLRV
        Pull    "r1-r11, pc"

addremoveit
        TEQ     r3, #0                  ; remove the palette?
        BNE     addpalette

        ; remove palette, simple case, assuming r1 -> sprite control block, r2 -> sprite
        ; we must move the data in the sprite and then update the header followed by
        ; the control block.
removepalette
        LDR     r4, [r2, #spImage]      ; r4   = offset to image
        LDR     r5, [r2, #spTrans]      ; r5   = offset to trans mask
        CMP     r4, r5
        MOVGT   r4, r5                  ; r4   = offset to first part of sprite data

        LDR     r8, [r1, #saFree]       ; r8   = free index into sprite area

        SUBS    r0, r4, #spPalette      ; r0   = size of palette block, if none then exit!
        ADDNE   r9, r2, #spPalette
        ADDNE   r10, r1, r8             ; r10 -> free in sprite area
        BLNE    move_memory_down

        SUB     r8, r8, r0              ; adjust free space
        STR     r8, [r1, #saFree]

        LDR     r3, [r2, #spNext]       ; adjust next sprite pointer
        SUB     r3, r3, r0
        STR     r3, [r2, #spNext]

        ADD     r3, r2, #spImage        ; adjust offsets to image data
        LDMIA   r3, {r4, r5}
        SUB     r4, r4, r0
        SUB     r5, r5, r0
        STMIA   r3, {r4, r5}

        CLRV
        Pull    "r1-r11, pc"


        ; add sprite palettes, this involves creating a gap for the sprite
        ; palette and then writing the actual data for the palette into this area
        ; these routines will use a general purpose function 'addpalette' taking
        ; r1,r2 as sprite pointers and r4 as a mask to be applied to the maximum
        ; number of colours.  This mask is used to generate the real number of
        ; colours written into the sprite header.
addpalette
        ; the test for 16/32bpp only occurs here, so that read palette size, and
        ; remove palette won't complain

        LDR     r0, [r2, #spMode]       ; r0  = mode
        MOVS    lr, r0, LSR #27         ; EQ for old format sprite, NE otherwise
        BEQ     %FT10

        ;now allow T=1 to T=4 to have palettes
        CMP     lr, #SpriteType_New16bpp
        BCC     %FT10

        ADR     r0, ErrorBlock_BadDepth
        addr    R1, Title
        BL      copy_error_one          ; this call sets V
        Pull    "r1-r11, PC",VS
10
        TST     r3, #1:SHL:31           ; add extended palette?
        MOVNE   r4, #255
        MOVEQ   r4, #63                 ; max colours that can be added

        Push    "r1-r2"

        LDR     r0, [r2, #spMode]       ; r0  = mode
        MOV     r1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable    ; no need to check for CS here - will already
        ADR     r1, palettetables       ; have happened in TestFor16or32bpp
        LDR     r5, [r1, r2, ASL #2]    ; r5  = offset to palette tables
        ADD     r5, r5, r1              ; r5 -> absolute table
        MOV     r8, #2                  ; shift =2 as reading from table (single entries)

        MOV     r0, #1
        MOV     r1, r0, ASL r2
        RSB     r3, r0, r0, ASL r1      ; r3  = maximum number of colours
        AND     r4, r3, r4              ; r4  = maximum number of written colours

        Pull    "r1-r2"                 ; preserve sprite pointers

        ADD     r0, r4, #1
        MOV     r0, r0, ASL #3          ; r0 = size of a palette block

        ADD     r6, r2, #spImage
        LDMIA   r6, {r6, r7}            ; r6, r7 -> sprite, mask

        MOV     lr, r6
        CMP     lr, r7
        MOVGT   lr, r7                  ; lr =lowest offset

        SUBS    lr, lr, #spPalette      ; lr =size of current palette
        SUBNE   r0, r0, lr
        ADDNE   r5, r2, #spPalette      ; if adjusting size then modify from current palette
        MOVNE   r8, #3                  ; shift =3, reading from sprite palette (double entries)

        LDR     r9, [r1, #saFree]
        LDR     r10, [r1, #saEnd]
        ADD     r9, r9, r0              ; r9 => end of free area
        CMP     r9, r10                 ; is there enough room?
        BHI     add_no_room             ; no, so complain!

        STR     r9, [r1, #saFree]       ; store new offset to start of free area
        ADD     r10, r9, r1             ; r10 -> new start of free area
        SUB     r10, r10, r0            ; r10 -> old start of free area
                                        ;     =  end address+1 of block to move

        LDR     r9, [r2, #spNext]
        ADD     r9, r9, r0
        STR     r9, [r2, #spNext]       ; adjust the offset to next sprite

        ADD     r6, r6, r0
        ADD     r7, r7, r0
        ADD     r9, r2, #spImage
        STMIA   r9, {r6, r7}            ; stash updated image + mask offsets

        ADD     r9, r2, #spPalette      ; r9 -> start of current palette
        ADD     r9, r9, lr              ; r9 -> end of current palette+1
                                        ;    =  start address of block to move
        BL      move_memory_up

        ADD     r6, r2, #spPalette      ; r6 -> destination buffer
        ADD     r6, r6, r4, LSL #3
        LDR     r7, =&0F0F0F00          ; r7  = masked used when ensuring palette entries

        ; r1 -> sprite blk
        ; r2 -> sprite
        ; r3  = total colours (ie. 1, 3, 15, 255)
        ; r4  = number of colours to write ( 1, 3, 15, 63 / 255)
        ; r5 -> table containing base palette entries
        ; r6 -> block to write data into
        ; r7  = &0F0F0F00
        ; r8  = shift to use when getting palette entries
addpalette_main
        AND     r0, r4, #15
        ADD     r0, r5, r0, LSL r8
        LDR     r0, [r0]                ; r0 = BGR combination

        TEQ     r3, #255                ; is it a 8bpp palette?
        BNE     addpalette_gotvalues

        BIC     r0, r0, #&80000000      ; transfer hard blue bit
        AND     lr, r4, #&80
        ORR     r0, r0, lr, LSL #31-7
        BIC     r0, r0, #&00C00000      ; transfer hard green bit
        AND     lr, r4, #&60
        ORR     r0, r0, lr, LSL #23-6
        BIC     r0, r0, #&00008000      ; transfer hard red bit
        AND     lr, r4, #&10
        ORR     r0, r0, lr, LSL #15-4

        BIC     r0, r0, r7
        ORR     r0, r0, r0, LSR #4      ; change from &B0G0R00 -> &BBGGRR00

addpalette_gotvalues
        ORR     r0, r0, #&10
        STR     r0, [r6],#4
        STR     r0, [r6],#-12           ; write and advance index

        SUBS    r4, r4, #1
        BGE     addpalette_main         ; loop whilst colour counter >= 0

        Pull    "r1-r11, pc"

add_no_room
        ADR     r0, ErrorBlock_NotEnoughRoom
        addr    r1, Title
        BL      copy_error_one          ; Always sets the V flag
        Pull    "r1-r11, PC"

        MakeSpriteErrorBlock NotEnoughRoom,,NoMem

        ; tables for creating default palettes
palettetables
        DCD     bpp1 -palettetables
        DCD     bpp2 -palettetables
        DCD     bpp4 -palettetables
        DCD     bpp8 -palettetables

bpp1    DCD     &00000000       ;  black
        DCD     &FFFFFF00       ;  white

bpp2    DCD     &00000000       ;  black
        DCD     &0000FF00       ;  red
        DCD     &00FFFF00       ;  yellow
        DCD     &FFFFFF00       ;  white

bpp4    DCD     &00000000       ;  black
        DCD     &0000FF00       ;  red
        DCD     &00FF0000       ;  green
        DCD     &00FFFF00       ;  yellow
        DCD     &FF000000       ;  blue
        DCD     &FF00FF00       ;  magenta
        DCD     &FFFF0000       ;  cyan
        DCD     &FFFFFF00       ;  white
        DCD     &00000000       ;  black
        DCD     &0000FF00       ;  red
        DCD     &00FF0000       ;  green
        DCD     &00FFFF00       ;  yellow
        DCD     &FF000000       ;  blue
        DCD     &FF00FF00       ;  magenta
        DCD     &FFFF0000       ;  cyan
        DCD     &FFFFFF00       ;  white

bpp8    DCD     &00000000       ;  0000
        DCD     &10101000       ;  0001
        DCD     &20202000       ;  0010
        DCD     &30303000       ;  0011
        DCD     &00004000       ;  0100
        DCD     &10105000       ;  0101
        DCD     &20206000       ;  0110
        DCD     &30307000       ;  0111
        DCD     &40000000       ;  1000
        DCD     &50101000       ;  1001
        DCD     &60202000       ;  1010
        DCD     &70303000       ;  1011
        DCD     &40004000       ;  1100
        DCD     &50105000       ;  1101
        DCD     &60206000       ;  1110
        DCD     &70307000       ;  1111

        LTORG

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteReason_SetPointerShape
; ----------------------------
; Entry: R1 --> sprite area
;        R2 --> sprite name/address
;        R3 bits 0..3 = pointer shape number (1..4)
;        R3 bit 4 set => don't use the sprite's image
;        R3 bit 5 set => don't use the sprite's palette
;        R3 bit 6 set => don't set the current shape number afterwards
;        R4,R5 = coordinates of active point (pixels from top-left)
;        R6 --> factors (<=0 ==> use default, depending on mode)
;        R7 --> pixel translation table (==> 2 bpp)
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_SetPointerShape
        Push    "R10,R11,LR"
        Debug   so,"Regs at start = ",R0,R1,R2,R3,R4
        Debug   so,"                ",R5,R6,R7,R8,R9

spp_ptrno       *       2_00001111
spp_noimage     *       2_00010000
spp_nopalette   *       2_00100000
spp_nosetnum    *       2_01000000

tempareasize    *       &200

        Debug   pt, "R3 = ",R3
        TST     R3,#spp_noimage
        BLEQ    go_setimage
        Pull    "R10,R11,PC",VS

        Debug   pt, "done setimage"
        TST     R3,#spp_nopalette
        BLEQ    go_setpointerpalette
        Pull    "R10,R11,PC",VS

        Debug   pt, "done setpalette"
        TST     R3,#spp_nosetnum
        BLEQ    go_setpointernum

        Debug   pt, "done setno",R3
        LDRVC   R0, spritecode
        Debug   gs,"Regs at end   = ",R0,R1,R2,R3,R4
        Debug   gs,"                ",R5,R6,R7,R8,R9
        Pull    "R10,R11,PC"
go_setimage
        LDR     R0,spritecode
        Push    "R0-R7,LR"              ; may need to find sprite again

        Debug   pt,"Input sprite:",#spritecode,R1,R2
        BL      findsprite              ; R2 --> sprite definition

        BLVC    TestFor16or32bpp

        Debug   pt,"Input sprite address is:",R2
        MOV     R1,R2                   ; R1 --> sprite definition
        MOV     R11,R2                  ; R11 --> sprite definition

        Debug   so,"gonna do a create sprite"
        LDRVC   R0,[R2,#spMode]
        BLVC    readspritevars          ; log2px/y
        BLVC    readvduvars             ; inlog2px/y, save_inlog2bpp
        BVS     %FT99
        Debug   so,"still gonna do a create sprite"

        ASSERT  saEnd=0                 ; create area header for output sprite
        MOV     R14,#tempareasize       ; should be big enough
        STR     R14,[sp,-R14]!
        MOV     R14,#0
        STR     R14,[sp,#saNumber]
        MOV     R14,#saExten
        STR     R14,[sp,#saFirst]
        STR     R14,[sp,#saFree]

        Debug   so,"definately still gonna do a create sprite"
        MOV     R0,#SpriteReason_CreateSprite   ; create output sprite
        ADD     R0,R0,#&100
        MOV     R1,sp
        ADRL    R2,sillyname
        MOV     R3,#0                   ; no palette
        MOV     R4,#32                  ; 32 pixels wide
        MOV     R5,#32                  ; 32 pixels high
        MOV     R6,#1                   ; mode 1 seems reasonable
        Debug   pt,"Output sprite:",R0,R1,R2,R3,R4
        SWI     XOS_SpriteOp
        Debug   so,"returned from create swi"
        BVS     %FT96
        Debug   so,"V not set"
96
        MOVVC   R0,#SpriteReason_SwitchOutputToSprite
        ADDVC   R0,R0,#&100
        MOVVC   R3,#0                   ; no save area
        SWIVC   XOS_SpriteOp            ; on exit R0-R3 = old params
        Debug   so,"returned from swi"
        ADDVS   sp,sp,#tempareasize
        BVS     %FT99
        Debug   so,"V not set"

        Push    "R0-R3"
        ADD     R14,sp,#4*4 + tempareasize + 1*4
        LDMIA   R14,{R1-R7}
        Debug   pt,"Recovered input params:",R1,R2,R3,R4,R5,R6,R7

        CMP     R6,#0
        LDMNEIA R6,{R2-R5}              ; user-supplied scaling
        BNE     %FT01

        MOV     R14,#1
        LDR     R2,inlog2px             ; if no R6, make up the values!
        MOV     R2,R14,LSL R2
        LDR     R3,inlog2py
        MOV     R3,R14,LSL R3
        LDR     R4,log2px
        MOV     R4,R14,LSL R4
        LDR     R5,log2py
        MOV     R5,R14,LSL R5
        LDR     R14,Log2bpp             ; double-pixel modes ...
        MOV     R4,R4,LSL R14           ; ... are not quite as they seem !
        LDR     R14,Log2bpc
        MOV     R4,R4,ASR R14
        LDR     R14,modeflags
        TST     R14,#Flag_HiResMono
        MOVNE   R4,R4,LSL #1            ; 1/2 width in hi-res mono
01
        Push    "R2-R5"
        MOV     R6,sp

        MOV     R0,#SpriteReason_PutSpriteScaled     ; plot input into output
        ADD     R0,R0,#&200
        MOV     R2,R11                  ; R2 --> sprite

        LDR     R3,[R2,#spLBit]         ; first bit number used
        RSB     R3,R3,#1                ; remember bits are inclusive
        LDR     R14,[R2,#spRBit]        ; R14 = no of bits used in r.h. word
        ADD     R3,R3,R14
        LDR     R14,[R2,#spWidth]       ; R14 = no of words in the middle + 1
        ADD     R3,R3,R14,LSL #5        ; R3 = total width (bits)
        LDR     R14,save_inlog2bpc
        MOV     R4,R3,LSR R14           ; R4 = width (pixels)
        Debug   pt,"Input sprite width =",R4
        SUB     R6,R6,#4
        BL      mulR4                   ; scale to output pixels
        ADD     R6,R6,#4
        RSBS    R3,R4,#32               ; R3 = x-coordinate to plot sprite at
        MOVLT   R3,#0                   ; keep lhs visible!

        LDR     R4,[R2,#spHeight]
        ADD     R4,R4,#1                ; R4 = input height (pixels)
        BL      mulR4
        Push    "R3,R4"                 ; R4 = height of pointer (pixels)

        MOV     R3,R3,LSL #2            ; sprite is in mode 1 (log2px = 2)
        MOV     R4,R4,LSL #2            ; sprite is in mode 1 (log2py = 2)
        RSB     R4,R4,#32*4             ; make top-left match up

        MOV     R5,#0

        LDR     R14,modeflags

        TEQ     R7,#0                   ; PRM 1-780 zero means no table supplied
        BNE     %FT05
        TST     R14,#Flag_HiResMono
        ADRNE   R7,hiresmonottr         ; use built in table iff R7=0 and in hi-res mono
05
        Debug   pt,"Input sprite address,x,y =",R2,R3,R4
        SWI     XOS_SpriteOp
        Pull    "R10,R11"               ; R10,R11 = xcoord,height

        ADD     R14,sp,#4*4             ; skip factor block
        LDMIA   R14,{R0-R3}             ; SwitchOutput back to old parameters
        Debug   pt,"Switching back to",R0,R1,R2,R3
        SWI     XOS_SpriteOp

        ADD     R2,sp,#8*4+saExten      ; R2 --> output sprite
        Debug   pt,"Output sprite address =",R2
        LDR     R5,[R2,#spImage]
        ADD     R14,R2,R5               ; R14 --> sprite image
        Debug   pt,"Output sprite image =",R14
        MOV     R5,#0                   ; stick in reason code (= 0)
        Push    "R5,R6,R14"             ; [sp + 8] = sprite ptr
        CMP     R11,#32
        MOVGT   R11,#32
        ADD     R1,sp,#2                ; need correct alignment
        STRB    R11,[R1,#3]
        MOV     R14,#16/2               ; R14 = width (bytes)
        STRB    R14,[R1,#2]

        ADD     R14,sp,#3*4 + 8*4 + tempareasize + 3*4
        LDMIA   R14,{R3-R5}             ; shape no, active X, active Y
        AND     R3,R3,#spp_ptrno        ; bottom 4 bits = shape no
        STRB    R3,[R1,#1]
        SUB     R6,R6,#4                ; fool it so x-values used
        BL      mulR4
        ADD     R4,R4,R10               ; allow for sprite origin
        STRB    R4,[R1,#4]
        MOV     R4,R5
        ADD     R6,R6,#4                ; fool it so y-values used
        BL      mulR4
        STRB    R4,[R1,#5]

      [ debugpt
        LDMIA   sp,{R3-R5}
        Debug   pt,"OS_Word block =",R3,R4,R5
      ]
        MOV     R0,#OsWord_DefinePointerAndMouse
        SWI     XOS_Word
        ADD     sp,sp,#3*4 + 8*4 + tempareasize   ; correct stack
99
        LDR     R14,[sp],#4
        STR     R14,spritecode          ; may be needed later
        Pull    "R1-R7,PC"

hiresmonottr    DCB     0,1,3,3         ; translate colour 2 into colour 3
                ALIGN

sillyname       DCB     "pointer",0
                ALIGN

go_setpointerpalette
        Push    "R1-R5,LR"

        Debug   pt,"Input sprite:",#spritecode,R1,R2
        BL      findsprite              ; R2 --> sprite definition
        Debug   pt,"Input sprite address:",R2
        LDRVC   R0,[R2,#spMode]
        MOV     R1, R2
        BLVC    readspritevars          ; get save_inlog2bpp
        Pull    "R1-R5,PC",VS
;
        MOV     R14,#1
        LDR     R5,save_inlog2bpp
        MOV     R5,R14,LSL R5           ; R5 = no of elements in ttr table

        MOV     R3,#3                   ; start with colour 3
01
        TEQ     R7,#0                   ; PRM 1-780 zero means no table supplied
        MOVEQ   R4,R3                   ; substitute 1:1 translation
        BEQ     %FT03                   ; got colour
        MOV     R4,#0
02
        LDRB    R14,[R7,R4]
        TEQ     R14,R3                  ; found it?
        BEQ     %FT03
        ADD     R4,R4,#1
        CMP     R4,R5                   ; R5 = no of entries in table
        BCC     %BT02
        B       %FT04                   ; don't bother programming palette
03
        Debug   pt,"Programming palette entry",R4
        MOV     R14,#spPalette+1        ; point at RGB of first flash state
        ADD     R4,R14,R4,LSL #3        ; 8 bytes per palette entry
        LDR     R14,[R2,#spImage]
        CMP     R14,R4                  ; is there a palette entry for this?
        BLE     %FT04
        SWI     XOS_WriteI+19           ; program palette
        MOV     R0,R3
        SWI     XOS_WriteC              ; mouse colour number
        SWI     XOS_WriteI+25           ; program mouse colour
        ADD     R0,R2,R4
        MOV     R1,#3
        SWI     XOS_WriteN              ; R,G,B
04
        SUBS    R3,R3,#1
        BNE     %BT01                   ; forget colour 0

        Pull    "R1-R5,PC"

go_setpointernum
        Push    "R1-R3,LR"

        MOV     R0,#&6A
        AND     R1,R3,#spp_ptrno        ; pointer shape
        MOV     R2,#0                   ; pointer linked to mouse
        SWI     XOS_Byte

        Pull    "R1-R3,PC"

        ; Scale to output pixels
        ; Entry: R4 = input coordinate (pixels)
        ;        R6 --> scale factors ([R6,#4] and [R6,#12])
        ; Exit:  R4 = R4 * R6!4 / R6!12
mulR4
        Push    "R5,R11,LR"
        LDR     R14,[R6,#1*4]           ; y-magnification
        MUL     R11,R14,R4
        LDR     R5,[R6,#3*4]            ; y-division
        DivRem  R4,R11,R5, R14          ; R4 = output height
        Pull    "R5,R11,PC"

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteReason_PaintCharScaled
; ----------------------------
; Entry: R1 = character code
;        R3,R4 = x,y coordinates
;        R6 --> scale factors
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_PaintCharScaled
        Push    "R1-R9,LR"

        STRB    R1,charblock
        ADRL    R1,charblock
        MOV     R0,#OsWord_ReadCharacterDefinition
        SWI     XOS_Word

        BLVC    readvduvars
        BVS     exitchar
        ;
        ; contruct a sprite header in module workspace
        ;
        MOV     R10, #0
        MOV     R11, #7                 ; Mode 0 sprite starting uses bits 0-7

        ADR     R14,areahdr             ; 3 words for area header,
        LDMIA   R14,{R1-R3,R6-R8}       ; and 3 for sprite name
        MOV     R5,#spriteSize+4*8*2    ; R5 = 44 + 2*8 rows of 1 word
        ADD     R4,R5,#spriteCB-areaCB  ; R4 = saFree
        ADR     R14,areaCB
        Debug   ch,"areaCB = &",R14
        STMIA   R14!,{R1-R3,R4,R5,R6-R8,R10}    ; up to spWidth
        MOV     R8,#7                   ; R8 = spHeight (no of rows - 1)
        MOV     R9,#0                   ; R9 = spLbit
        STMIA   R14!,{R8,R9,R11}        ; R11 = spRbit
        MOV     R8,#spriteSize
        ADD     R9,R8,#4*8              ; R9 = R8 + 8 rows of 1 word
        MOV     R11, #0                 ; mode 0 sprite
        STMIA   R14!,{R8,R9,R11}        ; R14 --> sprite pixels
        SUB     R7,R9,R8
        ADD     R7,R14,R7               ; R7 --> sprite mask
        ;
        ; now construct a sprite mask from the character definition
        ;
        ADRL    R10, charblock+1
        MOV     R5, #8
01
        LDRB    R9, [R10], #1
        MOV     R8, R9, LSR #7
        TST     R9, #&40
        ORRNE   R8, R8, #&02
        TST     R9, #&20
        ORRNE   R8, R8, #&04
        TST     R9, #&10
        ORRNE   R8, R8, #&08
        TST     R9, #&08
        ORRNE   R8, R8, #&10
        TST     R9, #&04
        ORRNE   R8, R8, #&20
        TST     R9, #&02
        ORRNE   R8, R8, #&40
        TST     R9, #&01
        ORRNE   R8, R8, #&80
        STR     R8, [R7], #4
        SUBS    R5, R5, #1
        BNE     %BT01
        ;
        ; now set up the parameters for a scaled mask plot
        ;
        MOV     R14,#VduDriverWorkSpace + FgEcfOraEor
        STR     R14,save_ecflimit       ; char painting uses fg ecf pattern

        ADR     R1,areaCB
        ADD     R2,R1,#spriteCB-areaCB
        ADD     R14,R13,#2*4            ; salvage params from stack
        LDMIA   R14,{R3-R7}

        MOV     R0,#&200                ; indicates that R2 --> sprite defn
        ADD     R0,R0,#SpriteReason_PlotMaskScaled

        LDR     R14,spritecode
        Push    "R14"
        STR     R0,spritecode
        MOV     r0, #SpriteReason_PaintCharScaled
        BL      Go_PlotMaskScaled
        Pull    "R14"
        STR     R14,spritecode

exitchar
        Pull    "R1-R9,PC"

areahdr
        DCD     spriteAreaSize
        DCD     1
        DCD     spriteCB - areaCB
spritehdr
        DCB     "character",0,0,0       ; Note - 12 chars long exactly
        ALIGN

        ;
        ; Read Vdu Variables
        ; also process the ones that the OS doesn't always provide
        ;
vduinputbuffer
        DCD     VduExt_XEigFactor               ; log2 pixels per unit (x)
        DCD     VduExt_YEigFactor               ; log2 pixels per unit (y)
        DCD     VduExt_Log2BPP                  ; for no. of colours
        DCD     VduExt_Log2BPC                  ; for double-pixel modes

        DCD     VduExt_OrgX
        DCD     VduExt_OrgY
        DCD     VduExt_GWLCol                   ; graphics window
        DCD     VduExt_GWBRow
        DCD     VduExt_GWRCol
        DCD     VduExt_GWTRow

        DCD     VduExt_LineLength
        DCD     VduExt_ScreenStart
        DCD     VduExt_YWindLimit

        DCD     VduExt_ModeFlags                ; for hi-res mono checking

        DCD     -1

readvduvars
        Push    "R0-R4,LR"
        ;
        ; read real vdu variables (mode is only required for PaintChar)
        ;
        ADR     R0,vduinputbuffer
        ADR     R1,vduoutputbuffer
        SWI     XOS_ReadVduVariables
        BVS     %FT99

        MOV     R0,#1
        LDR     R14,Log2bpc
        MOV     R14,R0,ASL R14
        STR     R14,BPC                 ; bpc = 2^log2bpc
        LDR     R14,Log2bpp
        MOV     R14,R0,ASL R14
        STR     R14,BPP                 ; bpp = 2^log2bpp

        Debug   sc,"ScreenStart,YWindLimit,LineLength,bpc =",#screenstart,#ywindlimit,#linelength,#BPC
99
        STRVS   R0,[R13]
        Pull    "R0-R4,PC"

        ;
        ; read mode-specific info for mode (R0)
        ;
readspritevars
        ; NB saved registers extended to include R3 purely for jpeg.
        ; R0=the sprite's mode word
        ; R1->the sprite
        Push    "R1-R3,LR"

      [ jpeg
      [ {TRUE}

        ; look for a JPEG sprite, and if you find it lie about these dimensions.
        LDR     R2,[R1,#spImage]        ; get offset to image data
        ADD     R2,R2,R1                ; get address of image data
        ADD     R2,R2,#4                ; skip past compression ID word

        ;        LDRB    R3,[R2,#6]              ; get 6th byte
        ;        CMP     R3,#'J'                 ; give me a J?
        ;        BNE     rsv_not_jpeg_file
        ;        LDRB    R3,[R2,#7]              ; if so, get 7th byte
        ;        CMP     R3,#'F'                 ; give me a F?
        ;        LDREQB  R3,[R2,#8]              ; if so, get 8th byte
        ;        CMPEQ   R3,#'I'                 ; give me a I?
        ;        LDREQB  R3,[R2,#9]              ; if so, get 9th byte
        ;        CMPEQ   R3,#'F'                 ; give me a F?
        ;        BNE     rsv_not_jpeg_file       ; what does that spell?
        ; This test is watered down a bit, to allow slightly more deviant files
        ; (eg. the ones that Tony Sumner finds on the net). Anything really wrong will
        ; still get caught later on in the C code. Still, note this increases (slightly)
        ; the risk that an innocent normal sprite will get accused of being JPEG.
        ; Alternative test:
        LDRB    R3,[R2]                 ; load first byte
        CMP     R3,#&ff                 ; is it ff?
        LDREQB  R3,[R2,#1]              ; load second byte
        CMPEQ   R3,#&d8                 ; is it d8?
        LDREQB  R3,[R2,#2]              ; load third byte
        CMPEQ   R3,#&ff                 ; is it ff?
        BNE     rsv_not_jpeg_file

        ; We have found a JPEG file - pretend it's a 32bpp sprite.
        Debug   in,"It's a JPEG file"
        LDR     R3,spritecode           ; check we're doing a PutSpriteScaled
        AND     R3,R3,#255              ; mask out to get op code
        CMP     R3,#SpriteReason_PutSpriteScaled
        Pull    "R1-R3,LR",NE           ; if not, discard stuff from readspritevars
        Pull    "R0-R12,PC",NE          ; and exit doing nothing.
                                        ; >>>> should produce an error return here.

        MOV     R3,#5                   ; log2 of 32
        STR     R3,save_inlog2bpc
        STR     R3,save_inlog2bpp
        MOV     R3,#32                  ; bits per pixel
        STR     R3,save_inbpp
        MOV     R3,#1                   ; log2 of 2 OS-units
        STR     R3,inlog2px             ; pretend to be VGA-size pixels
        STR     R3,inlog2py             ; pretend to be VGA-size pixels
        STR     R3,is_it_jpeg           ; mark as a JPEG sprite
        STR     R0,inmode               ; the mode vars are correct for mode 20 (old format) or new format compressed sprites.
        STR     R1,save_sprite          ; so that later code can find the JPEG data again
        Pull    "R1-R3,PC"
rsv_not_jpeg_file
        ; if the last sprite was jpeg but this one is not then we MUST reload the sprite variables.
        LDR     R3,is_it_jpeg           ; was the last one JPEG? 0 -> not JPEG
        CMP     R3,#0
        MOVNE   R3,#0                   ; if the previous one was JPEG
        STRNE   R3,is_it_jpeg           ;   mark as not a JPEG sprite this time
        MOVNE   R14,#-1                 ;   force reload of sprite mode vars
        LDREQ   R14,inmode              ; else, vars reflect this sprite's mode
      |
        MOV     R14,#0                  ; jpeg stuff outside jpeg condition? stupid? (GPS)
        STR     R14,is_it_jpeg          ; ditto
        LDR     R14,inmode              ; check for sprite vars already right
      ]
      ]

        TEQ     R0,R14
        Pull    "R1-R3,PC",EQ           ; already have these variables!
        STR     R0,inmode

        MOV     R1,#VduExt_Log2BPC
        SWI     XOS_ReadModeVariable
        SETV    CS

        STRVC   R2,save_inlog2bpc       ; input log2(bytes per char)
        MOVVC   R1,#VduExt_Log2BPP
        SWIVC   XOS_ReadModeVariable
        STRVC   R2,save_inlog2bpp       ; input log2(bits per pixel)
        MOVVC   R1,#VduExt_XEigFactor
        SWIVC   XOS_ReadModeVariable
        STRVC   R2,inlog2px
        MOVVC   R1,#VduExt_YEigFactor
        SWIVC   XOS_ReadModeVariable
        STRVC   R2,inlog2py
        Pull    "R1-R3,PC",VS

        MOV     R14,#1
        LDR     R1,save_inlog2bpp
        MOV     R14,R14,ASL R1
        STR     R14,save_inbpp

        Debug   in,"Input log2bpc/p, bpp = ",#save_inlog2bpc,#save_inlog2bpp,#save_inbpp

        Pull    "R1-R3,PC"

        ;
        ; Find sprite address given areaCBptr/name
        ; Entry:  [spritecode] = reason code (including bits above bit 7)
        ;         R1 = areaCBptr
        ;         R2 --> sprite name
        ; Exit:   R2 --> sprite definition
        ;
findsprite
        Push    "LR"
        LDR     R0,spritecode
        BL      getspritename
        BLVC    getspriteaddr
        Pull    "PC"

        ; Entry:  R0 = original sprite code
getspritename
        Push    "R0-R1,R3-R4,LR"

        BICS    R14,R0,#&FF             ; R0 < 256 ==> system sprite (can't!)
        TEQ     R14,#&200               ; R2 --> sprite already
        STREQ   R2,spritename           ; if so, 1st word is sprite addr
        BEQ     %FT99

        ADRL    R3,spritename
        ADD     R4,R3,#12
01
        LDRB    R0,[R2],#1
        CMP     R0,#" "                 ; ignore control characters and <space> too!
        BLS     %FT02
        ASCII_LowerCase R0,R14
        STRB    R0,[R3],#1
        CMP     R3,R4                   ; terminate after 12 characters
        BCC     %BT01
02
        MOV     R0,#0
03
        CMP     R3,R4                   ; pad with nulls
        STRCCB  R0,[R3],#1
        BCC     %BT03
99
        STRVS   R0,[R13]
        Pull    "R0-R1,R3-R4,PC"
        MakeSpriteErrorBlock NoWorkSpace,,NoWork

        ;
        ; scan the sprite list to find the address
        ;
getspriteaddr
        Push    "R1,R3-R9,LR"

        BICS    R14,R0,#&FF             ; R0 < 256 ==> system sprite (can't!)
        BNE     %FT00

        MOV     R0,#3
        SWI     XOS_ReadDynamicArea
        TEQ     R1,#0
        ADREQ   R0, ErrorBlock_NoWorkSpace   ; can't do anything with this!
        addr    r1, Title, EQ
        BLEQ    copy_error_one          ; Always sets the V bit
        BVS     %FT99

        MOV     R1,R0                   ; R1 -> system sprite area
        STR     R1,[sp]                 ; return for AppendSprite
00
        TEQ     R14,#&200               ; R2 --> sprite already
        BEQ     %FT99

        ADRL    R14,spritename
        LDMIA   R14,{R3,R4,R5}

        LDR     R14,[R1,#saFree]
        ADD     R9,R1,R14               ; R9 --> free area
        LDR     R14,[R1,#saFirst]
        ADD     R2,R1,R14               ; R2 --> first sprite
04
        CMP     R2,R9
      [ debuger
        BCC     %FT01
        ADR     R14,spritename
        DebugS  er,"Sprite doesn't exist ",R14
01
      ]

        BLCS    get_sprite_doesnt_exist_error  ; r0-> error block, V set
        BVS     %FT99

        LDMIA   R2,{R1,R6,R7,R8}       ; get link plus name
        TEQ     R6,R3
        TEQEQ   R7,R4
        TEQEQ   R8,R5
        ADDNE   R2,R2,R1
        BNE     %BT04
99
        Pull    "R1,R3-R9,PC"

        ; makepalette16bpp
        ; convert palette to 16bpp for output to 16bpp mode plotting directly from palette
        ; if this is not done, the spriteextend blitter will run out of registers!
makepalette16bpp
      [ debugso
        Push    "R1-R7,LR"
        LDR     r7, sprite_doesnt_exist_error
        Debug   so, "converting palette..."
      |
        Push    "R1-R6,LR"
      ]
        LDR     r1, save_inbpp
        MOV     r2, #1
        MOV     r1, r2, LSL r1        ; number of entries in palette
        Debug   so, "going to do this many entries = ",R1
        LDR     r2, trns_palette      ; pointer to palette data
        ADRL    r3, newtranstable     ; where to store altered palette
        STR     r3, trns_palette      ; where to store altered palette
01
        LDR     r4, [r2], #8
        MOV     r4, r4, LSR #8                ; BBGGRR00 to 00BBGGRR
                                              ; r4 = in2, r5 = im1, r6 = ttr
                                              ;       fedcba9876543210 fedcba9876543210
                                              ; in2 = 00000000bbbbbbbb ggggggggrrrrrrrr
        AND     r5,r4,#&F80000                ; im1 = 00000000bbbbb000 0000000000000000
        MOV     r6,r5,LSL #7                  ; ttr = 0bbbbb0000000000
        AND     r5,r4,#&F800                  ; im1 = 0000000000000000 ggggg00000000000
        ORR     r6,r6,r5,LSL #10              ; ttr = 0bbbbbggggg00000
        AND     r5,r4,#&F8                    ; im1 = 0000000000000000 00000000rrrrr000
        ORR     r4,r6,r5,LSL #13              ; in2 = 0bbbbbgggggrrrrr
        ; NB result in top half of register
        ; Needs two work registers
        MOV     r4, r4, LSR #16
        Debug   so, "gonna store thingy at thingy",R4,R3
        STR     r4, [R3],#4
        SUBS    r1, r1, #1
        Debug   so, "R1 now ",R1
        BNE     %BT01
      [ debugso
        ; check workspace word at end of 'newtranstable' space to confirm we haven't walked off the edge
        LDR     r6, sprite_doesnt_exist_error
        Debug   so, "converted palette...check words are ",R6, R7
        Pull    "R1-R7,PC"
      |
        Pull    "R1-R6,PC"
      ]

        ;
        ; Fatal exits for divide by zero and other panics
        ;
        MakeInternatErrorBlock DivZero
diverror
        ADR     R0, ErrorBlock_DivZero
        addr    r1, Title
        BL      copy_error_one          ; Always sets the V bit

exitbiggie
        Pull    "R1-R9,PC"

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteOp_PutSpriteGreyScaled
; ----------------------------
; Entry: [spritecode] = reason code
;        R1 --> areaCBptr
;        R2 --> sprite name
;        R3,R4 = coords
;        R5 = 0
;        R6 --> scaling factors
;        R7 --> pixel translation table
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_PutSpriteGreyScaled
        Push    "LR"
        ADR     R0, ErrorBlock_NoGrScl
        BL      copy_error_one          ; also sets V
        Pull    "PC"

        MakeSpriteErrorBlock NoGrScl

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteOp_PlotMaskScaled
; -----------------------
; Entry: [spritecode] = reason code
;        R1 --> areaCBptr
;        R2 --> sprite name
;        R3,R4 = coords
;        R6 --> scaling factors
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_PlotMaskScaled
        Push    "R1-R9,LR"
        MOV     R5,#8                   ; avoid masko being knackered later
        MOV     R7,#0                   ; no pixel translation
        MOV     R8,#0                   ; no calibration table
        B       %FT01

getbgaction
        DCD     VduExt_GPLBMD
        DCD     -1

getfgaction
        DCD     VduExt_GPLFMD
        DCD     -1

getbgcolour
        DCD     VduExt_GBCOL
        DCD     -1

        MakeSpriteErrorBlock BadTranslation,,BadTran

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteOp_PutSpriteScaled
; ------------------------
; Entry: [spritecode] = reason code
;        R1 --> areaCBptr
;        R2 --> sprite name
;        R3,R4 = coords
;        R5 = 0
;        R6 --> scaling factors
;        R7 --> pixel translation table
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_PutSpriteScaled
        Push    "R1-R9,LR"
        MOV     R8,#0                   ; no printer calibration table
01
        Debug   in,"Draw sprite: R0,R1,R2 =",R0,R1,R2
        Debug   in,"Coords, gcol, &scale, &ttr =",R3,R4,R5,R6,R7
        Debug   in,"Calibration table =",R8


        CLRPSR  I_bit, R14              ; re-enable interrupts
      [ flagbit
        MOV     R14, R5, LSR #4
        STR     R14, trns_flags2         ; store flags
      [ widetrans
        Debug   so,"flags are ",R14
        BICS    R14, R14, #flg2_ignorettr + flg2_widetrans + flg2_ditheron
      |
        BICS    R14, R14, #flg2_ignorettr + flg2_ditheron
      ]
        Debug   so,"flags are ",R14
        ADRNEL  R0, ErrorBlock_BadFlags
        addr    r1, Title, NE
        BLNE    copy_error_one          ; Always sets the V bit
        BVS     exitbiggie
      ]
        AND     R5,R5,#&0F              ; only bottom 4 bits are interesting

        STR     R8,calibration_table

        ;
        ; Check to see if truecolour sprites should be dithered
        ;
      [ flagbit
        LDR     r0, trns_flags2
        TST     r0, #flg2_ditheron
        MOVEQ   r0, #0
        MOVNE   r0, #1
        STR     r0, dither_truecolour
      ]
        ;
        ; see if reason code indicated a sprite name or sprite pointer in R2
        ;

        BL      findsprite              ; R2 --> sprite

        MOVVC   R1,R2                   ; now R1 --> sprite

        ; read input/output mode variables

        BLVC    readvduvars
        LDRVC   R0,[r1,#spMode]
        BLVC    readspritevars
        BVS     exitbiggie
        Debug   cc,"read sprite vars",R1

        ; Alternate entry point from JPEG plotting
putsprscaled_frompjs
        LDR     R0,[R1,#spMode]         ; get sprite's original mode
        STR     R0,save_spr_type
        ;
        ; read scaling factors (set up default if necessary)
        ;
        CMP     R6,#0
        LDMNEIA R6,{R8-R11}
        BNE     %FT01

        MOV     R8, #1
        MOV     R9, #1
        MOV     R10, #1
        MOV     R11, #1
01
        ;let's try to rationalise the scale factors...
        ; *************************************************
        ; ***  DivRem - Integer division and remainder  ***
        ; ***  rc := ra DIV rb; ra := ra REM rb         ***
        ; ***  rb preserved, rtemp corrupt              ***
        ; ***  DivRem   rc, ra, rb, rtemp               ***
        ; *************************************************
        Push    "R5-R7"
        MOV     R5, R8
        DivRem  R7, R5, R10, R6
        CMP     R5, #0
        MOVEQ   R8, R7
        MOVEQ   R10, #1
        MOV     R5, R9
        DivRem  R7, R5, R11, R6
        CMP     R5, #0
        MOVEQ   R9, R7
        MOVEQ   R11, #1
        Pull    "R5-R7"

        STR     R8,save_xmag
        STR     R9,save_ymag
        STR     R10,save_xdiv
        STR     R11,save_ydiv
        Debug   in,"x mag/div, y mag/div:",r8,r9,r10,r11

      [ ignore_ttr
        ;
        ; check for a ttr. If it's present and the sprite has a full entry palette set up to
        ; use that instead.
        ;
        MOV     R14, #0
        STR     R14, trns_palette

        LDR     R14,[R1,#spImage]
        CMP     R14,#SpriteCBsize
        BEQ     has_no_palette

        LDR     R0,[R1,#spTrans]
        CMP     R0,#SpriteCBsize
        BEQ     has_no_palette

        ;validate it (to exclude 8bpp without full palettes)
        ;test is that palette size should be 8*ncolours

        ;find the lower of the sprite start and mask start
        CMP     R14,R0
        MOVCS   R14,R0
        SUB     R14,R14,#SpriteCBsize

        MOV     R14,R14,LSR #3 ;divide by 8 for number of palette entries
        MOV     R0,#1
        LDR     R9,save_inbpp
        MOV     R0,R0,ASL R9

        CMP     R0,R14
        BNE     has_no_palette

      [ flagbit
        LDR     R14, trns_flags2
        Debug   so,"Checking ttr flag", R14
        TST     R14, #flg2_ignorettr

        BEQ       has_no_palette   ;use ttr and ignore any palette

01
      |
        Debug   so,"ttr flag is not set"
        B       has_no_palette
      ]
        Debug   so, "We're going to plot from palette"
        LDR     R14,[R1,#spTrans]
        ADD     R14,R1,#SpriteCBsize
        STR     R14, trns_palette
        LDR     R14, BPP
        CMP     R14, #16
        MOVEQ   r7, #0                   ; junk any translation table we were given to stop checktrans overwriting 16bpp palette
        BLEQ    makepalette16bpp
has_no_palette
      ]

        ;
        ; validate supplied translation table (if any)
        ;
        Debug   cc,"about to check trans table",R1
        CMP     R7,#0                    ; 0 ==> no translation
        BLNE    checktrans
        BVS     exitbiggie
        Debug   cc,"trans table is OK",R1
      [ ignore_ttr
        ; if doing a sprite of <16bpp to >8bpp, and it has a palette, change the ttr pointer
        ; to point at the palette data instead of the ttr data. Note that the two are different
        ; formats, so there is also a different plotting routine to include too...

        LDR     R14, BPP                               ; output bpp
        CMP     R14, #16
        MOVCC   R14, #0
        STRCC   R14, trns_palette
        BCC     %FT45
        LDR     R14, save_inbpp
        CMP     R14, #16
        MOVCS   R14, #0
        STRCS   R14, trns_palette
        BCS     %FT45
        LDR     R14, trns_palette
        TEQ     R14, #0
        MOVNE   R7,R14
        MOVEQ   R14,#0
        STREQ   R14, trns_palette ;only non-zero if going to use this

        ; trns_palette doubles as a pointer to the palette up this far, and then becomes a
        ; compilation flag for the macro generation (with the value being passed in as the
        ; ttr address)

45
      ]

        STR     R7,ColourTTR
        CMP     R7,#0
        BNE     notrans

        LDRB    R14,spritecode          ; R14 = bottom 8 bits of reason code
        TEQ     R14,#SpriteReason_PlotMaskScaled
      [ jpeg
        LDRNE   R14,is_it_jpeg
        CMPNE   R14,#1
        Debug   gs,"1. is it JPEG?",R0
      ]
        LDRNE   R14,save_inbpp
        LDRNE   R0,BPP
        TEQNE   R14,R0                  ; OK if same bpp or mask plotting

        ;however, don't error if going 16>32 or 32>16
        BEQ     notrans ;dispose of the equal case

        CMP     R0,#16
        CMPEQ   R14,#32
        BEQ     notrans

        CMP     R0,#32
        CMPEQ   R14,#16
        BEQ     notrans
errtrans
        ADRL    R0,ErrorBlock_BadTranslation   ; different bpp ==> must have table
errexitscaled
        addr    r1, Title
        BL      copy_error_one          ; Always set the V bit
        Debug   so,"error from trans table"
        BVS     exitbiggie
notrans
        Debug   so,"Checked out trans table palette"
        ;
        ; try to optimise by using existing sprite plot code
        ; can only be done if bpp equal, no trans, no scaling and output not to sprite
        ;
      [ debug
        TEQ     R7,#0
        LDREQ   R14,save_inbpp
        LDREQ   R0,BPP
        TEQEQ   R14,R0                  ; if same bpp, try to optimise

        LDREQ   R14,vduspritename
        TEQEQ   R14,#0
        LDR     R6,save_xmag            ; load always!
        LDR     R7,save_ymag            ; load always!
        LDREQ   R8,save_xdiv
        LDREQ   R9,save_ydiv
        TEQEQ   R6,R8
        TEQEQ   R7,R9
        BNE     %FT87
87
      |
        TEQ     R7,#0
        LDREQ   R14,save_inbpp
        ANDS    R7, R14, #32+16         ; If we're dealing with deep sprites then the kernel is slower!!!
        LDREQ   R0,BPP
        TEQEQ   R14,R0                  ; if same bpp, try to optimise

        LDREQ   R14,vduspritename
        TEQEQ   R14,#0
        LDR     R6,save_xmag            ; load always!
        LDR     R7,save_ymag            ; load always!
        LDREQ   R8,save_xdiv
        LDREQ   R9,save_ydiv
        TEQEQ   R6,R8
        TEQEQ   R7,R9
        BNE     cantdoinOS

      [ jpeg
        LDREQ   R0,is_it_jpeg
        TEQEQ   R0,#0
        Debug   gs,"is it JPEG?",R0
        BNE     cantdoinOS
      ]
        ;
        ; if all parameters are ineffective, call the OS routine!
        ;
        LDR     R0,spritecode
        AND     R14,R0,#&FF             ; R14 = reason code only
        BIC     R0,R0,#&FF              ; R0 = code except for reason code
        TEQ     R14,#SpriteReason_PutSpriteScaled
        ORREQ   R0,R0,#SpriteReason_PutSpriteUserCoords
        BEQ     %FT01
        TEQ     R14,#SpriteReason_PlotMaskScaled
        ORREQ   R0,R0,#SpriteReason_PlotMaskUserCoords
        LDREQ   R14,save_ecflimit       ; is this background plotting?
        TEQEQ   R14,#VduDriverWorkSpace + BgEcfOraEor
        BNE     cantdoinOS
01
        LDMIA   R13,{R1,R2}             ; get original R1,R2
        Debug   so,"doing it in OS"
        BL      Go_SpriteOp             ; call the OS version if there is one
        Pull    "R1-R9,PC"
cantdoinOS
      ]
        Debug   so,"we can't do it in OS"
        ;
        ; convert coords to internal pixels
        ; R6,R7 = x,y magnification
        ;
        LDR     R14,orgx
        ADD     R3,R3,R14
        LDR     R14,log2px
        MOV     R3,R3,ASR R14
        LDR     R14,orgy
        ADD     R4,R4,R14
        LDR     R14,log2py
        MOV     R4,R4,ASR R14           ; convert to internal form
        ;
        ; bodge x-coords (inc. graphics window) so we can forget double-pixels
        ;
        LDR     R14,Log2bpc
        LDR     R0,Log2bpp
        SUBS    R14,R14,R0
        MOVNE   R3,R3,LSL R14
        LDRNE   R0,gwx0
        MOVNE   R0,R0,LSL R14
        STRNE   R0,gwx0
        LDRNE   R0,gwx1
        ADDNE   R0,R0,#1                ; make exclusive
        MOVNE   R0,R0,LSL R14
        SUBNE   R0,R0,#1                ; make inclusive again
        STRNE   R0,gwx1
        ;
        ; bodge x multiplier and divisor
        ;
        MOVNE   R6,R6,LSL R14
        STRNE   R6,save_xmag

        LDR     R14,save_inlog2bpc
        LDR     R0,save_inlog2bpp
        SUBS    R14,R14,R0
        LDRNE   R0,save_xdiv
        MOVNE   R0,R0,LSL R14
        STRNE   R0,save_xdiv
        ;
        ; R8, [save_ysize] <-- size of sprite in pixels (NOT double-pixels)
        ;
        LDR     R11,save_inlog2bpp      ; R11 = log2 ( bpp )

        LDR     R0,[R1,#spLBit]
        Debug   so, "spLBit is ? = ",R0
        LDR     R2,[R1,#spRBit]
        Debug   so, "spRbit is ? = ",R2
        SUB     R2,R2,R0
        ADD     R2,R2,#1                ; make inclusive
        Debug   so, "R2 is ? = ",R2
        LDR     R0,[R1,#spWidth]
        Debug   so, "Spwidth = ",R0
        ADD     R8,R2,R0,ASL #5         ; 32 bits per word
        Debug   so, "R8 = ",R8
      [ jpeg
        ; >>> old-format (ie pilot development) JPEG files are mode 20 sprites, with JPEG data.
        ; For such a sprite the 'width' is quoted as if log2bpp were 2, ie 4bpp sprites,
        ; so that Draw gets the right width for the sprite. But this means we must shift
        ; right by 2 rather than by r11, which will be 5.
        Debug   gs, "X-size 1 = ",R8
        LDR     R14,is_it_jpeg
        CMP     R14,#0
        MOVEQ   R8,R8,ASR R11
        BEQ     %FT01
        ; It's JPEG - check for mode 20 or other.
        LDR     R14,inmode
        CMP     R14,#20
        MOVEQ   R8,R8,ASR #2            ; temp format - ASR by 2
        MOVNE   R8,R8,ASR R11           ; kosher new Medusa Sprite format - ASR by 5
        Debug   gs, "X-size 2 = ",R8
        ; >>>> This option can be thrown away when we're sure that pilot-format JPEG files
        ;      don't exist any more.

        ;        ; Finally, JPEG files claim to have twice as many pixels as they actually do.
        ;        ; This is in case we want to do interpolation in the X direction on scaling up.
        ;        MOV     R8,R8,LSL #1            ; R8 = no of pixels
1
      |
        MOV     R8,R8,ASR R11           ; R8 = no of pixels
      ]
        STR     R8,save_inputxsize

        Debug   so, "X-size 3 = ",R8
        LDR     R14,[R1,#spHeight]
        ADD     R14,R14,#1
        STR     R14,save_inputysize

        ;
        ; do x-clipping
        ; Entry:  R6 = x-magnification
        ;         R8 = input x-size
        ; Exit:   R9 = xleft
        ;         [save_xcoord] = start x-coord on screen
        ;         [save_xcount] = initial xcount
        ;         [save_xsize] = output x-size
        ;
        Debug   cc,"Do x-clipping",r6,r8

        LDR     R14,gwx0
        SUBS    R9,R14,R3
        MOVLT   R9,#0                   ; R9 = no of pixels to skip on left
        ADD     R14,R3,R9
        STR     R14,save_xcoord

        MUL     R2,R8,R6                ; R2 = x-size * x-mag
        LDR     R0,save_xdiv            ; R0 = x-div
        TEQ     R0,#0
        BEQ     diverror
        DivRem  R8,R2,R0, R14           ; R8 = no of output pixels
        LDR     R14,gwx1
        ADD     R14,R14,#1
        SUB     R14,R14,R3
        CMP     R8,R14                  ; clip on right
        MOVGT   R8,R14
        SUBS    R8,R8,R9                ; R8 = number of output pixels
        BLE     exitbiggie              ; none of sprite is visible
        STR     R8,save_xsize

        LDR     R14,save_xdiv
        MUL     R10,R9,R14              ; R10 = initial count
        TEQ     R6,#0
        BEQ     diverror
        DivRem  R9,R10,R6, R14          ; R9 = initial INPUT pixel coord
        RSB     R10,R10,R6              ; R10 = amount of 1st input pixel left
        STR     R10,save_xcount

        Debug   in,"xcoord,xleft,xcount,xsize =",#save_xcoord,R9,#save_xcount,#save_xsize
        ;
        ; do y-clipping
        ; Entry:  [save_inputysize] = input y-size
        ; Exit:   R10 = ybot
        ;         [save_ycoord] = start y-coord on screen
        ;         [save_ycount] = initial y-counter
        ;         [save_ysize] = output y-size
        ;
        Debug   cc,"Do y-clipping",r10
        LDR     R14,gwy0
        SUBS    R10,R14,R4
        MOVLT   R10,#0                  ; R10 = no of pixels to skip at bottom
        ADD     R14,R4,R10
        STR     R14,save_ycoord

        LDR     R8,save_inputysize
        MUL     R2,R8,R7                ; R2 = y-size * ymag
        LDR     R0,save_ydiv
        TEQ     R0,#0
        BEQ     diverror
        DivRem  R8,R2,R0, R14           ; R8 = no of output pixels
        LDR     R14,gwy1
        ADD     R14,R14,#1
        SUB     R14,R14,R4
        CMP     R8,R14
        MOVGT   R8,R14
        SUBS    R8,R8,R10               ; number of output pixels
        BLE     exitbiggie
        STR     R8,save_ysize

        LDR     R14,save_ydiv
        MUL     R8,R10,R14              ; R8 = initial count
        TEQ     R7,#0
        BEQ     diverror
        DivRem  R10,R8,R7, R14          ; R10 = initial INPUT pixel y-coord
        RSB     R8,R8,R7                ; R8 = amount of first input pixel left
        STR     R8,save_ycount

        Debug   in,"ycoord,ybot,ycount,ysize =",#save_ycoord,R10,#save_ycount,#save_ysize
        ;
        ; update ChangedBox coords (if enabled)
        ; Entry:  [save_xcoord],[save_ycoord],[save_xsize],[save_ysize] set up
        ;
        LDR     R14,changedbox          ; read in initialisation routine
        LDR     R0,[R14],#4
        TST     R0,#1                   ; enabled?
        BEQ     %FT01

        Push    "R3-R10"
        LDR     R3,save_xcoord          ; coordinate after clipping
        LDR     R4,save_ycoord          ; ditto
        LDR     R5,save_xsize           ; size in output pixels
        LDR     R6,save_ysize           ; ditto
        LDR     R7,Log2bpc
        LDR     R8,Log2bpp
        SUBS    R7,R7,R8
        MOVNE   R3,R3,ASR R7            ; unbodge double-pixel stuff
        MOVNE   R5,R5,ASR R7
        ADD     R5,R3,R5
        ADD     R6,R4,R6
        SUB     R5,R5,#1                ; make inclusive
        SUB     R6,R6,#1
        LDMIA   R14,{R7-R10}            ; original box
        CMP     R7,R3
        MOVGT   R7,R3
        CMP     R8,R4
        MOVGT   R8,R4
        CMP     R9,R5
        MOVLT   R9,R5
        CMP     R10,R6
        MOVLT   R10,R6
        STMIA   R14,{R7-R10}            ; new box
        Pull    "R3-R10"
01
        ;
        ; get input address and shift
        ; Entry:  R9,R10 = initial input pixel coord (within sprite)
        ;         R11 = input log2bpp
        ; Exit:   [save_inptr] --> first input word
        ;         [save_inshift] = initial bit position
        ;         [save_inoffset] = length of 1 sprite row (bytes)
        ;         [save_masko] = offset from image to mask (0 ==> none)
        ;         [save_maskinshift] = initial mask bit position
        ;         [save_maskinptr] => first mask word
        ;         [save_maskinoffset] = length of 1 mask row (bytes)
        ;

      [ jpeg
        ; for a JPEG sprite we'll need the actual coordinates, not the computed
        ; address, for the input data.
        Debug   cc,"input coords",R9,R10
        STR     R9,in_x
        STR     R10,in_y
      ]

        ; set up things for 1bpp masks too - we decide later whether to use it
        AND     R14,R9,#31              ; mask is 1bpp, and there's no lh wastage
                                        ; so this is a lot simpler!
        STR     R14,save_maskinshift    ; save the inshift value for later

        Push    "R9"                    ; save unadjusted R9 for later

        LDR     R14,[R1,#spLBit]
        ADD     R9,R14,R9,LSL R11       ; R9 = initial bit position
        AND     R14,R9,#31
        STR     R14,save_inshift

        LDR     R14,[R1,#spImage]
      [ debugso
        MOV     R0, R14
        Debug   so,"Sprite Image at", R0
      ]
        ADD     R14,R1,R14              ; R14 --> sprite image
        LDR     R0,[R1,#spTrans]
        ADD     R0,R1,R0                ; R0 --> sprite mask
        Debug   so,"Sprite mask at", R0
        TST     R5,#8
        MOVEQ   R0,R14                  ; R0=R14 ==> no mask
        SUBS    R0,R0,R14
        BICEQ   R5,R5,#8                ; R5 bit 3 ==> is there a mask?
        STR     R0,save_masko

        LDR     R2,[R1,#spHeight]
        SUB     R10,R2,R10              ; R10 = no of rows from top

        LDR     R3,save_inputxsize      ; number of pixels(==number of bits)
        ANDS    R4,R3,#&1F              ; R4=number of bits
        MOVNE   R4,#1                   ; R4=1 if R3 MOD 31 is not zero
        ADD     R4,R4,R3,LSR #5         ; R4=number of words
        MOV     R4,R4,ASL #2            ; R4=number of bytes for full row
        STR     R4,save_maskinoffset

        LDR     R2,[R1,#spWidth]
        ADD     R2,R2,#1
        MOV     R2,R2,ASL #2            ; R2 = line length
        STR     R2,save_inoffset

        MOV     R9,R9,ASR #5            ; R9 = word offset

        Pull    "R3"                    ; pull unaltered R9 from earlier
        MOV     R3,R3,ASR #5

        ADD     R11,R0,R3,ASL #2        ; R11= mask addr + byte offset
        MLA     R3,R10,R4,R11           ; R3= #rows * bytes_per_row + maskaddr & byteoffset
                                        ; R3=> first mask input word
        LDR     R11,[R1,#spImage]
        ADD     R3,R3,R11
        ADD     R3,R3,R1
        STR     R3,save_maskinptr
        Debug   so, "Maskinptr is = ",R3

        ADD     R14,R14,R9,ASL #2
        MLA     R14,R10,R2,R14          ; R14 --> first input word
        STR     R14,save_inptr


        ; note that this points at the start of the last row of the sprite!

        Debug   in,"inptr,inshift,inoffset,masko =",#save_inptr,#save_inshift,#save_inoffset,#save_masko
        ;
        ; get output address and shift
        ; Entry:  [save_x/ycoord] = output x,y coord
        ;         [screenstart], [ywindlimit], [linelength] set up
        ; Exit:   [save_outptr] --> output address
        ;         [save_outword] = initial marker bit position
        ;         [save_outoffset] = line length
        ;
        LDR     R3,save_xcoord
        LDR     R4,save_ycoord
        LDR     R11,Log2bpp                     ; R11 = output log2(bpp)

        LDR     R14,ywindlimit
        SUB     R4,R14,R4                       ; R4 = no of rows down from top
        LDR     R2,save_ecflimit
        ADD     R14,R2,R4,ASL #3                ; R14 = offset into ecf table
        ADD     R14,R14,#8                      ; R14 --> initial ecf position
        STR     R14,save_ecfptr

        LDR     R14,linelength
        STR     R14,save_outoffset
        LDR     R2,screenstart
        MLA     R10,R14,R4,R2                   ; R10 = R2 + (R14 * R4)
        MOV     R3,R3,ASL R11
        AND     R14,R3,#31                      ; R14 = initial bit posn in word;
        MOV     R3,R3,ASR #5
        ADD     R10,R10,R3,ASL #2               ; R10 --> output address
        STR     R10,save_outptr

        MOV     R0,#&80000000
        MOV     R0,R0,LSR R14                   ; R0 = initial marker bit
        STR     R0,save_outword

        Debug   in,"Outptr, outword =",#save_outptr,#save_outword


        ; Register summary:
        ;
        ;       R0      R1      R2      R3      R4      R5      R6      R7      R8      R9      R10     R11     R12
        ;       ------  ------  ------  ------  ------  ------  ------  ------  ------  ------  ------  ------  ------
        ;               spriteptr       xcoord  ycoord  gcol    x-mag   y-mag
        ;       -x-             -x-     R3      R4              R6      R7      -x-     -x-     -x-     log2bpp
        ;    outoffset  inoffset                ydiv    yadd                    ysize   ycount
        ;               inshift
        ;       in1     in2     inptr   outptr  outword outmask xdiv    xadd    xsize   xcount  masko   bpp     inmask
        ;
        ;       in1     in2     inptr   outptr  outword outmask xdiv    xadd    xsize   xcount  ttr     bpp     inbpp
        ;                                                       -x-     -x-                     ecfptr  ecfora  ecfeor

        ;
        ; compute xadd, yadd
        ;
        ASSERT  yadd<>R6
        STR     R7,save_yadd            ; yadd = size of input pixels (y-mag)
        LDR     R14,save_xdiv
        ADD     xadd,R14,R6             ; xadd = save_xdiv + xmag
        STR     xadd,save_xadd

        MOV     R14,#-1                 ; bit set ==> don't touch!
        STR     R14,save_outmask
        MOV     R14,#0
        STR     R14,save_vcount         ; bodge for 1st row

        ; Jump to the C code run time compiler
        B       new_putscaled_compiler

        ;-----------------------------------------------------------------------------
        ; Function to validate the pixel translation table, remapping as required
        ; to take into account depth changes etc.
        ;
        ; in   R7 -> pixtrans table supplied to call
        ; out  R0 -> error block (V set)
        ;      R7 -> pixtrans table (may have been relocated!)
        ;-----------------------------------------------------------------------------
        
        ; The following rules apply:
        ;
        ;    1/2/4/8 to 1/2/4/8 bpp   : no change
        ;          8 to   16/32 bpp   : use mungeGCOL8toXX routine, applied here
        ;      16/32 to 1/2/4/8 bpp   : only valid table is a CTrans 32K table providing 5 bit mappings
        ;         16 to      16 bpp   : no table allowed
        ;         32 to      32 bpp   : no table allowed
        ;      16/32 to   32/16 bpp   : no table, but a munge function will be applied instead
        ;                               (nb, this is called during plotting, since there's no table
        ;                               to modify in advance)
        ;
        ;    ColourTrans uses the following structure for the return from Select/GenerateTable:
        ;
        ;    Word 0 : &33324B2E "32K."
        ;    Word 4 : Pointer to the 32K table
        ;    Word 8 : &33324B2E "32K."
        ;
checktrans Entry

        LDR     LR,save_inbpp
        MOV     R8,#1
        MOV     R8,R8,ASL LR            ; R8 = number of input colours
        LDR     R9,BPP                  ; R9 = output depth
        MOV     R10,#0                  ; R10 = index / counter

        CMP     LR,#16
        BCC     checktrans1             ; branch if input is 8bpp or below

        ;now check out the following:
        ; 16 -> 16 : \                  ;
        ; 32 -> 32 :  \__ A table should never be used, however if one is presented
        ; 16 -> 32 :  /   it will be discarded but not faulted here.
        ; 32 -> 16 : /
        ; 16/32 -> 8 or lower : table must be CTrans 32K type

        CMP     R9,#16
        BCS     checktrans_ignorettr

        ;this is 16/32 to 8/lower, so check for a 32K table
      [ jpeg
        ; JPEG possibility - might get 32bpp data into small target, with no table.
        LDR     LR,is_it_jpeg
        TEQ     LR,#0                     ; 0 -> not JPEG
        BNE     checktrans_exitok
      ]
        LDR     LR,word32k
        LDR     R8,[R7,#0]
        CMP     R8,LR
        BNE     checktrans_giveerror
        LDR     R8,[R7,#8]
        CMP     R8,LR
        BNE     checktrans_giveerror
        B       checktrans_exitok

checktrans_ignorettr
        MOVS    R7,#0
        B       checktrans_exitok

word32k =       "32K."

checktrans1
        CMP     R9,#16                  ; are we outputting to >= 16 bit per pixel
        BLT     checktrans_old          ; if not then ignore the remapping

        ; For backwards compatibility we attempt to remap the colours by taking the GCOL
        ; byte which is in VIDC 1 format and convert it to a word value sensible for this
        ; depth of display.

        ; The functions which decode this byte now look for a word value which is then
        ; combined into the scan line being rendered.

        Push    "R0-R2,R9"
        LDR     R9,Log2bpp
        SUB     R9,R9,#3                ; convert the output Log2 bpp into a sensible index for conversion
      [ widetrans
        ; don't need to do anything for 32bpp, for 15bpp entries need to be moved to
        ; be word based. Problem is that CTrans produces a half word table, but the
        ; plot code here expects them to be words

        LDR     R14,trns_flags2         ; get the flag word
        TST     R14,#flg2_widetrans     ; if set we don't need to do the expansion
        BEQ     no_widetrans            ; skip if new bit is unset
        TSTNE   R9,#2                   ; check for 32bpp (note the the SUB #3 above)
        LDMNEFD R13!,{R0-R2,R9}         ; it is 32bit - reload registers...
        BNE     checktrans_exitok       ; ...and go straight out

        Debug   so,"We're gonna scribble all over newtranstable 1"
        ;now do the expansion needed for 15bpp
        ADR     R2,newtranstable        ; new table space
        MOV     R8,R8,LSR #1            ; loading a word collects two entries, so
                                        ; halve the number of colours to do
expand_15bpp
        SUBS    R8,R8,#1                ; decrement count
        Pull    "R0-R2,R9",MI           ; when we go minus we've finished, reload registers...
        ADRMI   R7,newtranstable        ; ...change the translation table pointer...
        BMI     checktrans_exitok       ; ...and get out of here!

        LDR     R0,[R7,R8,LSL #2]       ; load two values from original ctrans table
        MOV     LR,R0,LSR #16           ; put the second value in the low 16 bits of LR
        EOR     R0,R0,LR,LSL #16        ; and remove the second value from R0
        MOV     R8,R8,LSL #1            ; double the output pointer cos we've two words to store
        STR     R0,[R2,R8,LSL #2]       ; store the first word
        ADD     R8,R8,#1                ; increment
        STR     LR,[R2,R8,LSL #2]       ; and store the second word
        MOV     R8,R8,LSR #1            ; and bring the output pointer back to normal
        B       expand_15bpp            ; back to the loop to check for completion

no_widetrans
      ]

        ADR     R2,newtranstable        ; -> new translation table (passed out on exit)

converttrans_new
        SUBS    R8,R8,#1
        Pull    "R0-R2,R9",MI           ; preserve important registers
        ADRMI   R7,newtranstable
        BMI     checktrans_exitok

        LDRB    R0,[R7,R8]
        MOV     LR,PC
        ADD     PC,PC,R9,LSL #2         ; call function to remap the colour byte
        STR     R0,[R2,R8,LSL #2]       ; and store the converted value
        B       converttrans_new        ; outbpp = 3 (and inbpp < 4 log2bpp)
        B       mungeGCOL8to16          ; outbpp = 4
        ; Fall  mungeGCOL8to32          ; outbpp = 5
        ; thru

mungeGCOL8to32
        Push    "R2,LR"
                                        ;      fedcba98 76543210 fedcba98 76543210
                                        ; R0 =                            bbggrrtt
        MOV     R2,R0,LSL #4            ; R2 =                       bbgg rrtt
        ORR     R2,R2,R0,LSL#10         ; R2 =                bb ggrrXXgg rrtt
        ORR     R2,R2,R0,LSL#16         ; R2 =          bbggrrXX ggrrXXgg rrtt
        AND     LR,R0,#&03              ; LR =                                  tt
        BIC     R2,R2,#&3F0000          ; R2 =          bb       ggrrXXgg rrtt
        BIC     R2,R2,#&3F00            ; R2 =          bb       gg       rrtt
        ORR     R2,R2,R14,LSL #12       ; R2 =          bb       ggtt     rrtt
        ORR     R0,R2,R14,LSL #20       ; R0 =          bbtt     ggtt     rrtt
        ORR     R0,R0,R0,LSR #4         ; R0 =          bbttbbtt ggttggtt rrttrrtt

        Pull    "R2,PC"

mungeGCOL8to16
      [ vidc20
                                        ;      fedcba9876543210fedcba9876543210

        Push    "R2,LR"
                                        ; R0 =                         bbggrrtt

        MOV     LR,R0,LSL #30           ; LR = tt
        ORR     R2,LR,R0,LSR #6         ; R2 = tt                            bb
        ORR     LR,LR,R0,LSR #4         ; LR = tt                          bbgg
        BIC     LR,LR,#&0C              ; LR = tt                            gg
        MOV     R0,R0,LSL #1            ; R0 =                        bbggrrtt
        AND     R0,R0,#&1E              ; R0 =                            rrtt
        ORR     R0,R0,LR,ROR #24        ; R0 =                       ggtt rrtt
        ORR     R0,R0,R2,ROR #19        ; R0 =                  bbtt ggtt rrtt
        ;and now fill in the bottom bit of the colour
        MOV     LR,LR,LSR #30           ; LR =                               tt
        AND     LR,LR,#1                ; LR =                                t
        ORR     LR,LR,LR,LSL #5         ; LR =                           t    t
        ORR     LR,LR,LR,LSL #5         ; LR =                      t    t    t
        ORR     R0,R0,LR                ; R0 =                  bbtttggtttrrttt

        Pull    "R2,PC"
      |

        ; Tim's function for remapping the packed GCOL number to a value
        ; suitable for display memory.

        AND     R1,R0,#4_000033
        ORR     R0,R1,R0,LSL #4
        AND     R1,R0,#4_030330
        EOR     R1,R1,R1,LSR #6
        EOR     R1,R1,R1,LSR #4
        AND     R1,R1,#4_000330
        EOR     R0,R0,R1,LSL #4
        MOV     PC,LR
      ]

checktrans_old
        SUBS    R8,R8,#1                ; loop until -ve value

        BMI     checktrans_exitok       ;fix bug that was making this routine inactive! AMG

        LDRB    LR,[R7,R10]             ; get a byte from the table
        MOVS    LR,LR,LSR R9            ; if any bits are non-zero that shouldn't be then complain
        BEQ     checktrans_old
checktrans_giveerror
        Debug   so, "We think the ttable is duff (XXX)"
        ADRL    R0,ErrorBlock_BadTranslation
        addr    R1,Title
        BL      copy_error_one          ; returns error block with V set, for bad table
        EXIT

checktrans_exitok
        CLRV
        EXIT

        ; some of these values used in SprTrans
mc_gcol         *       2_00000111              ; bits 0..2 of R5
mc_hasmask      *       2_00001000              ; bit 3 of R5
mc_ttr          *       2_00010000              ; R7
mc_plotmask     *       2_00100000              ; R0
mc_transformed  *       2_01000000              ; 0 if scaled, 1 if transformed sprite plot

                [ ignore_ttr
mc_ttrispalette *       2_10000000              ; use palette instead of ttr (ttr points at it)
                ]

mcb_inbpp       *       8
mcb_outbpp      *       16
mcb_sprtype     *       24

xxx             *       2

BNE             *       &1A000000
BCS             *       &2A000000
BCC             *       &3A000000
BMI             *       &4A000000
BPL             *       &5A000000
BVS             *       &6A000000
BVC             *       &7A000000
BHI             *       &8A000000
BLS             *       &9A000000
BGE             *       &AA000000
BLT             *       &BA000000
BGT             *       &CA000000
BLE             *       &DA000000
BAL             *       &EA000000
BNV             *       &FA000000

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteOp_AppendSprite
; ---------------------
; Entry: R0 = reason code
;        R1 = areaCBptr
;        R2 = name/ptr of 1st sprite
;        R3 = name/ptr of 2nd sprite
;        R4 = flags (0 ==> merge horizontally, else vertically)
; Exit:  1st sprite is result of merging both
;        2nd sprite is deleted
;        scratch space used (no extra memory apart from that)
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Go_AppendSprite
        Push    "R1-R11,LR"

        CLRPSR  I_bit, R14              ; re-enable interrupts
        BL      findsprite
        STRVC   R1,sp1_areaCBptr        ; R1 may be updated if system area
        MOVVC   R5,R2                   ; R5 --> first sprite
        MOVVC   R2,R3
        BLVC    findsprite              ; R2 --> second sprite
        BVS     %FT99

        ;
        ; Algorithm:
        ;       check that sprites are compatible - if not, give error/adjust sprite
        ;       move sprite2 to end of sprites
        ;       move sprite1 just before sprite2
        ;       eliminate sprite2 header
        ;       if masked, swap(mask1,data2)
        ;       if R4=0 (horiz. merge) interleave rows of data (& then masks if nec)
        ;       reclaim wastage (depends on h/v merge) of data + masks
        ;       correct sprite end ptrs, no of sprites etc.
        ;       correct size of sprite
        ;
        TEQ     R2,R5                   ; same sprite?
        BEQ     badappend
        LDR     R14,[R2,#spMode]
        LDR     R0,[R5,#spMode]
        Debug   ag,"Modes are",R0,R14   ; Merged from 0.62 (GPS)
        TEQ     R0,R14
        BNE     badappend               ; can't merge different modes
        ;
        ; check that rows/columns are same (depends on R4)
        ;
        TEQ     R4,#0                   ; R4=0 ==> horiz merge (check rows)
        BNE     %FT01

        LDR     R14,[R2,#spHeight]
        LDR     R0,[R5,#spHeight]
        Debug   ag,"Heights are",R0,R14 ; Merged from 0.62 (GPS)
        TEQ     R0,R14
        BEQ     %FT02
badappend
        Debug   ag,"Bad append, setting up error block and exiting"
        ADR     R0, ErrorBlock_BadAppend
        addr    r1, Title
        BL      copy_error_one          ; Always sets the V bit
        B       %FT99
        MakeSpriteErrorBlock BadAppend,,AppErr

01
        Push    "R2"
        BL      getspritewidth          ; R2 --> sprite defn, R3 = width
        MOV     R2,R5
        MOV     R6,R3
        BL      getspritewidth
        Pull    "R2"
        Debug   ag,"Widths are",R3,R6   ; Merged from 0.62 (GPS)
        TEQ     R3,R6
        BNE     badappend
02
        ;
        ; if one has a mask & the other doesn't, create mask
        ;
        LDR     R14,[R2,#spImage]
        LDR     R0,[R2,#spTrans]
        EORS    R6,R0,R14
        MOVNE   R6,#-1                  ; R6 = (2nd sprite has mask)
        LDR     R14,[R5,#spImage]
        LDR     R0,[R5,#spTrans]
        EORS    R0,R0,R14
        MOVNE   R0,#-1
        TEQ     R0,R6                   ; if same, skip next bit
        BEQ     %FT01

        Debug   ag,"One of the sprites has no mask"   ; Merged from 0.62 (GPS)
        Push    "R2"
        TEQ     R6,#0
        MOVNE   R2,R5                   ; if 2nd sprite has mask, enmask 1st
        MOV     R0,#SpriteReason_CreateMask
        ADD     R0,R0,#&200             ; since R2 --> sprite definition
        BL      Go_SpriteOp             ; call MOS version
        Pull    "R2"
        BVS     %FT99                   ; error (eg. memory full)
01
        ;
        ; move sprites to end of memory (need to get addresses back again)
        ;
        LDMFD   R13,{R1-R3}

        BL      findsprite
        MOVVC   R5,R2                   ; R5 --> first sprite
        MOVVC   R2,R3
        BLVC    findsprite              ; R2 --> second sprite
        BVS     %FT99

        Debug   ag,"First sprite is at",R5      ; Merged from 0.62 (GPS)
        Debug   ag,"Second sprite is at",R2     ; Merged from 0.62 (GPS)

        LDR     R3,[R1,#saFree]         ; R3 --> end of sprites
        Debug   ag,"End of sprite area is",R3   ; Merged from 0.62 (GPS)
        ADD     R3,R1,R3
        MOV     R1,R2                   ; R1 --> sprite2
        LDR     R2,[R1,#spNext]
        ADD     R2,R1,R2                ; R2 --> end of sprite2
        Debug   ag,"End of sprite2 is",R2       ; Merged from 0.62 (GPS)
        BL      swapblocks
        MOV     R6,R1                   ; R6 --> sprite2 (in new position)

        CMP     R5,R2                   ; if sprite1 was after sprite2
        DebugIf CS,ag,"Sprite1 was after sprite2"   ; Merged from 0.62 (GPS)
        SUBCS   R14,R3,R1
        SUBCS   R5,R5,R14               ; move down by (R3-R1) ie. sprite2 size

        MOV     R3,R1
        MOV     R1,R5                   ; R1 --> sprite1
        LDR     R2,[R1,#spNext]
        ADD     R2,R1,R2
        Debug   ag,"End of sprite1 is",R2     ; Merged from 0.62 (GPS)
        BL      swapblocks              ; R1 --> sprite1 (in new position)
        ;
        ; note the sprite mode of sprite 1 (should = sprite2)
        ; and the spNext parameter of sprite 2
        ;
        LDR     R2, [R1,#spMode]        ; Merged from 0.62 (GPS)
        STR     R2, sp_mode             ; Merged from 0.62 (GPS)
        LDR     R2, [R6,#spNext]        ; Merged from 0.62 (GPS)
        STR     R2, sp2_next            ; Merged from 0.62 (GPS)
        ;
        ; note down sprite1's header
        ;
        MOV     R2,R1
        ADRL    R3,sp1_data
        BL      savespriteheader        ; sets up R5,R7-R11
        ;
        ; note down sprite2's header, and delete it
        ;
        MOV     R2,R6
        ADRL    R3,sp2_data
        BL      savespriteheader        ; sets up R5,R7-R11

        MOV     R1,R6                   ; destination
        ADD     R2,R1,R10               ; source (image)
        LDR     R3,[R1]
        SUB     R3,R3,R10               ; counter
        BL      copyblock               ; R3 bytes from R2 to R1
        MOV     R2,R6                   ; R2 --> sprite2 image
        ;
        ; if there is a mask (new or old), swap (mask1, image2) & do both sets of data
        ;
        SUBS    R11,R11,R10             ; offset from image to mask (sprite2)
        BEQ     %FT01

        Debug   ag,"These sprites have masks!"        ; Merged from 0.62 (GPS)
        LDR     R1,sp1_header
        LDR     R14,[R1,#spTrans]       ; R1 --> start of sprite1 mask
        ADD     R1,R1,R14               ; R2 --> sprite2 image
        ADD     R3,R2,R11               ; R3 --> end of sprite2 image
        BL      swapblocks              ; R2, R1 --> image2, mask1

        Push    "R1,R3"

        MOV     R3,R1                   ; R3 --> end of image2
        LDR     R1,sp1_header
        SUB     R14,R3,R1
        STR     R14,sp1_trans           ; for later
        LDR     R14,sp1_image
        ADD     R1,R1,R14               ; R1 --> start of image1
        BL      mergeblocks             ; takes note of R4

        Pull    "R1,R2"                 ; R1 --> mask1, R2 --> mask2
        LDR     R3,sp2_trans
        LDR     R14,sp2_next            ; Merged from 0.62 (GPS)

        SUB     R3,R14,R3               ; R3 = size of mask2 (poss+) - From 0.62 (GPS)

        ADD     R3,R2,R3                ; R3 --> end of mask2
        LDR     R14,sp1_header
        SUB     R14,R1,R14


        Push    "R14"                   ; merged from 0.62 (GPS)
        LDR     R14, sp_mode            ; merged from 0.62 (GPS)
        MOVS    R14, R14, LSR #27       ; Old or new?                 merged from 0.62 (GPS)
        Pull    "R14"                                               ; merged from 0.62 (GPS)
        DebugIf EQ,ag,"Old format mergeblocks to be executed"       ; merged from 0.62 (GPS)
        BLEQ    mergeblocks             ;   if old format             merged from 0.62 (GPS)
        DebugIf NE,ag,"New format mergeblocks to be executed"       ; merged from 0.62 (GPS)
        BLNE    maskmergeblocks         ;   if new format             merged from 0.62 (GPS)
        B       %FT02
        ;
        ; otherwise just merge the images
        ;
01
        Debug   ag,"These sprites don't have masks"                 ; merged from 0.62 (GPS)
        LDR     R1,sp1_header
        LDR     R14,sp1_image
        ADD     R1,R1,R14               ; R1 --> sprite1 image
        LDR     R2,sp2_header           ; R2 --> sprite2 image
        LDR     R14,sp2_imagesize
        ADD     R3,R2,R14               ; R3 --> end of sprite2 image
        BL      mergeblocks
02
        ;
        ; now rationalise the final image - shunt all bits down
        ;
        Debug   ag,"Now rationalising..."          ; merged from 0.62 (GPS)
        LDR     R2,sp1_header
        LDR     R3,sp1_image
        ADD     R1,R2,R3
        MOV     R3,R1
        BL      chunterblock            ; R1 --> start of block
        LDR     R0,sp1_image
        LDR     R14,sp1_trans
        TEQ     R14,R0                  ; was there a mask?



        BEQ     %FT03                   ; if not, skip the next bit
        SUB     R14,R3,R2                                           ; merged from 0.62 (GPS)
        STR     R14,[R2,#spTrans]       ; point to mask               merged from 0.62 (GPS)
        LDR     R14, sp_mode                                        ; merged from 0.62 (GPS)
        MOVS    R14, R14, LSR #27       ; New or old format mask?     merged from 0.62 (GPS)
        DebugIf EQ,ag,"Old format chunterblock to be executed"      ; merged from 0.62 (GPS)
        BLEQ    chunterblock            ; process old format mask     merged from 0.62 (GPS)
        DebugIf EQ,ag,"New format chunterblock to be executed"      ; merged from 0.62 (GPS)
        BLNE    maskchunterblock        ; process new format mask     merged from 0.62 (GPS)

03
        SUB     R14,R3,R2
        STR     R14,[R2,#spNext]        ; sprite size
        LDR     R2,sp1_areaCBptr
        SUB     R14,R3,R2
        STR     R14,[R2,#saFree]        ; area size

        LDR     R14,[R2,#saNumber]
        SUB     R14,R14,#1              ; sprite2 deleted
        STR     R14,[R2,#saNumber]

99
        MOVVC   r0, #35                 ; made conditional to stop error block being junked (GPS)
        Pull    "R1-R11,PC"

        ; Chunterblock
        ;
        ; Entry:  R1 --> input data
        ;         R3 --> output data
        ;         R4 = 0/1 (horizontal/vertical merge)
        ;         [sp1/2_data] = original state of sprites
        ;         [sp1/2_imagesize] = amount of data to process
        ; Exit:   R1 --> end of input
        ;         R3 --> end of output
        ;         [[sp1_header]] contains correct width, height, lbit, rbit
        ;         sprite data crunched into shape
        ;
chunterblock
        Push    "R5-R11,LR"
        Debug   ag,"Chunterblock entry R1,R3,R4",R1,R3,R4

        TEQ     R4,#0
        BNE     %FT04

        LDR     R11,sp1_height          ; R11 = no of rows to do (-1)
01
        MOV     R10,R3                  ; original output ptr

        LDR     R5,sp1_width            ; R5 = no of intermediate words
        LDR     R6,sp1_lbit             ; R6 = input bit wastage on left
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        LDR     R8,sp1_rbit             ; bit non-wastage on right
        BL      chunterrow              ; R7 = no of bits set up in last word

        LDR     R5,sp2_width
        LDR     R6,sp2_lbit
        LDR     R8,sp2_rbit
        BL      chunterrow

        TEQ     R7,#0
        MOVEQ   R7,#32
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1
        BPL     %BT01

        B       setupwidth

04
        LDR     R11,sp1_height
01
        LDR     R5,sp1_width            ; R5 = no of intermediate words
        LDR     R6,sp1_lbit             ; R6 = input bit wastage on left
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        LDR     R8,sp1_rbit             ; bit non-wastage on right
        BL      chunterrow              ; R7 = no of bits set up in last word
        TEQ     R7,#0
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1
        BPL     %BT01

        LDR     R11,sp2_height
02
        MOV     R10,R3
        LDR     R5,sp2_width            ; R5 = no of intermediate words
        LDR     R6,sp2_lbit             ; R6 = input bit wastage on left
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        LDR     R8,sp2_rbit             ; bit non-wastage on right
        BL      chunterrow              ; R7 = no of bits set up in last word
        TEQ     R7,#0
        MOVEQ   R7,#32
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1
        BPL     %BT02

        LDR     R14,sp1_height
        LDR     R0,sp2_height
        ADD     R14,R14,R0
        ADD     R14,R14,#1              ; since each is <no of rows>-1
        STR     R14,[R2,#spHeight]
        ;
        ; R2 --> sprite header, R3-R10 = width of 1 row, R7 = spRBit+1, spLBit=0
        ;
setupwidth
        SUB     R14,R3,R10
        MOV     R14,R14,LSR #2
        SUB     R14,R14,#1
        STR     R14,[R2,#spWidth]       ; spWidth = no of words - 1
        MOV     R14,#0
        STR     R14,[R2,#spLBit]        ; spLBit = 0
        SUB     R7,R7,#1
        STR     R7,[R2,#spRBit]         ; spRBit = R7-1

        Debug   ag,"Chunterblock exit R1,R3,R4",R1,R3,R4

        Pull    "R5-R11,PC"

        ; Maskchunterblock
        ;
        ; Entry:  R1 --> input data
        ;         R3 --> output data
        ;         R4 = 0/1 (horizontal/vertical merge)
        ;         [sp1/2_data] = original state of sprites
        ;         [sp1/2_imagesize] = amount of data to process
        ; Exit:   R1 --> end of input
        ;         R3 --> end of output
        ;         [[sp1_header]] contains correct width, height, lbit, rbit
        ;         sprite data crunched into shape
        ;
        ; NOTE:   Only called for new format (1bpp) sprites
        ;
maskchunterblock
        Push    "R5-R11,LR"

        Debug   ag,"Chunterblock entry R1,R3,R4",R1,R3,R4

        TEQ     R4,#0                   ; Vertical or horizontal join?
        BNE     %FT04                   ; Go elsewhere if vertical join

        LDR     R11,sp1_height          ; R11 = no of rows to do (-1)
01
        MOV     R10,R3                  ; original output ptr

        LDR     R8,sp1_rbit
        LDR     R5,sp1_width            ; R5 = no of intermediate words
        BL      FindMaskWidth           ; Alters R5 & sets up R8 to Rbit
        MOV     R6,#0                   ; Lbit wastage = 0
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        BL      chunterrow              ; R7 = no of bits set up in last word
        
        LDR     R8,sp2_rbit
        LDR     R5,sp2_width
        BL      FindMaskWidth           ; Alters R5 & sets up R8 to Rbit for mask
        MOV     R6,#0                   ; Lbit wastage always = 0
        BL      chunterrow

        TEQ     R7,#0
        MOVEQ   R7,#32
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1              ; One line less to do...
        BPL     %BT01                   ; Do the next one


        Debug   ag,"Maskchunterblock exit R1,R3,R4",R1,R3,R4

        Pull    "R5-R11,PC"

04
        LDR     R11,sp1_height
01
        LDR     R8,sp1_rbit
        LDR     R5,sp1_width            ; R5 = no of intermediate words
        BL      FindMaskWidth           ; Convert R5 & set up R8
        MOV     R6,#0                   ; Lbit is zero for this format sprite
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        BL      chunterrow              ; R7 = no of bits set up in last word
        TEQ     R7,#0
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1
        BPL     %BT01

        LDR     R11,sp2_height
02
        MOV     R10,R3
        LDR     R8,sp2_rbit
        LDR     R5,sp2_width            ; R5 = no of intermediate words
        BL      FindMaskWidth
        MOV     R6,#0                   ; Lbit = 0 always
        MOV     R7,#0                   ; R7 = no of bits set up (none)
        BL      chunterrow              ; R7 = no of bits set up in last word
        TEQ     R7,#0
        MOVEQ   R7,#32
        ADDNE   R3,R3,#4                ; skip last word if anything in it

        SUBS    R11,R11,#1
        BPL     %BT02
        Pull    "R5-R11,PC"

        ;
        ; R2 --> sprite header, R3-R10 = width of 1 row, R7 = spRBit+1, spLBit=0
        ;
masksetupwidth
        SUB     R14,R3,R10
        MOV     R14,R14,LSR #2
        SUB     R14,R14,#1
        STR     R14,[R2,#spWidth]       ; spWidth = no of words - 1
        MOV     R14,#0
        STR     R14,[R2,#spLBit]        ; spLBit = 0
        SUB     R7,R7,#1
        STR     R7,[R2,#spRBit]         ; spRBit = R7-1
        
        Pull    "R2,R5-R11,PC"


        ; Chunterrow
        ;
        ; Entry:  R1 --> input data
        ;         R3 --> output data (some of 1st word may be valid)
        ;         R5 = no of whole words to do (excluding the end 2)
        ;         R6 = bit offset of 1st valid bit in input word
        ;         R7 = bit offset of 1st unused bit in output word
        ;         R8 = bit offset of last used bit in output word
        ; Exit:   R1 --> end of input data
        ;         R3 --> end of output data
        ;         R7 = no of valid bits in [R3] - 0..31 (if 32, output word & go on)
        ;
chunterrow
        Push    "R0,R5,R6,R8-R10,LR"
        Debug   ag,"Chunterrow entered R1,R3,R5-R8",R1,R3,R5,R6,R7,R8

        ADD     R8,R8,#1                ; make exclusive
        LDR     R0,[R1],#4
        MOV     R0,R0,LSR R6
        LDR     R14,[R3]
        RSB     R10,R7,#32              ; for later
        MOV     R14,R14,LSL R10
        MOV     R14,R14,LSR R10
        ORR     R14,R14,R0,LSL R7
        TEQ     R5,#0
        RSBEQ   R9,R6,R8                ; R9 = no of valid bits shifted in
        RSBNE   R9,R6,#32
        ADD     R7,R7,R9
        CMP     R7,#32
        SUBCS   R7,R7,#32
        STRCS   R14,[R3],#4
        MOVCS   R14,R0,LSR R10          ; R14 = rest of input word
        RSB     R9,R7,#32               ; R9 = no of bits to be carried over
        ;
        ; R14 = output word, R7 = no of valid bits, R1 --> input (from bit 0)
        ;
        TEQ     R5,#0
        BEQ     %FT05                   ; 1st word = last - we've finished!

        MOV     R6,#0
        SUBS    R5,R5,#1
        BEQ     %FT04                   ; middle section is null

        TEQ     R7,#0
        BNE     %FT02
01
        LDR     R14,[R1],#4             ; do this the quick way!
        STR     R14,[R3],#4
        SUBS    R5,R5,#1
        BNE     %BT01
        MOV     R14,#0                  ; no more bits remaining!
        B       %FT04
02
        LDR     R0,[R1],#4              ; R0 = input word - from bit 0
        ORR     R14,R14,R0,LSL R7
        STR     R14,[R3],#4
        MOV     R14,R0,LSR R9           ; R14 = remaining bits of R0
        SUBS    R5,R5,#1
        BNE     %BT02
        ;
        ; R1 --> final word of input, R3 --> next output word
        ; R14 = output word, R7 = no of valid bits, R6 = first valid bit in [R1]
        ;
04
        LDR     R0,[R1],#4              ; read final word
        MOV     R0,R0,LSR R6
        SUB     R8,R8,R6                ; no of bits used in R0
        ORR     R14,R14,R0,LSL R7
        ADD     R7,R7,R8
        CMP     R7,#32
        SUBCS   R7,R7,#32
        STRCS   R14,[R3],#4
        MOVCS   R14,R0,LSR R9
05
        TEQ     R7,#0                   ; avoid splattering next word of input
        STRNE   R14,[R3]                ; for next time

        Debug   ag,"Chunterrow exit R1,R3,R5-R8",R1,R3,R5,R6,R7,R8

        Pull    "R0,R5,R6,R8-R10,PC"


        ; Savespriteheader
        ;
        ; Entry:  R2 --> sprite definition
        ;         R3 --> where to put data
        ; Exit:   R5,R7-R11 = width,height,Lbit,Rbit,Image,Trans
        ;         also copied to [R3]
        ;         [R3,#header] = address of header block
        ;         [R3,#imagesize] = size of sprite image (excl. mask)
        ;
savespriteheader
        Push    "R2,LR"

        STR     R2,[R3,#sp1_header-sp1_data]
        LDR     R14,[R2],#spWidth
        LDMIA   R2,{R5,R7-R11}          ; width,height,Lbit,Rbit,Image,Trans
        STMIA   R3,{R5,R7-R11}
        Debug   ag,"Sprite header",R5,R7,R8,R9,R10,R11
        LDR     R14,[R1,#spNext]        ; Try & work out the image size
        TEQ     R10,R11                 ; Hang on, but is there a mask?
        LDRNE   R14,[R1,#spTrans]       ; Yes, so we have to work it out different
        SUB     R14,R14,R10             ; Convert the offset to a 'size'
        STR     R14,[R3,#sp1_imagesize-sp1_data]

        Pull    "R2,PC"

        ; Getspritewidth
        ;
        ; Entry:  R2 --> sprite defn
        ; Exit:   R3 = sprite width (bits)
        ;
getspritewidth
        Push    "LR"

        LDR     R14,[R2,#spLBit]
        LDR     R3,[R2,#spRBit]
        SUB     R3,R3,R14
        ADD     R3,R3,#1                ; make inclusive
        LDR     R14,[R2,#spWidth]
        ADD     R3,R3,R14,LSL #5        ; 32 bits per word
        Debug   ag,"Getspritewidth thinks the sprite width (bits) is",R3

        Pull    "PC"

        ; Swapblocks
        ;
        ; Entry:  R1 --> start of 1st block
        ;         R2 --> start of 2nd block (consecutive)
        ;         R3 --> end of 2nd block
        ; Exit:   blocks swapped over
        ;         R1 --> start of 1st block (after 2nd block)
        ;         R2 --> start of 2nd block
        ;
swapblocks
        Push    "R3,LR"

        Debug   mg,"Swap blocks: ",R1,R2,R3

        TEQ     R1,R2
        TEQNE   R2,R3                   ; Z set if either block is null
        SUB     R14,R3,R2               ; R14 = length of 2nd block
        MOV     R2,R1
        ADD     R1,R1,R14
        Pull    "R3,PC",EQ              ; ensure R1,R2 set up correctly on exit
        Push    "R1,R2"

        MOV     R1,R2
        MOV     R2,R3
        BL      reverseblock

        LDR     R2,[R13,#0*4]           ; reverse new 2nd block
        BL      reverseblock
        MOV     R1,R2
        LDR     R2,[R13,#2*4]           ; reverse new 1st block
        BL      reverseblock

        Pull    "R1-R3,PC"

        ; Reverse block
        ;
        ; Entry:  R1 --> start of block
        ;         R2 --> end of block (JUST AFTER)
        ; Exit:   block reversed (words)
        ;
reverseblock
        Push    "R1-R4,LR"
01
        CMP     R1,R2
        LDRCC   R3,[R1]
        LDRCC   R4,[R2,#-4]!
        STRCC   R3,[R2]
        STRCC   R4,[R1],#4
        BLT     %BT01

        Pull    "R1-R4,PC"

        ; Copyblock
        ;
        ; Entry:  R1 --> destination
        ;         R2 --> source
        ;         R3 = no of bytes (must be a whole number of words)
        ;
copyblock
        Debug   mg,"Copy block (to, from, count): ",R1,R2,R3

        TEQ     R1,R2           ; check for null copy
        TEQNE   R3,#0
        MOVEQ   PC,LR

        Push    "R1-R3,LR"

        CMP     R1,R2
        BCS     copyup
01
        LDR     R14,[R2],#4
        STR     R14,[R1],#4
        SUBS    R3,R3,#4
        BGT     %BT01

        Pull    "R1-R3,PC"

copyup
        ADD     R1,R1,R3
        ADD     R2,R2,R3
01
        LDR     R14,[R2,#-4]!
        STR     R14,[R1,#-4]!
        SUBS    R3,R3,#4
        BGT     %BT01

        Pull    "R1-R3,PC"

        ; Mergeblocks
        ;
        ; Entry:  R1,R2,R3 --> delimit 2 blocks (consecutive)
        ;         R4 = 0/1 (merge horizontally/vertically)
        ;
mergeblocks
        Debug   mg,"Merge blocks: ",R1,R2,R3

        TEQ     R4,#0
        MOVNE   PC,LR                   ; vertical merge

        Push    "R1-R3,R5-R6,LR"

        LDR     R5,sp1_width
        ADD     R5,R5,#1
        MOV     R5,R5,LSL #2            ; R5 = size of 1 row (bytes)
        LDR     R6,sp2_width
        ADD     R6,R6,#1
        MOV     R6,R6,LSL #2            ; R6 = size of 1 row (bytes)
01
        ADD     R1,R1,R5
        CMP     R1,R2
        BCS     %FT02                   ; no more to do
        ADD     R3,R2,R6
        BL      swapblocks
        ADD     R1,R2,R6
        MOV     R2,R3
        B       %BT01
02
        Pull    "R1-R3,R5-R6,PC"



        ; Maskmergeblocks
        ;
        ; Entry:  R1,R2,R3 --> delimit 2 blocks (consecutive)
        ;         R4 = 0/1 (merge horizontally/vertically)
        ;NOTE: Only called for new format sprites
        ;
maskmergeblocks
        Debug   mg,"Mask merge blocks: ",R1,R2,R3

        TEQ     R4,#0
        MOVNE   PC,LR                   ; vertical merge, so return

        Push    "R1-R3,R5-R6,R8,LR"

        LDR     R8,sp2_rbit
        LDR     R5,sp2_width
        BL      FindMaskWidth           ;Convert R5
        ADD     R6,R5,#1
        MOV     R6,R6,LSL #2            ; R6 = size of 1 row (bytes)
        Debug   ag,"Size of 1 row of sprite2 is (bytes)",R6
        LDR     R8,sp1_rbit
        LDR     R5,sp1_width
        BL      FindMaskWidth           ;Convert R5
        ADD     R5,R5,#1
        MOV     R5,R5,LSL #2            ; R5 = size of 1 row (bytes)
        Debug   ag,"Size of 1 row of sprite1 is (bytes)",R5
01
        ADD     R1,R1,R5
        CMP     R1,R2
        BCS     %FT02                   ; no more to do
        ADD     R3,R2,R6
        BL      swapblocks
        ADD     R1,R2,R6
        MOV     R2,R3
        B       %BT01
02
        Pull    "R1-R3,R5-R6,R8,PC"

        ; FindMaskWidth - convert spWidth for data to spWidth for mask (1bpp masks)
        ;
        ; NOTE: This routine should be similar to GetMaskspWidth in VduGrafH except it does
        ;       not return the updated PSR
        ;
        ;       Internal routine.
        ;
        ; in:   R5 = spWidth (ie width in words-1)
        ;       R8 = spRbit
        ;       (expects R2->sprite info)
        ;
        ; out:  R5 = spWidth (words -1) for mask data
        ;       R8 = Last bit (spRBit) used in mask data
        ; MUST only be called for new format sprites
FindMaskWidth ROUT
        Push    "R0, LR"
        Debug   ag,"Entered GetMaskspWidth with R5,R8",R5,R8

        LDR     LR, sp_mode             ; 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
        LDRB    LR, [R0, LR]            ; 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
        ADD     R8, R8, #1
        ADD     R5, R5, R8, 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

        Debug   ag,"Left GetMaskspWidth with R5,R8",R5,R8

        Pull    "R0, PC"


; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; SpriteOp_CheckSpriteArea
; ------------------------
; Entry: R0 = reason code
;        R1 = areaCBptr
;        R10 & R11 stacked
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Go_CheckSpriteArea
        MOVS    r0, r0, LSR #8          ; system area sprites historically not checked
        MOVEQ   pc, lr

        Push    "r1-r5, lr"             ; 6 regs + r0/r10/r11 to mess with, total 9

        TST     r1, #3                  ; at least need an aligned area to start with
        BNE     %FT99
        
        ASSERT  SpriteAreaCBsize = (4 * 4)
        LDMIA   r1, {r2-r5}
        Debug   so, "Read saEnd/saNumber/saFirst/saFree",r2,r3,r4,r5

        ORR     r14, r4, r5
        TST     r14, #3
        BNE     %FT99                   ; saFirst or saFree not word aligned

        CMP     r4, r5                  ; saFirst > saFree
        CMPLS   r5, r2                  ; saFree > saEnd
        BHI     %FT99 

        ADD     r11, r1, r5             ; abs address of end of used portion
        ADD     r10, r1, r2             ; abs address of end of entire area
        B       %FT40
10
        ; For each sprite, do the following checks
        Debug   so, "Contemplating sprite at",r1

        ADD     r0, r1, #SpriteCBsize
        CMP     r0, r11                 ; range check the header before reading from it
        BHI     %FT99
        Debug   so," + passed control block accessible"

        CheckAlignedAndWithin r1,spNext,r11,r0
        Debug   so," + passed spNext within used area test"

        CheckAlignedAndWithin r1,spImage,r10,r0
        Debug   so," + passed spImage within area test"

        CheckAlignedAndWithin r1,spTrans,r10,r0
        Debug   so," + passed spTrans within area test"

        LDR     r4, [r1, #spLBit]
        LDR     r0, [r1, #spRBit]
        ORR     r0, r0, r4
        CMP     r0, #32
        BCS     %FT99
        Debug   so," + passed LBit/RBit bit count test"

        LDR     r5, [r1, #spMode]
        ASSERT  SpriteType_Old = 0
        MOVS    r0, r5, LSR#27
        TEQNE   r4, #0
        BNE     %FT99
        Debug   so," + passed if new type sprite has LBit=0"

        LDR     r0, [r1, #spWidth]
        LDR     r4, [r1, #spHeight]
        ADD     r0, r0, #1
        MOV     r0, r0, LSL#2           ; width in bytes because bw = (ww + 1) x 4
        MLA     r4, r0, r4, r0          ; image bytes because bw x (h + 1) = (bw x h) + bw
        Debug   so," ...(w + 1) x (h + 1) in bytes",r4

        LDR     r0, [r1, #spImage]
        LDR     r2, [r1, #spTrans]
        TEQ     r0, r2                  ; check if there's a mask
        BEQ     %FT20
        Debug   so," ...has a mask"

        SUB     r14, r2, r0
        CMP     r14, r4
        BCC     %FT99
        Debug   so," + passed image space >= image size" 

        ASSERT  SpriteType_Old = 0
        MOVS    r14, r5, LSR#27
        BNE     %FT15
        Debug   so," ...type = MODE number",r5

        LDR     r14, [r1, #spNext]
        SUB     r14, r14, r2
        CMP     r14, r4
        BCC     %FT99
        Debug   so," + passed mask space >= image size"

        SUB     r2, r2, r0
        TEQ     r2, r14
        BNE     %FT99
        Debug   so," + passed mask space = image space"
        B       %FT30
15
        Debug   so," ...type = new sprite type",r5
        TST     r5, #1
        MOVNE   r14, r5, LSL#18
        MOVNES  r14, r14, LSR#19        ; horizontal dpi in bits 1-13
        MOVNE   r14, r5, LSL#5
        MOVNES  r14, r14, LSR#19        ; vertical dpi in bits 14-26
        BEQ     %FT99
        Debug   so," + mode b0=1 and non zero dpi"

        ADRL    r4, NSM_bpptable
        MOV     r14, r5, LSR#27
        LDRB    r4, [r4, r14]
        Debug   so," ...log2bpp",r4
        LDR     r0, [r1, #spRBit]
        ADD     r0, r0, #1
        MOV     r0, r0, LSR r4          ; pixels_in_part_words = (spRBit + 1) >> log2bpp
        RSB     r14, r4, #5
        LDR     r4, [r1, #spWidth]
        MOV     r4, r4, LSL r14         ; pixels_in_whole_words = width << (5 - log2bpp)
        ADD     r14, r0, r4
        ADD     r14, r14, #31
        BIC     r14, r14, #31           ; round up to nearest whole word
        MOV     r14, r14, LSR#3         ; convert to bytes since it's assumed 1bpp
        LDR     r0, [r1, #spHeight]
        MLA     r14, r0, r14, r14       ; bytes size = width * (height + 1)

        LDR     r0, [r1, #spNext]
        ADD     r2, r14, r2
        TEQ     r0, r2
        BNE     %FT99
        Debug   so," + image dimensions as 1bpp mask = mask size of",r14
        B       %FT30
20
        Debug   so," ...has no mask"
        LDR     r14, [r1, #spNext]
        SUB     r14, r14, r0
        CMP     r14, r4
        BCC     %FT99
        Debug   so," + passed image space >= image size" 
30
        ; Next
        SUB     r3, r3, #1
        LDR     r4, [r1, #spNext]
40
        ADD     r1, r1, r4
        TEQ     r3, #0
        BNE     %BT10
        Debug   so, "Passed all checks"
        CLRV
        Pull    "r1-r5, pc"
99
        Debug   so, "There's something wrong with the sprite area"
        ADR     r0, ErrorBlock_BadData
        BL      copy_error_one          ; also sets V
        Pull    "r1-r5, pc"

        MakeSpriteErrorBlock BadData,,BadData

        END