# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080724055504-ril13j8f5vdkel89
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: 9035bd89bd8d56da840e7e32479d8d691fc3b4d3
# timestamp: 2008-07-23 22:57:23 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080724055433-leolv8zxt7jj6a11
# 
# 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-07-19 21:39:24 +0000
+++ conf/common.rmk	2008-07-24 05:36:41 +0000
@@ -269,6 +269,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
 
 # For hello.mod.
@@ -276,6 +277,15 @@
 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_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-19 21:39:45 +0000
+++ conf/i386-pc.rmk	2008-07-19 21:46:40 +0000
@@ -46,11 +46,13 @@
 	kern/i386/tsc.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)
@@ -248,7 +250,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-24 05:36:41 +0000
@@ -0,0 +1,155 @@
+/* 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 grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_wait_after_message ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  view = grub_gfxmenu_view_new ("/boot/theme.txt", model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      return grub_errno;
+    }
+
+  grub_gfxmenu_model_set_selected_index (model, 0);
+
+  int done = 0;
+  while (!done && !grub_menu_viewer_should_return ())
+    {
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      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)
+            goto fail;
+        }
+      else if (c == 't')
+        {
+          /* The write hook for 'menuviewer' will cause 
+           * grub_menu_viewer_should_return to return nonzero. */
+          grub_env_set ("menuviewer", "terminal");
+        }
+      else if (nested && c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+fail:
+  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-24 05:36:41 +0000
@@ -0,0 +1,135 @@
+/* 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/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>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+};
+
+
+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->entries = 0;
+  model->selected_entry_index = 0;
+  model->num_entries = menu->size;
+  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);
+}
+
+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/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-07-24 05:36:41 +0000
@@ -0,0 +1,694 @@
+/* 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/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_view.h>
+
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t status_font;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_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;
+  char *screen_title;
+
+  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);
+
+/* TODO Make image files referenced by a theme relative to theme path. */
+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 (100, 100, 100),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  init_terminal (view);
+
+  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->title_font = default_font;
+  view->item_font = default_font;
+  view->status_font = default_font;
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->screen_title = grub_strdup ("GRUB Boot Menu");
+
+  view->model = model;
+
+  grub_gfxmenu_view_load_theme (view, theme_path);
+
+  return view;
+}
+
+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);
+  grub_free (view->screen_title);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+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;
+}
+
+/* Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr, char *pattern)
+{
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  star = grub_strchr (pattern, '*');
+  if (! star)
+    return 0;
+
+  prefix = grub_malloc (star - pattern + 1);
+  if (! prefix)
+    return 0;
+  grub_memcpy (prefix, pattern, star - pattern);
+  prefix[star - pattern] = '\0';
+  suffix = star + 1;
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  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)
+{
+  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 ("status-font", name))
+    view->status_font = grub_font_get (value);
+  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 ("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 ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      if (grub_video_bitmap_load (&raw_bitmap, value) != GRUB_ERR_NONE)
+        goto fail;
+      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);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value);
+
+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;
+
+  grub_free (name);
+}
+
+static char *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;
+}
+
+/* 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)
+{
+#if 1
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    return 0;
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      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,
+                            new_substring (buf, name_start, name_end),
+                            new_substring (buf, value_start, value_end));
+        }
+      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 = 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,
+                            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 (buf);
+  grub_file_close (file);
+  return 1;
+#else
+  theme_set_string (view, 
+                    grub_strdup ("title-font"),
+                    grub_strdup ("Helvetica Bold 24"));
+  theme_set_string (view, 
+                    grub_strdup ("item-font"),
+                    grub_strdup ("Helvetica Bold 12"));
+  theme_set_string (view, 
+                    grub_strdup ("status-font"),
+                    grub_strdup ("Helvetica 12"));
+  theme_set_string (view, 
+                    grub_strdup ("title-color"),
+                    grub_strdup ("255,255,0"));
+  theme_set_string (view, 
+                    grub_strdup ("item-color"),
+                    grub_strdup ("0,0,255"));
+  theme_set_string (view, 
+                    grub_strdup ("status-color"),
+                    grub_strdup ("255,255,0"));
+  theme_set_string (view, 
+                    grub_strdup ("status-bg-color"),
+                    grub_strdup ("64,64,64"));
+  theme_set_string (view, 
+                    grub_strdup ("menu-box"),
+                    grub_strdup ("/boot/images/menubox_*.tga"));
+  theme_set_string (view, 
+                    grub_strdup ("selected-item-box"),
+                    grub_strdup ("/boot/images/select_blue_*.tga"));
+  return 1;
+#endif
+}
+
+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 struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view __attribute__((unused)),
+               int item_index __attribute__((unused)))
+{
+  /* TODO Implement icons.  */
+  return 0;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+  int boxpad = 14;
+  int icon_text_space = 4;
+  int item_vspace = 16;
+
+  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++)
+    {
+      if (i == grub_gfxmenu_model_get_selected_index (view->model))
+        {
+          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);
+
+      grub_video_draw_string (grub_gfxmenu_model_get_entry_title (view->model, i),
+                              view->item_font, 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->screen_title)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font, 
+                                                view->screen_title);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->screen_title,
+                          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);
+}
+
+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);
+}
+
+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 grub_gfxmenu_box_t term_box;
+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)))
+{
+  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;
+  int termy = term_view->screen.y + term_view->screen.height / 10;
+
+  if (term_box)
+    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 * 8 / 10;
+  term_target_height = view->screen.height * 8 / 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;
+
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            "Fixed 10", 16);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  term_box->set_content_size (term_box, term_target_width, term_target_height);
+
+  /* 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_box->destroy (term_box);
+  term_box = 0;
+  grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  grub_video_delete_render_target (term_target);
+  grub_term_set_current (term_original);
+}
+
+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-24 05:36:41 +0000
@@ -0,0 +1,45 @@
+/* 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
+
+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);
+
+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-24 05:36:41 +0000
@@ -0,0 +1,50 @@
+/* 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_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-07-19 18:40:28 +0000
@@ -0,0 +1,51 @@
+/* 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
+
+/* 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;
+
+#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-03-26 12:01:02 +0000
+++ include/grub/normal.h	2008-07-19 19:24:29 +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,12 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
 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_entry_run (grub_menu_entry_t entry);
+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);

=== 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-02-02 16:48:52 +0000
+++ normal/main.c	2008-07-19 19:07:47 +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;
 
@@ -476,7 +477,7 @@
 
   if (menu && menu->size)
     {
-      grub_menu_run (menu, nested);
+      grub_menu_viewer_show_menu (menu, nested);
       if (nested)
 	free_menu (menu);
     }
@@ -519,6 +520,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 +538,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-19 19:24:29 +0000
@@ -24,6 +24,7 @@
 #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;
@@ -364,7 +365,7 @@
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
       timeout = get_timeout ();
@@ -468,6 +469,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +481,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,8 +497,8 @@
     grub_command_execute ("boot", 0);
 }
 
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
@@ -513,7 +519,7 @@
 
       grub_printf ("  Booting \'%s\'\n\n", e->title);
   
-      run_menu_entry (e);
+      grub_menu_execute_entry (e);
 
       /* Deal with a fallback entry.  */
       /* FIXME: Multiple fallback entries like GRUB Legacy.  */
@@ -526,7 +532,7 @@
 	  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);
+	  grub_menu_execute_entry (e);
 	}
 
       if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,12 @@
 	  grub_wait_after_message ();
 	}
     }
