changeset 168:330ef38ad968 src

Add files from libdvdread. This is to provide better integration with libdvdnav. Better error messages reported to the application etc. We will modify libdvdread files to be portable across all compilers. Currently, libdvdread only behaves with gcc. libdvdread does not like Microsoft Compilers. This portability modification will change libdvdread so much, that I thought it best to finally combine libdvdnav and libdvdread into one lib. Currently the added files are not used. Future commits will change that.
author jcdutton
date Sun, 27 Apr 2003 00:00:49 +0000
parents e9987c07e255
children 90b1f6f0520e
files bswap.h dvd_input.c dvd_input.h dvd_reader.c dvd_reader.h dvd_udf.c dvd_udf.h dvdread_internal.h ifo_print.c ifo_print.h ifo_read.c ifo_read.h ifo_types.h md5.c md5.h nav_print.c nav_print.h nav_read.c nav_read.h nav_types.h
diffstat 20 files changed, 8754 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bswap.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,98 @@
+#ifndef BSWAP_H_INCLUDED
+#define BSWAP_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001 Billy Biggs <vektor@dumbterm.net>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <config.h>
+
+#if defined(WORDS_BIGENDIAN)
+/* All bigendian systems are fine, just ignore the swaps. */  
+#define B2N_16(x) (void)(x)
+#define B2N_32(x) (void)(x)
+#define B2N_64(x) (void)(x)
+
+#else 
+
+/* For __FreeBSD_version */
+#if defined(HAVE_SYS_PARAM_H)
+#include <sys/param.h>
+#endif
+
+#if defined(__linux__)
+#include <byteswap.h>
+#define B2N_16(x) x = bswap_16(x)
+#define B2N_32(x) x = bswap_32(x)
+#define B2N_64(x) x = bswap_64(x)
+
+#elif defined(__NetBSD__)
+#include <sys/endian.h>
+#define B2N_16(x) BE16TOH(x)
+#define B2N_32(x) BE32TOH(x)
+#define B2N_64(x) BE64TOH(x)
+
+#elif defined(__OpenBSD__)
+#include <sys/endian.h>
+#define B2N_16(x) x = swap16(x)
+#define B2N_32(x) x = swap32(x)
+#define B2N_64(x) x = swap64(x)
+
+#elif defined(__FreeBSD__) && __FreeBSD_version >= 470000
+#include <sys/endian.h>
+#define B2N_16(x) x = be16toh(x)
+#define B2N_32(x) x = be32toh(x)
+#define B2N_64(x) x = be64toh(x)
+
+/* This is a slow but portable implementation, it has multiple evaluation 
+ * problems so beware.
+ * Old FreeBSD's and Solaris don't have <byteswap.h> or any other such 
+ * functionality! 
+ */
+
+#elif defined(__FreeBSD__) || defined(__sun) || defined(__bsdi__)
+#define B2N_16(x) \
+ x = ((((x) & 0xff00) >> 8) | \
+      (((x) & 0x00ff) << 8))
+#define B2N_32(x) \
+ x = ((((x) & 0xff000000) >> 24) | \
+      (((x) & 0x00ff0000) >>  8) | \
+      (((x) & 0x0000ff00) <<  8) | \
+      (((x) & 0x000000ff) << 24))
+#define B2N_64(x) \
+ x = ((((x) & 0xff00000000000000) >> 56) | \
+      (((x) & 0x00ff000000000000) >> 40) | \
+      (((x) & 0x0000ff0000000000) >> 24) | \
+      (((x) & 0x000000ff00000000) >>  8) | \
+      (((x) & 0x00000000ff000000) <<  8) | \
+      (((x) & 0x0000000000ff0000) << 24) | \
+      (((x) & 0x000000000000ff00) << 40) | \
+      (((x) & 0x00000000000000ff) << 56))
+
+#else
+
+/* If there isn't a header provided with your system with this functionality
+ * add the relevant || define( ) to the portable implementation above.
+ */
+#error "You need to add endian swap macros for you're system"
+
+#endif
+
+#endif /* WORDS_BIGENDIAN */
+
+#endif /* BSWAP_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_input.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2002 Samuel Hocevar <sam@zoy.org>,
+ *                    Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ * 
+ * This program 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, USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "dvd_reader.h"
+#include "dvd_input.h"
+
+/* The function pointers that is the exported interface of this file. */
+dvd_input_t (*dvdinput_open)  (const char *);
+int         (*dvdinput_close) (dvd_input_t);
+int         (*dvdinput_seek)  (dvd_input_t, int);
+int         (*dvdinput_title) (dvd_input_t, int); 
+int         (*dvdinput_read)  (dvd_input_t, void *, int, int);
+char *      (*dvdinput_error) (dvd_input_t);
+
+#ifdef HAVE_DVDCSS_DVDCSS_H
+/* linking to libdvdcss */
+#include <dvdcss/dvdcss.h>
+#define DVDcss_open(a) dvdcss_open((char*)(a))
+#define DVDcss_close   dvdcss_close
+#define DVDcss_seek    dvdcss_seek
+#define DVDcss_title   dvdcss_title
+#define DVDcss_read    dvdcss_read
+#define DVDcss_error   dvdcss_error
+#else
+/* dlopening libdvdcss */
+#include <dlfcn.h>
+typedef struct dvdcss_s *dvdcss_handle;
+static dvdcss_handle (*DVDcss_open)  (const char *);
+static int           (*DVDcss_close) (dvdcss_handle);
+static int           (*DVDcss_seek)  (dvdcss_handle, int, int);
+static int           (*DVDcss_title) (dvdcss_handle, int); 
+static int           (*DVDcss_read)  (dvdcss_handle, void *, int, int);
+static char *        (*DVDcss_error) (dvdcss_handle);
+#endif
+
+/* The DVDinput handle, add stuff here for new input methods. */
+struct dvd_input_s {
+  /* libdvdcss handle */
+  dvdcss_handle dvdcss;
+  
+  /* dummy file input */
+  int fd;
+};
+
+
+/**
+ * initialize and open a DVD device or file.
+ */
+static dvd_input_t css_open(const char *target)
+{
+  dvd_input_t dev;
+    
+  /* Allocate the handle structure */
+  dev = (dvd_input_t) malloc(sizeof(dvd_input_t));
+  if(dev == NULL) {
+    fprintf(stderr, "libdvdread: Could not allocate memory.\n");
+    return NULL;
+  }
+  
+  /* Really open it with libdvdcss */
+  dev->dvdcss = DVDcss_open(target);
+  if(dev->dvdcss == 0) {
+    fprintf(stderr, "libdvdread: Could not open %s with libdvdcss.\n", target);
+    free(dev);
+    return NULL;
+  }
+  
+  return dev;
+}
+
+/**
+ * return the last error message
+ */
+static char *css_error(dvd_input_t dev)
+{
+  return DVDcss_error(dev->dvdcss);
+}
+
+/**
+ * seek into the device.
+ */
+static int css_seek(dvd_input_t dev, int blocks)
+{
+  /* DVDINPUT_NOFLAGS should match the DVDCSS_NOFLAGS value. */
+  return DVDcss_seek(dev->dvdcss, blocks, DVDINPUT_NOFLAGS);
+}
+
+/**
+ * set the block for the begining of a new title (key).
+ */
+static int css_title(dvd_input_t dev, int block)
+{
+  return DVDcss_title(dev->dvdcss, block);
+}
+
+/**
+ * read data from the device.
+ */
+static int css_read(dvd_input_t dev, void *buffer, int blocks, int flags)
+{
+  return DVDcss_read(dev->dvdcss, buffer, blocks, flags);
+}
+
+/**
+ * close the DVD device and clean up the library.
+ */
+static int css_close(dvd_input_t dev)
+{
+  int ret;
+
+  ret = DVDcss_close(dev->dvdcss);
+
+  if(ret < 0)
+    return ret;
+
+  free(dev);
+
+  return 0;
+}
+
+
+
+
+
+
+/**
+ * initialize and open a DVD device or file.
+ */
+static dvd_input_t file_open(const char *target)
+{
+  dvd_input_t dev;
+  
+  /* Allocate the library structure */
+  dev = (dvd_input_t) malloc(sizeof(dvd_input_t));
+  if(dev == NULL) {
+    fprintf(stderr, "libdvdread: Could not allocate memory.\n");
+    return NULL;
+  }
+  
+  /* Open the device */
+  dev->fd = open(target, O_RDONLY);
+  if(dev->fd < 0) {
+    perror("libdvdread: Could not open input");
+    free(dev);
+    return NULL;
+  }
+  
+  return dev;
+}
+
+/**
+ * return the last error message
+ */
+static char *file_error(dvd_input_t dev)
+{
+  /* use strerror(errno)? */
+  return (char *)"unknown error";
+}
+
+/**
+ * seek into the device.
+ */
+static int file_seek(dvd_input_t dev, int blocks)
+{
+  off_t pos;
+
+  pos = lseek(dev->fd, (off_t)blocks * (off_t)DVD_VIDEO_LB_LEN, SEEK_SET);
+  if(pos < 0) {
+      return pos;
+  }
+  /* assert pos % DVD_VIDEO_LB_LEN == 0 */
+  return (int) (pos / DVD_VIDEO_LB_LEN);
+}
+
+/**
+ * set the block for the begining of a new title (key).
+ */
+static int file_title(dvd_input_t dev, int block)
+{
+  return -1;
+}
+
+/**
+ * read data from the device.
+ */
+static int file_read(dvd_input_t dev, void *buffer, int blocks, int flags)
+{
+  size_t len;
+  ssize_t ret;
+  
+  len = (size_t)blocks * DVD_VIDEO_LB_LEN;
+  
+  while(len > 0) {
+    
+    ret = read(dev->fd, buffer, len);
+    
+    if(ret < 0) {
+      /* One of the reads failed, too bad.  We won't even bother
+       * returning the reads that went ok, and as in the posix spec
+       * the file postition is left unspecified after a failure. */
+      return ret;
+    }
+    
+    if(ret == 0) {
+      /* Nothing more to read.  Return the whole blocks, if any, that we got.
+	 and adjust the file possition back to the previous block boundary. */
+      size_t bytes = (size_t)blocks * DVD_VIDEO_LB_LEN - len;
+      off_t over_read = -(bytes % DVD_VIDEO_LB_LEN);
+      /*off_t pos =*/ lseek(dev->fd, over_read, SEEK_CUR);
+      /* should have pos % 2048 == 0 */
+      return (int) (bytes / DVD_VIDEO_LB_LEN);
+    }
+    
+    len -= ret;
+  }
+
+  return blocks;
+}
+
+/**
+ * close the DVD device and clean up.
+ */
+static int file_close(dvd_input_t dev)
+{
+  int ret;
+
+  ret = close(dev->fd);
+
+  if(ret < 0)
+    return ret;
+
+  free(dev);
+
+  return 0;
+}
+
+
+/**
+ * Setup read functions with either libdvdcss or minimal DVD access.
+ */
+int dvdinput_setup(void)
+{
+  void *dvdcss_library = NULL;
+  char **dvdcss_version = NULL;
+
+#ifdef HAVE_DVDCSS_DVDCSS_H
+  /* linking to libdvdcss */
+  dvdcss_library = &dvdcss_library;  /* Give it some value != NULL */
+  /* the DVDcss_* functions have been #defined at the top */
+  dvdcss_version = &dvdcss_interface_2;
+
+#else
+  /* dlopening libdvdcss */
+  dvdcss_library = dlopen("libdvdcss.so.2", RTLD_LAZY);
+  
+  if(dvdcss_library != NULL) {
+#if defined(__OpenBSD__) && !defined(__ELF__)
+#define U_S "_"
+#else
+#define U_S
+#endif
+    DVDcss_open = (dvdcss_handle (*)(const char*))
+      dlsym(dvdcss_library, U_S "dvdcss_open");
+    DVDcss_close = (int (*)(dvdcss_handle))
+      dlsym(dvdcss_library, U_S "dvdcss_close");
+    DVDcss_title = (int (*)(dvdcss_handle, int))
+      dlsym(dvdcss_library, U_S "dvdcss_title");
+    DVDcss_seek = (int (*)(dvdcss_handle, int, int))
+      dlsym(dvdcss_library, U_S "dvdcss_seek");
+    DVDcss_read = (int (*)(dvdcss_handle, void*, int, int))
+      dlsym(dvdcss_library, U_S "dvdcss_read");
+    DVDcss_error = (char* (*)(dvdcss_handle))
+      dlsym(dvdcss_library, U_S "dvdcss_error");
+    
+    dvdcss_version = (char **)dlsym(dvdcss_library, U_S "dvdcss_interface_2");
+
+    if(dlsym(dvdcss_library, U_S "dvdcss_crack")) {
+      fprintf(stderr, 
+	      "libdvdread: Old (pre-0.0.2) version of libdvdcss found.\n"
+	      "libdvdread: You should get the latest version from "
+	      "http://www.videolan.org/\n" );
+      dlclose(dvdcss_library);
+      dvdcss_library = NULL;
+    } else if(!DVDcss_open  || !DVDcss_close || !DVDcss_title || !DVDcss_seek
+	      || !DVDcss_read || !DVDcss_error || !dvdcss_version) {
+      fprintf(stderr,  "libdvdread: Missing symbols in libdvdcss.so.2, "
+	      "this shouldn't happen !\n");
+      dlclose(dvdcss_library);
+    }
+  }
+#endif /* HAVE_DVDCSS_DVDCSS_H */
+  
+  if(dvdcss_library != NULL) {
+    /*
+    char *psz_method = getenv( "DVDCSS_METHOD" );
+    char *psz_verbose = getenv( "DVDCSS_VERBOSE" );
+    fprintf(stderr, "DVDCSS_METHOD %s\n", psz_method);
+    fprintf(stderr, "DVDCSS_VERBOSE %s\n", psz_verbose);
+    */
+    fprintf(stderr, "libdvdread: Using libdvdcss version %s for DVD access\n",
+	    *dvdcss_version);
+    
+    /* libdvdcss wrapper functions */
+    dvdinput_open  = css_open;
+    dvdinput_close = css_close;
+    dvdinput_seek  = css_seek;
+    dvdinput_title = css_title;
+    dvdinput_read  = css_read;
+    dvdinput_error = css_error;
+    return 1;
+    
+  } else {
+    fprintf(stderr, "libdvdread: Encrypted DVD support unavailable.\n");
+
+    /* libdvdcss replacement functions */
+    dvdinput_open  = file_open;
+    dvdinput_close = file_close;
+    dvdinput_seek  = file_seek;
+    dvdinput_title = file_title;
+    dvdinput_read  = file_read;
+    dvdinput_error = file_error;
+    return 0;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_input.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,47 @@
+#ifndef DVD_INPUT_H_INCLUDED
+#define DVD_INPUT_H_INCLUDED
+
+/*
+ * Copyright (C) 2001, 2002 Samuel Hocevar <sam@zoy.org>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ * 
+ * This program 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, USA.
+ */
+
+/**
+ * Defines and flags.  Make sure they fit the libdvdcss API!
+ */
+#define DVDINPUT_NOFLAGS         0
+
+#define DVDINPUT_READ_DECRYPT    (1 << 0)
+
+typedef struct dvd_input_s *dvd_input_t;
+
+/**
+ * Pointers which will be filled either the input methods functions.
+ */
+extern dvd_input_t (*dvdinput_open)  (const char *);
+extern int         (*dvdinput_close) (dvd_input_t);
+extern int         (*dvdinput_seek)  (dvd_input_t, int);
+extern int         (*dvdinput_title) (dvd_input_t, int); 
+extern int         (*dvdinput_read)  (dvd_input_t, void *, int, int);
+extern char *      (*dvdinput_error) (dvd_input_t);
+
+/**
+ * Setup function accessed by dvd_reader.c.  Returns 1 if there is CSS support.
+ */
+int dvdinput_setup(void);
+
+#endif /* DVD_INPUT_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_reader.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,1121 @@
+/*
+ * Copyright (C) 2001, 2002, 2003 Billy Biggs <vektor@dumbterm.net>,
+ *                                Håkan Hjort <d95hjort@dtek.chalmers.se>,
+ *                                Björn Englund <d4bjorn@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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, USA.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h> /* For the timing of dvdcss_title crack. */
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <dirent.h>
+ 
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__bsdi__)|| defined(__DARWIN__)
+#define SYS_BSD 1
+#endif
+
+#if defined(__sun)
+#include <sys/mnttab.h>
+#elif defined(SYS_BSD)
+#include <fstab.h>
+#elif defined(__linux__)
+#include <mntent.h>
+#endif
+
+#include "dvd_udf.h"
+#include "dvd_input.h"
+#include "dvd_reader.h"
+#include "md5.h"
+
+#define DEFAULT_UDF_CACHE_LEVEL 1
+
+struct dvd_reader_s {
+    /* Basic information. */
+    int isImageFile;
+  
+    /* Hack for keeping track of the css status. 
+     * 0: no css, 1: perhaps (need init of keys), 2: have done init */
+    int css_state;
+    int css_title; /* Last title that we have called dvdinpute_title for. */
+
+    /* Information required for an image file. */
+    dvd_input_t dev;
+
+    /* Information required for a directory path drive. */
+    char *path_root;
+  
+    /* Filesystem cache */
+    int udfcache_level; /* 0 - turned off, 1 - on */
+    void *udfcache;
+};
+
+struct dvd_file_s {
+    /* Basic information. */
+    dvd_reader_t *dvd;
+  
+    /* Hack for selecting the right css title. */
+    int css_title;
+
+    /* Information required for an image file. */
+    uint32_t lb_start;
+    uint32_t seek_pos;
+
+    /* Information required for a directory path drive. */
+    size_t title_sizes[ 9 ];
+    dvd_input_t title_devs[ 9 ];
+
+    /* Calculated at open-time, size in blocks. */
+    ssize_t filesize;
+};
+
+/**
+ * Set the level of caching on udf
+ * level = 0 (no caching)
+ * level = 1 (caching filesystem info)
+ */
+int DVDUDFCacheLevel(dvd_reader_t *device, int level)
+{
+  struct dvd_reader_s *dev = (struct dvd_reader_s *)device;
+  
+  if(level > 0) {
+    level = 1;
+  } else if(level < 0) {
+    return dev->udfcache_level;
+  }
+
+  dev->udfcache_level = level;
+  
+  return level;
+}
+
+void *GetUDFCacheHandle(dvd_reader_t *device)
+{
+  struct dvd_reader_s *dev = (struct dvd_reader_s *)device;
+  
+  return dev->udfcache;
+}
+
+void SetUDFCacheHandle(dvd_reader_t *device, void *cache)
+{
+  struct dvd_reader_s *dev = (struct dvd_reader_s *)device;
+
+  dev->udfcache = cache;
+}
+
+
+
+/* Loop over all titles and call dvdcss_title to crack the keys. */
+static int initAllCSSKeys( dvd_reader_t *dvd )
+{
+    struct timeval all_s, all_e;
+    struct timeval t_s, t_e;
+    char filename[ MAX_UDF_FILE_NAME_LEN ];
+    uint32_t start, len;
+    int title;
+	
+    char *nokeys_str = getenv("DVDREAD_NOKEYS");
+    if(nokeys_str != NULL)
+      return 0;
+    
+    fprintf( stderr, "\n" );
+    fprintf( stderr, "libdvdread: Attempting to retrieve all CSS keys\n" );
+    fprintf( stderr, "libdvdread: This can take a _long_ time, "
+	     "please be patient\n\n" );
+	
+    gettimeofday(&all_s, NULL);
+	
+    for( title = 0; title < 100; title++ ) {
+	gettimeofday( &t_s, NULL );
+	if( title == 0 ) {
+	    sprintf( filename, "/VIDEO_TS/VIDEO_TS.VOB" );
+	} else {
+	    sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, 0 );
+	}
+	start = UDFFindFile( dvd, filename, &len );
+	if( start != 0 && len != 0 ) {
+	    /* Perform CSS key cracking for this title. */
+	    fprintf( stderr, "libdvdread: Get key for %s at 0x%08x\n", 
+		     filename, start );
+	    if( dvdinput_title( dvd->dev, (int)start ) < 0 ) {
+		fprintf( stderr, "libdvdread: Error cracking CSS key for %s (0x%08x)\n", filename, start);
+	    }
+	    gettimeofday( &t_e, NULL );
+	    fprintf( stderr, "libdvdread: Elapsed time %ld\n",  
+		     (long int) t_e.tv_sec - t_s.tv_sec );
+	}
+	    
+	if( title == 0 ) continue;
+	    
+	gettimeofday( &t_s, NULL );
+	sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, 1 );
+	start = UDFFindFile( dvd, filename, &len );
+	if( start == 0 || len == 0 ) break;
+	    
+	/* Perform CSS key cracking for this title. */
+	fprintf( stderr, "libdvdread: Get key for %s at 0x%08x\n", 
+		 filename, start );
+	if( dvdinput_title( dvd->dev, (int)start ) < 0 ) {
+	    fprintf( stderr, "libdvdread: Error cracking CSS key for %s (0x%08x)!!\n", filename, start);
+	}
+	gettimeofday( &t_e, NULL );
+	fprintf( stderr, "libdvdread: Elapsed time %ld\n",  
+		 (long int) t_e.tv_sec - t_s.tv_sec );
+    }
+    title--;
+    
+    fprintf( stderr, "libdvdread: Found %d VTS's\n", title );
+    gettimeofday(&all_e, NULL);
+    fprintf( stderr, "libdvdread: Elapsed time %ld\n",  
+	     (long int) all_e.tv_sec - all_s.tv_sec );
+    
+    return 0;
+}
+
+
+
+/**
+ * Open a DVD image or block device file.
+ */
+static dvd_reader_t *DVDOpenImageFile( const char *location, int have_css )
+{
+    dvd_reader_t *dvd;
+    dvd_input_t dev;
+    
+    dev = dvdinput_open( location );
+    if( !dev ) {
+	fprintf( stderr, "libdvdread: Can't open %s for reading\n", location );
+	return 0;
+    }
+
+    dvd = (dvd_reader_t *) malloc( sizeof( dvd_reader_t ) );
+    if( !dvd ) return 0;
+    dvd->isImageFile = 1;
+    dvd->dev = dev;
+    dvd->path_root = 0;
+    
+    dvd->udfcache_level = DEFAULT_UDF_CACHE_LEVEL;
+    dvd->udfcache = NULL;
+
+    if( have_css ) {
+      /* Only if DVDCSS_METHOD = title, a bit if it's disc or if
+       * DVDCSS_METHOD = key but region missmatch. Unfortunaly we
+       * don't have that information. */
+    
+      dvd->css_state = 1; /* Need key init. */
+    }
+    dvd->css_title = 0;
+    
+    return dvd;
+}
+
+static dvd_reader_t *DVDOpenPath( const char *path_root )
+{
+    dvd_reader_t *dvd;
+
+    dvd = (dvd_reader_t *) malloc( sizeof( dvd_reader_t ) );
+    if( !dvd ) return 0;
+    dvd->isImageFile = 0;
+    dvd->dev = 0;
+    dvd->path_root = strdup( path_root );
+
+    dvd->udfcache_level = DEFAULT_UDF_CACHE_LEVEL;
+    dvd->udfcache = NULL;
+    
+    dvd->css_state = 0; /* Only used in the UDF path */
+    dvd->css_title = 0; /* Only matters in the UDF path */
+
+    return dvd;
+}
+
+#if defined(__sun)
+/* /dev/rdsk/c0t6d0s0 (link to /devices/...)
+   /vol/dev/rdsk/c0t6d0/??
+   /vol/rdsk/<name> */
+static char *sun_block2char( const char *path )
+{
+    char *new_path;
+
+    /* Must contain "/dsk/" */ 
+    if( !strstr( path, "/dsk/" ) ) return (char *) strdup( path );
+
+    /* Replace "/dsk/" with "/rdsk/" */
+    new_path = malloc( strlen(path) + 2 );
+    strcpy( new_path, path );
+    strcpy( strstr( new_path, "/dsk/" ), "" );
+    strcat( new_path, "/rdsk/" );
+    strcat( new_path, strstr( path, "/dsk/" ) + strlen( "/dsk/" ) );
+
+    return new_path;
+}
+#endif
+
+#if defined(SYS_BSD)
+/* FreeBSD /dev/(r)(a)cd0c (a is for atapi), recomended to _not_ use r
+   OpenBSD /dev/rcd0c, it needs to be the raw device
+   NetBSD  /dev/rcd0[d|c|..] d for x86, c (for non x86), perhaps others
+   Darwin  /dev/rdisk0,  it needs to be the raw device
+   BSD/OS  /dev/sr0c (if not mounted) or /dev/rsr0c ('c' any letter will do) */
+static char *bsd_block2char( const char *path )
+{
+    char *new_path;
+
+    /* If it doesn't start with "/dev/" or does start with "/dev/r" exit */ 
+    if( !strncmp( path, "/dev/",  5 ) || strncmp( path, "/dev/r", 6 ) ) 
+      return (char *) strdup( path );
+
+    /* Replace "/dev/" with "/dev/r" */
+    new_path = malloc( strlen(path) + 2 );
+    strcpy( new_path, "/dev/r" );
+    strcat( new_path, path + strlen( "/dev/" ) );
+
+    return new_path;
+}
+#endif
+
+dvd_reader_t *DVDOpen( const char *path )
+{
+    struct stat fileinfo;
+    int ret, have_css;
+    char *dev_name = 0;
+
+    if( path == NULL )
+      return 0;
+
+    ret = stat( path, &fileinfo );
+    if( ret < 0 ) {
+	/* If we can't stat the file, give up */
+	fprintf( stderr, "libdvdread: Can't stat %s\n", path );
+	perror("");
+	return 0;
+    }
+
+    /* Try to open libdvdcss or fall back to standard functions */
+    have_css = dvdinput_setup();
+
+    /* First check if this is a block/char device or a file*/
+    if( S_ISBLK( fileinfo.st_mode ) || 
+	S_ISCHR( fileinfo.st_mode ) || 
+	S_ISREG( fileinfo.st_mode ) ) {
+
+	/**
+	 * Block devices and regular files are assumed to be DVD-Video images.
+	 */
+#if defined(__sun)
+	return DVDOpenImageFile( sun_block2char( path ), have_css );
+#elif defined(SYS_BSD)
+	return DVDOpenImageFile( bsd_block2char( path ), have_css );
+#else
+	return DVDOpenImageFile( path, have_css );
+#endif
+
+    } else if( S_ISDIR( fileinfo.st_mode ) ) {
+	dvd_reader_t *auth_drive = 0;
+	char *path_copy;
+#if defined(SYS_BSD)
+	struct fstab* fe;
+#elif defined(__sun) || defined(__linux__)
+	FILE *mntfile;
+#endif
+
+	/* XXX: We should scream real loud here. */
+	if( !(path_copy = strdup( path ) ) ) return 0;
+
+	/* Resolve any symlinks and get the absolut dir name. */
+	{
+	    char *new_path;
+	    int cdir = open( ".", O_RDONLY );
+	    
+	    if( cdir >= 0 ) {
+		chdir( path_copy );
+		new_path = getcwd( NULL, PATH_MAX );
+		fchdir( cdir );
+		close( cdir );
+		if( new_path ) {
+		    free( path_copy );
+		    path_copy = new_path;
+		}
+	    }
+	}
+	
+	/**
+	 * If we're being asked to open a directory, check if that directory
+	 * is the mountpoint for a DVD-ROM which we can use instead.
+	 */
+
+	if( strlen( path_copy ) > 1 ) {
+	    if( path_copy[ strlen( path_copy ) - 1 ] == '/' ) 
+		path_copy[ strlen( path_copy ) - 1 ] = '\0';
+	}
+
+	if( strlen( path_copy ) > 9 ) {
+	    if( !strcasecmp( &(path_copy[ strlen( path_copy ) - 9 ]), 
+			     "/video_ts" ) ) {
+	      path_copy[ strlen( path_copy ) - 9 ] = '\0';
+	    }
+	}
+
+#if defined(SYS_BSD)
+	if( ( fe = getfsfile( path_copy ) ) ) {
+	    dev_name = bsd_block2char( fe->fs_spec );
+	    fprintf( stderr,
+		     "libdvdread: Attempting to use device %s"
+		     " mounted on %s for CSS authentication\n",
+		     dev_name,
+		     fe->fs_file );
+	    auth_drive = DVDOpenImageFile( dev_name, have_css );
+	}
+#elif defined(__sun)
+	mntfile = fopen( MNTTAB, "r" );
+	if( mntfile ) {
+	    struct mnttab mp;
+	    int res;
+
+	    while( ( res = getmntent( mntfile, &mp ) ) != -1 ) {
+		if( res == 0 && !strcmp( mp.mnt_mountp, path_copy ) ) {
+		    dev_name = sun_block2char( mp.mnt_special );
+		    fprintf( stderr, 
+			     "libdvdread: Attempting to use device %s"
+			     " mounted on %s for CSS authentication\n",
+			     dev_name,
+			     mp.mnt_mountp );
+		    auth_drive = DVDOpenImageFile( dev_name, have_css );
+		    break;
+		}
+	    }
+	    fclose( mntfile );
+	}
+#elif defined(__linux__)
+        mntfile = fopen( MOUNTED, "r" );
+        if( mntfile ) {
+            struct mntent *me;
+ 
+            while( ( me = getmntent( mntfile ) ) ) {
+                if( !strcmp( me->mnt_dir, path_copy ) ) {
+		    fprintf( stderr, 
+			     "libdvdread: Attempting to use device %s"
+			     " mounted on %s for CSS authentication\n",
+			     me->mnt_fsname,
+			     me->mnt_dir );
+                    auth_drive = DVDOpenImageFile( me->mnt_fsname, have_css );
+		    dev_name = strdup(me->mnt_fsname);
+                    break;
+                }
+            }
+            fclose( mntfile );
+	}
+#endif
+	if( !dev_name ) {
+	  fprintf( stderr, "libdvdread: Couldn't find device name.\n" );
+	} else if( !auth_drive ) {
+	    fprintf( stderr, "libdvdread: Device %s inaccessible, "
+		     "CSS authentication not available.\n", dev_name );
+	}
+
+	free( dev_name );
+	free( path_copy );
+
+        /**
+         * If we've opened a drive, just use that.
+         */
+        if( auth_drive ) return auth_drive;
+
+        /**
+         * Otherwise, we now try to open the directory tree instead.
+         */
+        return DVDOpenPath( path );
+    }
+
+    /* If it's none of the above, screw it. */
+    fprintf( stderr, "libdvdread: Could not open %s\n", path );
+    return 0;
+}
+
+void DVDClose( dvd_reader_t *dvd )
+{
+    if( dvd ) {
+        if( dvd->dev ) dvdinput_close( dvd->dev );
+        if( dvd->path_root ) free( dvd->path_root );
+	if( dvd->udfcache ) FreeUDFCache( dvd->udfcache );
+        free( dvd );
+    }
+}
+
+/**
+ * Open an unencrypted file on a DVD image file.
+ */
+static dvd_file_t *DVDOpenFileUDF( dvd_reader_t *dvd, char *filename )
+{
+    uint32_t start, len;
+    dvd_file_t *dvd_file;
+
+    start = UDFFindFile( dvd, filename, &len );
+    if( !start ) return 0;
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) return 0;
+    dvd_file->dvd = dvd;
+    dvd_file->lb_start = start;
+    dvd_file->seek_pos = 0;
+    memset( dvd_file->title_sizes, 0, sizeof( dvd_file->title_sizes ) );
+    memset( dvd_file->title_devs, 0, sizeof( dvd_file->title_devs ) );
+    dvd_file->filesize = len / DVD_VIDEO_LB_LEN;
+
+    return dvd_file;
+}
+
+/**
+ * Searches for <file> in directory <path>, ignoring case.
+ * Returns 0 and full filename in <filename>.
+ *     or -1 on file not found.
+ *     or -2 on path not found.
+ */
+static int findDirFile( const char *path, const char *file, char *filename ) 
+{
+    DIR *dir;
+    struct dirent *ent;
+
+    dir = opendir( path );
+    if( !dir ) return -2;
+
+    while( ( ent = readdir( dir ) ) != NULL ) {
+        if( !strcasecmp( ent->d_name, file ) ) {
+            sprintf( filename, "%s%s%s", path,
+                     ( ( path[ strlen( path ) - 1 ] == '/' ) ? "" : "/" ),
+                     ent->d_name );
+            return 0;
+        }
+    }
+
+    return -1;
+}
+
+static int findDVDFile( dvd_reader_t *dvd, const char *file, char *filename )
+{
+    char video_path[ PATH_MAX + 1 ];
+    const char *nodirfile;
+    int ret;
+
+    /* Strip off the directory for our search */
+    if( !strncasecmp( "/VIDEO_TS/", file, 10 ) ) {
+        nodirfile = &(file[ 10 ]);
+    } else {
+        nodirfile = file;
+    }
+
+    ret = findDirFile( dvd->path_root, nodirfile, filename );
+    if( ret < 0 ) {
+        /* Try also with adding the path, just in case. */
+        sprintf( video_path, "%s/VIDEO_TS/", dvd->path_root );
+        ret = findDirFile( video_path, nodirfile, filename );
+        if( ret < 0 ) {
+            /* Try with the path, but in lower case. */
+            sprintf( video_path, "%s/video_ts/", dvd->path_root );
+            ret = findDirFile( video_path, nodirfile, filename );
+            if( ret < 0 ) {
+                return 0;
+            }
+        }
+    }
+
+    return 1;
+}
+
+/**
+ * Open an unencrypted file from a DVD directory tree.
+ */
+static dvd_file_t *DVDOpenFilePath( dvd_reader_t *dvd, char *filename )
+{
+    char full_path[ PATH_MAX + 1 ];
+    dvd_file_t *dvd_file;
+    struct stat fileinfo;
+    dvd_input_t dev;
+
+    /* Get the full path of the file. */
+    if( !findDVDFile( dvd, filename, full_path ) ) return 0;
+
+    dev = dvdinput_open( full_path );
+    if( !dev ) return 0;
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) return 0;
+    dvd_file->dvd = dvd;
+    dvd_file->lb_start = 0;
+    dvd_file->seek_pos = 0;
+    memset( dvd_file->title_sizes, 0, sizeof( dvd_file->title_sizes ) );
+    memset( dvd_file->title_devs, 0, sizeof( dvd_file->title_devs ) );
+    dvd_file->filesize = 0;
+
+    if( stat( full_path, &fileinfo ) < 0 ) {
+        fprintf( stderr, "libdvdread: Can't stat() %s.\n", filename );
+        free( dvd_file );
+        return 0;
+    }
+    dvd_file->title_sizes[ 0 ] = fileinfo.st_size / DVD_VIDEO_LB_LEN;
+    dvd_file->title_devs[ 0 ] = dev;
+    dvd_file->filesize = dvd_file->title_sizes[ 0 ];
+
+    return dvd_file;
+}
+
+static dvd_file_t *DVDOpenVOBUDF( dvd_reader_t *dvd, int title, int menu )
+{
+    char filename[ MAX_UDF_FILE_NAME_LEN ];
+    uint32_t start, len;
+    dvd_file_t *dvd_file;
+
+    if( title == 0 ) {
+        sprintf( filename, "/VIDEO_TS/VIDEO_TS.VOB" );
+    } else {
+        sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, menu ? 0 : 1 );
+    }
+    start = UDFFindFile( dvd, filename, &len );
+    if( start == 0 ) return 0;
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) return 0;
+    dvd_file->dvd = dvd;
+    /*Hack*/ dvd_file->css_title = title << 1 | menu;
+    dvd_file->lb_start = start;
+    dvd_file->seek_pos = 0;
+    memset( dvd_file->title_sizes, 0, sizeof( dvd_file->title_sizes ) );
+    memset( dvd_file->title_devs, 0, sizeof( dvd_file->title_devs ) );
+    dvd_file->filesize = len / DVD_VIDEO_LB_LEN;
+
+    /* Calculate the complete file size for every file in the VOBS */
+    if( !menu ) {
+        int cur;
+
+        for( cur = 2; cur < 10; cur++ ) {
+            sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, cur );
+            if( !UDFFindFile( dvd, filename, &len ) ) break;
+            dvd_file->filesize += len / DVD_VIDEO_LB_LEN;
+        }
+    }
+    
+    if( dvd->css_state == 1 /* Need key init */ ) {
+        initAllCSSKeys( dvd );
+	dvd->css_state = 2;
+    }
+    /*    
+    if( dvdinput_title( dvd_file->dvd->dev, (int)start ) < 0 ) {
+        fprintf( stderr, "libdvdread: Error cracking CSS key for %s\n",
+		 filename );
+    }
+    */
+    
+    return dvd_file;
+}
+
+static dvd_file_t *DVDOpenVOBPath( dvd_reader_t *dvd, int title, int menu )
+{
+    char filename[ MAX_UDF_FILE_NAME_LEN ];
+    char full_path[ PATH_MAX + 1 ];
+    struct stat fileinfo;
+    dvd_file_t *dvd_file;
+    int i;
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) return 0;
+    dvd_file->dvd = dvd;
+    /*Hack*/ dvd_file->css_title = title << 1 | menu;
+    dvd_file->lb_start = 0;
+    dvd_file->seek_pos = 0;
+    memset( dvd_file->title_sizes, 0, sizeof( dvd_file->title_sizes ) );
+    memset( dvd_file->title_devs, 0, sizeof( dvd_file->title_devs ) );
+    dvd_file->filesize = 0;
+    
+    if( menu ) {
+        dvd_input_t dev;
+
+        if( title == 0 ) {
+            sprintf( filename, "VIDEO_TS.VOB" );
+        } else {
+            sprintf( filename, "VTS_%02i_0.VOB", title );
+        }
+        if( !findDVDFile( dvd, filename, full_path ) ) {
+            free( dvd_file );
+            return 0;
+        }
+
+        dev = dvdinput_open( full_path );
+        if( dev == NULL ) {
+            free( dvd_file );
+            return 0;
+        }
+
+        if( stat( full_path, &fileinfo ) < 0 ) {
+            fprintf( stderr, "libdvdread: Can't stat() %s.\n", filename );
+            free( dvd_file );
+            return 0;
+        }
+        dvd_file->title_sizes[ 0 ] = fileinfo.st_size / DVD_VIDEO_LB_LEN;
+        dvd_file->title_devs[ 0 ] = dev;
+	dvdinput_title( dvd_file->title_devs[0], 0);
+        dvd_file->filesize = dvd_file->title_sizes[ 0 ];
+
+    } else {
+        for( i = 0; i < 9; ++i ) {
+
+            sprintf( filename, "VTS_%02i_%i.VOB", title, i + 1 );
+            if( !findDVDFile( dvd, filename, full_path ) ) {
+                break;
+            }
+
+            if( stat( full_path, &fileinfo ) < 0 ) {
+                fprintf( stderr, "libdvdread: Can't stat() %s.\n", filename );
+                break;
+            }
+
+            dvd_file->title_sizes[ i ] = fileinfo.st_size / DVD_VIDEO_LB_LEN;
+            dvd_file->title_devs[ i ] = dvdinput_open( full_path );
+	    dvdinput_title( dvd_file->title_devs[ i ], 0 );
+            dvd_file->filesize += dvd_file->title_sizes[ i ];
+        }
+        if( !dvd_file->title_devs[ 0 ] ) {
+            free( dvd_file );
+            return 0;
+        }
+    }
+
+    return dvd_file;
+}
+
+dvd_file_t *DVDOpenFile( dvd_reader_t *dvd, int titlenum, 
+			 dvd_read_domain_t domain )
+{
+    char filename[ MAX_UDF_FILE_NAME_LEN ];
+    
+    /* Check arguments. */
+    if( dvd == NULL || titlenum < 0 )
+      return NULL;
+
+    switch( domain ) {
+    case DVD_READ_INFO_FILE:
+        if( titlenum == 0 ) {
+            sprintf( filename, "/VIDEO_TS/VIDEO_TS.IFO" );
+        } else {
+            sprintf( filename, "/VIDEO_TS/VTS_%02i_0.IFO", titlenum );
+        }
+        break;
+    case DVD_READ_INFO_BACKUP_FILE:
+        if( titlenum == 0 ) {
+            sprintf( filename, "/VIDEO_TS/VIDEO_TS.BUP" );
+        } else {
+            sprintf( filename, "/VIDEO_TS/VTS_%02i_0.BUP", titlenum );
+        }
+        break;
+    case DVD_READ_MENU_VOBS:
+        if( dvd->isImageFile ) {
+            return DVDOpenVOBUDF( dvd, titlenum, 1 );
+        } else {
+            return DVDOpenVOBPath( dvd, titlenum, 1 );
+        }
+        break;
+    case DVD_READ_TITLE_VOBS:
+        if( titlenum == 0 ) return 0;
+        if( dvd->isImageFile ) {
+            return DVDOpenVOBUDF( dvd, titlenum, 0 );
+        } else {
+            return DVDOpenVOBPath( dvd, titlenum, 0 );
+        }
+        break;
+    default:
+        fprintf( stderr, "libdvdread: Invalid domain for file open.\n" );
+        return NULL;
+    }
+    
+    if( dvd->isImageFile ) {
+        return DVDOpenFileUDF( dvd, filename );
+    } else {
+        return DVDOpenFilePath( dvd, filename );
+    }
+}
+
+void DVDCloseFile( dvd_file_t *dvd_file )
+{
+    int i;
+
+    if( dvd_file ) {
+        if( dvd_file->dvd->isImageFile ) {
+	    ;
+	} else {
+            for( i = 0; i < 9; ++i ) {
+                if( dvd_file->title_devs[ i ] ) {
+                    dvdinput_close( dvd_file->title_devs[i] );
+                }
+            }
+        }
+
+        free( dvd_file );
+        dvd_file = 0;
+    }
+}
+
+/* Internal, but used from dvd_udf.c */
+int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
+			 size_t block_count, unsigned char *data, 
+			 int encrypted )
+{
+   int ret;
+   printf("UDFReadBlocksRAW: lb_number = 0x%x, block_count = 0x%x\n",
+           lb_number, block_count);
+   if( !device->dev ) {
+     	fprintf( stderr, "libdvdread: Fatal error in block read.\n" );
+	return 0;
+   }
+
+   ret = dvdinput_seek( device->dev, (int) lb_number );
+   if( ret != (int) lb_number ) {
+     	fprintf( stderr, "libdvdread: Can't seek to block %u\n", lb_number );
+	return 0;
+   }
+   printf("UDFReadBlocksRAW: seek done\n");
+
+   ret = dvdinput_read( device->dev, (char *) data, 
+			 (int) block_count, encrypted );
+   printf("UDFReadBlocksRAW: read done\n");
+   return ret;
+}
+
+/* This is using a single input and starting from 'dvd_file->lb_start' offset.
+ *
+ * Reads 'block_count' blocks from 'dvd_file' at block offset 'offset'
+ * into the buffer located at 'data' and if 'encrypted' is set
+ * descramble the data if it's encrypted.  Returning either an
+ * negative error or the number of blocks read. */
+static int DVDReadBlocksUDF( dvd_file_t *dvd_file, uint32_t offset,
+			     size_t block_count, unsigned char *data,
+			     int encrypted )
+{
+    printf("DVDReadBlocksUDF: offset = 0x%x, block_count = 0x%x, RAW = 0x%x\n",
+            offset, block_count, dvd_file->lb_start);
+    return UDFReadBlocksRaw( dvd_file->dvd, dvd_file->lb_start + offset,
+			     block_count, data, encrypted );
+}
+
+/* This is using possibly several inputs and starting from an offset of '0'.
+ *
+ * Reads 'block_count' blocks from 'dvd_file' at block offset 'offset'
+ * into the buffer located at 'data' and if 'encrypted' is set
+ * descramble the data if it's encrypted.  Returning either an
+ * negative error or the number of blocks read. */
+static int DVDReadBlocksPath( dvd_file_t *dvd_file, unsigned int offset,
+			      size_t block_count, unsigned char *data,
+			      int encrypted )
+{
+    int i;
+    int ret, ret2, off;
+
+    ret = 0;
+    ret2 = 0;
+    for( i = 0; i < 9; ++i ) {
+      if( !dvd_file->title_sizes[ i ] ) return 0; /* Past end of file */
+
+        if( offset < dvd_file->title_sizes[ i ] ) {
+            if( ( offset + block_count ) <= dvd_file->title_sizes[ i ] ) {
+		off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset );
+                if( off < 0 || off != (int)offset ) {
+		    fprintf( stderr, "libdvdread: Can't seek to block %d\n", 
+			     offset );
+		    return off < 0 ? off : 0;
+		}
+                ret = dvdinput_read( dvd_file->title_devs[ i ], data,
+				     (int)block_count, encrypted );
+                break;
+            } else {
+                size_t part1_size = dvd_file->title_sizes[ i ] - offset;
+		/* FIXME: Really needs to be a while loop.
+                 * (This is only true if you try and read >1GB at a time) */
+		
+                /* Read part 1 */
+                off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset );
+                if( off < 0 || off != (int)offset ) {
+		    fprintf( stderr, "libdvdread: Can't seek to block %d\n", 
+			     offset );
+		    return off < 0 ? off : 0;
+		}
+                ret = dvdinput_read( dvd_file->title_devs[ i ], data,
+				     (int)part1_size, encrypted );
+		if( ret < 0 ) return ret;
+		/* FIXME: This is wrong if i is the last file in the set. 
+                 * also error from this read will not show in ret. */
+		
+		/* Does the next part exist? If not then return now. */
+		if( !dvd_file->title_devs[ i + 1 ] ) return ret;
+
+                /* Read part 2 */
+                off = dvdinput_seek( dvd_file->title_devs[ i + 1 ], 0 );
+                if( off < 0 || off != 0 ) {
+		    fprintf( stderr, "libdvdread: Can't seek to block %d\n", 
+			     0 );
+		    return off < 0 ? off : 0;
+		}
+                ret2 = dvdinput_read( dvd_file->title_devs[ i + 1 ], 
+				      data + ( part1_size
+					       * (int64_t)DVD_VIDEO_LB_LEN ),
+				      (int)(block_count - part1_size),
+				      encrypted );
+                if( ret2 < 0 ) return ret2;
+		break;
+            }
+        } else {
+            offset -= dvd_file->title_sizes[ i ];
+        }
+    }
+
+    return ret + ret2;
+}
+
+/* This is broken reading more than 2Gb at a time is ssize_t is 32-bit. */
+ssize_t DVDReadBlocks( dvd_file_t *dvd_file, int offset, 
+		       size_t block_count, unsigned char *data )
+{
+    int ret;
+    printf("DVDReadBlocks: offset = 0x%x to 0x%x.\n",offset, block_count);  
+    /* Check arguments. */
+    if( dvd_file == NULL || offset < 0 || data == NULL )
+      return -1;
+    
+    /* Hack, and it will still fail for multiple opens in a threaded app ! */
+    if( dvd_file->dvd->css_title != dvd_file->css_title ) {
+      dvd_file->dvd->css_title = dvd_file->css_title;
+      if( dvd_file->dvd->isImageFile ) {
+        printf("DVDReadBlocks: dvdinput_title: isImageFile\n");
+	dvdinput_title( dvd_file->dvd->dev, (int)dvd_file->lb_start );
+      } 
+      /* Here each vobu has it's own dvdcss handle, so no need to update 
+      else {
+        printf("DVDReadBlocks: dvdinput_title: !isImageFile\n");
+	dvdinput_title( dvd_file->title_devs[ 0 ], (int)dvd_file->lb_start );
+      }*/
+    }
+    
+    if( dvd_file->dvd->isImageFile ) {
+        printf("DVDReadBlocks: DVDReadBlocksUDF: isImageFile\n");
+	ret = DVDReadBlocksUDF( dvd_file, (uint32_t)offset, 
+				block_count, data, DVDINPUT_READ_DECRYPT );
+    } else {
+        printf("DVDReadBlocks: DVDReadBlocksPATH: !isImageFile\n");
+	ret = DVDReadBlocksPath( dvd_file, (unsigned int)offset, 
+				 block_count, data, DVDINPUT_READ_DECRYPT );
+    }
+    
+    return (ssize_t)ret;
+}
+
+int32_t DVDFileSeek( dvd_file_t *dvd_file, int32_t offset )
+{
+    /* Check arguments. */
+    if( dvd_file == NULL || offset < 0 )
+       return -1;
+    
+    if( offset > dvd_file->filesize * DVD_VIDEO_LB_LEN ) {
+       return -1;
+    }
+    dvd_file->seek_pos = (uint32_t) offset;
+    return offset;
+}
+
+ssize_t DVDReadBytes( dvd_file_t *dvd_file, void *data, size_t byte_size )
+{
+    unsigned char *secbuf;
+    unsigned int numsec, seek_sector, seek_byte;
+    int ret;
+    
+    /* Check arguments. */
+    if( dvd_file == NULL || data == NULL )
+      return -1;
+
+    seek_sector = dvd_file->seek_pos / DVD_VIDEO_LB_LEN;
+    seek_byte   = dvd_file->seek_pos % DVD_VIDEO_LB_LEN;
+
+    numsec = ( ( seek_byte + byte_size ) / DVD_VIDEO_LB_LEN ) +
+      ( ( ( seek_byte + byte_size ) % DVD_VIDEO_LB_LEN ) ? 1 : 0 );
+    
+    secbuf = (unsigned char *) malloc( numsec * DVD_VIDEO_LB_LEN );
+    if( !secbuf ) {
+	fprintf( stderr, "libdvdread: Can't allocate memory " 
+		 "for file read!\n" );
+        return 0;
+    }
+    
+    if( dvd_file->dvd->isImageFile ) {
+	ret = DVDReadBlocksUDF( dvd_file, (uint32_t) seek_sector, 
+				(size_t) numsec, secbuf, DVDINPUT_NOFLAGS );
+    } else {
+	ret = DVDReadBlocksPath( dvd_file, seek_sector, 
+				 (size_t) numsec, secbuf, DVDINPUT_NOFLAGS );
+    }
+
+    if( ret != (int) numsec ) {
+        free( secbuf );
+        return ret < 0 ? ret : 0;
+    }
+
+    memcpy( data, &(secbuf[ seek_byte ]), byte_size );
+    free( secbuf );
+
+    dvd_file->seek_pos += byte_size;
+    return byte_size;
+}
+
+ssize_t DVDFileSize( dvd_file_t *dvd_file )
+{
+    /* Check arguments. */
+    if( dvd_file == NULL )
+      return -1;
+    
+    return dvd_file->filesize;
+}
+
+int DVDDiscID( dvd_reader_t *dvd, unsigned char *discid )
+{
+    struct md5_ctx ctx;
+    int title;
+
+    /* Check arguments. */
+    if( dvd == NULL || discid == NULL )
+      return 0;
+    
+    /* Go through the first 10 IFO:s, in order, 
+     * and md5sum them, i.e  VIDEO_TS.IFO and VTS_0?_0.IFO */
+    md5_init_ctx( &ctx );
+    for( title = 0; title < 10; title++ ) {
+	dvd_file_t *dvd_file = DVDOpenFile( dvd, title, DVD_READ_INFO_FILE );
+	if( dvd_file != NULL ) {
+	    ssize_t bytes_read;
+	    size_t file_size = dvd_file->filesize * DVD_VIDEO_LB_LEN;
+	    char *buffer = malloc( file_size );
+	    
+	    if( buffer == NULL ) {
+		fprintf( stderr, "libdvdread: DVDDiscId, failed to "
+			 "allocate memory for file read!\n" );
+		return -1;
+	    }
+	    bytes_read = DVDReadBytes( dvd_file, buffer, file_size );
+	    if( bytes_read != file_size ) {
+		fprintf( stderr, "libdvdread: DVDDiscId read returned %d bytes"
+			 ", wanted %d\n", bytes_read, file_size );
+		DVDCloseFile( dvd_file );
+		return -1;
+	    }
+	    
+	    md5_process_bytes( buffer, file_size,  &ctx );
+	    
+	    DVDCloseFile( dvd_file );
+	    free( buffer );
+	}
+    }
+    md5_finish_ctx( &ctx, discid );
+    
+    return 0;
+}
+
+
+int DVDISOVolumeInfo( dvd_reader_t *dvd,
+		      char *volid, unsigned int volid_size,
+		      unsigned char *volsetid, unsigned int volsetid_size )
+{
+  unsigned char *buffer;
+  int ret;
+
+  /* Check arguments. */
+  if( dvd == NULL )
+    return 0;
+  
+  if( dvd->dev == NULL ) {
+    /* No block access, so no ISO... */
+    return -1;
+  }
+  
+  buffer = malloc( DVD_VIDEO_LB_LEN );
+  if( buffer == NULL ) {
+    fprintf( stderr, "libdvdread: DVDISOVolumeInfo, failed to "
+	     "allocate memory for file read!\n" );
+    return -1;
+  }
+
+  ret = UDFReadBlocksRaw( dvd, 16, 1, buffer, 0 );
+  if( ret != 1 ) {
+    fprintf( stderr, "libdvdread: DVDISOVolumeInfo, failed to "
+	     "read ISO9660 Primary Volume Descriptor!\n" );
+    return -1;
+  }
+  
+  if( (volid != NULL) && (volid_size > 0) ) {
+    unsigned int n;
+    for(n = 0; n < 32; n++) {
+      if(buffer[40+n] == 0x20) {
+	break;
+      }
+    }
+    
+    if(volid_size > n+1) {
+      volid_size = n+1;
+    }
+
+    memcpy(volid, &buffer[40], volid_size-1);
+    volid[volid_size-1] = '\0';
+  }
+  
+  if( (volsetid != NULL) && (volsetid_size > 0) ) {
+    if(volsetid_size > 128) {
+      volsetid_size = 128;
+    }
+    memcpy(volsetid, &buffer[190], volsetid_size);
+  }
+  return 0;
+}
+
+
+int DVDUDFVolumeInfo( dvd_reader_t *dvd,
+		      char *volid, unsigned int volid_size,
+		      unsigned char *volsetid, unsigned int volsetid_size )
+{
+  int ret;
+  /* Check arguments. */
+  if( dvd == NULL )
+    return -1;
+  
+  if( dvd->dev == NULL ) {
+    /* No block access, so no UDF VolumeSet Identifier */
+    return -1;
+  }
+  
+  if( (volid != NULL) && (volid_size > 0) ) {
+    ret = UDFGetVolumeIdentifier(dvd, volid, volid_size);
+    if(!ret) {
+      return -1;
+    }
+  }
+  if( (volsetid != NULL) && (volsetid_size > 0) ) {
+    ret =  UDFGetVolumeSetIdentifier(dvd, volsetid, volsetid_size);
+    if(!ret) {
+      return -1;
+    }
+  }
+    
+  return 0;  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_reader.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,265 @@
+#ifndef DVD_READER_H_INCLUDED
+#define DVD_READER_H_INCLUDED
+
+/*
+ * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>,
+ *                          Björn Englund <d4bjorn@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <sys/types.h>
+
+/**
+ * The DVD access interface.
+ *
+ * This file contains the functions that form the interface to to
+ * reading files located on a DVD.
+ */
+
+/**
+ * The current version.
+ */
+#define DVDREAD_VERSION 904
+
+/**
+ * The length of one Logical Block of a DVD.
+ */
+#define DVD_VIDEO_LB_LEN 2048
+
+/**
+ * Maximum length of filenames allowed in UDF.
+ */
+#define MAX_UDF_FILE_NAME_LEN 2048
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+/**
+ * Opaque type that is used as a handle for one instance of an opened DVD.
+ */
+typedef struct dvd_reader_s dvd_reader_t;
+  
+/**
+ * Opaque type for a file read handle, much like a normal fd or FILE *.
+ */
+typedef struct dvd_file_s dvd_file_t;
+
+/**
+ * Opens a block device of a DVD-ROM file, or an image file, or a directory
+ * name for a mounted DVD or HD copy of a DVD.
+ *
+ * If the given file is a block device, or is the mountpoint for a block
+ * device, then that device is used for CSS authentication using libdvdcss.
+ * If no device is available, then no CSS authentication is performed, 
+ * and we hope that the image is decrypted.
+ *
+ * If the path given is a directory, then the files in that directory may be
+ * in any one of these formats:
+ *
+ *   path/VIDEO_TS/VTS_01_1.VOB
+ *   path/video_ts/vts_01_1.vob
+ *   path/VTS_01_1.VOB
+ *   path/vts_01_1.vob
+ *
+ * @param path Specifies the the device, file or directory to be used. 
+ * @return If successful a a read handle is returned. Otherwise 0 is returned.
+ *
+ * dvd = DVDOpen(path);
+ */
+dvd_reader_t *DVDOpen( const char * );
+
+/**
+ * Closes and cleans up the DVD reader object.
+ *
+ * You must close all open files before calling this function.
+ *
+ * @param dvd A read handle that should be closed.
+ *
+ * DVDClose(dvd);
+ */
+void DVDClose( dvd_reader_t * );
+
+/**
+ * 
+ */
+typedef enum {
+  DVD_READ_INFO_FILE,        /**< VIDEO_TS.IFO  or VTS_XX_0.IFO (title) */
+  DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP  or VTS_XX_0.BUP (title) */
+  DVD_READ_MENU_VOBS,        /**< VIDEO_TS.VOB  or VTS_XX_0.VOB (title) */
+  DVD_READ_TITLE_VOBS        /**< VTS_XX_[1-9].VOB (title).  All files in 
+				  the title set are opened and read as a
+				  single file. */
+} dvd_read_domain_t;
+
+/**
+ * Opens a file on the DVD given the title number and domain.
+ *
+ * If the title number is 0, the video manager information is opened
+ * (VIDEO_TS.[IFO,BUP,VOB]).  Returns a file structure which may be
+ * used for reads, or 0 if the file was not found.
+ *
+ * @param dvd  A dvd read handle.
+ * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
+ * @param domain Which domain. 
+ * @return If successful a a file read handle is returned, otherwise 0.
+ *
+ * dvd_file = DVDOpenFile(dvd, titlenum, domain); */
+dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
+
+/**
+ * Closes a file and frees the associated structure.
+ *
+ * @param dvd_file  The file read handle to be closed.
+ *
+ * DVDCloseFile(dvd_file);
+ */
+void DVDCloseFile( dvd_file_t * );
+
+/**
+ * Reads block_count number of blocks from the file at the given block offset.
+ * Returns number of blocks read on success, -1 on error.  This call is only
+ * for reading VOB data, and should not be used when reading the IFO files.  
+ * When reading from an encrypted drive, blocks are decrypted using libdvdcss 
+ * where required.
+ *
+ * @param dvd_file  A file read handle.
+ * @param offset Block offset from the start of the file to start reading at.
+ * @param block_count Number of block to read.
+ * @param data Pointer to a buffer to write the data into.
+ * @return Returns number of blocks read on success, -1 on error.
+ *
+ * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
+ */
+ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
+
+/**
+ * Seek to the given position in the file.  Returns the resulting position in
+ * bytes from the beginning of the file.  The seek position is only used for
+ * byte reads from the file, the block read call always reads from the given
+ * offset.
+ *
+ * @param dvd_file  A file read handle.
+ * @param seek_offset Byte offset from the start of the file to seek to.
+ * @return The resulting position in bytes from the beginning of the file.
+ *
+ * offset_set = DVDFileSeek(dvd_file, seek_offset);
+ */
+int DVDFileSeek( dvd_file_t *, int );
+
+/**
+ * Reads the given number of bytes from the file.  This call can only be used
+ * on the information files, and may not be used for reading from a VOB.  This
+ * reads from and increments the currrent seek position for the file.
+ *
+ * @param dvd_file  A file read handle.
+ * @param data Pointer to a buffer to write the data into.
+ * @param bytes Number of bytes to read.
+ * @return Returns number of bytes read on success, -1 on error.
+ *
+ * bytes_read = DVDReadBytes(dvd_file, data, bytes);
+ */
+ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
+
+/**
+ * Returns the file size in blocks.
+ *
+ * @param dvd_file  A file read handle.
+ * @return The size of the file in blocks, -1 on error.
+ *
+ * blocks = DVDFileSize(dvd_file);
+ */
+ssize_t DVDFileSize( dvd_file_t * );
+
+/**
+ * Get a unique 128 bit disc ID.
+ * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
+ * in title order (those that exist).
+ * If you need a 'text' representation of the id, print it as a
+ * hexadecimal number, using lowercase letters, discid[0] first. 
+ * I.e. the same format as the command-line 'md5sum' program uses.
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param discid The buffer to put the disc ID into. The buffer must
+ *               have room for 128 bits (16 chars).
+ * @return 0 on success, -1 on error.
+ */
+int DVDDiscID( dvd_reader_t *, unsigned char * );
+
+/**
+ * Get the UDF VolumeIdentifier and VolumeSetIdentifier
+ * from the PrimaryVolumeDescriptor.
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param volid The buffer to put the VolumeIdentifier into.
+ *              The VolumeIdentifier is latin-1 encoded (8bit unicode)
+ *              null terminated and max 32 bytes (including '\0')
+ * @param volid_size No more than volid_size bytes will be copied to volid.
+ *                   If the VolumeIdentifier is truncated because of this
+ *                   it will still be null terminated.
+ * @param volsetid The buffer to put the VolumeSetIdentifier into.
+ *                 The VolumeIdentifier is 128 bytes as
+ *                 stored in the UDF PrimaryVolumeDescriptor.
+ *                 Note that this is not a null terminated string.
+ * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
+ * @return 0 on success, -1 on error.
+ */
+int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
+		      unsigned char *, unsigned int );
+
+/**
+ * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
+ *
+ * * Only use this function as fallback if DVDUDFVolumeInfo returns 0   *
+ * * this will happen on a disc mastered only with a iso9660 filesystem *
+ * * All video DVD discs have UDF filesystem                            *
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param volid The buffer to put the VolumeIdentifier into.
+ *              The VolumeIdentifier is coded with '0-9','A-Z','_'
+ *              null terminated and max 33 bytes (including '\0')
+ * @param volid_size No more than volid_size bytes will be copied to volid.
+ *                   If the VolumeIdentifier is truncated because of this
+ *                   it will still be null terminated.
+ * @param volsetid The buffer to put the VolumeSetIdentifier into.
+ *                 The VolumeIdentifier is 128 bytes as
+ *                 stored in the ISO9660 PrimaryVolumeDescriptor.
+ *                 Note that this is not a null terminated string.
+ * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
+ * @return 0 on success, -1 on error.
+ */
+int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
+		      unsigned char *, unsigned int );
+
+/**
+ * Sets the level of caching that is done when reading from a device
+ *
+ * @param dvd A read handle to get the disc ID from
+ * @param level The level of caching wanted.
+ *             -1 - returns the current setting.
+ *              0 - UDF Cache turned off.
+ *              1 - (default level) Pointers to IFO files and some data from
+ *                  PrimaryVolumeDescriptor are cached. 
+ *
+ * @return The level of caching.
+ */
+int DVDUDFCacheLevel( dvd_reader_t *, int );
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* DVD_READER_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_udf.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,996 @@
+/*
+ * This code is based on dvdudf by:
+ *   Christian Wolff <scarabaeus@convergence.de>.
+ *
+ * Modifications by:
+ *   Billy Biggs <vektor@dumbterm.net>.
+ *   Björn Englund <d4bjorn@dtek.chalmers.se>.
+ *
+ * dvdudf: parse and read the UDF volume information of a DVD Video
+ * Copyright (C) 1999 Christian Wolff for convergence integrated media
+ * GmbH The author can be reached at scarabaeus@convergence.de, the
+ * project's page is at http://linuxtv.org/dvd/
+ * 
+ * This program 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.
+ * 
+ * This program 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.  Or, point your browser to
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+ 
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "dvd_reader.h"
+#include "dvd_udf.h"
+
+/* Private but located in/shared with dvd_reader.c */
+extern int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
+				size_t block_count, unsigned char *data, 
+				int encrypted );
+
+/* It's required to either fail or deliver all the blocks asked for. */
+static int DVDReadLBUDF( dvd_reader_t *device, uint32_t lb_number,
+			 size_t block_count, unsigned char *data, 
+			 int encrypted )
+{
+  int ret;
+  size_t count = block_count;
+  
+  while(count > 0) {
+    
+    ret = UDFReadBlocksRaw(device, lb_number, count, data, encrypted);
+        
+    if(ret <= 0) {
+      /* One of the reads failed or nothing more to read, too bad.
+       * We won't even bother returning the reads that went ok. */
+      return ret;
+    }
+    
+    count -= (size_t)ret;
+    lb_number += (uint32_t)ret;
+  }
+
+  return block_count;
+}
+
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+struct Partition {
+    int valid;
+    char VolumeDesc[128];
+    uint16_t Flags;
+    uint16_t Number;
+    char Contents[32];
+    uint32_t AccessType;
+    uint32_t Start;
+    uint32_t Length;
+};
+
+struct AD {
+    uint32_t Location;
+    uint32_t Length;
+    uint8_t  Flags;
+    uint16_t Partition;
+};
+
+struct extent_ad {
+  uint32_t location;
+  uint32_t length;
+};
+
+struct avdp_t {
+  struct extent_ad mvds;
+  struct extent_ad rvds;
+};
+
+struct pvd_t {
+  uint8_t VolumeIdentifier[32];
+  uint8_t VolumeSetIdentifier[128];
+};
+
+struct lbudf {
+  uint32_t lb;
+  uint8_t *data;
+};
+
+struct icbmap {
+  uint32_t lbn;
+  struct AD file;
+  uint8_t filetype;
+};
+
+struct udf_cache {
+  int avdp_valid;
+  struct avdp_t avdp;
+  int pvd_valid;
+  struct pvd_t pvd;
+  int partition_valid;
+  struct Partition partition;
+  int rooticb_valid;
+  struct AD rooticb;
+  int lb_num;
+  struct lbudf *lbs;
+  int map_num;
+  struct icbmap *maps;
+};
+
+typedef enum {
+  PartitionCache, RootICBCache, LBUDFCache, MapCache, AVDPCache, PVDCache
+} UDFCacheType;
+
+extern void *GetUDFCacheHandle(dvd_reader_t *device);
+extern void SetUDFCacheHandle(dvd_reader_t *device, void *cache);
+
+void FreeUDFCache(void *cache)
+{
+  struct udf_cache *c = (struct udf_cache *)cache;
+  if(c == NULL) {
+    return;
+  }
+  if(c->lbs) {
+    free(c->lbs);
+  }
+  if(c->maps) {
+    free(c->maps);
+  }
+  free(c);
+}
+
+
+static int GetUDFCache(dvd_reader_t *device, UDFCacheType type,
+		       uint32_t nr, void *data)
+{
+  int n;
+  struct udf_cache *c;
+
+  if(DVDUDFCacheLevel(device, -1) <= 0) {
+    return 0;
+  }
+  
+  c = (struct udf_cache *)GetUDFCacheHandle(device);
+  
+  if(c == NULL) {
+    return 0;
+  }
+  
+  switch(type) {
+  case AVDPCache:
+    if(c->avdp_valid) {
+      *(struct avdp_t *)data = c->avdp;
+      return 1;
+    }    
+    break;
+  case PVDCache:
+    if(c->pvd_valid) {
+      *(struct pvd_t *)data = c->pvd;
+      return 1;
+    }    
+    break;
+  case PartitionCache:
+    if(c->partition_valid) {
+      *(struct Partition *)data = c->partition;
+      return 1;
+    }
+    break;
+  case RootICBCache:
+    if(c->rooticb_valid) {
+      *(struct AD *)data = c->rooticb;
+      return 1;
+    }
+    break;
+  case LBUDFCache:
+    for(n = 0; n < c->lb_num; n++) {
+      if(c->lbs[n].lb == nr) {
+      *(uint8_t **)data = c->lbs[n].data;
+      return 1;
+      }
+    }
+    break;
+  case MapCache:
+    for(n = 0; n < c->map_num; n++) {
+      if(c->maps[n].lbn == nr) {
+       *(struct icbmap *)data = c->maps[n];
+       return 1;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  
+  return 0;
+}
+
+static int SetUDFCache(dvd_reader_t *device, UDFCacheType type,
+		       uint32_t nr, void *data)
+{
+  int n;
+  struct udf_cache *c;
+
+  if(DVDUDFCacheLevel(device, -1) <= 0) {
+    return 0;
+  }
+
+  c = (struct udf_cache *)GetUDFCacheHandle(device);
+  
+  if(c == NULL) {
+    c = calloc(1, sizeof(struct udf_cache));    
+    //    fprintf(stderr, "calloc: %d\n", sizeof(struct udf_cache));    
+    if(c == NULL) {
+      return 0;
+    }
+    SetUDFCacheHandle(device, c);
+  }
+  
+  
+  switch(type) {
+  case AVDPCache:
+    c->avdp = *(struct avdp_t *)data; 
+    c->avdp_valid = 1;
+    break;
+  case PVDCache:
+    c->pvd = *(struct pvd_t *)data; 
+    c->pvd_valid = 1;
+    break;
+  case PartitionCache:
+    c->partition = *(struct Partition *)data; 
+    c->partition_valid = 1;
+    break;
+  case RootICBCache:
+    c->rooticb = *(struct AD *)data; 
+    c->rooticb_valid = 1;
+    break;
+  case LBUDFCache:
+    for(n = 0; n < c->lb_num; n++) {
+      if(c->lbs[n].lb == nr) {
+       /* replace with new data */
+       c->lbs[n].data = *(uint8_t **)data;
+       c->lbs[n].lb = nr;
+       return 1;
+      }
+    }
+    c->lb_num++;
+    c->lbs = realloc(c->lbs, c->lb_num * sizeof(struct lbudf));
+    /*
+    fprintf(stderr, "realloc lb: %d * %d = %d\n",
+	    c->lb_num, sizeof(struct lbudf),
+	    c->lb_num * sizeof(struct lbudf));
+    */
+    if(c->lbs == NULL) {
+      c->lb_num = 0;
+      return 0;
+    }
+    c->lbs[n].data = *(uint8_t **)data;
+    c->lbs[n].lb = nr;
+    break;
+  case MapCache:
+    for(n = 0; n < c->map_num; n++) {
+      if(c->maps[n].lbn == nr) {
+       /* replace with new data */
+       c->maps[n] = *(struct icbmap *)data;
+       c->maps[n].lbn = nr;
+       return 1;
+      }
+    }
+    c->map_num++;
+    c->maps = realloc(c->maps, c->map_num * sizeof(struct icbmap));
+    /*
+    fprintf(stderr, "realloc maps: %d * %d = %d\n",
+	    c->map_num, sizeof(struct icbmap),
+	    c->map_num * sizeof(struct icbmap));
+    */
+    if(c->maps == NULL) {
+      c->map_num = 0;
+      return 0;
+    }
+    c->maps[n] = *(struct icbmap *)data;
+    c->maps[n].lbn = nr;
+    break;
+  default:
+    return 0;
+  }
+    
+  return 1;
+}
+
+
+/* For direct data access, LSB first */
+#define GETN1(p) ((uint8_t)data[p])
+#define GETN2(p) ((uint16_t)data[p] | ((uint16_t)data[(p) + 1] << 8))
+#define GETN3(p) ((uint32_t)data[p] | ((uint32_t)data[(p) + 1] << 8) \
+		  | ((uint32_t)data[(p) + 2] << 16))
+#define GETN4(p) ((uint32_t)data[p] \
+		  | ((uint32_t)data[(p) + 1] << 8) \
+		  | ((uint32_t)data[(p) + 2] << 16) \
+		  | ((uint32_t)data[(p) + 3] << 24))
+/* This is wrong with regard to endianess */
+#define GETN(p, n, target) memcpy(target, &data[p], n)
+
+static int Unicodedecode( uint8_t *data, int len, char *target ) 
+{
+    int p = 1, i = 0;
+
+    if( ( data[ 0 ] == 8 ) || ( data[ 0 ] == 16 ) ) do {
+        if( data[ 0 ] == 16 ) p++;  /* Ignore MSB of unicode16 */
+        if( p < len ) {
+            target[ i++ ] = data[ p++ ];
+        }
+    } while( p < len );
+
+    target[ i ] = '\0';
+    return 0;
+}
+
+static int UDFDescriptor( uint8_t *data, uint16_t *TagID ) 
+{
+    *TagID = GETN2(0);
+    // TODO: check CRC 'n stuff
+    return 0;
+}
+
+static int UDFExtentAD( uint8_t *data, uint32_t *Length, uint32_t *Location ) 
+{
+    *Length   = GETN4(0);
+    *Location = GETN4(4);
+    return 0;
+}
+
+static int UDFShortAD( uint8_t *data, struct AD *ad, 
+		       struct Partition *partition ) 
+{
+    ad->Length = GETN4(0);
+    ad->Flags = ad->Length >> 30;
+    ad->Length &= 0x3FFFFFFF;
+    ad->Location = GETN4(4);
+    ad->Partition = partition->Number; // use number of current partition
+    return 0;
+}
+
+static int UDFLongAD( uint8_t *data, struct AD *ad )
+{
+    ad->Length = GETN4(0);
+    ad->Flags = ad->Length >> 30;
+    ad->Length &= 0x3FFFFFFF;
+    ad->Location = GETN4(4);
+    ad->Partition = GETN2(8);
+    //GETN(10, 6, Use);
+    return 0;
+}
+
+static int UDFExtAD( uint8_t *data, struct AD *ad )
+{
+    ad->Length = GETN4(0);
+    ad->Flags = ad->Length >> 30;
+    ad->Length &= 0x3FFFFFFF;
+    ad->Location = GETN4(12);
+    ad->Partition = GETN2(16);
+    //GETN(10, 6, Use);
+    return 0;
+}
+
+static int UDFICB( uint8_t *data, uint8_t *FileType, uint16_t *Flags )
+{
+    *FileType = GETN1(11);
+    *Flags = GETN2(18);
+    return 0;
+}
+
+
+static int UDFPartition( uint8_t *data, uint16_t *Flags, uint16_t *Number,
+			 char *Contents, uint32_t *Start, uint32_t *Length )
+{
+    *Flags = GETN2(20);
+    *Number = GETN2(22);
+    GETN(24, 32, Contents);
+    *Start = GETN4(188);
+    *Length = GETN4(192);
+    return 0;
+}
+
+/**
+ * Reads the volume descriptor and checks the parameters.  Returns 0 on OK, 1
+ * on error.
+ */
+static int UDFLogVolume( uint8_t *data, char *VolumeDescriptor )
+{
+    uint32_t lbsize, MT_L, N_PM;
+    Unicodedecode(&data[84], 128, VolumeDescriptor);
+    lbsize = GETN4(212);  // should be 2048
+    MT_L = GETN4(264);    // should be 6
+    N_PM = GETN4(268);    // should be 1
+    if (lbsize != DVD_VIDEO_LB_LEN) return 1;
+    return 0;
+}
+
+static int UDFFileEntry( uint8_t *data, uint8_t *FileType, 
+			 struct Partition *partition, struct AD *ad )
+{
+    uint16_t flags;
+    uint32_t L_EA, L_AD;
+    unsigned int p;
+
+    UDFICB( &data[ 16 ], FileType, &flags );
+   
+    /* Init ad for an empty file (i.e. there isn't a AD, L_AD == 0 ) */
+    ad->Length = GETN4( 60 ); // Really 8 bytes a 56
+    ad->Flags = 0;
+    ad->Location = 0; // what should we put here? 
+    ad->Partition = partition->Number; // use number of current partition
+
+    L_EA = GETN4( 168 );
+    L_AD = GETN4( 172 );
+    p = 176 + L_EA;
+    while( p < 176 + L_EA + L_AD ) {
+        switch( flags & 0x0007 ) {
+            case 0: UDFShortAD( &data[ p ], ad, partition ); p += 8;  break;
+            case 1: UDFLongAD( &data[ p ], ad );  p += 16; break;
+            case 2: UDFExtAD( &data[ p ], ad );   p += 20; break;
+            case 3:
+                switch( L_AD ) {
+                    case 8:  UDFShortAD( &data[ p ], ad, partition ); break;
+                    case 16: UDFLongAD( &data[ p ], ad );  break;
+                    case 20: UDFExtAD( &data[ p ], ad );   break;
+                }
+                p += L_AD;
+                break;
+            default:
+                p += L_AD; break;
+        }
+    }
+    return 0;
+}
+
+static int UDFFileIdentifier( uint8_t *data, uint8_t *FileCharacteristics,
+			      char *FileName, struct AD *FileICB )
+{
+    uint8_t L_FI;
+    uint16_t L_IU;
+
+    *FileCharacteristics = GETN1(18);
+    L_FI = GETN1(19);
+    UDFLongAD(&data[20], FileICB);
+    L_IU = GETN2(36);
+    if (L_FI) Unicodedecode(&data[38 + L_IU], L_FI, FileName);
+    else FileName[0] = '\0';
+    return 4 * ((38 + L_FI + L_IU + 3) / 4);
+}
+
+/**
+ * Maps ICB to FileAD
+ * ICB: Location of ICB of directory to scan
+ * FileType: Type of the file
+ * File: Location of file the ICB is pointing to
+ * return 1 on success, 0 on error;
+ */
+static int UDFMapICB( dvd_reader_t *device, struct AD ICB, uint8_t *FileType,
+		      struct Partition *partition, struct AD *File ) 
+{
+    uint8_t LogBlock[DVD_VIDEO_LB_LEN];
+    uint32_t lbnum;
+    uint16_t TagID;
+    struct icbmap tmpmap;
+
+    lbnum = partition->Start + ICB.Location;
+    tmpmap.lbn = lbnum;
+    if(GetUDFCache(device, MapCache, lbnum, &tmpmap)) {
+      *FileType = tmpmap.filetype;
+      *File = tmpmap.file;
+      return 1;
+    }
+
+    do {
+        if( DVDReadLBUDF( device, lbnum++, 1, LogBlock, 0 ) <= 0 ) {
+            TagID = 0;
+        } else {
+            UDFDescriptor( LogBlock, &TagID );
+        }
+
+        if( TagID == 261 ) {
+            UDFFileEntry( LogBlock, FileType, partition, File );
+           tmpmap.file = *File;
+           tmpmap.filetype = *FileType;
+           SetUDFCache(device, MapCache, tmpmap.lbn, &tmpmap);
+            return 1;
+        };
+    } while( ( lbnum <= partition->Start + ICB.Location + ( ICB.Length - 1 )
+             / DVD_VIDEO_LB_LEN ) && ( TagID != 261 ) );
+
+    return 0;
+}
+
+/**
+ * Dir: Location of directory to scan
+ * FileName: Name of file to look for
+ * FileICB: Location of ICB of the found file
+ * return 1 on success, 0 on error;
+ */
+static int UDFScanDir( dvd_reader_t *device, struct AD Dir, char *FileName,
+                       struct Partition *partition, struct AD *FileICB,
+		       int cache_file_info) 
+{
+    char filename[ MAX_UDF_FILE_NAME_LEN ];
+    uint8_t directory[ 2 * DVD_VIDEO_LB_LEN ];
+    uint32_t lbnum;
+    uint16_t TagID;
+    uint8_t filechar;
+    unsigned int p;
+    uint8_t *cached_dir = NULL;
+    uint32_t dir_lba;
+    struct AD tmpICB;
+    int found = 0;
+    int in_cache = 0;
+
+    /* Scan dir for ICB of file */
+    lbnum = partition->Start + Dir.Location;
+    
+    if(DVDUDFCacheLevel(device, -1) > 0) {
+      /* caching */
+      
+      if(!GetUDFCache(device, LBUDFCache, lbnum, &cached_dir)) {
+	dir_lba = (Dir.Length + DVD_VIDEO_LB_LEN) / DVD_VIDEO_LB_LEN;
+	if((cached_dir = malloc(dir_lba * DVD_VIDEO_LB_LEN)) == NULL) {
+	  return 0;
+	}
+	if( DVDReadLBUDF( device, lbnum, dir_lba, cached_dir, 0) <= 0 ) {
+	  free(cached_dir);
+	  cached_dir = NULL;
+	}
+	/*
+	if(cached_dir) {
+	  fprintf(stderr, "malloc dir: %d\n",
+		  dir_lba * DVD_VIDEO_LB_LEN);
+	}
+	*/
+	SetUDFCache(device, LBUDFCache, lbnum, &cached_dir);
+      } else {
+	in_cache = 1;
+      }
+      
+      if(cached_dir == NULL) {
+	return 0;
+      }
+      
+      p = 0;
+      
+      while( p < Dir.Length ) {
+        UDFDescriptor( &cached_dir[ p ], &TagID );
+        if( TagID == 257 ) {
+	  p += UDFFileIdentifier( &cached_dir[ p ], &filechar,
+				  filename, &tmpICB );
+	  if(cache_file_info && !in_cache) {
+	    uint8_t tmpFiletype;
+	    struct AD tmpFile;
+	    
+	    if( !strcasecmp( FileName, filename ) ) {
+	      *FileICB = tmpICB;
+	      found = 1;
+	      
+	    }
+	    UDFMapICB(device, tmpICB, &tmpFiletype,
+		      partition, &tmpFile);
+	  } else {
+	    if( !strcasecmp( FileName, filename ) ) {
+	      *FileICB = tmpICB;
+	      return 1;
+	    }
+	  }
+        } else {
+	  if(cache_file_info && (!in_cache) && found) {
+	    return 1;
+	  }
+	  return 0;
+        }
+      }
+      if(cache_file_info && (!in_cache) && found) {
+	return 1;
+      }
+      return 0;
+    }
+
+    if( DVDReadLBUDF( device, lbnum, 2, directory, 0 ) <= 0 ) {
+        return 0;
+    }
+
+    p = 0;
+    while( p < Dir.Length ) {
+        if( p > DVD_VIDEO_LB_LEN ) {
+            ++lbnum;
+            p -= DVD_VIDEO_LB_LEN;
+            Dir.Length -= DVD_VIDEO_LB_LEN;
+            if( DVDReadLBUDF( device, lbnum, 2, directory, 0 ) <= 0 ) {
+                return 0;
+            }
+        }
+        UDFDescriptor( &directory[ p ], &TagID );
+        if( TagID == 257 ) {
+            p += UDFFileIdentifier( &directory[ p ], &filechar,
+                                    filename, FileICB );
+            if( !strcasecmp( FileName, filename ) ) {
+                return 1;
+            }
+        } else {
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+static int UDFGetAVDP( dvd_reader_t *device,
+		       struct avdp_t *avdp)
+{
+  uint8_t Anchor[ DVD_VIDEO_LB_LEN ];
+  uint32_t lbnum, MVDS_location, MVDS_length;
+  uint16_t TagID;
+  uint32_t lastsector;
+  int terminate;
+  struct avdp_t; 
+  
+  if(GetUDFCache(device, AVDPCache, 0, avdp)) {
+    return 1;
+  }
+
+  /* Find Anchor */
+  lastsector = 0;
+  lbnum = 256;   /* Try #1, prime anchor */
+  terminate = 0;
+  
+  for(;;) {
+    if( DVDReadLBUDF( device, lbnum, 1, Anchor, 0 ) > 0 ) {
+      UDFDescriptor( Anchor, &TagID );
+    } else {
+      TagID = 0;
+    }
+    if (TagID != 2) {
+      /* Not an anchor */
+      if( terminate ) return 0; /* Final try failed */
+      
+      if( lastsector ) {
+	
+	/* We already found the last sector.  Try #3, alternative
+	 * backup anchor.  If that fails, don't try again.
+	 */
+	lbnum = lastsector;
+	terminate = 1;
+      } else {
+	/* TODO: Find last sector of the disc (this is optional). */
+	if( lastsector ) {
+	  /* Try #2, backup anchor */
+	  lbnum = lastsector - 256;
+	} else {
+	  /* Unable to find last sector */
+	  return 0;
+	}
+      }
+    } else {
+      /* It's an anchor! We can leave */
+      break;
+    }
+  }
+  /* Main volume descriptor */
+  UDFExtentAD( &Anchor[ 16 ], &MVDS_length, &MVDS_location );
+  avdp->mvds.location = MVDS_location;
+  avdp->mvds.length = MVDS_length;
+  
+  /* Backup volume descriptor */
+  UDFExtentAD( &Anchor[ 24 ], &MVDS_length, &MVDS_location );
+  avdp->rvds.location = MVDS_location;
+  avdp->rvds.length = MVDS_length;
+  
+  SetUDFCache(device, AVDPCache, 0, avdp);
+  
+  return 1;
+}
+
+/**
+ * Looks for partition on the disc.  Returns 1 if partition found, 0 on error.
+ *   partnum: Number of the partition, starting at 0.
+ *   part: structure to fill with the partition information
+ */
+static int UDFFindPartition( dvd_reader_t *device, int partnum,
+			     struct Partition *part ) 
+{
+    uint8_t LogBlock[ DVD_VIDEO_LB_LEN ];
+    uint32_t lbnum, MVDS_location, MVDS_length;
+    uint16_t TagID;
+    int i, volvalid;
+    struct avdp_t avdp;
+
+    
+    if(!UDFGetAVDP(device, &avdp)) {
+      return 0;
+    }
+
+    /* Main volume descriptor */
+    MVDS_location = avdp.mvds.location;
+    MVDS_length = avdp.mvds.length;
+
+    part->valid = 0;
+    volvalid = 0;
+    part->VolumeDesc[ 0 ] = '\0';
+    i = 1;
+    do {
+        /* Find Volume Descriptor */
+        lbnum = MVDS_location;
+        do {
+
+            if( DVDReadLBUDF( device, lbnum++, 1, LogBlock, 0 ) <= 0 ) {
+                TagID = 0;
+            } else {
+                UDFDescriptor( LogBlock, &TagID );
+            }
+
+            if( ( TagID == 5 ) && ( !part->valid ) ) {
+                /* Partition Descriptor */
+                UDFPartition( LogBlock, &part->Flags, &part->Number,
+                              part->Contents, &part->Start, &part->Length );
+                part->valid = ( partnum == part->Number );
+            } else if( ( TagID == 6 ) && ( !volvalid ) ) {
+                /* Logical Volume Descriptor */
+                if( UDFLogVolume( LogBlock, part->VolumeDesc ) ) {  
+                    /* TODO: sector size wrong! */
+                } else {
+                    volvalid = 1;
+                }
+            }
+
+        } while( ( lbnum <= MVDS_location + ( MVDS_length - 1 )
+                 / DVD_VIDEO_LB_LEN ) && ( TagID != 8 )
+                 && ( ( !part->valid ) || ( !volvalid ) ) );
+
+        if( ( !part->valid) || ( !volvalid ) ) {
+	  /* Backup volume descriptor */
+	  MVDS_location = avdp.mvds.location;
+	  MVDS_length = avdp.mvds.length;
+        }
+    } while( i-- && ( ( !part->valid ) || ( !volvalid ) ) );
+
+    /* We only care for the partition, not the volume */
+    return part->valid;
+}
+
+uint32_t UDFFindFile( dvd_reader_t *device, char *filename,
+		      uint32_t *filesize )
+{
+    uint8_t LogBlock[ DVD_VIDEO_LB_LEN ];
+    uint32_t lbnum;
+    uint16_t TagID;
+    struct Partition partition;
+    struct AD RootICB, File, ICB;
+    char tokenline[ MAX_UDF_FILE_NAME_LEN ];
+    char *token;
+    uint8_t filetype;
+
+    *filesize = 0;
+    tokenline[0] = '\0';
+    strcat( tokenline, filename );
+
+    
+    if(!(GetUDFCache(device, PartitionCache, 0, &partition) &&
+        GetUDFCache(device, RootICBCache, 0, &RootICB))) {
+      /* Find partition, 0 is the standard location for DVD Video.*/
+      if( !UDFFindPartition( device, 0, &partition ) ) return 0;
+      SetUDFCache(device, PartitionCache, 0, &partition);
+      
+      /* Find root dir ICB */
+      lbnum = partition.Start;
+      do {
+        if( DVDReadLBUDF( device, lbnum++, 1, LogBlock, 0 ) <= 0 ) {
+            TagID = 0;
+        } else {
+            UDFDescriptor( LogBlock, &TagID );
+        }
+
+        /* File Set Descriptor */
+        if( TagID == 256 ) {  // File Set Descriptor
+            UDFLongAD( &LogBlock[ 400 ], &RootICB );
+        }
+    } while( ( lbnum < partition.Start + partition.Length )
+             && ( TagID != 8 ) && ( TagID != 256 ) );
+
+    /* Sanity checks. */
+    if( TagID != 256 ) return 0;
+    if( RootICB.Partition != 0 ) return 0;
+    SetUDFCache(device, RootICBCache, 0, &RootICB);
+    }
+
+    /* Find root dir */
+    if( !UDFMapICB( device, RootICB, &filetype, &partition, &File ) ) return 0;
+    if( filetype != 4 ) return 0;  /* Root dir should be dir */
+
+    {
+      int cache_file_info = 0;
+      /* Tokenize filepath */
+      token = strtok(tokenline, "/");
+      
+      while( token != NULL ) {
+       
+        if( !UDFScanDir( device, File, token, &partition, &ICB,
+                        cache_file_info)) {
+         return 0;
+       }
+        if( !UDFMapICB( device, ICB, &filetype, &partition, &File ) ) {
+         return 0;
+       }
+       if(!strcmp(token, "VIDEO_TS")) {
+         cache_file_info = 1;
+       }
+        token = strtok( NULL, "/" );
+      }
+    } 
+
+    /* Sanity check. */
+    if( File.Partition != 0 ) return 0;
+   
+    *filesize = File.Length;
+    /* Hack to not return partition.Start for empty files. */
+    if( !File.Location )
+      return 0;
+    else
+      return partition.Start + File.Location;
+}
+
+
+
+/**
+ * Gets a Descriptor .
+ * Returns 1 if descriptor found, 0 on error.
+ * id, tagid of descriptor
+ * bufsize, size of BlockBuf (must be >= DVD_VIDEO_LB_LEN).
+ */
+static int UDFGetDescriptor( dvd_reader_t *device, int id,
+			     uint8_t *descriptor, int bufsize) 
+{
+  uint32_t lbnum, MVDS_location, MVDS_length;
+  struct avdp_t avdp;
+  uint16_t TagID;
+  uint32_t lastsector;
+  int i, terminate;
+  int desc_found = 0;
+  /* Find Anchor */
+  lastsector = 0;
+  lbnum = 256;   /* Try #1, prime anchor */
+  terminate = 0;
+  if(bufsize < DVD_VIDEO_LB_LEN) {
+    return 0;
+  }
+  
+  if(!UDFGetAVDP(device, &avdp)) {
+    return 0;
+  }
+
+  /* Main volume descriptor */
+  MVDS_location = avdp.mvds.location;
+  MVDS_length = avdp.mvds.length;
+  
+  i = 1;
+  do {
+    /* Find  Descriptor */
+    lbnum = MVDS_location;
+    do {
+      
+      if( DVDReadLBUDF( device, lbnum++, 1, descriptor, 0 ) <= 0 ) {
+	TagID = 0;
+      } else {
+	UDFDescriptor( descriptor, &TagID );
+      }
+      
+      if( (TagID == id) && ( !desc_found ) ) {
+	/* Descriptor */
+	desc_found = 1;
+      }
+    } while( ( lbnum <= MVDS_location + ( MVDS_length - 1 )
+	       / DVD_VIDEO_LB_LEN ) && ( TagID != 8 )
+	     && ( !desc_found) );
+    
+    if( !desc_found ) {
+      /* Backup volume descriptor */
+      MVDS_location = avdp.rvds.location;
+      MVDS_length = avdp.rvds.length;
+    }
+  } while( i-- && ( !desc_found )  );
+  
+  return desc_found;
+}
+
+
+static int UDFGetPVD(dvd_reader_t *device, struct pvd_t *pvd)
+{
+  uint8_t pvd_buf[DVD_VIDEO_LB_LEN];
+  
+  if(GetUDFCache(device, PVDCache, 0, pvd)) {
+    return 1;
+  }
+
+  if(!UDFGetDescriptor( device, 1, pvd_buf, sizeof(pvd_buf))) {
+    return 0;
+  }
+  
+  memcpy(pvd->VolumeIdentifier, &pvd_buf[24], 32);
+  memcpy(pvd->VolumeSetIdentifier, &pvd_buf[72], 128);
+  SetUDFCache(device, PVDCache, 0, pvd);
+  
+  return 1;
+}
+
+/**
+ * Gets the Volume Identifier string, in 8bit unicode (latin-1)
+ * volid, place to put the string
+ * volid_size, size of the buffer volid points to
+ * returns the size of buffer needed for all data
+ */
+int UDFGetVolumeIdentifier(dvd_reader_t *device, char *volid,
+			   unsigned int volid_size)
+{
+  struct pvd_t pvd;
+  unsigned int volid_len;
+
+  /* get primary volume descriptor */
+  if(!UDFGetPVD(device, &pvd)) {
+    return 0;
+  }
+
+  volid_len = pvd.VolumeIdentifier[31];
+  if(volid_len > 31) {
+    /* this field is only 32 bytes something is wrong */
+    volid_len = 31;
+  }
+  if(volid_size > volid_len) {
+    volid_size = volid_len;
+  }
+  Unicodedecode(pvd.VolumeIdentifier, volid_size, volid);
+  
+  return volid_len;
+}
+
+/**
+ * Gets the Volume Set Identifier, as a 128-byte dstring (not decoded)
+ * WARNING This is not a null terminated string
+ * volsetid, place to put the data
+ * volsetid_size, size of the buffer volsetid points to 
+ * the buffer should be >=128 bytes to store the whole volumesetidentifier
+ * returns the size of the available volsetid information (128)
+ * or 0 on error
+ */
+int UDFGetVolumeSetIdentifier(dvd_reader_t *device, uint8_t *volsetid,
+			      unsigned int volsetid_size)
+{
+  struct pvd_t pvd;
+
+  /* get primary volume descriptor */
+  if(!UDFGetPVD(device, &pvd)) {
+    return 0;
+  }
+
+
+  if(volsetid_size > 128) {
+    volsetid_size = 128;
+  }
+  
+  memcpy(volsetid, pvd.VolumeSetIdentifier, volsetid_size);
+  
+  return 128;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_udf.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,59 @@
+#ifndef DVD_UDF_H_INCLUDED
+#define DVD_UDF_H_INCLUDED
+
+/*
+ * This code is based on dvdudf by:
+ *   Christian Wolff <scarabaeus@convergence.de>.
+ *
+ * Modifications by:
+ *   Billy Biggs <vektor@dumbterm.net>.
+ *   Björn Englund <d4bjorn@dtek.chalmers.se>.
+ * 
+ * dvdudf: parse and read the UDF volume information of a DVD Video
+ * Copyright (C) 1999 Christian Wolff for convergence integrated media
+ * GmbH The author can be reached at scarabaeus@convergence.de, the
+ * project's page is at http://linuxtv.org/dvd/
+ * 
+ * This program 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.
+ * 
+ * This program 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.  Or, point your browser to
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <inttypes.h>
+
+#include "dvd_reader.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Looks for a file on the UDF disc/imagefile and returns the block number
+ * where it begins, or 0 if it is not found.  The filename should be an
+ * absolute pathname on the UDF filesystem, starting with '/'.  For example,
+ * '/VIDEO_TS/VTS_01_1.IFO'.  On success, filesize will be set to the size of
+ * the file in bytes.
+ */
+uint32_t UDFFindFile( dvd_reader_t *device, char *filename, uint32_t *size );
+
+void FreeUDFCache(void *cache);
+int UDFGetVolumeIdentifier(dvd_reader_t *device,
+			   char *volid, unsigned int volid_size);
+int UDFGetVolumeSetIdentifier(dvd_reader_t *device,
+			      uint8_t *volsetid, unsigned int volsetid_size);
+#ifdef __cplusplus
+};
+#endif
+#endif /* DVD_UDF_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdread_internal.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,12 @@
+#ifndef DVDREAD_INTERNAL_H
+#define DVDREAD_INTERNAL_H
+
+
+#define CHECK_VALUE(arg) \
+ if(!(arg)) { \
+   fprintf(stderr, "\n*** libdvdread: CHECK_VALUE failed in %s:%i ***" \
+                   "\n*** for %s ***\n\n", \
+                   __FILE__, __LINE__, # arg ); \
+ }
+
+#endif /* DVDREAD_INTERNAL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ifo_print.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,1148 @@
+/* 
+ * Copyright (C) 2000, 2001, 2002, 2003
+ *               Björn Englund <d4bjorn@dtek.chalmers.se>, 
+ *               Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "ifo_types.h"
+#include "ifo_read.h"
+#include "ifo_print.h"
+#include "dvdread_internal.h"
+
+/* Put this in some other file / package?  It's used in nav_print too. */
+static void ifoPrint_time(dvd_time_t *dtime) {
+  const char *rate;
+  CHECK_VALUE((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa);
+  CHECK_VALUE((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa);
+  CHECK_VALUE((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa);
+  CHECK_VALUE((dtime->frame_u&0xf) < 0xa);
+  
+  printf("%02x:%02x:%02x.%02x", 
+	 dtime->hour,
+	 dtime->minute,
+	 dtime->second,
+	 dtime->frame_u & 0x3f);
+  switch((dtime->frame_u & 0xc0) >> 6) {
+  case 1:
+    rate = "25.00";
+    break;
+  case 3:
+    rate = "29.97";
+    break;
+  default:
+    if(dtime->hour == 0 && dtime->minute == 0 
+       && dtime->second == 0 && dtime->frame_u == 0)
+      rate = "no";
+    else
+      rate = "(please send a bug report)";
+    break;
+  } 
+  printf(" @ %s fps", rate);
+}
+
+/* Put this in some other file / package?  It's used in nav_print too.
+   Possibly also by the vm / navigator. */
+static void ifoPrint_CMD(int row, vm_cmd_t *command) {
+  int i;
+
+  printf("(%03d) ", row + 1);
+  for(i=0;i<8;i++)
+    printf("%02x ", command->bytes[i]);
+  printf("| ");
+
+  //vmcmd(command);
+  printf("\n");
+}
+
+static void ifoPrint_video_attributes(video_attr_t *attr) {
+  
+  /* The following test is shorter but not correct ISO C,
+     memcmp(attr,my_friendly_zeros, sizeof(video_attr_t)) */
+  if(attr->mpeg_version == 0 
+     && attr->video_format == 0 
+     && attr->display_aspect_ratio == 0 
+     && attr->permitted_df == 0 
+     && attr->unknown1 == 0 
+     && attr->line21_cc_1 == 0 
+     && attr->line21_cc_2 == 0 
+     && attr->bit_rate == 0 
+     && attr->video_format == 0 
+     && attr->letterboxed == 0 
+     && attr->film_mode == 0) {
+    printf("-- Unspecified --");
+    return;
+  }
+  
+  switch(attr->mpeg_version) {
+  case 0:
+    printf("mpeg1 ");
+    break;
+  case 1:
+    printf("mpeg2 ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  switch(attr->video_format) {
+  case 0:
+    printf("ntsc ");
+    break;
+  case 1:
+    printf("pal ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  switch(attr->display_aspect_ratio) {
+  case 0:
+    printf("4:3 ");
+    break;
+  case 3:
+    printf("16:9 ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  // Wide is allways allowed..!!!
+  switch(attr->permitted_df) {
+  case 0:
+    printf("pan&scan+letterboxed ");
+    break;
+  case 1:
+    printf("only pan&scan "); //??
+    break;
+  case 2:
+    printf("only letterboxed ");
+    break;
+  case 3:
+    // not specified
+    break;
+  default:
+    printf("(please send a bug report)");
+  }
+  
+  printf("U%x ", attr->unknown1);
+  CHECK_VALUE(!attr->unknown1);
+  
+  if(attr->line21_cc_1 || attr->line21_cc_2) {
+    printf("NTSC CC ");
+    if(attr->line21_cc_1)
+      printf("1 ");
+    if(attr->line21_cc_2)
+      printf("2 ");
+  }
+
+  switch(attr->bit_rate) {
+    case 0:
+      printf("Variable Bit Rate ");
+      break;
+    case 1:
+      printf("Constant Bit Rate ");
+      break;
+    default:
+      printf("(please send a bug report)");
+  }
+  
+  {
+    int height = 480;
+    if(attr->video_format != 0) 
+      height = 576;
+    switch(attr->picture_size) {
+    case 0:
+      printf("720x%d ", height);
+      break;
+    case 1:
+      printf("704x%d ", height);
+      break;
+    case 2:
+      printf("352x%d ", height);
+      break;
+    case 3:
+      printf("352x%d ", height/2);
+      break;      
+    default:
+      printf("(please send a bug report) ");
+    }
+  }
+
+  if(attr->letterboxed) {
+    printf("source letterboxed ");
+  }
+  
+  if(attr->film_mode) {
+    printf("film");
+  } else {
+    printf("video"); //camera
+  }
+}
+
+static void ifoPrint_audio_attributes(audio_attr_t *attr) {
+  
+  if(attr->audio_format == 0
+     && attr->multichannel_extension == 0
+     && attr->lang_type == 0
+     && attr->application_mode == 0
+     && attr->quantization == 0
+     && attr->sample_frequency == 0
+     && attr->channels == 0
+     && attr->lang_code == 0
+     && attr->lang_extension == 0
+     && attr->code_extension == 0
+     && attr->unknown3 == 0
+     && attr->unknown1 == 0) {
+    printf("-- Unspecified --");
+    return;
+  }
+  
+  switch(attr->audio_format) {
+  case 0:
+    printf("ac3 ");
+    break;
+  case 1:
+    printf("(please send a bug report) ");
+    break;
+  case 2:
+    printf("mpeg1 ");
+    break;
+  case 3:
+    printf("mpeg2ext ");
+    break;
+  case 4:
+    printf("lpcm ");
+    break;
+  case 5:
+    printf("(please send a bug report) ");
+    break;
+  case 6:
+    printf("dts ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  if(attr->multichannel_extension)
+    printf("multichannel_extension ");
+  
+  switch(attr->lang_type) {
+  case 0:
+    // not specified
+    CHECK_VALUE(attr->lang_code == 0 || attr->lang_code == 0xffff);
+    break;
+  case 1:
+    printf("%c%c (%c) ", attr->lang_code>>8, attr->lang_code & 0xff,
+           attr->lang_extension ? attr->lang_extension : ' ');
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+
+  switch(attr->application_mode) {
+  case 0:
+    // not specified
+    break;
+  case 1:
+    printf("karaoke mode ");
+    break;
+  case 2:
+    printf("surround sound mode ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  switch(attr->quantization) {
+  case 0:
+    printf("16bit ");
+    break;
+  case 1:
+    printf("20bit ");
+    break;
+  case 2:
+    printf("24bit ");
+    break;
+  case 3:
+    printf("drc ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  switch(attr->sample_frequency) {
+  case 0:
+    printf("48kHz ");
+    break;
+  case 1:
+    printf("??kHz ");
+    break;
+  default:
+    printf("sample_frequency %i (please send a bug report) ", 
+	   attr->sample_frequency);
+  }
+  
+  printf("%dCh ", attr->channels + 1);
+  
+  switch(attr->code_extension) {
+  case 0:
+    printf("Not specified ");
+    break;
+  case 1: // Normal audio
+    printf("Normal Caption ");
+    break;
+  case 2: // visually imparied
+    printf("Audio for visually impaired ");
+    break;
+  case 3: // Directors 1
+    printf("Director's comments 1 ");
+    break;
+  case 4: // Directors 2
+    printf("Director's comments 2 ");
+    break;
+    //case 4: // Music score ?    
+  default:
+    printf("(please send a bug report) ");
+  }
+    
+  printf("%d ", attr->unknown3);
+  if(attr->application_mode == 1) {
+    printf("ca=%d ", attr->app_info.karaoke.channel_assignment);
+    printf("%d ", attr->app_info.karaoke.version);
+    if(attr->app_info.karaoke.mc_intro) 
+      printf("mc intro ");
+    printf("%s ", attr->app_info.karaoke.mode ? "duet" : "solo");
+    printf("%d ", attr->app_info.karaoke.unknown4);
+  }
+  if(attr->application_mode == 2) {
+    if(attr->app_info.surround.dolby_encoded) {
+      printf("dolby surround ");
+    }
+    printf("%d ", attr->app_info.surround.unknown5);
+    printf("%d ", attr->app_info.surround.unknown6);
+  }    
+}
+
+static void ifoPrint_subp_attributes(subp_attr_t *attr) {
+  
+  if(attr->type == 0
+     && attr->code_mode == 0
+     && attr->lang_code == 0
+     && attr->lang_extension == 0
+     && attr->zero1 == 0
+     && attr->zero2 == 0
+     && attr->code_extension == 0) {
+    printf("-- Unspecified --");
+    return;
+  }
+  
+  switch(attr->code_mode) {
+  case 0:
+    printf("Coding Mode RLE ");
+    break;
+  case 1:
+    printf("Coding Mode Extended ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }    
+ 
+  if(attr->type == 1) {
+    if(isalpha((int)(attr->lang_code >> 8))
+       && isalpha((int)(attr->lang_code & 0xff))) {
+      printf("%c%c ", attr->lang_code >> 8, attr->lang_code & 0xff);
+    } else {
+      printf("%02x%02x ", attr->lang_code >> 8, attr->lang_code & 0xff);
+    }
+  } else {
+      printf("lang not specified ");
+  }
+  
+  printf("%d ", attr->zero1);
+  printf("%d ", attr->zero2);
+  printf("%d ", attr->code_extension);
+  
+  /* Is this correct?  should it not be subp_code_ext here instead? */
+  switch(attr->lang_extension) {
+  case 0:
+    printf("Not specified ");
+    break;
+  case 1:
+    printf("Caption with normal size character ");
+    break;
+  case 2:
+    printf("Caption with bigger size character ");
+    break;
+  case 3: 
+    printf("Caption for children ");
+    break;
+  case 4:
+    printf("reserved ");
+    break;
+  case 5:
+    printf("Closed Caption with normal size character ");
+    break;
+  case 6:
+    printf("Closed Caption with bigger size character ");
+    break;
+  case 7:
+    printf("Closed Caption for children ");
+    break;
+  case 8:
+    printf("reserved ");
+    break;
+  case 9:
+    printf("Forced Caption");
+    break;
+  case 10:
+    printf("reserved ");
+    break;
+  case 11:
+    printf("reserved ");
+    break;
+  case 12:
+    printf("reserved ");
+    break;
+  case 13:
+    printf("Director's comments with normal size character ");
+    break;
+  case 14:
+    printf("Director's comments with bigger size character ");
+    break;
+  case 15:
+    printf("Director's comments for children ");
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+
+}
+
+
+static void ifoPrint_USER_OPS(user_ops_t *user_ops) {
+  uint32_t uops;
+  unsigned char *ptr = (unsigned char *)user_ops;
+  
+  uops  = (*ptr++ << 24);
+  uops |= (*ptr++ << 16);
+  uops |= (*ptr++ << 8);
+  uops |= (*ptr++);
+  
+  if(uops == 0) {
+    printf("None\n");
+  } else if(uops == 0x01ffffff) {
+    printf("All\n");
+  } else {
+    if(user_ops->title_or_time_play)
+      printf("Title or Time Play, ");
+    if(user_ops->chapter_search_or_play)
+      printf("Chapter Search or Play, ");
+    if(user_ops->title_play)
+      printf("Title Play, ");
+    if(user_ops->stop)
+      printf("Stop, ");
+    if(user_ops->go_up)
+      printf("Go Up, ");
+    if(user_ops->time_or_chapter_search)
+      printf("Time or Chapter Search, ");
+    if(user_ops->prev_or_top_pg_search)
+      printf("Prev or Top PG Search, ");
+    if(user_ops->next_pg_search)
+      printf("Next PG Search, ");
+    if(user_ops->forward_scan)
+      printf("Forward Scan, ");
+    if(user_ops->backward_scan)
+      printf("Backward Scan, ");
+    if(user_ops->title_menu_call)
+      printf("Title Menu Call, ");
+    if(user_ops->root_menu_call)
+      printf("Root Menu Call, ");
+    if(user_ops->subpic_menu_call)
+      printf("SubPic Menu Call, ");
+    if(user_ops->audio_menu_call)
+      printf("Audio Menu Call, ");
+    if(user_ops->angle_menu_call)
+      printf("Angle Menu Call, ");
+    if(user_ops->chapter_menu_call)
+      printf("Chapter Menu Call, ");
+    if(user_ops->resume)
+      printf("Resume, ");
+    if(user_ops->button_select_or_activate)
+      printf("Button Select or Activate, ");
+    if(user_ops->still_off)
+      printf("Still Off, ");
+    if(user_ops->pause_on)
+      printf("Pause On, ");
+    if(user_ops->audio_stream_change)
+      printf("Audio Stream Change, ");
+    if(user_ops->subpic_stream_change)
+      printf("SubPic Stream Change, ");
+    if(user_ops->angle_change)
+      printf("Angle Change, ");
+    if(user_ops->karaoke_audio_pres_mode_change)
+      printf("Karaoke Audio Pres Mode Change, ");
+    if(user_ops->video_pres_mode_change)
+      printf("Video Pres Mode Change, ");
+    printf("\n");
+  }
+}
+
+
+void ifoPrint_VMGI_MAT(vmgi_mat_t *vmgi_mat) {
+  
+  printf("VMG Identifier: %.12s\n", vmgi_mat->vmg_identifier);
+  printf("Last Sector of VMG: %08x\n", vmgi_mat->vmg_last_sector);
+  printf("Last Sector of VMGI: %08x\n", vmgi_mat->vmgi_last_sector);
+  printf("Specification version number: %01x.%01x\n", 
+	 vmgi_mat->specification_version >> 4, 
+	 vmgi_mat->specification_version & 0xf);
+  /* Byte 2 of 'VMG Category' (00xx0000) is the Region Code */
+  printf("VMG Category: %08x\n", vmgi_mat->vmg_category);
+  printf("VMG Number of Volumes: %i\n", vmgi_mat->vmg_nr_of_volumes);
+  printf("VMG This Volume: %i\n", vmgi_mat->vmg_this_volume_nr);
+  printf("Disc side %i\n", vmgi_mat->disc_side);
+  printf("VMG Number of Title Sets %i\n", vmgi_mat->vmg_nr_of_title_sets);
+  printf("Provider ID: %.32s\n", vmgi_mat->provider_identifier);
+  printf("VMG POS Code: %08x", (uint32_t)(vmgi_mat->vmg_pos_code >> 32));
+  printf("%08x\n", (uint32_t)vmgi_mat->vmg_pos_code);
+  printf("End byte of VMGI_MAT: %08x\n", vmgi_mat->vmgi_last_byte);
+  printf("Start byte of First Play PGC FP PGC: %08x\n", 
+	 vmgi_mat->first_play_pgc);
+  printf("Start sector of VMGM_VOBS: %08x\n", vmgi_mat->vmgm_vobs);
+  printf("Start sector of TT_SRPT: %08x\n", vmgi_mat->tt_srpt);
+  printf("Start sector of VMGM_PGCI_UT: %08x\n", vmgi_mat->vmgm_pgci_ut);
+  printf("Start sector of PTL_MAIT: %08x\n", vmgi_mat->ptl_mait);
+  printf("Start sector of VTS_ATRT: %08x\n", vmgi_mat->vts_atrt);
+  printf("Start sector of TXTDT_MG: %08x\n", vmgi_mat->txtdt_mgi);
+  printf("Start sector of VMGM_C_ADT: %08x\n", vmgi_mat->vmgm_c_adt);
+  printf("Start sector of VMGM_VOBU_ADMAP: %08x\n", 
+	 vmgi_mat->vmgm_vobu_admap);
+  printf("Video attributes of VMGM_VOBS: ");
+  ifoPrint_video_attributes(&vmgi_mat->vmgm_video_attr);
+  printf("\n");
+  printf("VMGM Number of Audio attributes: %i\n", 
+	 vmgi_mat->nr_of_vmgm_audio_streams);
+  if(vmgi_mat->nr_of_vmgm_audio_streams > 0) {
+    printf("\tstream %i status: ", 1);
+    ifoPrint_audio_attributes(&vmgi_mat->vmgm_audio_attr);
+    printf("\n");
+  }
+  printf("VMGM Number of Sub-picture attributes: %i\n", 
+	 vmgi_mat->nr_of_vmgm_subp_streams);
+  if(vmgi_mat->nr_of_vmgm_subp_streams > 0) {
+    printf("\tstream %2i status: ", 1);
+    ifoPrint_subp_attributes(&vmgi_mat->vmgm_subp_attr);
+    printf("\n");
+  }
+}
+
+
+void ifoPrint_VTSI_MAT(vtsi_mat_t *vtsi_mat) {
+  int i;
+
+  printf("VTS Identifier: %.12s\n", vtsi_mat->vts_identifier);
+  printf("Last Sector of VTS: %08x\n", vtsi_mat->vts_last_sector);
+  printf("Last Sector of VTSI: %08x\n", vtsi_mat->vtsi_last_sector);
+  printf("Specification version number: %01x.%01x\n", 
+	 vtsi_mat->specification_version>>4, 
+	 vtsi_mat->specification_version&0xf);
+  printf("VTS Category: %08x\n", vtsi_mat->vts_category);
+  printf("End byte of VTSI_MAT: %08x\n", vtsi_mat->vtsi_last_byte);
+  printf("Start sector of VTSM_VOBS:  %08x\n", vtsi_mat->vtsm_vobs);
+  printf("Start sector of VTSTT_VOBS: %08x\n", vtsi_mat->vtstt_vobs);
+  printf("Start sector of VTS_PTT_SRPT: %08x\n", vtsi_mat->vts_ptt_srpt);
+  printf("Start sector of VTS_PGCIT:    %08x\n", vtsi_mat->vts_pgcit);
+  printf("Start sector of VTSM_PGCI_UT: %08x\n", vtsi_mat->vtsm_pgci_ut);
+  printf("Start sector of VTS_TMAPT:    %08x\n", vtsi_mat->vts_tmapt);
+  printf("Start sector of VTSM_C_ADT:      %08x\n", vtsi_mat->vtsm_c_adt);
+  printf("Start sector of VTSM_VOBU_ADMAP: %08x\n",vtsi_mat->vtsm_vobu_admap);
+  printf("Start sector of VTS_C_ADT:       %08x\n", vtsi_mat->vts_c_adt);
+  printf("Start sector of VTS_VOBU_ADMAP:  %08x\n", vtsi_mat->vts_vobu_admap);
+
+  printf("Video attributes of VTSM_VOBS: ");
+  ifoPrint_video_attributes(&vtsi_mat->vtsm_video_attr);
+  printf("\n");
+  
+  printf("VTSM Number of Audio attributes: %i\n", 
+	 vtsi_mat->nr_of_vtsm_audio_streams);
+  if(vtsi_mat->nr_of_vtsm_audio_streams > 0) {
+    printf("\tstream %i status: ", 1);
+    ifoPrint_audio_attributes(&vtsi_mat->vtsm_audio_attr);
+    printf("\n");
+  }
+  
+  printf("VTSM Number of Sub-picture attributes: %i\n", 
+	 vtsi_mat->nr_of_vtsm_subp_streams);
+  if(vtsi_mat->nr_of_vtsm_subp_streams > 0) {
+    printf("\tstream %2i status: ", 1);
+    ifoPrint_subp_attributes(&vtsi_mat->vtsm_subp_attr);
+    printf("\n");
+  }
+  
+  printf("Video attributes of VTS_VOBS: ");
+  ifoPrint_video_attributes(&vtsi_mat->vts_video_attr);
+  printf("\n");
+  
+  printf("VTS Number of Audio attributes: %i\n", 
+	 vtsi_mat->nr_of_vts_audio_streams);
+  for(i = 0; i < vtsi_mat->nr_of_vts_audio_streams; i++) {
+    printf("\tstream %i status: ", i);
+    ifoPrint_audio_attributes(&vtsi_mat->vts_audio_attr[i]);
+    printf("\n");
+  }
+  
+  printf("VTS Number of Subpicture attributes: %i\n", 
+	 vtsi_mat->nr_of_vts_subp_streams);
+  for(i = 0; i < vtsi_mat->nr_of_vts_subp_streams; i++) {
+    printf("\tstream %2i status: ", i);
+    ifoPrint_subp_attributes(&vtsi_mat->vts_subp_attr[i]);
+    printf("\n");
+  }
+  
+  /* FIXME:  Add printing of MultiChannel Extension */
+}
+
+
+static void ifoPrint_PGC_COMMAND_TBL(pgc_command_tbl_t *cmd_tbl) {
+  int i;
+  
+  if(cmd_tbl == NULL) {
+    printf("No Command table present\n");
+    return;
+  }
+  
+  printf("Number of Pre commands: %i\n", cmd_tbl->nr_of_pre);
+  for(i = 0; i < cmd_tbl->nr_of_pre; i++) {
+    ifoPrint_CMD(i, &cmd_tbl->pre_cmds[i]);
+  }
+
+  printf("Number of Post commands: %i\n", cmd_tbl->nr_of_post);
+  for(i = 0; i < cmd_tbl->nr_of_post; i++) {
+    ifoPrint_CMD(i, &cmd_tbl->post_cmds[i]);
+  }
+
+  printf("Number of Cell commands: %i\n", cmd_tbl->nr_of_cell);
+  for(i = 0; i < cmd_tbl->nr_of_cell; i++) {
+    ifoPrint_CMD(i, &cmd_tbl->cell_cmds[i]);
+  }
+}
+
+
+static void ifoPrint_PGC_PROGRAM_MAP(pgc_program_map_t *program_map, int nr) {
+  int i;
+  
+  if(program_map == NULL) {
+    printf("No Program map present\n");
+    return;
+  }
+  
+  for(i = 0; i < nr; i++) {
+    printf("Program %3i Entry Cell: %3i\n", i + 1, program_map[i]);
+  }
+}
+
+
+static void ifoPrint_CELL_PLAYBACK(cell_playback_t *cell_playback, int nr) {
+  int i;
+  
+  if(cell_playback == NULL) {
+    printf("No Cell Playback info present\n");
+    return;
+  }
+  
+  for(i=0;i<nr;i++) {
+    printf("Cell: %3i ", i + 1);
+
+    ifoPrint_time(&cell_playback[i].playback_time);
+    printf("\t");
+
+    if(cell_playback[i].block_mode || cell_playback[i].block_type) {
+      const char *s;
+      switch(cell_playback[i].block_mode) {
+      case 0:
+	s = "not a"; break;
+      case 1:
+	s = "the first"; break;
+      case 2:
+      default:
+	s = ""; break;
+      case 3:
+	s = "last"; break;
+      }
+      printf("%s cell in the block ", s);
+      
+      switch(cell_playback[i].block_type) {
+      case 0:
+	printf("not part of the block ");
+	break;
+      case 1:
+	printf("angle block ");
+	break;
+      case 2:
+      case 3:
+	printf("(send bug repport) ");
+	break;
+      }
+    }
+    if(cell_playback[i].seamless_play)
+      printf("presented seamlessly ");
+    if(cell_playback[i].interleaved)
+      printf("cell is interleaved ");
+    if(cell_playback[i].stc_discontinuity)
+      printf("STC_discontinuty ");
+    if(cell_playback[i].seamless_angle)
+      printf("only seamless angle ");
+    if(cell_playback[i].restricted)
+      printf("restricted cell ");
+    
+    if(cell_playback[i].still_time)
+      printf("still time %d ", cell_playback[i].still_time);
+    if(cell_playback[i].cell_cmd_nr)
+      printf("cell command %d", cell_playback[i].cell_cmd_nr);
+    
+    printf("\n\tStart sector: %08x\tFirst ILVU end  sector: %08x\n", 
+	   cell_playback[i].first_sector, 
+	   cell_playback[i].first_ilvu_end_sector);
+    printf("\tEnd   sector: %08x\tLast VOBU start sector: %08x\n", 
+	   cell_playback[i].last_sector, 
+	   cell_playback[i].last_vobu_start_sector);
+  }
+}
+
+static void ifoPrint_CELL_POSITION(cell_position_t *cell_position, int nr) {
+  int i;
+  
+  if(cell_position == NULL) {
+    printf("No Cell Position info present\n");
+    return;
+  }
+  
+  for(i=0;i<nr;i++) {
+    printf("Cell: %3i has VOB ID: %3i, Cell ID: %3i\n", i + 1, 
+	   cell_position[i].vob_id_nr, cell_position[i].cell_nr);
+  }
+}
+
+
+void ifoPrint_PGC(pgc_t *pgc) {
+  int i;
+  
+  printf("Number of Programs: %i\n", pgc->nr_of_programs);
+  printf("Number of Cells: %i\n", pgc->nr_of_cells);
+  /* Check that time is 0:0:0:0 also if nr_of_programs==0 */
+  printf("Playback time: ");
+  ifoPrint_time(&pgc->playback_time); printf("\n");
+
+  /* If no programs/no time then does this mean anything? */
+  printf("Prohibited user operations: ");
+  ifoPrint_USER_OPS(&pgc->prohibited_ops);
+  
+    for(i = 0; i < 8; i++) {
+      if(pgc->audio_control[i] & 0x8000) { /* The 'is present' bit */
+	printf("Audio stream %i control: %04x\n", 
+	       i, pgc->audio_control[i]);
+      }
+    }
+  
+  for(i = 0; i < 32; i++) {
+    if(pgc->subp_control[i] & 0x80000000) { /* The 'is present' bit */
+      printf("Subpicture stream %2i control: %08x\n", 
+	     i, pgc->subp_control[i]);
+    }
+  }
+  
+  printf("Next PGC number: %i\n", pgc->next_pgc_nr);
+  printf("Prev PGC number: %i\n", pgc->prev_pgc_nr);
+  printf("GoUp PGC number: %i\n", pgc->goup_pgc_nr);
+  if(pgc->nr_of_programs != 0) {
+    printf("Still time: %i seconds (255=inf)\n", pgc->still_time);
+    if(pgc->pg_playback_mode == 0)
+      printf("PG Playback mode: Sequential\n");
+    else if(!(pgc->pg_playback_mode & 0x80))
+      printf("PG Playback mode: Random %i\n", pgc->pg_playback_mode);
+    else
+      printf("PG Playback mode: Shuffle %i\n", pgc->pg_playback_mode & 0x7f );
+  }
+  
+  if(pgc->nr_of_programs != 0) {
+    for(i = 0; i < 16; i++) {
+      printf("Color %2i: %08x\n", i, pgc->palette[i]);
+    }
+  }
+  
+  /* Memmory offsets to div. tables. */
+  ifoPrint_PGC_COMMAND_TBL(pgc->command_tbl);
+  ifoPrint_PGC_PROGRAM_MAP(pgc->program_map, pgc->nr_of_programs);
+  ifoPrint_CELL_PLAYBACK(pgc->cell_playback, pgc->nr_of_cells);
+  ifoPrint_CELL_POSITION(pgc->cell_position, pgc->nr_of_cells);
+}
+
+
+void ifoPrint_TT_SRPT(tt_srpt_t *tt_srpt) {
+  int i;
+  
+  printf("Number of TitleTrack search pointers: %i\n",
+	 tt_srpt->nr_of_srpts);
+  for(i=0;i<tt_srpt->nr_of_srpts;i++) {
+    printf("Title Track index %i\n", i + 1);
+    printf("\tTitle set number (VTS): %i", 
+	   tt_srpt->title[i].title_set_nr);
+    printf("\tVTS_TTN: %i\n", tt_srpt->title[i].vts_ttn);
+    printf("\tNumber of PTTs: %i\n", tt_srpt->title[i].nr_of_ptts);
+    printf("\tNumber of angles: %i\n", 
+	   tt_srpt->title[i].nr_of_angles);
+    printf("\tTitle playback type: %s%s%s%s%s%s%s\n",
+	   tt_srpt->title[i].pb_ty.multi_or_random_pgc_title ? 
+	   " One Random PGC Title or Multi PGC Title" : 
+	   " One Sequential PGC Title",
+	   tt_srpt->title[i].pb_ty.jlc_exists_in_cell_cmd ?
+	   "" : ", No Link/Jump/Call exists in Cell command",
+	   tt_srpt->title[i].pb_ty.jlc_exists_in_prepost_cmd ?
+	   "" : ", No Link/Jump/Call exists in Pre- and/or Post-command",
+	   tt_srpt->title[i].pb_ty.jlc_exists_in_button_cmd ?
+	   "" : ", No Link/Jump/Call exists in Button command",
+	   tt_srpt->title[i].pb_ty.jlc_exists_in_tt_dom ?
+	   "" : ", No Link/Jump/Call exists in TT_DOM",
+	   tt_srpt->title[i].pb_ty.chapter_search_or_play ?
+	   ", UOP1 (TT_Play and PTT_Search) prohibited" : "",
+	   tt_srpt->title[i].pb_ty.title_or_time_play ?
+	   ", UOP0 (Time_Play and Time_Search) prohibited" : ""
+	   );    
+    printf("\tParental ID field: %04x\n",
+	   tt_srpt->title[i].parental_id);
+    printf("\tTitle set starting sector %08x\n", 
+	   tt_srpt->title[i].title_set_sector);
+  }
+}
+
+
+void ifoPrint_VTS_PTT_SRPT(vts_ptt_srpt_t *vts_ptt_srpt) {
+  int i, j;
+  printf(" nr_of_srpts %i last byte %i\n", 
+	 vts_ptt_srpt->nr_of_srpts, 
+	 vts_ptt_srpt->last_byte);
+  for(i=0;i<vts_ptt_srpt->nr_of_srpts;i++) {
+    printf("\nVTS_PTT number %d has a offset %d relative to VTS_PTT_SRPT\n", 
+				i + 1, vts_ptt_srpt->ttu_offset[i]);
+    for(j=0;j<vts_ptt_srpt->title[i].nr_of_ptts;j++) {
+      printf("VTS_PTT_SRPT - Title %3i part %3i: PGC: %3i PG: %3i\n",
+	     i + 1, j + 1, 
+	     vts_ptt_srpt->title[i].ptt[j].pgcn,
+	     vts_ptt_srpt->title[i].ptt[j].pgn );
+    }
+  }
+}
+
+
+void ifoPrint_PTL_MAIT(ptl_mait_t *ptl_mait) {
+  int i, level, vts;
+  
+  printf("Number of Countries: %i\n", ptl_mait->nr_of_countries);
+  printf("Number of VTSs: %i\n", ptl_mait->nr_of_vtss);
+  printf("Last byte: %i\n", ptl_mait->last_byte);
+  
+  for(i = 0; i < ptl_mait->nr_of_countries; i++) {
+    
+    printf("Start byte: %i\n", ptl_mait->countries[i].pf_ptl_mai_start_byte);
+    printf("Parental Masks for country: %c%c\n",
+	   ptl_mait->countries[i].country_code >> 8,
+	   ptl_mait->countries[i].country_code & 0xff);
+    
+    for(vts = 0; vts <= ptl_mait->nr_of_vtss; vts++) {
+      if( vts == 0 ) {
+	printf("VMG    "); 
+      } else {
+	printf("VTS %2d ", vts);
+      }
+      for(level = 0; level < 8; level++) {
+	printf("%d: %04x  ", level,
+	       ptl_mait->countries[i].pf_ptl_mai[vts][level] );
+      }
+      printf("\n");
+    }
+  }
+}
+
+void ifoPrint_VTS_TMAPT(vts_tmapt_t *vts_tmapt) {
+  unsigned int timeunit;
+  int i, j;
+  
+  printf("Number of VTS_TMAPS: %i\n", vts_tmapt->nr_of_tmaps);
+  printf("Last byte: %i\n", vts_tmapt->last_byte);
+
+  for(i = 0; i < vts_tmapt->nr_of_tmaps; i++) {
+    printf("TMAP %i\n", i + 1);
+    printf("  offset %d relative to VTS_TMAPTI\n", vts_tmapt->tmap_offset[i]);
+    printf("  Time unit (seconds): %i\n", vts_tmapt->tmap[i].tmu);
+    printf("  Number of entries: %i\n", vts_tmapt->tmap[i].nr_of_entries);
+    timeunit = vts_tmapt->tmap[i].tmu;
+    for(j = 0; j < vts_tmapt->tmap[i].nr_of_entries; j++) {
+      unsigned int ac_time = timeunit * (j + 1);
+      printf("Time: %2i:%02i:%02i  VOBU Sector: 0x%08x %s\n", 
+	     ac_time / (60 * 60), (ac_time / 60) % 60, ac_time % 60,
+	     vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff,
+	     (vts_tmapt->tmap[i].map_ent[j] >> 31) ? "discontinuity" : "");
+    }
+  }
+}
+
+void ifoPrint_C_ADT(c_adt_t *c_adt) {
+  int i, entries;
+  
+  printf("Number of VOBs in this VOBS: %i\n", c_adt->nr_of_vobs);
+  //entries = c_adt->nr_of_vobs;
+  entries = (c_adt->last_byte + 1 - C_ADT_SIZE)/sizeof(c_adt_t);
+  
+  for(i = 0; i < entries; i++) {
+    printf("VOB ID: %3i, Cell ID: %3i   ", 
+	   c_adt->cell_adr_table[i].vob_id, c_adt->cell_adr_table[i].cell_id);
+    printf("Sector (first): 0x%08x   (last): 0x%08x\n",
+	   c_adt->cell_adr_table[i].start_sector, 
+	   c_adt->cell_adr_table[i].last_sector);
+  }
+}
+
+
+void ifoPrint_VOBU_ADMAP(vobu_admap_t *vobu_admap) {
+  int i, entries;
+  
+  entries = (vobu_admap->last_byte + 1 - VOBU_ADMAP_SIZE)/4;
+  for(i = 0; i < entries; i++) {
+    printf("VOBU %5i  First sector: 0x%08x\n", i + 1,
+	   vobu_admap->vobu_start_sectors[i]);
+  }
+}
+
+
+void ifoPrint_PGCIT(pgcit_t *pgcit) {
+  int i;
+  
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    printf("\nProgram (PGC): %3i\t", i + 1);
+    printf("PGC Category: Entry id 0x%02x, ", pgcit->pgci_srp[i].entry_id);
+    printf("Parental ID mask 0x%04x\n", pgcit->pgci_srp[i].ptl_id_mask);
+    ifoPrint_PGC(pgcit->pgci_srp[i].pgc);
+  }
+}
+
+
+void ifoPrint_PGCI_UT(pgci_ut_t *pgci_ut) {
+  int i;
+  
+  printf("Number of Menu Language Units (PGCI_LU): %3i\n", pgci_ut->nr_of_lus);
+  for(i = 0; i < pgci_ut->nr_of_lus; i++) {
+    printf("\nMenu Language Code: %c%c (%c)\n",
+	   pgci_ut->lu[i].lang_code >> 8,
+	   pgci_ut->lu[i].lang_code & 0xff,
+	   pgci_ut->lu[i].lang_extension ? pgci_ut->lu[i].lang_extension :' ');
+    printf("Menu Existence: %02x\n", pgci_ut->lu[i].exists);
+    ifoPrint_PGCIT(pgci_ut->lu[i].pgcit);
+  }
+}
+
+
+static void ifoPrint_VTS_ATTRIBUTES(vts_attributes_t *vts_attributes) {
+  int i;
+  
+  printf("VTS_CAT Application type: %08x\n", vts_attributes->vts_cat);
+ 
+  printf("Video attributes of VTSM_VOBS: ");
+  ifoPrint_video_attributes(&vts_attributes->vtsm_vobs_attr);
+  printf("\n");
+  printf("Number of Audio streams: %i\n", 
+	 vts_attributes->nr_of_vtsm_audio_streams);
+  if(vts_attributes->nr_of_vtsm_audio_streams > 0) {
+    printf("\tstream %i attributes: ", 1);
+    ifoPrint_audio_attributes(&vts_attributes->vtsm_audio_attr);
+    printf("\n");
+  }
+  printf("Number of Subpicture streams: %i\n", 
+	 vts_attributes->nr_of_vtsm_subp_streams);
+  if(vts_attributes->nr_of_vtsm_subp_streams > 0) {
+    printf("\tstream %2i attributes: ", 1);
+    ifoPrint_subp_attributes(&vts_attributes->vtsm_subp_attr);
+    printf("\n");
+  }
+   
+  printf("Video attributes of VTSTT_VOBS: ");
+  ifoPrint_video_attributes(&vts_attributes->vtstt_vobs_video_attr);
+  printf("\n");
+  printf("Number of Audio streams: %i\n", 
+	 vts_attributes->nr_of_vtstt_audio_streams);
+  for(i = 0; i < vts_attributes->nr_of_vtstt_audio_streams; i++) {
+    printf("\tstream %i attributes: ", i);
+    ifoPrint_audio_attributes(&vts_attributes->vtstt_audio_attr[i]);
+    printf("\n");
+  }
+  
+  printf("Number of Subpicture streams: %i\n", 
+	 vts_attributes->nr_of_vtstt_subp_streams);
+  for(i = 0; i < vts_attributes->nr_of_vtstt_subp_streams; i++) {
+    printf("\tstream %2i attributes: ", i);    
+    ifoPrint_subp_attributes(&vts_attributes->vtstt_subp_attr[i]);
+    printf("\n");
+  }
+}
+
+
+void ifoPrint_VTS_ATRT(vts_atrt_t *vts_atrt) {
+  int i;
+  
+  printf("Number of Video Title Sets: %3i\n", vts_atrt->nr_of_vtss);
+  for(i = 0; i < vts_atrt->nr_of_vtss; i++) {
+    printf("\nVideo Title Set %i\n", i + 1);
+    printf("  offset %d relative to VMG_VTS_ATRT\n", 
+	   vts_atrt->vts_atrt_offsets[i]);
+    ifoPrint_VTS_ATTRIBUTES(&vts_atrt->vts[i]);
+  }
+}
+
+
+void ifoPrint(dvd_reader_t *dvd, int title) {
+  ifo_handle_t *ifohandle;
+
+  ifohandle = ifoOpen(dvd, title);
+  if(!ifohandle) {
+    fprintf(stderr, "Can't open info file for title %d\n", title);
+    return;
+  }
+  
+  
+  if(ifohandle->vmgi_mat) {
+
+    printf("VMG top level\n-------------\n");
+    ifoPrint_VMGI_MAT(ifohandle->vmgi_mat);
+
+    printf("\nFirst Play PGC\n--------------\n");
+    ifoPrint_PGC(ifohandle->first_play_pgc);
+
+    printf("\nTitle Track search pointer table\n");
+    printf(  "------------------------------------------------\n");
+    ifoPrint_TT_SRPT(ifohandle->tt_srpt);
+
+    printf("\nMenu PGCI Unit table\n");
+    printf(  "--------------------\n");
+    if(ifohandle->pgci_ut) {
+      ifoPrint_PGCI_UT(ifohandle->pgci_ut);
+    } else {
+      printf("No PGCI Unit table present\n");
+    }
+
+    printf("\nParental Manegment Information table\n");
+    printf(  "------------------------------------\n");
+    if(ifohandle->ptl_mait) {
+      ifoPrint_PTL_MAIT(ifohandle->ptl_mait);
+    } else {
+      printf("No Parental Management Information present\n");
+    }
+
+    printf("\nVideo Title Set Attribute Table\n");
+    printf(  "-------------------------------\n");
+    ifoPrint_VTS_ATRT(ifohandle->vts_atrt);
+    
+    printf("\nText Data Manager Information\n");
+    printf(  "-----------------------------\n");
+    if(ifohandle->txtdt_mgi) {
+      //ifoPrint_TXTDT_MGI(&(vmgi->txtdt_mgi));
+    } else {
+      printf("No Text Data Manager Information present\n");
+    }
+
+    printf("\nMenu Cell Adress table\n");
+    printf(  "-----------------\n");
+    if(ifohandle->menu_c_adt) {
+      ifoPrint_C_ADT(ifohandle->menu_c_adt);
+    } else {
+      printf("No Menu Cell Adress table present\n");
+    }
+
+    printf("\nVideo Manager Menu VOBU address map\n");
+    printf(  "-----------------\n");
+    if(ifohandle->menu_vobu_admap) {
+      ifoPrint_VOBU_ADMAP(ifohandle->menu_vobu_admap);
+    } else {
+      printf("No Menu VOBU address map present\n");   
+    }
+  }
+
+
+  if(ifohandle->vtsi_mat) {
+
+    printf("VTS top level\n-------------\n");
+    ifoPrint_VTSI_MAT(ifohandle->vtsi_mat);
+
+    printf("\nPart of Title Track search pointer table\n");
+    printf(  "----------------------------------------------\n");
+    ifoPrint_VTS_PTT_SRPT(ifohandle->vts_ptt_srpt);
+
+    printf("\nPGCI Unit table\n");
+    printf(  "--------------------\n");
+    ifoPrint_PGCIT(ifohandle->vts_pgcit);
+
+    printf("\nMenu PGCI Unit table\n");
+    printf(  "--------------------\n");
+    if(ifohandle->pgci_ut) {
+      ifoPrint_PGCI_UT(ifohandle->pgci_ut);
+    } else {
+      printf("No Menu PGCI Unit table present\n");
+    }
+    
+    printf("\nTime Search table\n");
+    printf(  "-----------------\n");
+    if(ifohandle->vts_tmapt) {
+      ifoPrint_VTS_TMAPT(ifohandle->vts_tmapt);
+    } else {
+      printf("No Time Search table present\n");
+    }
+
+    printf("\nMenu Cell Adress table\n");
+    printf(  "-----------------\n");
+    if(ifohandle->menu_c_adt) {
+      ifoPrint_C_ADT(ifohandle->menu_c_adt);
+    } else {
+      printf("No Cell Adress table present\n");
+    }
+
+    printf("\nVideo Title Set Menu VOBU address map\n");
+    printf(  "-----------------\n");
+    if(ifohandle->menu_vobu_admap) {
+      ifoPrint_VOBU_ADMAP(ifohandle->menu_vobu_admap);
+    } else {
+      printf("No Menu VOBU address map present\n");
+    }
+
+    printf("\nCell Adress table\n");
+    printf(  "-----------------\n");
+    ifoPrint_C_ADT(ifohandle->vts_c_adt);
+
+    printf("\nVideo Title Set VOBU address map\n");
+    printf(  "-----------------\n");
+    ifoPrint_VOBU_ADMAP(ifohandle->vts_vobu_admap);
+  } 
+
+  ifoClose(ifohandle);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ifo_print.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,59 @@
+#ifndef IFO_PRINT_H_INCLUDED
+#define IFO_PRINT_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001 Björn Englund <d4bjorn@dtek.chalmers.se>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <dvdread/ifo_types.h>
+#include <dvdread/dvd_reader.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This file provides example functions for printing information about the IFO
+ * file to stdout.
+ */
+
+/**
+ * Print the complete parsing information for the given file.
+ */
+
+/* ifoPrint(dvd, title); */
+void ifoPrint(dvd_reader_t *, int);
+
+void ifoPrint_VMGI_MAT(vmgi_mat_t *);
+void ifoPrint_VTSI_MAT(vtsi_mat_t *);
+
+void ifoPrint_PTL_MAIT(ptl_mait_t *);
+void ifoPrint_VTS_ATRT(vts_atrt_t *);
+void ifoPrint_TT_SRPT(tt_srpt_t *);
+void ifoPrint_VTS_PTT_SRPT(vts_ptt_srpt_t *);
+void ifoPrint_PGC(pgc_t *);
+void ifoPrint_PGCIT(pgcit_t *);
+void ifoPrint_PGCI_UT(pgci_ut_t *);
+void ifoPrint_VTS_TMAPT(vts_tmapt_t *);
+void ifoPrint_C_ADT(c_adt_t *);
+void ifoPrint_VOBU_ADMAP(vobu_admap_t *);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* IFO_PRINT_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ifo_read.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,2022 @@
+/*
+ * Copyright (C) 2000, 2001, 2002, 2003
+ *               Björn Englund <d4bjorn@dtek.chalmers.se>, 
+ *               Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "bswap.h"
+#include "ifo_types.h"
+#include "ifo_read.h"
+#include "dvd_reader.h"
+#include "dvdread_internal.h"
+
+#ifndef DVD_BLOCK_LEN
+#define DVD_BLOCK_LEN 2048
+#endif
+
+#ifndef NDEBUG
+#define CHECK_ZERO0(arg) \
+  if(arg != 0) { \
+    fprintf(stderr, "*** Zero check failed in %s:%i\n    for %s = 0x%x\n", \
+            __FILE__, __LINE__, # arg, arg); \
+  }
+#define CHECK_ZERO(arg) \
+  if(memcmp(my_friendly_zeros, &arg, sizeof(arg))) { \
+    unsigned int i_CZ; \
+    fprintf(stderr, "*** Zero check failed in %s:%i\n    for %s = 0x", \
+            __FILE__, __LINE__, # arg ); \
+    for(i_CZ = 0; i_CZ < sizeof(arg); i_CZ++) \
+      fprintf(stderr, "%02x", *((uint8_t *)&arg + i_CZ)); \
+    fprintf(stderr, "\n"); \
+  }
+static const uint8_t my_friendly_zeros[2048];
+#else
+#define CHECK_ZERO0(arg) (void)(arg)
+#define CHECK_ZERO(arg) (void)(arg)
+#endif
+
+
+/* Prototypes for internal functions */
+static int ifoRead_VMG(ifo_handle_t *ifofile);
+static int ifoRead_VTS(ifo_handle_t *ifofile);
+static int ifoRead_PGC(ifo_handle_t *ifofile, pgc_t *pgc, unsigned int offset);
+static int ifoRead_PGC_COMMAND_TBL(ifo_handle_t *ifofile, 
+                                   pgc_command_tbl_t *cmd_tbl, 
+				   unsigned int offset);
+static int ifoRead_PGC_PROGRAM_MAP(ifo_handle_t *ifofile, 
+                                   pgc_program_map_t *program_map, 
+                                   unsigned int nr, unsigned int offset);
+static int ifoRead_CELL_PLAYBACK_TBL(ifo_handle_t *ifofile, 
+                                     cell_playback_t *cell_playback, 
+                                     unsigned int nr, unsigned int offset);
+static int ifoRead_CELL_POSITION_TBL(ifo_handle_t *ifofile, 
+                                     cell_position_t *cell_position, 
+                                     unsigned int nr, unsigned int offset);
+static int ifoRead_VTS_ATTRIBUTES(ifo_handle_t *ifofile, 
+                                  vts_attributes_t *vts_attributes, 
+                                  unsigned int offset);
+static int ifoRead_C_ADT_internal(ifo_handle_t *ifofile, c_adt_t *c_adt, 
+                                  unsigned int sector);
+static int ifoRead_VOBU_ADMAP_internal(ifo_handle_t *ifofile, 
+                                       vobu_admap_t *vobu_admap, 
+				       unsigned int sector);
+static int ifoRead_PGCIT_internal(ifo_handle_t *ifofile, pgcit_t *pgcit, 
+                                  unsigned int offset);
+
+static void ifoFree_PGC(pgc_t *pgc);
+static void ifoFree_PGC_COMMAND_TBL(pgc_command_tbl_t *cmd_tbl);
+static void ifoFree_PGCIT_internal(pgcit_t *pgcit);
+
+
+static inline int DVDFileSeek_( dvd_file_t *dvd_file, uint32_t offset ) {
+  return (DVDFileSeek(dvd_file, (int)offset) == (int)offset);
+}
+
+
+ifo_handle_t *ifoOpen(dvd_reader_t *dvd, int title) {
+  ifo_handle_t *ifofile;
+
+  ifofile = (ifo_handle_t *)malloc(sizeof(ifo_handle_t));
+  if(!ifofile)
+    return 0;
+
+  memset(ifofile, 0, sizeof(ifo_handle_t));
+
+  ifofile->file = DVDOpenFile(dvd, title, DVD_READ_INFO_FILE);
+  if(!ifofile->file) /* Should really catch any error and try to fallback */
+    ifofile->file = DVDOpenFile(dvd, title, DVD_READ_INFO_BACKUP_FILE);
+  if(!ifofile->file) {
+    if(title) {
+      fprintf(stderr, "libdvdread: Can't open file VTS_%02d_0.IFO.\n", title);
+    } else {
+      fprintf(stderr, "libdvdread: Can't open file VIDEO_TS.IFO.\n");
+    }
+    free(ifofile);
+    return 0;
+  }
+
+  /* First check if this is a VMGI file. */
+  if(ifoRead_VMG(ifofile)) {
+
+    /* These are both mandatory. */
+    if(!ifoRead_FP_PGC(ifofile) || !ifoRead_TT_SRPT(ifofile)) {
+      fprintf(stderr, "libdvdread: Invalid main menu IFO (VIDEO_TS.IFO).\n");
+      ifoClose(ifofile);
+      return 0;
+    }
+
+    ifoRead_PGCI_UT(ifofile);
+    ifoRead_PTL_MAIT(ifofile);
+
+    /* This is also mandatory. */
+    if(!ifoRead_VTS_ATRT(ifofile)) {
+      fprintf(stderr, "libdvdread: Invalid main menu IFO (VIDEO_TS.IFO).\n");
+      ifoClose(ifofile);
+      return 0;
+    }
+
+    ifoRead_TXTDT_MGI(ifofile);
+    ifoRead_C_ADT(ifofile);
+    ifoRead_VOBU_ADMAP(ifofile);
+
+    return ifofile;
+  }
+
+  if(ifoRead_VTS(ifofile)) {
+
+    if(!ifoRead_VTS_PTT_SRPT(ifofile) || !ifoRead_PGCIT(ifofile)) {
+      fprintf(stderr, "libdvdread: Invalid title IFO (VTS_%02d_0.IFO).\n",
+              title);
+      ifoClose(ifofile);
+      return 0;
+    }
+
+
+    ifoRead_PGCI_UT(ifofile);
+    ifoRead_VTS_TMAPT(ifofile);
+    ifoRead_C_ADT(ifofile);
+    ifoRead_VOBU_ADMAP(ifofile);
+
+    if(!ifoRead_TITLE_C_ADT(ifofile) || !ifoRead_TITLE_VOBU_ADMAP(ifofile)) {
+      fprintf(stderr, "libdvdread: Invalid title IFO (VTS_%02d_0.IFO).\n",
+              title);
+      ifoClose(ifofile);
+      return 0;
+    }
+
+    return ifofile;
+  }
+
+  if(title) {
+    fprintf(stderr, "libdvdread: Invalid IFO for title %d (VTS_%02d_0.IFO).\n",
+	    title, title);
+  } else {
+    fprintf(stderr, "libdvdread: Invalid IFO for VMGM (VIDEO_TS.IFO).\n");
+  }
+  ifoClose(ifofile);
+  return 0;
+}
+
+
+ifo_handle_t *ifoOpenVMGI(dvd_reader_t *dvd) {
+  ifo_handle_t *ifofile;
+
+  ifofile = (ifo_handle_t *)malloc(sizeof(ifo_handle_t));
+  if(!ifofile)
+    return 0;
+
+  memset(ifofile, 0, sizeof(ifo_handle_t));
+
+  ifofile->file = DVDOpenFile(dvd, 0, DVD_READ_INFO_FILE);
+  if(!ifofile->file) /* Should really catch any error and try to fallback */
+    ifofile->file = DVDOpenFile(dvd, 0, DVD_READ_INFO_BACKUP_FILE);
+  if(!ifofile->file) {
+    fprintf(stderr, "libdvdread: Can't open file VIDEO_TS.IFO.\n");
+    free(ifofile);
+    return 0;
+  }
+
+  if(ifoRead_VMG(ifofile))
+    return ifofile;
+
+  fprintf(stderr, "libdvdread: Invalid main menu IFO (VIDEO_TS.IFO).\n");
+  ifoClose(ifofile);
+  return 0;
+}
+
+
+ifo_handle_t *ifoOpenVTSI(dvd_reader_t *dvd, int title) {
+  ifo_handle_t *ifofile;
+  
+  ifofile = (ifo_handle_t *)malloc(sizeof(ifo_handle_t));
+  if(!ifofile)
+    return 0;
+
+  memset(ifofile, 0, sizeof(ifo_handle_t));
+  
+  if(title <= 0 || title > 99) {
+    fprintf(stderr, "libdvdread: ifoOpenVTSI invalid title (%d).\n", title);
+    free(ifofile);
+    return 0;
+  }
+    
+  ifofile->file = DVDOpenFile(dvd, title, DVD_READ_INFO_FILE);
+  if(!ifofile->file) /* Should really catch any error and try to fallback */
+    ifofile->file = DVDOpenFile(dvd, title, DVD_READ_INFO_BACKUP_FILE);
+  if(!ifofile->file) {
+    fprintf(stderr, "libdvdread: Can't open file VTS_%02d_0.IFO.\n", title);
+    free(ifofile);
+    return 0;
+  }
+
+  ifoRead_VTS(ifofile);
+  if(ifofile->vtsi_mat)
+    return ifofile;
+
+  fprintf(stderr, "libdvdread: Invalid IFO for title %d (VTS_%02d_0.IFO).\n",
+          title, title);
+  ifoClose(ifofile);
+  return 0;
+}
+
+
+void ifoClose(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  ifoFree_VOBU_ADMAP(ifofile);
+  ifoFree_TITLE_VOBU_ADMAP(ifofile);
+  ifoFree_C_ADT(ifofile);
+  ifoFree_TITLE_C_ADT(ifofile);
+  ifoFree_TXTDT_MGI(ifofile);
+  ifoFree_VTS_ATRT(ifofile);
+  ifoFree_PTL_MAIT(ifofile);
+  ifoFree_PGCI_UT(ifofile);
+  ifoFree_TT_SRPT(ifofile);
+  ifoFree_FP_PGC(ifofile);
+  ifoFree_PGCIT(ifofile);
+  ifoFree_VTS_PTT_SRPT(ifofile);
+
+  if(ifofile->vmgi_mat)
+    free(ifofile->vmgi_mat);
+
+  if(ifofile->vtsi_mat)
+    free(ifofile->vtsi_mat);
+
+  DVDCloseFile(ifofile->file);
+  ifofile->file = 0;
+  free(ifofile);
+  ifofile = 0;
+}
+
+
+static int ifoRead_VMG(ifo_handle_t *ifofile) {
+  vmgi_mat_t *vmgi_mat;
+
+  vmgi_mat = (vmgi_mat_t *)malloc(sizeof(vmgi_mat_t));
+  if(!vmgi_mat)
+    return 0;
+
+  ifofile->vmgi_mat = vmgi_mat;
+
+  if(!DVDFileSeek_(ifofile->file, 0)) {
+    free(ifofile->vmgi_mat);
+    ifofile->vmgi_mat = 0;
+    return 0;
+  }
+
+  if(!DVDReadBytes(ifofile->file, vmgi_mat, sizeof(vmgi_mat_t))) {
+    free(ifofile->vmgi_mat);
+    ifofile->vmgi_mat = 0;
+    return 0;
+  }
+
+  if(strncmp("DVDVIDEO-VMG", vmgi_mat->vmg_identifier, 12) != 0) {
+    free(ifofile->vmgi_mat);
+    ifofile->vmgi_mat = 0;
+    return 0;
+  }
+  
+  B2N_32(vmgi_mat->vmg_last_sector);
+  B2N_32(vmgi_mat->vmgi_last_sector);
+  B2N_32(vmgi_mat->vmg_category);
+  B2N_16(vmgi_mat->vmg_nr_of_volumes);
+  B2N_16(vmgi_mat->vmg_this_volume_nr);
+  B2N_16(vmgi_mat->vmg_nr_of_title_sets);
+  B2N_64(vmgi_mat->vmg_pos_code);
+  B2N_32(vmgi_mat->vmgi_last_byte);
+  B2N_32(vmgi_mat->first_play_pgc);
+  B2N_32(vmgi_mat->vmgm_vobs);
+  B2N_32(vmgi_mat->tt_srpt);
+  B2N_32(vmgi_mat->vmgm_pgci_ut);
+  B2N_32(vmgi_mat->ptl_mait);
+  B2N_32(vmgi_mat->vts_atrt);
+  B2N_32(vmgi_mat->txtdt_mgi);
+  B2N_32(vmgi_mat->vmgm_c_adt);
+  B2N_32(vmgi_mat->vmgm_vobu_admap);
+  B2N_16(vmgi_mat->vmgm_audio_attr.lang_code);
+  B2N_16(vmgi_mat->vmgm_subp_attr.lang_code);
+
+
+  CHECK_ZERO(vmgi_mat->zero_1);
+  CHECK_ZERO(vmgi_mat->zero_2);
+  CHECK_ZERO(vmgi_mat->zero_3);
+  CHECK_ZERO(vmgi_mat->zero_4);
+  CHECK_ZERO(vmgi_mat->zero_5);
+  CHECK_ZERO(vmgi_mat->zero_6);
+  CHECK_ZERO(vmgi_mat->zero_7);
+  CHECK_ZERO(vmgi_mat->zero_8);
+  CHECK_ZERO(vmgi_mat->zero_9);
+  CHECK_ZERO(vmgi_mat->zero_10);  
+  CHECK_VALUE(vmgi_mat->vmg_last_sector != 0);
+  CHECK_VALUE(vmgi_mat->vmgi_last_sector != 0);
+  CHECK_VALUE(vmgi_mat->vmgi_last_sector * 2 <= vmgi_mat->vmg_last_sector);
+  CHECK_VALUE(vmgi_mat->vmgi_last_sector * 2 <= vmgi_mat->vmg_last_sector);
+  CHECK_VALUE(vmgi_mat->vmg_nr_of_volumes != 0);
+  CHECK_VALUE(vmgi_mat->vmg_this_volume_nr != 0);
+  CHECK_VALUE(vmgi_mat->vmg_this_volume_nr <= vmgi_mat->vmg_nr_of_volumes);
+  CHECK_VALUE(vmgi_mat->disc_side == 1 || vmgi_mat->disc_side == 2);
+  CHECK_VALUE(vmgi_mat->vmg_nr_of_title_sets != 0);
+  CHECK_VALUE(vmgi_mat->vmgi_last_byte >= 341);
+  CHECK_VALUE(vmgi_mat->vmgi_last_byte / DVD_BLOCK_LEN <= 
+         vmgi_mat->vmgi_last_sector);
+  /* It seems that first_play_pgc is optional. */
+  CHECK_VALUE(vmgi_mat->first_play_pgc < vmgi_mat->vmgi_last_byte);
+  CHECK_VALUE(vmgi_mat->vmgm_vobs == 0 || 
+        (vmgi_mat->vmgm_vobs > vmgi_mat->vmgi_last_sector &&
+         vmgi_mat->vmgm_vobs < vmgi_mat->vmg_last_sector));
+  CHECK_VALUE(vmgi_mat->tt_srpt <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->vmgm_pgci_ut <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->ptl_mait <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->vts_atrt <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->txtdt_mgi <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->vmgm_c_adt <= vmgi_mat->vmgi_last_sector);
+  CHECK_VALUE(vmgi_mat->vmgm_vobu_admap <= vmgi_mat->vmgi_last_sector);
+
+  CHECK_VALUE(vmgi_mat->nr_of_vmgm_audio_streams <= 1);
+  CHECK_VALUE(vmgi_mat->nr_of_vmgm_subp_streams <= 1);
+
+  return 1;
+}
+
+
+static int ifoRead_VTS(ifo_handle_t *ifofile) {
+  vtsi_mat_t *vtsi_mat;
+  int i;
+
+  vtsi_mat = (vtsi_mat_t *)malloc(sizeof(vtsi_mat_t));
+  if(!vtsi_mat)
+    return 0;
+  
+  ifofile->vtsi_mat = vtsi_mat;
+
+  if(!DVDFileSeek_(ifofile->file, 0)) {
+    free(ifofile->vtsi_mat);
+    ifofile->vtsi_mat = 0;
+    return 0;
+  }
+
+  if(!(DVDReadBytes(ifofile->file, vtsi_mat, sizeof(vtsi_mat_t)))) {
+    free(ifofile->vtsi_mat);
+    ifofile->vtsi_mat = 0;
+    return 0;
+  }
+
+  if(strncmp("DVDVIDEO-VTS", vtsi_mat->vts_identifier, 12) != 0) {
+    free(ifofile->vtsi_mat);
+    ifofile->vtsi_mat = 0;
+    return 0;
+  }
+
+  B2N_32(vtsi_mat->vts_last_sector);
+  B2N_32(vtsi_mat->vtsi_last_sector);
+  B2N_32(vtsi_mat->vts_category);
+  B2N_32(vtsi_mat->vtsi_last_byte);
+  B2N_32(vtsi_mat->vtsm_vobs);
+  B2N_32(vtsi_mat->vtstt_vobs);
+  B2N_32(vtsi_mat->vts_ptt_srpt);
+  B2N_32(vtsi_mat->vts_pgcit);
+  B2N_32(vtsi_mat->vtsm_pgci_ut);
+  B2N_32(vtsi_mat->vts_tmapt);
+  B2N_32(vtsi_mat->vtsm_c_adt);
+  B2N_32(vtsi_mat->vtsm_vobu_admap);
+  B2N_32(vtsi_mat->vts_c_adt);
+  B2N_32(vtsi_mat->vts_vobu_admap);
+  B2N_16(vtsi_mat->vtsm_audio_attr.lang_code);
+  B2N_16(vtsi_mat->vtsm_subp_attr.lang_code);
+  for(i = 0; i < 8; i++)
+    B2N_16(vtsi_mat->vts_audio_attr[i].lang_code);
+  for(i = 0; i < 32; i++)
+    B2N_16(vtsi_mat->vts_subp_attr[i].lang_code);
+
+
+  CHECK_ZERO(vtsi_mat->zero_1);
+  CHECK_ZERO(vtsi_mat->zero_2);
+  CHECK_ZERO(vtsi_mat->zero_3);
+  CHECK_ZERO(vtsi_mat->zero_4);
+  CHECK_ZERO(vtsi_mat->zero_5);
+  CHECK_ZERO(vtsi_mat->zero_6);
+  CHECK_ZERO(vtsi_mat->zero_7);
+  CHECK_ZERO(vtsi_mat->zero_8);
+  CHECK_ZERO(vtsi_mat->zero_9);
+  CHECK_ZERO(vtsi_mat->zero_10);
+  CHECK_ZERO(vtsi_mat->zero_11);
+  CHECK_ZERO(vtsi_mat->zero_12);
+  CHECK_ZERO(vtsi_mat->zero_13);
+  CHECK_ZERO(vtsi_mat->zero_14);
+  CHECK_ZERO(vtsi_mat->zero_15);
+  CHECK_ZERO(vtsi_mat->zero_16);
+  CHECK_ZERO(vtsi_mat->zero_17);
+  CHECK_ZERO(vtsi_mat->zero_18);
+  CHECK_ZERO(vtsi_mat->zero_19);
+  CHECK_ZERO(vtsi_mat->zero_20);
+  CHECK_ZERO(vtsi_mat->zero_21);
+  CHECK_VALUE(vtsi_mat->vtsi_last_sector*2 <= vtsi_mat->vts_last_sector);
+  CHECK_VALUE(vtsi_mat->vtsi_last_byte/DVD_BLOCK_LEN <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vtsm_vobs == 0 || 
+       (vtsi_mat->vtsm_vobs > vtsi_mat->vtsi_last_sector &&
+         vtsi_mat->vtsm_vobs < vtsi_mat->vts_last_sector));
+  CHECK_VALUE(vtsi_mat->vtstt_vobs == 0 || 
+        (vtsi_mat->vtstt_vobs > vtsi_mat->vtsi_last_sector &&
+         vtsi_mat->vtstt_vobs < vtsi_mat->vts_last_sector));
+  CHECK_VALUE(vtsi_mat->vts_ptt_srpt <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vts_pgcit <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vtsm_pgci_ut <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vts_tmapt <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vtsm_c_adt <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vtsm_vobu_admap <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vts_c_adt <= vtsi_mat->vtsi_last_sector);
+  CHECK_VALUE(vtsi_mat->vts_vobu_admap <= vtsi_mat->vtsi_last_sector);
+  
+  CHECK_VALUE(vtsi_mat->nr_of_vtsm_audio_streams <= 1);
+  CHECK_VALUE(vtsi_mat->nr_of_vtsm_subp_streams <= 1);
+
+  CHECK_VALUE(vtsi_mat->nr_of_vts_audio_streams <= 8);
+  for(i = vtsi_mat->nr_of_vts_audio_streams; i < 8; i++)
+    CHECK_ZERO(vtsi_mat->vts_audio_attr[i]);
+
+  CHECK_VALUE(vtsi_mat->nr_of_vts_subp_streams <= 32);
+  for(i = vtsi_mat->nr_of_vts_subp_streams; i < 32; i++)
+    CHECK_ZERO(vtsi_mat->vts_subp_attr[i]);      
+  
+  for(i = 0; i < 8; i++) {
+    CHECK_ZERO0(vtsi_mat->vts_mu_audio_attr[i].zero1);
+    CHECK_ZERO0(vtsi_mat->vts_mu_audio_attr[i].zero2);
+    CHECK_ZERO0(vtsi_mat->vts_mu_audio_attr[i].zero3);
+    CHECK_ZERO0(vtsi_mat->vts_mu_audio_attr[i].zero4);
+    CHECK_ZERO0(vtsi_mat->vts_mu_audio_attr[i].zero5);
+    CHECK_ZERO(vtsi_mat->vts_mu_audio_attr[i].zero6);
+  }
+  
+  return 1;
+}
+
+
+static int ifoRead_PGC_COMMAND_TBL(ifo_handle_t *ifofile, 
+                                   pgc_command_tbl_t *cmd_tbl, 
+				   unsigned int offset) {
+  
+  memset(cmd_tbl, 0, sizeof(pgc_command_tbl_t));
+  
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, cmd_tbl, PGC_COMMAND_TBL_SIZE)))
+    return 0;
+
+  B2N_16(cmd_tbl->nr_of_pre);
+  B2N_16(cmd_tbl->nr_of_post);
+  B2N_16(cmd_tbl->nr_of_cell);
+
+  CHECK_VALUE(cmd_tbl->nr_of_pre + cmd_tbl->nr_of_post + cmd_tbl->nr_of_cell<= 255);
+     
+  if(cmd_tbl->nr_of_pre != 0) {
+    unsigned int pre_cmds_size  = cmd_tbl->nr_of_pre * COMMAND_DATA_SIZE;
+    cmd_tbl->pre_cmds = (vm_cmd_t *)malloc(pre_cmds_size);
+    if(!cmd_tbl->pre_cmds)
+      return 0;
+
+    if(!(DVDReadBytes(ifofile->file, cmd_tbl->pre_cmds, pre_cmds_size))) {
+      free(cmd_tbl->pre_cmds);
+      return 0;
+    }
+  }
+  
+  if(cmd_tbl->nr_of_post != 0) {
+    unsigned int post_cmds_size = cmd_tbl->nr_of_post * COMMAND_DATA_SIZE;
+    cmd_tbl->post_cmds = (vm_cmd_t *)malloc(post_cmds_size);
+    if(!cmd_tbl->post_cmds) {
+      if(cmd_tbl->pre_cmds) 
+	free(cmd_tbl->pre_cmds);
+      return 0;
+    }
+    if(!(DVDReadBytes(ifofile->file, cmd_tbl->post_cmds, post_cmds_size))) {
+      if(cmd_tbl->pre_cmds) 
+	free(cmd_tbl->pre_cmds);
+      free(cmd_tbl->post_cmds);
+      return 0;
+    }
+  }
+
+  if(cmd_tbl->nr_of_cell != 0) {
+    unsigned int cell_cmds_size = cmd_tbl->nr_of_cell * COMMAND_DATA_SIZE;
+    cmd_tbl->cell_cmds = (vm_cmd_t *)malloc(cell_cmds_size);
+    if(!cmd_tbl->cell_cmds) {
+      if(cmd_tbl->pre_cmds)
+	free(cmd_tbl->pre_cmds);
+      if(cmd_tbl->post_cmds)
+	free(cmd_tbl->post_cmds);
+      return 0;
+    }
+    if(!(DVDReadBytes(ifofile->file, cmd_tbl->cell_cmds, cell_cmds_size))) {
+      if(cmd_tbl->pre_cmds) 
+	free(cmd_tbl->pre_cmds);
+      if(cmd_tbl->post_cmds) 
+	free(cmd_tbl->post_cmds);
+      free(cmd_tbl->cell_cmds);
+      return 0;
+    }
+  }
+  
+  /* 
+   * Make a run over all the commands and see that we can interpret them all?
+   */
+  return 1;
+}
+
+
+static void ifoFree_PGC_COMMAND_TBL(pgc_command_tbl_t *cmd_tbl) {
+  if(cmd_tbl) {
+    if(cmd_tbl->nr_of_pre && cmd_tbl->pre_cmds)
+      free(cmd_tbl->pre_cmds);
+    if(cmd_tbl->nr_of_post && cmd_tbl->post_cmds)
+      free(cmd_tbl->post_cmds);
+    if(cmd_tbl->nr_of_cell && cmd_tbl->cell_cmds)
+      free(cmd_tbl->cell_cmds);
+    free(cmd_tbl);
+  }
+}
+
+static int ifoRead_PGC_PROGRAM_MAP(ifo_handle_t *ifofile, 
+                                   pgc_program_map_t *program_map, 
+				   unsigned int nr, unsigned int offset) {
+  unsigned int size = nr * sizeof(pgc_program_map_t);
+
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+ 
+  if(!(DVDReadBytes(ifofile->file, program_map, size)))
+    return 0;
+
+  return 1;
+}
+
+static int ifoRead_CELL_PLAYBACK_TBL(ifo_handle_t *ifofile, 
+                                     cell_playback_t *cell_playback,
+                                     unsigned int nr, unsigned int offset) {
+  unsigned int i;
+  unsigned int size = nr * sizeof(cell_playback_t);
+
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, cell_playback, size)))
+    return 0;
+
+  for(i = 0; i < nr; i++) {
+    B2N_32(cell_playback[i].first_sector);
+    B2N_32(cell_playback[i].first_ilvu_end_sector);
+    B2N_32(cell_playback[i].last_vobu_start_sector);
+    B2N_32(cell_playback[i].last_sector);
+    
+    /* Changed < to <= because this was false in the movie 'Pi'. */
+    CHECK_VALUE(cell_playback[i].last_vobu_start_sector <= 
+           cell_playback[i].last_sector);
+    CHECK_VALUE(cell_playback[i].first_sector <= 
+           cell_playback[i].last_vobu_start_sector);
+  }
+
+  return 1;
+}
+
+
+static int ifoRead_CELL_POSITION_TBL(ifo_handle_t *ifofile, 
+                                     cell_position_t *cell_position, 
+                                     unsigned int nr, unsigned int offset) {
+  unsigned int i;
+  unsigned int size = nr * sizeof(cell_position_t);
+
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, cell_position, size)))
+    return 0;
+
+  for(i = 0; i < nr; i++) {
+    B2N_16(cell_position[i].vob_id_nr);
+    CHECK_ZERO(cell_position[i].zero_1);
+  }
+
+  return 1;
+}
+
+static int ifoRead_PGC(ifo_handle_t *ifofile, pgc_t *pgc, unsigned int offset) {
+  unsigned int i;
+
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+ 
+  if(!(DVDReadBytes(ifofile->file, pgc, PGC_SIZE)))
+    return 0;
+
+  B2N_16(pgc->next_pgc_nr);
+  B2N_16(pgc->prev_pgc_nr);
+  B2N_16(pgc->goup_pgc_nr);
+  B2N_16(pgc->command_tbl_offset);
+  B2N_16(pgc->program_map_offset);
+  B2N_16(pgc->cell_playback_offset);
+  B2N_16(pgc->cell_position_offset);
+
+  for(i = 0; i < 8; i++)
+    B2N_16(pgc->audio_control[i]);
+  for(i = 0; i < 32; i++)
+    B2N_32(pgc->subp_control[i]);
+  for(i = 0; i < 16; i++)
+    B2N_32(pgc->palette[i]);
+  
+  CHECK_ZERO(pgc->zero_1);
+  CHECK_VALUE(pgc->nr_of_programs <= pgc->nr_of_cells);
+
+  /* verify time (look at print_time) */
+  for(i = 0; i < 8; i++)
+    if(!pgc->audio_control[i] & 0x8000) /* The 'is present' bit */
+      CHECK_ZERO(pgc->audio_control[i]);
+  for(i = 0; i < 32; i++)
+    if(!pgc->subp_control[i] & 0x80000000) /* The 'is present' bit */
+      CHECK_ZERO(pgc->subp_control[i]);
+  
+  /* Check that time is 0:0:0:0 also if nr_of_programs == 0 */
+  if(pgc->nr_of_programs == 0) {
+    CHECK_ZERO(pgc->still_time);
+    CHECK_ZERO(pgc->pg_playback_mode); // ??
+    CHECK_VALUE(pgc->program_map_offset == 0);
+    CHECK_VALUE(pgc->cell_playback_offset == 0);
+    CHECK_VALUE(pgc->cell_position_offset == 0);
+  } else {
+    CHECK_VALUE(pgc->program_map_offset != 0);
+    CHECK_VALUE(pgc->cell_playback_offset != 0);
+    CHECK_VALUE(pgc->cell_position_offset != 0);
+  }
+  
+  if(pgc->command_tbl_offset != 0) {
+    pgc->command_tbl = malloc(sizeof(pgc_command_tbl_t));
+    if(!pgc->command_tbl)
+      return 0;
+
+    if(!ifoRead_PGC_COMMAND_TBL(ifofile, pgc->command_tbl, 
+                                offset + pgc->command_tbl_offset)) {
+      free(pgc->command_tbl);
+      return 0;
+    }
+  } else {
+    pgc->command_tbl = NULL;
+  }
+  
+  if(pgc->program_map_offset != 0) {
+    pgc->program_map = malloc(pgc->nr_of_programs * sizeof(pgc_program_map_t));
+    if(!pgc->program_map) {
+      ifoFree_PGC_COMMAND_TBL(pgc->command_tbl);
+      return 0;
+    }
+    if(!ifoRead_PGC_PROGRAM_MAP(ifofile, pgc->program_map,pgc->nr_of_programs,
+                                offset + pgc->program_map_offset)) {
+      ifoFree_PGC_COMMAND_TBL(pgc->command_tbl);
+      free(pgc->program_map);
+      return 0;
+    }
+  } else {
+    pgc->program_map = NULL;
+  }
+  
+  if(pgc->cell_playback_offset != 0) {
+    pgc->cell_playback = malloc(pgc->nr_of_cells * sizeof(cell_playback_t));
+    if(!pgc->cell_playback) {
+      ifoFree_PGC_COMMAND_TBL(pgc->command_tbl);
+      if(pgc->program_map)
+	free(pgc->program_map);
+      return 0;
+    }
+    if(!ifoRead_CELL_PLAYBACK_TBL(ifofile, pgc->cell_playback, 
+				  pgc->nr_of_cells,
+                                  offset + pgc->cell_playback_offset)) {
+      ifoFree_PGC_COMMAND_TBL(pgc->command_tbl);
+      if(pgc->program_map)
+	free(pgc->program_map);
+      free(pgc->cell_playback);
+      return 0;
+    }
+  } else {
+    pgc->cell_playback = NULL;
+  }
+  
+  if(pgc->cell_position_offset != 0) {
+    pgc->cell_position = malloc(pgc->nr_of_cells * sizeof(cell_position_t));
+    if(!pgc->cell_position) {
+      ifoFree_PGC(pgc);
+      return 0;
+    }
+    if(!ifoRead_CELL_POSITION_TBL(ifofile, pgc->cell_position, 
+				  pgc->nr_of_cells,
+                                  offset + pgc->cell_position_offset)) {
+      ifoFree_PGC(pgc);
+      return 0;
+    }
+  } else {
+    pgc->cell_position = NULL;
+  }
+
+  return 1;
+}
+
+int ifoRead_FP_PGC(ifo_handle_t *ifofile) {
+
+  if(!ifofile)
+    return 0;
+
+  if(!ifofile->vmgi_mat)
+    return 0;
+  
+  /* It seems that first_play_pgc is optional after all. */
+  ifofile->first_play_pgc = 0;
+  if(ifofile->vmgi_mat->first_play_pgc == 0)
+    return 1;
+  
+  ifofile->first_play_pgc = (pgc_t *)malloc(sizeof(pgc_t));
+  if(!ifofile->first_play_pgc)
+    return 0;
+  
+  if(!ifoRead_PGC(ifofile, ifofile->first_play_pgc, 
+                  ifofile->vmgi_mat->first_play_pgc)) {
+    free(ifofile->first_play_pgc);
+    ifofile->first_play_pgc = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+static void ifoFree_PGC(pgc_t *pgc) {
+  if(pgc) {
+    ifoFree_PGC_COMMAND_TBL(pgc->command_tbl);
+    if(pgc->program_map)
+      free(pgc->program_map);
+    if(pgc->cell_playback)
+      free(pgc->cell_playback);
+    if(pgc->cell_position)
+      free(pgc->cell_position);
+  }
+}
+
+void ifoFree_FP_PGC(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->first_play_pgc) {
+    ifoFree_PGC(ifofile->first_play_pgc);
+    free(ifofile->first_play_pgc);
+    ifofile->first_play_pgc = 0;
+  }
+}
+
+
+int ifoRead_TT_SRPT(ifo_handle_t *ifofile) {
+  tt_srpt_t *tt_srpt;
+  int i, info_length;
+
+  if(!ifofile)
+    return 0;
+
+  if(!ifofile->vmgi_mat)
+    return 0;
+
+  if(ifofile->vmgi_mat->tt_srpt == 0) /* mandatory */
+    return 0;
+
+  if(!DVDFileSeek_(ifofile->file, ifofile->vmgi_mat->tt_srpt * DVD_BLOCK_LEN))
+    return 0;
+
+  tt_srpt = (tt_srpt_t *)malloc(sizeof(tt_srpt_t));
+  if(!tt_srpt)
+    return 0;
+
+  ifofile->tt_srpt = tt_srpt;
+  
+  if(!(DVDReadBytes(ifofile->file, tt_srpt, TT_SRPT_SIZE))) {
+    fprintf(stderr, "libdvdread: Unable to read read TT_SRPT.\n");
+    free(tt_srpt);
+    return 0;
+  }
+
+  B2N_16(tt_srpt->nr_of_srpts);
+  B2N_32(tt_srpt->last_byte);
+  
+  info_length = tt_srpt->last_byte + 1 - TT_SRPT_SIZE;
+
+  tt_srpt->title = (title_info_t *)malloc(info_length); 
+  if(!tt_srpt->title) {
+    free(tt_srpt);
+    ifofile->tt_srpt = 0;
+    return 0;
+  }
+  if(!(DVDReadBytes(ifofile->file, tt_srpt->title, info_length))) {
+    fprintf(stderr, "libdvdread: Unable to read read TT_SRPT.\n");
+    ifoFree_TT_SRPT(ifofile);
+    return 0;
+  }
+
+  for(i =  0; i < tt_srpt->nr_of_srpts; i++) {
+    B2N_16(tt_srpt->title[i].nr_of_ptts);
+    B2N_16(tt_srpt->title[i].parental_id);
+    B2N_32(tt_srpt->title[i].title_set_sector);
+  }
+  
+
+  CHECK_ZERO(tt_srpt->zero_1);
+  CHECK_VALUE(tt_srpt->nr_of_srpts != 0);
+  CHECK_VALUE(tt_srpt->nr_of_srpts < 100); // ??
+  CHECK_VALUE((int)tt_srpt->nr_of_srpts * sizeof(title_info_t) <= info_length);
+  
+  for(i = 0; i < tt_srpt->nr_of_srpts; i++) {
+    CHECK_VALUE(tt_srpt->title[i].pb_ty.zero_1 == 0);
+    CHECK_VALUE(tt_srpt->title[i].nr_of_angles != 0);
+    CHECK_VALUE(tt_srpt->title[i].nr_of_angles < 10);
+    //CHECK_VALUE(tt_srpt->title[i].nr_of_ptts != 0);
+    // XXX: this assertion breaks Ghostbusters:
+    CHECK_VALUE(tt_srpt->title[i].nr_of_ptts < 1000); // ??
+    CHECK_VALUE(tt_srpt->title[i].title_set_nr != 0);
+    CHECK_VALUE(tt_srpt->title[i].title_set_nr < 100); // ??
+    CHECK_VALUE(tt_srpt->title[i].vts_ttn != 0);
+    CHECK_VALUE(tt_srpt->title[i].vts_ttn < 100); // ??
+    //CHECK_VALUE(tt_srpt->title[i].title_set_sector != 0);
+  }
+  
+  // Make this a function
+#if 0
+  if(memcmp((uint8_t *)tt_srpt->title + 
+            tt_srpt->nr_of_srpts * sizeof(title_info_t), 
+            my_friendly_zeros, 
+            info_length - tt_srpt->nr_of_srpts * sizeof(title_info_t))) {
+    fprintf(stderr, "VMG_PTT_SRPT slack is != 0, ");
+    hexdump((uint8_t *)tt_srpt->title + 
+            tt_srpt->nr_of_srpts * sizeof(title_info_t), 
+            info_length - tt_srpt->nr_of_srpts * sizeof(title_info_t));
+  }
+#endif
+
+  return 1;
+}
+
+
+void ifoFree_TT_SRPT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->tt_srpt) {
+    free(ifofile->tt_srpt->title);
+    free(ifofile->tt_srpt);
+    ifofile->tt_srpt = 0;
+  }
+}
+
+
+int ifoRead_VTS_PTT_SRPT(ifo_handle_t *ifofile) {
+  vts_ptt_srpt_t *vts_ptt_srpt;
+  int info_length, i, j;
+  uint32_t *data;
+
+  if(!ifofile)
+    return 0;
+  
+  if(!ifofile->vtsi_mat)
+    return 0;
+
+  if(ifofile->vtsi_mat->vts_ptt_srpt == 0) /* mandatory */
+    return 0;
+    
+  if(!DVDFileSeek_(ifofile->file,
+		   ifofile->vtsi_mat->vts_ptt_srpt * DVD_BLOCK_LEN))
+    return 0;
+
+  vts_ptt_srpt = (vts_ptt_srpt_t *)malloc(sizeof(vts_ptt_srpt_t));
+  if(!vts_ptt_srpt)
+    return 0;
+
+  ifofile->vts_ptt_srpt = vts_ptt_srpt;
+
+  if(!(DVDReadBytes(ifofile->file, vts_ptt_srpt, VTS_PTT_SRPT_SIZE))) {
+    fprintf(stderr, "libdvdread: Unable to read PTT search table.\n");
+    free(vts_ptt_srpt);
+    return 0;
+  }
+
+  B2N_16(vts_ptt_srpt->nr_of_srpts);
+  B2N_32(vts_ptt_srpt->last_byte);
+
+  CHECK_ZERO(vts_ptt_srpt->zero_1);
+  CHECK_VALUE(vts_ptt_srpt->nr_of_srpts != 0);
+  CHECK_VALUE(vts_ptt_srpt->nr_of_srpts < 100); // ??
+  
+  info_length = vts_ptt_srpt->last_byte + 1 - VTS_PTT_SRPT_SIZE;
+  
+  data = (uint32_t *)malloc(info_length); 
+  if(!data) {
+    free(vts_ptt_srpt);
+    ifofile->vts_ptt_srpt = 0;
+    return 0;
+  }
+  if(!(DVDReadBytes(ifofile->file, data, info_length))) {
+    fprintf(stderr, "libdvdread: Unable to read PTT search table.\n");
+    free(vts_ptt_srpt);
+    free(data);
+    ifofile->vts_ptt_srpt = 0;
+    return 0;
+  }
+
+  for(i = 0; i < vts_ptt_srpt->nr_of_srpts; i++) {
+    B2N_32(data[i]);
+    /* assert(data[i] + sizeof(ptt_info_t) <= vts_ptt_srpt->last_byte + 1);
+       Magic Knight Rayearth Daybreak is mastered very strange and has 
+       Titles with 0 PTTs. They all have a data[i] offsets beyond the end of
+       of the vts_ptt_srpt structure. */
+    CHECK_VALUE(data[i] + sizeof(ptt_info_t) <= vts_ptt_srpt->last_byte + 1 + 4);
+  }
+ 
+  vts_ptt_srpt->ttu_offset = data;
+  
+  vts_ptt_srpt->title = malloc(vts_ptt_srpt->nr_of_srpts * sizeof(ttu_t));
+  if(!vts_ptt_srpt->title) {
+    free(vts_ptt_srpt);
+    free(data);
+    ifofile->vts_ptt_srpt = 0;
+    return 0;
+  }
+  for(i = 0; i < vts_ptt_srpt->nr_of_srpts; i++) {
+    int n;
+    if(i < vts_ptt_srpt->nr_of_srpts - 1)
+      n = (data[i+1] - data[i]);
+    else
+      n = (vts_ptt_srpt->last_byte + 1 - data[i]);
+    /* assert(n > 0 && (n % 4) == 0);
+       Magic Knight Rayearth Daybreak is mastered very strange and has 
+       Titles with 0 PTTs. */
+    if(n < 0) n = 0;
+    CHECK_VALUE(n % 4 == 0);
+    
+    vts_ptt_srpt->title[i].nr_of_ptts = n / 4;
+    vts_ptt_srpt->title[i].ptt = malloc(n * sizeof(ptt_info_t));
+    if(!vts_ptt_srpt->title[i].ptt) {
+      for(n = 0; n < i; n++)
+        free(vts_ptt_srpt->title[n].ptt);
+      free(vts_ptt_srpt);
+      free(data);
+      ifofile->vts_ptt_srpt = 0;
+      return 0;
+    }
+    for(j = 0; j < vts_ptt_srpt->title[i].nr_of_ptts; j++) {
+      /* The assert placed here because of Magic Knight Rayearth Daybreak */
+      CHECK_VALUE(data[i] + sizeof(ptt_info_t) <= vts_ptt_srpt->last_byte + 1);
+      vts_ptt_srpt->title[i].ptt[j].pgcn 
+        = *(uint16_t*)(((char *)data) + data[i] + 4*j - VTS_PTT_SRPT_SIZE);
+      vts_ptt_srpt->title[i].ptt[j].pgn 
+        = *(uint16_t*)(((char *)data) + data[i] + 4*j + 2 - VTS_PTT_SRPT_SIZE);
+    }
+  }
+  
+  for(i = 0; i < vts_ptt_srpt->nr_of_srpts; i++) {
+    for(j = 0; j < vts_ptt_srpt->title[i].nr_of_ptts; j++) {
+      B2N_16(vts_ptt_srpt->title[i].ptt[j].pgcn);
+      B2N_16(vts_ptt_srpt->title[i].ptt[j].pgn);
+    }
+  }
+  
+  for(i = 0; i < vts_ptt_srpt->nr_of_srpts; i++) {
+    CHECK_VALUE(vts_ptt_srpt->title[i].nr_of_ptts < 1000); // ??
+    for(j = 0; j < vts_ptt_srpt->title[i].nr_of_ptts; j++) {
+      CHECK_VALUE(vts_ptt_srpt->title[i].ptt[j].pgcn != 0 );
+      CHECK_VALUE(vts_ptt_srpt->title[i].ptt[j].pgcn < 1000); // ??
+      CHECK_VALUE(vts_ptt_srpt->title[i].ptt[j].pgn != 0);
+      CHECK_VALUE(vts_ptt_srpt->title[i].ptt[j].pgn < 100); // ??
+    }
+  }
+
+  return 1;
+}
+
+
+void ifoFree_VTS_PTT_SRPT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->vts_ptt_srpt) {
+    int i;
+    for(i = 0; i < ifofile->vts_ptt_srpt->nr_of_srpts; i++)
+      free(ifofile->vts_ptt_srpt->title[i].ptt);
+    free(ifofile->vts_ptt_srpt->ttu_offset);
+    free(ifofile->vts_ptt_srpt->title);
+    free(ifofile->vts_ptt_srpt);
+    ifofile->vts_ptt_srpt = 0;
+  }
+}
+
+
+int ifoRead_PTL_MAIT(ifo_handle_t *ifofile) {
+  ptl_mait_t *ptl_mait;
+  int info_length;
+  unsigned int i, j;
+
+  if(!ifofile)
+    return 0;
+  
+  if(!ifofile->vmgi_mat)
+    return 0;
+  
+  if(ifofile->vmgi_mat->ptl_mait == 0)
+    return 1;
+
+  if(!DVDFileSeek_(ifofile->file, ifofile->vmgi_mat->ptl_mait * DVD_BLOCK_LEN))
+    return 0;
+
+  ptl_mait = (ptl_mait_t *)malloc(sizeof(ptl_mait_t));
+  if(!ptl_mait)
+    return 0;
+
+  ifofile->ptl_mait = ptl_mait;
+
+  if(!(DVDReadBytes(ifofile->file, ptl_mait, PTL_MAIT_SIZE))) {
+    free(ptl_mait);
+    ifofile->ptl_mait = 0;
+    return 0;
+  }
+
+  B2N_16(ptl_mait->nr_of_countries);
+  B2N_16(ptl_mait->nr_of_vtss);
+  B2N_32(ptl_mait->last_byte);
+  
+  CHECK_VALUE(ptl_mait->nr_of_countries != 0);
+  CHECK_VALUE(ptl_mait->nr_of_countries < 100); // ??
+  CHECK_VALUE(ptl_mait->nr_of_vtss != 0);
+  CHECK_VALUE(ptl_mait->nr_of_vtss < 100); // ??  
+  CHECK_VALUE(ptl_mait->nr_of_countries * PTL_MAIT_COUNTRY_SIZE 
+	      <= ptl_mait->last_byte + 1 - PTL_MAIT_SIZE);
+  
+  info_length = ptl_mait->nr_of_countries * sizeof(ptl_mait_country_t);
+  ptl_mait->countries = (ptl_mait_country_t *)malloc(info_length);
+  if(!ptl_mait->countries) {
+    free(ptl_mait);
+    ifofile->ptl_mait = 0;
+    return 0;
+  }
+  
+  for(i = 0; i < ptl_mait->nr_of_countries; i++) {
+    if(!(DVDReadBytes(ifofile->file, &ptl_mait->countries[i], PTL_MAIT_COUNTRY_SIZE))) {
+      fprintf(stderr, "libdvdread: Unable to read PTL_MAIT.\n");
+      free(ptl_mait->countries);
+      free(ptl_mait);
+      ifofile->ptl_mait = 0;
+      return 0;
+    }
+  }
+
+  for(i = 0; i < ptl_mait->nr_of_countries; i++) {
+    B2N_16(ptl_mait->countries[i].country_code);
+    B2N_16(ptl_mait->countries[i].pf_ptl_mai_start_byte);
+  }
+  
+  for(i = 0; i < ptl_mait->nr_of_countries; i++) {
+    CHECK_ZERO(ptl_mait->countries[i].zero_1);
+    CHECK_ZERO(ptl_mait->countries[i].zero_2);    
+    CHECK_VALUE(ptl_mait->countries[i].pf_ptl_mai_start_byte
+		+ 8*2 * (ptl_mait->nr_of_vtss + 1) <= ptl_mait->last_byte + 1);
+  }
+
+  for(i = 0; i < ptl_mait->nr_of_countries; i++) {
+    uint16_t *pf_temp;
+    
+    if(!DVDFileSeek_(ifofile->file, 
+		     ifofile->vmgi_mat->ptl_mait * DVD_BLOCK_LEN
+                     + ptl_mait->countries[i].pf_ptl_mai_start_byte)) {
+      fprintf(stderr, "libdvdread: Unable to seak PTL_MAIT table.\n");
+      free(ptl_mait->countries);
+      free(ptl_mait);
+      return 0;
+    }
+    info_length = (ptl_mait->nr_of_vtss + 1) * sizeof(pf_level_t);
+    pf_temp = (uint16_t *)malloc(info_length);
+    if(!pf_temp) {
+      for(j = 0; j < i ; j++) {
+         free(ptl_mait->countries[j].pf_ptl_mai);
+      }
+      free(ptl_mait->countries);
+      free(ptl_mait);
+      return 0;
+    }
+    if(!(DVDReadBytes(ifofile->file, pf_temp, info_length))) {
+       fprintf(stderr, "libdvdread: Unable to read PTL_MAIT table.\n");
+       free(pf_temp);
+       for(j = 0; j < i ; j++) {
+	  free(ptl_mait->countries[j].pf_ptl_mai);
+       }
+       free(ptl_mait->countries);
+       free(ptl_mait);
+       return 0;
+    }
+    for (j = 0; j < ((ptl_mait->nr_of_vtss + 1) * 8); j++) {
+        B2N_16(pf_temp[j]);
+    }
+    ptl_mait->countries[i].pf_ptl_mai = (pf_level_t *)malloc(info_length);
+    if(!ptl_mait->countries[i].pf_ptl_mai) {
+      free(pf_temp);
+      for(j = 0; j < i ; j++) {
+	free(ptl_mait->countries[j].pf_ptl_mai);
+      }
+      free(ptl_mait->countries);
+      free(ptl_mait);
+      return 0;
+    }
+    { /* Transpose the array so we can use C indexing. */
+      int level, vts;
+      for(level = 0; level < 8; level++) {
+	for(vts = 0; vts <= ptl_mait->nr_of_vtss; vts++) {
+	  ptl_mait->countries[i].pf_ptl_mai[vts][level] =
+	    pf_temp[(7-level)*(ptl_mait->nr_of_vtss+1) + vts];
+	}
+      }
+      free(pf_temp);
+    }
+  }
+  return 1;
+}
+
+void ifoFree_PTL_MAIT(ifo_handle_t *ifofile) {
+  unsigned int i;
+  
+  if(!ifofile)
+    return;
+  
+  if(ifofile->ptl_mait) {
+    for(i = 0; i < ifofile->ptl_mait->nr_of_countries; i++) {
+       free(ifofile->ptl_mait->countries[i].pf_ptl_mai);
+    }
+    free(ifofile->ptl_mait->countries);
+    free(ifofile->ptl_mait);
+    ifofile->ptl_mait = 0;
+  }
+}
+
+int ifoRead_VTS_TMAPT(ifo_handle_t *ifofile) {
+  vts_tmapt_t *vts_tmapt;
+  uint32_t *vts_tmap_srp;
+  unsigned int offset;
+  int info_length;
+  unsigned int i, j;
+  
+  if(!ifofile)
+    return 0;
+
+  if(!ifofile->vtsi_mat)
+    return 0;
+
+  if(ifofile->vtsi_mat->vts_tmapt == 0) { /* optional(?) */
+    ifofile->vts_tmapt = NULL;
+    fprintf(stderr,"Please send bug report - no VTS_TMAPT ?? \n");
+    return 1;
+  }
+  
+  offset = ifofile->vtsi_mat->vts_tmapt * DVD_BLOCK_LEN;
+  
+  if(!DVDFileSeek_(ifofile->file, offset)) 
+    return 0;
+  
+  vts_tmapt = (vts_tmapt_t *)malloc(sizeof(vts_tmapt_t));
+  if(!vts_tmapt)
+    return 0;
+  
+  ifofile->vts_tmapt = vts_tmapt;
+  
+  if(!(DVDReadBytes(ifofile->file, vts_tmapt, VTS_TMAPT_SIZE))) {
+    fprintf(stderr, "libdvdread: Unable to read VTS_TMAPT.\n");
+    free(vts_tmapt);
+    ifofile->vts_tmapt = NULL;
+    return 0;
+  }
+
+  B2N_16(vts_tmapt->nr_of_tmaps);
+  B2N_32(vts_tmapt->last_byte);
+  
+  CHECK_ZERO(vts_tmapt->zero_1);
+  
+  info_length = vts_tmapt->nr_of_tmaps * 4;
+  
+  vts_tmap_srp = (uint32_t *)malloc(info_length);
+  if(!vts_tmap_srp) {
+    free(vts_tmapt);
+    ifofile->vts_tmapt = NULL;
+    return 0;
+  }
+
+  vts_tmapt->tmap_offset = vts_tmap_srp;
+  
+  if(!(DVDReadBytes(ifofile->file, vts_tmap_srp, info_length))) {
+    fprintf(stderr, "libdvdread: Unable to read VTS_TMAPT.\n");
+    free(vts_tmap_srp);
+    free(vts_tmapt);
+    ifofile->vts_tmapt = NULL;
+    return 0;
+  }
+
+  for (i = 0; i < vts_tmapt->nr_of_tmaps; i++) {
+     B2N_32(vts_tmap_srp[i]); 
+  }
+
+  
+  info_length = vts_tmapt->nr_of_tmaps * sizeof(vts_tmap_t);
+  
+  vts_tmapt->tmap = (vts_tmap_t *)malloc(info_length);
+  if(!vts_tmapt->tmap) {
+    free(vts_tmap_srp);
+    free(vts_tmapt);
+    ifofile->vts_tmapt = NULL;
+    return 0;
+  }
+
+  memset(vts_tmapt->tmap, 0, info_length); /* So ifoFree_VTS_TMAPT works. */
+  
+  for(i = 0; i < vts_tmapt->nr_of_tmaps; i++) {
+    if(!DVDFileSeek_(ifofile->file, offset + vts_tmap_srp[i])) {
+      ifoFree_VTS_TMAPT(ifofile);
+      return 0;
+    }
+
+    if(!(DVDReadBytes(ifofile->file, &vts_tmapt->tmap[i], VTS_TMAP_SIZE))) {
+      fprintf(stderr, "libdvdread: Unable to read VTS_TMAP.\n");
+      ifoFree_VTS_TMAPT(ifofile);
+      return 0;
+    }
+    
+    B2N_16(vts_tmapt->tmap[i].nr_of_entries);
+    CHECK_ZERO(vts_tmapt->tmap[i].zero_1);
+    
+    if(vts_tmapt->tmap[i].nr_of_entries == 0) { /* Early out if zero entries */
+      vts_tmapt->tmap[i].map_ent = NULL;
+      continue;
+    }
+    
+    info_length = vts_tmapt->tmap[i].nr_of_entries * sizeof(map_ent_t);
+    
+    vts_tmapt->tmap[i].map_ent = (map_ent_t *)malloc(info_length);
+    if(!vts_tmapt->tmap[i].map_ent) {
+      ifoFree_VTS_TMAPT(ifofile);
+      return 0;
+    }
+
+    if(!(DVDReadBytes(ifofile->file, vts_tmapt->tmap[i].map_ent, info_length))) {
+      fprintf(stderr, "libdvdread: Unable to read VTS_TMAP_ENT.\n");
+      ifoFree_VTS_TMAPT(ifofile);
+      return 0;
+    }
+    
+    for(j = 0; j < vts_tmapt->tmap[i].nr_of_entries; j++)
+      B2N_32(vts_tmapt->tmap[i].map_ent[j]);
+  }    
+  
+  return 1;
+}
+
+void ifoFree_VTS_TMAPT(ifo_handle_t *ifofile) {
+  unsigned int i;
+  
+  if(!ifofile)
+    return;
+  
+  if(ifofile->vts_tmapt) {  
+    for(i = 0; i < ifofile->vts_tmapt->nr_of_tmaps; i++)
+      if(ifofile->vts_tmapt->tmap[i].map_ent)
+	free(ifofile->vts_tmapt->tmap[i].map_ent);
+    free(ifofile->vts_tmapt->tmap);
+    free(ifofile->vts_tmapt->tmap_offset);
+    free(ifofile->vts_tmapt);
+    ifofile->vts_tmapt = NULL;
+  }
+}
+
+
+int ifoRead_TITLE_C_ADT(ifo_handle_t *ifofile) {
+
+  if(!ifofile)
+    return 0;
+
+  if(!ifofile->vtsi_mat)
+    return 0;
+
+  if(ifofile->vtsi_mat->vts_c_adt == 0) /* mandatory */
+    return 0;
+
+  ifofile->vts_c_adt = (c_adt_t *)malloc(sizeof(c_adt_t));
+  if(!ifofile->vts_c_adt)
+    return 0;
+
+  if(!ifoRead_C_ADT_internal(ifofile, ifofile->vts_c_adt, 
+                             ifofile->vtsi_mat->vts_c_adt)) {
+    free(ifofile->vts_c_adt);
+    ifofile->vts_c_adt = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+int ifoRead_C_ADT(ifo_handle_t *ifofile) {
+  unsigned int sector;
+
+  if(!ifofile)
+    return 0;
+  
+  if(ifofile->vmgi_mat) {
+    if(ifofile->vmgi_mat->vmgm_c_adt == 0)
+      return 1;
+    sector = ifofile->vmgi_mat->vmgm_c_adt;
+  } else if(ifofile->vtsi_mat) {
+    if(ifofile->vtsi_mat->vtsm_c_adt == 0)
+      return 1;
+    sector = ifofile->vtsi_mat->vtsm_c_adt;
+  } else {
+    return 0;
+  }
+  
+  ifofile->menu_c_adt = (c_adt_t *)malloc(sizeof(c_adt_t));
+  if(!ifofile->menu_c_adt)
+    return 0;
+
+  if(!ifoRead_C_ADT_internal(ifofile, ifofile->menu_c_adt, sector)) {
+    free(ifofile->menu_c_adt);
+    ifofile->menu_c_adt = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ifoRead_C_ADT_internal(ifo_handle_t *ifofile, 
+                                  c_adt_t *c_adt, unsigned int sector) {
+  int i, info_length;
+
+  if(!DVDFileSeek_(ifofile->file, sector * DVD_BLOCK_LEN))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, c_adt, C_ADT_SIZE)))
+    return 0;
+
+  B2N_16(c_adt->nr_of_vobs);
+  B2N_32(c_adt->last_byte);
+  
+  info_length = c_adt->last_byte + 1 - C_ADT_SIZE;
+  
+  CHECK_ZERO(c_adt->zero_1);
+  /* assert(c_adt->nr_of_vobs > 0);  
+     Magic Knight Rayearth Daybreak is mastered very strange and has 
+     Titles with a VOBS that has no cells. */
+  CHECK_VALUE(info_length % sizeof(cell_adr_t) == 0);
+  
+  /* assert(info_length / sizeof(cell_adr_t) >= c_adt->nr_of_vobs);
+     Enemy of the State region 2 (de) has Titles where nr_of_vobs field
+     is to high, they high ones are never referenced though. */
+  if(info_length / sizeof(cell_adr_t) < c_adt->nr_of_vobs) {
+    fprintf(stderr, "libdvdread: *C_ADT nr_of_vobs > avaiable info entries\n");
+    c_adt->nr_of_vobs = info_length / sizeof(cell_adr_t);
+  }
+  
+  c_adt->cell_adr_table = (cell_adr_t *)malloc(info_length);
+  if(!c_adt->cell_adr_table)
+    return 0;
+
+  if(info_length && 
+     !(DVDReadBytes(ifofile->file, c_adt->cell_adr_table, info_length))) {
+    free(c_adt->cell_adr_table);
+    return 0;
+  }
+
+  for(i = 0; i < info_length/sizeof(cell_adr_t); i++) {
+    B2N_16(c_adt->cell_adr_table[i].vob_id);
+    B2N_32(c_adt->cell_adr_table[i].start_sector);
+    B2N_32(c_adt->cell_adr_table[i].last_sector);
+
+    CHECK_ZERO(c_adt->cell_adr_table[i].zero_1);
+    CHECK_VALUE(c_adt->cell_adr_table[i].vob_id > 0);
+    CHECK_VALUE(c_adt->cell_adr_table[i].vob_id <= c_adt->nr_of_vobs);
+    CHECK_VALUE(c_adt->cell_adr_table[i].cell_id > 0);
+    CHECK_VALUE(c_adt->cell_adr_table[i].start_sector < 
+	   c_adt->cell_adr_table[i].last_sector);
+  }
+
+  return 1;
+}
+
+
+static void ifoFree_C_ADT_internal(c_adt_t *c_adt) {
+  if(c_adt) {
+    free(c_adt->cell_adr_table);
+    free(c_adt);
+  }
+}
+
+void ifoFree_C_ADT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  ifoFree_C_ADT_internal(ifofile->menu_c_adt);
+  ifofile->menu_c_adt = 0;
+}
+
+void ifoFree_TITLE_C_ADT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  ifoFree_C_ADT_internal(ifofile->vts_c_adt);
+  ifofile->vts_c_adt = 0;
+}
+
+int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return 0;
+
+  if(!ifofile->vtsi_mat)
+    return 0;
+  
+  if(ifofile->vtsi_mat->vts_vobu_admap == 0) /* mandatory */
+    return 0;
+  
+  ifofile->vts_vobu_admap = (vobu_admap_t *)malloc(sizeof(vobu_admap_t));
+  if(!ifofile->vts_vobu_admap)
+    return 0;
+
+  if(!ifoRead_VOBU_ADMAP_internal(ifofile, ifofile->vts_vobu_admap,
+                                  ifofile->vtsi_mat->vts_vobu_admap)) {
+    free(ifofile->vts_vobu_admap);
+    ifofile->vts_vobu_admap = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+int ifoRead_VOBU_ADMAP(ifo_handle_t *ifofile) {
+  unsigned int sector;
+
+  if(!ifofile)
+    return 0;
+     
+  if(ifofile->vmgi_mat) {
+    if(ifofile->vmgi_mat->vmgm_vobu_admap == 0)
+      return 1;
+    sector = ifofile->vmgi_mat->vmgm_vobu_admap;
+  } else if(ifofile->vtsi_mat) {
+    if(ifofile->vtsi_mat->vtsm_vobu_admap == 0)
+      return 1;
+    sector = ifofile->vtsi_mat->vtsm_vobu_admap;
+  } else {
+    return 0;
+  }
+  
+  ifofile->menu_vobu_admap = (vobu_admap_t *)malloc(sizeof(vobu_admap_t));
+  if(!ifofile->menu_vobu_admap)
+    return 0;
+  
+  if(!ifoRead_VOBU_ADMAP_internal(ifofile, ifofile->menu_vobu_admap, sector)) {
+    free(ifofile->menu_vobu_admap);
+    ifofile->menu_vobu_admap = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ifoRead_VOBU_ADMAP_internal(ifo_handle_t *ifofile, 
+                                       vobu_admap_t *vobu_admap, 
+				       unsigned int sector) {
+  unsigned int i;
+  int info_length;
+
+  if(!DVDFileSeek_(ifofile->file, sector * DVD_BLOCK_LEN))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, vobu_admap, VOBU_ADMAP_SIZE)))
+    return 0;
+
+  B2N_32(vobu_admap->last_byte);
+  
+  info_length = vobu_admap->last_byte + 1 - VOBU_ADMAP_SIZE;
+  /* assert(info_length > 0);
+     Magic Knight Rayearth Daybreak is mastered very strange and has 
+     Titles with a VOBS that has no VOBUs. */
+  CHECK_VALUE(info_length % sizeof(uint32_t) == 0);
+  
+  vobu_admap->vobu_start_sectors = (uint32_t *)malloc(info_length); 
+  if(!vobu_admap->vobu_start_sectors) {
+    return 0;
+  }
+  if(info_length && 
+     !(DVDReadBytes(ifofile->file, 
+		    vobu_admap->vobu_start_sectors, info_length))) {
+    free(vobu_admap->vobu_start_sectors);
+    return 0;
+  }
+
+  for(i = 0; i < info_length/sizeof(uint32_t); i++)
+    B2N_32(vobu_admap->vobu_start_sectors[i]);
+
+  return 1;
+}
+
+
+static void ifoFree_VOBU_ADMAP_internal(vobu_admap_t *vobu_admap) {
+  if(vobu_admap) {
+    free(vobu_admap->vobu_start_sectors);
+    free(vobu_admap);
+  }
+}
+
+void ifoFree_VOBU_ADMAP(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  ifoFree_VOBU_ADMAP_internal(ifofile->menu_vobu_admap);
+  ifofile->menu_vobu_admap = 0;
+}
+
+void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  ifoFree_VOBU_ADMAP_internal(ifofile->vts_vobu_admap);
+  ifofile->vts_vobu_admap = 0;
+}
+
+int ifoRead_PGCIT(ifo_handle_t *ifofile) {
+
+  if(!ifofile)
+    return 0;
+  
+  if(!ifofile->vtsi_mat)
+    return 0;
+  
+  if(ifofile->vtsi_mat->vts_pgcit == 0) /* mandatory */
+    return 0;
+  
+  ifofile->vts_pgcit = (pgcit_t *)malloc(sizeof(pgcit_t));
+  if(!ifofile->vts_pgcit)
+    return 0;
+
+  if(!ifoRead_PGCIT_internal(ifofile, ifofile->vts_pgcit, 
+                             ifofile->vtsi_mat->vts_pgcit * DVD_BLOCK_LEN)) {
+    free(ifofile->vts_pgcit);
+    ifofile->vts_pgcit = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ifoRead_PGCIT_internal(ifo_handle_t *ifofile, pgcit_t *pgcit, 
+                                  unsigned int offset) {
+  int i, info_length;
+  uint8_t *data, *ptr;
+  
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, pgcit, PGCIT_SIZE)))
+    return 0;
+
+  B2N_16(pgcit->nr_of_pgci_srp);
+  B2N_32(pgcit->last_byte);
+  
+  CHECK_ZERO(pgcit->zero_1);
+  /* assert(pgcit->nr_of_pgci_srp != 0);
+     Magic Knight Rayearth Daybreak is mastered very strange and has 
+     Titles with 0 PTTs. */
+  CHECK_VALUE(pgcit->nr_of_pgci_srp < 10000); // ?? seen max of 1338
+  
+  info_length = pgcit->nr_of_pgci_srp * PGCI_SRP_SIZE;
+  data = malloc(info_length);
+  if(!data)
+    return 0;
+
+  if(info_length && !(DVDReadBytes(ifofile->file, data, info_length))) {
+    free(data);
+    return 0;
+  }
+
+  pgcit->pgci_srp = malloc(pgcit->nr_of_pgci_srp * sizeof(pgci_srp_t));
+  if(!pgcit->pgci_srp) {
+    free(data);
+    return 0;
+  }
+  ptr = data;
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    memcpy(&pgcit->pgci_srp[i], ptr, PGCI_LU_SIZE);
+    ptr += PGCI_LU_SIZE;
+    B2N_16(pgcit->pgci_srp[i].ptl_id_mask);
+    B2N_32(pgcit->pgci_srp[i].pgc_start_byte);
+    CHECK_VALUE(pgcit->pgci_srp[i].unknown1 == 0);
+  }
+  free(data);
+  
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++)
+    CHECK_VALUE(pgcit->pgci_srp[i].pgc_start_byte + PGC_SIZE <= pgcit->last_byte+1);
+  
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    pgcit->pgci_srp[i].pgc = malloc(sizeof(pgc_t));
+    if(!pgcit->pgci_srp[i].pgc) {
+      int j;
+      for(j = 0; j < i; j++) {
+        ifoFree_PGC(pgcit->pgci_srp[j].pgc);
+        free(pgcit->pgci_srp[j].pgc);
+      }
+      return 0;
+    }
+    if(!ifoRead_PGC(ifofile, pgcit->pgci_srp[i].pgc, 
+                    offset + pgcit->pgci_srp[i].pgc_start_byte)) {
+      int j;
+      for(j = 0; j < i; j++) {
+        ifoFree_PGC(pgcit->pgci_srp[j].pgc);
+        free(pgcit->pgci_srp[j].pgc);
+      }
+      free(pgcit->pgci_srp);
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static void ifoFree_PGCIT_internal(pgcit_t *pgcit) {
+  if(pgcit) {
+    int i;
+    for(i = 0; i < pgcit->nr_of_pgci_srp; i++)
+      ifoFree_PGC(pgcit->pgci_srp[i].pgc);
+    free(pgcit->pgci_srp);
+  }
+}
+
+void ifoFree_PGCIT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->vts_pgcit) {
+    ifoFree_PGCIT_internal(ifofile->vts_pgcit);
+    free(ifofile->vts_pgcit);
+    ifofile->vts_pgcit = 0;
+  }
+}
+
+
+int ifoRead_PGCI_UT(ifo_handle_t *ifofile) {
+  pgci_ut_t *pgci_ut;
+  unsigned int sector;
+  unsigned int i;  
+  int info_length;
+  uint8_t *data, *ptr;
+
+  if(!ifofile)
+    return 0;
+  
+  if(ifofile->vmgi_mat) {
+    if(ifofile->vmgi_mat->vmgm_pgci_ut == 0)
+      return 1;
+    sector = ifofile->vmgi_mat->vmgm_pgci_ut;
+  } else if(ifofile->vtsi_mat) {
+    if(ifofile->vtsi_mat->vtsm_pgci_ut == 0)
+      return 1;
+    sector = ifofile->vtsi_mat->vtsm_pgci_ut;
+  } else {
+    return 0;
+  }
+  
+  ifofile->pgci_ut = (pgci_ut_t *)malloc(sizeof(pgci_ut_t));
+  if(!ifofile->pgci_ut)
+    return 0;
+  
+  if(!DVDFileSeek_(ifofile->file, sector * DVD_BLOCK_LEN)) {
+    free(ifofile->pgci_ut);
+    ifofile->pgci_ut = 0;
+    return 0;
+  }
+  
+  if(!(DVDReadBytes(ifofile->file, ifofile->pgci_ut, PGCI_UT_SIZE))) {
+    free(ifofile->pgci_ut);
+    ifofile->pgci_ut = 0;
+    return 0;
+  }
+  
+  pgci_ut = ifofile->pgci_ut;
+  
+  B2N_16(pgci_ut->nr_of_lus);
+  B2N_32(pgci_ut->last_byte);
+  
+  CHECK_ZERO(pgci_ut->zero_1);
+  CHECK_VALUE(pgci_ut->nr_of_lus != 0);
+  CHECK_VALUE(pgci_ut->nr_of_lus < 100); // ?? 3-4 ?
+  CHECK_VALUE((uint32_t)pgci_ut->nr_of_lus * PGCI_LU_SIZE < pgci_ut->last_byte);
+
+  info_length = pgci_ut->nr_of_lus * PGCI_LU_SIZE;
+  data = malloc(info_length);
+  if(!data) {
+    free(pgci_ut);
+    ifofile->pgci_ut = 0;
+    return 0;
+  }
+  if(!(DVDReadBytes(ifofile->file, data, info_length))) {
+    free(data);
+    free(pgci_ut);
+    ifofile->pgci_ut = 0;
+    return 0;
+  }
+
+  pgci_ut->lu = malloc(pgci_ut->nr_of_lus * sizeof(pgci_lu_t));
+  if(!pgci_ut->lu) {
+    free(data);
+    free(pgci_ut);
+    ifofile->pgci_ut = 0;
+   return 0;
+  }
+  ptr = data;
+  for(i = 0; i < pgci_ut->nr_of_lus; i++) {
+    memcpy(&pgci_ut->lu[i], ptr, PGCI_LU_SIZE);
+    ptr += PGCI_LU_SIZE;
+    B2N_16(pgci_ut->lu[i].lang_code); 
+    B2N_32(pgci_ut->lu[i].lang_start_byte); 
+  }
+  free(data);
+  
+  for(i = 0; i < pgci_ut->nr_of_lus; i++) {
+    // Maybe this is only defined for v1.1 and later titles?
+    /* If the bits in 'lu[i].exists' are enumerated abcd efgh then:
+            VTS_x_yy.IFO        VIDEO_TS.IFO
+       a == 0x83 "Root"         0x82 "Title"
+       b == 0x84 "Subpicture"
+       c == 0x85 "Audio"
+       d == 0x86 "Angle"
+       e == 0x87 "PTT"
+    */
+    CHECK_VALUE((pgci_ut->lu[i].exists & 0x07) == 0);
+  }
+
+  for(i = 0; i < pgci_ut->nr_of_lus; i++) {
+    pgci_ut->lu[i].pgcit = malloc(sizeof(pgcit_t));
+    if(!pgci_ut->lu[i].pgcit) {
+      unsigned int j;
+      for(j = 0; j < i; j++) {
+        ifoFree_PGCIT_internal(pgci_ut->lu[j].pgcit);
+        free(pgci_ut->lu[j].pgcit);
+      }
+      free(pgci_ut->lu);
+      free(pgci_ut);
+      ifofile->pgci_ut = 0;
+      return 0;
+    }
+    if(!ifoRead_PGCIT_internal(ifofile, pgci_ut->lu[i].pgcit, 
+                               sector * DVD_BLOCK_LEN 
+                               + pgci_ut->lu[i].lang_start_byte)) {
+      unsigned int j;
+      for(j = 0; j < i; j++) {
+        ifoFree_PGCIT_internal(pgci_ut->lu[j].pgcit);
+        free(pgci_ut->lu[j].pgcit);
+      }
+      free(pgci_ut->lu[i].pgcit);
+      free(pgci_ut->lu);
+      free(pgci_ut);
+      ifofile->pgci_ut = 0;
+      return 0;
+    }
+    // FIXME: Iterate and verify that all menus that should exists accordingly
+    //        to pgci_ut->lu[i].exists really do?
+  }
+
+  return 1;
+}
+
+
+void ifoFree_PGCI_UT(ifo_handle_t *ifofile) {
+  unsigned int i;
+
+  if(!ifofile)
+    return;
+  
+  if(ifofile->pgci_ut) {
+    for(i = 0; i < ifofile->pgci_ut->nr_of_lus; i++) {
+      ifoFree_PGCIT_internal(ifofile->pgci_ut->lu[i].pgcit);
+      free(ifofile->pgci_ut->lu[i].pgcit);
+    }
+    free(ifofile->pgci_ut->lu);
+    free(ifofile->pgci_ut);
+    ifofile->pgci_ut = 0;
+  }
+}
+
+static int ifoRead_VTS_ATTRIBUTES(ifo_handle_t *ifofile, 
+                                  vts_attributes_t *vts_attributes, 
+                                  unsigned int offset) {
+  unsigned int i;
+
+  if(!DVDFileSeek_(ifofile->file, offset))
+    return 0;
+
+  if(!(DVDReadBytes(ifofile->file, vts_attributes, sizeof(vts_attributes_t))))
+    return 0;
+
+  B2N_32(vts_attributes->last_byte);
+  B2N_32(vts_attributes->vts_cat);
+  B2N_16(vts_attributes->vtsm_audio_attr.lang_code);
+  B2N_16(vts_attributes->vtsm_subp_attr.lang_code);
+  for(i = 0; i < 8; i++)
+    B2N_16(vts_attributes->vtstt_audio_attr[i].lang_code);
+  for(i = 0; i < 32; i++)
+    B2N_16(vts_attributes->vtstt_subp_attr[i].lang_code);
+  
+  CHECK_ZERO(vts_attributes->zero_1);
+  CHECK_ZERO(vts_attributes->zero_2);
+  CHECK_ZERO(vts_attributes->zero_3);
+  CHECK_ZERO(vts_attributes->zero_4);
+  CHECK_ZERO(vts_attributes->zero_5);
+  CHECK_ZERO(vts_attributes->zero_6);
+  CHECK_ZERO(vts_attributes->zero_7);
+  CHECK_VALUE(vts_attributes->nr_of_vtsm_audio_streams <= 1);
+  CHECK_VALUE(vts_attributes->nr_of_vtsm_subp_streams <= 1);
+  CHECK_VALUE(vts_attributes->nr_of_vtstt_audio_streams <= 8);
+  for(i = vts_attributes->nr_of_vtstt_audio_streams; i < 8; i++)
+    CHECK_ZERO(vts_attributes->vtstt_audio_attr[i]);
+  CHECK_VALUE(vts_attributes->nr_of_vtstt_subp_streams <= 32);
+  {
+    unsigned int nr_coded;
+    CHECK_VALUE(vts_attributes->last_byte + 1 >= VTS_ATTRIBUTES_MIN_SIZE);  
+    nr_coded = (vts_attributes->last_byte + 1 - VTS_ATTRIBUTES_MIN_SIZE)/6;
+    // This is often nr_coded = 70, how do you know how many there really are?
+    if(nr_coded > 32) { // We haven't read more from disk/file anyway
+      nr_coded = 32;
+    }
+    CHECK_VALUE(vts_attributes->nr_of_vtstt_subp_streams <= nr_coded);
+    for(i = vts_attributes->nr_of_vtstt_subp_streams; i < nr_coded; i++)
+      CHECK_ZERO(vts_attributes->vtstt_subp_attr[i]);
+  }
+
+  return 1;
+}
+
+
+
+int ifoRead_VTS_ATRT(ifo_handle_t *ifofile) {
+  vts_atrt_t *vts_atrt;
+  unsigned int i, info_length, sector;
+  uint32_t *data;
+
+  if(!ifofile)
+    return 0;
+  
+  if(!ifofile->vmgi_mat)
+    return 0;
+  
+  if(ifofile->vmgi_mat->vts_atrt == 0) /* mandatory */
+    return 0;
+  
+  sector = ifofile->vmgi_mat->vts_atrt;
+  if(!DVDFileSeek_(ifofile->file, sector * DVD_BLOCK_LEN))
+    return 0;
+
+  vts_atrt = (vts_atrt_t *)malloc(sizeof(vts_atrt_t));
+  if(!vts_atrt)
+    return 0;
+
+  ifofile->vts_atrt = vts_atrt;
+  
+  if(!(DVDReadBytes(ifofile->file, vts_atrt, VTS_ATRT_SIZE))) {
+    free(vts_atrt);
+    ifofile->vts_atrt = 0;
+    return 0;
+  }
+
+  B2N_16(vts_atrt->nr_of_vtss);
+  B2N_32(vts_atrt->last_byte);
+
+  CHECK_ZERO(vts_atrt->zero_1);
+  CHECK_VALUE(vts_atrt->nr_of_vtss != 0);
+  CHECK_VALUE(vts_atrt->nr_of_vtss < 100); //??
+  CHECK_VALUE((uint32_t)vts_atrt->nr_of_vtss * (4 + VTS_ATTRIBUTES_MIN_SIZE) + 
+         VTS_ATRT_SIZE < vts_atrt->last_byte + 1);
+
+  info_length = vts_atrt->nr_of_vtss * sizeof(uint32_t);
+  data = (uint32_t *)malloc(info_length);
+  if(!data) {
+    free(vts_atrt);
+    ifofile->vts_atrt = 0;
+    return 0;
+  }
+
+  vts_atrt->vts_atrt_offsets = data;   
+
+  if(!(DVDReadBytes(ifofile->file, data, info_length))) {
+    free(data);
+    free(vts_atrt);
+    ifofile->vts_atrt = 0;
+    return 0;
+  }
+  
+  for(i = 0; i < vts_atrt->nr_of_vtss; i++) {
+    B2N_32(data[i]);
+    CHECK_VALUE(data[i] + VTS_ATTRIBUTES_MIN_SIZE < vts_atrt->last_byte + 1);
+  }
+  
+  info_length = vts_atrt->nr_of_vtss * sizeof(vts_attributes_t);
+  vts_atrt->vts = (vts_attributes_t *)malloc(info_length);
+  if(!vts_atrt->vts) {
+    free(data);
+    free(vts_atrt);
+    ifofile->vts_atrt = 0;
+    return 0;
+  }
+  for(i = 0; i < vts_atrt->nr_of_vtss; i++) {
+    unsigned int offset = data[i];
+    if(!ifoRead_VTS_ATTRIBUTES(ifofile, &(vts_atrt->vts[i]),
+                               (sector * DVD_BLOCK_LEN) + offset)) {
+      free(data);
+      free(vts_atrt);
+      ifofile->vts_atrt = 0;
+      return 0;
+    }
+
+    // This assert cant be in ifoRead_VTS_ATTRIBUTES
+    CHECK_VALUE(offset + vts_atrt->vts[i].last_byte <= vts_atrt->last_byte + 1);
+    // Is this check correct?
+  }
+
+  return 1;
+}
+
+
+void ifoFree_VTS_ATRT(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->vts_atrt) {
+    free(ifofile->vts_atrt->vts);
+    free(ifofile->vts_atrt->vts_atrt_offsets);
+    free(ifofile->vts_atrt);
+    ifofile->vts_atrt = 0;
+  }
+}
+
+
+int ifoRead_TXTDT_MGI(ifo_handle_t *ifofile) {
+  txtdt_mgi_t *txtdt_mgi;
+
+  if(!ifofile)
+    return 0;
+  
+  if(!ifofile->vmgi_mat)
+    return 0;
+ 
+  /* Return successfully if there is nothing to read. */ 
+  if(ifofile->vmgi_mat->txtdt_mgi == 0)
+    return 1;
+
+  if(!DVDFileSeek_(ifofile->file, 
+		   ifofile->vmgi_mat->txtdt_mgi * DVD_BLOCK_LEN))
+    return 0;
+  
+  txtdt_mgi = (txtdt_mgi_t *)malloc(sizeof(txtdt_mgi_t));
+  if(!txtdt_mgi) {
+    return 0;
+  }
+  ifofile->txtdt_mgi = txtdt_mgi;
+
+  if(!(DVDReadBytes(ifofile->file, txtdt_mgi, TXTDT_MGI_SIZE))) {
+    fprintf(stderr, "libdvdread: Unable to read TXTDT_MGI.\n");
+    free(txtdt_mgi);
+    ifofile->txtdt_mgi = 0;
+    return 0;
+  }
+
+  // fprintf(stderr, "-- Not done yet --\n");
+  return 1;
+}
+
+void ifoFree_TXTDT_MGI(ifo_handle_t *ifofile) {
+  if(!ifofile)
+    return;
+  
+  if(ifofile->txtdt_mgi) {
+    free(ifofile->txtdt_mgi);
+    ifofile->txtdt_mgi = 0;
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ifo_read.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,227 @@
+#ifndef IFO_READ_H_INCLUDED
+#define IFO_READ_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>,
+ *                                Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <dvdread/ifo_types.h>
+#include <dvdread/dvd_reader.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * handle = ifoOpen(dvd, title);
+ *
+ * Opens an IFO and reads in all the data for the IFO file corresponding to the
+ * given title.  If title 0 is given, the video manager IFO file is read.
+ * Returns a handle to a completely parsed structure.
+ */
+ifo_handle_t *ifoOpen(dvd_reader_t *, int );
+
+/**
+ * handle = ifoOpenVMGI(dvd);
+ *
+ * Opens an IFO and reads in _only_ the vmgi_mat data.  This call can be used
+ * together with the calls below to read in each segment of the IFO file on
+ * demand.
+ */
+ifo_handle_t *ifoOpenVMGI(dvd_reader_t *);
+
+/**
+ * handle = ifoOpenVTSI(dvd, title);
+ *
+ * Opens an IFO and reads in _only_ the vtsi_mat data.  This call can be used
+ * together with the calls below to read in each segment of the IFO file on
+ * demand.
+ */
+ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int);
+
+/**
+ * ifoClose(ifofile);
+ * Cleans up the IFO information.  This will free all data allocated for the
+ * substructures.
+ */
+void ifoClose(ifo_handle_t *);
+
+/**
+ * The following functions are for reading only part of the VMGI/VTSI files.
+ * Returns 1 if the data was successfully read and 0 on error.
+ */
+
+/**
+ * okay = ifoRead_PLT_MAIT(ifofile);
+ *
+ * Read in the Parental Management Information table, filling the
+ * ifofile->ptl_mait structure and its substructures.  This data is only
+ * located in the video manager information file.  This fills the
+ * ifofile->ptl_mait structure and all its substructures.
+ */
+int ifoRead_PTL_MAIT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_VTS_ATRT(ifofile);
+ *
+ * Read in the attribute table for the main menu vob, filling the
+ * ifofile->vts_atrt structure and its substructures.  Only located in the
+ * video manager information file.  This fills in the ifofile->vts_atrt
+ * structure and all its substructures.
+ */
+int ifoRead_VTS_ATRT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_TT_SRPT(ifofile);
+ *
+ * Reads the title info for the main menu, filling the ifofile->tt_srpt
+ * structure and its substructures.  This data is only located in the video
+ * manager information file.  This structure is mandatory in the IFO file.
+ */
+int ifoRead_TT_SRPT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_VTS_PTT_SRPT(ifofile);
+ *
+ * Reads in the part of title search pointer table, filling the
+ * ifofile->vts_ptt_srpt structure and its substructures.  This data is only
+ * located in the video title set information file.  This structure is
+ * mandatory, and must be included in the VTSI file.
+ */
+int ifoRead_VTS_PTT_SRPT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_FP_PGC(ifofile);
+ *
+ * Reads in the first play program chain data, filling the
+ * ifofile->first_play_pgc structure.  This data is only located in the video
+ * manager information file (VMGI).  This structure is optional.
+ */
+int ifoRead_FP_PGC(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_PGCIT(ifofile);
+ *
+ * Reads in the program chain information table for the video title set.  Fills
+ * in the ifofile->vts_pgcit structure and its substructures, which includes
+ * the data for each program chain in the set.  This data is only located in
+ * the video title set information file.  This structure is mandatory, and must
+ * be included in the VTSI file.
+ */
+int ifoRead_PGCIT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_PGCI_UT(ifofile);
+ *
+ * Reads in the menu PGCI unit table for the menu VOB.  For the video manager,
+ * this corresponds to the VIDEO_TS.VOB file, and for each title set, this
+ * corresponds to the VTS_XX_0.VOB file.  This data is located in both the
+ * video manager and video title set information files.  For VMGI files, this
+ * fills the ifofile->vmgi_pgci_ut structure and all its substructures.  For
+ * VTSI files, this fills the ifofile->vtsm_pgci_ut structure.
+ */
+int ifoRead_PGCI_UT(ifo_handle_t *);
+  
+/**
+ * okay = ifoRead_VTS_TMAPT(ifofile);
+ *
+ * Reads in the VTS Time Map Table, this data is only located in the video
+ * title set information file.  This fills the ifofile->vts_tmapt structure
+ * and all its substructures.  When pressent enables VOBU level time-based
+ * seeking for One_Sequential_PGC_Titles.
+ */
+int ifoRead_VTS_TMAPT(ifo_handle_t *);
+  
+/**
+ * okay = ifoRead_C_ADT(ifofile);
+ *
+ * Reads in the cell address table for the menu VOB.  For the video manager,
+ * this corresponds to the VIDEO_TS.VOB file, and for each title set, this
+ * corresponds to the VTS_XX_0.VOB file.  This data is located in both the
+ * video manager and video title set information files.  For VMGI files, this
+ * fills the ifofile->vmgm_c_adt structure and all its substructures.  For VTSI
+ * files, this fills the ifofile->vtsm_c_adt structure.
+ */
+int ifoRead_C_ADT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_TITLE_C_ADT(ifofile);
+ *
+ * Reads in the cell address table for the video title set corresponding to
+ * this IFO file.  This data is only located in the video title set information
+ * file.  This structure is mandatory, and must be included in the VTSI file.
+ * This call fills the ifofile->vts_c_adt structure and its substructures.
+ */
+int ifoRead_TITLE_C_ADT(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_VOBU_ADMAP(ifofile);
+ *
+ * Reads in the VOBU address map for the menu VOB.  For the video manager, this
+ * corresponds to the VIDEO_TS.VOB file, and for each title set, this
+ * corresponds to the VTS_XX_0.VOB file.  This data is located in both the
+ * video manager and video title set information files.  For VMGI files, this
+ * fills the ifofile->vmgm_vobu_admap structure and all its substructures.  For
+ * VTSI files, this fills the ifofile->vtsm_vobu_admap structure.
+ */
+int ifoRead_VOBU_ADMAP(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile);
+ *
+ * Reads in the VOBU address map for the associated video title set.  This data
+ * is only located in the video title set information file.  This structure is
+ * mandatory, and must be included in the VTSI file.  Fills the
+ * ifofile->vts_vobu_admap structure and its substructures.
+ */
+int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *);
+
+/**
+ * okay = ifoRead_TXTDT_MGI(ifofile);
+ *
+ * Reads in the text data strings for the DVD.  Fills the ifofile->txtdt_mgi
+ * structure and all its substructures.  This data is only located in the video
+ * manager information file.  This structure is mandatory, and must be included
+ * in the VMGI file.
+ */
+int ifoRead_TXTDT_MGI(ifo_handle_t *);
+
+/**
+ * The following functions are used for freeing parsed sections of the
+ * ifo_handle_t structure and the allocated substructures.  The free calls
+ * below are safe:  they will not mind if you attempt to free part of an IFO
+ * file which was not read in or which does not exist.
+ */
+void ifoFree_PTL_MAIT(ifo_handle_t *);
+void ifoFree_VTS_ATRT(ifo_handle_t *);
+void ifoFree_TT_SRPT(ifo_handle_t *);
+void ifoFree_VTS_PTT_SRPT(ifo_handle_t *);
+void ifoFree_FP_PGC(ifo_handle_t *);
+void ifoFree_PGCIT(ifo_handle_t *);
+void ifoFree_PGCI_UT(ifo_handle_t *);
+void ifoFree_VTS_TMAPT(ifo_handle_t *);
+void ifoFree_C_ADT(ifo_handle_t *);
+void ifoFree_TITLE_C_ADT(ifo_handle_t *);
+void ifoFree_VOBU_ADMAP(ifo_handle_t *);
+void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *);
+void ifoFree_TXTDT_MGI(ifo_handle_t *);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* IFO_READ_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ifo_types.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,884 @@
+#ifndef IFO_TYPES_H_INCLUDED
+#define IFO_TYPES_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001 Björn Englund <d4bjorn@dtek.chalmers.se>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <inttypes.h>
+#include <dvdread/dvd_reader.h>
+
+
+#undef ATTRIBUTE_PACKED
+#undef PRAGMA_PACK_BEGIN 
+#undef PRAGMA_PACK_END
+
+#if defined(__GNUC__)
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define ATTRIBUTE_PACKED __attribute__ ((packed))
+#define PRAGMA_PACK 0
+#endif
+#endif
+
+#if !defined(ATTRIBUTE_PACKED)
+#define ATTRIBUTE_PACKED
+#define PRAGMA_PACK 1
+#endif
+
+#if PRAGMA_PACK
+#pragma pack(1)
+#endif
+
+
+/**
+ * Common
+ *
+ * The following structures are used in both the VMGI and VTSI.
+ */
+
+
+/**
+ * DVD Time Information.
+ */
+typedef struct {
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+  uint8_t frame_u; /* The two high bits are the frame rate. */
+} ATTRIBUTE_PACKED dvd_time_t;
+
+/**
+ * Type to store per-command data.
+ */
+typedef struct {
+  uint8_t bytes[8];
+} ATTRIBUTE_PACKED vm_cmd_t;
+#define COMMAND_DATA_SIZE 8
+
+
+/**
+ * Video Attributes.
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int mpeg_version         : 2;
+  unsigned int video_format         : 2;
+  unsigned int display_aspect_ratio : 2;
+  unsigned int permitted_df         : 2;
+  
+  unsigned int line21_cc_1          : 1;
+  unsigned int line21_cc_2          : 1;
+  unsigned int unknown1             : 1;
+  unsigned int bit_rate             : 1;
+  
+  unsigned int picture_size         : 2;
+  unsigned int letterboxed          : 1;
+  unsigned int film_mode            : 1;
+#else
+  unsigned int permitted_df         : 2;
+  unsigned int display_aspect_ratio : 2;
+  unsigned int video_format         : 2;
+  unsigned int mpeg_version         : 2;
+  
+  unsigned int film_mode            : 1;
+  unsigned int letterboxed          : 1;
+  unsigned int picture_size         : 2;
+  
+  unsigned int bit_rate             : 1;
+  unsigned int unknown1             : 1;
+  unsigned int line21_cc_2          : 1;
+  unsigned int line21_cc_1          : 1;
+#endif
+} ATTRIBUTE_PACKED video_attr_t;
+
+/**
+ * Audio Attributes.
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int audio_format           : 3;
+  unsigned int multichannel_extension : 1;
+  unsigned int lang_type              : 2;
+  unsigned int application_mode       : 2;
+  
+  unsigned int quantization           : 2;
+  unsigned int sample_frequency       : 2;
+  unsigned int unknown1               : 1;
+  unsigned int channels               : 3;
+#else
+  unsigned int application_mode       : 2;
+  unsigned int lang_type              : 2;
+  unsigned int multichannel_extension : 1;
+  unsigned int audio_format           : 3;
+  
+  unsigned int channels               : 3;
+  unsigned int unknown1               : 1;
+  unsigned int sample_frequency       : 2;
+  unsigned int quantization           : 2;
+#endif
+  uint16_t lang_code;
+  uint8_t  lang_extension;
+  uint8_t  code_extension;
+  uint8_t unknown3;
+  union {
+    struct ATTRIBUTE_PACKED {
+#ifdef WORDS_BIGENDIAN
+      unsigned int unknown4           : 1;
+      unsigned int channel_assignment : 3;
+      unsigned int version            : 2;
+      unsigned int mc_intro           : 1; /* probably 0: true, 1:false */
+      unsigned int mode               : 1; /* Karaoke mode 0: solo 1: duet */
+#else
+      unsigned int mode               : 1;
+      unsigned int mc_intro           : 1;
+      unsigned int version            : 2;
+      unsigned int channel_assignment : 3;
+      unsigned int unknown4           : 1;
+#endif
+    } karaoke;
+    struct ATTRIBUTE_PACKED {
+#ifdef WORDS_BIGENDIAN
+      unsigned int unknown5           : 4;
+      unsigned int dolby_encoded      : 1; /* suitable for surround decoding */
+      unsigned int unknown6           : 3;
+#else
+      unsigned int unknown6           : 3;
+      unsigned int dolby_encoded      : 1;
+      unsigned int unknown5           : 4;
+#endif
+    } surround;
+  } app_info;
+} ATTRIBUTE_PACKED audio_attr_t;
+
+
+/**
+ * MultiChannel Extension
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int zero1      : 7;
+  unsigned int ach0_gme   : 1;
+
+  unsigned int zero2      : 7;
+  unsigned int ach1_gme   : 1;
+
+  unsigned int zero3      : 4;
+  unsigned int ach2_gv1e  : 1;
+  unsigned int ach2_gv2e  : 1;
+  unsigned int ach2_gm1e  : 1;
+  unsigned int ach2_gm2e  : 1;
+
+  unsigned int zero4      : 4;
+  unsigned int ach3_gv1e  : 1;
+  unsigned int ach3_gv2e  : 1;
+  unsigned int ach3_gmAe  : 1;
+  unsigned int ach3_se2e  : 1;
+
+  unsigned int zero5      : 4;
+  unsigned int ach4_gv1e  : 1;
+  unsigned int ach4_gv2e  : 1;
+  unsigned int ach4_gmBe  : 1;
+  unsigned int ach4_seBe  : 1;
+#else
+  unsigned int ach0_gme   : 1;
+  unsigned int zero1      : 7;
+
+  unsigned int ach1_gme   : 1;
+  unsigned int zero2      : 7;
+
+  unsigned int ach2_gm2e  : 1;
+  unsigned int ach2_gm1e  : 1;
+  unsigned int ach2_gv2e  : 1;
+  unsigned int ach2_gv1e  : 1;
+  unsigned int zero3      : 4;
+
+  unsigned int ach3_se2e  : 1;
+  unsigned int ach3_gmAe  : 1;
+  unsigned int ach3_gv2e  : 1;
+  unsigned int ach3_gv1e  : 1;
+  unsigned int zero4      : 4;
+
+  unsigned int ach4_seBe  : 1;
+  unsigned int ach4_gmBe  : 1;
+  unsigned int ach4_gv2e  : 1;
+  unsigned int ach4_gv1e  : 1;
+  unsigned int zero5      : 4;
+#endif
+  uint8_t zero6[19];
+} ATTRIBUTE_PACKED multichannel_ext_t;
+
+
+/**
+ * Subpicture Attributes.
+ */
+typedef struct {
+  /*
+   * type: 0 not specified
+   *       1 language
+   *       2 other
+   * coding mode: 0 run length
+   *              1 extended
+   *              2 other
+   * language: indicates language if type == 1
+   * lang extension: if type == 1 contains the lang extension
+   */
+#ifdef WORDS_BIGENDIAN
+  unsigned int code_mode : 3;
+  unsigned int zero1     : 3;
+  unsigned int type      : 2;
+#else
+  unsigned int type      : 2;
+  unsigned int zero1     : 3;
+  unsigned int code_mode : 3;
+#endif
+  uint8_t  zero2;
+  uint16_t lang_code;
+  uint8_t  lang_extension;
+  uint8_t  code_extension;
+} ATTRIBUTE_PACKED subp_attr_t;
+
+
+
+/**
+ * PGC Command Table.
+ */ 
+typedef struct {
+  uint16_t nr_of_pre;
+  uint16_t nr_of_post;
+  uint16_t nr_of_cell;
+  uint16_t zero_1;
+  vm_cmd_t *pre_cmds;
+  vm_cmd_t *post_cmds;
+  vm_cmd_t *cell_cmds;
+} ATTRIBUTE_PACKED pgc_command_tbl_t;
+#define PGC_COMMAND_TBL_SIZE 8
+
+/**
+ * PGC Program Map
+ */
+typedef uint8_t pgc_program_map_t; 
+
+/**
+ * Cell Playback Information.
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int block_mode       : 2;
+  unsigned int block_type       : 2;
+  unsigned int seamless_play    : 1;
+  unsigned int interleaved      : 1;
+  unsigned int stc_discontinuity: 1;
+  unsigned int seamless_angle   : 1;
+  
+  unsigned int playback_mode    : 1;  /**< When set, enter StillMode after each VOBU */
+  unsigned int restricted       : 1;  /**< ?? drop out of fastforward? */
+  unsigned int unknown2         : 6;
+#else
+  unsigned int seamless_angle   : 1;
+  unsigned int stc_discontinuity: 1;
+  unsigned int interleaved      : 1;
+  unsigned int seamless_play    : 1;
+  unsigned int block_type       : 2;
+  unsigned int block_mode       : 2;
+  
+  unsigned int unknown2         : 6;
+  unsigned int restricted       : 1;
+  unsigned int playback_mode    : 1;
+#endif
+  uint8_t still_time;
+  uint8_t cell_cmd_nr;
+  dvd_time_t playback_time;
+  uint32_t first_sector;
+  uint32_t first_ilvu_end_sector;
+  uint32_t last_vobu_start_sector;
+  uint32_t last_sector;
+} ATTRIBUTE_PACKED cell_playback_t;
+
+#define BLOCK_TYPE_NONE         0x0
+#define BLOCK_TYPE_ANGLE_BLOCK  0x1
+
+#define BLOCK_MODE_NOT_IN_BLOCK 0x0
+#define BLOCK_MODE_FIRST_CELL   0x1
+#define BLOCK_MODE_IN_BLOCK     0x2
+#define BLOCK_MODE_LAST_CELL    0x3
+
+/**
+ * Cell Position Information.
+ */
+typedef struct {
+  uint16_t vob_id_nr;
+  uint8_t  zero_1;
+  uint8_t  cell_nr;
+} ATTRIBUTE_PACKED cell_position_t;
+
+/**
+ * User Operations.
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int zero                           : 7; /* 25-31 */
+  unsigned int video_pres_mode_change         : 1; /* 24 */
+  
+  unsigned int karaoke_audio_pres_mode_change : 1; /* 23 */
+  unsigned int angle_change                   : 1;
+  unsigned int subpic_stream_change           : 1;
+  unsigned int audio_stream_change            : 1;
+  unsigned int pause_on                       : 1;
+  unsigned int still_off                      : 1;
+  unsigned int button_select_or_activate      : 1;
+  unsigned int resume                         : 1; /* 16 */
+  
+  unsigned int chapter_menu_call              : 1; /* 15 */
+  unsigned int angle_menu_call                : 1;
+  unsigned int audio_menu_call                : 1;
+  unsigned int subpic_menu_call               : 1;
+  unsigned int root_menu_call                 : 1;
+  unsigned int title_menu_call                : 1;
+  unsigned int backward_scan                  : 1;
+  unsigned int forward_scan                   : 1; /* 8 */
+  
+  unsigned int next_pg_search                 : 1; /* 7 */
+  unsigned int prev_or_top_pg_search          : 1;
+  unsigned int time_or_chapter_search         : 1;
+  unsigned int go_up                          : 1;
+  unsigned int stop                           : 1;
+  unsigned int title_play                     : 1;
+  unsigned int chapter_search_or_play         : 1;
+  unsigned int title_or_time_play             : 1; /* 0 */
+#else
+  unsigned int video_pres_mode_change         : 1; /* 24 */
+  unsigned int zero                           : 7; /* 25-31 */
+  
+  unsigned int resume                         : 1; /* 16 */
+  unsigned int button_select_or_activate      : 1;
+  unsigned int still_off                      : 1;
+  unsigned int pause_on                       : 1;
+  unsigned int audio_stream_change            : 1;
+  unsigned int subpic_stream_change           : 1;
+  unsigned int angle_change                   : 1;
+  unsigned int karaoke_audio_pres_mode_change : 1; /* 23 */
+  
+  unsigned int forward_scan                   : 1; /* 8 */
+  unsigned int backward_scan                  : 1;
+  unsigned int title_menu_call                : 1;
+  unsigned int root_menu_call                 : 1;
+  unsigned int subpic_menu_call               : 1;
+  unsigned int audio_menu_call                : 1;
+  unsigned int angle_menu_call                : 1;
+  unsigned int chapter_menu_call              : 1; /* 15 */
+  
+  unsigned int title_or_time_play             : 1; /* 0 */
+  unsigned int chapter_search_or_play         : 1;
+  unsigned int title_play                     : 1;
+  unsigned int stop                           : 1;
+  unsigned int go_up                          : 1;
+  unsigned int time_or_chapter_search         : 1;
+  unsigned int prev_or_top_pg_search          : 1;
+  unsigned int next_pg_search                 : 1; /* 7 */
+#endif
+} ATTRIBUTE_PACKED user_ops_t;
+
+/**
+ * Program Chain Information.
+ */
+typedef struct {
+  uint16_t zero_1;
+  uint8_t  nr_of_programs;
+  uint8_t  nr_of_cells;
+  dvd_time_t playback_time;
+  user_ops_t prohibited_ops;
+  uint16_t audio_control[8]; /* New type? */
+  uint32_t subp_control[32]; /* New type? */
+  uint16_t next_pgc_nr;
+  uint16_t prev_pgc_nr;
+  uint16_t goup_pgc_nr;
+  uint8_t  still_time;
+  uint8_t  pg_playback_mode;
+  uint32_t palette[16]; /* New type struct {zero_1, Y, Cr, Cb} ? */
+  uint16_t command_tbl_offset;
+  uint16_t program_map_offset;
+  uint16_t cell_playback_offset;
+  uint16_t cell_position_offset;
+  pgc_command_tbl_t *command_tbl;
+  pgc_program_map_t  *program_map;
+  cell_playback_t *cell_playback;
+  cell_position_t *cell_position;
+} ATTRIBUTE_PACKED pgc_t;
+#define PGC_SIZE 236
+
+/**
+ * Program Chain Information Search Pointer.
+ */
+typedef struct {
+  uint8_t  entry_id;
+#ifdef WORDS_BIGENDIAN
+  unsigned int block_mode : 2;
+  unsigned int block_type : 2;
+  unsigned int unknown1   : 4;
+#else
+  unsigned int unknown1   : 4;
+  unsigned int block_type : 2;
+  unsigned int block_mode : 2;
+#endif  
+  uint16_t ptl_id_mask;
+  uint32_t pgc_start_byte;
+  pgc_t *pgc;
+} ATTRIBUTE_PACKED pgci_srp_t;
+#define PGCI_SRP_SIZE 8
+
+/**
+ * Program Chain Information Table.
+ */
+typedef struct {
+  uint16_t nr_of_pgci_srp;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  pgci_srp_t *pgci_srp;
+} ATTRIBUTE_PACKED pgcit_t;
+#define PGCIT_SIZE 8
+
+/**
+ * Menu PGCI Language Unit.
+ */
+typedef struct {
+  uint16_t lang_code;
+  uint8_t  lang_extension;
+  uint8_t  exists;
+  uint32_t lang_start_byte;
+  pgcit_t *pgcit;
+} ATTRIBUTE_PACKED pgci_lu_t;
+#define PGCI_LU_SIZE 8
+
+/**
+ * Menu PGCI Unit Table.
+ */
+typedef struct {
+  uint16_t nr_of_lus;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  pgci_lu_t *lu;
+} ATTRIBUTE_PACKED pgci_ut_t;
+#define PGCI_UT_SIZE 8
+
+/**
+ * Cell Address Information.
+ */
+typedef struct {
+  uint16_t vob_id;
+  uint8_t  cell_id;
+  uint8_t  zero_1;
+  uint32_t start_sector;
+  uint32_t last_sector;
+} ATTRIBUTE_PACKED cell_adr_t;
+
+/**
+ * Cell Address Table.
+ */
+typedef struct {
+  uint16_t nr_of_vobs; /* VOBs */
+  uint16_t zero_1;
+  uint32_t last_byte;
+  cell_adr_t *cell_adr_table;  /* No explicit size given. */
+} ATTRIBUTE_PACKED c_adt_t;
+#define C_ADT_SIZE 8
+
+/**
+ * VOBU Address Map.
+ */
+typedef struct {
+  uint32_t last_byte;
+  uint32_t *vobu_start_sectors;
+} ATTRIBUTE_PACKED vobu_admap_t;
+#define VOBU_ADMAP_SIZE 4
+
+
+
+
+/**
+ * VMGI
+ *
+ * The following structures relate to the Video Manager.
+ */
+
+/**
+ * Video Manager Information Management Table.
+ */
+typedef struct {
+  char     vmg_identifier[12];
+  uint32_t vmg_last_sector;
+  uint8_t  zero_1[12];
+  uint32_t vmgi_last_sector;
+  uint8_t  zero_2;
+  uint8_t  specification_version;
+  uint32_t vmg_category;
+  uint16_t vmg_nr_of_volumes;
+  uint16_t vmg_this_volume_nr;
+  uint8_t  disc_side;
+  uint8_t  zero_3[19];
+  uint16_t vmg_nr_of_title_sets;  /* Number of VTSs. */
+  char     provider_identifier[32];
+  uint64_t vmg_pos_code;
+  uint8_t  zero_4[24];
+  uint32_t vmgi_last_byte;
+  uint32_t first_play_pgc;
+  uint8_t  zero_5[56];
+  uint32_t vmgm_vobs;             /* sector */
+  uint32_t tt_srpt;               /* sector */
+  uint32_t vmgm_pgci_ut;          /* sector */
+  uint32_t ptl_mait;              /* sector */
+  uint32_t vts_atrt;              /* sector */
+  uint32_t txtdt_mgi;             /* sector */
+  uint32_t vmgm_c_adt;            /* sector */
+  uint32_t vmgm_vobu_admap;       /* sector */
+  uint8_t  zero_6[32];
+  
+  video_attr_t vmgm_video_attr;
+  uint8_t  zero_7;
+  uint8_t  nr_of_vmgm_audio_streams; /* should be 0 or 1 */
+  audio_attr_t vmgm_audio_attr;
+  audio_attr_t zero_8[7];
+  uint8_t  zero_9[17];
+  uint8_t  nr_of_vmgm_subp_streams; /* should be 0 or 1 */
+  subp_attr_t  vmgm_subp_attr;
+  subp_attr_t  zero_10[27];  /* XXX: how much 'padding' here? */
+} ATTRIBUTE_PACKED vmgi_mat_t;
+
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int zero_1                    : 1;
+  unsigned int multi_or_random_pgc_title : 1; /* 0: one sequential pgc title */
+  unsigned int jlc_exists_in_cell_cmd    : 1;
+  unsigned int jlc_exists_in_prepost_cmd : 1;
+  unsigned int jlc_exists_in_button_cmd  : 1;
+  unsigned int jlc_exists_in_tt_dom      : 1;
+  unsigned int chapter_search_or_play    : 1; /* UOP 1 */
+  unsigned int title_or_time_play        : 1; /* UOP 0 */
+#else
+  unsigned int title_or_time_play        : 1;
+  unsigned int chapter_search_or_play    : 1;
+  unsigned int jlc_exists_in_tt_dom      : 1;
+  unsigned int jlc_exists_in_button_cmd  : 1;
+  unsigned int jlc_exists_in_prepost_cmd : 1;
+  unsigned int jlc_exists_in_cell_cmd    : 1;
+  unsigned int multi_or_random_pgc_title : 1;
+  unsigned int zero_1                    : 1;
+#endif
+} ATTRIBUTE_PACKED playback_type_t;
+
+/**
+ * Title Information.
+ */
+typedef struct {
+  playback_type_t pb_ty;
+  uint8_t  nr_of_angles;
+  uint16_t nr_of_ptts;
+  uint16_t parental_id;
+  uint8_t  title_set_nr;
+  uint8_t  vts_ttn;
+  uint32_t title_set_sector;
+} ATTRIBUTE_PACKED title_info_t;
+
+/**
+ * PartOfTitle Search Pointer Table.
+ */
+typedef struct {
+  uint16_t nr_of_srpts;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  title_info_t *title;
+} ATTRIBUTE_PACKED tt_srpt_t;
+#define TT_SRPT_SIZE 8
+
+
+/**
+ * Parental Management Information Unit Table.
+ * Level 1 (US: G), ..., 7 (US: NC-17), 8
+ */
+typedef uint16_t pf_level_t[8];
+
+/**
+ * Parental Management Information Unit Table.
+ */
+typedef struct {
+  uint16_t country_code;
+  uint16_t zero_1;
+  uint16_t pf_ptl_mai_start_byte;
+  uint16_t zero_2;
+  pf_level_t *pf_ptl_mai; /* table of (nr_of_vtss + 1), video_ts is first */
+} ATTRIBUTE_PACKED ptl_mait_country_t;
+#define PTL_MAIT_COUNTRY_SIZE 8
+
+/**
+ * Parental Management Information Table.
+ */
+typedef struct {
+  uint16_t nr_of_countries;
+  uint16_t nr_of_vtss;
+  uint32_t last_byte;
+  ptl_mait_country_t *countries;
+} ATTRIBUTE_PACKED ptl_mait_t;
+#define PTL_MAIT_SIZE 8
+
+/**
+ * Video Title Set Attributes.
+ */
+typedef struct {
+  uint32_t last_byte;
+  uint32_t vts_cat;
+  
+  video_attr_t vtsm_vobs_attr;
+  uint8_t  zero_1;
+  uint8_t  nr_of_vtsm_audio_streams; /* should be 0 or 1 */
+  audio_attr_t vtsm_audio_attr;
+  audio_attr_t zero_2[7];  
+  uint8_t  zero_3[16];
+  uint8_t  zero_4;
+  uint8_t  nr_of_vtsm_subp_streams; /* should be 0 or 1 */
+  subp_attr_t vtsm_subp_attr;
+  subp_attr_t zero_5[27];
+  
+  uint8_t  zero_6[2];
+  
+  video_attr_t vtstt_vobs_video_attr;
+  uint8_t  zero_7;
+  uint8_t  nr_of_vtstt_audio_streams;
+  audio_attr_t vtstt_audio_attr[8];
+  uint8_t  zero_8[16];
+  uint8_t  zero_9;
+  uint8_t  nr_of_vtstt_subp_streams;
+  subp_attr_t vtstt_subp_attr[32];
+} ATTRIBUTE_PACKED vts_attributes_t;
+#define VTS_ATTRIBUTES_SIZE 542
+#define VTS_ATTRIBUTES_MIN_SIZE 356
+
+/**
+ * Video Title Set Attribute Table.
+ */
+typedef struct {
+  uint16_t nr_of_vtss;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  vts_attributes_t *vts;
+  uint32_t *vts_atrt_offsets; /* offsets table for each vts_attributes */
+} ATTRIBUTE_PACKED vts_atrt_t;
+#define VTS_ATRT_SIZE 8
+
+/**
+ * Text Data. (Incomplete)
+ */
+typedef struct {
+  uint32_t last_byte;    /* offsets are relative here */
+  uint16_t offsets[100]; /* == nr_of_srpts + 1 (first is disc title) */
+#if 0  
+  uint16_t unknown; /* 0x48 ?? 0x48 words (16bit) info following */
+  uint16_t zero_1;
+  
+  uint8_t type_of_info; /* ?? 01 == disc, 02 == Title, 04 == Title part */
+  uint8_t unknown1;
+  uint8_t unknown2;
+  uint8_t unknown3;
+  uint8_t unknown4; /* ?? allways 0x30 language?, text format? */
+  uint8_t unknown5;
+  uint16_t offset; /* from first */
+  
+  char text[12]; /* ended by 0x09 */
+#endif
+} ATTRIBUTE_PACKED txtdt_t;
+
+/**
+ * Text Data Language Unit. (Incomplete)
+ */ 
+typedef struct {
+  uint16_t lang_code;
+  uint16_t unknown;      /* 0x0001, title 1? disc 1? side 1? */
+  uint32_t txtdt_start_byte;  /* prt, rel start of vmg_txtdt_mgi  */
+  txtdt_t  *txtdt;
+} ATTRIBUTE_PACKED txtdt_lu_t;
+#define TXTDT_LU_SIZE 8
+
+/**
+ * Text Data Manager Information. (Incomplete)
+ */
+typedef struct {
+  char disc_name[14];            /* how many bytes?? */
+  uint16_t nr_of_language_units; /* 32bit??          */
+  uint32_t last_byte;
+  txtdt_lu_t *lu;
+} ATTRIBUTE_PACKED txtdt_mgi_t;
+#define TXTDT_MGI_SIZE 20
+
+
+/**
+ * VTS
+ *
+ * Structures relating to the Video Title Set (VTS).
+ */
+
+/**
+ * Video Title Set Information Management Table.
+ */
+typedef struct {
+  char vts_identifier[12];
+  uint32_t vts_last_sector;
+  uint8_t  zero_1[12];
+  uint32_t vtsi_last_sector;
+  uint8_t  zero_2;
+  uint8_t  specification_version;
+  uint32_t vts_category;
+  uint16_t zero_3;
+  uint16_t zero_4;
+  uint8_t  zero_5;
+  uint8_t  zero_6[19];
+  uint16_t zero_7;
+  uint8_t  zero_8[32];
+  uint64_t zero_9;
+  uint8_t  zero_10[24];
+  uint32_t vtsi_last_byte;
+  uint32_t zero_11;
+  uint8_t  zero_12[56];
+  uint32_t vtsm_vobs;       /* sector */
+  uint32_t vtstt_vobs;      /* sector */
+  uint32_t vts_ptt_srpt;    /* sector */
+  uint32_t vts_pgcit;       /* sector */
+  uint32_t vtsm_pgci_ut;    /* sector */
+  uint32_t vts_tmapt;       /* sector */
+  uint32_t vtsm_c_adt;      /* sector */
+  uint32_t vtsm_vobu_admap; /* sector */
+  uint32_t vts_c_adt;       /* sector */
+  uint32_t vts_vobu_admap;  /* sector */
+  uint8_t  zero_13[24];
+  
+  video_attr_t vtsm_video_attr;
+  uint8_t  zero_14;
+  uint8_t  nr_of_vtsm_audio_streams; /* should be 0 or 1 */
+  audio_attr_t vtsm_audio_attr;
+  audio_attr_t zero_15[7];
+  uint8_t  zero_16[17];
+  uint8_t  nr_of_vtsm_subp_streams; /* should be 0 or 1 */
+  subp_attr_t vtsm_subp_attr;
+  subp_attr_t zero_17[27];
+  uint8_t  zero_18[2];
+  
+  video_attr_t vts_video_attr;
+  uint8_t  zero_19;
+  uint8_t  nr_of_vts_audio_streams;
+  audio_attr_t vts_audio_attr[8];
+  uint8_t  zero_20[17];
+  uint8_t  nr_of_vts_subp_streams;
+  subp_attr_t vts_subp_attr[32];
+  uint16_t zero_21;
+  multichannel_ext_t vts_mu_audio_attr[8];
+  /* XXX: how much 'padding' here, if any? */
+} ATTRIBUTE_PACKED vtsi_mat_t;
+
+/**
+ * PartOfTitle Unit Information.
+ */
+typedef struct {
+  uint16_t pgcn;
+  uint16_t pgn;
+} ATTRIBUTE_PACKED ptt_info_t;
+
+/**
+ * PartOfTitle Information.
+ */
+typedef struct {
+  uint16_t nr_of_ptts;
+  ptt_info_t *ptt;
+} ATTRIBUTE_PACKED ttu_t;
+
+/**
+ * PartOfTitle Search Pointer Table.
+ */
+typedef struct {
+  uint16_t nr_of_srpts;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  ttu_t  *title;
+  uint32_t *ttu_offset; /* offset table for each ttu */
+} ATTRIBUTE_PACKED vts_ptt_srpt_t;
+#define VTS_PTT_SRPT_SIZE 8
+
+
+/**
+ * Time Map Entry.
+ */
+/* Should this be bit field at all or just the uint32_t? */
+typedef uint32_t map_ent_t;
+
+/**
+ * Time Map.
+ */
+typedef struct {
+  uint8_t  tmu;   /* Time unit, in seconds */
+  uint8_t  zero_1;
+  uint16_t nr_of_entries;
+  map_ent_t *map_ent;
+} ATTRIBUTE_PACKED vts_tmap_t;
+#define VTS_TMAP_SIZE 4
+
+/**
+ * Time Map Table.
+ */
+typedef struct {
+  uint16_t nr_of_tmaps;
+  uint16_t zero_1;
+  uint32_t last_byte;
+  vts_tmap_t *tmap;
+  uint32_t *tmap_offset; /* offset table for each tmap */
+} ATTRIBUTE_PACKED vts_tmapt_t;
+#define VTS_TMAPT_SIZE 8
+
+
+#if PRAGMA_PACK
+#pragma pack()
+#endif
+
+
+/**
+ * The following structure defines an IFO file.  The structure is divided into
+ * two parts, the VMGI, or Video Manager Information, which is read from the
+ * VIDEO_TS.[IFO,BUP] file, and the VTSI, or Video Title Set Information, which
+ * is read in from the VTS_XX_0.[IFO,BUP] files.
+ */
+typedef struct {
+  dvd_file_t *file;
+  
+  /* VMGI */
+  vmgi_mat_t     *vmgi_mat;
+  tt_srpt_t      *tt_srpt;
+  pgc_t          *first_play_pgc;    
+  ptl_mait_t     *ptl_mait;
+  vts_atrt_t     *vts_atrt;
+  txtdt_mgi_t    *txtdt_mgi;
+  
+  /* Common */
+  pgci_ut_t      *pgci_ut;
+  c_adt_t        *menu_c_adt;
+  vobu_admap_t   *menu_vobu_admap;
+  
+  /* VTSI */
+  vtsi_mat_t     *vtsi_mat;
+  vts_ptt_srpt_t *vts_ptt_srpt;
+  pgcit_t        *vts_pgcit;
+  vts_tmapt_t    *vts_tmapt;
+  c_adt_t        *vts_c_adt;
+  vobu_admap_t   *vts_vobu_admap;
+} ifo_handle_t;
+
+#endif /* IFO_TYPES_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md5.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,417 @@
+/* md5.c - Functions to compute MD5 message digest of files or memory blocks
+   according to the definition of MD5 in RFC 1321 from April 1992.
+   Copyright (C) 1995, 1996, 2001 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+   This program 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, or (at your option) any
+   later version.
+
+   This program 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.  */
+
+/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#if STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef HAVE_MEMCPY
+#  define memcpy(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include "md5.h"
+//#include "unlocked-io.h"
+
+#ifdef _LIBC
+# include <endian.h>
+# if __BYTE_ORDER == __BIG_ENDIAN
+#  define WORDS_BIGENDIAN 1
+# endif
+#endif
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n)							\
+    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+   64-byte boundary.  (RFC 1321, 3.1: Step 1)  */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };
+
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+void
+md5_init_ctx (ctx)
+     struct md5_ctx *ctx;
+{
+  ctx->A = 0x67452301;
+  ctx->B = 0xefcdab89;
+  ctx->C = 0x98badcfe;
+  ctx->D = 0x10325476;
+
+  ctx->total[0] = ctx->total[1] = 0;
+  ctx->buflen = 0;
+}
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result
+   must be in little endian byte order.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_read_ctx (ctx, resbuf)
+     const struct md5_ctx *ctx;
+     void *resbuf;
+{
+  ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
+  ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
+  ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
+  ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
+
+  return resbuf;
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+   prolog according to the standard and write the result to RESBUF.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_finish_ctx (ctx, resbuf)
+     struct md5_ctx *ctx;
+     void *resbuf;
+{
+  /* Take yet unprocessed bytes into account.  */
+  md5_uint32 bytes = ctx->buflen;
+  size_t pad;
+
+  /* Now count remaining bytes.  */
+  ctx->total[0] += bytes;
+  if (ctx->total[0] < bytes)
+    ++ctx->total[1];
+
+  pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+  memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+  /* Put the 64-bit file length in *bits* at the end of the buffer.  */
+  *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
+  *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
+							(ctx->total[0] >> 29));
+
+  /* Process last bytes.  */
+  md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+  return md5_read_ctx (ctx, resbuf);
+}
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.  */
+int
+md5_stream (stream, resblock)
+     FILE *stream;
+     void *resblock;
+{
+  /* Important: BLOCKSIZE must be a multiple of 64.  */
+#define BLOCKSIZE 4096
+  struct md5_ctx ctx;
+  char buffer[BLOCKSIZE + 72];
+  size_t sum;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Iterate over full file contents.  */
+  while (1)
+    {
+      /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
+	 computation function processes the whole buffer so that with the
+	 next round of the loop another block can be read.  */
+      size_t n;
+      sum = 0;
+
+      /* Read block.  Take care for partial reads.  */
+      do
+	{
+	  n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
+
+	  sum += n;
+	}
+      while (sum < BLOCKSIZE && n != 0);
+      if (n == 0 && ferror (stream))
+        return 1;
+
+      /* If end of file is reached, end the loop.  */
+      if (n == 0)
+	break;
+
+      /* Process buffer with BLOCKSIZE bytes.  Note that
+			BLOCKSIZE % 64 == 0
+       */
+      md5_process_block (buffer, BLOCKSIZE, &ctx);
+    }
+
+  /* Add the last bytes if necessary.  */
+  if (sum > 0)
+    md5_process_bytes (buffer, sum, &ctx);
+
+  /* Construct result in desired memory.  */
+  md5_finish_ctx (&ctx, resblock);
+  return 0;
+}
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+void *
+md5_buffer (buffer, len, resblock)
+     const char *buffer;
+     size_t len;
+     void *resblock;
+{
+  struct md5_ctx ctx;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Process whole buffer but last len % 64 bytes.  */
+  md5_process_bytes (buffer, len, &ctx);
+
+  /* Put result in desired memory area.  */
+  return md5_finish_ctx (&ctx, resblock);
+}
+
+
+void
+md5_process_bytes (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+  /* When we already have some bits in our internal buffer concatenate
+     both inputs first.  */
+  if (ctx->buflen != 0)
+    {
+      size_t left_over = ctx->buflen;
+      size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+      memcpy (&ctx->buffer[left_over], buffer, add);
+      ctx->buflen += add;
+
+      if (left_over + add > 64)
+	{
+	  md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
+	  /* The regions in the following copy operation cannot overlap.  */
+	  memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+		  (left_over + add) & 63);
+	  ctx->buflen = (left_over + add) & 63;
+	}
+
+      buffer = (const char *) buffer + add;
+      len -= add;
+    }
+
+  /* Process available complete blocks.  */
+  if (len > 64)
+    {
+      md5_process_block (buffer, len & ~63, ctx);
+      buffer = (const char *) buffer + (len & ~63);
+      len &= 63;
+    }
+
+  /* Move remaining bytes in internal buffer.  */
+  if (len > 0)
+    {
+      memcpy (ctx->buffer, buffer, len);
+      ctx->buflen = len;
+    }
+}
+
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+   and defined in the RFC 1321.  The first function is a little bit optimized
+   (as found in Colin Plumbs public domain implementation).  */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+   It is assumed that LEN % 64 == 0.  */
+
+void
+md5_process_block (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+  md5_uint32 correct_words[16];
+  const md5_uint32 *words = buffer;
+  size_t nwords = len / sizeof (md5_uint32);
+  const md5_uint32 *endp = words + nwords;
+  md5_uint32 A = ctx->A;
+  md5_uint32 B = ctx->B;
+  md5_uint32 C = ctx->C;
+  md5_uint32 D = ctx->D;
+
+  /* First increment the byte count.  RFC 1321 specifies the possible
+     length of the file up to 2^64 bits.  Here we only compute the
+     number of bytes.  Do a double word increment.  */
+  ctx->total[0] += len;
+  if (ctx->total[0] < len)
+    ++ctx->total[1];
+
+  /* Process all bytes in the buffer with 64 bytes in each round of
+     the loop.  */
+  while (words < endp)
+    {
+      md5_uint32 *cwp = correct_words;
+      md5_uint32 A_save = A;
+      md5_uint32 B_save = B;
+      md5_uint32 C_save = C;
+      md5_uint32 D_save = D;
+
+      /* First round: using the given function, the context and a constant
+	 the next context is computed.  Because the algorithms processing
+	 unit is a 32-bit word and it is determined to work on words in
+	 little endian byte order we perhaps have to change the byte order
+	 before the computation.  To reduce the work for the next steps
+	 we store the swapped words in the array CORRECT_WORDS.  */
+
+#define OP(a, b, c, d, s, T)						\
+      do								\
+        {								\
+	  a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T;		\
+	  ++words;							\
+	  a = rol (a, s);						\
+	  a += b;							\
+        }								\
+      while (0)
+
+      /* Before we start, one word to the strange constants.
+	 They are defined in RFC 1321 as
+
+	 T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64, or
+	 perl -e 'foreach(1..64){printf "0x%08x\n", int (4294967296 * abs (sin $_))}'
+       */
+
+      /* Round 1.  */
+      OP (A, B, C, D,  7, 0xd76aa478);
+      OP (D, A, B, C, 12, 0xe8c7b756);
+      OP (C, D, A, B, 17, 0x242070db);
+      OP (B, C, D, A, 22, 0xc1bdceee);
+      OP (A, B, C, D,  7, 0xf57c0faf);
+      OP (D, A, B, C, 12, 0x4787c62a);
+      OP (C, D, A, B, 17, 0xa8304613);
+      OP (B, C, D, A, 22, 0xfd469501);
+      OP (A, B, C, D,  7, 0x698098d8);
+      OP (D, A, B, C, 12, 0x8b44f7af);
+      OP (C, D, A, B, 17, 0xffff5bb1);
+      OP (B, C, D, A, 22, 0x895cd7be);
+      OP (A, B, C, D,  7, 0x6b901122);
+      OP (D, A, B, C, 12, 0xfd987193);
+      OP (C, D, A, B, 17, 0xa679438e);
+      OP (B, C, D, A, 22, 0x49b40821);
+
+      /* For the second to fourth round we have the possibly swapped words
+	 in CORRECT_WORDS.  Redefine the macro to take an additional first
+	 argument specifying the function to use.  */
+#undef OP
+#define OP(f, a, b, c, d, k, s, T)					\
+      do 								\
+	{								\
+	  a += f (b, c, d) + correct_words[k] + T;			\
+	  a = rol (a, s);						\
+	  a += b;							\
+	}								\
+      while (0)
+
+      /* Round 2.  */
+      OP (FG, A, B, C, D,  1,  5, 0xf61e2562);
+      OP (FG, D, A, B, C,  6,  9, 0xc040b340);
+      OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
+      OP (FG, B, C, D, A,  0, 20, 0xe9b6c7aa);
+      OP (FG, A, B, C, D,  5,  5, 0xd62f105d);
+      OP (FG, D, A, B, C, 10,  9, 0x02441453);
+      OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
+      OP (FG, B, C, D, A,  4, 20, 0xe7d3fbc8);
+      OP (FG, A, B, C, D,  9,  5, 0x21e1cde6);
+      OP (FG, D, A, B, C, 14,  9, 0xc33707d6);
+      OP (FG, C, D, A, B,  3, 14, 0xf4d50d87);
+      OP (FG, B, C, D, A,  8, 20, 0x455a14ed);
+      OP (FG, A, B, C, D, 13,  5, 0xa9e3e905);
+      OP (FG, D, A, B, C,  2,  9, 0xfcefa3f8);
+      OP (FG, C, D, A, B,  7, 14, 0x676f02d9);
+      OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+
+      /* Round 3.  */
+      OP (FH, A, B, C, D,  5,  4, 0xfffa3942);
+      OP (FH, D, A, B, C,  8, 11, 0x8771f681);
+      OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
+      OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
+      OP (FH, A, B, C, D,  1,  4, 0xa4beea44);
+      OP (FH, D, A, B, C,  4, 11, 0x4bdecfa9);
+      OP (FH, C, D, A, B,  7, 16, 0xf6bb4b60);
+      OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
+      OP (FH, A, B, C, D, 13,  4, 0x289b7ec6);
+      OP (FH, D, A, B, C,  0, 11, 0xeaa127fa);
+      OP (FH, C, D, A, B,  3, 16, 0xd4ef3085);
+      OP (FH, B, C, D, A,  6, 23, 0x04881d05);
+      OP (FH, A, B, C, D,  9,  4, 0xd9d4d039);
+      OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
+      OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+      OP (FH, B, C, D, A,  2, 23, 0xc4ac5665);
+
+      /* Round 4.  */
+      OP (FI, A, B, C, D,  0,  6, 0xf4292244);
+      OP (FI, D, A, B, C,  7, 10, 0x432aff97);
+      OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
+      OP (FI, B, C, D, A,  5, 21, 0xfc93a039);
+      OP (FI, A, B, C, D, 12,  6, 0x655b59c3);
+      OP (FI, D, A, B, C,  3, 10, 0x8f0ccc92);
+      OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
+      OP (FI, B, C, D, A,  1, 21, 0x85845dd1);
+      OP (FI, A, B, C, D,  8,  6, 0x6fa87e4f);
+      OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+      OP (FI, C, D, A, B,  6, 15, 0xa3014314);
+      OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
+      OP (FI, A, B, C, D,  4,  6, 0xf7537e82);
+      OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
+      OP (FI, C, D, A, B,  2, 15, 0x2ad7d2bb);
+      OP (FI, B, C, D, A,  9, 21, 0xeb86d391);
+
+      /* Add the starting values of the context.  */
+      A += A_save;
+      B += B_save;
+      C += C_save;
+      D += D_save;
+    }
+
+  /* Put checksum in context given as argument.  */
+  ctx->A = A;
+  ctx->B = B;
+  ctx->C = C;
+  ctx->D = D;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/md5.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,161 @@
+/* md5.h - Declaration of functions and data types used for MD5 sum
+   computing library functions.
+   Copyright (C) 1995, 1996, 1999 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+   This program 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, or (at your option) any
+   later version.
+
+   This program 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.  */
+
+#ifndef _MD5_H
+#define _MD5_H 1
+
+#include <stdio.h>
+
+#if defined HAVE_LIMITS_H || _LIBC
+# include <limits.h>
+#endif
+
+/* The following contortions are an attempt to use the C preprocessor
+   to determine an unsigned integral type that is 32 bits wide.  An
+   alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
+   doing that would require that the configure script compile and *run*
+   the resulting executable.  Locally running cross-compiled executables
+   is usually not possible.  */
+
+#ifdef _LIBC
+# include <sys/types.h>
+typedef u_int32_t md5_uint32;
+#else
+# if defined __STDC__ && __STDC__
+#  define UINT_MAX_32_BITS 4294967295U
+# else
+#  define UINT_MAX_32_BITS 0xFFFFFFFF
+# endif
+
+/* If UINT_MAX isn't defined, assume it's a 32-bit type.
+   This should be valid for all systems GNU cares about because
+   that doesn't include 16-bit systems, and only modern systems
+   (that certainly have <limits.h>) have 64+-bit integral types.  */
+
+# ifndef UINT_MAX
+#  define UINT_MAX UINT_MAX_32_BITS
+# endif
+
+# if UINT_MAX == UINT_MAX_32_BITS
+   typedef unsigned int md5_uint32;
+# else
+#  if USHRT_MAX == UINT_MAX_32_BITS
+    typedef unsigned short md5_uint32;
+#  else
+#   if ULONG_MAX == UINT_MAX_32_BITS
+     typedef unsigned long md5_uint32;
+#   else
+     /* The following line is intended to evoke an error.
+        Using #error is not portable enough.  */
+     "Cannot determine unsigned 32-bit data type."
+#   endif
+#  endif
+# endif
+#endif
+
+#undef __P
+#if defined (__STDC__) && __STDC__
+#define	__P(x) x
+#else
+#define	__P(x) ()
+#endif
+
+/* Structure to save state of computation between the single steps.  */
+struct md5_ctx
+{
+  md5_uint32 A;
+  md5_uint32 B;
+  md5_uint32 C;
+  md5_uint32 D;
+
+  md5_uint32 total[2];
+  md5_uint32 buflen;
+  char buffer[128];
+};
+
+/*
+ * The following three functions are build up the low level used in
+ * the functions `md5_stream' and `md5_buffer'.
+ */
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+extern void md5_init_ctx __P ((struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is necessary that LEN is a multiple of 64!!! */
+extern void md5_process_block __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is NOT required that LEN is a multiple of 64.  */
+extern void md5_process_bytes __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Process the remaining bytes in the buffer and put result from CTX
+   in first 16 bytes following RESBUF.  The result is always in little
+   endian byte order, so that a byte-wise output yields to the wanted
+   ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF be correctly
+   aligned for a 32 bits value.  */
+extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf));
+
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result is
+   always in little endian byte order, so that a byte-wise output yields
+   to the wanted ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf));
+
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.  */
+extern int md5_stream __P ((FILE *stream, void *resblock));
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock));
+
+/* The following is from gnupg-1.0.2's cipher/bithelp.h.  */
+/* Rotate a 32 bit integer by n bytes */
+#if defined __GNUC__ && defined __i386__
+static inline md5_uint32
+rol(md5_uint32 x, int n)
+{
+  __asm__("roll %%cl,%0"
+	  :"=r" (x)
+	  :"0" (x),"c" (n));
+  return x;
+}
+#else
+# define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) )
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nav_print.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2000, 2001, 2002, 2003 Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * Much of the contents in this file is based on VOBDUMP.
+ *
+ * VOBDUMP: a program for examining DVD .VOB filse
+ *
+ * Copyright 1998, 1999 Eric Smith <eric@brouhaha.com>
+ *
+ * VOBDUMP is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.  Note that I am not
+ * granting permission to redistribute or modify VOBDUMP under the
+ * terms of any later version of the General Public License.
+ *
+ * This program is distributed in the hope that it will be useful (or
+ * at least amusing), 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
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "nav_types.h"
+#include "nav_print.h"
+#include "dvdread_internal.h"
+
+static void print_time(dvd_time_t *dtime) {
+  const char *rate;
+  CHECK_VALUE((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa);
+  CHECK_VALUE((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa);
+  CHECK_VALUE((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa);
+  CHECK_VALUE((dtime->frame_u&0xf) < 0xa);
+  
+  printf("%02x:%02x:%02x.%02x", 
+	 dtime->hour,
+	 dtime->minute,
+	 dtime->second,
+	 dtime->frame_u & 0x3f);
+  switch((dtime->frame_u & 0xc0) >> 6) {
+  case 1:
+    rate = "25.00";
+    break;
+  case 3:
+    rate = "29.97";
+    break;
+  default:
+    rate = "(please send a bug report)";
+    break;
+  } 
+  printf(" @ %s fps", rate);
+}
+
+
+static void navPrint_PCI_GI(pci_gi_t *pci_gi) {
+  int i;
+
+  printf("pci_gi:\n");
+  printf("nv_pck_lbn    0x%08x\n", pci_gi->nv_pck_lbn);
+  printf("vobu_cat      0x%04x\n", pci_gi->vobu_cat);
+  printf("vobu_uop_ctl  0x%08x\n", *(uint32_t*)&pci_gi->vobu_uop_ctl);
+  printf("vobu_s_ptm    0x%08x\n", pci_gi->vobu_s_ptm);
+  printf("vobu_e_ptm    0x%08x\n", pci_gi->vobu_e_ptm);
+  printf("vobu_se_e_ptm 0x%08x\n", pci_gi->vobu_se_e_ptm);
+  printf("e_eltm        ");
+  print_time(&pci_gi->e_eltm);
+  printf("\n");
+  
+  printf("vobu_isrc     \"");
+  for(i = 0; i < 32; i++) {
+    char c = pci_gi->vobu_isrc[i];
+    if((c >= ' ') && (c <= '~'))
+      printf("%c", c);
+    else
+      printf(".");
+  }
+  printf("\"\n");
+}
+
+static void navPrint_NSML_AGLI(nsml_agli_t *nsml_agli) {
+  int i, j = 0;
+  
+  for(i = 0; i < 9; i++)
+    j |= nsml_agli->nsml_agl_dsta[i];
+  if(j == 0)
+    return;
+  
+  printf("nsml_agli:\n");
+  for(i = 0; i < 9; i++)
+    if(nsml_agli->nsml_agl_dsta[i])
+      printf("nsml_agl_c%d_dsta  0x%08x\n", i + 1, 
+	     nsml_agli->nsml_agl_dsta[i]);
+}
+
+static void navPrint_HL_GI(hl_gi_t *hl_gi, int *btngr_ns, int *btn_ns) {
+  
+  if((hl_gi->hli_ss & 0x03) == 0)
+    return;
+  
+  printf("hl_gi:\n");
+  printf("hli_ss        0x%01x\n", hl_gi->hli_ss & 0x03);
+  printf("hli_s_ptm     0x%08x\n", hl_gi->hli_s_ptm);
+  printf("hli_e_ptm     0x%08x\n", hl_gi->hli_e_ptm);
+  printf("btn_se_e_ptm  0x%08x\n", hl_gi->btn_se_e_ptm);
+
+  *btngr_ns = hl_gi->btngr_ns;
+  printf("btngr_ns      %d\n",  hl_gi->btngr_ns);
+  printf("btngr%d_dsp_ty    0x%02x\n", 1, hl_gi->btngr1_dsp_ty);
+  printf("btngr%d_dsp_ty    0x%02x\n", 2, hl_gi->btngr2_dsp_ty);
+  printf("btngr%d_dsp_ty    0x%02x\n", 3, hl_gi->btngr3_dsp_ty);
+  
+  printf("btn_ofn       %d\n", hl_gi->btn_ofn);
+  *btn_ns = hl_gi->btn_ns;
+  printf("btn_ns        %d\n", hl_gi->btn_ns);
+  printf("nsl_btn_ns    %d\n", hl_gi->nsl_btn_ns);
+  printf("fosl_btnn     %d\n", hl_gi->fosl_btnn);
+  printf("foac_btnn     %d\n", hl_gi->foac_btnn);
+}
+
+static void navPrint_BTN_COLIT(btn_colit_t *btn_colit) {
+  int i, j;
+  
+  j = 0;
+  for(i = 0; i < 6; i++)
+    j |= btn_colit->btn_coli[i/2][i&1];
+  if(j == 0)
+    return;
+  
+  printf("btn_colit:\n");
+  for(i = 0; i < 3; i++)
+    for(j = 0; j < 2; j++)
+      printf("btn_cqoli %d  %s_coli:  %08x\n",
+	     i, (j == 0) ? "sl" : "ac",
+	     btn_colit->btn_coli[i][j]);
+}
+
+static void navPrint_BTNIT(btni_t *btni_table, int btngr_ns, int btn_ns) {
+  int i, j;
+  
+  printf("btnit:\n");
+  printf("btngr_ns: %i\n", btngr_ns);
+  printf("btn_ns: %i\n", btn_ns);
+  
+  if(btngr_ns == 0)
+    return;
+  
+  for(i = 0; i < btngr_ns; i++) {
+    for(j = 0; j < (36 / btngr_ns); j++) {
+      if(j < btn_ns) {
+	btni_t *btni = &btni_table[(36 / btngr_ns) * i + j];
+	
+	printf("group %d btni %d:  ", i+1, j+1);
+	printf("btn_coln %d, auto_action_mode %d\n",
+	       btni->btn_coln, btni->auto_action_mode);
+	printf("coords   (%d, %d) .. (%d, %d)\n",
+	       btni->x_start, btni->y_start, btni->x_end, btni->y_end);
+	
+	printf("up %d, ", btni->up);
+	printf("down %d, ", btni->down);
+	printf("left %d, ", btni->left);
+	printf("right %d\n", btni->right);
+	
+	// ifoPrint_COMMAND(&btni->cmd);
+	printf("\n");
+      }
+    }
+  }
+}
+
+static void navPrint_HLI(hli_t *hli) {
+  int btngr_ns = 0, btn_ns = 0;
+  
+  printf("hli:\n");
+  navPrint_HL_GI(&hli->hl_gi, & btngr_ns, & btn_ns);
+  navPrint_BTN_COLIT(&hli->btn_colit);
+  navPrint_BTNIT(hli->btnit, btngr_ns, btn_ns);
+}
+
+void navPrint_PCI(pci_t *pci) {
+  printf("pci packet:\n");
+  navPrint_PCI_GI(&pci->pci_gi);
+  navPrint_NSML_AGLI(&pci->nsml_agli);
+  navPrint_HLI(&pci->hli);
+}
+
+static void navPrint_DSI_GI(dsi_gi_t *dsi_gi) {
+  printf("dsi_gi:\n");
+  printf("nv_pck_scr     0x%08x\n", dsi_gi->nv_pck_scr);
+  printf("nv_pck_lbn     0x%08x\n", dsi_gi->nv_pck_lbn );
+  printf("vobu_ea        0x%08x\n", dsi_gi->vobu_ea);
+  printf("vobu_1stref_ea 0x%08x\n", dsi_gi->vobu_1stref_ea);
+  printf("vobu_2ndref_ea 0x%08x\n", dsi_gi->vobu_2ndref_ea);
+  printf("vobu_3rdref_ea 0x%08x\n", dsi_gi->vobu_3rdref_ea);
+  printf("vobu_vob_idn   0x%04x\n", dsi_gi->vobu_vob_idn);
+  printf("vobu_c_idn     0x%02x\n", dsi_gi->vobu_c_idn);
+  printf("c_eltm         ");
+  print_time(&dsi_gi->c_eltm);
+  printf("\n");
+}
+
+static void navPrint_SML_PBI(sml_pbi_t *sml_pbi) {
+  printf("sml_pbi:\n");
+  printf("category 0x%04x\n", sml_pbi->category);
+  if(sml_pbi->category & 0x8000)
+    printf("VOBU is in preunit\n");
+  if(sml_pbi->category & 0x4000)
+    printf("VOBU is in ILVU\n");
+  if(sml_pbi->category & 0x2000)
+    printf("VOBU at the beginning of ILVU\n");
+  if(sml_pbi->category & 0x1000)
+    printf("VOBU at end of PREU of ILVU\n");
+  
+  printf("ilvu_ea       0x%08x\n", sml_pbi->ilvu_ea);
+  printf("nxt_ilvu_sa   0x%08x\n", sml_pbi->ilvu_sa);
+  printf("nxt_ilvu_size 0x%04x\n", sml_pbi->size);
+  
+  printf("vob_v_s_s_ptm 0x%08x\n", sml_pbi->vob_v_s_s_ptm);
+  printf("vob_v_e_e_ptm 0x%08x\n", sml_pbi->vob_v_e_e_ptm);
+  
+  /* $$$ more code needed here */
+}
+
+static void navPrint_SML_AGLI(sml_agli_t *sml_agli) {
+  int i;
+  printf("sml_agli:\n");
+  for(i = 0; i < 9; i++) {
+    printf("agl_c%d address: 0x%08x size 0x%04x\n", i,
+	   sml_agli->data[i].address, sml_agli->data[i].size);
+  }
+}
+
+static void navPrint_VOBU_SRI(vobu_sri_t *vobu_sri) {
+  int i;
+  int stime[19] = { 240, 120, 60, 20, 15, 14, 13, 12, 11, 
+		     10,   9,  8,  7,  6,  5,  4,  3,  2, 1};
+  printf("vobu_sri:\n");
+  printf("Next VOBU with Video %08x\n", vobu_sri->next_video);
+  for(i = 0; i < 19; i++) {
+    printf("%3.1f %08x ", stime[i]/2.0, vobu_sri->fwda[i]);
+  }
+  printf("\n");
+  printf("Next VOBU %08x\n", vobu_sri->next_vobu);
+  printf("--\n");
+  printf("Prev VOBU %08x\n", vobu_sri->prev_vobu);
+  for(i = 0; i < 19; i++) {
+    printf("%3.1f %08x ", stime[18 - i]/2.0, vobu_sri->bwda[i]);
+  }
+  printf("\n");
+  printf("Prev VOBU with Video %08x\n", vobu_sri->prev_video);
+}
+
+static void navPrint_SYNCI(synci_t *synci) {
+  int i;
+  
+  printf("synci:\n");
+  /* $$$ more code needed here */
+  for(i = 0; i < 8; i++)
+    printf("%04x ", synci->a_synca[i]);
+  for(i = 0; i < 32; i++)
+    printf("%08x ", synci->sp_synca[i]);
+}
+
+void navPrint_DSI(dsi_t *dsi) {
+  printf("dsi packet:\n");
+  navPrint_DSI_GI(&dsi->dsi_gi);
+  navPrint_SML_PBI(&dsi->sml_pbi);
+  navPrint_SML_AGLI(&dsi->sml_agli);
+  navPrint_VOBU_SRI(&dsi->vobu_sri);
+  navPrint_SYNCI(&dsi->synci);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nav_print.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,50 @@
+#ifndef NAV_PRINT_H_INCLUDED
+#define NAV_PRINT_H_INCLUDED
+
+/*
+ * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
+ *                          Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <dvdread/nav_types.h>
+
+/**
+ * Pretty printing of the NAV packets, PCI and DSI structs.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Prints information contained in the PCI to stdout.
+ *
+ * @param pci Pointer to the PCI data structure to be printed.
+ */
+void navPrint_PCI(pci_t *);
+  
+/**
+ * Prints information contained in the DSI to stdout.
+ *
+ * @param dsi Pointer to the DSI data structure to be printed.
+ */
+void navPrint_DSI(dsi_t *);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* NAV_PRINT_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nav_read.c	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2000, 2001, 2002, 2003 Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "bswap.h"
+#include "nav_types.h"
+#include "nav_read.h"
+#include "dvdread_internal.h"
+
+void navRead_PCI(pci_t *pci, unsigned char *buffer) {
+  int i, j;
+
+  CHECK_VALUE(sizeof(pci_t) == PCI_BYTES - 1); // -1 for substream id
+  
+  memcpy(pci, buffer, sizeof(pci_t));
+
+  /* Endian conversions  */
+
+  /* pci pci_gi */
+  B2N_32(pci->pci_gi.nv_pck_lbn);
+  B2N_16(pci->pci_gi.vobu_cat);
+  B2N_32(pci->pci_gi.vobu_s_ptm);
+  B2N_32(pci->pci_gi.vobu_e_ptm);
+  B2N_32(pci->pci_gi.vobu_se_e_ptm);
+
+  /* pci nsml_agli */
+  for(i = 0; i < 9; i++)
+    B2N_32(pci->nsml_agli.nsml_agl_dsta[i]);
+
+  /* pci hli hli_gi */
+  B2N_16(pci->hli.hl_gi.hli_ss);
+  B2N_32(pci->hli.hl_gi.hli_s_ptm);
+  B2N_32(pci->hli.hl_gi.hli_e_ptm);
+  B2N_32(pci->hli.hl_gi.btn_se_e_ptm);
+
+  /* pci hli btn_colit */
+  for(i = 0; i < 3; i++)
+    for(j = 0; j < 2; j++)
+      B2N_32(pci->hli.btn_colit.btn_coli[i][j]);
+
+  /* NOTE: I've had to change the structure from the disk layout to get
+   * the packing to work with Sun's Forte C compiler. */
+  
+  /* pci hli btni */
+  for(i = 0; i < 36; i++) {
+    char tmp[sizeof(pci->hli.btnit[i])], swap;
+    memcpy(tmp, &(pci->hli.btnit[i]), sizeof(pci->hli.btnit[i]));
+    /* Byte 4 to 7 are 'rotated' was: ABCD EFGH IJ is: ABCG DEFH IJ */
+    swap   = tmp[6]; 
+    tmp[6] = tmp[5];
+    tmp[5] = tmp[4];
+    tmp[4] = tmp[3];
+    tmp[3] = swap;
+    
+    /* Then there are the two B2N_24(..) calls */
+#ifndef WORDS_BIGENDIAN
+    swap = tmp[0];
+    tmp[0] = tmp[2];
+    tmp[2] = swap;
+    
+    swap = tmp[4];
+    tmp[4] = tmp[6];
+    tmp[6] = swap;
+#endif
+    memcpy(&(pci->hli.btnit[i]), tmp, sizeof(pci->hli.btnit[i]));
+  }
+
+
+#ifndef NDEBUG
+  /* Asserts */
+
+  /* pci pci gi */ 
+  CHECK_VALUE(pci->pci_gi.zero1 == 0);
+
+  /* pci hli hli_gi */
+  CHECK_VALUE(pci->hli.hl_gi.zero1 == 0);
+  CHECK_VALUE(pci->hli.hl_gi.zero2 == 0);
+  CHECK_VALUE(pci->hli.hl_gi.zero3 == 0);
+  CHECK_VALUE(pci->hli.hl_gi.zero4 == 0);
+  CHECK_VALUE(pci->hli.hl_gi.zero5 == 0);
+
+  /* Are there buttons defined here? */
+  if((pci->hli.hl_gi.hli_ss & 0x03) != 0) {
+    CHECK_VALUE(pci->hli.hl_gi.btn_ns != 0); 
+    CHECK_VALUE(pci->hli.hl_gi.btngr_ns != 0); 
+  } else {
+    CHECK_VALUE((pci->hli.hl_gi.btn_ns != 0 && pci->hli.hl_gi.btngr_ns != 0) 
+	   || (pci->hli.hl_gi.btn_ns == 0 && pci->hli.hl_gi.btngr_ns == 0));
+  }
+
+  /* pci hli btnit */
+  for(i = 0; i < pci->hli.hl_gi.btngr_ns; i++) {
+    for(j = 0; j < (36 / pci->hli.hl_gi.btngr_ns); j++) {
+      int n = (36 / pci->hli.hl_gi.btngr_ns) * i + j;
+      CHECK_VALUE(pci->hli.btnit[n].zero1 == 0);
+      CHECK_VALUE(pci->hli.btnit[n].zero2 == 0);
+      CHECK_VALUE(pci->hli.btnit[n].zero3 == 0);
+      CHECK_VALUE(pci->hli.btnit[n].zero4 == 0);
+      CHECK_VALUE(pci->hli.btnit[n].zero5 == 0);
+      CHECK_VALUE(pci->hli.btnit[n].zero6 == 0);
+      
+      if (j < pci->hli.hl_gi.btn_ns) {	
+	CHECK_VALUE(pci->hli.btnit[n].x_start <= pci->hli.btnit[n].x_end);
+	CHECK_VALUE(pci->hli.btnit[n].y_start <= pci->hli.btnit[n].y_end);
+	CHECK_VALUE(pci->hli.btnit[n].up <= pci->hli.hl_gi.btn_ns);
+	CHECK_VALUE(pci->hli.btnit[n].down <= pci->hli.hl_gi.btn_ns);
+	CHECK_VALUE(pci->hli.btnit[n].left <= pci->hli.hl_gi.btn_ns);
+	CHECK_VALUE(pci->hli.btnit[n].right <= pci->hli.hl_gi.btn_ns);
+	//vmcmd_verify(pci->hli.btnit[n].cmd);
+      } else {
+	int k;
+	CHECK_VALUE(pci->hli.btnit[n].btn_coln == 0);
+	CHECK_VALUE(pci->hli.btnit[n].auto_action_mode == 0);
+	CHECK_VALUE(pci->hli.btnit[n].x_start == 0);
+	CHECK_VALUE(pci->hli.btnit[n].y_start == 0);
+	CHECK_VALUE(pci->hli.btnit[n].x_end == 0);
+	CHECK_VALUE(pci->hli.btnit[n].y_end == 0);
+	CHECK_VALUE(pci->hli.btnit[n].up == 0);
+	CHECK_VALUE(pci->hli.btnit[n].down == 0);
+	CHECK_VALUE(pci->hli.btnit[n].left == 0);
+	CHECK_VALUE(pci->hli.btnit[n].right == 0);
+	for (k = 0; k < 8; k++)
+	  CHECK_VALUE(pci->hli.btnit[n].cmd.bytes[k] == 0); //CHECK_ZERO?
+      }
+    }
+  }
+#endif /* !NDEBUG */
+}
+
+void navRead_DSI(dsi_t *dsi, unsigned char *buffer) {
+  int i;
+
+  CHECK_VALUE(sizeof(dsi_t) == DSI_BYTES - 1); // -1 for substream id
+  
+  memcpy(dsi, buffer, sizeof(dsi_t));
+
+  /* Endian conversions */
+
+  /* dsi dsi gi */
+  B2N_32(dsi->dsi_gi.nv_pck_scr);
+  B2N_32(dsi->dsi_gi.nv_pck_lbn);
+  B2N_32(dsi->dsi_gi.vobu_ea);
+  B2N_32(dsi->dsi_gi.vobu_1stref_ea);
+  B2N_32(dsi->dsi_gi.vobu_2ndref_ea);
+  B2N_32(dsi->dsi_gi.vobu_3rdref_ea);
+  B2N_16(dsi->dsi_gi.vobu_vob_idn);
+
+  /* dsi sml pbi */
+  B2N_16(dsi->sml_pbi.category);
+  B2N_32(dsi->sml_pbi.ilvu_ea);
+  B2N_32(dsi->sml_pbi.ilvu_sa);
+  B2N_16(dsi->sml_pbi.size);
+  B2N_32(dsi->sml_pbi.vob_v_s_s_ptm);
+  B2N_32(dsi->sml_pbi.vob_v_e_e_ptm);
+
+  /* dsi sml agli */
+  for(i = 0; i < 9; i++) {
+    B2N_32(dsi->sml_agli.data[ i ].address);
+    B2N_16(dsi->sml_agli.data[ i ].size);
+  }
+
+  /* dsi vobu sri */
+  B2N_32(dsi->vobu_sri.next_video);
+  for(i = 0; i < 19; i++)
+    B2N_32(dsi->vobu_sri.fwda[i]);
+  B2N_32(dsi->vobu_sri.next_vobu);
+  B2N_32(dsi->vobu_sri.prev_vobu);
+  for(i = 0; i < 19; i++)
+    B2N_32(dsi->vobu_sri.bwda[i]);
+  B2N_32(dsi->vobu_sri.prev_video);
+
+  /* dsi synci */
+  for(i = 0; i < 8; i++)
+    B2N_16(dsi->synci.a_synca[i]);
+  for(i = 0; i < 32; i++)
+    B2N_32(dsi->synci.sp_synca[i]);
+
+  
+  /* Asserts */
+
+  /* dsi dsi gi */
+  CHECK_VALUE(dsi->dsi_gi.zero1 == 0);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nav_read.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,51 @@
+#ifndef NAV_READ_H_INCLUDED
+#define NAV_READ_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001, 2002 Håkan Hjort <d95hjort@dtek.chalmers.se>.
+ *
+ * This program 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.
+ *
+ * This program 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
+ */
+
+#include <dvdread/nav_types.h>
+
+/**
+ * Parsing of NAV data, PCI and DSI parts.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Reads the PCI packet data pointed to into th pci struct.
+ * 
+ * @param pci Pointer to the PCI data structure to be filled in.
+ * @param bufffer Pointer to the buffer of the on disc PCI data.
+ */  
+void navRead_PCI(pci_t *, unsigned char *);
+
+/**
+ * Reads the DSI packet data pointed to into dsi struct.
+ * 
+ * @param dsi Pointer to the DSI data structure to be filled in.
+ * @param bufffer Pointer to the buffer of the on disc DSI data.
+ */
+void navRead_DSI(dsi_t *, unsigned char *);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* NAV_READ_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nav_types.h	Sun Apr 27 00:00:49 2003 +0000
@@ -0,0 +1,307 @@
+#ifndef NAV_TYPES_H_INCLUDED
+#define NAV_TYPES_H_INCLUDED
+
+/*
+ * Copyright (C) 2000, 2001, 2002 Håkan Hjort <d95hjort@dtek.chalmers.se>
+ *
+ * The data structures in this file should represent the layout of the
+ * pci and dsi packets as they are stored in the stream.  Information
+ * found by reading the source to VOBDUMP is the base for the structure
+ * and names of these data types.
+ *
+ * VOBDUMP: a program for examining DVD .VOB files.
+ * Copyright 1998, 1999 Eric Smith <eric@brouhaha.com>
+ *
+ * VOBDUMP is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.  Note that I am not
+ * granting permission to redistribute or modify VOBDUMP under the terms
+ * of any later version of the General Public License.
+ *
+ * This program is distributed in the hope that it will be useful (or at
+ * least amusing), 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
+ */
+
+#include <inttypes.h>
+#include <dvdread/ifo_types.h> /* only dvd_time_t, vm_cmd_t and user_ops_t */
+
+
+#undef ATTRIBUTE_PACKED
+#undef PRAGMA_PACK_BEGIN 
+#undef PRAGMA_PACK_END
+
+#if defined(__GNUC__)
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define ATTRIBUTE_PACKED __attribute__ ((packed))
+#define PRAGMA_PACK 0
+#endif
+#endif
+
+#if !defined(ATTRIBUTE_PACKED)
+#define ATTRIBUTE_PACKED
+#define PRAGMA_PACK 1
+#endif
+
+
+/* The length including the substream id byte. */
+#define PCI_BYTES 0x3d4
+#define DSI_BYTES 0x3fa
+
+#define PS2_PCI_SUBSTREAM_ID 0x00
+#define PS2_DSI_SUBSTREAM_ID 0x01
+
+/* Remove this */
+#define DSI_START_BYTE 1031
+
+
+#if PRAGMA_PACK
+#pragma pack(1)
+#endif
+
+
+/**
+ * PCI General Information 
+ */
+typedef struct {
+  uint32_t nv_pck_lbn;      /**< sector address of this nav pack */
+  uint16_t vobu_cat;        /**< 'category' of vobu */
+  uint16_t zero1;           /**< reserved */
+  user_ops_t vobu_uop_ctl;  /**< UOP of vobu */
+  uint32_t vobu_s_ptm;      /**< start presentation time of vobu */
+  uint32_t vobu_e_ptm;      /**< end presentation time of vobu */
+  uint32_t vobu_se_e_ptm;   /**< end ptm of sequence end in vobu */
+  dvd_time_t e_eltm;        /**< Cell elapsed time */
+  char vobu_isrc[32];
+} ATTRIBUTE_PACKED pci_gi_t;
+
+/**
+ * Non Seamless Angle Information
+ */
+typedef struct {
+  uint32_t nsml_agl_dsta[9];  /**< address of destination vobu in AGL_C#n */
+} ATTRIBUTE_PACKED nsml_agli_t;
+
+/** 
+ * Highlight General Information 
+ *
+ * For btngrX_dsp_ty the bits have the following meaning:
+ * 000b: normal 4/3 only buttons
+ * XX1b: wide (16/9) buttons
+ * X1Xb: letterbox buttons
+ * 1XXb: pan&scan buttons
+ */
+typedef struct {
+  uint16_t hli_ss; /**< status, only low 2 bits 0: no buttons, 1: different 2: equal 3: eual except for button cmds */
+  uint32_t hli_s_ptm;              /**< start ptm of hli */
+  uint32_t hli_e_ptm;              /**< end ptm of hli */
+  uint32_t btn_se_e_ptm;           /**< end ptm of button select */
+#ifdef WORDS_BIGENDIAN
+  unsigned int zero1 : 2;          /**< reserved */
+  unsigned int btngr_ns : 2;       /**< number of button groups 1, 2 or 3 with 36/18/12 buttons */
+  unsigned int zero2 : 1;          /**< reserved */
+  unsigned int btngr1_dsp_ty : 3;  /**< display type of subpic stream for button group 1 */
+  unsigned int zero3 : 1;          /**< reserved */
+  unsigned int btngr2_dsp_ty : 3;  /**< display type of subpic stream for button group 2 */
+  unsigned int zero4 : 1;          /**< reserved */
+  unsigned int btngr3_dsp_ty : 3;  /**< display type of subpic stream for button group 3 */
+#else
+  unsigned int btngr1_dsp_ty : 3;
+  unsigned int zero2 : 1;
+  unsigned int btngr_ns : 2;
+  unsigned int zero1 : 2;
+  unsigned int btngr3_dsp_ty : 3;
+  unsigned int zero4 : 1;
+  unsigned int btngr2_dsp_ty : 3;
+  unsigned int zero3 : 1;
+#endif
+  uint8_t btn_ofn;     /**< button offset number range 0-255 */
+  uint8_t btn_ns;      /**< number of valid buttons  <= 36/18/12 (low 6 bits) */  
+  uint8_t nsl_btn_ns;  /**< number of buttons selectable by U_BTNNi (low 6 bits)   nsl_btn_ns <= btn_ns */
+  uint8_t zero5;       /**< reserved */
+  uint8_t fosl_btnn;   /**< forcedly selected button  (low 6 bits) */
+  uint8_t foac_btnn;   /**< forcedly activated button (low 6 bits) */
+} ATTRIBUTE_PACKED hl_gi_t;
+
+
+/** 
+ * Button Color Information Table 
+ * Each entry beeing a 32bit word that contains the color indexs and alpha
+ * values to use.  They are all represented by 4 bit number and stored
+ * like this [Ci3, Ci2, Ci1, Ci0, A3, A2, A1, A0].   The actual palette
+ * that the indexes reference is in the PGC.
+ * @TODO split the uint32_t into a struct
+ */
+typedef struct {
+  uint32_t btn_coli[3][2];  /**< [button color number-1][select:0/action:1] */
+} ATTRIBUTE_PACKED btn_colit_t;
+
+/** 
+ * Button Information
+ *
+ * NOTE: I've had to change the structure from the disk layout to get
+ * the packing to work with Sun's Forte C compiler.
+ * The 4 and 7 bytes are 'rotated' was: ABC DEF GHIJ  is: ABCG DEFH IJ
+ */
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int btn_coln         : 2;  /**< button color number */
+  unsigned int x_start          : 10; /**< x start offset within the overlay */
+  unsigned int zero1            : 2;  /**< reserved */
+  unsigned int x_end            : 10; /**< x end offset within the overlay */
+
+  unsigned int zero3            : 2;  /**< reserved */
+  unsigned int up               : 6;  /**< button index when pressing up */
+
+  unsigned int auto_action_mode : 2;  /**< 0: no, 1: activated if selected */
+  unsigned int y_start          : 10; /**< y start offset within the overlay */
+  unsigned int zero2            : 2;  /**< reserved */
+  unsigned int y_end            : 10; /**< y end offset within the overlay */
+
+  unsigned int zero4            : 2;  /**< reserved */
+  unsigned int down             : 6;  /**< button index when pressing down */
+  unsigned int zero5            : 2;  /**< reserved */
+  unsigned int left             : 6;  /**< button index when pressing left */
+  unsigned int zero6            : 2;  /**< reserved */
+  unsigned int right            : 6;  /**< button index when pressing right */
+#else
+  unsigned int x_end            : 10;
+  unsigned int zero1            : 2;
+  unsigned int x_start          : 10;
+  unsigned int btn_coln         : 2;
+
+  unsigned int up               : 6;
+  unsigned int zero3            : 2;
+
+  unsigned int y_end            : 10;
+  unsigned int zero2            : 2;
+  unsigned int y_start          : 10;
+  unsigned int auto_action_mode : 2;
+
+  unsigned int down             : 6;
+  unsigned int zero4            : 2;
+  unsigned int left             : 6;
+  unsigned int zero5            : 2;
+  unsigned int right            : 6;
+  unsigned int zero6            : 2;
+#endif
+  vm_cmd_t cmd;
+} ATTRIBUTE_PACKED btni_t;
+
+/**
+ * Highlight Information 
+ */
+typedef struct {
+  hl_gi_t     hl_gi;
+  btn_colit_t btn_colit;
+  btni_t      btnit[36];
+} ATTRIBUTE_PACKED hli_t;
+
+/**
+ * PCI packet
+ */
+typedef struct {
+  pci_gi_t    pci_gi;
+  nsml_agli_t nsml_agli;
+  hli_t       hli;
+  uint8_t     zero1[189];
+} ATTRIBUTE_PACKED pci_t;
+
+
+
+
+/**
+ * DSI General Information 
+ */
+typedef struct {
+  uint32_t nv_pck_scr;
+  uint32_t nv_pck_lbn;      /**< sector address of this nav pack */
+  uint32_t vobu_ea;         /**< end address of this VOBU */
+  uint32_t vobu_1stref_ea;  /**< end address of the 1st reference image */
+  uint32_t vobu_2ndref_ea;  /**< end address of the 2nd reference image */
+  uint32_t vobu_3rdref_ea;  /**< end address of the 3rd reference image */
+  uint16_t vobu_vob_idn;    /**< VOB Id number that this VOBU is part of */
+  uint8_t  zero1;           /**< reserved */
+  uint8_t  vobu_c_idn;      /**< Cell Id number that this VOBU is part of */
+  dvd_time_t c_eltm;        /**< Cell elapsed time */
+} ATTRIBUTE_PACKED dsi_gi_t;
+
+/**
+ * Seamless Playback Information
+ */
+typedef struct {
+  uint16_t category;       /**< 'category' of seamless VOBU */
+  uint32_t ilvu_ea;        /**< end address of interleaved Unit */
+  uint32_t ilvu_sa;        /**< start address of next interleaved unit */
+  uint16_t size;           /**< size of next interleaved unit */
+  uint32_t vob_v_s_s_ptm;  /**< video start ptm in vob */
+  uint32_t vob_v_e_e_ptm;  /**< video end ptm in vob */
+  struct {
+    uint32_t stp_ptm1;
+    uint32_t stp_ptm2;
+    uint32_t gap_len1;
+    uint32_t gap_len2;      
+  } vob_a[8];
+} ATTRIBUTE_PACKED sml_pbi_t;
+
+/**
+ * Seamless Angle Infromation for one angle
+ */
+typedef struct {
+    uint32_t address; /**< offset to next ILVU, high bit is before/after */
+    uint16_t size;    /**< byte size of the ILVU pointed to by address */
+} ATTRIBUTE_PACKED sml_agl_data_t;
+
+/**
+ * Seamless Angle Infromation
+ */
+typedef struct {
+  sml_agl_data_t data[9];
+} ATTRIBUTE_PACKED sml_agli_t;
+
+/**
+ * VOBU Search Information 
+ */
+typedef struct {
+  uint32_t next_video; /**< Next vobu that contains video */
+  uint32_t fwda[19];   /**< Forwards, time */
+  uint32_t next_vobu;
+  uint32_t prev_vobu;
+  uint32_t bwda[19];   /**< Backwards, time */
+  uint32_t prev_video;
+} ATTRIBUTE_PACKED vobu_sri_t;
+
+#define SRI_END_OF_CELL 0x3fffffff
+
+/**
+ * Synchronous Information
+ */ 
+typedef struct {
+  uint16_t a_synca[8];   /**< offset to first audio packet for this VOBU */
+  uint32_t sp_synca[32]; /**< offset to first subpicture packet */
+} ATTRIBUTE_PACKED synci_t;
+
+/**
+ * DSI packet
+ */
+typedef struct {
+  dsi_gi_t   dsi_gi;
+  sml_pbi_t  sml_pbi;
+  sml_agli_t sml_agli;
+  vobu_sri_t vobu_sri;
+  synci_t    synci;
+  uint8_t    zero1[471];
+} ATTRIBUTE_PACKED dsi_t;
+
+
+#if PRAGMA_PACK
+#pragma pack()
+#endif
+
+#endif /* NAV_TYPES_H_INCLUDED */