ROM calls and other tricks

Basic and Machine Language

Moderator: Moderators

User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

A replacement for INPUT#, especially useable for reading records in REL files. Here's an example.

Greetings,

Michael

Code: Select all

*=$033c

; REL file READ RECORD written 2008 by Michael Kircher

; writing a record into a REL file with PRINT# is okay, but reading it
; back with INPUT# is a bit of a nuisance, since INPUT# is restricted to
; 88 (on the 64: 80) chars, and also barfs at anything unexpected, like
; commas, quotes, and colons.

; this pair of routines reads in a complete record up to (but not including)
; the terminating CR, and assigns it to a string, without any those problems.

; - if the USR vector is not at 828, lower RAMTOP with POKE56,PEEK(56)-1:CLR
; - prepare Z$ once: Z$="I'M 17 CHARS LONG":Z$=Z$+Z$+Z$+Z$+Z$:Z$=Z$+Z$+Z$
; - POKE the following code to address 828
; - set up the USR vector with: POKE1,60:POKE2,3

; INPUT#channel,R$ is now replaced by: R$=LEFT$(Z$,USR(channel)):SYS873,R$

; which calls ...
.GetLine
 JSR $D7F7       ; FAC#1 to Integer in $14/$15
 LDA $15
 BNE GetLine_02  ; sec. address >= 256 -> error
 LDX $14
 JSR $E11B       ; CHKIN for channel in $14
 LDY #$00
 STY $FE         ; reset buffer pointer
.GetLine_00
 JSR $E10F       ; CHRIN
 CMP #$0D
 BEQ GetLine_01  ; exit loop, if CR
 LDY $FE
 STA ($37),Y     ; store into buffer
 INY
 STY $FE
 CPY #$FF        ; maximum length reached?
 BNE GetLine_00  ; no
.GetLine_01
 JSR $FFCC       ; restore I/O
 LDY $FE
 JMP $D3A2       ; return string length in FAC#1
.GetLine_02
 JMP $D248       ; flag error

; ... and is completed with:
.Assign
 JSR $CEFD       ; check for ','
 JSR $D08B       ; get variable address
 STA $14         ; which is the descriptor
 STY $15         ; for a string
 LDY #$00
 LDA ($14),Y
 PHA             ; push length
 INY
 LDA ($14),Y
 PHA             ; push low byte
 INY
 LDA ($14),Y
 STA $15         ; store high byte
 PLA
 STA $14         ; store low byte
 PLA
 STA $FE         ; store length
 LDY #$00
.Assign_00
 CPY $FE
 BEQ Assign_01
 LDA ($37),Y     ; copy from buffer to string
 STA ($14),Y
 INY
 BNE Assign_00
.Assign_01
 RTS             ; scary, but it works.
User avatar
Ghislain
Realms of Quest
Posts: 1283
Joined: Sun Aug 08, 2004 12:54 am

Post by Ghislain »

To do < = > you can do the following:

JSR $DC5B ;compare floating point pointed to by A:Y with FAC1; if FAC1=mem then A=0, if FAC1<mem then A=255, if FAC1>mem then A=1

Source:

http://www.experts-exchange.com/Program ... 19228.html

(which refers to BC5B in the C64 BASIC ROM, but the VIC-20 location for this is DC5B)
"A slave is one who waits for someone to come and free him." -- Ezra Pound
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Coding small time-delays

Post by Mike »

This small sub-routine may be useful for coding a small time delay up to 4 seconds:

Code: Select all

.Delay
 LDA $A2
 STA $FB
.Delay_00
 SEC
 LDA $A2
 SBC $FB
 CMP #xx
 BCC Delay_00
 RTS
... where xx gives the delay in 1/60 seconds, and $FB could also be another free (ZP) address. There's a small inaccuracy of up to 1/60 second, depending on whether the jiffy-clock has been updated right before, or is after the first LDA $A2 - but this shouldn't be important, if a delay of half a second (xx=30) or more is specified.
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

