
**********************************************************************
files: SaveLoad.TXT, SaveLoad.TAP
       SAVEasOne.BAS, BASICode.BAS, LoadCode128.TAP
       StakProg.BAS, StakProg.ASM
       Auto-RUN.ASM, SAVEline.BAS, SAVEline.ASM, SCRslice.BAS
       CheckBAS.BAS
**********************************************************************


** SAVE-ing SCREEN$ & BASIC as one file
   ====================================

The demo below shows one method for saving the current screen & program as one file which can be restored with LOAD "" CODE. The program will then continue running from the next command after the SAVE - the GO TO 30 in this case. The CLEAR down is to make the file as small as possible; the program would need to move RAMTOP higher on resuming. Note that the "Start tape" message won't appear before the SAVE ... CODE but you still need to press a key beforehand. The loop at line 30 is just to show that the screen has been restored.

SAVEasOne.BAS [code]
  10 LIST : GO SUB 20: SAVE !"screen"SCREEN$ :
     CLEAR stkend+300: GO SUB 20: PRINT ramtop: PAUSE 0:
     LOAD !"screen"SCREEN$ : POKE chans,111:
     SAVE "screenprog"CODE 16384,ramtop-16384: GO TO 30
  20 LET stkend=PEEK 23653+256*PEEK 23654:
     LET ramtop=PEEK 23730+256*PEEK 23731:
     LET chans=PEEK 23631+256*PEEK 23632:
     RETURN
  30 IF INKEY$<>"j" THEN GO TO 30
  40 CLS : PRINT "I'm back!": STOP
[/code]

   ===================================

To save a BASIC program as CODE and restore it cleanly, it needs to have been saved from 23552 (start of system variables) to RAMTOP. Restoring this will return to the program after the original SAVE command. If less than this was saved then either the system variables or the machine & gosub stacks will have been lost, so even if the BASIC chunk is restored into the correct place the Speccy will either crash or return "Nonsense in BASIC" as the environment won't match that which existed when it was saved. Restoring it as a normal BASIC program file can be done as follows.

