# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080803040022-bxhp6libdkx40muv
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: a9261a62ae1b371e0a9b27a0f1f55c4c880aaf1e
# timestamp: 2008-08-03 07:26:21 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080803035922-trvxiqbrfge9pilr
# 
# Begin patch
=== modified file 'commands/videotest.c'
--- commands/videotest.c	2008-07-09 16:14:04 +0000
+++ commands/videotest.c	2008-07-19 18:25:18 +0000
@@ -49,19 +49,6 @@
   int double_buffering;
 };
 
-/* A 2D rectangle type.
- * This could be worth integrating into the video API if it proves useful.*/
-struct grub_video_rect
-{
-  /* These are signed because if there are unsigned it causes Bad Things 
-   * to happen when arithmetic and comparisions involving signed types is 
-   * done.  Important signed types include offsets from absolute locations. */
-  int x;
-  int y;
-  int width;
-  int height;
-};
-typedef struct grub_video_rect grub_video_rect_t;
 
 static void
 basic_video_test (struct videotest_options *vt_opts)

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-08-03 03:57:46 +0000
+++ conf/common.rmk	2008-08-03 04:00:22 +0000
@@ -274,6 +274,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod crc.mod
 
 # For hello.mod.
@@ -281,6 +282,16 @@
 hello_mod_CFLAGS = $(COMMON_CFLAGS)
 hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = \
+	gfxmenu/gfxmenu.c \
+	gfxmenu/model.c \
+	gfxmenu/view.c \
+	gfxmenu/widget-box.c \
+	gfxmenu/stringutil.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-28 16:26:46 +0000
+++ conf/i386-pc.rmk	2008-07-28 17:02:56 +0000
@@ -48,11 +48,13 @@
 	kern/generic/rtc_get_time_ms.c \
 	kern/generic/millisleep.c \
 	kern/env.c \
+	kern/menu_viewer.c \
 	term/i386/pc/console.c \
 	symlist.c
 kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \
+	menu_viewer.h \
 	machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
 	machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
 kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -250,7 +252,7 @@
 play_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For video.mod.
-video_mod_SOURCES = video/video.c
+video_mod_SOURCES = video/video.c video/setmode.c
 video_mod_CFLAGS = $(COMMON_CFLAGS)
 video_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'config.h.in'
--- config.h.in	2008-07-13 00:55:15 +0000
+++ config.h.in	2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
 /* Number of bits in a file offset, on hosts where this is settable. */
 #undef _FILE_OFFSET_BITS
 
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
 /* Enable GNU extensions on systems that have them.  */
 #ifndef _GNU_SOURCE
 # undef _GNU_SOURCE
 #endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
 
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-27 00:06:20 +0000
@@ -0,0 +1,221 @@
+/* gfxmenu.c - Graphical menu interface controller. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_model.h>
+#include <grub/gfxmenu_view.h>
+
+static void switch_to_text_menu (void)
+{
+  grub_env_set ("menuviewer", "terminal");
+}
+
+static void
+process_key_press (int c,
+                   grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  /* When a key is pressed, stop the timeout.  */
+  grub_gfxmenu_model_clear_timeout (model);
+
+  if (c == 'j' || c == GRUB_TERM_DOWN)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      int num_items = grub_gfxmenu_model_get_num_entries (model);
+      if (i < num_items - 1)
+        {
+          i++;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == 'k' || c == GRUB_TERM_UP)
+    {
+      int i = grub_gfxmenu_model_get_selected_index (model);
+      if (i > 0)
+        {
+          i--;
+          grub_gfxmenu_model_set_selected_index (model, i);
+        }
+    }
+  else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT)
+    {
+      int selected = grub_gfxmenu_model_get_selected_index (model);
+      int num_entries = grub_gfxmenu_model_get_num_entries (model);
+      if (selected >= 0 && selected < num_entries)
+        {
+          grub_menu_entry_t entry =
+            grub_gfxmenu_model_get_entry (model, selected);
+          grub_gfxmenu_view_execute_entry (view, entry);
+        }
+    }
+  else if (c == 'c')
+    {
+      grub_gfxmenu_view_run_terminal (view);
+      if (grub_errno != GRUB_ERR_NONE)
+        *should_exit = 1;
+    }
+  else if (c == 't')
+    {
+      /* The write hook for 'menuviewer' will cause
+       * grub_menu_viewer_should_return to return nonzero. */
+      switch_to_text_menu ();
+      *should_exit = 1;
+    }
+  else if (c == '1')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/proto/theme.txt");
+    }
+  else if (c == '2')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/winter/theme.txt");
+    }
+  else if (nested && c == GRUB_TERM_ESC)
+    {
+      *should_exit = 1;
+    }
+}
+
+static void
+handle_key_events (grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  while (!*should_exit && grub_checkkey () != -1)
+    {
+      int key = grub_getkey ();
+      int c = GRUB_TERM_ASCII_CHAR (key);
+      process_key_press (c, model, view, nested, should_exit);
+    }
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_printf ("Initializing menu data for graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  /* Create the view.  */
+  const char *theme_path = grub_env_get ("theme");
+  if (! theme_path)
+    theme_path = "/boot/grub/themes/proto/theme.txt";
+
+  view = grub_gfxmenu_view_new (theme_path, model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_printf ("Starting graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  /* Initially select the default menu entry.  */
+  int default_index = grub_menu_get_default_entry_index (menu);
+  grub_gfxmenu_model_set_selected_index (model, default_index);
+
+  /* Start the timer to execute the default entry.  */
+  grub_gfxmenu_model_set_timeout (model);
+
+  /* Main event loop.  */
+  int exit_requested = 0;
+  while (!exit_requested && !grub_menu_viewer_should_return ())
+    {
+      if (grub_gfxmenu_model_timeout_expired (model))
+        {
+          grub_gfxmenu_model_clear_timeout (model);
+          int i = grub_gfxmenu_model_get_selected_index (model);
+          grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i);
+          grub_gfxmenu_view_execute_with_fallback (view, e);
+          continue;
+        }
+
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+      handle_key_events (model, view, nested, &exit_requested);
+    }
+
+  grub_gfxmenu_view_destroy (view);
+  grub_gfxmenu_model_destroy (model);
+
+  return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)),
+                  int argc __attribute__ ((unused)),
+                  char **args __attribute__ ((unused)))
+{
+  grub_menu_t menu = grub_env_get_data_slot ("menu");
+  if (!menu)
+    return grub_error (GRUB_ERR_MENU, "No menu context");
+
+  return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+  .name = "gfxmenu",
+  .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+  (void) mod;                   /* To stop warning. */
+  grub_menu_viewer_register (&menu_viewer);
+  grub_register_command ("gfxmenu",
+                         grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+                         "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+  grub_unregister_command ("gfxmenu");
+}

=== added file 'gfxmenu/model.c'
--- gfxmenu/model.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/model.c	2008-07-26 23:37:04 +0000
@@ -0,0 +1,192 @@
+/* model.c - Graphical menu interface MVC model. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/normal.h>
+#include <grub/menu.h>
+#include <grub/time.h>
+#include <grub/gfxmenu_model.h>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  grub_menu_t menu;
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+  int timeout_set;
+  grub_uint64_t timeout_start;
+  grub_uint64_t timeout_at;
+};
+
+
+grub_gfxmenu_model_t
+grub_gfxmenu_model_new (grub_menu_t menu)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_malloc (sizeof (*model));
+  if (! model)
+    return 0;
+
+  model->menu = menu;
+  model->num_entries = menu->size;
+  model->entries = 0;
+  model->selected_entry_index = 0;
+  model->timeout_set = 0;
+  model->timeout_at = 0;
+  if (model->num_entries > 0)
+    {
+      model->entries = grub_malloc (model->num_entries 
+                                    * sizeof (*model->entries));
+      if (! model->entries)
+        goto fail_and_free;
+
+      int i;
+      grub_menu_entry_t cur;
+      for (i = 0, cur = menu->entry_list;
+           i < model->num_entries;
+           i++, cur = cur->next)
+        {
+          model->entries[i] = cur;
+        }
+    }
+
+  return model;
+
+fail_and_free:
+  grub_free (model->entries);
+  grub_free (model);
+  return 0;
+}
+
+void
+grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model)
+{
+  if (! model)
+    return;
+
+  grub_free (model->entries);
+  model->entries = 0;
+
+  grub_free (model);
+}
+
+grub_menu_t
+grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model)
+{
+  return model->menu;
+}
+
+void
+grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model)
+{
+  int timeout_sec = grub_menu_get_timeout ();
+  if (timeout_sec >= 0)
+    {
+      model->timeout_start = grub_get_time_ms ();
+      model->timeout_at = model->timeout_start + timeout_sec * 1000;
+      model->timeout_set = 1;
+    }
+  else
+    {
+      model->timeout_set = 0;
+    }
+}
+
+void
+grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model)
+{
+  model->timeout_set = 0;
+  grub_menu_set_timeout (-1);
+}
+
+int
+grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - model->timeout_start;
+}
+
+int
+grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - grub_get_time_ms ();
+}
+
+int
+grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model)
+{
+  if (model->timeout_set 
+      && grub_get_time_ms () >= model->timeout_at)
+    return 1;
+
+  return 0;
+}
+
+int 
+grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model)
+{
+  return model->num_entries;
+}
+
+int 
+grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model)
+{
+  return model->selected_entry_index;
+}
+
+void 
+grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index)
+{
+  model->selected_entry_index = index;
+}
+
+const char *
+grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index]->title;
+}
+
+grub_menu_entry_t
+grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index)
+{
+  if (index < 0 || index >= model->num_entries)
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+      return 0;
+    }
+
+  return model->entries[index];
+}
+

=== added file 'gfxmenu/stringutil.c'
--- gfxmenu/stringutil.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c	2008-07-26 00:44:48 +0000
@@ -0,0 +1,196 @@
+/* stringutil.c - String utilities. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/stringutil.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+   The range of buf included is the half-open interval [START,END).
+   The index START is inclusive, END is exclusive.  */
+char *
+grub_new_substring (const char *buf,
+                    grub_size_t start, grub_size_t end)
+{
+  char *s = grub_malloc (end - start + 1);
+  if (! s)
+    return 0;
+  grub_memcpy (s, buf + start, end - start);
+  s[end - start] = '\0';
+  return s;
+}
+
+/* Eliminate "." and ".." path elements from PATH.  A new heap-allocated
+   string is returned.  */
+static char *
+canonicalize_path (const char *path)
+{
+  int i;
+  const char *p;
+  char *newpath = 0;
+
+  /* Count the path components in path.  */
+  int components = 1;
+  for (p = path; *p; p++)
+    if (*p == '/')
+      components++;
+
+  char **path_array = grub_malloc (components * sizeof (*path_array));
+  if (! path_array)
+    return 0;
+
+  /* Initialize array elements to NULL pointers; in case once of the
+     allocations fails, the cleanup code can just call grub_free() for all
+     pointers in the array.  */
+  for (i = 0; i < components; i++)
+    path_array[i] = 0;
+
+  /* Parse the path into path_array.  */
+  p = path;
+  for (i = 0; i < components && p; i++)
+    {
+      /* Find the end of the path element.  */
+      const char *end = grub_strchr (p, '/');
+      if (!end)
+        end = p + grub_strlen (p);
+
+      /* Copy the element.  */
+      path_array[i] = grub_new_substring (p, 0, end - p);
+      if (!path_array[i])
+        goto cleanup;
+
+      /* Advance p to point to the start of the next element, or NULL.  */
+      if (*end)
+        p = end + 1;
+      else
+        p = 0;
+    }
+
+  /* Eliminate '.' and '..' elements from the path array.  */
+  int newpath_length = 0;
+  for (i = components - 1; i >= 0; --i)
+    {
+      if (! grub_strcmp (path_array[i], "."))
+        {
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else if (! grub_strcmp (path_array[i], "..")
+               && i > 0)
+        {
+          /* Delete the '..' and the prior path element.  */
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+          --i;
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else
+        {
+          newpath_length += grub_strlen (path_array[i]) + 1;
+        }
+    }
+
+  /* Construct a new path string.  */
+  newpath = grub_malloc (newpath_length + 1);
+  if (! newpath)
+    goto cleanup;
+
+  newpath[0] = '\0';
+  char *newpath_end = newpath;
+  int first = 1;
+  for (i = 0; i < components; i++)
+    {
+      char *element = path_array[i];
+      if (element)
+        {
+          /* For all components but the first, prefix with a slash.  */
+          if (! first)
+            newpath_end = grub_stpcpy (newpath_end, "/");
+          newpath_end = grub_stpcpy (newpath_end, element);
+          first = 0;
+        }
+    }
+
+cleanup:
+  for (i = 0; i < components; i++)
+    grub_free (path_array[i]);
+  grub_free (path_array);
+
+  return newpath;
+}
+
+/* Return a new heap-allocated string representing to absolute path
+   to the file referred to by PATH.  If PATH is an absolute path, then
+   the returned path is a copy of PATH.  If PATH is a relative path, then
+   BASE is with PATH used to construct the absolute path.  */
+char *
+grub_resolve_relative_path (const char *base, const char *path)
+{
+  char *abspath;
+  char *canonpath;
+  char *p;
+
+  /* If PATH is an absolute path, then just use it as is.  */
+  if (path[0] == '/' || path[0] == '(')
+    return canonicalize_path (path);
+
+  abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1);
+  if (!abspath)
+    return 0;
+
+  /* Concatenate BASE and PATH.
+     Note that BASE is expected to have a trailing slash.  */
+  p = grub_stpcpy (abspath, base);
+  grub_stpcpy (p, path);
+
+  canonpath = canonicalize_path (abspath);
+  if (!canonpath)
+    return abspath;
+
+  grub_free (abspath);
+  return canonpath;
+}
+
+/* Get the path of the directory where the file at FILE_PATH is located.
+   FILE_PATH should refer to a file, not a directory.  The returned path
+   includes a trailing slash.
+   This does not handle GRUB "(hd0,0)" paths properly yet since it only
+   looks at slashes.  */
+char *
+grub_get_dirname (const char *file_path)
+{
+  int i;
+  int last_slash;
+
+  last_slash = -1;
+  for (i = grub_strlen (file_path) - 1; i >= 0; --i)
+    {
+      if (file_path[i] == '/')
+        {
+          last_slash = i;
+          break;
+        }
+    }
+  if (last_slash == -1)
+    return grub_strdup ("/");
+
+  return grub_new_substring (file_path, 0, last_slash + 1);
+}

=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-08-03 02:14:40 +0000
@@ -0,0 +1,1096 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/stringutil.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/time.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_view.h>
+
+/* Specifies a time (in ms) to delay so the user can read the message if a
+   default boot attempt fails and an attempt is made to fall back on
+   another entry.  */
+#define FALLBACK_MESSAGE_DELAY 2000
+
+typedef struct grub_gfxmenu_view_icon
+{
+  const char *class_name;
+  struct grub_video_bitmap *bitmap;
+  struct grub_gfxmenu_view_icon *next;
+} *grub_gfxmenu_view_icon_t;
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_font_t status_font;
+  char *terminal_font_name;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t selected_item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_bg_color;
+  grub_video_color_t progress_bar_border_color;
+  grub_video_color_t progress_bar_fg_color;
+  grub_video_color_t progress_bar_bg_color;
+  struct grub_video_bitmap *desktop_image;
+  grub_video_color_t desktop_color;
+  grub_gfxmenu_box_t menu_box;
+  grub_gfxmenu_box_t selected_item_box;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+  char *theme_path;
+  /* Icon cache: linked list w/ dummy head node.  */
+  struct grub_gfxmenu_view_icon icon_cache;
+
+  grub_gfxmenu_model_t model;
+};
+
+static void init_terminal (grub_gfxmenu_view_t view);
+static void destroy_terminal (void);
+static grub_err_t set_graphics_mode (void);
+static grub_err_t set_text_mode (void);
+
+/* Create a new view object, loading the theme specified by THEME_PATH and
+   associating MODEL with the view.  */
+grub_gfxmenu_view_t
+grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
+{
+  grub_gfxmenu_view_t view;
+
+  view = grub_malloc (sizeof (*view));
+  if (! view)
+    return 0;
+
+  set_graphics_mode ();
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_video_get_viewport ((unsigned *) &view->screen.x,
+                           (unsigned *) &view->screen.y,
+                           (unsigned *) &view->screen.width,
+                           (unsigned *) &view->screen.height);
+
+  /* Clear the screen; there may be garbage left over in video memory, and
+     loading the menu style (particularly the background) can take a while. */
+  grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  grub_font_t default_font;
+  grub_video_color_t default_fg_color;
+  grub_video_color_t default_bg_color;
+
+  default_font = grub_font_get ("Helvetica 12");
+  default_fg_color = grub_video_map_rgb (0, 0, 0);
+  default_bg_color = grub_video_map_rgb (255, 255, 255);
+
+  view->icon_width = 32;
+  view->icon_height = 32;
+  view->item_height = 42;
+  view->item_padding = 14;
+  view->item_icon_space = 4;
+  view->item_spacing = 16;
+  view->title_font = default_font;
+  view->item_font = default_font;
+  view->selected_item_font = default_font;
+  view->status_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->selected_item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->progress_bar_border_color = default_fg_color;
+  view->progress_bar_fg_color = grub_video_map_rgb (160, 160, 160);
+  view->progress_bar_bg_color = default_bg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->terminal_box = 0;
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+  view->theme_path = 0;
+  view->icon_cache.class_name = 0;
+  view->icon_cache.bitmap = 0;
+  view->icon_cache.next = 0;
+
+  view->model = model;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+static void
+free_icon_cache (grub_gfxmenu_view_t view)
+{
+  grub_gfxmenu_view_icon_t cur;
+  grub_gfxmenu_view_icon_t next;
+  for (cur = view->icon_cache.next; cur; cur = next)
+    {
+      next = cur->next;
+      grub_free (cur);
+    }
+  view->icon_cache.next = 0;
+}
+
+/* Destroy the view object.  All used memory is freed.  */
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+  grub_video_bitmap_destroy (view->desktop_image);
+  if (view->menu_box)
+    view->menu_box->destroy (view->menu_box);
+  if (view->selected_item_box)
+    view->selected_item_box->destroy (view->selected_item_box);
+  if (view->terminal_box)
+    view->terminal_box->destroy (view->terminal_box);
+  grub_free (view->terminal_font_name);
+  grub_free (view->title_text);
+  grub_free (view->progress_message_text);
+  grub_free (view->theme_path);
+  free_icon_cache (view);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view.  The string MESSAGE
+   must be heap-allocated and will be owned by VIEW.  MESSAGE can be 0, in
+   which case no message is displayed.  */
+static void
+set_progress_message (grub_gfxmenu_view_t view, char *message)
+{
+  grub_free (view->progress_message_text);
+  view->progress_message_text = message;
+}
+
+/* Parse a color string of the form "r, g, b".
+   Whitespace is insignificant.  */
+static grub_video_color_t
+parse_color (const char *s)
+{
+  int red = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int green = grub_strtoul (s, 0, 0);
+  if ((s = grub_strchr (s, ',')) == 0)
+    goto fail;
+  s++;
+  int blue = grub_strtoul (s, 0, 0);
+  int alpha;
+  if ((s = grub_strchr (s, ',')) == 0)
+    alpha = 255;
+  else
+    {
+      s++;
+      alpha = grub_strtoul (s, 0, 0);
+    }
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  return grub_video_map_rgba (red, green, blue, alpha);
+
+fail:
+  return 0;
+}
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+   storing the new widget at *BOXPTR.  PATTERN should be of the form:
+   "somewhere/style*.png".  The '*' then gets substituted with the various
+   pixmap names that the widget uses.
+   Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+               char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  grub_memcpy (prefix, abspattern, star - abspattern);
+  prefix[star - abspattern] = '\0';
+
+  /* Suffix:  Everything after the '*' is the suffix.  */
+  suffix = star + 1;
+
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (abspattern);   /* Note: suffix, star point into abspattern.  */
+  grub_free (prefix);
+  if (! box)
+    return 0;
+
+  if (*boxptr)
+    (*boxptr)->destroy (*boxptr);
+  *boxptr = box;
+  return 1;
+
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+   This function takes ownership of both NAME and VALUE, so the caller
+   should pass pointers to new heap-allocated strings.  */
+static void
+theme_set_string (grub_gfxmenu_view_t view, char *name, char *value,
+                  const char *theme_dir)
+{
+  if (! grub_strcmp ("title-font", name))
+    view->title_font = grub_font_get (value);
+  else if (! grub_strcmp ("item-font", name))
+    view->item_font = grub_font_get (value);
+  else if (! grub_strcmp ("selected-item-font", name))
+    view->selected_item_font = grub_font_get (value);
+  else if (! grub_strcmp ("status-font", name))
+    view->status_font = grub_font_get (value);
+  else if (! grub_strcmp ("terminal-font", name))
+    {
+      grub_free (view->terminal_font_name);
+      view->terminal_font_name = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+  else if (! grub_strcmp ("title-color", name))
+    view->title_color = parse_color (value);
+  else if (! grub_strcmp ("item-color", name))
+    view->item_color = parse_color (value);
+  else if (! grub_strcmp ("selected-item-color", name))
+    view->selected_item_color = parse_color (value);
+  else if (! grub_strcmp ("status-color", name))
+    view->status_color = parse_color (value);
+  else if (! grub_strcmp ("status-bg-color", name))
+    view->status_bg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-border-color", name))
+    view->progress_bar_border_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-fg-color", name))
+    view->progress_bar_fg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-bg-color", name))
+    view->progress_bar_bg_color = parse_color (value);
+  else if (! grub_strcmp ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      char *path;
+      path = grub_resolve_relative_path (theme_dir, value);
+      if (! path)
+        goto fail;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_free (path);
+          goto fail;
+        }
+      grub_free(path);
+      grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                       view->screen.width,
+                                       view->screen.height,
+                                       raw_bitmap,
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+      grub_video_bitmap_destroy (raw_bitmap);
+      if (!scaled_bitmap)
+        goto fail;
+
+      grub_video_bitmap_destroy (view->desktop_image);
+      view->desktop_image = scaled_bitmap;
+    }
+  else if (! grub_strcmp ("desktop-color", name))
+    view->desktop_color = parse_color (value);
+  else if (! grub_strcmp ("menu-box", name))
+    theme_set_box (&view->menu_box, value, theme_dir);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value, theme_dir);
+  else if (! grub_strcmp ("terminal-box", name))
+    theme_set_box (&view->terminal_box, value, theme_dir);
+  else if (! grub_strcmp ("title-text", name))
+    {
+      grub_free (view->title_text);
+      view->title_text = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+
+fail:
+  grub_free (value);
+  grub_free (name);
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+   This function takes ownership NAME, so the caller should pass a pointer
+   to a new heap-allocated string.  */
+static void
+theme_set_number (grub_gfxmenu_view_t view, char *name, int value)
+{
+  if (! grub_strcmp ("icon-width", name))
+    view->icon_width = value;
+  else if (! grub_strcmp ("icon-height", name))
+    view->icon_height = value;
+  else if (! grub_strcmp ("item-height", name))
+    view->item_height = value;
+  else if (! grub_strcmp ("item-padding", name))
+    view->item_padding = value;
+  else if (! grub_strcmp ("item-icon-space", name))
+    view->item_icon_space = value;
+  else if (! grub_strcmp ("item-spacing", name))
+    view->item_spacing = value;
+
+  grub_free (name);
+}
+
+/* Set properties on the view based on settings from the specified
+   theme file.  Returns nonzero on success, zero on failure.  */
+int
+grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
+{
+  char *theme_dir;
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  theme_dir = grub_get_dirname (theme_path);
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    {
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  pos = 0;
+  while (pos < len)
+    {
+      /* Skip comments (lines beginning with #).  */
+      if (pos < len && buf[pos] == '#')
+        goto nextline;
+
+      /* Get name.  */
+      /* Find a word character.  */
+      while (pos < len && grub_isspace(buf[pos]))
+        pos++;
+      int name_start = pos;
+      /* Find the end of the name.  */
+      while (pos < len
+             && (grub_isalpha(buf[pos])
+                 || grub_isdigit(buf[pos])
+                 || buf[pos] == '_'
+                 || buf[pos] == '-'))
+        pos++;
+      int name_end = pos;
+
+      if (name_end - name_start < 1)
+        goto nextline;
+
+      /* Skip whitespace before separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Read separator.  */
+      if (buf[pos] != ':')
+        goto nextline;
+
+      pos++;  /* Skip separator.  */
+
+      /* Skip whitespace after separator.  */
+      while (pos < len
+             && (buf[pos] == ' '
+                 || buf[pos] == '\t'
+                 || buf[pos] == '\f'))
+        pos++;
+
+      /* Get the value based on its type.  */
+      if (pos < len && buf[pos] == '"')
+        {
+          /* String value.  (e.g., '"My string"')  */
+
+          int value_start;
+          int value_end;
+
+          /* Skip the opening quotation mark.  */
+          pos++;
+          /* Get string value.  */
+          value_start = pos;
+          /* Find the ending quotation mark.  */
+          while (pos < len
+                 && !(buf[pos] == '"'
+                      || buf[pos] == '\n'))
+            pos++;
+          value_end = pos;
+          theme_set_string (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            grub_new_substring (buf, value_start, value_end),
+                            theme_dir);
+        }
+      else if (pos < len && grub_isdigit(buf[pos]))
+        {
+          /* Numeric value.  (e.g., '123')  */
+
+          int value_start;
+          int value_end;
+          char *value_str;
+          int value;
+
+          /* Get numeric value.  */
+          value_start = pos;
+          /* Find the end of the digit sequence.  */
+          while (pos < len && grub_isdigit(buf[pos]))
+            pos++;
+          value_end = pos;
+          value_str = grub_new_substring (buf, value_start, value_end);
+          if (!value_str)
+            continue;
+          value = grub_strtoul (value_str, 0, 0);
+          grub_free (value_str);
+          theme_set_number (view,
+                            grub_new_substring (buf, name_start, name_end),
+                            value);
+        }
+
+nextline:
+      /* Eat characters up to the newline.  */
+      while (pos < len && buf[pos] != '\n')
+        pos++;
+      pos++;   /* Eat the newline.  */
+    }
+
+  grub_free (view->theme_path);
+  view->theme_path = grub_strdup (theme_path);
+  free_icon_cache (view);   /* There may be new icons for this theme.  */
+  grub_free (buf);
+  grub_file_close (file);
+  grub_free (theme_dir);
+  return 1;
+}
+
+static void
+draw_background (grub_gfxmenu_view_t view)
+{
+  if (view->desktop_image)
+    {
+      struct grub_video_bitmap *img = view->desktop_image;
+      grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
+                              view->screen.x, view->screen.y, 0, 0,
+                              grub_video_bitmap_get_width (img),
+                              grub_video_bitmap_get_height (img));
+    }
+  else
+    {
+      grub_video_fill_rect (view->desktop_color,
+                            view->screen.x, view->screen.y,
+                            view->screen.width, view->screen.height);
+    }
+}
+
+static const char icon_extension[] = ".png";
+
+static struct grub_video_bitmap *
+try_loading_icon (grub_gfxmenu_view_t view,
+                  const char *dir, const char *class_name)
+{
+  char *path = grub_malloc (grub_strlen (dir)
+                            + grub_strlen (class_name)
+                            + grub_strlen (icon_extension)
+                            + 100);
+  if (! path)
+    return 0;
+
+  grub_strcpy (path, dir);
+  grub_strcat (path, class_name);
+  grub_strcat (path, icon_extension);
+
+  struct grub_video_bitmap *raw_bitmap;
+  grub_video_bitmap_load (&raw_bitmap, path);
+  grub_free (path);
+  grub_errno = GRUB_ERR_NONE;  /* Critical to clear the error!!  */
+  if (! raw_bitmap)
+    return 0;
+
+  struct grub_video_bitmap *scaled_bitmap;
+  grub_video_bitmap_create_scaled (&scaled_bitmap,
+                                   view->icon_width, view->icon_height,
+                                   raw_bitmap,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  grub_video_bitmap_destroy (raw_bitmap);
+  if (! scaled_bitmap)
+    return 0;
+
+  return scaled_bitmap;
+}
+
+static struct grub_video_bitmap *
+get_icon_by_class (grub_gfxmenu_view_t view, const char *class_name)
+{
+
+  /* First check the view's icon cache.  */
+  grub_gfxmenu_view_icon_t vicon;
+  for (vicon = view->icon_cache.next; vicon; vicon = vicon->next)
+    {
+      if (grub_strcmp (vicon->class_name, class_name) == 0)
+        return vicon->bitmap;
+    }
+
+  /* Otherwise, we search for an icon to load.  */
+  if (! view->theme_path)
+    return 0;
+
+  char *theme_dir = grub_get_dirname (view->theme_path);
+  char *icons_dir;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  /* First try the theme's own icons, from "grub/themes/NAME/icons/"  */
+  icons_dir = grub_resolve_relative_path (theme_dir, "icons/");
+  if (icons_dir)
+    {
+      icon = try_loading_icon (view, icons_dir, class_name);
+      grub_free (icons_dir);
+    }
+  if (! icon)
+    {
+      /* If the theme doesn't have an appropriate icon, check in
+         "grub/themes/icons".  */
+      icons_dir = grub_resolve_relative_path (theme_dir, "../icons/");
+      if (icons_dir)
+        {
+          icon = try_loading_icon (view, icons_dir, class_name);
+          grub_free (icons_dir);
+        }
+    }
+  grub_free (theme_dir);
+
+  if (! icon)
+    return 0;
+
+  vicon = grub_malloc (sizeof (*vicon));
+  if (! vicon)
+    {
+      grub_video_bitmap_destroy (icon);
+      return 0;
+    }
+  vicon->class_name = class_name;
+  vicon->bitmap = icon;
+  vicon->next = view->icon_cache.next;
+  view->icon_cache.next = vicon;   /* Link it into the cache.  */
+  return vicon->bitmap;
+}
+
+static struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view, int item_index)
+{
+  grub_menu_entry_t entry;
+  entry = grub_gfxmenu_model_get_entry (view->model, item_index);
+  if (! entry)
+    return 0;
+
+  struct grub_menu_entry_class *c;
+  struct grub_video_bitmap *icon;
+  icon = 0;
+  for (c = entry->classes->next; c && ! icon; c = c->next)
+    icon = get_icon_by_class (view, c->name);
+  return icon;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+  int boxpad = view->item_padding;
+  int icon_text_space = view->item_icon_space;
+  int item_vspace = view->item_spacing;
+
+  int ascent = grub_font_get_ascent (view->item_font);
+  int descent = grub_font_get_descent (view->item_font);
+  int item_height = view->item_height;
+
+  int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+  grub_video_rect_t r;
+  r.width = view->screen.width * 4 / 5;
+  /* Set the menu box height to fit the items.  */
+  r.height = (item_height * num_items
+              + item_vspace * (num_items - 1)
+              + 2 * boxpad);
+  r.x = (view->screen.width - r.width) / 2;
+  r.y = (view->screen.height - r.height) / 2;
+  view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+  int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+  int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+  view->menu_box->draw (view->menu_box,
+                        r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      int is_selected =
+        (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+      if (is_selected)
+        {
+          view->selected_item_box->set_content_size (view->selected_item_box,
+                                                     r.width - 2 * boxpad,
+                                                     item_height);
+          int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+          int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+          view->selected_item_box->draw (view->selected_item_box,
+                                         item_left - leftpad,
+                                         item_top - toppad);
+        }
+
+      struct grub_video_bitmap *icon;
+      if ((icon = get_item_icon (view, i)) != 0)
+        grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - view->icon_height) / 2,
+                                0, 0, view->icon_width, view->icon_height);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (view->model, i);
+      grub_video_draw_string (item_title,
+                              (is_selected
+                               ? view->selected_item_font
+                               : view->item_font),
+                              (is_selected
+                               ? view->selected_item_color
+                               : view->item_color),
+                              item_left + view->icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+  if (! view->title_text)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font,
+                                                view->title_text);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->title_text,
+                          view->title_font, view->title_color,
+                          x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+  int descent = grub_font_get_descent (view->status_font);
+  int ascent = grub_font_get_ascent (view->status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (view->status_bg_color,
+                        0, view->screen.height - h,
+                        view->screen.width, view->screen.height - 1);
+
+  int texty = view->screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          view->status_font, view->status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          view->status_font, view->status_color, 30, texty);
+}
+
+static void
+draw_timeout (grub_gfxmenu_view_t view)
+{
+  int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+  if (timeout == -1)
+    return;
+
+  int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+  if (remaining < 0)
+    remaining = 0;
+
+  int t = timeout - remaining;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 3 / 5;
+  f.height = view->screen.height / 25;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90;
+
+  /* First attempt just uses filled rectangles;
+     TODO we should enhance with a pixmap themed progress bar component.  */
+
+  /* Border.  */
+  grub_video_fill_rect (view->progress_bar_border_color,
+                        f.x - 1, f.y - 1,
+                        f.width + 2, f.height + 2);
+
+  /* Bar background.  */
+  int barwidth = f.width * t / timeout;
+  grub_video_fill_rect (view->progress_bar_bg_color,
+                        f.x + barwidth, f.y,
+                        f.width - barwidth, f.height);
+
+  /* Bar foreground.  */
+  grub_video_fill_rect (view->progress_bar_fg_color,
+                        f.x, f.y,
+                        barwidth, f.height);
+
+  char *text = grub_malloc (200);
+  if (!text)
+    return;
+  int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+  grub_sprintf (text,
+                "The highlighted entry will be booted automatically in %d s.",
+                seconds_remaining_rounded_up);
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->progress_bar_border_color;
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+  grub_free (text);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->status_color;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 4 / 5;
+  f.height = 50;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+  /* Border.  */
+  grub_video_fill_rect (color,
+                        f.x-1, f.y-1, f.width+2, f.height+2);
+  /* Fill.  */
+  grub_video_fill_rect (view->status_bg_color,
+                        f.x, f.y, f.width, f.height);
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+}
+
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  draw_background (view);
+  draw_menu (view);
+  draw_title (view);
+  draw_status (view);
+  draw_timeout (view);
+  draw_message (view);
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *doublebuf_str = grub_env_get ("doublebuffering");
+  int doublebuf_flags =
+    (doublebuf_str && doublebuf_str[0] == 'n')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  const char *modestr = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static int term_initialized;
+static grub_term_t term_original;
+static grub_gfxmenu_view_t term_view;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+                  int y __attribute ((unused)),
+                  int width __attribute ((unused)),
+                  int height __attribute ((unused)))
+{
+  if (! term_view)
+    return;
+
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  grub_gfxmenu_view_draw (term_view);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  int termx = term_view->screen.x
+    + term_view->screen.width * (10 - 7) / 10 / 2;
+  int termy = term_view->screen.y
+    + term_view->screen.height * (10 - 7) / 10 / 2;
+
+  grub_gfxmenu_box_t term_box = term_view->terminal_box;
+  if (term_box)
+    {
+      term_box->set_content_size (term_box,
+                                  term_target_width, term_target_height);
+
+      term_box->draw (term_box,
+                      termx - term_box->get_left_pad (term_box),
+                      termy - term_box->get_top_pad (term_box));
+    }
+
+  grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+                                 termx, termy,
+                                 0, 0, term_target_width, term_target_height);
+  grub_video_swap_buffers ();
+}
+
+static void
+init_terminal (grub_gfxmenu_view_t view)
+{
+  term_original = grub_term_get_current ();
+
+  term_target_width = view->screen.width * 7 / 10;
+  term_target_height = view->screen.height * 7 / 10;
+
+  grub_video_create_render_target (&term_target,
+                                   term_target_width,
+                                   term_target_height,
+                                   GRUB_VIDEO_MODE_TYPE_RGB
+                                   | GRUB_VIDEO_MODE_TYPE_ALPHA);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  /* Note: currently there is no API for changing the gfxterm font
+     on the fly, so whatever font the initially loaded theme specifies
+     will be permanent.  */
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            view->terminal_font_name, 3);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+  term_initialized = 1;
+
+  /* XXX: store static pointer to the 'view' object so the repaint callback can access it.  */
+  term_view = view;
+  grub_gfxterm_set_repaint_callback (repaint_terminal);
+  grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+  term_view = 0;
+  if (term_initialized)
+    grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  if (term_target)
+    grub_video_delete_render_target (term_target);
+  if (term_original)
+    grub_term_set_current (term_original);
+}
+
+
+static void
+notify_booting (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Booting '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+}
+
+static void
+notify_fallback (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Falling back to '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (FALLBACK_MESSAGE_DELAY);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__ ((unused)))
+{
+}
+
+
+static struct grub_menu_execute_callback execute_callback =
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+int
+grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                         grub_menu_entry_t entry)
+{
+  grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
+                                   entry, &execute_callback, (void *) view);
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  /* If we returned, there was a failure.  */
+  set_progress_message (view, grub_strdup ("Unable to automatically boot.  "
+                                           "Press SPACE to continue."));
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  while (GRUB_TERM_ASCII_CHAR(grub_getkey ()) != ' ')
+    {
+      /* Wait for SPACE to be pressed.  */
+    }
+
+  set_progress_message (view, 0);   /* Clear the message.  */
+
+  return 1;   /* Ok.  */
+}
+
+int
+grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                 grub_menu_entry_t entry)
+{
+  /* Currently we switch back to text mode by restoring
+     the original terminal before executing the menu entry.
+     It is hard to make it work when executing a menu entry
+     that switches video modes -- it using gfxterm in a
+     window, the repaint callback seems to crash GRUB. */
+  /* TODO: Determine if this works when 'gfxterm' was set as
+     the current terminal before invoking the gfxmenu. */
+  destroy_terminal ();
+
+  grub_menu_execute_entry (entry);
+  if (grub_errno != GRUB_ERR_NONE)
+    grub_wait_after_message ();
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  init_terminal (view);
+  return 1;   /* Ok.  */
+}
+
+void
+grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused)))
+{
+  grub_cmdline_run (1);
+}
+