CHRIN patch sub-routine

Post by Mike »

Normally, CHRIN is supposed to set the C flag when that operation didn't succeed. However, I found with serial transfers the routine would always return with C cleared. But at least the status byte ST at $90 is set correctly, and I designed this patch sub-routine to get the desired behaviour:

Code: Select all

.GetByte
 JSR $FFCF ; call CHRIN
 PHA
 LDA $90
 CMP #$01  ; set C, if ST >= 1 (i.e. ST != 0)
 PLA       ; restore Accu, keep C flag, and set Z flag, if A=0
 RTS
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

PRIMM

Post by Mike »

I just dug this out from the depths of my HD:

It's a small (30 bytes) and relocatable PRIMM routine. The data loader below allows the ZP address be freely chosen, where PRIMM keeps the pointer to the next char to be printed:

Code: Select all

1 AD=673:ZP=251
2 FORT=0TO29:READA
3 IFA<0THENA=ZP-A-1
4 POKEAD+T,A:NEXT
5 :
6 DATA 104,133,-1,104,133,-2,230,-1,208,2,230,-2,160,0,177
7 DATA -1,240,5,32,210,255,144,239,165,-2,72,165,-1,72,96
The routine does not bother to stack the registers. Here's the source code:

Code: Select all

.Primm
 PLA
 STA zp
 PLA
 STA zp+1
.Primm_00
 INC zp
 BNE Primm_01
 INC zp+1
.Primm_01
 LDY #0
 LDA (zp),Y
 BEQ Primm_02
 JSR $FFD2
 BCC Primm_00 
.Primm_02
 LDA zp+1
 PHA
 LDA zp
 PHA
 RTS
User avatar
Wilson
Vic 20 Devotee
Posts: 228
Joined: Mon Sep 28, 2009 7:19 am
Location: Brooklyn, NY

Post by Wilson »

$E5BB (58811): The KERNAL's routine to initialize the VIC registers ($9000-$900F)
$E5B5 (58805): Sets the cursor to the home position and calls $E5BB.
wimoos
Vic 20 Afficionado
Posts: 352
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Compare by using EOR

Post by wimoos »

Comparing A to a memory location is normally done using CMP. CMP leaves the value of A intact and sets the N, Z and C-flag according to the result of the comparison.

Sometimes:
- you need to compare for equality and
- you want to keep the C-flag and/or
- need to clear some memory location or register after the comparison.
- and don't care about A no more.

The way to do this is using EOR (and maybe followed STA, TAY or TAX), fast and efficiently.

Sometimes, when you DO care about A, it is even quicker to EOR again, to recover the lost value.


Example from WimBasic where the '.' is handled as replacement for current line:

Code: Select all


(A contains first character after GOTO/LIST/etc and P holds status after JSR $0073)

LA673	JSR  LA679     	 ; handle possible '.' processing
	     JMP  $C8A3     	 

LA679	EOR  #'.'	; save carry while comparing to period
	     BNE  LA690     	 
	     LDA  $39       	 
	     LDX  $3A       	 
	     CPX  #$FF      	 
	     BNE  LA689     	 
	     LDA  $3B       	 
	     LDX  $3C       	 
LA689	STA  $14       	 
	     STX  $15       	 
        JMP  $0073  ; swallow the dot    	 

LA690	EOR  #'.'      	 ; recover character and perform goto linenumber (saves doing another JSR $0079)
        JMP  $C96B     	 


Another example from WimBasic where SuperNumbers are handled:

Code: Select all


LGETVADR	EOR  #$5C   ; pound sign
	     BEQ  LGTSNADR
	     JMP  $D08B  ; this routine will do a JSR $0079 again

LGTSNADR	STA  $0D ; A holds zero here, clear these locations to signify a float variable
	     STA  $0E
	     JSR  LA46F  ; calculate address of SuperNumber
	     JMP  $D18F  ; store it like $D08B does

