changeset 0:3ddf0eaece51 src

Initial revision
author richwareham
date Tue, 12 Mar 2002 19:45:53 +0000
parents
children ce4358919a67
files Makefile.am decoder.c decoder.h dvd_types.h dvdnav.c dvdnav.h dvdnav_events.h dvdnav_internal.h highlight.c navigation.c read_cache.c read_cache.h searching.c settings.c vm.c vm.h vmcmd.c vmcmd.h
diffstat 18 files changed, 6054 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.am	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,48 @@
+CFLAGS = @GLOBAL_CFLAGS@
+DEBUG_CFLAGS = @DEBUG_CFLAGS@
+
+lib_LTLIBRARIES = libdvdnav.la
+
+libdvdnav_la_SOURCES = decoder.c dvdnav.c vm.c vmcmd.c \
+		       read_cache.c navigation.c highlight.c \
+		       searching.c settings.c
+libdvdnav_la_LDFLAGS = $(DVDREAD_LIBS)		       
+
+include_HEADERS = decoder.h dvdnav.h dvdnav_events.h \
+	dvdnav_internal.h vm.h vmcmd.h read_cache.h dvd_types.h
+
+###
+# Install header files (default=$includedir/xine)
+#
+install-includeHEADERS: $(include_HEADERS)
+	@$(NORMAL_INSTALL)
+	$(mkinstalldirs) $(DESTDIR)$(includedir)/dvdnav
+	@list='$(include_HEADERS)'; for p in $$list; do \
+	  if test -f "$$p"; then d= ; else d="$(srcdir)/"; fi; \
+	  echo " $(INSTALL_DATA) $$d$$p $(DESTDIR)$(includedir)/dvdnav/$$p"; \
+	  $(INSTALL_DATA) $$d$$p $(DESTDIR)$(includedir)/dvdnav/$$p; \
+	done
+
+
+###
+# Remove them
+#
+uninstall-includeHEADERS:
+	@$(NORMAL_UNINSTALL)
+	list='$(include_HEADERS)'; for p in $$list; do \
+	  rm -f $(DESTDIR)$(includedir)/dvdnav/$$p; \
+	done
+
+debug:
+	$(MAKE) CFLAGS="$(DEBUG_CFLAGS)"
+
+install-debug: debug
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+mostlyclean-generic:
+	-rm -f *~ \#* .*~ .\#*
+
+maintainer-clean-generic:
+	-@echo "This command is intended for maintainers to use;"
+	-@echo "it deletes files that may require special tools to rebuild."
+	-rm -f Makefile.in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/decoder.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * 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 <inttypes.h>
+#include <string.h>  /* For memset */
+#include <dvdread/ifo_types.h> /* vm_cmd_t */
+#include "vmcmd.h"
+#include "decoder.h"
+
+#ifndef bool
+typedef int bool;
+#endif
+
+typedef struct
+{
+  uint8_t bits[8];
+  uint8_t examined[8];
+} cmd_t;
+
+/*  Fix theses two.. pass as parameters instead. */
+static cmd_t cmd;
+static registers_t *state;
+
+/* Get count bits of command from byte and bit position. */
+static uint32_t bits(int byte, int bit, int count) {
+  uint32_t val = 0;
+  int bit_mask;
+  
+  while(count--) {
+    if(bit > 7) {
+      bit = 0;
+      byte++;
+    }
+    bit_mask = 0x01 << (7 - bit);
+    val <<= 1;
+    if(cmd.bits[byte] & bit_mask)
+      val |= 1;
+    cmd.examined[byte] |= bit_mask;
+    bit++;
+  }
+  return val;
+}
+
+
+/* Eval register code, can either be system or general register.
+   SXXX_XXXX, where S is 1 if it is system register. */
+static uint16_t eval_reg(uint8_t reg) {
+  if(reg & 0x80) {
+    return state->SPRM[reg & 0x1f]; /*  FIXME max 24 not 32 */
+  } else {
+    return state->GPRM[reg & 0x0f];
+  }
+}
+
+/* Eval register or immediate data.
+   AAAA_AAAA BBBB_BBBB, if immediate use all 16 bits for data else use
+   lower eight bits for the system or general purpose register. */
+static uint16_t eval_reg_or_data(int imm, int byte) {
+  if(imm) { /*  immediate */
+    return bits(byte, 0, 16);
+  } else {
+    return eval_reg(bits(byte + 1, 0, 8));
+  }
+}
+
+/* Eval register or immediate data.
+   xBBB_BBBB, if immediate use all 7 bits for data else use
+   lower four bits for the general purpose register number. */
+/* Evaluates gprm or data depending on bit, data is in byte n */
+uint16_t eval_reg_or_data_2(int imm, int byte) {
+  if(imm) /* immediate */
+    return bits(byte, 1, 7);
+  else
+    return state->GPRM[bits(byte, 4, 4)];
+}
+
+
+/* Compare data using operation, return result from comparison. 
+   Helper function for the different if functions. */
+static bool eval_compare(uint8_t operation, uint16_t data1, uint16_t data2) {
+  switch(operation) {
+    case 1:
+      return data1 & data2;
+    case 2:
+      return data1 == data2;
+    case 3:
+      return data1 != data2;
+    case 4:
+      return data1 >= data2;
+    case 5:
+      return data1 >  data2;
+    case 6:
+      return data1 <= data2;
+    case 7:
+      return data1 <  data2;
+  }
+  fprintf(stderr,"eval_compare: Invalid comparison code\n");
+  return 0;
+}
+
+
+/* Evaluate if version 1.
+   Has comparison data in byte 3 and 4-5 (immediate or register) */
+static bool eval_if_version_1(void) {
+  uint8_t op = bits(1, 1, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(bits(3, 0, 8)), 
+                            eval_reg_or_data(bits(1, 0, 1), 4));
+  }
+  return 1;
+}
+
+/* Evaluate if version 2.
+   This version only compares register which are in byte 6 and 7 */
+static bool eval_if_version_2(void) {
+  uint8_t op = bits(1, 1, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(bits(6, 0, 8)), 
+                            eval_reg(bits(7, 0, 8)));
+  }
+  return 1;
+}
+
+/* Evaluate if version 3.
+   Has comparison data in byte 2 and 6-7 (immediate or register) */
+static bool eval_if_version_3(void) {
+  uint8_t op = bits(1, 1, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(bits(2, 0, 8)), 
+                            eval_reg_or_data(bits(1, 0, 1), 6));
+  }
+  return 1;
+}
+
+/* Evaluate if version 4.
+   Has comparison data in byte 1 and 4-5 (immediate or register) 
+   The register in byte 1 is only the lowe nibble (4bits) */
+static bool eval_if_version_4(void) {
+  uint8_t op = bits(1, 1, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(bits(1, 4, 4)), 
+                            eval_reg_or_data(bits(1, 0, 1), 4));
+  }
+  return 1;
+}
+
+/* Evaluate special instruction.... returns the new row/line number,
+   0 if no new row and 256 if Break. */
+static int eval_special_instruction(bool cond) {
+  int line, level;
+  
+  switch(bits(1, 4, 4)) {
+    case 0: /*  NOP */
+      line = 0;
+      return cond ? line : 0;
+    case 1: /*  Goto line */
+      line = bits(7, 0, 8);
+      return cond ? line : 0;
+    case 2: /*  Break */
+      /*  max number of rows < 256, so we will end this set */
+      line = 256;
+      return cond ? 256 : 0;
+    case 3: /*  Set temporary parental level and goto */
+      line = bits(7, 0, 8); 
+      level = bits(6, 4, 4);
+      if(cond) {
+	/*  This always succeeds now, if we want real parental protection */
+	/*  we need to ask the user and have passwords and stuff. */
+	state->SPRM[13] = level;
+      }
+      return cond ? line : 0;
+  }
+  return 0;
+}
+
+/* Evaluate link by subinstruction.
+   Return 1 if link, or 0 if no link
+   Actual link instruction is in return_values parameter */
+static bool eval_link_subins(bool cond, link_t *return_values) {
+  uint16_t button = bits(6, 0, 6);
+  uint8_t  linkop = bits(7, 3, 5);
+  
+  if(linkop > 0x10)
+    return 0;    /*  Unknown Link by Sub-Instruction command */
+
+  /*  Assumes that the link_cmd_t enum has the same values as the LinkSIns codes */
+  return_values->command = linkop;
+  return_values->data1 = button;
+  return cond;
+}
+
+
+/* Evaluate link instruction.
+   Return 1 if link, or 0 if no link
+   Actual link instruction is in return_values parameter */
+static bool eval_link_instruction(bool cond, link_t *return_values) {
+  uint8_t op = bits(1, 4, 4);
+  
+  switch(op) {
+    case 1:
+	return eval_link_subins(cond, return_values);
+    case 4:
+	return_values->command = LinkPGCN;
+	return_values->data1   = bits(6, 1, 15);
+	return cond;
+    case 5:
+	return_values->command = LinkPTTN;
+	return_values->data1 = bits(6, 6, 10);
+	return_values->data2 = bits(6, 0, 6);
+	return cond;
+    case 6:
+	return_values->command = LinkPGN;
+	return_values->data1 = bits(7, 1, 7);
+	return_values->data2 = bits(6, 0, 6);
+	return cond;
+    case 7:
+	return_values->command = LinkCN;
+	return_values->data1 = bits(7, 0, 8);
+	return_values->data2 = bits(6, 0, 6);
+	return cond;
+  }
+  return 0;
+}
+
+
+/* Evaluate a jump instruction.
+   returns 1 if jump or 0 if no jump
+   actual jump instruction is in return_values parameter */
+static bool eval_jump_instruction(bool cond, link_t *return_values) {
+  
+  switch(bits(1, 4, 4)) {
+    case 1:
+      return_values->command = Exit;
+      return cond;
+    case 2:
+      return_values->command = JumpTT;
+      return_values->data1 = bits(5, 1, 7);
+      return cond;
+    case 3:
+      return_values->command = JumpVTS_TT;
+      return_values->data1 = bits(5, 1, 7);
+      return cond;
+    case 5:
+      return_values->command = JumpVTS_PTT;
+      return_values->data1 = bits(5, 1, 7);
+      return_values->data2 = bits(2, 6, 10);
+      return cond;
+    case 6:
+      switch(bits(5,0,2)) {
+        case 0:
+          return_values->command = JumpSS_FP;
+          return cond;
+        case 1:
+          return_values->command = JumpSS_VMGM_MENU;
+          return_values->data1 =  bits(5, 4, 4);
+          return cond;
+        case 2:
+          return_values->command = JumpSS_VTSM;
+          return_values->data1 =  bits(4, 0, 8);
+          return_values->data2 =  bits(3, 0, 8);
+          return_values->data3 =  bits(5, 4, 4);
+          return cond;
+        case 3:
+          return_values->command = JumpSS_VMGM_PGC;
+          return_values->data1 =  bits(2, 1, 15);
+          return cond;
+        }
+      break;
+    case 8:
+      switch(bits(5,0,2)) {
+        case 0:
+          return_values->command = CallSS_FP;
+          return_values->data1 = bits(4, 0, 8);
+          return cond;
+        case 1:
+          return_values->command = CallSS_VMGM_MENU;
+          return_values->data1 = bits(5, 4, 4);
+          return_values->data2 = bits(4, 0 ,8);
+          return cond;
+        case 2:
+          return_values->command = CallSS_VTSM;
+          return_values->data1 = bits(5, 4, 4);
+          return_values->data2 = bits(4, 0, 8);
+          return cond;
+        case 3:
+          return_values->command = CallSS_VMGM_PGC;
+          return_values->data1 = bits(2, 1, 15);
+          return_values->data2 = bits(4, 0, 8);
+          return cond;
+      }
+      break;
+  }
+  return 0;
+}
+
+/* Evaluate a set sytem register instruction 
+   May contain a link so return the same as eval_link */
+static bool eval_system_set(int cond, link_t *return_values) {
+  int i;
+  uint16_t data, data2;
+  
+  switch(bits(0, 4, 4)) {
+    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
+      for(i = 1; i <= 3; i++) {
+        if(bits(2 + i, 0, 1)) {
+          data = eval_reg_or_data_2(bits(0, 3, 1), 2 + i);
+          if(cond) {
+            state->SPRM[i] = data;
+          }
+        }
+      }
+      break;
+    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
+      data = eval_reg_or_data(bits(0, 3, 1), 2);
+      data2 = bits(5, 0, 8); /*  ?? size */
+      if(cond) {
+	state->SPRM[9] = data; /*  time */
+	state->SPRM[10] = data2; /*  pgcN */
+      }
+      break;
+    case 3: /*  Mode: Counter / Register + Set */
+      data = eval_reg_or_data(bits(0, 3, 1), 2);
+      data2 = bits(5, 4, 4);
+      if(bits(5, 0, 1)) {
+	fprintf(stderr, "Detected SetGPRMMD Counter!! This is unsupported.\n");
+	/* exit(-1); */
+      } else {
+	;
+      }
+      if(cond) {
+	state->GPRM[data2] = data;
+      }
+      break;
+    case 6: /*  Set system reg 8 (Highlighted button) */
+      data = eval_reg_or_data(bits(0, 3, 1), 4); /*  Not system reg!! */
+      if(cond) {
+	state->SPRM[8] = data;
+      }
+      break;
+  }
+  if(bits(1, 4, 4)) {
+    return eval_link_instruction(cond, return_values);
+  }
+  return 0;
+}
+
+
+/* Evaluate set operation
+   Sets the register given to the value indicated by op and data.
+   For the swap case the contents of reg is stored in reg2.
+*/
+static void eval_set_op(int op, int reg, int reg2, int data) {
+  switch(op) {
+    case 1:
+      state->GPRM[reg] = data;
+      break;
+    case 2: /* SPECIAL CASE - SWAP! */
+      state->GPRM[reg2] = state->GPRM[reg];
+      state->GPRM[reg] = data;
+      break;
+    case 3:
+      state->GPRM[reg] += data;
+      break;
+    case 4:
+      state->GPRM[reg] -= data;
+      break;
+    case 5:
+      state->GPRM[reg] *= data;
+      break;
+    case 6:
+      state->GPRM[reg] /= data;
+      break;
+    case 7:
+      state->GPRM[reg] %= data;
+      break;
+    case 8: /* SPECIAL CASE - RND! */
+      state->GPRM[reg] += data; /*  TODO FIXME */
+      break;
+    case 9:
+      state->GPRM[reg] &= data;
+      break;
+    case 10:
+      state->GPRM[reg] |= data;
+      break;
+    case 11:
+      state->GPRM[reg] ^= data;
+      break;
+  }
+}
+
+/* Evaluate set instruction, combined with either Link or Compare. */
+static void eval_set_version_1(int cond) {
+  uint8_t  op   = bits(0, 4, 4);
+  uint8_t  reg  = bits(3, 4, 4); /*  Erhumm.. */
+  uint8_t  reg2 = bits(5, 4, 4);
+  uint16_t data = eval_reg_or_data(bits(0, 3, 1), 4);
+
+  if(cond) {
+    eval_set_op(op, reg, reg2, data);
+  }
+}
+
+
+/* Evaluate set instruction, combined with both Link and Compare. */
+static void eval_set_version_2(int cond) {
+  uint8_t  op   = bits(0, 4, 4);
+  uint8_t  reg  = bits(1, 4, 4);
+  uint8_t  reg2 = bits(3, 4, 4); /*  Erhumm.. */
+  uint16_t data = eval_reg_or_data(bits(0, 3, 1), 2);
+
+  if(cond) {
+    eval_set_op(op, reg, reg2, data);
+  }
+}
+
+
+/* Evaluate a command
+   returns row number of goto, 0 if no goto, -1 if link.
+   Link command in return_values */
+static int eval_command(uint8_t *bytes, link_t *return_values) {
+  int i, extra_bits;
+  int cond, res = 0;
+
+  for(i = 0; i < 8; i++) {
+    cmd.bits[i] = bytes[i];
+    cmd.examined[i] = 0;
+  }
+  memset(return_values, 0, sizeof(link_t));
+
+  switch(bits(0, 0, 3)) { /* three first bits */
+    case 0: /*  Special instructions */
+      cond = eval_if_version_1();
+      res = eval_special_instruction(cond);
+      if(res == -1) {
+	fprintf(stderr, "Unknown Instruction!\n");
+	/* exit(0); */
+      }
+      break;
+    case 1: /*  Link/jump instructions */
+      if(bits(0, 3, 1)) {
+        cond = eval_if_version_2();
+        res = eval_jump_instruction(cond, return_values);
+      } else {
+        cond = eval_if_version_1();
+        res = eval_link_instruction(cond, return_values);
+      }
+      if(res)
+	res = -1;
+      break;
+    case 2: /*  System set instructions */
+      cond = eval_if_version_2();
+      res = eval_system_set(cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 3: /*  Set instructions, either Compare or Link may be used */
+      cond = eval_if_version_3();
+      eval_set_version_1(cond);
+      if(bits(1, 4, 4)) {
+	res = eval_link_instruction(cond, return_values);
+      }
+      if(res)
+	res = -1;
+      break;
+    case 4: /*  Set, Compare -> Link Sub-Instruction */
+      eval_set_version_2(/*True*/ 1);
+      cond = eval_if_version_4();
+      res = eval_link_subins(cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 5: /*  Compare -> (Set and Link Sub-Instruction) */
+      cond = eval_if_version_4();
+      eval_set_version_2(cond);
+      res = eval_link_subins(cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 6: /*  Compare -> Set, allways Link Sub-Instruction */
+      cond = eval_if_version_4();
+      eval_set_version_2(cond);
+      res = eval_link_subins(/*True*/ 1, return_values);
+      if(res)
+	res = -1;
+      break;
+  }
+  /*  Check if there are bits not yet examined */
+
+  extra_bits = 0;
+  for(i = 0; i < 8; i++)
+    if(cmd.bits [i] & ~cmd.examined [i]) {
+      extra_bits = 1;
+      break;
+    }
+  if(extra_bits) {
+    fprintf(stderr, "[WARNING, unknown bits:");
+    for(i = 0; i < 8; i++)
+      fprintf(stderr, " %02x", cmd.bits [i] & ~cmd.examined [i]);
+    fprintf(stderr, "]\n");
+  }
+
+  return res;
+}
+
+/* Evaluate a set of commands in the given register set (which is
+ * modified */
+int vmEval_CMD(vm_cmd_t commands[], int num_commands, 
+	       registers_t *registers, link_t *return_values) {
+  int i = 0;
+  int total = 0;
+  
+  state = registers; /*  TODO FIXME */
+
+#ifdef TRACE
+  /*  DEBUG */
+  if(1) {
+    int i;
+    fprintf(stderr, "   #   ");
+    for(i = 0; i < 24; i++)
+    fprintf(stderr, " %2d |", i);
+    fprintf(stderr, "\nSRPMS: ");
+    for(i = 0; i < 24; i++)
+      fprintf(stderr, "%04x|", state->SPRM[i]);
+    fprintf(stderr, "\nGRPMS: ");
+    for(i = 0; i < 16; i++)
+      fprintf(stderr, "%04x|", state->GPRM[i]);
+    fprintf(stderr, "\n");
+  }
+  if(1) {
+    int i;
+    for(i = 0; i < num_commands; i++)
+      vmPrint_CMD(i, &commands[i]);
+    fprintf(stderr, "--------------------------------------------\n");
+  } /*  end DEBUG */
+#endif
+  
+  while(i < num_commands && total < 100000) {
+    int line;
+    
+    if(0) vmPrint_CMD(i, &commands[i]);
+    line = eval_command(&commands[i].bytes[0], return_values);
+    
+    if (line < 0) { /*  Link command */
+      fprintf(stderr, "eval: Doing Link/Jump/Call\n"); 
+      return 1;
+    }
+    
+    if (line > 0) /*  Goto command */
+      i = line - 1;
+    else /*  Just continue on the next line */
+      i++;
+
+    total++;
+  }
+  
+  memset(return_values, 0, sizeof(link_t));
+  return 0;
+}
+
+static char *linkcmd2str(link_cmd_t cmd) {
+  switch(cmd) {
+  case LinkNoLink:
+    return "LinkNoLink";
+  case LinkTopC:
+    return "LinkTopC";
+  case LinkNextC:
+    return "LinkNextC";
+  case LinkPrevC:
+    return "LinkPrevC";
+  case LinkTopPG:
+    return "LinkTopPG";
+  case LinkNextPG:
+    return "LinkNextPG";
+  case LinkPrevPG:
+    return "LinkPrevPG";
+  case LinkTopPGC:
+    return "LinkTopPGC";
+  case LinkNextPGC:
+    return "LinkNextPGC";
+  case LinkPrevPGC:
+    return "LinkPrevPGC";
+  case LinkGoUpPGC:
+    return "LinkGoUpPGC";
+  case LinkTailPGC:
+    return "LinkTailPGC";
+  case LinkRSM:
+    return "LinkRSM";
+  case LinkPGCN:
+    return "LinkPGCN";
+  case LinkPTTN:
+    return "LinkPTTN";
+  case LinkPGN:
+    return "LinkPGN";
+  case LinkCN:
+    return "LinkCN";
+  case Exit:
+    return "Exit";
+  case JumpTT:
+    return "JumpTT";
+  case JumpVTS_TT:
+    return "JumpVTS_TT";
+  case JumpVTS_PTT:
+    return "JumpVTS_PTT";
+  case JumpSS_FP:
+    return "JumpSS_FP";
+  case JumpSS_VMGM_MENU:
+    return "JumpSS_VMGM_MENU";
+  case JumpSS_VTSM:
+    return "JumpSS_VTSM";
+  case JumpSS_VMGM_PGC:
+    return "JumpSS_VMGM_PGC";
+  case CallSS_FP:
+    return "CallSS_FP";
+  case CallSS_VMGM_MENU:
+    return "CallSS_VMGM_MENU";
+  case CallSS_VTSM:
+    return "CallSS_VTSM";
+  case CallSS_VMGM_PGC:
+    return "CallSS_VMGM_PGC";
+  case PlayThis:
+    return "PlayThis";
+  }
+  return "*** (bug)";
+}
+
+void vmPrint_LINK(link_t value) {
+  char *cmd = linkcmd2str(value.command);
+    
+  switch(value.command) {
+  case LinkNoLink:
+  case LinkTopC:
+  case LinkNextC:
+  case LinkPrevC:
+  case LinkTopPG:
+  case LinkNextPG:
+  case LinkPrevPG:
+  case LinkTopPGC:
+  case LinkNextPGC:
+  case LinkPrevPGC:
+  case LinkGoUpPGC:
+  case LinkTailPGC:
+  case LinkRSM:
+    fprintf(stderr, "%s (button %d)\n", cmd, value.data1);
+    break;
+  case LinkPGCN:
+  case JumpTT:
+  case JumpVTS_TT:
+  case JumpSS_VMGM_MENU: /*  == 2 -> Title Menu */
+  case JumpSS_VMGM_PGC:
+    fprintf(stderr, "%s %d\n", cmd, value.data1);
+    break;
+  case LinkPTTN:
+  case LinkPGN:
+  case LinkCN:
+    fprintf(stderr, "%s %d (button %d)\n", cmd, value.data1, value.data2);
+    break;
+  case Exit:
+  case JumpSS_FP:
+  case PlayThis: /*  Humm.. should we have this at all.. */
+    fprintf(stderr, "%s\n", cmd);
+    break;
+  case JumpVTS_PTT:
+    fprintf(stderr, "%s %d:%d\n", cmd, value.data1, value.data2);
+    break;
+  case JumpSS_VTSM:
+    fprintf(stderr, "%s vts %d title %d menu %d\n", 
+	    cmd, value.data1, value.data2, value.data3);
+    break;
+  case CallSS_FP:
+    fprintf(stderr, "%s resume cell %d\n", cmd, value.data1);
+    break;
+  case CallSS_VMGM_MENU: /*  == 2 -> Title Menu */
+  case CallSS_VTSM:
+    fprintf(stderr, "%s %d resume cell %d\n", cmd, value.data1, value.data2);
+    break;
+  case CallSS_VMGM_PGC:
+    fprintf(stderr, "%s %d resume cell %d\n", cmd, value.data1, value.data2);
+    break;
+  }
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/decoder.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * 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$
+ *
+ */
+
+#ifndef DECODER_H_INCLUDED
+#define DECODER_H_INCLUDED
+
+#include "config.h"
+#include <inttypes.h>
+#include <dvdread/ifo_types.h> /*  vm_cmd_t */
+
+/* Uncomment for tracing */
+#define TRACE 
+
+typedef enum {
+  LinkNoLink  = 0,
+
+  LinkTopC    = 1,
+  LinkNextC   = 2,
+  LinkPrevC   = 3,
+
+  LinkTopPG   = 5,
+  LinkNextPG  = 6,
+  LinkPrevPG  = 7,
+
+  LinkTopPGC  = 9,
+  LinkNextPGC = 10,
+  LinkPrevPGC = 11,
+  LinkGoUpPGC = 12,
+  LinkTailPGC = 13,
+
+  LinkRSM     = 16,
+
+  LinkPGCN,
+  LinkPTTN,
+  LinkPGN,
+  LinkCN,
+
+  Exit,
+
+  JumpTT, /* 22 */
+  JumpVTS_TT,
+  JumpVTS_PTT,
+
+  JumpSS_FP,
+  JumpSS_VMGM_MENU,
+  JumpSS_VTSM,
+  JumpSS_VMGM_PGC,
+
+  CallSS_FP, /* 29 */
+  CallSS_VMGM_MENU,
+  CallSS_VTSM,
+  CallSS_VMGM_PGC,
+
+  PlayThis
+} link_cmd_t;
+
+typedef struct {
+  link_cmd_t command;
+  uint16_t   data1;
+  uint16_t   data2;
+  uint16_t   data3;
+} link_t;
+
+typedef struct {
+  uint16_t SPRM[24];
+  uint16_t GPRM[16];
+  /* Need to have some thing to indicate normal/counter mode for every GPRM */
+  /* int GPRM_mode[16]; */
+} registers_t;
+
+int vmEval_CMD(vm_cmd_t commands[], int num_commands, 
+	       registers_t *registers, link_t *return_values);
+
+void vmPrint_LINK(link_t value);
+
+#endif /* DECODER_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_types.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2000, 2001 Björn Englund, Håkan Hjort
+ *
+ * This file is part of libdvdnav, a DVD navigation library. It is a modified
+ * file originally part of the Ogle DVD player project.
+ * 
+ * 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$
+ *
+ */
+
+#ifndef DVD_H_INCLUDED
+#define DVD_H_INCLUDED
+
+#include <inttypes.h>
+
+/**
+ * DVD Domain
+ */
+typedef enum {
+  DVD_DOMAIN_FirstPlay,  /**< First Play Domain */
+  DVD_DOMAIN_VMG,        /**< Video Manager Domain */
+  DVD_DOMAIN_VTSMenu,    /**< Video Title Set Menu Domain */
+  DVD_DOMAIN_VTSTitle,   /**< Video Title Set Domain */
+  DVD_DOMAIN_Stop        /**< Stop Domain */
+} DVDDomain_t;
+
+/**
+ * DVD Menu
+ */
+typedef enum {
+  DVD_MENU_Title      = 2, /**< TBD */
+  DVD_MENU_Root       = 3, /**< TBD */
+  DVD_MENU_Subpicture = 4, /**< TBD */
+  DVD_MENU_Audio      = 5, /**< TBD */
+  DVD_MENU_Angle      = 6, /**< TBD */
+  DVD_MENU_Part       = 7  /**< TBD */
+} DVDMenuID_t;
+
+/**
+ * User operations
+ */
+typedef enum {
+  UOP_FLAG_TitleOrTimePlay            = 0x00000001, 
+  UOP_FLAG_ChapterSearchOrPlay        = 0x00000002, 
+  UOP_FLAG_TitlePlay                  = 0x00000004, 
+  UOP_FLAG_Stop                       = 0x00000008,  
+  UOP_FLAG_GoUp                       = 0x00000010,
+  UOP_FLAG_TimeOrChapterSearch        = 0x00000020, 
+  UOP_FLAG_PrevOrTopPGSearch          = 0x00000040,  
+  UOP_FLAG_NextPGSearch               = 0x00000080,   
+  UOP_FLAG_ForwardScan                = 0x00000100,  
+  UOP_FLAG_BackwardScan               = 0x00000200,
+  UOP_FLAG_TitleMenuCall              = 0x00000400,
+  UOP_FLAG_RootMenuCall               = 0x00000800,
+  UOP_FLAG_SubPicMenuCall             = 0x00001000,
+  UOP_FLAG_AudioMenuCall              = 0x00002000,
+  UOP_FLAG_AngleMenuCall              = 0x00004000,
+  UOP_FLAG_ChapterMenuCall            = 0x00008000,
+  UOP_FLAG_Resume                     = 0x00010000,
+  UOP_FLAG_ButtonSelectOrActivate     = 0x00020000,
+  UOP_FLAG_StillOff                   = 0x00040000,
+  UOP_FLAG_PauseOn                    = 0x00080000,
+  UOP_FLAG_AudioStreamChange          = 0x00100000,
+  UOP_FLAG_SubPicStreamChange         = 0x00200000,
+  UOP_FLAG_AngleChange                = 0x00400000,
+  UOP_FLAG_KaraokeAudioPresModeChange = 0x00800000,
+  UOP_FLAG_VideoPresModeChange        = 0x01000000 
+} DVDUOP_t;
+
+
+/**
+ * Parental Level
+ */
+typedef enum {
+  DVD_PARENTAL_LEVEL_1 = 1,
+  DVD_PARENTAL_LEVEL_2 = 2,
+  DVD_PARENTAL_LEVEL_3 = 3,
+  DVD_PARENTAL_LEVEL_4 = 4,
+  DVD_PARENTAL_LEVEL_5 = 5,
+  DVD_PARENTAL_LEVEL_6 = 6,
+  DVD_PARENTAL_LEVEL_7 = 7,
+  DVD_PARENTAL_LEVEL_8 = 8,
+  DVD_PARENTAL_LEVEL_None = 15
+} DVDParentalLevel_t;
+
+/**
+ * Language ID (ISO-639 language code)
+ */
+typedef uint16_t DVDLangID_t;
+
+/**
+ * Country ID (ISO-3166 country code)
+ */
+typedef uint16_t DVDCountryID_t;
+
+/**
+ * Register
+ */
+typedef uint16_t DVDRegister_t;
+
+typedef enum {
+  DVDFalse = 0,
+  DVDTrue = 1
+} DVDBool_t; 
+
+typedef DVDRegister_t DVDGPRMArray_t[16];
+typedef DVDRegister_t DVDSPRMArray_t[24];
+
+typedef int DVDStream_t;
+
+/**
+ * Angle number (1-9 or default?)
+ */
+typedef int DVDAngle_t;
+
+typedef int DVDPTT_t;
+typedef int DVDTitle_t;
+typedef struct {
+  uint8_t Hours;
+  uint8_t Minutes;
+  uint8_t Seconds;
+  uint8_t Frames;
+} DVDTimecode_t;
+
+/** 
+ * Subpicture stream number (0-31,62,63)
+ */
+typedef int DVDSubpictureStream_t;  
+
+/** 
+ * Audio stream number (0-7, 15(none))
+ */
+typedef int DVDAudioStream_t;  
+
+
+/**
+ * The audio application mode
+ */
+typedef enum {
+  DVD_AUDIO_APP_MODE_None     = 0, /**< app mode none     */
+  DVD_AUDIO_APP_MODE_Karaoke  = 1, /**< app mode karaoke  */
+  DVD_AUDIO_APP_MODE_Surround = 2, /**< app mode surround */
+  DVD_AUDIO_APP_MODE_Other    = 3  /**< app mode other    */
+} DVDAudioAppMode_t;
+
+/**
+ * The audio format
+ */
+typedef enum {
+  DVD_AUDIO_FORMAT_AC3       = 0, /**< Dolby AC-3 */
+  DVD_AUDIO_FORMAT_MPEG1     = 1, /**< MPEG-1 */
+  DVD_AUDIO_FORMAT_MPEG1_DRC = 2, /**< MPEG-1 with dynamic range control */
+  DVD_AUDIO_FORMAT_MPEG2     = 3, /**< MPEG-2 */
+  DVD_AUDIO_FORMAT_MPEG2_DRC = 4, /**< MPEG-2 with dynamic range control */
+  DVD_AUDIO_FORMAT_LPCM      = 5, /**< Linear Pulse Code Modulation */
+  DVD_AUDIO_FORMAT_DTS       = 6, /**< Digital Theater Systems */
+  DVD_AUDIO_FORMAT_SDDS      = 7, /**< Sony Dynamic Digital Sound */
+  DVD_AUDIO_FORMAT_Other     = 8  /**< Other format*/
+} DVDAudioFormat_t;
+
+/**
+ * Audio language extension
+ */
+typedef enum {
+  DVD_AUDIO_LANG_EXT_NotSpecified       = 0, /**< TBD */
+  DVD_AUDIO_LANG_EXT_NormalCaptions     = 1, /**< TBD */
+  DVD_AUDIO_LANG_EXT_VisuallyImpaired   = 2, /**< TBD */
+  DVD_AUDIO_LANG_EXT_DirectorsComments1 = 3, /**< TBD */
+  DVD_AUDIO_LANG_EXT_DirectorsComments2 = 4  /**< TBD */
+} DVDAudioLangExt_t;
+
+/**
+ * Subpicture language extension
+ */
+typedef enum {
+  DVD_SUBPICTURE_LANG_EXT_NotSpecified  = 0,
+  DVD_SUBPICTURE_LANG_EXT_NormalCaptions  = 1,
+  DVD_SUBPICTURE_LANG_EXT_BigCaptions  = 2,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensCaptions  = 3,
+  DVD_SUBPICTURE_LANG_EXT_NormalCC  = 5,
+  DVD_SUBPICTURE_LANG_EXT_BigCC  = 6,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensCC  = 7,
+  DVD_SUBPICTURE_LANG_EXT_Forced  = 9,
+  DVD_SUBPICTURE_LANG_EXT_NormalDirectorsComments  = 13,
+  DVD_SUBPICTURE_LANG_EXT_BigDirectorsComments  = 14,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensDirectorsComments  = 15,
+} DVDSubpictureLangExt_t;  
+
+/**
+ * Karaoke Downmix mode
+ */
+typedef enum {
+  DVD_KARAOKE_DOWNMIX_0to0 = 0x0001,
+  DVD_KARAOKE_DOWNMIX_1to0 = 0x0002,
+  DVD_KARAOKE_DOWNMIX_2to0 = 0x0004,
+  DVD_KARAOKE_DOWNMIX_3to0 = 0x0008,
+  DVD_KARAOKE_DOWNMIX_4to0 = 0x0010,
+  DVD_KARAOKE_DOWNMIX_Lto0 = 0x0020,
+  DVD_KARAOKE_DOWNMIX_Rto0 = 0x0040,
+  DVD_KARAOKE_DOWNMIX_0to1 = 0x0100,
+  DVD_KARAOKE_DOWNMIX_1to1 = 0x0200,
+  DVD_KARAOKE_DOWNMIX_2to1 = 0x0400,
+  DVD_KARAOKE_DOWNMIX_3to1 = 0x0800,
+  DVD_KARAOKE_DOWNMIX_4to1 = 0x1000,
+  DVD_KARAOKE_DOWNMIX_Lto1 = 0x2000,
+  DVD_KARAOKE_DOWNMIX_Rto1 = 0x4000
+} DVDKaraokeDownmix_t;
+
+typedef int DVDKaraokeDownmixMask_t;
+
+typedef enum {
+  DVD_DISPLAY_MODE_ContentDefault = 0,
+  DVD_DISPLAY_MODE_16x9 = 1,
+  DVD_DISPLAY_MODE_4x3PanScan = 2,
+  DVD_DISPLAY_MODE_4x3Letterboxed = 3  
+} DVDDisplayMode_t;
+
+typedef int DVDAudioSampleFreq_t;  /**< TBD */
+typedef int DVDAudioSampleQuant_t; /**< TBD */
+typedef int DVDChannelNumber_t;    /**< TBD */
+
+
+typedef struct {
+  DVDAudioAppMode_t     AppMode;
+  DVDAudioFormat_t      AudioFormat;
+  DVDLangID_t           Language;
+  DVDAudioLangExt_t     LanguageExtension;
+  DVDBool_t             HasMultichannelInfo;
+  DVDAudioSampleFreq_t  SampleFrequency;
+  DVDAudioSampleQuant_t SampleQuantization;
+  DVDChannelNumber_t    NumberOfChannels;
+} DVDAudioAttributes_t;
+
+typedef enum {
+  DVD_SUBPICTURE_TYPE_NotSpecified = 0,
+  DVD_SUBPICTURE_TYPE_Language     = 1,
+  DVD_SUBPICTURE_TYPE_Other        = 2
+} DVDSubpictureType_t;
+
+typedef enum {
+  DVD_SUBPICTURE_CODING_RunLength = 0,
+  DVD_SUBPICTURE_CODING_Extended  = 1,
+  DVD_SUBPICTURE_CODING_Other     = 2
+} DVDSubpictureCoding_t;
+
+typedef struct {
+  DVDSubpictureType_t    Type;
+  DVDSubpictureCoding_t  CodingMode;
+  DVDLangID_t            Language;
+  DVDSubpictureLangExt_t LanguageExtension;
+} DVDSubpictureAttributes_t;
+
+typedef int DVDVideoCompression_t; /**< TBD */
+
+typedef struct {
+  DVDBool_t PanscanPermitted;
+  DVDBool_t LetterboxPermitted;
+  int AspectX;
+  int AspectY;
+  int FrameRate;
+  int FrameHeight;
+  DVDVideoCompression_t Compression;
+  DVDBool_t Line21Field1InGop;
+  DVDBool_t Line21Field2InGop;
+  int more_to_come;
+} DVDVideoAttributes_t;
+
+
+#endif /* DVD_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,971 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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 <pthread.h>
+#include <dvdnav.h>
+#include "dvdnav_internal.h"
+#include "read_cache.h"
+
+#include <dvdread/nav_read.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+dvdnav_status_t dvdnav_open(dvdnav_t** dest, char *path) {
+  dvdnav_t *self;
+  
+  /* Create a new structure */
+  (*dest) = NULL;
+  self = (dvdnav_t*)malloc(sizeof(dvdnav_t));
+  if(!self)
+   return S_ERR;
+  memset(self, 0, (sizeof(dvdnav_t) ) ); /* Make sure self structure is clean */
+
+  pthread_mutex_init(&self->vm_lock, NULL);
+  /* Initialise the error string */
+  printerr("");
+
+  /* Initialise the VM */
+  self->vm = vm_new_vm();
+  if(!self->vm) {
+    printerr("Error initialising the DVD VM");
+    return S_ERR;
+  }
+  if(vm_reset(self->vm, path) == -1) {
+    printerr("Error starting the VM / opening the DVD device");
+    return S_ERR;
+  }
+
+  /* Set the path. FIXME: Is a deep copy 'right' */
+  strncpy(self->path, path, MAX_PATH_LEN);
+
+  /* Set initial values of flags */
+  self->expecting_nav_packet = 1;
+  self->started = 0;
+  
+  self->open_vtsN = -1;
+  self->open_domain = -1;
+  self->file = NULL;
+  self->cell = NULL;
+  self->at_soc = 1;
+  self->jumping = 0;
+  self->seeking = 0;
+  self->still_frame = -1;
+  self->cache_buffer = NULL;
+  self->cache_start_sector = -1;
+  self->cache_block_count = 0;
+  self->cache_valid = 0;
+  self->use_read_ahead = 1;
+  self->stop = 0;
+  self->highlight_changed = 0;
+  self->spu_clut_changed = 0;
+
+  self->vobu_start = self->vobu_length = 0;
+ 
+  /* Pre-open and close a file so that the CSS-keys are cached. */
+  self->file = DVDOpenFile(vm_get_dvd_reader(self->vm), 0, DVD_READ_MENU_VOBS);
+  if (self->file) DVDCloseFile(self->file);
+  self->file = NULL;
+    
+  if(!self->started) {
+    /* Start the VM */
+    vm_start(self->vm);
+    self->started = 1;
+  }
+
+  (*dest) = self;
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_close(dvdnav_t *self) {
+  if(!self) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+  printf("dvdnav:close:called\n");
+  if (self->file) {
+    DVDCloseFile(self->file);
+    printf("dvdnav:close:file closing\n");
+    self->file = NULL;
+  }
+
+  /* Free the VM */
+  if(self->vm) {
+    vm_free_vm(self->vm);
+  }
+  if (self->file) {
+    DVDCloseFile(self->file);
+    printf("dvdnav:close2:file closing\n");
+    self->file = NULL;
+  }
+  pthread_mutex_destroy(&self->vm_lock);
+  /* Finally free the entire structure */
+  free(self);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_path(dvdnav_t *self, char** path) {
+  if(!self || !path || !(*path)) {
+    return S_ERR;
+  }
+
+  /* FIXME: Is shallow copy 'right'? */
+  (*path) = self->path;
+
+  return S_OK;
+}
+
+char* dvdnav_err_to_string(dvdnav_t *self) {
+  if(!self) {
+    /* Shold this be "passed a NULL pointer?" */
+    return NULL;
+  }
+  
+  return self->err_str;
+}
+
+/**
+ * Returns 1 if block contains NAV packet, 0 otherwise.
+ * Precesses said NAV packet if present.
+ *
+ * Most of the code in here is copied from xine's MPEG demuxer
+ * so any bugs which are found in that should be corrected here also.
+ */
+int dvdnav_check_packet(dvdnav_t *self, uint8_t *p) {
+  int            bMpeg1=0;
+  uint32_t       nHeaderLen;
+  uint32_t       nPacketLen;
+  uint32_t       nStreamID;
+/* uint8_t       *p_start=p; */
+
+
+  if (p==NULL) {
+    printf("Passed a NULL pointer.\n");
+    return 0;
+  }
+
+  /* dprint("Checking packet...\n"); */
+
+  if (p[3] == 0xBA) { /* program stream pack header */
+
+    int nStuffingBytes;
+
+    /* xprintf (VERBOSE|DEMUX, "program stream pack header\n"); */
+
+    bMpeg1 = (p[4] & 0x40) == 0;
+
+    if (bMpeg1) {
+      p   += 12;
+    } else { /* mpeg2 */
+      nStuffingBytes = p[0xD] & 0x07;
+      p += 14 + nStuffingBytes;
+    }
+  }
+
+
+  if (p[3] == 0xbb) { /* program stream system header */
+    int nHeaderLen;
+
+    nHeaderLen = (p[4] << 8) | p[5];
+    p += 6 + nHeaderLen;
+  }
+
+  /* we should now have a PES packet here */
+
+  if (p[0] || p[1] || (p[2] != 1)) {
+    printf("demux error! %02x %02x %02x (should be 0x000001) \n",p[0],p[1],p[2]);
+    return 0;
+  }
+
+  nPacketLen = p[4] << 8 | p[5];
+  nStreamID  = p[3];
+
+  nHeaderLen = 6;
+  p += nHeaderLen;
+
+  if (nStreamID == 0xbf) { /* Private stream 2 */
+/*
+ *   int i;
+ *    printf("dvdnav:nav packet=%u\n",p-p_start-6);
+ *   for(i=0;i<80;i++) {
+ *     printf("%02x ",p[i-6]);
+ *   }
+ *   printf("\n");
+ */
+    if(p[0] == 0x00) {
+#ifdef HAVE_DVDREAD9
+      navRead_PCI(&(self->pci), p+1);
+#else
+      navRead_PCI(&(self->pci), p+1, nPacketLen - 1);
+#endif
+    }
+
+    p += nPacketLen;
+
+    /* We should now have a DSI packet. */
+    if(p[6] == 0x01) {
+      int num=0, current=0;
+
+      nPacketLen = p[4] << 8 | p[5];
+      p += 6;
+      /* dprint("NAV DSI packet\n");  */
+#ifdef HAVE_DVDREAD9
+      navRead_DSI(&(self->dsi), p+1);
+#else
+      navRead_DSI(&(self->dsi), p+1, sizeof(dsi_t));
+#endif
+
+      self->vobu_start = self->dsi.dsi_gi.nv_pck_lbn;
+      self->vobu_length = self->dsi.dsi_gi.vobu_ea;
+      
+      /**
+       * If we're not at the end of this cell, we can determine the next
+       * VOBU to display using the VOBU_SRI information section of the
+       * DSI.  Using this value correctly follows the current angle,
+       * avoiding the doubled scenes in The Matrix, and makes our life
+       * really happy.
+       *
+       * Otherwise, we set our next address past the end of this cell to
+       * force the code above to go to the next cell in the program.
+       */
+      if( self->dsi.vobu_sri.next_vobu != SRI_END_OF_CELL ) {
+	self->next_vobu = self->dsi.dsi_gi.nv_pck_lbn 
+      	 + ( self->dsi.vobu_sri.next_vobu & 0x7fffffff );
+      } else {
+        self->next_vobu = self->vobu_start + self->vobu_length;
+      }
+      
+      dvdnav_get_angle_info(self, &current, &num);
+      if(num == 1) {
+	/* This is to switch back to angle one when we
+	 * finish */
+	dvdnav_angle_change(self, 1);
+      }
+      
+      if(num != 0) {
+	uint32_t next = self->pci.nsml_agli.nsml_agl_dsta[current-1];
+
+       	if(next != 0) {
+	  int dir = 0;
+	  if(next & 0x80000000) {
+	    dir = -1;
+	    next = next & 0x3fffffff;
+	  } else {
+	    dir = 1;
+	  }
+
+	  if(next != 0) {
+	    self->next_vobu = self->vobu_start + dir * next;
+	  }
+	} else if( self->dsi.sml_agli.data[current-1].address != 0 ) {
+	  next = self->dsi.sml_agli.data[current-1].address;
+	  self->vobu_length = self->dsi.sml_pbi.ilvu_ea;
+
+	  if((next & 0x80000000) && (next != 0x7fffffff)) {
+	    self->next_vobu = self->dsi.dsi_gi.nv_pck_lbn - (next & 0x7fffffff);
+	  } else {
+	    self->next_vobu = self->dsi.dsi_gi.nv_pck_lbn + next;
+	  }
+	}
+      }
+    }
+    return 1;
+  }
+
+  return 0;
+}
+
+dvdnav_status_t dvdnav_get_next_block(dvdnav_t *self, unsigned char *buf,
+ 				      int *event, int *len) {
+  dvd_state_t *state;
+  int result;
+  if(!self || !event || !len || !buf) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+  pthread_mutex_lock(&self->vm_lock); 
+  
+  if(!self->started) {
+    /* Start the VM */
+    vm_start(self->vm);
+    self->started = 1;
+  }
+
+  state = &(self->vm->state);
+  (*event) = DVDNAV_NOP;
+  (*len) = 0;
+ 
+  /* Check the STOP flag */
+  if(self->stop) {
+    (*event) = DVDNAV_STOP;
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+
+  if(self->spu_clut_changed) {
+    (*event) = DVDNAV_SPU_CLUT_CHANGE;
+    printf("libdvdnav:SPU_CLUT_CHANGE\n");
+    (*len) = sizeof(dvdnav_still_event_t);
+    memcpy(buf, &(state->pgc->palette), 16 * sizeof(uint32_t));
+    self->spu_clut_changed = 0;
+    printf("libdvdnav:SPU_CLUT_CHANGE returning S_OK\n");
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+  
+  if(self->spu_stream_changed) {
+    dvdnav_stream_change_event_t stream_change;
+    (*event) = DVDNAV_SPU_STREAM_CHANGE;
+    printf("libdvdnav:SPU_STREAM_CHANGE\n");
+    (*len) = sizeof(dvdnav_stream_change_event_t);
+    stream_change.physical= vm_get_subp_active_stream( self->vm );
+    memcpy(buf, &(stream_change), sizeof( dvdnav_stream_change_event_t));
+    self->spu_stream_changed = 0;
+    printf("libdvdnav:SPU_STREAM_CHANGE stream_id=%d returning S_OK\n",stream_change.physical);
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+  
+  if(self->audio_stream_changed) {
+    dvdnav_stream_change_event_t stream_change;
+    (*event) = DVDNAV_AUDIO_STREAM_CHANGE;
+    printf("libdvdnav:AUDIO_STREAM_CHANGE\n");
+    (*len) = sizeof(dvdnav_stream_change_event_t);
+    stream_change.physical= vm_get_audio_active_stream( self->vm );
+    memcpy(buf, &(stream_change), sizeof( dvdnav_stream_change_event_t));
+    self->audio_stream_changed = 0;
+    printf("libdvdnav:AUDIO_STREAM_CHANGE stream_id=%d returning S_OK\n",stream_change.physical);
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+     
+  /* Check the HIGHLIGHT flag */
+  if(self->highlight_changed) {
+    dvdnav_highlight_event_t hevent;
+   
+    /* Fill in highlight struct with appropriate values */
+    if(self->hli_state != 0) {
+      hevent.display = 1;
+
+      /* Copy current button bounding box. */
+      hevent.sx = self->hli_bbox[0];
+      hevent.sy = self->hli_bbox[1];
+      hevent.ex = self->hli_bbox[2];
+      hevent.ey = self->hli_bbox[3];
+
+      hevent.palette = self->hli_clut;
+      hevent.pts = self->hli_pts;
+      hevent.buttonN = self->hli_buttonN;
+
+    } else {
+      hevent.display = 0;
+    }
+    
+    (*event) = DVDNAV_HIGHLIGHT;
+    memcpy(buf, &(hevent), sizeof(hevent));
+    (*len) = sizeof(hevent);
+    
+    self->highlight_changed = 0;
+    
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+
+  /* Check to see if we need to change the curently opened VOB */
+  if((self->open_vtsN != state->vtsN) || 
+     (self->open_domain != state->domain)) {
+    dvd_read_domain_t domain;
+    int vtsN;
+    dvdnav_vts_change_event_t vts_event;
+    
+    if(self->file) {
+      dvdnav_read_cache_clear(self);
+      DVDCloseFile(self->file);
+      self->file = NULL;
+    }
+
+    vts_event.old_vtsN = self->open_vtsN;
+    vts_event.old_domain = self->open_domain;
+     
+    /* Use the current DOMAIN to find whether to open menu or title VOBs */
+    switch(state->domain) {
+     case FP_DOMAIN:
+     case VMGM_DOMAIN:
+      domain = DVD_READ_MENU_VOBS;
+      vtsN = 0;
+      break;
+     case VTSM_DOMAIN:
+      domain = DVD_READ_MENU_VOBS;
+      vtsN = state->vtsN;
+      break;
+     case VTS_DOMAIN:
+      domain = DVD_READ_TITLE_VOBS;
+      vtsN = state->vtsN;
+      break;
+     default:
+      printerr("Unknown domain when changing VTS.");
+      pthread_mutex_unlock(&self->vm_lock); 
+      return S_ERR;
+    }
+    
+    self->open_domain = state->domain;
+    self->open_vtsN = state->vtsN;
+    dvdnav_read_cache_clear(self);
+    self->file = DVDOpenFile(vm_get_dvd_reader(self->vm), vtsN, domain);
+    vts_event.new_vtsN = self->open_vtsN;
+    vts_event.new_domain = self->open_domain;
+
+    /* If couldn't open the file for some reason, moan */
+    if(self->file == NULL) {
+      printerrf("Error opening vtsN=%i, domain=%i.", vtsN, domain);
+      pthread_mutex_unlock(&self->vm_lock); 
+      return S_ERR;
+    }
+
+    /* File opened successfully so return a VTS change event */
+    (*event) = DVDNAV_VTS_CHANGE;
+    memcpy(buf, &(vts_event), sizeof(vts_event));
+    (*len) = sizeof(vts_event);
+
+    /* On a VTS change, we want to disable any highlights which
+     * may have been shown (FIXME: is this valid?) */
+    self->highlight_changed = 1;
+    self->spu_clut_changed = 1;
+    self->spu_stream_changed = 1;
+    self->audio_stream_changed = 1;
+    self->hli_state = 0; /* Hide */
+    self->expecting_nav_packet = 1;
+     
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+ 
+  /* Check the STILLFRAME flag */
+  if(self->still_frame != -1) {
+    dvdnav_still_event_t still_event;
+
+    still_event.length = self->still_frame;
+
+    (*event) = DVDNAV_STILL_FRAME;
+    (*len) = sizeof(dvdnav_still_event_t);
+    memcpy(buf, &(still_event), sizeof(dvdnav_still_event_t));
+ 
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+
+  if(self->at_soc) {
+    dvdnav_cell_change_event_t cell_event;
+    cell_playback_t *cell = &(state->pgc->cell_playback[state->cellN - 1]);
+    
+    cell_event.old_cell = self->cell;
+    self->vobu_start = cell->first_sector;
+    self->cell = cell;
+    cell_event.new_cell = self->cell;
+     
+    self->at_soc = 0;
+
+    (*event) = DVDNAV_CELL_CHANGE;
+    (*len) = sizeof(dvdnav_cell_change_event_t);
+    memcpy(buf, &(cell_event), sizeof(dvdnav_cell_change_event_t));
+
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+
+  if(self->expecting_nav_packet) {
+    dvdnav_nav_packet_event_t nav_event;
+
+    /* Perform the jump if necessary (this is always a 
+     * VOBU boundary). */
+
+    if(self->seeking) {
+      /* FIXME:Need to handle seeking outside current cell. */
+      vobu_admap_t *admap = NULL;
+	
+      printf("Seeking to target %u ...\n",
+              self->seekto_block);
+
+      /* Search through the VOBU_ADMAP for the nearest VOBU
+       * to the target block */
+      switch(state->domain) {
+        case FP_DOMAIN:
+        case VMGM_DOMAIN:
+          //ifo = vm_get_vmgi();
+          //ifoRead_VOBU_ADMAP(ifo);
+          admap = self->vm->vmgi->menu_vobu_admap;
+          break;
+        case VTSM_DOMAIN:
+          //ifo = vm_get_vtsi();
+          //ifoRead_VOBU_ADMAP(ifo);
+          admap = self->vm->vtsi->menu_vobu_admap;
+          break;
+        case VTS_DOMAIN:
+          //ifo = vm_get_vtsi();
+          //ifoRead_TITLE_VOBU_ADMAP(ifo);
+          admap = self->vm->vtsi->vts_vobu_admap;
+          break;
+        default:
+          printf("Error: Unknown domain for seeking seek.\n");
+      }
+
+      if(admap) {
+        uint32_t address = 0;
+        uint32_t vobu_start, next_vobu;
+        int found = 0;
+  
+        /* Search through ADMAP for best sector */
+        vobu_start = 0x3fffffff;
+   
+        while((!found) && ((address<<2) < admap->last_byte)) {
+          next_vobu = admap->vobu_start_sectors[address];
+
+          /* printf("Found block %u\n", next_vobu); */
+    
+          if(vobu_start <= self->seekto_block && 
+                 next_vobu > self->seekto_block) {
+            found = 1;
+          } else {
+            vobu_start = next_vobu;
+          }
+    
+          address ++;
+        }
+        if(found) {
+          self->vobu_start = vobu_start;
+          self->blockN = 0;
+          self->seeking = 0;
+          //self->at_soc = 1;
+          (*event) = DVDNAV_SEEK_DONE;
+          (*len) = 0;
+          pthread_mutex_unlock(&self->vm_lock); 
+          return S_OK;
+        } else {
+          printf("Could not locate block\n");
+          return -1;
+        }
+      }
+    }   
+    if(self->jumping) {
+      printf("doing jumping\n");
+      self->vobu_start = self->jmp_vobu_start;
+      self->blockN = self->jmp_blockN;
+      self->jumping = 0;
+      self->at_soc = 1;
+    }
+    
+    result = DVDReadBlocks(self->file, self->vobu_start + self->blockN, 1, buf);
+
+    if(result <= 0) {
+      printerr("Error reading NAV packet.");
+      pthread_mutex_unlock(&self->vm_lock); 
+      return S_ERR;
+    }
+
+    if(dvdnav_check_packet(self, buf) == 0) {
+      printerr("Expected NAV packet but none found.");
+      pthread_mutex_unlock(&self->vm_lock); 
+      return S_ERR;
+    }
+    
+    self->blockN++;
+    self->expecting_nav_packet = 0;
+
+    dvdnav_pre_cache_blocks(self, self->vobu_start, self->vobu_length+1);
+    
+    /* Successfully got a NAV packet */
+    nav_event.pci = &(self->pci);
+    nav_event.dsi = &(self->dsi);
+
+    (*event) = DVDNAV_NAV_PACKET;
+    //memcpy(buf, &(nav_event), sizeof(dvdnav_nav_packet_event_t));
+    //(*len) = sizeof(dvdnav_nav_packet_event_t);
+    (*len) = 2048;
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_OK;
+  }
+  
+  /* If we've got here, it must just be a normal block. */
+  if(!self->file) {
+    printerr("Attempting to read without opening file");
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_ERR;
+  }
+
+  result = dvdnav_read_cache_block(self, self->vobu_start + self->blockN, 1, buf);
+  if(result <= 0) {
+    printerr("Error reading from DVD.");
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_ERR;
+  }
+  self->blockN++;
+  (*len) = 2048;
+  (*event) = DVDNAV_BLOCK_OK;
+
+  if(self->blockN > self->vobu_length) {
+    self->vobu_start = self->next_vobu;
+    self->blockN = 0;
+    self->expecting_nav_packet = 1;
+
+    if(self->dsi.vobu_sri.next_vobu == SRI_END_OF_CELL) {
+      cell_playback_t *cell = &(state->pgc->cell_playback[state->cellN - 1]);
+
+      if(cell->still_time != 0xff) {
+        vm_get_next_cell(self->vm);
+      }
+
+      if(cell->still_time != 0) {
+	self->still_frame = cell->still_time;
+      }
+
+      self->at_soc = 1;
+    }
+  }
+  
+  pthread_mutex_unlock(&self->vm_lock); 
+  return S_OK;
+}
+
+uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream) {
+  ifo_handle_t *vtsi;
+  dvd_state_t  *state;
+  
+  if(!self)
+    return -1;
+  
+  pthread_mutex_lock(&self->vm_lock); 
+
+  vtsi = self->vm->vtsi;
+  state = &(self->vm->state);
+
+  if((vtsi == NULL) || (state == NULL) || (state->domain != VTS_DOMAIN))
+    goto __failed;
+  
+  if(stream >= vtsi->vtsi_mat->nr_of_vts_audio_streams)
+    goto __failed;
+  
+  if(vtsi->vtsi_mat->vts_audio_attr[stream].lang_type != 1)
+    goto __failed;
+  
+  pthread_mutex_unlock(&self->vm_lock); 
+  return vtsi->vtsi_mat->vts_audio_attr[stream].lang_code;
+  
+ __failed:
+  pthread_mutex_unlock(&self->vm_lock); 
+  return 0xffff;
+}
+
+int8_t dvdnav_audio_logical_to_physical(dvdnav_t *self, uint8_t logical) {
+  audio_status_t *audio_status;
+  dvd_state_t    *state;
+  int             i = 0;
+  
+  if(!self)
+    return -1;
+  
+  pthread_mutex_lock(&self->vm_lock); 
+  
+  state = &(self->vm->state);
+  
+  if((!state) || (!state->pgc) || (!state->pgc->audio_control))
+    goto __failed;
+  
+  if(logical > 7) {
+    fprintf(stderr, "Invalid logical audio channel: %i\n", logical);
+    goto __failed;
+  }
+
+  while (i < 8) {
+    audio_status = (audio_status_t*) &(state->pgc->audio_control[i]);
+
+    if(!audio_status)
+      goto __failed;
+
+    if(audio_status->available)
+      break;
+    
+    i++;
+  }
+  
+  if (i > 7)
+    goto __failed;
+
+  if ((logical+i) > 7)
+    goto __failed;
+  
+  audio_status = (audio_status_t*) &(state->pgc->audio_control[logical+i]);
+
+  if(!audio_status)
+    goto __failed;
+  
+  if(audio_status->available) {
+    /* Stream is available */
+    pthread_mutex_unlock(&self->vm_lock); 
+    return audio_status->stream_number; 
+  }
+  
+ __failed:
+  pthread_mutex_unlock(&self->vm_lock); 
+  return -1;
+}
+
+int8_t dvdnav_audio_physical_to_logical(dvdnav_t *self, uint8_t physical) {
+  int8_t logical = -1;
+  int    i       = 0; 
+  
+  if((!self) || (physical > 7))
+    return -1;
+  
+  do {
+    if(dvdnav_audio_logical_to_physical(self, i) == physical) 
+      logical = i;
+    
+    i++;
+  } while((i<8) && (logical == -1));
+
+  return logical;
+}
+
+uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream) {
+  ifo_handle_t *vtsi;
+  dvd_state_t  *state;
+
+  if(!self)
+    return -1;
+  
+  pthread_mutex_lock(&self->vm_lock); 
+  
+  vtsi = self->vm->vtsi;
+  state = &(self->vm->state);
+
+  if((vtsi == NULL) || (state == NULL) || (state->domain != VTS_DOMAIN)) {
+    goto __failed;
+  }
+
+  if(stream >= vtsi->vtsi_mat->nr_of_vts_subp_streams) {
+    goto __failed;
+  }
+
+  if(vtsi->vtsi_mat->vts_subp_attr[stream].type != 1) {
+    goto __failed;
+  }
+  
+  pthread_mutex_unlock(&self->vm_lock); 
+  return vtsi->vtsi_mat->vts_subp_attr[stream].lang_code;
+  
+ __failed:
+  pthread_mutex_unlock(&self->vm_lock); 
+  return 0xffff;
+}
+
+int8_t dvdnav_spu_logical_to_physical(dvdnav_t *self, uint8_t logical) {
+  spu_status_t *spu_status;
+  dvd_state_t  *state;
+  ifo_handle_t *vtsi;
+ 
+  if(!self)
+    return -1;
+  
+  pthread_mutex_lock(&self->vm_lock); 
+  
+  vtsi = self->vm->vtsi;
+  state = &(self->vm->state);
+  
+  if((!state) || (!vtsi))
+    goto __failed;
+
+  if(logical > 31) {
+    fprintf(stderr, "Invalid logical spu channel: %i\n", logical);
+    goto __failed;
+  }
+  
+  spu_status = (spu_status_t*) &(state->pgc->subp_control[logical]);
+  
+  if(!spu_status)
+    goto __failed;
+  
+  if(spu_status->available) {
+    int8_t        logical = -1;
+    video_attr_t *attr;
+    
+    attr = &(vtsi->vtsi_mat->vtsm_video_attr);
+  
+    if(!attr)
+      goto __failed;
+
+    /* Stream is available */
+    switch(attr->display_aspect_ratio) {
+    case 0: /* 4:3 */
+      logical = spu_status->stream_number_4_3;
+      break;
+    case 3: /* 16:9 */
+      logical = spu_status->stream_number_letterbox;
+      break;
+    }
+    pthread_mutex_unlock(&self->vm_lock); 
+    return logical; 
+  }
+  
+ __failed:
+  pthread_mutex_unlock(&self->vm_lock); 
+  return -1;
+}
+
+int8_t dvdnav_spu_physical_to_logical(dvdnav_t *self, uint8_t physical) {
+  int8_t logical = -1;
+  int    i       = 0;
+  
+  if(physical > 31)
+    return -1;
+
+  do {
+    if(dvdnav_spu_logical_to_physical(self, i) == physical) 
+      logical = i;
+    
+    i++;
+  } while((i<32) && (logical == -1));
+
+  return logical;
+}
+
+/* Current domain (backend to dvdnav_is_domain_() funcs) */
+static int8_t _dvdnav_is_domain(dvdnav_t *self, domain_t domain) {
+  dvd_state_t  *state;
+  
+  if((!self) || (!self->started) || (!self->vm))
+    return -1;
+  
+  pthread_mutex_lock(&self->vm_lock); 
+  state = &(self->vm->state);
+  pthread_mutex_unlock(&self->vm_lock);
+
+  if(!state)
+    return -1;
+
+  return (state->domain == domain) ? 1 : 0;
+}
+
+/* First Play domain. (Menu) */
+int8_t dvdnav_is_domain_fp(dvdnav_t *self) {
+  return _dvdnav_is_domain(self, FP_DOMAIN);
+}
+/* Video management Menu domain. (Menu) */
+int8_t dvdnav_is_domain_vmgm(dvdnav_t *self) {
+  return _dvdnav_is_domain(self, VMGM_DOMAIN);
+}
+/* Video Title Menu domain (Menu) */
+int8_t dvdnav_is_domain_vtsm(dvdnav_t *self) {
+  return _dvdnav_is_domain(self, VTSM_DOMAIN);
+}
+/* Video Title domain (playing movie). */
+int8_t dvdnav_is_domain_vts(dvdnav_t *self) { 
+  return _dvdnav_is_domain(self, VTS_DOMAIN);
+}
+
+/* Generally delegate angle information handling to 
+ * VM */
+dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int angle) {
+  int num, current;
+  
+  if(!self) {
+    return S_ERR;
+  }
+
+  if(dvdnav_get_angle_info(self, &current, &num) != S_OK) {
+    printerr("Error getting angle info");
+    return S_ERR;
+  }
+  
+  /* Set angle SPRM if valid */
+  if((angle > 0) && (angle <= num)) {
+    self->vm->state.AGL_REG = angle;
+  } else {
+    printerr("Passed an invalid angle number");
+    return S_ERR;
+  }
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *self, int* current_angle,
+				     int *number_of_angles) {
+  if(!self || !self->vm) {
+    return S_ERR;
+  }
+
+  if(!current_angle || !number_of_angles) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+
+  vm_get_angle_info(self->vm, number_of_angles, current_angle);
+
+  return S_OK;
+}
+
+
+/*
+ * $Log$
+ * Revision 1.1  2002/03/12 19:45:57  richwareham
+ * Initial revision
+ *
+ * Revision 1.28  2002/02/02 23:26:20  richwareham
+ * Restored title selection
+ *
+ * Revision 1.27  2002/02/01 15:48:10  richwareham
+ * Re-implemented angle selection and title/chapter display
+ *
+ * Revision 1.26  2002/01/31 16:53:49  richwareham
+ * Big patch from Daniel Caujolle-Bert to (re)implement SPU/Audio language display
+ *
+ * Revision 1.25  2002/01/24 20:53:50  richwareham
+ * Added option to _not_ use DVD read-ahead to options
+ *
+ * Revision 1.24  2002/01/20 15:54:59  jcdutton
+ * Implement seeking.
+ * It is still a bit buggy, but works sometimes.
+ * I need to find out how to make the jump clean.
+ * At the moment, some corruption of the mpeg2 stream occurs, 
+ * which causes libmpeg2 to crash.
+ *
+ * Revision 1.23  2002/01/18 00:23:52  jcdutton
+ * Support Ejecting of DVD.
+ * It will first un-mount the DVD, then eject it.
+ *
+ * Revision 1.22  2002/01/17 14:50:32  jcdutton
+ * Fix corruption of stream during menu transitions.
+ * Menu transitions are now clean.
+ *
+ * Revision 1.21  2002/01/15 00:37:03  jcdutton
+ * Just a few cleanups, and a assert fix. (memset fixed it)
+ *
+ * Revision 1.20  2002/01/13 22:17:57  jcdutton
+ * Change logging.
+ *
+ *
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,470 @@
+/* 
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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$
+ *
+ */
+
+#ifndef DVDNAV_H_INCLUDED
+#define DVDNAV_H_INCLUDED
+
+/* Defines the various events and dvdnav_event_t */
+#include "dvdnav_events.h"
+
+/* Various useful types */
+#include "dvd_types.h"
+
+#include <dvdread/dvd_reader.h>
+
+/* Opaque dvdnav data-type */
+typedef struct dvdnav_s dvdnav_t;
+
+/* Status */
+typedef int dvdnav_status_t;
+
+#define DVDNAV_STATUS_ERR 0
+#define DVDNAV_STATUS_OK  1
+
+/**
+ * NOTE: Unless otherwise stated, all functions return DVDNAV_STATUS_OK if
+ * they succeeded, otherwise DVDNAV_STATUS_ERR is returned and the error may
+ * be obtained by calling dvdnav_err_to_string().
+ */
+
+/*** Initialisation & housekeeping functions ***/
+
+/**
+ * Attempts to open the DVD drive at the specifiec path and pre-cache
+ * any CSS-keys that your hacked libdvdread may use.
+ *
+ * Arguments:
+ *   dest     -- Pointer to a dvdnav_t pointer to fill in.
+ *   path     -- Any libdvdread acceptable path
+ */
+dvdnav_status_t dvdnav_open(dvdnav_t** dest, char *path); 
+
+/**
+ * Closes a dvdnav_t previously opened with dvdnav_open(), freeing any 
+ * memory associated with it.
+ *
+ * Arguments:
+ *   self     -- dvdnav_t to close.
+ */
+dvdnav_status_t dvdnav_close(dvdnav_t *self);
+
+/**
+ * Fills a pointer wiht a value pointing to a string describing
+ * the path associated with an open dvdnav_t. It assigns it NULL
+ * on error.
+ *
+ * Arguments:
+ *   path     -- Pointer to char* to fill in.
+ */
+dvdnav_status_t dvdnav_path(dvdnav_t *self, char** path);
+
+/**
+ * Returns a human-readable string describing the last error.
+ *
+ * Returns: A pointer to said string.
+ */
+char* dvdnav_err_to_string(dvdnav_t *self);
+
+/** DVD Player characteristics (FIXME: Need more entries) **/
+
+/**
+ * Returns the region mask (bit 0 - region 1, bit 1 - R 2, etc) of the
+ * VM
+ */
+dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *self, int *region);
+
+/**
+ * Sets the region mask of the VM.
+ *
+ * mask: 0x00..0xff
+ */
+dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int mask);
+
+/**
+ * Specify whether read-ahead caching should be used 
+ * 
+ * use_readahead: 0 - no, 1 - yes
+ */
+dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int use_readahead);
+
+/**
+ * Query state of readahead flag
+ *
+ * flag: pointer to int to recieve flag value
+ */
+dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *self, int* flag);
+
+/** Getting data **/
+
+/**
+ * Gets the next block off the DVD places it in 'buf'. If there is any
+ * special information, the value pointed to by 'event' gets set 
+ * accordingly.
+ *
+ * If 'event' is DVDNAV_BLOCK_OK then 'buf' is filled with the next block
+ * (note that means it has to be at /least/ 2048 bytes big). 'len' is
+ * then set to 2048.
+ *
+ * Otherwise, buf is filled with an appropriate event structure and
+ * len is set to the length of that structure,
+ */
+dvdnav_status_t dvdnav_get_next_block(dvdnav_t *self, unsigned char *buf,
+				      int *event, int *len);
+
+/** Navigation **/
+
+/**
+ * Returns the number of titles on the disk in titles.
+ */
+dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int *titles);
+
+/**
+ * If we are currently in a still-frame, skip it.
+ */
+dvdnav_status_t dvdnav_still_skip(dvdnav_t *self);
+
+/**
+ * Plays a specified title of the DVD 
+ *
+ * Arguments:
+ *   title  -- 1..99
+ */
+dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int title);
+
+/**
+ * Plays the specifiec title, starting from the specified
+ * part (chapter)
+ *
+ * Arguments:
+ *   title  -- 1..99
+ *   part   -- 1..999
+ */
+dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int title, int part);
+
+/**
+ * Play the specified amount of parts of the specified title of
+ * the DVD then STOP.
+ *
+ * Arguments:
+ *   title  -- 1..99
+ *   part   -- 1..999
+ *   parts_to_play -- 1..999
+ */
+dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *self, int title,
+					  int part, int parts_to_play);
+
+/**
+ * Play the specified title starting from the specified time
+ *
+ * Arguments:
+ *   title -- 1..99
+ *   time: (timecode) hours, minutes, seconds + frames.
+ */
+dvdnav_status_t dvdnav_time_play(dvdnav_t *self, int title,
+				unsigned long int time);
+
+/**
+ * Stops playing the current title (causes a STOP action in
+ * dvdnav_get_next_block).
+ */
+dvdnav_status_t dvdnav_stop(dvdnav_t *self);
+
+/**
+ * Stop playing current title and play the "GoUp"-program chain  
+ */
+dvdnav_status_t dvdnav_go_up(dvdnav_t *self);
+
+/** SEARCHING **/
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the specified timecode.
+ *
+ * Arguments:
+ *   time -- timecode.
+ */
+dvdnav_status_t dvdnav_time_search(dvdnav_t *self, 
+				  unsigned long int time);
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the specified sector offset.
+ *
+ * Arguments:
+ *   offset -- sector offset.
+ *   origin -- Start from here, start or end.
+ */
+dvdnav_status_t dvdnav_sector_search(dvdnav_t *self, 
+				  unsigned long int offset, int origin);
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the specified part (chapter).
+ *
+ * Arguments:
+ *   part: 1..999
+ */
+dvdnav_status_t dvdnav_part_search(dvdnav_t *self, int part);
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the previous program (if it exists)
+ */
+dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self);
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the first program.
+ */
+dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *self);
+
+/**
+ * Stop playing the current title and start playback of the title
+ * from the next program (if it exists)
+ */
+dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self);
+
+/**
+ * Stop playing the current title and jump to the specified menu.
+ *
+ * Arguments:
+ *   menu -- Which menu to call
+ */
+dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu);
+
+/**
+ * Return the title number and chapter currently being played or
+ * -1 if in a menu.
+ */
+dvdnav_status_t dvdnav_current_title_info(dvdnav_t *self, int *title,
+			     		  int *part);
+
+/**
+ * Return a string describing the title
+ */
+dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, char **title_str);
+
+/**
+ * Return the current position (in blocks) within the current
+ * part and the length (in blocks) of said part.
+ */
+dvdnav_status_t dvdnav_get_position(dvdnav_t *self, unsigned int* pos,
+				   unsigned int *len);
+
+/** Highlights **/
+
+/**
+ * Set the value pointed to to the currently highlighted button 
+ * number (1..36) or 0 if no button is highlighed.
+ *
+ * Arguments:
+ *   button -- Pointer to the value to fill in.
+ */
+dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int* button);
+
+/**
+ * Move button highlight around (e.g. with arrow keys)
+ */
+dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self);
+dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self);
+dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self);
+dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self);
+
+/**
+ * Activate (press) highlighted button
+ */
+dvdnav_status_t dvdnav_button_activate(dvdnav_t *self);
+
+/**
+ * Highlight a specific button button
+ *
+ * button -- 1..39.
+ */
+dvdnav_status_t dvdnav_button_select(dvdnav_t *self, int button);
+
+/**
+ * Activate (press) specified button.
+ *
+ * Arguments:
+ *   button: 1..36
+ */
+dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *self, int button);
+
+/**
+ * Select button at specified (image) co-ordinates.
+ *
+ * Arguments:
+ *   x,y: Image co-ordinates
+ */
+dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, int x, int y);
+
+/**
+ * Activate (press) button at specified co-ordinates.
+ */
+dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, int x, int y);
+
+/** i18n **/
+
+/**
+ * Set specified menu language.
+ *
+ * Arguments:
+ *   code: 1 char ISO 639 Language code 
+ */
+dvdnav_status_t dvdnav_menu_languge_select(dvdnav_t *self,
+					  char *code);
+
+/** SPU/Audio streams **/
+
+/**
+ * Set a specific PHYSICAL MPEG stream.
+ *
+ * Arguments:
+ *   audio: 0..7
+ */
+dvdnav_status_t dvdnav_physical_audio_stream_change(dvdnav_t *self,
+						   int audio);
+
+/**
+ * Set a specific logical audio stream.
+ *
+ * Arguments:
+ *   audio: 0..7
+ */
+dvdnav_status_t dvdnav_logical_audio_stream_change(dvdnav_t *self,
+		       				  int audio);
+
+/**
+ * Set the int pointed to to the current PHYSICAL audio
+ * stream.
+ *
+ * Arguments:
+ *   audio: Pointer to value
+ */
+dvdnav_status_t dvdnav_get_physical_audio_stream(dvdnav_t *self, int* audio);
+
+/**
+ * Set the int pointed to to the current LOGICAL audio
+ * stream.
+ *
+ * Arguments:
+ *   audio: Pointer to value
+ */
+dvdnav_status_t dvdnav_get_logical_audio_stream(dvdnav_t *self, int* audio);
+
+/**
+ * Set a specific PHYSICAL MPEG SPU stream and whether it should be
+ * displayed.
+ *
+ * Arguments:
+ *   stream: 0..31 or 63 (dummy)
+ *   display: 0..1
+ */
+dvdnav_status_t dvdnav_physical_spu_stream_change(dvdnav_t *self,
+					  	  int stream, int display);
+
+/**
+ * Set a specific LOGICAL SPU stream and whether it should be
+ * displayed.
+ *
+ * Arguments:
+ *   stream: 0..31 or 63 (dummy)
+ *   display: 0..1
+ */
+dvdnav_status_t dvdnav_logical_spu_stream_change(dvdnav_t *self,
+						 int stream, int display);
+
+/**
+ * Set the ints pointed to to the current PHYSICAL SPU
+ * stream & display flag.
+ *
+ * Arguments:
+ *   stream, display: Pointers to value
+ */
+dvdnav_status_t dvdnav_get_physical_spu_stream(dvdnav_t *self,
+					       int* stream, int* disply);
+
+/**
+ * Set the ints pointed to to the current LOGICAL SPU
+ * stream & display flag.
+ *
+ * Arguments:
+ *   stream, display: Pointers to value
+ */
+dvdnav_status_t dvdnav_get_logical_spu_stream(dvdnav_t *self,
+		       			     int* stream, int* disply);
+
+/** ANGLES **/
+
+/**
+ * Sets the current angle 
+ *
+ * Arguments:
+ *   angle: 1..9
+ */
+dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int angle);
+
+/**
+ * Returns the current angle and number of angles present
+ */
+dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *self, int* current_angle,
+				     int *number_of_angles);
+
+/**
+ * Converts a *logical* subpicture stream id into country code 
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream);
+uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream);
+
+/**
+ * Converts a _logical_ audio/spu stream number (as in the SPRMs) to the physical
+ * stream number encoded on the DVD. Returns -1 if this audio/spu stream is not
+ * present.
+ */
+int8_t dvdnav_audio_logical_to_physical(dvdnav_t *self, uint8_t logical);
+int8_t dvdnav_spu_logical_to_physical(dvdnav_t *self, uint8_t logical);
+
+/**
+ * The exact opposite of the above two calls
+ */
+int8_t dvdnav_audio_physical_to_logical(dvdnav_t *self, uint8_t physical);
+int8_t dvdnav_spu_physical_to_logical(dvdnav_t *self, uint8_t physical);
+
+/* Following functions returns:
+ *  -1 on failure,
+ *   1 if condition is true,
+ *   0 if condition is false
+ */
+/***  Current VM domain state  ***/
+/* First Play domain. (Menu) */
+int8_t dvdnav_is_domain_fp(dvdnav_t *self);
+/* Video management Menu domain. (Menu) */
+int8_t dvdnav_is_domain_vmgm(dvdnav_t *self);
+/* Video Title Menu domain (Menu) */
+int8_t dvdnav_is_domain_vtsm(dvdnav_t *self);
+/* Video Title domain (playing movie). */
+int8_t dvdnav_is_domain_vts(dvdnav_t *self);
+
+#endif /* DVDNAV_H_INCLUDED */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav_events.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,90 @@
+/* 
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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$
+ *
+ */
+
+#ifndef DVDNAV_EVENTS_H_INCLUDED
+#define DVDNAV_EVENTS_H_INCLUDED
+
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_types.h>
+#include <dvdread/dvd_reader.h>
+
+/* This header defines events and event types */
+
+/*** EVENTS ***/
+
+#define DVDNAV_BLOCK_OK     0 /* The next black was returned */
+#define DVDNAV_NOP          1 /* No action should be taken   */
+#define DVDNAV_STILL_FRAME  2 /* The preceeding block was the last in a 
+				 still frame. */
+#define DVDNAV_SPU_STREAM_CHANGE    3 /* The SPU stream was changed */
+#define DVDNAV_AUDIO_STREAM_CHANGE  4 /* The Audio stream was changed */
+#define DVDNAV_VTS_CHANGE   5 /* We have changed VTS */ 
+#define DVDNAV_CELL_CHANGE  6 /* We have jumped to a new cell */
+#define DVDNAV_NAV_PACKET   7 /* The packet just passed was the NAV packet */
+#define DVDNAV_STOP         8 /* The last block was final, no more are coming */
+#define DVDNAV_HIGHLIGHT    9 /* Change highlight region */
+#define DVDNAV_SPU_CLUT_CHANGE 10 /* SPU CLUT */
+#define DVDNAV_SEEK_DONE    11 /* Seek done, subtitles should be reset */
+
+/*** EVENT TYPES ***/
+
+typedef struct {
+  int length;   /* The length (in seconds) the still frame
+		 * should be displayed for, or 0xff if
+		 * indefinate. */
+} dvdnav_still_event_t;
+
+typedef struct {
+  int physical; /* The physical (MPEG) stream number. */
+  int logical;  /* The logical (DVD) stream number.   */
+} dvdnav_stream_change_event_t;
+
+typedef struct {
+  int old_vtsN; /* The old VTS number */
+  dvd_read_domain_t old_domain; /* The old domain */
+  int new_vtsN; /* The new one */
+  dvd_read_domain_t new_domain;
+} dvdnav_vts_change_event_t;
+
+typedef struct {
+  cell_playback_t *old_cell; /* The old cell (or NULL if this is 
+				the first cell) */
+  cell_playback_t *new_cell; /* The cell_playback_t for the new cell */
+} dvdnav_cell_change_event_t;
+
+typedef struct {
+  pci_t *pci;
+  dsi_t *dsi;
+} dvdnav_nav_packet_event_t;
+
+typedef struct {
+  int display; /* 0 - hide, 1 - show, entries below only guaranteed useful
+		  if this is '1' */
+  uint32_t palette;  /* The CLUT entries for the highlight palette 
+                        (4-bits per entry -> 4 entries) */
+  uint16_t sx,sy,ex,ey; /* The start/end x,y positions */
+  uint32_t pts;         /* Highlight PTS to match with SPU */
+  uint32_t buttonN;     /* Button number for the SPU decoder. */
+} dvdnav_highlight_event_t;
+
+#endif /* DVDNAV_EVENTS_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav_internal.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,174 @@
+/* 
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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$
+ *
+ */
+
+#ifndef DVDNAV_INTERNAL_H_INCLUDED
+#define DVDNAV_INTERNAL_H_INCLUDED
+
+#include "dvdnav.h"
+#include "vm.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+
+
+/* Maximum length of an error string */
+#define MAX_ERR_LEN 255
+
+/* Use the POSIX PATH_MAX if available */
+#ifdef PATH_MAX
+#define MAX_PATH_LEN PATH_MAX
+#else
+#define MAX_PATH_LEN 255 /* Arbitary */
+#endif
+
+#ifndef DVD_VIDEO_LB_LEN
+#define DVD_VIDEO_LB_LEN 2048
+#endif
+
+/*
+ * These are defined here because they are
+ * not in ifo_types.h, they maybe one day 
+ */
+
+#ifndef audio_status_t
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int available     : 1;
+  unsigned int zero1         : 4;
+  unsigned int stream_number : 3;
+  uint8_t zero2;
+#else
+  uint8_t zero2;
+  unsigned int stream_number : 3;
+  unsigned int zero1         : 4;  
+  unsigned int available     : 1;
+#endif
+} ATTRIBUTE_PACKED audio_status_t;
+#endif
+
+#ifndef spu_status_t
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int available               : 1;
+  unsigned int zero1                   : 2;
+  unsigned int stream_number_4_3       : 5;
+  unsigned int zero2                   : 3;
+  unsigned int stream_number_wide      : 5;
+  unsigned int zero3                   : 3;
+  unsigned int stream_number_letterbox : 5;
+  unsigned int zero4                   : 3;
+  unsigned int stream_number_pan_scan  : 5;
+#else
+  unsigned int stream_number_pan_scan  : 5;
+  unsigned int zero4                   : 3;
+  unsigned int stream_number_letterbox : 5;
+  unsigned int zero3                   : 3;
+  unsigned int stream_number_wide      : 5;
+  unsigned int zero2                   : 3;
+  unsigned int stream_number_4_3       : 5;
+  unsigned int zero1                   : 2;
+  unsigned int available               : 1;
+#endif
+} ATTRIBUTE_PACKED spu_status_t;
+#endif
+
+/* The main DVDNAV type */
+
+struct dvdnav_s {
+  /* General data */
+  char path[MAX_PATH_LEN];        /* Path to DVD device/dir */
+  dvd_file_t *file;               /* Currently opened file */
+  int open_vtsN;                  /* The domain and number of the... */
+  int open_domain;                /* ..currently opened VOB */
+ 
+  /* Position data */
+  uint32_t vobu_start;
+  uint32_t vobu_length;
+  uint32_t blockN;
+  uint32_t next_vobu;
+  cell_playback_t *cell;
+  uint32_t jmp_blockN;
+  uint32_t jmp_vobu_start;
+  uint32_t seekto_block;
+
+  /* NAV data */
+  pci_t pci;
+  dsi_t dsi;
+  
+  /* Flags */
+  int expecting_nav_packet;
+  int at_soc;      /* Are we at the start of a cell? */
+  int still_frame; /* >=0 send still frame event with len still_frame
+                    * -1 don't send event. */
+  int jumping;     /* non-zero if we are in the process of jumping */
+  int seeking;     /* non-zero if we are in the process of seeking */
+  int stop;        /* Are we stopped? (note not paused, actually stopped) */
+  int highlight_changed; /* The highlight changed */
+  int spu_clut_changed; /* The SPU CLUT changed */ 
+  int spu_stream_changed; /* The SPU STREAM changed */ 
+  int audio_stream_changed; /* The AUDIO STREAM changed */ 
+  int started; /* vm_start has been called? */
+  int use_read_ahead; /* 1 - use read-ahead cache, 0 - don't */
+  /* VM */
+  vm_t* vm;
+  pthread_mutex_t vm_lock;
+
+  /* Highlight */
+  int hli_state;  /* State of highlight 0 - disabled, 1 - selected,
+		 2 - activated */
+  uint16_t hli_bbox[4]; /* Highlight bounding box */
+  uint32_t hli_clut; /* Highlight palette */
+  uint32_t hli_pts;  /* Highlight PTS for matching with SPU packet. */
+  uint32_t hli_buttonN;  /* Button number for SPU decoder. */
+  /* Read-ahead cache. */
+  uint8_t      *cache_buffer;
+  int32_t      cache_start_sector; /* -1 means cache invalid */
+  size_t       cache_block_count;
+  size_t       cache_malloc_size;
+  int          cache_valid;
+  
+  /* Errors */
+  char err_str[MAX_ERR_LEN];
+};
+
+/* Common things we do everytime we do a jump */
+void dvdnav_do_post_jump(dvdnav_t *self);
+
+/** USEFUL MACROS **/
+
+#define printerrf(format, args...) snprintf(self->err_str, MAX_ERR_LEN, format, ## args);
+#define printerr(str) strncpy(self->err_str, str, MAX_ERR_LEN);
+/* Save my typing */
+
+#define S_ERR DVDNAV_STATUS_ERR
+#define S_OK  DVDNAV_STATUS_OK
+
+#endif /* DVDNAV_INTERNAL_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/highlight.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,277 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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
+
+/*
+#define BUTTON_TESTING
+*/
+
+#include <dvdnav.h>
+#include "dvdnav_internal.h"
+
+#include "vm.h"
+#
+#include <dvdread/nav_types.h>
+
+#ifdef BUTTON_TESTING
+#include <dvdread/nav_print.h>
+#endif
+
+/* Highlighting API calls */
+
+dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int* button) {
+  if(!self)
+   return S_ERR;
+
+  /* Simply return the appropriate value based on the SPRM */
+  (*button) = (self->vm->state.HL_BTNN_REG) >> 10;
+  
+  return S_OK;
+}
+
+btni_t *__get_current_button(dvdnav_t *self) {
+  int button = 0;
+
+  if(dvdnav_get_current_highlight(self, &button) != S_OK) {
+    printerrf("Unable to get information on current highlight.");
+    return NULL;
+  }
+#ifdef BUTTON_TESTING
+  navPrint_PCI(&(self->pci));
+#endif
+  
+  return &(self->pci.hli.btnit[button-1]);
+}
+
+dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self) {
+  btni_t *button_ptr;
+  
+  if(!self)
+   return S_ERR;
+
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    return S_ERR;
+  }
+
+  dvdnav_button_select(self, button_ptr->up);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self) {
+  btni_t *button_ptr;
+  
+  if(!self)
+   return S_ERR;
+
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    return S_ERR;
+  }
+
+  dvdnav_button_select(self, button_ptr->down);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self) {
+  btni_t *button_ptr;
+  
+  if(!self)
+   return S_ERR;
+
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    printerr("Error fetching information on current button.");
+    return S_ERR;
+  }
+
+  dvdnav_button_select(self, button_ptr->right);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self) {
+  btni_t *button_ptr;
+  
+  if(!self)
+   return S_ERR;
+
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    return S_ERR;
+  }
+
+  dvdnav_button_select(self, button_ptr->left);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_button_activate(dvdnav_t *self) {
+  int button;
+  btni_t *button_ptr;
+  
+  if(!self) 
+   return S_ERR;
+  pthread_mutex_lock(&self->vm_lock); 
+
+  /* Precisely the same as selecting a button except we want
+   * a different palette */
+  if(dvdnav_get_current_highlight(self, &button) != S_OK) {
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_ERR;
+  }
+  if(dvdnav_button_select(self, button) != S_OK) {
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_ERR;
+  }
+  
+  /* Now get the current button's info */
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    printerr("Error fetching information on current button.");
+    pthread_mutex_unlock(&self->vm_lock); 
+    return S_ERR;
+  }
+
+  /* And set the palette */
+  if(button_ptr->btn_coln != 0) {
+    self->hli_clut = 
+     self->pci.hli.btn_colit.btn_coli[button_ptr->btn_coln-1][1];
+  } else {
+    self->hli_clut = 0;
+  }
+
+  /* Finally, make the VM execute the appropriate code and
+   * scedule a jump */
+
+  if(vm_eval_cmd(self->vm, &(button_ptr->cmd)) == 1) {
+    /* Cammand caused a jump */
+    dvdnav_do_post_jump(self);
+  }
+  
+  pthread_mutex_unlock(&self->vm_lock); 
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_button_select(dvdnav_t *self, int button) {
+  btni_t *button_ptr;
+  
+  if(!self) {
+   printerrf("Unable to select button number %i as self state bad",
+	      button);
+   return S_ERR;
+  }
+ 
+  printf("Button select %i\n", button); 
+  
+  /* Set the highlight SPRM if the passed button was valid*/
+  if((button <= 0) || (button > self->pci.hli.hl_gi.btn_ns)) {
+    printerrf("Unable to select button number %i as it doesn't exist",
+	      button);
+    return S_ERR;
+  }
+  self->vm->state.HL_BTNN_REG = (button << 10);
+
+  /* Now get the current button's info */
+  if((button_ptr = __get_current_button(self)) == NULL) {
+    printerr("Error fetching information on current button.");
+    return S_ERR;
+  }
+
+  self->hli_bbox[0] = button_ptr->x_start;
+  self->hli_bbox[1] = button_ptr->y_start;
+  self->hli_bbox[2] = button_ptr->x_end;
+  self->hli_bbox[3] = button_ptr->y_end;
+  self->hli_state = 1; /* Selected */
+
+  if(button_ptr->btn_coln != 0) {
+    self->hli_clut = 
+     self->pci.hli.btn_colit.btn_coli[button_ptr->btn_coln-1][0];
+  } else {
+    self->hli_clut = 0;
+  }
+  self->hli_pts = self->pci.hli.hl_gi.hli_s_ptm;
+  self->hli_buttonN = button;
+  self->highlight_changed = 1;
+#ifdef BUTTON_TESTING
+  printf("highlight.c:Highlight area is (%u,%u)-(%u,%u), display = %i, button = %u\n",
+	       button_ptr->x_start, button_ptr->y_start,
+	       button_ptr->x_end, button_ptr->y_end,
+	       1,
+	       button);
+#endif
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *self, 
+						  int button) {
+  /* A trivial function */
+  if(dvdnav_button_select(self, button) != S_ERR) {
+    return dvdnav_button_activate(self);
+  }
+  
+  /* Should never get here without an error */
+  return S_ERR;
+}
+
+dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, int x, int y) {
+  int button, cur_button;
+  
+  /* FIXME: At the moment, the case of no button matchin (x,y) is
+   * silently ignored, is this OK? */
+  if(!self)
+   return S_ERR;
+
+  if(dvdnav_get_current_highlight(self, &cur_button) != S_OK) {
+    return S_ERR;
+  }
+
+  /* Loop through each button */
+  for(button=1; button <= self->pci.hli.hl_gi.btn_ns; button++) {
+    btni_t *button_ptr = NULL;
+
+    button_ptr = &(self->pci.hli.btnit[button-1]);
+    if((x >= button_ptr->x_start) && (x <= button_ptr->x_end) &&
+       (y >= button_ptr->y_start) && (y <= button_ptr->y_end)) {
+      /* As an efficiency measure, only re-select the button
+       * if it is different to the previously selected one. */
+      if(button != cur_button) {
+	dvdnav_button_select(self, button);
+      }
+    }
+  }
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, int x, int y) {
+  /* A trivial function */
+  if(dvdnav_mouse_select(self, x,y) != S_ERR) {
+    return dvdnav_button_activate(self);
+  }
+  
+  /* Should never get here without an error */
+  return S_ERR;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/navigation.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,170 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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 <dvdnav.h>
+#include "dvdnav_internal.h"
+
+#include "vm.h"
+
+/* Navigation API calls */
+
+/* Common things we do everytime we do a jump */
+void dvdnav_do_post_jump(dvdnav_t *self) {
+  dvd_state_t *state = &(self->vm->state);
+  cell_playback_t *cell = &(state->pgc->cell_playback[state->cellN - 1]);
+
+  self->jmp_blockN = 0; /* FIXME: Should this be different? */
+  self->jmp_vobu_start = cell->first_sector;
+  self->jumping = 1;
+  self->still_frame = -1;
+}
+
+dvdnav_status_t dvdnav_still_skip(dvdnav_t *self) {
+  if(!self)
+   return S_ERR;
+
+  self->still_frame = -1;
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int *titles) {
+  if(!self)
+   return S_ERR;
+
+  if(!titles) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+
+  (*titles) = vm_get_vmgi(self->vm)->tt_srpt->nr_of_srpts;
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int title) {
+  int num_titles;
+
+  if(!self) {
+    return S_ERR;
+  }
+
+  /* Check number of titles */
+  dvdnav_get_number_of_titles(self, &num_titles);
+  if((title > num_titles) || (title <= 0)) {
+    printerrf("Invalid title passed (%i, maximum %i)", title,
+	      num_titles);
+    return S_ERR;
+  }
+  
+  vm_start_title(self->vm, title);
+
+  /* self->expecting_nav_packet = 1; */
+
+  dvdnav_do_post_jump(self);
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int title, int part) {
+  int num_titles, num_progs;
+
+  if(!self) {
+    return S_ERR;
+  }
+
+  /* Check number of titles */
+  dvdnav_get_number_of_titles(self, &num_titles);
+  if((title > num_titles) || (title <= 0)) {
+    printerrf("Invalid title passed (%i, maximum %i)", title,
+	      num_titles);
+    return S_ERR;
+  }
+ 
+  vm_start_title(self->vm, title);
+
+
+  /* Check number of parts */
+  num_progs = self->vm->state.pgc->nr_of_programs;
+  if((part > num_progs) || (part <= 0)) {
+    printerrf("Invalid program passed (%i, maximum %i)", part,
+	      num_progs);
+    return S_ERR;
+  }
+   
+  vm_jump_prog(self->vm, part);
+  
+   /* self->expecting_nav_packet = 1; */
+
+  dvdnav_do_post_jump(self);
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *self, int title,
+					  int part, int parts_to_play) {
+  /* Perform jump as per usual */
+
+  return dvdnav_part_play(self, title, part);
+  
+  /* FIXME: Impement auto-stop */
+  
+  /* return S_OK;*/ 
+}
+
+dvdnav_status_t dvdnav_time_play(dvdnav_t *self, int title,
+				unsigned long int time) {
+  /* FIXME: Implement */
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_stop(dvdnav_t *self) {
+  if(!self)
+   return S_ERR;
+
+  /* Set the STOP flag */
+  
+  self->stop = 1;
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_go_up(dvdnav_t *self) {
+  if(!self)
+   return S_ERR;
+
+  /* A nice easy function... delegate to the VM */
+  vm_go_up(self->vm);
+
+  dvdnav_do_post_jump(self);
+
+  return S_OK;
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/read_cache.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,91 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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 "read_cache.h"
+
+/* This function MUST be called whenever self->file changes. */
+void dvdnav_read_cache_clear(dvdnav_t *self) {
+  if(!self)
+   return;
+  
+  self->cache_start_sector = -1;
+  self->cache_valid = 0;
+}
+/* This function is called just after reading the NAV packet. */
+void dvdnav_pre_cache_blocks(dvdnav_t *self, int sector, size_t block_count) {
+  int result;
+ 
+  if(!self)
+   return;
+  
+  if(!self->use_read_ahead) {
+    self->cache_valid = 0;
+    self->cache_start_sector = -1;
+    return;
+  }
+  
+  if (self->cache_buffer) {
+    if( block_count > self->cache_malloc_size) {
+      self->cache_buffer = realloc(self->cache_buffer, block_count * DVD_VIDEO_LB_LEN);
+      self->cache_malloc_size = block_count;
+    } 
+  } else {
+    self->cache_buffer = malloc(block_count * DVD_VIDEO_LB_LEN);
+    self->cache_malloc_size = block_count;
+  }
+  self->cache_start_sector = sector;
+  self->cache_block_count = block_count;
+  result = DVDReadBlocks( self->file, sector, block_count, self->cache_buffer);
+  self->cache_valid = 1;
+}
+
+/* This function will do the cache read once implemented */
+int dvdnav_read_cache_block( dvdnav_t *self, int sector, size_t block_count, uint8_t *buf) {
+  int result;
+ 
+  if(!self)
+   return 0;
+
+  if(self->cache_valid && self->use_read_ahead) {
+    if (self->cache_start_sector != -1 ) {
+      if ((sector >= self->cache_start_sector) && 
+	  (sector < self->cache_start_sector + self->cache_block_count)) {
+	memcpy(buf, self->cache_buffer + ((off_t)((off_t)sector - (off_t)self->cache_start_sector) * DVD_VIDEO_LB_LEN), DVD_VIDEO_LB_LEN);
+	return DVD_VIDEO_LB_LEN;
+      }
+    }
+  } else {
+    result = DVDReadBlocks( self->file, sector, block_count, buf);
+    return result;
+  }
+  
+  printf("DVD read cache miss! sector=%d, start=%d\n", sector, self->cache_start_sector); 
+  result = DVDReadBlocks( self->file, sector, block_count, buf);
+  return result;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/read_cache.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,36 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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$
+ *
+ */
+
+#ifndef __DVDNAV_READ_CACHE_H
+#define __DVDNAV_READ_CACHE_H
+
+#include "dvdnav_internal.h"
+
+/* This function MUST be called whenever self->file changes. */
+void dvdnav_read_cache_clear(dvdnav_t *self);
+/* This function is called just after reading the NAV packet. */
+void dvdnav_pre_cache_blocks(dvdnav_t *self, int sector, size_t block_count);
+/* This function will do the cache read */
+int dvdnav_read_cache_block(dvdnav_t *self, int sector, size_t block_count, uint8_t *buf);
+
+#endif /* __DVDNAV_READ_CACHE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/searching.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,320 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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 <dvdnav.h>
+#include "dvdnav_internal.h"
+
+#include "vm.h"
+#include <dvdread/nav_types.h>
+
+/* Searching API calls */
+
+dvdnav_status_t dvdnav_time_search(dvdnav_t *self,
+				   unsigned long int time) {
+/* Time search the current PGC based on the xxx table */
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_sector_search(dvdnav_t *self,
+				   unsigned long int offset, int origin) {
+/* FIXME: Implement */
+
+  uint32_t target = 0;
+  uint32_t length = 0;
+  uint32_t first_cell_nr, last_cell_nr, cell_nr, fnd_cell_nr;
+  int found;
+  cell_playback_t *cell, *fnd_cell;
+  dvd_state_t *state;
+  dvdnav_status_t result;
+
+  if((!self) || (!self->vm) )
+    return -1;
+  
+  state = &(self->vm->state);
+  if((!state) || (!state->pgc) )
+    return -1;
+   
+  if(offset == 0) 
+    return -1;
+
+  if(self->still_frame != -1)
+    /* Cannot do seeking in a still frame. */
+    return -1;
+
+  pthread_mutex_lock(&self->vm_lock);
+  result = dvdnav_get_position(self, &target, &length);
+  fprintf(stderr,"FIXME: seeking to offset=%lu pos=%u length=%u\n", offset, target, length); 
+  fprintf(stderr,"FIXME: Before cellN=%u blockN=%u\n" ,
+      state->cellN,
+      state->blockN);
+  if(!result) {
+    pthread_mutex_unlock(&self->vm_lock);
+    return -1;
+  }
+ 
+  switch(origin) {
+   case SEEK_SET:
+    if(offset > length) {
+      pthread_mutex_unlock(&self->vm_lock);
+      return -1;
+    }
+    target = offset;
+    break;
+   case SEEK_CUR:
+    if(target + offset > length) {
+      pthread_mutex_unlock(&self->vm_lock);
+      return -1;
+    }
+    target += offset;
+    break;
+   case SEEK_END:
+    if(length - offset < 0) {
+      pthread_mutex_unlock(&self->vm_lock);
+      return -1;
+    }
+    target = length - offset;
+   default:
+    /* Error occured */
+    pthread_mutex_unlock(&self->vm_lock);
+    return -1;
+  }
+
+  /* First find closest cell number in program */
+  first_cell_nr = state->pgc->program_map[state->pgN-1];
+  if(state->pgN < state->pgc->nr_of_programs) {
+    last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+  } else {
+    last_cell_nr = state->pgc->nr_of_cells;
+  }
+    
+  found = 0; target += state->pgc->cell_playback[first_cell_nr-1].first_sector;
+  fnd_cell_nr = last_cell_nr + 1;
+  for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
+    cell =  &(state->pgc->cell_playback[cell_nr-1]);
+    if((cell->first_sector <= target) && (cell->last_sector >= target)) {
+      state->cellN = cell_nr;
+      state->blockN = 0;
+      found = 1;
+      fnd_cell_nr = cell_nr;
+      fnd_cell = cell;
+    }
+  }
+
+  if(fnd_cell_nr <= last_cell_nr) {
+    printf("Seeking to cell %i from choice of %i to %i\n",
+	   fnd_cell_nr, first_cell_nr, last_cell_nr);
+    self->seekto_block = target;
+    self->seeking = 1;
+    /* 
+     * Clut does not actually change,
+     * but as the decoders have been closed then opened,
+     * A new clut has to be sent.
+     */
+    self->spu_clut_changed = 1;
+    //ogle_do_post_jump(ogle);
+    fprintf(stderr,"FIXME: After cellN=%u blockN=%u\n" ,
+      state->cellN,
+      state->blockN);
+    
+    pthread_mutex_unlock(&self->vm_lock);
+    return target;
+  } else {
+    fprintf(stderr, "Error when seeking, asked to seek outside program\n");
+  }
+
+
+
+  printf("FIXME: Implement seeking to location %u\n", target); 
+
+//  self->seekto_block=target;
+//  self->seeking = 1;
+
+  pthread_mutex_unlock(&self->vm_lock);
+  return -1;
+}
+
+dvdnav_status_t dvdnav_part_search(dvdnav_t *self, int part) {
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self) {
+  dvd_state_t *state;
+  state = &(self->vm->state);
+  /* Make sure this is not the first chapter */
+  
+  if(state->pgN <= 1 ) {
+    printf("dvdnav: at first chapter. prev chapter failed.\n");
+    return S_ERR;
+  }
+  printf("dvdnav: previous chapter\n");
+  vm_jump_prog(self->vm, state->pgN - 1);
+  dvdnav_do_post_jump(self);
+  printf("dvdnav: previous chapter done\n");
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *self) {
+
+  printf("dvdnav: top chapter. NOP.\n");
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self) {
+  dvd_state_t *state;
+  state = &(self->vm->state);
+  /* Make sure this is not the last chapter */
+  if(state->pgN >= state->pgc->nr_of_programs) {
+    printf("dvdnav: at last chapter. next chapter failed.\n");
+    return S_ERR;
+  }
+  printf("dvdnav: next chapter\n");
+  vm_jump_prog(self->vm, state->pgN + 1);
+  dvdnav_do_post_jump(self);
+  printf("dvdnav: next chapter done\n");
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu) {
+  dvd_state_t *state;
+
+  pthread_mutex_lock(&self->vm_lock); 
+  state = &(self->vm->state);
+  vm_menu_call(self->vm, menu, 0); 
+  dvdnav_do_post_jump(self);
+  pthread_mutex_unlock(&self->vm_lock); 
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_current_title_info(dvdnav_t *self, int *tt, int *pr) {
+int vts_ttn = 0;
+  int vts, i;
+  domain_t domain;
+  tt_srpt_t* srpt;
+  
+  if(!self)
+   return S_ERR;
+
+  if(!tt || !pr) {
+    printerr("Passed a NULL pointer");
+  }
+
+  if(tt)
+   *tt = -1;
+  if(*pr)
+   *pr = -1;
+
+  domain = self->vm->state.domain;
+  if((domain == FP_DOMAIN) || (domain == VMGM_DOMAIN)) {
+    /* Not in a title */
+    return S_OK;
+  }
+  
+  vts_ttn = self->vm->state.VTS_TTN_REG;
+  vts = self->vm->state.vtsN;
+
+  if(pr) {
+    *pr = self->vm->state.pgN;
+  }
+
+  /* Search TT_SRPT for title */
+  if(!(vm_get_vmgi(self->vm))) {
+    printerr("Oh poo, no SRPT");
+    return S_ERR;
+  }
+  
+  srpt = vm_get_vmgi(self->vm)->tt_srpt;
+  for(i=0; i<srpt->nr_of_srpts; i++) {
+    title_info_t* info = &(srpt->title[i]);
+    if((info->title_set_nr == vts) && (info->vts_ttn == vts_ttn)) {
+      if(tt)
+       *tt = i+1;
+    }
+  }
+
+  return S_OK;
+}
+
+static char __title_str[] = "DVDNAV";
+
+dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, char **title_str) {
+  if(!self)
+   return S_ERR;
+
+  if(!title_str) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+
+  (*title_str) = __title_str;
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_get_position(dvdnav_t *self, unsigned int* pos,
+				    unsigned int *len) {
+  uint32_t cur_sector;
+  uint32_t first_cell_nr;
+  uint32_t last_cell_nr;
+  cell_playback_t *first_cell;
+  cell_playback_t *last_cell;
+  dvd_state_t *state;
+  if((!self) || (!self->vm) )
+   return 0;
+  
+  state = &(self->vm->state);
+  if((!state) || (!state->pgc) )
+   return 0;
+   
+  /* Sanity check */
+  if(state->pgN > state->pgc->nr_of_programs) {
+    return 0;
+  }
+  
+  /* Get current sector */
+  cur_sector = self->vobu_start + self->blockN;
+
+  /* Find start cell of program. */
+  first_cell_nr = state->pgc->program_map[state->pgN-1];
+  first_cell = &(state->pgc->cell_playback[first_cell_nr-1]);
+  if(state->pgN < state->pgc->nr_of_programs) {
+    last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+  } else {
+    last_cell_nr = state->pgc->nr_of_cells;
+  }
+  last_cell = &(state->pgc->cell_playback[last_cell_nr-1]);
+
+  *pos= cur_sector - first_cell->first_sector;
+  *len= last_cell->last_sector - first_cell->first_sector;
+  /* printf("dvdnav:searching:current pos=%u length=%u\n",*pos,*len); */
+
+
+  return S_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/settings.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,89 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * 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 <dvdnav.h>
+#include "dvdnav_internal.h"
+
+#include "vm.h"
+
+/* Characteristics/setting API calls */
+
+dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *self, int *region) {
+  if(!self)
+   return S_ERR;
+
+  if(!region) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+
+  if(!self->vm) {
+    printerr("VM not yet initialised");
+    return S_ERR;
+  }
+
+  (*region) = self->vm->state.registers.SPRM[20];
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int mask) {
+  if(!self)
+   return S_ERR;
+
+  if(!self->vm) {
+    printerr("VM not yet initialised");
+    return S_ERR;
+  }
+
+  self->vm->state.registers.SPRM[20] = (mask & 0xff);
+  
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int use_readahead) {
+  if(!self)
+   return S_ERR;
+
+  self->use_read_ahead = use_readahead;
+
+  return S_OK;
+}
+
+dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *self, int* flag) {
+  if(!self)
+   return S_ERR;
+
+  if(!flag) {
+    printerr("Passed a NULL pointer");
+    return S_ERR;
+  }
+
+  (*flag) = self->use_read_ahead;
+  return S_OK;
+}
+
--- /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.
+ *
+ *
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,130 @@
+/*
+ * 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$
+ *
+ */
+
+#ifndef VM_H_INCLUDED
+#define VM_H_INCLUDED
+
+#include "decoder.h"
+#include <dvd_types.h>
+
+/* DOMAIN enum */
+
+typedef enum {
+  FP_DOMAIN = 1,
+  VTS_DOMAIN = 2,
+  VMGM_DOMAIN = 4,
+  VTSM_DOMAIN = 8
+} domain_t;  
+
+/**
+ * State: SPRM, GPRM, Domain, pgc, pgN, cellN, ?
+ */
+typedef struct {
+  registers_t registers;
+  
+  pgc_t *pgc; /*  either this or *pgc is enough? */
+  
+  domain_t domain;
+  int vtsN; /*  0 is vmgm? */
+  /*   int pgcN; // either this or *pgc is enough. Which to use? */
+  int pgN;  /*  is this needed? can allways fid pgN from cellN? */
+  int cellN;
+  int blockN;
+  
+  /* Resume info */
+  int rsm_vtsN;
+  int rsm_blockN; /* of nav_packet */
+  uint16_t rsm_regs[5]; /* system registers 4-8 */
+  int rsm_pgcN;
+  int rsm_cellN;
+} dvd_state_t;
+
+typedef struct {
+  dvd_reader_t *dvd;
+  ifo_handle_t *vmgi;
+  ifo_handle_t *vtsi;
+  dvd_state_t   state;
+  int  badness_counter;
+} vm_t;
+
+
+/*  Audio stream number */
+#define AST_REG      registers.SPRM[1]
+/*  Subpicture stream number */
+#define SPST_REG     registers.SPRM[2]
+/*  Angle number */
+#define AGL_REG      registers.SPRM[3]
+/*  Title Track Number */
+#define TTN_REG      registers.SPRM[4]
+/*  VTS Title Track Number */
+#define VTS_TTN_REG  registers.SPRM[5]
+/*  PGC Number for this Title Track */
+#define TT_PGCN_REG  registers.SPRM[6]
+/*  Current Part of Title (PTT) number for (One_Sequential_PGC_Title) */
+#define PTTN_REG     registers.SPRM[7]
+/*  Highlighted Button Number (btn nr 1 == value 1024) */
+#define HL_BTNN_REG  registers.SPRM[8]
+/*  Parental Level */
+#define PTL_REG      registers.SPRM[13]
+
+/* Initialisation & destruction */
+vm_t* vm_new_vm();
+void vm_free_vm(vm_t *self);
+
+/* IFO access */
+ifo_handle_t *vm_get_vmgi(vm_t *self);
+ifo_handle_t *vm_get_vtsi(vm_t *self);
+
+/* Reader Access */
+dvd_reader_t *vm_get_dvd_reader(vm_t *self);
+
+/* Jumping */
+int vm_start_title(vm_t *self, int tt);
+int vm_jump_prog(vm_t *self, int pr);
+
+/* Other calls */
+int vm_reset(vm_t *self, char *dvdroot); /*  , register_t regs); */
+int vm_start(vm_t *self);
+int vm_eval_cmd(vm_t *self, vm_cmd_t *cmd);
+int vm_get_next_cell(vm_t *self);
+int vm_menu_call(vm_t *self, DVDMenuID_t menuid, int block);
+int vm_resume(vm_t *self);
+int vm_go_up(vm_t *self);
+int vm_top_pg(vm_t *self);
+int vm_next_pg(vm_t *self);
+int vm_prev_pg(vm_t *self);
+int vm_get_audio_stream(vm_t *self, int audioN);
+int vm_get_audio_active_stream(vm_t *self);
+int vm_get_subp_stream(vm_t *self, int subpN);
+int vm_get_subp_active_stream(vm_t *self);
+void vm_get_angle_info(vm_t *self, int *num_avail, int *current);
+void vm_get_audio_info(vm_t *self, int *num_avail, int *current);
+void vm_get_subp_info(vm_t *self, int *num_avail, int *current);
+subp_attr_t vm_get_subp_attr(vm_t *self, int streamN);
+audio_attr_t vm_get_audio_attr(vm_t *self, int streamN);
+void vm_get_video_res(vm_t *self, int *width, int *height);
+
+#endif /* VM_HV_INCLUDED */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vmcmd.c	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * 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 <ctype.h>
+#include <inttypes.h>
+
+#include "vmcmd.h"
+
+
+/*  freebsd compatibility */
+#ifndef PRIu8
+#define PRIu8 "d"
+#endif
+
+/*  freebsd compatibility */
+#ifndef PRIu16
+#define PRIu16 "d"
+#endif
+
+
+typedef struct
+{
+  uint8_t bits[8];
+  uint8_t examined[8];
+} cmd_t;
+
+/*  Fix this.. pass as parameters instead. */
+static cmd_t cmd;
+
+static const char *cmp_op_table[] = {
+  NULL, "&", "==", "!=", ">=", ">", "<=", "<"
+};
+static const char *set_op_table[] = {
+  NULL, "=", "<->", "+=", "-=", "*=", "/=", "%=", "rnd", "&=", "|=", "^="
+};
+
+static const char *link_table[] = {
+  "LinkNoLink",  "LinkTopC",    "LinkNextC",   "LinkPrevC",
+  NULL,          "LinkTopPG",   "LinkNextPG",  "LinkPrevPG",
+  NULL,          "LinkTopPGC",  "LinkNextPGC", "LinkPrevPGC",
+  "LinkGoUpPGC", "LinkTailPGC", NULL,          NULL,
+  "RSM"
+};
+
+static const char *system_reg_table[] = {
+  "Menu Description Language Code",
+  "Audio Stream Number",
+  "Sub-picture Stream Number",
+  "Angle Number",
+  "Title Track Number",
+  "VTS Title Track Number",
+  "VTS PGC Number",
+  "PTT Number for One_Sequential_PGC_Title",
+  "Highlighted Button Number",
+  "Navigation Timer",
+  "Title PGC Number for Navigation Timer",
+  "Audio Mixing Mode for Karaoke",
+  "Country Code for Parental Management",
+  "Parental Level",
+  "Player Configurations for Video",
+  "Player Configurations for Audio",
+  "Initial Language Code for Audio",
+  "Initial Language Code Extension for Audio",
+  "Initial Language Code for Sub-picture",
+  "Initial Language Code Extension for Sub-picture",
+  "Player Regional Code",
+  "Reserved 21",
+  "Reserved 22",
+  "Reserved 23"
+};
+
+static const char *system_reg_abbr_table[] = {
+  NULL,
+  "ASTN",
+  "SPSTN",
+  "AGLN",
+  "TTN",
+  "VTS_TTN",
+  "TT_PGCN",
+  "PTTN",
+  "HL_BTNN",
+  "NVTMR",
+  "NV_PGCN",
+  NULL,
+  "CC_PLT",
+  "PLT",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+};
+    
+
+
+static uint32_t bits(int byte, int bit, int count) {
+  uint32_t val = 0;
+  int bit_mask;
+  
+  while(count--) {
+    if(bit > 7) {
+      bit = 0;
+      byte++;
+    }
+    bit_mask = 0x01 << (7-bit);
+    val <<= 1;
+    if((cmd.bits[byte]) & bit_mask)
+      val |= 1;
+    cmd.examined[byte] |= bit_mask;
+    bit++;
+  }
+  return val;
+}
+
+
+static void print_system_reg(uint16_t reg) {
+  if(reg < sizeof(system_reg_abbr_table) / sizeof(char *))
+    fprintf(stderr, system_reg_table[reg]);
+  else
+    fprintf(stderr, " WARNING: Unknown system register ");
+}
+
+static void print_reg(uint8_t reg) {
+  if(reg & 0x80)
+    print_system_reg(reg & 0x7f);
+  else
+    if(reg < 16)
+      fprintf(stderr, "g[%" PRIu8 "]", reg);
+    else
+      fprintf(stderr, " WARNING: Unknown general register ");
+}
+
+static void print_cmp_op(uint8_t op) {
+  if(op < sizeof(cmp_op_table) / sizeof(char *) && cmp_op_table[op] != NULL)
+    fprintf(stderr, " %s ", cmp_op_table[op]);
+  else
+    fprintf(stderr, " WARNING: Unknown compare op ");
+}
+
+static void print_set_op(uint8_t op) {
+  if(op < sizeof(set_op_table) / sizeof(char *) && set_op_table[op] != NULL)
+    fprintf(stderr, " %s ", set_op_table[op]);
+  else
+    fprintf(stderr, " WARNING: Unknown set op ");
+}
+
+static void print_reg_or_data(int immediate, int byte) {
+  if(immediate) {
+    int i = bits(byte,0,16);
+    
+    fprintf(stderr, "0x%x", i);
+    if(isprint(i & 0xff) && isprint((i>>8) & 0xff))
+      fprintf(stderr, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff));
+  } else {
+    print_reg(bits(byte + 1,0,8));
+  }
+}
+
+static void print_reg_or_data_2(int immediate, int byte) {
+  if(immediate)
+    fprintf(stderr, "0x%x", bits(byte,1,7));
+  else
+    fprintf(stderr, "g[%" PRIu8 "]", bits(byte,4,4));
+}
+
+static void print_if_version_1(void) {
+  uint8_t op = bits(1,1,3);
+  
+  if(op) {
+    fprintf(stderr, "if (");
+    print_reg(bits(3,0,8));
+    print_cmp_op(op);
+    print_reg_or_data(bits(1,0,1), 4);
+    fprintf(stderr, ") ");
+  }
+}
+
+static void print_if_version_2(void) {
+  uint8_t op = bits(1,1,3);
+  
+  if(op) {
+    fprintf(stderr, "if (");
+    print_reg(bits(6,0,8));
+    print_cmp_op(op);
+    print_reg(bits(7,0,8));
+    fprintf(stderr, ") ");
+  }
+}
+
+static void print_if_version_3(void) {
+  uint8_t op = bits(1,1,3);
+  
+  if(op) {
+    fprintf(stderr, "if (");
+    print_reg(bits(2,4,4));
+    print_cmp_op(op);
+    print_reg_or_data(bits(1,0,1), 6);
+    fprintf(stderr, ") ");
+  }
+}
+
+static void print_if_version_4(void) {
+  uint8_t op = bits(1,1,3);
+  
+  if(op) {
+    fprintf(stderr, "if (");
+    print_reg(bits(1,4,4));
+    print_cmp_op(op);
+    print_reg_or_data(bits(1,0,1), 4);
+    fprintf(stderr, ") ");
+  }
+}
+
+static void print_special_instruction(void) {
+  uint8_t op = bits(1,4,4);
+  
+  switch(op) {
+    case 0: /*  NOP */
+      fprintf(stderr, "Nop");
+      break;
+    case 1: /*  Goto line */
+      fprintf(stderr, "Goto %" PRIu8, bits(7,0,8));
+      break;
+    case 2: /*  Break */
+      fprintf(stderr, "Break");
+      break;
+    case 3: /*  Parental level */
+      fprintf(stderr, "SetTmpPML %" PRIu8 ", Goto %" PRIu8, 
+	      bits(6,4,4), bits(7,0,8));
+      break;
+    default:
+      fprintf(stderr, "WARNING: Unknown special instruction (%i)", 
+	      bits(1,4,4));
+  }
+}
+
+static void print_linksub_instruction(void) {
+  int linkop = bits(7,3,5);
+  int button = bits(6,0,6);
+  
+  if(linkop < sizeof(link_table)/sizeof(char *) && link_table[linkop] != NULL)
+    fprintf(stderr, "%s (button %" PRIu8 ")", link_table[linkop], button);
+  else
+    fprintf(stderr, "WARNING: Unknown linksub instruction (%i)", linkop);
+}
+
+static void print_link_instruction(int optional) {
+  uint8_t op = bits(1,4,4);
+  
+  if(optional && op)
+    fprintf(stderr, ", ");
+  
+  switch(op) {
+    case 0:
+      if(!optional)
+      fprintf(stderr, "WARNING: NOP (link)!");
+      break;
+    case 1:
+      print_linksub_instruction();
+      break;
+    case 4:
+      fprintf(stderr, "LinkPGCN %" PRIu16, bits(6,1,15));
+      break;
+    case 5:
+      fprintf(stderr, "LinkPTT %" PRIu16 " (button %" PRIu8 ")", 
+	      bits(6,6,10), bits(6,0,6));
+      break;
+    case 6:
+      fprintf(stderr, "LinkPGN %" PRIu8 " (button %" PRIu8 ")", 
+	      bits(7,1,7), bits(6,0,6));
+      break;
+    case 7:
+      fprintf(stderr, "LinkCN %" PRIu8 " (button %" PRIu8 ")", 
+	      bits(7,0,8), bits(6,0,6));
+      break;
+    default:
+      fprintf(stderr, "WARNING: Unknown link instruction");
+  }
+}
+
+static void print_jump_instruction(void) {
+  switch(bits(1,4,4)) {
+    case 1:
+      fprintf(stderr, "Exit");
+      break;
+    case 2:
+      fprintf(stderr, "JumpTT %" PRIu8, bits(5,1,7));
+      break;
+    case 3:
+      fprintf(stderr, "JumpVTS_TT %" PRIu8, bits(5,1,7));
+      break;
+    case 5:
+      fprintf(stderr, "JumpVTS_PTT %" PRIu8 ":%" PRIu16, 
+	      bits(5,1,7), bits(2,6,10));
+      break;
+    case 6:
+      switch(bits(5,0,2)) {
+        case 0:
+          fprintf(stderr, "JumpSS FP");
+          break;
+        case 1:
+          fprintf(stderr, "JumpSS VMGM (menu %" PRIu8 ")", bits(5,4,4));
+          break;
+        case 2:
+          fprintf(stderr, "JumpSS VTSM (vts %" PRIu8 ", title %" PRIu8 
+		  ", menu %" PRIu8 ")", bits(4,0,8), bits(3,0,8), bits(5,4,4));
+          break;
+        case 3:
+          fprintf(stderr, "JumpSS VMGM (pgc %" PRIu8 ")", bits(2,1,15));
+          break;
+        }
+      break;
+    case 8:
+      switch(bits(5,0,2)) {
+        case 0:
+          fprintf(stderr, "CallSS FP (rsm_cell %" PRIu8 ")",
+              bits(4,0,8));
+          break;
+        case 1:
+          fprintf(stderr, "CallSS VMGM (menu %" PRIu8 
+		  ", rsm_cell %" PRIu8 ")", bits(5,4,4), bits(4,0,8));
+          break;
+        case 2:
+          fprintf(stderr, "CallSS VTSM (menu %" PRIu8 
+		  ", rsm_cell %" PRIu8 ")", bits(5,4,4), bits(4,0,8));
+          break;
+        case 3:
+          fprintf(stderr, "CallSS VMGM (pgc %" PRIu8 ", rsm_cell %" PRIu8 ")", 
+		  bits(2,1,15), bits(4,0,8));
+          break;
+      }
+      break;
+    default:
+      fprintf(stderr, "WARNING: Unknown Jump/Call instruction");
+  }
+}
+
+static void print_system_set(void) {
+  int i;
+  
+  switch(bits(0,4,4)) {
+    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
+      for(i = 1; i <= 3; i++) {
+        if(bits(2+i,0,1)) {
+          print_system_reg(i);
+          fprintf(stderr, " = ");
+          print_reg_or_data_2(bits(0,3,1), 2 + i);
+          fprintf(stderr, " ");
+        }
+      }
+      break;
+    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
+      print_system_reg(9);
+      fprintf(stderr, " = ");
+      print_reg_or_data(bits(0,3,1), 2);
+      fprintf(stderr, " ");
+      print_system_reg(10);
+      fprintf(stderr, " = %" PRIu8, bits(5,0,8)); /*  ?? */
+      break;
+    case 3: /*  Mode: Counter / Register + Set */
+      fprintf(stderr, "SetMode ");
+      if(bits(5,0,1))
+	fprintf(stderr, "Counter ");
+      else
+	fprintf(stderr, "Register ");
+      print_reg(bits(5,4,4));
+      print_set_op(0x1); /*  '=' */
+      print_reg_or_data(bits(0,3,1), 2);
+      break;
+    case 6: /*  Set system reg 8 (Highlighted button) */
+      print_system_reg(8);
+      if(bits(0,3,1)) /*  immediate */
+        fprintf(stderr, " = 0x%x (button no %d)", bits(4,0,16), bits(4,0,6));
+      else
+        fprintf(stderr, " = g[%" PRIu8 "]", bits(5,4,4));
+      break;
+    default:
+      fprintf(stderr, "WARNING: Unknown system set instruction (%i)", 
+	      bits(0,4,4));
+  }
+}
+
+static void print_set_version_1(void) {
+  uint8_t set_op = bits(0,4,4);
+  
+  if(set_op) {
+    print_reg(bits(3,0,8));
+    print_set_op(set_op);
+    print_reg_or_data(bits(0,3,1), 4);
+  } else {
+    fprintf(stderr, "NOP");
+  }
+}
+
+static void print_set_version_2(void) {
+  uint8_t set_op = bits(0,4,4);
+  
+  if(set_op) {
+    print_reg(bits(1,4,4));
+    print_set_op(set_op);
+    print_reg_or_data(bits(0,3,1), 2);
+  } else {
+    fprintf(stderr, "NOP");
+  }
+}
+
+void vmPrint_mnemonic(vm_cmd_t *command)  {
+  int i, extra_bits;
+  
+  for(i = 0; i < 8; i++) {
+    cmd.bits[i] = command->bytes[i];
+    cmd.examined[i] = 0;
+  }
+
+  switch(bits(0,0,3)) { /* three first bits */
+    case 0: /*  Special instructions */
+      print_if_version_1();
+      print_special_instruction();
+      break;
+    case 1: /*  Jump/Call or Link instructions */
+      if(bits(0,3,1)) {
+        print_if_version_2();
+        print_jump_instruction();
+      } else {
+        print_if_version_1();
+        print_link_instruction(0); /*  must be pressent */
+      }
+      break;
+    case 2: /*  Set System Parameters instructions */
+      print_if_version_2();
+      print_system_set();
+      print_link_instruction(1); /*  either 'if' or 'link' */
+      break;
+    case 3: /*  Set General Parameters instructions */
+      print_if_version_3();
+      print_set_version_1();
+      print_link_instruction(1); /*  either 'if' or 'link' */
+      break;
+    case 4: /*  Set, Compare -> LinkSub instructions */
+      print_set_version_2();
+      fprintf(stderr, ", ");
+      print_if_version_4();
+      print_linksub_instruction();
+      break;
+    case 5: /*  Compare -> (Set and LinkSub) instructions */
+      print_if_version_4();
+      fprintf(stderr, "{ ");
+      print_set_version_2();
+      fprintf(stderr, ", ");
+      print_linksub_instruction();
+      fprintf(stderr, " }");
+      break;
+    case 6: /*  Compare -> Set, always LinkSub instructions */
+      print_if_version_4();
+      fprintf(stderr, "{ ");
+      print_set_version_2();
+      fprintf(stderr, " } ");
+      print_linksub_instruction();
+      break;
+    default:
+      fprintf(stderr, "WARNING: Unknown instruction type (%i)", bits(0,0,3));
+  }
+  /*  Check if there still are bits set that were not examined */
+
+  extra_bits = 0;
+  for(i = 0; i < 8; i++)
+    if(cmd.bits[i] & ~ cmd.examined[i]) {
+      extra_bits = 1;
+      break;
+    }
+  if(extra_bits) {
+    fprintf(stderr, " [WARNING, unknown bits:");
+    for(i = 0; i < 8; i++)
+      fprintf(stderr, " %02x", cmd.bits[i] & ~ cmd.examined[i]);
+    fprintf(stderr, "]");
+  }
+}
+
+void vmPrint_CMD(int row, vm_cmd_t *command) {
+  int i;
+
+  fprintf(stderr, "(%03d) ", row + 1);
+  for(i = 0; i < 8; i++)
+    fprintf(stderr, "%02x ", command->bytes[i]);
+  fprintf(stderr, "| ");
+
+  vmPrint_mnemonic(command);
+  fprintf(stderr, "\n");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vmcmd.h	Tue Mar 12 19:45:53 2002 +0000
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * 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$
+ *
+ */
+
+#ifndef VMCMD_H_INCLUDED
+#define VMCMD_H_INCLUDED
+
+#include "config.h"
+#include <inttypes.h>
+#include <dvdread/ifo_types.h> /*  Only for vm_cmd_t  */
+
+void vmPrint_mnemonic(vm_cmd_t *command);
+void vmPrint_CMD(int row, vm_cmd_t *command);
+
+#endif /* VMCMD_H_INCLUDED */