# Amiga File Transfer

Transfer files between Amiga and Linux, using a MIDI serial link.

# 1 Protocols

Data is packed into MIDI SysEx messages:

0xF0 0x7D data bytes go here 0xF7

Data bytes must have the highest bit clear, as per MIDI standard.

# 1.1 8

Payload not encoded in any way, only safe for 7bit data (eg ASCII text).

# 1.2 4

Each payload 8-bit byte split into two 4-bit nibbles, transmitted as bytes (MSB first). Simple to implement, but inefficient.

# 1.3 7

Each payload 8-bit byte is concatenated into a bitstream, which is split into 7-bit nuggets transmitted in order. Complex to implement, but efficient.

# 2 Prerequisites

# 2.1 Linux

  • MIDI interface and cables
  • amidi tool
  • C build system
  • optional: FS-UAE with VBCC or amiga-gcc (to compile C programs for Amiga)

# 2.2 Amiga

  • MIDI interface and cables
  • Blitz Basic 2

# 3 Bootstrap

  1. run make on Linux
  2. type in recv8.bb2 to Blitz Basic 2 on Amiga and compile it to recv8
  3. run recv8 on Amiga
  4. run ./send8.sh < recv4.bb2 on Linux
  5. move RAM:out to recv4.bb2 on Amiga and compile it to recv4
  6. run recv4 on Amiga
  7. run ./send4.sh < recv7 on Linux
  8. move RAM:out to recv7 on Amiga
  9. run recv7 10100 > send7 on Amiga
  10. run ./send7.sh < send7 on Linux

# 4 Usage

# 4.1 Sending from Linux to Amiga

  • run recv7 100100 > file on Amiga, change maximum size if necessary
  • run send7.sh < file on Linux

# 4.2 Sending from Amiga to Linux

  • run recv7.sh > file on Linux
  • run send7 file on Amiga

# 5 Bugs

  • no way to interrupt sending or receiving on the Amiga side
  • recv7 error messages go to output file instead of console
  • Linux scripts may need additional arguments (e.g. amidi -p hw:1,0,0)

# 6 Source

Download all: amiga-file-transfer.tar.gz

# 6.1 Amiga

# 6.1.1 recv8.bb2

WBStartup
ser.l = OpenSerial("serial.device", 0, 31250, 144)
SetSerialBuffer 0, 100100
out.l = WriteFile(1, "RAM:out")
Repeat
  c.w = ReadSerial(0)
Until c.w = $F0
c.w = ReadSerial(0)
c.w = ReadSerial(0)
While c.w <> $F7
  char.b = c.w
  WriteMem 1, &char.b, 1
  c.w = ReadSerial(0)
Wend
CloseFile 1
CloseSerial 0
End

# 6.1.2 recv4.bb2

WBStartup
ser.l = OpenSerial("serial.device", 0, 31250, 144)
SetSerialBuffer 0, 100100
out.l = WriteFile(1, "RAM:out")
Repeat
  c.w = ReadSerial(0)
Until c.w = $F0
c.w = ReadSerial(0)
c.w = ReadSerial(0)
While c.w <> $F7
  b.w = (c.w & $F) LSL 4
  c.w = ReadSerial(0)
  If c.w <> $F7
    b.w = (c.w & $F) | b.w
    char.b = b.w
    WriteMem 1, &char.b, 1
    c.w = ReadSerial(0)
  EndIf
Wend
CloseFile 1
CloseSerial 0
End

# 6.1.3 recv7.c

