/*
cmc.c - Cyan's Megadrive ROM copier support for uCON64

Copyright (c) 1999 - 2004                Cyan Helkaraxe
Portions copyright (c) 2004, 2015 - 2017 dbjh

Special thanks to dbjh for helping with the uCON64 integration
of this software, and providing the wrapping code.

CMC version: 2.5
For hardware version 1.x

Copies Sega Megadrive/Genesis cartridges into .BIN format ROM files.


This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
Additional information:
This software is distributed in accordance with the GNU General
Public License. The author of the hardware design/documentation and
software, Cyan Helkaraxe, retains the copyright over the 'cmc' code
('software'), the hardware design and construction documentation itself.
Cyan grants you the rights of the GNU General Public License over
the software, as stated above. The copyright does not affect your
rights in relation to the 'cmc' code under the GNU General Public
License in any way.
Cyan Helkaraxe does NOT grant you any rights relating to the hardware
design/documentation, over and above the right to build the device for
personal, not-for-profit use. Likewise, the same applies to the ROM
copier construction documentation, available at
http://www.emulationzone.org/projects/cyan/docs/ for not-for-profit
personal use.

Obviously, feel free to make changes to this software, but if you do so,
*please* clearly mark the fact it is modified, to avoid confusion.
A define is provided below this commented area, which should be edited
as described. This is to inform users which version of the code they are
using at runtime, and if they encounter a problem, it makes it far easier
for me to help debug it. I have no evil nefarious intentions. =P

Obviously, your changes must adhere to the GNU GPL, and please keep the
copyright, Cyan's name, e-mail and web site address in the comments
somewhere.

If you wish to do anything with the hardware design or the documentation
that goes beyond personal use, get in touch with Cyan first;
cyan@emulationzone.org

Disclaimer / Warranty:
This is to emphasise, not replace, any warranty information provided in
the GNU GPL or uCON64.
There is no warranty whatsoever, for either the software or accompanying
hardware/design/documentation. This software is provided free of charge,
AS-IS, in the hope that it may be useful.
Use it at your own risk. The author does not make any guarantee that you
will be able to use this software or accompanying
hardware/design/documentation, or that it will perform smoothly. There is
a possibility that damage or loss (including but not limited to financial
loss, hardware or software damage, data corruption, privacy violation,
or personal injury, suffering, legal action, imprisonment or death) may
arise through the use of this software or the accompanying
hardware/design/documentation, in addition to many other possible outcomes.
You take sole responsibility for any outcomes; by using this software or
accompanying hardware/design/ documentation, you agree that the author will
not be held responsible for anything that may occur. If your jurisdiction
does not allow the author to be isolated from responsibility, then you
must *not* use this software or accompanying hardware/design/documentation.
The author does not condone software piracy. You may not use this software
to engage in piracy. The author is not responsible for anything you choose
to use this software for, although the author strongly recommends that you
use the software for the purpose for which it was intended to be used --
primarily as an educational tool, and secondarily to make backup copies of
your expensive/rare game cartridges, or a similarly harmless and legal
purpose.
Note that although the author isn't aware of any patent violations caused
by this software/hardware/design/documentation, it is possible that
there may be patents covering parts of this device. It is your
responsibility to check this before building or using the hardware or
software, and Cyan may not be held responsible in the event of an
infringement.

That being said, if you do encounter any problems with the hardware or
software, then feel free to get in touch with the author; use the subject
line 'ROM Copier Technical Support'. No promises or guarantees are made,
however.

Also note that the author is not affiliated with anyone involved with uCON64;
therefore, only correspondence relating to this particular file (the 'cmc'
code) or the accompanying hardware design should be directed to Cyan.
If you have more general uCON64 questions, Cyan is *not* the person to ask.
Likewise, the terms "the author" and "software" in this file (cmc.c, and
additionally cmc.h), along with similar terms, apply only to Cyan, and the
CMC software you see in this file. The disclaimer above, for example, relates
exclusively to the CMC code.

All trademarks, indicated or otherwise, are the property of their
respective owners.
*/

/*
  NOTE!
  Please edit the following line, and remove the word "original" from it
  if you have made any modifications to this file. This reduces user
  confusion.
*/
#define INTRO_TEXT "Cyan's Megadrive Copier             (c) 1999-2004 Cyan Helkaraxe\n" \
                   "Software version 2.5, designed for hardware version 1.x\n\n"


