diff -Naur libdvdnav.orig/src/dvdnav/dvdnav.h libdvdnav/src/dvdnav/dvdnav.h
--- libdvdnav.orig/src/dvdnav/dvdnav.h	2009-03-13 18:28:22.000000000 -0700
+++ libdvdnav/src/dvdnav/dvdnav.h	2009-04-30 10:56:29.000000000 -0700
@@ -279,6 +279,11 @@
 dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part);
 
 /*
+ * Plays the specified title, starting from the specified program
+ */
+dvdnav_status_t dvdnav_program_play(dvdnav_t *this, int32_t title, int32_t pgcn, int32_t pgn);
+
+/*
  * Stores in *times an array (that the application *must* free) of
  * dvdtimes corresponding to the chapter times for the chosen title.
  * *duration will have the duration of the title
@@ -320,6 +325,13 @@
 					  int32_t *part);
 
 /*
+ * Return the title number, pgcn and pgn currently being played.
+ * A title of 0 indicates, we are in a menu.
+ */
+dvdnav_status_t dvdnav_current_title_program(dvdnav_t *self, int32_t *title,
+					  int32_t *pgcn, int32_t *pgn);
+
+/*
  * Return the current position (in blocks) within the current
  * title and the length (in blocks) of said title.
  *
diff -Naur libdvdnav.orig/src/navigation.c libdvdnav/src/navigation.c
--- libdvdnav.orig/src/navigation.c	2009-01-08 14:57:11.000000000 -0800
+++ libdvdnav/src/navigation.c	2009-04-30 10:55:47.000000000 -0700
@@ -122,10 +122,90 @@
   return DVDNAV_STATUS_ERR;
 }
 
+dvdnav_status_t dvdnav_current_title_program(dvdnav_t *this, int32_t *title, int32_t *pgcn, int32_t *pgn) {
+  int32_t retval;
+  int32_t part;
+
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->vtsi || !this->vm->vmgi) {
+    printerr("Bad VM state.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->started) {
+    printerr("Virtual DVD machine not started.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if ( (this->vm->state.domain == VTSM_DOMAIN)
+      || (this->vm->state.domain == VMGM_DOMAIN) ) {
+    /* Get current Menu ID: into *part. */
+    if(! vm_get_current_menu(this->vm, &part)) {
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    if (part > -1) {
+      *title = 0;
+      *pgcn = this->vm->state.pgcN;
+      *pgn = this->vm->state.pgN;
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_OK;
+    }
+  }
+  if (this->vm->state.domain == VTS_DOMAIN) {
+    retval = vm_get_current_title_part(this->vm, title, &part);
+    *pgcn = this->vm->state.pgcN;
+    *pgn = this->vm->state.pgN;
+    pthread_mutex_unlock(&this->vm_lock);
+    return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
+  }
+  printerr("Not in a title or menu.");
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_ERR;
+}
+
 dvdnav_status_t dvdnav_title_play(dvdnav_t *this, int32_t title) {
   return dvdnav_part_play(this, title, 1);
 }
 
+dvdnav_status_t dvdnav_program_play(dvdnav_t *this, int32_t title, int32_t pgcn, int32_t pgn) {
+  int32_t retval;
+
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->vmgi) {
+    printerr("Bad VM state.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->started) {
+    /* don't report an error but be nice */
+    vm_start(this->vm);
+    this->started = 1;
+  }
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if((title < 1) || (title > this->vm->vmgi->tt_srpt->nr_of_srpts)) {
+    printerr("Title out of range.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  retval = vm_jump_title_program(this->vm, title, pgcn, pgn);
+  if (retval)
+    this->vm->hop_channel++;
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
+}
+
 dvdnav_status_t dvdnav_part_play(dvdnav_t *this, int32_t title, int32_t part) {
   int32_t retval;
 
diff -Naur libdvdnav.orig/src/vm/vm.c libdvdnav/src/vm/vm.c
--- libdvdnav.orig/src/vm/vm.c	2009-03-13 18:28:22.000000000 -0700
+++ libdvdnav/src/vm/vm.c	2009-04-30 11:07:35.000000000 -0700
@@ -83,6 +83,8 @@
 static int  set_PTT(vm_t *vm, int tt, int ptt);
 static int  set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn);
 static int  set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part);
+static int  set_PROG(vm_t *vm, int tt, int pgcn, int pgn);
+static int  set_VTS_PROG(vm_t *vm, int vtsN, int vts_ttn, int pgcn, int pgn);
 static int  set_FP_PGC(vm_t *vm);
 static int  set_MENU(vm_t *vm, int menu);
 static int  set_PGCN(vm_t *vm, int pgcN);
@@ -516,6 +518,24 @@
   return 1;
 }
 
