Color BASIC and string concatenation

The C programming language has a few standard library functions that deal with strings, namely strcpy() (string copy) and strcat() (string concatenate).

Microsoft BASIC has similar string manipulation features built in to the language. For example, to copy an 8 character “string” in to a buffer (array of chars):

char buffer1[80];

strcpy(buffer1, "12345678");

printf("%s\n", buffer1);

In BASIC, you do not need to allocate space for individual strings. Color BASIC allows doing whatever you want with a string provided it is 255 characters or less, and provided the total string space is large enough. By default, Color BASIC reserves 200 bytes for string storage. If you wanted to strcpy() “12345678” to a string variable, you would just do:

BUFFER1$="12345678"

Yes, that’s legal, but Color BASIC only recognizes the first two characters of a string name, so in reality, that is just like doing:

BU$="12345678"

If you need more than the default 200 bytes, the CLEAR command will reserve more string storage. For example, “CLEAR 500” or “CLEAR 10000”.

“CLEAR 500” would let you have five 100 character strings, or 500 one character strings.

And, keep in mind, strings stored in PROGRAM MEMORY do not use this space. For example, if you reduced string space to only 9 bytes, then tried to make a 10 byte string direct from BASIC:

CLEAR 9
A$="1234567890"
?OS ERROR

The wonderful “?OS ERROR” (Out of String Space).

BUT, if strings are declared inside PROGRAM data, BASIC references them from within your program instead of string memory:

5 CLEAR 9
10 A$="1234567890"
20 B$="1234567890"
30 C$="1234567890"
40 D$="1234567890"
50 E$="1234567890"

Yes, that actually works. If you would like to know more, please see my String Theory series.

But I digress…

The other common C string function is strcat(), which appends a string at the end of another:

char buffer1[80];

strcpy(buffer1, "12345678");
strcat(buffer1, "plus this");

printf("%s\n", buffer1);

That code would COPY “12345678” in to the buffer1 memory, then concatenate “plus this” to the end of it, printing out “12345678plus this”.

In BASIC, string concatenation is done by adding the strings together, such as:

A$="12345678"
A$=A$+"plus this"

PRINT A$

Color BASIC allows for strings to be up to 255 characters long, and no more:

The wonderful “?LS ERROR” (Length of String).

Make it Bigger!

In something I am writing, I started out with an 8 character string, and wanted to duplicate it until it was 64 characters long. I did it like this:

10 A$="12345678"
20 A$=A$+A$+A$+A$+A$+A$+A$+A$

In C, if you tried to strcat() a buffer on top of the buffer, it would not work like that.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char buffer1[80];
    
    // buffer1$ = "12345678"
    strcpy(buffer1, "12345678");
    // Result: buffer1="12345678"
    printf ("%s\n", buffer1);
    
    // buffer1$ = buffer1$ + buffer1$
    strcat(buffer1, buffer1);
    // Result: buffer1$="1234567812345678"
    printf ("%s\n", buffer1);

    // buffer1$ = buffer1$ + buffer1$    
    strcat(buffer1, buffer1);
    // Result: buffer1$="12345678123456781234567812345678"
    printf ("%s\n", buffer1);

    return EXIT_SUCCESS;
}

As you can see, each strcat() copies all of the previous buffer to the end of the buffer, doubling the size each time.

The same thing happens if you do it step by step in BASIC:

A$="12345678"
REM RESULT: A$="12345678"

A$=A$+A$
REM RESULT: A$="1234567812345678"

A$=A$+A$
REM RESULT: A$="12345678123456781234567812345678"

But, saying “A$=A$+A$+A$+A$” is not the same as saying “A$=A$+A$ followed by A$=A$+A$”

For example, if you add two strings three times, you get a doubling of string size at each step:

5 CLEAR 500
10 A$="12345678"
20 A$=A$+A$
30 A$=A$+A$
40 A$=A$+A$
50 PRINT A$

Above creates a 64 character string (8 added to 8 to make 16, then 16 added to 16 to make 32, then 32 added to 32 to make 64).

BUT, if you had done the six adds on one line:

5 CLEAR 500
10 A$="12345678"
20 A$=A$+A$ + A$+A$ + A$+A$
50 PRINT A$

…you would get a 48 character string (8 characters, added 6 times).

In C, using strcat(buffer, buffer) with the same buffer has a doubling effect each time, just like A$=A$+A$ does in BASIC each time.

And, adding a bunch of strings together like…

A$=A$+A$+A$+A$+A$+A$+A$+A$ '64 characters

…could also be done in three doubling steps like this:

A$=A$+A$:A$=A$+A$:A$=A$+A$ ' 64 characters

Two different ways to concatenate strings together to make a longer string. Which one should we use?

Must … Benchmark …

In my code, I just added my 8 character A$ up 8 times to make a 64 character string. Then I started thinking about it. And here we go…

Using my standard benchmark program, we will declare an 8 character string then add it together 8 times to make a 64 character string. Over and over and over, and time the results.

0 REM strcat1.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$="12345678"
40 A$=A$+A$+A$+A$+A$+A$+A$+A$
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This produces an average of 1235.

Now we switch to the doubling three times approach:

0 REM strcat2.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$="12345678"
40 A$=A$+A$:A$=A$+A$:A$=A$+A$
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This drops the time down to 888!

Adding separately three times versus add together eight times is a significant speed improvement.

Based on what I learned when exploring string theory (and being shocked when I realized how MID$, LEFT$ and RIGHT$ worked), I believe every time you do a string add, there is a new string created:

“A$=A$+A$+A$+A$+A$+A$+A$+A$” creates eight strings along the way.

“A$=A$+A$:A$=A$+A$:A$=A$+A$” creates six.

No wonder it is faster.

Looks like I need to go rewrite my experiment.

Until next time…

