I'm not the one that did these, but since you asked... ;-)

The source RPM is available on:

ftp://ftp.caldera.com/pub/eDesktop/Packages/SRPMS/grub-0.5.94-5.src.rpm

and the relevant patches from that are attached.

initrd1G - places the initrd below 1G so large mem machines will boot.
nomeminfo - tries to help some broken BIOSes with mem detection
splash - graphical menu (splash) screen, CD ioctl, etc.

--
Tim Riker - http://rikers.org/ - short SIGs! <g>
All I need to know I could have learned in Kindergarten
... if I'd just been paying attention.
--- grub-0.5.94/stage2/boot.c.orig	Thu Feb 10 10:25:16 2000
+++ grub-0.5.94/stage2/boot.c	Thu Feb 10 14:49:49 2000
@@ -544,7 +544,7 @@
       return 0;
     }
 
-  moveto = ((mbi.mem_upper + 0x400) * 0x400 - len) & 0xfffff000;
+  moveto = ((mbi.mem_upper + 0x400) * 0x400 - len) & 0x3ffff000;
   memmove ((void *) RAW_ADDR (moveto), (void *) cur_addr, len);
 
   if (cls_hook_seg)
--- grub-0.5.94/stage2/boot.c	Thu Feb 10 18:12:26 2000
+++ grub-0.5.94.new/stage2/boot.c	Thu Feb 10 16:05:52 2000
@@ -261,17 +261,26 @@
 	  /* copy command-line plus memory hack to staging area */
 	  {
 	    char *src = arg;
-	    char *dest = (char *) (CL_MY_LOCATION + 4);
-
-	    memmove ((char *) CL_MY_LOCATION, "mem=", 4);
-
-	    *((unsigned short *) CL_OFFSET) = CL_MY_LOCATION - CL_BASE_ADDR;
-	    *((unsigned short *) CL_MAGIC_ADDR) = CL_MAGIC;
-
-	    dest = convert_to_ascii (dest, 'u', (mbi.mem_upper + 0x400));
-	    *(dest++) = 'K';
-	    *(dest++) = ' ';
+	    char *dest = (char *) (CL_MY_LOCATION);
 
+	    *((unsigned short *) CL_OFFSET) = CL_MY_LOCATION - CL_BASE_ADDR;
+	    *((unsigned short *) CL_MAGIC_ADDR) = CL_MAGIC;
+
+	    /* Help Linux to find memory only if more than 64MB are present.
+	     * Up to that amount it is fairly capable to find by itself, 
+	     * and at least newer Phoenix BIOSes are known to put a
+	     * 10k hole just before 64MB, but report a proper total.
+	     */
+	    if (mbi.mem_upper > 65535) /* might subtract 1MB lower mem here */
+	      {
+		memmove ((char *) CL_MY_LOCATION, "mem=", 4);
+		dest = (char *) (CL_MY_LOCATION + 4);
+
+		dest = convert_to_ascii (dest, 'u', (mbi.mem_upper + 0x400));
+		*(dest++) = 'K';
+		*(dest++) = ' ';
+	      }
+	    
 	    while (*src && *src != ' ')
 	      src++;
 
diff -ur grub-0.5.94.orig/grub/asmstub.c grub-0.5.94/grub/asmstub.c
--- grub-0.5.94.orig/grub/asmstub.c	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/grub/asmstub.c	Wed Jan 19 15:38:06 2000
@@ -43,6 +43,8 @@
 #ifdef __linux__
 # include <sys/ioctl.h>		/* ioctl */
 # include <linux/hdreg.h>	/* HDIO_GETGEO */
+# include <linux/cdrom.h>	/* CDROM_GET_CAPABILITY to identify CDs */
+# define SOME_CDROM_IOCTL	CDROM_GET_CAPABILITY
 # if __GLIBC__ < 2
 /* Maybe libc doesn't have large file support.  */
 #  include <linux/unistd.h>	/* _llseek */
@@ -56,6 +58,8 @@
 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
 # include <sys/ioctl.h>		/* ioctl */
 # include <sys/disklabel.h>
+# include <sys/cdio.h>		/* CDIOCCLRDEBUG to identify CDs */
+# define SOME_CDROM_IOCTL	CDIOCCLRDEBUG
 #endif /* __FreeBSD__ || __NetBSD__ || __OpenBSD__ */
 
 #ifdef HAVE_OPENDISK
@@ -83,6 +87,9 @@
 unsigned long boot_drive = 0;
 char version_string[] = VERSION;
 char config_file[128] = "/boot/grub/menu.lst"; /* FIXME: arbitrary */
+char splashscreen_file[128];
+
+unsigned short cls_hook_seg;
 
 /* Emulation requirements. */
 char *grub_scratch_mem = 0;
@@ -538,6 +545,11 @@
       fclose (fp);
       return 0;
     }