=== added file 'gfxmenu/widget-box.c'
--- gfxmenu/widget-box.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/widget-box.c	2008-07-19 18:25:18 +0000
@@ -0,0 +1,244 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/gfxwidgets.h>
+
+enum box_pixmaps
+{
+  BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW,
+  BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W,
+  BOX_PIXMAP_CENTER
+};
+
+static const char *box_pixmap_names[] = {
+  /* Corners: */
+  "nw", "ne", "se", "sw",
+  /* Sides: */
+  "n", "e", "s", "w",
+  /* Center: */
+  "c"
+};
+
+#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names))
+
+static void
+draw (grub_gfxmenu_box_t self, int x, int y)
+{
+  int height_n;
+  int height_s;
+  int height_e;
+  int height_w;
+  int width_n;
+  int width_s;
+  int width_e;
+  int width_w;
+  unsigned i;
+
+  /* Don't try to draw if any pixmaps are null, except the center. */
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0)
+        return;
+    }
+
+  height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+  width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+  width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+  width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+  width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+  /* Draw sides.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], 
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y, 0, 0, width_n, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w, y + height_n + height_w,
+                          0, 0, width_s, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n,
+                          0, 0, width_e, height_e);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n, 0, 0, width_w, height_w);
+
+  /* Draw corners.  */
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y, 0, 0, width_w, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y, 
+                          0, 0, width_e, height_n);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x + width_w + width_n, y + height_n + height_e, 
+                          0, 0, width_e, height_s);
+  grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW],
+                          GRUB_VIDEO_BLIT_BLEND,
+                          x, y + height_n + height_w, 
+                          0, 0, width_w, height_s);
+
+  /* Draw center.  */
+  if (self->scaled_pixmaps[BOX_PIXMAP_CENTER])
+    grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER],
+                            GRUB_VIDEO_BLIT_BLEND,
+                            x + width_w, y + height_n,
+                            0, 0, self->width, self->height);
+}
+
+static void
+scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h)
+{
+  struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i];
+  struct grub_video_bitmap *raw = self->raw_pixmaps[i];
+
+  if (raw == 0)
+    return;
+
+  if (w == -1)
+    w = grub_video_bitmap_get_width (raw);
+  if (h == -1)
+    h = grub_video_bitmap_get_height (raw);
+
+  if (*scaled == 0 
+      || ((int) grub_video_bitmap_get_width (*scaled) != w)
+      || ((int) grub_video_bitmap_get_height (*scaled) != h))
+    {
+      if (*scaled)
+        {
+          grub_video_bitmap_destroy (*scaled);
+          *scaled = 0;
+        }
+      grub_video_bitmap_create_scaled (scaled, w, h, raw, 
+                                       GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+    }
+}
+
+static void
+set_content_size (grub_gfxmenu_box_t self,
+                  int width, int height)
+{
+  self->width = width;
+  self->height = height;
+  /* Resize sides to match the width and height.  */
+  /* It is assumed that the corners width/height match the adjacent sides.  */
+
+  /* Resize N and S sides to match width.  */
+  scale_pixmap(self, BOX_PIXMAP_N, width, -1);
+  scale_pixmap(self, BOX_PIXMAP_S, width, -1);
+
+  /* Resize E and W sides to match height.  */
+  scale_pixmap(self, BOX_PIXMAP_E, -1, height);
+  scale_pixmap(self, BOX_PIXMAP_W, -1, height);
+
+  /* Don't scale the corners--they are assumed to match the sides. */
+  scale_pixmap(self, BOX_PIXMAP_NW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SW, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_NE, -1, -1);
+  scale_pixmap(self, BOX_PIXMAP_SE, -1, -1);
+
+  /* Scale the center area. */
+  scale_pixmap(self, BOX_PIXMAP_CENTER, width, height);
+}
+
+static int
+get_left_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+  return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (grub_gfxmenu_box_t self)
+{
+  unsigned i;
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      if (self->raw_pixmaps[i])
+        grub_video_bitmap_destroy(self->raw_pixmaps[i]);
+      self->raw_pixmaps[i] = 0;
+
+      if (self->scaled_pixmaps[i])
+        grub_video_bitmap_destroy(self->scaled_pixmaps[i]);
+      self->scaled_pixmaps[i] = 0;
+    }
+  grub_free (self->raw_pixmaps);
+  self->raw_pixmaps = 0;
+  grub_free (self->scaled_pixmaps);
+  self->scaled_pixmaps = 0;
+
+  grub_free (self);             /* Free self:  must be the last step! */
+}
+
+
+grub_gfxmenu_box_t
+grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                         const char *pixmaps_suffix)
+{
+  char path[200];
+  unsigned i;
+  grub_gfxmenu_box_t box;
+
+  box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box));
+  if (!box)
+    return 0;
+  box->width = 0;
+  box->height = 0;
+  box->raw_pixmaps =
+    (struct grub_video_bitmap **) 
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+  box->scaled_pixmaps =
+    (struct grub_video_bitmap **)
+    grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+
+  for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+    {
+      /* TODO XXX dynamically allocate PATH, making sure it's large enough */
+      grub_sprintf (path, "%s%s%s",
+                    pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+      grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+      box->scaled_pixmaps[i] = 0;
+    }
+
+  box->draw = draw;
+  box->set_content_size = set_content_size;
+  box->get_left_pad = get_left_pad;
+  box->get_top_pad = get_top_pad;
+  box->destroy = destroy;
+
+  return box;
+}
+

=== added file 'include/grub/gfxmenu_model.h'
--- include/grub/gfxmenu_model.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_model.h	2008-07-26 23:37:04 +0000
@@ -0,0 +1,59 @@
+/* gfxmenu_model.h - gfxmenu model interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+#include <grub/menu.h>
+
+struct grub_gfxmenu_model;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t;
+
+
+grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu);
+
+void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model);
+
+grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model,
+                                            int index);
+
+const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model,
+                                                int index);
+
+grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model,
+                                                int index);
+
+#endif /* GRUB_GFXMENU_MODEL_HEADER */
+

=== added file 'include/grub/gfxmenu_view.h'
--- include/grub/gfxmenu_view.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_view.h	2008-07-26 23:37:04 +0000
@@ -0,0 +1,53 @@
+/* gfxmenu_view.h - gfxmenu view interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_VIEW_HEADER
+#define GRUB_GFXMENU_VIEW_HEADER 1
+
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/menu.h>
+#include <grub/gfxmenu_model.h>
+
+struct grub_gfxmenu_view;   /* Forward declaration of opaque type.  */
+typedef struct grub_gfxmenu_view *grub_gfxmenu_view_t;
+
+
+grub_gfxmenu_view_t grub_gfxmenu_view_new (const char *theme_path,
+                                           grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view);
+
+/* Set properties on the view based on settings from the specified
+   theme file. */
+int grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view,
+                                  const char *theme_path);
+
+void grub_gfxmenu_view_draw (grub_gfxmenu_view_t view);
+
+int grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                             grub_menu_entry_t entry);
+
+int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+                                     grub_menu_entry_t entry);
+
+void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view);
+
+#endif /* GRUB_GFXMENU_VIEW_HEADER */
+

=== added file 'include/grub/gfxterm.h'
--- include/grub/gfxterm.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxterm.h	2008-07-19 19:56:06 +0000
@@ -0,0 +1,41 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXTERM_HEADER
+#define GRUB_GFXTERM_HEADER	1
+
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/term.h>
+#include <grub/video.h>
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width);
+
+void grub_gfxterm_destroy_window (void);
+
+grub_term_t grub_gfxterm_get_term (void);
+
+typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y, 
+                                                int width, int height);
+
+void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func);
+
+#endif /* ! GRUB_GFXTERM_HEADER */

=== added file 'include/grub/gfxwidgets.h'
--- include/grub/gfxwidgets.h	1970-01-01 00:00:00 +0000
+++ include/grub/gfxwidgets.h	2008-07-19 18:25:18 +0000
@@ -0,0 +1,47 @@
+/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu).  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXWIDGETS_HEADER
+#define GRUB_GFXWIDGETS_HEADER 1
+
+#include <grub/video.h>
+
+typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t;
+
+struct grub_gfxmenu_box
+{
+  /* The size of the content.  */
+  int width;
+  int height;
+
+  struct grub_video_bitmap **raw_pixmaps;
+  struct grub_video_bitmap **scaled_pixmaps;
+
+  void (*draw) (grub_gfxmenu_box_t self, int x, int y);
+  void (*set_content_size) (grub_gfxmenu_box_t self,
+                            int width, int height);
+  int (*get_left_pad) (grub_gfxmenu_box_t self);
+  int (*get_top_pad) (grub_gfxmenu_box_t self);
+  void (*destroy) (grub_gfxmenu_box_t self);
+};
+
+grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix,
+                                            const char *pixmaps_suffix);
+
+#endif /* ! GRUB_GFXWIDGETS_HEADER */

=== added file 'include/grub/menu.h'
--- include/grub/menu.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu.h	2008-08-03 02:14:40 +0000
@@ -0,0 +1,63 @@
+/* menu.h - Menu and menu entry model declarations. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MENU_HEADER
+#define GRUB_MENU_HEADER 1
+
+struct grub_menu_entry_class
+{
+  char *name;
+  struct grub_menu_entry_class *next;
+};
+
+/* The menu entry.  */
+struct grub_menu_entry
+{
+  /* The title name.  */
+  const char *title;
+
+  /* The classes associated with the menu entry:
+     used to choose an icon or other style attributes.
+     This is a dummy head node for the linked list, so for an entry E,
+     E.classes->next is the first class if it is not NULL.  */
+  struct grub_menu_entry_class *classes;
+
+  /* The commands associated with this menu entry.  */
+  struct grub_script *commands;
+
+  /* The sourcecode of the menu entry, used by the editor.  */
+  const char *sourcecode;
+
+  /* The next element.  */
+  struct grub_menu_entry *next;
+};
+typedef struct grub_menu_entry *grub_menu_entry_t;
+
+/* The menu.  */
+struct grub_menu
+{
+  /* The size of a menu.  */
+  int size;
+
+  /* The list of menu entries.  */
+  grub_menu_entry_t entry_list;
+};
+typedef struct grub_menu *grub_menu_t;
+
+#endif /* GRUB_MENU_HEADER */

=== added file 'include/grub/menu_viewer.h'
--- include/grub/menu_viewer.h	1970-01-01 00:00:00 +0000
+++ include/grub/menu_viewer.h	2008-07-19 19:07:47 +0000
@@ -0,0 +1,48 @@
+/* menu_viewer.h - Interface to menu viewer implementations. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MENU_VIEWER_HEADER
+#define GRUB_MENU_VIEWER_HEADER 1
+
+#include <grub/err.h>
+#include <grub/symbol.h>
+#include <grub/types.h>
+#include <grub/menu.h>
+
+struct grub_menu_viewer
+{
+  /* The menu viewer name.  */
+  const char *name;
+
+  grub_err_t (*show_menu) (grub_menu_t menu, int nested);
+
+  struct grub_menu_viewer *next;
+};
+typedef struct grub_menu_viewer *grub_menu_viewer_t;
+
+void EXPORT_FUNC(grub_menu_viewer_init) (void);
+
+void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer);
+
+grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested);
+
+/* Return nonzero iff the menu viewer should clean up and return ASAP. */
+int EXPORT_FUNC(grub_menu_viewer_should_return) (void);
+
+#endif /* GRUB_MENU_VIEWER_HEADER */

=== modified file 'include/grub/normal.h'
--- include/grub/normal.h	2008-08-03 03:47:20 +0000
+++ include/grub/normal.h	2008-08-03 04:00:22 +0000
@@ -25,6 +25,7 @@
 #include <grub/err.h>
 #include <grub/arg.h>
 #include <grub/env.h>
+#include <grub/menu.h>
 
 /* The maximum size of a command-line.  */
 #define GRUB_MAX_CMDLINE	1600
@@ -84,34 +85,6 @@
 };
 typedef struct grub_command *grub_command_t;
 
-/* The menu entry.  */
-struct grub_menu_entry
-{
-  /* The title name.  */
-  const char *title;
-
-    /* The commands associated with this menu entry.  */
-  struct grub_script *commands;
-
-  /* The sourcecode of the menu entry, used by the editor.  */
-  const char *sourcecode;
-
-  /* The next element.  */
-  struct grub_menu_entry *next;
-};
-typedef struct grub_menu_entry *grub_menu_entry_t;
-
-/* The menu.  */
-struct grub_menu
-{
-  /* The size of a menu.  */
-  int size;
-
-  /* The list of menu entries.  */
-  grub_menu_entry_t entry_list;
-};
-typedef struct grub_menu *grub_menu_t;
-
 /* This is used to store the names of filesystem modules for auto-loading.  */
 struct grub_fs_module_list
 {
@@ -123,10 +96,27 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+  void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_failure) (void *userdata);
+} 
+*grub_menu_execute_callback_t;
+
 void grub_enter_normal_mode (const char *config);
 void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