/*
  vc +kick13m -c99 -sc -sd -O2 -o recv7 recv7.c -lamigas
  recv7 100100 > path:to/file
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/alib_protos.h>
#include <dos/dos.h>
#include <devices/serial.h>

struct DosLibrary *DOSBase;

int atoi(const char *s)
{
  int n = 0;
  while (*s)
  {
    n = 10 * n + (*s++ - '0');
  }
  return n;
}

int strlen(const char *s)
{
  int n = 0;
  while (*s++)
  {
    n++;
  }
  return n;
}

LONG read(UBYTE *buffer, LONG bytes);
LONG decode(UBYTE *buffer, LONG bytes);

int main(char *args)
{
  int retval = 20;
  if ((DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 0)))
  {
    LONG bytes = atoi(args);
    LONG msgbytes = (((bytes << 3) + 6) / 7) + 3;
    UBYTE *buffer;
    if ((buffer = AllocMem(msgbytes, 0L)))
    {
      LONG inbytes = read(buffer, msgbytes);
      if (inbytes >= 3)
      {
        LONG outbytes = decode(buffer, inbytes);
        if (outbytes >= 0)
        {
          Write(Output(), buffer, outbytes);
          retval = 0;
        }
        else
        {
          Write(Output(), "decode\n", 7);
        }
      }
      else
      {
        Write(Output(), "read\n", 5);
      }
      FreeMem(buffer, msgbytes);
    }
    else
    {
      Write(Output(), "alloc\n", 6);
    }
    CloseLibrary((struct Library *) DOSBase);
  }
  return retval;
}

LONG decode(UBYTE *buffer, LONG bytes)
{
  UBYTE *msg = buffer;
  UBYTE *out = buffer;
  ULONG n = 0;
  do
  {
    if (n++ >= bytes)
    {
      Write(Output(), "start1\n", 7);
      return -1;
    }
  }
  while (*msg++ != (UBYTE)0xF0);
  if (n++ >= bytes)
  {
    return -1;
  }
  if (*msg++ == (UBYTE)0x7D)
  {
    UBYTE output = 0;
    BYTE shift = -1;
    while (n++ < bytes)
    {
      UBYTE input = *msg++;
      if (input == 0xF7)
      {
        break;
      }
      if (input & 0x80)
      {
        Write(Output(), "high\n", 5);
        return -1;
      }
      if (shift == -1)
      {
        output |= input << 1;
        shift = 6;
      }
      else
      {
        output |= input >> shift;
        *out++ = output;
        output = (input & ((1 << shift) - 1)) << (8 - shift);
        --shift;
      }
    }
    if (shift == 6)
    {
      *out++ = output;
    }
    return out - buffer;
  }
  else
  {
    Write(Output(), "start2\n", 7);
  }
  return -1;
}

LONG read(UBYTE *buffer, LONG bytes)
{
  LONG retval = -1;
  struct MsgPort *port;
  if ((port = CreatePort(NULL, 0)))
  {
    struct IOExtSer *io;
    if ((io = (struct IOExtSer *) CreateExtIO(port, sizeof(struct IOExtSer))))
    {
      if (0 == OpenDevice("serial.device", 0, (struct IORequest *) io, 0L))
      {
        struct IOTArray term = { 0xF7F7F7F7, 0xF7F7F7F7 };
        io->IOSer.io_Command = SDCMD_SETPARAMS;
        io->io_RBufLen = ((bytes + 63) >> 6) << 6;
        io->io_Baud = 31250;
        io->io_ReadLen = 8;
        io->io_StopBits = 1;
        io->io_TermArray = term;
        io->io_SerFlags = SERF_EOFMODE | SERF_XDISABLED | SERF_RAD_BOOGIE;
        if (0 == DoIO((struct IORequest *) io))
        {
          io->IOSer.io_Command = CMD_READ;
          io->IOSer.io_Length = bytes;
          io->IOSer.io_Data = buffer;
          if (0 == DoIO((struct IORequest *) io))
          {
            retval = io->IOSer.io_Actual;
          }
          else
          {
            Write(Output(), "CMD_READ\n", 9);
          }
        }
        else
        {
          Write(Output(), "SDCMD_SETPARAMS\n", 16);
        }
        CloseDevice((struct IORequest *) io);
      }
      else
      {
        Write(Output(), "OpenDevice\n", 11);
      }
      DeleteExtIO((struct IORequest *) io);
    }
    else
    {
      Write(Output(), "ExtIO\n", 6);
    }
    DeletePort(port);
  }
  else
  {
    Write(Output(), "Port\n", 5);
  }
  return retval;
}

# 6.1.4 send7.c

/*
  vc +kick13m -c99 -sc -sd -O2 -o send7 send7.c -lamigas
  send7 path:to/file
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/alib_protos.h>
#include <dos/dos.h>
#include <devices/serial.h>

struct DosLibrary *DOSBase;

int strlen(char *s)
{
  int n = 0;
  while (*s++)
  {
    n++;
  }
  return n;
}

int main(char *filename)
{
  int retval = 20;
  if ((DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 0)))
  {
    BPTR lock;
    if ((lock = Lock(filename, ACCESS_READ)))
    {
      struct FileInfoBlock fib;
      if (Examine(lock, &fib))
      {
        if (fib.fib_DirEntryType < 0) /* is a file */
        {
          LONG inbytes = fib.fib_Size;
          LONG msgbytes = (((inbytes << 3) + 6) / 7) + 3;
          BPTR file;
          if ((file = Open(filename, MODE_OLDFILE)))
          {
            UBYTE *buffer;
            if ((buffer = AllocMem(msgbytes, 0L)))
            {
              /*
                  read file into end part of larger message buffer
                  ensyx and encode7 into earlier part of buffer
              */
              UBYTE *msg = buffer;
              UBYTE *in = msg + (msgbytes - inbytes);
              if (inbytes == Read(file, in, inbytes))
              {
                *msg++ = 0xF0;
                *msg++ = 0x7D;
                UBYTE output = 0;
                UBYTE shift = 1;
                for (LONG i = 0; i < inbytes; ++i)
                {
                  UBYTE input = *in++;
                  output |= (input >> shift);
                  *msg++ = output;
                  output = (input & ((1 << shift) - 1)) << (7 - shift);
                  if (++shift == 8)
                  {
                    *msg++ = output;
                    shift = 1;
                    output = 0;
                  }
                }
                if (shift != 1)
                {
                  *msg++ = output;
                }
              }
              *msg++ = 0xF7;
              /*
                  send buffer via MIDI
              */
              struct MsgPort *port;
              if ((port = CreatePort(NULL, 0)))
              {
                struct IOExtSer *io;
                if ((io = (struct IOExtSer *) CreateExtIO(port, sizeof(struct IOExtSer))))
                {
                  if (0 == OpenDevice("serial.device", 0, (struct IORequest *) io, 0L))
                  {
                    io->IOSer.io_Command = SDCMD_SETPARAMS;
                    io->io_Baud = 31250;
                    io->io_ReadLen = 8;
                    io->io_StopBits = 1;
                    io->io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE;
                    if (0 == DoIO((struct IORequest *) io))
                    {
                      BPTR out = Output();
                      Write(out, "Sending '", 9);
                      Write(out, filename, strlen(filename));
                      Write(out, "'... ", 5);
                      io->IOSer.io_Command = CMD_WRITE;
                      io->IOSer.io_Length = msgbytes;
                      io->IOSer.io_Data = buffer;
                      if (0 == DoIO((struct IORequest *) io))
                      {
                        Write(out, "OK!\n", 4);
                        retval = 0;
                      }
                      else
                      {
                        Write(out, "FAILED!\n", 8);
                      }
                    }
                    CloseDevice((struct IORequest *) io);
                  }
                  DeleteExtIO((struct IORequest *) io);
                }
                DeletePort(port);
              }
              FreeMem(buffer, msgbytes);
            }
            Close(file);
          }
        }
      }
      UnLock(lock);
    }
    CloseLibrary((struct Library *) DOSBase);
  }
  return retval;
}