+int vm_jump_title_program(vm_t *vm, int title, int pgcn, int pgn) {
+  link_t link;
+
+  if(!set_PROG(vm, title, pgcn, pgn))
+    return 0;
+  /* Some DVDs do not want us to jump directly into a title and have
+   * PGC pre commands taking us back to some menu. Since we do not like that,
+   * we do not execute PGC pre commands that would do a jump. */
+  /* process_command(vm, play_PGC_PG(vm, (vm->state).pgN)); */
+  link = play_PGC_PG(vm, (vm->state).pgN);
+  if (link.command != PlayThis)
+    /* jump occured -> ignore it and play the PG anyway */
+    process_command(vm, play_PG(vm));
+  else
+    process_command(vm, link);
+  return 1;
+}
+
 int vm_jump_title_part(vm_t *vm, int title, int part) {
   link_t link;
 
@@ -1644,6 +1664,42 @@
   return res;
 }
 
+static int set_PROG(vm_t *vm, int tt, int pgcn, int pgn) {
+  assert(tt <= vm->vmgi->tt_srpt->nr_of_srpts);
+  return set_VTS_PROG(vm, vm->vmgi->tt_srpt->title[tt - 1].title_set_nr,
+		     vm->vmgi->tt_srpt->title[tt - 1].vts_ttn, pgcn, pgn);
+}
+
+static int set_VTS_PROG(vm_t *vm, int vtsN, int vts_ttn, int pgcn, int pgn) {
+  int pgcN, pgN, res, title, part;
+
+  (vm->state).domain = VTS_DOMAIN;
+
+  if (vtsN != (vm->state).vtsN)
+    if (!ifoOpenNewVTSI(vm, vm->dvd, vtsN))  /* Also sets (vm->state).vtsN */
+      return 0;
+
+  if ((vts_ttn < 1) || (vts_ttn > vm->vtsi->vts_ptt_srpt->nr_of_srpts)) {
+    return 0;
+  }
+
+  pgcN = pgcn;
+  pgN = pgn;
+
+  (vm->state).TT_PGCN_REG = pgcN;
+  (vm->state).TTN_REG     = get_TT(vm, vtsN, vts_ttn);
+  assert( (vm->state.TTN_REG) != 0 );
+  (vm->state).VTS_TTN_REG = vts_ttn;
+  (vm->state).vtsN        = vtsN;  /* Not sure about this one. We can get to it easily from TTN_REG */
+  /* Any other registers? */
+
+  res = set_PGCN(vm, pgcN);   /* This clobber's state.pgN (sets it to 1), but we don't want clobbering here. */
+  (vm->state).pgN = pgN;
+  vm_get_current_title_part(vm, &title, &part);
+  (vm->state).PTTN_REG    = part;
+  return res;
+}
+
 static int set_FP_PGC(vm_t *vm) {
   (vm->state).domain = FP_DOMAIN;
   if (!vm->vmgi->first_play_pgc) {
diff -Naur libdvdnav.orig/src/vm/vm.h libdvdnav/src/vm/vm.h
--- libdvdnav.orig/src/vm/vm.h	2009-03-13 18:28:22.000000000 -0700
+++ libdvdnav/src/vm/vm.h	2009-04-30 10:57:02.000000000 -0700
@@ -139,6 +139,7 @@
 int vm_jump_pg(vm_t *vm, int pg);
 int vm_jump_cell_block(vm_t *vm, int cell, int block);
 int vm_jump_title_part(vm_t *vm, int title, int part);
+int vm_jump_title_program(vm_t *vm, int title, int pgcn, int pgn);
 int vm_jump_top_pg(vm_t *vm);
 int vm_jump_next_pg(vm_t *vm);
 int vm_jump_prev_pg(vm_t *vm);