Insta360 X3 also has the same open WiFi and root telnet as the ONE X2

Updates:

Earlier this week, Insta360 introduced a new camera – the X3. The X3 is the latest 360 action camera, and builds upon the feature set of the ONE X2 which came before it (and the ONE X before it, and so on).

Insta360 X3

One of the features that was brought over from the ONE X2 is the unprotected WiFi hotspot that is active any time the camera is powered up. This allows anyone within WiFi range the ability to connect to the camera and browse (or download) any photos or videos that are stored on it:

http://192.168.42.1/DCIM
http://192.168.42.1/DCIM/Camera01

In addition to this, telnet is still enabled, with the root account having no password. Just telnet to that IP address and log in as root to have full access to the camera’s file system:

telnet 192.168.42.1

It is disappointing to see this unsecure access method continued in the next generation of their camera, but the company has posted that they are aware of this and are working on it.

Let’s hope it is fixed before the X4 is released…

Erico’s CoCo revamp of the 1K ZX81 game

Earlier in December, @BASIC10Liners on Twitter retweeted a tweet from Tony Lyon. The original tweet contained a photograph of a ZX81 BASIC program listing found in ‘Computer & Video Games’ magazine. The game ran on a 1K machine, and was a Space Invaders-style game, featuring a single invader.

This tweet was picked up and shared in the Tandy CoCo group on Facebook, and led to a number of folks porting it over to the CoCo. Jim Gerrie also ported it to the MC-10:

Jim also did the research to find the issue that the full article appeared in:

For the past few weeks, folks have been sharing optimizations and game improvements on Facebook, and I wanted to highlight one in particular.

Erico Monteiro is known for doing some impressive graphics work using the 8-color 64×32 resolution “mode” of the CoCo. He took the basic framework of the game, then sped it up and added new colors, animations and features.

David Healey, if you are out there, thank you for inspiring so many with your 1981 ZX81 program in that magazine…

Insta360 X3 360 camera released

Today, Insta360 released their new X3 360 camera:

https://www.insta360.com/product/insta360-x3

This is the fourth camera in their popular (?) ONE X line, which began with the ONE, followed by the ONE X, then the ONE X2.

The new camera drops the “ONE” name, so it’s not just an X3. This is good because everyone called the “ONE X2” just X2 anyway.

I’ll share more details soon, including details on if this model has the same open WiFi and password-less telnet root access that the ONE X2 has.

PRINT MEM is not accurate?

While writing up my article on Color BASIC memory, I ran across something I was unaware of. Special thanks to William “Lost Wizard” Astle for his responses on the CoCo mailing list that got me pointed in the right direction…

BASIC memory looks like this:

+-------------+ 0
| SYSTEM USE  |
+-------------+ 1024
| TEXT SCREEN |
+-------------+ 1536
| DISK USE    |
+-------------+
| HI-RES GFX  |
+-------------+
| BASIC PROG  |
+-------------+
| VARIABLES   |
+-------------+
| ARRAYS      |
+-------------+
| FREE MEMORY |
+-------------+
| STRINGS     |
+-------------+ 32767

Without Disk BASIC, “DISK USE” is not there, and without Extended BASIC, neither is “HI-RES GFX”.

To determine free memory available to BASIC (for program, variables or arrays), you would subtract the end of “ARRAYS” from the start of “STRINGS.” The locations that store this are at &H1F-&H20 (31-32) and &H21-&H22 (33-34).

Color BASIC Unraveled, page A1.

Each of those two byte locations holds a 16-bit address to some area of RAM. You can get the values by taking the first byte, multiplying it by 256, and adding the second byte.

PRINT "MEMSIZE:";PEEK(&H27)*256+PEEK(&H28)

Here is a program that prints out some pertinent information:

0 ' BASINFO2.BAS

10 ' START OF BASIC PROGRAM
20 BS=PEEK(25)*256+PEEK(26)

30 ' START OF VARIABLES
40 VS=PEEK(27)*256+PEEK(28)

50 ' START OF ARRAYS
60 SA=PEEK(29)*256+PEEK(30)

70 ' END OF ARRAYS (+1)
80 EA=PEEK(31)*256+PEEK(32)

90 ' START OF STRING STORAGE
100 SS=PEEK(33)*256+PEEK(34)

110 ' START OF STRING VARIABLES
120 SV=PEEK(35)*256+PEEK(36)

130 ' TOP OF STRING SPACE/MEMSIZ
140 ST=PEEK(39)*256+PEEK(40)

150 PRINT "PROG  SIZE";(VS-BS),"STR SPACE";(ST-SS)
170 PRINT "ARRAY SIZE";(EA-SA)," STR USED";(ST-SV)
180 PRINT " VARS SIZE";(SA-VS)," FREE MEM";(SS-EA)

999 END

And here is that PRINT code, without using any variables. It could be made a subroutine that you could GOSUB to and see status of your memory usage:

10 PRINT "PROG  SIZE";(PEEK(27)*256+PEEK(28))-(PEEK(25)*256+PEEK(26)),;
20 PRINT "STR SPACE";(PEEK(39)*256+PEEK(40))-(PEEK(33)*256+PEEK(34))
30 PRINT "ARRAY SIZE";(PEEK(31)*256+PEEK(32))-(PEEK(29)*256+PEEK(30)),;
40 PRINT " STR USED";(PEEK(39)*256+PEEK(40))-(PEEK(35)*256+PEEK(36))
50 PRINT " VARS SIZE";(PEEK(29)*256+PEEK(30))-(PEEK(27)*256+PEEK(28)),;
60 PRINT " FREE MEM";(PEEK(33)*256+PEEK(34))-(PEEK(31)*256+PEEK(32))

There are a few mysteries here. First, if I wanted to get the size of my program, I would take MEMSIZE and subtract ARYEND. That would be this:

