Silvercrest SL-65
Data retrieval experimentations
The "sl65" source code v0.03

Note 1

This source is subject to the licence conditions that are supplied within the official archive.

Note 2

What is presented below is not an identical copy of the source code. It has been reformatted slightly for display, and the colourisation was added manually (phew!); therefore I would not advise you compile from this in case any errors kept in. Download the source and go from there...

Note 3

This source is intended for Borland's TurboC++ v1.0 compiler, on a DOS platform.

Compiler options:

Memory model = Tiny; Word align variables; Duplicate strings merged; Test for stack overflow; No FP; Code for 80286 class; No debug info; Register and Jump optimisations; NO 'Register' variables; Optimise for Speed; ALL warnings and messages (yes, ALL).
Compiles clean with no errors and no messages.

Additionally, it was written with the express intention of getting something working, rather than being 'model' code. As it doesn't work correctly for me, even in plain DOS - serial handling still appears to drop data, I've not put much effort into making it better/faster/more bulletproof.
I expect I will, in time. But I'd rather show you hacky unfinished code than no code at all.

Enjoy! ☺

 

Known bugs

 

The code

/* Silvercrest SL-65 Firmware Extractor, version 0.03

   by Rick Murray, 2007/04/01


   URI  : http://www.heyrick.co.uk/ricksworld/digibox/sl65f.html
   Email: heyrick -at- merseymail -dot- com


   Assembles with Borland TurboC++ v1.0 (a FREE download from Borland!).


   Requires DIRECT access to IRQ controller and serial port. Thus, it
   probably won't work on NT or XP...
   
   You may modify this program as you see fit, and I encourage you to
   send me your updates. HOWEVER PLEASE NOTE THAT YOU DO NOT HAVE THE
   RIGHT TO (RE)DISTRIBUTE MODIFIED VERSIONS without first obtaining
   my permission. Just ask...
*/



/* Inclusions */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <time.h>
#include "dos.h"
#include "conio.h"



/* C still hasn't fixed on what TRUE and FALSE are. Some say TRUE is a
   non-zero valid, some say TRUE is -1 (spot a BASIC coder!). C doesn't
   have an opinion on this; so we have to define them ourselves! */
#define TRUE  1
#define FALSE 0



/* Global variables */
char appvers[] = "0.03";
char appdate[] = "2007/04/01";
int  port = 0;         /* Is filled with address to serial port   */
char buffer[1024];     /* Ring buffer memory block                */
int  bufferhead = 0;   /* Ring buffer head (incoming writes here) */
int  buffertail = 0;   /* Ring buffer tail (we read from here)    */
char command[512];     /* Command string for our outgoing data    */



/* Function prototypes */
void ctrlc_catch(int *reglist);
void restore_vector(void);
void serial_senddata(char *data);
void serial_writebyte(int mybyte);
int  serial_awaitreplyfrom(int mybyte);
int  buffer_ready(void);
int  buffer_readstring(void);
int  buffer_readbyte(void);
void decode_command(void);
void cursor_off(void);
void cursor_on(void);
void get_true_version(void);
void show_help(void);


void interrupt (*oldserial)(); /* old interrupt function pointer */
void interrupt serial_event(); /* interrupt prototype */



/* Herewith the code. I *HATED* PASCAL's liking of the 'main' function at
   the end, so you'll instead find it here. Right at the start. :-) */
