Unfinished Bitness

Finishing bits of technology, vintage and new.

  • Home
  • About
  • Contact
  • Software
    • CubeDot
    • Dr. Love
    • Fuji Do
    • Fuji Poo
    • Mr. Paint
    • Total Recall
    • Action! Library
    • A8 C Library
    • Pascal Library

Scriptee, Part 26

Posted by Ripdubski on 2026.10.12
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

This post was going to be about the CD command. However, I had no luck with getting it to work on any of the DOS’s. No errors, but just not working. Until I can figure out why, the CD command is being shelved while I work out the issue in a test program. So, for this post I’m going to summarize the current list of supported commands. Gathering this data will also aid in creating the Scriptee documentation. And as it turned out, also helped me identify several areas of code that need hardening. For simplicity, I’m presenting them in alphabetical order:


:<LABEL>

Defines a name/label for a section of script code. The label can be up to 40 characters in length and should only contain alpha-numeric characters. There is a maximum of 20 labels. And I just realized the labels need to be cleared when the CHAIN command is invoked because they will be invalid after the new script is loaded. There also needs to be a size check on the label. Look for those modifications in another post.


BACK <ICODE>

Sets the background of the screen to a specific character. This must be specified in the internal character code, not ATASCII. All values from 0 to 255 are accepted. Get your copy of Compute’s Third Book Of Atari handy as there is a great character reference in the back.


BOX <BOXID> X Y W H <STYLE>

Draws a box at the X and Y screen coordinate of width W and height H in given style. This requires six parameters. The first is a BOXID, which can be a letter from A through J (10 boxes are supported). You can reuse a BOXID if you want, but will lose the previous coordinate information by doing so.

The second and third parameters are the X and Y screen coordinate at which to place the box. The X and Y parameters can be separated with a space or comma.

The fourth and fifth are the width and height of the box. The W and H parameters can be separated with a space or comma.

The sixth is the style. Support styles are BLOCK, FAT, NONE, THIN, and XE.

If invalid coordinates or sizing are specified, script execution will stop and Scriptee will exit with an error.


CHAIN "DRIVE:>PATH>FILENAME.SCT"

Loads a new script and begins execution. The script name should be fully qualified and enclosed in quotes such that the drive, path (if any), filename, and extension are specified. The script name parameter can be up to 80 characters.


Note: When chaining scripts together, the internal variables for the real time clock and pick routines remain intact.


CLS

Clears the screen (Clear Le Screen).


DATET

Outputs the date to the current cursor position on the screen. The date format is YYYY.MM.DD. Currently the ability to alter the format does not exist.


EXIT

Stops execution of the running script and exits Scriptee. For command line DOS’s this should return control back to the command prompt. For non command line DOS’s, it may or may not return to DOS and load the DUP menu.


GETTD[G]

Gets the time and date from the real time clock device. This is the second real time clock command that should be used in a script, following RTC itself. If the APE-Time real time clock is in use, you can optionally add a G to the end of the command which tells Scriptee to get GMT instead of local time. On some APE-Time devices local time may report as GMT until the time zone is set. Use the SETTZ command to set the local time zone.

This command can be called as frequently as needed, and stores the retrieved values for later use.

If this is called without setting the real time clock type with the RTC command, script execution will stop and Scriptee will exit with an error.


GOXY X Y

Moves the cursor to the X and Y position specified. The next display output will appear at this location unless a coordinate is specified with it as well. The X and Y parameters can be separated with a space or comma. The X value is limited to the screen width in columns and the Y value is limited to the screen height in rows, both being 0 based.

If an invalid value is passed, the script will stop execution and Scriptee will exit with an error.


GOBXY <BOXID> X Y

Moves the cursor to the X and Y position in relation to the top left corner of the BOX specified by BOXID. This requires three parameters. The X and Y parameters can be separated with a space or comma. The first is the BOXID of a previously defined BOX.

The second and third are the X and Y offsets of the top left of the BOX coordinate.

If the coordinates are invalid, the script will stop execution and Scriptee will exit with an error.


GOTO <LABEL>

Sets the next script execution line to the first line following the label specified. If an invalid label is specified, the script execution will stop and Scriptee will exit with an error.


HDIV[K] <BOXID>/NOBOX Y <STYLE>

Draws a horizontal divider. This requires three parameters. The first parameter is a BOXID in which to draw the divider, which must have previously been defined. This can optionally be specified as NOBOX to draw a divider anywhere on the screen.

The second parameter is the Y location. When a BOXID is given as the first parameter, the Y location is the offset from the top of the box. When NOBOX is given as the first parameter, the Y location is the Y screen coordinate, and the divider is drawn the full screen width on that row.

The third parameter is the style to draw the divider in. Supported styles are BLOCK, LOWER, UPPER, NONE, and XE.

When the first parameter is a BOXID, the command supports an optional suffix of K. The K tells Scriptee to Keep the existing box frame styles intact. This allows mixing styles between the divider and box for additional creative control.

If the Y coordinate or style is invalid, script execution will stop and Scriptee will exit with an error.


HELP

Displays a brief help message. This parameter is only available when passed to Scriptee via the command line, limiting its’ usage to command line DOS’s. This displays the same version and copyright information as the VER command, but also includes a usage syntax message.


IF 
    IF PICK <CHAR> <COMMAND>
    IF KEY TRUE  <COMMAND>
    IF KEY FALSE <COMMAND>

Tests one of two conditions and performs an action based on the result. The IF command supports two conditions: PICK and KEY.

IF PICK is used to test if the CHAR matches what the last PICK command retrieved from the user. If it does match, the COMMAND following will be processed.

IF KEY is used to test if a key has been pressed or not. IF KEY uses the next parameter to determine which case to test. Specifying TRUE checks for a keystroke. Specifying FALSE checks for NO keystroke. If the condition passes the COMMAND following will be processed.

Not all Scriptee commands are supported by the IF command – while technically possible, some don’t make sense to allow. Commands supported are: CLS, EXIT, GOTO, POKE, RUN, TEXT[E], TEXTXY[E], and WAIT.

If any of the following conditions occur, script execution will stop and Scriptee will exit with an error: a) something other than PICK or KEY is specified; b) something other than TRUE or FALSE is specified for IF KEY; c) an unsupported COMMAND is specified.


INV ON/OFF

Turns the inverse video mode on or off. Valid parameters are ON to cause all displayed output to be in inverse video, or OFF to cause all displayed output to be in normal video.


NL

Outputs a newline to the screen which moves the cursor to position 0 (far left) and the next row of the screen.


PICK <CHAR>...<CHAR>

Gets a choice of a single character from the user. This requires one variable length parameter which is a list of the characters that are valid responses. The command will continue processing keystrokes until a character from the parameter string it pressed. The inputs are forced capital. The parameter string should be listed in UPPERCASE, not enclosed in quotes, and can contain alpha-numerics only (A-Z, 0-9).


POKE <ADDRESS> <VALUE>

Sets a memory location to a value, just like the BASIC POKE command you remember. It requires two parameters – the memory address specified in decimal form from 0 to 65535, and the value to place in the location from 0 to 255. These parameters can be separated with a space or comma.

This was a quick cheat to allow setting screen colors, cursor speed, etc without using a bunch of commands which would increase code size. It turned out to be the correct solution.


RTC APE/RT8

Tells Scriptee which real time clock to use. Supported values are APE for the APE-Time clock accessible over the SIO bus (if present), or RT8 for the ICD R-Time 8 cartridge. This should be the very first real time clock command used.


RUN "DRIVE:>PATH>FILENAME.XEX"

Loads and runs a binary executable. The binary name should be fully qualified and enclosed in quotes such that the drive, path (if any), filename, and extension are specified. The binary name parameter can be up to 80 characters.


SETTZ "EST5EDT"

Sets the timezone to use for local time when using the APE-Time real time clock. The parameter should a valid timezone definition for APE-Time, an example is shown as “EST5EDT” which is U.S. Eastern.

If an attempt to use this command when the real time clock is set to RT8, an error will be displayed and Scriptee will exit.


TEXT[E] "TEXT"

Displays the quoted text at the current cursor position. This requires a single parameter which is the text string to display and must be enclosed in quotes. Adding the E suffix to the command causes a newline to be output after the text, returning the cursor to position 0 on the next line.


TEXTXY[E] X Y "TEXT"

Displays the quoted text at the screen coordinate denoted by X and Y. This requires three parameters. The first two are the X and Y coordinates at which to display the text, and the third is the text string to display. The X and Y parameters can be separated with a space or comma. The text string must be enclosed in quotes. Adding the E suffix to the command causes a newline to be output after the text, returning the cursor to position 0 on the next line.


TEXTBXY <BOXID> X Y "TEXT"

Displays the quoted text the screen coordinate denoted by X and Y in relation to the top left corner of the BOX specified by BOXID. This requires four parameters. The first is the BOXID of a previously defined BOX. The second and third are the X and Y offsets of the top left of the BOX coordinate. The X and Y parameters can be separated with a space or comma. The fourth is the text string to display, and must be enclosed in quotes. Because this is BOX related, the E suffix does not apply for TEXTBXY.


TIMET

Outputs the time to the current cursor position on the screen. The time format is HH:MM:SS. Currently the ability to alter the format does not exist.


TITLE[I] <BOXID> LEFT/RIGHT/CENTER TOP/BOTTOM <STYLE> "TEXT"

Displays the text string as a box frame title with left and right ornaments in the designated style. This requires five parameters. The first parameter is the BOXID to place the title on, and must have been previously defined.

The second parameter specifies the left to right location on the box frame specified as one of LEFT, RIGHT, or CENTER.

The third parameter specifies the top or bottom location on the box frame specified as TOP or BOTTOM. For the location parameters you can use the whole word, or just the first letter, and they can be separated with a space or comma.

The fourth parameter designates the style for the title ornaments (characters that appear on the left and right sides of the title text), and supports: BLOCK, FAT, NONE, THIN, and XE. If you want a nice blend into the frame, use the same style the box was defined with.

The fifth parameter is the title string itself. The text is limited in length to the box width minus two. The TITLE command supports and optional command suffix of I.