#ifdef  HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "misc/archive.h"
#include "misc/bswap.h"
#include "misc/misc.h"
#include "misc/term.h"
#include "ucon64.h"
#include "ucon64_misc.h"
#include "backup/cmc.h"


#ifdef  USE_PARALLEL

#define RSLO 0x40                               // reset line 1 d7
#define RSHI 0x01                               // reset line 2 d0
#define CNLO 0x10                               // counter line 1 d4
#define CNHI 0x04                               // counter line 2 d2
#define INLO 0x40                               // input 1 ack (int disabled)
#define INHI 0x10                               // input 2 selectin

#define MBYTE (1024 * 1024)
#define DEFAULT_SPEED 3


/************************
 *  Internal functions  *
 ************************/

static inline void
cyan_write_copier (unsigned char data, unsigned short parport)
// write a value to the data register of the parallel port
{
  outportb (parport + PARPORT_DATA, data);
}


static inline unsigned char
cyan_read_copier (unsigned short parport)
// read a value from the status register of the parallel port
{
  return inportb (parport + PARPORT_STATUS);
}


static inline unsigned char
cyan_verify_copier (unsigned short parport)
// read a value back from the data register for verification
{
  return inportb (parport + PARPORT_DATA);
}


/**** non-hardware, non-accessing ****/

static unsigned long
cyan_calculate_rom_size (unsigned char *buffer, int test_mode)
/*
  Calculate the ROM size, by looking at the ROM size entry in the ROM 'header',
  and the overall structure.
  This function always returns a value rounded to a power of two between 128
  kbytes and 4 Mbytes. It also inspects the ROM for 0's or ffh's. If test_mode
  is 1 it causes an error on that condition, frees the buffer and exits.
*/
{
  unsigned long i = 0x80000000, reported_size;

  // look at reported size
  reported_size = (buffer[0x1a4] << 24) +
                  (buffer[0x1a5] << 16) +
                  (buffer[0x1a6] << 8) +
                   buffer[0x1a7] + 1;
  // cap
  // there is a minimum valid size for ROMs, according to some sources
  if (reported_size < MBIT)
    reported_size = MBIT;
  if (reported_size > 4 * MBYTE)
    reported_size = 4 * MBYTE;
  // round
  if (reported_size & (reported_size - 1))
    {
      while (!(reported_size & 0x80000000))
        {
          i >>= 1;
          reported_size = (reported_size << 1) | 1;
        }
      reported_size = i << 1;
    }
  // calculate real size
  for (i = 2 * MBYTE; i >= 65536; i >>= 1)
    if (memcmp (buffer, buffer + i, i))
      {
        i >>= 1;
        break;
      }
  i <<= 2;
  if (reported_size < i)
    reported_size = i;                          // pick the safest (largest) of the two
  if (i == MBIT)
    {
      for (i = 0; i < MBIT; i++)
        if ((buffer[i] != 0xff) && (buffer[i] != 0x00))
          break;
      if (i == MBIT)
        {
          FILE *output;

          if (test_mode)
            {
              output = stderr;
              fputs ("\nERROR:   ", stderr);
            }
          else
            {
              output = stdout;
              fputs ("\nWARNING: ", stdout);
            }

          //     "WARNING: "
          fputs (         "The ROM file appears to consist of nothing but 0x00 / 0xff values.\n"
                 "         This usually indicates a serious problem. Perhaps your parallel port\n"
                 "         isn't configured correctly, or there is some problem with the ROM\n"
                 "         copier. Is it getting power? Is a cartridge inserted? Is it properly\n"
                 "         attached to the PC?\n",
                 output);
          if (test_mode)
            {
              free (buffer);
              exit (1);
            }
        }
    }
  return reported_size;
}


static int
cyan_checksum_rom (unsigned char *buffer)
// return 0 on success, -1 on failure
{
  unsigned char *buffer2 = buffer;
  unsigned short reported_sum, running_sum = 0;

  reported_sum = (buffer2[0x18e] << 8) + buffer2[0x18f];
  buffer2 += ((buffer2[0x1a4] << 24) +
              (buffer2[0x1a5] << 16) +
              (buffer2[0x1a6] << 8) +
              (buffer2[0x1a7] & 0xfe)) + 2;
  if (buffer2 > buffer + 4 * MBYTE)
    buffer2 = buffer + 4 * MBYTE;
  buffer += 0x200;
  if (buffer2 < buffer + 2)
    return -1;

  while (buffer2 != buffer)
    {
      running_sum += *--buffer2;
      running_sum += (*--buffer2) << 8;
    }

  return (running_sum & 0xffff) != reported_sum ? -1 : 0;
}