+
+  /* Make sure CD-ROMs don't get assigned a BIOS disk number 
+     before SCSI disks ! */
+  if (ioctl(fileno(fp), SOME_CDROM_IOCTL, 0) >= 0)
+    return 0;
 
   fclose (fp);
   return 1;
diff -ur grub-0.5.94.orig/stage2/asm.S grub-0.5.94/stage2/asm.S
--- grub-0.5.94.orig/stage2/asm.S	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/stage2/asm.S	Wed Jan 19 15:38:06 2000
@@ -1345,7 +1345,7 @@
 
 	movb	%bl, %al
 	movb	$0xe, %ah
-	movw	$1, %bx
+	movw	$0x8f, %bx	/* invert all planes */
 	int	$0x10
 
 	DATA32	call	EXT_C(real_to_prot)
@@ -1688,24 +1688,45 @@
 
 
 ENTRY(cls)
-	push	%ebp
-	push	%eax
-	push	%ebx                    /* save EBX */
+	pusha
 
 	call	EXT_C(prot_to_real)
 	.code16
 
+	movw	EXT_C(cls_hook_seg), %ax
+	cmpw	$0, %ax
+	je	cls_self
+
+	
+	mov	%ds,%ax
+	push	%ax
+	
+	mov	%es,%ax
+	push	%ax
+
+	lcall	(cls_hook)
+	
+	pop	%ax
+	mov	%ax,%es
+	
+	pop	%ax
+	mov	%ax,%ds
+
+	jmp	cls_ret
+
+cls_self:
+
 	movb	$0xf, %ah
 	int	$0x10			/* Get Current Video mode */
         xorb	%ah, %ah
+	movw	$0x0003, %ax		/* hardcode 80x25 color XXX */
         int	$0x10                   /* Set Video mode (clears screen) */
 
+cls_ret:
 	DATA32	call	EXT_C(real_to_prot)
 	.code32
 
-	pop	%ebx
-	pop	%eax
-	pop	%ebp
+	popa
 	ret
 
 	
@@ -1861,10 +1882,19 @@
 	call	EXT_C(prot_to_real)
 	.code16
 
+	movw	EXT_C(cls_hook_seg),%ax
+	cmpw	$0, %ax
+	je	sa_text
+
+	movb	$0xDB, %al
+	movw	$0x8f, %bx	/* invert all planes */
+	jmp	sa_common
+sa_text:
 	movb	$0x8, %ah
 	int	$0x10
-	movb	$0x9, %ah
 	movb	%cl, %bl
+sa_common:
+	movb	$0x9, %ah
 	movw	$1, %cx
 	int	$0x10
 
@@ -2068,6 +2098,14 @@
 
 protstack:
 	.long	PROTSTACKINIT
+
+cls_hook:
+	.word	0		/* offset -- hard-wired for now */
+VARIABLE(cls_hook_seg)
+	.word	0
+
+VARIABLE(splashscreen_file)	
+	.string	"\0/boot/grub/splashscreenXXXXXXXXXXXXXXXXXX"
 
 VARIABLE(boot_drive)
 	.long	0
diff -ur grub-0.5.94.orig/stage2/boot.c grub-0.5.94/stage2/boot.c
--- grub-0.5.94.orig/stage2/boot.c	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/stage2/boot.c	Wed Jan 19 15:54:39 2000
@@ -209,8 +209,9 @@
 	  return KERNEL_TYPE_NONE;
 	}
 