PRINT (PEEK(33)*256+PEEK(34))-(PEEK(31)*256+PEEK(32))

But that disagrees with that PRINT MEM shows:

There are some bytes missing somewhere. This made me wonder which was correct, so I consulted the PRINT MEM code in Color BASIC Unravelled. It had comments that shed some light on what is going on:

Color BASIC Unraveled, page B34.

“This is not a true indicator of free memory because BASIC requires a STKBUF size buffer for the stack for which MEM does not allow.”

– Color BASIC Unraveled, page B34.

On page A1, I see the definition for STKBUF:

STKBUF  EQU  58    STACK BUFFER ROOM

I see code in the ROM that takes this in to consideration, adding the value of ARYEND plus the value of STKBUF.

Color BASIC Unraveled, page B20.

That routine is used to test if enough memory is available for something, and if there isn’t, it returns the ?OM ERROR.

But the difference I get is 12 bytes, not 58. What it BOTSTK? It looks like it is just before the pointer variables I have been working with. I just did not know what it was.

0017    BOTSTK  RMB  2    BOTTOM OF STACK AT LAST CHECK

&H17 would be memory location 23. Let’s see where it is by doing…

PRINT PEEK(23)*256+PEEK(24)

That gives me a location about 30 bytes before FRETOP (start of string storage). Here is the program I am using:

0 ' BASPTRS.BAS

10 ' BOTTOM OF STACK
20 SP=PEEK(23)*256+PEEK(24)

30 ' START OF BASIC PROGRAM
40 BS=PEEK(25)*256+PEEK(26)

50 ' START OF VARIABLES
60 VS=PEEK(27)*256+PEEK(28)

70 ' START OF ARRAYS
80 SA=PEEK(29)*256+PEEK(30)

90 ' END OF ARRAYS (+1)
100 EA=PEEK(31)*256+PEEK(32)

110 ' START OF STRING STORAGE
120 SS=PEEK(33)*256+PEEK(34)

130 ' START OF STRING VARIABLES
140 SV=PEEK(35)*256+PEEK(36)

150 ' TOP OF STRING SPACE/MEMSIZ
160 ST=PEEK(39)*256+PEEK(40)

180 PRINT "START OF PROG",BS;(VS-BS)
190 PRINT "START OF VARS",VS;(SA-VS)

200 PRINT "START OF ARRAYS",SA;(EA-SA)
210 PRINT "END OF ARRAYS+1",EA

215 PRINT "BOTTOM OF STACK",SP

229 PRINT "START/STR STORE",SS;(ST-SS)
230 PRINT "START/STR VARS",SV;(ST-SV)
240 PRINT "TOP OF STRINGS",ST

250 PRINT "FREE MEMORY",(SP-EA)

999 END

William clarified a bit of this in a follow-up post.

Actually, currently available memory would be approximately the difference between BOTSTK and ARYEND less the STKBUF amount. MEM neglects to add the STKBUF adjustment in when it calculates the difference. Total usable memory under current PMODE/FILES/CLEAR settings would be the difference between BOTSTK and TXTTAB.

Note that the stack (and consequently the BOTSTK value) will grow downward from FOR loops, GOSUB, and expression evaluation. Expression evaluation can use a surprising amount of stack space depending on how many times it has to save state to evaluate a higher precedence operation, how many function calls are present, etc.

ARYEND is the top of all the memory used by the program itself, PMODE graphics pages, disk buffers and file descriptors, scalar variables, and arrays.

When calculating OM errors, it takes ARYEND, adds the amount of memory requested, adds the STKBUF amount, and then compares that with the current stack pointer. It does the comparison by storing S since you can’t directly compare two registers.

STKBUF is 58 for whatever reason. That’s sufficient to allow for a full interrupt frame (12 bytes) plus a buffer so routines can use the stack for saving registers, routine calls, etc., within reason, without having to continually do OM checks. It does this to prevent corrupting variables when memory is tight. Even so, there may be a few routines in Disk Basic that may still cause the stack to overflow into variable space when memory is very tight.

– William Astle, 7/8/2022

So how can we test? Using the XRoar emulator, I started with a standard 64K CoCo with Disk BASIC. PRINT MEM returns 22832 bytes free.

On startup, a disk-based CoCo has 22823 bytes available for BASIC.

For testing, I’ll create one line of BASIC that is just a REM statement:

10 REM

Now PRINT MEM shows 22817 bytes free. That is 6 bytes less, which is two bytes for where the next line will be (38 7), two bytes for the line number (00 10), the token for REM (130), and a NULL byte (0). Each new line number takes up 5 bytes plus whatever the content of the line is.

Now I want to reserve most of that memory for strings. I’ll do CLEAR 22800. After this, PRINT MEM shows 207… That can’t be right. Shouldn’t it really be 17?

In this case, the original PRINT MEM was already counting 200 bytes reserved for strings. When I did CLEAR 22800, it was release that 200 bytes, then redoing it as 22800 (so, 228600+200).

Let’s start over…

Okay, so let’s redo this. I restarted the CoCo back to where PRINT MEM shows 22823.

The next thing I did was type CLEAR 0 to reserve NO string space, then check PRINT MEM. It shows 23023 — the largest BASIC program I can have on this disk-based machine, unless I deallocate some graphics memory (6K is reserved for that, by default).

Now I enter in the “10 REM” line, and PRINT MEM again. It shows 23017 — 6 bytes less, as expected.

I try to type CLEAR 23017, which gives ?OM ERROR.

I back that off a bit to CLEAR 22960. PRINT MEM shows ?OM ERROR. And now I have hosed BASIC. Even CLEAR 0 now returns ?OM ERROR.

Is this a bug? I can still EDIT my line 10, and LIST it, but while CLEAR by itself works, it changes nothing. I’ve managed to consume so much memory, I can’t run the command I need to give it back.