If specified it will cause the text portion of the title, not the ornaments, to be displayed in inverse video.


VER

Displays Scriptee version and copyright. This parameter is only available when passed to Scriptee via the command line, limiting its’ usage to command line DOS’s.


WAIT <0...240>

Pauses script execution for the specified number of seconds. Values can range from 0 seconds to 240 seconds (4 minutes). Why 0? It allows eating up an unspecified amount of time less than 1 second – think of it as a very quick pause. If you need a longer timeout than 240 seconds, you can call WAIT multiple times.

I’ve never been quite happy with the real time clock commands, and looking at the list I might consolidate them into just the RTC command itself. So there would be commands might be:
RTC APE/RT8/GETTD/SETTZ/DATE/TIME. I need to think about this some more.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 25

Posted by Ripdubski on 2026.10.09
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

This post is all about having Scriptee run another program. My first attempts to directly load a binary file into memory from Scriptee and then jump to run address failed miserably. I realized the loaded code was overwriting Scriptee itself! So I looked for an alternate way to load.

The next logical option was to fall back to letting DOS handle it. To do that I needed a way to execute an XIO call from C. Having done SIO calls in the past, I thought it shouldn’t be much different. But, initially my attempts failed.

While searching for more information about setting up the IOCB in CC65, I stumbled on a thread in the comp.sys.atari.8bit newsgroup from Bill Kendrick that is 21 years old where he was attempting something similar. You can find it here: https://comp.sys.atari.8bit.narkive.com/WroTYrOS/xio-in-c