-      printf ("   [Linux-%s, setup=0x%x, size=0x%x]\n",
-	      (big_linux ? "bzImage" : "zImage"), data_len, text_len);
+      if (!cls_hook_seg)
+	printf ("   [Linux-%s, setup=0x%x, size=0x%x]\n",
+		(big_linux ? "bzImage" : "zImage"), data_len, text_len);
 
       if (mbi.mem_lower >= 608)
 	{
@@ -546,7 +547,8 @@
   moveto = ((mbi.mem_upper + 0x400) * 0x400 - len) & 0xfffff000;
   memmove ((void *) RAW_ADDR (moveto), (void *) cur_addr, len);
 
-  printf ("   [Linux-initrd @ 0x%x, 0x%x bytes]\n", moveto, len);
+  if (!cls_hook_seg)
+    printf ("   [Linux-initrd @ 0x%x, 0x%x bytes]\n", moveto, len);
 
   ramdisk = (unsigned long *) (LINUX_SETUP + LINUX_SETUP_INITRD);
   ramdisk[0] = RAW_ADDR (moveto);
diff -ur grub-0.5.94.orig/stage2/builtins.c grub-0.5.94/stage2/builtins.c
--- grub-0.5.94.orig/stage2/builtins.c	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/stage2/builtins.c	Wed Jan 19 15:57:29 2000
@@ -168,6 +168,14 @@
   if (kernel_type != KERNEL_TYPE_NONE)
     unset_int15_handler ();
   
+  if (kernel_type != KERNEL_TYPE_NONE &&
+      kernel_type != KERNEL_TYPE_LINUX &&
+      kernel_type != KERNEL_TYPE_BIG_LINUX)
+    {
+      cls_hook_seg = 0;
+      cls();
+    }
+
   switch (kernel_type)
     {
     case KERNEL_TYPE_FREEBSD:
@@ -2022,7 +2030,8 @@
   bootdev = set_bootdev (hdbias);
 
   /* Print the type of the filesystem.  */
-  print_fsys_type ();
+  if (!cls_hook_seg)
+    print_fsys_type ();
 
   return 0;
 }
@@ -2524,6 +2533,28 @@
 };
 
 
+/* spiffy splash screen */
+static int
+splashscreen_func (char *arg, int flags)
+{
+  char * f = splashscreen_file;
+  char * p = arg;
+  while ((*f++ = *p++))
+    ;
+  return 0;
+}
+
+static struct builtin builtin_splashscreen =
+{
+  "splashscreen",
+  splashscreen_func,
+  BUILTIN_CMDLINE | BUILTIN_MENU,
+  "splashscreen FILE",
+  "Load file as splashscreen hook and call it each time "
+  "the screen is to be cleared"
+};
+
+
 /* testload */
 static int
 testload_func (char *arg, int flags)
@@ -2750,6 +2781,7 @@
   &builtin_rootnoverify,
   &builtin_setkey,
   &builtin_setup,
+  &builtin_splashscreen,
   &builtin_testload,
   &builtin_timeout,
   &builtin_title,
diff -ur grub-0.5.94.orig/stage2/char_io.c grub-0.5.94/stage2/char_io.c
--- grub-0.5.94.orig/stage2/char_io.c	Thu Nov 11 21:43:14 1999
+++ grub-0.5.94/stage2/char_io.c	Wed Jan 19 15:38:06 2000
@@ -169,8 +169,9 @@
 {
   cls ();
 
-  printf ("\n    GRUB  version %s  (%dK lower / %dK upper memory)\n\n",
-	  version_string, mbi.mem_lower, mbi.mem_upper);
+  if (!cls_hook_seg)
+    printf ("\n    GRUB  version %s  (%dK lower / %dK upper memory)\n\n",
+	    version_string, mbi.mem_lower, mbi.mem_upper);
 }
 
 /* The number of the history entries.  */
diff -ur grub-0.5.94.orig/stage2/cmdline.c grub-0.5.94/stage2/cmdline.c
--- grub-0.5.94.orig/stage2/cmdline.c	Fri Nov 19 18:41:29 1999
+++ grub-0.5.94/stage2/cmdline.c	Wed Jan 19 15:38:06 2000
@@ -182,7 +182,8 @@
 	;
 
       grub_memmove (heap, old_entry, (int) cur_entry - (int) old_entry);