Let’s start over again…

CoCo reset. CLEAR 0 entered. PRINT MEM shows 22823. 10 REM typed. PRINT MEM shows 23017.

This time I’ll try CLEAR 22950 to give BASIC a bit more room. PRINT MEM shows 67.

If this is accurate, I should be able to add 67 characters to the end of that REM statement.

I type “EDIT 10”, then “X” to extend to the end. I enter ten characters, then ENTER.

10 REM1234567890

PRINT MEM gives me an ?OM ERROR.

Now, the program still LISTS and would run (if it did anything), but clearly other things are broken.

I EDIT 10 and try to add another ten characters.

10 REM12345678901234567890

?OM ERROR.

And now, LINE 10 is completely gone! EDIT must try to “add” a line when it’s done, and if there’s no memory, the line is … erased?

PRINT MEM still shows 67, even though the “10 REM” seems to be gone. Where did that memory go? I try adding a total of 15 characters after the REM:

10 REM123456789012345

That works. PRINT MEM shows ?OM ERROR.

Let’s start over again again.

I reboot the CoCo, then CLEAR 22950. PRINT MEM shows 73 (there is no line number yet).

This “should” mean I could enter a line (5 bytes) that has 73-5=68 characters. But, if I tried to type anything longer than 16, I get ?OM ERROR (REM token is one byte, then 15 characters).

This tells me that when PRINT MEM shows me 67, I really only had 5+16=21 bytes available. 67-21=46 bytes difference.

Looking back at my BASIC POINTERS program output:

…I see the calculated free memory was 22042, versus PRINT MEM showing 22038. That is only four bytes different.

Questions keep stacking up

It is clear you cannot use PRINT MEM to know how much room you will have for a BASIC program. It is more of a guideline, within 50 or so bytes.

The wildcard is probably this mysterious (to me) “stack” that BASIC needs for operation. Depending on what is going on, the stack in-use may be larger or smaller, but the max limit is 58 bytes. And, STKBOT notes that is is “bottom of stack at last check” so it may not be reflecting the actual stack at the particular moment when the code looked at it.

I really don’t have any conclusions from this. When I started writing, I expected I’d find a set of PEEKs I could subtract to get a more accurate PRINT MEM value.

But I failed.

Anyone have any suggestions?

Until then…

Invisible lines in Color BASIC

Updates:

  • 2022-09-19 – John K pointed out a typo in the program listing, which has been corrected.

This one comes from Carl England. We met him at the 1990 Atlanta CoCoFest. That was the first time Sub-Etha Software appeared anywhere, and we couldn’t afford a full booth so we split on with Carl. He will always be the SuperBoot guy to me (my all time favorite utility), but most may know him as the programmer being The Defeater – a disk copy tool that could make clones of copy protected disk. (Download some of his items here.)

Carl popped up in some Facebook comments recently with this odd bit of code.

10 PRINT"THIS LINE IS INVISIBLE":'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
20 FORI=PEEK(25)*256+PEEK(26)TOPEEK(27)*256+PEEK(28)
30 IFPEEK(I)=37THENPOKEI,8
40 NEXT

Line 10 is a PRINT statement, followed by a comment and a bunch of percent symbols.

Line 20 is a loop that goes from the Start of BASIC to the End of BASIC.

Line 30 uses PEEK to look for a 37, which is the ASCII code for a percent sign. If it finds one, it POKEs it to 8 — a backspace.

Line 40 is just the NEXT for the loop.

If you run this program, it will modify line to and replace all those %’s with backspace characters. Then, if you LIST it, BASIC displays the line then immediately backspaces over it, too quick to see, making it look like there is no line there.

You could RUN this to modify the program, then DELete lines 20-40, and save it to tape or disk. You could then have a program you could load and run that would print a message, but LISTing it would appear as if nothing was there.

Carl said he used this to hide lines in some of his programs, but mentioned you could still see the code if you sent it to a printer using LLIST. Also, if you knew there was a line 10, you could EDIT 10 and SPACE through the line, revealing the code. I suppose you could SPACE until you got to the colon and then Hack the rest of the line, removing those backspace.

Fun.

Until next time…

Counting 6809 cycles with LWASM

Followers of my ramblings know that I enjoy benchmarking BASIC. It is interesting to see how minor changes can produce major speed differences. And while BASIC is supposedly “completely predictable,” there are so many items that must be considered — line numbers, line length, number of variables, amount of strings, etc. — you can’t really look at any bit of code and know how fast it will run unless it’s something self contained like this:

10 FOR A=1 TO 1000
20 NEXT

Beyond things like that, there’s a lot of trail-and-error needed. Code like this:

...
100 FOR A=1 TO 100
110 Z=Z+1
120 NEXT
...

…can have dramatically different speeds depending on how many other variables there are, and where Z was declared in the list of them.

6809 assembly language is far more predictable. Every machine language instruction has a known amount of CPU cycles it takes to operate — based on variations of that code. For example, loading register A with a value:

lda #42

…should be 100% predictable simply by looking up the cycle counts for “load A” in some 6809 reference guide. The Motorola data sheet for the 6809 tells me that “LDA” takes 2 cycles.

So, really, there’s no benchmarking needed. You just have to look up the instructions (and their specific type — direct, indirect, etc.) and add up some numbers.

But where’s the fun in that?

William “Lost Wizard” Astle‘s LWTOOLS provides us with the lwasm assembler for Mac, Windows, Linux, etc. One of its features is cycle counting. It can generate a list of all the machine language bytes that the assembly source code turns in to and, optionally, include the cycle count for each line of assembly code.

I just learned about this and had to experiment…

Here is a simple assembly loop that clears the 32-column screen. I’ll add some comments that explain what it does, as if it were BASIC…

