text: #include <stdio.h>
  #include <stdlib.h>
  #include <stdarg.h>
  #include <string.h>
  #include <limits.h>
  #include <ctype.h>
  
  #include <iostream>
  
  #include "proploader.h"
  #include "loadelf.h"
  #include "propimage.h"
  #include "packet.h"
  #include "loader.h"
  #include "serialpropconnection.h"
  #include "serialprop2connection.h"
  #include "wifipropconnection.h"
  #include "wifiprop2connection.h"
  #include "config.h"
  
  /* default port name prefix if only a partial name is specified */
  #if defined(CYGWIN) || defined(WIN32) || defined(MINGW)
  # ifndef WIN32
  #  define WIN32
  # endif
    #define PORT_PREFIX "COM"
  #elif defined(LINUX)
    #ifdef RASPBERRY_PI
      #define PORT_PREFIX "/dev/serial"
    #else
      #define PORT_PREFIX "/dev/ttyUSB"
    #endif
  #elif defined(MACOSX)
    #define PORT_PREFIX "/dev/cu.usbserial-"
  #else
    #define PORT_PREFIX ""
  #endif
  
  static bool prompt_before_exit = false;
  
  void promptexit(int status)
  {
      int c;
      if (prompt_before_exit)
        {
  	fflush(stderr);
  	printf("Press return to continue...\n");
  	fflush(stdout);
  	for(;;) {
  	  c = getchar();
  	  if (c < 0 || c == '\n') break;
  	}
        }
      exit(status);
  }
  void clearpromptflag()
  {
      prompt_before_exit = false;
  }
  
  static void usage(const char *progname)
  {
  printf("\
  PropLoader (totalspectrum branch) %s\n\
  \n\
  usage: %s [options] [<file>]\n\
  \n\
  options:\n\
      -b <type>       select target board and subtype (default is 'default:default')\n\
      -c              display numeric message codes\n\
      -D var=value    define a board configuration variable\n\
      -e              program eeprom (and halt, unless combined with -r)\n\
      -f <file>       write a file to the SD card\n\
      -i <ip-addr>    IP address of the Parallax Wi-Fi module\n\
      -I <path>       add a directory to the include path\n\
      -k              prompt before exiting due to an error\n\
      -n <name>       set the name of a Parallax Wi-Fi module\n\
      -p <port>       serial port\n\
      -P              show all serial ports\n\
      -q              during terminal display check for exit status from Propeller\n\
      -r              run program after downloading (useful with -e)\n\
      -R              reset the Propeller\n\
      -s              do a serial download\n\
      -t              enter terminal mode after the load is complete\n\
      -T              enter pst-compatible terminal mode after the load is complete\n\
      -v              enable verbose debugging output\n\
      -W              show all discovered wifi modules\n\
      -2              assume Prop2 (same as chipver=P2)\n\
      -9 <dir>        start plan 9 file system with root at <dir>\n\
      -?              display a usage message and exit\n\
  \n\
  file:               binary file to load (.elf or .binary)\n\
  \n\
  Target board type can be either a single identifier like 'propboe' in which case the subtype\n\
  defaults to 'default' or it can be of the form <type>:<subtype> like 'c3:ram'.\n\
  \n\
  Module names should only include the characters A-Z, a-z, 0-9, or '-' and should not begin or\n\
  end with a '-'. They must also be less than 32 characters long.\n\
  \n\
  Variables that can be set with -D are:\n\
  \n\
  Used by the loader:\n\
    loader reset clkfreq clkmode fast-loader-clkfreq fast-loader-clkmode\n\
    baud-rate loader-baud-rate fast-loader-baud-rate chipver\n\
  \n\
  Used by the SD file writer:\n\
    sdspi-do sdspi-clk sdspi-di sdspi-cs\n\
    sdspi-clr sdspi-inc sdspi-start sdspi-width spdspi-addr\n\
    sdspi-config1 sdspi-config2\n\
  \n\
  Value expressions for -D can include:\n\
    rcfast rcslow xinput xtal1 xtal2 xtal3 pll1x pll2x pll4x pll8x pll16x k m mhz true false\n\
    an integer or two operands with a binary operator + - * / %% & | or unary + or -\n\
    or a parenthesized expression.\n\
  \n\
  Examples:\n\
    loader=rom  to use the ROM loader instead of the fast loader\n", VERSION, progname);
      promptexit(1);
  }
  
  static void ShowPorts(bool check);
  static void ShowWiFiModules(bool check);
  static int WriteFileToSDCard(BoardConfig *config, PropConnection *connection, const char *path, const char *target);
  static int LoadSDHelper(BoardConfig *config, PropConnection *connection);
  #ifdef WIN32
  static void EnableVTMode();
  #endif
  
  int main(int argc, char *argv[])
  {
      BoardConfig *config, *configSettings;
      bool useFastLoader = true;
      bool done = false;
      bool reset = false;
      bool showPorts = false;
      bool showModules = false;
      bool terminalMode = false;
      bool pstTerminalMode = false;
      bool check_for_exit = false;
      const char *board = NULL;
      const char *subtype = NULL;
      const char *ipaddr = NULL;
      const char *port = NULL;
      const char *name = NULL;
      const char *file = NULL;
      const char *plan9root = NULL;
      uint8_t *image = NULL;
      int imageSize;
      int loadType = ltShutdown;
      bool useSerial = false;
      bool writeFile = false;
      bool chipVerP2 = false;
      SerialPropConnection *serialConnection = NULL;
      WiFiPropConnection *wifiConnection = NULL;
      PropConnection *connection;
      Loader loader;
      const char *p;
      int sts, i;
  
      /* setup a configuration to collect command line -D settings */
      configSettings = NewBoardConfig(NULL, "");
  
      /* get the arguments */
      for (i = 1; i < argc; ++i) {
  
          /* handle switches */
          if (argv[i][0] == '-') {
              switch (argv[i][1]) {
              case 'b':   // select a target board
                  if (argv[i][2])
                      board = &argv[i][2];
                  else if (++i < argc)
                      board = argv[i];
                  else
                      usage(argv[0]);
                  break;
              case 'c':   // display numeric message codes
                  showMessageCodes = true;
                  break;
              case 'D':
                  if (argv[i][2])
                      p = &argv[i][2];
                  else if (++i < argc)
                      p = argv[i];
                  else
                      usage(argv[0]);
                  {
                      const char *p2;
                      char var[128];
                      if ((p2 = strchr(p, '=')) == NULL)
                          usage(argv[0]);
                      if (p2 - p > (int)sizeof(var) - 1) {
                          printf("error: variable name too long");
                          promptexit(1);
                      }
                      strncpy(var, p, p2 - p);
                      var[p2 - p] = '\0';
                      SetConfigField(configSettings, var, p2 + 1);
                  }
                  break;
              case 'e':   // program eeprom
                  loadType |= ltDownloadAndProgram;
                  break;
              case 'f':   // write a file to the SD card
                  if (argv[i][2])
                      file = &argv[i][2];
                  else if (++i < argc)
                      file = argv[i];
                  else
                      usage(argv[0]);
                  writeFile = true;
                  break;
              case 'i':   // set the ip address
                  if (argv[i][2])
                      ipaddr = &argv[i][2];
                  else if (++i < argc)
                      ipaddr = argv[i];
                  else
                      usage(argv[0]);
                  useSerial = false;
                  break;
              case 'I':   // add a directory to the .cfg include path
                  if (argv[i][2])
                      p = &argv[i][2];
                  else if (++i < argc)
                      p = argv[i];
                  else
                      usage(argv[0]);
                  xbAddPath(p);
                  break;
  	    case 'k':
  	        prompt_before_exit = true;
  		break;
              case 'n':   // name a wifi module
                  if (argv[i][2])
                      name = &argv[i][2];
                  else if (++i < argc)
                      name = argv[i];
                  else
                      usage(argv[0]);
                  done = true;
                  break;
              case 'p':   // select a serial port
                  if (argv[i][2])
                      port = &argv[i][2];
                  else if (++i < argc)
                      port = argv[i];
                  else
                      usage(argv[0]);
  #if defined(CYGWIN) || defined(WIN32) || defined(LINUX)
                  if (isdigit((int)port[0])) {
                      static char buf[64];
                      sprintf(buf, "%s%d", PORT_PREFIX, atoi(port));
                      port = buf;
                  }
  #endif
  #if defined(MACOSX) || defined(LINUX)
                  if (port[0] != '/') {
                      static char buf[64];
                      sprintf(buf, "%s%s", PORT_PREFIX, port);
                      port = buf;
                  }
  #endif
  #if defined(MACOSX)
                  if (strncmp(port, "/dev/tty.", 9) == 0) {
                      static char buf[64];
                      sprintf(buf, "/dev/cu.%s", &port[9]);
                      nmessage(INFO_USING_ALTERNATE_PORT, buf, port);
                      port = buf;
                  }
  #endif
                  useSerial = true;
                  break;
              case 'P':   // show serial ports
                  showPorts = true;
                  break;
  	    case 'q':  // enable magic exit code
  	        check_for_exit = true;
  		break;
              case 'r':   // run program after loading
                  loadType |= ltDownloadAndRun;
                  break;
              case 'R':   // reset the Propeller
                  reset = true;
                  done = true;
                  break;
              case 's':   // use the serial loader instead of the wifi loader
                  useSerial = true;
                  break;
              case 't':   // enter terminal emulator mode after loading
                  terminalMode = true;
                  pstTerminalMode = false;
                  break;
              case 'T':   // enter pst-compatible terminal emulator mode after loading
                  terminalMode = true;
                  pstTerminalMode = true;
                  break;
              case '2':
                  chipVerP2 = true;
                  useFastLoader = false;
                  break;
  	    case '9':
  	        terminalMode = true;
  		if (argv[i][2])
  		    plan9root = &argv[i][2];
  		else if (++i < argc)
  		    plan9root = argv[i];
  		else
  		    usage(argv[0]);
  	        break;
              case 'v':   // enable verbose debugging output
                  ++verbose;
                  break;
              case 'W':   // show wifi modules
                  showModules = true;
                  break;
              case '?':
              default:
                  usage(argv[0]);
                  break;
              }
          }
          
          /* remember the file to load */
          else {
              if (file)
                  usage(argv[0]);
              file = argv[i];
          }
      }
  
      /* show ports if requested */
      if (showPorts) {
          ShowPorts(false);
          done = true;
      }
      
      /* show modules if requested */
      if (showModules) {
          ShowWiFiModules(true);
          done = true;
      }
  
      /* before we do anything else, make sure we can read the Propeller image file */
      if (file && !writeFile) {
          nmessage(INFO_OPENING_FILE, file);
          if (!(image = Loader::readFile(file, &imageSize))) {
              nmessage(ERROR_CANT_OPEN_FILE, file);
              promptexit(1);
          }
      }
         
  /*
  1) look in the directory specified by the -I command line option (added above)
  2) look in the directory where the elf file resides
  3) look in the directory pointed to by the environment variable PROPELLER_ELF_LOAD
  4) look in the directory where the loader executable resides if possible
  5) look in /opt/parallax/propeller-load
  */
  
      /* finish the include path */
      if (file)
          xbAddFilePath(file);
      xbAddEnvironmentPath("PROPELLER_LOAD_PATH");
      xbAddProgramPath(argv);
  #if defined(LINUX) || defined(MACOSX) || defined(CYGWIN)
      xbAddPath("/opt/parallax/propeller-load");
  #endif
      
      /* parse the board option */
      char boardBuffer[128];
      if (board) {
      
          /* split the board type from the subtype */
          if ((p = strchr(board, ':')) != NULL) {
              if (p - board >= (int)sizeof(boardBuffer)) {
                  printf("error: board type name too long\n");
                  promptexit(1);
              }
              strncpy(boardBuffer, board, p - board);
              boardBuffer[p - board] = '\0';
              board = boardBuffer;
              subtype = p + 1;
          }
          
          /* no subtype */
          else
              subtype = DEF_SUBTYPE;
      }
      
      else {
          board = DEF_BOARD;
          subtype = DEF_SUBTYPE;
      }
  
      /* setup for the selected board */
      if (!(config = ParseConfigurationFile(board))) {
          printf("error: can't find board configuration '%s'\n", board);
          promptexit(1);
      }
      
      /* select the subtype */
      if (subtype) {
          if (!(config = GetConfigSubtype(config, subtype))) {
              printf("error: can't find board configuration subtype '%s'\n", subtype);
              promptexit(1);
          }
      }
      
      /* override with any command line settings */
      config = MergeConfigs(config, configSettings);
  
      /* set programming style based on chip version */
      if ((p = GetConfigField(config, "chipver")) != NULL && strcmp(p, "P2") == 0) {
          chipVerP2 = true;
          useFastLoader = false;
          message("Chip Version set to P2 and P1-fast-loader disabled");
      }
  
      if (!chipVerP2) {
          switch (PropImage::validate(image, imageSize)) {
          case PropImage::SUCCESS:
              // success
              break;
          case PropImage::IMAGE_TRUNCATED:
              nmessage(ERROR_FILE_TRUNCATED);
              promptexit(1);
          case PropImage::IMAGE_CORRUPTED:
              nmessage(ERROR_FILE_CORRUPTED);
              promptexit(1);
          default:
              nmessage(ERROR_INTERNAL_CODE_ERROR);
              promptexit(1);
          }
      }
      /* decide whether to use the fast or rom loader */
      if ((p = GetConfigField(config, "loader")) != NULL && strcmp(p, "rom") == 0)
          useFastLoader = false;
      
      /* make sure a file to load was specified */
      if (!done && !reset && !file && !terminalMode)
          usage(argv[0]);
          
      /* check to there is anything more to do */
      if (!reset && !file && !name && !terminalMode)
          goto finish;
  
      /* default to 'download and run' if neither -e nor -r are specified */
      if (loadType == ltShutdown)
          loadType = ltDownloadAndRun;
          
   tryUsingSerial:
      /* do a serial download */
      // TODO: Implement P2 serial loader based on chipver value
      if (useSerial) {
          if (chipVerP2) {
              serialConnection = new SerialProp2Connection();
          } else {
              serialConnection = new SerialPropConnection();
          }
          {
              SerialInfo info; // needs to stay in scope as long as we're using port
              if (!serialConnection) {
                  nmessage(ERROR_INSUFFICIENT_MEMORY);
                  promptexit(1);
              }
              if (!port) {
                  SerialInfoList ports;
                  if (SerialPropConnection::findPorts(true, ports) != 0) {
                      nmessage(ERROR_SERIAL_PORT_DISCOVERY_FAILED);
                      promptexit(1);
                  }
                  if (ports.size() == 0) {
                      nmessage(ERROR_NO_SERIAL_PORTS_FOUND);
                      promptexit(1);
                  }
                  info = ports.front();
                  port = info.port();
              }
              int loaderBaudRate;
              if (!GetNumericConfigField(config, "loader-baud-rate", &loaderBaudRate))
                  loaderBaudRate = DEF_LOADER_BAUDRATE;
              if ((sts = serialConnection->open(port, loaderBaudRate)) != 0) {
                  nmessage(ERROR_UNABLE_TO_CONNECT_TO_PORT, port);
                  promptexit(1);
              }
              connection = serialConnection;
          }
      }
      /* do a wifi download */
      else {
          if (chipVerP2) {
              wifiConnection = new WiFiProp2Connection;
          } else {
              wifiConnection = new WiFiPropConnection;
          }
          if (!wifiConnection) {
              nmessage(ERROR_INSUFFICIENT_MEMORY);
              promptexit(1);
          }
          if (!ipaddr) {
              WiFiInfoList addrs;
              if (wifiConnection->findModules(false, addrs, 1) != 0) {
  	      //nmessage(ERROR_WIFI_MODULE_DISCOVERY_FAILED);
  	      //promptexit(1);
  	      useSerial = true;
  	      goto tryUsingSerial;
              }
              if (addrs.size() == 0) {
  	      useSerial = true;
  	      goto tryUsingSerial;
  	      //  nmessage(ERROR_NO_WIFI_MODULES_FOUND);
                //  promptexit(1);
              }
              const char *ipaddr2 = addrs.front().address();
              char *p;
              if (!(p = (char *)malloc(strlen(ipaddr2) + 1))) {
                  nmessage(ERROR_INSUFFICIENT_MEMORY);
                  promptexit(1);
              }
              strcpy(p, ipaddr2);
              ipaddr = p;
          }
          if ((sts = wifiConnection->setAddress(ipaddr)) != 0) {
              nmessage(ERROR_INVALID_MODULE_ADDRESS, ipaddr);
              promptexit(1);
          }
          if (wifiConnection->getVersion() != 0) {
              nmessage(ERROR_UNABLE_TO_CONNECT_TO_MODULE, ipaddr);
              promptexit(1);
          }
          if ((sts = wifiConnection->checkVersion()) != 0) {
              nmessage(ERROR_WRONG_WIFI_MODULE_FIRMWARE, wifiConnection->version(), WIFI_REQUIRED_MAJOR_VERSION);
              promptexit(1);
          }
          connection = wifiConnection;
      }
      
      /* set the connection configuration */
      connection->setConfig(config);
      
      /* setup the reset method */
      if ((p = GetConfigField(config, "reset")) != NULL) {
          if (connection->setResetMethod(p) != 0) {
              nmessage(ERROR_NO_RESET_METHOD, p);
              promptexit(1);
          }
      }
          
      /* reset the Propeller */
      if (reset) {
          if (connection->generateResetSignal() != 0) {
              printf("error: failed to reset Propeller\n");
              promptexit(1);
          }
      }
      
      /* set the wifi module name */
      if (name) {
          if (!wifiConnection) {
              nmessage(ERROR_CAN_ONLY_NAME_WIFI_MODULES);
              promptexit(1);
          }
          
  #define isAllowed(ch)   (isupper(ch) || islower(ch) || isdigit(ch) || (ch) == '-')
  
          char cleanName[32], *p;
          
          /* remove leading spaces or hyphens */
          p = cleanName;
          while (*name && (isspace(*name) || *name == '-'))
              ++name;
          
          /* copy the rest of the name */
          bool inStringOfSpaces = false;
          while (*name != '\0' && p < &cleanName[sizeof(cleanName) - 1]) {
              if (isspace(*name)) {
                  if (!inStringOfSpaces)
                      *p++ = '-';
                  inStringOfSpaces = true;
              }
              else if (isAllowed(*name)) {
                  *p++ = *name;
                  inStringOfSpaces = false;
              }
              ++name;
          }
              
          /* remove trailing spaces or hyphens */
          while (p > cleanName && (isspace(p[-1]) || p[-1] == '-'))
              --p;
              
          /* terminate the clean name */
          *p = '\0';
          
          /* if we deleted every character then this is an invalid name */
          if (!cleanName[0]) {
              nmessage(ERROR_INVALID_MODULE_NAME);
              promptexit(1);
          }
          
          /* show the clean name if it is different from what the user requested */
          if (strcmp(name, cleanName) != 0)
              nmessage(INFO_SETTING_MODULE_NAME, cleanName);
              
          if (wifiConnection->setName(cleanName) != 0) {
              nmessage(ERROR_FAILED_TO_SET_MODULE_NAME);
              promptexit(1);
          }
      }
      
      /* write a file to the SD card */
      if (writeFile) {
          nmessage(INFO_WRITING_TO_SD_CARD, file);
          if (WriteFileToSDCard(config, connection, file, NULL) != 0) {
              nmessage(ERROR_FAILED_TO_WRITE_TO_SD_CARD, file);
              promptexit(1);
          }
      }
      
      /* load a file */
      else if (file) {
          loader.setConnection(connection);
          if (useFastLoader) {
              if ((sts = loader.fastLoadImage(image, imageSize, (LoadType)loadType)) != 0) {
                  nmessage(ERROR_DOWNLOAD_FAILED);
                  promptexit(1);
              }
          }
          else {
              if ((sts = loader.loadImage(image, imageSize, (LoadType)loadType)) != 0) {
                  nmessage(ERROR_DOWNLOAD_FAILED);
                  promptexit(1);
              }
          }
          nmessage(INFO_DOWNLOAD_SUCCESSFUL);
      }
      
      /* set the baud rate used by the program */
      int baudRate;
      if (!GetNumericConfigField(config, "baud-rate", &baudRate)
      &&  !GetNumericConfigField(config, "baudrate", &baudRate)) // for backwards compatibility
          baudRate = DEF_TERMINAL_BAUDRATE;
      if (connection->setBaudRate(baudRate) != 0) {
          nmessage(ERROR_FAILED_TO_SET_BAUD_RATE);
          promptexit(1);
      }
      
      /* enter terminal mode */
      if (terminalMode) {
  #ifdef WIN32
          EnableVTMode();
  #endif	
          nmessage(INFO_TERMINAL_MODE);
          
          /* open a connection to the target */
          if (!connection->isOpen() && connection->connect() != 0) {
              message("Can't open connection to target");
              nmessage(ERROR_FAILED_TO_ENTER_TERMINAL_MODE);
              promptexit(1);
          }
          
          /* enter terminal mode */
          if (connection->terminal(check_for_exit, pstTerminalMode, plan9root) != 0) {
              nmessage(ERROR_FAILED_TO_ENTER_TERMINAL_MODE);
              promptexit(1);
          }
      }
      
      /* disconnect from the target */
      connection->disconnect();
      
  finish:
      /* return successfully */
      promptexit(0);
  }
  
  static void ShowPorts(bool check)
  {
      SerialInfoList ports;
      SerialPropConnection serconn;
      int r;
      int loaderBaudRate;
      int version;
  
      loaderBaudRate = DEF_LOADER_BAUDRATE;
  
      if (SerialPropConnection::findPorts(check, ports) == 0) {
          SerialInfoList::iterator i = ports.begin();
          while (i != ports.end()) {
              version = -1;
              if (check) {
                  r = serconn.open(i->port(), loaderBaudRate);
                  if (r == 0) {
                      r = serconn.identify(&version);
                  }
                  std::cout << i->port() << " version: " << version << std::endl;
              } else {
                  std::cout << i->port() << std::endl;
              }
              ++i;
          }
      }
  }
  
  static void ShowWiFiModules(bool show)
  {
      WiFiInfoList modules;
      WiFiPropConnection::findModules(true, modules);
  }
  
  #define TYPE_FILE_WRITE     0
  #define TYPE_DATA           1
  #define TYPE_EOF            2
  
  static int WriteFileToSDCard(BoardConfig *config, PropConnection *connection, const char *path, const char *target)
  {
      PacketDriver packetDriver(*connection);
      uint8_t buf[PKTMAXLEN];
      size_t size, remaining, cnt;
      FILE *fp;
  
      /* open the file */
      nmessage(INFO_OPENING_FILE, path);
      if ((fp = fopen(path, "rb")) == NULL)
          return nerror(ERROR_CANT_OPEN_FILE, path);
  
      if (!target) {
          if (!(target = strrchr(path, '/')))
              target = path;
          else
              ++target; // skip past the slash
      }
  
      fseek(fp, 0, SEEK_END);
      size = remaining = ftell(fp);
      fseek(fp, 0, SEEK_SET);
  
      message("Loading SD helper");
      if (LoadSDHelper(config, connection) != 0) {
          fclose(fp);
          return error("Loading SD helper");
      }
  
      /* wait for the SD helper to complete initialization */
      if (!packetDriver.waitForInitialAck())
          return error("Failed to connect to helper");
  
      if (!packetDriver.sendPacket(TYPE_FILE_WRITE, (uint8_t *)target, strlen(target) + 1)) {
          fclose(fp);
          return error("SendPacket FILE_WRITE failed");
      }
  
      while ((cnt = fread(buf, 1, PKTMAXLEN, fp)) > 0) {
          nprogress(INFO_BYTES_REMAINING, (long)remaining);
          if (!packetDriver.sendPacket(TYPE_DATA, buf, cnt)) {
              fclose(fp);
              return error("SendPacket DATA failed");
          }
          remaining -= cnt;
      }
      nmessage(INFO_BYTES_SENT, (long)size);
  
      fclose(fp);
  
      if (!packetDriver.sendPacket(TYPE_EOF, (uint8_t *)"", 0))
          return error("SendPacket EOF failed");
  
      /*
         We send two EOF packets for SD card writes.  The reason is that the EOF
         packet does actual work, and that work takes time.  The packet
         transmission protocol uses read-ahead buffering on the receiving end.
         Therefore, we need to make sure the first EOF packet was received and
         processed before resetting the Prop!
      */
      if (!packetDriver.sendPacket(TYPE_EOF, (uint8_t *)"", 0))
          return error("Second SendPacket EOF failed");
  
      return 0;
  }
  
  extern "C" {
      extern uint8_t sd_helper_array[];
      extern int sd_helper_size;
  }
  
  /* DAT header in sd_helper.spin */
  typedef struct {
      uint32_t baudrate;
      uint8_t tvpin;
      uint8_t dopin;
      uint8_t clkpin;
      uint8_t dipin;
      uint8_t cspin;
      uint8_t select_address;
      uint32_t select_inc_mask;
      uint32_t select_mask;
  } SDHelperDatHdr;
  
  static int LoadSDHelper(BoardConfig *config, PropConnection *connection)
  {
      Loader loader(connection);
      PropImage image(sd_helper_array, sd_helper_size);
      SpinHdr *hdr = (SpinHdr *)image.imageData();
      SpinObj *obj = (SpinObj *)(image.imageData() + hdr->pbase);
      SDHelperDatHdr *dat = (SDHelperDatHdr *)((uint8_t *)obj + (obj->pubcnt + obj->objcnt) * sizeof(uint32_t));
      int ivalue;
  
      /* patch SD helper */
      if (GetNumericConfigField(config, "clkfreq", &ivalue))
          hdr->clkfreq = ivalue;
      if (GetNumericConfigField(config, "clkmode", &ivalue))
          hdr->clkmode = ivalue;
      if (GetNumericConfigField(config, "baudrate", &ivalue)) // for backwards compatibility
          dat->baudrate = ivalue;
      if (GetNumericConfigField(config, "baud-rate", &ivalue))
          dat->baudrate = ivalue;
      if (GetNumericConfigField(config, "tvpin", &ivalue))
          dat->tvpin = ivalue;
  
      if (GetNumericConfigField(config, "sdspi-do", &ivalue))
          dat->dopin = ivalue;
      else
          return error("Missing sdspi-do pin configuration");
  
      if (GetNumericConfigField(config, "sdspi-clk", &ivalue))
          dat->clkpin = ivalue;
      else
          return error("Missing sdspi-clk pin configuration");
  
      if (GetNumericConfigField(config, "sdspi-di", &ivalue))
          dat->dipin = ivalue;
      else
          return error("Missing sdspi-di pin configuration");
  
      if (GetNumericConfigField(config, "sdspi-cs", &ivalue))
          dat->cspin = ivalue;
      else if (GetNumericConfigField(config, "sdspi-clr", &ivalue))
          dat->cspin = ivalue;
      else
          return error("Missing sdspi-cs or sdspi-clr pin configuration");
  
      if (GetNumericConfigField(config, "sdspi-sel", &ivalue))
          dat->select_inc_mask = ivalue;
      else if (GetNumericConfigField(config, "sdspi-inc", &ivalue))
          dat->select_inc_mask = 1 << ivalue;
  
      if (GetNumericConfigField(config, "sdspi-msk", &ivalue))
          dat->select_mask = ivalue;
  
      if (GetNumericConfigField(config, "sdspi-addr", &ivalue))
          dat->select_address = (uint8_t)ivalue;
  
      /* recompute the checksum */
      image.updateChecksum();
  
      /* load the SD helper program */
      if (loader.fastLoadImage(image.imageData(), image.imageSize(), ltDownloadAndRun) != 0)
          return error("Helper load failed");
          
      /* select the sd helper baud rate */
      connection->setBaudRate(dat->baudrate);
  
      return 0;
  }
  
  #ifdef WIN32
  
  #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
  #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
  #endif
  #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
  #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
  #endif
  
  static void EnableVTMode()
  {
      HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
      HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
      DWORD dwMode;
      
      if (hOut == INVALID_HANDLE_VALUE) return;
      if (!GetConsoleMode(hOut, &dwMode)) return;
      dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
      SetConsoleMode(hOut, dwMode);
  
      if (hIn == INVALID_HANDLE_VALUE) return;
      if (!GetConsoleMode(hIn, &dwMode)) return;
      dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
      SetConsoleMode(hIn, dwMode);
  }
  #endif
  