-      grub_printf ("%s\n", old_entry);
+      if (!cls_hook_seg)
+	grub_printf ("%s\n", old_entry);
 
       if (! *heap)
 	{
diff -ur grub-0.5.94.orig/stage2/shared.h grub-0.5.94/stage2/shared.h
--- grub-0.5.94.orig/stage2/shared.h	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/stage2/shared.h	Wed Jan 19 15:38:06 2000
@@ -439,6 +439,18 @@
 extern void assign_device_name (int drive, const char *device);
 #endif
 
+extern char splashscreen_file[];
+/* the segment a loaded clear screen hook
+ * (splashscreen) resides in
+ */
+extern unsigned short
+cls_hook_seg;
+
+/* The menu geometry */
+extern int menu_w, menu_h, morig_x, morig_y;
+
+extern int timeout_x, timeout_y;
+
 #ifndef STAGE1_5
 /* GUI interface variables. */
 extern int fallback_entry;
diff -ur grub-0.5.94.orig/stage2/stage2.c grub-0.5.94/stage2/stage2.c
--- grub-0.5.94.orig/stage2/stage2.c	Wed Jan 19 12:58:36 2000
+++ grub-0.5.94/stage2/stage2.c	Wed Jan 19 15:38:06 2000
@@ -38,13 +38,15 @@
   return list;
 }
 