+void grub_menu_execute_with_fallback (grub_menu_t menu,
+                                      grub_menu_entry_t entry,
+                                      grub_menu_execute_callback_t callback,
+                                      void *callback_data);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
 void grub_cmdline_run (int nested);
 int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
 		      int echo_char, int readline);

=== added file 'include/grub/stringutil.h'
--- include/grub/stringutil.h	1970-01-01 00:00:00 +0000
+++ include/grub/stringutil.h	2008-07-25 15:52:57 +0000
@@ -0,0 +1,32 @@
+/* gfxmenu_stringutil.h - String utilities for the graphical menu interface. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_STRINGUTIL_HEADER
+#define GRUB_GFXMENU_STRINGUTIL_HEADER 1
+
+#include <grub/types.h>
+
+char *grub_new_substring (const char *buf,
+                          grub_size_t start, grub_size_t end);
+
+char *grub_resolve_relative_path (const char *base, const char *path);
+
+char *grub_get_dirname (const char *file_path);
+
+#endif /* GRUB_GFXMENU_STRINGUTIL_HEADER */

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-07-03 14:12:08 +0000
+++ include/grub/video.h	2008-07-19 19:31:46 +0000
@@ -151,6 +151,16 @@
   grub_uint8_t a; /* Reserved bits value (0-255).  */
 };
 
+/* A 2D rectangle type.  */
+struct grub_video_rect
+{
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
 struct grub_video_adapter
 {
   /* The video adapter name.  */
@@ -307,4 +317,21 @@
 
 grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
 
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height);
+
+
 #endif /* ! GRUB_VIDEO_HEADER */

=== added file 'kern/menu_viewer.c'
--- kern/menu_viewer.c	1970-01-01 00:00:00 +0000
+++ kern/menu_viewer.c	2008-07-19 19:07:47 +0000
@@ -0,0 +1,99 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2002,2003,2005,2007  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/menu_viewer.h>
+#include <grub/menu.h>
+
+/* The list of menu viewers.  */
+static grub_menu_viewer_t menu_viewer_list;
+
+static int should_return;
+static int menu_viewer_changed;
+
+void
+grub_menu_viewer_register (grub_menu_viewer_t viewer)
+{
+  viewer->next = menu_viewer_list;
+  menu_viewer_list = viewer;
+}
+
+static grub_menu_viewer_t get_current_menu_viewer (void)
+{
+  const char *selected_name = grub_env_get ("menuviewer");
+
+  /* If none selected, pick the last registered one. */
+  if (selected_name == 0)
+    return menu_viewer_list;
+
+  grub_menu_viewer_t cur;
+  for (cur = menu_viewer_list; cur; cur = cur->next)
+    {
+      if (grub_strcmp (cur->name, selected_name) == 0)
+        return cur;
+    }
+
+  /* Fall back to the first entry (or null).  */
+  return menu_viewer_list;
+}
+
+grub_err_t
+grub_menu_viewer_show_menu (grub_menu_t menu, int nested)
+{
+  grub_err_t err;
+  int repeat = 0;
+  do
+    {
+      repeat = 0;
+      menu_viewer_changed = 0;
+      grub_menu_viewer_t cur = get_current_menu_viewer ();
+      if (!cur) 
+        return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available.");
+
+      should_return = 0;
+      err = cur->show_menu (menu, nested);
+      if (menu_viewer_changed)
+        repeat = 1;
+    }
+  while (repeat);
+  return err;
+}
+
+int
+grub_menu_viewer_should_return (void)
+{
+  return should_return;
+}
+
+static char *
+menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)),
+                       const char *val)
+{
+  menu_viewer_changed = 1;
+  should_return = 1;
+  return grub_strdup (val);
+}
+
+void
+grub_menu_viewer_init (void)
+{
+  grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook);
+}
+

=== modified file 'normal/main.c'
--- normal/main.c	2008-07-29 14:07:47 +0000
+++ normal/main.c	2008-08-03 04:00:22 +0000
@@ -28,6 +28,7 @@
 #include <grub/env.h>
 #include <grub/parser.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 grub_jmp_buf grub_exit_env;
 
@@ -147,14 +148,132 @@
   grub_env_unset_data_slot ("menu");
 }
 
+static void
+free_menu_entry_classes (struct grub_menu_entry_class *head)
+{
+  /* Free all the classes.  */
+  while (head)
+    {
+      struct grub_menu_entry_class *next;
+
+      grub_free (head->name);
+      next = head->next;
+      grub_free (head);
+      head = next;
+    }
+}
+
+/* The tag that can be added to a menu entry's title to specify a class
+   for the UI to use in selecting an icon or other visual attributes.  */
+static const char entry_class_attr_tag[] = "|class=";
+
+#define ENTRY_ATTR_SEPARATOR_CHAR ','
+
+/* Parse and strip a possible "class" attribute in the title.
+   This code is not designed to support other attributes than "class"
+   in the title since, we are planning to use a better method of
+   specifying this information in the future.  The parameter TITLE is
+   modified by storing a '\0' at the appropriate location to strip the
+   class information, if it exists.  The class list is stored into *HEAD.  */
+static struct grub_menu_entry_class *
+get_classes_from_entry_title (char *title)
+{
+  struct grub_menu_entry_class *head;
+  char *attr_start;
+
+  head = grub_malloc (sizeof (struct grub_menu_entry_class));
+  if (! head)
+    return 0;
+  head->name = 0;
+  head->next = 0;
+
+  attr_start = grub_strstr (title, entry_class_attr_tag);
+  if (attr_start)
+    {
+      struct grub_menu_entry_class *tail;
+      const char *p;
+
+      /* Trim the properties off of the title.  */
+      *attr_start = '\0';
+
+      /* Move the pointer to the beginning of the first class name.  */
+      attr_start += grub_strlen (entry_class_attr_tag);
+
+      tail = head;
+      p = attr_start;
+      while (p && *p)
+        {
+          const char *q;
+          const char *end;
+          const char *next_start;
+
+          /* Skip any leading whitespace.  */
+          while (*p && grub_isspace (*p))
+            p++;
+
+          /* Find the comma terminating this one ...  */
+          q = grub_strchr (p, ENTRY_ATTR_SEPARATOR_CHAR);
+          /* ... or if it's the last one, find the '\0' terminator.  */
+          if (q)
+            {
+              end = q - 1;
+              next_start = q + 1;
+            }
+          else
+            {
+              /* For the last class, extend it to the end.  */
+              end = p + grub_strlen (p);
+              next_start = 0;
+            }
+
+          /* Trim any trailing whitespace.  */
+          while (end > p && grub_isspace (*end))
+            end--;
+
+          grub_size_t len = end - p + 1;
+          /* Copy the class name into a new string.  */
+          char *class_name = grub_malloc (len + 1);
+          if (! class_name)
+            {
+              free_menu_entry_classes (head);
+              return 0;
+            }
+          grub_memcpy (class_name, p, len);
+          class_name[len] = '\0';
+
+          /* Create a new class and add it at the tail of the list.  */
+          struct grub_menu_entry_class *new_class;
+          new_class = grub_malloc (sizeof (struct grub_menu_entry_class));
+          if (! new_class)
+            {
+              grub_free (class_name);
+              free_menu_entry_classes (head);
+              return 0;
+            }
+          /* Fill in the new class node.  */
+          new_class->name = class_name;
+          new_class->next = 0;
+          /* Link the tail to it, and make it the new tail.  */
+          tail->next = new_class;
+          tail = new_class;
+
+          /* Advance the character pointer.  */
+          p = next_start;
+        }
+    }
+
+  return head;
+}
+
 grub_err_t
 grub_normal_menu_addentry (const char *title, struct grub_script *script,
 			   const char *sourcecode)
 {
-  const char *menutitle;
+  char *menutitle;
   const char *menusourcecode;
   grub_menu_t menu;
   grub_menu_entry_t *last;
+  struct grub_menu_entry_class *classes;
 
   menu = grub_env_get_data_slot("menu");
   if (! menu)
@@ -173,6 +292,14 @@
       return grub_errno;
     }
 
+  classes = get_classes_from_entry_title (menutitle);
+  if (! classes)
+    {
+      grub_free ((void *) menutitle);
+      grub_free ((void *) menusourcecode);
+      return grub_errno;
+    }
+
   /* Add the menu entry at the end of the list.  */
   while (*last)
     last = &(*last)->next;
@@ -180,6 +307,7 @@
   *last = grub_malloc (sizeof (**last));
   if (! *last)
     {
+      free_menu_entry_classes (classes);
       grub_free ((void *) menutitle);
       grub_free ((void *) menusourcecode);
       return grub_errno;
@@ -187,6 +315,7 @@
 
   (*last)->commands = script;
   (*last)->title = menutitle;
+  (*last)->classes = classes;
   (*last)->next = 0;
   (*last)->sourcecode = menusourcecode;
 
@@ -476,7 +605,7 @@
 
   if (menu && menu->size)
     {
-      grub_menu_run (menu, nested);
+      grub_menu_viewer_show_menu (menu, nested);
       if (nested)
 	free_menu (menu);
     }
@@ -519,6 +648,8 @@
   if (mod)
     grub_dl_ref (mod);
 
+  grub_menu_viewer_register (&grub_normal_terminal_menu_viewer);
+
   grub_set_history (GRUB_DEFAULT_HISTORY_SIZE);
 
   /* Register a command "normal" for the rescue mode.  */
@@ -535,6 +666,8 @@
 
   /* This registers some built-in commands.  */
   grub_command_init ();
+
+  grub_menu_viewer_init ();
 }
 
 GRUB_MOD_FINI(normal)

=== modified file 'normal/menu.c'
--- normal/menu.c	2008-02-09 11:00:19 +0000
+++ normal/menu.c	2008-07-26 23:37:04 +0000
@@ -21,9 +21,11 @@
 #include <grub/misc.h>
 #include <grub/loader.h>
 #include <grub/mm.h>
+#include <grub/time.h>
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -241,8 +243,8 @@
 
 /* Return the current timeout. If the variable "timeout" is not set or
    invalid, return -1.  */
-static int
-get_timeout (void)
+int
+grub_menu_get_timeout (void)
 {
   char *val;
   int timeout;
@@ -269,8 +271,8 @@
 }
 
 /* Set current timeout in the variable "timeout".  */
-static void
-set_timeout (int timeout)
+void
+grub_menu_set_timeout (int timeout)
 {
   /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
   if (timeout > 0)
@@ -308,6 +310,57 @@
   return entry;
 }
 
+/* Get the default menu entry index.  */
+int
+grub_menu_get_default_entry_index (grub_menu_t menu)
+{
+  int i = get_entry_number ("default");
+
+  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
+     the first entry.  */
+  if (i < 0 || i >= menu->size)
+    i = 0;
+
+  return i;
+}
+
+/* Get the first entry number from the variable NAME, which is a 
+   space-separated list of nonnegative integers.  The entry number which
+   is returned is stripped from the value of NAME.  */
+static int
+get_and_remove_first_entry_number (const char *name)
+{
+  char *val;
+  char *tail;
+  int entry;
+  
+  val = grub_env_get (name);
+  if (! val)
+    return -1;
+  
+  grub_error_push ();
+  
+  entry = (int) grub_strtoul (val, &tail, 0);
+
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      /* Skip whitespace to find the next digit.  */
+      while (*tail && grub_isspace (*tail))
+	tail++;
+      grub_env_set (name, tail);
+    }
+  else
+    {
+      grub_env_unset (name);
+      grub_errno = GRUB_ERR_NONE;
+      entry = -1;
+    }
+
+  grub_error_pop ();
+  
+  return entry;
+}
+
 static void
 print_timeout (int timeout, int offset, int second_stage)
 {
@@ -332,15 +385,10 @@
   
   first = 0;
   
-  default_entry = get_entry_number ("default");
-
-  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
-     the first entry.  */
-  if (default_entry < 0 || default_entry >= menu->size)
-    default_entry = 0;
+  default_entry = grub_menu_get_default_entry_index (menu);
 
   /* If timeout is 0, drawing is pointless (and ugly).  */
-  if (get_timeout () == 0)
+  if (grub_menu_get_timeout () == 0)
     return default_entry;
 
   offset = default_entry;
@@ -359,15 +407,15 @@
   print_entries (menu, first, offset);
   grub_refresh ();
 
-  timeout = get_timeout ();
+  timeout = grub_menu_get_timeout ();
 
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
-      timeout = get_timeout ();
+      timeout = grub_menu_get_timeout ();
       
       if (timeout > 0)
 	{
@@ -377,7 +425,7 @@
 	  if (current_time - saved_time >= GRUB_TICKS_PER_SECOND)
 	    {
 	      timeout--;
-	      set_timeout (timeout);
+	      grub_menu_set_timeout (timeout);
 	      saved_time = current_time;
 	      print_timeout (timeout, offset, 1);
 	    }
@@ -468,6 +516,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +528,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,15 +544,80 @@
     grub_command_execute ("boot", 0);
 }
 
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+   in the environment variable "fallback" if it fails.  CALLBACK is a 
+   pointer to a struct of function pointers which are used to allow the
+   caller provide feedback to the user.  */
 void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+				 grub_menu_entry_t entry,
+				 grub_menu_execute_callback_t callback,
+				 void *callback_data)
+{
+  int fallback_entry;
+
+  callback->notify_booting (callback_data, entry);
+
+  grub_menu_execute_entry (entry);
+
+  /* Deal with fallback entries.  */
+  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+	 >= 0)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      entry = get_entry (menu, fallback_entry);
+      callback->notify_fallback (callback_data, entry);
+      grub_menu_execute_entry (entry);
+    }
+
+  if (grub_errno != GRUB_ERR_NONE)
+    callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+		grub_menu_entry_t entry)
+{
+  grub_printf ("  Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+		 grub_menu_entry_t entry)
+{
+  grub_printf ("\n  Falling back to \'%s\'\n\n", entry->title);
+  grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      grub_wait_after_message ();
+    }
+}
+
+static struct grub_menu_execute_callback execution_callback = 
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
       int boot_entry;
       grub_menu_entry_t e;
-      int fallback_entry;
-      
+
       boot_entry = run_menu (menu, nested);
       if (boot_entry < 0)
 	break;
@@ -507,34 +625,18 @@
       e = get_entry (menu, boot_entry);
       if (! e)
 	continue; /* Menu is empty.  */
-	
+
       grub_cls ();
       grub_setcursor (1);
 
-      grub_printf ("  Booting \'%s\'\n\n", e->title);
-  
-      run_menu_entry (e);
-
-      /* Deal with a fallback entry.  */
-      /* FIXME: Multiple fallback entries like GRUB Legacy.  */
-      fallback_entry = get_entry_number ("fallback");
-      if (fallback_entry >= 0)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-	  
-	  e = get_entry (menu, fallback_entry);
-	  grub_env_unset ("fallback");
-	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
-	}
-
-      if (grub_errno != GRUB_ERR_NONE)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-
-	  grub_wait_after_message ();
-	}
+      grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-07-03 14:28:01 +0000
+++ term/gfxterm.c	2008-07-19 19:56:06 +0000
@@ -28,12 +28,12 @@
 #include <grub/mm.h>
 #include <grub/env.h>
 #include <grub/video.h>
+#include <grub/gfxterm.h>
 #include <grub/bitmap.h>
 #include <grub/bitmap_scale.h>
 
 #define DEFAULT_VIDEO_WIDTH	640
 #define DEFAULT_VIDEO_HEIGHT	480
-#define DEFAULT_VIDEO_FLAGS	0
 
 #define DEFAULT_BORDER_WIDTH	10
 
@@ -108,10 +108,20 @@
   struct grub_colored_char *text_buffer;
 };
 
+static int refcount;
+static struct grub_video_render_target *render_target;
+static grub_video_rect_t window;
 static struct grub_virtual_screen virtual_screen;
+static grub_gfxterm_repaint_callback_t repaint_callback;
+
+static grub_err_t init_window (struct grub_video_render_target *target,
+                               int x, int y, int width, int height, 
+                               const char *font_name, int border_width);
+
+static void destroy_window (void);
+
 
 static grub_dl_t my_mod;
-static struct grub_video_mode_info mode_info;
 
 static struct grub_video_render_target *text_layer;
 
@@ -235,287 +245,129 @@
 }
 
 static grub_err_t
+init_window (struct grub_video_render_target *target,
+             int x, int y, int width, int height, 
+             const char *font_name, int border_width)
+{
+  /* Clean up any prior instance.  */
+  destroy_window ();
+
+  /* Create virtual screen.  */
+  if (grub_virtual_screen_setup (border_width, border_width, 
+                                 width - 2 * border_width, 
+                                 height - 2 * border_width, 
+                                 font_name) 
+      != GRUB_ERR_NONE)
+    {
+      return grub_errno;
+    }
+
+  /* Set the render target.  */
+  render_target = target;
+
+  /* Set window bounds.  */
+  window.x = x;
+  window.y = y;
+  window.width = width;
+  window.height = height;
+
+  /* Mark whole window as dirty.  */
+  dirty_region_reset ();
+  dirty_region_add (0, 0, width, height);
+
+  return (grub_errno = GRUB_ERR_NONE);
+}
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+                          int x, int y, int width, int height, 
+                          const char *font_name, int border_width)
+{
+  grub_errno = GRUB_ERR_NONE;
+  if (refcount++ == 0)
+    init_window (target, x, y, width, height, font_name, border_width);
+  return grub_errno;
+}
+
+static grub_err_t
 grub_gfxterm_init (void)
 {
-  char *font_name;
-  char *modevar;
-  int width = DEFAULT_VIDEO_WIDTH;
-  int height = DEFAULT_VIDEO_HEIGHT;
-  int depth = -1;
-  int flags = DEFAULT_VIDEO_FLAGS;
-  grub_video_color_t color;
-
-  /* Select the font to use. */
-  font_name = grub_env_get ("gfxterm_font");
-  if (!font_name)
-    font_name = "";   /* Allow fallback to any font. */
+  /* If gfxterm has already been initialized by calling the init_window
+     function, then leave it alone when it is set as the current terminal. */
+  if (refcount++ != 0)
+    return GRUB_ERR_NONE;
 
   /* Parse gfxmode environment variable if set.  */
-  modevar = grub_env_get ("gfxmode");
-  if (modevar)
-    {
-      char *tmp;
-      char *next_mode;
-      char *current_mode;
-      char *param;
-      char *value;
-      int mode_found = 0;
-
-      /* Take copy of env.var. as we don't want to modify that.  */
-      tmp = grub_strdup (modevar);
-      modevar = tmp;
-
-      if (grub_errno != GRUB_ERR_NONE)
-        return grub_errno;
-        
-      /* Initialize next mode.  */
-      next_mode = modevar;
-      
-      /* Loop until all modes has been tested out.  */
-      while (next_mode != NULL)
-        {
-          /* Use last next_mode as current mode.  */
-          tmp = next_mode;
-          
-          /* Reset video mode settings.  */
-          width = DEFAULT_VIDEO_WIDTH;
-          height = DEFAULT_VIDEO_HEIGHT;
-          depth = -1;
-          flags = DEFAULT_VIDEO_FLAGS;
-        
-          /* Save position of next mode and separate modes.  */
-          next_mode = grub_strchr(next_mode, ';');
-          if (next_mode)
-            {
-              *next_mode = 0;
-              next_mode++;
-            }
-
-          /* Skip whitespace.  */
-          while (grub_isspace (*tmp))
-            tmp++;
-
-          /* Initialize token holders.  */
-          current_mode = tmp;
-          param = tmp;
-          value = NULL;
-
-          /* Parse <width>x<height>[x<depth>]*/
-
-          /* Find width value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          *param = 0;
-          param++;
-
-          width = grub_strtoul (value, 0, 0);
-          if (grub_errno != GRUB_ERR_NONE)
-            {
-              grub_err_t rc;
-              
-              /* First setup error message.  */
-              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                               "Invalid mode: %s\n",
-                               current_mode);
-              
-              /* Free memory before returning.  */
-              grub_free (modevar);
-              
-              return rc;
-            }
-
-          /* Find height value.  */
-          value = param;
-          param = grub_strchr(param, 'x');
-          if (param == NULL)
-            {
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          else
-            {
-              /* We have optional color depth value.  */
-              *param = 0;
-              param++;
-
-              height = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-
-              /* Convert color depth value.  */
-              value = param;
-              depth = grub_strtoul (value, 0, 0);
-              if (grub_errno != GRUB_ERR_NONE)
-                {
-                  grub_err_t rc;
-                  
-                  /* First setup error message.  */
-                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
-                                   "Invalid mode: %s\n",
-                                   current_mode);
-                                   
-                  /* Free memory before returning.  */
-                  grub_free (modevar);
-                  
-                  return rc;
-                }
-            }
-          
-          /* Try out video mode.  */
-          
-          /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-          if ((depth <= 8) && (depth != -1))
-            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-          /* We have more than 8 bits, then assume that it is RGB color mode.  */
-          if (depth > 8)
-            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-          /* If user requested specific depth, forward that information to driver.  */
-          if (depth != -1)
-            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                     & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-          /* Try to initialize requested mode.  Ignore any errors.  */
-          grub_error_push ();
-          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-            {
-              grub_error_pop ();
-              continue;
-            }
-            
-          /* Figure out what mode we ended up.  */
-          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-            {
-              /* Couldn't get video mode info, restore old mode and continue to next one.  */
-              grub_error_pop ();
-              
-              grub_video_restore ();
-              continue;
-            }
-          
-          /* Restore state of error stack.  */
-          grub_error_pop ();
-          
-          /* Mode found!  Exit loop.  */
-          mode_found = 1;
-          break;
-        }
-
-      /* Free memory.  */
-      grub_free (modevar);
-      
-      if (!mode_found)
-        return grub_error (GRUB_ERR_BAD_ARGUMENT,
-                           "No suitable mode found.");
-    }
-  else
-    {
-      /* No gfxmode variable set, use defaults.  */
-      
-      /* If we have 8 or less bits, then assume that it is indexed color mode.  */
-      if ((depth <= 8) && (depth != -1))
-        flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
-      /* We have more than 8 bits, then assume that it is RGB color mode.  */
-      if (depth > 8)
-        flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
-      /* If user requested specific depth, forward that information to driver.  */
-      if (depth != -1)
-        flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
-                 & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
-      /* Initialize user requested mode.  */
-      if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
-        return grub_errno;
-
-      /* Figure out what mode we ended up.  */
-      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
-        {
-          grub_video_restore ();
-          return grub_errno;
-        }
+  const char *modevar = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modevar, 0, 
+                                       DEFAULT_VIDEO_WIDTH, 
+                                       DEFAULT_VIDEO_HEIGHT) 
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  /* Figure out what mode we ended up.  */
+  struct grub_video_mode_info mode_info;
+  if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+    {
+      grub_video_restore ();
+      return grub_errno;
     }
 
   /* Make sure screen is black.  */
-  color = grub_video_map_rgb (0, 0, 0);
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (grub_video_map_rgb (0, 0, 0), 
+                        0, 0, mode_info.width, mode_info.height);
   bitmap = 0;
 
+  /* Select the font to use.  */
+  char *font_name = grub_env_get ("gfxterm_font");
+  if (!font_name)
+    font_name = "";   /* Allow fallback to any font.  */
+
   /* Leave borders for virtual screen.  */
-  width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH);
-  height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH);
-
-  /* Create virtual screen.  */
-  if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
-                                 width, height, font_name) != GRUB_ERR_NONE)
+  if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+                   0, 0, mode_info.width, mode_info.height,
+                   font_name, 
+                   DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE)
     {
       grub_video_restore ();
       return grub_errno;
     }
 
-  /* Mark whole screen as dirty.  */
-  dirty_region_reset ();
-  dirty_region_add (0, 0, mode_info.width, mode_info.height);
-
   return (grub_errno = GRUB_ERR_NONE);
 }
 
+static void
+destroy_window (void)
+{
+  if (bitmap)
+    {
+      grub_video_bitmap_destroy (bitmap);
+      bitmap = 0;
+    }
+
+  repaint_callback = 0;
+  grub_virtual_screen_free ();
+}
+
+void
+grub_gfxterm_destroy_window (void)
+{
+  if (--refcount == 0)
+    destroy_window ();
+}
+
 static grub_err_t
 grub_gfxterm_fini (void)
 {
-  if (bitmap)
+  /* Don't destroy an explicitly initialized terminal instance when it is
+     unset as the current terminal.  */
+  if (--refcount == 0)
     {
-      grub_video_bitmap_destroy (bitmap);
-      bitmap = 0;
+      destroy_window ();
+      grub_video_restore ();
     }
 
-  grub_virtual_screen_free ();
-
-  grub_video_restore ();
-
-  return GRUB_ERR_NONE;
+  return (grub_errno = GRUB_ERR_NONE);
 }
 
 static void
@@ -523,9 +375,15 @@
                     unsigned int width, unsigned int height)
 {
   grub_video_color_t color;
-
-  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
-
+  grub_video_rect_t saved_view;
+
+  grub_video_set_active_render_target (render_target);
+  /* Save viewport and set it to our window. */
+  grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                           (unsigned *) &saved_view.y, 
+                           (unsigned *) &saved_view.width, 
+                           (unsigned *) &saved_view.height);
+  grub_video_set_viewport (window.x, window.y, window.width, window.height);
 
   if (bitmap)
     {
@@ -592,6 +450,14 @@
                                      y - virtual_screen.offset_y,
                                      width, height);      
     }