I talked to Bill about this, and he shared how he did it. In this discussion he cited a reference from De Re Atari (https://www.atarimania.com/8bit/files/De-Re-Atari.pdf), which is one place I had not looked. Doh! The code cited that was the clue for me is as follows:

LDX #IOCBNUM ;set the IOCB index (IOCB * 16)
JSR CIOV ;system routine vector to CIO

It was the comment on the Load X register line (LDX) that clued me in. It says to set the index with the IOCB number multiplied by 16. In all my research I somehow missed this important tidbit. Everything I read always showed examples of LDX #$10, which is what I was trying, but I am using IOCB 5. That means I need LDX #$50! I changed my code to use $50 and it started working.

With my test program working I set about adding the ability to Scriptee. To support the command, I added this definition:

// Run binary
#define S_CMD_RUN "run"
#define S_CMD_RUN_SZ 3

Because the run command is dependent on XIO, I also setup some definitions to support that. Initially I wanted to support both RUN and CD so I set a FUNCtion type for LR (XIO Load/Run). Due to various DOS implementations, I needed to differentiate between command 39 and 40, and setting the AUX1 byte. And finally a definition for the IOCB number:

// ----- XIO/CIO Call Function Types -----
#define S_XIO_FUNC_LR 'R'
#define S_XIO_CMD_LR_39 39
#define S_XIO_CMD_LR_40 40
#define S_XIO_AUX_LR_0 0
#define S_XIO_AUX_LR_4 4
#define S_XIO_AUX_LR_128 128
#define S_XIO_IOCB_ID 5

Finally I setup readability shortcuts which compile as inline assembly. These load the X registry (correctly!) and go to the CIO vector via JSR (Jump Sub Routine):

#define LDX50() __asm__("LDX #$50")
#define CIOV() __asm__("JSR $E456")

Before making an XIO call, I needed to determine if the underlying DOS supports it. While CC65 determines DOS at a BASIC level, that wasn’t enough as several DOS’s report back as the same value. I recently blogged about how I solved this here (https://unfinishedbitness.info/2026/09/24/cc65-and-atari-dos-type-revisited/). I added my new DOSID() function to Scriptee. To support determining DOS, I added these definitions, where 10 through 15 are the variants my routine detects:

// ----- DOS IDs -----
#define S_DOS_SPARTA 0
#define S_DOS_REAL 1
#define S_DOS_BW 2
#define S_DOS_OSS 3
#define S_DOS_X 4
#define S_DOS_ATARIG 5
#define S_DOS_MY 6
#define S_DOS_ATARI2 10
#define S_DOS_ATARI25 11
#define S_DOS_SMART 12
#define S_DOS_SUPER 13
#define S_DOS_TOP 14
#define S_DOS_TURBO 15
#define S_DOS_UNKNOWN 255

I also added a global variable to hold the detected value:

unsigned char gbDV = S_DOS_UNKNOWN;

And that global variable is set in the main() function just before the command processing loop. This way the version is always available to any command that might need it:

    // ---------- GET DOS VERSION ----------
    gbDV = DOSID();

My DOSID() routine accepts no parameters and returns a value representing the DOS detected. The routine then checks two bytes in memory where DOS resides – locations $0700 and $0709. It then checks the value found in $0700.

If the value in $0700 is 0, it then looks at the value in $0709, because several DOS’s have a 0 in $0709 making it not enough to distinguish. Looking at $0709 lets me identify the DOS for this condition. If the value is 7, it is Atari DOS 2.0. If the value is 3, it is Atari DOS 2.5. If the value is 4, it is SmartDOS 6.1. If the value is 125, it is TopDOS Pro 1d.

If the value in $0700 is 1, I don’t look any further. It is TurboDOS XE.

If the value in $0700 is 121, I don’t look any further. It is SuperDOS 2.0.

If the value in $0700 is not 0, 1, or 121, then the value of the CC65 derived global _dos_type is returned.

Here is the DOSID() routine:

// ------------------------------------------------------------
// Func...: unsigned char DOSID(void)
// Desc...: Determines DOS version for AtariDOS & compatibles
// Returns: unsigned char with unique ID
// 0 = SPARTADOS (_dos_type)
// 1 = REALDOS (_dos_type)
// 2 = BWDOS (_dos_type)
// 3 = OSADOS (_dos_type)
// 4 = XDOS (_dos_type)
// 5 = ATARIDOSG (_dos_type)
// 6 = MYDOS (_dos_type)
// 10 = Atari DOS 2.0
// 11 = Atari DOS 2.5
// 12 = SmartDOS 6.1
// 13 = SuperDOS 2.0
// 14 = TopDOS Pro 1d
// 15 = TurboDOS XE
// 255 = Unknown
// ------------------------------------------------------------
unsigned char DOSID()
{
unsigned char bA, bB, bR = S_DOS_UNKNOWN;
// Get byte pair that make up unique signature
bA = PEEK(0x0700);
bB = PEEK(0x0709);
// Check bytes to determine DOS (generic Atari compatible)
switch (bA) {
case 0 : if (bB == 7) { bR = S_DOS_ATARI2; }
else if (bB == 3) { bR = S_DOS_ATARI25; }
else if (bB == 4) { bR = S_DOS_SMART; }
else if (bB == 125) { bR = S_DOS_TOP; }
break;
case 1 : bR = S_DOS_TURBO;
break;
case 121 : bR = S_DOS_SUPER;
break;
// None of the above, use what CC65 found
default : bR = _dos_type;
}
return (bR);
}

Knowing the DOS allowed me to finish the XIO function. I wanted to be able to reuse the XIO function for both RUN and CD, so I set it up to accept a parameter to specify which command was being asked for. The XIO() function sets a default command value to S_DOS_UNKNOWN (255), and the default AUX1 value to 0.

The XIO() function then does a sanity check. If the command is ‘R’ (S_XIO_FUNC_LR – Load/Run), it then checks the extended DOS value determined with my DOSID() routine that was saved into a global variable. If the value matches that of SpartaDOS, or RealDOS the XIO command value is set to 40 and the AUX1 value is set to 4. For XDOS, only the XIO command value needs to be set and is set to 40. For MyDOS, the XIO command value is set to 39 and the AUX1 value is set to 4. BW-DOS (BeweDOS) supports a load/run XIO call but I couldn’t get it to work properly, and as such it is currently commented out.

You may be asking why there is an if() structure checking against S_XIO_FUNC_LR since there is no other condition. It is to distinguish between RUN and CD commands, which I’ll get to later.

After the command and AUX1 values are determined, the sanity check is made. If XIO() got through the if() structure and still has a command value of S_DOS_UNKNOWN (255), Scriptee exits with error S_ERR_DOSVER, which has the message “No DOS support.”.

If the sanity check passes I proceed to setup the parameters for the XIO call into IOCB #5’s memory locations. The buffer is set to the global filename string. The aux and status bytes are all cleared. And the command and aux1 bytes are set to what the XIO() function determined based on DOS type. And last it sets the X register and calls the CIO vector to handle the XIO call. I’m not expecting to return to Scriptee at this point. Finger crossed and away we go. Here is the XIO() function:

// -----------------------------------
// Proc..: void XIO(unsigned char bC)
// bC = XIO function to execute
// S_XIO_FUNC_LR = Load/Run
// S_XIO_FUNC_CD = Change Directory
// Desc..: Calls the given CIO routine.
// Notes.: A function "name" is passed instead of the #
// because the # varies by DOS for Load/Run.
// Buffer will be picked up from global gcFI.
//
// 0 = SPARTADOS (_dos_type) (LR: 40,AUX1=4 (SPD), 64 (ATA), AUX2=0-Load/Run, CD: 44)
// 1 = REALDOS (_dos_type) (LR: 40,AUX1=4 (SPD), 64 (ATA), AUX2=0-Load/Run, CD: 44)
// 2 = BWDOS (_dos_type) (LR: 40,AUX1=4 (SPD); not AUX1=0-Load, 128-Load/Run, CD: 44) aka BeweDOS
// 3 = OSADOS (_dos_type) (LR: NO CALL AVAILABLE, CD: NA)
// 4 = XDOS (_dos_type) (LR: 40,AUX1=0, CD: NA)
// 5 = ATARIDOSG (_dos_type) (LR: NO CALL AVAILABLE, CD: NA)
// 6 = MYDOS (_dos_type) (LR: 39,AUX1=4, CD: 41)
// 10 = Atari DOS 2.0 (LR: NO CALL AVAILABLE, CD: NA)
// 11 = Atari DOS 2.5 (LR: NO CALL AVAILABLE, CD: NA)
// 12 = SmartDOS (LR: NO CALL AVAILABLE, CD: NA)
// 13 = SuperDOS (LR: NO CALL AVAILABLE, CD: NA)
// 14 = TopDOS (LR: NO CALL AVAILABLE, CD: 44)
// 15 = TurboDOS (LR: NO CALL AVAILABLE, CD: NA)
// -----------------------------------
void XIO(unsigned char bC)
{
unsigned char bN = S_DOS_UNKNOWN; // Command number (default Atari)
unsigned char bA = S_XIO_AUX_LR_0; // Aux byte (default Atari)
// If LOAD/RUN
if (bC == S_XIO_FUNC_LR) {
// Adjust command and aux byte based on DOS type
switch (gbDV) {
case S_DOS_SPARTA : // SpartaDOS
case S_DOS_REAL : // RealDOS
bN = S_XIO_CMD_LR_40;
bA = S_XIO_AUX_LR_4;
break;
// BW-DOS not working with AUX1 of 0, 4, or 128. Hangs at XIO call.
// case S_DOS_BW : // BWDOS
// bN = S_XIO_CMD_LR_40;
// bA = S_XIO_AUX_LR_128;
// break;
case S_DOS_X : // XDOS
bN = S_XIO_CMD_LR_40;
break;
case S_DOS_MY : // MyDOS
bN = S_XIO_CMD_LR_39;
bA = S_XIO_AUX_LR_4;
break;
}
}
// If we made it out without setting the command, its invalid for this DOS, so exit
if (bN == S_DOS_UNKNOWN) {
Later(S_ERR_DOSVER, gcLn);
}
// Setup IOCB for use
OS.iocb[S_XIO_IOCB_ID].command = bN;
OS.iocb[S_XIO_IOCB_ID].buffer = gcFI;
OS.iocb[S_XIO_IOCB_ID].buflen = strlen(gcFI);
OS.iocb[S_XIO_IOCB_ID].aux1 = bA;
OS.iocb[S_XIO_IOCB_ID].aux2 = 0;
OS.iocb[S_XIO_IOCB_ID].aux3 = 0;
OS.iocb[S_XIO_IOCB_ID].aux4 = 0;
OS.iocb[S_XIO_IOCB_ID].aux5 = 0;
OS.iocb[S_XIO_IOCB_ID].status = 0;
// Take a ride on a jet plane, we may never be back again!
// Load the X register with the (IOCB number * 16), then call the CIO vector
// S_XIO_IOCB_ID is 5, so X should be $50.
LDX50();
CIOV();
}

I then added the actual RUN command to the main() routines command processing loop just before the EXIT command. This ensures that at least one parameter is specified which should be the file to run which could optionally contain a drive and path (currently limited to 80 bytes). It then gets that first token from the command line and stores it the global filename buffer (Oh! I see an overrun check needs to be made!). It then invokes the XIO() function specifying the Load/Run command type:

        // ---------- RUN Command ----------
        else if (strnicmp(gcLn, S_CMD_RUN, S_CMD_RUN_SZ) == 0) {
            // RUN drive:[path\]filename.ext
            ChkPC(1, bC);

            // Get token which should be filename
            bWZ = GTk(gcLn, 1, gcFI);

            // Take a ride on a jet plane!
            XIO(S_XIO_FUNC_LR);
        }

Then it was just a matter of testing. This wasn’t a simple task. As shown above the values are correct. Some of the command and AUX1 values I found were incorrect and it took some trial and error to find the right combinations for the various DOS’s. Time consuming to say the least. With it working across the DOS’s that support it, I grabbed some screenshots showing the success of Scriptee calling my DOSTYPE.XEX program. Here they are:

SpartaDOS
RealDOS
XDOS
MyDOS
BW-DOS (Hanging at the XIO call!)

And here is one trying to use the RUN command where the DOS (Atari DOS 2.5) doesn’t support it (prior to me shortening the error message to “No DOS support.”:

I still have some testing to do for RUN, which will take some time. While I’m doing that I will also start work on the CD command.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 24

Posted by Ripdubski on 2026.10.05
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

Shortly after I completed the implementation of the HDIV command, I wanted to expand it so that it could be used without a BOX. To support the modification, I added a few definitions that are used. The first two (S_ALLOW_NOBOX_*) are parameters that are passed to GetID which tell that function if “nobox” is allowed as a box identifier. There is a box ID for when nobox is specified as a box, and the nobox command option string and its size:

// ----- NOBOX options -----
#define S_ALLOW_NOBOX_T 1
#define S_ALLOW_NOBOX_F 0
#define S_NOBOX_ID 255
#define S_NOBOX_CMD "nobox"
#define S_NOBOX_CMD_SZ 5

As inferred above, GetID() needed to be modified to allow “nobox” to be specified as a box handle. This was done because GetID() normally tries to validate if the box handle specified is valid, and “nobox” would evaluate to box “N” which is invalid. The function was modified to accept a parameter that determines if “nobox” is allowed.

The updated GetID() function adds an if() clause just after it gets the ID token from the command line. If “nobox” is allowed, it then checks if “nobox” was passed as the token. If “nobox” was passed, it returns the S_NOBOX_ID (255) which is a value that it outside any valid BOX id. The rest of the function remains the same. Here is the new function code:

// ----------------------------------------------------------------------
// Func.......: unsigned char GetID(unsigned char bL)
// Description: Gets box id token from command line
// Param......: bL = Allow or disallow NOBOX
// S_ALLOW_NOBOX_T = Allow
// S_ALLOW_NOBOX_F = Disallow
// Notes......: Will always be 1st parameter when used
// ----------------------------------------------------------------------
unsigned char GetID(unsigned char bL)
{
unsigned char cW[S_WORD_SZ]; // Token buffer, cleared by GTk before use.
unsigned char bWZ, bV;
// Get ID token
bWZ = GTk(gcLn, 1, cW);
// If NOBOX is allowed...
if (bL == S_ALLOW_NOBOX_T) {
// If NOBOX is set return nobox id (255)
if (strnicmp(cW, S_NOBOX_CMD, S_NOBOX_CMD_SZ) == 0) {
return(S_NOBOX_ID);
}
}
// Get and uppercase first letter of token (rep A-...)
bV = toupper(cW[0]);
// Sanity check value. Bail if bad.
// Cant be less than 'A or more than 'A + max boxes (ie: J for 10)
if ((bV < 65) || (bV > 64 + S_BOX_MAX)) {
Later(S_ERR_BADBOXID, gcLn);
}
// Subtract 'A from the extracted value to get the real id
return(bV - 65);
}

With the GetID() function now supporting “nobox” as an id, I modified the HDIV command routine. It now calls GetID() with S_ALLOW_NOBOX_T to enable “nobox” as a valid id.

Currently the HDIV command is the only one that needs it, so all the other calls to GetID were modified to pass S_ALLOW_NOBOX_F. Previously the routine checked for screen overrun based solely on if the Y coordinate was greater than the box height. This was amended to also check if the Y coordinate is greater than the screen height when “nobox” has been specified.

The next big change follows the style determination block. If nobox mode is set, I set the line width to the full screen width, and the X offset to 0. If nobox mode is not set, I set the line width to the window width minus two. This was previously done when setting the line characters. For this case, the X coordinate is set to the box X coordinate and the box Y coordinate is added to the specified Y location, both of which were previously done in the Disp() function call.

And last, the Display block Disp() function calls were modified. The X and Y coordinates now reference the derived X and Y coordinates instead of the box X and Y coordinates. And in the case where the frame styles are not being kept, if “nobox” was specified I output the inner line character in place of the box ornaments defined by the style. Here is the new routine code:

        // ---------- HDIV/HDIVK Command ----------
        else if (strnicmp(gcLn, S_CMD_HDIV, S_CMD_HDIV_SZ) == 0) {
            // HDIV id|NOBOX Y style
            // HDIVK id Y style   (keep box frame style)
            ChkPC(3, bC);

            // Get id
            bV = GetID(S_ALLOW_NOBOX_T);

            // Check if command ends in K to keep sides as box style
            bH = CE(S_END_K);

            // Get y coord (box relative) for div, convert to bytes
            bWZ = GTk(gcLn, 2, cW);
            bY = ToB(cW, S_SCRNY_SZ);

            // Check size for box overrun or screen overrun (w/o box) and bail if too big
            if (((bV != S_NOBOX_ID) && (bY > gaB[bV].bH)) ||
                ((bV == S_NOBOX_ID) && (bY > S_SCRNY_SZ))) {
                Later(S_ERR_BADCOORD, gcLn);
            }

            // Set build string (style) based on selection type
            bWZ = GTk(gcLn, 3, cW);
            if (stricmp(cW, S_STYLE_THIN) == 0)         { cBox = cDTH; }
            else if (stricmp(cW, S_STYLE_FAT_TOP) == 0) { cBox = cDFT; }
            else if (stricmp(cW, S_STYLE_FAT_BOT) == 0) { cBox = cDFB; }
            else if (stricmp(cW, S_STYLE_BLOCK) == 0)   { cBox = cDBL; }
            else if (stricmp(cW, S_STYLE_NONE) == 0)    { cBox = cDNO; }
            else if (stricmp(cW, S_STYLE_XE) == 0) {
                cBox = cDXE;
                bH = FALSE;   // Disallow KEEP for XE style (doesnt draw right)
            }
            else {
                Later(S_ERR_BADSTYLE, gcLn);
            }

            if (bV == S_NOBOX_ID) {
                // NOBOX mode.
                // Set full screen width, set X coord to 0
                bW = S_SCRNX_SZ - 1;
                bX = 0;
            } else {
                // BOX mode.
                // Set width of window, add box Y offset, grab box X coord
                bW = gaB[bV].bW - 2;
                bY += gaB[bV].bY;
                bX = gaB[bV].bX;
            }

            // Create divider line at full box width without frame handles
            memset(gcLM, cBox[S_DIVCH_M], bW);
            gcLM[bW] = 0;

            // If XE
            if (stricmp(cW, S_STYLE_XE) == 0) {
                gcLM[0] = cBox[S_DIVCH_L1];
                gcLM[1] = cBox[S_DIVCH_L2];
                gcLM[bW - 2] = cBox[S_DIVCH_R1];
                gcLM[bW - 1] = cBox[S_DIVCH_R2];
            }

            // Display
            if (bH != TRUE) {   // If not keeping frame styles, output them as well
                Disp(bX, bY, "%c%s%c", (bV == S_NOBOX_ID ? cBox[S_DIVCH_M] : cBox[S_DIVCH_L1]),
                                       gcLM,
                                       (bV == S_NOBOX_ID ? cBox[S_DIVCH_M] : cBox[S_DIVCH_R1]));
            } else {
                Disp(bX + 1, bY, "%s", gcLM, NULL, NULL);
            }
        }

With the modifications in place, I updated the Scriptee HDIV test script I previously wrote. Here is the updated script I used to test the new NOBOX functionality alongside the BOX usage:

# HDIV.SCT
CLS
BACK 14
BOX A 3,1,34,5 THIN
TITLEI A C,T THIN " Box:THIN "
HDIV A 2 THIN
INV ON
HDIV NOBOX 7 THIN
BOX C 3,9,34,5,THIN
TITLEI C C,T THIN " Box:THIN "
TITLE C L,B THIN "Inverse"
TITLE C R,B THIN "Keep"
HDIVK C 2 THIN
INV OFF
HDIVK NOBOX 16 UPPER
HDIV NOBOX 17 XE
HDIVK NOBOX 18 LOWER
GOXY 0,20

Screenshot of the script output:

Now Scriptee if feeling pretty good. Just a couple things left before release. One, I want a way to change the current disk directory (for DOS’s that support it); and two, I want, scratch that, NEED to be able to load and run another program. That will be the subject of the next post.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 23

Posted by Ripdubski on 2026.10.02
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

While reviewing the code to date making sure I didn’t miss something and looking for improvement opportunities, I discovered I didn’t check if the user specified an invalid memory address for the POKE command. Under normal operating conditions it would not produce an error, but it could alter a different memory location. Why? Specifying a value greater than the maximum allowed for an unsigned integer simply rolls over to 1. So, 65536 becomes 1. I decided I needed to correct this omission.

Because the value will roll over during conversion, I can’t just check if the return value exceeds the maximum. Without using a long integer, I got a little creative.

Why not just a long value? I thought about that, but I would still need to check if the user tried to overrun it because its being parsed from a string. Though the validation might be simpler, it would still need two checks (length and value), and my solution involves only two checks (well, three in two conditions).

Since I grab the address as a token on the command line in string form, I check the first two characters to see if they are greater than 64 (65+, the beginning of the maximum address of 65535). I then convert the last three characters of the string to an integer and check that against a value greater than 535 (the end of the maximum address of 65535). The if() clause also checks that the token length for the address is not greater than 5 characters because that would be an invalid address no matter what. If the if() clause doesn’t pass Scriptee exits with the S_ERR_BADPOKE error (“Bad address.”).

To support the validation check, I added a definition for the lower part of the highest address value (535):

#define S_POKE_MAX 535 // Highest memory address

A keen observer would note that I introduced a new error above (S_ERR_BADPOKE). The definition and error string are defined as follows, where 22 is the index into the global error string array. Both seen here:

#define S_ERR_BADPOKE 22
const unsigned char *gcEA[24] = {
<...snip...>
"Bad address.",
"Missing parameter."
};

Here is the new POKE command routine that I described:

        // ---------- POKE Command ----------
        else if (strnicmp(gcLn, S_CMD_POKE, S_CMD_POKE_SZ) == 0) {
            // POKE xxxxx,0
            ChkPC(2, bC);

            // Grab address from 1st token/param
            bWZ = GSt(gcLn + S_CMD_POKE_SZ + 1, S_STRBRK_COMMA, cW);
            // Grab value from 2nd token/param
            bVZ = GSt(gcLn + S_CMD_POKE_SZ + 1 + strlen(cW) + 1, S_STRBRK_RETURN, cV);

            // Convert address and value to numerics
            iV = atoi(cW);
            // We cant check converted value for extents due to rollover,
            // so check if the first 2 digits are 65 or greater, then convert the
            // last 3 digits to an int, and check against the MAX 0 based
            // adddress.  65 AND 535, 65535 (highest address).  -OR-
            // If the length of the token is longer than 5 it cant be right.
            if (((strncmp(cW, "64", 2) > 0) && (atoi(cW+2) > S_POKE_MAX)) ||
               (strlen(cW) > 5)) {
                Later(S_ERR_BADPOKE, gcLn);
            }
            bV = (unsigned char) atoi(cV);

            // Issue Poke
            POKE(iV, bV);
        }

The following is the Scriptee script I used to test:

CLS
TEXTE "710,144"
POKE 710,144
TEXTE "1536,02"
POKE 1536,02
TEXTE "65535,15"
POKE 65535,15
TEXTE "128536,0"
POKE 128536,0

I changed the last two lines for each test with these values: 65536, 75536, and 128536. It also tests 1536 and 65535 (the highest valid address).

Here are screenshots of the errant addresses being caught by Scriptee:

I’ve been thinking about allowing the horizontal divider command (HDIV) to be used without a box. The next post will describe code modifications to allow that to work.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 22

Posted by Ripdubski on 2026.09.28
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

To allow Scriptee to handle scripts that would otherwise be larger than the free memory available, I decided to implement the CHAIN command. This will allow the user to break up an otherwise large script into smaller pieces that can operate self contained. That means code in each file can only reference code in that file – you won’t be able to GOTO a label in another script.

To support the CHAIN command, I first setup the command definition:

// Chain
#define S_CMD_CHAIN "chain"
#define S_CMD_CHAIN_SZ 5

Next I had to refactor some of the main() function. I needed to move these commands which occur ed just prior to the command processing loop, to just immediately inside the command processing loop:

Before implementation:

    <...snip...>
    // ---------- LOAD FILE ----------
    iRZ = Load();

    // ---------- DISCOVER LABELS -----------
    DLbl(iRZ);

    // ---------- EXECUTE CODE ----------

    // Reset pL to start of buffer
    pL = gcB;

    // Loop through buffer memory until exit is reached
    while (TRUE) {
        // Reset error
        bR = S_ERR_NONE;
	<...snip...>



After implementation:

    <...snip...>
    // ---------- EXECUTE CODE ----------

    // Loop through buffer memory until exit is reached
    while (TRUE) {
        // If new buffer flag set, load it
        if (bN == TRUE) {
            // Load file
            iRZ = Load();

            // Discover Labels
            DLbl(iRZ);

            // Reset pL to start of buffer
            pL = gcB;

            // Clear new buffer flag
            bN = FALSE;
        }

        // Reset error
        bR = S_ERR_NONE;
        <...snip...>

As you can see those commands are placed inside an if() structure as well. This checks to see if a file load is needed. To support the load check a new flag was created and defaults to TRUE at program start so that the initial file will load. The if() will only execute the inner code if the bN flag is set to TRUE. Once the inner code is executed (file load, label discovery, and script execution location pointer set), the bN flag is set to false before leaving the if() structure. The bN flag variable is added to the beginning of the main function like this:

	unsigned char bC = 0, bL = 0, bN = TRUE;      // Argument, loop counter, new buffer flag

Previously the main function cleared the script buffer before doing much else. That command was removed and relocated to inside the Load() function. Here is the old code that was removed inside main():

    // Clear buffer memory
    memset(gcB, 0, S_BUFF_SZ);

Here is the clear buffer codes new location inside the Load() function. It executes only after determining if the script file was opened successfully:

    <...snip...>
    // If file opened
    if (fP == NULL) {
        Later(S_ERR_BADFILE, gcFI);
    }

    // Clear buffer
    memset(gcB, 0, S_BUFF_SZ);

    // Find file size and reset
    <...snip...>

Then I implemented the actual CHAIN command routine itself. I placed this in the main() functions command processing loop, just before the EXIT command which is dead last. It first checks that the minimum number of parameters was passed, which is the filename to chain to. If the parameter wasn’t passed, Scriptee will exit with a missing parameter error. If the parameter was passed, then I grab the token from the command line and copy it to the global filename buffer. Last I set the new buffer flag to TRUE to cause the next loop iteration to load the file and start executing it. Here is the implementation code:

        // ---------- CHAIN Commmand
        else if (strnicmp(gcLn, S_CMD_CHAIN, S_CMD_CHAIN_SZ) == 0) {
            // CHAIN filename
            ChkPC(1, bC);

            // Grab filename from 1st token/param () and copy it to global filename
            bWZ = GSt(gcLn + S_CMD_CHAIN_SZ + 1, S_STRBRK_RETURN, cW);
            strcpy(gcFI, cW);

            // Set new buffer flag
            bN = TRUE;
        }

I wrote three scripts to test with. The first displays what is happening and which file it is running from, then checks for a keypress. If a keypress is found, it exits, otherwise it uses CHAIN to load the second script. Here it is:

# CHAIN 1.SCT
NL
NL
TEXTE "Chained File Demo"
NL
TEXTE "In file : 1"
IF KEY TRUE EXIT
CHAIN DPCL1:CHAIN2.SCT

The second script displays which file it is running from, then uses CHAIN to load the third script. Here it is:

# CHAIN2.SCT
TEXTE "In file : 2"
CHAIN DPCL1:CHAIN3.SCT

The third script displays which file it is running from, then uses CHAIN to load the first script. Here it is:

# CHAIN3.SCT
TEXTE "In file : 3"
CHAIN DPCL1:CHAIN1.SCT

When running, this will loop through each script until a key is pressed. Here is a screenshot of it running through two iterations of the full chain and starting the third when a key is pressed:

And a screenshot showing how an invalid CHAIN filename is handled:

I’m going to do a code review and look for omissions and opportunities to improve code. I already spotted one area and that will be the subject of the next post.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 21

Posted by Ripdubski on 2026.09.25
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

I previously implemented PICK and IFP commands to get and process user choices. Remember, I’m targeting menu type scripts, not interpreted language stuff. Since Scriptee is capable of processing loop operations, I needed a way to get out of a loop via user interaction. To do that I decided to implement a KEY press check in the form of the new IF KEY command. Because I already had IFP, I had to refactor the code a little to efficiently handle both IF PICK and IF KEY. So, IFP was replaced by the IF PICK command. Essentially both PICK and KEY are handled by the IF command alone.

Beginning the refactor I replaced the command definitions. I removed both PICK and IFP, and added IF, PICK, and KEY. The PICK and KEY commands are prefixed with IF because they are only used with the IF command. Here are the definition changes:

Definitions before:

// Process choice/pick
#define S_CMD_IFPICK "ifp"
#define S_CMD_IFPICK_SZ 3

Definitions after:

// Process IF pick and IF key
#define S_CMD_IF "if"
#define S_CMD_IF_SZ 2
// IF pick type
#define S_CMD_IF_PICK "pick"
#define S_CMD_IF_PICK_SZ 4
// IF key type
#define S_CMD_IF_KEY "key"
#define S_CMD_IF_KEY_SZ 3

Because both PICK and KEY are supported by IF, I needed a way to keep track of which condition was being invoked. This is because the cW variable is used during each token extraction, and would be overwritten before it is used with the code organization. To support the KEY condition, I needed a way to test that the keypress is detected or not, thus a definition for TRUE and FALSE. Here are the definitions:

// IF options
#define S_OPT_PICK 'P'
#define S_OPT_KEY 'K'
#define S_OPT_TRUE 'T'
#define S_OPT_FALSE 'F'

Next I changed the main() functions command processing loop code. I removed the old logic routine:

        // ---------- IFP (If Pick) Command
        else if (strnicmp(gcLn, S_CMD_IFPICK, S_CMD_IFPICK_SZ) == 0) {
	        <...snip...>
	    }

And I added a new logic routine in its place. The new routine checks for the IF command by itself. If found, it then checks for the minimum required number of parameters and will exit if not met. Then I grab the first token from the command line which should be either PICK or KEY. The token is compared to the PICK and KEY options and a flag is set to indicate which it is. If it doesn’t match PICK or KEY, Scriptee will exit with S_ERR_BADIFCMD (“Bad command for IF.”).

Next I grab the second token from the command line. This will either be the letter choice to check for PICK option, or it will be TRUE or FALSE for the KEY option.

With the minimum parameters process to make a logic decision, a not-so fun if is performed. It handles both IF options. The first two lines of the if() process the KEY option, the last line is for the PICK option. Lines one and two are OR’d together, and that pair is OR’d with the third line. If any one line (condition) passes, the if() logic routine will be entered. In a nutshell:

The first line says – if the IF option is KEY, AND the parameter is TRUE, AND there is a keypress (location 764 has a value that is NOT 255), then pass into the if() logic routine.

The second line says – if the IF option is KEY, AND the parameter is FALSE, AND there is NOT a keypress (location 764 has value 255), then pass into the if() logic routine.

The third line says – if the IF option is PICK, AND the parameter char matches the last PICK command character, then pass into the if() logic routine.

Inside the if() logic, I grab the third token from the command line and check if it is valid for use with the IF command. If it matches one of the allowed commands, the command line is manipulated to copy the “then” portion of the IF command line, the in-logic flag is set, and the main() functions command processing loop continues (which will then execute the “then” portion of the IF command line. If the third token is not one of the allowed commands, Scriptee will exit with S_ERR_BADIFCMD (“Bad command for IF.”). Here is the new routine:

        // ---------- IF PICK/KEY Command
        else if (strnicmp(gcLn, S_CMD_IF, S_CMD_IF_SZ) == 0) {
            // IF PICK char GOTO label
            // IF PICK char TEXT/TEXTE/TEXTXY
            // IF PICK char EXIT
            // IF PICK char CLS
            // IF PICK char WAIT
            // IF PICK char POKE
            // IF PICK char RUN
            // IF KEY TRUE  ^command
            // IF KEY FALSE ^command
            ChkPC(3, bC);

            // Extract first token to get IF type,  set flag or error if invalid
            // Re-using bL loop var (temp)
            bWZ = GTk(gcLn, 1, cW);

            if (strnicmp(cW, S_CMD_IF_PICK, S_CMD_IF_PICK_SZ) == 0) {
                bL = S_OPT_PICK;
            }
            else if (strnicmp(cW, S_CMD_IF_KEY, S_CMD_IF_KEY_SZ) == 0) {
                bL = S_OPT_KEY;
            }
            else {
                Later(S_ERR_BADIFCMD, gcLn);
            }

            // Extract second token to get test value char (PICK) or value T/F (KEY)
            bWZ = GTk(gcLn, 2, cW);

            if ((((bL == S_OPT_KEY) && (cW[0] == S_OPT_TRUE) && (PEEK(S_MAP_KEY) != 255)) ||
                 ((bL == S_OPT_KEY) && (cW[0] == S_OPT_FALSE) && (PEEK(S_MAP_KEY) == 255))) ||
                ((bL == S_OPT_PICK) && (cW[0] == gbPC))) {
                // Extract third token to find command IF should execute
                bWZ = GTk(gcLn, 3, cW);

                // If checks valid so far, edit line and set reprocess flag (in logic)
                if ((strnicmp(cW, S_CMD_GOTO, S_CMD_GOTO_SZ) == 0) ||
                    (strnicmp(cW, S_CMD_TEXT, S_CMD_TEXT_SZ) == 0) ||
                    (strnicmp(cW, S_CMD_EXIT, S_CMD_EXIT_SZ) == 0) ||
                    (strnicmp(cW, S_CMD_CLS, S_CMD_CLS_SZ) == 0) ||
                    (strnicmp(cW, S_CMD_WAIT, S_CMD_WAIT_SZ) == 0) ||
                    (strnicmp(cW, S_CMD_POKE, S_CMD_POKE_SZ) == 0)) {    
                    // Copy execution portion of line as a new line to process - 3rd space of line
                    // Then copy the new line back to the global command line for reprocessing.
                    memset(gcLe, 0, S_LINE_SZ);
                    strcpy(gcLe, CAt(gcLn, S_STRBRK_SPACE, 3));
                    memset(gcLn, 0, S_LINE_SZ);
                    strcpy(gcLn, gcLe);

                    // Set In Logic flag to TRUE
                    bIL = TRUE;
                } else {
                    Later(S_ERR_BADIFCMD, gcLn);
                }
            }
        }

With the changes and additions to the IF command I updated the Scriptee Clock app to use them. Now while the clock is running, pressing any key will exit. This uses the new IF PICK and IF KEY commands. Here is the updated code and an image taken after exiting with a keypress:

# Script: CLOCK.SCT
# Author: Wade Ripkowski
# Date..: 2026.09
CLS
INV ON
TEXT "Scriptee Clock Demonstration "
INV OFF
NL
TEXTE "Which clock source?"
NL
TEXTE "A = APE Time"
TEXTE "R = RT8 Time"
TEXTE "Q = Quit"
PICK ARQ
IF PICK Q EXIT
CLS
BACK 14
BOX A 8,8 24,7 XE
TITLE A C,T XE " Scriptee Clock "
IF PICK A GOTO APE
IF PICK R GOTO RT8
:APE
POKE 710,66
TITLE A C,B XE "APE"
RTC APE
GETDT
SETTZ "EST5EDT"
GETDT
GOTO CLOCK
:RT8
POKE 710,160
TITLE A C,B XE "RT8"
RTC RT8
GETDT
:CLOCK
TEXTBXY A 4,2 "Date:"
TEXTBXY A 4,4 "Time:"
:LOOP
GETDT
GOBXY A 10,2
DATET
GOBXY A 10,4
TIMET
WAIT 1
IF KEY TRUE EXIT
GOTO LOOP

So far, so good! While playing with various script files, some were starting to grow a little large (for Scriptee, but still small byte wise) and it made me think about them possibly growing beyond what Scriptee will be capable of (which is looking about 8K right now). So I thought what about chaining scripts. That will be the subject of the next post.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

CC65 and Atari DOS Type – Revisited

Posted by Ripdubski on 2026.09.24
Posted in: Atari, Programming. Tagged: 8 bit, C (CC65), DOS. 1 Comment

Previously I needed to identify the DOS that was running, and essentially couldn’t determine the DOS for most of the Atari DOS & compatibles. Working again on the Scriptee RUN command, I found I needed to know this last bit. Specifically, I needed to determine which of these DOS’s was running: Atari DOS 2.0, Atari DOS 2.5, SmartDOS, SuperDOS, TopDOS, and TurboDOS XE.

I couldn’t find enough information about identifying each so I loaded each up and dumped out the first approximately 50 bytes of DOS starting at address $0700, inclusive of SpartaDOS X as a baseline. I then took that data in columnar form for analysis. Rather than work through finding a unique pattern in all the data myself, I asked AI to assist. While I don’t condone AI assisted coding (don’t bother messaging me about this), its’ use for data extraction, summarization, and analysis is a task well suited for it. Here is the data set:

// SDX : 53 44 09 4C 00 08 4C 81 0D 4C 7A 0D 4C 67 08 FF FF FF FF FF FF 4C F8 0B 00 00 00 00 00 00 44 0E FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 6F 4E 54 6A
// ATD2.0 : 00 03 00 07 40 15 4C 14 07 07 03 00 7C 1A 01 04 00 7D CB 07 AC 0E 07 F0 36 AD 12 07 85 43 8D 04 03 AD 13 07 85 44 8D 05 03 AD 10 07 AC 0F 07 18 AE 0E 07 20 6C 07 30 17
// ATD2.5 : 00 03 00 07 40 15 4C 14 07 03 03 00 CC 19 01 04 00 7D CB 07 AC 0E 07 F0 35 20 5F 07 AD 10 07 AC 0F 07 A6 24 8E 04 03 A6 25 8E 05 03 18 20 6C 07 30 1C AC 11 07 B1 24
// SmartDOS: 00 03 00 07 D4 15 4C 14 07 04 03 00 18 18 01 04 00 7D CB 07 AC 0E 07 F0 36 AD 12 07 85 43 8D 04 03 AD 13 07 85 44 8D 05 03 AD 10 07 AC 0F 07 18 AE 0E 07
// SuperDOS: 79 02 00 07 40 15 4C 14 07 04 03 00 7C 1A 01 04 00 7D CB 07 AC 0E 07 F0 36 AD 12 07 85 43 8D 04 03 AD 13 07 85 44 8D 05 03 AD 10 07 AC 0F 07 18 AE 0E 07 20 6C 07 30 17 AC 11 07
// TopDOS : 80 03 00 07 40 15 4C 24 07 7D FD 55 02 01 04 00 FD 80 08 00 03 40 AC 0E 15 B9 00 14 29 20 F0 48 4C 07 09 BB AE 11 07 AD 12 07 20 BD 07 AC 0E 07 AD 0F 07 18
// TurboDOS: 01 03 00 07 40 15 4C 16 07 03 07 E0 E0 00 00 31 18 00 7B 1A 6A 18 A9 01 F0 35 20 9F 07 20 CE 07 A9 00 A0 04 A6 24 8E 04 03 A6 25 8E 05 03 18

I asked AI to locate a column that had all unique values. There wasn’t one. I then asked it to locate a combination of two columns that would produce a set of values that would uniquely identify a row. It took only a couple of minutes for the answer – a task that would have taken me far far longer. Columns 1 and 10 were identified as having a unique byte pair per row. Here is what AI provided:

The CC65 source defines the essential supported values:

#define SPARTADOS 0
#define REALDOS 1
#define BWDOS 2
#define OSADOS 3
#define XDOS 4
#define ATARIDOS 5
#define MYDOS 6
#define NODOS 255

I’ve added these values to my code, which start the sub typing at value 10:

#define DOS_ATARI2 10
#define DOS_ATARI25 11
#define DOS_SMART 12
#define DOS_SUPER 13
#define DOS_TOP 14
#define DOS_TURBO 15
#define DOS_UNKNOWN 255

I then wrote a function to determine the sub type of the DOS that is running. The DOSID() function looks at locations $0700 and $0709 (the first and tenth column in the data set) where the unique pair of values resides. Based on the values, it sets a return value and passes that back to the calling routine. I also updated the main() function to call DOSID() and display the sub type results. Here is the updated program:

// ------------------------------------------------------------
// Program: dostype.c
// Desc...: dostype demonstration
// Author.: Wade Ripkowski
// Date...: 20260924
// Notes..: cl65 -v -O -t atari dostype.c -o dostype.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <peekpoke.h>
#include <atari.h>
#define DOS_ATARI2 10
#define DOS_ATARI25 11
#define DOS_SMART 12
#define DOS_SUPER 13
#define DOS_TOP 14
#define DOS_TURBO 15
#define DOS_UNKNOWN 255
// ------------------------------------------------------------
// Func...: unsigned char DOSID(void)
// Desc...: Determines DOS version for AtariDOS & compatibles
// Returns: unsigned char with unique ID
// 10 = Atari DOS 2.0
// 11 = Atari DOS 2.5
// 12 = SmartDOS
// 13 = SuperDOS
// 14 = TopDOS
// 15 = TurboDOS
// ------------------------------------------------------------
unsigned char DOSID()
{
unsigned char bA, bB, bR = DOS_UNKNOWN;
// Get bytes from $0700 and $0709 which have a unique pair signature
bA = PEEK(0x0700);
bB = PEEK(0x0709);
// Check bytes to determine DOS
switch (bA) {
case 0 : if (bB == 7) { bR = DOS_ATARI2; }
else if (bB == 3) { bR = DOS_ATARI25; }
else if (bB == 4) { bR = DOS_SMART; }
else if (bB == 125) { bR = DOS_TOP; }
break;
case 1 : bR = DOS_TURBO;
break;
case 121 : bR = DOS_SUPER;
break;
}
return (bR);
}
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
unsigned char bD = 0;
// Setup screen
clrscr();
cprintf("-[CC65 DOS Type]------------------------\r\n");
// Show type
cprintf("DOS Type (_dos_type): %d\r\n\r\n", _dos_type);
// Show legend
cprintf("0 = SpartaDOS 4 = XDOS\r\n");
cprintf("1 = Real DOS 5 = Atari DOS &\r\n");
cprintf("2 = BW-DOS Compatibles\r\n");
cprintf("3 = OSS DOS 6 = MyDOS\r\n");
cprintf("255 = No DOS\r\n\r\n");
// For DOS's that report back as 5, dig deeper
if (_dos_type == 5) {
// Call custom routine to ID dos
bD = DOSID();
// Show sub type legend
cprintf("Sub Type: %d\r\n\r\n", bD);
cprintf("10 = Atari DOS 2.0\r\n");
cprintf("11 = Atari DOS 2.5\r\n");
cprintf("12 = SmartDOS\r\n");
cprintf("13 = SuperDOS\r\n");
cprintf("14 = TopDOS\r\n");
cprintf("15 = TurboDOS\r\n\r\n");
}
// Wait for keystroke
while(PEEK(764) == 255) {};
}

Then I ran the version of my DOS Type program on each. I did find one error in the data and not sure how it got there. When I dumped the memory values for TopDOS, the first byte was $80 (128). But when my program was running it was finding a value of 0 in the primary location ($0700). So I altered the detection code to add a check for its secondary location ($0709) which holds value $7D (125). Fixed.

Here are screenshots of the outputs from each DOS’s sub type (with captions below each):

Atari DOS 2.0
Atari DOS 2.5
SmartDOS
SuperDOS
TopDOS
TurboDOS XE

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 20

Posted by Ripdubski on 2026.09.21
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

While I was creating the last development blog post and ended up creating the first Scriptee application program – CLOCK, I realized I needed a way to position the cursor inside a box relative to the upper left corner of the box, much like the TEXTBXY command. This entry covers the new command GOBXY.

As with the other commands I first added the definition for it:

// Goto XY in box
#define S_CMD_GOBXY "gobxy"
#define S_CMD_GOBXY_SZ 5

This command takes three parameters, the box ID, and the X and Y coordinate within the box. The command routine first checks that the required number of parameters is passed. It then extracts the X and Y coordinate, then extracts the box ID. Then it checks if the box has been used or not. If it hasn’t been used, Scriptee will exit with the S_ERR_NULLBOX (“Unknown box encountered.”) error message. If all is well, the cursor is moved to the specified X and Y coordinate relative to the upper left corner of the given box. Here is the code which was placed immediately prior to the GOXY command in the main() functions command processing loop:

        // ---------- GOBXY Command
        else if (strnicmp(gcLn, S_CMD_GOBXY, S_CMD_GOBXY_SZ) == 0) {
            // GOBXY id x,y
            ChkPC(3, bC);

            // Extract elements and convert to bytes
            GetXY(&bX, &bY, 2);

            // Get box ID and check if its in use.  If not, bail out.
            bV = GetID();
            if (gaB[bV].bU == FALSE) {
                Later(S_ERR_NULLBOX, gcLn);
            }

            // Goto location within box
            gotoxy(gaB[bV].bX + bX, gaB[bV].bY + bY);
        }

I also renamed the DATETXT and TIMETXT commands. I didn’t like how long they were. So they are now just DATET and TIMET.

Afterward I updated the CLOCK program quite a bit. It will first ask which clock source to use. It will then initialize and collect data and time information from the chosen source. I also updated it use the new GOBXY command and revised DATET and TIMET commands, as well as adding a lower title element to indicate which RTC source is being used. The screen colors are also changed based on the RTC source. Here is the updated CLOCK.SCT source:

CLS
INV ON
TEXT "Scriptee Clock Demonstration "
INV OFF
NL
TEXTE "Which clock source?"
NL
TEXTE "A = APE Time"
TEXTE "R = RT8 Time"
TEXTE "Q = Quit"
PICK ARQ
IFP Q EXIT
CLS
BACK 14
BOX A 8,8 24,7 XE
TITLE A C,T XE " Scriptee Clock "
IFP A GOTO APE
IFP R GOTO RT8
:APE
POKE 710,66
TITLE A C,B XE "APE"
RTC APE
GETDT
SETTZ "EST5EDT"
GOTO CLOCK
:RT8
POKE 710,160
TITLE A C,B XE "RT8"
RTC RT8
GETDT
:CLOCK
TEXTBXY A 4,2 "Date:"
TEXTBXY A 4,4 "Time:"
:LOOP
GETDT
GOBXY A 10,2
DATET
GOBXY A 10,4
TIMET
WAIT 1
GOTO LOOP

And an image of it running for both APE Time and R-Time 8 sources:

One interesting side effect of using APE Time as the clock source, is every second when it polls the APE device for the time, there is an I/O blip which makes it sound like a ticking clock. Neat! Turning Noisy I/O off with a POKE would remove it.

The new version of the CLOCK program demonstrates the use of many Scriptee commands.

While the clock is running, there is no way to get out without a cold start. This got me thinking about adding an IF KEY and/or IF NOKEY command to check for keypress or lack there of. This I believe will be the topic of the next development post.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

CC65 and Atari DOS Type

Posted by Ripdubski on 2026.09.20
Posted in: Atari, Programming. Tagged: 8 bit, C (CC65), DOS. Leave a comment

I’ve been working on Scriptee a lot lately. I am working on the part which loads and runs another program and I need to know which DOS Scriptee is running on because the XIO calls differ between them. I was about to start writing my own routine to check and set a DOS version variable and had noticed in the CC65 documentation there is a function for the Atari 8 bit that checks if the DOS is a command line DOS. That function is:

unsigned char _is_cmdline_dos(void); /* does DOS support command lines */

So thats a start, but not what I need. I need to know which DOS. I had started put together a table of identifying some DOS’s which I was going to use to write a routine to return a distinct value based on checks in the table. The table is missing some DOS’s. Here it is, which includes data I found all over the web:

Mem-Loc Value DOS
3889 ($0F31) 0 SpartaDOS 2.3e
13 Atari/Antic DOS 4.0
15 SpartaDOS 1.1
19 Atari DOS 2.5
76/78 Atari DOS 3.0
89 SpartaDOS 3.2d
108 MYDOS 4.0
207 OSS OS/A+ 4.00
221 MYDOS 4.50
238 Atari DOS 2.0S, OSS DOS XL 2.3 (check 1804)
244 Atari DOS XE
1804 ($070C) 0 OSS DOS XL 2.3
124 Atari DOS 2.0s

As I started down a logic tree in pseudo-code, I started thinking about how CC65 determines if the DOS is command line or not, so I poked around the CC65 source code. As it turns out a globally available variable is set and supported. This is mentioned in the documentation for the Apple II platform, but not for the Atari 8 bit. Well, there is one reference in section 5.1 (https://cc65.github.io/doc/atari.html) where it is listed in the Atari specific functions and global variables with no information about its usage. The linked function reference (https://cc65.github.io/doc/funcref.html) doesn’t reference it for the Atari 8 bit either. So which is it – function or variable? So I started scouring the CC65 source.

The source file atari.h includes definitions for “unsigned char _dos_type” which holds a value denoting which DOS is in use:

extern unsigned char _dos_type; /* the DOS flavour */

The source file also defines the essential supported values:

#define SPARTADOS 0
#define REALDOS 1
#define BWDOS 2
#define OSADOS 3
#define XDOS 4
#define ATARIDOS 5
#define MYDOS 6
#define NODOS 255

The values are conveniently arranged such that all command line DOSs are first, so you can check if the value is less than 5 (Atari DOS) for CLI support, or use the _is_cmdline_dos() function supplied by CC65.

With that information, I started looking around more and discovered in libsrc/atari a file called dosdetect.s, which, suprise!, does the DOS detection and sets the global variable. At the beginning it exports the symbol to make it available in the CC65 library:

    .export         __dos_type

It then goes through the detection process, and finally sets the value (while defaulting to ATARIDOS, value 5):

    sta     __dos_type

Wow! So DOS detection appears inherent, and I just needed to test. I wrote a small program to do just that. It simply displays the value of __dos_type, the legend of possible values, and waits for a key. Here it is:

// ------------------------------------------------------------
// Program: dostype.c
// Desc...: dostype demonstration
// Author.: Wade Ripkowski
// Date...: 20260919
// Notes..: cl65 -v -O -t atari dostype.c -o dostype.xex
// ------------------------------------------------------------
// Pull in include files
#include <stdio.h>
#include <conio.h>
#include <peekpoke.h>
#include <atari.h>
// ------------------------------------------------------------
// Func...: void main(void)
// Desc...: Main routine
// ------------------------------------------------------------
void main(void)
{
// Setup screen
clrscr();
printf("-[CC65 DOS Type]------------------------\n");
// Show type
printf("DOS Type (_dos_type): %d\n\n", _dos_type);
// Show legend
printf("0 = SpartaDOS 5 = Atari DOS\n");
printf("1 = Real DOS 5 = SmartDOS\n");
printf("2 = BW-DOS 5 = SuperDOS\n");
printf("3 = OSS DOS 6 = MyDOS\n");
printf("4 = XDOS\n");
printf("255 = No DOS\n\n");
// Wait for keystroke
while(PEEK(764) == 255) {};
}

Through testing, I found that SuperDOS, SmartDOS, TopDOS, and TurboDOS XE all report as Atari DOS (5), but none are defined in the file. This is likely because they are Atari DOS compatibles. Additionally, I found that direct loading the XEX without DOS doesn’t yield value 255, instead it’s 5 (the default for Atari DOS). Also, using the No-DOS binary menu loader produces a value of 5. So a complete list of definitions in atari.h might be as follows but you would have a hard time determining which Atari compatible is in use (if any in the case of direct binary load which is possible via emulation or cartridge loader). It doesn’t make sense to add this to CC65 unless the Atari compatibles can be delineated further. For references, here are the type values in definition form:

#define SPARTADOS 0
#define REALDOS 1
#define BWDOS 2
#define OSADOS 3
#define XDOS 4
#define ATARIDOS 5
#define SUPERDOS 5
#define SMARTDOS 5
#define TOPDOS 5
#define TURBODOSXE 5
#define NODOSMENU 5
#define XEXLOAD 5
#define MYDOS 6
#define NODOS 255

I tried to get the output for K-DOS, but could not. I had to recompile my program with a specialized memory map to move the load location from $2000 to above $2DFF which I chose $3000. Then it complained again about not being able to overlay DOS up to $3CFF, so I chose $8000. Then it loaded and ran, but did nothing. So, IDK what its value is.

Here are screenshots of the outputs from each DOS in the _dos_type value order (with captions below each):

SpartaDOS
RealDOS
BW-DOS
OSS DOS XL
XDOS
Atari DOS 2
SmartDOS
SuperDOS
Top DOS
Turbo DOS XE
Direct XEX Load
No-DOS Binary Menu Loader
MyDOS

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Scriptee, Part 19

Posted by Ripdubski on 2026.09.18
Posted in: Atari, MSDOS, Programming. Tagged: 8 bit, C (CC65), Scripting. Leave a comment

The bulk of Scriptee has been completed. I’ve started work of loading and executing a program, but having limited success at the moment. I need to research and think over the solution. Yes a human is creating this entire project because I refuse to use AI for any of my programming endeavors. Why take the challenge and fun out?

In the mean time I decided to add real time clock (RTC) support. I started with APE Time. I already added APE Time support to my A8 C-Library so porting it into Scriptee was pretty straight forward. I merely needed to add some commands to access it. Because Scriptee is a “language” interpreter I needed a way to determine if the date/time had been retrieved before allowing use. And because APE Time supports GMT and Local time I wanted to support that option, which meant those same checks need to extend to it.

I completed adding APE Time RTC support and then decided to add ICD R-Time 8 RTC support (RT8). One drawback to using RT8 is that it doesn’t support GMT – but that is likely fine for those using it. Adding RT8 support proved to be quite problematic. Documentation is sparse, and that which did exist, was either not clear, incorrect, or in assembler. I’ll refer you to my blog post on getting this to work:
https://unfinishedbitness.info/2026/09/05/reading-icd-r-time-8/
So, now there is good documentation on how to work with it, with examples in BASIC and C!

After getting it to work in C, I set about adding it to Scriptee. The solution I used for the standalone test wouldn’t work and it took me a couple of nights to work through one that did. I still don’t understand why it wouldn’t work, but the workaround was effective and I’m pseudo-happy with it.

To add support for RT8 on top of APE Time, I had to re-think and re-architect how the date/time commands were used. First I needed a way for the user to tell Scriptee which RTC to use. I settled on the RTC command which takes one parameter (either APE or RT8), which is the required first date/time command to be used before any other date/time commands. I added a definition for this command:

// Set rtc type
#define S_CMD_RTC "rtc"
#define S_CMD_RTC_SZ 3

I also need an error message in case the user supplied a bad value to RTC. While discussing errors, I also need an error in case the APE or RT8 get time calls fail due to not finding the device, as well as if the device hasn’t been initialized (this is more Scriptee not being initialized to use it), and if one of the commands is attempted but the RTC type hasn’t been defined. I added the following error definitions and message strings to support these:

#define S_ERR_BADRTC 18
#define S_ERR_RTCNONE 19
#define S_ERR_RTCINIT 20
#define S_ERR_RTCUNSET 21
const unsigned char *gcEA[23] = {
".",
...
"Invalid RTC type.",
"RTC device not found.",
"RTC not initialized.",
"RTC type not defined.",
};

To support the RTC commands I needed some additional definitions to prevent magic values in the code. These I believe are pretty explanatory:

// ----- RTC IDs -----
#define S_RTC_ID_NONE 0
#define S_RTC_ID_APE 1
#define S_RTC_ID_RT8 2
#define S_RTC_TYPE_APE "ape"
#define S_RTC_TYPE_RT8 "rt8"

Since this command should only be used one, I put it just before EXIT (the last command) in the main() functions command processing loop. First the routine checks to ensure at least one parameter is passed. Then the parameter is extracted from the command line. Then the parameter is checked against the possible types of APE and RT8. An internal flag is set based on the type, or the error S_ERR_BADRTC (“Invalid RTC type.”) is thrown and Scriptee exits. Here it is:

        // ---------- RTC Commmand
        else if (strnicmp(gcLn, S_CMD_RTC, S_CMD_RTC_SZ) == 0) {
            // RTC APE|RT8
            ChkPC(1, bC);

            // Grab type from 1st token/param ()
            bWZ = GSt(gcLn + S_CMD_RTC_SZ + 1, S_STRBRK_RETURN, cW);

            // Set clock type based on command line parameter
            if (stricmp(cW, S_RTC_TYPE_APE) == 0) {          // APE
                gbRT = S_RTC_ID_APE;
            } else if (stricmp(cW, S_RTC_TYPE_RT8) == 0) {   // RT8
                gbRT = S_RTC_ID_RT8;
            } else {                                    // Oopsie
                Later(S_ERR_BADRTC, gcLn);
            }
        }

After a user sets the RTC type with the RTC command, Scriptee needs to see if the device is actually present. Rather than waste cycles and possible device timeout during the type set, I decided to set device availability based on the result from the command to get the date and time, which is GETDT. It has an optional variant called GETDTG for APE Time users which retrieves GMT instead of local time. To support the GETDTG variant I added a command definition and a line ending definition which is used in checked the commands ending character, as I’ve done with some other command variants. That definitions are:

// Get date time
#define S_CMD_GETDT "getdt"
#define S_CMD_GETDT_SZ 5
// ----- Command Endings -----
#define S_END_G 'G'

If the user tries to use the GETDT command before the RTC command, the error S_ERR_RTCUNSET (“RTC type not defined.”) is thrown and Scriptee exits.

The GETDT command retrieves the date and time from the defined device. Scriptee will keep the retrieved values in a global variable accessible at any point afterward. If called multiple times, the values will be updated. If the GETDT command gets a device timeout from APE Time it will get 0 values. If RT8 fails it will have 0 values, or it could have a month greater than 12. These conditions flag the RTC as being unavailable and cause Scriptee to exit with the S_ERR_RTCNONE (“RTC device not found.”) error message. If all is well it sets the RTC available flag to TRUE. Here is the code which was added to the main() functions command processing loop just before the RTC command:

        // ---------- GETDT / GETDTG Commmand
        else if (strnicmp(gcLn, S_CMD_GETDT, S_CMD_GETDT_SZ) == 0) {
            // Read time from device based on device type specified by RTC command
            if (gbRT == S_RTC_ID_APE) {         // APE
                // Get date check result (ends in G, use GMT)
                ApeGet(CE(S_END_G) == TRUE ? APE_GET_TIME_GMT : APE_GET_TIME_LCL);
            } else if (gbRT == S_RTC_ID_RT8) {  // RT8
                // Passing a temp array in.
                // Declaring locally inside function prevents read for some reason.
                RT8Get(bDT);
            } else {
                // If RTC command not issued yet, get out
                Later(S_ERR_RTCUNSET, gcLn);
            }

            // If year, month, and day are empty, APETime not present.
            // Or if month greater than 12, RT8 not present and date is invalid.
            // Exit.
            if (((gbDT[RTC_YEAR] == 0) && (gbDT[RTC_MONTH] == 0) && (gbDT[RTC_DAY] == 0)) || (gbDT[RTC_MONTH] > 12)) {
                Later(S_ERR_RTCNONE, gcLn);
            } else {
                // No 0 values, set available flag to TRUE.
                gbRA = TRUE;
            }
        }

So, the first two commands to be executed when using RTC support are (in order): RTC [APE|RT8] followed by GETDT[G]. This effectively sets the type of RTC being used, determines if it’s available while also retrieving the the date and time if it is available.

To retrieve the local time, nothing needs to be done for RT8. But for APE Time, especially if using older firmware versions (< 1.5) of FujiNet, this next command is required to get the correct local time. The command SETTZ has been added to support setting the time zone. SETTZ requires one parameter which is the time zone as specified in the APE Time spec. I’m not going to detail all the values here, but for the U.S. eastern time zone that is “EST5EDT”. To support the SETTZ command I added the following command definition:

// Set time zone
#define S_CMD_SETTZ "settz"
#define S_CMD_SETTZ_SZ 5

The SETTZ routine checks that one parameter is passed. If the device type was not set to APE or APE has been deemed unavailable via GETDT, the error S_ERR_RTCINIT (“RTC not initialized.”) is thrown and Scriptee will exit. Otherwise, the timezone parameter is extracted from the command line, and then a call to APE Time’s set time zone function is made using the parameter. Here is the code that was added to the main() functions command processing loop just before the RTC command:

        // ---------- SETTZ Commmand
        else if (strnicmp(gcLn, S_CMD_SETTZ, S_CMD_SETTZ_SZ) == 0) {
            // SETTZ "EST5EDT"
            ChkPC(1, bC);

            // If type is not APE or RTC not available, exit
            if ((gbRT != S_RTC_ID_APE) || (gbRA == FALSE)) {
                Later(S_ERR_RTCINIT, gcLn);
            }

            // Get token containing the timezone
            bWZ = GTk(gcLn, 1, cW);

            // Set TZ through APE call
            ApeSetTZ(cW);
        }

Next Scriptee needed a way to display the date and time values. I thought about how to do this efficiently while giving the user control over where it’s displayed. I landed on having a command for date and a command for time, that simply display the corresponding value at the current cursor position. I didn’t want to add a bunch of infrequently used code to allow custom formatting, so for now, the time is displayed as HH:MM:SS, and the date is displayed as “YYYY.MM.DD”. I added the following command definitions:

// Date
#define S_CMD_DATE "datetxt"
#define S_CMD_DATE_SZ 7
// Time
#define S_CMD_TIME "timetxt"
#define S_CMD_TIME_SZ 7

I’m not happy with the command names. I don’t want them to end in TXT. I had to add something after the words DATE and TIME because they both end in E, and well, ending in E causes Scriptee to output a newline after the value – always. I will likely shorten these to DATET and TIMET in the future.

The DATETXT and TIMETXT command implementations are rather simple. They are both added to the main() functions command processing loop just before the other RTC commands. Both commands immediately check if the RTC has been deemed available. If the RTC is not available, the error S_ERR_RTCINIT (“RTC not initialized.”) is thrown and Scriptee exits. Then the value is displayed. And last if the command ends in E, it outputs a newline. These might get tweaked in the future as I test various outputs more. Here are the routines:

        // ---------- TIME Commmand
        else if (strnicmp(gcLn, S_CMD_TIME, S_CMD_TIME_SZ) == 0) {
            // Check if RTC available
            if (! gbRA) {
                Later(S_ERR_RTCINIT, gcLn);
            }

            Disp(S_COORD_NO, S_COORD_NO, "%02d:%02d:%02d", gbDT[RTC_HOUR], gbDT[RTC_MINUTE], gbDT[RTC_SECOND]);
            if (CE(S_END_E) == TRUE) { cprintf("\r\n"); }
        }

        // ---------- DATE Commmand
        else if (strnicmp(gcLn, S_CMD_DATE, S_CMD_DATE_SZ) == 0) {
            // Check if RTC available
            if (! gbRA) {
                Later(S_ERR_RTCINIT, gcLn);
            }

            Disp(S_COORD_NO, S_COORD_NO, "20%02d.%02d.%02d", gbDT[RTC_YEAR], gbDT[RTC_MONTH], gbDT[RTC_DAY]);
            if (CE(S_END_E) == TRUE) { cprintf("\r\n"); }
        }

The following is a Scriptee script that demonstrates the APE Time device usage:

CLS
TEXTE "APE Time Test"
NL
RTC APE
GETDTG
TEXT "GMT ="
DATETXTE
TEXT " "
TIMETXTE
GETDT
TEXT "Local B4 ="
DATETXTE
TEXT " "
TIMETXTE
SETTZ "EST5EDT"
GETDT
TEXT "Local Aft="
DATETXTE
TEXT " "
TIMETXTE
EXIT

The following is a Scriptee script that demonstrates the RT8 device usage:

CLS
TEXTE "RT8 Time Test"
NL
RTC RT8
GETDT
TEXT "Local ="
DATETXTE
TEXT " "
TIMETXTE
EXIT

Errors for when APE and RT8 are not found:

Errors showing the RTC not being defined or initialized:

Theoretically you can now write a clock application in Scriptee, and in fact I did just that using the RT8 real time clock. Here is my clock script and output:

CLS
BACK 14
POKE 710,160
RTC RT8
GETDT
BOX A 8,8 24,7 XE
TITLE A C,T XE " Scriptee Clock "
TEXTBXY A 4,2 "Date:"
TEXTBXY A 4,4 "Time:"
:LOOP
GETDT
GOXY 18,10
DATETXT
GOXY 18,12
TIMETXT
WAIT 1
GOTO LOOP
EXIT

Here is a quick video showing it running:

During development of the clock script I realized I need another command, a variant of GOXY that allows for moving to a coordinate in a box, similar to TEXTBXY. I’ll add that next.

Share this:

  • Email a link to a friend (Opens in new window) Email
  • Share on Facebook (Opens in new window) Facebook
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Reddit (Opens in new window) Reddit
  • Share on X (Opens in new window) X
Like Loading...

Posts navigation

← Older Entries
  • Follow Unfinished Bitness on WordPress.com
  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 45 other subscribers
  • Recent Posts

    • Scriptee, Part 26
    • Scriptee, Part 25
    • Scriptee, Part 24
    • Scriptee, Part 23
    • Scriptee, Part 22
  • Categories

    • Apple
    • Atari
    • Atari ST
    • Audio
    • BBS
    • Bookmarks
    • DD-WRT
    • General
    • Google
    • iOS
    • MSDOS
    • NAS
    • Notes
    • OSX
    • Programming
    • Router
    • Security
  • Tags

    1Password 8 bit 12v 110v 6502 Action! Alpine Assembly Atari Atari 400 Atari 800 Atari XE Atari XL BASIC Bookmarks boombox BootScript C (CC65) Car Audio CDA7893 CIO cubeSQL CX430 database DD-WRT Delicious DHCP DiskStation DNSMasq EverNote Fitbit FujINet ghetto-blaster Google GTO605C iCloud IOCB iOS iTunes JBL Library Mac OSX Mac OS X Migrate Opcode OSX Scripting Synology Task Manager Windows
  • Archives

    • October 2026
    • September 2026
    • August 2026
    • June 2026
    • May 2026
    • April 2026
    • March 2026
    • February 2026
    • July 2025
    • April 2025
    • January 2025
    • February 2024
    • February 2023
    • September 2022
    • August 2022
    • July 2022
    • June 2022
    • December 2021
    • February 2021
    • December 2020
    • September 2020
    • October 2019
    • May 2018
    • April 2018
    • June 2017
    • April 2017
    • February 2017
    • December 2016
    • September 2016
    • August 2016
    • July 2016
    • June 2016
    • May 2016
    • April 2016
    • September 2015
    • August 2015
    • July 2015
    • May 2015
    • April 2015
    • March 2015
    • February 2015
    • January 2015
    • December 2014
    • November 2014
    • October 2014
    • September 2014
    • August 2014
    • July 2014
    • June 2014
    • May 2014
    • April 2014
    • March 2014
    • February 2014
    • December 2013
    • October 2013
    • June 2013
    • May 2013
    • March 2013
    • February 2013
    • January 2013
  • Meta

    • Create account
    • Log in
    • Entries feed
    • Comments feed
    • WordPress.com
Blog at WordPress.com.
Unfinished Bitness
Blog at WordPress.com.
  • Subscribe Subscribed
    Unfinished Bitness
    Join 45 other subscribers

    Have a WordPress.com account? Log in now.

  • Unfinished Bitness
    View site in Reader
    Manage subscriptionsSign upLog in
    Report this content
    Collapse this bar
Loading Comments...
%d