Bas2Txt

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Utility

Bas2Txt is a machine code utility that converts a BASIC program stored in memory into a plain ASCII text file. The BASIC loader (lines 10–170) encodes a Z80 machine code routine as a long hexadecimal string, which is decoded byte-by-byte via POKE into RAM starting at address 64900. Two entry points exist: USR 64909 relocates the BASIC area, and USR 65042 performs the actual translation, writing the program out as a SAVE-compatible text file. The hex-decode loop at line 9030 converts each pair of ASCII hex digits into a byte using a compact expression that handles both digit (0–9) and uppercase letter (A–F) characters without branching on IF. Comments in the REM statements (lines 9050–9054) document the multi-step workflow: load the code, run it, then load the resulting ASCII file back.


Program Structure

The program is organized into three logical sections:

  1. Lines 10–170: Build a long hex-encoded machine code string in A$ through concatenation across multiple lines.
  2. Lines 1010–9040: Decode the hex string and POKE each byte into RAM starting at address 64900.
  3. Lines 9050–9054: REM statements documenting the two-phase usage workflow.

Hex String Construction

The machine code is split across lines 10–170 as uppercase hex pairs stored in A$. Each line appends additional hex data using LET A$=A$+"...". This approach sidesteps any single-line length limit and allows the full binary payload to be embedded in BASIC source. The total encoded data spans roughly 175+ bytes of Z80 code.

Hex Decode Loop (Line 9030)

The decode loop at lines 1020–9040 iterates over A$ two characters at a time. The core POKE expression is:

(CODE A$(I)-(48 AND CODE A$(I)<58)-(55 AND CODE A$(I)>64))*16 + CODE A$(I+1)-(48 AND CODE A$(I+1)<58)-(55 AND CODE A$(I+1)>64)

This converts a hex digit character to its numeric value branchlessly:

  • For digits '0'–'9' (ASCII 48–57): subtracts 48, yielding 0–9.
  • For letters 'A'–'F' (ASCII 65–70): subtracts 55, yielding 10–15.
  • The high nibble is multiplied by 16 before adding the low nibble.

The AND keyword is used here as a conditional mask (its Sinclair BASIC boolean behavior returning 0 or the left operand), not as a bitwise operator — a characteristic idiom for branchless conditional arithmetic.

Memory Layout and Entry Points

AddressPurpose
64900Start of machine code block (CLEAR target)
64909First entry point: relocates the BASIC area in memory
65042Second entry point: translates BASIC to ASCII text

The CLEAR 64899 at line 1 sets the top of BASIC RAM just below the code block, protecting the poked bytes from being overwritten by BASIC’s variable storage.

Machine Code Functionality

The embedded Z80 routine implements the actual BASIC-to-text conversion. Readable ASCII strings are embedded directly in the hex data; decoding the payload reveals messages such as "BASIC has been moved.", "PRESS ANY KEY TO CLEAR MEMORY.", and the save prompt 'To save BASIC as text file: SAVE "name" CODE 28416,'. The routine uses ROM calls (indicated by addresses such as CD B6 FF, CD C9 FF, etc.) to perform output and interact with the BASIC system.

Workflow and Usage

The REM statements (lines 9050–9054) document a multi-step process:

  1. Run the BASIC loader to poke the machine code into RAM.
  2. Execute PRINT USR 64909 to relocate BASIC.
  3. Load the target BASIC program into the now-relocated area.
  4. Execute PRINT USR 65042 to translate and save the program as a text file.
  5. The saved file can be loaded back and will be in ASCII format.

Notable Techniques

  • The branchless hex-digit decode in line 9030 avoids IF statements, keeping the loop tight and efficient.
  • Splitting the hex string across multiple LET A$=A$+"..." lines works around line-length constraints while keeping the loader self-contained.
  • The CLEAR statement in line 1 is integral to the program’s function, not merely a safety measure — it reserves high RAM for the machine code before any variables are allocated.
  • Line numbering is non-uniform (10–170 for data, 1010 and 9030–9040 for the decode loop), suggesting the program was edited over time with new sections inserted.
  • The note in line 9054 warns that line 1’s CLEAR address may need to be adjusted to 768 depending on the system configuration, indicating the tool was designed with some portability in mind.

Image Gallery

Source Code

    1 CLEAR 64899
   10 LET a$="000000000000000000ED4B535C2A4B5CED422323E5C1ED4389FDED5BB25CED5387FD2A4B5CEDB813ED5385FD11D8FD013A00"
   20 LET A$=A$+"CD3C20CD8E027BFEFF20F87AFEFF20F3CD8E027BFEFF28F811006FED53B25CCDB711160000424153494320686173206265656E206D6F7665642E20"
   30 LET A$=A$+"160200505245535320414E59204B455920544F20434C454152204D454D4F52592E"
   40 LET A$=A$+"2AB25C228BFD2184FD36002A85FD56235E232323E5E5C12A87FDAFED42D2AEFEE11176FE013700"
   50 LET A$=A$+"CD3C202A8BFD11006FED522B111027CD69FE11E803CD69FE116400CD69FE110A00CD69FE110100CD69FE3EFD"
   60 LET A$=A$+"CD0116C9AF3CED52380218F919C62FD7C9160000546F207361766520424153494320617320746578742066696C653A0D0D5341564520226E616D652220434F444520"
  100 LET A$=A$+"32383431362C00EB1E2001E803CDB6FF016400CDB6FF010A00CDB6FF7DFE202802C630CDC9FFE17E23E5"
  110 LET A$=A$+"FE22200AF53A84FDEE013284FDF1FE0D2817FE0E2009E1010500ED4AE518DDFE203002"
  120 LET A$=A$+"18D7FE7B302AF53A84FDCB8F3284FDF1FE0DCC19FFFE3ACC19FFCDC9FFFE0D20B8E1C320FEF53A84FDCB973284FDF1C9"
  130 LET A$=A$+"FE80301DFE7C280FFE7E280B2184FDCB4620C3CB5620BFD61FCD77FFCD6EFF1888"
  140 LET A$=A$+"FE9030043E2018ADFEA53004D64F18A5FEEA200AF53A84FDCBD73284FDF1D6A5CD77FFCD6EFF"
  150 LET A$=A$+"C3CDFE3A84FDCBCF3284FDC9119500F5CD410C380C3A84FDCB4F20053E20CDA5FF1AE67FCDA5FF"
  160 LET A$=A$+"1A138730F5D1FE482803FE82D87AFE03D83E20D5D9CDC9FFD9D1C97BFEFF200E3E00180AAFED423C30FB093D28EDFE202805"
  170 LET A$=A$+"C63011FF00E52A8BFD7723228BFDE1C9"
 1010 LET ADDRESS=64900: LET C=0
 1020 FOR I=1 TO LEN A$-1 STEP 2
 9030 POKE (ADDRESS+C),(CODE A$(I)-(48 AND CODE A$(I)<58)-(55 AND CODE A$(I)>64))*16+CODE A$(I+1)-(48 AND CODE A$(I+1)<58)-(55 AND CODE A$(I+1)>64)
 9035 LET C=C+1
 9040 NEXT I
 9050 REM CLEAR 64900-LOAD"BAS2TXTBYT",64900 
 9051 REM  PRINT USR 64909 MOVES BASIC--THEN PRINT USR 65042 TO  TRANSLATE
 9052 REM SAVE program that was loaded after code was loaded and run as directed
 9053 REM LOAD back in program area it will be in ASCII
 9054 REM Please check line #1 it may be 768 also last few lines for corrections.
 9999 SAVE "BAS2TXT" LINE 1

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.