static inline unsigned long
cyan_get_address (unsigned long b)
// return the true address (word -- 0 - 2 M) based on word input
{
  return ((b & 0x000800) >> 11) |               // bit 0
         ((b & 0x002000) >> 12) |               // bit 1
         ((b & 0x004000) >> 12) |               // bit 2
         ((b & 0x000020) >> 2) |                // bit 3
         ((b & 0x100000) >> 16) |               // bit 4
         ((b & 0x020000) >> 12) |               // bit 5
         ((b & 0x000400) >> 4) |                // bit 6
         ((b & 0x000001) << 7) |                // bit 7
         ((b & 0x000002) << 7) |                // bit 8
         ((b & 0x000010) << 5) |                // bit 9
         ((b & 0x000040) << 4) |                // bit 10
         ((b & 0x040000) >> 7) |                // bit 11
         ((b & 0x080000) >> 7) |                // bit 12
         ((b & 0x000080) << 6) |                // bit 13
         ((b & 0x008000) >> 1) |                // bit 14
         ((b & 0x010000) >> 1) |                // bit 15
         ((b & 0x001000) << 4) |                // bit 16
         ((b & 0x000004) << 15) |               // bit 17
         ((b & 0x000008) << 15) |               // bit 18
         ((b & 0x000200) << 10) |               // bit 19
         ((b & 0x000100) << 12);                // bit 20
}


/**** non-hardware, indirectly accessing ****/

static inline void
cyan_delay (int speed, unsigned short parport)
// Delays a certain amount of time depending on speed selected. 0=long delay,
//  used for reset and hi counter.
{
  int i, scritch = 0;

  switch (speed)
    {
    case 0:
      for (i = 0; i < 128; i++)
        scritch += cyan_read_copier (parport);
    case 1:                                     // falling through
      for (i = 0; i < 64; i++)
        scritch += cyan_read_copier (parport);
    case 2:                                     // falling through
      for (i = 0; i < 12; i++)
        scritch += cyan_read_copier (parport);
    case 3:                                     // falling through
      scritch += cyan_read_copier (parport);
      scritch += cyan_read_copier (parport);
    }
}


static void
cyan_reset (unsigned short parport)
// resets the copier
{
  cyan_delay (0, parport);
  // zero all data outputs first, before going into SPP mode
  cyan_write_copier (0, parport);
  // reset the port to SPP, float all control lines high
  cyan_write_copier (0, parport + PARPORT_CONTROL);
  cyan_delay (0, parport);
  cyan_write_copier (RSLO | RSHI, parport);     // both reset lines hi
  cyan_delay (0, parport);
  cyan_write_copier (0, parport);               // both reset lines lo
  cyan_delay (0, parport);
}