clear
    lda #96     * A=96 (green space)
clearwitha
    ldx #1024   * X=1024 (top left of screen)
loop
    sta ,x+     * POKE X,A:X=X+1
    cmpx #1536  * Compare X to 1536 
    bne loop    * If X<>1536, GOTO loop
    rts         * RETURN

To clear the screen to spaces (character 96), it is called with:

 bsr clear

To clear the screen with a different value, such as 128 for black, it can be called like this:

 lda #128
 bsr clearwitha

LWASM is able to tell me how many CPU cycles each instruction will take. To generate this, you have to include a special pragma command in the source code, or pass it in on the command line. In source code, it is done by using the special “opt” keyword followed by the pragma. The ones we are interested in are listed in the manual:

opt c  - enable cycle counts: [8]
opt cd - enable detailed cycle counts breaking down addressing modes: [5+3]
opt ct - show a running subtotal of cycles
opt cc - clear the running subtotal

Adding ” opt c” at the top of the source code will enable it, and then you would use the “-l” command line option to generate the list file which will not contain cycle counts. (You can also send the list output to a file using -lfilename if you prefer.)

You can also pass in this pragma using a command line “–pragma=c”, like this:

lwasm clear.asm -fbasic -oclear.bas --pragma=c -l

Above, I am assembling the program in to a BASIC loader which I can load from BASIC, and then RUN to load the machine language program in to memory. Here is what that command displays for me:

allenh@alsmacbookpro asm % lwasm clear.asm -fbasic -oclear.bas --pragma=c -l
0000                  (        clear.asm):00001         clear
0000 8660             (        clear.asm):00002 (2)         lda #96     * A=96 (green space)
0002                  (        clear.asm):00003         clearwitha
0002 8E0400           (        clear.asm):00004 (3)         ldx #1024   * X=1024 (top left of screen)
0005                  (        clear.asm):00005         loop
0005 A780             (        clear.asm):00006 (5)         sta ,x+     * POKE X,A:X=X+1
0007 8C0600           (        clear.asm):00007 (3)         cmpx #1536  * Compare X to 1536 
000A 26F9             (        clear.asm):00008 (3)         bne loop    * If X<>1536, GOTO loop
000C 39               (        clear.asm):00009 (4)         rts         * RETURN
                      (        clear.asm):00010         
                      (        clear.asm):00011             END

That’s a bit too wide of a listing for my comfort, so from now on I’ll just include the right portion of it — starting with the (number) in parenthesis. That is the cycle count. If you look at the “lda #96” line you will see it confirms “lda” takes two cycles:

(2)         lda #96     * A=96 (green space)

Another pragma of interest is one that will start counting the total number of cycles code takes.

opt ct - show a running subtotal of cycles

If you just turned it on, it would be not be very useful since it would just be adding up all the instructions from top to bottom of the source, not taking in to consideration branching or subroutines or loops. But, we can clear that counter and start it at any point by including the “opt” keyword in the code around routines we are interested in.

opt cc - clear the running subtotal

And we can turn them off by putting “no” in front:

opt noct - STOP showing a running subtotal of cycles

In the case of my clear.asm program, I would want to clear the counter and turn it on right at the start of loop, and turn it off at the end of the loop. This would show me a running count of how many cycles that loop takes:

        clear
(2)         lda #96
        clearA
(3)         ldx #1024
            opt ct,cc
                loop
(5)     5           sta ,x+
(3)     8           cmpx #1536
(3)     11          bne loop
                    opt noct
(4)         rts

The numbers to the right of the (cycle) count numbers are the sum of all instructions from the moment the counter was enabled.

The code from “loop” to “bne loop” takes 11 cycles. Since each loop sets one byte on the screen, and since there are 512 bytes on the screen, clearing the screen this way will take 11 * 512 = 5632 cycles (plus a few extra before the loop, setting up X and A).

Instead of clearing the screen 8-bits at a time, I learned that using a 16-bit register would be faster. I changed the code to use a 16-bit D register instead of the 8-bit A register, like this:

clear16
    lda #96     * A=96
    tfr a,b     * B=A (D=A*256+B)
clearA16
    ldx #1024   * X=1024 (top left of screen)
    opt ct,cc   * Clear counter, turn it on.
loop16
    std ,x++    * POKE X,A:POKE X+1,B:X=X+1
    cmpx #1536  * Compare X to 1536.
    bne loop16  * If X<>1536, GOTO loop16
    opt noct    * Turn off counter.
    rts         * RETURN

Since 16-bit register D is made up of 8-bit registers A and B, I simply transfer whatever is in A to B and that makes both bytes of D the same as A. Then in the loop, I store D at X, and increment it by 2 (to get to the next two bytes). Looking at cycles again…

        clear16
(2)         lda #96
(4)         tfr a,b
        clearA16
(3)         ldx #1024
            opt ct,cc
                loop16
(7)     7           std ,x++
(3)     10          cmpx #1536
(3)     13          bne loop16

The code from “loop16” to “bne loop16” takes 13 cycles, which is longer than the original. But, each loop does two bytes instead of one. Instead of needing 512 times through the loop, it only needs 256. 13 * 256 = 3328 cycles. Progress!

And, if we can do 16-bits at a time, why not 32? Currently, D is the value to store, and X is where to store it. We could just store D twice in a row…

clear32
    lda #96     * A=96
    tfr a,b     * B=A (D=A*256+B)
clearA32
    ldx #1024   * X=1024 (top left of screen)
    opt ct,cc   * Clear counter, turn it on.
loop32
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    cmpx #1536  * Compare X to 1536.
    bne loop32  * If X<>1536, GOTO loop32
    opt noct    * Turn off counter.
    rts         * RETURN

Let’s see what that does…

        clear32
(2)         lda #96     * A=96
(4)         tfr a,b     * B=A (D=A*256+B)
        clearA32
