diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ca6cb64f..9aa4bcdb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -225,6 +225,9 @@ add_library(libavrdude
     updi_readwrite.h
     updi_state.c
     updi_state.h
+    urclock.c
+    urclock.h
+    urclock_private.h
     usbasp.c
     usbasp.h
     usbdevs.h
diff --git a/src/Makefile.am b/src/Makefile.am
index 61b93dc5..50e24d4f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -180,6 +180,9 @@ libavrdude_a_SOURCES = \
 	updi_readwrite.h \
 	updi_nvm.c \
 	updi_nvm.h \
+	urclock.c \
+	urclock.h \
+	urclock_private.h \
 	usbdevs.h \
 	usb_hidapi.c \
 	usb_libusb.c \
diff --git a/src/avr.c b/src/avr.c
index 9352a2b6..dee653c0 100644
--- a/src/avr.c
+++ b/src/avr.c
@@ -434,7 +434,7 @@ int avr_read_mem(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, con
     }
     if (!failure)
       return avr_mem_hiaddr(mem);
-    /* else: fall back to byte-at-a-time write, for historical reasons */
+    /* else: fall back to byte-at-a-time read, for historical reasons */
   }
 
   if (strcmp(mem->desc, "signature") == 0) {
@@ -1088,8 +1088,7 @@ int compare_memory_masked(AVRMEM * m, uint8_t b1, uint8_t b2) {
  *
  * Return the number of bytes verified, or -1 if they don't match.
  */
-int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int size)
-{
+int avr_verify(const PROGRAMMER *pgm, const AVRPART *p, const AVRPART *v, const char *memtype, int size) {
   int i;
   unsigned char * buf1, * buf2;
   int vsize;
@@ -1118,14 +1117,34 @@ int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int s
     size = vsize;
   }
 
+  int verror = 0, vroerror = 0, maxerrs = verbose >= MSG_DEBUG? size+1: 10;
   for (i=0; i<size; i++) {
     if ((b->tags[i] & TAG_ALLOCATED) != 0 && buf1[i] != buf2[i]) {
       uint8_t bitmask = get_fuse_bitmask(a);
-      if((buf1[i] & bitmask) != (buf2[i] & bitmask)) {
+      if(pgm->readonly && pgm->readonly(pgm, p, a, i)) {
+        if(quell_progress < 2) {
+          if(vroerror < 10) {
+            if(!(verror + vroerror))
+              pmsg_warning("verification mismatch%s\n",
+                avr_mem_is_flash_type(a)? " in r/o areas, expected for vectors and/or bootloader": "");
+            imsg_warning("device 0x%02x != input 0x%02x at addr 0x%04x (read only location)\n",
+              buf1[i], buf2[i], i);
+          } else if(vroerror == 10)
+            imsg_warning("suppressing further mismatches in read-only areas\n");
+        }
+        vroerror++;
+      } else if((buf1[i] & bitmask) != (buf2[i] & bitmask)) {
         // Mismatch is not just in unused bits
-        pmsg_error("verification mismatch, first encountered at addr 0x%04x\n", i);
-        imsg_error("device 0x%02x != input 0x%02x\n", buf1[i], buf2[i]);
-        return -1;
+        if(verror < maxerrs) {
+          if(!(verror + vroerror))
+            pmsg_warning("verification mismatch\n");
+          imsg_error("device 0x%02x != input 0x%02x at addr 0x%04x (error)\n", buf1[i], buf2[i], i);
+        } else if(verror == maxerrs) {
+          imsg_warning("suppressing further verification errors\n");
+        }
+        verror++;
+        if(verbose < 1)
+          return -1;
       } else {
         // Mismatch is only in unused bits
         if ((buf1[i] | bitmask) != 0xff) {
@@ -1143,7 +1162,7 @@ int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int s
     }
   }
 
-  return size;
+  return verror? -1: size;
 }
 
 
diff --git a/src/avrcache.c b/src/avrcache.c
index a0fa9082..cca15483 100644
--- a/src/avrcache.c
+++ b/src/avrcache.c
@@ -204,23 +204,6 @@ int avr_is_and(const unsigned char *s1, const unsigned char *s2, const unsigned
 }
 
 
-static int initCache(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p) {
-  AVRMEM *basemem = avr_locate_mem(p, cp == pgm->cp_flash? "flash": "eeprom");
-
-  if(!basemem || !avr_has_paged_access(pgm, basemem))
-    return LIBAVRDUDE_GENERAL_FAILURE;
-
-  cp->size = basemem->size;
-  cp->page_size = basemem->page_size;
-  cp->offset = basemem->offset;
-  cp->cont = cfg_malloc("initCache()", cp->size);
-  cp->copy = cfg_malloc("initCache()", cp->size);
-  cp->iscached = cfg_malloc("initCache()", cp->size/cp->page_size);
-
-  return LIBAVRDUDE_SUCCESS;
-}
-
-
 static int cacheAddress(int addr, const AVR_Cache *cp, const AVRMEM *mem) {
   int cacheaddr = addr + (int) (mem->offset - cp->offset);
 
@@ -261,6 +244,29 @@ static int loadCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p,
 }
 
 
+static int initCache(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p) {
+  AVRMEM *basemem = avr_locate_mem(p, cp == pgm->cp_flash? "flash": "eeprom");
+
+  if(!basemem || !avr_has_paged_access(pgm, basemem))
+    return LIBAVRDUDE_GENERAL_FAILURE;
+
+  cp->size = basemem->size;
+  cp->page_size = basemem->page_size;
+  cp->offset = basemem->offset;
+  cp->cont = cfg_malloc("initCache()", cp->size);
+  cp->copy = cfg_malloc("initCache()", cp->size);
+  cp->iscached = cfg_malloc("initCache()", cp->size/cp->page_size);
+
+  if((pgm->prog_modes & PM_SPM) && avr_mem_is_flash_type(basemem)) { // Could be vector bootloader
+    // Caching the vector page gives control to the progammer that then can patch the reset vector
+    if(loadCachePage(cp, pgm, p, basemem, 0, 0, 0) < 0)
+      return LIBAVRDUDE_GENERAL_FAILURE;
+  }
+
+  return LIBAVRDUDE_SUCCESS;
+}
+
+
 static int writeCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int base, int nlOnErr) {
   // Write modified page cont to device; if unsuccessful try bytewise access
   if(avr_write_page_default(pgm, p, mem, base, cp->cont + base) < 0) {
@@ -597,11 +603,15 @@ int avr_read_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *
  *  - Used if paged routines available and if memory is EEPROM or flash
  *  - Otherwise fall back to pgm->write_byte()
  *  - Out of memory addr: synchronise cache with device and return whether successful
+ *  - If programmer indicates a readonly spot, return LIBAVRDUDE_SOFTFAIL
  *  - Cache is automagically created and initialised if needed
  */
 int avr_write_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
   unsigned long addr, unsigned char data) {
 
+  if(pgm->readonly && pgm->readonly(pgm, p, mem, addr))
+    return LIBAVRDUDE_SOFTFAIL;
+
   // Use pgm->write_byte() if not EEPROM/flash or no paged access
   if(!avr_has_paged_access(pgm, mem))
     return fallback_write_byte(pgm, p, mem, addr, data);
@@ -636,9 +646,10 @@ int avr_chip_erase_cached(const PROGRAMMER *pgm, const AVRPART *p) {
     { avr_locate_mem(p, "flash"), pgm->cp_flash, 1 },
     { avr_locate_mem(p, "eeprom"), pgm->cp_eeprom, 0 },
   };
+  int rc;
 
-  if(pgm->chip_erase(pgm, p) < 0)
-    return LIBAVRDUDE_GENERAL_FAILURE;
+  if((rc = pgm->chip_erase(pgm, p)) < 0)
+    return rc;
 
   for(size_t i = 0; i < sizeof mems/sizeof*mems; i++) {
     AVRMEM *mem = mems[i].mem;
diff --git a/src/avrdude.1 b/src/avrdude.1
index a2225a1a..0e3b8e90 100644
--- a/src/avrdude.1
+++ b/src/avrdude.1
@@ -147,9 +147,17 @@ programming mode.  The programmer type is ``wiring''.  Note that the -D option
 will likely be required in this case, because the bootloader will rewrite the
 program memory, but no true chip erase can be performed.
 .Pp
-The Arduino (which is very similar to the STK500 1.x) is supported via
-its own programmer type specification ``arduino''.  This programmer works for
-the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader.
+Serial bootloaders that run a skeleton of the STK500 1.x protocol are
+supported via their own programmer type ``arduino''.  This programmer works
+for the Arduino Uno Rev3 or any AVR that runs the Optiboot bootloader.
+.Pp
+Urprotocol is a leaner version of the STK500 1.x protocol that is designed
+to be backwards compatible with STK500 v1.x, and allows bootloaders to be
+much smaller, eg, as implemented in the urboot project
+https://github.com/stefanrueger/urboot. The programmer type ``urclock''
+caters for these urboot programmers. Owing to its backward compatibility,
+any bootloader that can be served by the arduino programmer can normally
+also be served by the urclock programmer.
 .Pp
 The BusPirate is a versatile tool that can also be used as an AVR programmer.
 A single BusPirate can be connected to up to 3 independent AVRs. See
@@ -397,8 +405,8 @@ programming requires the memory be erased to 0xFF beforehand.
 .Fl A
 should be used when the programmer hardware, or bootloader
 software for that matter, does not carry out chip erase and
-instead handles the memory erase on a page level. The popular
-Arduino bootloader exhibits this behaviour; for this reason
+instead handles the memory erase on a page level. Popular
+Arduino bootloaders exhibit this behaviour; for this reason
 .Fl A
 is engaged by default when specifying
 . Fl c
@@ -432,7 +440,7 @@ contents would exclusively cause bits to be programmed from the value
 .Ql 1
 to
 .Ql 0 .
-Note that in order to reprogram EERPOM cells, no explicit prior chip
+Note that in order to reprogram EEPROM cells, no explicit prior chip
 erase is required since the MCU provides an auto-erase cycle in that
 case before programming the cell.
 .It Xo Fl E Ar exitspec Ns
@@ -1141,6 +1149,106 @@ programmer creates errors during initial sequence.
 Specify how many connection retry attemps to perform before exiting.
 Defaults to 10 if not specified.
 .El
+.It Ar Urclock
+.Bl -tag -offset indent -width indent
+.It Ar showall
+Show all info for the connected part, then exit. The -xshow... options
+below can be used to assemble a bespoke response consisting of a subset
+(or only one item) of all available relevant information about the
+connected part and bootloader.
+.It Ar showid
+Show a unique Urclock ID stored in either flash or EEPROM of the MCU, then exit.
+.It Ar id=<E|F>.<addr>.<len>
+Historically, the Urclock ID was a six-byte unique little-endian number
+stored in Urclock boards at EEPROM address 257. The location of this
+number can be set by the -xid=<E|F>.<addr>.<len> extended parameter. E
+stands for EEPROM and F stands for flash. A negative address addr counts
+from the end of EEPROM and flash, respectively. The length len of the
+Urclock ID can be between 1 and 8 bytes.
+.It Ar showdate
+Show the last-modified date of the input file for the flash application,
+then exit. If the input file was stdin, the date will be that of the
+programming.
+.It Ar showfilename
+Show the input filename (or title) of the last flash writing session, then exit.
+.It Ar title=<string>
+When set, <string> will be used in lieu of the input filename. The maximum
+string length for the title/filename field is 254 bytes including
+terminating nul.
+.It Ar showapp
+Show the size of the programmed application, then exit.
+.It Ar showstore
+Show the size of the unused flash between the application and metadata, then exit.
+.It Ar showmeta
+Show the size of the metadata just below the bootloader, then exit.
+.It Ar showboot
+Show the size of the bootloader, then exit.
+.It Ar showversion
+Show bootloader version and capabilities, then exit.
+.It Ar showvector
+Show the vector number and name of the interrupt table vector used by the
+bootloader for starting the application, then exit. For hardware-supported
+bootloaders this will be vector 0 (Reset), and for vector bootloaders this
+will be any other vector number of the interrupt vector table or the slot
+just behind the vector table with the name VBL_ADDITIONAL_VECTOR.
+.It Ar showpart
+Show the part for which the bootloader was compiled, then exit.
+.It Ar bootsize=<size>
+Manual override for bootloader size. Urboot bootloaders put the number of used
+bootloader pages into a table at the top of flash, so the urclock programmer can
+look up the bootloader size itself. In backward-compatibility mode, when programming
+via other bootloaders, this option can be used to tell the programmer the
+size, and therefore the location, of the bootloader.
+.It Ar vectornum=<arg>
+Manual override for vector number. Urboot bootloaders put the vector
+number used by a vector bootloader into a table at the top of flash, so
+this option is normally not needed for urboot bootloaders. However, it is
+useful in backward-compatibility mode (or when the urboot bootloader does
+not offer flash read). Specifying a vector number in these circumstances
+implies a vector bootloader whilst the default assumption would be a
+hardware-supported bootloader.
+.It Ar eepromrw
+Manual override for asserting EEPROM read/write capability. Not normally
+needed for urboot bootloaders, but useful for in backward-compatibility
+mode if the bootloader offers EEPROM read/write.
+.It Ar emulate_ce
+If an urboot bootloader does not offer a chip erase command it will tell
+the urclock programmer so during handshake. In this case the urclock
+programmer emulates a chip erase, if warranted by user command line
+options, by filling the remainder of unused flash below the bootloader
+with 0xff. If this option is specified, the urclock programmer will assume
+that the bootloader cannot erase the chip itself. The option is useful
+for backwards-compatible bootloaders that do not implement chip erase.
+.It Ar restore
+Upload unchanged flash input files and trim below the bootloader if
+needed. This is most useful when one has a backup of the full flash and
+wants to play that back onto the device. No metadata are written in this
+case and no vector patching happens either if it is a vector bootloader.
+However, for vector bootloaders, even under the option -xrestore an
+input file will not be uploaded for which the reset vector does not point
+to the vector bootloader. This is to avoid writing an input file to the
+device that would render the vector bootloader not functional as it would
+not be reached after reset.
+.It Ar initstore
+On writing to flash fill the store space between the flash application and
+the metadata section with 0xff.
+.It Ar nofilename
+On writing to flash do not store the application input filename (nor a title).
+.It Ar nodate
+On writing to flash do not store the application input filename (nor a
+title) and no date either.
+.It Ar nometadata
+On writing to flash do not store any metadata. The full flash below the
+bootloader is available for the application. In particular, no data store
+frame is programmed.
+.It Ar delay=<n>
+Add a <n> ms delay after reset. This can be useful if a board takes a
+particularly long time to exit from external reset. <n> can be negative,
+in which case the default 80 ms delay after issuing reset will be
+shortened accordingly.
+.It Ar help
+Show this help menu and exit
+.El
 .It Ar buspirate
 .Bl -tag -offset indent -width indent
 .It Ar reset={cs,aux,aux2}
diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in
index 9cec45a0..5d277349 100644
--- a/src/avrdude.conf.in
+++ b/src/avrdude.conf.in
@@ -71,7 +71,7 @@
 #       prog_modes       = PM_<i/f> {| PM_<i/f>}  # interfaces, eg, PM_SPM|PM_ISP|PM_HVPP|PM_debugWIRE
 #       mcuid            = <num>;                 # unique id in 0..2039 for 8-bit AVRs
 #       n_interrupts     = <num>;                 # number of interrupts, used for vector bootloaders
-#       n_page_erase     = <num>;                 # if set, number of pages erased during NVM erase
+#       n_page_erase     = <num>;                 # if set, number of pages erased during SPM erase
 #       hvupdi_variant   = <num> ;                # numeric -1 (n/a) or 0..2
 #       devicecode       = <num> ;                # deprecated, use stk500_devcode
 #       stk500_devcode   = <num> ;                # numeric
@@ -756,6 +756,19 @@ programmer
     connection_type        = serial;
 ;
 
+#------------------------------------------------------------
+# urclock
+#------------------------------------------------------------
+
+# See https://github.com/stefanrueger/urboot
+programmer
+    id                     = "urclock";
+    desc                   = "Urclock programmer for urboot bootloaders using urprotocol";
+    type                   = "urclock";
+    prog_modes             = PM_SPM;
+    connection_type        = serial;
+;
+
 #------------------------------------------------------------
 # xbee
 #------------------------------------------------------------
diff --git a/src/avrintel.c b/src/avrintel.c
index 293362c4..a74d964e 100644
--- a/src/avrintel.c
+++ b/src/avrintel.c
@@ -6,10 +6,10 @@
  * Atmel AVR8L, AVR8, XMEGA and AVR8X family description of interrupts and more
  *
  * published under GNU General Public License, version 3 (GPL-3.0)
- * meta-author: Stefan Rueger <stefan.rueger@urclocks.com>
+ * meta-author Stefan Rueger <stefan.rueger@urclocks.com>
  *
  * v 1.1
- * 30.08.2022
+ * 20.11.2022
  *
  */
 
@@ -256,9 +256,9 @@ const uPcore_t uP_table[] = {   // Value of -1 typically means unknown
   {"ATA8515",          224,  F_AVR8, {0x1E, 0x95, 0x63},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42,    vtab_ata8515}, // atdf
   {"ATA664251",        225,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20,  vtab_attiny167}, // atdf, avr-gcc 12.2.0
   {"M3000",            226,  F_AVR8, {0xff,   -1,   -1},       0, 0x10000,    -1, -1,     -1,      -1,     -1, -1, 0x1000, 0x1000, -1, -1,   0,            NULL}, // avr-gcc 12.2.0
-  {"LGT8F88P",         227,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040, -1,     -1,       0, 0x0200,  4,     -1,     -1, -1, -1,   0,            NULL}, // avrdude
-  {"LGT8F168P",        228,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080, -1,     -1,       0, 0x0200,  4,     -1,     -1, -1, -1,   0,            NULL}, // avrdude
-  {"LGT8F328P",        229,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080, -1,     -1,       0, 0x0400,  4,     -1,     -1, -1, -1,   0,            NULL}, // avrdude
+  {"LGT8F88P",         227,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26, vtab_atmega328p}, // avrdude, from ATmega88
+  {"LGT8F168P",        228,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26, vtab_atmega328p}, // avrdude, from ATmega168P
+  {"LGT8F328P",        229,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26, vtab_atmega328p}, // avrdude, from ATmega328P
 
   {"ATxmega8E5",       230, F_XMEGA, {0x1E, 0x93, 0x41},       0, 0x02800, 0x080,  1, 0x0800,       0, 0x0200, 32, 0x2000, 0x0400,  7,  1,  43, vtab_atxmega32e5}, // atdf, avr-gcc 12.2.0, avrdude
   {"ATxmega16A4",      231, F_XMEGA, {0x1E, 0x94, 0x41},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1,  94, vtab_atxmega32a4}, // atdf, avr-gcc 12.2.0, avrdude
diff --git a/src/avrintel.h b/src/avrintel.h
index 77147aad..0ab72822 100644
--- a/src/avrintel.h
+++ b/src/avrintel.h
@@ -9,7 +9,7 @@
  * meta-author Stefan Rueger <stefan.rueger@urclocks.com>
  *
  * v 1.1
- * 30.08.2022
+ * 20.11.2022
  *
  */
 
@@ -770,7 +770,7 @@ typedef struct {                // Value of -1 typically means unknown
 #define vts_avr128da64       64
 #define vts_avr128db64       65
 
-// Suggested vector bootloader interrupt number (first unused vector or, failing that, slot just above vector table)
+// Suggested vector bootloader interrupt: first unused vector or slot just above vector table
 #define vbu_attiny4          10
 #define vbu_attiny5          11
 #define vbu_attiny9          10
diff --git a/src/config.c b/src/config.c
index efb33a5a..a0cf35cf 100644
--- a/src/config.c
+++ b/src/config.c
@@ -704,7 +704,7 @@ char *cfg_escape(const char *s) {
   char buf[50*1024], *d = buf;
 
   *d++ = '"';
-  for(; *s && d-buf < sizeof buf-7; s++) {
+  for(; *s && d-buf < (long) sizeof buf-7; s++) {
     switch(*s) {
     case '\n':
       *d++ = '\\'; *d++ = 'n';
@@ -855,7 +855,7 @@ void cfg_update_mcuid(AVRPART *part) {
     return;
 
   // Find an entry that shares the same name, overwrite mcuid with known, existing mcuid
-  for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++) {
+  for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) {
     if(strcasecmp(part->desc, uP_table[i].name) == 0) {
       if(part->mcuid != (int) uP_table[i].mcuid) {
         if(part->mcuid >= 0 && verbose >= MSG_DEBUG)
@@ -867,7 +867,7 @@ void cfg_update_mcuid(AVRPART *part) {
   }
 
   // None have the same name: an entry with part->mcuid might be an error
-  for(int i=0; i < sizeof uP_table/sizeof *uP_table; i++)
+  for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++)
     if(part->mcuid == (int) uP_table[i].mcuid) {
       // Complain unless it can be considered a variant, eg, ATmega32L and ATmega32
       AVRMEM *flash = avr_locate_mem(part, "flash");
@@ -876,7 +876,7 @@ void cfg_update_mcuid(AVRPART *part) {
         if(strncasecmp(part->desc, uP_table[i].name, l1 < l2? l1: l2) ||
             flash->size != uP_table[i].flashsize ||
             flash->page_size != uP_table[i].pagesize ||
-            part->n_interrupts != uP_table[i].ninterrupts)
+            part->n_interrupts != (int8_t) uP_table[i].ninterrupts)
           yywarning("mcuid %d is reserved for %s, use a free number >= %d",
             part->mcuid, uP_table[i].name, sizeof uP_table/sizeof *uP_table);
       }
diff --git a/src/developer_opts.c b/src/developer_opts.c
index 54a4b0ef..b6fd722e 100644
--- a/src/developer_opts.c
+++ b/src/developer_opts.c
@@ -1152,9 +1152,9 @@ static void dev_pgm_raw(const PROGRAMMER *pgm) {
     dev_raw_dump(dp.usbproduct, strlen(dp.usbproduct)+1, id, "usbprod", 0);
 
   // Zap all bytes beyond terminating nul of desc, type and port array
-  if((len = strlen(dp.type)+1) < sizeof dp.type)
+  if((len = (int) strlen(dp.type)+1) < (int) sizeof dp.type)
     memset(dp.type + len, 0, sizeof dp.type - len);
-  if((len = strlen(dp.port)+1) < sizeof dp.port)
+  if((len = (int) strlen(dp.port)+1) < (int) sizeof dp.port)
     memset(dp.port + len, 0, sizeof dp.port - len);
 
   // Zap address values
diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi
index cb8bd103..04183324 100644
--- a/src/doc/avrdude.texi
+++ b/src/doc/avrdude.texi
@@ -1860,7 +1860,7 @@ part
     prog_modes       = PM_<i/f> @{| PM_<i/f>@}  # interfaces, eg, PM_SPM|PM_ISP|PM_HVPP|PM_debugWIRE
     mcuid            = <num>;                 # unique id in 0..2039 for 8-bit AVRs
     n_interrupts     = <num>;                 # number of interrupts, used for vector bootloaders
-    n_page_erase     = <num>;                 # if set, number of pages erased during NVM erase
+    n_page_erase     = <num>;                 # if set, number of pages erased during SPM erase
     hvupdi_variant   = <num> ;                # numeric -1 (n/a) or 0..2
     devicecode       = <num> ;                # deprecated, use stk500_devcode
     stk500_devcode   = <num> ;                # numeric
diff --git a/src/libavrdude.h b/src/libavrdude.h
index 38bc1f42..83556af3 100644
--- a/src/libavrdude.h
+++ b/src/libavrdude.h
@@ -580,6 +580,7 @@ const char * pinmask_to_str(const pinmask_t * const pinmask);
    The target file will be selected at configure time. */
 
 extern long serial_recv_timeout;  /* ms */
+extern long serial_drain_timeout; /* ms */
 
 union filedescriptor
 {
@@ -798,6 +799,7 @@ typedef struct programmer_t {
   int  (*parseextparams) (const struct programmer_t *pgm, const LISTID xparams);
   void (*setup)          (struct programmer_t *pgm);
   void (*teardown)       (struct programmer_t *pgm);
+  int  (*flash_readhook) (const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *flm, const char *fname, int size);
   // Cached r/w API for terminal reads/writes
   int (*write_byte_cached)(const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m,
                           unsigned long addr, unsigned char value);
@@ -806,6 +808,8 @@ typedef struct programmer_t {
   int (*chip_erase_cached)(const struct programmer_t *pgm, const AVRPART *p);
   int (*page_erase_cached)(const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m,
                           unsigned int baseaddr);
+  int (*readonly)        (const struct programmer_t *pgm, const AVRPART *p, const AVRMEM *m,
+                          unsigned int addr);
   int (*flush_cache)     (const struct programmer_t *pgm, const AVRPART *p);
   int (*reset_cache)     (const struct programmer_t *pgm, const AVRPART *p);
   AVR_Cache *cp_flash, *cp_eeprom;
@@ -885,7 +889,7 @@ int avr_write(const PROGRAMMER *pgm, const AVRPART *p, const char *memtype, int
 
 int avr_signature(const PROGRAMMER *pgm, const AVRPART *p);
 
-int avr_verify(const AVRPART * p, const AVRPART * v, const char * memtype, int size);
+int avr_verify(const PROGRAMMER *pgm, const AVRPART *p, const AVRPART *v, const char *m, int size);
 
 int avr_get_cycle_count(const PROGRAMMER *pgm, const AVRPART *p, int *cycles);
 
diff --git a/src/main.c b/src/main.c
index 69db5c7c..5fd65f6a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1393,9 +1393,12 @@ int main(int argc, char * argv [])
     if (uflags & UF_NOWRITE) {
       pmsg_warning("conflicting -e and -n options specified, NOT erasing chip\n");
     } else {
-      msg_info("erasing chip\n");
+      pmsg_info("erasing chip\n");
       exitrc = avr_chip_erase(pgm, p);
-      if(exitrc)
+      if(exitrc == LIBAVRDUDE_SOFTFAIL) {
+        imsg_info("delaying chip erase until first -U upload to flash\n");
+        exitrc = 1;
+      } else if(exitrc)
         goto main_exit;
     }
   }
diff --git a/src/pgm.c b/src/pgm.c
index ccded0cc..dca0ea87 100644
--- a/src/pgm.c
+++ b/src/pgm.c
@@ -119,12 +119,12 @@ PROGRAMMER *pgm_new(void) {
   pgm->err_led        = pgm_default_led;
   pgm->pgm_led        = pgm_default_led;
   pgm->vfy_led        = pgm_default_led;
-  pgm->read_byte_cached = avr_read_byte_cached;
+  pgm->read_byte_cached  = avr_read_byte_cached;
   pgm->write_byte_cached = avr_write_byte_cached;
   pgm->chip_erase_cached = avr_chip_erase_cached;
   pgm->page_erase_cached = avr_page_erase_cached;
   pgm->flush_cache    = avr_flush_cache;
-  pgm->reset_cache = avr_reset_cache;
+  pgm->reset_cache    = avr_reset_cache;
 
   /*
    * optional functions - these are checked to make sure they are
@@ -154,6 +154,8 @@ PROGRAMMER *pgm_new(void) {
   pgm->parseextparams = NULL;
   pgm->setup          = NULL;
   pgm->teardown       = NULL;
+  pgm->readonly       = NULL;
+  pgm->flash_readhook = NULL;
 
   // For allocating "global" memory by the programmer
   pgm->cookie          = NULL;
diff --git a/src/pgm_type.c b/src/pgm_type.c
index 45ce8dca..f14b065b 100644
--- a/src/pgm_type.c
+++ b/src/pgm_type.c
@@ -51,6 +51,7 @@
 #include "stk500generic.h"
 #include "stk500v2.h"
 #include "teensy.h"
+#include "urclock.h"
 #include "usbasp.h"
 #include "usbtiny.h"
 #include "wiring.h"
@@ -102,6 +103,7 @@ const PROGRAMMER_TYPE programmers_types[] = { // Name(s) the programmers call th
   {"stk600hvsp", stk600hvsp_initpgm, stk600hvsp_desc}, // "STK600HVSP"
   {"stk600pp", stk600pp_initpgm, stk600pp_desc}, // "STK600PP"
   {"teensy", teensy_initpgm, teensy_desc}, // "teensy"
+  {"urclock", urclock_initpgm, urclock_desc}, // "Urclock"
   {"usbasp", usbasp_initpgm, usbasp_desc}, // "usbasp"
   {"usbtiny", usbtiny_initpgm, usbtiny_desc}, // "USBtiny" or "usbtiny"
   {"wiring", wiring_initpgm, wiring_desc}, // "Wiring"
@@ -171,5 +173,3 @@ void walk_programmer_types(walk_programmer_types_cb cb, void *cookie)
     cb(p->id, p->desc, cookie);
   }
 }
-
-
diff --git a/src/ser_posix.c b/src/ser_posix.c
index ecc25baf..5f1a7eae 100644
--- a/src/ser_posix.c
+++ b/src/ser_posix.c
@@ -52,6 +52,7 @@
 #include "libavrdude.h"
 
 long serial_recv_timeout = 5000; /* ms */
+long serial_drain_timeout = 250; /* ms */
 
 struct baud_mapping {
   long baud;
@@ -550,7 +551,7 @@ static int ser_drain(const union filedescriptor *fd, int display) {
   unsigned char buf;
 
   timeout.tv_sec = 0;
-  timeout.tv_usec = 250000;
+  timeout.tv_usec = serial_drain_timeout*1000L;
 
   if (display) {
     msg_info("drain>");
diff --git a/src/ser_win32.c b/src/ser_win32.c
index 6ed44646..16df6fe9 100644
--- a/src/ser_win32.c
+++ b/src/ser_win32.c
@@ -37,6 +37,7 @@
 #include "libavrdude.h"
 
 long serial_recv_timeout = 5000; /* ms */
+long serial_drain_timeout = 250; /* ms */
 
 #define W32SERBUFSIZE 1024
 
@@ -635,7 +636,7 @@ static int net_drain(const union filedescriptor *fd, int display) {
 	}
 
 	timeout.tv_sec  = 0;
-	timeout.tv_usec = 250000;
+	timeout.tv_usec = serial_drain_timeout*1000L;
 
 	while (1) {
 		FD_ZERO(&rfds);
@@ -712,7 +713,7 @@ static int ser_drain(const union filedescriptor *fd, int display) {
 		return -1;
 	}
 
-	serial_w32SetTimeOut(hComPort,250);
+	serial_w32SetTimeOut(hComPort, serial_drain_timeout);
   
 	if (display) {
 		msg_info("drain>");
diff --git a/src/term.c b/src/term.c
index 44becd3f..01b669b7 100644
--- a/src/term.c
+++ b/src/term.c
@@ -187,7 +187,7 @@ static int chardump_line(char *buffer, unsigned char *p, int n, int pad) {
   unsigned char b[128];
 
   // Sanity check
-  n = n < 1? 1: n > sizeof b? sizeof b: n;
+  n = n < 1? 1: n > (int) sizeof b? (int) sizeof b: n;
 
   memcpy(b, p, n);
   for (int i = 0; i < n; i++)
@@ -664,19 +664,21 @@ static int cmd_write(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
   report_progress(0, 1, avr_has_paged_access(pgm, mem)? "Caching": "Writing");
   for (i = 0; i < len + data.bytes_grown; i++) {
     int rc = pgm->write_byte_cached(pgm, p, mem, addr+i, buf[i]);
-    if (rc) {
+    if (rc == LIBAVRDUDE_SOFTFAIL) {
+      pmsg_warning("(write) programmer write protects %s address 0x%04x\n", mem->desc, addr+i);
+    } else if(rc) {
       pmsg_error("(write) error writing 0x%02x at 0x%05lx, rc=%d\n", buf[i], (long) addr+i, (int) rc);
       if (rc == -1)
         imsg_error("%*swrite operation not supported on memory type %s\n", 8, "", mem->desc);
       werror = true;
-    }
-
-    uint8_t b;
-    rc = pgm->read_byte_cached(pgm, p, mem, addr+i, &b);
-    if (b != buf[i]) {
-      pmsg_error("(write) error writing 0x%02x at 0x%05lx cell=0x%02x\n", buf[i], (long) addr+i, b);
-      werror = true;
-    }
+    } else {
+      uint8_t b;
+      rc = pgm->read_byte_cached(pgm, p, mem, addr+i, &b);
+      if (b != buf[i]) {
+        pmsg_error("(write) verification error writing 0x%02x at 0x%05lx cell=0x%02x\n", buf[i], (long) addr+i, b);
+        werror = true;
+      }
+   }
 
     if (werror)
       pgm->err_led(pgm, ON);
@@ -755,8 +757,47 @@ static int cmd_send(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
 
 static int cmd_erase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
   term_out("erasing chip ...\n");
+
   // Erase chip and clear cache
-  pgm->chip_erase_cached(pgm, p);
+  int rc = pgm->chip_erase_cached(pgm, p);
+
+  if(rc == LIBAVRDUDE_SOFTFAIL) {
+    pmsg_info("(erase) emulating chip erase by writing 0xff to flash ");
+    AVRMEM *flm = avr_locate_mem(p, "flash");
+    if(!flm) {
+      msg_error("but flash not defined for part %s?\n", p->desc);
+      return -1;
+    }
+    int addr, beg = 0, end = flm->size-1;
+    if(pgm->readonly) {
+      for(addr=beg; addr < flm->size; addr++)
+        if(!pgm->readonly(pgm, p, flm, addr)) {
+          beg = addr;
+          break;
+        }
+      if(addr >= flm->size) {
+        msg_info("but all flash is write protected\n");
+        return 0;
+      }
+      for(addr=end; addr >= 0; addr--)
+        if(!pgm->readonly(pgm, p, flm, addr)) {
+          end = addr;
+          break;
+        }
+    }
+
+    msg_info("[0x%04x, 0x%04x]; undo with abort\n", beg, end);
+    for(int addr=beg; addr <= end; addr++)
+      if(!pgm->readonly || !pgm->readonly(pgm, p, flm, addr))
+        if(pgm->write_byte_cached(pgm, p, flm, addr, 0xff) == -1)
+          return -1;
+    return 0;
+  }
+
+  if(rc) {
+    pmsg_error("(erase) programmer %s failed erasing the chip\n", (char *) ldata(lfirst(pgm->id)));
+    return -1;
+  }
 
   return 0;
 }
diff --git a/src/update.c b/src/update.c
index 319766ee..0a7166cd 100644
--- a/src/update.c
+++ b/src/update.c
@@ -431,7 +431,7 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags
   AVRMEM *mem;
   int size;
   int rc;
-  Filestats fs;
+  Filestats fs, fs_patched;
 
   mem = avr_locate_mem(p, upd->memtype);
   if (mem == NULL) {
@@ -486,11 +486,10 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags
       return LIBAVRDUDE_GENERAL_FAILURE;
     }
 
-    size = rc;
     pmsg_info("reading input file %s for %s%s\n",
       update_inname(upd->filename), mem->desc, alias_mem_desc);
 
-    if(memstats(p, upd->memtype, size, &fs) < 0)
+    if(memstats(p, upd->memtype, rc, &fs) < 0)
       return LIBAVRDUDE_GENERAL_FAILURE;
 
     imsg_info("with %d byte%s in %d section%s within %s\n",
@@ -507,6 +506,38 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags
       msg_info("\n");
     }
 
+    // Patch flash input, eg, for vector bootloaders
+    if(pgm->flash_readhook) {
+      AVRMEM *mem = avr_locate_mem(p, upd->memtype);
+      if(mem && !strcmp(mem->desc, "flash")) {
+        rc = pgm->flash_readhook(pgm, p, mem, upd->filename, rc);
+        if (rc < 0) {
+          pmsg_notice("readhook for file %s failed\n", update_inname(upd->filename));
+          return LIBAVRDUDE_GENERAL_FAILURE;
+        }
+        if(memstats(p, upd->memtype, rc, &fs_patched) < 0)
+          return LIBAVRDUDE_GENERAL_FAILURE;
+        if(memcmp(&fs_patched, &fs, sizeof fs)) {
+          pmsg_info("preparing flash input for device%s\n",
+            pgm->prog_modes & PM_SPM? " bootloader": "");
+            imsg_notice2("with %d byte%s in %d section%s within %s\n",
+              fs_patched.nbytes, update_plural(fs_patched.nbytes),
+              fs_patched.nsections, update_plural(fs_patched.nsections),
+              update_interval(fs_patched.firstaddr, fs_patched.lastaddr));
+            if(mem->page_size > 1) {
+              imsg_notice2("using %d page%s and %d pad byte%s",
+                fs_patched.npages, update_plural(fs_patched.npages),
+                fs_patched.nfill, update_plural(fs_patched.nfill));
+              if(fs_patched.ntrailing)
+                msg_notice2(", and %d trailing 0xff byte%s",
+                  fs_patched.ntrailing, update_plural(fs_patched.ntrailing));
+              msg_notice2("\n");
+            }
+        }
+      }
+    }
+    size = rc;
+
     // Write the buffer contents to the selected memory type
     pmsg_info("writing %d byte%s %s%s ...\n", fs.nbytes,
       update_plural(fs.nbytes), mem->desc, alias_mem_desc);
@@ -585,7 +616,7 @@ int do_op(const PROGRAMMER *pgm, const AVRPART *p, UPDATE *upd, enum updateflags
     if (quell_progress < 2)
       pmsg_notice2("verifying ...\n");
 
-    rc = avr_verify(p, v, upd->memtype, size);
+    rc = avr_verify(pgm, p, v, upd->memtype, size);
     if (rc < 0) {
       pmsg_error("verification mismatch\n");
       pgm->err_led(pgm, ON);
diff --git a/src/urclock.c b/src/urclock.c
new file mode 100644
index 00000000..a987153d
--- /dev/null
+++ b/src/urclock.c
@@ -0,0 +1,2503 @@
+/*
+ * AVRDUDE - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2022, Stefan Rueger <stefan.rueger@urclocks.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* $Id$ */
+
+/*
+ * The Urclock programmer
+ *
+ *  - Reads/writes flash/EEPROM of boards directly via the MCU bootloader and a serial connection
+ *  - Works best in tandem with the urboot bootloader, but can deal with optiboot and similar
+ *  - Implements urprotocol, a communication protocol designed for small bootloader sizes
+ *  - Supports vector bootloaders by patching relevant interrupt vectors during upload:
+ *     + Vector bootloaders run on all devices, not only those with a dedicated boot section
+ *     + Can be considerably smaller than the smallest dedicated boot sections of a part, eg,
+ *       only 256 bytes for ATmega2560 with an otherwise smallest boot section of 1024 bytes
+ *  - Checks sizes of applications so they don't overwrite the bootloader
+ *  - Provides a 4-byte metadata interface for
+ *     + Allowing applications to utilise unused flash in a similar fashion to EEPROM
+ *     + Storing in top flash the file name and last-modified-date of the uploaded application
+ *     + Displaying file name and date of the application that was last uploaded
+ *
+ * As an example, the urboot bootloader including EEPROM r/w for the popular ATmega328p is only 384
+ * bytes, which frees up 128 bytes. On an ATmega1284p the urboot bootloader without EEPROM r/w is
+ * only 256 bytes, freeing 786 bytes on that device. Urboot bootloaders can be configured to
+ *  - Upload and download applications
+ *  - Read and write EEPROM
+ *  - Provide an application function that writes flash memory pages; as this function is located
+ *    at FLASHEND-4+1, no linker information is needed for the application
+ *  - Operate dual boot from external SPI flash memory in addition to EEPROM r/w at a slightly
+ *    increased bootloader of 512 bytes for a range of devices from the small ATtiny167, via the
+ *    popular ATmega328p to the mighty ATmega2560.
+ *
+ *
+ * Urprotocol (the gory details, see also https://github.com/stefanrueger/urboot)
+ *
+ * The **explicit communication** between an uploader/downloader program (*"the programmer"*) and
+ * the bootloader is driven by the programmer, which sends command sequences to the bootloader and
+ * evaluates their return sequences. A command sequence starts by a command byte, followed by its
+ * parameters, followed by an end-of-parameter byte UR_EOP. In return the bootloader sends a fixed
+ * byte UR_INSYNC to acknowledge the command, then executes it, possibly returning data, followed
+ * by sending a different fixed byte UR_OK.
+ *
+ * Although the UR_INSYNC and UR_OK are *fixed constants* for a particular bootloader, they *can
+ * vary* between bootloaders to indicate
+ *  - Which MCU the bootloader sits on (using one of up to 2040 predefined different MCU IDs)
+ *  - Whether or not the bootloader provides a paged read flash command
+ *  - Whether or not the bootloader has implemented the chip erase command
+ *  - Whether or not writing a memory page memory looks like programming NOR memory
+ *  - Two more whether-or-not bits that are currently reserved
+ *
+ * As UR_INSYNC and UR_OK should always differ, there are 256*255 possible combinations, one of
+ * which is reserved for backward compatibility mode where UR_INSYNC and UR_OK coincide with the
+ * respective STK500v1 constants. This protocol definition enables the bootloader to pass to the
+ * programmer log2(256*255-1) bits = 15.994331... bits of configuration information without having
+ * to spend a single additional byte of bootloader code. Subtracting the 5 bits for the "whether or
+ * not" info leaves 10.994331... bits which allows 2040 ≈ 2**10.994331... MCU ids.
+ *
+ * **Parameters.** Paged EEPROM/flash access commands and page erase are the only commands that
+ * need parameters. In this case the parameters are the address, followed by the length of the
+ * block to read or write and, if needed, followed by the bytes to be written. As in STK500v1,
+ * addresses are given as little endian (low byte first) and length as big endian (high byte
+ * first). The address always is a byte address (unless in compability mode). It is a 16-bit
+ * address for MCUs that have 65536 bytes flash or less, and a 24-bit address for MCUs with larger
+ * flash. Zero-length reads or  writes are not supported by the protocol. If the *flash* page size
+ * is 256 or less, then the length parameter is sent as one byte (where 0 means 256 bytes).
+ * Otherwise the length parameter is sent as two bytes (where 0 means 65536). Note, however, that
+ * the only valid length for the write flash page command is the MCU page size; also the *maximum*
+ * valid length for EEPROM writes is 256 or the *flash* page size, whichever is higher. EEPROM
+ * write page commands should never exceed the size of half of SRAM though. The other two (read)
+ * paged-access commands are free to request any length between 1 and 256, and 1 and 65536,
+ * respectively. However, the programmer must never ask for an address block that would access
+ * bytes outside the range of EEPROM or flash on the device. Whilst  the number of parameter bytes
+ * differs between bootloaders, for a particular bootloader the address and length is given always
+ * in the same way. This means that the EEPROM address on an MCU with a  large flash will be a
+ * 24-bit address even though the EEPROM might only have 8192 bytes. Even though the write flash
+ * page command only allows one length, and page erase does not need a page at all, it must always
+ * be specified. This is to simplify the bootloader effort to decode the programmer's commands.
+ *
+ *
+ * Urprotocol commands
+ *
+ *  - **UR_GET_SYNC:** The bootloader does nothing except returning the two protocol bytes. Its
+ *    purpose is to synchronise the programmer with the bootloader and to identify the type of
+ *    bootloader and (some of) its properties. For synchronisation, the programmer should issue a
+ *    number of UR_GET_SYNC commands until it receives consistent UR_INSYNC and UR_OK values.
+ *    At this point the programmer knows whether or not to switch to backward compatibility mode
+ *    using the STK500v1 protocol as in -c arduino, which MCU is to be programmed etc. It is
+ *    advised the programmer sets its read timeout in the synchronisation phase to less than 100 ms
+ *    when reading the bootloader reply to avoid triggering the bootloader's watchdog timer. It is
+ *    also recommended that the input is "drained" after successfully reading two response bytes to
+ *    ensure the response has not been brought about by an application program of the connected
+ *    board before the board was reset into bootloader mode. This command can also be used
+ *    periodically to prevent the bootloader from timing out.
+ *
+ *  - **UR_PROG_PAGE_FL:** One flash page is written to the device. In the absence of a
+ *    UR_CHIP_ERASE (see below), the bootloader is expected to program the flash page as atomic
+ *    page erase, page load and page write. If the bootloader implements UR_CHIP_ERASE, it has the
+ *    choice of erasing a flash page before programming it or not. In case the bootloader erases
+ *    pages before writing them, the payload of the UR_PROG_PAGE is programmed exactly as is; the
+ *    programmer should implement desired sub-page modifications by first reading the flash
+ *    contents of the not-to-be-modified page parts to correctly pad the page payload. If the
+ *    bootloader does not erase pages before writing them, effectively the payload is *and*ed to
+ *    the existing contents of the page thereby exposing the physical property of the underlying
+ *    NOR flash memory; sub-page modifications can be carried out by padding the page buffer
+ *    payload with 0xff, as programming 0xff is a NOP for AVR NOR flash memories.
+ *
+ *  - **UR_CHIP_ERASE** (optional): If implemented, the bootloader erases to 0xff all flash
+ *    except itself. After issuing the chip erase request it is advised the programmer set its
+ *    timeout for reading the next character to more time than the bootloader will need to erase
+ *    flash to avoid the programmer resuming communication before the bootloader comes back from
+ *    the chip erase. 20 s should be sufficient. If the bootloader does not implement chip erase
+ *    then the programmer should ensure that flash is erased to 0xff by, eg, repeated
+ *    UR_PROG_PAGE calls with 0xff-only contents or equivalent; this normally takes longer than
+ *    bootloader chip erase but is otherwise functionally equivalent to a UR_CHIP_ERASE
+ *    implementation in the bootloader. The protocol does not expect EEPROM to be erased in either
+ *    case. However, when implementing UR_CHIP_ERASE the bootloader is free to read fuses to
+ *    determine whether or not EEPROM should also be erased and erase EEPROM accordingly.
+ *
+ *  - **UR_READ_PAGE_FL** (optional) returns n=length bytes of flash from the given address
+ *
+ *  - **UR_READ_PAGE_EE** (optional) returns n=length bytes of EEPROM from the given address
+ *
+ *  - **UR_PROG_PAGE_EE** (optional) writes n=length bytes to the EEPROM at the given address
+ *
+ *  - **UR_PAGE_ERASE** (optional) erases to 0xff a page at the given address (length must be given
+ *    but is ignored)
+ *
+ *  - **UR_LEAVE_PROGMODE** (optional): some bootloaders reduce the Watchdog timeout so that the
+ *    application is started faster after programming
+ *
+ *  - **Any other command**, should behave like UR_GET_SYNC, ie, the bootloader returns
+ *    UR_INSYNC and UR_OK.
+ *
+ *
+ * **Error handling.** It is generally considered an error if the programmer asks for not
+ * implemented functionality, as it knows after synchronisation how the bootloader is configured.
+ * Hence, the bootloader WDT should reset on request of an optional, not implemented command.
+ * Typically, the bootloader would need to save the payload of EEPROM/flash writes to SRAM; for
+ * security reasons the bootloader should trigger a WDT reset if an illegitimate length of a paged
+ * write could overwrite the stack (eg, a request for writing 256 bytes EEPROM on a part with only
+ * 256 bytes SRAM). A protocol error detected by the bootloader (failure to match UR_EOP) should
+ * lead to a WDT reset. Protocol errors detected by the programmer (not matching UR_INSYNC or
+ * UR_OK) should normally lead to a termination of programming attempts. Frame errors in serial
+ * communication should also lead to a WDT reset or termination of programming, respectively. The
+ * bootloader should protect itself from being overwritten through own page writes and page erases.
+ *
+ *
+ * **Implicit communication** of further bootloader properties happens through a small table
+ * located at the top of flash. Normally, the programmer can read this table after establishing the
+ * MCU id, and therefore the location of top flash of the part for which the bootloader was
+ * compiled. The 6-byte table contains (top to bottom):
+ *   - Version number: one byte, minor version 0..7 in three lsb, major version 0..31 in the 5 msb
+ *   - Capabilities byte detailing, eg, whether the bootloader supports EEPROM r/w, dual boot etc
+ *   - Two-byte rjmp to a writepage(ram, flash) function or a ret opcode if not implemented
+ *   - Number 1..127 of pages that the bootloader occupies
+ *   - Vector number 1..127 used for the r/jmp to the application if it is a vector bootloader
+ * If the bootloader does not have read capabilities the user needs to supply necessary information
+ * such as the bootloader size to the programmer on the command line via -x extended parameters.
+ *
+ * **Backward compatibility mode.** When urprotocol after synchronisation with the bootloader
+ * settles on UR_INSYNC and UR_OK values that turn out to be the STK500v1 values of 0x14 and 0x10,
+ * this triggers a backward compatibility mode. In this instance the programmer behaves (almost)
+ * like the STK500v1 implementation in avrdude's arduino programmer, ie, it handles optiboot and
+ * legacy bootloaders gracefully: in particular, the programmer can issue STK_READ_SIGN and two
+ * STK_UNIVERSAL requests (load extended address and chip erase) that the bootloader must implement
+ * in the backward compatibility mode. All EEPROM/flash addresses are sent as two-byte word
+ * addresses *little* endian, all length arguments are two-byte *big* endian, etc. Unlike avrdude
+ * -c arduino the programmer for the urprotocol should not pass on get and set hardware parameter
+ * requests, enquire software and hardware versions etc, as these requests would be wasteful for
+ * the bootloader. Under the urprotocol, bootloaders should be assured they do not need to even
+ * provide code to ignore these requests, even if they operate in the backwards compatibility mode.
+ *
+ *
+ * **Limitations.** Urprotocol has only provisions for reading EEPROM and flash, for writing EEPROM
+ * and for writing flash other than the bootloader area. In particular, urprotocol has no
+ * provisions for reading other memories such as the signature (other than in backward
+ * compatibility mode), calibration bytes, locks or fuses, and neither for writing lock bytes. The
+ * protocol does not consider sub-page flash writes, which are shifted to the programmer. If the
+ * bootloader's flash write does *not* look like NOR programming *and* if the bootloader does *not*
+ * provide flash read, then sub-page modifications simply cannot be done. Installing a bootloader
+ * has security implications as it provides a means to modify flash thus weakening the Harvard
+ * architecture of AVR microprocessors. Even bootloader implementations that are hardened against
+ * prohibited address and length parameters have, out of necessity, somewhere a code sequence that
+ * manipulates flash memory. A flawed application might still give an attacker a way to call these
+ * code sequences, so be warned here be dragons.
+ *
+ */
+
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+
+#include "urclock.h"
+#include "urclock_private.h"
+
+#define urmax(a, b) ((a) > (b)? (a): (b))
+#define urmin(a, b) ((a) < (b)? (a): (b))
+
+static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p);
+static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t addr, int len,
+  char memchr);
+static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *idp);
+static int urclock_send(const PROGRAMMER *pgm, unsigned char *buf, size_t len);
+static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len);
+static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res);
+
+static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
+  unsigned int page_size, unsigned int addr, unsigned int n_bytes);
+
+
+// Context of the programmer
+typedef struct {
+  char desc[32];                // Text description of bootloader version and capabilities
+
+  bool urprotocol;              // Bootloader uses the urboot modification of the STK500v1 protocol
+  uint8_t urfeatures;           // Bootloader features (chip erase, can read flash, ...)
+  int STK_INSYNC, STK_OK;       // Variable but fixed bootloader responses for urprotocol
+
+  struct {
+    uint8_t seen, stk_ok, stk_insync;
+  } gs;                         // Needed for urclock_getsync()
+
+
+  unsigned char ext_addr_byte;  // Ext-addr byte for STK500v1 protocol and MCUs with > 128k
+
+  uPcore_t uP;                  // Info about the connected processor (copied from uP_table)
+
+  bool initialised;             // Is this structure initialised?
+  bool bleepromrw;              // Bootloader has EEPROM r/w support
+  bool emulate_ce;              // Emulate chip erase when bootloader cannot and user wants it
+  bool done_ce;                 // Set when flash of chip has been erased after first write
+
+  int sync_silence;             // Temporarily set during start of synchronisation
+
+  // Info needed about bootloader to patch, if needed, the reset vector and one other vector
+  int vblvectornum,             // Vector bootloader vector number for jump to application op code
+      vbllevel,                 // 0=n/a, 1=patch externally, 2=bl patches, 3=bl patches & verifies
+      blurversion,              // Octal byte 076 means v7.6 (minor version number is lowest 3 bit)
+                                // Small numbers < 070 probably are optiboot major version number
+      bloptiversion,            // Optiboot version as (major<<8) + minor
+      blguessed;                // Guessed the bootloader from hash data
+
+  int32_t blstart;              // Bootloader start address, eg, for bootloader write protection
+
+  int idmchr;                   // Either 'E' or 'F' for the memory where the Urclock ID is located
+  int idaddr;                   // The address of the Urclock ID
+  int idlen;                    // Number 1..8 of Urclock ID bytes (location, see iddesc below)
+
+  int32_t storestart;           // Store (ie, unused flash) start address, same as application size
+  int32_t storesize;            // Store size
+
+  // Metadata for free flash memory to be used for store support
+  char filename[254];           // Filename of uploaded application, must be max 254 bytes incl nul
+  int16_t  yyyy;                // Date stamp of uploaded application file: 4 digit year,
+  int8_t mm, dd, hr, mn;        // Month (1..12), day (1..31), hour (0..23) and minute (0..59)
+  uint8_t freeflash[3];         // 24-bit little endian number (storesize)
+  uint8_t mcode;                // 255 = no metadata, 0 = only freeflash, 1 = freeflash + date,
+                                // 2-254 = freeflash + date + that many bytes filename incl nul
+  // Note:
+  // blstart = application size + freeflash + nmeta(mcode, flashsize)
+  // FLASHEND+1 = application size + freeflash + nmeta(mcode, flashsize) + bootloader size
+
+  // Extended parameters for Urclock
+  int showall,                  // Show all pieces of info for connected part and exit
+      showid,                   //   ... Urclock ID
+      showdate,                 //   ... last-modified date of last uploaded application
+      showfilename,             //   ... filename of last uploaded application
+      showapp,                  //   ... application size
+      showstore,                //   ... store size
+      showmeta,                 //   ... metadata size
+      showboot,                 //   ... bootloader size
+      showversion,              //   ... bootloader version and capabilities
+      showvector,               //   ... vector bootloader level, vector number and name
+      showpart,                 //   ... part for which bootloader was compiled
+      xbootsize,                // Manual override for size of bootloader section
+      xvectornum,               //   ... for vector number (implies vbllevel = 1)
+      xeepromrw,                //   ... for EEPROM r/w capability
+      xemulate_ce,              //   ... for making avrdude emulate any chip erase
+      initstore,                // Zap store when writing the application, ie, fill with 0xff
+//@@@ copystore,                // Copy over store as far as possible when writing the application
+      restore,                  // Restore a flash backup exactly as it is trimming the bootloader
+      nofilename,               // Don't store application filename when writing the application
+      nodate,                   // Don't store application filename and no date either
+      nometadata,               // Don't store any metadata at all (implies no store support)
+      delay;                    // Additional delay [ms] after resetting the board, can be negative
+
+  char title[254];              // Use instead of filename for metadata - same size as filename
+  char iddesc[64];              // Location of Urclock ID, eg F.12324.6 or E.-4.4 (default E.257.6)
+} Urclock_t;
+
+// Use private programmer data as if it were a global structure ur
+#define ur (*(Urclock_t *)(pgm->cookie))
+
+#define Return(...) do { pmsg_error(__VA_ARGS__); msg_error("\n"); return -1; } while (0)
+
+
+// Return how many bytes metadata are needed given the mcode byte just below bootloader
+static int nmeta(int mcode, int flashsize) {
+  // The size of the structure that holds info about metadata (sits just below bootloader)
+  int nheader = 2*(flashsize > (1<<16)? 4: 2) + 1;
+
+  return mcode == 0xff? 0:      // No metadata at all
+    mcode > 1? mcode+6+nheader: // Application filename, app date and structure for pgm store
+    mcode? 6+nheader:           // Application date and structure describing pgm store
+      nheader;                  // Structure describing pgm store
+}
+
+
+// Given the MCU id return index in uP_table or -1 if not found
+static int upidxmcuid(int mcuid) {
+  for(size_t i=0; i< sizeof uP_table/sizeof *uP_table; i++) {
+    if(mcuid == uP_table[i].mcuid)
+      return i;
+  }
+  return -1;
+}
+
+// Given three signature bytes return index in uP_table or -1 if not found
+static int upidxsig(const uint8_t *sigs) {
+  for(size_t i=0; i< sizeof uP_table/sizeof *uP_table; i++) {
+    if(0 == memcmp(sigs, uP_table[i].sigs, sizeof uP_table->sigs))
+      return i;
+  }
+  return -1;
+}
+
+// Given the long name of a part return index in uP table or -1 if not found
+static int upidxname(const char *name) {
+  for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++)
+    if(0 == strcasecmp(name, uP_table[i].name))
+      return i;
+
+  return -1;
+}
+
+// Given sig bytes return number of matching indices in uP_table and create a list of names in p
+static int upmatchingsig(uint8_t sigs[3], char *p, size_t n) {
+  int matching = 0;
+  uPcore_t up = {0, };
+
+  // Scan table for the given signature
+  for(size_t i=0; i < sizeof uP_table/sizeof *uP_table; i++) {
+    if(0 == memcmp(sigs, uP_table[i].sigs, sizeof uP_table->sigs)) {
+      if(matching == 0) {       // First match, initialise uP information
+        matching = 1;
+        up = uP_table[i];
+        if(p) {
+          size_t len = strlen(uP_table[i].name);
+          if(n > len) {
+            strcpy(p, uP_table[i].name);
+            n -= len; p += len;
+          }
+        }
+      } else {
+        // Same signature, but are these chips materially different as far as urboot is concerned?
+        if( up.ninterrupts != uP_table[i].ninterrupts ||
+            up.pagesize != uP_table[i].pagesize ||
+            up.nboots != uP_table[i].nboots ||
+            up.bootsize != uP_table[i].bootsize ||
+            up.flashsize != uP_table[i].flashsize ||
+            up.flashoffset != uP_table[i].flashoffset ) {
+          matching++;
+          if(p) {
+            size_t len = 2 + strlen(uP_table[i].name);
+            if(n > len) {
+              strcpy(p, ", ");
+              strcpy(p+2, uP_table[i].name);
+              n -= len; p += len;
+            }
+          }
+
+        }
+      }
+    }
+  }
+
+  return matching;
+}
+
+
+// Need to know a bit about avr opcodes, in particular jmp and rjmp for patching vector table
+
+#define ret_opcode 0x9508
+
+
+// Is the opcode an rjmp, ie, a relative jump [-4094, 4096] bytes from opcode address?
+static int isRjmp(uint16_t opcode) {
+  return (opcode & 0xf000) == 0xc000;
+}
+
+
+/*
+ * Map distances to [-flashsize/2, flashsize/2) for smaller devices. As rjmp can go +/- 4 kB, so
+ * smaller flash than 8k (eg, 4k) benefit from wrap around logic.
+ */
+static int rjmpdistwrap(int addis, int flashsize) {
+  int size = flashsize > 8182? 8192: flashsize;
+
+  if((size & (size-1)) == 0) {  // Sanity check to assert size is a power of 2; will be true
+    addis &= size-1;
+    if(addis >= size/2)
+      addis -= size;
+  }
+
+  return addis;
+}
+
+
+// Compute from rjmp opcode the relative distance in bytes (rjmp address minus destination address)
+static int dist_rjmp(uint16_t rjmp, int flashsize) {
+  int16_t dist;
+
+  dist = rjmp & 0xfff;          // Signed 12-bit word distance
+  dist = (int16_t)(dist<<4)>>3; // Sign-extend and multiply by 2
+
+  return rjmpdistwrap(dist+2, flashsize);  // Wraps around 0 (eg, in flashes smaller than 8k)
+}
+
+
+// rjmp opcode from byte distance; 0xcfff is an endless loop, 0xc000 is a nop
+static uint16_t rjmp_opcode(int dist, int flashsize) {
+  dist = rjmpdistwrap(dist, flashsize);
+  return 0xc000 | (((dist >> 1) - 1) & 0x0fff);
+}
+
+
+// rjmp opcode from reset to bootloader start; same as above if bl start is in top half of flash
+static uint16_t rjmp_bwd_blstart(int blstart, int flashsize) { // flashsize must be power of 2
+  return 0xc000 | (((uint16_t)((blstart-flashsize-2)/2)) & 0xfff); // Urboot uses this formula
+}
+
+
+// jmp opcode from byte address
+static uint32_t jmp_opcode(int32_t addr) {
+  // jmp uses word address; hence, shift by that one extra bit more
+  return (((addr>>1) & 0xffff)<<16) | 0x940c | (((addr>>18) & 31)<<4) | (((addr>>17) & 1)<<0);
+}
+
+
+// Byte address from jmp opcode
+static int addr_jmp(uint32_t jmp) {
+  int addr;
+
+  addr  = jmp >> 16;            // Low 16 bit of word address are in upper word of op code
+  addr |= (jmp & 1) << 16;      // Add extra address bits from least significant bytes of op code
+  addr |= (jmp & 0x1f0) << (17-4);
+  addr <<= 1;                   // Convert to byte address
+
+  return addr;
+}
+
+
+// Is the instruction word the lower 16 bit part of a 32-bit instruction?
+static int isop32(uint16_t opcode) {
+  return
+    (opcode & 0xfe0f) == 0x9200 || // sts
+    (opcode & 0xfe0f) == 0x9000 || // lds
+    (opcode & 0xfe0e) == 0x940c || // jmp
+    (opcode & 0xfe0e) == 0x940e;   // call
+}
+
+
+// Is the instruction word the lower 16 bit part of a jmp instruction?
+static int isJmp(uint16_t opcode) {
+  return (opcode & 0xfe0e) == 0x940c;
+}
+
+
+// Assemble little endian 32-bit word from buffer
+static uint32_t buf2uint32(const unsigned char *buf) {
+  return buf[0] | buf[1]<<8 | buf[2]<<16 | buf[3]<<24;
+}
+
+
+// Assemble little endian 16-bit word from buffer
+static uint16_t buf2uint16(const unsigned char *buf) {
+  return buf[0] | buf[1]<<8;
+}
+
+
+// Write little endian 32-bit word into buffer
+void uint32tobuf(unsigned char *buf, uint32_t opcode32) {
+  buf[0] = opcode32;
+  buf[1] = opcode32>>8;
+  buf[2] = opcode32>>16;
+  buf[3] = opcode32>>24;
+}
+
+
+// Write little endian 16-bit word into buffer
+void uint16tobuf(unsigned char *buf, uint16_t opcode16) {
+  buf[0] = opcode16;
+  buf[1] = opcode16>>8;
+}
+
+
+// Set filename/title and date for metadata
+static void set_date_filename(const PROGRAMMER *pgm, const char *fname) {
+  const char *base;
+  struct stat b;
+  struct tm *t;
+  time_t when;
+
+  // Last modification date of file or, if unavailable, current time
+  when = fname && *fname && strcmp(fname, "-") && !stat(fname, &b)? b.st_mtime: time(NULL);
+  when += 30;                   // Round to minute
+  if((t=localtime(& when))) {
+    ur.yyyy = t->tm_year + 1900;
+    ur.mm = t->tm_mon+1;
+    ur.dd = t->tm_mday;
+    ur.hr = t->tm_hour;
+    ur.mn = t->tm_min;
+  }
+
+  // Compute basename of file unless title was set
+  if(*ur.title)
+    memcpy(ur.filename, ur.title, sizeof ur.filename);
+  else {
+    ur.filename[0] = 0;
+    if(fname && *fname) {
+#if !defined (WIN32)
+      if((base=strrchr(fname, '/')))
+        base++;
+#else
+      if((base=strrchr(fname, '\\')))
+        base++;
+#endif
+      else
+        base = fname;
+      strncpy(ur.filename, base, sizeof ur.filename-1);
+      ur.filename[sizeof ur.filename-1] = 0;
+    }
+  }
+}
+
+
+
+// Put destination address of reset vector jmp or rjmp into addr, return -1 if not an r/jmp
+static int reset2addr(const unsigned char *opcode, int vecsz, int flashsize, int *addrp) {
+  int op32, addr, rc = 0;
+  uint16_t op16;
+
+  op16 = buf2uint16(opcode); // First word of the jmp or the full rjmp
+  op32 = vecsz == 2? op16: buf2uint32(opcode);
+
+  if(vecsz == 4 && isJmp(op16)) {
+    addr = addr_jmp(op32);      // Accept compiler's destination (do not normalise)
+  } else if(isRjmp(op16)) {     // rjmp might be generated for larger parts, too
+    addr = dist_rjmp(op16, flashsize);
+    while(addr < 0)             // If rjmp was backwards
+      addr += flashsize;        // OK for small parts, likely(!) OK if flashsize is a power of 2
+    while(addr > flashsize)     // Sanity (should not happen): rjmp jumps over FLASHEND
+      addr -= flashsize;
+  } else
+    rc = -1;
+
+  if(addrp && rc == 0)
+    *addrp = addr;
+
+  return rc;
+}
+
+
+// What reset looks like for vector bootloaders
+static int set_reset(const PROGRAMMER *pgm, unsigned char *jmptoboot, int vecsz) {
+  // Small part or larger flash that is power or 2: urboot P reset vector protection uses this
+  if(vecsz == 2 || (ur.uP.flashsize & (ur.uP.flashsize-1)) == 0) {
+    uint16tobuf(jmptoboot, rjmp_bwd_blstart(ur.blstart, ur.uP.flashsize));
+    return 2;
+  }
+
+  uint32tobuf(jmptoboot, jmp_opcode(ur.blstart));
+  return 4;
+}
+
+
+// Called after the input file has been read for writing or verifying flash
+static int urclock_flash_readhook(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *flm,
+  const char *fname, int size) {
+
+  int nmdata, maxsize, firstbeg, firstlen;
+  int vecsz = ur.uP.flashsize <= 8192? 2: 4; // Small parts use rjmp, large parts need 4-byte jmp
+
+  set_date_filename(pgm, fname);
+
+  // Record how extensive the metadata should be, given the command line options (default: all)
+  ur.mcode = ur.nometadata? 0xff: ur.nodate? 0: ur.nofilename? 1: strlen(ur.filename)+1;
+  nmdata = nmeta(ur.mcode, ur.uP.flashsize);
+
+  maxsize = ur.blstart? ur.blstart: flm->size;
+
+  // Compute begin and length of first contiguous block in input
+  for(firstbeg=0; firstbeg < size; firstbeg++)
+    if(flm->tags[firstbeg] & TAG_ALLOCATED)
+      break;
+  for(firstlen=0; firstbeg+firstlen < size; firstlen++)
+    if(!(flm->tags[firstbeg+firstlen] & TAG_ALLOCATED))
+      break;
+
+  pmsg_notice2("%s %04d.%02d.%02d %02d.%02d meta %d boot %d\n", ur.filename,
+    ur.yyyy, ur.mm, ur.dd, ur.hr, ur.mn, nmdata, ur.blstart > 0? flm->size-ur.blstart: 0);
+
+  // Force upload of exactly this file, no patching, no metadata update, just trim if too big
+  if(ur.restore) {
+    if(size > maxsize)
+      size = maxsize;
+
+    goto nopatch_nometa;
+  }
+
+  // Sanity: no patching and no metadata if bootloader location is unknown
+  if(!ur.blstart)
+    goto nopatch_nometa;
+
+  // Sanity check the bootloader start address
+  if(ur.blstart < 0 || ur.blstart >= flm->size)
+    Return("bootloader at 0x%04x outside flash [0, 0x%04x]?", ur.blstart, flm->size-1);
+
+  // Check size of uploded application and protect bootloader from being overwritten
+  if(size > ur.blstart)
+    Return("input [0x%04x, 0x%04x] overlaps bootloader [0x%04x, 0x%04x]",
+      firstbeg, size-1, ur.blstart, flm->size-1);
+
+  if(nmdata >= nmeta(0, ur.uP.flashsize) && size > ur.blstart - nmeta(0, ur.uP.flashsize))
+    Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnometadata",
+      firstbeg, size-1, ur.blstart-nmeta(0, ur.uP.flashsize), ur.blstart-1);
+
+  if(nmdata >= nmeta(1, ur.uP.flashsize) && size > ur.blstart - nmeta(1, ur.uP.flashsize))
+    Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnodate",
+      firstbeg, size-1, ur.blstart-nmeta(1, ur.uP.flashsize), ur.blstart-1);
+
+  if(size > ur.blstart - nmdata)
+    Return("input [0x%04x, 0x%04x] overlaps metadata [0x%04x, 0x%04x], consider -xnofilename",
+      firstbeg, size-1, ur.blstart-nmdata, ur.blstart-1);
+
+  bool llcode = firstbeg == 0 && firstlen > ur.uP.ninterrupts*vecsz; // Looks like code
+  bool llvectors = firstbeg == 0 && firstlen >= ur.uP.ninterrupts*vecsz; // Looks like vector table
+  for(int i=0; llvectors && i<ur.uP.ninterrupts*vecsz; i+=vecsz) {
+    uint16_t op16 = buf2uint16(flm->buf+i);
+    if(!isRjmp(op16) && !(vecsz == 4 && isJmp(op16)))
+      llvectors = 0;
+  }
+
+  if(llcode && !llvectors && ur.vblvectornum > 0 && ur.vbllevel)
+    pmsg_warning("not patching jmp to application as input does not start with a vector table\n");
+
+  // Patch vectors if input looks like code and it's a vector bootloader with known vector number
+  if(llcode && llvectors && ur.vblvectornum > 0 && ur.vbllevel) {
+    // From v7.5 patch all levels but for earlier and unknown versions only patch level 1
+    if(ur.blurversion >= 075 || ((ur.blurversion==0 || ur.blurversion >= 072) && ur.vbllevel==1)) {
+      uint16_t reset16;
+      int reset32, appstart, appvecloc;
+
+      appvecloc = ur.vblvectornum*vecsz; // Location of jump-to-application in vector table
+      reset16 = buf2uint16(flm->buf);    // First reset word of to-be-uploaded application
+      reset32 = vecsz == 2? reset16: buf2uint32(flm->buf);
+
+      /*
+       * Compute where the application starts from the reset vector. The assumptions are that the
+       *  - Vector table, and therefore the reset vector, resides at address zero
+       *  - Compiler puts either a jmp or an rjmp at address zero
+       *  - Compiler does not shorten the vector table if no or few interrupts are used
+       *  - Compiler does not utilise unused interrupt vectors to place code there
+       * These are not necessarily true, but work for run-of-the-mill setups; the code below makes
+       * a reasonable effort to detect whether the assumptions are violated, so at least there is
+       * an error thrown if so.
+       */
+
+      if(reset2addr(flm->buf, vecsz, flm->size, &appstart) < 0) {
+        pmsg_warning("not patching input as opcode word %04x at reset is not a%sjmp\n",
+          reset16, vecsz==2? "n r": " ");
+        goto nopatch;
+      }
+
+      // Only patch if appstart does not already point to the bootloader
+      if(appstart != ur.blstart) {
+        int vectorsend = vecsz*ur.vblvectornum;
+        if(appstart < vectorsend || appstart >= size) { // appstart should be in [vectorsend, size)
+          if(appstart != ur.blstart) {
+            pmsg_warning("not patching as reset opcode %0*x jumps to 0x%04x,\n",
+              vecsz*2, reset32, appstart);
+            imsg_warning("ie, outside code area [0x%04x, 0x%04x)\n",
+              vectorsend, size);
+          }
+          goto nopatch;
+        }
+
+        // OK, now have bootloader start and application start: patch
+        set_reset(pgm, flm->buf+0, vecsz);
+        if(vecsz == 4)
+          uint32tobuf(flm->buf+appvecloc, jmp_opcode(appstart));
+        else
+          uint16tobuf(flm->buf+appvecloc, rjmp_opcode(appstart - appvecloc, ur.uP.flashsize));
+      }
+    }
+  }
+
+nopatch:
+
+  if(nmdata) {
+    int32_t nfree = ur.blstart - size;
+
+    if(nfree >= nmdata) {
+      unsigned char *p = flm->buf + ur.blstart - nmdata;
+
+      if(ur.mcode > 1) {        // Save filename (ur.mcode cannot be 0xff b/c nmdata is non-zero)
+        memcpy(p, ur.filename, ur.mcode);
+        p += ur.mcode;
+      }
+
+      if(ur.mcode >= 1) {       // Save date
+        *p++ = ur.yyyy;
+        *p++ = ur.yyyy>>8;
+        *p++ = ur.mm;
+        *p++ = ur.dd;
+        *p++ = ur.hr;
+        *p++ = ur.mn;
+      }
+
+      *p++ = size;              // Save where the pgm store begins
+      *p++ = size >> 8;
+      if(ur.uP.flashsize > (1<<16)) {
+        *p++ = size >> 16;
+        *p++ = size >> 24;
+      }
+
+      nfree -= nmdata;
+      *p++ = nfree;             // Save how much is free
+      *p++ = nfree >> 8;
+      if(ur.uP.flashsize > (1<<16)) {
+        *p++ = nfree >> 16;
+        *p++ = nfree >> 24;
+      }
+      *p++ = ur.mcode;
+
+      // Set tags so metadata get burned onto chip
+      memset(flm->tags + ur.blstart - nmdata, TAG_ALLOCATED, nmdata);
+
+      if(ur.initstore)          // Zap the pgm store
+        memset(flm->tags + size, TAG_ALLOCATED, nfree);
+
+      size = ur.blstart;
+    }
+  }
+
+  // Storing no metadata: put a 0xff byte just below bootloader
+  if(size < ur.blstart && nmdata == 0) {
+    flm->buf[ur.blstart-1] = 0xff;
+    flm->tags[ur.blstart-1] = TAG_ALLOCATED;
+    size = ur.blstart;
+  }
+
+nopatch_nometa:
+
+  // Delete metadata on device (if any) that's between new input and metadata
+  if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable?
+    uint8_t devmcode;           // Metadata marker on the device
+    if(ur.blstart && ur_readEF(pgm, p, &devmcode, ur.blstart-1, 1, 'F') == 0) {
+      int devnmeta=nmeta(devmcode, ur.uP.flashsize);
+      for(int addr=ur.blstart-devnmeta; addr < ur.blstart; addr++) {
+         if(!(flm->tags[addr] & TAG_ALLOCATED)) {
+            flm->tags[addr] |= TAG_ALLOCATED;
+            flm->buf[addr] = 0xff;
+         }
+      }
+    }
+  }
+
+  // Emulate chip erase if bootloader unable to: mark all bytes for upload on first -U flash:w:...
+  if(ur.emulate_ce) {
+    for(int ai = 0; ai < maxsize; ai++)
+      flm->tags[ai] = TAG_ALLOCATED;
+    ur.emulate_ce = 0;
+  }
+
+
+  // Ensure that vector bootloaders have correct r/jmp at address 0
+  if(ur.blstart && ur.vbllevel==1) {
+    int rc, set=0;
+    for(int i=0; i < vecsz; i++)
+      if(flm->tags[i] & TAG_ALLOCATED)
+        set++;
+
+
+    // Reset vector not programmed? Or -F? Ensure a jmp to bootloader
+    if(ovsigck || set != vecsz) {
+      unsigned char jmptoboot[4];
+      int resetsize = set_reset(pgm, jmptoboot, vecsz);
+
+      if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) { // Flash readable?
+        int resetdest;
+
+        if(set != vecsz) {
+          unsigned char device[4];
+          // Read reset vector from device flash
+          if((rc = ur_readEF(pgm, p, device, 0, vecsz, 'F')) < 0)
+            return rc;
+
+          // Mix with already set bytes
+          for(int i=0; i < vecsz; i++)
+            if(!(flm->tags[i] & TAG_ALLOCATED))
+              flm->buf[i] = device[i];
+        }
+
+        if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0 || resetdest != ur.blstart) {
+          for(int i=0; i < resetsize; i++) {
+            flm->buf[i] = jmptoboot[i];
+            flm->tags[i] |= TAG_ALLOCATED;
+          }
+        }
+      } else {                  // Flash not readable: patch reset vector unconditionally
+        for(int i=0; i < resetsize; i++) {
+          flm->buf[i] = jmptoboot[i];
+          flm->tags[i] |= TAG_ALLOCATED;
+        }
+      }
+    } else {                    // Double-check reset vector jumps to bootloader
+      int resetdest;
+
+      if(reset2addr(flm->buf, vecsz, flm->size, &resetdest) < 0)
+        Return("input would overwrite the reset vector bricking the bootloader\n"
+          "%*susing -F will try to patch the input but this may not be what is needed",
+          (int) strlen(progname)+1, "");
+
+      if(resetdest != ur.blstart)
+        Return("input points reset to 0x%04x, not to bootloader at 0x%04x\n"
+          "%*susing -F will try to patch the input but this may not be what is needed",
+          resetdest, ur.blstart, (int) strlen(progname)+1, "");
+    }
+  }
+
+  // Effective page size, can be 4*pagesize for 4-page erase parts
+  int pgsize = p->n_page_erase > 0? p->n_page_erase*ur.uP.pagesize: ur.uP.pagesize;
+  if((pgsize & (pgsize-1)) || pgsize < 1 || pgsize > maxsize || maxsize % pgsize)
+    Return("effective page size %d implausible for size %d below bootloader", pgsize, maxsize);
+
+  if(!ur.done_ce) {             // Unless chip erase was just issued (where all mem is 0xff)
+    if((ur.urprotocol && !(ur.urfeatures & UB_FLASH_LL_NOR)) || !ur.urprotocol) {
+      // Scan the memory for eff pages with unset bytes and read these bytes from device flash
+      int ai, npe, addr, nset;
+
+      uint8_t spc[2048];
+
+      for(addr = 0; addr < maxsize; addr += pgsize) {
+        // How many bytes are set in this effective page?
+        for(ai = addr, nset = 0; ai < addr + pgsize; ai++)
+          if(flm->tags[ai] & TAG_ALLOCATED)
+            nset++;
+
+        // Holes in this page that needs writing? read them in from the chip
+        if(nset && nset != pgsize) {
+          for(npe=0; npe < pgsize/ur.uP.pagesize; npe++) {
+            // Identify a covering interval for all holes in page
+            int istart, isize, beg, end;
+
+            beg = addr + npe*ur.uP.pagesize;
+            end = beg + ur.uP.pagesize;
+
+            // Lowest address with unset byte (there might be none)
+            for(ai = beg; ai < end; ai++)
+              if(!(flm->tags[ai] & TAG_ALLOCATED))
+                break;
+            istart = ai;
+
+            if(istart < end) {
+              // Highest address with unset byte
+              for(ai = end - 1; ai >= istart; ai--)
+                if(!(flm->tags[ai] & TAG_ALLOCATED))
+                  break;
+              isize = ai - istart + 1;
+
+              if(isize < 1 || isize > (int) sizeof spc) // Should not happen
+                Return("isize=%d out of range (enlarge spc[] and recompile)", isize);
+
+              if(ur_readEF(pgm, p, spc, istart, isize, 'F') == 0) {
+                pmsg_debug("padding [0x%04x, 0x%04x]\n", istart, istart+isize-1);
+
+                for(ai = istart; ai < istart + isize; ai++)
+                  if(!(flm->tags[ai] & TAG_ALLOCATED)) {
+                    flm->tags[ai] |= TAG_ALLOCATED;
+                    flm->buf[ai] = spc[ai-istart];
+                  }
+              } else {
+                pmsg_notice2("cannot read flash [0x%04x, 0x%04x] to pad page bytes\n",
+                  istart, istart+isize-1);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  ur.done_ce = 0;               // From now on can no longer rely on being deleted
+
+  // Fill remaining holes (chip was erased, could not be read or memory looks like NOR memory)
+  int ai, addr, nset;
+
+  for(addr = 0; addr < maxsize; addr += pgsize) {
+    for(ai = addr, nset = 0; ai < addr + pgsize; ai++)
+      if(flm->tags[ai] & TAG_ALLOCATED)
+        nset++;
+
+    if(nset && nset != pgsize) { // Page has holes: fill them
+      pmsg_debug("0xff padding page addr 0x%04d\n", addr);
+      for(ai = addr, nset = 0; ai < addr + pgsize; ai++)
+        if(!(flm->tags[ai] & TAG_ALLOCATED)) {
+          flm->tags[ai] |= TAG_ALLOCATED;
+          flm->buf[ai] = 0xff;
+        }
+    }
+  }
+
+
+  return size;
+}
+
+
+// Put version string into a buffer of max 19 characters incl nul (normally 15-16 bytes incl nul)
+static void urbootPutVersion(const PROGRAMMER *pgm, char *buf, uint16_t ver, uint16_t rjmpwp) {
+  uint8_t hi = ver>>8, type = ver & 0xff, flags;
+
+  if(ver == 0xffff)             // Unknown provenance
+    hi = type = 0;
+
+  if(hi >= 072) {               // These are urboot versions
+    sprintf(buf, "u%d.%d ", hi>>3, hi&7);
+    buf += strlen(buf);
+    *buf++ = (hi < 077 && (type & UR_PGMWRITEPAGE)) || (hi >= 077 && rjmpwp != ret_opcode)? 'w': '-';
+    *buf++ = type & UR_EEPROM? 'e': '-';
+    if(hi >= 076) {             // From urboot version 7.6 URPROTOCOL has its own bit
+      *buf++ = type & UR_URPROTOCOL? 'u': 's';
+      *buf++ = type & UR_DUAL? 'd': '-';
+    } else {
+      *buf++ = '-';             // Dummy bit
+      flags = (type/UR_DUAL) & 3;
+      // D = Dual boot with SE & SPI restoration, d = dual boot with SE, f = dual boot only
+      *buf++ = flags==3? 'D': flags==2? 'd': flags? 'f': '-';
+    }
+    flags = (type/UR_VBL) & 3;
+    // V = VBL, patch & verify, v = VBL, patch only, j = VBL, jump only
+    *buf++ = flags==3? 'V': flags==2? 'v': flags? 'j': 'h';
+    *buf++ = hi < 077? (type & UR_PROTECTME? 'p': '-'): (type & UR_PROTECTME? 'P': 'p');
+    *buf++ = (hi < 077 && (type & UR_RESETFLAGS)) || hi >= 077? 'r': '-';
+    *buf++ = hi >= 077 && (type & UR_AUTOBAUD)? 'a': '-';                 // - means no
+    *buf++ = hi >= 077 && (type & UR_HAS_CE)? 'c': hi >= 077? '-': '.';   // . means don't know
+    *buf = 0;
+  } else if(hi) {               // Version number in binary from optiboot v4.1
+    sprintf(buf, "o%d.%d -%cs-%c-r--", hi, type,
+      ur.blguessed? (ur.bleepromrw? 'e': '-'): '?',
+      ur.blguessed? "hjvV"[ur.vbllevel & 3]: '?');
+  } else
+    sprintf(buf, "x0.0 .........");
+
+  return;
+}
+
+
+// Return name of the vector with number num
+static const char *vblvecname(const PROGRAMMER *pgm, int num) {
+  // This should never happen
+  if(num < -1 || num > ur.uP.ninterrupts || !ur.uP.isrtable)
+    return("unknown");
+  if(num == -1)
+    return "none";
+  if(num == ur.uP.ninterrupts)
+    return "VBL_ADDITIONAL_VECTOR";
+  return ur.uP.isrtable[num];
+}
+
+
+// Check protocol bytes and read result if needed
+static int urclock_res_check(const PROGRAMMER *pgm, const char *funcname, int ignore,
+  unsigned char *res, int expected) {
+
+  unsigned char chr;
+
+  if(urclock_recv(pgm, &chr, 1) < 0)
+    return -1;
+  if(chr != ur.STK_INSYNC) {
+    pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x in %s()\n",
+      ur.STK_INSYNC, chr, funcname);
+    return -1;
+  }
+
+  // Potentially ignore some initial bytes of the reply
+  while(ignore--)
+    if(urclock_recv(pgm, &chr, 1) < 0)
+      return -1;
+
+  // Read the reply from previous command if requested
+  if(res && expected > 0)
+    if(urclock_recv(pgm, res, expected) < 0)
+      return -1;
+
+  if(urclock_recv(pgm, &chr, 1) < 0)
+    return -1;
+  if(chr != ur.STK_OK) {
+    pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x in %s()\n",
+      ur.STK_OK, chr, funcname);
+    return -1;
+  }
+
+  return 0;
+}
+
+
+// set ur.uP from mcuid, potentially overwritten by p
+static void set_uP(const PROGRAMMER *pgm, const AVRPART *p, int mcuid, int mcuid_wins) {
+  int idx_m = -1, idx_p = -1;
+
+  if(mcuid < 0 && !p)           // This should never happen
+    pmsg_warning("cannot set ur.uP as neither mcuid nor part given\n");
+
+  if(mcuid >= 0)
+    if((idx_m = upidxmcuid(mcuid)) < 0)
+      pmsg_warning("uP_table does not know mcuid %d\n", mcuid);
+
+  if(p) {
+    if(p->mcuid >= 0)
+      idx_p = upidxmcuid(p->mcuid);
+    if(idx_p < 0 && p->desc && *p->desc)
+      idx_p = upidxname(p->desc);
+    if(idx_p < 0)
+      pmsg_warning("uP_table does not know mcuid %d nor part %s\n",
+        p->mcuid, p->desc && *p->desc? p->desc: "???");
+  }
+
+  ur.uP.name = NULL;
+  if(idx_m >= 0 && idx_p >= 0)
+    ur.uP = uP_table[mcuid_wins? idx_m: idx_p];
+  else if(idx_m >= 0)
+    ur.uP = uP_table[idx_m];
+  else if(idx_p >= 0)
+    ur.uP = uP_table[idx_p];
+
+  if(!ur.uP.name && p) {        // Not found in uP_table? Fill in from p; -1 means unknown
+    AVRMEM *mem;
+
+    ur.uP.name = p->desc;
+    ur.uP.mcuid = p->mcuid;
+    ur.uP.avrarch =
+      p->prog_modes & PM_UPDI? F_AVR8X:
+      p->prog_modes & PM_PDI? F_XMEGA:
+      p->prog_modes & PM_TPI? F_AVR8L:
+      p->prog_modes & (PM_ISP | PM_HVPP | PM_HVSP)? F_AVR8: 0;
+    memcpy(ur.uP.sigs, p->signature, sizeof ur.uP.sigs);
+    if((mem = avr_locate_mem(p, "flash"))) {
+      ur.uP.flashoffset = mem->offset;
+      ur.uP.flashsize = mem->size;
+      ur.uP.pagesize = mem->page_size;
+    } else {
+      ur.uP.flashoffset = -1;
+      ur.uP.flashsize = -1;
+      ur.uP.pagesize = -1;
+    }
+    ur.uP.nboots = -1;
+    ur.uP.bootsize = -1;
+    if((mem = avr_locate_mem(p, "eeprom"))) {
+      ur.uP.eepromoffset = mem->offset;
+      ur.uP.eepromsize = mem->size;
+      ur.uP.eeprompagesize = mem->page_size;
+    } else {
+      ur.uP.eepromoffset = -1;
+      ur.uP.eepromsize = -1;
+      ur.uP.eeprompagesize = -1;
+    }
+    ur.uP.sramstart = -1;
+    ur.uP.sramsize = -1;
+    ur.uP.nfuses = -1;
+    ur.uP.nlocks = -1;
+    ur.uP.ninterrupts = p->n_interrupts;
+    ur.uP.isrtable = NULL;
+  }
+}
+
+
+// https://en.wikipedia.org/wiki/Jenkins_hash_function
+static uint32_t jenkins_hash(const uint8_t* key, size_t length) {
+  size_t i = 0;
+  uint32_t hash = 0;
+
+  while (i != length) {
+    hash += key[i++];
+    hash += hash << 10;
+    hash ^= hash >> 6;
+  }
+  hash += hash << 3;
+  hash ^= hash >> 11;
+  hash += hash << 15;
+
+  return hash;
+}
+
+typedef struct {
+  uint16_t sz, ee;
+  uint32_t h256, hash;
+} Blhash_t;
+
+static int cmpblhash(const void *va, const void *vb) {
+  const Blhash_t *a = va, *b = vb;
+  return a->sz > b->sz? 1: a->sz < b->sz? -1: a->hash > b->hash? 1: a->hash < b->hash? -1: 0;
+}
+
+static void guessblstart(const PROGRAMMER *pgm, const AVRPART *p) {
+  if(ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) // Cannot read flash
+    return;
+
+  Blhash_t blist[] = {
+    { 1024, 0, 0x35445c45, 0x9ef77953 }, // ATmegaBOOT-prod-firmware-2009-11-07.hex
+    { 1024, 0, 0x32b1376c, 0xceba80bb }, // ATmegaBOOT.hex
+    { 2048, 0, 0x08426ba2, 0x29e81e21 }, // ATmegaBOOT_168.hex
+    { 4096, 0, 0x1bf8ed1b, 0x272e49ed }, // ATmegaBOOT_168_atmega1280.hex
+    { 2048, 0, 0x9774b926, 0x335016ed }, // ATmegaBOOT_168_atmega328.hex
+    { 4096, 0, 0x3242ddd3, 0x809632a3 }, // ATmegaBOOT_168_atmega328_bt.hex
+    { 2048, 0, 0xc553f5b4, 0x56be91cb }, // ATmegaBOOT_168_atmega328_pro_8MHz.hex
+    { 2048, 0, 0x12ab8da0, 0xca46a3ca }, // ATmegaBOOT_168_diecimila.hex
+    { 2048, 0, 0x3242ddd3, 0xf3e94dba }, // ATmegaBOOT_168_ng.hex
+    { 2048, 0, 0x2eed30b3, 0x47d14ffa }, // ATmegaBOOT_168_pro_8MHz.hex
+    { 4096, 0, 0xc52edd05, 0xa3371f94 }, // Caterina-LilyPadUSB.hex
+    { 4096, 0, 0x663b8f7e, 0x7efdda2b }, // Caterina-Robot-Control.hex
+    { 4096, 0, 0x3c6387e7, 0x7e96eea2 }, // Caterina-Robot-Motor.hex
+    { 2048, 0, 0x1cef0d75, 0x6cfbac49 }, // LilyPadBOOT_168.hex
+    { 1024, 1, 0x6ca0f37b, 0x31bae545 }, // bigboot_328.hex
+    {  512, 0, 0x035cbc07, 0x24ba435e }, // optiboot_atmega168.hex
+    {  512, 0, 0x455050db, 0x1d53065f }, // optiboot_atmega328-Mini.hex
+    {  512, 0, 0xd2001ddb, 0x16c9663b }, // optiboot_atmega328.hex v4.4
+    {  512, 0, 0x49c1e9a4, 0xa450759b }, // optiboot_atmega328.hex v8.3
+    {  512, 0, 0xc54dcd6c, 0x5bfc5d06 }, // optiboot_atmega8.hex
+    {  256, 0, 0x5a01c55b, 0x5a01c55b }, // picobootArduino168.hex
+    {  256, 0, 0x1451061b, 0x1451061b }, // picobootArduino168v3b2.hex
+    {  512, 0, 0x3242ddd3, 0x53348738 }, // picobootArduino328.hex
+    {  512, 0, 0x858e12de, 0xc80a44a4 }, // picobootArduino328v3beta.hex
+    {  256, 0, 0xaa62bafc, 0xaa62bafc }, // picobootArduino8v3rc1.hex
+    {  256, 0, 0x56263965, 0x56263965 }, // picobootSTK500-168p.hex
+    {  512, 0, 0x3242ddd3, 0x5ba5f5f6 }, // picobootSTK500-328p.hex
+  };
+
+  uint8_t buf[4096], b128[128];
+
+  qsort(blist, sizeof blist/sizeof*blist, sizeof*blist, cmpblhash);
+  for(int ii, si = 0, sz = 0, bi = 0; si < (int) sizeof blist/sizeof*blist; si++) {
+    if(blist[si].sz > sz) { // Read in and compare
+      sz = blist[si].sz;
+      if(sz > ur.uP.flashsize/2 || (sz+127)/128*128 > (int) sizeof buf)
+        return;
+      while(bi < sz) {
+       if(ur_readEF(pgm, p, b128, ur.uP.flashsize-bi-128, 128, 'F') < 0)
+         return;
+       for(int ti=127; ti >= 0; ti--) // read in backwards
+         buf[bi++] = b128[ti];
+      }
+
+      // Does the hash for the full size match? OK: found a known bootloader
+      uint32_t hash = jenkins_hash(buf, sz);
+      for(ii = 0; ii < (int) sizeof blist/sizeof*blist; ii++)
+        if(blist[ii].hash == hash && sz == blist[ii].sz && !(sz & (ur.uP.pagesize-1))) {
+          // Page aligned bootloader size matches
+          ur.blstart = ur.uP.flashsize-sz;
+          if(blist[ii].ee)
+            ur.bleepromrw = 1;
+          ur.blguessed = 1;
+          return;
+        }
+
+      // Can we exclude the top 256 byte flash from the botloader list?
+      if(sz == 256) {
+        for(ii = 0; ii < (int) sizeof blist/sizeof*blist; ii++)
+          if(hash == blist[ii].h256)
+            break;
+        if(ii >= (int) sizeof blist/sizeof*blist)
+          return;
+      }
+    }
+  }
+}
+
+
+/*
+ * Read signature bytes - Urclock version
+ *
+ * Piggy back reading urboot specific configuration from chip
+ *  - whether it is an urboot bootloader, if so,
+ *     + which version
+ *     + where the bootloader starts
+ *     + whether it is a vector bootloader, if so which vector is used
+ *  - urclock board ID from EEPROM
+ */
+static int urclock_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *sigmem) {
+  uint8_t buf[16];
+  int conn_idx;
+
+  // Signature byte reads are always 3 bytes
+  if(sigmem->size < 3) {
+    pmsg_error("memsize too small for sig byte read\n");
+    return -1;
+  }
+
+  if(ur.urprotocol) {           // Urprotocol's STK_INSYNC/STK_OK have already identified the part
+    // Got the signature already
+    memcpy(sigmem->buf, ur.uP.sigs, 3);
+  } else {
+    // Have to ask the bootloader directly
+    buf[0] = Cmnd_STK_READ_SIGN;
+    buf[1] = Sync_CRC_EOP;
+    if(urclock_send(pgm, buf, 2) < 0)
+      return -1;
+    if(urclock_res_check(pgm, __func__, 0, sigmem->buf, 3) < 0)
+      return -1;
+  }
+
+  if(ur.initialised)
+    return 3;
+
+  if(ovsigck || !ur.uP.name) {  // Keep -p ... MCU when given -F  or when not initialised
+    set_uP(pgm, p, -1, 0);
+    if(!ur.uP.name)
+      Return("cannot identify MCU from part %s", p->desc);
+  } else {
+    // If signatures of command line part and connected part differ complain and return
+    if(memcmp(sigmem->buf, p->signature, 3)) {
+      char names[1024];
+
+      if(ur.urprotocol)
+        Return("connected part %s differs in signature from -p %s (override with -F or use -p %s)",
+          ur.uP.name, p->desc, ur.uP.name);
+      if((conn_idx = upidxsig(sigmem->buf)) == -1)
+        Return("no uP_table entry from signature %02x %02x %02x (override with -F)",
+          sigmem->buf[0], sigmem->buf[1], sigmem->buf[2]);
+      if(upmatchingsig(sigmem->buf, names, sizeof names) == 1)
+        Return("connected part %s signature does not match -p %s's "
+          "(override with -F or use -p %s)",
+          uP_table[conn_idx].name, p->desc, uP_table[conn_idx].name);
+      Return("connected part's signature %02x%02x%02x is one of %s; neither matches -p %s's "
+        "(override with -F or use -p ...)",
+        sigmem->buf[0], sigmem->buf[1], sigmem->buf[2], names, p->desc);
+    }
+  }
+
+  return ur_initstruct(pgm, p);
+}
+
+static int ur_initstruct(const PROGRAMMER *pgm, const AVRPART *p) {
+  uint8_t spc[2048];
+  AVRMEM *flm;
+  int rc;
+
+  if(!(flm = avr_locate_mem(p, "flash")))
+    Return("cannot obtain flash memory for %s", p->desc);
+
+  if(flm->page_size > (int) sizeof spc)
+    Return("%s's flash page size %d is too large (enlarge spc[] and recompile)",
+      p->desc, flm->page_size);
+
+  if(flm->page_size <= 0)
+    Return("cannot deal with %s's flash page size of %d", p->desc, flm->page_size);
+
+  if(flm->page_size & (flm->page_size-1))
+    Return("cannot deal with %s's flash page size %d as not a power of 2",
+      p->desc, flm->page_size);
+
+  // Bail if command line part and connected part differ in important aspects (should not happen)
+  if(ur.uP.flashsize != flm->size)
+    Return("connected %s's flash size 0x%04x differs from -p %s's (0x%04x); "
+      "use correct -p ... or override with -F",
+      ur.uP.name, ur.uP.flashsize, p->desc, flm->size);
+  if(ur.uP.pagesize != flm->page_size)
+    Return("connected %s's flash page size %d differs from -p %s's (%d); "
+      "use correct -p ... or override with -F",
+      ur.uP.name, ur.uP.pagesize, p->desc, flm->page_size);
+  if(ur.uP.ninterrupts != p->n_interrupts)
+    Return("connected %s's number %d of interrupts differs from -p %s's (%d); "
+      "use correct -p ... or override with -F",
+      ur.uP.name, ur.uP.ninterrupts, p->desc, p->n_interrupts);
+
+  // No urboot bootloaders on AVR32 parts, neither on really small devices
+  if((p->prog_modes & PM_aWire) || flm->size < 512)
+    goto alldone;
+
+  ur.blstart = 0;
+  ur.vbllevel = 0;
+  ur.vblvectornum = -1;
+  ur.bleepromrw = 0;
+
+  // Manual provision of above bootloader parameters
+  if(ur.xbootsize) {
+    if(ur.xbootsize % ur.uP.pagesize)
+      Return("-xbootsize=%d size not a multiple of flash page size %d",
+        ur.xbootsize, ur.uP.pagesize);
+    if(ur.xbootsize < 64 || ur.xbootsize > urmin(8192, ur.uP.flashsize/4))
+      Return("implausible -xbootsize=%d, should be in [64, %d]",
+        ur.xbootsize, urmin(8192, ur.uP.flashsize/4));
+    ur.blstart = ur.uP.flashsize - ur.xbootsize;
+  }
+
+  if((int8_t) ur.uP.ninterrupts >= 0) // valid range is 0..127
+    if(ur.xvectornum < -1 || ur.xvectornum > ur.uP.ninterrupts)
+      Return("unknown interrupt vector #%d for vector bootloader -- should be in [-1, %d]",
+        ur.xvectornum, ur.uP.ninterrupts);
+  if(ur.xvectornum > 0) {
+    ur.vbllevel = 1;
+    ur.vblvectornum = ur.xvectornum;
+  }
+
+  if(ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH)) // Bootloader that cannot read flash?
+    if(!ur.blstart)
+      Return("please specify -xbootsize=<num> and, if needed, -xvectornum=<num> or -xeepromrw");
+
+  uint16_t v16 = 0xffff, rjmpwp = ret_opcode;
+
+  // Sporting chance that we can read top flash to get intell about bootloader
+  if(!ur.urprotocol || (ur.urfeatures & UB_READ_FLASH)) {
+    // Read top 6 bytes from flash memory to obtain extended information about bootloader and type
+    if((rc = ur_readEF(pgm, p, spc, flm->size-6, 6, 'F')))
+      return rc;
+
+    // In a urboot bootloader (v7.2 onwards) these six are as follows
+    uint8_t numpags = spc[0];   // Actually, these two only exist from v7.5 onwards
+    uint8_t vectnum = spc[1];
+    rjmpwp = buf2uint16(spc+2); // rjmp to bootloader pgm_write_page() or ret opcode
+    uint8_t cap = spc[4];       // Capability byte
+    uint8_t urver = spc[5];     // Urboot version (low three bits are minor version: 076 is v7.6)
+    v16 = buf2uint16(spc+4);    // Combo word for neatly printed version line of urboot bootloader
+
+    // Extensively check this is an urboot bootloader v7.2 .. v12.7 == 0147 and extract properties
+    if(urver >= 072 && urver <= 0147 && (isRjmp(rjmpwp) || rjmpwp == ret_opcode)) { // Prob urboot
+      ur.blurversion = urver;
+      ur.bleepromrw = iseeprom_cap(cap);
+      // Vector bootloader: 0 = none, 1 = external patching, 2 = bl patches, 3 = patches + verifies
+      if(!ur.vbllevel)          // Unless manually overwritten
+        ur.vbllevel = vectorbl_level_cap(cap);
+      if(urver >= 075) {        // Urboot v7.5+ encodes # of bootloader pages and vbl vector number
+        int blsize = numpags*flm->page_size;
+        // Size of urboot bootloader should be in [64, 2048] (in v7.6 these are 224-512 bytes)
+        if(blsize >= 64 && blsize <= 2048 && vectnum <= ur.uP.ninterrupts) { // Within range
+          int dfromend  = dist_rjmp(rjmpwp, ur.uP.flashsize) - 4;
+          // Further check whether writepage() rjmp opcode jumps backwards into bootloader
+          if(rjmpwp == ret_opcode || (dfromend >= -blsize && dfromend < -6)) { // Due diligence
+            if(ur.xbootsize) {
+              if(flm->size - blsize != ur.blstart) {
+                pmsg_warning("urboot bootloader size %d explicitly overwritten by -xbootsize=%d\n",
+                  blsize, ur.xbootsize);
+                if(!ovsigck && ur.vbllevel) {
+                  imsg_warning("this can lead to bricking the vector bootloader\n");
+                  return -1;
+                }
+              }
+            } else
+              ur.blstart = flm->size - blsize;
+
+            if(ur.xvectornum != -1) {
+              if(ur.vblvectornum != vectnum) {
+                pmsg_warning("urboot vector number %d overwritten by -xvectornum=%d\n",
+                  vectnum, ur.xvectornum);
+                imsg_warning("the application might not start\n");
+              }
+            } else
+              ur.vblvectornum = vectnum;
+          }
+        }
+      }
+    } else if(urver != 0xff) {  // Probably optiboot where the version number is two bytes
+      ur.bloptiversion = (urver<<8) + cap;
+    }
+
+    if(!ur.blstart && ur.vbllevel) { // An older version urboot vector bootloader?
+      int vecsz = ur.uP.flashsize <= 8192? 2: 4;
+
+       // Reset vector points to the bootloader and the bootloader has r/jmp to application?
+      if((rc = ur_readEF(pgm, p, spc, 0, 4, 'F')))
+        return rc;
+
+      uint16_t reset16 = buf2uint16(spc);
+
+      if(isRjmp(reset16)) {     // rjmp op code (could be from a large or a small part)
+        if((flm->size & (flm->size-1)) == 0) { // Flash size a power of 2? True for small parts
+          int guess = dist_rjmp(reset16, ur.uP.flashsize); // Relative destination to reset vector
+          while(guess < 0)      // Convert to absolute address
+            guess += flm->size;
+          if((guess & (flm->page_size-1)) == 0) // Page aligned? Good
+            if(flm->size - guess <= 2048) // Accept unless size of bootloader exceeds 2048 bytes
+              ur.blstart = guess;
+        }
+      } else if(vecsz == 4 && isJmp(reset16)) { // Jmp op code
+        int guess = addr_jmp(buf2uint32(spc));
+        if(guess < flm->size)
+          if((guess & (flm->page_size-1)) == 0) // Page aligned? Good
+            if(flm->size - guess <= 2048) // Accept unless size of bootloader exceeds 2048 bytes
+              ur.blstart = guess;
+      }
+
+      if(ur.blstart && ur.vblvectornum > 0)
+        goto vblvecfound;
+
+      if(ur.blstart) {          // Read bootloader to identify jump to vbl vector
+        int i, npages, j, n, toend, dist, wasop32, wasjmp, op16;
+        uint8_t *q;
+        uint16_t opcode;
+
+        op16 = wasjmp = wasop32 = 0;
+        toend = flm->size-ur.blstart; // Number of bytes to FLASHEND
+        npages = toend/flm->page_size;
+        for(i=0; i<npages; i++) {
+          // Read bootloader page by page
+          if((rc = ur_readEF(pgm, p, spc, ur.blstart+i*flm->page_size, flm->page_size, 'F')))
+            return rc;
+          for(n=flm->page_size/2, q=spc, j=0; j<n; j++, q+=2, toend-=2) { // Check 16-bit opcodes
+            opcode = buf2uint16(q);
+            if(wasjmp) {        // Opcode is the word address of the destination
+              wasjmp=0;
+              int dest = addr_jmp((opcode<<16) | op16);
+              if(dest % vecsz == 0 && dest <= ur.uP.ninterrupts*vecsz) { // "<=" for extended table
+                ur.vblvectornum = dest/vecsz; // Solve for the vbl vector number
+                goto vblvecfound;
+              }
+              op16 = 0;
+            } else if(wasop32) { // Skip opcode evaluation
+              wasop32 = 0;
+            } else if(isRjmp(opcode) && toend > 4) { // 4 top bytes of bl are data, not rjmp
+              // Does that rjmp end in the vector table?
+              if((dist = dist_rjmp(opcode, ur.uP.flashsize)) > toend &&
+                  dist <= toend+ur.uP.ninterrupts*vecsz) { // "<=" for extended vector table
+                ur.vblvectornum = (dist-toend)/vecsz; // Solve for the vbl vector number
+                goto vblvecfound;
+              }
+            } else if(isJmp(opcode) && toend > 6) { // 4 top bytes are data + 2 the jmp addr
+              op16 = opcode;
+              wasjmp = 1;       // Look at destination address in next loop iteration
+            } else if(isop32(opcode)) { // Skip next opcode, too
+              wasop32 = 1;
+            }
+          }
+        }
+      }
+    }
+
+    // Still no bootloader start address? Read in top flash and guess bootloader start
+    if(!ur.blstart)
+      guessblstart(pgm, p);
+
+    // Still no bootloader start address?
+    if(!ur.blstart) {
+      if(ur. bloptiversion)
+       Return("bootloader might be optiboot %d.%d? Please use -xbootsize=<num>\n",
+         ur.bloptiversion>>8, ur.bloptiversion & 255);
+      Return("unknown bootloader ... please specify -xbootsize=<num>\n");
+    }
+  }
+
+vblvecfound:
+  urbootPutVersion(pgm, ur.desc, v16, rjmpwp);
+
+  ur.mcode = 0xff;
+  if(ur.blstart) {
+    int nm = nmeta(1, ur.uP.flashsize); // 6 for date + size of store struct + 1 for mcode byte
+    // Showing properties mostly requires examining the bytes below bootloader for metadata
+    if(ur.showall || (ur.showid && *ur.iddesc && *ur.iddesc != 'E') || ur.showapp ||
+      ur.showstore || ur.showmeta || ur.showboot || ur.showversion || ur.showvector ||
+      ur.showpart || ur.showdate || ur.showfilename) {
+
+      if((rc = ur_readEF(pgm, p, spc, ur.blstart-nm, nm, 'F')))
+        return rc;
+
+      if(spc[nm-1] != 0xff) {
+        int32_t storesize = ur.uP.flashsize > (1<<16)? buf2uint32(spc+nm-5): buf2uint16(spc+nm-3);
+        int32_t storestart = ur.uP.flashsize > (1<<16)? buf2uint32(spc+nm-9): buf2uint16(spc+nm-5);
+        uint8_t mcode = spc[nm-1];
+        int nmdata = nmeta(mcode, ur.uP.flashsize);
+
+        // Check plausibility of metadata header just below bootloader
+        if(storestart > 0 && storestart == ur.blstart-nmdata-storesize) {
+          ur.storestart = storestart;
+          ur.storesize = storesize;
+          ur.mcode = mcode;
+          if(mcode) {
+            int16_t  yyyy;
+            int8_t mm, dd, hr, mn;
+            mn = spc[5];
+            hr = spc[4];
+            dd = spc[3];
+            mm = spc[2];
+            yyyy = buf2uint16(spc);
+
+            // Is the date plausible? carry on; note this won't work after the year 2999 CE :-O
+            if(yyyy > 0 && yyyy < 3000 && mm > 0 && mm < 13 && dd > 0 && dd < 32 &&
+              hr >= 0 && hr < 24 && mn >= 0 && mn < 60) {
+
+              ur.yyyy = yyyy;
+              ur.mm = mm;
+              ur.dd = dd;
+              ur.hr = hr;
+              ur.mn = mn;
+              if(mcode > 1) {   // Copy application name over
+                rc = ur_readEF(pgm, p, spc, ur.blstart-nmeta(mcode, ur.uP.flashsize), mcode, 'F');
+                if(rc < 0)
+                  return rc;
+                int len = mcode<sizeof ur.filename? mcode: sizeof ur.filename;
+                memcpy(ur.filename, spc, len);
+                ur.filename[len-1] = 0;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+  // Print and exit when option show... was given
+  int first=1;
+  int single = !ur.showall && (!!ur.showid + !!ur.showapp + !!ur.showstore + !!ur.showmeta +
+    !!ur.showboot + !!ur.showversion + !!ur.showvector + !!ur.showpart + !!ur.showdate +
+    !!ur.showfilename) == 1;
+
+  if(ur.showid || ur.showall) {
+    uint64_t urclockID;
+    if((rc = readUrclockID(pgm, p, &urclockID)) == -1)
+      return rc;
+    term_out("%0*lx", 2*ur.idlen, urclockID), first=0;
+  }
+  if(ur.showdate || ur.showall)
+    term_out(&" %04d-%02d-%02d %02d.%02d"[first], ur.yyyy, ur.mm, ur.dd, ur.hr, ur.mn), first=0;
+  if(ur.showfilename || ur.showall)
+    term_out(&" %s"[first], *ur.filename? ur.filename: ""), first=0;
+  if(ur.showapp || ur.showall)
+    term_out(&" %s%d"[first], single || *ur.filename? "": "application ", ur.storestart), first=0;
+  if(ur.showstore || ur.showall)
+    term_out(&" %s%d"[first], single? "": "store ", ur.storesize), first=0;
+  if(ur.showmeta || ur.showall)
+    term_out(&" %s%d"[first], single? "": "meta ", nmeta(ur.mcode, ur.uP.flashsize)), first=0;
+  if(ur.showboot || ur.showall)
+    term_out(&" %s%d"[first], single? "": "boot ", ur.blstart? flm->size-ur.blstart: 0), first=0;
+  if(ur.showversion || ur.showall)
+    term_out(&" %s"[first], ur.desc+(*ur.desc==' ')), first=0;
+  if(ur.showvector || ur.showall) {
+    int vnum = ur.vbllevel? ur.vblvectornum & 0x7f: 0;
+    term_out(&" vector %d (%s)"[first], vnum, vblvecname(pgm, vnum)), first=0;
+  }
+  if(ur.showall || ur.showpart)
+    term_out(&" %s"[first], ur.uP.name);
+  if(!first) {
+    term_out("\n");
+    exit(0);
+  }
+
+alldone:
+
+  ur.initialised = 1;
+  return 3;
+}
+
+
+// STK500 section from stk500.c but modified significantly for use with urboot bootloaders
+
+// STK500v1 load correct address for flash/eeprom, memchr is 'E'/'F'
+static int urclock_load_baddr(const PROGRAMMER *pgm, const AVRPART *p, char memchr,
+  unsigned int baddr) {
+
+  unsigned char buf[16], ext_byte;
+
+  // For classic parts (think optiboot, avrisp) use word addr, otherwise byte addr (optiboot_x etc)
+  int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire));
+  unsigned int addr = classic? baddr/2: baddr;
+  int effpgsiz = classic? ur.uP.pagesize/2: ur.uP.pagesize;
+
+  // STK500 protocol: support flash > 64k words/bytes with the correct extended-address byte
+  if(memchr == 'F' && ur.uP.flashsize > (classic? 128*1024: 64*1024)) {
+    ext_byte = (addr >> 16) & 0xff;
+    if(ext_byte != ur.ext_addr_byte) {
+      // Either this is the first addr load, or a 64k boundary is crossed
+      buf[0] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>24);
+      buf[1] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT>>16);
+      buf[2] = ext_byte;
+      buf[3] = (uint8_t) (Subc_STK_UNIVERSAL_LEXT);
+      urclock_cmd(pgm, buf, buf);
+      ur.ext_addr_byte = ext_byte;
+    }
+    /*
+     * Ensure next paged r/w will reload ext addr if page is just below a 64k boundary
+     * to iron out a bug in some bootloaders
+     */
+    if((addr & 0xffff0000) != ((addr+effpgsiz) & 0xffff0000))
+      ur.ext_addr_byte = 0xff;
+  }
+
+  buf[0] = Cmnd_STK_LOAD_ADDRESS;
+  buf[1] = addr & 0xff;
+  buf[2] = (addr >> 8) & 0xff;
+  buf[3] = Sync_CRC_EOP;
+
+  if(urclock_send(pgm, buf, 4) < 0)
+    return -1;
+
+  return urclock_res_check(pgm, __func__, 0, NULL, 0);
+}
+
+
+/*
+ * Send a paged cmd to device
+ *  - rwop is Cmnd_STK_READ/PROG_PAGE
+ *  - badd is the byte address, len the length of data
+ *  - mchr is 'F' (flash) or 'E' (EEPROM)
+ *  - payload for bytes to write or NULL for read
+ */
+static int urclock_paged_rdwr(const PROGRAMMER *pgm, const AVRPART *part, char rwop,
+  unsigned int badd, int len, char mchr, char *payload) {
+
+  int i;
+  uint8_t buf[1024 + 5];
+
+  // STK500v1 only: tell the bootloader which address should be used by next paged command
+  if(!ur.urprotocol && urclock_load_baddr(pgm, part, mchr, badd) < 0)
+      return -1;
+
+  if(mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE) {
+    if(len != ur.uP.pagesize)
+      Return("len %d must be page size %d for paged flash writes", len, ur.uP.pagesize);
+
+    if(badd < 4U && ur.blstart && ur.vbllevel==1) {
+      int vecsz = ur.uP.flashsize <= 8192? 2: 4;
+      unsigned char jmptoboot[4];
+      int resetsize = set_reset(pgm, jmptoboot, vecsz);
+
+      if(badd < (unsigned int) resetsize) { // Ensure reset vector points to bl
+        int n = urmin((unsigned int) resetsize - badd, (unsigned int) len);
+        int resetdest;
+
+        if(badd == 0 && len >= vecsz) {
+          if(reset2addr((unsigned char *) payload, vecsz, ur.uP.flashsize, &resetdest) < 0 ||
+            resetdest != ur.blstart) {
+
+            memcpy(payload, jmptoboot, resetsize);
+            pmsg_info("forcing reset vector to point to vector bootloader\n");
+          }
+        } else if(memcmp(payload, jmptoboot+badd, n)) {
+          memcpy(payload, jmptoboot+badd, n);
+          pmsg_info("forcing partial reset vector to point to vector bootloader\n");
+        }
+      }
+    }
+  }
+
+  if(ur.urprotocol) {
+    uint8_t *q = buf, op =
+      mchr == 'F' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_FL:
+      mchr == 'E' && rwop == Cmnd_STK_PROG_PAGE? Cmnd_UR_PROG_PAGE_EE:
+      mchr == 'F' && rwop == Cmnd_STK_READ_PAGE? Cmnd_UR_READ_PAGE_FL:
+      mchr == 'E' && rwop == Cmnd_STK_READ_PAGE? Cmnd_UR_READ_PAGE_EE: 0xff;
+
+    if(op == 0xff)
+      Return("command not recognised");
+
+    *q++ = op;
+    *q++ = badd & 0xff;
+    *q++ = (badd >> 8) & 0xff;
+    // Flash is larger than 64 kBytes, extend address (even for EEPROM)
+    if(ur.uP.flashsize > 0x10000)
+      *q++ = (badd >> 16) & 0xff;
+
+    if(ur.uP.pagesize <= 256) {
+      if(len > 256)
+        Return("urprotocol paged r/w len %d cannot exceed 256", len);
+      *q++ = len;               // len==256 is sent as 0
+    } else {
+      int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256;
+      if(len > max)
+        Return("urprotocol paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name);
+      *q++ = len>>8;            // Big endian length when needed
+      *q++ = len;
+    }
+    i = q-buf;
+
+  } else {
+    int max = ur.uP.pagesize > 256? ur.uP.pagesize: 256;
+    if(len > max)
+      Return("stk500 paged r/w len %d cannot exceed %d for %s", len, max, ur.uP.name);
+
+    buf[0] = rwop;
+    buf[1] = len>>8;            // Big endian length when needed
+    buf[2] = len;
+    buf[3] = mchr;
+    i = 4;
+  }
+
+  if(payload) {                 // Bytes to write
+    if(len < 0 || len > (int) sizeof buf - 5)
+      Return("too small buf[] for len %d (enlarge buf[] and recompile)", len);
+    memcpy(buf+i, payload, len);
+    i += len;
+  }
+
+  buf[i] = Sync_CRC_EOP;
+
+  return urclock_send(pgm, buf, i+1);
+}
+
+
+/*
+ * Read len bytes at byte address addr of EEPROM (mchr == 'E') or flash (mchr == 'F') from
+ * device fd into buffer buf, using extended addressing if needed (extd); returns 0 on success
+ */
+static int ur_readEF(const PROGRAMMER *pgm, const AVRPART *p, uint8_t *buf, uint32_t badd, int len,
+  char mchr) {
+
+  int classic = !(p->prog_modes & (PM_UPDI | PM_PDI | PM_aWire));
+
+  pmsg_debug("ur_readEF(%s, %s, %s, %p, 0x%06x, %d, %c)\n",
+    (char *) ldata(lfirst(pgm->id)), p->desc, mchr=='F'? "flash": "eeprom", buf, badd, len, mchr);
+
+  if(mchr == 'F' && ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH))
+    Return("bootloader does not have flash read capability");
+
+  if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw)
+    Return("bootloader does not %shave EEPROM access capability", ur.blurversion? "": "seem to ");
+
+  if(len < 1 || len > urmax(ur.uP.pagesize, 256))
+    Return("len %d exceeds range [1, %d]", len, urmax(ur.uP.pagesize, 256));
+
+  // Odd byte address under word-address protocol for "classic" parts (optiboot, avrisp etc)
+  int odd = !ur.urprotocol && classic && (badd&1);
+  if(odd) {                     // Need to read one extra byte
+    len++;
+    badd &= ~1;
+    if(len > urmax(ur.uP.pagesize, 256))
+      Return("len+1 = %d odd address exceeds range [1, %d]", len, urmax(ur.uP.pagesize, 256));
+  }
+
+  if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, badd, len, mchr, NULL) < 0)
+    return -1;
+
+  return urclock_res_check(pgm, __func__, odd, buf, len-odd);
+}
+
+
+static int parseUrclockID(const PROGRAMMER *pgm) {
+  if(*ur.iddesc) {              // User override of ID, eg, -xid=F.-4.2 for penultimate flash word
+    char *idstr = cfg_strdup(__func__, ur.iddesc), *idlenp, *end;
+    unsigned long ad, lg;
+
+    if(!(strchr("EF", *idstr) && idstr[1] == '.')) {
+      pmsg_warning("-xid=%s string must start with E. or F.\n", ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+
+    if(!(idlenp = strchr(idstr+2, '.'))) {
+      pmsg_warning("-xid=%s string must look like [E|F].<addr>.<len>\n", ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+    *idlenp++ = 0;
+    ad = strtoul(idstr+2, &end, 0);
+    if(*end || end == idstr+2) {
+      pmsg_warning("cannot parse address %s of -xid=%s\n", idstr+2, ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+    long sad = *(long *) &ad;
+    if(sad < INT_MIN || sad > INT_MAX) {
+      pmsg_warning("address %s of -xid=%s has implausible size\n", idstr+2, ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+
+    lg = strtoul(idlenp, &end, 0);
+    if(*end || end == idlenp) {
+      pmsg_warning("cannot parse length %s of -xid=%s string\n", idlenp, ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+    if(!lg || lg > 8) {
+      pmsg_warning("length %s of -xid=%s string must be between 1 and 8\n", idlenp, ur.iddesc);
+      free(idstr);
+      return -1;
+    }
+
+    ur.idmchr = *idstr;
+    ur.idaddr = sad;
+    ur.idlen = lg;
+
+    free(idstr);
+  }
+
+  return 0;
+}
+
+
+static int readUrclockID(const PROGRAMMER *pgm, const AVRPART *p, uint64_t *urclockIDp) {
+  uint8_t spc[16];
+  int mchr, addr, len, size;
+
+  if(ur.idlen)
+    mchr = ur.idmchr, addr = ur.idaddr, len = ur.idlen;
+  else
+    mchr = 'E', addr = 256+1, len = 6; // Default location for unique id on urclock boards
+
+  *urclockIDp = 0;
+
+  // Sanity for small boards in absence of user -xid=... option
+  if(!ur.idlen && (addr >= ur.uP.eepromsize || addr+len > ur.uP.eepromsize)) {
+    addr = 0;
+    if(ur.uP.eepromsize < 8)
+      mchr = 'F';
+  }
+
+  const char *memtype = mchr == 'E'? "eeprom": "flash";
+
+  size = mchr == 'F'? ur.uP.flashsize: ur.uP.eepromsize;
+
+  if(ur.uP.name && size > 0) {
+    if(addr < 0)                // X.-4.4 asks for 4 bytes at top memory
+      addr += size;
+
+    if(addr < 0 || addr >= size)
+      Return("effective address %d of -xids=%s string out of %s range [0, 0x%04x]\n",
+        addr, ur.iddesc, memtype, size-1);
+
+    if(addr+len > size)
+      Return("memory range [0x%04x, 0x%04x] of -xid=%s out of %s range [0, 0x%04x]\n",
+        addr, addr+len-1, ur.iddesc, memtype, size-1);
+  }
+
+  memset(spc, 0, sizeof spc);
+  if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw)
+    return -2;
+
+  if(ur_readEF(pgm, p, spc, addr, len, mchr) < 0)
+    return -1;
+
+  // Urclock ID
+  for(int i = len-1; i >= 0; i--)
+    *urclockIDp <<= 8, *urclockIDp |= spc[i];
+  ur.idlen = len;
+
+  return 0;
+}
+
+
+static int urclock_send(const PROGRAMMER *pgm, unsigned char *buf, size_t len) {
+  return serial_send(&pgm->fd, buf, len);
+}
+
+
+static int urclock_recv(const PROGRAMMER *pgm, unsigned char *buf, size_t len) {
+  int rv;
+
+  rv = serial_recv(&pgm->fd, buf, len);
+  if(rv < 0) {
+    if(!ur.sync_silence)
+      pmsg_warning("programmer is not responding%s\n", ur.uP.name? "": "; try, eg, -xdelay=200");
+    return -1;
+  }
+
+  return 0;
+}
+
+
+#define MAX_SYNC_ATTEMPTS 23
+
+/*
+ * The modified protocol makes stk_insync and stk_ok responses variable but fixed for a single
+ * programming session, so bootloader can pass on en passant 16 bit info about which part it was
+ * compiled for and some of its own properties. Urlcock_getsync() therefore tries a few times to
+ * sync until the stk_insync/ok responses coincide with the the most recent responses.
+ */
+static int urclock_getsync(const PROGRAMMER *pgm) {
+  unsigned char iob[2];
+  int attempt;
+
+  // Reduce timeout for establishing comms
+  serial_recv_timeout = 80;     // ms
+
+  ur.sync_silence = 1;
+  iob[0] = Cmnd_STK_GET_SYNC;   // Initial sync for autobaud - drain response
+  iob[1] = Sync_CRC_EOP;
+  urclock_send(pgm, iob, 2);
+  serial_drain(&pgm->fd, 0);
+
+  for(attempt = 0; attempt < MAX_SYNC_ATTEMPTS; attempt++) {
+    iob[0] = Cmnd_STK_GET_SYNC;
+    iob[1] = Sync_CRC_EOP;
+    urclock_send(pgm, iob, 2);
+    if(urclock_recv(pgm, iob, 2) == 0) { // Expect bootloader to respond with two bytes
+      if(!ur.gs.seen || iob[0] != ur.gs.stk_insync || iob[1] != ur.gs.stk_ok || iob[0] == iob[1]) {
+        ur.gs.stk_insync = iob[0];
+        ur.gs.stk_ok = iob[1];
+        if(ur.gs.seen)
+          serial_drain(&pgm->fd, 0);
+        ur.gs.seen = 1;
+      } else
+        break;
+    }
+    if(attempt > 2) {           // Don't report first three attempts
+      ur.sync_silence = 0;
+      pmsg_warning("attempt %d of %d: not in sync\n", attempt - 2, MAX_SYNC_ATTEMPTS-3);
+    }
+  }
+  ur.sync_silence = 0;
+
+  serial_recv_timeout = 500;    // ms
+
+  if(attempt == MAX_SYNC_ATTEMPTS)
+    return -1;
+
+  ur.STK_INSYNC = ur.gs.stk_insync;
+  ur.STK_OK     = ur.gs.stk_ok;
+  memset(&ur.uP, 0, sizeof ur.uP);
+
+  // One of the STK500 protocol bytes different from ordinary? If so, it's the urboot protocol
+  if(ur.gs.stk_insync != Resp_STK_INSYNC || ur.gs.stk_ok != Resp_STK_OK) {
+    // Regain urboot info from stk_insync and stk_ok
+    if(ur.gs.stk_insync == 255 && ur.gs.stk_ok == 254) {
+      ur.gs.stk_insync = Resp_STK_INSYNC;
+      ur.gs.stk_ok = Resp_STK_OK;
+    } else if(ur.gs.stk_ok > ur.gs.stk_insync)
+      ur.gs.stk_ok--;
+
+    int16_t bootinfo = ur.gs.stk_insync*255 + ur.gs.stk_ok;
+    int mcuid = UB_MCUID(bootinfo);
+    ur.urfeatures = UB_FEATURES(bootinfo);
+    ur.urprotocol = 1;
+
+    set_uP(pgm, partdesc? locate_part(part_list, partdesc): NULL, mcuid, 1);
+    if(!ur.uP.name)
+      Return("cannot identify MCU");
+    if(!partdesc)               // Provide partdesc info, so user does not have to set it
+      partdesc = cache_string(ur.uP.name);
+  } else {
+    ur.urprotocol = 0;
+    if(partdesc) {              // Initialise uP from command line for now
+      set_uP(pgm, locate_part(part_list, partdesc), -1, 0);
+      if(!ur.uP.name)
+        Return("cannot identify MCU from partdesc %s", partdesc);
+    }
+  }
+
+  return 0;
+}
+
+
+/*
+ * The urclock bootloader ignores all but two STK_UNIVERSAL commands (load extended address and
+ * chip erase) , so only sending these through. Transmits the device command and returns the
+ * results; 'cmd' and 'res' must point to at least a 4 byte data buffer
+ */
+static int urclock_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
+  if(cmd[0] == (Subc_STK_UNIVERSAL_LEXT>>24) ||
+    (cmd[0] == (Subc_STK_UNIVERSAL_CE>>24) && cmd[1] == (uint8_t)(Subc_STK_UNIVERSAL_CE>>16))) {
+
+    unsigned char buf[32];
+
+    buf[0] = Cmnd_STK_UNIVERSAL;
+    buf[1] = cmd[0];
+    buf[2] = cmd[1];
+    buf[3] = cmd[2];
+    buf[4] = cmd[3];
+    buf[5] = Sync_CRC_EOP;
+
+    if(urclock_send(pgm, buf, 6) < 0)
+      return -1;
+    if(urclock_recv(pgm, buf, 1) < 0)
+      return -1;
+    if(buf[0] != ur.STK_INSYNC) {
+      pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x\n", ur.STK_INSYNC, buf[0]);
+      return -1;
+    }
+
+    res[0] = cmd[1];
+    res[1] = cmd[2];
+    res[2] = cmd[3];
+    if(urclock_recv(pgm, &res[3], 1) < 0)
+      return -1;
+
+    if(urclock_recv(pgm, buf, 1) < 0)
+      return -1;
+    if(buf[0] != ur.STK_OK) {
+      pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x\n", ur.STK_OK, buf[0]);
+      return -1;
+    }
+
+  } else {
+    // All other requests: pretend call happened and all is good, returning 0xff each time
+    memcpy(res, cmd+1, 3);
+    res[3] = 0xff;
+  }
+
+  return 0;
+}
+
+
+// Either emulate chip erase or send appropriate command to bootloader
+static int urclock_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
+  unsigned char buf[16];
+  long bak_timeout = serial_recv_timeout;
+
+  // Set timeout to 20 ms per page as chip erase may take a long time
+  serial_recv_timeout = ur.uP.pagesize > 2? 500 + ur.uP.flashsize/ur.uP.pagesize * 20: 20000;
+
+  int emulated = 0;
+
+  if(ur.xemulate_ce ||
+    (ur.urprotocol && !(ur.urfeatures & UB_CHIP_ERASE)) ||
+    ur.bloptiversion || (ur.blurversion && ur.blurversion < 076)) {
+
+    // Bootloader does not implement chip erase: don't send command to bootloader
+    ur.emulate_ce = 1;
+    emulated = 1;
+
+  } else if(ur.urprotocol) {    // Urprotocol uses chip erase command directly
+    pmsg_notice2("chip erase via urprotocol\n");
+
+    buf[0] = Cmnd_STK_CHIP_ERASE;
+    buf[1] = Sync_CRC_EOP;
+
+    if(urclock_send(pgm, buf, 2) < 0)
+      return -1;
+    if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0)
+      return -1;
+
+  } else {                      // Legacy bootloaders use universal extension
+    pmsg_notice2("chip erase via universal STK500v1 command\n");
+
+    if (pgm->cmd == NULL) {     // Should not happen
+      pmsg_error("%s programmer does not provide a cmd() method\n", pgm->type);
+      return -1;
+    }
+
+    memset(buf, 0, sizeof(buf));
+
+    buf[0] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>24);
+    buf[1] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>16);
+    buf[2] = (uint8_t) (Subc_STK_UNIVERSAL_CE>>8);
+    buf[3] = (uint8_t) (Subc_STK_UNIVERSAL_CE);
+
+    if(urclock_cmd(pgm, buf, buf+4) < 0)
+      return -1;
+  }
+
+  serial_recv_timeout = bak_timeout;
+  ur.done_ce = 1;
+
+  if(!emulated) {               // Write jump to boot section to reset vector
+    if(ur.blstart && ur.vbllevel==1) {
+      AVRMEM *flm = avr_locate_mem(p, "flash");
+      int vecsz = ur.uP.flashsize <= 8192? 2: 4;
+      if(flm && flm->page_size >= vecsz) {
+        unsigned char *page = cfg_malloc(__func__, flm->page_size);
+        memset(page, 0xff, flm->page_size);
+        set_reset(pgm, page, vecsz);
+        if(avr_write_page_default(pgm, p, flm, 0, page) < 0) {
+          free(page);
+          return -1;
+        }
+        free(page);
+      }
+    }
+  }
+
+  return emulated? LIBAVRDUDE_SOFTFAIL: 0;
+}
+
+
+// Issue the 'program enable' command to the AVR device
+static int urclock_program_enable(const PROGRAMMER *pgm, const AVRPART *p_unused) {
+  unsigned char buf[16];
+
+  buf[0] = Cmnd_STK_ENTER_PROGMODE;
+  buf[1] = Sync_CRC_EOP;
+
+  if(urclock_send(pgm, buf, 2) < 0)
+    return -1;
+
+  return urclock_res_check(pgm, __func__, 0, NULL, 0);
+}
+
+
+static void urclock_enable(PROGRAMMER *pgm_unused, const AVRPART *p_unused) {
+  return;
+}
+
+
+// Initialise the AVR device and prepare it to accept commands
+static int urclock_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
+  return pgm->program_enable(pgm, p);
+}
+
+
+static void urclock_disable(const PROGRAMMER *pgm) {
+  unsigned char buf[16];
+
+  buf[0] = Cmnd_STK_LEAVE_PROGMODE;
+  buf[1] = Sync_CRC_EOP;
+
+  if(urclock_send(pgm, buf, 2) < 0)
+    return;
+  if(urclock_recv(pgm, buf, 1) < 0)
+    return;
+  if(buf[0] != ur.STK_INSYNC) {
+    pmsg_error("protocol expects sync byte 0x%02x but got 0x%02x\n", ur.STK_INSYNC, buf[0]);
+    return;
+  }
+
+  if(urclock_recv(pgm, buf, 1) < 0)
+    return;
+  if(buf[0] == ur.STK_OK)
+    return;
+
+  pmsg_error("protocol expects OK byte 0x%02x but got 0x%02x\n", ur.STK_OK, buf[0]);
+  return;
+}
+
+
+static int urclock_open(PROGRAMMER *pgm, const char *port) {
+  union pinfo pinfo;
+
+  strcpy(pgm->port, port);
+  pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
+  if(serial_open(port, pinfo, &pgm->fd) == -1)
+    return -1;
+
+  // Clear DTR and RTS to unload the RESET capacitor
+  serial_set_dtr_rts(&pgm->fd, 0);
+  usleep(20*1000);              // 20 ms is ample for dis/charging the cap from reset to DTR/RTS
+  // Set DTR and RTS back to high
+  serial_set_dtr_rts(&pgm->fd, 1);
+
+  if((80+ur.delay) > 0)
+    usleep((80+ur.delay)*1000); // Wait until board comes out of reset
+
+  // Drain any extraneous input
+  serial_drain_timeout = 80;    // ms
+  serial_drain(&pgm->fd, 0);
+
+  if(urclock_getsync(pgm) < 0)
+    return -1;
+
+  return 0;
+}
+
+
+static void urclock_close(PROGRAMMER *pgm) {
+  serial_set_dtr_rts(&pgm->fd, 0);
+  serial_close(&pgm->fd);
+  pgm->fd.ifd = -1;
+  if(ur.bloptiversion)          // Optiboot needs a pause between two successive avrdude calls
+    usleep(200*1000);
+}
+
+
+static int urclock_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
+  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
+
+  int mchr, chunk;
+  unsigned int n;
+
+  if(n_bytes) {
+    // Paged writes only valid for flash and eeprom
+    mchr = avr_mem_is_flash_type(m)? 'F': 'E';
+    if(mchr == 'E' && !avr_mem_is_eeprom_type(m))
+      return -2;
+
+    if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw)
+      Return("bootloader does not %shave paged EEPROM write capability",
+        ur.blurversion? "": "seem to ");
+
+    n = addr + n_bytes;
+
+    for(; addr < n; addr += chunk) {
+      chunk = n-addr < page_size? n-addr: page_size;
+
+      if(urclock_paged_rdwr(pgm, p, Cmnd_STK_PROG_PAGE, addr, chunk, mchr, (char *) m->buf+addr)<0)
+        return -3;
+      if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0)
+        return -4;
+    }
+  }
+
+  return n_bytes;
+}
+
+
+static int urclock_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
+  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
+
+  int mchr, chunk;
+  unsigned int n;
+
+  if(n_bytes) {
+    // Paged reads only valid for flash and eeprom
+    mchr = avr_mem_is_flash_type(m)? 'F': 'E';
+    if(mchr == 'E' && !avr_mem_is_eeprom_type(m))
+      return -2;
+
+    if(mchr == 'F' && ur.urprotocol && !(ur.urfeatures & UB_READ_FLASH))
+      Return("bootloader does not have flash read capability");
+
+    if(mchr == 'E' && !ur.bleepromrw && !ur.xeepromrw)
+      Return("bootloader does not %shave paged EEPROM read capability",
+        ur.blurversion? "": "seem to ");
+
+    n = addr + n_bytes;
+    for(; addr < n; addr += chunk) {
+      chunk = n-addr < page_size? n-addr: page_size;
+
+      if(urclock_paged_rdwr(pgm, p, Cmnd_STK_READ_PAGE, addr, chunk, mchr, NULL) < 0)
+        return -3;
+      if(urclock_res_check(pgm, __func__, 0, &m->buf[addr], chunk) < 0)
+        return -4;
+
+      if(addr == 0 && mchr == 'F') { // Ensure reset vector points to bl
+        int vecsz = ur.uP.flashsize <= 8192? 2: 4;
+        if(chunk >= vecsz && ur.blstart && ur.vbllevel == 1) {
+          unsigned char jmptoboot[4];
+          int resetsize = set_reset(pgm, jmptoboot, vecsz);
+          int resetdest;
+
+          if(reset2addr(m->buf, vecsz, ur.uP.flashsize, &resetdest) < 0 || resetdest != ur.blstart) {
+            memcpy(m->buf, jmptoboot, resetsize);
+            pmsg_info("en passant forcing reset vector to point to vector bootloader\n");
+            if(urclock_paged_rdwr(pgm, p, Cmnd_STK_PROG_PAGE, 0, chunk, mchr, (char *) m->buf) < 0)
+              return -5;
+            if(urclock_res_check(pgm, __func__, 0, NULL, 0) < 0)
+              return -6;
+          }
+        }
+      }
+    }
+  }
+
+  return n_bytes;
+}
+
+
+int urclock_write_byte(const PROGRAMMER *pgm_uu, const AVRPART *p_uu, const AVRMEM *mem,
+  unsigned long addr_uu, unsigned char data_uu) {
+
+  pmsg_error("bootloader does not implement byte-wise write to %s \n", mem->desc);
+  return -1;
+}
+
+int urclock_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
+  unsigned long addr, unsigned char *value) {
+
+  // Byte-wise read only valid for flash and eeprom
+  int mchr = avr_mem_is_flash_type(mem)? 'F': 'E';
+  if(mchr == 'E' && !avr_mem_is_eeprom_type(mem)) {
+    if(!strcmp(mem->desc, "signature") && pgm->read_sig_bytes) {
+       if((int) addr < 0 || (int) addr >= mem->size) {
+         return -1;
+       }
+       pgm->read_sig_bytes(pgm, p, mem);
+       *value = mem->buf[(int) addr];
+       return 0;
+    }
+    pmsg_error("bootloader cannot read from %s \n", mem->desc);
+    return -1;
+  }
+
+  return ur_readEF(pgm, p, value, (uint32_t) addr, 1, mchr);
+}
+
+// Periodic call in terminal mode to keep bootloader alive
+static int urclock_term_keep_alive(const PROGRAMMER *pgm, const AVRPART *p_unused) {
+  unsigned char buf[16];
+
+  buf[0] = Cmnd_STK_GET_SYNC;
+  buf[1] = Sync_CRC_EOP;
+
+  if(urclock_send(pgm, buf, 2) < 0)
+    return -1;
+
+  return urclock_res_check(pgm, __func__, 0, NULL, 0);
+}
+
+
+// Display what we know so far (too early in the process to say much)
+static void urclock_display(const PROGRAMMER *pgm, const char *p_unused) {
+  if(ur.urprotocol) {
+    imsg_info("Urboot protocol for %s\n", ur.uP.name);
+  } else {
+    imsg_info("Bootloader using STK500v1 communication protocol\n");
+  }
+
+  return;
+}
+
+// End of STK500 section
+
+
+// Return whether an address is write protected
+static int urclock_readonly(const struct programmer_t *pgm, const AVRPART *p_unused,
+  const AVRMEM *mem, unsigned int addr) {
+
+  if(avr_mem_is_flash_type(mem)) {
+    if(ur.blstart) {
+      if(addr >= (unsigned int) ur.blstart)
+        return 1;
+      if(addr < 512 && ur.vbllevel) {
+        unsigned int vecsz = ur.uP.flashsize <= 8192? 2u: 4u;
+        if(addr < vecsz)
+          return 1;
+        if(ur.vblvectornum > 0) {
+          unsigned int appvecloc = ur.vblvectornum*vecsz;
+          if(addr >= appvecloc && addr < appvecloc+vecsz)
+            return 1;
+        }
+      }
+    }
+  } else if(!avr_mem_is_eeprom_type(mem))
+    return 1;
+
+  return 0;
+}
+
+static int urclock_parseextparms(const PROGRAMMER *pgm, LISTID extparms) {
+  int help = 0;
+
+  struct {
+    const char *name;
+    int *optionp;
+    int nstrbuf;
+    char *strbuf;
+    bool assign;
+    const char *help;
+  } options[] = {
+#define ARG  0, NULL, 1
+#define NA   0, NULL, 0
+    {"showall", &ur.showall, NA,          "Show all info for connected part and exit"},
+    {"showid", &ur.showid, NA,            "Show Urclock ID and exit"},
+    {"showdate", &ur.showdate, NA,        "Show last-modified date of flash application and exit"},
+    {"showfilename", &ur.showfilename, NA,"Show filename of last uploaded application and exit"},
+    {"showapp", &ur.showapp, NA,          "Show application size and exit"},
+    {"showstore", &ur.showstore, NA,      "Show store size and exit"},
+    {"showmeta", &ur.showmeta, NA,        "Show metadata size and exit"},
+    {"showboot", &ur.showboot, NA,        "Show bootloader size and exit"},
+    {"showversion", &ur.showversion, NA,  "Show bootloader version and capabilities and exit"},
+    {"showvector", &ur.showvector, NA,    "Show vector bootloader vector # and name and exit"},
+    {"id", NULL, sizeof ur.iddesc, ur.iddesc, 1, "Location of Urclock ID, eg, F.12345.6"},
+    {"title", NULL, sizeof ur.title, ur.title, 1, "Title stored and shown in lieu of a filename"},
+    {"bootsize", &ur.xbootsize, ARG,      "Override/set bootloader size"},
+    {"vectornum", &ur.xvectornum, ARG,    "Treat bootloader as vector b/loader using this vector"},
+    {"eepromrw", &ur.xeepromrw, NA,       "Assert bootloader EEPROM read/write capability"},
+    {"emulate_ce", &ur.xemulate_ce, NA,   "Emulate chip erase"},
+    {"restore", &ur.restore, NA,          "Restore a flash backup as is trimming the bootloader"},
+    {"initstore", &ur.initstore, NA,      "Fill store with 0xff on writing to flash"},
+    //@@@  {"copystore", &ur.copystore, NA, "Copy over store on writing to flash"},
+    {"nofilename", &ur.nofilename, NA,    "Do not store filename on writing to flash"},
+    {"nodate", &ur.nodate, NA,            "Do not store application filename and no date either"},
+    {"nometadata", &ur.nometadata, NA,    "Do not store metadata at all (ie, no store support)"},
+    {"delay", &ur.delay, ARG,             "Add delay [ms] after reset, can be negative"},
+    {"help", &help, NA,                   "Show this help menu and exit"},
+  };
+
+  int rc = 0;
+  for(LNODEID ln = lfirst(extparms); ln; ln = lnext(ln)) {
+    const char *extended_param = ldata(ln);
+    size_t i, olen, plen = strlen(extended_param);
+
+    for(i=0; i<sizeof options/sizeof*options; i++) {
+      olen = strlen(options[i].name);
+      if(strncmp(extended_param, options[i].name, olen) == 0) {
+        if(!options[i].nstrbuf) {
+          if(plen == olen && !options[i].assign) {
+            if(options[i].optionp) {
+              if(*options[i].optionp < 0)
+                 *options[i].optionp = 0;
+              (*options[i].optionp)++;
+              pmsg_notice2("%s set\n", options[i].name);
+            }
+            break;
+          } else if(plen > olen &&  extended_param[olen] == '=' && options[i].assign) {
+            const char *arg = extended_param+olen+1;
+            char *end;
+            long ret = strtol(arg, &end, 0);
+            if(*end || end == arg) {
+             pmsg_error("cannot parse -x%s\n", arg);
+             return -1;
+            }
+            if((int) ret != ret) {
+             pmsg_error("out of integer range -x%s\n", arg);
+             return -1;
+            }
+            *options[i].optionp = ret;
+            pmsg_notice2("%s=%d set\n", options[i].name, (int) ret);
+            break;
+          }
+        } else if(options[i].nstrbuf > 0) {
+          if(plen <= olen || extended_param[olen] != '=') {
+            pmsg_error("missing argument for option %s=...\n", extended_param);
+            rc = -1;
+          } else {
+            if(options[i].strbuf) {
+              strncpy(options[i].strbuf, extended_param+olen+1, options[i].nstrbuf-1);
+              pmsg_notice2("%s=%s set\n", options[i].name, options[i].strbuf);
+            }
+            break;
+          }
+        }
+      }
+    }
+    if(i >= sizeof options/sizeof*options) {
+      pmsg_error("invalid extended parameter %s\n", extended_param);
+      rc = -1;
+    }
+  }
+
+  if(help || rc < 0) {
+    msg_error("%s -c %s extended options:\n", progname, (char *) ldata(lfirst(pgm->id)));
+    for(size_t i=0; i<sizeof options/sizeof*options; i++) {
+      msg_error("  -x%s%s%*s%s\n", options[i].name, options[i].assign? "=<arg>": "",
+        urmax(0, 16-(int) strlen(options[i].name)-(options[i].assign? 6: 0)), "", options[i].help);
+    }
+    if(rc == 0)
+      exit(0);
+  }
+
+  if(parseUrclockID(pgm) < 0)
+    return -1;
+
+  return rc;
+}
+
+
+static void urclock_setup(PROGRAMMER *pgm) {
+  // Allocate ur
+  pgm->cookie = cfg_malloc(__func__, sizeof(Urclock_t));
+
+  ur.xvectornum    = -1;        // Initialise, to ascertain whether user had set to 0
+  ur.ext_addr_byte = 0xff;      // So first memory address will load extended address
+  ur.STK_INSYNC    = Resp_STK_INSYNC;
+  ur.STK_OK        = Resp_STK_OK;
+}
+
+
+static void urclock_teardown(PROGRAMMER *pgm) {
+  free(pgm->cookie);
+  pgm->cookie = NULL;
+}
+
+
+const char urclock_desc[] = "Urclock programmer for urboot bootloaders (arduino compatible)";
+
+void urclock_initpgm(PROGRAMMER *pgm) {
+  strcpy(pgm->type, "Urclock");
+
+  pgm->read_sig_bytes = urclock_read_sig_bytes;
+
+  // Mandatory functions
+  pgm->initialize = urclock_initialize;
+  pgm->display = urclock_display;
+  pgm->enable = urclock_enable;
+  pgm->disable = urclock_disable;
+  pgm->program_enable = urclock_program_enable;
+  pgm->chip_erase = urclock_chip_erase;
+  pgm->cmd = urclock_cmd;
+  pgm->open = urclock_open;
+  pgm->close = urclock_close;
+  pgm->read_byte = urclock_read_byte;
+  pgm->write_byte = urclock_write_byte;
+
+  // Optional functions
+  pgm->paged_write = urclock_paged_write;
+  pgm->paged_load = urclock_paged_load;
+  pgm->setup = urclock_setup;
+  pgm->teardown = urclock_teardown;
+  pgm->parseextparams = urclock_parseextparms;
+  pgm->term_keep_alive = urclock_term_keep_alive;
+  pgm->readonly = urclock_readonly;
+  pgm->flash_readhook = urclock_flash_readhook;
+
+  disable_trailing_ff_removal();
+#if defined(HAVE_LIBREADLINE)
+  pmsg_notice2("libreadline is used; avrdude -t -c urclock should work");
+#else
+  pmsg_warning("compiled without readline library, cannot use avrdude -t -c urclock");
+#endif
+}
diff --git a/src/urclock.h b/src/urclock.h
new file mode 100644
index 00000000..9ce538d6
--- /dev/null
+++ b/src/urclock.h
@@ -0,0 +1,29 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2022, Stefan Rueger <stefan.rueger@urclocks.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* $Id$ */
+
+#ifndef urclock_h__
+#define urclock_h__
+
+extern const char urclock_desc[];
+void urclock_initpgm (PROGRAMMER *pgm);
+
+#endif
+
+
diff --git a/src/urclock_private.h b/src/urclock_private.h
new file mode 100644
index 00000000..fb4471d6
--- /dev/null
+++ b/src/urclock_private.h
@@ -0,0 +1,156 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2022, Stefan Rueger <stefan.rueger@urclocks.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* $Id$ */
+
+#ifndef urclock_private_h__
+#define urclock_private_h__
+
+#include "avrintel.h"
+
+// EEPROM or flash cache for bytewise access
+typedef struct {
+  int base, size;
+  char *page, *copy;
+} Cache;
+
+
+// STK500v1 protocol constants
+
+#define Resp_STK_OK             0x10
+#define Resp_STK_INSYNC         0x14
+
+#define Sync_CRC_EOP            0x20
+
+#define Cmnd_STK_GET_SYNC       0x30
+#define Cmnd_STK_ENTER_PROGMODE 0x50
+#define Cmnd_STK_LEAVE_PROGMODE 0x51
+#define Cmnd_STK_CHIP_ERASE     0x52
+#define Cmnd_STK_LOAD_ADDRESS   0x55
+#define Cmnd_STK_UNIVERSAL      0x56
+
+#define Cmnd_STK_PROG_PAGE      0x64
+#define Cmnd_STK_READ_PAGE      0x74
+#define Cmnd_STK_READ_SIGN      0x75
+
+
+// Urprotocol command extensions to STK500v1
+
+#define Cmnd_UR_PROG_PAGE_EE    0x00
+#define Cmnd_UR_READ_PAGE_EE    0x01
+
+#define Cmnd_UR_PROG_PAGE_FL    0x02
+#define Cmnd_UR_READ_PAGE_FL    0x03
+
+
+// STK_UNIVERSAL commands for backward compatibility
+#define Subc_STK_UNIVERSAL_LEXT 0x4d000000u // Load extended address
+#define Subc_STK_UNIVERSAL_CE   0xac800000u // Chip erase
+
+
+// Urboot protocol side channel info about MCU id and 5 binary bootloader features
+
+// Number of differnt MCU ids
+#define UB_N_MCU           2040 // MCU id 0..2039
+
+// 5 bootloader features
+#define UB_RESERVED_1         1
+#define UB_RESERVED_2         2
+#define UB_READ_FLASH         4 // Bootloader can read flash
+#define UB_FLASH_LL_NOR       8 // Bootloader flash programming looks like a NOR memory
+#define UB_CHIP_ERASE        16 // Bootloader has a flash-only chip erase that protects itself
+
+#define UB_INFO(ub_features, ub_mcuid) (ub_features*UB_N_MCU + ub_mcuid)
+#define UB_FEATURES(ub_info) ((uint16_t)(ub_info)/UB_N_MCU)
+#define UB_MCUID(ub_info)    ((uint16_t)(ub_info)%UB_N_MCU)
+
+
+/*
+ * Urboot layout of top six bytes
+ *
+ * FLASHEND-5: numblpags, only from v7.5: 1 byte number 1..127 of bootloader flash pages
+ * FLASHEND-4: vblvecnum, only from v7.5: 1 byte vector number 1..127 for vector bootloader
+ * FLASHEND-3: 2 byte rjmp opcode to bootloader pgm_write_page(sram, flash) or ret opcode
+ * FLASHEND-1: capability byte of bootloader
+ * FLASHEND-0: version number of bootloader: 5 msb = major version, 3 lsb = minor version
+ */
+
+// Capability byte of bootloader from version 7.2 onwards
+#define UR_PGMWRITEPAGE     128 // pgm_write_page() can be called from application at FLASHEND+1-4
+#define UR_AUTOBAUD         128 // Bootloader has autobaud detection (from  v7.7)
+#define UR_EEPROM            64 // EEPROM read/write support
+#define UR_URPROTOCOL        32 // Bootloader uses urprotocol that requires avrdude -c urclock
+#define UR_DUAL              16 // Dual boot
+#define UR_VBLMASK           12 // Vector bootloader bits
+#define UR_VBLPATCHVERIFY    12 // Patch reset/interrupt vectors and show original ones on verify
+#define UR_VBLPATCH           8 // Patch reset/interrupt vectors only (expect an error on verify)
+#define UR_VBL                4 // Merely start application via interrupt vector instead of reset
+#define UR_NO_VBL             0 // Not a vector bootloader, must set fuses to HW bootloader support
+#define UR_PROTECTME          2 // Bootloader safeguards against overwriting itself
+#define UR_RESETFLAGS         1 // Load reset flags into register R2 before starting application
+#define UR_HAS_CE             1 // Bootloader has Chip Erase (from v7.7)
+
+#define verbyte_cv(capver)      ((uint8_t) ((uint16_t) (capver) >> 8))
+#define hascapbyte_cv(capver)   ({ uint8_t _vh = verbyte_cv(capver); _vh >= 072 && _vh != 0xff; })
+#define hasextendedv_cv(capver) ({ uint8_t _vh = verbyte_cv(capver); _vh >= 075 && _vh != 0xff; })
+#define capabilities_cv(capver) ({ uint16_t _vc = capver; \
+  (uint8_t) (hascapbyte_cv(_vc)? _vc&0xff: 0); })
+#define vblvecnum_cv(capver)    ({ uint16_t _vc = capver; \
+  (uint8_t) (hasextendedv_cv(_vc)? pgm_read_b1(FLASHEND-4): 0); })
+#define numblpages_cv(capver)   ({ uint16_t _vc = capver; \
+  (uint8_t) (hasextendedv_cv(_vc)? pgm_read_b1(FLASHEND-5): 0); })
+#define blurversion_cv(capver)  ({ uint8_t _vh = verbyte_cv(capver); \
+  (uint8_t) (_vh >= 072 && _vh != 0xff? _vh: 0); })
+
+#define vercapis(capver, mask)  ({ uint16_t _vi = capver; !!(capabilities_cv(_vi) & (mask)); })
+#define ispgmwritepage_cv(capver) vercapis(capver, UR_PGMWRITEPAGE) // up to v7.6
+#define isautobaud_cv(capver)     vercapis(capver, UR_AUTOBAUD)     // from v7.7
+#define iseeprom_cv(capver)       vercapis(capver, UR_EEPROM)
+#define isurprotocol_cv(capver)   vercapis(capver, UR_URPROTOCOL)
+#define isdual_cv(capver)         vercapis(capver, UR_DUAL)
+#define isvectorbl_cv(capver)     vercapis(capver, UR_VBLMASK)
+#define isprotectme_cv(capver)    vercapis(capver, UR_PROTECTME)
+#define isresetflags_cv(capver)   vercapis(capver, UR_RESETFLAGS)   // up to v7.6
+#define ishas_ce_cv(capver)       vercapis(capver, UR_HAS_CE)       // from v7.7
+
+// Capability bits incl position
+#define pgmwritepage_bit_cap(cap) ((cap) & UR_PGMWRITEPAGE) // up to v7.6
+#define autibaud_bit_cap(cap)     ((cap) & UR_AUTOBAUD)     // from v7.7
+#define eeprom_bit_cap(cap)       ((cap) & UR_EEPROM)
+#define dual_bit_cap(cap)         ((cap) & UR_DUAL)
+#define vector_bits_cap(cap)      ((cap) & UR_VBLMASK))
+#define protectme_bit_cap(cap)    ((cap) & UR_PROTECTME)
+#define urprotocol_bit_cap(cap)   ((cap) & UR_URPROTOCOL)
+#define resetflags_bit_cap(cap)   ((cap) & UR_RESETFLAGS)  // up to v7.6
+#define has_ce_bit_cap(cap)       ((cap) & UR_HAS_CE)      // from v7.7
+
+// Boolean capabilities
+#define ispgmwritepage_cap(cap) (!!((cap) & UR_PGMWRITEPAGE)) // up to v7.6
+#define isautobaud_cap(cap)     (!!((cap) & UR_AUTOBAUD))     // from v7.7
+#define iseeprom_cap(cap)       (!!((cap) & UR_EEPROM))
+#define isdual_cap(cap)         (!!((cap) & UR_DUAL))
+#define isvectorbl_cap(cap)     (!!((cap) & UR_VBLMASK)))
+#define isprotectme_cap(cap)    (!!((cap) & UR_PROTECTME))
+#define isurprotocol_cap(cap)   (!!((cap) & UR_URPROTOCOL))
+#define isresetflags_cap(cap)   (!!((cap) & UR_RESETFLAGS))   // up to v7.6
+#define ishas_ce_cap(cap)       (!!((cap) & UR_HAS_CE))       // from v7.7
+
+// Capability levels 0, 1, 2 or 3
+#define vectorbl_level_cap(cap) (((cap) & UR_VBLMASK)/UR_VBL)
+
+#endif