+
+  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/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
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWQg8+38AZnh/gHf//9//////
///f/v////9gfb7x71q828tCbddJU5dd7NWDteHl9al32w6Hu5ZYTvCvrXoN60pvUmBSVeNaW03X
ubtm8vcfTQen2u7fd1bn3zPRlgHyoZ1gpSAo5j5veR2tmxN98qoFvfN7750cqZsnefe+n1tqVPNo
+tSS6yttPYved4BDPcA6XPd0oo5sBC0xCl4POffbyHs8oL6kVKkJSAiqOnuN55Gm7nNsubu3cPJ4
CqLu0iI94ZyAzhejNHeQPUV1s7ykvvuNfXfXK5GCun0OuhRgejdAJN7ijqLWHRZjRiHW2aEqUdKx
DYYUujCjoxq7cdABpWUrCsNd2O2C9773I8pH0NS+O6x3ZboOsnIUM2NUu23VyEk2zp73uD0JQQBG
gIQEaT0U9TYpppPKT9KPSZGjEAGmIAAGjJoNGnqMNNAEhTQCNJ6SPKaD1AABo0GTQAAAAAAAAAkI
SEjJCU/J5JqntKeptGSe1Gk8jUfqj9UANimj9Uep5JpoGg0HqGgyACTSSEE1TaNI0NU/U9NT00ST
9T2pT9Jk3pKPapp5IepkxNMj1MNQGmmg0MgaMIkiCAEGiaZDRqYI0eoaAKp+0Cp7AQ9Kfqaeo0no
1M1NI0Hqek8p6gFSRACAgIBEPUaak9TZJ7SR5E0G0g9TQNHqPUZAaGgAAHiH+B30GuQgAoECB/TK
SQfb9X1Yn9oQ/2xJ/+3jP1loVERn1Ws3ZpxiIKSoVAQthURLErAP0H1mZIoa60xh7msn4kqCihu4
kN9EoLMSYknTRfxMjDWRFJiFBchtg3ufsfP8n3/15fBwyb1VcV05vUI4ZVPqem3OW9R2Cvv06IUk
8KqjlI4O2tNtsFpnnOud8ZtxMhDeO6fQRzYHQP7Tw967kSO9ZdpqCVxX8t0LNV0V/3NeTdjz76Rd
at043q8vx0etZdOZpXVych1JUumwopy7hUiO7LDLWVnHO47nxbmad2Zh/VZeKamM4US+TXi3SYkH
/k/h/iNU/jOXXxb/s0m9ZqvfCXjIn/CLOg3/VV6jOq09kl/lio6qlv5D+YjCHxH/kK5lD3lDX/xY
r8OHockO7tT7vKV5G1U8m2/tccmW/WequFjt69rLjs3uPMOcYiSepAoxRVc7Tq5qCoGnry+4NBQn
lgJpZUNWVIDlzUJhO2XtRRyamQ1xSRXy93+//Fy8P+X/0afJ/v2zyXy3FT19m/Ch2TrsZglDRfM5
Wefw+DRTGupsKXfNKOjGXUIGzGS3Tztu6YrKryWFsuFvVjsqElwR04quWwUVSlLpSa7TtaE8zAxD
7iQN4dRiMX4dumIiKp0YMRemKZvcj01mMZKky3ud3BBYWbY5G6zuyayIiJbNuWh6ByYLO5Iah0EH
YPNfHFhxTB7JltkUbqOKIbKKJVZrUDAm7+YLne4FvuPnyUXJWgixXWz3aCIJyqiG3jz8eO7EyIJ/
SEKJIEjGCEYo8kAsOqUiwFUFYyIon8hagyR8dBhTK7nHebA5/N6kiJ74dE6dVMizK3ph4J8mQdT2
PJnJ9Dr7WnDpz08aKoo6LTQsDWFiejjPFrBmi5dRtoth6jbSMY7kGzRMrabadWK+lqvK4yZxvemq
ZxkuFHy0NdNbPGsw1bsFtqCmCGRkRgbGGC4u22YVSnFZSMG2GDbExig0zV2Wka7L221NHF5WFuZi
mpqpxM3wwNsJwyJjbOq6oi2yFTm4g8qpKypmU0W1OMLOHfCyi5AMkFaHnVlWNshyorQzTQ48HZvt
swwOzKpuKawo5bu5NRWBozTimqusldFsTCiJTXdkD0absJgxSSLUqYyTZBynEE9yK8qsjjB0A9oI
fADAaB5QfIA5I7u/4p/rv+Qd174VbAeSDwDgAUQ+BPIIoKVKMkxkxosRQYB11URx+yk1RH+uJ4uh
0LQB6KCNT8W9awnCPeeWSe9JJOvBASlG2BxcwKlVRhBiSXUhDQLOLPW1W3GcsmXFlcpeznVHVysx
XBIJqLFG9AbJQd4bywibSqTSADdgUnz2RAk7PV7mVJKzskrOiB3OJpCLDZkDZxDqgHCVCbIY772R
QxILMYBiBSYBjQij17vOfD6Wbq2dg0kkPygptknHQQRSMkWLJ1LRD1ej6j9TybeHTY5dMQ7CZfN8
rI1ZZVMniuVpnR6xvFRXgyFDU7OfjpLEaN9XVXIPLqsTju2CpqcdZgGVW2NtS3Ym2l2bh5BmNWSg
bTlyEDkO5zWIEDUZs3Ti8GVVzkzlK2rbIK3kAtThtM9w6iLkTSzS1ly+hZoNLreW1zDzcRepnTLk
C0LZOpkFot6MqYnVDYJG98xQlDsABwhMmF8ni6GvzeLzQp4sdBTcA1RVdH3kyjKWTJJMG/DJvDeS
DKG+ZRGioVVj0zWQYwdtHUxWw0MxoZUUUUuTxdxoxJINxnk4ZCpBmAfv+Hf9BUPc7+/z8MsQ6Yj7
ICfFMIqb+AvJBDriknVVBCSAWhJ720r1QckUQfXoQ64eCc7ejgc9/ZfBFkBwiw7jQdcVOQqmQUBH
X1UqqgRT8EVEYRBRLRFGQBBvqkEIKiiBkEsAWIB9I8esJaJphZ1EqAEyge0JYVki8JRBYNRkUoyK
chbH/KHoXugS4A/2g3NItifPS8GCmAlyUSoSTnUEFiVENMQEwigWBoaQkkUSj7//1+k5Pff38vs/
90PwsZFRiqoLv6MnbqbHwno88giCQmFVlRaSJaa7C2IcEw0HY5+op8Z5K0bUTIJUPkKHZQVWE9vY
vqe7u6G2iYnY4+Qz4dcfivN6nzkkoik+j/4x+hh5FFHsCU5CFWQj9VA2N0QnS0YGZoPETVG4fRaS
DBjMmh5/Ymn2HbQZprpUbzrGgtqE30X5qSsjJKrF1HIF2t2MaTOtmkjYsOYRoUCwAXd3aYTpHYB9
OrJ2Nzcz0ka+U1C7xYhmenszYDffnW0tW8hqd4S9RQSTwO8rnmLUpXza5M0awb/9/4e+iIHRFQ9d
ZlfVLLps2m8vCrMw1BW8c3rG+KTJSZpLXl+icbeJ1jaS5BsVWnZQkCiFlCIgqcQjmg8E7aFzlPuj
OoQPxoQDRUiW3xxW+yFE5LQdK2KdVUkMynTcoRl9ZdJofCwsQzfO7w19rRoTzHHJOQnI04SgMa0V
0GI3d11hBkZmRmZWZw0SnX9Z5JwaTLgqqZBmOZkwYUXvbHTu13IqilNwVRatd8ZQ63qqBDOdsQGG
axePSzcotqTKUjUNWMLniNMNQrn6VJUSO05dBpyXG63OCvzBAjoMIQvmy7SqpT7GNSY4OtvRgtU7
hsbKnwYjzQ0g9Nw7bpxibbFCyHPESiD62VAiKogxERWCixQSCgsSH1N692eOimJUnnfg83xePXAc
FIaYGGUqFGDFMyybb3hyEOUorUZlPumVhhwLtjA8FWzwxcpgm7JwmWVT0lK9trnmUpW1LjVow6TB
ivMWK7Zi2Js/W+B9ZKmB+JlpZZZZtTzR5vmKcuUipAgge1FD+JqOZEOgXSyQsgKLFFIRZCLFIsii
JFBEVYLBVAUUQSRSRGRVihFgCwWCkFgCwFA/2mpD+6y298kgjCHxWKIMGIsIoIyMIh8SdqQB2RRT
6/m+rm2fWfRz5uWeZjxy0WxvkPT7OUNYvMxIhRIkhCEWEI/Kc58ac/eadqrjWH2+p+gObBxZeWr0
VnXJ23SPSULAZmFe1cmWHBLbqBBTWw2Zzg2e2HA5gbpMwWyOgBAfmUICFIRQyrGyjRrdW4bfiHyd
/DvI40HGlY3GilcCVYla3zhIE6VFHCjGxhZe0wK9M0aJN4uFKVYam4Lm4J6NkBPQhHcB4xoBE1e2
g6c+xk0HyVoomRkKimScHb9HZylt/aInG20m1nzGkNYTSJCliOgGTlZjwbOdoVSmpVAVlMh7lFVg
3UVyd+JLwTa0taJwMLm9cmc2O51M2LhJBvBahGlQTZRGxTcMApSTBYvy2IT840iB6wbOtLLCFpEN
KHYE/ZacU0swahrYcMpsZK6lVIGVUnchQQg4c84EbUszi0NBdzGnhp2nYsCOHjYrlBTS73mZeaY9
OVd6VGHDdutDR1z2gDxKodb2Z7AuA7RhGAFACChNkLN7PxsstqHUhbWMvT0XV8DiSOClnmQoNxqW
GOmJTQWbK1vIptlsaLM25cucIJjYUoDWSdUioNjUiRtkPioVVFQvmzQOYYN0wmN94wPY2KgnW63q
GFEcDx9wtxc8ubbUb73MqFdsV4jhYdpSesbOjtbNHhODUSxitpaiZe0X5FsWZlNFzzS8TZs25Xyx
tnNt5psel8/bsaUngl4PufFO2hVPgMg+Z84frKJ9x+plAyzJsiqZOuZG5OX3zj+Ugyy9Zyg14Zrt
IqkMKCs7ACw6GSqOtJzvDEl+ArF0KOn4g+cNdkGpo6FBH+3eBncqOhU9l0GlYQMzZDZNrr+bEPgo
7yzgl+RducZQzwqtkO0a5Du73D9VShwogaDswXNajttkZBdIQRAdRwLSlkbT0iKMoaQovGGsLOI5
aDlY9EDkVRUUBRYSn8qKfTONiMa1TuBUL4OycrfF8KH4j8oJ2PzgmbQp9qfP6/mlQJANv8kTxJ39
28Hza10CxNiJCjbqubcEaGKbHcULmS+L17AL1wVAFn4IaQKkJg0sAaAeMAfLQuzlRTayB5s86qiv
mjkqSTBaF6Dz9veMWBk5gv9gQcB9oOdlDBMf+r3QqWSeyCDmC7a0C3sZ2GUca48RC+Tynq1bh4Zf
6Xonzg6Dyg319IIddkMuykoGOmAGOaDylgcCLIOMoI+7YGpz+7pt/4B5gSRfo1gT9vi4de4EqMdj
4meBZm59TR+g/JJ1BOHUyK+Krej3KfKr4XiM3Pxd9nnyCexsoxZzSJ/GQP1//PcR6x7eU+PdnmiI
vX8oJ3R2vImfkFOqnyLxHSGH8WGBIJ9oqCoV3/aaO1Sh3wLro2Q6aJnIEbxLsrgyEXqsvzi45GQb
3C85HkdjAun0ggVPqhX3QS6HYITo30T5onAUXOYIoiZubv0lMSijBGSdPuqg9PYac8g5pxt4oboS
KbyFqoLEkIQaqufg0m0paMPUzPt85+d0EJ/xZaYQNFZCm6Ta6qCKbbbWD23NxTyDRRXZSE6hcriU
xmZGZkUqpGc35Ddk2TqfTRbS6GTMG5NFyAzs0Xw7JvyPPY63cdhbXls2IV9mvMHLAWrFomnobEje
UkN34sbcfBD3ekATsW4J6KZWGUz7miJAbw/Ps81MlATNRFzfkuWNhMK8uaG7R5CZC5b6MuVEuiWM
3AE/rrfu43gQO6HED1eySkKqTp7IxbSKQOfM4qexyFu+peH4t+c7+1HzWngdgL2IYMfeANQSUosh
6UWSmlVJNhlFMDFnXVMiguZyZqZVo6ubaJc8Mmk0np9M4D32aOUEm7Qx2wzN5TfVkvcLDbFMEyJi
gR0l8IFGoZJNmCqqhCyZtJgaLAuDIbKJ0IBJivTU9q8JqCclQ7fkIPVFFFEUb45QVO23mxmZLMzJ
bcMvbv7iT1wNjw3DIGdUUY7p49qHOJYJ0OcWAhIwEgCqIGnj7vP+hzNIatZDvYE+RqnYemWGKyBK
Cv5ncSE482nkzISsNOHCvBr6nQhwSMIDJEIsjmQEbEIZSpEqKIoISoqRgJAiENvg8u5zkmjhpy8f
aiby15GcPSA2L58Sxpl0eUylrUeWzvBv1lgaZqyyceTAkOJEqKL5DApCiXZIT6c8JlzVOwJvRypq
n6ohkuusdkmnt90LqXZ9fXUVsy3NHY0UPmXvSuX4eRWLZ+qz0gTQcVSVEYfnK191QNAlOqse/ciL
IOBRRV7R2XyuZPC4p5deIHzy6ukIIclVBYooKmQtyJnckmbI4u/Sb+nucwMoOBbYVoOm1ievfhkX
mt5YbNpuwy3g88B17hcMbzgidgI3wOeqOGSWOrhFG0zkmivKDJkkde+rMjgkCjlOV4Gs535VMOJ4
NvAhWce49Fu04CwF6AWeFtldj7GbzBzhRNsLnvmFs8icAZoB+EGJss46nZZtSpctsnYEEVC0y5WX
Ydf6rQiuTt18st8hMSzUh4rs+WmZRHSHg41wT1KpsaoyMMmBXFYUYWZGRHRG6F5jv3YLacuybFyK
E5uuxvOXYeTcFu4HJJN2HKHlCE98RRRRRRRRRRRRRRRRERERRTl4bHZ3Ooc1SDv51wxt/mkihCD0
bHXQLzE82F5OWxDGYNEBUQOxcwWjl3noa+lJhPQ2R520LIMchDILdwIsh4zFKfy0/NL4uN4Y0gQ8
z1VfVRe+Wykpgm61UwKffxvTjw9p4ZNUi4shkMrdX0weaFFWPoyqiqg4yLsOVimw94ea9YrgnAz9
6lIxDnmO/0zEYFXUpTRLLLFlvFkvzU7231bWxUSjVyyUy93uEQEkmfeYNCCWCuxkgm+zAmWNPnaq
wuCdAQKSTsTHJzkz3HldOKTsZjmSwrcBIun1ZKN3Ggqgs5Cst+SLDvzjWw5bCRtUBElmclOBUKZX
gS1ydAUK/WZQrSIvQQQVITyFUWbHU5Kk0nnvQ1iopzblUh0+2EZMRVRlWScv3xMurmRkvoY7l8c8
8mGOBfG8IqmD+iEgTRCF46k2SBrl74t5d0kAgqeC5WU5dpJv7O+DO4/gkRhRT9M29lQEtBRUIG2j
YLzz7V8sdCpkXU9w6AQIrTX0dlVSCIjViMu7gmCL9HTkdd+sMN7jOs7d5bYQQmcqyJcwLpkZUbVe
7IE8DPnlk09hRIHnz7CZ38odLmEl7edcws24joG+jJwZxR2boju7OjujNkebo5tVzx8j8GnSkiQj
gePnRhv1eUHjiePkNpkJ0E8lJSMUSIl9YTz4E+Pbbj2xnvgfjL7uvlP/YRgSCfbJFkAkfuihZYRJ
AgeX0/k+ivwN/Vho9/Pwi/q+rQWKujVxQbIW4jFV4efp37fZb32939tYt6exp0N/XXS+WzUgfL40
2x8EEKIIfMPXPIHm6N6Xb4tkqxgT1kvRL25M8lxRxFfP58jnrulVDd+ItklNSpL+p47ZfEqnJhxC
WvB6vkfc4U7yS50YPeeosWPWfXGzudjZU9ng5+4+cXXhozrqXnrOYsh2dY+Tx2rGMJDMNtB6N5xZ
9drbcS8Yv0VncPXLjq1tdzUef21bZstxoq65tvO3C9OcuR4XbnXs6xtxz09loa89Ou75R7cwIrwT
pGMn9p8DOUjuwrDNaBqfWHhX5VP5L9WW8zizu3zMDYZ1jK8r4Dyo4rzH3Al22aWZ1WvcbNpevLVE
JJj6kTmQ84FCIQioEiiGec4bfk8nbetOrxePTo6J2+Dpx16u3t270TzoneI8f45XlVD/W/3T6cJ/
oxLJmCqiBYtYFL/LgSFyWQWCUYUYwiCU/4FLRmGCSxCkqf5ocZAsgMQkgoIesDkE60Tagp7G48RO
cPS+AoPMaSHkpPxLsgP99j7iSA/fytcESkKFIkAkSKgvvGK/JjPSPr6wIQdvrfqo9z4p7+75MrZA
x/U/UbW2mee7Dv8J9ej6H5PYfXv5d1JJ6yAniiu2+lLKiHgJIEitoihsnhglRUf+5ERqIfZh7PDR
cF8UzIAD8BEPsCAJeK/ojUkkdBJD+eIdUIcIENn0siIAWJAxkPsgIVCCyHDJ0EJKz6v5x4LUrFyw
hEUfza/Afklfq+mvZ+G512vb8Hw8b8mdHN8ZpPBDSdv7q/Df1G0/fb8sDetFGXFvtja0gdTRWKjg
KKhOnv6/+p2yPSWM809BbZ/UwVa8byYditIT6aLlyVaVQjrGGrMkY7LLsuvd+/+7/g5oFr6t+07G
FOj6qcbvScI+3oyQ6MWv+1Pw6entm+i96jkZeMIKP4sK/BF4JCYzirXpvOVfnTzFIiztk6XWXshe
XpDIh4aQde4608Bf8Rv7y39n+nksFybbsvs8dwBzvr1zOOxbZk6guE9REglZAlzSYjL96ygLRZoJ
VZHROp4dzvP0uyIzf47yTMs7IgROfE/COsotjpOVUDWRhEHQIjfURsXx+3Hr/Sx3UwbXxB8n0c+Q
qNpEEPyDLYPiCoUga6e2vAIyFxg0Mj7H6woLAg857rv5tUOyo8ApSeVKlMQOh19gVPBXwW3K2jVf
Xkn86RDOP3Yfgvx3uNhat7fAx23lD4HVn9C1A/EtclkjRpYTtjvrr3BznL94erptNXJe5WGh5EK4
FF06rD0FKqUTljos8s5Yid4vcqqsUCXTh8KsZ70NTYpoKtcyB51nSyxQ5TK2O/B0ey17t3XQ5/gP
pBTvZDZTnXfrZkInsZIipKL9ue/bee2dt+zdAWlzuxg1OmQD7v3BXY0o2ftibqbq+cCFqy1u02xI
ixEemw3nr+D7UYgqQiMVhEVZFgET/CNIQjOv0FkH5yBtiAJlBuTWQjFkFkRBQGDESH1v7dsDTHNQ
zEowCRJD44JgQqAtEFKA4HcJtPc/EUnkp3RYJoIUE8a/H0bvQOyno7IHtU9VxNl8m8/WM/T193ki
UpkTDp+8ifjXGM2ZlmUyvsZmcTZUamWWZCMBtNDOnzGqSY8ImXcVG6SLJvw5GskgkFePV297swzU
D94gCTN7IEjG2kY9JplGncO7wnBIpDNzuOlnrVtA2Q/yFx2F4FRPq8OqNr7C3xFUp2VRdeygl/dp
Oia61BazC04HJRDdV5nGornE+G7PWOAQSp+dPQ/BTVdVFF/BZ5ZmvdsaSUign7MtzQg/Tzg/icM3
wLk4lATKXZk9kmh3uKqWdZtd2SiRiFqIYpcxiHJk4HIOPn3jib0McNtX8evUx8KuBgammigmdpMV
9Mos6ZwTrEaH4KMLhbThYEtCMofkn4HMCsZiMTFFDlKk5hX3zHNytXZlII6Njx85V++UBilNGb1a
Ne29VTVU8+98hn6HxNifCVPtDCx5j3Q/KDBAhBD2QKgVDTFdfl982+dBTxbjhK2b/IXblr4YK6lD
goUpjesYDVrelDzIHzANKw6ihSgoaBBoiIUMUUoKChH3ETUYoGUkhQUehBTjBLCbUdwBgu1wEU4m
1D2c6uoTYbyyvlMQQ7ExW91kkBJEuoFmI2W5gWRG+LAQkYTJARD3nYrdV9qRQN3+Eu6kGeG4cgJz
C3xwlV5Lq8RKcMlDUJp9w8gvaJwb/aLtUYgnWieDahw0D4TeGYmkOr79bhgY8i0ulrPAOi3iO7kn
G4N1OgGW/sBmXVlWWXDE9N+XV1516vU+YO43lZJX1lBxBuFHkrtmBALr6KqxhnVkClOAUKFJIki6
Ti+aeLkE7L3HD5Iwzso3O1qxTP9cEYuuwIpTT0trvT6WzVY4uTnZ0v+hU9nTVyKqUnTp+xHlSq4k
KudFP2z7jHoeJ6+50X3Me/3r8I+92Z4/5Q189110H+o9vLymQBmGc574E4NCHm/tmhQdJ10YjMbL
wnDEbvjaAKzcNGGibIAEe5CgXIw1v+XWXDFcyyXyWq0aNBgrMOaqvpzb0xU1aGolJHoAUlslktNL
Q1IaSRKfbcMNnkDynGo3lvmTp4g2QmjGJaLTHEUSUUbAfI68pucXAPeIXBy0M2QhEjCMMD6TDjM+
72Bv3aBw1NLG0KCmzZosMSimBaJUoMpYhbFY1s5wkIdAn7AhMQ+8ERGRVP5bCk/O0GX7LaKYamil
mv3NBdhpSrVu2XG2LcHDJgxBqOoMlVFKmXCxtot/hgVTSPSC8XwfY9MP2nEt29l8TjYJOrWih4kc
QPAD0Z5tVQ1VLJJIiSSSSKSSQJJJJISSSKSKSS1VpLdBO3dO2dfjvL0fP3970d4pyLrpoOiHp3Bp
U8oOjBd1BQ4H70SgdGqn4AbgiYB5Av3kTVALRc84bP5gfZ7/t7+/vO/vMfEe7qhaaZGSGnsWbZrd
AXufNwRIQNpBq/JRHpkZDTkSTonNurCdFTozkydTpxrW6TEgaYScmGmVmkJvzsIpggj0SSsnRCaG
cmGwgpw7JyTSckN2G6EKgcaiWzSpi5NEuBrY5bVnn75r5RhaEf0muATYqGg9A0GAxIgP7sn0fR7t
/zf5GVstm24cBXKzVFOcJqinr3YgmN8stvJWMx7bomA44+o6cbMoWUl2liBPFowX0frM3p6N2zhq
+xBrF4tZARCBfMVs7rlOAx3mUBiZEvdc2JM9HkiyIBNKHzQEQsVQRI3GP05HH2akiR56Z2C1ayxh
fqYoymUm6OzZm6MWzddhvissVCzbJEi4ak7HBUkBgHMyBYmbzQe8byZ7OcpcZtHC8hnUmtn3VuG+
rRaEjoz4pZ5JMRNHUgNkRKDHA6kycs4k6ScLoCIRI5owoiQzpIm4trIVw6rGPVoynS28inCtw5TX
GQkjWwc2HRoZGayyjJrNkV0cOTRZZlkrhizcMPqic4Qt5ylKUQpSlSGb/2nPNk6sXdyUdHPd3U8/
O7F4t2rNwU1U4eDuzU2aPBZzeLk7NH/2Xd1NVnTFg/PCRZtyhIrk2Yc11vDiKYC5oVMETYyJjEj6
xCByZDGYnBoObH5nah4RN+4NLBbibekflj024oUm7ovM8RtEBZAPK+VkJPIh5N93lrdhpXa1TTWu
7cQ5IKrRCAB0qacIKNl0OgrMqSmNp1zJtXlsZwbtTQKiYsW4x4upnCEDIcIvC/pOai+nRNGesB5L
mNs5JGMfIQoKg9CCmYtkSPM6SFn16cKKpgttY7cYzoqHJisETS8fuENz6oWFIZ0ZMhR4ystjGhow
U+kXAmVMzqRNzchKU0BUdTr0gDEGvujJHdiYoWKjCUSho5qvJguZniARRKmsHQfQSnaa6hgRUpnQ
V+a3UWV5SGRY5SwJvIGCbHVQaBgynxtlLc8c8bb5Q3lPPKd3tdzOlY9M4Rxhs63UhSEZisPu+TXJ
3a8xTSedDKKoIOfeIW3TUbPZoJvuSOsFWArIgJEZMyRQsOMZl0Qzz2m9aXaorqluNYQyc3LVmtFm
l4nKaVJdgRZJ8tWGO7S9Y833Bg4cOzu4xMapyOHzcaKX2JGCUgkC9BaropboZjpc2LHQiR1qTdwv
ccN+8jlg7KWylpg0DLraZs8q6mhUXA7FmTYV1QvzfK8dzcazmByILqWARIGZCshzJLG4bnrnpYms
oZKeVREHRCUssoCCWiIR4ms6hML8ao2FDyeTiXNJkZb5N824KheCdACyIItRQoB1WpUOUxLNhY0U
ktk8WerDwxWybtXk5/LKSlI5CHID/2HvP3dzM2JniYIFTsdDocjHBcY7DFTvJkDkqbBc0KkSwxg8
hE0GImByhqYJFjw8oruzu25MHC7h3U7P62TZypitdds1dGimTo3ex6f0PuI1kveEfH3SH70k3RyD
Vmv4716hW6S14Ol7i4YbMRGlTWm6ylGV21txh39TC5AdiAqCQR5QURN3yUtb0zO2UzkwtKrNAaam
XNUZWxerJrdETrptLlhMEIoQW/DQJrs3bTCDPkf7UWKpGKjHrIEkPTh13wPoQTsPDJARCQ9zJfYT
NSpJFWNKWYQdxdjecNenTsMiNLBeFPUXKIgbSBXXc3YzMyMGM42XEHusjOGmUZpUrBxDwM4GgoOP
sXHNfKBK5qNjDJBW1bWFzCJBHqMjmoYexUogTIYVO7WId1Pa6OTKibVK3pdpO9tEHgs+mAiMbEyp
MqmstRcxRGOYOhAwZhc0FllWhrm7EtZOQUgcUSPKR6IvS0by2RnRosZGx2eTBTmv4LOfXZnIqVCv
Nqt3UnN4K9LydCA2RkQLGosUUVFWOBnSBsOnUQjmVGIdVkZwkV3WdHVl2y44VazW6yNJE74mPe05
0YkKNgzH4FRO0CBscG5pNbOz6b76iJugIREjnKSkJ1KDyU4OBhRThUiKikzApsZljhXCQYySRUhh
ornaKVraz/qgj2L6NS4p87FTBM0FOTcgQJGDg2GOTBUmWHMHk5MXJw3ZtmjVw4cmTwWarI6tWDme
Ppw9r3o+M+Uhs+48EcI+aPXWD3eNdu23hjevJhmkdlom58Jxo6ccwJrYoWCKXpyHcDYEsqkmCGnJ
yoeyXEtJIbIbaxQijpaxzNvUhoUNVrAjS2yTvyyplW4fgJkgkxPqBoRyUNIScIKJ8qev5Wl5uqVJ
os5K5U3VKYPg6mLu83vabFFItYia+Ry0TAYNVQByhtqR8siQYMFDctEgqrlwvbe0bICILEQsMkjc
0c300cNhBc1D6MEY7mWJ6qQsojXdjYQe04ZIuCQxLY2IEIWb2kdRRsbpUGh2eEaVDsRz3KRttBlg
sFF9hrrDMXoZFjBOdrU2kiJBHmK5QbQzRCzmpjTBU3rh1cXqBVQccaVxDHF2WQriqaQInBAUqYMl
IheDJPGYE0RUwR3OTY+BMF3i8HRgNF3jCRzYt3RdvVdOlbJ3ivHDdza81WRszxauO7s2ZM0k7lKk
lRcP0h6QMddjk5OTg0TJQVODMiKKQOpycFCpnnuXLlkkToQMxxiJg4MyBEyyzJnJkYIFFnV0dXDJ
wYM20hyxV78FYsVMnZ1lOzk0cnduuu5MV1mDaQ/dknWJ9BlZHNG0wnincV3pLyzq80SLaQVKS5jM
HU0JIG5SkZ0g4YUWjIVNtORWXqhWWx1M6dtKk6WZp3pX2nqfHF86rlZYMyMYcQsEFQr4Z6JKTWhV
AIiQp+4M3kTJydWUztGjO+yC3IlF4Q5KcObJ3dWTWaYtdsJDBNnJVozWcLa5csOOSUpBJlTsVMFy
IRzds7YglnaBUggSO/vse9bCJ0ZNW3sdFl8811onFriqmcs8spMZDop8kpTKRoYnOHEDqC4iHn6A
pNUFkFkSwq2UsCfQuKRM5kclLDEjQqMMQFODvENiU7tTMQ2k3oLrXSbqtnho2YW7wkZOGG9NML73
wIhrcmegq0VEDVzBI6kjMuXHmjO65EF1ZDwSNC5Cx17hxqjXNxnoMZjFCAks0JY3GK2Q2SYFOKzS
Y8CzUHfDMhAPKCBt48hZ+2p+0o/K7jhTupu5wh15Ok55NHo9XZ6O72+/dyerJl0dWCz1c2C70fGE
izF2ZMlOZ0XWbN27VowU6tqbNFnNqTFm2ZMn9xkGssoedQ3i84na+X3Rctg8u/Ts27IW4QrWdpj8
NVuGsLe7vtDE0NhQW9MHvwb3ZLLDsIWA5qb0WmFE3q4iVoPQ1TNM6XWnqxUZUSh0uohhEMql7PhQ
ICE128xaaxiAjIR7EbpZFYJzcrfLfDIt7GrDPZZccKYOa6mE6hBdZEKmhRsoSwOpkGZYRCpY6mkE
suLUdBBXIVIsLwNoneKeiyJUQEMEzIkgyC3XVdSC5GpQgaogajG5M2PiiAx7hCks82M1eTJC7ETk
kOWNDVNjYhY1mxUiZQC2asMqNkKiJUQuFhRUBDNEzIGT8WciUDYmKxC1+IxzngURJkuvmu+5JIl/
mBLM6nJkUFDsZpVA3lE6IiZFSXQmWOnJs5yEjNM0ZxTQlSSVKTO8Q2sLzEmQMxGOi6RXFKSOChVT
Y4LhcwcFDI5LMm7o8Ga7wbNm7ydJi1YOGynmxeCNTVgybOHDo2Zsmhsp1YOTbZita7RrCRdetMXR
x0cN2LdZdkpm3bPwxMGjmp2c2zVk0cPWe4frweMyR1RpJ/TScJMT6kSBrrvfFWPB1lB0UpmRDxRV
W6V6TKILrJzTxImmLFIaHEVAZBJuYVwKU1VM8CL7gMKADmwvnHVrjpdIaUCBFoXWrihGZ2wrw5mZ
nnXNjQ4KwInI1CuXjMkKIlhS4BMgZjijpRRVSVnHVBTsK545W3UoSRBl3JmDUwMQlPKh4En3WBcs
FDIQiVFkMZawfiTYN6pWERk52iaiZHGRNFb8hRz7NBEwOlEQJ+RUsMcmxAgKAORKjF8MXObja16j
4SJyS6kJnIYM81XES5VEadLqyMzrysLygBLYJ5EjBgoHUYkfsJlZcLDMg8FzgNclA4IXzIj0ZSpI
czJyIkFxZCxg0LnZytKnIuhAg04rQwbGhTMXUWpq5rKfGJo2RDeEjlNLOrd92d3R5eW7J3KGCB2q
SGIkDYc4HNTBmgUJFRzkYUucGRwAfIToqC0D1iWHOOLg5RI1RODBcU1OTmwXcl3mxYPv/XD5o0ny
g8JE029eV/V7bevQctqtyqAAPIWsqPDgYU80gALvYmn6GfNRoRNaZw0qKaFZqSAFKNdYTfbvUvTg
lgKCIGgSLQMRnF5xra+H29tcg0aPEXLXhOhos7jQGILsLRdJmeLpEQgzTxipEEEQO8yJmQ7F3dWT
PbnpgvgRg3h/MlSqiN2CDR3dijmhwRDMvtvTVX1HsKSKoiQI0yAjkTMyhQUd1ac6EjG/BCxcvsTR
FFFRUuX1MkQNkg7t3Cz2yGmXg47FFWg8PjdfyWEnk7ubgkhQqEWvKpbM1UzPOiVzMG4poQNiUMzM
466EW0ImQxcuchEnc34WW13QEQfhxSeQpsRuRCSOCiGtLlr5YNKPwYmaGzkSZUqbiIkbFtGbmu5u
rMs9k/lSJj2V3c1Ozhws6+tDknMWYp0MGhqUIGVFWJuTMHJEsaEixYcsQJj55jMORLnBMoCkjgcm
KXNtplCxc2LH40TwTTyE9oh7U/VRA7dO1qPflNeJyl4klms9wlVZrcLeakZGKGFUiBQixqWh5zNR
pZiaKq4WcNLlB6eikRQiLgaE6ftvq1rN71hkQloBI4DcYIySKkSkXXkyWxwxyz0ywNUOzBX7CsrS
7xPLIuEFREQRDvnmZEjwHLQil6U8N572MikTYyN7WsXAPAMGQx9BB9pc4YfMcQRttzBwXGHKFpWj
WECWmkjoVGGJmZUQcoXK1sTWNHoiCZFDUrXPkxmXNg1QTTNIGMmUcch7JkShsclChqTMyCIZYaVa
gjwzKnI+A6BU1LwjYLVOXCZU4hCqtUsiH2BubkkMpaK79DgsYJnO4WHKHBmXcyiwMDvaMF4vPBoy
depn4uzwPFyc1l4ji77T2wkeTkybNHDR3Mr9FrYslMISOrRgZubk8G7xU5NWbwaNmKllGCzRkpop
qsybsmLz9MGTFu5ua/RWC7hze45uzdSz65Pm+cItCGALiFgb8aP06Hejv374zWU4DqyOmJpBKTle
s3Adly3Xanu5LYQtWMyDSmYqWaw83cr7fV4Yxp2tCKKiIJKXigCg4opDgiZTMYEJX6l4IgMMwhQj
QpdC8oM7NCTQbqYhHDSyzYwKXEabMbiSrosB1VBOKkRPYpJwsX3VM7aZcNbSHVvYTTV6Oh8WGLjs
yXkO9I6tXe6OHd20dmhk6sG7Jg9vt6MGTu7tuuVia8tvyQRAuqBwNddRUSAB7GzNhiBwSIWLyXSR
QrE1JDXBLnCiJoQsTFOMEKjGxoWFHKkdLxnClFiK1xDNhz3ol5GXMXSpudDEFgq7My6zVVmTHHFN
zYeaVZyJyaEjPPYwWLtR8NJh4uzZ96vu32X44d2rVpJOJJI75YKti0C2pgzNTYciQMyZEkZDDlTs
a69ipqamCZcuSMClCRgGGKEzMccYcgUMFgU5Nll1lLufPdk0UWaLxOTo5HDBl0knT2RHh5ACvZdH
Q9g+3zG8jkUcYnssdh+D+Rh5S1e7+igeU1/EDvbMkEOiChzqiWEe4LI9SJ03VIghFewVS/UghZVT
C4UrggpA2py+y1uP6u2w+uB/nke3m/bXu/Da+rM7OWrwkmWNv7ujb/v+3PLzGbUKYPyQ9kd84QmT
ut6Pv6dF9DlXJe0fqUWcoQ7dFnXL5Mnpm9G9ePhy98o4YXsMgZ6sCn0embDH5yHe7oGNOS6+r5LO
fSHl96bdjYPFbrdUTRTXZguqVFTRTCo/G7sP1dlLKlVyUFw+dYxiDIlKUUU40iDgp6syjqx1Fj+V
k1e3x2Tjr8WDnfzmjoyVDu7U5js8ZSGz2Yr0hWU7iTI8bhMKJ7uVsEJnOPaDZxtvYVpXxwTEJJSd
oOJ5rEWZdWC3hrHTJaE+GaHVGB1AosFJB3nMUamOMLXMrFyMOGFg1/JxbDno001YgRV+CIF+qgOW
JOBjbudmvPkv4/GZXmdFrQ79Dn5dH4OhE9xDtppE9x3DOui2FzyJuooTVN1+foMJdeVIqYrn5ViG
YqBpxGE8LqzTaA5AeBAUMttcEM5/W5U1mxsxkjyjnkbeE5pb6kFspRfcqHj7uLw3VH9K34jmtNWu
opBhKqEFBurFReLIQfx385Z2cW+h059VibK+Xrcki6H8gLSEet6DuOC0I+ED9URP198FgbKET40S
Il3oaBGhBKYDQIn3IkFShT9g/30UFRA/zthcsDRSy4wxgo5JIVFkAXoZIGRiwDIWGwZY5CQKkkKz
4C0JuMJBW5ALkELxP8uhE6WdYQYBBPgRGExirFFGCqC+OEGxLCCBqAChMKVhUUsZ8cIMhkIIYfDG
JGMjFjBixYxixBixYjFjGMQYyCRhkMCNAEIhFVAYrAUREViwV7uYST6fYHpIH2BCyMIwkAkApjQ2
gq0ielE8SJQYxGsRdIXqqqlbxGhYl4iyYINjm96I2M0SkUSo2fpPePn3lL9AEQbFJFIo5QEabpP1
P4SflPrQ/MKWGL/NJ+g/+T+E0f/P3G5LLE0J+vVyJhsTKJNAH8Af4A5Wyt/+I4qePH1f8H8VZBGB
BeZCwgyBBIxIMUGRSPVEKCIEC5qA/jF/xBghkMBM/tIPyEIwJCRRVKKUXTCCF/WhHpPQ82iSpPQd
IaEoIQGWhc1C5r2okQTT9hpUpfq/hxj+OVP35k1Em7mheRzKxE9GUykXRnZLjMYDKrMY9KHxT5e+
9M4kYHo8baiVxxhQNgPGIbjqg/pRsEcCHBS6nkajiKDESYFQwVIq5PilocSAh5HBXPMMDWDvXQu4
XmHgGJohtmfkNQxc8SoFloChDI8MU5upHgPOOowB352JVlyUbk9F3uhBUNqjeemCS6SVKBQ84dW8
sTYneMgmitaEe8lREpSGyk9v/Kj/h/EP8uKT/9WCk0kUT5ScxViHFD4o9jzidkj9gwkMaekUzHl6
6P6w44i9wC9qkHsN8NG7s7QeFTtO08SnaGesHNcwbqN+JMuCKQN/OIHRE8fFK8X+/k9Y+4bc04Og
9xPK8ineR1pO8ly5LyTE3O6FIx6XQbxB6LH3CvWsV1ZBmBS2YxyBoCaGmhgJmU+FcRmgnFukYcZ7
NFjMzkbqkdLbxIyUORLXYZ1LxEyZKGJKiknxIWMkxSxKmBKMWHsrcqe5HSRN0eclmj3nviJzYPQn
l5nokVunqjcm6+ESO4k5sfHwvE/OhuvrEjM2kewv7CaWMkpkuS6SK9leS1iGEMKkehLEtbJmThGB
MDksmMGdJmRV4ZEuS0gUTI9RCXaCpSixWgctEQtxORwLBtBowmc1ExdORdahgDHiUlrCcVMAYEWh
6HE0IG8iMige9NpnvB4G4IxcwTSjyFNBTCjinSnTpJsTqZPAl5DmjGWJgTVC0jmC6mIUbS4UuSHX
BkQkMwExVHWSoIYz0THzJknKJ5iyepz2R0JSTAHiGhH/XjT1OYq0TiZVLA8zxbgWB004zHFwdPMZ
xkRTYOAwbGBe+hFKcgfADvUgr74ZI/uD6D5R9sf2EBPmgL9qhPycXQH7bPjDEMD7ZD4ePp/MFWC+
Mr6TKR+dD1/V8cuqlbrN7uWGJviGg4/NKKs3lvZbUeRSvpeWwVbx85vPdA828Cl7V8akVgefEfOn
x8/dr9WJmpE15kEQAY1HSGSmTTCYJP5d7eH02jFoxi8HeMypbbv27YH4ODml5Mt5qhg6RlETslpO
F3VMJIbaln32sjM3Gesjmr4adcarqYpu5SOabIZWSN5USU6JmsyOS52ekTaSPpU7WD+s+8pelE/Y
nmtAkhIj/SgctySEoAGB+31CiLf8qxGKsVYKRBEZGMUVVBVirFWLiURAD8yKQH1gxdQmK+XxVvJJ
TKurqoWWFy3KuhVVVwhbG4jEHCVm6VpEGje7XM6iJQamap0JoOAOPuoxdyG0k4s0MWidgsJk7AEE
GQLOQqZDJOUD2nDEROXcb0wcaUpEtpS0pcxmZm8JMJ3lKmkxTvWCZFUm0domppSxglguJWXEQgQ2
kDIHIRZWtKKJYyUKNOcwQlgIIQRgwY5IANkjITS0MYxgsR8QxDHixCO8+hJcaDgV7AyVJLGCGUkk
UjAfeVSr92limsJJpXg6hNgMaAJAgpGA5sNsYRUyHWmOzHeEJGUMKZBqAWbqLQciRBU0IoHQOk6T
pNSd3iJvgrGK21VWIuAHihy5KIsBQFWKqPaZDlNS7Pk3hUjrCdDEyZSThlJScjKy6kNQOrbm7HRa
MkwC67namvFTgalMXZBEm8LIYB2SG54kFFgooLEWIoLIqirBRYj4gNrCScz/PYXBNqcoUkLKJZLw
gkhSFJvdCZvJp1654ksyiij60LbZyfXSUn3SavjjjFuTviNi9IDCI3gWhUcMKKNjeyyIRCXqbZvx
y435BJgCCQlh17SF3F71qgrSqbTiWGEeM4ZKVV01JiN1UrOau/FpIDsMhg6kCpSguQF24HM2kYMl
hoh751h0MrzmDMBmC1ARnaRCIYebY2IM+VeDkRFgL9H7EFrZ+GfEOuiRzquRKfzntn6Wj3P0H96E
jFi6vi1fkaMWlVSzl03ZucIWcLvY/S/b/w5iF2TmzbsHDFg1dFl1m7myXavYjm0YM2zo0b/TJmqd
eunE7qpTJd+qTBUraqxdni6MXZTq5t2LU/K4eTp00kugSKDnkYJIdDM5MjYkFg0GJlypyT69piRO
DQzHOP8+Ui42R0Dg3IDFw3KHsTTyZPgN8CDfHhopq7PQ5KZPH7qKUoqVVSn9XCJahvUH2/dGf1SN
LXh9EnnqQ/XssHR1eTV6vU8JkOl0Ox2aSHeYA4aSU3rfOsqjwmw+18vlpBTccHqOo3lzcQ5jB8pv
OUezxD1PKQCkjBE6lVKUuCbNYvqOsGvlmGKHhQbe8OL8PYYtieZU97YCFJI8indeyYJCCQkNsCRK
EiWSnBVfJguK7vy8+KO6LriaBL+Eloj9v8uNnWKO8XegARiaUTvNBiMV8xcxRLrrJ7QYH/vCzAIQ
HrKT3kyHs0JkG+URNlIxiogxiM7BPa53RGaAknstmTrwwJZxVuwO80lyIOsSCVgSSOSzJmIf7V2p
gyv8OTBrEwn2ZSaExglrHO68WEQMmPnUdi0fpYJx8yHMxA0t02u3ldb9EBMK0g78JiIndB7dlzDZ
VCFi99QOsTERTmvQQ+LSg52vlQZLvRbYiPoiPWaHV2ZV+zRKnmRvJB2PPn+LzPsnulnt9gi7u0KV
QT1/g8j9TWakmwDTBFVEBB1BD6ToK33H2ECx+Y+47iKHflaeKsp9pc+43Yvi0arNXuXbOrRqyasn
aEizR0bOTdFARBjUYUofd9zHBIkTGMjg5zJ2tucG4cEzUnP6hEcd4mRscnBc1PCtczkNzkGFLmpd
2NmTo5OTdmxOrhiu1bu7quxMnXriwM3Jk6vKRJEfjexW9K9Hd3avFg4ZPJi4eiT6JUn0xZ85+n6+
j6WMPlB1Oj3OS72vBZi8jk4PI5EQt3SIDOp4HgSJFDMawtCZkeXlMyIEypAU9poMOSJG4ge1MGRH
w58iB5EIRVWgXODoKW+ODMmcjIJ6Ckk4OpqcGhwOKcHcScmLxWyb/D8T9PzhH0yOaR6SJ5QEdyoC
MEk/E1Woo0KIlGz8QfZ92O81olgM5Jko9Z1PXBsSob1MiVSKpQeMRKX89A9jIBjAUikZ8z34Sm5X
tLFmRmwoMtEuWBYDSbAPMiegXfmmLmZVQVKjEInOAUkITQn1mJcHlRTjrPFcE/Op3ETpYRYv1Ar8
0UVkVVw9aQ3KRJDUiGIchawnivSKVFKgM8d7oj8PK/yfP6rsX7xn9pUQqJumEMfm+cJGrZd/KYLr
vvPvvV9aoSLOjXPre2b6mSi57i58jMU+QpIqUIFByJ0+kCZY1JGBqisYIg4xEczLW0MiBYwXKHqX
NCJg1LETUi4fdhw7NGTudGLBdi6Nm7opZo3YMmR27cOGzF1YFOHdkxUjByYOTuxWk+CPT2jsxbt2
q7h06aSPw+xJJ80fCVJUYVB9P2i3dXtcOryWU6OjyYvRWCsHue1dm8mT2tlfcV03ejq6Pa4dpoHs
pVJSpk+HR0Wsspgp7mXN+SQ0J6Ek9iSaVDeYs+DySebk83k8GLFuxd2Z80nWfUTiI6+51pT+RU+v
+S0QqqEJJJEyScVyE8PQB0W4CyBESPmiBguDlyIbHaAjoVoYGlXAxP8ETyk9afdKRlBLASWCUgyU
BKDYlAYUEoNiUGxKRliUGxLIhQSg2JQbEsEoJQaJSMsEpGWFSlIyypSyilLKUsBJSwoozFMQ2IUn
rigelABIXHJ9lCnoA3CHgASxJFSvM+Gfh+8f4vMMQz8TIv/IT/SJsm0SB/95lFiIxYHsH5yksAdI
InXN6HSL2qpCAREvJEQI+EoiUjUuZLt+clI6FiR28p+993SgbkDUh9ShsDR2hIp6sMVHYiC00Hzz
UxNJKg0ApKFPuixKmFLSIliSkmxmJFRjBQyK2kvZqWUTQVEQCwVFIRS4pkY4XTLBMGAuDWZTdmEQ
dxgSr976Pqv4O3PjgVpWVAE5mGZCFjQy1bKLjcLYLlow6syE7j7xwcj7hRPaZv/r5lSEfsPxP2GL
8b7Wjh35cvDxws0WZGboscUoFz69b2FkMZnJM0KUsUIFjvM3HGODUckblyZkKZZTLmZ6xj94m2hI
bYY2Mjg4ImZdgyWdjJ2dXdk8FPNENWS7hYxavJJI5vFg4cOTGmjBTBzcojI2cnNdSzFzcOjw8OZ2
buqy7drOThPT1kRH4CyYjF6SotAnuhnLIdW7n4tUXd3saNGC30bwmzzZGbq9Gzu82k7jB6PZCRiy
WYNGjFq83V3di7dzYOjJizZv8FPBE+cEUHTBbS0qIZ76bokBaEUtqNJvOBqMlI7PYxdWLN9udXnM
CkN0aMYPuVP8slOFADwHKoK4BfxVT/J5wfLrRqKtCTxaTRFgSMQ0pmD5D9zUH0YBgWjsgtEYQmEK
MLfNFt9Hz0GV6gubrVTp7SbzrOg4kOwo9X0Lrvc97V5wcRH5oQ5k6fgnx6/TnafD35nR/tXKDAZj
9QZqHzvrdHMoZ/lRj8M+L6/+L3R9B8pT5h7SEnr9Z80EZo3ffdltq222rsBa1pJjzQOqHsE7AESz
6UTmExe8MRT0AfBJ7t7vo+hSn0Ur2ksSvf+3+HDHHvS9OhkMZ+unj04uLWq454xOdMOR/hXedR2x
IbAmEFyxGXWXLrGG59uwCGwTjiTOfdxFFDaKVkrncOyTu8r1xFc0BFBAcRREuQxTCBQ9JtsbdOzf
lHhVAqo6WtaRC10TRfEF3twU+2009Ojc3+p31SQLRrB8+YLKLNiuqwLfwGjgkaAJUnAEhVsiW9AT
PSpKaTUwVYtXNHfFhbSbS1qKnGHg07eFO+tql18FW1nXxYLWI33Shz35dWcXttbMBNEOwhjBcd8F
IEzKmzDarNM0m6ab66TiQNES6CQIqYUQmmrhW8bUbmR82D6fHeP5IsnrfnPtKQiD9dCco8kV4ZEV
li+kZsXIxD8dQ0anBWO9Bp1p4ml4usEloCP4At5gWN6YUrMkASC1hSmSIu0u8UEqGDOMb8YySOws
mjcUCMugpgc5zkIdByQfQuSjLp1rZb/kusX5zPY3TJofZpL8+nEj7yRAMhj7zy/DqdDqZn4GQopE
oZCkGHOx2Mz9xOXFgaoCIOdjMy7uvcvr5uPH8fdD68rspd70k75+Oz+Hbxcq1GFXXr71EP0BjUO/
m3zUwMd9+jtfYeYIniHnAjvIFgrEzIRFKkj48lTzFGPI7iRUqdhhhyD4MnDk+Xy6vc3aKcnDVgp2
Yujospq4UzMFhRSp7o9jVDrv7QSRJfKU/g3fCGh4s6SZh3+BMbYZql1BughcRNW9Q1QUNgBGRVVY
HQKEOc9hGByNRGagdg1IJJRARoEjoVUpKwGKXPQpDWxbgYUzUqHF6qZyjyTrXq5PR6vV8Vndg2eD
ybN/vfv5L3phOa6n1GEhzv73Jw1We1gyYHjOw7lHQj0MfEYnIbzibgd4GDyllM2HdSsSpEK+5B83
0fDEfEMZe/0VVs72zJT6Ak/aeIJk170oylqITiQBHFkpBUiOArZyMmdK8ea0LEZIuZUEo5XDKUQQ
ZBlLqyozlavU9r7f2/R5vgxfQuc+ZzSScxUsSdkPe1fFu5vaj4tifX69JeE8iUSPzk+U1eaz6fF0
UzeXV6t338TvarQRuhUJSOGhZsW0nm5jiYomAbTQIWX7khQmoSYquwW1gek8ah2qinrekGwOhfDE
QPUEVHPpNUiqGVzZZRbHv/H9H03Bup/UBvDfAelGKq+gsq8zQFkfIRFLCXLPllpYeSqCcpKoGYYB
cECRH2cx7/fu/ss0wY3QO0i36iH1b/vxGHuJJ2NIslKE7y1qOm1kl5QXRWwly/sw/aQmJq1cyq7R
+ckh3JVlp7nJhawKx/ZaMM8KTAytAO3r1AIFCP2xUZEkBQX6YuX4MlKPMAK+1A62njpQeKQOcuj8
Ql7hjjgD8FEPcjiIzVxeEYUrmspgmewwYJgrxikDD56tlLyyJ7znsvciJAhEIMBLKtQSjmRDSF8S
Kczq5W4ZwcPFBVX4oB3ECQ1j6OheJ0KOgSArQxJvsmRvLa6qPIg7AFN1jyDiQbVbkrjHGT3jI4HP
gW0U7asDxqqZYrU9mw0J8pGGKSM6qlkTPThPgbo1vY6mGaIyoW88bom8bFpS3A4jOAknUJ3CKQiB
9s+kbYjLaOURaiMUixkEhEisQgiQCEIxkUiwCCwIKXBjsfYWNZwAwxAkiGpEpU0qoaoDIIzxHDHB
BNDq6Ac1UHrXLSMA++ihFGJFBYQXIZDeT44DCup4AixBFQEBAAKHbQFgtSHT0RNzyInQLqPADCRF
JIMJFQIxAyipRAOmLVesf7CA2LwtVJSNU2ECKhXZVJe8rYvt9uVhf9DMNHRKVFVEqKiHgZvsfkU9
V37R4OH5GsPyo6P1laZeD8azzdHJT9pdi3kxZqZrNV2Smbm6OjF1hIp/OU5MomSMmDVTFc+nh1as
1OqzRu7uzReYnRmxWaMl127hopi5KbMmqn5vzXdWjsd2x0WcMnR2U5OrZy5aMmTXqpdddSzkxbPD
ygbuTJq6tnVsTMjc119UQ2qKXMe4dV9zqDqg6CHBoWt0OVPupHDN6NWRq6vBg8WazwXWT6YPXZmt
Z5uGvi6N2bhmxYvNzUpuaLwhTwyYtzJeU2ZzmpFIqPj65K5s267RzUxXaTNBDoAevzJYE8bv/IoM
Pw+1KPg8QI1moQiCMDyklB8qgkUuCD+SMow86jrE1oNlL/hajBSoAH5fo2/6fy/65YJktHzQZFTa
JEUsyMCIMBhVDvhvsI9IETnRCC+dEwX/GHxKIpyg8xzIvk76RDVEfmwKZ9xgIYxBI3Q/ATN98HIx
xQSYXtvgyQhJRYwU1mTcH1Cw1gQJIrr/fPisCgaEz7LKcQc0CMAvFfQyRbLULMFLAx6LT7F6rXEL
3W97i4A4lEE89F8YPwpxO5w7rgGA1CFAwGBP38FY8EhtADugmbbrMCEzMUyEhmBy+whgFP4IbsNo
VjGIbkqQm1xQ9XQqA/zoAJEO1GBifSvyidA/H4hTFB9n0+YH9nwT4CSPu15EH0C7ko50OBY+UGdC
jlBFQkWe1/B+i6OLWMEoVSn6JZ9BIbBpCwzCpRC2klG2FmKqSxmMy0qFkEfloYwU2b/Xq7OgWCxU
Gk9KSzTNIpvaqxHil2kBNkMclq0aCUcTGYxfGMkx3br9V+/o1xvh11TdBkSqoSaJVTUdBDJ95lmQ
P76iEa/hFEIkDYTGQP1dodkxmfDvhrB/O/+mStSaTdOTvp78kkwZObXaNSsYySoR2eeEqRUdEcLC
mJ3JnGjz793q9oe0pRYMniQ1InlUfxrhieZ/D5g+QfyEUPnhykS6ISxk8jA8HdCI7M/DIiiKMZYB
QxiSAQgqSIlCFkSimex+wxsBdASCGEF+YLoCEsmUEWEhcA/SwKV/ajQIH8CAyKjCJIkhCLICyDEg
LFJH4Px7HMJ0+kgggsT5kIxsV/DQHC8hl5MRF9lr3ioyxsY5CEQWCljEs2wwUcJUayzgtraMo99i
DYANxCLCQAre7re1eVeUExyQMsst5VRkjzYPR9Il/5xJow2lTTSOkBwR/XQBdfEX0igO8Hyj/5uI
msTr6VBXW/+LUMEhCSI1app6VD2ofU/MobxP5nV/MrpI/gE/b+qcSVXX6vKAj64COMokfWQrND8s
FGiAh5VOn+IA4g4c4leXrRPEcncdcGQPP1RnzGHokkJ7fuaG0kJEkOCHIfmBHcu6lXaoidSIrxH3
POv7ogKfqOpRsT1icKiUiKNSEj78dJD8lQqkfcJYKCgxhRA8uTJ9yfj2mybC7ULaS1GzZwyhURql
KSsQardrJcR42UpBDzPYDEuJYHa/G9PqNwsEA4RQWkMQ4oPrdXcHMYh8YBpF1yArCAi9CsQsr1HD
zUJ2qaBE8nmXFPlSrX/InbKJ86nlEUcUkXWheAj70R9SgjEIRIRx8CAmXyInQP4lVOCm4iGBGEIM
ZD3pK5xF9ziu2bCNKQory5oZQXwHXvKQZzsljVe1ydeHj7seEIa8jEzzx7I0gwV0zQa2HOpDu9yS
+DPEme6lDyfD4sDeKIvyiSezzynuPmoGrdwQruIBRmhhP+/ZpP62BsMDYSVUihBFgxd5h9llciE+
z89VP33DIilh+vagc4QLnwkElqcL9IfJ8h9aJyomQDh1gxiJBBgMIKp0MBUlUkdPX0yJ3tvpA8e3
yldnpGw2TYukgDCMIyBHMnj84x7gclUHCxQI+i53wOAF7SXIAto+4POaiAMpMh76ABNw5K3BPD6h
Nrtz74dZ8Z1pbAAPoqSI7CiralEYNYp/192ZtIKT9g/R/U6P3k8DhUeYzWkjQFsjPi2jXRMZadIn
nPRJJDLzzIZL+R5lDzA89I9C9nnrzoD8niVB7d1HciYI6ge01+VGegSnzh9in+URHzC6d8h7EVEh
IwYA9j+AVGLFX9qNGMD8TkJ+UP+4c3BcVDIdCP7X9TcX4xTUCHsX8wb1v1DIkgHQ2FpE8KCnI0YT
A/znMr/UBcfhQ+DmDP8p9WB5O1OKBoB9aqmGZw/QO4T+s6BM10Clz9yv6TlR4KFu08+1HqQgFiLC
B8fSEoIuXMuM77SGKwruMGIsksFDGQoxFZuEcSWl+AhAS6S8SiQIOhysGT4xO1XSHATYgQ5hkiov
FjSjCFjO6BYWBGYeyCaNfB4mwsUrnPpF0okf6kMUxFkUl66BNQeBJ+wiFMk7lig5VeWl6rcbac+U
zxyYiuxQbKNnFYhCbBx4xYIkTeakYugimVgRszISN13/wd+QBwUE3ZqpoOtcSIETVjKF+CelzDYa
Mm8EXWhtWvrXYCH84OvNYD38ooibO85Qo63hw1A94xKkVteSbngpVZRLQbJF/AnFLK4hEKEbTrRi
P8ZgiTWJuB+Q260ZgjNAKHF42wqq8TimlBJsBzUOhF2fF8aJkRrEVwImvhQlUSioNptHv3Fvua+9
yWYV1CWF7jWVlnSQ1LbZarshEhTjjZGoxYqNIahBNwoSEDYhkknyAiOxuq0tV1Cf6fvnnBIeyQjJ
wdI7oiIiIJEQRRRBERERGIrB1K0FQSARUIJQhQlDIAQkhIIEBiwDo5EHlfaH2FwxResTeDzdMVYh
FIwBYFgaCgBwPdxUShPddhw2luYeezwUjjCUoFX50kISyqXSKkwcmosw9FOmMY0jmpaqpRZQiwi9
GWpYCtvfhPrcX17mZktJZYBQ2EsREfCMGZKEDL7FkyByQJ+H7N+RUVWImZVVUmgIgyP19CQd8qOg
ANEjCESYxE+Tn0ETX9umCI4acNEswYCXNFsQ9trcjyhkNkyRPxInYiW7aAT4kE9le+L+1DpHV4xB
1DZExsiafWJgPSjgMQD7XDcMiLJBemBEpChPMBEqJ/V+rxN3rcAzYvVJ9hZwLniiEvhgJMtGNSxT
0OsOeFVgeL0LykyCQGKXWYlEhSPKDAfIB5ld6cUTl+dBDnsffSN6/65o+AGKp4VH1hrpBOxfRik6
R6ciFYifblmYtr4TawhgRRdGilWhFkSooYyuf4pazIhEWWOirBglehwZZqo6KyWw0mQ0EbGabMiY
roJQlJrRn4NprGifyQ8yfOJy9zOBm9aSdZmUXJJiEqGNiT3gQDQ84FAn69gaQSImNh9op74uhQvj
BkAxJoJaMLIIZoAJLRsQRGb0pKGI84f5mKjaAuXlWDxF7CnxRJfy4BYiCGEXAO9A1nWjNHTtkIiU
qDe62QiXCLLJNILCjJYuBiIZUJkkkMKYK0/GtBISSVAYAF1U9IlA/MoWUXEMCbkspIzqlEYMRI8R
SRmwtmiHMHhMHfiD8SJwGj+hnLDmgRoxnol7fDLWqchNiSKJQdZKhXg+1Eewnfv1dzmK9EhA50FP
xa3JEtfFR2MV+EToLJq86vm9oYHuvN1OauybO8TdtgSBgCt1SBQoblKIRBtZShKLABRFBkFVAj8F
UKCQU0gVhCWRJYUJoSFUxoMSZkIUkMhJEIkGShQSyDZ6i2MPBguo7FDRkYSQdZkiS9QgwMiWIidi
VJIRgU2K93dDKCqnxKKkME5EhtJuwidIMkk2utOu4fUGJ9H2/ZIfWvKlhr3ywTwpP8lQTZbWmLRa
hBGyaspGA0U0tffmzUZijBSaDgN6SWTkhYezpCdCaQEdAIPxifSg/BzULNf41+ETMSIhjD/pGoqk
iKVEATy7iubzu41I7UYiMCfSg7Ae9QT8L9YpzhxuV8mR/imjfpn40TlQATuokKkMHOb3hhARhwWC
fGpRKdA0AxgEWMA/6PC7cGBEGBJxIlK84MRKIgh2RpGIqQQmCYeCs5PzonHIBwBvsM0vGCh1oqRQ
4aQ2nIF/t025jiLqoHY5qdESQR2mQPdWSClw9VkdYptFoHdiIbKUiJYGiEKPda4TBLnug2BxZ6cK
METUkALJEaVU9uPIJZebpBzC0SEgfKj8XIZsC5oTEzRYonB4bOdExnIbE0CkYIlgB3PPXPaxNatC
+6eHxvNyYI+wHcnWLs9KP6QfGibLoc+8B59CcVhInOg7jEdQO57TQjSeKOb6tqjxxq7bdYVurYmJ
bUIA4gLxE6QkUVVVVX35Q+dxoLARgGQEynkuyJGUU9OCOECXpIxwqEoOlXW8QbOKpEwCCJFikFxA
oA4CGzrF7ifOWloc/RS2g4uCLgIdoE6H3z8A+srpo1OKQEe5E/Eie0VTkywHIjrZCpTtV6nxnzhn
CHxOZKk5HgLIv3pEiyPWekeZG9c0WVClSDm/VS/lIrlYokB12axfOpsZIS0OCmH4k6P4H4S6/3KE
5Q6/CTvCMJzFN7Sna71p9imJ3se+I98RZURaIsx9tjgJ6+2SdEnrHpkTh8H9Y2HqPdPSaVaGDEk+
yhcvMJtAoXAX3AJknyhHVHHtHZH6J4MY7sUnJJsYOZKhOxxmDFUARIfcBhTCApBSSJrifPtCmGyO
EyZYBjNliRRgorJIsNBMMJiJSyRYSLkwKDB/IcTffW5lWJvDVtYWOtJEqxFhDiMRsLT+D+b/dwR/
4qR+dHyRmqCmuxvTi8caKQu+zCG4whqCE1IVkbGWtLZKNKpsLmTmhgqpgiFhfhehEzB5P8mSC6+p
ysRe9Hu9yvoJJRlQ1/f+jG4H42otCGZ6j73QJxOcU5sQilIkWkGc1UAXsWtZgFQi1aH47I+jLp97
mPogBcHAUj6Jq6TjyvcYE9sJ9gKSaz2KCNoTH62qz6kSXA+eFGslAMwPreq2wHlBGKQBAi0L+ghs
Og7iivff2qgd3RbeC6XzjHaVelhBj1h0BmAoYNQOiJQurGW4wpJF1HY0GMlxMP2xosS6fKiZIlAj
8qJFALIPMp1I8hmDCzEBJ5EUja9gW/1C2Q8AQug4jGMhHkHqyfkZ5scizXvRS/ywffSXQMFCSUIU
hPjEVAnnAUgQWMUEIOBYbeghC66pNhU3KHWhqOG9BTQKxPGDGnIBdCIagYJqVIAhTBztQ643WEgD
dbAWSBaBQEYxjQKYIWuOAt1SoiQMHNRVHFUdD9MeiyCnHSZcwl1CyjP5pIhRGQiJxEoCkSAHyiY6
52ni7/cZt3+3ycRTYvSlXiFHrIcHM06brK/LW96cDvMYuZbQMQi0epkeLkIozKqqoIo2usAT4IfG
zM4ip+Rzq1vWPBrmCoKDXIFGYoNBlILvEioTW2O1QmfKDuJAr7XxFChtdx0CQea4U2hJJyyqJyFj
nPPBzIZHFYcCtGjl39A/MEyeOtXHFWB04IVypGbUcdhFBRUQT+8frDogfviDnciCXsTvtfnDphSR
szNGPEMOgyCDCCREpC4JV6agyylFFcUyUOUAuhEfKhi5xsbER206Q+IP7f9qOkuUQ+5BP6DMbcA9
ykKXsXP7tGF/mZAHwHKxhAjAEBij9qQOc3n7+9FV6NFee1xP4aXe3dWju1+y2cb5mzszWqaZYbSw
63PenQnqCeXb4HxTfgA4qmJ90CE7rg1kOdEAiDpFSR+oE8jODxS+9IezM2KxUVVM+kula00/mUsp
ofzeGsgKMBIzhKMgW08tPIwdifp8dHIDtMcS0UsL4dy673TU1hISwDNtvntRLhyBySW+KF1gwD8o
ibTnd3BBDABDqB6DNWzz1RA2NUTkPactr/nE40PmFUlFm3Q/GWJyiKJUiSMj4aw3mglVNhER6bBJ
GPEqu9XFH0iqfqXLD7xO8Ty2TkgO359Njnw0jpcC1FWMHliX5xOpA70MmpCIrOcgROjYAHRaUGEw
uy6S9mPeo0JpxuiQrnnmhvmwIwZ//WK61SKQDUPUTz6TlSqqo1Uf0FMOpO5kPmtYCdqql1KjoCEC
EFS4DA6Ukk0COgJLA5zrpUUVFFCgw9Soi+zRk0DPUnxOtWjk4hSMe984Idn3woOTFLidvZXyN/Ge
ApIRJFJ8ZD8mvzInOp6jR64duw6cNuGJWgfDdrIFUFSzREZgWDakG1hhgtfA1tyGLNgPb2GwTTS9
i4rqMYETTEDD4Xawhphtpb79Z7ILRXGXJvLpM2C+xteK2slKmlWqZ4AzzxGxB91mJy7bcIHMI7GU
4emasMY0bKUtEiyI0pL4Gx+mVxXHEomORmFVMcs7ppFhw51o3ToKQG/FHWxU0VFz69uhq9HVOErN
96q4dGV0cG0jZmyKDnSVcc1lgwbEqOy9PZWYymloKNq3NK1xElKYr6um6pdys0CZA4mGqxcV0g8G
ndJqqQt+sw1ScQ4nBxlDnkFhmSTowF7MpopEhvy52rHjYxvvDINKoF0m0bF5ZneBcYcNvBhgM47O
VXJttsZuTksQAItdMBecRv9B6+UhOk8U7umqGGXjJp7i60KIxthsJhYxKKPSieBE3iJdwzzU3DFx
CkecLG0FImZSWMUUkWiLKFUsRIrHIGQiRupQNl5BcihukUwUHASALSNOQwI0mAWIwoRwSC5MAujq
NNclrg14AsA6HXS7UXxBB7caZFG4BURU5ja0AnkdRrdgRhVBWwCNUjUgTi58ESyN1iGBRiRc9zCQ
wBTlQrMuUNhG12NABhsraEAtwpItIzkwpKyBve74USxgMLA47ki+NWBJ8aKnZhR3JT9xQxkwUFmC
li8UlF34VHkxH2KKUfjVMKjKIqH23yLln8Mp4hQP9BD/AooObbB5OfocOpiX0ySIUKDE7ESkC3jU
e9fjUzO7zlUgFMAWA0J0yenAuhD0oKqMBQFBQhWBKwLGVgTpAPy/+KEINpIA2z3oonIBN7E8fZU8
MPGZHskwR37gafPBrwxGr8llvwEUgFhU4mIicvuGMyqjqMQ6gu3mEbGININNrWeRDOB4wo4W720I
zlkhVVTUTRlFNulAecN4NbSKg3A1Hz6JNkk3IgCjGeW0mWwlQoU7SHzP0GH9dKWFsG0NeKiO2MzV
XRzoc4PqTifp8gug6Ykkkkic5FEyAgMRgOkFiDwg0wBX1oKUCb4qHEk31PNjcD5lC+Aib4KpdEsi
cq0ewGkS4c2I4RGFCSUI/phdJ4xDB5JPn3jF4VEbEywLonOJ6TtNaPYin2h7HsbpHqpDgIyEVCMg
fd22y0WwG2SAGxcySJ+UT6XY9PMuCICZrdRVHSifnBDlBwOCdxD1EclR6wEecWLj8hQLv4clFVVW
MGKmCfOCliCGgdvK9PTf3lwQ8Itdi6J22MYjSEfCIuQ0NFv79eAzRrBTr/WyQWi4imz3/UAYDgMr
gFUichvYe5KVgpKKHzdY/dH8STyG8koKYcr0OaCuCJ+wnWLYBNR0JEkOMIiREqeIgfXyg7LwGRNH
7rgmuxSMQgMS2DujYf3+As6bwj6T+7PwJNp+sniaSO78PjpKe+vd8RTPPoI0DBYMGMH8VGX7HtUy
ip38wv9J8vy+BeXuF5w5UDRGKkBNdEB5VZPcvyEvfQfaSP3EY4VB9h+PqdVpMI7ogDtaADDNqMG0
vQ0Hh8RBjYW4000DGP12/kspiCijT/VmJhP7+ZDIg76w+uyy7v5PK7uVve2MNvwOMVcexY3LkacP
3CqRwzDw8a8yQwYjCERFifrQaMSQkQCimgIPT4Cjdd8KqD5/QBF9wMQeoCIQgQgQCO1V0NAZLmB+
hQ6tC6EgnyRKNlgiIIXaZS+RzOdo7USjVLay0vf3ddTqybvJrILKkjaGZOMylye0KQJMiT+4ESWK
sRiKJEEEGbfKSYYzjgssBhMLGAiDKYpGIwViWHDQnyHpRCwD8FGQhnAELqckDx9UZJKQ8Bwoqq+E
D8G12RChg/lSG36modM0YLq4Hw6hggxjGDhRnmbGBhDbJLAyFRP3pQIUCIfl+ukPRnVQaYnLxEpX
lgBzHwCmDZbC9BWo+cTD8AB8C9gPgB8JgJpEknKnnD1yjuMAwtsqSUNjaXvQyPLLRg4G6GEGe3Xm
KLZyQtZZLY0irJW8FKhRvEBswLRmQsOhwnPWWAiZQdD94fgY0XE+xsfSgQ6ADJzmJNBJAs/qfG4W
bqhDwflUKPWPUc7wZyq+aPH+zP2cID+iEHZGz805k/FF1UmInpLCbIv3sAULoIQXXENQvOLcDs9u
w4FweYGjPYNMYQhEkCGFRUZRFPM7hFTApyQYgNGUiQsnYkUtOv2hndDX58dxCNigXFMQHJClabWf
yslkONCo6AnW5DOaVLpR7idjKKmhJTnmGSmeisoZsRPO6wDiJ33EwPEh4xrdfY6USlgQQLLGKZie
BfzIEepzso83hE3iX0o9aB7Q97weEAreg4quQv2KHQ4dEd4BZ7fzPQocPGCD1bCPSJ7joROYX7Ba
F7vYEfh+dEP0iH6Ef8g9obYoh/qn7C1KxgTJT9uP+M//VLiqZhtNk20IwFYKpYVSIRC43dWC4QYD
j//i7kinChIBB59v4A==