Regards,

Wim.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
wimoos
Vic 20 Afficionado
Posts: 352
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

ML Optimization tips and tricks

Post by wimoos »

If you're in for ML optimization, then the following may help you. These hints are geared towards optimization in size. This generally speeds up processing the code as well, but not always with dramatic effect.
  • Remove NOP’s
    In general, remove code that does not do anything, like SEC when you know the carry is already set or loading a register with a value you know is already in there.
    Remove code that is never accessed
    Remove data that is not touched
    Change JSRs, followed by RTS into JMPs
    Rearrange code in order to remove JMPs or branches on a known condition
    Change JMPs into branches on a known condition
    Change CPX #$FF, CPX #$01, CPY #$FF, CPY #$01 followed by BEQ/BNE into INX, DEX, INY, DEY respectively, when the value in X or Y respectively is no longer necessary (carry will not be affected !)
    Change JMP or branch on a known condition over a single or double-byte instruction into a construction with BIT $zp or BIT $absolute instruction.
    A BEQ-branch taken after a CMP, CPY or CPX, will have the carry set, so no need to set it again.
    A comparison using EOR affects A and Z, but not the carry !
    A BEQ-branch taken after an EOR will have 0 in A: useful when X, Y or another memory location needs to be filled with 0 using TAX, TAY or STA.
    A subroutine that is called from only one location, may be moved inline to the calling code.
    Checking for a value of $80 or higher in A, X or Y can be done by TAX/TAY, TXA, TYA followed by BMI, provided that the value in the destination register is no longer necessary.
    Checking for a value of $00 in A, X or Y can be done by TAX/TAY, TXA, TYA followed by BEQ, provided that the value in the destination register is no longer necessary
    An ASL/ROL may help to save b7 (negative) a little longer in the carry.
    An LSR/ROR may help to save b0 a little longer in the carry
    Use the BIT instruction to test the status of a b6 or b7 in memory, instead of LDA, optionally followed by AND.
    Gather pieces of code together into subroutines.