+
+  /* Restore saved viewport. */
+  grub_video_set_viewport (saved_view.x, saved_view.y,
+                           saved_view.width, saved_view.height);
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  
+  if (repaint_callback)
+    repaint_callback (x, y, width, height);
 }
 
 static void
@@ -786,7 +652,16 @@
       dirty_region_add_virtualscreen ();
     }
   else
-    {      
+    {
+      grub_video_rect_t saved_view;
+      grub_video_set_active_render_target (render_target);
+      /* Save viewport and set it to our window.  */
+      grub_video_get_viewport ((unsigned *) &saved_view.x, 
+                               (unsigned *) &saved_view.y, 
+                               (unsigned *) &saved_view.width, 
+                               (unsigned *) &saved_view.height);
+      grub_video_set_viewport (window.x, window.y, window.width, window.height);
+
       /* Clear new border area.  */
       grub_video_fill_rect (color, 
                             virtual_screen.offset_x, virtual_screen.offset_y, 
@@ -795,10 +670,18 @@
       /* Scroll physical screen.  */
       grub_video_scroll (color, 0, -virtual_screen.char_height);      
 
+      /* Restore saved viewport. */
+      grub_video_set_viewport (saved_view.x, saved_view.y,
+                               saved_view.width, saved_view.height);
+      grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
       /* Draw cursor if visible.  */
       if (virtual_screen.cursor_state)
         write_cursor ();
     }
+
+  if (repaint_callback)
+    repaint_callback (window.x, window.y, window.width, window.height);
 }
 
 static void
@@ -898,7 +781,7 @@
 }
 
 static grub_ssize_t
-grub_gfxterm_getcharwidth (grub_uint32_t c)
+grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused)))
 {
 #if 0
   struct grub_font_glyph *glyph;
@@ -970,7 +853,8 @@
   /* Clear text layer.  */
   grub_video_set_active_render_target (text_layer);
   color = virtual_screen.bg_color;
-  grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+  grub_video_fill_rect (color, 0, 0,
+                        virtual_screen.width, virtual_screen.height);
   grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
 
   /* Mark virtual screen to be redrawn.  */
@@ -1039,6 +923,11 @@
   dirty_region_redraw ();
 }
 
+void 
+grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func)
+{
+  repaint_callback = func;
+}
 
 /* Option array indices. */
 #define BACKGROUND_CMD_ARGINDEX_MODE 0
@@ -1066,7 +955,7 @@
       
       /* Mark whole screen as dirty.  */
       dirty_region_reset ();
-      dirty_region_add (0, 0, mode_info.width, mode_info.height);
+      dirty_region_add (0, 0, window.width, window.height);
     }
 
   /* If filename was provided, try to load that.  */
@@ -1082,13 +971,13 @@
         || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
                         "stretch") == 0)
         {
-          if (mode_info.width != grub_video_bitmap_get_width (bitmap)
-              || mode_info.height != grub_video_bitmap_get_height (bitmap)) 
+          if (window.width != (int) grub_video_bitmap_get_width (bitmap)
+              || window.height != (int) grub_video_bitmap_get_height (bitmap)) 
             {
               struct grub_video_bitmap *scaled_bitmap;
               grub_video_bitmap_create_scaled (&scaled_bitmap,
-                                              mode_info.width, 
-                                              mode_info.height,
+                                              window.width, 
+                                              window.height,
                                               bitmap,
                                               GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
               if (grub_errno == GRUB_ERR_NONE)
@@ -1110,7 +999,7 @@
         
         /* Mark whole screen as dirty.  */
         dirty_region_reset ();
-        dirty_region_add (0, 0, mode_info.width, mode_info.height);
+        dirty_region_add (0, 0, window.width, window.height);
       }
     }
   
@@ -1141,9 +1030,16 @@
     .next = 0
   };
 
+grub_term_t
+grub_gfxterm_get_term (void)
+{
+  return &grub_video_term;
+}
+
 GRUB_MOD_INIT(term_gfxterm)
 {
   my_mod = mod;
+  refcount = 0;
   grub_term_register (&grub_video_term);
 
   grub_register_command ("background_image",

=== modified file 'video/bitmap_scale.c'
--- video/bitmap_scale.c	2008-07-03 14:27:43 +0000
+++ video/bitmap_scale.c	2008-08-03 01:30:02 +0000
@@ -43,6 +43,7 @@
                                  enum grub_video_bitmap_scale_method
                                  scale_method)
 {
+  *dst = 0;
 
   /* Verify the simplifying assumptions. */
   if (src == 0)
@@ -94,6 +95,7 @@
     {
       /* Destroy the bitmap and return the error code. */
       grub_video_bitmap_destroy (*dst);
+      *dst = 0;
       return ret;
     }
 }

=== modified file 'video/i386/pc/vbe.c'
--- video/i386/pc/vbe.c	2008-07-09 20:59:34 +0000
+++ video/i386/pc/vbe.c	2008-07-19 19:42:37 +0000
@@ -652,6 +652,8 @@
   if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE)
     return 1;   /* Unable to set the display start.  */
 
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
   return 0;
 }
 
@@ -690,6 +692,8 @@
   doublebuf_state.update_screen = doublebuf_blit_update_screen;
   doublebuf_state.destroy = doublebuf_blit_destroy;
 
+  framebuffer.render_target.mode_info.mode_type
+    |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
   return 0;
 }
 
@@ -713,6 +717,9 @@
   framebuffer.render_target.data = framebuffer.ptr;
   doublebuf_state.update_screen = doublebuf_null_update_screen;
   doublebuf_state.destroy = doublebuf_null_destroy;
+
+  framebuffer.render_target.mode_info.mode_type
+    &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
   return 0;
 }
 

=== added file 'video/setmode.c'
--- video/setmode.c	1970-01-01 00:00:00 +0000
+++ video/setmode.c	2008-07-19 19:31:46 +0000
@@ -0,0 +1,249 @@
+/* video/setmode.c - Smart video mode selection based on preferences. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2006,2007,2008  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/video.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   For example:  640x480;800x600x8;400x300x32
+
+   If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+   DEFAULT_HEIGHT are used to set the mode.  The MODE_FLAGS argument determines
+   the video mode flags such as double buffering that are used.  */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+                                 int default_width, int default_height)
+{
+  int mode_found = 0;
+
+  if (mode_list != NULL)
+    {
+      /* Take copy of mode_list as we don't want tat.  */
+      char *const modes_copy = grub_strdup (mode_list);
+      if (modes_copy == NULL)
+        return grub_errno;
+
+      /* Initialize next mode.  */
+      char *next_mode = modes_copy;
+
+      /* Loop until all modes has been tested out.  */
+      while ((next_mode != NULL) && !mode_found)
+        {
+          /* Use last next_mode as current mode.  */
+          char *tmp = next_mode;
+
+          int width = -1;
+          int height = -1;
+          int depth = -1;
+
+          /* Save position of next mode and separate modes.  */
+          next_mode = grub_strchr(next_mode, ';');
+          if (next_mode)
+            {
+              *next_mode = 0;
+              next_mode++;
+            }
+
+          /* Skip whitespace.  */
+          while (grub_isspace (*tmp))
+            tmp++;
+
+          /* Initialize token holders.  */
+          char *current_mode = tmp;
+          char *param = tmp;
+          char *value = NULL;
+
+          /* Parse <width>x<height>[x<depth>]*/
+
+          /* Find width value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          *param = 0;
+          param++;
+
+          width = grub_strtoul (value, 0, 0);
+          if (grub_errno != GRUB_ERR_NONE)
+            {
+              grub_err_t rc;
+
+              /* First setup error message.  */
+              rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                               "Invalid mode: %s\n",
+                               current_mode);
+
+              /* Free memory before returning.  */
+              grub_free (modes_copy);
+
+              return rc;
+            }
+
+          /* Find height value.  */
+          value = param;
+          param = grub_strchr(param, 'x');
+          if (param == NULL)
+            {
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+          else
+            {
+              /* We have optional color depth value.  */
+              *param = 0;
+              param++;
+
+              height = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+
+              /* Convert color depth value.  */
+              value = param;
+              depth = grub_strtoul (value, 0, 0);
+              if (grub_errno != GRUB_ERR_NONE)
+                {
+                  grub_err_t rc;
+
+                  /* First setup error message.  */
+                  rc = grub_error (GRUB_ERR_BAD_ARGUMENT, 
+                                   "Invalid mode: %s\n",
+                                   current_mode);
+
+                  /* Free memory before returning.  */
+                  grub_free (modes_copy);
+
+                  return rc;
+                }
+            }
+
+          /* Try out video mode.  */
+
+          int flags = mode_flags;
+          /* If we have <= 8 bits, assume it is an indexed color mode.  */
+          if ((depth <= 8) && (depth != -1))
+            flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+
+          /* We have > 8 bits; assume that it is RGB color mode.  */
+          if (depth > 8)
+            flags |= GRUB_VIDEO_MODE_TYPE_RGB;
+
+          /* If user requested specific depth, pass the request to driver.  */
+          if (depth != -1)
+            flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+              & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
+
+          /* Try to initialize requested mode.  Ignore any errors.  */
+          grub_error_push ();
+          if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
+            {
+              grub_error_pop ();
+              continue;
+            }
+
+          /* Figure out what mode we ended up.  */
+          struct grub_video_mode_info mode_info;
+          if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+            {
+              /* Couldn't get video mode info, restore old mode
+                 and continue to next one.  */
+              grub_error_pop ();
+
+              grub_video_restore ();
+              continue;
+            }
+
+          /* Restore state of error stack.  */
+          grub_error_pop ();
+
+          /* Mode found!  Exit loop.  */
+          mode_found = 1;
+        }
+
+      /* Free memory.  */
+      grub_free (modes_copy);
+    }
+
+  if (!mode_found)
+    {
+      /* No gfxmode variable set, or no listed mode was supported.
+         Use the caller-specified defaults.  */
+      int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB;
+
+      /* Initialize user requested mode.  */
+      if (grub_video_setup (default_width, default_height, flags) 
+          != GRUB_ERR_NONE)
+        return grub_errno;
+
+      /* Figure out what mode we ended up.  */
+      struct grub_video_mode_info mode_info;
+      if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+        grub_video_restore ();
+      else
+        mode_found = 1;
+    }
+
+  if (!mode_found)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       "No suitable mode found.");
+
+  return (grub_errno = GRUB_ERR_NONE);
+}