(3)         ldx #1024   * X=1024 (top left of screen)
            opt ct,cc   * Clear counter, turn it on.
                loop32
(7)     7           std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     14          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(3)     17          cmpx #1536  * Compare X to 1536.
(3)     20          bne loop32  * If X<>1536, GOTO loop32
                    opt noct    * Turn off counter.
(4)         rts         * RETURN

Above, the “loop32” to “bne loop32” takes 20 cycles. Each loop does four bytes, so only 128 times through the loop to clear all 512 bytes of the screen. 20 * 128 = 2560 cycles. More than double the speed of the original one byte version.

We could do 48-bits at a time by storing three times, but that math doesn’t work out since 512 is not divisible by 6 (I get 85.33333333). Perhaps we could do the loop 85 times to clear the first 510 bytes (6 * 85 = 510), then manually do one last 16-bit store to complete it. Maybe like this:

clear48
    lda #96     * A=96
    tfr a,b     * B=A (D=A*256+B)
clearA48
    ldx #1024   * X=1024 (top left of screen)
    opt ct,cc   * Clear counter, turn it on.
loop48
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    cmpx #1536  * Compare X to 1536.
    bne loop48  * If X<>1536, GOTO loop32
    opt noct    * Turn off counter.
    std ,x      * POKE X,A:POKE X+1,B:X=X+2
    rts         * RETURN

And LWASM shows me:

        clear48
(2)         lda #96     * A=96
(4)         tfr a,b     * B=A (D=A*256+B)
        clearA48
(3)         ldx #1024   * X=1024 (top left of screen)
            opt ct,cc   * Clear counter, turn it on.
                loop48
(7)     7           std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     14          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     21          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(3)     24          cmpx #1536  * Compare X to 1536.
(3)     27          bne loop48  * If X<>1536, GOTO loop32
                    opt noct    * Turn off counter.
(5)                 std ,x      * POKE X,A:POKE X+1,B:X=X+2
(4)         rts         * RETURN

We have jumped to 27 cycles per loop. Each loop stores 6 bytes, and it takes 85 times to get 510 bytes, plus 5 extra after it is over for the last two bytes. 27 * 85 = 2295 cycles + 5 = 2300 cycles! We are still moving in the right direction.

Just for fun, what if we did four stores, 8 bytes at a time?

clear64
    lda #96     * A=96
    tfr a,b     * B=A (D=A*256+B)
clearA64
    ldx #1024   * X=1024 (top left of screen)
    opt ct,cc   * Clear counter, turn it on.
loop64
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    std ,x++    * POKE X,A:POKE X+1,B:X=X+2
    cmpx #1536  * Compare X to 1536.
    bne loop64  * If X<>1536, GOTO loop32
    opt noct    * Turn off counter.
    rts         * RETURN

And that gives us:

        clear64
(2)         lda #96     * A=96
(4)         tfr a,b     * B=A (D=A*256+B)
        clearA64
(3)         ldx #1024   * X=1024 (top left of screen)
            opt ct,cc   * Clear counter, turn it on.
                loop64
(7)     7           std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     14          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     21          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(7)     28          std ,x++    * POKE X,A:POKE X+1,B:X=X+2
(3)     31          cmpx #1536  * Compare X to 1536.
(3)     34          bne loop64  * If X<>1536, GOTO loop32
                    opt noct    * Turn off counter.
(4)         rts         * RETURN

34 cycles stores 8 bytes. 64 times through the loop to do all 512 screen bytes, so 64 * 34 = 2176 cycles.

By now, I think you can see where this is going. I believe this is called “loop unrolling”, since, if you wanted the fewest cycles, you could just code 256 “std ,x++” in a row (7 * 256) for 1792 cycles which would be fast but bulky code (each std ,x++ takes two bytes, so 512 bytes just for this copy routine).

There is always some balance between code size and speed. Larger programs took longer to load from tape or disk. But, if you didn’t mind load time, and you had extra memory available, tricks like this could really speed things up.

Blast it…

I have also read about “stack blasting” where you load values in to registers and then, instead of storing each register, you set a stack pointer to the destination and just push the registers on the stack. I’ve never done that before. Let’s see if we can figure it out.

There are two stacks in the 6809 — one is the normal one used by the program (SP, I believe is the register?), and the other is the User Stack (register U). If we aren’t using it for a stack, we can use it as a 16-bit register, too.

The stack grows “up”, so if the stack pointer is 5000, and you push an 8-bit register, the pointer will move to 4999 (pointing to the most recent register pushed). If you then push a 16-bit register, it will move to 4997. This means it will have to work in reverse from our previous examples. By pointing the stack register to the end of the screen, we should be able to push registers on to the stack causing it to grow “up” to the top of the screen.

At first glance, it doesn’t look promising, since pushing D on to the user stack (U) takes more cycles than storing D at U:

(5)         std ,u

(6)         pshu d

But, it seems we make that up when pushing multiple registers since the cycle count does not grow as much as multiple stores do:

(7)         std ,U++
(7)         stx ,U++
(8)         sty ,U++
        
(10)        pshu d,x,y

I also I see that STY is one cycle longer than STD or STX. This tells me to maybe avoid using Y like this…?

It looks good, though. 22 cycles compared to 10 seems quite the win. Let me see if I can do a clear routine using the User stack pointer and three 16-bit registers. We’ll compare this to the 48-bit clear shown earlier.

clear48s
    lda #96     * A=96
clearA48s
    tfr a,b     * B=A (D=A*256+B)
    tfr d,x     * X=D
    tfr d,y     * Y=D
    ldu #1536   * U=1536 (1 past end of screen)
    opt ct,cc   * Clear counter, turn it on.