static inline unsigned short
cyan_get_word (int speed, unsigned short parport)
// gets a byte pair from the ROM and return two bytes in big endian byte order
{
  unsigned short value = 0;
  unsigned char tempz;

  cyan_write_copier (0, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= tempz & INLO;                        // bit 6
  value |= (tempz & INHI) << 8;                 // bit 12

  cyan_write_copier (CNLO, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) >> 5;                 // bit 1
  value |= (tempz & INHI) << 9;                 // bit 13

  cyan_write_copier (0, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) << 3;                 // bit 9
  value |= (tempz & INHI) << 10;                // bit 14

  cyan_write_copier (CNLO, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) >> 1;                 // bit 5
  value |= (tempz & INHI) << 11;                // bit 15

  cyan_write_copier (0, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) >> 4;                 // bit 2
  value |= (tempz & INHI) << 4;                 // bit 8

  cyan_write_copier (CNLO, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) << 4;                 // bit 10
  value |= (tempz & INHI) >> 4;                 // bit 0

  cyan_write_copier (0, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) >> 2;                 // bit 4
  value |= (tempz & INHI) << 3;                 // bit 7

  cyan_write_copier (CNLO, parport);
  cyan_delay (speed, parport);
  tempz = cyan_read_copier (parport);
  value |= (tempz & INLO) >> 3;                 // bit 3
  value |= (tempz & INHI) << 7;                 // bit 11

  return me2be_16 (value);
}


static inline int
check_exit (void)
// check for user abort
{
  int temp;

  if (ucon64.frontend)
    return 0;
  if (!kbhit ())
    return 0;
  temp = getch ();
  if (temp == 'q' || (temp == 27))
    return 1;
  return 0;
}


static unsigned char *
cyan_read_rom (int speed, unsigned short parport, unsigned char *buffer)
/*
  Read the ROM and return a pointer to a 4 MB area of memory containing all ROM
  data. Designed to be used from inside cyan_copy_rom(), although it can be
  called elsewhere if a raw (but decoded) dump is required.
*/
{
  unsigned long q;
  time_t t;

  // allocate the dump area
  if (!buffer)
    if ((buffer = (unsigned char *) malloc (4 * MBYTE)) == NULL)
      {
        fprintf (stderr, ucon64_msg[ROM_BUFFER_ERROR], 4 * MBYTE);
        exit (1);
      }

  cyan_reset (parport);                         // reset the copier

  t = time (NULL);
  // copy routine
  for (q = 0; q < 2 * MBYTE; )                  // loop through all words
    {
      // get a (16-bit) word from the ROM
      ((unsigned short *) buffer)[cyan_get_address (q)] = cyan_get_word (speed, parport);

      // periodically update progress bar, without hammering ucon64_gauge()
      if (!(q & (0xfff >> (5 - speed))))
        {
          if (check_exit ())
            {
              free (buffer);
              puts ("\n"
                    "Copy aborted.\n"
                    "Don't forget to turn the ROM copier off and never insert or remove a cartridge\n"
                    "with the power on");
              break;
            }
          ucon64_gauge (t, q * 2, 4 * MBYTE);
        }

      if (!(++q & 0x3ff)) // advance loop counter and carry to hi counter (11 bits)
        {
          cyan_delay (0, parport);
          cyan_write_copier (CNHI, parport);
          cyan_delay (0, parport);
          cyan_write_copier (0, parport);
          cyan_delay (0, parport);
        }
    }

  // make sure it's left in a state where it's safe to remove the cart
  cyan_reset (parport);

  if (q != 2 * MBYTE)
    return NULL;

  ucon64_gauge (t, q * 2, 4 * MBYTE);           // make the progress bar reach 100%

  return buffer;
}


static void
cyan_test_parport (unsigned short parport)
// Test the parallel port to see if it appears to be functioning correctly, and
//  terminate if there's an error.
{
  unsigned short temp;

  cyan_reset (parport);
  fputs ("Basic parallel port test: ", stdout);
  fflush (stdout);
  cyan_write_copier (170, parport);
  cyan_delay (0, parport);
  temp = cyan_verify_copier (parport) & 170;
  cyan_reset (parport);

  // even in unidirectional mode, the parallel port is bidirectional; at
  //  least, for a few short moments before the driver IC smokes
  if ((cyan_verify_copier (parport) & 170) != 0 || temp != 170)
    {
      puts ("FAILED");
      fputs ("ERROR: Parallel port error\n"
             "       Check that your parallel port is configured properly, in the BIOS, OS,\n"
             "       and uCON64, and check for short circuits on the parallel port connector.\n"
             "       Also ensure that the ROM copier is getting power, and a cartridge is\n"
             "       inserted\n",
             stderr);
      exit (1);
    }
  else
    puts ("Passed");

  // discharge caps to see if we've got power
  cyan_reset (parport);
  cyan_reset (parport);
  cyan_write_copier (CNLO + CNHI, parport);
  cyan_delay (0, parport);
  for (temp = 0; temp < 1000; temp++)
    {
      cyan_write_copier (0, parport);
      cyan_delay (3, parport);
      cyan_write_copier (CNLO + CNHI, parport);
      cyan_delay (3, parport);
    }
  cyan_reset (parport);
  cyan_reset (parport);

  fputs ("Parallel port output test: ", stdout);
  fflush (stdout);
  cyan_write_copier (255, parport);
  cyan_delay (0, parport);
  temp = (cyan_verify_copier (parport) != 255);
  cyan_write_copier (0, parport);
  cyan_delay (0, parport);
  temp |= (cyan_verify_copier (parport) != 0);
  cyan_write_copier (CNLO, parport);
  cyan_delay (0, parport);
  temp |= (cyan_verify_copier (parport) != CNLO);
  cyan_write_copier (CNHI, parport);
  cyan_delay (0, parport);
  temp |= (cyan_verify_copier (parport) != CNHI);
  cyan_write_copier (RSLO, parport);
  cyan_delay (0, parport);
  temp |= (cyan_verify_copier (parport) != RSLO);
  cyan_write_copier (RSHI, parport);
  cyan_delay (0, parport);
  temp |= (cyan_verify_copier (parport) != RSHI);
  cyan_reset (parport);

  // if it's still okay after that, then try reading the first set of inputs
  //  with lines high and low
  if (!temp)
    {
      fputs ("Passed\n"
             "Input crosstalk test: ",
             stdout);
      fflush (stdout);
      temp = cyan_read_copier (parport) & (INLO | INHI);
      cyan_write_copier (255 - CNLO, parport);
      cyan_delay (0, parport);
      temp = (temp != (cyan_read_copier (parport) & (INLO | INHI)));
      cyan_reset (parport);
    }

  if (temp)
    {
      puts ("FAILED");
      fputs ("ERROR: Parallel port error\n"
             "Possible causes: ROM copier not getting power (check or replace battery)\n"
             "                 Short circuit or bad connection (on parallel port or board)\n"
             "                 Cartridge not inserted properly (or not inserted at all)\n"
             "                 Parallel port not configured correctly\n"
             "                 Orange, grey or green wire(s) soldered to the wrong locations\n"
             "                 Chips inserted backwards\n"
             "NOTE: Don't forget the ROM copier needs to be turned on before starting!\n",
             stderr);
      exit (1);
    }
  else
    puts ("Passed");
}


static int
cyan_test_copier (int test, int speed, unsigned short parport)
{
  unsigned char *buffer1, *buffer2 = NULL;
  int count = 1;

  fputs (INTRO_TEXT, stdout);
  parport_print_info ();

  switch (test)
    {
    // reliability test -- NOTE: this test may be required to run for 8 hours or more
    case 1:
      printf ("Reliability test mode selected, speed %d\n", speed);
      cyan_test_parport (parport);
      puts ("\n"
            "Entering non-stop reliability test mode (press escape or q to exit, and turn\n"
            "ROM copier off immediately afterwards)\n"
            "\n"
            "Copy process will continue indefinitely until an error is encountered, at\n"
            "which point the program will terminate.\n"
            "A large number of passes suggests that the copier is working reliably at the\n"
            "selected speed\n");
      printf ("                                                                          P %2d",
              count);
      fflush (stdout);
      if (ucon64.frontend)
        fputc ('\n', stdout);
      buffer1 = cyan_read_rom (speed, parport, NULL);
      if (!buffer1)                             // user abort
        return 0;

      // detect if ROM is all 0x00 or 0xff and print an error if so
      cyan_calculate_rom_size (buffer1, 1);

      for (;;)
        {
          clear_line ();                        // remove last gauge
          printf ("   Pass %2d OK\n", count);
          count++;

          // verify checksum of first pass
          if (count == 2)                       // check only in first iteration
            if (cyan_checksum_rom (buffer1))    // verify checksum
              puts ("\n"
                    "WARNING: Checksum of ROM does not appear to be correct.\n"
                    "         This may be normal for this ROM, or it may indicate a bad copy\n");

          printf ("                                                                          P %2d",
                  count);
          fflush (stdout);
          if (ucon64.frontend)
            fputc ('\n', stdout);
          buffer2 = cyan_read_rom (speed, parport, buffer2);
          if (!buffer2)
            {
              free (buffer1);
              return 0;
            }
          if (memcmp (buffer1, buffer2, 4 * MBYTE))
            {
              // error
              printf ("\n"
                      "\n"
                      "Error detected on pass number %d\n"
                      "\n",
                      count);
              if (count == 2)
                puts ("A failure this early suggests a critical fault, such as a misconfigured or\n"
                      "incompatible parallel port, extremely poor wiring, or power supply problems --\n"
                      "you may wish to replace the battery or try another power supply, and use\n"
                      "shorter cables.\n"
                      "Try lowering the speed and running this test again, as a too high speed can\n"
                      "often cause these symptoms.\n"
                      "Alternatively, it may have been a one-time glitch; re-run the test to be sure.\n"
                      "When (if?) you find a lower speed which works reliably, use that speed for\n"
                      "copying ROMs\n");
              else
                puts ("The first couple of passes were successful. This indicates that you have a\n"
                      "minor intermittent problem; most likely power supply problems, bad wiring, or\n"
                      "some kind of one-time glitch.\n"
                      "You may wish to replace the battery or try another power supply, and use\n"
                      "shorter cables.\n"
                      "Make sure no electrical appliances turn on or off during the copy.\n"
                      "Re-run the test to be sure; it is recommended that you use a lower speed\n");
              free (buffer1);
              free (buffer2);
              return 1;
            }
        }
      break;
    // manual test
    case 2:
      cyan_reset (parport);
      cyan_write_copier (CNHI, parport);
      cyan_delay (0, parport);
      cyan_write_copier (0, parport);
      cyan_delay (0, parport);

      if (speed != DEFAULT_SPEED)
        puts ("Ignoring specified speed; test bench mode does not require a speed setting");
      // print screen
      puts ("Entering manual test bench mode\n"
            "\n"
            "Probe the board and verify that the counters are being clocked, and are\n"
            "counting correctly. The upper counter should be one count ahead of the lower\n"
            "counter, with both clocked at the same rate.\n"
            "Inject logic levels into the multiplexers to verify that the data bits are\n"
            "being read correctly:\n"
            "*=high .=low, layout: L H L H L H L H (L=low multiplexer, H=high multiplexer)\n"
            "NOTE: The signals in question are the chip native signals; D0 below corresponds\n"
            "to D0 on the multiplexer, NOT D0 on the cartridge port. Likewise with the\n"
            "address lines. The input lines are in counter order, left to right.\n"
            "Press escape or q to exit; be sure to turn the ROM copier off immediately after\n"
            "exiting, to reset the device.\n"
            "\n"
            "If the above didn't make any sense to you, press escape or q and turn the ROM\n"
            "copier off immediately!\n"
            "This test is designed for advanced users only\n");
      for (;;)
        {
          const char *status[2] = {"* ", ". "};

          fputc ('\r', stdout);

          cyan_write_copier (0, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (CNLO | CNHI, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (0, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (CNLO | CNHI, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (0, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (CNLO | CNHI, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (0, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (CNLO | CNHI, parport);
          cyan_delay (1, parport);
          count = cyan_read_copier (parport);
          fputs (status[((count ^ INLO) >> 6) & 1], stdout);
          fputs (status[((count ^ INHI) >> 4) & 1], stdout);

          cyan_write_copier (0, parport);
          cyan_delay (1, parport);
          fflush (stdout);

          if (check_exit ())
            {
              cyan_reset (parport);
              puts ("\nUser aborted test");
              return 0;
            }
        }
      break;
    default: // cmc_test() should only pass a correct speed value
      fputs ("INTERNAL ERROR: Invalid test number passed to cyan_test_copier()\n", stderr);
      exit (1);
    }
//  return 0;
}


static int
cyan_copy_rom (const char *filename, int speed, unsigned short parport)
/*
  Copy a ROM file -- this assumes the filename is valid and the file does not
  already exist, since it will blindly try to write (overwrite) the filename you
  give it.
  If the open failed due to an invalid filename or path, it prints an error.
  Speed setting should be between 1-4, 3 is default, and this is verified.
*/
{
  unsigned long romsize;
  unsigned char *buffer;
  FILE *f;

  fputs (INTRO_TEXT, stdout);
  parport_print_info ();

  if (!strlen (filename))
    {
      fputs ("ERROR: Filename not specified\n"
             "       You must specify a filename on the commandline, as follows:\n"
             "       ucon64 " OPTION_LONG_S "xcmc dump.bin\n", stderr);
      exit (1);
    }

  printf ("Speed %d selected\n", speed);
  cyan_test_parport (parport);
  printf ("Destination file: %s\n", filename);

  if ((f = fopen (filename, "wb")) == NULL)
    {
      fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], filename);
      exit (1);
    }
  fclose (f);

  puts ("NOTE: Dumping copier's full address space (file will be automatically trimmed\n"
        "      after dumping)\n"
        "Press escape or q to abort\n");

  buffer = cyan_read_rom (speed, parport, NULL);
  if (!buffer)
    {
      remove (filename);
      exit(0);
    }

  fputc ('\n', stdout);
  romsize = cyan_calculate_rom_size (buffer, 0);

  fputs ("Writing ROM to disk... ", stdout);
  fflush (stdout);
  if ((f = fopen (filename, "wb")) == NULL)
    {
      puts ("FAILED");
      fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], filename);
      free (buffer);
      exit (1);
    }
  if (fwrite (buffer, 1, romsize, f) != romsize)
    {
      puts ("FAILED");
      fprintf (stderr, ucon64_msg[WRITE_ERROR], filename);
      free (buffer);
      fclose (f);
      exit (1);
    }
  fclose (f);
  printf ("%d kBytes OK\n"
          "Verifying checksum... ", (int) (romsize / 1024));
  fflush (stdout);

  if (cyan_checksum_rom (buffer))
    {
      puts ("FAILED\n"
            "WARNING: Checksum of ROM does not appear to be correct.\n"
            "         This may be normal for this ROM, or it may indicate a bad copy.\n"
            "         Please verify the ROM, and consider running a copier test");
    }
  else
    puts ("OK");

  puts ("Copy complete!\n"
        "Don't forget to turn the ROM copier off, and never insert or remove a\n"
        "cartridge with the power on");

  free (buffer);
  return 0;

}

#endif // USE_PARALLEL


/*******************
 * uCON64 wrapping *
 *******************/

#ifdef  USE_PARALLEL
static st_ucon64_obj_t cmc_obj[] =
  {
    {UCON64_GEN, WF_STOP | WF_NO_ROM},
    {UCON64_GEN, WF_SWITCH}
  };
#endif

const st_getopt2_t cmc_usage[] =
  {
    {
      NULL, 0, 0, 0,
      NULL, "Cyan's Megadrive ROM copier"/*"1999-2004 Cyan Helkaraxe"*/,
      NULL
    },
#ifdef  USE_PARALLEL
    {
      "xcmc", 0, 0, UCON64_XCMC,
      NULL, "receive ROM from Cyan's Megadrive ROM copier; " OPTION_LONG_S "port" OPTARG_S "PORT",
      &cmc_obj[0]
    },
    {
      "xcmct", 1, 0, UCON64_XCMCT,
      "TEST", "run test TEST\n"
      "TEST" OPTARG_S "1 burn-in reliability test (specify speed)\n"
      "TEST" OPTARG_S "2 testbench mode (experts only)",
      &cmc_obj[0]
    },
    {
      "xcmcm", 1, 0, UCON64_XCMCM,
      "SPEED", "specify transfer speed\n"
      "SPEED" OPTARG_S "1 slow (debug)\n"
      "SPEED" OPTARG_S "2 medium\n"
      "SPEED" OPTARG_S "3 fast (default)\n"     // verify with value of DEFAULT_SPEED
      "SPEED" OPTARG_S "4 full speed (risky)",
      &cmc_obj[1]
    },
#endif // USE_PARALLEL
    {NULL, 0, 0, 0, NULL, NULL, NULL}
  };


#ifdef  USE_PARALLEL

int
cmc_read_rom (const char *filename, unsigned short parport, int speed)
{
#if     (defined __unix__ || defined __BEOS__) && !defined __MSDOS__
  init_conio ();
  if (register_func (deinit_conio) == -1)
    {
      fputs ("ERROR: Could not register function with register_func()\n", stderr);
      exit (1);
    }
#endif

  if (speed < 1 || speed > 4)
    speed = DEFAULT_SPEED;
  cyan_copy_rom (filename, speed, parport);

  return 0;
}


int
cmc_test (int test, unsigned short parport, int speed)
{
#if     (defined __unix__ || defined __BEOS__) && !defined __MSDOS__
  init_conio ();
  if (register_func (deinit_conio) == -1)
    {
      fputs ("ERROR: Could not register function with register_func()\n", stderr);
      exit (1);
    }
#endif

  if (test < 1 || test > 2)
    {
      fputs ("ERROR: Choose a test between 1 and 2 (inclusive)\n", stderr);
      exit (1);
    }
  if (speed < 1 || speed > 4)
    speed = DEFAULT_SPEED;
  cyan_test_copier (test, speed, parport);

  return 0;
}

#endif // USE_PARALLEL