# 6.2 Linux

# 6.2.1 ensyx.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  putchar(0xF0);
  putchar(0x7D);
  while (EOF != (input = getchar()))
  {
    if (input & 0x80)
    {
      fprintf(stderr, "ensyx: warning: high bit ignored\n");
      input &= 0x7F;
    }
    putchar(input);
  }
  putchar(0xF7);
  return 0;
}

# 6.2.2 encode4.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  while (EOF != (input = getchar()))
  {
    putchar((input & 0xF0) >> 4);
    putchar(input & 0x0F);
  }
  return 0;
}

# 6.2.3 encode7.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  int output = 0;
  int shift = 1;
  while (EOF != (input = getchar()))
  {
    output |= (input >> shift);
    putchar(output);
    output = (input & ((1 << shift) - 1)) << (7 - shift);
    if (++shift == 8)
    {
      putchar(output);
      shift = 1;
      output = 0;
    }
  }
  if (shift != 1)
  {
    putchar(output);
  }
  return 0;
}

# 6.2.4 send8.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
"${ME}/ensyx" > "${TEMPFILE}"
amidi -s "${TEMPFILE}"
rm -f "${TEMPFILE}"

# 6.2.5 send4.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
"${ME}/encode4" | "${ME}/ensyx" > "${TEMPFILE}"
amidi -s "${TEMPFILE}"
rm -f "${TEMPFILE}"

# 6.2.6 send7.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
"${ME}/encode7" | "${ME}/ensyx" > "${TEMPFILE}"
amidi -s "${TEMPFILE}"
rm -f "${TEMPFILE}"

# 6.2.7 desyx.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  if (0xF0 == getchar())
  {
    if (0x7D == getchar())
    {
      while (0xF7 != (input = getchar()))
      {
        if (input == EOF)
        {
          fprintf(stderr, "desyx: unexpected EOF\n");
          return 1;
        }
        putchar(input);
      }
      return 0;
    }
  }
  return 1;
}

# 6.2.8 decode4.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  int output = 0;
  int shift = 4;
  while (EOF != (input = getchar()))
  {
    if (input & 0xF0)
    {
      fprintf(stderr, "decode4: warning: high nibble ignored\n");
      input &= 0x0F;
    }
    output |= input << shift;
    if (shift == 0)
    {
      putchar(output);
      output = 0;
    }
    shift = 4 - shift;
  }
  return 0;
}

# 6.2.9 decode7.c

#include <stdio.h>

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  int input;
  int output = 0;
  int shift = -1;
  while (EOF != (input = getchar()))
  {
    if (input & 0x80)
    {
      fprintf(stderr, "decode7: warning: high bit ignored\n");
      input &= 0x7F;
    }
    if (shift == -1)
    {
      output |= input << 1;
      shift = 6;
    }
    else
    {
      output |= input >> shift;
      putchar(output);
      output = (input & ((1 << shift) - 1)) << (8 - shift);
      --shift;
    }
  }
  if (shift == 6)
  {
    putchar(output);
  }
  return 0;
}

# 6.2.10 recv8.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
amidi -r "${TEMPFILE}" -t 10
"${ME}/desyx" < "${TEMPFILE}"
rm -f "${TEMPFILE}"

# 6.2.11 recv4.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
amidi -r "${TEMPFILE}" -t 10
"${ME}/desyx" < "${TEMPFILE}" | "${ME}/decode4"
rm -f "${TEMPFILE}"

# 6.2.12 recv7.sh

#!/bin/sh
ME="$(dirname "$(readlink -e "${0}")")"
TEMPFILE="$(mktemp)"
amidi -r "${TEMPFILE}" -t 10
"${ME}/desyx" < "${TEMPFILE}" | "${ME}/decode7"
rm -f "${TEMPFILE}"