loop48s
    pshu d,x,y
    cmpu #1026  * Compare U to 1026 (two bytes from start).
    bgt loop48s * If X<>1026, GOTO loop48s. 
    opt noct    * Turn off counter.
    pshu d      * Final 2 bytes.
    rts         * RETURN

And the results are…

        clear48s
(2)         lda #96     * A=96
        clearA48s
(4)         tfr a,b     * B=A (D=A*256+B)
(4)         tfr d,x     * X=D
(4)         tfr d,y     * Y=D
(3)         ldu #1536   * U=1536 (1 past end of screen)
            opt ct,cc   * Clear counter, turn it on.
                loop48s
(10)    10          pshu d,x,y
(4)     14          cmpu #1026  * Compare U to 1026 (two bytes from start).
(3)     17          bgt loop48s * If X>1026, GOTO loop48s
                    opt noct    * Turn off counter.
(6)                 pshu d      * Final 2 bytes.
(4)         rts         * RETURN

From “loop48s” to “bgt loop48s” we end up with 17 cycles compared to 27 using the std method. 85 * 17 = 1445 cycles + 6 final cycles = 1551 cycles. It looks like using stack push/pulls might be a real nice way to do this type of thing, provided the user stack is available, of course.

Side Note: here is a fantastic writeup of this and the techniques on the 6809, as used in some unnamed CoCo 3 game back in the day: https://blog.moertel.com/posts/2013-12-14-great-old-timey-game-programming-hack.html

The fastest way to zero

But wait! There’s more…

When setting a register to zero, I have been told to use “CLR” instead of “LDx #0”. Let’s see what that is all about…

(2)         lda #0
(1)         clra

(3)         ldd #0
(2)         clrd

Ah, now know a CLRA is twice as fast as LDA #0, and CLRD is one cycle faster than LDD #0. Nice.

Other 16-bit registers such as X, Y, and U do not have a CLR op code, so LDx will be have to be used there, I suppose.

I then wondered if it made more sense to CLR a memory location, or clear a register then store that register there.

(6)         clr 1024
        
(1)         clra
(4)         sta 1024

It appears in this case, it is less cycles to clear a register then store it in memory. Interesting. And using a 16-bit value:

(3)         ldd #0
(5)         std 1024

That is one cycle faster than doing a “clra / sta 1024 / sta 1025” it seems. It is also one byte less in size, so win win.

There is a lot to learn here, and from these experiments, I’m already seeing some things are not like I would have guessed.

I hope this inspires you to play with these LWASM options and see what your code is doing. During the writing of this article, I learned how to use that User Stack, and I expect that will come in handy if I decide to do any updates to my Invaders09 game some day…

Until next time…

ScrewX2 and the Insta360 WiFi security hole.

See also: my previous article.

Over on REDDIT, the subject of the gaping Insta360 WiFi security hole has come up again. User K1N6P1X3l linked to this recent article that summarizes the issue’s history:

https://petapixel.com/2022/08/29/vulnerability-in-insta360-cameras-lets-anyone-download-your-photos/

Of course, the majority of users simply will not care. “It’s very unlikely to happen to me,” they say. “And if it does, so what, they get my photos.”

In other words, this is just like any other security issue out there. Some folks treat them seriously and take steps to avoid the problems, and others just don’t care. If it were not for the “don’t care” crowd, we wouldn’t have such great malware, viruses and ransomware :)

The exploit allows anyone within WiFi range the ability to connect to your camera and do “stuff.” According to the PetaPixel article, Insta360 has already plugged some of this:

Currently the list_directory has already been terminated and it is no longer possible to access the camera content through the browser.

– Insta360 response, per PetaPixel article

Unfortunately, this is not true. Using the current available firmware, v1.0.59_build1, on my ONE X2 I see it appear on WiFi as expected:

…and if I select this interface, and then open the URL in a browser, I find all my files are indeed able to be listed:

I can then click on one of the .insv video files and play it (or save off a copy):

“And it’s just that easy!”

With this camera, there is no privacy because the camera is broadcasting itself to any WiFi devices around it, and allowing any of them to connect without authentication and then browse and view/download anything on the microSD card.

What’s worse is you can also telnet in to the device. I tried that to see if it still worked:

You will notice it did not ask for a password here, either.

Both of these screenshots (web browser and telnet) were done on my iPad.

WHILE I was connected to my X2 via the iPad, I then connected a Windows PC. Using the default password of “88888888” I was now connected from two devices (which for some reason folks think isn’t possible). Both my Windows PC and my iPad were connected and able to access the files from a web browser.

At least two firmware updates have come out since this first appeared on REDDIT, and it does not appear anything has changed.

ScrewX2: The proof-of-concept that a script kiddie could have written

Shortly after this exploit was first mentioned, someone could have easily created a script that would look for WiFi hotspots following the name “ONE X2 xxxxx” and connect to them. The script could then issue http GET commands to retrieve files on the memory card, or telnet in to delete things, potentially bricking the camera.

Worse, the script could deliver a payload of malware, and if the user ever mounted that memory card in a computer, the malware could have been ran accidentally. Hopefully most of us will never run some random executable or installer found on a camera memory card, but it would be tempting to try to find out what “360VIEWER.EXE” does, or “Insta360MacConverter.dmg” is.

I will not link to any such “screwx2” script, and will delete any links to such posted in the comments here. The cat is firmly out of the bag, with the default WiFi password known, and NO password on the web interface or telnet, so all we can do is hope that Insta360 addresses this issue eventually.

To be continued…

Disk BASIC and drive numbers BEFORE the filename?

In a follow up to an article I posted about undocumented syntax, here we go again…

When all you had was a cassette recorder, loading and saving was simple:

CLOAD "PROGRAM"

CSAVE "PROGRAM"

When all you had was a single disk drive, loading and saving was simple:

LOAD "PROGRAM.BAS"

SAVE "PROGRAM.BAS"