# Begin bundle
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWXDYNfUAsRD/gH///9//////
/////v////9gyT7wOLwr1du9KNAeSexyrWkvL23jYZbPQHu93d0SqoZNzgfXpXTzk92HAXp2d9h6
9D0AFAO9nvpn2+9OuS9AN2Ou+zTt5Vcqknu2ve3pdbW3HZDoxA1yGOzPe89IeriGgbAdAKAXOTgK
PoZduQVXeI+t8l5KLsWXcBdrvlB1oDQHPh8zvqlXMOuSldA11889vZ5IAAUDbxZ2UVClBW4e3PVC
lRa0N70eeiSShA9z67vaSUrtoFhmXWq+mSh28+vdVBKoPW4zRCiAcMe+xrX15ARySQlAH1dsQBXW
qCoQN2XG4fNlO2asw2Yb20u07gDUiOqG2DdiZQZtTDoDdAZmG7TIBPQwUveUGqOsbsNIu+23R3nz
vfeAVVvh3Vt5uu2D0JszY92SdLNE9wCgZ0DTS2GAAoiCAFEQC9ZIUSFAII2d1KKyoEECQgSppmzA
itqyWxoFAL67tcg6rsQNtY0EgCjn3a+72g4vsxoNRRPZ60d73GjdbuNFUUIlAoA+7FA6BHTEISIg
L27s9UASggE0AmhNAmQTENTT0RqZJ6mJqNlDTE3ko0A9TIAG1NlDQ0GmgBIiEJkmk9T9TCSHqeRq
ABoB6gBoAAAAAAA0GmEgkmik8p5I/TSnqbSB6hp6g0GTRoAAAAANAADRoDQSaSIRBBMSMmmKabSY
1NomE01T9ooam9RMwkNtUenpTTT01HqGajygaZqBEkQmgIAACAAJoA0CBqMT0mjFPTTTRMpsmmmp
pmSaMAKkiABNCIQmSNomptKPap6hp6Ro0yaAA0B6g0yDQAAAaM3tA/tPdQbJGAKi3QpYEYEWUn6v
3XUmrQPogfk+n6e1X79/oh+Wn/DXYD+/kVn8C0KKvB/DmIiDOKWIjprDbg4gmMsWLJUhUBRRGsKx
jLErAP+87GZNMKyIn/JJUzyoYyfnZUgfoaxQUWKcsMYTjUqoixZjJiAeOin6EKhp4tAxRFCBZPAR
EF6PI/n/j938PG2T+uU4Q2szcZvHl/5DfoKk89HmJdf0fo85cktiiViRC1Mlu/dar2VPZKqVE9bf
Zx3DDgYx1UdWF7xyslqfIpsl5jIDzKuGFQarHOS9WVdR5yQ8UJ5eYHmh/We/dO2tIKRS3eAXipWq
RV2wgYURdBq2gJZQtWmEQ+86bGA2gD3YhEplRgr9RR0VB38wkkyBZYsy1bGUIMO+yxffIjSN5qBi
JZC4LkTVs0ahgESIfFumRCF5M6JbHqf1g6DSMoWWW5ZQ6ZjPLvcTRfHjebMtinHFg6O6XDbPovli
hbUemFggWVlBlEcz/zykMDXYxqKoaKKG0Bh6tJFVZ8+3XoZCBJVoL/r6f+39h5un/wWgtm9NZXiy
Tzl43kP+QyjoL+hPWJuWTvw2f9WQya5f3HgRhDwH/1lTIoe8jDX7Kw/+TW660hGTch4qtBnVf9g2
eA7F1NBa+oKPYlwu8Or6grIVSODQyb99jDz6kwUgh2d6j2eEPwE014M68W+CIe4cOIQGFE+1XEue
/nWrvfF3vMqaObj13DuxIxEJ4RKnfFNUCxGQkkmO1ylrrJAdcOzR4g8R4mElYSHdBjU8uqQmlYQO
UPT6asAzKQOXE+w3KHqw8fWedJWVIKLKMqDGMjIAeI8hUw/8ey5hD9v/Y8Hn2eD/xv99eqeXHVxF
J73v69PxnZ3rWRQX6mGYILdINfq9r19XjjG1D6h8L9d42UxrvuzOr0giqCad62bmauMtUbD6xuzN
2UOEVunHjjWs2UrvSqool9kEIG0NsYaOEMsoGBNhN05zdgdF04qqozbpxkFFUtFLmppXTqGhp2yF
8Z6PhbiBUmCQrD7qQmMOZPMSRGKfL9fnx1FfF4TBxqq1Ixi3wFYYuizI4ttQTw1mevi8M1XhsZMZ
WZl8naYILCy7cqWlIl1nqyaMWIicUzJicduTgvjDj47JWeYwmyeIkXg80+vvx8dHvp5aRR6m7WVE
sq5WywUHFHFd5E0krBEHsv3heL8WErUFzwRkwSCAyRSH7yFFAqogyA13cU38M+/vlLVRhRAsCUVB
YxkBiQ7xPV7qAVigahp/4/H8hmyLAVQVBgIicWf7BMQQRBRUqoNwwMRY89dInd3YEeYKhEWEODej
4/A5w+q23X66Vukhis1h2Tbt3swVE2RGVu7m6QWLDfgojkUQXKj9qYPz1RhFkUftN/a5YDojjnKc
MSKdrwZTpORQHh146Hq3LEuimBmY3pw8EfDeud8t7Oad5mVWG2cJ8EVJ3Ypgxtck9mQOgQTm1+HL
gqDyWdGeFzONNNEohIo6KJo7QMjWENdEYBHC0VeJ02AzcYJxKTCB4KoSXAoY8puUaCFlGAiS2ysF
MEMjJ07ZgkK64xpqIQsloJPDYbpcO2ALo0SAjQ54CmEzDaHSTM5uAcFKSnW81NUCls5554d8WRrw
xMBzTTKK9U5cLqzUCEGRhGEZEAhoivD48a42bNLqzjKQUd2mmwvarJpZpkBtCYLaSpoXpRkQHJcG
BnENjCQhu0Dgjvx3mheLMW8azwwxdm8KcsGvA7nO9oYoeLK9WqPIpS7pkQwa8xQBUSaAga1ELiIN
MIu5IWxSJEBVEMINBU7MF8XZAHXeE8gjUXM4dKgyEQ8UK10xGJ2svN3bHKPfwc6SV+ECfbiBxoEj
lANgvmETnFgtC8QvSC5K6+n45//b+ubvnXexxyq2K8EXIN8AogckNJCSVqVKMAxltDMmYiMRQdB7
vSgOPzUkqA/yzkPXvZCcm4I980O6kkMFSqqqTmw5X8urHVnhnhfHScaDuFVz0IBCFESVSGhlUwoV
lUalGApCCSSTwIQYgCRrz+GnOQvHXvYvqemXXZSr4oiJ1lqZU3kZYvRcKXJMZAXSQTMwNNCJzDRY
hXXK6QDiQJ5tVqiFl4421x7P38goN1QNfpAAO3EQy7bCi4R0w2xKjo20jUdswhIg7Y7WQxnkjbq2
QOUrJiEx7PmMDTDvqwDjtZOubOUmJ3QOWHdkqYxQnZDpmntzmV3d4ErIaQBZtnCHLAqTu4wOuaQO
Q7UrvKs5YIhCoFtxowA5CAQQIKByAA1DApGioepO1ri3V5s1M6yTGboUQhJGFFNxunfq9koJ6gog
CID8wkS9u9nbyEEUjJFiyeRaIMRhCgQIzLInd49QQ9fDnxa8DTrtA1LnRhqJaXIxZwygsUXw6tJH
gzBMBgbhQwvaX3SwgGSPLn1vi+7g90SRKEnkER7quAsUYXFNzTTNyGiFcal4twzDMR03kJbmsZeR
N6yZFnIOTps0qXana4yy6xVRiPcmXZleiz54qQRqwaXNJTIklhOJRWgVWqZigFgUolaXcsWgKrmh
AgljtatrqvCnXlxutc4Lbas5atGFmrHlYfJlYaHcq0Comdmgyi2FXlMMlKSrVJtapcwtT6MNhqcz
TtCvb5sVOtchqZozRKvM3cWy6quLmS+Iq7RXWwvlCFgzLW77rteNrNbC2uYc4uPlnN4E513CDyKV
FIS14+ABkiqoiKCi2WjenbV0w+eJgodvlKz98YCihwAbmSCwISzbRla1FpJvamce10XTj4acb8OK
VDtveFYZmDmh43TjinFkoPDDxZwnPKwQkEMRRBBFED3lihGqFHYzzvbhW9qoc4cKkL6ZuJYorJFm
EIaT7vf3HzMFYT3/h+Pw4pJVRKr0wU9sVfbHGIPuhs2oHbHjhKoCQQkIKKfXtgxIsFDEWfA/P6i5
9yk6AgqHp0nBw0u3v5CxpnE6KOMwv7b4CsiOEQh4z2GwPBHiiLsLUyAuqIIqHWb8aAAEYL86QVVI
QUJFUALRAAkJJCYJCbayDBQIoijAwglkkHslREkPoG5+2SZRMSUPMQqQg8v7aFXAuh7haUqCSAZw
CoUQaIQWMZRkiwUhWJ2Ny5Oc7AGjfV96HjA8kSYgL/WLlgaBC4s82qi8BJCQQR0SYJJShPS4SOJA
FJFkJBggNoISIqtMEQJYWlqCEMaJRgQiogiATCSyUgoqyEikCn7D++er857fv4n+u/J/xp+VFBgK
IxFVBePkyeXhNnwGfD8f1urtNMN1oFEERv1d6dP3Nmoq1Khq2+jACnFnsh+Hrt5H/LmYfunpwgfc
3u+CfHRIZ0EEMARoMH2DoPo3L8WjoSFsyQcNEZHUVUX4wdBDWlhyGbUlkSgA4pIn9g55a5+m9nxP
DO37oAG2CKiknt+j56xrJ7VFHXANH0U0XooYQUYfRZJWGHY+sCONkJyNJiakzynhJUbh7y0kGDIe
C1BN3AjjpPT8IjyFWz5wfqhvns4DysJL3W0noXPEMqm8dNxRhEkDdXdGDj1SnkNJC8VU8CNCvQZg
7lGSDKqQxlF4pEG3OaHa80yTqeZI2RT5ju6KbI5gAO4iYugWPlqusdVSeCSETWs7ZPAg1SNTsBL3
F7ughylLk11tHA0xKJILP2M31FI6Yi5aeLaaFh8isEinwCZAOcNKahqf2/+nfFEQmiievBYy9yYr
vzbTTifQrsZveEXiosIu0Litl46LEjk1q1tMnNqPRcBrnWryyLJzyqdSb46TVpGaGpisyx9LRrFI
vTZ0qxmhR377uxaa5RlhjNPtm3MzYPymWkEE7kOIhgqSLw6Q1MrcGPI4wZKJ0MeMK+Wuxz37qVq5
TkQ0iaw8OpSHAjZEgy+KM8efMXRYRIkb83IWkrPJQkTZAgihIu5OkDygcliKbFGKpzChI1okkGNq
K6DEcHd8taststrbkPUHX1KfnTie09Ht6KniCp5EyMoAqQKIqo4orvyGQzBc+UpTXZp07qbxkCqg
I1gCWdQwxVPOqfy8pPmOaoVACjeaDDyhC2cBhmuYR4MbI5KiMcuoo2OGBPCNEIwdB9hifuZM35qz
EoGS6ttDHZsjHX0sSokeJyuNOS114VjcZnqETe25NJLCIl8eHNrlslSSEPlMYuN3OpzmOBePCoaQ
1MeowA446hdd077oHuYFfClkBZ7WSSiT7kagosFiMUUBEiCpBRQFgyCgxREkV+GVv+ba7YSYXKDg
3ikotR2404xvEO2c3x9HVyYOo0hUM6JBbRnDMbPhj6Irk5UlEKUpWVWSoPCKk6aOmcU2fNaPyvrb
L+ttkxFl7GPAgNZMBk+8LGF4iKWJI5M9BmrAcibpgN7ZmBuW947yQvjbVvVWGIbGjrCxCSb7NmFB
gpmaSsHFowJYcHBuGswNYuo9fCeJfEDOFgeHyaBOA+M1bLWLHx3LVLjecMJA88ef5n5LOOnpIokS
IL8qAh/ebz0gIaxHXJAAUWAq0ICIpFWKCJJIoLFAIiAxikUkUBRGRQYwUWKRZFBVgKCxBAFBYCwE
QRCKpFIpFCLBEUjGRQUFIjIsFgxCCwUkgskUgKEUQYH9U0RZPpYJW+0IRE9jCHtxVFEYxSMgoxYq
skRhIMZILFih8wnqGCrtiCp9XJr+rl+vk+PsHuDDRI+WoX14c21soUO8S7ObpYMsYrFzE5ejjR2K
OISMAgtQYSMgyKSLCDBkYMCQiwinpPl5SD8fJFo0nBwb+DtlnEl+4+nwRgeqSpcuprPeaqvDXx4Z
4KZqOvJI6Xw1wpW10IIFOr7nhmaX5A0mVgr2qcKXKoCOheTtbfGV0YxLRxYHkegPMQk9WNcB0AYw
Q77346kExJfBk4picN8NEdhz5bwLBGOcfD5vjKAEyhQeFdjHGGLTQKb1iOYud6+VWclUrv3Miuo5
BWXe05U0e2anA77Vi0qWUR0ocyx22cErnBGq5vnhLBGDjIolRxhbBS5JF4ukDpRdngHQZr3ckxnG
DcXmqxTJDVpgrsM9FSmMohVpg4100XjUZilYBfp4ti8UJKolVCELJY7DWJxEETPTt1a5bBwHnuTU
sXPiLcQDLWgTg8AoR4TofD+D8YcA3nPbd//v0AOI8o03s0Dfkt5RmkyO7RUVSKklYUdSCpRT3iFF
nc3XBKMRAddNNMK8fILJSZEMHZRyLZdVASFeE08uZ4VQq0lxGrJGlq+KJ9Cz8vfNDCkkLgXoFBar
x167VPBQzbJBEvrBhKizD5DOCQoOE4ptUHRSDJQaqjHPGGlHZvdzdHjeAtekOzw6tNmeGvHyQn9Z
5kJ5c7O27984rd0tt2TczeUZUany9giep/Q95aeO1QWB31PhYYhSPDBQcVpUbtMgqVHgi5Vosszs
GDEWcD0C1EKUE4omD6gPEdYoalOKRal6HH4RyzFNdRPFUbAVGNyFJsuDhQYRwoHIBIsl14H2Hk3q
5CpSWovKd1MOqSyioe3c4QIQRKslwoDqryqc9eIiW/OgMXVYwwcFedUX5g9EfoHBXOKMTSoJ4Qva
oAoIMFei2K4NEKCiSWwYyUN65bwiS9tZ5QUbQPC83IwiSInGWNqi8DThZVh1C6CtrdqX2itHCjlT
hTT4QoOg6wRpcDgAp4LNyugvOqFxXQbE50qhn5rudKdVDORRwjoqqwRZY0ejwyGTx7XMp4ZO4YTH
O7UVYl6uBvhNOwIOqqY6uUkOtVQ1CHCl6FhkUoscl4DqgKqUpeYAsJa69tAtfAHQ4iDIVRsu3Otl
WEcvHYZRvgssCYdQZ1gNG0w6RUUCynY+v6hXO56I5EZzpujUUO7OGCRe3TwRMtpiBJl62YysYDw4
ext0a00PQfIemxRAXQwY2ukFfDISkF+KtKCViatHgXRhuBMDiB4keF0w9dhPH4gex+sI7HnvwGHh
1Hk+HeHDFPFKTBWwj0Ior5vGblt6ZHGhQKORynCkLvoylXCqemZbFsL9On0ntno8NwaVnWQvB83k
nioRH5DGsg9fsHnXca9HZQo+ePOz0wVodPmhSvfKUSOFEPIGUX0jK9aWB4I4RzCIU4/6mqIEMrdm
PLL24VbsPKszB8gNkywroIkDtwSohAVRZl8T2mWJL+QrrAFCCfcgfQhtlvCkS+UUssMNXAgiPtbP
DDU3nzstzo7eGMeG38/58xhp3IZRmtv1GVuY55qVUuWOqSITYwK/F3WeNHwuVWxllupXDepBf1Ng
mfQUwxrs/S+E5RVW1HaNtR3d7obqUopTPaJROVycFgDRvorEk4NEj6ByAySgEinKEEkKHUeg1KWR
3PYiikoVf18hu3XSzO30OvbrHRIdk02g0w2b2eI8HHHsgeb7QRixBkETZxz2+8Yp2vHAjErVO5BU
C+p0DmqYZZXU/lrJIF1PmfURPVxn1i73BLYmEm3Cq9zXr9bUCQDv4dBxf5CJ3p8b+PX6pM1v2Wkl
gqR2iKb3eecXjurredIZkG3RhRNRiYAb5WGq8mi179oveLi2gpD++nFFsrBaAgLQLOugXx0Aa+UE
d5iIg+vrq220v0rbltupDgn+I/P7PUbmw3WwX/sImaVD4g2UWCMFgT/rNCtkwqvCIIYiIx3L85iJ
8+5sHmMbFHKXW/cjJDjvD5vW/ywN0PDBud/7qMgq/tESyZX8/dDSnLr9lPsES4cKnsEEO6kM5NnF
fPBMBY8EVNGlF5yxYhtzpTIIQgwIyDpqokTo0XFtN3STRsv9IvMLkFr+DuIn9G3bFDr359QS5A0O
Zp9mw1jc4aXjMFjHodYO45K8uVCJ2o8bhIXQ9yZhi3iPrdenZmLGLsxh4TsQ5MdG9reMmjLJe6w1
1Nfsc6w1wauQiYtokMK61gXhKY9D/UkD+T+ZDw/uUhJaeDMYwPZTjuzmiwXVxE2ff6CJPvXv54Tb
NlUsZ/QY6KfcUkSUgkbQh5MRcRFOF+oqCoYGvH+B5yVuBzFZ+3tnTx6HXpEyinihsNQjmYp7Nq6U
KcPwOjEOqVXNzSDIBCBdlcGQnFYdxI9dILL+8f2Nmti5gzoQ+AvGh1PMzdW298UCp2hXeAYIcwnE
W9X/Qn0A0Cq5fOoic3ETdUZdZDuL186i2AwFBy++9lgJjaDKcfr9JZx8mESjeIrVq1Tqm+adIHRO
LV48JmFDCCSDITfoYyuUoqzEKKCoyIsFVpSvHb25FaHlZ6UGdYdZhkTnBXq1Uzr5wdzNZHxvcm+h
D3QtoIIUl+GWF9/9UU05aG6AooqqrIj5c0xd9RVERTjljE+5TxuaCjaobqLBlRylkPLKhkX0aScn
txFBUgKVKEUSRO13yMHriMg+afeqN0nEJ0IkEVbtvjIkyIjL7cJFPaucj4OeqXPaPeeWKINl/g/n
nmbYrXXaVO08ekQNRk5OTE1zX497oKIYuZ1UGDnTflmq4JJBAfJgIZgQ6yFTkfrurjxGZ8tD7IPL
aQiPUt3I+5sxlYcoSjOJv5s/IUPHdxhxxxlFCpdEuNgrYHctpeynHWPX1jgv8cclNlxUlAU44gPN
Hfz3GN06JZJ81yjAm9Z5Oa+BqnMZgJOXGnxx4/p7xbS5b5gHTTOBDJWZgp4iCEhBByciaKIilRra
4uTNXp2+u80hnSh4U7/KhzhPFUpYBITg5n1qwmX9HHZgfH7Q+jf0vuhJ6udDYfqqQl8OC22EN1ab
Hh5TcJLbVKIw3hC6QaCiMD7IxR9ZSGBtFWQnow6dvRucCI4GLPTi3BGCwFVcwXxZoMqhYRndWBaX
xuBV+SzeF4ePSOSPLFuGgQhpPzJ/Lk1LZ2XOIOYlIz3CeAzfqfcYcIGBWbZsxgm5RrGzclScDRAg
7IuCZkeoEThmzeIUahgBtkWCqpJKA8URxIDIDOKiGyoGxAJMVx5R35Kefq0SEWkIlvM1YuZS6qqU
Dw+RZ80UUURRvOsFUtHh1TMlmZktuGXz8PFgHzEDZPd8OH1ugKQGG8VFVLqyKVLm7B03MXo7V7qn
V2Q1RRkF585vjsSeEWQFGQIkFkZCCIpCO89y+ufrj8L/tjZIdW/RZCkoCJ4/Ujbonkd08Kmu/9rk
FPlJuOeAid7x1IS+EW8SXcYia+N4zKWFRXnlKkHnjj6hYh7IMEYArAQFJ3GCNjIpAylZGVFBEYJI
VFSMgiRgJ1P01zSqqtl3yzxstbN8PEQQiQh+lm/tmP+tIDVn+IifSrCJzma+My0GVdvviT41nBeS
5KvDZaMXRE5CyO885dxhIur6BVP2YgqlV9rvTjWx1odL5TAJHYDQqBIs8bSQvrcqb56Gj2aezg7H
LdqTZq08gvNNmJA3T6iI022qQzT60MVNYI3Ixo8lMKlHpFaJ+dMh6IhWayaGYF1C1DUU655PIZwp
T0hkd9D2RyICMyspOyMPzxww0URNhJTqrFj6SlsKKwcChCr8h8cu5k9rinz+UfR79c8QSaauZSEE
OSoUHGUa8F0dWHWxXtFnM9Xger4PnnUhyMBQFiPkMqlTs7iorqLt+Wz1JLinJTLM1jPDYROcF4dX
VgW4EHRWeU8nGIkRUwxDgqgw+Hku24O50JIooyQ/YxydhVXrujGjvs9YpwtJEBRk22ipw6zKviMM
yoQWAiUEkTgVOovRGJOl2d1DjNZbl5TPZirj167WrkhmF5eQlrC5C/cLbR5sDfEvZz2OQPtpveZI
r4IUwhUpfdxRI+4Di2HLZk5BZqF8gsfzx3N3LHRNrr4MXDVkay8Py9nOb5F/bn+Oc0l6CIiDIGr5
E/XZ4BSjOvf5lAdKqWPvydeZ1fGEn99DH0+xfB+iFHfU1+OI+HX6s+QnxrJUQOerF4ZZXfFxiCmA
LFWO6Af/qw+yJRyYiZlDANza00U3JYlEKJoMsbbbpuHD4YLs+AnBjiLpV+6Ps7UQvhaZUULRbs+S
b9vJH2YETCAmFEQcCB49osPfJJD64iiiiiiiiiiiiiiiiIiIiiIooooooooooooooiIiIiKKiqV2
MCZbQYoQDBHQJby5Lk5NVX6DMjDDA2MHkqwRPtD9/WISZBXuwoEMwaOSGRBDmDJaPu8/jPvfPelL
Cfl8DsyK76FlEMSIZB/C43uCItfnxadLCmH+y37pe5xu/x2e8Ub2n0VT6KLSix7qG6ju1RE4Xad9
IIoqHKUvq/KpAXy9CinkZIw5JxZDILa2GUp9Bsci0uPCBwt8PQLCi/BUGYVruygqKog4yLz5mWci
tZVSSuR11pT0U6b0aJWKRz/QxaWrs24dAbebHk+TfC0LDCiSEI6FopoKKefnCaJDM2GMdHbAu0U5
auIjgYlK5qLnTKRQ9wgiIVJCpnsaGpBKByqY8kZERzNY9OPWQia7cnKt2xxWWQiYCCEoUm0mO8oE
SKv1ynhac8x2cYoQmGvl1cZa4KLDsN6pAHWBasSgRJYCH+7W3ki2seYDMDHdWwfxOOp6ufW40uyw
thX7mb8B/PaeKFAVNt5L0AA/jzUjyfAurEsxEaOMydCxZQvnWmFGxwkWMJaw0WGTS0TqoyBr3k4B
2VNN/tH2rrsr40F06bUeA4vm3ZUfwBBDqxwqFMpCqLdk8C53aC0SNXn1tq6vCZ+iJCa98WjAc+vP
CtaSPfFNb2fzn+xTCuVYJRVIKtE8YkO+RrOL+ekhQ0LUf2BpeLT85Xxj+ybwGOp4me0YncbYbrlL
BUqfzBURNkIaxsLNUgP6HFN/lRo+NM1+SoISVD1U8SwXU7dx57eLRiSbFx8mV6tYtzAYBRAh/OTI
nTWoXKC67/DFnFMKrGkRUIHThtjDDTxr6XoYgpcw9jCPmKQ9jIHvHJ0wY+GH2cr9q2u1VURBYzNF
qjAZnTh/FVmjKiGnEvcP3vPwOfQ5oOUZ1Saa8hsH2RAJncofucC5oLtiY2Y4X2ZCJl6i9p9zpoP7
PcWT0meHT3Fj2U6z63UQfI1SYtcLfA576BBriOgcsmTgzijs3RHd2dHdGR9ac94IiMdo3ge3hPxv
dBVQUVInv5oiaV3e0TM/FaZe4RO5zo1mtsrsCHHQ0skCQgwhXB4Reu4vIY46PoY8oP5mX8W/28x/
qKEiD00FEB/bJEJBJA/fASykJIEiG0pNfj92H6P5f4q/Hwfpwth+Fh+wab+U1saj9gLdOz3mBpGT
czavEwGyKXA+U6rKbOsnkzxRhqSf+p/b6w6ffy+mXxz+P71s3w9rTxOvz44/4qLf9Hc350Zozt6d
pbdsXzePVfp9uXhfLCMf8Pp0+ggh36CCFKQ6q3kDtOU6Eu3xSyFFjAhzpdKb1YlKWpN7RkLiCXRH
Z8/s9mZ2Vv1a1GmdBeFVaKaU8flQick2UrG0FKy0yiK2aUKompCFHrN73d57Pg+u/E1nxK6fJ2/K
+1xx9z9h6e75vRUqdMmyp0qfkfuLHoqbNHT6HTRJ6Nn6ft8fU32t8fxH51FPUcZ/Hx+e1nhZ/EPn
169dc+hW8/PH8Li42X6sJvfKHrGvdtO+05Wqr1cgQ6U0pGU84SznV45c8+vf2mcSf8Hwcs7QPwJO
+nufru7kcvYrr6s0cvR2NvYyWqqpYzvbp+5VulevGdsvH8d50XX49+nRpe/Vfq+Q58fl9Xx19u7B
cu2rdqs0emTcYv30nfvOt+9sWhhGMeU490WWTrBx5Qk03pnGEo1l9h95i3tzpQfBTmzjNjE6HvDo
nJ1q8O94r8YyK+4cw85UIOr4WeswcixoKQnh6uwYIQf7D+ANB88EVKM2OZLkLmaJJvJmKaDg6iqq
P9BE8GvHlE9EP9KoqJQGIAhCIoEiKIlFjldeNv5b/CDZW8/fbv8Pbj7znHRdGWDN8IezyvlhPbX4
fDHi8WjDfETYInPzCJDn/zaokQ/5H+j33f8qwC0/20ox6JJKgIkJJLFLYDf/Iwpn+7AkC5LIKX+l
LDCxAoMYAkQp/vLCsKZMBDC0lJYUCkJRH/XDtYFAMYqDIEH3CHMJ4hA3wFD4m4nYIdInwPnEwV8B
cLGkIebYFk/lU2xEr/GP7iQiH0wf8Yn8fGpTJxCJSFKJFgEIMCBH8PyecubbU7/yvH68reK3psdP
v0FtH3Z5W+e1o1c+UtS/60Pw9PzYUY/ZseYqH+gWJ1xf8P286/4v79e+tKT3t27Vo+vzn5tfa8+r
8P1luFOfgpLRe6IWnWsQOYgB5oIdJwduXbO3fHIH9sUUfRFfFJJCEIxEyyoDwxRTlnjgNRAT8BFA
KiP3fTlNFFxV8cNBASogP8IiJuIr94QV7oC/4RxiD/FAqSSf/hHYRGmBtijnEAMJywAhA2wUbMFL
xT8yECoCRAOmQ8xkJKz9P/sPJalYuUhEgAKKCCXP+NfUv/kZn/p/qVvb9VgdfdCMP9/93aUaf6cO
HPxvI96kvaVxXtow5th8mQ+eeMsrPKqr+A5TDRTikM/Hb40dpnQ20L5YuRWOnwXlPHS2lSwpKjHC
ygYmMQRlRVQVSXPlpotn/y6DYZ5HotLpNJVNdWTcm3H+nGzchw/0f58uX9csxcOTb3gR2c2hPv2e
87rtIs0qWCTRhwwyLjKXRZ36UkRMZY245ePHHFtFC+K4L/meLpdTnD/I3JzU4e1IR9OPm6R6sV4w
xz/v4Q8+vn7Gorxs54v4uSFlCkqHnK2tlBmVBehTOoI+MJYZ4Cn8uYujPhNjDX59+wUgi0vjhBLL
Pacb090cSHjhB17HSniL9i/5zd0t/V/Rdc4ti23VfZ4bgDna3TI5c4fH9z305y4f+B+9yaEAF3T5
gHKKUGsImoxNE2Ho4/tb3eataiKTWwCXFkeCaDVL+PvlKr3ZAZr/bi5NKphBkRDCJn7CS8TtRpPJ
6dt+1o7E0BaGhh3cOiEkCiM5OQZ56/w0nHD/ox/3JeOGhTHT4EYZw2w7Swf4K5zhpYr3McUUheEM
hajIqfzwRl0z76mPgd09l9PP2PquC46T79ZnKnfpQdbc1/6zUaRHRtrtmsLW48rWNKS5Fauq9sKz
O+LWhk+UIEa+xtF+nux7xnZDFgSNX8hU4XLHxrfCO5+HGmj+079fAzk+6uD/zqSord1r8789Hqhe
baqjarhcc6+Q+U16m+JWQs/kSZX9he80TX5qM9dffCqnLOj+FyHKybGdbPVTwxe+XJqmD7waTVan
qQMC9h9brhDOX7caV5Yvznhn44QaMyJwisthTUy1RtFlcUxuxgmfECBstfZrXa2UzwF4FVVkiEia
9cekYbbZwYr2vhU6YlFF1Xg5ndPmKseRwSPZjXDNYoyKbJbVcG0NdcnZOXd0pEyk3PNZOqlU7+n9
Z9ZKdWQ5KXXk18todsWQmejJEVJKRftzPC2FNsd8F7oucAtMDGJ2bI0OMQHhW+0k7BfU0XkL5+yR
7ET6LEdq0iLx++La+XkfP3TlzQUVt+Rfv18R7fAfIfiViqjCDIrFAhFRSRSAQf8f+FNgJAhGdyew
si8P4Ck5IoK90Js/z2HsEYwgsWpRIIkX+FALBUEZAqWCof6spN6oSpHWQwxK+3H8UuXiQiDIgQh9
cUP6YuZEtFd4heEit2AfikiBgIdZ7V9UB5Sj7yjsgQ+xjm7BQp9jIiFgPp6/XD+RV9581LH1zLvD
5/phCUW+5j4bsJ8lPtl+eZkUJzQKqn4Lt9FxQ1zisG+/8I13+mPyRMq12KBy/YiZrnnkzMsymN8L
mI0ThUYpe74osYDZ5mNPyGqSY9kS/kVGh3SLJTZ91JTTww8IWSaTV507oN2hB3GaVChcP6yYiROV
UCiZb5Rj0mmUad47vCcEeKLm52OlketWsYZhXRDj/uVciLCy5jIGGldvv8DBOjy9p118hVMe5VF5
lyTAmfbo6GymbYg9mFpynDRDgq/BW9qpvFdUT57tFfIOvELvj2E/nRU9T3KcLwoov8y0smmeo/jy
NZqRRE/X/L606G7Va3SsWnEfqhzYkrEqEXgRf5GRQp3rQlpQRPOmB/FJV8Axwv9mKL57LrBWjn61
xQhrpXMIpmloCqlHXVqu08SWjFiecW19YwyjJlUPtpL5OpMvx8pm8wcTljZ7z7n98MkVF5nyVASI
TDNNC6bKIkb4sw9sT4Lz1pCkk2im0hodsRhxdlvSFRExIRlCv62p5i9C0CTSrVEcqKSGDUjcVFJD
C1lVUtrbVQgG4ZlTGDMpFHRq4Gfn6Ux/jVgKIMjFkxTDPSDVSVVO/wB4jQdr3tie8qfUGFjyHo9i
avvFoRYRB90GoNQ1xA5/FjLH0fIb/pQUNou8ebt3H14dobx9s1kkSBGROSAGKCXUO2jCHMEIndgd
1FWKpesztYBmjNSH3p+4IH2pAKEClYdhQpQUBQCNERChgLFkSwsEsWWJA1/pIjwzg1pVKWLHkSG1
IuT+s9RPgMpNZkJCCEj0h2J7uP1IcKFuQCwwTcJvnEWQPMZAJgQB6+mcvhq1YbLYXtHC/wOIGSue
aySAkiUKNmAlkMjSFlAurtG/quAY5HYAcjwgKHpSCbAzk178mzyB5AckOnD0592q9apUvh0J1CB+
vtAaADsFzyz0Qqt2HRFmiLNKz5TpH5OYT3EtdGLv8D5kntGcwgnaAawWKoYd4iagTm399DWRXkyH
Wj1E7VJwi8PfUWR7bPn/o9TXV/Z2b7ThN2IMGLwpucDNwsJtLBkDij0Y93sJ3fB1dFt6n9390PD4
YCJZBJiIsf8QiLj63ac9JH6YeOflOvlThYbeTyU9jHeVLfAeZgKZKyo++vriPv9Fm7WTaMoKMTz4
fp1A8ZXRECAfaG/UWPk8k0sNP7yp+FIkEeToKFCkkWR4DFdRxNuFXPSqqenHtvHf81iD57aVC+k2
kNu2S0WdFjeByX83xfTO9U02PHSdHP8IiTP4pfk+YidWKCrfu69+OS5/rli2eqrHbEnPB2FMf0Kn
y4yciqotKU6/0y7L5cKK0lwW2kBfJJg+l1HKkQ/Sf4x4J8vmO37geP2ogNzxmX5hdte2PmW0rzpK
47vF4WfSodv2PdXfnwvRY85EzcgfGi19e6NKKpAFiDjDwJ+MCkIMyv83+ykFgMgxBmqgoSgmSQCI
lG0hBh1VttY1opzswgGzoMOA4JBC3d/b6kkkgfhw2SeJzDrq+h3qw3NXei68JxgLt4CgSBARwIAI
7SHDfmbMi5hWMC8AMzSUPyAWmMlktNLQyQ0kiU/xOGSDaA2lOhUXLMm+kn73FCjIbl1alKY4Vw04
iiauGFtoPjdmibzk4B4SGsaHAXP8nh4/RucOCEYEikSEYEkhoMjR1zX59Qa14MxOAx1tWCESlXSU
cDJdTUw0RgmhHCy0EzBKZTAZlEwsLQTEyVY3LkzuQADUofoIqXIHHpknSFveLD6KPx1ZW0lWti40
P9zSY0mmrBC5VtiJTDUuZYmTX69QuzcuQcKWtltLmGGMcKGMXMjhkwYgiIuZbY5lS5Zcu9WaJIEl
Xo/rq0ZL3Ij+0Xp+rn/b1/z9PT35H2ljLtwyyi/rO0VPKZKR7mYhAb01QBA6gFg/UodhA8795bSW
2S222FttttlttoW2220tttltltlqrSWrS2hbbaW22222222222yW0tttLbLbbS222W0IIhsu+ibv
j6tOf4ccLT7+XL6ZZPO6MzGQ1owCyhcTy8Sef0XU51dWQHHQUOR/eIlC6tu2z6hcBVcQgiQPQF/k
NG9sLnLEx9JC8siSLja+1NFfiUM2COJ/jET8J+n5eH8X5RX9Vpt5z+h+qY7FvgULWrrkVio4MmFn
HuVg3bhnll47YFoyqmbxAA1vvP2dzg44Ok4JCBtCBjMfBhXh4ygsk2mRIHgyHZhdUCgMrCdPfwoX
dhMVJiHKB3YTs7zuddZuKG0mmpphJjBQDpIG0ndIpwhpmM4Q2hOe1kinPN4EmkaXswDTioVhuMDu
mMkOBJrvYdk6EinD3entIyHSFZwgd2HZA5ZMSB0wmkCoVhDvzvJOkATiogF5bIiKQ5Ag6QiCLVEq
YqL0huljHLzYGj4ubpyrDVq/cRLaBE/chq2gEiGMuRT7ilBfQlZ0DJDUH+i3+3+38Wv9H9xvOZyq
07BWk3NbILzBNcU8vu3shcQCK8UGhoHRlpbsvIeW6JNqO0sbyEEIosxgVqrTuBIn4FHotptGLSXh
hoTGJ+DEmCNdGbUmbKUxtNyYMbhJI+zFgxYf34lnDF+h+hu4s6cOVz4iGhpnFS9qFVg1wEQJGXL8
m0ImAL5QnvquPDmHRna7NuwybzzeNVM2DNdos1Y3s8WL2vfXDImqs0i+KS5MKkmhgGK1M4zhfNg0
XaOBIbN2KSf4qNuV37d3p69avTdqu1dtPXfMWpwwrvLEwpMfMIwHDBGiMGGs/fsO0Z2jhq2bvce3
Dpi0as3hs3nVOI+agTUnonoBuwnEpEpwmFOxVE3Yzttu5b/KmyHJkwaPDJZuxeNEwyllJoLB2WaM
IwoIEypGpvvoVSiAl7qwIvhd+xfw8U8u3LASFPTVmwrXwqr44d8zVJhgwsyYkwzfHhg1bLqPTlju
na7w0dHt4y6VqyuzeNMT/U8iQxRojmR0qZ1JERrdaSQjyg3YcEVMXL4+PeJr6eXK7Zs6WvjfG3mJ
aaXW6YeA8TLSEYEkSLrXDNWL04Ze2UZaMSnDRiuo3ylOIyRU9GGj01WWXyVyYs3LCfvibCC34TRk
oiJAUCeTIHpYFTBjEIHef3ww1bt3Czd5aMWDdkpOmbw66cRNHpmwelPn5xauWThw5auynTVddk5d
um7ZTdq3bvhZqwbPE8TJk8O3D8Jd2wen4E/1kcmTVi9tnxTFTVg9pd9wkKa/IkKwZPxhjmwWv69Z
Rd2xcvl7Po7fLp6bqbO2rVds0fyooycJwpZ2zfVZypZ6Yvhg/nm6M4ak8I6fv+yPf1SfyowR2k+T
WUWYJ8QhqEyMetT6oGJxc3Ljw82Y/CRcUvjomNZVWalogMgoHBAwiIunGt2Je2RzXtoNobtUjUIq
zRNONhFF5QDaqtlEYmWjs6izeorvCQJzEGYQzs0BwRQ0GhQFSscXuCwBWyScLbV4pVxwCk7YOAnS
3ReTWcYLG7zqBnJ4UfBuznalcEMptnYOmDApGJTJkrrrSubpYzMYIMIRygwopRLQqUE7khxWCWY2
fxEWIyd5wrEd9hEQfKWTrw5bxHWu/1m3TIEJGMTzESgqqkSRzOYBQ3CZoKHUmtB3CQTde0+HY339
ZGASOwho4weLlXd4ntUmahSmDJ0v9xUfzZS0Ho5AcLEz6oEkCCJRjQd3ZaMitzFgewjGcaUqKjis
JU+XrPBHDZw8vhk+zBq+in4pJ1jYUUR1BWRUdivMhgshHGZCOmWaQR7j051rfRmpQtyrNOGLpkmb
K0cThLL2e/wdYnxl6Z4Vm2YPDds/gD1wm09FSd1K8U6q3edoKTaaEpiE3CjGbopOgyIiGZSKO4OZ
kD8MdemEk9KJuXbrxFeloPLlrdJ9qJ5QfqTHJU025TypCJMpRV2acaZGe/FKR5TphfbOuMcfHlwu
8i/HmJdbnyYTxvLUTsTnflbTsvXdWGfw/dd3146nDXV9eIzxox6nx08+OoB6Ow0Vo72yECDRdTho
/qI1aGDjx6jFhfzZJyi2MR5h8WT37ar4aLdtND2yxk1UWkkilZvaLpTZw7XWeHaR+TTR4pSdaUtp
rb1k3RT0luvLoQZsnw79csd92MM5glsYuwiV1Bqi0lzal/D0pSWRwMBDRkrlsMDJ3jGwzHRs/jQf
ojKcv6iKZPL2u8vq70GCqnmX+zwA5ibSHOJ6II7JrMqbliswO6gsMIQKCIjA8RoqYHJk4JQMzAij
FSRgSGNjoSJTAwqrV1swTqU++y6+NDwu63keMXh8MrzD4I1F0/LRuG3vG+spw3b4xXS0McVdvTDp
IGTjOMCkxTgeCokV0bPlOSSUz00QHzUFjjnhLWtNG3uScM8ZkixjkwwR5YnGxZXbqINakmbHNwsx
1nWrFgwmTlyfDh/Hhn9OeZLLBSsXlk0ojE+JCISBKcqMGnIbWQgmQ4MJSiTG0xcvD8kaY/Nvmu+W
K07YJF8f1/UsgeBgyh7iD3z1u4Yr8EtAjW4QahCImhhKUHRmRFUgeaiRGKiLhqikUjrCXkho+jdh
MEkVxiuk1+uOzhtow9qfTJ4Xmzpq+XSnZ7/VpSlKiUqKonqSB6D/6j9z/f+rt26aGD07ZNnT6s1m
rJZ8PKmbF9z72J8sHLgyXcLMFl27Ruyas2bzG7wwYtli7Z4bKcNn6Qs5+mKrbvDFyakCJImFyB27
RIkyRkbl/fqMSJCmJiYkTptgry8s2KzV+6mzqmK3DVdgxU6cuFm5gaii5pIbDcc306xN8H1OxcME
QO3qBPYr+cTpHkO4mnKmHvwyt9GePeK2FjcGuPnLvIcJDlhCnFgUNdGQ1Hd3xd7mlRRG5XdnwOzr
vhyavPNXdsnN4cLAYcMVPtBREmbzqyVfTqKGcl6bjhViZLmFPJdqGXNGxi21Ks41Y93GLFlUqCMR
ja21SWL00wk9ikqwdrM9bXADiHjSVaUlhC6ofExb97ILVOgmiHYhkDkhqUXNaPO8w71d2998fB46
B0XETfqR8U+BmsaakgwJIQJOAFRT23czrfA5ERiwB7EHmmR4i7HkRwtABEBxyw1C5viNzLEzA6FC
oTZlW73W7UUQokICs+XfWG+eXPGyN2bjZJeYyVJPjDzhO2cWVB2qLdyxGOLJnTZmYNHSmjpt3fUr
CzxjCNYjyNJYZXzBGRZ4JAwLDOIamcgj3CCl6oJYkxBCY+xIcfDb2LEngUGqEYGJsbCEtdWGVLLY
MMHrf1jluGjFjJnMeFmSqyEwvM4mTo8YF27F0w3hy4Y4TtRy1kie1Pxe2btum7tZ/mtt8JVQpnpe
WvVeLLZSlZTL66YY8RB8LSfCPWET0wP4tgtMnDpm6nxlyo0enK5d7WnGi0zdZNcIyw1emydKdzhf
XxXG88FVVRXrJd3glIPlItJ7RFYvS09vCx91SffLPb5Rhw1s5JWDBsh4+iruHpiZ/V0yWe2Ppdd+
pGD3v8SbGsi1WnVhe/bdjjU0xwJ4dL4z6MSPKhGbGyCoz9MW7amWM3Z+Wn3XjJU+ysVcsHpsxe27
R/NOXTw39c75cXLSL2LSubM863RnkZSRlGrtgaSNPS+bRbB8rTdUHrRlzh5U4EhTB5XcNXs7emLr
fa3lGlL1vfywtMLLr3WRmSfGMegypI184ReJXnpjdgjdqvGSKManh2s9P5bD8MWLZop8N26P3vD1
uqqkwxtevXfzmHxEGMZRZTLxnnwhZiyI2DgtcuTlLIdJEzgdDDAcc4VFsMgKTMlPh25elbREmMhI
qQYaK82kUrW1n/zIIfpX02Vs/lScCzpyp19/3tmbt9HbBoppp6XfQzdOGjk2aN32YPhm+j0u3fRq
dOWbF0pwyeWDNmvSnBZZy6ZtTV4dNGbl01bOHnz21amjcup5N1mylOWLFHbhZm5aGxzzu4Zunlsf
k/Wj9s/CPygeH0j4ELjdH1D8AnIUc4POJvCZj7gfcI4h4/UQ1QD1ovjy8MvKUDKLyJHdUKUeoiFV
8lDH1YF3jStFZIjmtg1iBTtKgAkA5DTT7REygoE0pdWJLESZMGI1aGrL3bNLTJ3cqiCDCys07Fqg
UQQ2TbMt44dayJRSMxYUk7UkjQgFWd2VnJPYRHeEK163Dtj35YLWmF/uU2h3juR1O5GMExSxWF8S
xGTRhZC9wqSRZnAzAMh8lju7DJDEt6jAyEtoLDYXeZLypLpuvLE8Fst7TlXDi0+GDV1+b4LNjc6m
BoYBFbpoNTBsVgSYzLE39oonw6XjBd2RUTY5KgDpY5ZmpCJL3ZkES0jQ4Nipc5jURJL8WqzW1c6f
PdI5z+XQkLX0RlywLI0ZvOBmcMUNtnCoguah4uXIBqTnqZmeDYE1SiyW7CQfDYbOUoQFELEaVgGw
SqnBIemACLMdMBS1ixE3FGGKSVSrXdWe83QSmdyAhApOrNf030X2k0Mma2G6mOizpSbZqLZNXywK
CGjEkBZLK64I5EdnV5Y4j1jyHGwM9oaquCzRSpo7eWLk5eFjs9OecnmJy7ppparyy097STGYQtus
s4HpHhg3VvCU7YLM/Ttdto3Xl+7PLBu4O/WRaJJUpB5a54Yc3whfBcSxOF5fRHtx71RvnlOl76K3
FqKaG4pQU1GJGpsaqPRCypq4y5MkNlvnFBrJkW1Wqxsu6ZvN+0qa7ravp/Chc0eXw9sIZuWD5Ehd
8PbN7e3th8K3tbu98ISiQg+tTMQV9iomhUdBzemhUwUSWM6kVGMScrZt3TwwwVgwwfCcMWYTdFKk
SovHiP78fxWbMk3d/X0+Xy+Xo9vc7o9rTN8PTRTNZo1Wau2DF2pkzWeFPHj4eXLNxOjZ+BNuF2TF
gyXU9PTt5YvLJm77yxVq8ruHazyus2bumL09N3howeDFo5hO9ExvGBEuVIWLFjMoKMObFIilDQgM
JQsaO3bpsxZtWC67tqzXYLsXKRx+yScRLC9gnhDR9SOAm0Ha77pMxO16S3JwcWvO3dwk4djRqBNt
nhSqXIlqeZSQMKAQVKe6DSAOJuTyhkPTyCQNKCBWTTK5cM4DDTbtMKIIbNUVtZYUjK23ORgxcW1e
Ncmtgw1SqCDlNL2pxZK4uUtM7GeCFu+6rT4+HeRfT2WiyxVlPSTnwAmyFhLJtkfFvV+LXpnI3V8M
Or3d4WK5DjpR/MV3Q5zcC6jfNJoc6dcNRnL6ckycyJMFI+540xJUWpiIO3aynpdo1Xenty6TN8kv
N5rvSq5usztnfjOEshASZU1MB4IkdypMhFMipGLX3lhvR9NcxGFQmjqJhzkrVm0dM2jp4cxN9pN4
b1K0NMD6TxvOSO7eEjUfEkgYCbmCU8ITQFIGI45iNHzezoPbN0w3b7+nbpkbVNN11zbxbLGJdfEq
mNLVLTxdy7W0Kk87SfC20Q9Phs6fDRk/GeLVvDutK7Z4JPhU2dpk0eFtZOFqZEt8eVt1va++OS+R
NVsRu4dM3TBm2aCUy1YZMIPSTGYZbOhCpiOWHKsaE3iIKKida3s9OcHWKlcrtmLdyxWWZqcPvR4M
tuarbaphgbY+FQcScYIxuFmTVljUkzZjVKkiT0cUXAg3IBECZvxUzKFisFMVpBWwU2mOOHInnLlT
K7lfw/BbW29JOVR7uxeW5YUgY4GRiXI1BVbOEFgxEUZGrlMEgh1SW9zIjYqdeZAbAuZGkWMjMeNx
hhiJyGMCAmtMRKFTkslw5ESEobKai4g0B0siLkUwHSTsfVXaqxWbFKQY/rRDrP5XP6Sn4qR+0iA+
Lr7ywe7KNJrYWolIWuaDmENRuKORTpg9sX4Poe38qMRBfw+Jra31ujN95wGMBzA7zvO8qUOxUmZG
h4+ehAUQuSJTHPgr4bM8VdsV3hRypq0eVku8PzEhZmuxZvTNopOmDdy0ZLHpddd01WU1YNTJ8PDV
Zm6ctTBg1wWct3WLBgyYruXDkmjZgpq3XaNHl/QwLunbh6JhIfxiQdAu4TE8j0dIBp3x6xOX5BMB
OTh2cPFwwtyqocEfeG9R6okX+FBhT6JYxHEcHaw1ZK2qytZtDqMFJxyze6dZefCh3dZtiF5usG4K
y2sKKNQLKIErl44ptVoMChcrCzbKaltxbqmCiwyKIu5C5kNN3Taq7tqGytGxuTQD485U1IxjFSqi
oD0KNZTMYc8VfwhAgLEI5Pt3wMd4PJZ4WMLylJXzVEJIp6GAjAiuJEJqqzUmEtGZkY0g6QVm/D3m
Qs9bGo80SFRIFzG/hZchU8rOGLyu2YqcF+89E0rwWqzG0BhBpkMlohkVGsXyjEmTrZBLFiNxGFBE
UqPO6COTIQkIg5c7FSxA8hSBeb0ZVVTB3Fc0fTMQfGUhBBeUieZRJuKvcLZMzYsIeHhjoy1WX/ir
dfbWIPZZqydqMqwSu6m1Z16a5K5W9sO22LBdo2Zz6xO4TJf4VMmRgbnkgJqOUMD4AlGqC7aubC5L
GVXQlOU3gDHAtB0bEcuQ5zRiYvINQ3LWiFxU5ycfNHtds1d5Q8Z1vfmsawxtFsVSTgl53nJbBGUT
hi+jZlM2qGUQXM85apPLFGzvFMllpSBQlLAxCxoYCxByA1Hq6DLfKcxDOMquWGERypkSK6+i5QQ5
G41kmRfcQ6Ehz1EDX2vs1cr5vLZ2tkjlZdSnDfrP15N1q9QGZWUExsID67kYXOIIiZmpLkYFy5oa
mxhDKRkMLoTBjbZVYuRxWq7K4OoOkNR5FoF9ypB9TMgmBlU8RDO7mQ+arIKFjEkOLQRmVthsJTcb
kWkjFTY1gVNdjErlQmame5AuYAzBrTzT4dHHG7w4U8npy7fRk8bva1KXYNHlZ6eWTVdMXDhdwu5P
U1ZM1mLlspu776aPgnJy7aNnLRTtj2ry9PDZq3aKctSz0xaM3pxqzWH4Xe2LhyJDwxYZ2rq2irsW
bvp05ZOVl2bweHDBTZy/GJ4Ym7pqlnli8KbsHLhuybLPDR90/BBcT1IHGcbuRzE4hNa+oA31cV8q
uGk5KfPv33TxllvwWUAU9tCJmLTiQt0wGRRZJVpCvQe3esVSBLexeI0Ewq2ZsTLXkqiArVPEZwmM
QzBvWpjGBoHTBIajQtMkSr1dOhgw62oYzMQ0ugIc4zNRtpmiztjwGubBMVbXXTS2DW33mtJDadZ6
Wtk2qb8VMps3RKggEWhZrV0IZK02E02vnjlbp0aWaSaZpgSWMUqqQxkImZyIJSJRjmPzKkcvChMU
RMDUZ0H3tWTy3cM2T7mi7KaOauq1Kqb89MGChhrs5AgpccaRmeFjO8LrBxSaIZjAhQTA3CUBEMie
zEzGIIYFBjY4zibnBbGljFxllrY/FtCcO3LNhqjnnT4poxE+GbRpXw6eUfDp0pZ6YLMWRMOXCtVV
ZaY5sbmFebZvYdRt6JeRaeBDIsSJCGwwyCR2gUQghmWNQiCmg2SSQdWfV/wMIns3RE3IcEUsgJEt
kc272v0wW6e30zZKDBo/hZsWzBipTB18XNBTfRL0vpZZSjrXimw8xB0UmlBjgnQjIsOVHNyBgZ5q
umJMyLojYXspJYI6O75MzNCKSgQZezv6+nhy2cXbvbFy9N3tfw3Yubs3R0ORIqZH0TAmtFhorKCj
aLF4KikXc2iMakmcdj1DBcEBsUjAzBZBkQHHqOw9IOVpo1fI7014ZNXbLL27bPfKZstFcLOWL21W
8N3loVGNDRbmFbrbOETF7liGJTUdyxiPgLcgYEu9qmrs3YtFM1lLP0RN3MIOhIam2d27t0/dOnh6
U7ao+v19MW6ztTdy9u3w7U4eGrazVq8LM3XWb43Vs2WYOWzTTFm4ascfbl5kdOG6zl2xcLunTtow
U2eGDVwu8PgP4U91FTt4PuyXXfD4+O2imLWM+ZvJi1YPhop0zdMF3K7t2/k9MGSzRZcc+Z8ED4gn
2CGKeiAdARMdLmmTdd5M7wfuPAlHvkV16xRcbuVIsPQRESgzpIIdVaqkWMDCgr3ZYoAAsQFwERVT
nIFaXiQBGspYgUItleQqyCxENUOCLF3Rt7oa7tMgSlgBRLswOWox7aGdw2YRRBU57dbT2/wVQbC9
pJciqmaGsugjSGaLI2tvlxptf8td3VuNi2mtXJgqRWV/w/DBGjHoXtqiaPCnCBAQ8CUVJop1NjQg
bFDwEoVJgxMrN7IuCwV9bKpq6ROgxo6ZIpiKkKiFI3c0gIR22ahZWL3aJtJER6em70ue1Mnw6Zrv
uR87dX1cXHs2WcKMitJbRdGRcjQcAB8qRJAWi6EcsrIPhZZETbB8sMCd1JJMUYvI4gQ3HE1KmHHe
5qKbGRY1y5x0chqzkceRDQjs9LzPVnJN9GtskZekGfLFixcNVNWRyIFRzHnOqqrtJcLGFbpcucuU
3I5FSGJwhQSUUVFRy43e13t+nwR5nni7Iwe2jy3fJ6zClPrwJrnyKeryBFjrU0wU7jRIvz02Z5MW
N3tuw+jBIPCoPTRmwPlxoU7W8dSmdDa18tGO3Th1hLlbPV3b3vOO3lTM3MhjYzJmxraRfTZ4ilBe
UjDegvUQlaO5U0GMzc0HNgkVql+SzyvWACIENYClKYjG5V6aYtHDzZ03Nl+1P3Q1LI2mnXSuXefP
k8tNsMHO7VO3vBk2at27F6D4Qkcxh503LlB0hEkGRc0KljLqxqTKikY+KdgRI1Ixv9GrZynDgxUp
27Xbbbtnls9NtlU5WZN1mj0aMXp4YMTE8bSqxbtG74Ym7tk2NGymDZixaNimPVl3TLHFmyU9uGjw
3auCnTVZg0ct1nDRTNZ05efO67l25LG+aiG48Yu09C9onEJ2uruHzAHKJ4RPA+FPign5Ahv4W10b
h1hv3p07SebL7JqEFmrwoDEKD5LuoIkZbOS4pQqZLXOBNcDL1RhDhoqUeUCEIz3N0VgXcLAClVfK
nS9vjxTVjYThEwzo5uXnUWnhve8HRFs0cuCHctWBYEmLGC2t8kaI2oivp/S1R0tTy84pShCdWAEQ
Cg4QYIykiiSkXakwkzXyxyz41ttnWc6qFidNnCUJSS4qCeHhE+hOOdu4UUcRCQz8niEeX5P4cmTE
aVIiI5za3hFe1lvJQwImxchjLI3k1UcVpW2ORmZ7vfSSaEy0jEV8ePLF4id9deW7w7C6lNmWWbpi
/UjL4728Uyyg75uTlMykIJCm5UM8+RmKQKEjkKYlTNZZJNIQGhlO95kSxYY1FYwgRMBCxYTAEzLG
xQzJFCg5llAguWA01MVbGUQRH9EEbMgT0LmhjjG4hwVTMhkOalRS45weI2yJyREu2qWdHwfe13Uh
GYYkinutPMzOZzMxTM330MjQ3Jnl5fDl7eGMjz591S+81rikL8Mny2cPb06ZdNeTwzU6e3eLTwUb
63VwvczaunLOnuc8FiZELmKIfQPfqXNjkKPPBDa7vBmtAnoOPqduXlsfT4Z9LPhZiyemT5avbvCJ
50tCyqDvJiUiK7VpM1NkqUOW4VMRTcVygaGRm8ODBiiOsX6lvyEhXy5eGazdoulLrtXDpi8pdpXl
a2T4arMRIeFMW7hZkasG726fDpqZsGTHZkyX5fX7dMmLJswU3bOGLyuupRi8rtFLNF3CnTRdtDUu
6WaszrrFo4ZNXlo7ZMZ5Vw1aMjpvFHcuBvG+UQozNJ3APfmVQ8Yhgj5jqRGqMkPtFmcQeUGqHaOz
9iH7Ij9qRz0ZO/ivUbxEjF6w4KTzLQZhlaCxlJBGVUHtJTdzKuGYbktKARC7ks+rMtC4+QRpSS5d
sNNjs0RM5ZWg9FUVVlmqbo0txkrDibYjDeOjsaMDFu/zZG4UHw3L+ncT6Wm+iT340QgRFIqNEnF/
01MVoFWKxY1dTPll+L8Xy87uy3VnPMTftfz+LnIiy3a6N2WmztiybqhKECLTiO8oNBpBY0Hxqk6Y
7D2rjg5mYyEC7JsTfhpciKzwReQPTC0hMKiRPyUk6WL+/fC5nne1bX8N8bkcHsv1gHs8LIyYsU1V
fBFdOLo4XentkuuyMH5mpRLK17QYoRRAuXOW22shCwTFMzSAh6KamxwYYVwyMjFCxAIGZIgUpsQM
iZsZGGuOBtVm4dttTgaSIG2bIG5SYciOazIjIkwDMriYkYFDkPcWhsOGBG5C+c1qXhZYz1URzAuZ
TPTFbZJq7pJ2w3ZqXfHlky1XYujzSV4m4xEialKa2ab2cZcpQglnmLgPsZgmlS5Eme1EzsacRQfN
FFKmJYYsKcjM1o1FWLTHmPtZc2bAiakCgpEsMXORiRM86EzcjnajNm0SEI2ZOGW7l0u55xe3lwup
sg/g8X8W2dVGFaVbPv5nb0+Hc/dX8d91/XXp7dOHty3kngInjTJVvg0eljrVk9O3fft4bOGTpo2Z
m6nLJvvutbti6dOmDNyuxZOHtw/Nxxdu8tXLtq5cs3amrJ20p5YL4KaNl11mzNiybPLFq3WXfRm/
QjLlWmSVm6Ys2Bg0Yu++2rFyszbNm6mCmazJywOF4npg9p6MmLT6z8U/gR7DsPck+PxJOmQdy/5o
5Q9nQ6EfAJvuwfj8h5dfg8lXdpRuO2k9FzI+3/Mw8xavZN05/5K/KQXrODfF4xLMhBXtgCHUKqWV
T0JZU8Aid9wQgCBFHuEFL9gCBuuoKY4JSuIChATfH4Jyyrf9LdP3fZ3wEJ+2bh3NA5Q/V0/a34ev
aEZ5VzsOLl0aaiqj1rDBo/5u/Db+z/L9tq95ZGUZFAp1bmugp8lTm7cKyzZj9VIPvdOTk/j1z+4j
H9+GkBNEwZ/CbQZe9yKZqTVySjHixY7ZjI5EL6qG2NPwbKm7Y2owOhaB7hg7xGn1oa0Wor7ze6V7
iN/f/E6Pd8ushyxyy+j2zeH4KQr6uyoiqnSmHLGInv+sXEvurIX2ZNFL6MiZKJ9vJjmqFtPJyaw6
5DplebmauvLdi1mp0ZLYR7Rjiz06Rd6VvtL2x4cSYqGy8KqiLl3QrlKUgYQovHHHPrbwgq7bzQdU
QFZRLINFEaarqET7meqA65eTBooU51y8ZyExFHWNLo5ioIy+KhnoyJ3GcPSvs+pvIBzwWeN9GAqZ
59tB79K7vnlVfYssNMDGf6WZCC+tGQNFRHdrComORfOH6DJggS9hWlftgmJlCMKizsKE9xgyqpYY
qgb3c/T+E9cVmU6Mzwbl7cPBOnOQVVAxWqiooHe5+BFvMbrwTPXv1hRwahyYl4GGRtsNXKY82FWZ
FxN/Rbu7G9hwM5v0lWIESRHQR0EALQA8UUJebAnZUONX1KRgmlq9Ie73G1IrV3ITix9X5YROu+MN
+i9SZ3Ee+eEQoHYZ11X34IxniUdRQoocKeB1GRLr0VCKnflouCr4+WEwzRUDbpGmMqqm+rs0x6ur
LEYi6wYIIoZ8ttSGE/tcqXmxuxdHlHK5t5zmlvegoWWnfq/tVDbwbH2S9r8tILLhQfuy0nFlNVpu
2eLCkWQ7C4KISUG8Ozl1wGbq6EH5dG9JRtDYXK4TXU3Y2xrqMSj+0UFVEdTAmN4fh7Vhx/BtKqqo
LbHT+8WCJ3/Yd5sOYwET3q/fFX9HxwWBulEkiRKKKgInYIkES72NCqUCpTBaARP2iJEVKAD+U/rg
LZLCskP7Mht0MhoLLC4JMYUtjgEDBJMYsJBe5qEMRjFkDJCwMAtTLCi0RUeySCtotFUYxGMEJEEu
RG5EBvE/4dYidSE3kIMAYMGE98hBgZkKBWCMiwFBBBiioxRFfUQhSoxRZSEErJBLIDCKlFDUAqAS
NBB5uwRKEL3giUBRzsYMYsZGJGRkYsWIMWLEYsYxiDGQSMiMQYsRixYsWLFixkYjFBEGIKCMWMYg
mEMJBpAGAMARERAYqAIiIiIyCiKySKSbNKInt0dw+JQPxIhRAgRjCMBkFpYpS3iAFCJ6BEwESlMB
IdiEMkJMVUWSKqrHwIQ6k1hJcRLI2FDY6H4QBLGgRKRVCo2frPrPcfVvlC/WIQRKCCBBIJ5hNyqq
GnYgf0n9Qv9J9ofgfaKQCHqSwGR/uQ/gfpD/0n9iGo/8/cbRaKFzF1RsiN3ULDaZKW0gf7RbFxcr
u50ZJgDpIYGwXBwR73Qnp/0P7lCQVgQA4hSwqkIhAYxYKQCRVSREj1RShiAQbmTpAyf7qA/wFihm
MBP/rD6iD85CMCQiQJCBCBLjgqCU9EUHxRA6DnnQFOYBQw+icJtFilJKjCXlmLkMpPdTZEIohq0/
cQHOyf1f2Yj/vYB/NsI6DWgtjNFeR4KxE+GUykfpwkaXkrAaDCGdWYAzVA9h0UPLl4RiYwIBIsCC
SYJEYXFbpMUJQv/7V0dwpvHVB/mULBByKo2Gz91KYq/U2HiMuqXI0hJszLIzVErBJ/CloM5JCn5z
GSbbGM6SaR6FD0k+knRk1p1Wn5t00WkrJzF7GA2pcXQ1Pxo5IhHkrs6xeEbgbcCQXgzsSqMUuLQb
VcjoLuqdigLHXAZBsHPcFsCEBYiEA4HlQ6Wja2UsTWryDkgubNMFTfBiAEIiaiFRDq6oH+h/tA/u
wF/9y5ByUgu1doG81YRNkB6UTnOZQ4PHEG/ypRQpjDnGxRxZgatJ0yfvTlyAPIgvgUi9hwEM997H
wC6Hdk3rvPAdqngCadguekDMXFVw5iaOBFIG88ip3QU6IHJHlpK7v++PrTyJy6objmidMdwcAFhe
akwuhIYNzjhxqe6T5Flykl5HCjZGMfspZJUbZVStTgBXnsfmFdYEV1hoQzAoMo3GMaQKQ4b3jYkQ
h0WfDIckTgT3zSkQI8ssdBgaC4HzaAujrIpurWCOREdotcEwYmtTCAzZqGKSopRST9KItGSmA0rG
4sDAv0zaE6YHWpwAuxTkApq+x+MB4YPuSfb7j2bKItun3o4ScLlxU4UF2nVJMeQ48zEwFzjCAfmE
Nhi3RTAMVMc51Bh0i2oMhhkYXFuqkDqMqOVtQKTETBiPOLQBXBbM0i4G0TEXANxQ4oGcHEEhoboG
QtxaVQgBkHQAi4BczAuU0qBYpW4unORS/2fExXO0lmNa1wrKYxvtpMhdKZpKj7KS1hMzmUxUIHJQ
FvVg4Qc3MwUOAgiqkRX77HZn6Se3h7qWKGovSTeJd8ry4tKOF3cdqko80tHPkcpPRm4SdTLGSKH4
U0Z9TQ4fo3mRqkxSdIWkfYxkVyUu15WLycPtSfNJapFU+KHdS0C2tVNKAHAMVBNCdC8ZkY84sE3x
ecCh6Q4TUJyCwXBhgrvDmr/03UnW5oLUGE4zKpyXFj0NwLC6SnGY4uA6dvIZxlFIpuQxDTChwMjC
+CKU46BeI5xbAwAKPhTQr+9PmfrPpGo/rICYd/1fZcPshGTKqhIQYLI/P+1Er+M6XSn54Fz9CnR/
WfeXh6DYTDD55o/j9Rf5oGtw2uWSlvv2UWfgonr4ebQd2bV0hBJxkGyoFTv/crDOEFf8XwPcCN1e
EsBVvDym8e4DyPwm+BYDyAeJSKwPPkPoT5uTR52bPZkGakTYZlrqoWd8u4bIbKyDJUhP9B2bv+uM
Yq0YReEXg9CxRC2+uv3kQ/CiYhmJ/zmI15nCGLzGcB7S0mq7SekxkhvsWcNpGhmM2pPDZb9OvnKq
8niZJy0keCboZrSRxKiSmaZrMjlc9PuQ2JPzkPCwD9ZD+B6lClfG9Ij+xKR+CXlQkaKqiwn80Ce3
axViKtJAP100H3wOYf6fQGoIjfL/srEQVYttkUloUUWAxiNSttUiqCqCrBdZVwRkA/lA/Eeok55A
bSiNtEHoPV3hPMRoGqscgMVYrkhQ3x1RzhtpCFEO1d2GGRdwMWBLTqGzZu7dVG31VQFjYFXQHOqg
0IXUx/glJdi544uo2FAUQoha9GpaNIiICqIx5DiToJ6M9aMXhjIInIQ7WGhiCNPC4UyAYHAMpCbo
ptUBixE3F1NJIRhGUmK4O8lowO8jJnpiKHUniAeJh+MrER8RfM1TFcSlKRKWlLS5lcMWZmcEk9JE
holN1CThizj+eFRNFvBeTI1R5RxLQS6ZJg4jccAxEwwc2KquQJYTSQWVrZRRliSqFGnhGG1fTv1o
lMNzQYFgGQYIAkZCMGAwEE4kgVaBUE0malKVHZUkzi2NWFQo2bhD0vkW6r3BkGHgQ36YdoEOoyS5
kkaO1SEiyMYfyKpXqYbN7FOKJJsQ3AU7VTeFxzsOBTCEBklUkALBBRpgDZhvxhEHQBfZIpZ2po3d
bgixpEsUlQMmoSE7Tx0Iw8kiCvI82laDbVYgsFQ8g7h4x8JDcnlE7r6AOcFQQVtqqsRaGBIeiHfp
RFgKAqxVR6hPQHQHhO5qRsmM158OQqJ5kJoZGjNNJq3XC7QsHaEc8huuKmsXRu2Gl2hpvGSYrdDe
dyasXNICcCRdamS7RhByAoHRRCOgPNIenmRFSKLBRVBRQUFYgioKRVEVgsEURVRAUUURUEfSQ5sC
E8JGH7GcjE7hoM44aNltAtGieiGg94NSJSQDIaKikUqN7yReJ1OBoe+c+s+MpJkyuuu+vA4RVc2M
VfNFRAZx1IjzjhhhhJKDVkAUN8ySd7ORh3obMtkKcxDDJ0dr/GbNnWxC6+N2i+OuG2+2mevZxeqU
XiTGlQoqSTIefpCXoXxWiwRaVTxnUA4DANk9wFgdh8kqtTmJKdRVHucwX2hRMYcqpW84PjhfC/GK
SJPDQKhnBaqRUMQLtxM20jBksBTyEWOpKNLKnaJhZcCyi1gMZ5AIRDUmj/718BVzQWGF+d4tRSWT
BBkEdf2fJYTRnOb+Yt/NJ/TWHApqtbYWSRUtJ/A9RuP6i52GshrOwsG02gIgVKBU9hY/aVIlcP2k
iEIMRIWjpdsRImgQWMi7l+hu/rf3NJIFzlo7WctnTyzYs2K7dw1YMWTyxdMXp22ZsmbVs/QjFHps
9etW7p7eXbwenCnX5ycqmjnn08s29p2k+9Fvhw2X5YMk8tV2ymTpg8uH9yTJUrjdiXvo1enpTJdy
s3aOHKz2wYNmb5ZOGjhiuxdt3ZdZ795uZnRw1X0ctmCxd+T+gPTWOfavL02eWynLV0oYmbN4YXqt
XTZwenJs7ZuH3Purg5U+W627h5dOmTR26ZNn93xxw2WvZuyNF2LyZMHLRTc4DSXOveCd7VUlIeUt
5W1W7h/f+dr9MCiQhVc8q9untZd5fV957emCl2y75/kQpSqqoQZGBA5zI/TbI2q0NIBIh0wE44i+
DtXX61NdWQ8SHVpFZCRfjofHGwyQiUYfL2+qmz6v1PT+B+ptuqim4+j2vPU9OlTNP6b49NpJpB2q
0qjO3hvb7FrPDh7WfgZu1PbV+ufDF8v4v4uGBozEh+T7NJwuzYHCQ3GDrOB5gOnxQyEnhAOdjylF
QYMYiJ1qCkGgcQTbipsQkJA9bDIIWFiPvIY9ehF7EG/2e4DsM35dBoSxPQA7Ifg8yEMuSY+pntJP
u7wMfDqBQSAizoUgRaUJUQMEs4AIeHyaDJYl2Lzb77Lr/PSzQIxGJEWKqMRRGQYx3RMCzWlUw7E+
+i4ZlK/u/4dJZ2CCcQvAqSAgQgKhGDIbRE+U38TVCEJEICcTNBjhNAiYo7iHvFi/6RLMUkJBe4Mv
wxumqaLDmnbrXO3XbEXQGAJtzUS9JJUUGQoIxRNcpcJ8o/S7xSEj4SKP3vi1W/Pd78Qz0cG9llYT
YbDqAcMD5S+0wYi7EYLMAQU4CjIzAE+EsaluY2794uZq54RhFY/SGSmlGQygjaxXO69wZGcLEBKY
HoBdKFHxsE5XJXYHIESkCK64sICOCxTArI2zgZrbx+yKFRklSjULuwxqsgRPMKG/UjrId3ZOdGsw
V3f5vEYsMrTJeztJ1sA1iJNpO1KwiDi/nbFBEixGOHVUBwpql0EPZ3PuzbohIIR4NSKelHxKhsdq
YjxBlPqgsekE3hFOBkSdPDQJDswH5QHqPoPgMT1bwN73uCVhe4W8F/n8E4vdhoy0cVrW0qxgJa1A
paKBKsxW5n5kxzzPqTMT8T8zvImAqeH5n5kjEtjTDflF83cgKaGZUzPzJilTU1LmJIgMWHLnmVMd
lZuWrpq5XaNnLBTVw9iQu5XcuXVnr1i6dtRIXemq6zy2cttsi701XbIGZA0KmpyiSNS0jMTQ3Ezz
Y2FLGxIYgSlg1enThuwdtNP2yTHqlKrDD22dqenbgxaOWb2s7eXgu/Ld116bG725O3lipd7dHp5e
minpsdtmzdszZNF3p0ybKNXTyyYNXTw9LsTFmwUzevWC1tV2Zku6WdvD5kSQPvferqlYvl4aNHts
xfDys6ZtlzEscqAdBCMg8dCvelnwv8PFqOA7z5cg9jmjR/vmmZkU8CBxHJuJRR0HGdpgXOIxO05y
iZM7kgq9xU7hELdpkDz86EZKeZmFTMMTAxMzA/FZ+DwyZNXbh5Y7tktd06eHnz24dMXSBYYiOMep
gMOTJn3AnAAe1JHBkT9nTmSIvb2+GeelVbNmp5fRS6nHXb6J28M3S7BwWg/OSjWbvlqp20csHw2Z
PbRy+Gzw1WMTmKMDAwOMq5u6Ot/V40QO0Q3xTmBfSJzqqocoRVVDAUPSfo++xrJAiGoLUnglRUfB
eQNx7ADxurkH0JGoA0ASALoEMfvhQeBeo5U8GCaxCYpI80SkN+NAFkFjDBGUv6aQ9bCQxkFkWSPp
nHYCjEqHlLFkkZuKDLOXLKsRodogcQiW/y/A+wXyA8aDqVxTUaWqAqVGCQesApIQmsC1qPuMSyvM
IvsQ7DeLI6+AH1CZAuk7VfURS3YBUJEd1AfiVF+uIosgqJIKgYO/uTGgswAiyBWwFMk4i9kfHegB
qIFRRObXZVDxb/Lfxnq9Hs9eBoPKmnzG7gsIhRLK7y4q5Z+o9QkOV6eHhiYMVy7w/cu/E/lZv0ou
IgfeSGORS9b8WlBSR9xYcoWIDikwsWMQ+jwpspmwZuGzJywbsXhqfgxfd++y1uHTN4ZmjBux4drX
ZPDBy6aHbRiwYKbLPPbVbvvRuxeXhy4fRHblvv2wZMMPC7tu2enbw3YM3lZvvZo8uH5w8unLZ7ap
TA9qUngzZumTJ5ZMFLvSy6zRS7lpfzM17Xus0YNSzp589PDw5ZrPTw78KzYqYuHDBoomDJg7bNnp
20Xk+z8kfxfT8ofRu8NXZRtLFzfLENx4XrIBJcscvLrV+PsVAp0aEZCEIQnh9NuA72lwHEII+KHr
xq+ijkPIvRUc7C+ZzPYKcyJzOh5kTyGIEjwPMmWHBlIjJ3jBIoYECJ4FjxKCnsMTIb4otT4iG0nF
2cvDpswarGDy0eX6lnl7k3awnwVVKUlVFKNn7Phi9tWF2DwxWZlKbt2LFw+k4+Hpq1f5EJ4SfckH
8CDGoepivr0fRXkOn3OzB5eH1ZsmzEzdvrYf2ZP2pKnr4kfYFclFRTgESqDvOOEO+Qn/EgPtj46p
qQ0ohCoYhX+5kk+ZNIooHXvSStblnCN0eiffPxg0n41MdFjFSKoi+6FtCYaqLohCwl+7QCjCb3Iz
t3JGT7hZRh/tZ8/4/ub9VLK5bj6uA3k3xmcDzwUcUaDqQjyiAHh4VasNJxovGJcAyTedaHV+oRPY
Bww+QoY0EKQg0kKEklgJQbEoDCwSg2JQbEpGWCUGxLIhQSg2JQbEsEoJQaJSMsEpGWFSlIyhUpZR
SllKWAkoFSllKWCUEsiFglBsSwSxKDYlBsSg2JQbEoNiUGxKDYlIMlglgliUGwqUsEoVhSkZSVKU
GlQpYJQSg0SkZSVKUiHjIB0B9SEpOKJIliIpxKqoVRByeKlE/IIZoU5QOqB0on6YojvGYKZWQMOX
QVGPZDPrg/b+MekdYFjNEjLQZI9Mwutkf9YXqGq7RHbAqJl/31Fi5ACLA+QfaJ1/R9se+gxHIQ8A
CPlnKpp6BD0EYohCISQQMJISCKQJqIQaRpmBoXgk+ghQBpAgx4Oc/tb7+lFN8dqBrQ/ECbTAjA2q
pIemzZQWOyIMSmpPmmpo1SiBUGgGpJcIwMn74sYVmDRpEbLSqRktQDRTEAw2klBNjDSEPbmsTCnA
OatVm2aCOiTRZIZHlOXSKxBY6KG2WCcDxuUguxlQLvP97wzZHVlG0aGR0ESogwjLe/5ff83v+ivo
9+XFkcJrNpA8fIUq5jghiAJhhjhCJGhlqtmVtUrZktRDKpbaIWhQPUhhkpkgv8XCbjQZnyGRcsNz
A+MMSGs2Gg/9HsSIAgbm5yIn9499jg0MKV0xwaEtHWnOrFxiQYHlgxpXlgxZPCzY0cP3+uZurNZo
fLN4U003dtXLtdo6WYXdLuWLFgyeGDs1XdNBjUzHJmBcmKZ56kiZM1PhOf5iU2JDWKkCD27YGDdd
y8PLyzdrsWyzM1Zs1NG700e3Sz6wg4XZsGTwu1c7q+F2Cm7J0+UJG7y8Pj43atmT2uzf2EdNpgsy
dO3pou2UybMFPaI2YmazV2u2YPHi61sGr20Ontnn9xLvCfDl5aLsHLd0ruvFlDVY6FAE+RLDYS8D
sXE6GiksILClYC5Hpwp5+WL4e3y4RoxYvqs2YFv2flDwV4/IUXhAv9v5BlSmWL8KLqOA3zWbDqO4
aMvwV8M2L8GWT8T4WWZt5PvQYvy+78W4kKct2rdi/Jk1cuX4NGzjjBq7d+VYNHx98Zt3Dy448Ltm
7pg5Ni7Zu/uynpUn6JE/kkSCHyJBYGONYpPD22aIAgBYEgXw6cPlfps5fD4ibpExfDJ8tWDRu3fo
nvbunbOd0EIRQ0A6jQnLF6Agh1x/wUhrQFZdnIIiGAV4ap/zdqvHsVjUSgAgkpXwaTf00khAkYhq
XNHYfW1B8OAYGM80AogxHaU3n4mTPsP5EOdVAOp9j0iaIAHb0F8Dc8z3HeHY8Rj0PQYPIPaSInd7
pFDwG08BY60TMTgFPWAgcvKr+cSvhZnjDj77UFiQj0geKKYwO+B4rFB4qO//Wnxj0i0LNP5F5Adq
D9rA9TPGA6+lBg6fgRifJPl+b/ze6PIchR4CaxQPTynGsSNzGcswkqqkqqGtXYRuNV49yHxIclPW
Q+SBAn+aBh8CHaA1mesF5BPca34w1CntAPCr6Dvi8WrA8m0IddD45RKoWhZ3eH8ns4THK/CaOeNp
hDTCpuQ0B7xyH407U3auLWq45oTvpUcJlvibLnY/jWnXe8404Cu8pDkEsZEIuWIJNM443dBkE3o3
KmIdTnbDtPHy7bqKZC68jNbzVNHEaWmJVkx36ylrnAX2F5r1hnrK574d53vetoksiOLyWDRlhlr4
iSWG+Qqc/liOH1PWcrBwnuvyO3h0hwiPQcekJrchLlgm8tA4baE4cxaN/ZQxmUC5mZ6aq+VUvEkZ
KummKzvlJjhliWcsL6rcXScf0LQfYEIkroCINPBkhEl5bhHr48jfS2qbCI9TqnPYNs7D2Z37G+J6
ybmkDhrAvFmNZ5KJ7Xfq23016Ktc8FHSKYWUEKnhYewaLlHI6LnMtooIh8F3A08I9dRFCn05PpOP
V3EKJCIUwgEuCpkEJRAAuI9giUERn8jPBZ/MJ5fcY7qpTc0aobNVTfrPe0b9zA2u2MIRWYOMSqog
UTGMAteRZ6ucSOf6PpAP0/dxP8pSfop8c+uZznIQCaraKtxsVpOKDcyLtLlhBEEiSJiMPNPkKmUU
JbnfQtgURl99rCCIIIzhDmJg6xgB4Hr9TV2a2Fz4pUJVdAViqeiFqoNNAIjB6weUqRQuKE7Shxau
wFAWAWsKUyRHyUHb6veZ38KapidA/B42cdn4GLlSzPSeM1DCWT2ukkVv9WYkND733KU/Ni/S/mZN
o/FdmJqVhSHh33yfxfTC/vbrvbCfPnpDY37fjLVcvSPXjtLCnK+35X9e2n8dt+gxY7iZoKQCHQVz
wPb+ryDc7GYdihUYseTHgNUiKXFGGDMNBiZIWBIc6FQ/3k2cWBY2ARAcgKZlxSBzlx4+fJW7+/k7
r4zv5SJW+fhD7c6NOjkuumrUvAy0yxk7N7OPZ19Jln8fKOMlzbC/tV8375zjp6tTtms4eMOjp4d1
p5uRXrKE1kLSfz548qRrZjx6OZUvazSl5S9hEofMRPU8ioxIcyJlToXLBXqRJkzzJmBmeo4eRcnQ
UqTLnfUy8xU0KjLtlzwyeGrBs4crrLMLq0ZOmhNWrBmMe73MVIlDYsRMDQuXHMy98yhsUKlUE5XZ
Mm56PSynl0zdvRZy9OMlSyzhT7T9U+40Q64r6r2ESZNfKdPk2UICtCuxeUkkqSdgg/xkq2Rgy1Xp
EYRJFhBgRHPmQcUgB5wWALBZFRZFirFVYTfgFkAzfMJELgl2MkC4PhNwYAwZY0NCqWBImlQUobBk
1fVkQgXG0kZFkYDQ5EUjrYhdEvDMgRHFtAyHO0jIyMJQRMV5Q4YcE5jrLu8WOk6Dsd45uRMQkcHY
6kDExMu5P4KKzIIsRRlAPVZSn9v+1hJilD8P2mUJ8Yfpe3pw8nbB6fRm5FiRMoHAOenp0PsBEzQT
yRU3SpoGZ6mozi8MXSzFT7LvvfKT6QfWaTOcMUOZT9tlKCgCA5RFSnyIp2HnkMqcAMGeEAxCAP6K
3q7EoMUERT3CIpTxKGYiShlB73zepBfEKO5xBliAiNBgspH4xgVCKVJF+IADPYv5aB6WHlB20uMw
iV8+kwOJJpFgcCJrAdFF66aGYjQ1B12omm9hvfXHDnmHiNh2h0EOg7OzoPvZMWi5358o9OZJD0Kl
yTp8Ifk4fwOOGw3HAbx0A853G4X1ac2wi84sUH9AvgoUwKEjUgDHt6hqKUM1RNT1GPBbtkfbAOTK
zUQn1niQtISic41Wjc+H62Tw7ZdtD9vMbDQqGIaTWbB4A0lK4qeVJJSupSZgBvoHB04GANE1IPjU
QbnqekW4vIal6yAgHkWCg5iTMeo2EiKPwezUJv5BMFF2lw+F+U+r67ql5JCSRkJGT9gHEhtiJ2Ix
VQ3hPWl1FOZWA0hYuCfZGEhsNiQTJQnzWxwD23WQvuBtI0VbBLKiRWBIhj8Gg2e6HIQ/uLHNPr+e
mY55PvyomK1H6qotTpEDkB3W/tET3b7a/zCJXgFA2LmUEYMYA8rU5KtA7JLF26R8RHGWDFUGwOLf
5Zh/SEJfF27dIqu+B9YizyiVGUKetyYWpJWDPj/ky5WAjA99BtN5Q/UlEk0gwlpQmq2QD1r3Viyq
VHKQFiH9FJBSoqpICnRBdB+LQgU+ZUEPoROhpxFXvefUg8gweYxR+cS910Y4C+qegsWKKlJR46Mi
ve443MGcLxylzOedCSIX8FnkzCRJOL26yBpDnr8+a7ux44u4Q7ym/RdCQghkKWFCkiwLUiz5kRmT
eGOconyMNgXDRBw72AAsigP0RHzkFkDWh1cy8pzAuYMUQpCLN6wGQb2tWXqq7avDhFd5NyApfgL+
DDErgtRCBJpIUQZKFeWyR8Z+FynCT+sjJ7xAVx2oy1dD30FDlIJVT7g9YdO/h7HC6641vZx4kOSS
+cFEEE4yZ4hUBEvJC6FiKF89q2+RiO6wGtYhrkjCKwgOlEWNQsbI3ESt4kSGNgsmgOozoCH32BPH
zCIMWAqkBBQWH2T8B8VzKJSlhUtompRoRWQGQikUixgMIMVYJERgsIRYMIDBIBFixUgRBwFqdpzo
wWnsYzIZ2tSOER+V5I4gOaQWQQhEQlnQhw5Yogdbqdk51dJ0HDcbCiDiuhz2AQSQI/2xKYwIRBki
pKRFRKhBxYLJMwNobVT9SSFkthmWT926cjx4oIiqLBgjFGCQFEFAYKAgIAEQZJCrMDxWAuFqQ6k6
YDwOsROgA2HSrCJIQ5BJUSKiJAIxCFZBGAUElQPhEDK/y/ZkSsaE1REFzWQyGKSfJmTZJIhIaZM1
QuWrLpL5Elvl+n4raMAX/UzDT0sIkYjEIKHB6SAf0FFAUQjRS8++R2H0H1H0n1FjuOYLmBYsYn1i
MQDYkTMkD+QQ/kKHQ+ovS69cLkjQiRHIjHlo0eWimzyXcM2CmjqTJwpus3ZKYLMyzRZuzbPDy8pq
7EhTN4WYs3CzaJqj+NLu2bw2bGrBZs1KaNFP29PTpo2eGa7hg4ZMkxYNmrRlMJPK7ZszMHDVqwaN
GT+x/Yu0bqaKenbywPD26cPCnPObJqmjNspuxN2Twp4XeGrw2eHbJq4YM27k2ZOXjx+h/SQ8N26n
g63Uu2cPDNiZLMnT02Zvp9n4RJvFJRw5auGD2+jopzMzg0MByhxxGxE+QJyuKYmnvHVdnVB1RPhC
hRKn81OV5+V5dnJD7NXLDx+D7nl8O3pi/FIu3ZPq5an0ctn1ZsVmT7NVl11mjJmwYJr/FBXVNFW+
5Y0as3Jv6dsXTZs3dNnTFw2eN1asGzJgwXaMn8yMW5wzEFPjdizeDNjjLN22spUiiUP1fr/HNXpo
7dMGz0pdi8np+iGLVmzaO3AgzZFHY/j6VOxMFXveI/KipH1dqU93eKpWbBVJ78JCmzACJ0KG0/HY
C5G500UUEiorL+vRUQf8EZRh+oJfvBJ6pD1QG2AsHhaibApWxGiDUBT8+6jg/1o/+/+VZYpSlAfX
AZBU31IIprLtRnrPIA0NhFgsDloyppyiNQbnG3Ue5SJ1KJED1CJiB/ZH5EeP4BUAOVBOY9Z4zhRH
0wNEUHbAGQQ+zHLIKqVdaq1u2SZOUCRKIOus7IgQ34WU/GQA1unxiziv68TlZs4Rmi+0QazGprlZ
g3UVSqEhCQkklI3IpoU1nDpcBdYsPwCaRCgJImiqVvvQ8FfLscEBAyTVxa7g8yOqQWRBA+7OLAKC
sl+DUDVzBiQFCQW4trWaKn4gO21xUvcC97i4K4lBLRD1BqELl8oJ8qdB5XD03EDBahCkBgDAH9eC
jFXmQhsIHlBNcHAjLwJelXXUpeIi9zT9CLws/ojKoxhUYxgYrUEcKtIHr6FVH+dVVCKbeIWkyD7F
94mCFvmO4UxUfh+zxi/n+GfFPjsVI+yu9BzQ8IPnAO0dwGBOYTiKPYq/UtoNukQJwKgdJ0jQJcBQ
u9h+3hsDwFVRhoiAon8kp9ghNBSwYSU3vFLhTSQhRRXVDTgYbmYaRnspMFcYViu/myHIzKUqFkEH
KGMFN2m2vv//t41z9S6y0ikFFFinRZRHCB+1+SyZOd3GV7tSsq8tqTbmjRkMVuYZNkDgQlE4vgzb
qWrRoJR0mMwSKGhIHKSGnlwzf7+j/T+5f69/mye5nXjQy+GC+vdFy2csqLlLhI7ZMylvMuGXdcCm
7P7I7yz9SHnqnognUMtGXigb/X1/q52Eh3RTZGMRgIVWBOydhMYSfm4lR7WVme3jNLg9n/gyVqTS
cJ0rxp7ZABQyITs1JuNSjGJAqEdvbCVIqOiOFhTGHkmc6D6nHj9z8IfhKUWEX5oGoRPSC3fsWYno
aF+z0CepPmIfT74v8bAQ/Q1nuEmwYiSYhkSB7coT3WqSIm2HKqAwTGiiixRWMQ61IRMWE5YRSERA
kUhMITRCGzFmMw+D3ifxuw9ye8fe/acYmvVIIa1FIAuyMCKbEuqgE2luGunrrIZBVYyGCJuYspQ4
P2o3BP90kHFgi0QQkkCQILABQkUgiApFEZIoLIoCwWDGRRQkUBZCJSHeJx18VnXzMqyv+JJxPMk9
H5SCRiixPoQKigb/TkMZ5z1Ga3dkbP32fCaqaFGMtqlQWCljEs3kwWOEqNZSvJlraLR8zEHEbgqc
ER4iH7qCoxkVLVWFUbXbhb7V516n8JzC5hFNJQrkac60lFRkJInVga4HedwmHYj+MSjVjwlE1lK6
xQwV/TQh9oOAneYDrRUeQWhM0PQP53ITYJ5xOcTv7BVFm8D/2ooYeaK0MCEI4EEb/BjYar1CbfMJ
kKH2o/Onq3A/hQeYT8zwfmV8jvAaov7lQ/j5E3sSCVAhre4H4utVVDiVVQ3GIi3RwESjIHyxVaIo
nVANIs7W1OH+IC5C4hlvCW8nm0iJ3rwz00Tv7+P4e+jEyg1kyYwlqCy+zt2lNYe4wS1eofQBnZlV
Rf4P55mwEmlRYIztCGey2gIaeJTlPgBU414qUDWoAm4VEOkfV61PbBBH9J1iOkXqqAE1hTVUpIkB
EgZgCIfAm89EJBHhiyqqLBEBRbKUH4bWCkFRkwpZD6tDJ88/wbDbtEF3QtpKtY2sd2mWFYiVRo1J
bYg1Uu7CXmT4cIFgIHj7YwWVBLqULWxBm1sJ63nPSb4BFUOCCKylME5VHwIet2caPlD5XcQCC5rQ
Jka1HbIgjCKINiGcoMTnVLyXEPK9rigGZ2HH46ErxKahFDweFbp9CVbkMPaPIZAHmg4CJE2EQ0QU
4YOBdQwVVQ3gH0kUjEIQhFhETQ9aimXzCBHjOOJCflUFOFU2kEwISQjGRjJIeySuZRfNoU2zYRpS
FFePQhykifVPj93qLgTGBE+OZ+1cLms3Stp76agZYHifxB4BDfvzpF62K/2Qi+8gj5NgobK5+eHb
55pDfOcamvDJcNQRbdFMZkLDqHPA2V6BN8Xf+nhpbc0bQt6KKkIQ5T4DeOAwTDhiaEJCH3FV6CAH
pNe1sH0nqoHCqyEgVORgGjJQogH9vT+DRqT+lhK8DAoyeA3LbQLbITkYcYVgxep1/DkA4Ef4f1Wp
/ZcciKWX3O8gc6wLutPEUpQS4EAxw6h5vwr85H7RE5BE0Ip1GXcrGCJACQWQAILGKCnU0tCKlUUk
db3TqKlRPK3sfWIH4H3CHkQD5XkOKwehm49owZEMJkPVJPRGSLAEQURFkRRhITWVyliX8Z61jqPK
LmKIOdOOCUg0P/PC3IBcF37z0rHWKb2VLYhluaATGzcolm0+dVVDIDJHEIYtCnbtV9qG4PQTv+NP
WflPkFFMUU3IB9NtK2iXZaUVcuUyIwcYp/d9OZuUrYFREP8h/D+SXP1sxZCTENZpDA9xlo0I5KXj
UP5MqjoYQuyyV9p+Kqkhl55kMl+9eMEPKLOOK8wtdB0ee55gRPf2qgHh3ijxiJhoB00OZEDWL4Av
+wHxAlekGh8yQPOL/viI+NXZ4wnvCKiECMZEBhBPQeAqMWLJ+kJRGQ9omgX2CWPudIn5V4D/knL6
wHjXQCa4yEZHaqet2txfjFPhR/QAdqYo9r+0HgF7WCnKJuX8YmYB/IHQL4HbFZFCEDVEkV7mFwWC
pYBQ6gpwgmB/mYVbiE1q/KJvib4gYHykVmAkEo+PM6F1/lPsyPJ5XmD0z2hHfQNiNlBTLScf3gcQ
OO8J+47RLLpFPNnkdKvcciPN6sgDlRDHATy3NPl7eEXvEglpCFqlApJcIQEgMUymYVcZ52wmOszI
LmZhwMBiCwJkBQ0w0kxgYwlGCLUsOCS5hpkNUoesIhNJCbC6szZqqIwQyDbIttEKKnhOs0HU+Yh3
kmpPeIeMgEO0mSKi82LGlLJQEjCAxdQBSUikY2fSiZmvheY2FjShcRC6fDD8fTSupTOvULtEAj/Q
hrTWISCbSKVhWgeoT0mKQIdpGSMJPMRGhk5b2LqidsgREB2XgwW+LCnf4C2QcehlsBAKoQCzsmGW
wqWRxpkRs6CsSSQ4h17PrtSIlYU5Daxk6EJ1hJIZPApOxBmIefn/kVFSi2gdoSedCUvaNFENbqO9
dBBCJo2RYQKF7geShlC19+L7XmOD3zsmNIkt4h1Je7+OT9iOYn3E9Ij9IvBksF+I/yOhBETi+A5w
o6Hm5ErkalzYLZ5UssYQCa7q4GxDnISBkjvicol4OUjGMiOqWqlVVf3vxLwXIKUYQafEjQrgQwES
DtQ2wCb4v0FFnq3ItiTeTJWnWgr1PVbFrqLW8bkOxEDeaghmCcgDoVHe82S+QH5hEpV1CJDagbzx
SCqIoqE4kl3EO1ScCv3APP7TUycktI0zbfsEsL6TUVnplUkSGxcnkno1TYKMNMKZOecg1GLFRpDR
CCchYQhA3IGSE+0ggiOzhVpVSS6P9WA+I7AgmWW0F+NG63dTM4QjBESJEEEFERBBGIxEGJAYuwE1
lltAYBFUiNhShKAiQAgSEkJEEisQIJp/YJ0m4R5B+gD8YVkJgOgQe1UzBrk6YgkBghGAgQLi0tAD
gfDgiJQ7WjmyLp63eXk1pbkHPhLnBVDFCJMqSqppVYRbNLhzb5AojCb6YrhEECCEHFpBKgPJ4w8Y
GYPkMUnKGwNsJ6RCr/hpYYJAUUAV8qUWpYCtvvYTX6LpT7ejMxREqWRlkApNiUERFPdCwYZChCZT
7oiBkJ06O1CQ+m50VFViMzKqqk1IRBkf0dyEB4yo6EFusihAAYNowh4c9JCF/mosQg4acM5ZgwUu
Z2b/JkP0mGHGqvEHHwkYJDUAYOcXUIn3CJcdAiY4Hi9iHFgInMjFD6EQPjK9wB4kOodRrfAiBiRd
y4CJawibfaJgusdiPtSZDEQOIuJ+t0ahkFZIr1wIlIUp5QIlRP6Pf4W73uAaIyfGvwmTZo/aZJum
3MjIMJalLBKMLLZZ8rrDtgVFIabaPYtZ6/lETsQskQBimKzQUSFAHIoxXtU8aLvJziJx/UAgfILz
Fz+qgDAGtD88dqEF6xYhrgodoL6Es9wNkQO9fQb4mhA4CEEzdpDbRUvYsH9e9hYcIxh3sWnFoYto
19EZtkIaAFB1RbdGEzRCGiyYYImGcCP/w47EdImykIUwS5mhwNZolCRSV18zLvHNGxlmNKOyoGkM
MgYsTIakjLmUyaGhkTEdVVSSkiURJcuhzI70bjm6dj9OzDJPgYfdE4/g/cs4TsMLWwBGvlMxlEcg
FpaC2iUSmK8CQQveUaRCkX8VlGlRgiX9wh85cXAfUJ9Ap6RPiANqD4dzpgLDwYMU8qtyylGBANSS
SQIbi1yyjREEGQDkGDQwXsD/iGQAYgE4Dr7pPu1ak+Rh9plgR+07QtShoSO7rW4b0KtAgDlhKHtO
jxZQDl+uSfqRbnFX5Fs7UpEWAZV8fzhpoOJIw5IMZCkDhgLUjECgMXJgoiEqWUkyQJDJTIIwksT8
oCEhJJUFiAXQAyEoX3IOKXBXyCUGJNQF1ZGd0KI95Q0IkelA8ES31tSpnZf2/mzxqRzIjI0Zz5aQ
aIj5FJPoUffcP9ESpiUrD9m85l18Hwr98pXIsQXi0cwMpxnDvPyNplrPSQh7hnmQBILB+0IARWDG
MQjIvCPiEIFJInE1QtdBi9QicvcLMQ5Mo8w2ETtJCJJI+5h1oKENP3E3o/MjmPhEtqETLRvViPkH
wbUQ40iBqEp8BNDIEHf+JR9fvExT5Hp8L6zNU0+DA8B3YBZvcvVixa5xzkDk9oNBwwJBDABeVILg
A1S2QApj6YWSYIkgiBrISyCUwohAwYChJFkAWCwAfGiwoIBl04AuJRIjAksC2iSyiKhMGFSCy8G7
aosYN7ANCtlQYIQGCUFIywjYOxlEWHxJFhqIGzWEZgpIRWF7oiL0SBgZJLQiHyWKoAXGGkJt3lsU
kJJHwkISRS0VGZpImSTOUoqqgptFQDa8tPfcJkCfEAdWQ+nr77qkbMCldPDjcE4oj/sQhWoaYcIH
BqUzCHMZTIbIzyQ0gcv9CdMlTGIwFiw+dldhDtOXowWl0wKDAJ8inhiQTYjsHGIQhgqi4h4hPwAJ
onIxAJqNjZPavynklwYuoggB0kRL/VTEWEFgYJArIAhUpUl6Qgx/0Gq/1/wJ5OQ7gqJChOsn4EXa
Lmq+RPY/Oqr8Cn4RToTmuUfTpP8E18WyflETlVVQ3EQVgJcd9i6rAl1VUIa28CWAB0dlIVStKUNN
CQhQpakKWsjGfqYGY/1T20mSRWEVkFgQgHSRaEOdRgiUJAEDwwKFhGJBYoqQFm3RYyqBl2VYk5hE
5QcUUwFxcDc0l85EjGIoZk7lQCKlXHeOFM8P10WON5QHVVJIEF2BoB5okJxsEdx5BMxeqraQRTAI
fBZX6k3CG+RYAbri8BksYLLREiEgkySUF/ClQwwbUz2U3DZ+Ekwk3H71McccEygWsIENY0g0sUKU
FPPlx8ZFbry9IuYWiQkJVNfErw8RmwcA0piGgQYqJwPBt5hHGbxsTNRJIxRMyCJcUd55ijltZwJC
ooUKe06wrveLgxjekGIPuV3Idguz1K/eL3omi4m7jiIceaciwJE40WG4xcI6hdz3PVwAOqMFulMg
Ad1ennKNaevgBefOrtnhs0RRugUeRMi0b74ILkKvnPBh8fWrsCSRJAkZJIxVPrBZ6hfnumglLApW
wJqHEQz/HK9BW8eg+NDjujgnRCNBEoCLEYgHKxVYJ2uk6b1c+nfbQcTdrJkY8KND0kPmgftST2z4
J+lnPwE/Z/qob/qSV5Vydr2ju10RcahBUixGK4AUiZiJj5QDpH3FUVQd/3MwzKDi4IuAgnrE90Ce
B5H1B9xflo1hiMFU9IifxiJ9Qgpw5YKZEXUJ3D4T2+c8EwKDtvCwCHlUUgg02qKtkhlIIlinYuOY
CSODLRa/D1Vv5t1V6ABHIjgkZwqSGFqtGG+J6HhV4w+0d8BA844ixfGcoaUfkjz9GIORRA2RAUsD
430r4Rj2oOM97m9N2IQiEIoHEfwhbfF0fxAGSlzdrxLhEkFt8Qcon1prR0L9EcgbB1AZx4kkIS8e
lXH1p3es7y6/yokOgODmPOpDoiDxDKdijmyqp9rz9VfkrF8rRwvI+xZ3ZU4v4GoEjMRoM5ZfOInn
ESggiVZEfRRZn+yyeQnv7TuSdh6S9oySdTxn5hdI9J8x3iaAShgwZPy0Aes8J7T4jYd+972mNExM
LcbmfEl0sNPn+8Q9hAKAyMOA+Uf1Yn9NB/IGHafhEHwj8vvRpOZ1DvnulWq1lyfX6k/NBmh+CP8u
e3A+zyM0lNg/fC/s+/NSSpJ9la3WKSqWAfjaxGQKTVIYaIQUkFJJEzifV2BTDaeYpTUmppsBSBxq
gVZUsrLaUbaCW2KWlYKSRYYSJimANKsSoMaQFbYQFmTPmQwjAT/LNnadHRlm+jV5t0RRdJg6qQpv
MrC5E+WmTfDJwVcUWIqgTvGIbkKf2fi/XcT/dFMDoE6QfSJY4h4xsgdiTBzHdOz1Tze1J55WypF8
8VIjNnC0hBgG5EkwmwmMjMjLWlZYKJLLygYFRPa8ubQxkJFzGRGCT8TO8RNWYux/wZIgW8YYliL9
Qh4PHX5CSUZUMo/2f2/zORiJ7WotAOwU0nnQ+A/pdYnEczjQCclByAzpRsoQbguBZYN0WsKpAuMz
MlKApXMAtyLB/b/M7kn0dlPnV68Ok/I9JCCGIuSrE7Rmw4TZDotQFxewRdiIQFzeuSKA6VBw7jMp
5WEUP2xrAD8MK1wKHE/DYsu0qkGXPrey28BjB5BVIDBQAs5DoKNBLB1Fi30T6rU4EZ07krivQyyb
/EVAoiDQVdb/rgUxmuAeEDxLDUGMNDUiinixKF3YhS4wqMBQmz45ScG6Xckz9DLMHRDsfUImgRLC
qfUIkVALKDwg9b1HMrxmoWGCwkBE8AkSuA8Ai0GGNhQw94BZTrX7yjABJlSRqFJGMICaEDKntp+g
J4MPJloMGeAGGYP8Qv0I+8TI4NBXCLNEEA++mkVCCDIqJAUPIIkQDwoQgAyACSLAkRisikMgpLhE
y8Yo1hwRDeNpFW1cYJ4hTS8uIopmKEQ8Qtiks5CrEcxQ1AhAdQjAVKfqRpDO9KmyJgLCRFuEOdAa
hGDqhWJAYkLChZQJBsJa44K4KpUESBg6RFETFUTS/ZA7bIKHZop0mhLHQJcQLpIokJHphKpFhFJB
iidQkKUSCJAAtfSKDoDPOei15s8hj1navImO1u5etUiElUjBBF3IC0AS2oG1FQCFKBdFVWRsQwqW
hFVV+YM0/mm5AhUYiTu2sRwyIM0kVk5kPuyPvn8vP1uS9tnj8tNJqiqAJb7HWG2/ympJqeZQ9IYb
zOXTgaFqxghbKHEe1Bkrv8IM6QySIeokuAmc9Yca57EmjCiFiceJrQJqmmpnIebwLCc8LrvbpvgO
HSQOC3Qf2v1+mCBxAJX8Bf6DFGUsrdZAwOosBmXozLMcbggdxt7aHIhmdCEMCjQaZvaqtzzfhYj0
jRcXZRzSs+7jhwT8KHGDAh6723y6V2DDyRLoFJAIiXYgHmPe+RwBO9D8wmB1Au7cZ7Zx23ctzgmD
wYlFjjqqxx4S+60Y6ZouolwRIIlkMQSsSmoMspRRXQuaJkAcihghxIUrRYXsEghodpq3JZyKwRFh
TrD5Q/f//0dJcohgP50QP1Ghb8S+akKA7mQM7d+kuh/NKgT4D2CMYiDEBAYo/IEO04m1/maMVe7R
U7buJ9LVZxbOFaPDX/DbOUOMu3bDWqGmWG5YeFzzncJPUyfVFo29D12lE3sAXEFMA/2TIjRMfLcM
RczDFG28r+9niUigffUrALQU/nMllKewSs8A9UuSB9vM2VioqrM++XStaMWOn+JSymh/i9usIKMB
IzlKMgW05KeOQJ54m1Q/TxpvraQgSULB3JlakxKiNHxlUodm+htvcdgVhISxVJQjN6322ol1OIOK
S2mF1gwD8ooG55FN03wEMZIH3JL/c0I7SsT6WtKOi1len87bPDD6BOQoeQAkHbRUSxu2QOVKV3hE
xKFoQUqKKkDswNKmZmTba1VCAg81gkIEhDkKr+QT41fyCaEdonpUBMwyZKV2v6Fvf94mXxA+O4HE
/WDQvFSYgW7dLXFzaAW66xmQbr3Kyu4Fy0LkMwSyWgm10H1CHxQD8UBOwqEYjEVQijIxijBCAKwQ
56adIDoSgj7jpKJufVISWb5s26C5dYCag6uzV1ZU1PxBJQME1BJ5c8EIJfgfmtqHvJ6ZBWRAE/sw
vywkiyQmoe4nx6Q6pVVRGqj/YUFj5NaGyIH1XsBz0eFQUwUojDUPBDBIjAgcBIhwEYLJNhUCwfsP
kUTGqZMCcEkjIes856NqisVRQr9v5q4WaRSsEEmMUVR1oyaBDeros9eqjLKqJdxrSjEIx4p2BB35
q50sm9kB0mK7zE6Qd3cJ3X+d0Fz6DYlkhIESMUF7HRj+PXf3iCEUQ6qB3hDSgo0GZyloMARgPdTY
agzjIa0aDVm58punRYc8w1Di0LmCRl5Hg4gc9H8DO3R4lJyYcpPnplVRBZQMiqMIT1aAwHlMc3jM
8y8Bz0GMwgG+QsZA57aRuiojzd6DubO3e8KKzg7VYpUbBjIIiJMbIfGbiutEgATHHuQUtBC51rk3
TjvnrYUQ2ldsp261ISVhhzuwGQ6v58NeVqeG9DOU8OMwky3hngSeGYenMxiN3h3fLPDjIaYlBbEY
0sIUYAwNeNCpqg4ZBotA/c6aN2G22Ju2MFwzC6RWli1MtNcPDC0GJ1Vt02lXLySbb3eKQO6oj1Dg
efAW9b1GalaJVkRxZpldR6EMnkqCyHPlTqGp2deW4G99tzg3FEFBQDRxr0uY+dzfabKly4krPFC4
HSMGZ3fAzkENeOElHYmptk4113wkDU561MCHbsSaM9+MGSDJJ78RSrzu0Wp1aqLMK/tMuQdE8t+S
m6T0dr6OfAdKUPRSMQNa0E6GHbkpjiYw06d6vCaKWFaQyduxrFdDbd5aIvqGUekm8DKcGjgbGHRr
n1bA1x6BExXAwkyAQJQGSBpHLW+O1MsbugmOltm4qqoXcN7eyQLEnMJ6RMAdV/UUeLgCI6eyA9+R
3ChiIFQy+poQlFUoBpaAr0wME9W7nGZDYmjXGypg1X7hCHiQh3CIxmGTSR5FSNkzi8T0YDeCoRoI
uCTUQLrk3XIg2FbqlEQkNCXKLiqXKQMc2mXYDIRI5OhSwhdDDhVocHQUMcUs2UIBkipkDEVOYJMV
iagGu4gkYM3MhzDQglgJaAZJBMiMI5sQxRtqXGAsjsC8Q035rPd1+K5afm+G9rmsXOwZopvJwUDq
ADIIPkxpkAC4v64IyWgCvQcQUinkfE9QesiIohWEs8wFSUbJFVEIZXtYnD1HPWcLZNCJgIYrATQU
aGIWZ6i9SgoJed+/vZwSU48dYwwLw3b69mnIlECSBa2NhBI5gYQEU72CrhqAwRwkc6xy55LITIXG
9xwESxgBCyOXAkA8asBfFAjwl4nGIQ6PykoYQMQNiAXbEEoYRIhgfAIfVNkPzDBn/rQe8g4QHIBg
PnDxhYos/nnsSQdOD23OYIL+w/9UH95YtYed4IIcp0R7HrXE7mdm3dGWTLde34KSiccjhUsUAQRU
gwMPKIlIFwPMC/MvvU1bJPX7SqtCMCQYEFxisA0Y2uWxVgwcQjok9Dgx1C/NQsmbHFIqkQGAqkFS
CgKIqoLBZI2ySxkFRQEBlllAYkSyxoE0gP8v/WhBZVAMiKkqlPkgonKBN8CCdnbT5FhyvJTDvg28
nfUN103DBpXUPCfeR4hOgED2B6yRsP+46b+y6FeIitcifoE0c+AuOCIsAqmxUCQES6D1ncAaNbyC
lPo4aSQ4kl0IWstQkiInVmOuZngtWzZaxNmIeEocguGEyhJaQmgWCtNrgXuHg2C6gnNsMrVVUw29
Ke/JMsMsm8IzKSNVVEGSsLD69mpSKcTtBE7A4Ba32KgFwMz8Ok4WBjGQZGQhyJIyMhFGMr9bLJiV
kKkikobwVvNguXU2k+Mx+KlLK2DeTbiqqb0GaBEhARM+UTYL7kxDYfJ5wCz0RZJISRMUON2hYUCy
EFghBY6hQpFN9gCUwAE+EBQw7m4CURQTiopQPMhF5pJIHDVSqJ15lwPrBL4KicMEFLiJyMuIlIlH
A/GLZUMA5chxROPChRSIpo/kAMFeVRMHdEOZHwiT1rzJkHPBE2i5XCwA8AB0ulM4HIDDxCb5xHEc
JsFekgOYsVCQgsRPHlljNxwRNtwUE5IBQArITigCNWaHsPpAV/XncxZvv+0nzH4ptADn4pNUSIju
I/tCJkLiYq5EewgZKidiABzKwHH6yhQrh3cXHYrgve98UxCKuUIv2ijY0IUial8Eo3+R6v/e+hgd
blYB+JdAnegV4SwCYcmKI2iD2kP69YITU1Wf260fWGsjSiPf+xkgBRcRQ2ny+oAwHBZW+FUI8hpA
e0YE0YFa4lFo2bAcgby/eIH9Avj5RNqBASPSBjxspcwFTAB/WTuVyuimw5n8QwokWR3Q440IwF+A
SF4+JiazDqN99YOsB5xYGiCyVAE3fwsBUVzqBChCKMF3R4RdZdMIBrdjmdfOJ6Am5D5gn7PIA+Wf
EbPsiVotW1trWX0dIgbs05Qf2Oj4VeB6BOsA8o9ibEDjdfhnX4cyVivzFfQ+7JLTFh9NDUS0CgTA
gwgw+wsX8x1GNdF7UQLQ8J1K/0H0+/NeU8gnQL0LnyhIQivhqmF+SQuWHIYgzAE3FgiBgKrHAttR
b/qvkBiPmFO0TJ4pxQKiZEQPeCfPxBxFLdOQBE6IKBgYZFjAiCMh+llZDv3KogsmIVGIiMYRIgMQ
f02/FZSGMF/U1i4f03EUUUwn+UzIZAbbbS2iPLJbdJ+LLDQKT7PhMGAbc1KurIGf0lEUxvy4CQiC
TKSINP5DJTeudeWzvksdo2c5tzslJ7PXIMgyD2gTBURhJBEWJ94BoxJBVAopoCD9qPT1tjfweoUQ
fXf6hPahZfUlwDrBihCLuZuoSxYIDAoQNTilgPtR0iahNgD/OCdnuR9rm2yYpDysKNlggjUlC9Rm
IX6rmHlbHdEajasLagypWC/j9Pq9OielIcp2ahBZKkBtkmZOsmMKJk+6FkhCYoowgeokYFirEYii
RBBBmz7cD8kCPnE9IB+MX6RDQaIGvWU0rAEsUwSESJSRSBECKjFsBp2gcR7lEsCfKJFOENxLvIaR
DVEUOkSzkD0wvBPB5JVEpqBJJSvgeeiqr4wPx8Cb8EoFH/MyTf49TbDvrZgxN3A8tTBBGKMYxg4U
4xMn13UYGiHGQLAyFRP55ZB+uIlkGB+a1Ce7RVQaYnFvHOayK3FOKDpWAGKcR84p2IWHmwW2AL0G
YNzQdYmj8gB3L9iPhF3hdiHJDKojsRJCUJ9AffbPkmw3nnltyKKUYfZMm6zNxNQ07G5B6hQwZ9Gf
jMNJthahYDWNIo2yLLyUrCiUpzAuTApToYYTsYEk5q/u0YYoDoLUuel9AnIj5w+5+0GnAzB9zY9C
BcSkxPpE7ADI1pslE3iSBZ/U+RwLNxEht2oNPtHsDbb64omgJE8QnP0A6QflcUyIM/WD3cOkECkB
RyE+HkE3H4XgR1B8qs1+FC46RPYjmYgsGcIyVSjE3NKotBBBwUQgugiG1XnVxE2mIHBsR+bu3zQc
hfIXhFtjNbvyOiEslMERIooyihREKMUE0HwkYeoU+KOHBYFQYIo+DdiwDaM7AYmDeMeLziDrNMSY
X7bSqETYTAkmWCttV4IkIrrACl6WFRGl+VjLJzyiGUENLHIgkEnz7rIB4ZPPMziCV8UxnZOHSUqN
9AHmXWDbSiqGGzXe5cKQyywsVnTCB4NJslhE87uAOgT47iYOJuE8KHiHBwnBHfZtETaLxgX9qkPS
8FoLMSxBnJSUTpH0k/00CNCfkE7HZgC6zZ5BO0TmEx3bO8TcASD95+/9iH2fwBeR/KjqJNIhYNw/
qgnK49nZts8wBg+akfM7HsBOXyCAmneYnejrEwHCkOd8iOtTAB6DtOVN4ROkX8atAFuUO4TtBs9w
lI/D8f1IIfzAB96B+0MB5oKj/zT7i1AkYMyIfb6/wJ/0f1QMAZAzXXDRBZAp1rruo3RvQwkJUglQ
QYiXQu7METCAxAx/+C7kinChIOGwa+o=