Use the "loadcode" program included with this archive (or else use an emulator's tape browser) to find the saved CODE start & length. The CODE can then be loaded above RAMTOP to identify the BASIC segment which was originally between PROG and VARS. If the original CODE block is loaded at address ORIG above RAMTOP, then the system variable PROG will be at ORIG+83 and VARS will be at ORIG+75. Then PROG+ORIG-23552 should give the start address of the BASIC segment as loaded above RAMTOP. To confirm this, P_RAMT should be at ORIG+180 and hold 65535.

If the BASIC segment is at address ADDR and length LEN, then:

* POKE ADDR+LEN,128 (the "end of VARS" marker, setting this at zero length).

* Calculate three numbers:
  PROG  = ADDR-17
  VARS  = ADDR+LEN-17
  ELINE = ADDR+LEN+1-17
  Do NOT assign these as variables, as the following POKEs will render them inaccessible; make a note of the values and type in the actual numbers in the following POKEs.

* Type in as a single direct command:
  POKE 23635,PROG-INT(PROG/256)*256:   POKE 23636,INT(PROG/256):
  POKE 23627,VARS-INT(VARS/256)*256:   POKE 23628,INT(VARS/256):
  POKE 23641,ELINE-INT(ELINE/256)*256: POKE 23642,INT(ELINE/256):
  SAVE "prog"

* Reset the Spectrum, as this will have messed up everything, and then LOAD "prog" - and there's the program, minus any variables that might have been saved with it originally. The subtraction of 17 from all the values is because the SAVE routine assigns 17 bytes of workspace for the tape header, and with PROG, etc, being located above the workspace rather than below it, that offset has to be deducted otherwise the wrong position is calculated from which to save the program.

   ===================================

This code saves on 48k/128k models in either mode and preserves the mode after loading. It saves from 22528 to RAMTOP but minimises the file size by shifting down RAMTOP close to STKEND while still preserving variables. As it saves to RAMTOP it also saves the machine stack including the error return address which varies between models.

A 48k save will load in 48k mode on any 48k/128k model. A 128k save will load in 128k mode on the same model but not on other models as the extra system variables area is incompatible between models. The original RAMTOP, VARS and variables are restored after both save & load. Although the demo does a STOP afterwards it could just continue running from the target line after restoring RAMTOP & VARS. The line number could be passed as a parameter or POKEd rather than being hard-coded.

I'm just using the standard tape routines, but those parts could be replaced with calls to alternative routines. To preserve the GO SUB stack as well use [url=http://www.worldofspectrum.org/forums/showpost.php?p=668647&postcount=3]PUTRAM[/url] instead of CLEAR.

BASICode.BAS [code]
   3 REM main program, lines 3-8999
7000 BORDER 4: PRINT "LOAD OK": GO TO 9006
9000 REM GO TO 9000 to SAVE program as headerless CODE
9001 DEF FN p(a)=PEEK a+256*PEEK (a+1)
9002 REM store RAMTOP in SEED, VARS in PR_CC, CLEAR in COORDS:
     REM preserve existing variables by shifting VARS to E_LINE then CLEAR:
9003 LET clrto=FN p(23653)+200: LET bytes=clrto-22528+1:
     RANDOMIZE clrto:        POKE 23677,PEEK 23670: POKE 23678,PEEK 23671
9004 LET RAMTOP=FN p(23730): POKE 23670,PEEK 23730: POKE 23671,PEEK 23731:
     LET VARS=FN p(23627):   POKE 23680,PEEK 23627: POKE 23681,PEEK 23628:
     LET ELINE=FN p(23641):  POKE 23627,PEEK 23641: POKE 23628,PEEK 23642:
     CLEAR FN p(23677)
9005 PRINT USR 65368
9006 REM restore RAMTOP & VARS, print restored variables
9007 PAUSE 0: CLEAR FN p(23670):
     POKE 23627,PEEK 23680: POKE 23628,PEEK 23681:
     PRINT clrto,bytes,RAMTOP,VARS,ELINE: STOP
[/code]

BASICode.ASM [code]
        org  65368
        ;save a BASIC program as headerless CODE
SAVEPROG:
        xor  a
        dec  a               ;A =$ff, Carry reset for SAVE
        ld   ix,22528        ;IX=start address
        ld   hl,(23730)
        ld   de,22528
        sbc  hl,de
        ex   de,hl
        inc  de              ;DE=bytes
        push de
        CALL $04C2           ;SA_BYTES
        pop  bc
        ret

        ;load a BASIC program as headerless CODE
;       test load with "RANDOMIZE bytes: CLS: PRINT USR 65390"

LOADPROG:
        ld   a,$ff
        scf                  ;A =$ff, Carry set for LOAD
        ld   ix,22528        ;IX=start address
        ld   de,(23670)      ;DE=bytes
        CALL $0556           ;LD-BYTES

        ld   hl,(23613)      ;(ERR_SP)
        ld   sp,hl           ;reset stack

        ld   hl,$1b9e        ;LINE_NEW
        bit  4,(iy+$01)
        jr   z,MCCONT
        ld   a,(19)
        inc  a               ;A =168 if +2A/+3 else 0
        ld   d,a
        ld   a,(5440)        ;A =83 if Sinclair else 65 if Amstrad
        add  a,d             ;A =83 +128k; =65 +2; =233 +2A/+3/+3e
        cp   233
        jr   nz,MCCONT
PLUS3M: ld   hl,$1031        ;STMT-RET (+2A/+3/+3e ROM.1)
        push hl
        ld   hl,$5b2f        ;REGNUOY  (+2A/+3/+3e paging)
MCCONT: push hl              ;stack ROM return address
        LD   A,$ff
        LD   (23610),A       ;reset error code
        LD   HL,7000         ;line number of BASIC return
        ld   a,1             ;statement number
        ld   d,a
MCLOOP: CALL $1f54           ;BREAK_KEY
        JP   C,$1e73         ;GO_TO_2 - jump if not BREAK
        JR   MCLOOP          ;ignore BREAK
[/code]


**********************************************************************


** SAVE-ing without waiting for a keypress
   =======================================

The usual method is to alter the channel K I/O routine addresses in the CHANS area (starting at PEEK 23631+256*PEEK 23632 - normally 23734 on a 48K Spectrum with nothing attached). Channel K data starts at the beginning of CHANS, Output address {2548=$09F4} followed by Input address {4264=$10A8} followed by device code (75="K").

So, for example, POKE 23734,111 changes the Output address to jump to a RET, thus stopping the message; whereas POKE 23736,20 changes the Input address to jump to a RET, thus stopping the pause.

You could also use POKE 23736,181 to stop the pause; this jumps into KEY-INPUT a little further down, skipping the keyboard scan and acting as though a key had already been pressed (whatever is in LAST_K).


**********************************************************************


** LOAD-ing without displaying the filename
   ========================================

The usual method would be to alter the channel S output routine address in the CHANS area (starting at PEEK 23631+256*PEEK 23632 - normally 23734 on a 48K Spectrum with nothing attached); channel S data starts at (CHANS)+5. POKE 23739,111 changes the output routine address to jump to a RET, thus stopping the message; POKE 23739,244 restores output.

The values would differ for the 128k Spectrums, as the ROMs vary, but the general approach is to note the address of the S channel output routine, change it to point to a byte containing 201 (a RET instruction) to disable screen output, then restore the original address when screen output is to be restored.

A similar approach for channel K data which starts at (CHANS) deals with the save message & pause. A channel definition block consists of a 2-byte output routine address + 2-byte input routine address + 1-byte device ID.


**********************************************************************


** Saving BASIC Programs in a Stack
   ================================

* Set RAMTOP as low as possible while still allowing each program space to run. Allowing 200 bytes or so above STKEND for small programs (say 1.5kb) would require about 2kb for each one, so even being a bit more generous there should be space for about 15 programs of that size.
* Load each program in turn.
* For each program, copy the area from 23552 (start of system variables) to RAMTOP to sequential chunks of memory above RAMTOP.
* Once they're all stacked up above RAMTOP, save them as one CODE chunk, preceded by the loader menu program.

If the loader menu itself is included in the program stack, then each program could include a bit of code to reload the menu program from the stack so it doesn't get lost.


Here's the machine code for manipulating the program stack, plus a small BASIC demo program.

To use the m/code routines:
RANDOMIZE n ( where n is the program number in the range 0-18 )
LET bc=USR get (to get program n from the stack)
LET bc=USR put (to put program n onto the stack)

Note:
RANDOMIZE 255 for program 0, as RANDOMIZE 0 puts FRAMES into SEED, not 0.
The program size is set to exactly 2k to simplify calculations, so RAMTOP should be set by CLEAR 25599.
The screen display & attributes are not saved.

StakProg.BAS [code]
  12 LET get=25600: LET put=25629
  13 INPUT "This Program# [0-18] ";this: LET this=INT this:
     IF this<0 OR this>18 THEN GO TO 13
1000 INPUT "Stack Program# [0-18] ";prog: LET prog=INT prog:
     IF prog<0 OR prog>18 THEN GO TO 1000
1001 IF NOT prog THEN LET prog=255
1010 INPUT "Get/Put? [gp] ";p$: CLS
1020 IF p$="p" THEN RANDOMIZE prog: LET bc=USR put:
     LET prog=prog AND prog<>255:
     PRINT "Program#";this;" PUT as #";prog
1030 IF p$="g" THEN RANDOMIZE prog: LET bc=USR get
1035 PRINT "This is program ";this
1040 INPUT "Again? [yn] ";a$:
     IF a$<>"y" AND a$<>"n" THEN GO TO 1040
1050 IF a$="y" THEN GO TO 13
1090 STOP
[/code]

StakProg.ASM [code]
        ORG  25600

GET:    LD   A,(23670)     ;A =program# from SEED (255=0)
        INC  A
        JR   Z,NODEC1
        DEC  A
NODEC1: LD   C,0
        LD   B,A
        SLA  B
        SLA  B
        SLA  B             ;BC=program# * 2048
        LD   HL,STACK      ;HL=base of program stack
        ADD  HL,BC         ;HL=base of program#
        LD   DE,23552      ;DE=start of system variables
        LD   BC,2048       ;BC=length of program
        LDIR               ;copy program# from stack
        RET

PUT:    LD   A,(23670)     ;A =program# from SEED (255=0)
        INC  A
        JR   Z,NODEC2
        DEC  A
NODEC2: LD   C,0
        LD   B,A
        SLA  B
        SLA  B
        SLA  B             ;BC=program# * 2048
        LD   HL,STACK      ;HL=base of program stack
        ADD  HL,BC         ;HL=base of program#
        EX   DE,HL         ;DE=base of program#
        LD   HL,23552      ;HL=start of system variables
        LD   BC,2048       ;BC=length of program
        LDIR               ;copy program# to stack
        RET

STACK:  NOP
[/code]


**********************************************************************


** Using the ROM's tape routines from assembler
   ============================================

To summarise all the options:
A = $00 for a header block, $FF for a data block
DE= number of bytes (17 ($11) for a header, otherwise whatever length)
IX= start address
Carry set for load, reset for verify, undefined for save

When using the ROM's tape routines, pressing BREAK/SPACE will interrupt the process and return to BASIC; it's supposed to do that.

Header format:
offset ($00) = type (0/1/2/3 = program/narray/$array/bytes)
------ ($01-$0A) = name ($FF = null name)
------ ($0B-$0C) = number of bytes in total
for program:
------ ($0D-$0E) = LINE number; ($0E)=$80 if no LINE
------ ($0F-$10) = number of bytes in program (ie. excluding variables)
for array:
------ ($0E) = array name
for bytes:
------ ($0D-$0E) = start address

The header is only essential if the BASIC LOAD command might be used to read the file.

   ============================================

Mix & match the lines as appropriate for the required option.

[code]
        XOR  A         ;A = $00 for a header block
or
        LD   A,$FF     ;$FF for a data block
        SCF            ;Carry set for load
or
        AND A          ;Carry reset for verify
        LD   DE,????   ;DE= number of bytes
        LD   IX,????   ;IX= start address
        CALL $04C2     ;SA-BYTES
or
        CALL $0556     ;LD-BYTES
[/code]


**********************************************************************


** Force Auto-RUN after LOAD
   =========================

Here's a routine to auto-run a program which wasn't saved with LINE. If the program already has an auto-run LINE in the header then that one is used otherwise LINE 1 is used.

Auto-RUN.ASM [code]
        org  65368
        NOP
AUTRUN: LD   BC,34
        RST  $30           ;make BC-SPACES of workspace room
        DEC  DE            ;DE= start of header workspace
        PUSH DE
        XOR  A             ;A = $00 for a header block
        SCF                ;Carry set for load
        LD   DE,17         ;DE= number of bytes
        POP  IX            ;IX= load address
        PUSH DE
        PUSH IX
        CALL $0556         ;LD-BYTES load new program header
        POP  IX
        PUSH IX
        LD   A,(IX+$0E)
        AND  $C0
        JR   Z,SKIPIT      ;jump if LINE already set
        LD   (IX+$0D),1
        LD   (IX+$0E),0    ;start LINE=1

SKIPIT: POP  HL            ;HL= address of workspace
        POP  BC            ;BC= length of header
        ADD  HL,BC
        PUSH HL
        POP  DE
        PUSH DE
        SBC  HL,BC
        LDIR               ;copy header to other half of workspace
                           ;then store old program dimensions
                           ;in the original header area
        LD HL, ($5C59)     ;E-LINE
        LD DE, ($5C53)     ;PROG
        SCF
        SBC HL, DE         ;length of old program & variables
        LD (IX+$0B), L
        LD (IX+$0C), H
        LD HL, ($5C4B)     ;VARS
        SBC HL, DE         ;length of old program
        LD (IX+$0F), L
        LD (IX+$10), H

        POP  IX            ;IX= address of header copy
        LD   HL,14
        ADD  HL,SP
        LD   SP,HL         ;undo USR
        EX DE, HL          ;HL= PROG
        JP   $0808         ;LD-CONTRL
[/code]


**********************************************************************


*  Saving part of a BASIC program
   ==============================
SAVEline
Here's a routine to save part of a BASIC program. The idea is that it can be merged into a program and then used to save sub-routines, etc. to be combined or merged into other programs. It's sort of a slow version of copy & paste. The two line numbers don't have to exist; it saves from the first line >= start to the last line <= end; it traps invalid pairs. There's no "Start tape ..." pause; it just saves the chunk straight away after typing in the numbers & file name.

The routine uses BASIC for the setup and a short m/code routine to do the actual save. The program file header is constructed in l$(). The start address 'ap' is put into SEED by the "RANDOMIZE ap"; the length 'l' is put on the calculator stack by the "s=l+USR VAL "m+20""; from which places the m/code can pick them up.

The call to FP_TO_BC removes that value from the stack and the interpreter will still expect it to be there, hence the call to STACK_BC at the end, which just stacks a trash value, otherwise the return to BASIC would give a "C Nonsense in BASIC" error.

The reasons that I've put the DEF FNs on line 3 are to avoid any potential conflict with DEF FNs in the main program, and to make the routine substantially quicker if it is used with a large program. Although the interpreter is quite quick at dealing with GO SUBs/TOs as it only checks the line numbers, with DEF FNs it checks every statement on every line and so is very much slower if they are located at the end of a large program.

   ==============================

SAVEline allows multiple saves and saving of variables. To save space the machine code is in a DATA line rather than a separate variable. The first run assembles the m/code in situ, so after the routine has been initialised then it won't be assembled again as it will already reside in the DATA line (although a LIST will stop at that line). Even more space is saved by discarding the original hex code as part of the initialisation procedure.

The routine is initialised with "RUN 9999"; this assembles the m/code and then uses itself to save itself excluding line 9999, thus discarding the initialisation routine and the hex code. It can then be MERGEd into other programs and run with "RUN 9990". Although line 9991 appears to be inaccessible, the preceding RETURN gets changed to RANDOMIZE by the initialisation sequence.

SAVEline loops prompting for "From" and "To" line numbers until zeroes are entered. To save the main program's variables (ie. those for the program into which the routine has been MERGEd) start the routine with GO TO rather than RUN, then type the letter 'v' into "From" and the letter 'e' into "To" (short for "From VARS To E_LINE"). The main program's variables are preserved by moving up VARS so that the routine doesn't change any which might happen to have the same names as those used in the routine itself. The original settings are restored when the program finishes.

The empty string DATA item is so that a READ of it can be used to set DATADD (system variable "address of terminator of last DATA item" at 23639) which can then be used by the m/code to find the position of the file header (DATADD+2) which is constructed in the next DATA item. The same technique is also used to calculate where to assemble the m/code.

SAVEline.BAS [code]
   3 DEF FN g(v)=VAL "v-FN h(v)*256": DEF FN h(v)=VAL "INT (v/256)":
     DEF FN p(a)=VAL "PEEK a+256*PEEK (a+1)":
     DEF FN n(n)=n-CODE "0"-(7 AND n>=CODE "A")
9990 CLS: RANDOMIZE USR VAL "FN p(23637)+96":
     LET v=FN p(VAL "16384"): LET e=FN p(VAL "16398"):
     LET k0=NOT PI: LET k1=SGN PI: LET k2=k1+k1: LET k4=k2+k2:
     LET m=VAL "FN p(23637)+27": POKE m,k0: RETURN
9991 GO SUB VAL "9992": GO TO VAL "9991": DATA "","01234567891234567",
     "012345678901234567890123456789012345678901234567890123456789012345678901234567891234567"
9992 INPUT "From ";a1,"To ";a2:
     IF a1=v AND a2=e THEN
       LET la=v: LET lz=e: LET ap=v: LET av=e: GO TO VAL "9994"
9993 LET a0=FN p(VAL "23635"): LET l1=k0:
     LET r=k1: LET l=a1: GO SUB VAL "9996": LET la=l1: LET ap=a0-k4-l2:
     LET r=k2: LET l=a2: GO SUB VAL "9996": LET lz=l1: LET av=a0-(k4+l2 AND lz>a2):
     LET lz=lz-(lz-l0 AND lz>a2)
9994 DIM f$(VAL "10"): INPUT "SAVE ";(la);" to ";(lz);" as:"'f$:
     IF f$(k1)=" " THEN
       PRINT #k0;"F Invalid file name, 9994:0":
       PAUSE k0: RANDOMIZE USR VAL "m+88": STOP
9995 LET l=av-ap: LET l$=CHR$ FN g(l)+CHR$ FN h(l):
     LET l$=f$+l$+CHR$ k0+CHR$ VAL "128"+(l$ AND ap<>v)+((CHR$ k0+CHR$ k0) AND ap=v):
     FOR s=k1 TO LEN l$: POKE m+s,CODE l$(s): NEXT s:
     RESTORE VAL "9991": READ f$: RANDOMIZE ap: LET s=l+USR VAL "m+20":
     RETURN
9996 FOR s=k0 TO k0 STEP k0: LET l0=l1:
       LET l1=PEEK a0*256+PEEK (a0+k1):
       LET l2=FN p(a0+k2): LET a0=a0+k4+l2:
       IF l1<l AND a0<v THEN PRINT AT r,k0;l1: NEXT s
9997 IF l1<a1 OR a2<l0 THEN
       PRINT #k0;("N Statement lost, "+STR$ l+":0") AND a1+a2:
       PAUSE NOT (a1+a2): RANDOMIZE USR VAL "m+88": STOP
9998 RETURN

9999 RESTORE 9999: READ f$: LET m=FN p(23639)+2:
     RESTORE 9991: READ f$: LET n=FN p(23639)+22:
     LET c=98: FOR r=0 TO 2: FOR s=m TO m+c-2 STEP 2:
       POKE n,16*FN n(PEEK s)+FN n(PEEK (s+1)): LET n=n+1:
     NEXT s: LET c=38: LET m=s+3: NEXT r:
     GO SUB 9990: POKE m-29,249: LET a1=1: LET a2=9998: GO SUB 9993: STOP:
     DATA "","AF1111002A575C2323E5DDE1CDC204CDA22DC5D12A765CE5DDE1192B7EF5E5FE0D280236803EFFCDC204CD2B2DE1F177C9",
     "214B5C110040011000EDB02A595C2B224B5CC9","210040114B5C011000EDB02A595C2B3680C900"
[/code]

SAVEline.ASM [code]
        ;stored in the line 9999 DATA statement
SAVEBA: xor  a               ;A =$00=header
        ld   de,17           ;DE=length of header
        ld   hl,(23639)      ;HL=(DATADD)="DATA ""," end address
        inc  hl
        inc  hl              ;skip over ,"
        push hl
        pop  ix              ;IX=start of program header
        call $04c2           ;SA_BYTES
        call $2da2           ;FP_TO_BC
        push bc
        pop  de              ;DE=length of program data
        ld   hl,(23670)      ;HL=(SEED)=start of program data
        push hl
        pop  ix
        add  hl,de
        dec  hl
        ld   a,(hl)          ;A =last byte to save
        push af
        push hl
        cp   $0d
        jr   z,NOVARS        ;if last byte=ENTER, then not saving variables
        ld   (hl),$80        ;temporary end-of-VARS marker
NOVARS: ld   a,$ff           ;A =$ff=data
        call $04c2           ;SA_BYTES
        call $2d2b           ;STACK_BC
        pop  hl
        pop  af
        ld   (hl),a          ;restore byte
        ret

SAVESV: ld   hl,23627
        ld   de,16384
        ld   bc,16
        ldir                 ;save (VARS)->(E_LINE)
        ld   hl,(23641)      ;HL=(E_LINE)
        dec  hl
        ld   (23627),hl      ;set  (VARS)=(E_LINE)-1
        ret

RESTSV: ld   hl,16384
        ld   de,23627
        ld   bc,16
        ldir                 ;restore (VARS)->(E_LINE)
        ld   hl,(23641)      ;HL=(E_LINE)
        dec  hl
        ld   (hl),128        ;restore end-of-VARS marker
        ret
[/code]


**********************************************************************


** Using MERGE in a BASIC program
   ==============================

If a MERGE is executed while a program is running, the possible results appear to be one of the following:

A. If there is another statement on the same line as the MERGE command, then that next statement will be executed after the MERGE;
B. Else, If the old next line is not overwritten by the MERGE, then that next line will be executed after the MERGE;
C. Else, If the old next line is overwritten by the MERGE, or there was no next line prior to the MERGE, then the program will finish with a 0 OK, LLLL.SSSS report, where LLLL.SSSS are the line.statement number of the MERGE command.

So the only way to get a program to continue within the MERGEd code would seem to be to append a GO TO statement on the MERGE line itself.


**********************************************************************


SAVE/LOAD a Screen Slice
========================
SCRslice.BAS
SCRslice.ASM
The two versions work in more or less the same way; the output files are interchangeable. I did the pair to compare timings, and also because it's a bit easier to understand the program structure with the BASIC version.

display file addressing (HL):
H =010ssppp, ss=segment 0-2, ppp=pixrow 0-7;
L =rrrccccc, rrr=char.row 0-7, ccccc=char.col 0-31

starting values:
segment = INT(row/8)
pixrow  = 0
charrow = row-INT(row/8)*8
charcol = 0

starting address:
H = 64 + segment*8
L = charrow*32
HL= H*256 + L

SCRslice.BAS [code]
  10 CLEAR 65535-24*32*8-24*32: LET df=FN p(23730)+1: LET ram=df:
     DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN l(v)=v-256*FN h(v): DEF FN h(v)=INT (v/256):
     REM find FN q() from DATADD
  20 DEF FN q(r,c$,s,d$,l)=33: RESTORE 20: LET fnq=FN p(23639)+8:
     LET qlo=FN l(fnq): LET qhi=FN h(fnq):
     POKE fnq+15,0: POKE fnq+32,0: GO TO 100:
     REM zero hi-bytes of length parameters

  33 POKE 23563,qlo: POKE 23564,qhi
  34 FOR a=fnq TO fnq+17 STEP 17:
       LET loc=VAL (CHR$ PEEK (a+os)): LET os=-os:
       POKE a+12,FN l(loc): POKE a+13,FN h(loc): POKE a+14,l
  35   POKE a+11,0:
     NEXT a: LET c$=d$: POKE 23564,0: RETURN

 100 INPUT "First row: ";first,"Last row: ";last:
     GO SUB 2100: PAUSE 0: LET os=0: GO SUB 2109
 141 SAVE "sliceBAS" CODE df,ram-df

1100 LET diff=last-first: INPUT "First row: ";first: LET last=first+diff:
     GO SUB 2100: LOAD "sliceBAS" CODE df,ram-df: CLS: LET ram=df:
     LET os=17: GO SUB 2109
1199 STOP

2100 LET first=ABS INT (first AND first>=0 AND first<=23):
     LET last=ABS INT (last-(last-first AND (last<first OR last>23))):
     LET H=256*(64+INT(first/8)*8): LET L=(first-INT (first/8)*8)*32
2101 LET p7=7*256: LET p8=p7+256: RETURN

2109 REM picture
2110 FOR r=first TO last
2120   FOR p=0 TO p7 STEP 256:
         GO SUB FN q(ram,"",H+p+L,"",32): LET ram=ram+32:
       NEXT p
2130   LET L=L+32: IF L>255 THEN LET L=0: LET H=H+p8
2135 NEXT r
2139 REM attributes
2140 LET BC=(last-first+1)*32: GO SUB FN q(ram,"",22528+first*32,"",BC):
     LET ram=ram+BC: RETURN
[/code]


**********************************************************************


** Recovering BASIC from a faulty tape
   ===================================

As long as the tape loading error occurs after the BASIC - ie. in the VARS variables area - then a CLEAR will reset that and leave you with the BASIC. However, if the error occurs within the BASIC then a CLEAR on its own won't work properly as the area between the error and the start of VARS will remain corrupted. The BASIC prior to the error can still be recovered; just set VARS to point to the start of the bad line and then do CLEAR.

To fix the latter situation, type in the following routine at line 1 and RUN it; this prints: line number, first command token, line address, lo&hi bytes of address; it will eventually start printing garbage. The address of the last line which makes sense, line 12 in the example, is where the error occurs, so set VARS to that address and CLEAR eg:
POKE 23627,57: POKE 23628,97: CLEAR
then running it again, or just doing a LIST, should confirm that the BASIC prior to the error is now OK.

CheckBAS.BAS [code]
   3 FOR a=PEEK 23635+256*PEEK 23636 TO PEEK 23627+256*PEEK 23628-1:
       LET n=256*PEEK a+PEEK (a+1): LET l=PEEK (a+2)+256*PEEK (a+3):
       PRINT n;TAB 5;CHR$ PEEK (a+4),
             a;TAB 24;a-INT (a/256)*256;TAB 28;INT (a/256):
       LET a=a+4+l-1:
     NEXT a: STOP
[/code]

[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/a001tempa.gif[/img] : [img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/a001tempb.gif[/img][/center]


**********************************************************************


** Why no 'Avalon' loading screen?
   ===============================

Quote:
Originally Posted by JuanF. Ramirez
It would be good if Steve Turner answered the question about why Avalon & Dragontorc didn't include a loading screen.
-----

In the meantime, here's some suggestions:
a) The dog ate it.
b) He left it on the bus.
c) He forgot to put the clock forward for BST and missed the bus.
d) "I've got your loading screen ri-i-ight he-e-re."
e) "Loading screens? We ain't got no loading screens. We don't need no loading screens! I don't have to show you any stinkin' loading screens!".
f) It was in his other trousers when his Mam washed them.
g) Mastertronic pinched it and then said they'd thought of it first.
h) He couldn't be bothered.
i) It's in the post.
j) It got lost in the post.
k) It's just arrived in the post.
l) He did one but it didn't turn out very well.
m) He forgot.
n) "So you're telling me NOW that you wanted a loading screen?!"
o) "You wanted it WHEN?!"
p) "I'm sorry, Dave, I'm afraid I can't do that."
q) The Spectrum crashed and he lost all his files.
r) The Microdrive crashed and he lost all his files.
s) The bus crashed and he lost all his files.
t) "It's everybody's fault but mine."
u) It's on his "to do" list.
v) It's on his "don't do" list.
w) "Ohhh, stupid loading screens. Who invented these dumb things, anyway?"
x) "You don't have to do loading screens when you're as big as I am."
y) "Loading screens? Where we're going, we don't need loading screens."
z) Yes, right, well, now, about that loading screen, the truth is ...


**********************************************************************