When you had more than one disk drive, loading and saving was still simple… Just add the drive number:

LOAD "PROGRAM.BAS:0"

LOAD "PROGRAM.BAS:1"

COPY "PROGRAM.BAS:0" TO "PROGRAM.BAS:1"

And we liked it that way…

0:, 1:, 2: and 3:???

Over in the CoCo Discord chat, I just learned from William “Lost Wizard” Astle that the drive number can also be put at the start of the filename:

SAVE "1:PROGRAM.BAS"

LOAD "1:PROGRAM.BAS"
Disk BASIC drive numbers … BEFORE the file???

Huh??? He points out that DOS modifications, such as RGB-DOS/HDB-DOS, do not support this.

And it’s just weird.

Checksums and zeros and XMODEM and randomness.

A year or two ago, I ran across some C code at my day that finally got me to do an experiment…

When I was first using a modem to dial in to BBSes, it was strictly a text-only interface. No pictures. No downloads. Just messages. (Heck a physical bulletin board at least would let you put pictures on it! Maybe whoever came up with the term BBS was just forward thinking?)

The first program I ever had that sent a program over the modem was DFT (direct file transfer). It was magic.

Later, I got one that used a protocol known as XMODEM. It seems like warp speed compared to DFT!

XMODEM would send a series of bytes, followed by a checksum of those bytes, then the other end would calculate a checksum over the received bytes and compare. If they matched, it went on to the next series of bytes… If it did not, it would resend those bytes.

Very simple. And, believe it or not, checksums are still being used by modern programmers today, even though newer methods have been created (such as CRC).

Checking the sum…

A checksum is simple the value you get when you add up all the bytes of some data. Checksum values are normally not floating point, so they will be limited to a fixed range. For example, an 8-bit checksum (using one byte) can hold a value of 0 to 255. A 16-bit checksum (2 bytes) can hold a value of 0-65535. Since checksums can be much higher values, especially if using an 8-bit checksum, the value just rolls over.

For example, if the current checksum calculated value is 250 for an 8-bit checksum, and the next byte being counted is a 10, the checksum would be 250+10, but that exceeds what a byte can hold. The value just rolls over, like this:

250 + 10: 251, 252, 253, 254, 255, 0, 1, 2, 3, 4

Thus, the checksum after adding that 10 is now 4.

Here is a simple 8-bit checksum routine for strings in Color BASIC:

0 REM CHKSUM8.BAS
10 INPUT "STRING";A$
20 GOSUB 100
30 PRINT "CHECKSUM IS";CK
40 GOTO 10

100 REM 8-BIT CHECKSUM ON A$
110 CK=0
120 FOR A=1 TO LEN(A$)
130 CK=CK+ASC(MID$(A$,A,1))
140 IF CK>255 THEN CK=CK-255
150 NEXT
160 RETURN

Line 140 is what handles the rollover. If we had a checksum of 250 and the next byte was a 10, it would be 260. That line would detect it, and subtract 255, making it 4. (The value starts at 0.)

The goal of a checksum is to verify data and make sure it hasn’t been corrupted. You send the data and checksum. The received passes the data through a checksum routine, then compares what it calculated with the checksum that was sent with the message. If they do not match, the data has something wrong with it. If they do match, the data is less likely to have something wrong with it.

Double checking the sum.

One of the problems with just adding (summing) up the data bytes is that two swapped bytes would still create the same checksum. For example “HELLO” would have the same checksum as “HLLEO”. Same bytes. Same values added. Same checksum.

A good 8-bit checksum.

However, if one byte got changed, the checksum would catch that.

A bad 8-bit checksum.

It would be quite a coincidence if two data bytes got swapped during transfer, but I still wouldn’t use a checksum on anything where lives were at stake if it processed a bad message because the checksum didn’t catch it ;-)

Another problem is that if the value rolls over, that means a long message or a short message could cause the same checksum. In the case of an 8-bit checksum, and data bytes that range from 0-255, you could have a 255 byte followed by a 1 byte and that would roll over to 0. A checksum of no data would also be 0. Not good.

Checking the sum: Extreme edition

A 16-bit or 32-bit checksum would just be a larger number, reducing how often it could roll over.

For a 16-bit value, ranging from 0-65535, you could hold up to 257 bytes of value 255 before it would roll over:

255 * 257 = 65535

But if the data were 258 bytes of value 255, it would roll over:

255 * 258 = 65790 -> rollover to 255.

Thus, a 258-byte message of all 255s would have the same checksum as a 1-byte message of a 255.

To update the Color BASIC program for 16-bit checksum, change line 140 to be:

140 IF CK>65535 THEN CK=CK-65535

Conclusion

Obviously, an 8-bit checksum is rather useless, but if a checksum is all you can do, at least use a 16-bit checksum. If you were using the checksum for data packets larger than 257 bytes, maybe a 48-bit checksum would be better.

Or just use a CRC. They are much better and catch things like bytes being out of order.

But I have no idea how I’d write one in BASIC.

One more thing…

I almost forgot what prompted me to write this. I found some code that would flag an error if the checksum value was 0. When I first saw that, I thought “but 0 can be a valid checksum!”

For example, if there was enough data bytes that caused the value to roll over from 65535 to 0, that would be a valid checksum. To avoid any large data causing value to add up to 0 and be flagged bad, I added a small check for the 16-bit checksum validation code:

if ((checksum == 0) && (datasize < 258)) // Don't bother doing this.
{
    // checksum appears invalid.
}
else if (checksum != dataChecksum)
{
    // checksum did not match.
}
else
{
    // guess it must be okay, then! Maybe...
}

But, what about a buffer full of 00s? The checksum would also be zero, which would be valid.

Conclusion: Don’t error check for a 0 checksum.

Better yet, use something better than a checksum…

Until next time…