diff vm.c @ 0:3ddf0eaece51 src

Initial revision
author richwareham
date Tue, 12 Mar 2002 19:45:53 +0000
parents
children 328eadb3f37e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,1547 @@
+/*
+ * Copyright (C) 2000, 2001 Håkan Hjort
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <dvdread/ifo_types.h>
+#include <dvdread/ifo_read.h>
+
+#include "decoder.h"
+#include "vmcmd.h"
+#include "vm.h"
+
+/* Local prototypes */
+
+static void saveRSMinfo(vm_t *self,int cellN, int blockN);
+static int set_PGN(vm_t *self);
+static link_t play_PGC(vm_t *self);
+static link_t play_PG(vm_t *self);
+static link_t play_Cell(vm_t *self);
+static link_t play_Cell_post(vm_t *self);
+static link_t play_PGC_post(vm_t *self);
+static link_t process_command(vm_t *self,link_t link_values);
+
+static void ifoOpenNewVTSI(vm_t *self,dvd_reader_t *dvd, int vtsN);
+static pgcit_t* get_PGCIT(vm_t *self);
+static int get_video_aspect(vm_t *self);
+
+/* Can only be called when in VTS_DOMAIN */
+static int get_TT(vm_t *self,int tt);
+static int get_VTS_TT(vm_t *self,int vtsN, int vts_ttn);
+static int get_VTS_PTT(vm_t *self,int vtsN, int vts_ttn, int part);
+
+static int get_MENU(vm_t *self,int menu); /*  VTSM & VMGM */
+static int get_FP_PGC(vm_t *self); /*  FP */
+
+/* Called in any domain */
+static int get_ID(vm_t *self,int id);
+static int get_PGC(vm_t *self,int pgcN);
+static int get_PGCN(vm_t *self);
+
+/* Initialisation */
+
+vm_t* vm_new_vm() {
+  vm_t *vm = (vm_t*)calloc(sizeof(vm_t), sizeof(char));
+
+  return vm;
+}
+
+static void vm_print_current_domain_state(vm_t *self) {
+  switch((self->state).domain) {
+    case VTS_DOMAIN:
+      fprintf(stderr, "Video Title Domain: -\n");
+      break;
+
+    case VTSM_DOMAIN:
+      fprintf(stderr, "Video Title Menu Domain: -\n");
+      break;
+
+    case VMGM_DOMAIN:
+      fprintf(stderr, "Video Manager Menu Domain: -\n");
+      break;
+
+    case FP_DOMAIN: 
+      fprintf(stderr, "First Play Domain: -\n");
+      break;
+
+    default:
+      fprintf(stderr, "Unknown Domain: -\n");
+      break;
+  }
+  fprintf(stderr, "VTS:%d PG:%u CELL:%u BLOCK:%u VTS_TTN:%u TTN:%u TT_PGCN:%u\n", 
+                   (self->state).vtsN,
+                   (self->state).pgN,
+                   (self->state).cellN,
+                   (self->state).blockN,
+                   (self->state).VTS_TTN_REG,
+                   (self->state).TTN_REG,
+                   (self->state).TT_PGCN_REG);
+}
+
+void vm_stop(vm_t *self) {
+  if(!self)
+   return;
+
+  if(self->vmgi) {
+    ifoClose(self->vmgi);
+  }
+
+  if(self->vtsi) {
+    ifoClose(self->vtsi);
+  }
+  
+  if(self->dvd) {
+    DVDClose(self->dvd);
+  }
+}
+
+void vm_free_vm(vm_t *self) {
+  if(self) {
+    vm_stop(self);
+    free(self);
+  }
+}
+
+/* IFO Access */
+
+ifo_handle_t *vm_get_vmgi(vm_t *self) {
+  if(!self)
+   return NULL;
+  
+  return self->vmgi;
+}
+
+ifo_handle_t *vm_get_vtsi(vm_t *self) {
+  if(!self)
+   return NULL;
+  
+  return self->vtsi;
+}
+
+/* Reader Access */
+
+dvd_reader_t *vm_get_dvd_reader(vm_t *self) {
+  if(!self)
+   return NULL;
+  
+  return self->dvd;
+}
+
+int vm_reset(vm_t *self, char *dvdroot) /*  , register_t regs) */ { 
+  /*  Setup State */
+  memset((self->state).registers.SPRM, 0, sizeof(uint16_t)*24);
+  memset((self->state).registers.GPRM, 0, sizeof((self->state).registers.GPRM));
+  (self->state).registers.SPRM[0] = ('e'<<8)|'n'; /*  Player Menu Languange code */
+  (self->state).AST_REG = 15; /*  15 why? */
+  (self->state).SPST_REG = 62; /*  62 why? */
+  (self->state).AGL_REG = 1;
+  (self->state).TTN_REG = 1;
+  (self->state).VTS_TTN_REG = 1;
+  /* (self->state).TT_PGCN_REG = 0 */
+  (self->state).PTTN_REG = 1;
+  (self->state).HL_BTNN_REG = 1 << 10;
+
+  (self->state).PTL_REG = 15; /*  Parental Level */
+  (self->state).registers.SPRM[12] = ('U'<<8)|'S'; /*  Parental Management Country Code */
+  (self->state).registers.SPRM[16] = ('e'<<8)|'n'; /*  Initial Language Code for Audio */
+  (self->state).registers.SPRM[18] = ('e'<<8)|'n'; /*  Initial Language Code for Spu */
+  /*  Player Regional Code Mask. 
+   *  bit0 = Region 1
+   *  bit1 = Region 2
+   */
+  (self->state).registers.SPRM[20] = 0x1; /*  Player Regional Code Mask. Region free! */
+  (self->state).registers.SPRM[14] = 0x100; /* Try Pan&Scan */
+   
+  (self->state).pgN = 0;
+  (self->state).cellN = 0;
+
+  (self->state).domain = FP_DOMAIN;
+  (self->state).rsm_vtsN = 0;
+  (self->state).rsm_cellN = 0;
+  (self->state).rsm_blockN = 0;
+  
+  (self->state).vtsN = -1;
+  
+  self->dvd = DVDOpen(dvdroot);
+  if(!self->dvd) {
+    fprintf(stderr, "vm: faild to open/read the DVD\n");
+    return -1;
+  }
+
+  self->vmgi = ifoOpenVMGI(self->dvd);
+  if(!self->vmgi) {
+    fprintf(stderr, "vm: faild to read VIDEO_TS.IFO\n");
+    return -1;
+  }
+  if(!ifoRead_FP_PGC(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_FP_PGC failed\n");
+    return -1;
+  }
+  if(!ifoRead_TT_SRPT(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_TT_SRPT failed\n");
+    return -1;
+  }
+  if(!ifoRead_PGCI_UT(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_PGCI_UT failed\n");
+    return -1;
+  }
+  if(!ifoRead_PTL_MAIT(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_PTL_MAIT failed\n");
+    ; /*  return -1; Not really used for now.. */
+  }
+  if(!ifoRead_VTS_ATRT(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_VTS_ATRT failed\n");
+    ; /*  return -1; Not really used for now.. */
+  }
+  if(!ifoRead_VOBU_ADMAP(self->vmgi)) {
+    fprintf(stderr, "vm: ifoRead_VOBU_ADMAP vgmi failed\n");
+    ; /*  return -1; Not really used for now.. */
+  }
+  /* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */
+
+  return 0;
+}
+
+/*  FIXME TODO XXX $$$ Handle error condition too... */
+int vm_start(vm_t *self)
+{
+  link_t link_values;
+
+  /*  Set pgc to FP pgc */
+  get_FP_PGC(self);
+  link_values = play_PGC(self); 
+  link_values = process_command(self,link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+
+  return 0; /* ?? */
+}
+
+int vm_start_title(vm_t *self, int tt) {
+  link_t link_values;
+
+  get_TT(self, tt);
+  link_values = play_PGC(self); 
+  link_values = process_command(self, link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+
+  return 0; /* ?? */
+}
+
+int vm_jump_prog(vm_t *self, int pr) {
+  link_t link_values;
+
+  (self->state).pgN = pr; /*  ?? */
+
+  get_PGC(self, get_PGCN(self));
+  link_values = play_PG(self); 
+  link_values = process_command(self, link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+  
+  return 0; /* ?? */
+}
+
+int vm_eval_cmd(vm_t *self, vm_cmd_t *cmd)
+{
+  link_t link_values;
+  
+  if(vmEval_CMD(cmd, 1, &(self->state).registers, &link_values)) {
+    link_values = process_command(self, link_values);
+    assert(link_values.command == PlayThis);
+    (self->state).blockN = link_values.data1;
+    return 1; /*  Something changed, Jump */
+  } else {
+    return 0; /*  It updated some state thats all... */
+  }
+}
+
+int vm_get_next_cell(vm_t *self)
+{
+  link_t link_values;
+  link_values = play_Cell_post(self);
+  link_values = process_command(self,link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+  
+  return 0; /*  ?? */
+}
+
+int vm_top_pg(vm_t *self)
+{
+  link_t link_values;
+  link_values = play_PG(self);
+  link_values = process_command(self,link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+  
+  return 1; /*  Jump */
+}
+
+int vm_go_up(vm_t *self)
+{
+  link_t link_values;
+ 
+  if(get_PGC(self, (self->state).pgc->goup_pgc_nr))
+   assert(0);
+
+  link_values = play_PGC(self);
+  link_values = process_command(self,link_values);
+  assert(link_values.command == PlayThis);
+  (self->state).blockN = link_values.data1;
+  
+  return 1; /*  Jump */
+}
+
+int vm_next_pg(vm_t *self)
+{
+  /*  Do we need to get a updated pgN value first? */
+  (self->state).pgN += 1; 
+  return vm_top_pg(self);
+}
+
+int vm_prev_pg(vm_t *self)
+{
+  /*  Do we need to get a updated pgN value first? */
+  (self->state).pgN -= 1;
+  if((self->state).pgN == 0) {
+    /*  Check for previous PGCN ?  */
+    (self->state).pgN = 1;
+    /*  return 0; */
+  }
+  return vm_top_pg(self);
+}
+
+
+static domain_t menuid2domain(DVDMenuID_t menuid)
+{
+  domain_t result = VTSM_DOMAIN; /*  Really shouldn't have to.. */
+  
+  switch(menuid) {
+  case DVD_MENU_Title:
+    result = VMGM_DOMAIN;
+    break;
+  case DVD_MENU_Root:
+  case DVD_MENU_Subpicture:
+  case DVD_MENU_Audio:
+  case DVD_MENU_Angle:
+  case DVD_MENU_Part:
+    result = VTSM_DOMAIN;
+    break;
+  }
+  
+  return result;
+}
+
+int vm_menu_call(vm_t *self, DVDMenuID_t menuid, int block)
+{
+  domain_t old_domain;
+  link_t link_values;
+  
+  /* Should check if we are allowed/can acces this menu */
+  
+  
+  /* FIXME XXX $$$ How much state needs to be restored 
+   * when we fail to find a menu? */
+  
+  old_domain = (self->state).domain;
+  
+  switch((self->state).domain) {
+  case VTS_DOMAIN:
+    saveRSMinfo(self, 0, block);
+    /* FALL THROUGH */
+  case VTSM_DOMAIN:
+  case VMGM_DOMAIN:
+    (self->state).domain = menuid2domain(menuid);
+    if(get_PGCIT(self) != NULL && get_MENU(self, menuid) != -1) {
+      link_values = play_PGC(self);
+      link_values = process_command(self, link_values);
+      assert(link_values.command == PlayThis);
+      (self->state).blockN = link_values.data1;
+      return 1; /*  Jump */
+    } else {
+      (self->state).domain = old_domain;
+    }
+    break;
+  case FP_DOMAIN: /* FIXME XXX $$$ What should we do here? */
+    break;
+  }
+  
+  return 0;
+}
+
+
+int vm_resume(vm_t *self)
+{
+  int i;
+  link_t link_values;
+  
+  /*  Check and see if there is any rsm info!! */
+  if((self->state).rsm_vtsN == 0) {
+    return 0;
+  }
+  
+  (self->state).domain = VTS_DOMAIN;
+  ifoOpenNewVTSI(self, self->dvd, (self->state).rsm_vtsN);
+  get_PGC(self, (self->state).rsm_pgcN);
+  
+  /* These should never be set in SystemSpace and/or MenuSpace */ 
+  /*  (self->state).TTN_REG = (self->state).rsm_tt; */
+  /*  (self->state).TT_PGCN_REG = (self->state).rsm_pgcN; */
+  /*  (self->state).HL_BTNN_REG = (self->state).rsm_btnn; */
+  for(i = 0; i < 5; i++) {
+    (self->state).registers.SPRM[4 + i] = (self->state).rsm_regs[i];
+  }
+
+  if((self->state).rsm_cellN == 0) {
+    assert((self->state).cellN); /*  Checking if this ever happens */
+    (self->state).pgN = 1;
+    link_values = play_PG(self);
+    link_values = process_command(self, link_values);
+    assert(link_values.command == PlayThis);
+    (self->state).blockN = link_values.data1;
+  } else { 
+    (self->state).cellN = (self->state).rsm_cellN;
+    (self->state).blockN = (self->state).rsm_blockN;
+    /* (self->state).pgN = ?? does this gets the righ value in play_Cell, no! */
+    if(set_PGN(self)) {
+      /* Were at or past the end of the PGC, should not happen for a RSM */
+      assert(0);
+      play_PGC_post(self);
+    }
+  }
+  
+  return 1; /*  Jump */
+}
+
+/**
+ * Return the substream id for 'logical' audio stream audioN.
+ *  0 <= audioN < 8
+ */
+int vm_get_audio_stream(vm_t *self, int audioN)
+{
+  int streamN = -1;
+  printf("dvdnav:vm.c:get_audio_stream audioN=%d\n",audioN); 
+  if((self->state).domain == VTSM_DOMAIN 
+     || (self->state).domain == VMGM_DOMAIN
+     || (self->state).domain == FP_DOMAIN) {
+    audioN = 0;
+  }
+  
+  if(audioN < 8) {
+    /* Is there any contol info for this logical stream */ 
+    if((self->state).pgc->audio_control[audioN] & (1<<15)) {
+      streamN = ((self->state).pgc->audio_control[audioN] >> 8) & 0x07;  
+    }
+  }
+  
+  if((self->state).domain == VTSM_DOMAIN 
+     || (self->state).domain == VMGM_DOMAIN
+     || (self->state).domain == FP_DOMAIN) {
+    if(streamN == -1)
+      streamN = 0;
+  }
+  
+  /* Should also check in vtsi/vmgi status that what kind of stream
+   * it is (ac3/lpcm/dts/sdds...) to find the right (sub)stream id */
+  return streamN;
+}
+
+/**
+ * Return the substream id for 'logical' subpicture stream subpN.
+ * 0 <= subpN < 32
+ */
+int vm_get_subp_stream(vm_t *self, int subpN)
+{
+  int streamN = -1;
+  int source_aspect = get_video_aspect(self);
+  
+  if((self->state).domain == VTSM_DOMAIN 
+     || (self->state).domain == VMGM_DOMAIN
+     || (self->state).domain == FP_DOMAIN) {
+    subpN = 0;
+  }
+  
+  if(subpN < 32) { /* a valid logical stream */
+    /* Is this logical stream present */ 
+    if((self->state).pgc->subp_control[subpN] & (1<<31)) {
+      if(source_aspect == 0) /* 4:3 */	     
+	streamN = ((self->state).pgc->subp_control[subpN] >> 24) & 0x1f;  
+      if(source_aspect == 3) /* 16:9 */
+	streamN = ((self->state).pgc->subp_control[subpN] >> 16) & 0x1f;
+    }
+  }
+  
+  /* Paranoia.. if no stream select 0 anyway */
+/* I am not paranoid */
+/* if((self->state).domain == VTSM_DOMAIN 
+     || (self->state).domain == VMGM_DOMAIN
+     || (self->state).domain == FP_DOMAIN) {
+    if(streamN == -1)
+      streamN = 0;
+  }
+*/
+  /* Should also check in vtsi/vmgi status that what kind of stream it is. */
+  return streamN;
+}
+
+int vm_get_subp_active_stream(vm_t *self)
+{
+  int subpN;
+  int streamN;
+  subpN = (self->state).SPST_REG & ~0x40;
+  streamN = vm_get_subp_stream(self, subpN);
+  
+  /* If no such stream, then select the first one that exists. */
+  if(streamN == -1) {
+    for(subpN = 0; subpN < 32; subpN++) {
+      if((self->state).pgc->subp_control[subpN] & (1<<31)) {
+      
+        streamN = vm_get_subp_stream(self, subpN);
+        break;
+      }
+    }
+  } 
+  
+  /* We should instead send the on/off status to the spudecoder / mixer */
+  /* If we are in the title domain see if the spu mixing is on */
+  if((self->state).domain == VTS_DOMAIN && !((self->state).SPST_REG & 0x40)) { 
+     /* Bit 7 set means hide, and only let Forced display show */
+     return (streamN | 0x80); 
+  } else {
+    return streamN;
+  }
+}
+
+int vm_get_audio_active_stream(vm_t *self)
+{
+  int audioN;
+  int streamN;
+  audioN = (self->state).AST_REG ;
+  streamN = vm_get_audio_stream(self, audioN);
+  
+  /* If no such stream, then select the first one that exists. */
+  if(streamN == -1) {
+    for(audioN = 0; audioN < 8; audioN++) {
+      if((self->state).pgc->audio_control[audioN] & (1<<15)) {
+        streamN = vm_get_audio_stream(self, audioN);
+        break;
+      }
+    }
+  } 
+  
+  return streamN;
+}
+
+
+void vm_get_angle_info(vm_t *self, int *num_avail, int *current)
+{
+  *num_avail = 1;
+  *current = 1;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    /*  TTN_REG does not allways point to the correct title.. */
+    title_info_t *title;
+    if((self->state).TTN_REG > self->vmgi->tt_srpt->nr_of_srpts)
+      return;
+    title = &self->vmgi->tt_srpt->title[(self->state).TTN_REG - 1];
+    if(title->title_set_nr != (self->state).vtsN || 
+       title->vts_ttn != (self->state).VTS_TTN_REG)
+      return; 
+    *num_avail = title->nr_of_angles;
+    *current = (self->state).AGL_REG;
+    if(*current > *num_avail) /*  Is this really a good idea? */
+      *current = *num_avail; 
+  }
+}
+
+
+void vm_get_audio_info(vm_t *self, int *num_avail, int *current)
+{
+  if((self->state).domain == VTS_DOMAIN) {
+    *num_avail = self->vtsi->vtsi_mat->nr_of_vts_audio_streams;
+    *current = (self->state).AST_REG;
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    *num_avail = self->vtsi->vtsi_mat->nr_of_vtsm_audio_streams; /*  1 */
+    *current = 1;
+  } else if((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN) {
+    *num_avail = self->vmgi->vmgi_mat->nr_of_vmgm_audio_streams; /*  1 */
+    *current = 1;
+  }
+}
+
+void vm_get_subp_info(vm_t *self, int *num_avail, int *current)
+{
+  if((self->state).domain == VTS_DOMAIN) {
+    *num_avail = self->vtsi->vtsi_mat->nr_of_vts_subp_streams;
+    *current = (self->state).SPST_REG;
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    *num_avail = self->vtsi->vtsi_mat->nr_of_vtsm_subp_streams; /*  1 */
+    *current = 0x41;
+  } else if((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN) {
+    *num_avail = self->vmgi->vmgi_mat->nr_of_vmgm_subp_streams; /*  1 */
+    *current = 0x41;
+  }
+}
+
+subp_attr_t vm_get_subp_attr(vm_t *self, int streamN)
+{
+  subp_attr_t attr;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vts_subp_attr[streamN];
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vtsm_subp_attr;
+  } else if((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN) {
+    attr = self->vmgi->vmgi_mat->vmgm_subp_attr;
+  }
+  return attr;
+}
+
+audio_attr_t vm_get_audio_attr(vm_t *self, int streamN)
+{
+  audio_attr_t attr;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vts_audio_attr[streamN];
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vtsm_audio_attr;
+  } else if((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN) {
+    attr = self->vmgi->vmgi_mat->vmgm_audio_attr;
+  }
+  return attr;
+}
+
+video_attr_t vm_get_video_attr(vm_t *self)
+{
+  video_attr_t attr;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vts_video_attr;
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    attr = self->vtsi->vtsi_mat->vtsm_video_attr;
+  } else if((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN) {
+    attr = self->vmgi->vmgi_mat->vmgm_video_attr;
+  }
+  return attr;
+}
+
+void vm_get_video_res(vm_t *self, int *width, int *height)
+{
+  video_attr_t attr;
+  
+  attr = vm_get_video_attr(self);
+  
+  if(attr.video_format != 0) 
+    *height = 576;
+  else
+    *height = 480;
+  switch(attr.picture_size) {
+  case 0:
+    *width = 720;
+    break;
+  case 1:
+    *width = 704;
+    break;
+  case 2:
+    *width = 352;
+    break;
+  case 3:
+    *width = 352;
+    *height /= 2;
+    break;
+  }
+}
+
+/*  Must be called before domain is changed (get_PGCN()) */
+static void saveRSMinfo(vm_t *self, int cellN, int blockN)
+{
+  int i;
+  
+  if(cellN != 0) {
+    (self->state).rsm_cellN = cellN;
+    (self->state).rsm_blockN = 0;
+  } else {
+    (self->state).rsm_cellN = (self->state).cellN;
+    (self->state).rsm_blockN = blockN;
+  }
+  (self->state).rsm_vtsN = (self->state).vtsN;
+  (self->state).rsm_pgcN = get_PGCN(self);
+  
+  /* assert((self->state).rsm_pgcN == (self->state).TT_PGCN_REG); // for VTS_DOMAIN */
+  
+  for(i = 0; i < 5; i++) {
+    (self->state).rsm_regs[i] = (self->state).registers.SPRM[4 + i];
+  }
+}
+
+
+
+/* Figure out the correct pgN from the cell and update (self->state). */ 
+static int set_PGN(vm_t *self) {
+  int new_pgN = 0;
+  
+  while(new_pgN < (self->state).pgc->nr_of_programs 
+	&& (self->state).cellN >= (self->state).pgc->program_map[new_pgN])
+    new_pgN++;
+  
+  if(new_pgN == (self->state).pgc->nr_of_programs) /* We are at the last program */
+    if((self->state).cellN > (self->state).pgc->nr_of_cells)
+      return 1; /* We are past the last cell */
+  
+  (self->state).pgN = new_pgN;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    playback_type_t *pb_ty;
+    if((self->state).TTN_REG > self->vmgi->tt_srpt->nr_of_srpts)
+      return 0; /*  ?? */
+    pb_ty = &self->vmgi->tt_srpt->title[(self->state).TTN_REG - 1].pb_ty;
+    if(pb_ty->multi_or_random_pgc_title == /* One_Sequential_PGC_Title */ 0) {
+#if 0 /* TTN_REG can't be trusted to have a correct value here... */
+      vts_ptt_srpt_t *ptt_srpt = vtsi->vts_ptt_srpt;
+      assert((self->state).VTS_TTN_REG <= ptt_srpt->nr_of_srpts);
+      assert(get_PGCN() == ptt_srpt->title[(self->state).VTS_TTN_REG - 1].ptt[0].pgcn);
+      assert(1 == ptt_srpt->title[(self->state).VTS_TTN_REG - 1].ptt[0].pgn);
+#endif
+      (self->state).PTTN_REG = (self->state).pgN;
+    }
+  }
+  
+  return 0;
+}
+
+
+
+
+
+static link_t play_PGC(vm_t *self) 
+{    
+  link_t link_values;
+  
+  fprintf(stderr, "vm: play_PGC:");
+  if((self->state).domain != FP_DOMAIN)
+    fprintf(stderr, " (self->state).pgcN (%i)\n", get_PGCN(self));
+  else
+    fprintf(stderr, " first_play_pgc\n");
+
+  /*  This must be set before the pre-commands are executed because they */
+  /*  might contain a CallSS that will save resume state */
+  (self->state).pgN = 1;
+  (self->state).cellN = 0;
+
+  /* eval -> updates the state and returns either 
+     - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
+     - just play video i.e first PG
+       (This is what happens if you fall of the end of the pre_cmds)
+     - or a error (are there more cases?) */
+  if((self->state).pgc->command_tbl && (self->state).pgc->command_tbl->nr_of_pre) {
+    if(vmEval_CMD((self->state).pgc->command_tbl->pre_cmds, 
+		  (self->state).pgc->command_tbl->nr_of_pre, 
+		  &(self->state).registers, &link_values)) {
+      /*  link_values contains the 'jump' return value */
+      return link_values;
+    } else {
+      fprintf(stderr, "PGC pre commands didn't do a Jump, Link or Call\n");
+    }
+  }
+  return play_PG(self);
+}  
+
+
+static link_t play_PG(vm_t *self)
+{
+  fprintf(stderr, "play_PG: (self->state).pgN (%i)\n", (self->state).pgN);
+  
+  assert((self->state).pgN > 0);
+  if((self->state).pgN > (self->state).pgc->nr_of_programs) {
+    fprintf(stderr, "(self->state).pgN (%i) == pgc->nr_of_programs + 1 (%i)\n", 
+	    (self->state).pgN, (self->state).pgc->nr_of_programs + 1);
+    assert((self->state).pgN == (self->state).pgc->nr_of_programs + 1);
+    return play_PGC_post(self);
+  }
+  
+  (self->state).cellN = (self->state).pgc->program_map[(self->state).pgN - 1];
+  
+  return play_Cell(self);
+}
+
+
+static link_t play_Cell(vm_t *self)
+{
+  fprintf(stderr, "play_Cell: (self->state).cellN (%i)\n", (self->state).cellN);
+  
+  assert((self->state).cellN > 0);
+  if((self->state).cellN > (self->state).pgc->nr_of_cells) {
+    fprintf(stderr, "(self->state).cellN (%i) == pgc->nr_of_cells + 1 (%i)\n", 
+	    (self->state).cellN, (self->state).pgc->nr_of_cells + 1);
+    assert((self->state).cellN == (self->state).pgc->nr_of_cells + 1); 
+    return play_PGC_post(self);
+  }
+  
+
+  /* Multi angle/Interleaved */
+  switch((self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode) {
+  case 0: /*  Normal */
+    assert((self->state).pgc->cell_playback[(self->state).cellN - 1].block_type == 0);
+    break;
+  case 1: /*  The first cell in the block */
+    switch((self->state).pgc->cell_playback[(self->state).cellN - 1].block_type) {
+    case 0: /*  Not part of a block */
+      assert(0);
+    case 1: /*  Angle block */
+      /* Loop and check each cell instead? So we don't get outsid the block. */
+      (self->state).cellN += (self->state).AGL_REG - 1;
+      assert((self->state).cellN <= (self->state).pgc->nr_of_cells);
+      assert((self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode != 0);
+      assert((self->state).pgc->cell_playback[(self->state).cellN - 1].block_type == 1);
+      break;
+    case 2: /*  ?? */
+    case 3: /*  ?? */
+    default:
+      fprintf(stderr, "Invalid? Cell block_mode (%d), block_type (%d)\n",
+	      (self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode,
+	      (self->state).pgc->cell_playback[(self->state).cellN - 1].block_type);
+    }
+    break;
+  case 2: /*  Cell in the block */
+  case 3: /*  Last cell in the block */
+  /*  These might perhaps happen for RSM or LinkC commands? */
+  default:
+    fprintf(stderr, "Cell is in block but did not enter at first cell!\n");
+  }
+  
+  /* Updates (self->state).pgN and PTTN_REG */
+  if(set_PGN(self)) {
+    /* Should not happen */
+    link_t tmp = {LinkTailPGC, /* No Button */ 0, 0, 0};
+    assert(0);
+    return tmp;
+  }
+  
+  {
+    link_t tmp = {PlayThis, /* Block in Cell */ 0, 0, 0};
+    return tmp;
+  }
+
+}
+
+static link_t play_Cell_post(vm_t *self)
+{
+  cell_playback_t *cell;
+  
+  fprintf(stderr, "play_Cell_post: (self->state).cellN (%i)\n", (self->state).cellN);
+  
+  cell = &(self->state).pgc->cell_playback[(self->state).cellN - 1];
+  
+  /* Still time is already taken care of before we get called. */
+  
+  /* Deal with a Cell command, if any */
+  if(cell->cell_cmd_nr != 0) {
+    link_t link_values;
+    
+    assert((self->state).pgc->command_tbl != NULL);
+    assert((self->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr);
+    fprintf(stderr, "Cell command pressent, executing\n");
+    if(vmEval_CMD(&(self->state).pgc->command_tbl->cell_cmds[cell->cell_cmd_nr - 1], 1,
+		  &(self->state).registers, &link_values)) {
+      return link_values;
+    } else {
+       fprintf(stderr, "Cell command didn't do a Jump, Link or Call\n");
+      /*  Error ?? goto tail? goto next PG? or what? just continue? */
+    }
+  }
+  
+  
+  /* Where to continue after playing the cell... */
+  /* Multi angle/Interleaved */
+  switch((self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode) {
+  case 0: /*  Normal */
+    assert((self->state).pgc->cell_playback[(self->state).cellN - 1].block_type == 0);
+    (self->state).cellN++;
+    break;
+  case 1: /*  The first cell in the block */
+  case 2: /*  A cell in the block */
+  case 3: /*  The last cell in the block */
+  default:
+    switch((self->state).pgc->cell_playback[(self->state).cellN - 1].block_type) {
+    case 0: /*  Not part of a block */
+      assert(0);
+    case 1: /*  Angle block */
+      /* Skip the 'other' angles */
+      (self->state).cellN++;
+      while((self->state).cellN <= (self->state).pgc->nr_of_cells 
+	    && (self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode >= 2) {
+	(self->state).cellN++;
+      }
+      break;
+    case 2: /*  ?? */
+    case 3: /*  ?? */
+    default:
+      fprintf(stderr, "Invalid? Cell block_mode (%d), block_type (%d)\n",
+	      (self->state).pgc->cell_playback[(self->state).cellN - 1].block_mode,
+	      (self->state).pgc->cell_playback[(self->state).cellN - 1].block_type);
+    }
+    break;
+  }
+  
+  
+  /* Figure out the correct pgN for the new cell */ 
+  if(set_PGN(self)) {
+    fprintf(stderr, "last cell in this PGC\n");
+    return play_PGC_post(self);
+  }
+
+  return play_Cell(self);
+}
+
+
+static link_t play_PGC_post(vm_t *self)
+{
+  link_t link_values;
+
+  fprintf(stderr, "play_PGC_post:\n");
+  
+  assert((self->state).pgc->still_time == 0); /*  FIXME $$$ */
+
+  /* eval -> updates the state and returns either 
+     - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
+     - or a error (are there more cases?)
+     - if you got to the end of the post_cmds, then what ?? */
+  if((self->state).pgc->command_tbl &&
+     vmEval_CMD((self->state).pgc->command_tbl->post_cmds,
+		(self->state).pgc->command_tbl->nr_of_post, 
+		&(self->state).registers, &link_values)) {
+    return link_values;
+  }
+  
+  /*  Or perhaps handle it here? */
+  {
+    link_t link_next_pgc = {LinkNextPGC, 0, 0, 0};
+    fprintf(stderr, "** Fell of the end of the pgc, continuing in NextPGC\n");
+    assert((self->state).pgc->next_pgc_nr != 0);
+    return link_next_pgc;
+  }
+}
+
+
+static link_t process_command(vm_t *self, link_t link_values)
+{
+  /* FIXME $$$ Move this to a separate function? */
+  self->badness_counter++;
+  if (self->badness_counter > 1) fprintf(stderr, "**** process_command re-entered %d*****\n",self->badness_counter);
+  while(link_values.command != PlayThis) {
+    
+    vmPrint_LINK(link_values);
+    
+    fprintf(stderr, "Link values %i %i %i %i\n", link_values.command, 
+	    link_values.data1, link_values.data2, link_values.data3);
+     
+    fprintf(stderr, "Before:");
+    vm_print_current_domain_state(self);
+    
+    switch(link_values.command) {
+    case LinkNoLink:
+      /* No Link */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      exit(1);
+      
+    case LinkTopC:
+      /* Link to Top?? Cell */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_Cell(self);
+      break;
+    case LinkNextC:
+      /* Link to Next Cell */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      (self->state).cellN += 1; /* FIXME: What if cellN becomes > nr_of_cells? */
+      link_values = play_Cell(self);
+      break;
+    case LinkPrevC:
+      /* Link to Previous Cell */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      (self->state).cellN -= 1; /*  FIXME: What if cellN becomes < 1? */
+      link_values = play_Cell(self);
+      break;
+      
+    case LinkTopPG:
+      /* Link to Top Program */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      /*  Does pgN always contain the current value? */
+      link_values = play_PG(self);
+      break;
+    case LinkNextPG:
+      /* Link to Next Program */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      /*  Does pgN always contain the current value? */
+      (self->state).pgN += 1; /*  FIXME: What if pgN becomes > pgc.nr_of_programs? */
+      link_values = play_PG(self);
+      break;
+    case LinkPrevPG:
+      /* Link to Previous Program */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      /*  Does pgN always contain the current value? */
+      (self->state).pgN -= 1; /*  FIXME: What if pgN becomes < 1? */
+      link_values = play_PG(self);
+      break;
+      
+    case LinkTopPGC:
+      /* Link to Top Program Chain */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_PGC(self);
+      break;
+    case LinkNextPGC:
+      /* Link to Next Program Chain */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((self->state).pgc->next_pgc_nr != 0);
+      if(get_PGC(self, (self->state).pgc->next_pgc_nr))
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case LinkPrevPGC:
+      /* Link to Previous Program Chain */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((self->state).pgc->prev_pgc_nr != 0);
+      if(get_PGC(self, (self->state).pgc->prev_pgc_nr))
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case LinkGoUpPGC:
+      /* Link to GoUp??? Program Chain */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((self->state).pgc->goup_pgc_nr != 0);
+      if(get_PGC(self, (self->state).pgc->goup_pgc_nr))
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case LinkTailPGC:
+      /* Link to Tail??? Program Chain */
+      if(link_values.data1 != 0)
+	(self->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_PGC_post(self);
+      break;
+      
+    case LinkRSM:
+      {
+	/* Link to Resume */
+	int i;
+	/*  Check and see if there is any rsm info!! */
+	(self->state).domain = VTS_DOMAIN;
+	ifoOpenNewVTSI(self, self->dvd, (self->state).rsm_vtsN);
+	get_PGC(self, (self->state).rsm_pgcN);
+	
+	/* These should never be set in SystemSpace and/or MenuSpace */ 
+	/* (self->state).TTN_REG = rsm_tt; ?? */
+	/* (self->state).TT_PGCN_REG = (self->state).rsm_pgcN; ?? */
+	for(i = 0; i < 5; i++) {
+	  (self->state).registers.SPRM[4 + i] = (self->state).rsm_regs[i];
+	}
+	
+	if(link_values.data1 != 0)
+	  (self->state).HL_BTNN_REG = link_values.data1 << 10;
+	
+	if((self->state).rsm_cellN == 0) {
+	  assert((self->state).cellN); /*  Checking if this ever happens */
+	  /* assert( time/block/vobu is 0 ); */
+	  (self->state).pgN = 1;
+	  link_values = play_PG(self);
+	} else { 
+	  /* assert( time/block/vobu is _not_ 0 ); */
+	  /* play_Cell_at_time */
+	  /* (self->state).pgN = ?? this gets the righ value in play_Cell */
+	  (self->state).cellN = (self->state).rsm_cellN;
+	  link_values.command = PlayThis;
+	  link_values.data1 = (self->state).rsm_blockN;
+	  if(set_PGN(self)) {
+	    /* Were at the end of the PGC, should not happen for a RSM */
+	    assert(0);
+	    link_values.command = LinkTailPGC;
+	    link_values.data1 = 0;  /* No button */
+	  }
+	}
+      }
+      break;
+    case LinkPGCN:
+      /* Link to Program Chain Number:data1 */
+      if(get_PGC(self, link_values.data1))
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case LinkPTTN:
+      /* Link to Part of this Title Number:data1 */
+      assert((self->state).domain == VTS_DOMAIN);
+      if(link_values.data2 != 0)
+	(self->state).HL_BTNN_REG = link_values.data2 << 10;
+      if(get_VTS_PTT(self, (self->state).vtsN, (self->state).VTS_TTN_REG, link_values.data1) == -1)
+	assert(0);
+      link_values = play_PG(self);
+      break;
+    case LinkPGN:
+      /* Link to Program Number:data1 */
+      if(link_values.data2 != 0)
+	(self->state).HL_BTNN_REG = link_values.data2 << 10;
+      /* Update any other state, PTTN perhaps? */
+      (self->state).pgN = link_values.data1;
+      link_values = play_PG(self);
+      break;
+    case LinkCN:
+      /* Link to Cell Number:data1 */
+      if(link_values.data2 != 0)
+	(self->state).HL_BTNN_REG = link_values.data2 << 10;
+      /* Update any other state, pgN, PTTN perhaps? */
+      (self->state).cellN = link_values.data1;
+      link_values = play_Cell(self);
+      break;
+      
+    case Exit:
+      exit(1); /*  What should we do here?? */
+      
+    case JumpTT:
+      /* Jump to VTS Title Domain */
+      /* Only allowed from the First Play domain(PGC) */
+      /* or the Video Manager domain (VMG) */
+      //fprintf(stderr,"****** JumpTT is Broken, please fix me!!! ****\n");
+      assert((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN); /* ?? */
+      if(get_TT(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case JumpVTS_TT:
+      /* Jump to Title:data1 in same VTS Title Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Title Set Domain(VTS) */
+      assert((self->state).domain == VTSM_DOMAIN || (self->state).domain == VTS_DOMAIN); /* ?? */
+      /* FIXME: Should be able to use get_VTS_PTT here */
+      if(get_VTS_TT(self,(self->state).vtsN, link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case JumpVTS_PTT:
+      /* Jump to Part:data2 of Title:data1 in same VTS Title Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Title Set Domain(VTS) */
+      assert((self->state).domain == VTSM_DOMAIN || (self->state).domain == VTS_DOMAIN); /* ?? */
+      if(get_VTS_PTT(self,(self->state).vtsN, link_values.data1, link_values.data2) == -1)
+	assert(0);
+      link_values = play_PG(self);
+      break;
+      
+    case JumpSS_FP:
+      /* Jump to First Play Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Manager domain (VMG) */
+      assert((self->state).domain == VMGM_DOMAIN || (self->state).domain == VTSM_DOMAIN); /* ?? */
+      get_FP_PGC(self);
+      link_values = play_PGC(self);
+      break;
+    case JumpSS_VMGM_MENU:
+      /* Jump to Video Manger domain - Title Menu:data1 or any PGC in VMG */
+      /* Allowed from anywhere except the VTS Title domain */
+      assert((self->state).domain == VMGM_DOMAIN || 
+	     (self->state).domain == VTSM_DOMAIN || 
+	     (self->state).domain == FP_DOMAIN); /* ?? */
+      (self->state).domain = VMGM_DOMAIN;
+      if(get_MENU(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case JumpSS_VTSM:
+      /* Jump to a menu in Video Title domain, */
+      /* or to a Menu is the current VTS */
+      /* FIXME: This goes badly wrong for some DVDs. */
+      /* FIXME: Keep in touch with ogle people regarding what to do here */
+      fprintf(stderr, "dvdnav: BUG TRACKING *******************************************************************\n");
+      fprintf(stderr, "dvdnav:              If you see this message, please report these values to the dvd-devel mailing list.\n");
+      fprintf(stderr, "    data1=%u data2=%u data3=%u\n", 
+                link_values.data1,
+                link_values.data2,
+                link_values.data3);
+      fprintf(stderr, "dvdnav: *******************************************************************\n");
+
+      if(link_values.data1 !=0) {
+	assert((self->state).domain == VMGM_DOMAIN || (self->state).domain == FP_DOMAIN); /* ?? */
+	(self->state).domain = VTSM_DOMAIN;
+	ifoOpenNewVTSI(self, self->dvd, link_values.data1);  /*  Also sets (self->state).vtsN */
+      } else {
+	/*  This happens on 'The Fifth Element' region 2. */
+	assert((self->state).domain == VTSM_DOMAIN);
+      }
+      /*  I don't know what title is supposed to be used for. */
+      /*  Alien or Aliens has this != 1, I think. */
+      /* assert(link_values.data2 == 1); */
+      (self->state).VTS_TTN_REG = link_values.data2;
+      if(get_MENU(self, link_values.data3) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case JumpSS_VMGM_PGC:
+      assert((self->state).domain == VMGM_DOMAIN ||
+	     (self->state).domain == VTSM_DOMAIN ||
+	     (self->state).domain == FP_DOMAIN); /* ?? */
+      (self->state).domain = VMGM_DOMAIN;
+      if(get_PGC(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+      
+    case CallSS_FP:
+      assert((self->state).domain == VTS_DOMAIN); /* ??    */
+      /*  Must be called before domain is changed */
+      saveRSMinfo(self, link_values.data1, /* We dont have block info */ 0);
+      get_FP_PGC(self);
+      link_values = play_PGC(self);
+      break;
+    case CallSS_VMGM_MENU:
+      assert((self->state).domain == VTS_DOMAIN); /* ??    */
+      /*  Must be called before domain is changed */
+      saveRSMinfo(self,link_values.data2, /* We dont have block info */ 0);      
+      (self->state).domain = VMGM_DOMAIN;
+      if(get_MENU(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case CallSS_VTSM:
+      assert((self->state).domain == VTS_DOMAIN); /* ??    */
+      /*  Must be called before domain is changed */
+      saveRSMinfo(self,link_values.data2, /* We dont have block info */ 0);
+      (self->state).domain = VTSM_DOMAIN;
+      if(get_MENU(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case CallSS_VMGM_PGC:
+      assert((self->state).domain == VTS_DOMAIN); /* ??    */
+      /*  Must be called before domain is changed */
+      saveRSMinfo(self,link_values.data2, /* We dont have block info */ 0);
+      (self->state).domain = VMGM_DOMAIN;
+      if(get_PGC(self,link_values.data1) == -1)
+	assert(0);
+      link_values = play_PGC(self);
+      break;
+    case PlayThis:
+      /* Should never happen. */
+      break;
+    }
+  fprintf(stderr, "After:");
+  vm_print_current_domain_state(self);
+    
+  }
+  self->badness_counter--;
+  return link_values;
+}
+
+static int get_TT(vm_t *self, int tt)
+{  
+  //fprintf(stderr,"****** get_TT is Broken, please fix me!!! ****\n");
+  assert(tt <= self->vmgi->tt_srpt->nr_of_srpts);
+  
+  (self->state).TTN_REG = tt;
+   
+  return get_VTS_TT(self, self->vmgi->tt_srpt->title[tt - 1].title_set_nr,
+		    self->vmgi->tt_srpt->title[tt - 1].vts_ttn);
+}
+
+
+static int get_VTS_TT(vm_t *self, int vtsN, int vts_ttn)
+{
+  int pgcN;
+  //fprintf(stderr,"****** get_VTS_TT is Broken, please fix me!!! ****\n");
+  fprintf(stderr,"title_set_nr=%d\n", vtsN);
+  fprintf(stderr,"vts_ttn=%d\n", vts_ttn);
+  
+  (self->state).domain = VTS_DOMAIN;
+  if(vtsN != (self->state).vtsN) {
+    fprintf(stderr,"****** opening new VTSI ****\n");
+    ifoOpenNewVTSI(self, self->dvd, vtsN); /*  Also sets (self->state).vtsN */
+    fprintf(stderr,"****** opened VTSI ****\n");
+  }
+  
+  pgcN = get_ID(self, vts_ttn); /*  This might return -1 */
+  assert(pgcN != -1);
+
+  /* (self->state).TTN_REG = ?? Must search tt_srpt for a matching entry...   */
+  (self->state).VTS_TTN_REG = vts_ttn;
+  /* Any other registers? */
+  
+  return get_PGC(self, pgcN);
+}
+
+
+static int get_VTS_PTT(vm_t *self, int vtsN, int /* is this really */ vts_ttn, int part)
+{
+  int pgcN, pgN;
+  
+  (self->state).domain = VTS_DOMAIN;
+  if(vtsN != (self->state).vtsN)
+    ifoOpenNewVTSI(self, self->dvd, vtsN); /*  Also sets (self->state).vtsN */
+  
+  assert(vts_ttn <= self->vtsi->vts_ptt_srpt->nr_of_srpts);
+  assert(part <= self->vtsi->vts_ptt_srpt->title[vts_ttn - 1].nr_of_ptts);
+  
+  pgcN = self->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgcn;
+  pgN = self->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgn;
+  
+  /* (self->state).TTN_REG = ?? Must search tt_srpt for a matchhing entry... */
+  (self->state).VTS_TTN_REG = vts_ttn;
+  /* Any other registers? */
+  
+  (self->state).pgN = pgN; /*  ?? */
+  
+  return get_PGC(self, pgcN);
+}
+
+
+
+static int get_FP_PGC(vm_t *self)
+{  
+  (self->state).domain = FP_DOMAIN;
+
+  (self->state).pgc = self->vmgi->first_play_pgc;
+  
+  return 0;
+}
+
+
+static int get_MENU(vm_t *self, int menu)
+{
+  assert((self->state).domain == VMGM_DOMAIN || (self->state).domain == VTSM_DOMAIN);
+  return get_PGC(self, get_ID(self, menu));
+}
+
+static int get_ID(vm_t *self, int id)
+{
+  int pgcN, i;
+  pgcit_t *pgcit;
+  
+  /* Relies on state to get the correct pgcit. */
+  pgcit = get_PGCIT(self);
+  assert(pgcit != NULL);
+  
+  /* Get menu/title */
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    if((pgcit->pgci_srp[i].entry_id & 0x7f) == id) {
+      assert((pgcit->pgci_srp[i].entry_id & 0x80) == 0x80);
+      pgcN = i + 1;
+      return pgcN;
+    }
+  }
+  fprintf(stderr, "** No such id/menu (%d) entry PGC\n", id);
+  return -1; /*  error */
+}
+
+
+
+static int get_PGC(vm_t *self, int pgcN)
+{
+  /* FIXME: Keep this up to date with the ogle people */
+  pgcit_t *pgcit;
+  
+  pgcit = get_PGCIT(self);
+  
+  assert(pgcit != NULL); /*  ?? Make this return -1 instead */
+  if(pgcN < 1 || pgcN > pgcit->nr_of_pgci_srp) {
+/*    if(pgcit->nr_of_pgci_srp != 1)  */
+     return -1; /* error */
+/*   pgcN = 1; */
+  }
+  
+  /* (self->state).pgcN = pgcN; */
+  (self->state).pgc = pgcit->pgci_srp[pgcN - 1].pgc;
+  
+  if((self->state).domain == VTS_DOMAIN)
+    (self->state).TT_PGCN_REG = pgcN;
+
+  return 0;
+}
+
+static int get_PGCN(vm_t *self)
+{
+  pgcit_t *pgcit;
+  int pgcN = 1;
+
+  pgcit = get_PGCIT(self);
+  
+  assert(pgcit != NULL);
+  
+  while(pgcN <= pgcit->nr_of_pgci_srp) {
+    if(pgcit->pgci_srp[pgcN - 1].pgc == (self->state).pgc)
+      return pgcN;
+    pgcN++;
+  }
+  
+  return -1; /*  error */
+}
+
+
+static int get_video_aspect(vm_t *self)
+{
+  int aspect = 0;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    aspect = self->vtsi->vtsi_mat->vts_video_attr.display_aspect_ratio;  
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    aspect = self->vtsi->vtsi_mat->vtsm_video_attr.display_aspect_ratio;
+  } else if((self->state).domain == VMGM_DOMAIN) {
+    aspect = self->vmgi->vmgi_mat->vmgm_video_attr.display_aspect_ratio;
+  }
+  fprintf(stderr, "dvdnav:get_video_aspect:aspect=%d\n",aspect);
+  assert(aspect == 0 || aspect == 3);
+  (self->state).registers.SPRM[14] &= ~(0x3 << 10);
+  (self->state).registers.SPRM[14] |= aspect << 10;
+  
+  return aspect;
+}
+
+
+
+
+
+
+static void ifoOpenNewVTSI(vm_t *self, dvd_reader_t *dvd, int vtsN) 
+{
+  if((self->state).vtsN == vtsN) {
+    return; /*  We alread have it */
+  }
+  
+  if(self->vtsi != NULL)
+    ifoClose(self->vtsi);
+  
+  self->vtsi = ifoOpenVTSI(dvd, vtsN);
+  if(self->vtsi == NULL) {
+    fprintf(stderr, "ifoOpenVTSI failed\n");
+    exit(1);
+  }
+  if(!ifoRead_VTS_PTT_SRPT(self->vtsi)) {
+    fprintf(stderr, "ifoRead_VTS_PTT_SRPT failed\n");
+    exit(1);
+  }
+  if(!ifoRead_PGCIT(self->vtsi)) {
+    fprintf(stderr, "ifoRead_PGCIT failed\n");
+    exit(1);
+  }
+  if(!ifoRead_PGCI_UT(self->vtsi)) {
+    fprintf(stderr, "ifoRead_PGCI_UT failed\n");
+    exit(1);
+  }
+  if(!ifoRead_VOBU_ADMAP(self->vtsi)) {
+    fprintf(stderr, "ifoRead_VOBU_ADMAP vtsi failed\n");
+    exit(1);
+  }
+  if(!ifoRead_TITLE_VOBU_ADMAP(self->vtsi)) {
+    fprintf(stderr, "ifoRead_TITLE_VOBU_ADMAP vtsi failed\n");
+    exit(1);
+  }
+  (self->state).vtsN = vtsN;
+}
+
+
+
+
+static pgcit_t* get_MENU_PGCIT(vm_t *self, ifo_handle_t *h, uint16_t lang)
+{
+  int i;
+  
+  if(h == NULL || h->pgci_ut == NULL) {
+    fprintf(stderr, "*** pgci_ut handle is NULL ***\n");
+    return NULL; /*  error? */
+  }
+  
+  i = 0;
+  while(i < h->pgci_ut->nr_of_lus
+	&& h->pgci_ut->lu[i].lang_code != lang)
+    i++;
+  if(i == h->pgci_ut->nr_of_lus) {
+    fprintf(stderr, "Language '%c%c' not found, using '%c%c' instead\n",
+	    (char)(lang >> 8), (char)(lang & 0xff),
+ 	    (char)(h->pgci_ut->lu[0].lang_code >> 8),
+	    (char)(h->pgci_ut->lu[0].lang_code & 0xff));
+    i = 0; /*  error? */
+  }
+  
+  return h->pgci_ut->lu[i].pgcit;
+}
+
+/* Uses state to decide what to return */
+static pgcit_t* get_PGCIT(vm_t *self) {
+  pgcit_t *pgcit;
+  
+  if((self->state).domain == VTS_DOMAIN) {
+    pgcit = self->vtsi->vts_pgcit;
+  } else if((self->state).domain == VTSM_DOMAIN) {
+    pgcit = get_MENU_PGCIT(self, self->vtsi, (self->state).registers.SPRM[0]);
+  } else if((self->state).domain == VMGM_DOMAIN) {
+    pgcit = get_MENU_PGCIT(self, self->vmgi, (self->state).registers.SPRM[0]);
+  } else {
+    pgcit = NULL;    /* Should never hapen */
+  }
+  
+  return pgcit;
+}
+
+/*
+ * $Log$
+ * Revision 1.1  2002/03/12 19:45:55  richwareham
+ * Initial revision
+ *
+ * Revision 1.18  2002/01/22 16:56:49  jcdutton
+ * Fix clut after seeking.
+ * Add a few virtual machine debug messages, to help diagnose problems with "Deep Purple - Total Abandon" DVD as I don't have the DVD itself.
+ * Fix a few debug messages, so they do not say FIXME.
+ * Move the FIXME debug messages to comments in the code.
+ *
+ * Revision 1.17  2002/01/21 01:16:30  jcdutton
+ * Added some debug messages, to hopefully get info from users.
+ *
+ * Revision 1.16  2002/01/20 21:40:46  jcdutton
+ * Start to fix some assert failures.
+ *
+ * Revision 1.15  2002/01/19 20:24:38  jcdutton
+ * Just some FIXME notes added.
+ *
+ * Revision 1.14  2002/01/13 22:17:57  jcdutton
+ * Change logging.
+ *
+ *
+ */