int main(int argc, char *argv[])
{
   long key = 0;
   int  byte = 0;
   long loop = 0;
   long amount = 0;
   int  portcount = 0;
   int  port1addr = 0;
   int  port2addr = 0;
   FILE *fp = NULL;

   /* Print introduction */
   textcolor(WHITE);
   cprintf("\r\n\r\nSilvercrest SL-65 Firmware Extractor");
   textcolor(LIGHTCYAN);
   cprintf(" - %s                 %s\r\n", appvers, appdate);
   textcolor(LIGHTGREEN);
   cputs("by Rick Murray  ");
   textcolor(LIGHTBLUE);
   cprintf("\
http://www.heyrick.co.uk/ricksworld/digibox/sl65f.html\r\n\r\n");

   /* Help? */
   if (argc > 1)
   {
     /* Covers: /? /h /help -? -h -help */
     if ( (argv[1][1] == '?') || (argv[1][1] == 'h') )
     {
        show_help();
        exit(0);
     }
   }

   /* Search for serial ports */
   portcount = peek(0x0040, 0x0010); /* Equipment list pointer */
   portcount = ((portcount >> 9) && 3);

   if (portcount == 0)
   {
     /* Waaah! */
      textcolor(LIGHTMAGENTA);
      cprintf("This computer doesn't appear to have serial ports!\r\n");
      textcolor(LIGHTGRAY);
      cprintf("\r\n");
      exit(0);
   }

   /* Interrogate DOS equip-seg for COM ports */
   port1addr = peek(0x0040, 0x000);
   if (portcount > 1)
      port2addr = peek(0x0040, 0x002);

   if (portcount > 1)
   {
      /* Get the desired port from the user */
      port = 0;
      do
      {
         textcolor(LIGHTCYAN);
         cputs("Please choose: COM[");
         textcolor(WHITE);
         putch('1');
         textcolor(LIGHTCYAN);
         cputs("] or COM[");
         textcolor(WHITE);
         putch('2');
         textcolor(LIGHTCYAN);
         cputs("]? ");

         /* Loop until a key pressed */
         while ( !kbhit() );
         key = getch();

         /* Choices */
         if (key == '1') port = port1addr; /* usu. 0x03F8 */
         if (key == '2') port = port2addr; /* usu. 0x02F8 */

         /* Default */
         if ((key == 13) || (key == 32))
         {
            port = port1addr; /* default to COM1 */
            key = '1';
         }

         /* Escape */
         if (key == 27)
         {
            textcolor(LIGHTMAGENTA);
            cprintf("Aborted, bye bye!\r\n\r\n");
            textcolor(LIGHTGRAY);
            exit(0);
         }

         /* Something invalid */
         if (port == 0)
         {
            textcolor(LIGHTMAGENTA);
            cprintf("Invalid input, '1' or '2' please!\r\n");
         }
      } while (port == 0);
   }
   else
   {
      /* Only the one... */
      textcolor(LIGHTCYAN);
      cputs("There appears to be only one serial port, so using ");
      port = port1addr;
      key = '1';
   }

   /* Visual feedback */
   cprintf("COM%c:\r\n", key);
   textcolor(LIGHTGREEN);
   cprintf("Linking into serial port (addr=0x%04X)", port);


   /* Swap interrupt vectors */
   oldserial  = getvect(0x0C);
   atexit(restore_vector);
   setvect(0x0C, serial_event);


   /* Set serial port status - direct hardware pokage! <g> */
   outp((port + 3), 187); /* 8E1, DLAB active */
   outp((port + 0),   1); /* for 115200bps */
   outp((port + 1),   0); /* high byte of baud divisor, not used */
   outp((port + 3),  59); /* 8E1, DLAB inactive */
   outp((port + 2),   0); /* FIFO trigger after 1 byte */
   outp((port + 4),  11); /* nDTR, nRTS, OUT2 for IRQs */
   outp(0x21, (inp(0x21) & 0xEF)); /* permit serial IRQs */
   outp((port + 1),   1); /* Set IRQ on serial byte received */
   /* *** IMPORTANT: The word format is 8E1, not the expected 8N1 */


   /* Set up a catch on ^C keypresses */
   signal(SIGINT, ctrlc_catch);


   if (argc > 1)
   {
      /* Yes, there are GOTOs. Deal with it... */
      outp((port + 2), 7); /* Clear FIFOs */
      bufferhead = 0; buffertail = 0; /* Reset our buffer */

      /* "-ash"? */
      if (tolower(argv[1][1]) == 'a')
      {
         textcolor(WHITE);
         cprintf("\r\n\r\nUser requested jump to \"ASH\" state.\r\n");
         goto AtASH;
      }

      /* "-conn"? */
      if (tolower(argv[1][1]) == 'c')
      {
         textcolor(WHITE);
         cprintf("\r\n\r\nUser requested jump to \"conn\" state.\r\n");
         goto AtConn;
      }

      /* "-dump"? */
      if (tolower(argv[1][1]) == 'd')
      {
         textcolor(WHITE);
         cprintf("\r\n\r\nUser requested jump to \"dup\" or \
\"| | \" state.\r\n");
         goto AtDump;
      }

      /* "-reset" (would accept "-reboot" or "-restart")? */
      if (tolower(argv[1][1]) == 'r')
      {
         textcolor(WHITE);
         cprintf("\r\n\r\nUser requested jump to reset receiver.\r\n");
         goto ReBoot;
      }

      cprintf("\r\n\r\nSorry, the command line parameter \"%s\" is not \
recognised.\r\n", argv[1]);
      cprintf("Continue as normal? (y/N) ");
      key = getch();
      if ( (key != 'Y') && (key != 'y') )
         exit(0);
   }


   /* Prompt user */
   textcolor(LIGHTCYAN);
   cprintf("\r\n\r\n\
Please ensure your receiver is ON and active, and the serial lead is\r\n\
correctly connected. Then press [SPACE]...");
   getch();
   cprintf("\r\n\r\n");



   /* Ensure serial buffer is empty - as switching receiver on or out of
      standby causes "APP  init OK[0D]" to appear via serial link.  */
   outp((port + 2), 7); /* Clear FIFOs */
   sleep(1);
   bufferhead = 0; buffertail = 0;


   /* ********
      STEP ONE: Try sending COMTEST to initialise serial port
      ********
   */
   textcolor(WHITE);
   cprintf("Verifying serial port...");
   /* Send "comtest" and control bytes observed */
   serial_writebyte('c');
   serial_writebyte('o');
   serial_writebyte('m');
   serial_writebyte('t');
   serial_writebyte('e');
   serial_writebyte('s');
   serial_writebyte('t');
   serial_writebyte(0x20);
   serial_writebyte(0x0D);
   serial_writebyte(0x63);
   serial_writebyte(0x0D);
   /* Read it back, we should see 'comtest' */
   sleep(1);
   if ( buffer_ready() != 0 )
      buffer_readstring();
   command[12] = '\0';
   /* Check to see if we got "APP  init ok" earlier than expected */
   if (strcmp(command, "APP  init ok") != 0)
   {
      /* now look for "comtest" */
      command[7] = '\0';
      if (strcmp(command, "comtest") != 0)
      {
         textcolor(LIGHTMAGENTA);
         cprintf("unable to establish connection.\r\n\r\n");
         textcolor(LIGHTGRAY);
         exit(0);
      }
      cprintf("okay.\r\n");


   /* ********
      STEP TWO: Await "APP  init ok" to say receiver has restarted
      ********
   */
      cprintf("Awaiting receiver reset...");
      /* Now await "APP  init ok" */
      loop = 0;
      do
      {
         sleep(1);
         if ( buffer_ready() != 0 )
         {
            buffer_readstring();
            command[12] = '\0';
         }

         loop++;
         if (loop > 5)
         {
            textcolor(LIGHTMAGENTA);
            cprintf("timed out, no response in 5 seconds.\r\n\r\n");
            textcolor(LIGHTGRAY);
            exit(0);
         }
      } while (strcmp(command, "APP  init ok") != 0);
   } /* end of "if isn't APP init while expecting comtest" loop */
   cprintf("okay.\r\n"); 


   /* **********
      STEP THREE: Send sequence to engage comms
      **********
   */
   cprintf("Engaging receiver's communications mode...");
   serial_writebyte('c');
   serial_writebyte(0x0D);
   serial_writebyte('c');
   serial_writebyte(0x0A);
   serial_writebyte(0x0D);

   /* At this stage, the LEDs will read "ASH" */
   sleep(2);
   if ( buffer_ready() == 0 )
   {
      textcolor(LIGHTMAGENTA);
      cprintf("failed, no response.\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }
   bufferhead = 0; buffertail = 0;/* clear buffer, it'd be a few ">:"s */
   cprintf("okay.\r\n");


   /* *********
      STEP FOUR: Switching to communications mode
      *********
   */
AtASH:
   sleep(1);
   cprintf("Switching to communications mode...");
   serial_writebyte('c');
   serial_writebyte('o');
   serial_writebyte('m');
   serial_writebyte('t');
   serial_writebyte('e');
   serial_writebyte('s');
   serial_writebyte('t');
   serial_writebyte(' ');
   serial_writebyte('1');
   serial_writebyte('0');
   serial_writebyte(0x0D);
   serial_writebyte('a');
   serial_writebyte('l');
   serial_writebyte('i');
   serial_writebyte(' ');
   serial_writebyte(' ');
   serial_writebyte('D');
   serial_writebyte('V');
   serial_writebyte('B');
   serial_writebyte(0x2D); /* '-' */
   serial_writebyte('S');

   /* At this stage, the LEDs will read "conn" */
   sleep(2);
   if ( buffer_ready() == 0 )
   {
      textcolor(LIGHTMAGENTA);
      cprintf("failed, no response.\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }
   bufferhead = 0; buffertail = 0; /* clear buffer, it'll be a ">:" */
   cprintf("okay.\r\n");


   /* *********
      STEP FIVE: Requesting firmware dump
      *********
   */
AtConn:
AtDump: /* is this acceptable? seems to work... */
   sleep(2);
   cprintf("Requesting firmware...");
   serial_writebyte('d');
   serial_writebyte('u');
   serial_writebyte('m';);
   serial_writebyte('p');
   serial_writebyte(0x0D);
   sleep(1);
   if ( buffer_ready() == 0 )
   {
      textcolor(LIGHTMAGENTA);
      cprintf("failed, no response.\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }
   /* look for '0x00' in returned data, it & next three bytes are size */
   do
   {
      buffertail++;
      byte = buffer[buffertail];
   } while ( (buffer[buffertail] != 0x00) && (buffertail <= bufferhead) );
   if (buffer[buffertail++] != 0x00)
   {
      textcolor(LIGHTMAGENTA);
      cprintf("failed, can't locate size marker.\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }
   /* Now read the amount */
   amount = 0;
   amount += (long)((long)buffer[buffertail++] << (long)16);
   amount += (long)((long)buffer[buffertail++] << (long) 8);
   amount += (long)((long)buffer[buffertail]              );
   buffertail = 0; bufferhead = 0; /* clear buffer */
   cprintf("okay, will read %ld bytes.\r\n", amount);
   /* Warn if unexpected/unusual size */
   if (amount != 2097152l)
   {
      textcolor(LIGHTGREEN);
      cprintf("[warning: expected bytecount to be 2,097,152 (2Mb)]\r\n");
   }
   if (amount == 0)
   {
      textcolor(LIGHTMAGENTA);
      cprintf("Bytecount is zero, this isn't right...\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }


   /* ********
      STEP SIX: Open output file
      ********
   */
   fp = fopen("firmware.bin", "wb");
   if (fp == NULL)
   {
      textcolor(LIGHTMAGENTA);
      cprintf("Unable to create/open \"firmware.bin\" file.\r\n\r\n");
      textcolor(LIGHTGRAY);
      exit(0);
   }


   /* **********
      STEP SEVEN: Okay, commence dump
      **********
   */
   bufferhead = 0; buffertail = 0;
   sleep(1);
   outp((port + 2), 129);  /* FIFO trigger after 8 bytes; give us room */
   serial_writebyte(0x4F); /* 'O'k, go for it! */

   textcolor(LIGHTCYAN);
   loop = 1;
   key = 0;
   do
   {
     if (buffer_ready() != 0)
     {
        byte = buffer_readbyte();
        fputc(byte, fp);
        loop++;
        key = 0;
     }
     else
     {
        /* Sanity check */
        key++;
     }

     if ((loop % 4096) == 0)
        cprintf("Status: %lu of %lu bytes read\r", loop, amount);

   } while ( (loop < amount) && (key < 123456l) );
   if (key >= 123456l)
   {
      textcolor(LIGHTMAGENTA);
      cprintf("\r\nTIMED OUT (%lu bytes read)!\r\n\r\n", loop);
      textcolor(LIGHTGRAY);
      exit(0);
   }

   if (loop != amount)
   {
      textcolor(LIGHTMAGENTA);
      cprintf("\r\nExpected to read %lu bytes, read %lu.\r\n\r\n",
              amount, loop);
      textcolor(LIGHTGRAY);
      exit(0);
   }

   textcolor(WHITE);
   cprintf("\r\n\r\nFINISHED.\r\n\r\n");
   textcolor(LIGHTGRAY);


   /* **********
      STEP EIGHT: Close output file and exit
      **********
   */
   fclose(fp);


   /* Bye! */
   return 0;

ReBoot:
   sleep(2);
   cprintf("Requesting reboot...\r\n");
   serial_writebyte('r');
   serial_writebyte('e');
   serial_writebyte('b');
   serial_writebyte('o');
   serial_writebyte('o');
   serial_writebyte('t');
   serial_writebyte(0x0D);

   return 0;
}



void ctrlc_catch(int *reglist)
{
   /* Traps ^C presses; we need a controlled exit
      else the interrupt claim will be all messed up. */
   reglist = reglist;
   textcolor(LIGHTMAGENTA);
   cprintf("\r\n\r\nUSER REQUESTED EXIT.\r\n");
   textcolor(LIGHTGRAY);
   cprintf("\r\n");
   exit(0);
}



void restore_vector(void)
{
   /* Restore serial port sanity, close file, normal screen. */

   /* Set serial port behaviour to normal */
   outp((port + 1), 0);   /* Disable IRQ on serial byte received */
   outp((port + 4), 0);   /* Disable DTR and RTS, no IRQs */

   /* Mask serial IRQs at 8259-alike */
   outp(0x21, (inp(0x21) | 0x10));

   /* Restore original serial handler */
   setvect(0x0C, oldserial);

   /* Restore the screen to normal */
   textbackground(BLACK);
   textcolor(LIGHTGRAY);
   gotoxy(1, 25);
   cprintf("\r\n");
   clreol();
   cprintf("\r\n");
   clreol();

   /* Print a bye-bye message [normal "printf", not via conio] */
   printf("SL-65 Firmware Extractor exited.\n\n");

   return;
}



void interrupt serial_event()
{
   /* Get serial byte and write it to ring buffer.
      This is an interrupt routine ... MUST BE QUICK!
   */

   int lsr = 0;

   /* Turn off RTS during read - we are NOT ready */
   outp((port + 4), 9);  /* nDTR and OUT2 */

   do
   {
      buffer[bufferhead++] = inp(port); /* read direct to buffer */
      if (bufferhead > 1023)
         bufferhead = 0;
      lsr = (inp(port + 5) & 1);
   } while (lsr == 1);

   outp(0x20, 0x20);    /* Flag IRQ handled */

   /* Turn on RTS again */
   outp((port + 4), 11);  /* nDTR, nRTS, and OUT2 - we can go again */

   return;
}



void serial_writebyte(int mybyte)
{
   /* Writes a byte on the serial port... */
   int  loop = 0;

   /* Wait for port to be ready */
   while ( ( (inp(port + 5) && 0x20) == 0 ) && ( loop < 1024 ) )
      loop++;

   /* Sanity check, so we don't lock up on a disconnected port... */
   if (loop > 1023)
   {
      printf("Unable to send byte %d - tried 1024 times.", mybyte);
      exit(0);
   }

   /* Send it */
   outp(port, mybyte);

   return;
}



int  serial_awaitreplyfrom(int mybyte)
{
   unsigned long loop = 0;
   unsigned long tick = 0;
   unsigned long tock = 0;

   /* Send the byte */
   serial_writebyte(mybyte);
    /* printf(" {sent byte %d } ", mybyte); */

   /* Wait for the buffer to have something in it */
   do
   {
      if (buffer_ready() != 0)
      {
         buffer_readstring();
         return (command[0]);
      }

      for (tock = 0; tock < 2; tock++)
      {
         tick = clock(); /* Wait for an 18.2th of a second (2x) */
         while (tick == clock());
      }

      loop++;
   } while (loop < 4);

   return -1;
}



int  buffer_ready(void)
{
   /* Returns whether or not there is anything in the input buffer.
      0 = nothing new; non-0 = data
   */

   return (bufferhead - buffertail);
}



int  buffer_readstring(void)
{
   /* Reads from the buffer into the 'command' string. String is clipped
      when we reach an ASCII 10, or buffer head=tail. Returns bytes read.
   */

   int  posn = 0;
   int  done = FALSE;

   /* Trap no data */
   if (bufferhead == buffertail)
   {
      strcpy(command, "");
      return 0;
   }

   /* Else get a string */
   while ( done == FALSE )
   {
      /* Copy the byte */
      command[posn++] = buffer[buffertail++];
      if (buffertail > 1023)
         buffertail = 0;

      /* If ASCII 10, fiddle for terminator */
      if (command[(posn - 1)] == 10)
      {
         done = TRUE;
         posn--;
      }

      /* End of data? */
      if (bufferhead == buffertail)
         done = TRUE;

      /* Out of buffer space? */
      if (posn > 510)
         done = TRUE;
   }

   /* Null terminate it for C... */
   command[posn] = '\0';

   /* Return bytes read */
   return posn;
}



int  buffer_readbyte(void)
{
   /* Reads from the buffer. Returns next byte available. */

   int  reply = 0;

   /* Trap no data */
   if (bufferhead == buffertail)
      return -1;

   /* Copy the byte */
   reply = buffer[buffertail++];

   /* Loop? */
   if (buffertail > 1023)
      buffertail = 0;

   /* Return byte */
   return reply;
}



void cursor_off(void)
{
   /* Turn off the cursor (generic video int method) */

   union REGS inreg, outreg;
   inreg.h.ah = 1;
   inreg.x.cx = 0x0F00;
   int86(0x10, &inreg, &outreg);
}



void cursor_on(void)
{
   /* Turn on the cursor again */

   union REGS inreg, outreg;
   inreg.h.ah = 1;
   inreg.x.cx = 0x0607;
   int86(0x10, &inreg, &outreg);
}



void get_true_version(void)
{
   /* Return TRUE version of DOS, pushes value into command buffer.
      [because Windows with SETVER can lie blatantly! :-) ] */
   int  maj = 0;
   int  min = 0;
   int  rev = 0;
   union REGS inreg, outreg;

   inreg.x.ax = 0x3306; /* actually clashes with some obscure thing! */
   int86(0x21, &inreg, &outreg);

   maj = outreg.h.bl;
   min = outreg.h.bh;
   rev = outreg.h.dl;

   sprintf(command, "%d.%02dr%d", maj, min, rev);

   return;
}



void show_help(void)
{
   textcolor(LIGHTGREEN);
   cprintf("\
This software is intended to be used to extract the firmware from a\r\n\
Silvercrest SL-65 digital satellite receiver (later model, based upon\r\n\
the ALI chip), namely because \"AliEditor\" doesn't seem to work \
for me.\r\n");
   textcolor(LIGHTCYAN);
   cprintf("\
Details of the process are available on-line, the URL is as given \
above.\r\n\r\n");
   textcolor(LIGHTGREEN);
   cprintf("Typing:\r\n");
   textcolor(WHITE);
   cprintf("    sl65\r\n");
   textcolor(LIGHTGREEN);
   cprintf("\
on its own will initiate the process and create a dump of the firmware\r\n\
as a file called \"firmware.bin\".\r\n\
However this process (or this code) may lose sync, so optional command\r\n\
line parameters are available to recover from a failed connection:\r\n");
   textcolor(WHITE);
   cprintf("    sl65 -ash          ");
   textcolor(LIGHTCYAN);
   cprintf("If the receiver's LEDs say \"ASH\"\r\n");
   textcolor(WHITE);
   cprintf("    sl65 -conn         ");
   textcolor(LIGHTCYAN);
   cprintf("If the receiver's LEDs say \"conn\"\r\n");
   textcolor(WHITE);
   cprintf("    sl65 -dump         ");
   textcolor(LIGHTCYAN);
   cprintf("If the receiver's LEDs say \"dup\" or \"| | \"\r\n\r\n");
   textcolor(LIGHTMAGENTA);
   cprintf("\
PLEASE NOTE THAT THERE ARE NO GUARANTEES THAT THE FILE CREATED CAN BE\r\n\
SUCCESSFULLY REFLASHED BACK TO THE RECEIVER. YOU USE THIS SOFTWARE AT\r\n\
YOUR OWN RISK. E&OE. ");
   textcolor(BLUE);
   cprintf("Source codes are available.\r\n");

   textbackground(BLACK);
   textcolor(LIGHTGRAY);
   gotoxy(1, 25);
   cprintf("\r\n");
   clreol();

   return;
}

 


Back to non-Sky receiver index · Digibox main index


Copyright © 2007 Richard Murray