The following ROM subroutines have the following known exit status:
  • $D391 (word to float) / $D3A2 (byte to float): V=0, Y=0
    $D7F7: V=0, C=0
    $D7EB / $D79B / $D79E: Y=0
    $D1B2 / $D1B5 / $D1B8: Y=0
    $DBA6 (float mem to acc#1): Y=0
    $C8C7: C=1
    $C8FB: Z=0
    $DDDD / $DDDF: A=0, Y=1
    $DDCD: C=1, Z=1
    $C96B: X=0, C=1
    $C660: Z=1
    $C8A3: Z=0
    $DCCC: A=contents of $61 (exp of acc#1)
    $CD9E: A,Z,N=contents of $61 (exp of acc#1), X=0, Y=$FF
    $DBC7: Y=0
    $DB0F: Y=0
There's many, many more..

Regards,

Wim.
Last edited by wimoos on Mon Dec 18, 2017 5:55 am, edited 2 times in total.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
metalfoot76
Vic 20 Amateur
Posts: 63
Joined: Fri May 31, 2013 10:37 pm

Re:

Post by metalfoot76 »

Mike wrote:Small routines for saving and loading blocks of memory:

SAVE memory block:

Code: Select all

SYS57809(N$),8,1:POKE193,start_lo:POKE194,start_hi
POKE780,193:POKE781,end_lo:POKE782,end_hi:SYS65496
'start' is inclusive, 'end' is exclusive. 'N$' contains the file name. Other device numbers than 8 can be used (also 1 for tape), but the additional ',1' must be kept.

LOAD memory block:

Code: Select all

SYS57809(N$),8,1:POKE780,0:SYS65493
Loads the file 'N$' to its correct address without restarting the BASIC program.

Michael

Edit: Removed unnecessary POKE147,0 instruction in LOAD memory block.
For the load block, does it work like the C64 equivalent routine, where you can force the exact load location by passing parameters through 781/782 (lb/hb)?
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

SAVEing and LOADing memory blocks

Post by Mike »

metalfoot76 wrote:For the load block, does it work like the C64 equivalent routine, where you can force the exact load location by passing parameters through 781/782 (lb/hb)?
Yes. The KERNAL jump table of VIC-20 and C64 was made for exactly this purpose. Even though the routines in ROM might be placed at slightly different addresses, the jump instructions in this table point to their place.

I found though there's a different behaviour of KERNAL for tape and disk, so it might be sensible to reprise this tip a bit and include the effect of different secondary addresses:


SAVE memory block:

Code: Select all

SYS57809(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
or 'SYS57809(N$),<device>,0'. <start> is inclusive, <end> is exclusive, N$ contains the file name. This should actually be the standard method for saving memory blocks. For disk, it makes no difference to the following method, but on tape only this one allows to load the block back to a different address it was saved from.


SAVE memory block (and force load address on tape):

Code: Select all

SYS57809(N$),<device>,1:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
For disk, effect is same as above. For tape, this forces to load back the back to the same address it was saved from, regardless what secondary address is given with the load command!


LOAD memory block ('relative' or 'relocating' load):

Code: Select all

SYS57809(N$),<device>:POKE780,0:POKE781,<start_lo>:POKE782,<start_hi>:SYS65493
or 'SYS57809(N$),<device>,0'. Load file to address <start>. This only works as intended unless the file had been saved on tape with 'force load address'. In that case, the load address on tape takes precedence!


LOAD memory block ('absolute' load):

Code: Select all

SYS57809(N$),<device>,1:POKE780,0:SYS65493
Load the file to the same address it was saved from.


Greetings,

Michael
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Comparing two registers

Post by Mike »

Normally, comparing two registers would involve (usually a zero-page) temporary:

Code: Select all

 STA aa
 CPX aa
... which does a "CPX A" or "CXA" (as in: calculate X-A, discard the result, and just reflect the result in the flags).

If you don't want to "spoil" a ZP address, but OTOH can afford a 1 byte longer and self-modifying code, the store instruction can instead write to the operand field of a CMP/CPX/CPY #imm instruction that follows:

Code: Select all

 STA compare+1
.compare
 CPX #$00
This also takes the same number of cycles (4+2 vs. 3+3) than the zp-temporary version.
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Divide 16-bit value by 8-bit constant

Post by Mike »

During a WIP, I (re-)discovered this handy routine to do an unsigned division of a 16-bit value by an 8-bit constant:

Code: Select all

 LDA #0
 LDX #16
.loop
 ASL zp
 ROL zp+1
 ROL A
 BCS subtract
 CMP #xx
 BCC skip
.subtract
 SBC #xx
 INC zp
.skip
 DEX
 BNE loop
zp and zp+1 contain the 16-bit value to be divided, and the result afterwards. The 8-bit divisor constant is encoded in the immediate fields of CMP #xx and SBC #xx. The remainder ends up in the Accumulator.

Of course, for divisors that are powers of 2, shifting/masking is shorter and faster. :)

The routine includes the bugfix suggested by J.E.E.K. for divisors >= $80 (see discussion). Constant divisors < $80 can omit the two lines "BCS subtract" and ".subtract".
User avatar
R'zo
Vic 20 Nerd
Posts: 512
Joined: Fri Jan 16, 2015 11:48 pm

Re: SAVEing and LOADing memory blocks

Post by R'zo »

Mike wrote:SAVE memory block:

Code: Select all

SYS57809(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
[...]
Is there any way to scratch or overwrite these files? Specifically using string input from user for the filename.
Last edited by Mike on Sat Jun 13, 2020 12:31 pm, edited 1 time in total.
Reason: quote shortened - please refer to the original post in the thread here for details
R'zo
I do not believe in obsolete...
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: SAVEing and LOADing memory blocks

Post by Mike »

R'zo wrote:Is there any way to scratch or overwrite these files? Specifically using string input from user for the filename.
You'd use the standard CBM DOS commands for this, e.g.:

OPEN15,8,15,"S0:"+N$:CLOSE15

deletes file N$ on disk.

You should *not* use the save-and-replace variant of the filename, i.e. prepend "@" or "@0:", as the implementation of this function is bugged on many 15xx disk drives and can corrupt the disk contents.
User avatar
Mike
Herr VC
Posts: 4976
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

BIT #imm using the VIC-20 BASIC ROM

Post by Mike »

Hi, y'all!

As yet another follow-up to a discussion concerning the BIT instruction I wrote this small program to find the first occurence of each byte value in the VIC-20 BASIC ROM:

Code: Select all

1 DIMS(255):R=49152:FORT=0TO8191:A=PEEK(R+T):IFS(A)=0THENS(A)=R+T
2 NEXT:OPEN2,8,2,"BIT.TXT,S,W":FORA=0TO255:B=A:GOSUB4:P$="$"+H$:B=INT(S(A)/256):GOSUB4
3 P$=P$+"  $"+H$:B=S(A)-256*B:GOSUB4:P$=P$+H$:PRINT#2,P$:NEXT:CLOSE2:END
4 H$="":P=INT(B/16):GOSUB5:P=B-16*P
5 H$=H$+CHR$(48+P-7*(P>9)):RETURN
This now allows to rewrite a BIT #imm instruction (which is not available on the NMOS 6502!) as BIT ABS instruction pointing into the BASIC ROM. The BIT instruction sets the Z flag corresponding to the AND result (and N, V directly from bits 7 and 6 of the tested address), but leaves the accumulator unaltered, which allows to perform several bit tests on a given value in A without the necessity to restore A in-between!

The most interesting values are probably the powers of 2, they're given here:

Code: Select all

$01  $C390
$02  $C39B
$04  $C3E7
$08  $C3B9
$10  $C338
$20  $C1A1
$40  $CB5A
$80  $C018
The whole list comprises:

Code: Select all

$00  $C058
$01  $C390
$02  $C39B
$03  $C3A0
$04  $C3E7
$05  $C01A
$06  $C42D
$07  $C3A8
$08  $C3B9
$09  $C414
$0A  $C377
$0B  $C06C
$0C  $C634
$0D  $C06E
$0E  $C35C
$0F  $C563
$10  $C338
$11  $C08A
$12  $C3B3
$13  $C44D
$14  $C50E
$15  $C09C
$16  $C67D
$17  $C572
$18  $C3B1
$19  $C67B
$1A  $C85A
$1B  $C9C5
$1C  $C024
$1D  $C010
$1E  $C35E
$1F  $C620
$20  $C1A1
$21  $C395
$22  $C3C5
$23  $C03A
$24  $C360
$25  $C33A
$26  $C048
$27  $C022
$28  $C40B
$29  $C459
$2A  $C087
$2B  $C534
$2C  $C030
$2D  $C4B0
$2E  $C02C
$2F  $C670
$30  $C00C
$31  $C3BC
$32  $C3BE
$33  $C40F
$34  $C409
$35  $C33C
$36  $C5D2
$37  $C07E
$38  $C3BF
$39  $C052
$3A  $C02A
$3B  $C33E
$3C  $C5A9
$3D  $C7BB
$3E  $C3FD
$3F  $C59D
$40  $CB5A
$41  $C008
$42  $C005
$43  $C004
$44  $C0A8
$45  $C09E
$46  $C092
$47  $C0C1
$48  $C136
$49  $C00A
$4A  $C02E
$4B  $C0FC
$4C  $C0BE
$4D  $C006
$4E  $C09F
$4F  $C0A2
$50  $C08F
$51  $C160
$52  $C034
$53  $C009
$54  $C0AA
$55  $C0AF
$56  $C040
$57  $C0E5
$58  $C056
$59  $C118
$5A  $C098
$5B  $C3C8
$5C  $DBC8
$5D  $C044
$5E  $CAB4
$5F  $C3C3
$60  $C3B7
$61  $C036
$62  $C77D
$63  $CA17
$64  $C032
$65  $C072
$66  $C779
$67  $C002
$68  $C068
$69  $C081
$6A  $C344
$6B  $C49D
$6C  $C437
$6D  $CE78
$6E  $CE7B
$6F  $CA5E
$70  $C020
$71  $C05E
$72  $C346
$73  $C48B
$74  $C715
$75  $CA57
$76  $C475
$77  $CDE0
$78  $C000
$79  $C080
$7A  $C04E
$7B  $C086
$7C  $C070
$7D  $C05A
$7E  $CA2A
$7F  $C03C
$80  $C018
$81  $C393
$82  $C026
$83  $C362
$84  $C3BD
$85  $C046
$86  $C486
$87  $CAC0
$88  $C3EC
$89  $C92F
$8A  $C3B0
$8B  $C076
$8C  $C514
$8D  $C511
$8E  $C65A
$8F  $CFAE
$90  $C34A
$91  $C3EA
$92  $D3EB
$93  $D377
$94  $C060
$95  $C422
$96  $DF1C
$97  $C881
$98  $C3CD
$99  $C5A1
$9A  $C683
$9B  $C042
$9C  $DF31
$9D  $C34C
$9E  $C05C
$9F  $C01E
$A0  $C389
$A1  $CEAC
$A2  $C413
$A3  $C0B1
$A4  $C014
$A5  $C396
$A6  $C57C
$A7  $C933
$A8  $C12C
$A9  $C44A
$AA  $C142
$AB  $C140
$AC  $C32A
$AD  $C074
$AE  $C7EB
$AF  $C143
$B0  $C3D7
$B1  $C06A
$B2  $C038
$B3  $C096
$B4  $C603
$B5  $C32C
$B6  $D51B
$B7  $CBDE
$B8  $C50B
$B9  $C522
$BA  $C04A
$BB  $C93F
$BC  $C14C
$BD  $C14B
$BE  $C016
$BF  $C9C8
$C0  $C5BE
$C1  $C0AB
$C2  $C0D5
$C3  $C04C
$C4  $C0A0
$C5  $C0D0
$C6  $C043
$C7  $C00F
$C8  $C00D
$C9  $C01D
$CA  $C03D
$CB  $C015
$CC  $C01B
$CD  $C011
$CE  $C09A
$CF  $C091
$D0  $C019
$D1  $C028
$D2  $C0A3
$D3  $C039
$D4  $C073
$D5  $C354
$D6  $C079
$D7  $C071
$D8  $C031
$D9  $C063
$DA  $C088
$DB  $C08B
$DC  $C053
$DD  $C3A4
$DE  $C144
$DF  $C05F
$E0  $C061
$E1  $C033
$E2  $C067
$E3  $C001
$E4  $C003
$E5  $C093
$E6  $C4E6
$E7  $C661
$E8  $C090
$E9  $C5D4
$EA  $C062
$EB  $CFDC
$EC  $C078
$ED  $C064
$EE  $C968
$EF  $C95D
$F0  $C334
$F1  $C4BA
$F2  $C3F9
$F3  $C925
$F4  $C461
$F5  $C5C0
$F6  $D0B9
$F7  $C012
$F8  $C529
$F9  $C3EE
$FA  $C41B
$FB  $C548
$FC  $C523
$FD  $C60A
$FE  $C512
$FF  $C336
$00 is probably not that useful, but has been included for completeness. :wink:

PAL and NTSC VIC-20s share the same BASIC ROM, so this method works for both types of VIC-20s. The list is also valid for the bug-fixed BASIC ROM that I devised a few years ago (see here).

Greetings,

Michael

(mod: follow-up discussion split off into own thread.)
Post Reply