+/* The (default) menu geometry */
+int menu_w = 71, menu_h = 12, morig_x = 3, morig_y = 3;
 
 static void
 print_entries (int y, int size, int first, char *menu_entries)
 {
   int i;
 
-  gotoxy (77, y + 1);
+  gotoxy (morig_x+menu_w, y + 1);
 
   if (first)
     putchar (DISP_UP);
@@ -57,11 +59,11 @@
     {
       int j = 0;
 
-      gotoxy (3, y + i);
+      gotoxy (morig_x, y + i);
 
       while (*menu_entries)
 	{
-	  if (j < 71)
+	  if (j < menu_w)
 	    {
 	      putchar (*menu_entries);
 	      j++;
@@ -73,11 +75,11 @@
       if (*(menu_entries - 1))
 	menu_entries++;
 
-      for (; j < 71; j++)
+      for (; j < menu_w; j++)
 	putchar (' ');
     }
 
-  gotoxy (77, y + size);
+  gotoxy (morig_x+menu_w, y + size);
 
   if (*menu_entries)
     putchar (DISP_DOWN);
@@ -91,6 +93,9 @@
 {
   int i;
 
+  if (cls_hook_seg)
+    return;
+
 #ifndef GRUB_UTIL
   /* Color the menu. The menu is 75 * 14 characters.  */
   for (i = 0; i < 14; i++)
@@ -138,7 +143,7 @@
 {
   int x;
 
-  for (x = 2; x < 75; x++)
+  for (x = morig_x-1; x < morig_x+menu_w+1; x++)
     {
       gotoxy (x, y);
       set_attrib (attr);
@@ -167,11 +172,15 @@
 #endif
 }
 
+int timeout_x = 3, timeout_y = 22;
+#define TIMEOUT_TEXT \
+"The highlighted entry will be booted automatically in %d seconds.    "
+
 static void
 run_menu (char *menu_entries, char *config_entries, int num_entries,
 	  char *heap, int entryno)
 {
-  int c, time1, time2 = -1, first_entry = 0;
+  int c, time1, time2 = -1, first_entry = 0, firsttimer=1;
   char *cur_entry;
 
   /*
@@ -179,7 +188,7 @@
    */
 
 restart:
-  while (entryno > 11)
+  while (entryno > menu_h-1)
     {
       first_entry++;
       entryno--;
@@ -190,8 +199,11 @@
   nocursor ();
 #endif
 
-  print_border (3, 12);
+  print_border (morig_y, menu_h);
 
+
+  if (!cls_hook_seg)
+    {
 #ifdef GRUB_UTIL
   grub_printf ("\n
       Use the up and down arrows for selecting which entry is highlighted.\n");
@@ -218,15 +230,16 @@
       after (\'O\' for before) the selected line, \'d\' to remove the
       selected line, or escape to go back to the main menu.");
     }
-
-  print_entries (3, 12, first_entry, menu_entries);
+    }
+  print_entries (morig_y, menu_h, first_entry, menu_entries);
 
   /* highlight initial line */
-  set_line_highlight (4 + entryno);
+  set_line_highlight (morig_y+1 + entryno);
 
   /* XX using RT clock now, need to initialize value */
   while ((time1 = getrtsecs()) == 0xFF);
 
+  firsttimer=1;
   while (1)
     {
       /* initilize to NULL just in case... */
@@ -237,14 +250,21 @@
 	  if (grub_timeout <= 0)
 	    {
 	      grub_timeout = -1;
+	      gotoxy (timeout_x, timeout_y);
+	      printf (TIMEOUT_TEXT, 0);
 	      break;
 	    }
 
 	  /* else not booting yet! */
 	  time2  = time1;
-	  gotoxy (3, 22);
-	  printf ("The highlighted entry will be booted automatically in %d seconds.    ", grub_timeout);
-	  gotoxy (74, 4 + entryno);
+	  gotoxy (timeout_x, timeout_y);
+	  if (!firsttimer)
+	    printf (TIMEOUT_TEXT, grub_timeout+1);
+	  else
+	    firsttimer=0;
+	  gotoxy (timeout_x, timeout_y);
+	  printf (TIMEOUT_TEXT, grub_timeout);
+	  gotoxy (morig_x+menu_w, morig_y+1 + entryno);
 	  grub_timeout--;
 	}
 
@@ -254,42 +274,48 @@
 
 	  if (grub_timeout >= 0)
 	    {
-	      gotoxy (3, 22);
+	      gotoxy (timeout_x, timeout_y);
+	      printf (TIMEOUT_TEXT, grub_timeout+1);
+	      gotoxy (timeout_x, timeout_y);
 	      printf ("                                                                    ");
 	      grub_timeout = -1;
 	      fallback_entry = -1;
-	      gotoxy (74, 4 + entryno);
+	      gotoxy (morig_x+menu_w, morig_y+1 + entryno);
 	    }
 
 	  if ((c == KEY_UP) || (ASCII_CHAR (c) == 16))
 	    {
 	      if (entryno > 0)
 		{
-		  set_line_normal (4 + entryno);
+		  set_line_normal (morig_y+1 + entryno);
 		  entryno--;
-		  set_line_highlight (4 + entryno);
+		  set_line_highlight (morig_y+1 + entryno);
 		}
 	      else if (first_entry > 0)
 		{
+		  set_line_normal (morig_y+1);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
 		  first_entry--;
-		  print_entries (3, 12, first_entry, menu_entries);
-		  set_line_highlight (4);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
+		  set_line_highlight (morig_y+1);
 		}
 	    }
 	  if (((c == KEY_DOWN) || (ASCII_CHAR (c) == 14))
 	      && (first_entry + entryno + 1) < num_entries)
 	    {
-	      if (entryno < 11)
+	      if (entryno < menu_h-1)
 		{
-		  set_line_normal (4 + entryno);
+		  set_line_normal (morig_y+1 + entryno);
 		  entryno++;
-		  set_line_highlight (4 + entryno);
+		  set_line_highlight (morig_y+1 + entryno);
 		}
-	      else if (num_entries > 12 + first_entry)
+	      else if (num_entries > menu_h + first_entry)
 		{
+		  set_line_normal (morig_y+menu_h);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
 		  first_entry++;
-		  print_entries (3, 12, first_entry, menu_entries);
-		  set_line_highlight (15);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
+		  set_line_highlight (morig_y+menu_h);
 		}
 	    }
 
@@ -304,7 +330,8 @@
 	    {
 	      if ((c == 'd') || (c == 'o') || (c == 'O'))
 		{
-		  set_line_normal (4 + entryno);
+		  set_line_normal (morig_y+1 + entryno);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
 
 		  /* insert after is almost exactly like insert before */
 		  if (c == 'o')
@@ -342,12 +369,12 @@
 
 		      if (entryno >= num_entries)
 			entryno--;
-		      if (first_entry && num_entries < 12 + first_entry)
+		      if (first_entry && num_entries < menu_h + first_entry)
 			first_entry--;
 		    }
 
-		  print_entries (3, 12, first_entry, menu_entries);
-		  set_line_highlight (4 + entryno);
+		  print_entries (morig_y, menu_h, first_entry, menu_entries);
+		  set_line_highlight (morig_y+1 + entryno);
 		}
 
 	      cur_entry = menu_entries;
@@ -401,6 +428,15 @@
 		  int num_entries = 0, i = 0;
 		  char *new_heap;
 
+		  morig_x = 3;
+		  morig_y = 3;
+		  menu_h = 12;
+		  menu_w = 71;
+		  timeout_x = 3;
+		  timeout_y = 22;
+		  cls_hook_seg = 0;
+		  cls();
+
 		  if (config_entries)
 		    {
 		      new_heap = heap;
@@ -496,12 +532,15 @@
     {
       cls ();
 
-      if (config_entries)
-	printf ("  Booting \'%s\'\n\n",
-		get_entry (menu_entries, first_entry + entryno, 0));
-      else
-	printf ("  Booting command-list\n\n");
-
+      if (!cls_hook_seg)
+	{
+	  if (config_entries)
+	    printf ("  Booting \'%s\'\n\n",
+		    get_entry (menu_entries, first_entry + entryno, 0));
+	  else
+	    printf ("  Booting command-list\n\n");
+	}
+      
       if (! cur_entry)
 	cur_entry = get_entry (config_entries, first_entry + entryno, 1);
 
@@ -696,6 +735,36 @@
 	  grub_close ();
 	}
 
+      if (*splashscreen_file && grub_open (splashscreen_file) )
+	{
+	  if ( grub_read ((char *) RAW_ADDR (0x50000), -1) > 0 )
+	    {
+	      cls_hook_seg = 0x5000;
+
+	      morig_x = 2;
+	      morig_y = 6;
+	      menu_h  = 20;
+	      menu_w  = 51;
+
+	      timeout_x = 2;
+	      timeout_y = 28;		/* Grafix screen has 80x30 Chars */
+	    }
+
+	  /* grub_printf("would use splashscreen file %s\n", splashscreen_file); */
+	  
+	  *splashscreen_file = '\0';
+	  grub_close ();
+	}
+      else
+	{
+	  morig_x = 3;
+	  morig_y = 3;
+	  menu_h = 12;
+	  menu_w = 71;
+	  timeout_x = 3;
+	  timeout_y = 22;
+	}
+
       if (! num_entries)
 	{
 	  /* If no acceptable config file, goto command-line, starting
@@ -705,6 +774,7 @@
 	}
       else
 	{
+	  cls();
 	  /* Run menu interface.  */
 	  run_menu (menu_entries, config_entries, num_entries,
 		    menu_entries + menu_len, default_entry);
--- grub-0.5.94/stage2/boot.c.orig	Thu Feb 10 21:16:20 2000
+++ grub-0.5.94/stage2/boot.c	Mon Feb 14 19:29:56 2000
@@ -229,7 +229,7 @@
 
 		/* Handle special strings.  */
 		if (substring ("normal", value) < 1)
-		  vid_mode = LINUX_VID_MODE_NORMAL;
+		  vid_mode = LINUX_VID_MODE_NORMAL, cls_hook_seg = 0;
 		else if (substring ("ext", value) < 1)
 		  vid_mode = LINUX_VID_MODE_EXTENDED;
 		else if (substring ("ask", value) < 1)
@@ -326,7 +326,8 @@
   mbi.syms.a.addr = 0;
   mbi.syms.a.pad = 0;
 
-  printf ("   [%s-%s", str2, str);
+  if (!cls_hook_seg)
+    printf ("   [%s-%s", str2, str);
 
   str = "";
 
@@ -489,7 +490,10 @@
     }
 
   if (!errnum)
-    printf (", entry=0x%x]\n", (int) entry_addr);
+    {
+      if (!cls_hook_seg)
+	printf (", entry=0x%x]\n", (int) entry_addr);
+    }
   else
     {
       putchar ('\n');

Reply via email to