changeset 0:427b7da5cbdb src

first split of dvdread; it's just a copy of dvdnav still to be cleaned
author nicodvb
date Sun, 01 Jun 2008 08:39:07 +0000
parents
children 1c897db44135
files FELLOWSHIP.map Makefile.am README.MAP dvd_types.h dvdnav.c dvdnav.h dvdnav_events.h dvdnav_internal.h highlight.c libdvdread/Makefile.am libdvdread/bitreader.c libdvdread/bitreader.h libdvdread/bswap.h libdvdread/dvd_input.c libdvdread/dvd_input.h libdvdread/dvd_reader.c libdvdread/dvd_reader.h libdvdread/dvd_udf.c libdvdread/dvd_udf.h libdvdread/dvdread_internal.h libdvdread/ifo_print.c libdvdread/ifo_print.h libdvdread/ifo_read.c libdvdread/ifo_read.h libdvdread/ifo_types.h libdvdread/md5.c libdvdread/md5.h libdvdread/nav_print.c libdvdread/nav_print.h libdvdread/nav_read.c libdvdread/nav_read.h libdvdread/nav_types.h navigation.c read_cache.c read_cache.h remap.c remap.h searching.c settings.c vm/Makefile.am vm/decoder.c vm/decoder.h vm/vm.c vm/vm.h vm/vmcmd.c vm/vmcmd.h
diffstat 46 files changed, 17801 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FELLOWSHIP.map	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,163 @@
+# The Lord of the Rings: Fellowship of the Ring (Widescreen Theatrical Release)
+domain 8, title 1, program 1, start 0x0000, end 0x1f0a   # Introduction
+
+# history of middle earth
+#domain 2, title 1, program 1, start 0x9cba, end 0xcea4   # violence/war
+#domain 2, title 1, program 1, start 0xf018, end 0x1272c  # violence/war
+domain 2, title 1, program 1, start 0x11ff3, end 0x1272c # gore
+#domain 2, title 1, program 1, start 0x173f1, end 0x185d6 # violence/death
+
+# hobbiton 
+domain 2, title 1, program 2, start 0x30373, end 0x31946 # vices/smoking
+domain 2, title 1, program 2, start 0x32942, end 0x34426 # vices/smoking
+domain 2, title 1, program 2, start 0x34895, end 0x34ad1 # vices/smoking
+domain 2, title 1, program 2, start 0x3965c, end 0x39d7c # vices/smoking
+
+# bag end
+domain 2, title 1, program 3, start 0x40288, end 0x412de # vices/drinking/talk
+domain 2, title 1, program 3, start 0x4cfbe, end 0x4f955 # vices/smoking
+
+# party
+domain 2, title 1, program 4, start 0x53ee0, end 0x54396 # vices/drinking
+domain 2, title 1, program 4, start 0x58851, end 0x5ae3d # imitative/theft
+domain 2, title 1, program 4, start 0x5cd0a, end 0x5e216 # jump 
+
+# gandalf departs
+domain 2, title 1, program 6, start 0x7f05d, end 0x7f1b3 # jump
+domain 2, title 1, program 6, start 0x7f51a, end 0x834a7 # vices/smoking
+
+# research
+domain 2, title 1, program 7, start 0x8e6e8, end 0x8e9d7 # vices/smoking
+
+# hobbiton
+domain 2, title 1, program 8, start 0x98f49, end 0x990ba # jump
+domain 2, title 1, program 8, start 0xa0ad1, end 0xa17ac # jump
+domain 2, title 1, program 8, start 0xa9e32, end 0xaaa70 # violence/torture
+domain 2, title 1, program 8, start 0xabc6c, end 0xabd92 # violence
+
+# orthanc
+domain 2, title 1, program 9, start 0xd50b7, end 0xd87f9 # violence
+domain 2, title 1, program 9, start 0xd8e49, end 0xda611 # violence
+
+# farmer maggot
+domain 2, title 1, program 10, start 0xde856, end 0xdeb07 # jump
+domain 2, title 1, program 10, start 0xe4cfe, end 0xe50b3 # gore/excrement
+domain 2, title 1, program 10, start 0xebba3, end 0xebf93 # gore/blood
+domain 2, title 1, program 10, start 0xed222, end 0xeda7e # gore/insects
+
+# buckleberry ferry
+domain 2, title 1, program 11, start 0xf6d77, end 0xf83f8 # jump
+
+# prancing pony in bree
+domain 2, title 1, program 12, start 0x10945e, end 0x10a2f2 # vices/drinking/rolemodel
+domain 2, title 1, program 12, start 0x10c103, end 0x10c417 # vices/smoking
+domain 2, title 1, program 12, start 0x10c934, end 0x10cb44 # vices/smoking
+# 10c934
+# 10c9b4
+# 10ca3c
+# 10cad6
+# 10cb44
+# 10cbb4
+domain 2, title 1, program 12, start 0x10ddab, end 0x10e0f2 # vices/smoking
+domain 2, title 1, program 12, start 0x10e1df, end 0x10e411 # vices/drinking
+
+# ring wraiths in bree
+domain 2, title 1, program 13, start 0x117853, end 0x117f6e # violence
+domain 2, title 1, program 13, start 0x117853, end 0x117f6e # gore/crushing
+domain 2, title 1, program 13, start 0x11b2b3, end 0x11bffc # violence
+
+# weathertop
+#domain 2, title 1, program 15, start 0x13b85b, end 0x141b38 # violence
+domain 2, title 1, program 15, start 0x14093a, end 0x141063 # gore/stabbing
+domain 2, title 1, program 15, start 0x142555, end 0x1466e9 # violence
+
+# isengard
+domain 2, title 1, program 16, start 0x152a28, end 0x1549fb # violence
+domain 2, title 1, program 16, start 0x152a28, end 0x1549fb # gore
+
+# the ford
+domain 2, title 1, program 17, start 0x173dbe, end 0x1748a6 # violence
+
+# rivendell
+domain 2, title 1, program 18, start 0x179793, end 0x17a667 # vices/smoking
+domain 2, title 1, program 18, start 0x17c452, end 0x17eab6 # violence
+
+# rivendell
+domain 2, title 1, program 20, start 0x19caaf, end 0x19db12 # violence
+
+# tomb of isildur
+domain 2, title 1, program 21, start 0x1a69ae, end 0x1a6a3d # gore/blood
+
+# rivendell
+domain 2, title 1, program 22, start 0x1b298b, end 0x1b2bae # kiss
+
+# rivendell
+domain 2, title 1, program 24, start 0x1d8253, end 0x1d8f5d # horror/posession
+
+# on the road south
+domain 2, title 1, program 25, start 0x1e2fc2, end 0x1e31b8 # vices/smoking
+
+# at the entrance to moria
+domain 2, title 1, program 27, start 0x214bdd, end 0x214fcc # gore/skeletons
+domain 2, title 1, program 27, start 0x2157d9, end 0x216731 # gore/skeletons
+domain 2, title 1, program 27, start 0x217ba6, end 0x21cdb4 # violence
+
+# moria
+#domain 2, title 1, program 28, start 0x22173d, end 0x221e58 # gore/skeletons
+#domain 2, title 1, program 28, start 0x222e0b, end 0x223345 # gore/skeletons
+domain 2, title 1, program 28, start 0x22173d, end 0x223345 # gore/skeletons
+domain 2, title 1, program 28, start 0x2239e2, end 0x223e8c # gore/skeletons
+domain 2, title 1, program 28, start 0x22589f, end 0x2259bf # vices/smoking
+
+# balin's tomb
+domain 2, title 1, program 29, start 0x234abb, end 0x234dd0 # gore/skeletons
+domain 2, title 1, program 29, start 0x2352ed, end 0x2361d7 # gore/skeletons
+domain 2, title 1, program 29, start 0x23898b, end 0x23955c # gore/skeletons
+domain 2, title 1, program 29, start 0x23b467, end 0x23db2b # gore/skeletons
+#domain 2, title 1, program 29, start 0x247360, end 0x25e24b # violence
+domain 2, title 1, program 29, start 0x2483b6, end 0x248b62 # gore/impalement
+domain 2, title 1, program 29, start 0x24a606, end 0x24a9f3 # gore/headshot
+domain 2, title 1, program 29, start 0x24acd8, end 0x24ae7e # gore/decapitation
+domain 2, title 1, program 29, start 0x24d708, end 0x24d88f # gore/bashing
+domain 2, title 1, program 29, start 0x25013a, end 0x250501 # gore/impalement
+domain 2, title 1, program 29, start 0x2540a0, end 0x254507 # jump
+domain 2, title 1, program 29, start 0x2560b3, end 0x256423 # gore/impalement
+domain 2, title 1, program 29, start 0x258152, end 0x258459 # gore/impalement
+domain 2, title 1, program 29, start 0x258152, end 0x258459 # gore/impalement
+
+# the bridge
+#domain 2, title 1, program 30, start 0x274235, end 0x275e59 # violence
+domain 2, title 1, program 30, start 0x2744c6, end 0x274a5e # gore/headshot
+domain 2, title 1, program 30, start 0x275661, end 0x275e59 # violence/death
+domain 2, title 1, program 30, start 0x289d2a, end 0x28a066 # gore/falling
+
+# lothlorien
+domain 2, title 1, program 32, start 0x2be304, end 0x2beb1d # violence
+domain 2, title 1, program 32, start 0x2c5d59, end 0x2c9a40 # horror/posession
+#domain 2, title 1, program 32, start 0x2c5d59, end 0x2c9e41 # horror/posession
+
+# parth galen
+domain 2, title 1, program 36, start 0x2fa317, end 0x2fb0b0 # violence
+
+# parth galen w/ orcs
+domain 2, title 1, program 37, start 0x30c8cb, end 0x313054 # violence
+domain 2, title 1, program 37, start 0x310428, end 0x313054 # gore/impalement
+#domain 2, title 1, program 37, start 0x317f45, end 0x332a52 # violence
+domain 2, title 1, program 37, start 0x322d8b, end 0x328f4d # gore/impalement
+domain 2, title 1, program 37, start 0x32f2a4, end 0x330731 # gore/impalement
+domain 2, title 1, program 37, start 0x330b3b, end 0x330c4d # gore/blood
+domain 2, title 1, program 37, start 0x331c4e, end 0x332a52 # gore/dismemberment
+domain 2, title 1, program 37, start 0x331c4e, end 0x332a52 # gore/decapitation
+
+# death of boromir
+domain 2, title 1, program 38, start 0x33322f, end 0x33c10c # gore/bodies
+domain 2, title 1, program 38, start 0x33b558, end 0x33c10c # horror/death
+
+# to the eastern shore
+domain 2, title 1, program 39, start 0x34a25b, end 0x34c1e3 # horror/drowning
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.am	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,35 @@
+include $(top_srcdir)/misc/Makefile.common
+
+SUBDIRS = libdvdread vm
+
+includedir = ${prefix}/include/dvdnav
+
+AM_CPPFLAGS = -DDVDNAV_COMPILE $(THREAD_CFLAGS) \
+	-I$(top_srcdir)/src/libdvdread -I$(top_srcdir)/src/vm
+
+EXTRA_DIST = README.MAP FELLOWSHIP.map
+
+lib_LTLIBRARIES = libdvdnav.la libdvdnavmini.la
+
+libdvdnav_la_SOURCES = dvdnav.c \
+	read_cache.c navigation.c highlight.c \
+	searching.c settings.c remap.c \
+	dvdnav_internal.h read_cache.h remap.h
+
+libdvdnav_la_LIBADD = $(THREAD_LIBS) \
+	$(top_builddir)/src/libdvdread/libdvdread.la \
+	$(top_builddir)/src/vm/libdvdvm.la
+
+libdvdnav_la_LDFLAGS = \
+	-version-info $(DVDNAV_LT_CURRENT):$(DVDNAV_LT_REVISION):$(DVDNAV_LT_AGE) \
+	-export-symbols-regex "(^dvdnav.*|^nav.*|^ifo.*|^DVD.*)"
+#	-release $(DVDNAV_MAJOR).$(DVDNAV_MINOR).$(DVDNAV_SUB)
+
+libdvdnavmini_la_SOURCES = $(libdvdnav_la_SOURCES)
+
+libdvdnavmini_la_LIBADD = $(THREAD_LIBS) \
+	$(top_builddir)/src/vm/libdvdvm.la
+
+libdvdnavmini_la_LDFLAGS = $(libdvdnav_la_LDFLAGS)
+
+include_HEADERS = dvdnav.h dvdnav_events.h dvd_types.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.MAP	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,105 @@
+Contents
+
+WHAT ARE MAP FILES
+HOWTO
+FINDING MAP FILES
+WHOM TO BLAME
+
+WHAT ARE MAP FILES
+==================
+
+Map files are an experimental feature that lets you customize the way
+you watch DVDs.  If you are opposed to violence, are sickened by gore,
+or would rather your two year old didn't ask you just yet why that 
+woman in the movie was naked, you might want to create a map file.  
+
+Map files identify sections of the movie that will be skipped during
+playback.  You can skip any section you like with the only restriction
+(right now) that the movie player must play at least the last VOBU 
+(about a fifth of a second) of each chapter in the movie in order
+to detect chapter changes correctly.  
+
+Included with this patch is an example map file that describes most
+of the potentially objectionable content in the new "The Lord of the 
+Rings" DVD.  I've added a comment after each block that identifies 
+what content it contains so that you can customize the file to your
+preferences.  The map file looks something like this:
+
+ # The Lord of the Rings: Fellowship of the Ring 
+ # (Widescreen Theatrical Release)
+ debug
+ domain 8, title 1, program 1, start 0x0000, end 0x1f0a   # Introduction
+
+ # history of middle earth
+ #domain 2, title 1, program 1, start 0x9cba, end 0xcea4   # violence/war
+ #domain 2, title 1, program 1, start 0xf018, end 0x1272c  # violence/war
+ domain 2, title 1, program 1, start 0x11ff3, end 0x1272c # gore
+ #domain 2, title 1, program 1, start 0x173f1, end 0x185d6 # violence/death
+
+Place the map file in your .xine directory to enable the selected
+cuts as follows:
+
+    cp FELLOWSHIP.map ~/.xine
+
+The debug command tells the map code that you would like to see the
+VOBU numbers as the movie is playing.  Comment out this line to hide
+this output.  The remaining lines are all either comments or blocks.
+Each block has a start and an end, and whenever Xine tries to load
+a VOBU between the start and end, it will be redirected to the end
+block instead.
+
+
+HOWTO
+=====
+
+To create your own map files you would create a new map file using the
+title of the disk as the filename, and add the debug line to it.  The
+map file should be placed in your '.xine' directory and have a '.map'
+extension added.  For example "The Lord Of The Rings" map must be stored 
+in the file ~/.xine/FELLOWSHIP.map
+
+After you create the file with the DEBUG line you will see output that
+looks like this in window where you started Xine:
+
+   FELLOWSHIP: domain 8, title 1, program 1, start 2205, next 22a8
+   FELLOWSHIP: domain 8, title 1, program 1, start 22a8, next 234b
+   FELLOWSHIP: domain 8, title 1, program 1, start 234b, next 23eb
+   FELLOWSHIP: domain 8, title 1, program 1, start 23eb, next 248a
+
+Each line represents one VOBU, and the start and end addresses match
+the start and end addresses that you should place in the map file
+if you want to skip that block.  If you want to skip multiple blocks
+you would just add one line with the start address of the first block
+to skip, and the end address of the last block to skip.  For example
+to skip these four blocks you would add the following line to your
+map file:
+
+   domain 8, title 1, program 1, start 0x2205, end 0x248a # 4 blocks
+
+Xine uses a buffered input chain so that if you pause the viewer you
+won't find the same VOBU being displayed on the terminal as is 
+currently on the screen.  In my testing the correct VOBU to use if
+you pause exactly on the section you want to cut will be about five
+or six lines above the last one printed to the screen.  Replay the
+scene with the deletion to see if you caught the correct blocks.
+
+
+WHERE TO FIND MAP FILES
+=======================
+
+If you create a map file for a new movie, please send it to me.  If
+there is widespread interest I'll put up a site where map files can
+be located and downloaded.
+
+
+WHOM TO BLAME
+=============
+
+If the patch doesn't work for you (and you want it to) you can contact
+me at 'kevin_smathers@hp.com'.  
+
+If the patch works for you and you don't want it to, remember that 
+not everyone in the world has the same needs.  The freedom to censor 
+movies in our own homes is quite different from the government 
+interfering into libraries and other public forums to censor movies 
+for us.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvd_types.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2000, 2001 Björn Englund, Håkan Hjort
+ *
+ * This file is part of libdvdnav, a DVD navigation library. It is a modified
+ * file originally part of the Ogle DVD player project.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+/*
+ * Various useful structs and enums for DVDs.
+ */
+
+#ifndef DVD_H_INCLUDED
+#define DVD_H_INCLUDED
+
+/*
+ * DVD Menu ID
+ * (see dvdnav_menu_call())
+ */
+typedef enum {
+  /* When used in VTS domain, DVD_MENU_Escape behaves like DVD_MENU_Root,
+   * but from within a menu domain, DVD_MENU_Escape resumes playback. */
+  DVD_MENU_Escape     = 0,
+  DVD_MENU_Title      = 2,
+  DVD_MENU_Root       = 3,
+  DVD_MENU_Subpicture = 4,
+  DVD_MENU_Audio      = 5,
+  DVD_MENU_Angle      = 6,
+  DVD_MENU_Part       = 7
+} DVDMenuID_t;
+
+
+/*
+ * Structure containing info on highlight areas
+ * (see dvdnav_get_highlight_area())
+ */
+typedef struct {
+  uint32_t palette;     /* The CLUT entries for the highlight palette 
+			   (4-bits per entry -> 4 entries) */
+  uint16_t sx,sy,ex,ey; /* The start/end x,y positions */
+  uint32_t pts;         /* Highlight PTS to match with SPU */
+
+  /* button number for the SPU decoder/overlaying engine */
+  uint32_t buttonN;
+} dvdnav_highlight_area_t;
+
+
+/* the following types are currently unused */
+
+#if 0
+
+/* Domain */
+typedef enum {
+  DVD_DOMAIN_FirstPlay,  /* First Play Domain */
+  DVD_DOMAIN_VMG,        /* Video Manager Domain */
+  DVD_DOMAIN_VTSMenu,    /* Video Title Set Menu Domain */
+  DVD_DOMAIN_VTSTitle,   /* Video Title Set Domain */
+  DVD_DOMAIN_Stop        /* Stop Domain */
+} DVDDomain_t;
+
+/* User operation permissions */
+typedef enum {
+  UOP_FLAG_TitleOrTimePlay            = 0x00000001, 
+  UOP_FLAG_ChapterSearchOrPlay        = 0x00000002, 
+  UOP_FLAG_TitlePlay                  = 0x00000004, 
+  UOP_FLAG_Stop                       = 0x00000008,  
+  UOP_FLAG_GoUp                       = 0x00000010,
+  UOP_FLAG_TimeOrChapterSearch        = 0x00000020, 
+  UOP_FLAG_PrevOrTopPGSearch          = 0x00000040,  
+  UOP_FLAG_NextPGSearch               = 0x00000080,   
+  UOP_FLAG_ForwardScan                = 0x00000100,  
+  UOP_FLAG_BackwardScan               = 0x00000200,
+  UOP_FLAG_TitleMenuCall              = 0x00000400,
+  UOP_FLAG_RootMenuCall               = 0x00000800,
+  UOP_FLAG_SubPicMenuCall             = 0x00001000,
+  UOP_FLAG_AudioMenuCall              = 0x00002000,
+  UOP_FLAG_AngleMenuCall              = 0x00004000,
+  UOP_FLAG_ChapterMenuCall            = 0x00008000,
+  UOP_FLAG_Resume                     = 0x00010000,
+  UOP_FLAG_ButtonSelectOrActivate     = 0x00020000,
+  UOP_FLAG_StillOff                   = 0x00040000,
+  UOP_FLAG_PauseOn                    = 0x00080000,
+  UOP_FLAG_AudioStreamChange          = 0x00100000,
+  UOP_FLAG_SubPicStreamChange         = 0x00200000,
+  UOP_FLAG_AngleChange                = 0x00400000,
+  UOP_FLAG_KaraokeAudioPresModeChange = 0x00800000,
+  UOP_FLAG_VideoPresModeChange        = 0x01000000 
+} DVDUOP_t;
+
+/* Parental Level */
+typedef enum {
+  DVD_PARENTAL_LEVEL_1 = 1,
+  DVD_PARENTAL_LEVEL_2 = 2,
+  DVD_PARENTAL_LEVEL_3 = 3,
+  DVD_PARENTAL_LEVEL_4 = 4,
+  DVD_PARENTAL_LEVEL_5 = 5,
+  DVD_PARENTAL_LEVEL_6 = 6,
+  DVD_PARENTAL_LEVEL_7 = 7,
+  DVD_PARENTAL_LEVEL_8 = 8,
+  DVD_PARENTAL_LEVEL_None = 15
+} DVDParentalLevel_t;
+
+/* Language ID (ISO-639 language code) */
+typedef uint16_t DVDLangID_t;
+
+/* Country ID (ISO-3166 country code) */
+typedef uint16_t DVDCountryID_t;
+
+/* Register */
+typedef uint16_t DVDRegister_t;
+typedef enum {
+  DVDFalse = 0,
+  DVDTrue = 1
+} DVDBool_t; 
+typedef DVDRegister_t DVDGPRMArray_t[16];
+typedef DVDRegister_t DVDSPRMArray_t[24];
+
+/* Navigation */
+typedef int DVDStream_t;
+typedef int DVDPTT_t;
+typedef int DVDTitle_t;
+
+/* Angle number (1-9 or default?) */
+typedef int DVDAngle_t;
+
+/* Timecode */
+typedef struct {
+  uint8_t Hours;
+  uint8_t Minutes;
+  uint8_t Seconds;
+  uint8_t Frames;
+} DVDTimecode_t;
+
+/* Subpicture stream number (0-31,62,63) */
+typedef int DVDSubpictureStream_t;  
+
+/* Audio stream number (0-7, 15(none)) */
+typedef int DVDAudioStream_t;  
+
+/* The audio application mode */
+typedef enum {
+  DVD_AUDIO_APP_MODE_None     = 0,
+  DVD_AUDIO_APP_MODE_Karaoke  = 1,
+  DVD_AUDIO_APP_MODE_Surround = 2,
+  DVD_AUDIO_APP_MODE_Other    = 3
+} DVDAudioAppMode_t;
+
+/* The audio format */
+typedef enum {
+  DVD_AUDIO_FORMAT_AC3       = 0,
+  DVD_AUDIO_FORMAT_MPEG1     = 1,
+  DVD_AUDIO_FORMAT_MPEG1_DRC = 2,
+  DVD_AUDIO_FORMAT_MPEG2     = 3,
+  DVD_AUDIO_FORMAT_MPEG2_DRC = 4,
+  DVD_AUDIO_FORMAT_LPCM      = 5,
+  DVD_AUDIO_FORMAT_DTS       = 6,
+  DVD_AUDIO_FORMAT_SDDS      = 7,
+  DVD_AUDIO_FORMAT_Other     = 8
+} DVDAudioFormat_t;
+
+/* Audio language extension */
+typedef enum {
+  DVD_AUDIO_LANG_EXT_NotSpecified       = 0,
+  DVD_AUDIO_LANG_EXT_NormalCaptions     = 1,
+  DVD_AUDIO_LANG_EXT_VisuallyImpaired   = 2,
+  DVD_AUDIO_LANG_EXT_DirectorsComments1 = 3,
+  DVD_AUDIO_LANG_EXT_DirectorsComments2 = 4
+} DVDAudioLangExt_t;
+
+/* Subpicture language extension */
+typedef enum {
+  DVD_SUBPICTURE_LANG_EXT_NotSpecified  = 0,
+  DVD_SUBPICTURE_LANG_EXT_NormalCaptions  = 1,
+  DVD_SUBPICTURE_LANG_EXT_BigCaptions  = 2,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensCaptions  = 3,
+  DVD_SUBPICTURE_LANG_EXT_NormalCC  = 5,
+  DVD_SUBPICTURE_LANG_EXT_BigCC  = 6,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensCC  = 7,
+  DVD_SUBPICTURE_LANG_EXT_Forced  = 9,
+  DVD_SUBPICTURE_LANG_EXT_NormalDirectorsComments  = 13,
+  DVD_SUBPICTURE_LANG_EXT_BigDirectorsComments  = 14,
+  DVD_SUBPICTURE_LANG_EXT_ChildrensDirectorsComments  = 15,
+} DVDSubpictureLangExt_t;  
+
+/* Karaoke Downmix mode */
+typedef enum {
+  DVD_KARAOKE_DOWNMIX_0to0 = 0x0001,
+  DVD_KARAOKE_DOWNMIX_1to0 = 0x0002,
+  DVD_KARAOKE_DOWNMIX_2to0 = 0x0004,
+  DVD_KARAOKE_DOWNMIX_3to0 = 0x0008,
+  DVD_KARAOKE_DOWNMIX_4to0 = 0x0010,
+  DVD_KARAOKE_DOWNMIX_Lto0 = 0x0020,
+  DVD_KARAOKE_DOWNMIX_Rto0 = 0x0040,
+  DVD_KARAOKE_DOWNMIX_0to1 = 0x0100,
+  DVD_KARAOKE_DOWNMIX_1to1 = 0x0200,
+  DVD_KARAOKE_DOWNMIX_2to1 = 0x0400,
+  DVD_KARAOKE_DOWNMIX_3to1 = 0x0800,
+  DVD_KARAOKE_DOWNMIX_4to1 = 0x1000,
+  DVD_KARAOKE_DOWNMIX_Lto1 = 0x2000,
+  DVD_KARAOKE_DOWNMIX_Rto1 = 0x4000
+} DVDKaraokeDownmix_t;
+typedef int DVDKaraokeDownmixMask_t;
+
+/* Display mode */
+typedef enum {
+  DVD_DISPLAY_MODE_ContentDefault = 0,
+  DVD_DISPLAY_MODE_16x9 = 1,
+  DVD_DISPLAY_MODE_4x3PanScan = 2,
+  DVD_DISPLAY_MODE_4x3Letterboxed = 3  
+} DVDDisplayMode_t;
+
+/* Audio attributes */
+typedef struct {
+  DVDAudioAppMode_t     AppMode;
+  DVDAudioFormat_t      AudioFormat;
+  DVDLangID_t           Language;
+  DVDAudioLangExt_t     LanguageExtension;
+  DVDBool_t             HasMultichannelInfo;
+  DVDAudioSampleFreq_t  SampleFrequency;
+  DVDAudioSampleQuant_t SampleQuantization;
+  DVDChannelNumber_t    NumberOfChannels;
+} DVDAudioAttributes_t;
+typedef int DVDAudioSampleFreq_t;
+typedef int DVDAudioSampleQuant_t;
+typedef int DVDChannelNumber_t;
+
+/* Subpicture attributes */
+typedef enum {
+  DVD_SUBPICTURE_TYPE_NotSpecified = 0,
+  DVD_SUBPICTURE_TYPE_Language     = 1,
+  DVD_SUBPICTURE_TYPE_Other        = 2
+} DVDSubpictureType_t;
+typedef enum {
+  DVD_SUBPICTURE_CODING_RunLength = 0,
+  DVD_SUBPICTURE_CODING_Extended  = 1,
+  DVD_SUBPICTURE_CODING_Other     = 2
+} DVDSubpictureCoding_t;
+typedef struct {
+  DVDSubpictureType_t    Type;
+  DVDSubpictureCoding_t  CodingMode;
+  DVDLangID_t            Language;
+  DVDSubpictureLangExt_t LanguageExtension;
+} DVDSubpictureAttributes_t;
+
+/* Video attributes */
+typedef struct {
+  DVDBool_t PanscanPermitted;
+  DVDBool_t LetterboxPermitted;
+  int AspectX;
+  int AspectY;
+  int FrameRate;
+  int FrameHeight;
+  DVDVideoCompression_t Compression;
+  DVDBool_t Line21Field1InGop;
+  DVDBool_t Line21Field2InGop;
+  int more_to_come;
+} DVDVideoAttributes_t;
+typedef int DVDVideoCompression_t;
+
+#endif
+
+#endif /* DVD_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,1165 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/*
+#define LOG_DEBUG
+*/
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/time.h>
+#include "dvd_types.h"
+#include "dvd_reader.h"
+#include "nav_types.h"
+#include "ifo_types.h" /* For vm_cmd_t */
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_events.h"
+#include "dvdnav_internal.h"
+#include "read_cache.h"
+#include "nav_read.h"
+#include "remap.h"
+
+static dvdnav_status_t dvdnav_clear(dvdnav_t * this) {
+  /* clear everything except file, vm, mutex, readahead */
+
+  pthread_mutex_lock(&this->vm_lock);
+  if (this->file) DVDCloseFile(this->file);
+  this->file = NULL;
+
+  memset(&this->pci,0,sizeof(this->pci));
+  memset(&this->dsi,0,sizeof(this->dsi));
+  this->last_cmd_nav_lbn = SRI_END_OF_CELL;
+
+  /* Set initial values of flags */
+  this->position_current.still = 0;
+  this->skip_still = 0;
+  this->sync_wait = 0;
+  this->sync_wait_skip = 0;
+  this->spu_clut_changed = 0;
+  this->started = 0;
+  this->cur_cell_time = 0;
+
+  dvdnav_read_cache_clear(this->cache);
+  pthread_mutex_unlock(&this->vm_lock);
+  
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_open(dvdnav_t** dest, const char *path) {
+  dvdnav_t *this;
+  struct timeval time;
+  
+  /* Create a new structure */
+  fprintf(MSG_OUT, "libdvdnav: Using dvdnav version %s from http://dvd.sf.net\n", VERSION);
+
+  (*dest) = NULL;
+  this = (dvdnav_t*)malloc(sizeof(dvdnav_t));
+  if(!this)
+    return DVDNAV_STATUS_ERR;
+  memset(this, 0, (sizeof(dvdnav_t) ) ); /* Make sure this structure is clean */
+ 
+  pthread_mutex_init(&this->vm_lock, NULL);
+  /* Initialise the error string */
+  printerr("");
+
+  /* Initialise the VM */
+  this->vm = vm_new_vm();
+  if(!this->vm) {
+    printerr("Error initialising the DVD VM.");
+    pthread_mutex_destroy(&this->vm_lock);
+    free(this);
+    return DVDNAV_STATUS_ERR;
+  }
+  if(!vm_reset(this->vm, path)) {
+    printerr("Error starting the VM / opening the DVD device.");
+    pthread_mutex_destroy(&this->vm_lock);
+    vm_free_vm(this->vm);
+    free(this);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  /* Set the path. FIXME: Is a deep copy 'right' */
+  strncpy(this->path, path, MAX_PATH_LEN - 1);
+  this->path[MAX_PATH_LEN - 1] = '\0';
+
+  /* Pre-open and close a file so that the CSS-keys are cached. */
+  this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), 0, DVD_READ_MENU_VOBS);
+    
+  /* Start the read-ahead cache. */
+  this->cache = dvdnav_read_cache_new(this);
+
+  /* Seed the random numbers. So that the DVD VM Command rand()
+   * gives a different start value each time a DVD is played. */
+  gettimeofday(&time, NULL);
+  srand(time.tv_usec);
+ 
+  dvdnav_clear(this);
+ 
+  (*dest) = this;
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_close(dvdnav_t *this) {
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: close:called\n");
+#endif
+
+  if (this->file) {
+    pthread_mutex_lock(&this->vm_lock); 
+    DVDCloseFile(this->file);
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: close:file closing\n");
+#endif
+    this->file = NULL;
+    pthread_mutex_unlock(&this->vm_lock); 
+  }
+
+  /* Free the VM */
+  if(this->vm)
+    vm_free_vm(this->vm);
+
+  pthread_mutex_destroy(&this->vm_lock);
+
+  /* We leave the final freeing of the entire structure to the cache,
+   * because we don't know, if there are still buffers out in the wild,
+   * that must return first. */
+  if(this->cache)
+    dvdnav_read_cache_free(this->cache);
+  else
+    free(this);
+  
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_reset(dvdnav_t *this) {
+  dvdnav_status_t result;
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: reset:called\n");
+#endif
+
+  pthread_mutex_lock(&this->vm_lock); 
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: reseting vm\n");
+#endif
+  if(!vm_reset(this->vm, NULL)) {
+    printerr("Error restarting the VM.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_ERR;
+  }
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: clearing dvdnav\n");
+#endif
+  result = dvdnav_clear(this);
+
+  pthread_mutex_unlock(&this->vm_lock); 
+  return result;
+}
+
+dvdnav_status_t dvdnav_path(dvdnav_t *this, const char** path) {
+  (*path) = this->path;
+
+  return DVDNAV_STATUS_OK;
+}
+
+const char* dvdnav_err_to_string(dvdnav_t *this) {
+  
+  if(!this)
+    return "Hey! You gave me a NULL pointer you naughty person!";
+  
+  return this->err_str;
+}
+
+/* converts a dvd_time_t to PTS ticks */
+int64_t dvdnav_convert_time(dvd_time_t *time) {
+  int64_t result;
+  int64_t frames;
+  
+  result  = (time->hour    >> 4  ) * 10 * 60 * 60 * 90000;
+  result += (time->hour    & 0x0f)      * 60 * 60 * 90000;
+  result += (time->minute  >> 4  )      * 10 * 60 * 90000;
+  result += (time->minute  & 0x0f)           * 60 * 90000;
+  result += (time->second  >> 4  )           * 10 * 90000;
+  result += (time->second  & 0x0f)                * 90000;
+  
+  frames  = ((time->frame_u & 0x30) >> 4) * 10;
+  frames += ((time->frame_u & 0x0f)     )     ;
+  
+  if (time->frame_u & 0x80)
+    result += frames * 3000;
+  else
+    result += frames * 3600;
+  
+  return result;
+}
+
+/*
+ * Returns 1 if block contains NAV packet, 0 otherwise.
+ * Processes said NAV packet if present.
+ *
+ * Most of the code in here is copied from xine's MPEG demuxer
+ * so any bugs which are found in that should be corrected here also.
+ */
+static int32_t dvdnav_decode_packet(dvdnav_t *this, uint8_t *p, dsi_t *nav_dsi, pci_t *nav_pci) {
+  int32_t        bMpeg1 = 0;
+  uint32_t       nHeaderLen;
+  uint32_t       nPacketLen;
+  uint32_t       nStreamID;
+
+  if (p[3] == 0xBA) { /* program stream pack header */
+    int32_t nStuffingBytes;
+
+    bMpeg1 = (p[4] & 0x40) == 0;
+
+    if (bMpeg1) {
+      p += 12;
+    } else { /* mpeg2 */
+      nStuffingBytes = p[0xD] & 0x07;
+      p += 14 + nStuffingBytes;
+    }
+  }
+
+  if (p[3] == 0xbb) { /* program stream system header */
+    nHeaderLen = (p[4] << 8) | p[5];
+    p += 6 + nHeaderLen;
+  }
+
+  /* we should now have a PES packet here */
+  if (p[0] || p[1] || (p[2] != 1)) {
+    fprintf(MSG_OUT, "libdvdnav: demux error! %02x %02x %02x (should be 0x000001) \n",p[0],p[1],p[2]);
+    return 0;
+  }
+
+  nPacketLen = p[4] << 8 | p[5];
+  nStreamID  = p[3];
+
+  nHeaderLen = 6;
+  p += nHeaderLen;
+
+  if (nStreamID == 0xbf) { /* Private stream 2 */
+#if 0
+    int32_t i;
+    fprintf(MSG_OUT, "libdvdnav: nav packet=%u\n",p-p_start-6);
+    for(i=0;i<80;i++)
+      fprintf(MSG_OUT, "%02x ",p[i-6]);
+    fprintf(MSG_OUT, "\n");
+#endif
+
+    if(p[0] == 0x00) {
+      navRead_PCI(nav_pci, p+1);
+    }
+
+    p += nPacketLen;
+
+    /* We should now have a DSI packet. */
+    if(p[6] == 0x01) {
+      nPacketLen = p[4] << 8 | p[5];
+      p += 6;
+      navRead_DSI(nav_dsi, p+1);
+    } 
+    return 1;
+  }
+  return 0;
+}
+
+/* DSI is used for most angle stuff. 
+ * PCI is used for only non-seemless angle stuff
+ */ 
+static int32_t dvdnav_get_vobu(dvdnav_t *this, dsi_t *nav_dsi, pci_t *nav_pci, dvdnav_vobu_t *vobu) {
+  uint32_t next;
+  int32_t angle, num_angle;
+
+  vobu->vobu_start = nav_dsi->dsi_gi.nv_pck_lbn; /* Absolute offset from start of disk */
+  vobu->vobu_length = nav_dsi->dsi_gi.vobu_ea; /* Relative offset from vobu_start */
+     
+  /*
+   * If we're not at the end of this cell, we can determine the next
+   * VOBU to display using the VOBU_SRI information section of the
+   * DSI.  Using this value correctly follows the current angle,
+   * avoiding the doubled scenes in The Matrix, and makes our life
+   * really happy.
+   *
+   * vobu_next is an offset value, 0x3fffffff = SRI_END_OF_CELL
+   * DVDs are about 6 Gigs, which is only up to 0x300000 blocks
+   * Should really assert if bit 31 != 1
+   */
+  
+#if 0
+  /* Old code -- may still be useful one day */
+  if(nav_dsi->vobu_sri.next_vobu != SRI_END_OF_CELL ) {
+    vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff );
+  } else {
+    vobu->vobu_next = vobu->vobu_length;
+  }
+#else
+  /* Relative offset from vobu_start */
+  vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff );
+#endif
+  
+  vm_get_angle_info(this->vm, &angle, &num_angle);
+
+  /* FIMXE: The angle reset doesn't work for some reason for the moment */
+#if 0
+  if((num_angle < angle) && (angle != 1)) {
+    fprintf(MSG_OUT, "libdvdnav: angle ends!\n");
+    
+    /* This is to switch back to angle one when we
+     * finish with angles. */
+    dvdnav_angle_change(this, 1);
+  } 
+#endif
+
+  if(num_angle != 0) {
+    
+    if((next = nav_pci->nsml_agli.nsml_agl_dsta[angle-1]) != 0) {
+      if((next & 0x3fffffff) != 0) {
+	if(next & 0x80000000)
+	  vobu->vobu_next = - (int32_t)(next & 0x3fffffff);
+	else
+	  vobu->vobu_next = + (int32_t)(next & 0x3fffffff);
+      }
+    } else if((next = nav_dsi->sml_agli.data[angle-1].address) != 0) {
+      vobu->vobu_length = nav_dsi->sml_pbi.ilvu_ea;
+      
+      if((next & 0x80000000) && (next != 0x7fffffff))
+	vobu->vobu_next =  - (int32_t)(next & 0x3fffffff);
+      else
+	vobu->vobu_next =  + (int32_t)(next & 0x3fffffff);
+    }
+  }
+
+  return 1;
+}
+
+/*
+ * These are the main get_next_block function which actually get the media stream video and audio etc.
+ *
+ * There are two versions: The second one is using the zero-copy read ahead cache and therefore
+ * hands out pointers targetting directly into the cache.
+ * The first one uses a memcopy to fill this cache block into the application provided memory.
+ * The benefit of this first one is that no special memory management is needed. The application is
+ * the only one responsible of allocating and freeing the memory associated with the pointer.
+ * The drawback is the additional memcopy.
+ */
+
+dvdnav_status_t dvdnav_get_next_block(dvdnav_t *this, uint8_t *buf,
+				      int32_t *event, int32_t *len) {
+  unsigned char *block;
+  dvdnav_status_t status;
+  
+  block = buf;
+  status = dvdnav_get_next_cache_block(this, &block, event, len);
+  if (status == DVDNAV_STATUS_OK && block != buf) {
+    /* we received a block from the cache, copy it, so we can give it back */
+    memcpy(buf, block, DVD_VIDEO_LB_LEN);
+    dvdnav_free_cache_block(this, block);
+  }
+  return status;
+}
+
+int64_t dvdnav_get_current_time(dvdnav_t *this) {
+  int i;
+  int64_t tm=0;
+  dvd_state_t *state = &this->vm->state;
+
+  for(i=0; i<state->cellN-1; i++) {
+    if(!
+        (state->pgc->cell_playback[i].block_type == BLOCK_TYPE_ANGLE_BLOCK &&
+         state->pgc->cell_playback[i].block_mode != BLOCK_MODE_FIRST_CELL)
+    )
+      tm += dvdnav_convert_time(&state->pgc->cell_playback[i].playback_time);
+  }
+  tm += this->cur_cell_time;
+
+  return tm;
+}
+ 
+dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *this, uint8_t **buf,
+					    int32_t *event, int32_t *len) {
+  dvd_state_t *state;
+  int32_t result;
+
+  pthread_mutex_lock(&this->vm_lock);
+  
+  if(!this->started) {
+    /* Start the VM */
+    if (!vm_start(this->vm)) {
+      printerr("Encrypted or faulty DVD");
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    this->started = 1;
+  }
+
+  state = &(this->vm->state);
+  (*event) = DVDNAV_NOP;
+  (*len) = 0;
+ 
+  /* Check the STOP flag */
+  if(this->vm->stopped) {
+    vm_stop(this->vm);
+    (*event) = DVDNAV_STOP;
+    this->started = 0;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  vm_position_get(this->vm, &this->position_next);
+  
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: POS-NEXT ");
+  vm_position_print(this->vm, &this->position_next);
+  fprintf(MSG_OUT, "libdvdnav: POS-CUR  ");
+  vm_position_print(this->vm, &this->position_current);
+#endif
+
+  /* did we hop? */
+  if(this->position_current.hop_channel != this->position_next.hop_channel) {
+    (*event) = DVDNAV_HOP_CHANNEL;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: HOP_CHANNEL\n");
+#endif
+    if (this->position_next.hop_channel - this->position_current.hop_channel >= HOP_SEEK) {
+      int32_t num_angles = 0, current;
+      
+      /* we seeked -> check for multiple angles */
+      vm_get_angle_info(this->vm, &current, &num_angles);
+      if (num_angles > 1) {
+        int32_t result, block;
+	/* we have to skip the first VOBU when seeking in a multiangle feature,
+	 * because it might belong to the wrong angle */
+	block = this->position_next.cell_start + this->position_next.block;
+	result = dvdnav_read_cache_block(this->cache, block, 1, buf);
+	if(result <= 0) {
+	  printerr("Error reading NAV packet.");
+	  pthread_mutex_unlock(&this->vm_lock); 
+	  return DVDNAV_STATUS_ERR;
+	}
+	/* Decode nav into pci and dsi. Then get next VOBU info. */
+	if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) {
+	  printerr("Expected NAV packet but none found.");
+	  pthread_mutex_unlock(&this->vm_lock); 
+	  return DVDNAV_STATUS_ERR;
+	}
+	dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu);
+	/* skip to next, if there is a next */
+	if (this->vobu.vobu_next != SRI_END_OF_CELL) {
+	  this->vobu.vobu_start += this->vobu.vobu_next;
+	  this->vobu.vobu_next   = 0;
+	}
+	/* update VM state */
+	this->vm->state.blockN = this->vobu.vobu_start - this->position_next.cell_start;
+      }
+    }
+    this->position_current.hop_channel = this->position_next.hop_channel;
+    /* update VOBU info */
+    this->vobu.vobu_start  = this->position_next.cell_start + this->position_next.block;
+    this->vobu.vobu_next   = 0;
+    /* Make blockN == vobu_length to do expected_nav */
+    this->vobu.vobu_length = 0;
+    this->vobu.blockN      = 0;
+    this->sync_wait        = 0;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* Check the HIGHLIGHT flag */
+  if(this->position_current.button != this->position_next.button) {
+    dvdnav_highlight_event_t *hevent = (dvdnav_highlight_event_t *)*buf;
+
+    (*event) = DVDNAV_HIGHLIGHT;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: HIGHLIGHT\n");
+#endif
+    (*len) = sizeof(dvdnav_highlight_event_t);
+    hevent->display = 1;
+    hevent->buttonN = this->position_next.button;
+    this->position_current.button = this->position_next.button;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+  
+  /* Check the WAIT flag */
+  if(this->sync_wait) {
+    (*event) = DVDNAV_WAIT;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: WAIT\n");
+#endif
+    (*len) = 0;
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* Check to see if we need to change the currently opened VOB */
+  if((this->position_current.vts != this->position_next.vts) || 
+     (this->position_current.domain != this->position_next.domain)) {
+    dvd_read_domain_t domain;
+    int32_t vtsN;
+    dvdnav_vts_change_event_t *vts_event = (dvdnav_vts_change_event_t *)*buf;
+    
+    if(this->file) {
+      DVDCloseFile(this->file);
+      this->file = NULL;
+    }
+
+    vts_event->old_vtsN = this->position_current.vts;
+    vts_event->old_domain = this->position_current.domain;
+     
+    /* Use the DOMAIN to find whether to open menu or title VOBs */
+    switch(this->position_next.domain) {
+    case FP_DOMAIN:
+    case VMGM_DOMAIN:
+      domain = DVD_READ_MENU_VOBS;
+      vtsN = 0;
+      break;
+    case VTSM_DOMAIN:
+      domain = DVD_READ_MENU_VOBS;
+      vtsN = this->position_next.vts; 
+      break;
+    case VTS_DOMAIN:
+      domain = DVD_READ_TITLE_VOBS;
+      vtsN = this->position_next.vts; 
+      break;
+    default:
+      printerr("Unknown domain when changing VTS.");
+      pthread_mutex_unlock(&this->vm_lock); 
+      return DVDNAV_STATUS_ERR;
+    }
+    
+    this->position_current.vts = this->position_next.vts; 
+    this->position_current.domain = this->position_next.domain;
+    dvdnav_read_cache_clear(this->cache);
+    this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), vtsN, domain);
+    vts_event->new_vtsN = this->position_next.vts; 
+    vts_event->new_domain = this->position_next.domain; 
+
+    /* If couldn't open the file for some reason, moan */
+    if(this->file == NULL) {
+      printerrf("Error opening vtsN=%i, domain=%i.", vtsN, domain);
+      pthread_mutex_unlock(&this->vm_lock); 
+      return DVDNAV_STATUS_ERR;
+    }
+
+    /* File opened successfully so return a VTS change event */
+    (*event) = DVDNAV_VTS_CHANGE;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: VTS_CHANGE\n");
+#endif
+    (*len) = sizeof(dvdnav_vts_change_event_t);
+
+    this->spu_clut_changed = 1;
+    this->position_current.cell = -1; /* Force an update */
+    this->position_current.spu_channel = -1; /* Force an update */
+    this->position_current.audio_channel = -1; /* Force an update */;
+     
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* Check if the cell changed */  
+  if( (this->position_current.cell != this->position_next.cell) ||
+      (this->position_current.cell_restart != this->position_next.cell_restart) ||
+      (this->position_current.cell_start != this->position_next.cell_start) ) {
+    dvdnav_cell_change_event_t *cell_event = (dvdnav_cell_change_event_t *)*buf;
+    int32_t first_cell_nr, last_cell_nr, i;
+    dvd_state_t *state = &this->vm->state;
+    
+    this->cur_cell_time = 0;
+    (*event) = DVDNAV_CELL_CHANGE;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: CELL_CHANGE\n");
+#endif
+    (*len) = sizeof(dvdnav_cell_change_event_t);
+    
+    cell_event->cellN = state->cellN;
+    cell_event->pgN   = state->pgN;
+    cell_event->cell_length =
+      dvdnav_convert_time(&state->pgc->cell_playback[state->cellN-1].playback_time);
+
+    cell_event->pg_length = 0;
+    /* Find start cell of program. */
+    first_cell_nr = state->pgc->program_map[state->pgN-1];
+    /* Find end cell of program */
+    if(state->pgN < state->pgc->nr_of_programs)
+      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+    else
+      last_cell_nr = state->pgc->nr_of_cells;
+    for (i = first_cell_nr; i <= last_cell_nr; i++)
+      cell_event->pg_length +=
+        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);
+    cell_event->pgc_length = dvdnav_convert_time(&state->pgc->playback_time);
+
+    cell_event->cell_start = 0;
+    for (i = 1; i < state->cellN; i++)
+      cell_event->cell_start +=
+        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);
+
+    cell_event->pg_start = 0;
+    for (i = 1; i < state->pgc->program_map[state->pgN-1]; i++)
+      cell_event->pg_start +=
+        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);
+
+    this->position_current.cell         = this->position_next.cell;
+    this->position_current.cell_restart = this->position_next.cell_restart;
+    this->position_current.cell_start   = this->position_next.cell_start;
+    this->position_current.block        = this->position_next.block;
+    
+    /* vobu info is used for mid cell resumes */
+    this->vobu.vobu_start               = this->position_next.cell_start + this->position_next.block;
+    this->vobu.vobu_next                = 0;
+    /* Make blockN == vobu_length to do expected_nav */
+    this->vobu.vobu_length = 0;
+    this->vobu.blockN      = 0;
+    
+    /* update the spu palette at least on PGC changes */
+    this->spu_clut_changed = 1;
+    this->position_current.spu_channel = -1; /* Force an update */
+    this->position_current.audio_channel = -1; /* Force an update */
+
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+ 
+  /* has the CLUT changed? */
+  if(this->spu_clut_changed) {
+    (*event) = DVDNAV_SPU_CLUT_CHANGE;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: SPU_CLUT_CHANGE\n");
+#endif
+    (*len) = 16 * sizeof(uint32_t);
+    memcpy(*buf, &(state->pgc->palette), 16 * sizeof(uint32_t));
+    this->spu_clut_changed = 0;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* has the SPU channel changed? */  
+  if(this->position_current.spu_channel != this->position_next.spu_channel) {
+    dvdnav_spu_stream_change_event_t *stream_change = (dvdnav_spu_stream_change_event_t *)*buf;
+
+    (*event) = DVDNAV_SPU_STREAM_CHANGE;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE\n");
+#endif
+    (*len) = sizeof(dvdnav_spu_stream_change_event_t);
+    stream_change->physical_wide      = vm_get_subp_active_stream(this->vm, 0);
+    stream_change->physical_letterbox = vm_get_subp_active_stream(this->vm, 1);
+    stream_change->physical_pan_scan  = vm_get_subp_active_stream(this->vm, 2);
+    this->position_current.spu_channel = this->position_next.spu_channel;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_wide=%d\n",stream_change->physical_wide);
+    fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_letterbox=%d\n",stream_change->physical_letterbox);
+    fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_pan_scan=%d\n",stream_change->physical_pan_scan);
+    fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE returning DVDNAV_STATUS_OK\n");
+#endif
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* has the audio channel changed? */  
+  if(this->position_current.audio_channel != this->position_next.audio_channel) {
+    dvdnav_audio_stream_change_event_t *stream_change = (dvdnav_audio_stream_change_event_t *)*buf;
+    
+    (*event) = DVDNAV_AUDIO_STREAM_CHANGE;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: AUDIO_STREAM_CHANGE\n");
+#endif
+    (*len) = sizeof(dvdnav_audio_stream_change_event_t);
+    stream_change->physical = vm_get_audio_active_stream( this->vm );
+    this->position_current.audio_channel = this->position_next.audio_channel;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: AUDIO_STREAM_CHANGE stream_id=%d returning DVDNAV_STATUS_OK\n",stream_change->physical);
+#endif
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+     
+  /* Check the STILLFRAME flag */
+  if(this->position_current.still != 0) {
+    dvdnav_still_event_t *still_event = (dvdnav_still_event_t *)*buf;
+
+    (*event) = DVDNAV_STILL_FRAME;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: STILL_FRAME\n");
+#endif
+    (*len) = sizeof(dvdnav_still_event_t);
+    still_event->length = this->position_current.still;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+
+  /* Have we reached the end of a VOBU? */
+  if (this->vobu.blockN >= this->vobu.vobu_length) {
+
+    /* Have we reached the end of a cell? */
+    if(this->vobu.vobu_next == SRI_END_OF_CELL) {
+      /* End of Cell from NAV DSI info */
+#ifdef LOG_DEBUG
+      fprintf(MSG_OUT, "libdvdnav: Still set to %x\n", this->position_next.still);
+#endif
+      this->position_current.still = this->position_next.still;
+
+      /* we are about to leave a cell, so a lot of state changes could occur;
+       * under certain conditions, the application should get in sync with us before this,
+       * otherwise it might show stills or menus too shortly */
+      if ((this->position_current.still || this->pci.hli.hl_gi.hli_ss) && !this->sync_wait_skip) {
+        this->sync_wait = 1;
+      } else {
+	if( this->position_current.still == 0 || this->skip_still ) {
+	  /* no active cell still -> get us to the next cell */
+	  vm_get_next_cell(this->vm);
+	  this->position_current.still = 0; /* still gets activated at end of cell */
+	  this->skip_still = 0;
+	  this->sync_wait_skip = 0;
+	}
+      }
+      /* handle related state changes in next iteration */
+      (*event) = DVDNAV_NOP;
+      (*len) = 0;
+      pthread_mutex_unlock(&this->vm_lock); 
+      return DVDNAV_STATUS_OK;
+    }
+
+    /* Perform remapping jump if necessary (this is always a 
+     * VOBU boundary). */
+    if (this->vm->map) {
+      this->vobu.vobu_next = remap_block( this->vm->map,
+        this->vm->state.domain, this->vm->state.TTN_REG,
+        this->vm->state.pgN,
+        this->vobu.vobu_start, this->vobu.vobu_next);
+    }
+
+    /* at the start of the next VOBU -> expecting NAV packet */
+    result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.vobu_next, 1, buf);
+
+    if(result <= 0) {
+      printerr("Error reading NAV packet.");
+      pthread_mutex_unlock(&this->vm_lock); 
+      return DVDNAV_STATUS_ERR;
+    }
+    /* Decode nav into pci and dsi. Then get next VOBU info. */
+    if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) {
+      printerr("Expected NAV packet but none found.");
+      pthread_mutex_unlock(&this->vm_lock); 
+      return DVDNAV_STATUS_ERR;
+    }
+    /* We need to update the vm state->blockN with which VOBU we are in.
+     * This is so RSM resumes to the VOBU level and not just the CELL level.
+     */
+    this->vm->state.blockN = this->vobu.vobu_start - this->position_current.cell_start;
+
+    dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu); 
+    this->vobu.blockN = 0;
+    /* Give the cache a hint about the size of next VOBU.
+     * This improves pre-caching, because the VOBU will almost certainly be read entirely.
+     */
+    dvdnav_pre_cache_blocks(this->cache, this->vobu.vobu_start+1, this->vobu.vobu_length+1);
+    
+    /* release NAV menu filter, when we reach the same NAV packet again */
+    if (this->last_cmd_nav_lbn == this->pci.pci_gi.nv_pck_lbn)
+      this->last_cmd_nav_lbn = SRI_END_OF_CELL;
+    
+    /* Successfully got a NAV packet */
+    (*event) = DVDNAV_NAV_PACKET;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: NAV_PACKET\n");
+#endif
+    (*len) = 2048; 
+    this->cur_cell_time = dvdnav_convert_time(&this->dsi.dsi_gi.c_eltm);
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  }
+  
+  /* If we've got here, it must just be a normal block. */
+  if(!this->file) {
+    printerr("Attempting to read without opening file.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_ERR;
+  }
+
+  this->vobu.blockN++;
+  result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.blockN, 1, buf);
+  if(result <= 0) {
+    printerr("Error reading from DVD.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_ERR;
+  }
+  (*event) = DVDNAV_BLOCK_OK;
+  (*len) = 2048;
+
+  pthread_mutex_unlock(&this->vm_lock); 
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_title_string(dvdnav_t *this, const char **title_str) {
+  (*title_str) = this->vm->dvd_name;
+  return DVDNAV_STATUS_OK;
+}
+
+uint8_t dvdnav_get_video_aspect(dvdnav_t *this) {
+  uint8_t         retval;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+
+  pthread_mutex_lock(&this->vm_lock);
+  retval = (uint8_t)vm_get_video_aspect(this->vm);
+  pthread_mutex_unlock(&this->vm_lock);
+  
+  return retval;
+}
+
+uint8_t dvdnav_get_video_scale_permission(dvdnav_t *this) {
+  uint8_t         retval;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock);
+  retval = (uint8_t)vm_get_video_scale_permission(this->vm);
+  pthread_mutex_unlock(&this->vm_lock);
+  
+  return retval;
+}
+
+uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *this, uint8_t stream) {
+  audio_attr_t  attr;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  attr = vm_get_audio_attr(this->vm, stream);
+  pthread_mutex_unlock(&this->vm_lock); 
+  
+  if(attr.lang_type != 1)
+    return 0xffff;
+  
+  return attr.lang_code;
+}
+
+uint16_t dvdnav_audio_stream_format(dvdnav_t *this, uint8_t stream) {
+  audio_attr_t  attr;
+  uint16_t format;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1; /* 0xffff */
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  attr = vm_get_audio_attr(this->vm, stream);
+  pthread_mutex_unlock(&this->vm_lock); 
+  
+  switch(attr.audio_format) {
+  case 0:
+    format = DVDNAV_FORMAT_AC3;
+    break;
+  case 2: /* MPEG-1 or MPEG-2 without extension bitstream. */
+  case 3: /* MPEG-2 with extension bitstream. */
+    format = DVDNAV_FORMAT_MPEGAUDIO;
+    break;
+  case 4:
+    format = DVDNAV_FORMAT_LPCM;
+    break;
+  case 6:
+    format = DVDNAV_FORMAT_DTS;
+    break;
+  case 7:
+    format = DVDNAV_FORMAT_SDDS;
+    break;
+  default: 
+    format = 0xffff;
+    break;
+  }
+  
+  return format;
+}
+
+uint16_t dvdnav_audio_stream_channels(dvdnav_t *this, uint8_t stream) {
+  audio_attr_t  attr;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1; /* 0xffff */
+  }
+
+  pthread_mutex_lock(&this->vm_lock);
+  attr = vm_get_audio_attr(this->vm, stream);
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return attr.channels + 1;
+}
+
+uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *this, uint8_t stream) {
+  subp_attr_t  attr;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  attr = vm_get_subp_attr(this->vm, stream);
+  pthread_mutex_unlock(&this->vm_lock); 
+  
+  if(attr.type != 1)
+    return 0xffff;
+  
+  return attr.lang_code;
+}
+
+int8_t dvdnav_get_audio_logical_stream(dvdnav_t *this, uint8_t audio_num) {
+  int8_t       retval;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  retval = vm_get_audio_stream(this->vm, audio_num);
+  pthread_mutex_unlock(&this->vm_lock); 
+
+  return retval;
+}
+
+dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *this, uint8_t audio_num, audio_attr_t *audio_attr) {
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  *audio_attr=vm_get_audio_attr(this->vm, audio_num);
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+int8_t dvdnav_get_spu_logical_stream(dvdnav_t *this, uint8_t subp_num) {
+  int8_t       retval;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  retval = vm_get_subp_stream(this->vm, subp_num, 0);
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return retval;
+}
+
+dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *this, uint8_t audio_num, subp_attr_t *subp_attr) {
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  *subp_attr=vm_get_subp_attr(this->vm, audio_num);
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}
+
+int8_t dvdnav_get_active_audio_stream(dvdnav_t *this) {
+  int8_t        retval;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  retval = vm_get_audio_active_stream(this->vm);
+  pthread_mutex_unlock(&this->vm_lock); 
+  
+  return retval;
+}
+
+int8_t dvdnav_get_active_spu_stream(dvdnav_t *this) {
+  int8_t        retval;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return -1;
+  }
+  retval = vm_get_subp_active_stream(this->vm, 0);
+  pthread_mutex_unlock(&this->vm_lock); 
+  
+  return retval;
+}
+
+static int8_t dvdnav_is_domain(dvdnav_t *this, domain_t domain) {
+  int8_t        retval;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return -1;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock);
+  retval = (this->vm->state.domain == domain);
+  pthread_mutex_unlock(&this->vm_lock);
+  
+  return retval;
+}
+
+/* First Play domain. (Menu) */
+int8_t dvdnav_is_domain_fp(dvdnav_t *this) {
+  return dvdnav_is_domain(this, FP_DOMAIN);
+}
+/* Video management Menu domain. (Menu) */
+int8_t dvdnav_is_domain_vmgm(dvdnav_t *this) {
+  return dvdnav_is_domain(this, VMGM_DOMAIN);
+}
+/* Video Title Menu domain (Menu) */
+int8_t dvdnav_is_domain_vtsm(dvdnav_t *this) {
+  return dvdnav_is_domain(this, VTSM_DOMAIN);
+}
+/* Video Title domain (playing movie). */
+int8_t dvdnav_is_domain_vts(dvdnav_t *this) { 
+  return dvdnav_is_domain(this, VTS_DOMAIN);
+}
+
+/* Generally delegate angle information handling to VM */
+dvdnav_status_t dvdnav_angle_change(dvdnav_t *this, int32_t angle) {
+  int32_t num, current;
+  
+  pthread_mutex_lock(&this->vm_lock);
+  vm_get_angle_info(this->vm, &current, &num);
+  /* Set angle SPRM if valid */
+  if((angle > 0) && (angle <= num)) {
+    this->vm->state.AGL_REG = angle;
+  } else {
+    printerr("Passed an invalid angle number.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *this, int32_t *current_angle,
+				      int32_t *number_of_angles) {
+  pthread_mutex_lock(&this->vm_lock);
+  vm_get_angle_info(this->vm, current_angle, number_of_angles);
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+pci_t* dvdnav_get_current_nav_pci(dvdnav_t *this) {
+  if(!this) return 0;
+  return &this->pci;
+}
+
+dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *this) {
+  if(!this) return 0;
+  return &this->dsi;
+}
+
+uint32_t dvdnav_get_next_still_flag(dvdnav_t *this) {
+  if(!this) return -1;
+  return this->position_next.still;
+}
+
+user_ops_t dvdnav_get_restrictions(dvdnav_t* this) {
+  /* 
+   * user_ops_t is a structure of 32 bits.  We want to compute 
+   * the union of two of those bitfields so to make this quicker 
+   * than performing 32 ORs, we will access them as 32bits words.
+   */
+  union {
+    user_ops_t ops_struct;
+    uint32_t   ops_int;
+  } ops;
+  
+  ops.ops_int = 0;
+  
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return ops.ops_struct;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock); 
+  ops.ops_int |= *(uint32_t*)&this->pci.pci_gi.vobu_uop_ctl;
+  
+  if(this->vm && this->vm->state.pgc)
+    ops.ops_int |= *(uint32_t*)&this->vm->state.pgc->prohibited_ops;
+  pthread_mutex_unlock(&this->vm_lock); 
+    
+  return ops.ops_struct;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,684 @@
+/* 
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+/*
+ * This is the main header file applications should include if they want
+ * to access dvdnav functionality.
+ */
+
+#ifndef DVDNAV_H_INCLUDED
+#define DVDNAV_H_INCLUDED
+
+#define MP_DVDNAV 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef DVDNAV_COMPILE
+#  include <dvdnav/dvd_types.h>
+#  include <libdvdread/dvd_reader.h>
+#  include <libdvdread/nav_types.h>
+#  include <libdvdread/ifo_types.h> /* For vm_cmd_t */
+#  include <dvdnav/dvdnav_events.h>
+#endif
+
+
+
+/*********************************************************************
+ * dvdnav data types                                                 *
+ *********************************************************************/
+
+/*
+ * Opaque data-type can be viewed as a 'DVD handle'. You should get
+ * a pointer to a dvdnav_t from the dvdnav_open() function.
+ * Never call free() on the pointer, you have to give it back with
+ * dvdnav_close().
+ */
+typedef struct dvdnav_s dvdnav_t;
+
+/* Status as reported by most of libdvdnav's functions */
+typedef int32_t dvdnav_status_t;
+
+/*
+ * Unless otherwise stated, all functions return DVDNAV_STATUS_OK if
+ * they succeeded, otherwise DVDNAV_STATUS_ERR is returned and the error may
+ * be obtained by calling dvdnav_err_to_string().
+ */
+#define DVDNAV_STATUS_ERR 0
+#define DVDNAV_STATUS_OK  1
+
+#define DVDNAV_FORMAT_AC3 0
+#define DVDNAV_FORMAT_MPEGAUDIO 3
+#define DVDNAV_FORMAT_LPCM 4
+#define DVDNAV_FORMAT_DTS 5
+#define DVDNAV_FORMAT_SDDS 6
+
+/*********************************************************************
+ * initialisation & housekeeping functions                           *
+ *********************************************************************/
+
+/*
+ * These functions allow you to open a DVD device and associate it
+ * with a dvdnav_t.
+ */
+
+/*
+ * Attempts to open the DVD drive at the specified path and pre-cache
+ * the CSS-keys. libdvdread is used to access the DVD, so any source
+ * supported by libdvdread can be given with "path". Currently,
+ * libdvdread can access: DVD drives, DVD image files, DVD file-by-file
+ * copies.
+ *
+ * The resulting dvdnav_t handle will be written to *dest.
+ */
+dvdnav_status_t dvdnav_open(dvdnav_t **dest, const char *path);
+
+/*
+ * Closes a dvdnav_t previously opened with dvdnav_open(), freeing any 
+ * memory associated with it.
+ */
+dvdnav_status_t dvdnav_close(dvdnav_t *self);
+
+/*
+ * Resets the DVD virtual machine and cache buffers.
+ */
+dvdnav_status_t dvdnav_reset(dvdnav_t *self);
+
+/*
+ * Fills a pointer with a value pointing to a string describing
+ * the path associated with an open dvdnav_t. It assigns *path to NULL
+ * on error.
+ */
+dvdnav_status_t dvdnav_path(dvdnav_t *self, const char **path);
+
+/*
+ * Returns a human-readable string describing the last error.
+ */
+const char* dvdnav_err_to_string(dvdnav_t *self);
+
+
+/*********************************************************************
+ * changing and reading DVD player characteristics                   *
+ *********************************************************************/
+
+/*
+ * These functions allow you to manipulate the various global characteristics
+ * of the DVD playback engine.
+ */
+
+/*
+ * Sets the region mask (bit 0 set implies region 1, bit 1 set implies
+ * region 2, etc) of the virtual machine. Generally you will only need to set
+ * this if you are playing RCE discs which query the virtual machine as to its
+ * region setting. 
+ *
+ * This has _nothing_ to do with the region setting of the DVD drive.
+ */
+dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int32_t region_mask);
+
+/*
+ * Returns the region mask (bit 0 set implies region 1, bit 1 set implies
+ * region 2, etc) of the virtual machine.
+ *
+ * This has _nothing_ to do with the region setting of the DVD drive.
+ */
+dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *self, int32_t *region_mask);
+
+/*
+ * Specify whether read-ahead caching should be used. You may not want this if your
+ * decoding engine does its own buffering.
+ *
+ * The default read-ahead cache does not use an additional thread for the reading
+ * (see read_cache.c for a threaded cache, but note that this code is currently
+ * unmaintained). It prebuffers on VOBU level by reading ahead several buffers
+ * on every read request. The speed of this prebuffering has been optimized to
+ * also work on slow DVD drives.
+ *
+ * If in addition you want to prevent memcpy's to improve performance, have a look
+ * at dvdnav_get_next_cache_block().
+ */
+dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int32_t read_ahead_flag);
+
+/*
+ * Query whether read-ahead caching/buffering will be used.
+ */
+dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *self, int32_t *read_ahead_flag);
+
+/*
+ * Specify whether the positioning works PGC or PG based.
+ * Programs (PGs) on DVDs are similar to Chapters and a program chain (PGC)
+ * usually covers a whole feature. This affects the behaviour of the
+ * functions dvdnav_get_position() and dvdnav_sector_search(). See there.
+ * Default is PG based positioning.
+ */
+dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *self, int32_t pgc_based_flag);
+
+/*
+ * Query whether positioning is PG or PGC based.
+ */
+dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *self, int32_t *pgc_based_flag);
+
+
+/*********************************************************************
+ * reading data                                                      *
+ *********************************************************************/
+
+/*
+ * These functions are used to poll the playback enginge and actually get data 
+ * off the DVD.
+ */
+
+/*
+ * Attempts to get the next block off the DVD and copies it into the buffer 'buf'. 
+ * If there is any special actions that may need to be performed, the value
+ * pointed to by 'event' gets set accordingly.
+ *
+ * If 'event' is DVDNAV_BLOCK_OK then 'buf' is filled with the next block
+ * (note that means it has to be at /least/ 2048 bytes big). 'len' is
+ * then set to 2048.
+ *
+ * Otherwise, buf is filled with an appropriate event structure and
+ * len is set to the length of that structure.
+ *
+ * See the dvdnav_events.h header for information on the various events.
+ */
+dvdnav_status_t dvdnav_get_next_block(dvdnav_t *self, uint8_t *buf,
+				      int32_t *event, int32_t *len);
+
+/*
+ * This basically does the same as dvdnav_get_next_block. The only difference is
+ * that it avoids a memcopy, when the requested block was found in the cache.
+ * I such a case (cache hit) this function will return a different pointer than
+ * the one handed in, pointing directly into the relevant block in the cache.
+ * Those pointers must _never_ be freed but instead returned to the library via
+ * dvdnav_free_cache_block().
+ */
+dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *self, uint8_t **buf,
+					    int32_t *event, int32_t *len);
+
+/*
+ * All buffers which came from the internal cache (when dvdnav_get_next_cache_block()
+ * returned a buffer different from the one handed in) have to be freed with this
+ * function. Although handing in other buffers not from the cache doesn't cause any harm.
+ */
+dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf);
+
+/*
+ * If we are currently in a still-frame this function skips it.
+ *
+ * See also the DVDNAV_STILL_FRAME event.
+ */
+dvdnav_status_t dvdnav_still_skip(dvdnav_t *self);
+
+/*
+ * If we are currently in WAIT state, that is: the application is required to
+ * wait for its fifos to become empty, calling this signals libdvdnav that this
+ * is achieved and that it can continue.
+ *
+ * See also the DVDNAV_WAIT event.
+ */
+dvdnav_status_t dvdnav_wait_skip(dvdnav_t *self);
+
+/*
+ * Returns the still time from the currently playing cell.
+ * The still time is given in seconds with 0xff meaning an indefinite still.
+ *
+ * This function can be used to detect still frames before they are reached.
+ * Some players might need this to prepare for a frame to be shown for a
+ * longer time than usual.
+ */
+uint32_t dvdnav_get_next_still_flag(dvdnav_t *self);
+
+/*
+ * Stops playback. The next event obtained with one of the get_next_block
+ * functions will be a DVDNAV_STOP event.
+ *
+ * It is not required to call this before dvdnav_close().
+ */
+dvdnav_status_t dvdnav_stop(dvdnav_t *self);
+
+
+/*********************************************************************
+ * title/part navigation                                             *
+ *********************************************************************/
+
+/*
+ * Returns the number of titles on the disk.
+ */
+dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int32_t *titles);
+
+/*
+ * Returns the number of parts within the given title.
+ */
+dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *self, int32_t title, int32_t *parts);
+
+/*
+ * Plays the specified title of the DVD from its beginning (that is: part 1).
+ */
+dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int32_t title);
+
+/*
+ * Plays the specified title, starting from the specified part.
+ */
+dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part);
+
+/*
+ * Stores in *times an array (that the application *must* free) of
+ * dvdtimes corresponding to the chapter times for the chosen title.
+ * *duration will have the duration of the title
+ * The number of entries in *times is the result of the function.
+ * On error *times is NULL and the output is 0
+ */
+uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration);
+
+/*
+ * Play the specified amount of parts of the specified title of
+ * the DVD then STOP.
+ *
+ * Currently unimplemented!
+ */
+dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *self, int32_t title,
+					   int32_t part, int32_t parts_to_play);
+
+/*
+ * Play the specified title starting from the specified time.
+ *
+ * Currently unimplemented!
+ */
+dvdnav_status_t dvdnav_time_play(dvdnav_t *self, int32_t title,
+				 uint64_t time);
+
+/*
+ * Stop playing the current position and jump to the specified menu.
+ *
+ * See also DVDMenuID_t from libdvdread
+ */
+dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu);
+
+/*
+ * Return the title number and part currently being played.
+ * A title of 0 indicates, we are in a menu. In this case, part
+ * is set to the current menu's ID.
+ */
+dvdnav_status_t dvdnav_current_title_info(dvdnav_t *self, int32_t *title,
+					  int32_t *part);
+
+/*
+ * Return the current position (in blocks) within the current
+ * title and the length (in blocks) of said title.
+ * 
+ * Current implementation is wrong and likely to behave unpredictably!
+ * Use is discouraged!
+ */
+dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *self,
+					     uint32_t *pos,
+					     uint32_t *len);
+
+/*
+ * This function is only available for compatibility reasons.
+ *
+ * Stop playing the current position and start playback of the current title
+ * from the specified part.
+ */
+dvdnav_status_t dvdnav_part_search(dvdnav_t *self, int32_t part);
+
+
+/*********************************************************************
+ * program chain/program navigation                                  *
+ *********************************************************************/
+
+/*
+ * Stop playing the current position and start playback from the last
+ * VOBU boundary before the given sector. The sector number is not
+ * meant to be an absolute physical DVD sector, but a relative sector
+ * in the current program. This function cannot leave the current
+ * program and will fail, if asked to do so.
+ *
+ * If program chain based positioning is enabled
+ * (see dvdnav_set_PGC_positioning_flag()), this will seek to the relative
+ * sector inside the current program chain.
+ *
+ * 'origin' can be one of SEEK_SET, SEEK_CUR, SEEK_END as defined in
+ * fcntl.h.
+ */
+dvdnav_status_t dvdnav_sector_search(dvdnav_t *self, 
+				     uint64_t offset, int32_t origin);
+
+/*
+ returns the current stream time in PTS ticks as reported by the IFO structures
+ divide it by 90000 to get the current play time in seconds
+ */
+int64_t dvdnav_get_current_time(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback of the title
+ * from the specified timecode.
+ *
+ * Currently unimplemented!
+ */
+dvdnav_status_t dvdnav_time_search(dvdnav_t *self, 
+				   uint64_t time);
+
+/*
+ * Stop playing current position and play the "GoUp"-program chain.
+ * (which generally leads to the title menu or a higer-level menu).
+ */
+dvdnav_status_t dvdnav_go_up(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * previous program (if it exists).
+ */
+dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * first program.
+ */
+dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *self);
+
+/*
+ * Stop playing the current position and start playback at the
+ * next program (if it exists).
+ */
+dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self);
+
+/*
+ * Return the current position (in blocks) within the current
+ * program and the length (in blocks) of current program.
+ *
+ * If program chain based positioning is enabled
+ * (see dvdnav_set_PGC_positioning_flag()), this will return the
+ * relative position in and the length of the current program chain.
+ */
+dvdnav_status_t dvdnav_get_position(dvdnav_t *self, uint32_t *pos,
+				    uint32_t *len);
+
+
+/*********************************************************************
+ * menu highlights                                                   *
+ *********************************************************************/
+
+/*
+ * Most functions related to highlights take a NAV PCI packet as a parameter.
+ * While you can get the such a packet from libdvdnav, for players with internal
+ * FIFOs, this will result in errors, because due to the FIFO length, libdvdnav will
+ * be ahead in the stream compared to what the user is seeing on screen.
+ * Therefore, player applications who have a NAV packet available, which is
+ * better in sync with the actual playback should always pass this one to these
+ * functions.
+ */
+
+/*
+ * Get the currently highlighted button 
+ * number (1..36) or 0 if no button is highlighted.
+ */
+dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int32_t *button);
+
+/*
+ * Returns the Presentation Control Information (PCI) structure associated
+ * with the current position.
+ *
+ * Read the general notes above.
+ * See also libdvdreads nav_types.h for definition of pci_t.
+ */
+pci_t* dvdnav_get_current_nav_pci(dvdnav_t *self);
+
+/*
+ * Returns the DSI (data search information) structure associated
+ * with the current position.
+ *
+ * Read the general notes above.
+ * See also libdvdreads nav_types.h for definition of dsi_t.
+ */
+dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *self);
+
+/*
+ * Get the area associated with a certain button.
+ */
+dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode,
+					  dvdnav_highlight_area_t *highlight);
+
+/*
+ * Move button highlight around as suggested by function name (e.g. with arrow keys).
+ */
+dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self, pci_t *pci);
+dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self, pci_t *pci);
+
+/*
+ * Activate ("press") the currently highlighted button.
+ */
+dvdnav_status_t dvdnav_button_activate(dvdnav_t *self, pci_t *pci);
+
+/*
+ * Highlight a specific button.
+ */
+dvdnav_status_t dvdnav_button_select(dvdnav_t *self, pci_t *pci, int32_t button);
+
+/*
+ * Activate ("press") specified button.
+ */
+dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *self, pci_t *pci, int32_t button);
+
+/*
+ * Activate (press) a button and execute specified command.
+ */
+dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *self, int32_t button, vm_cmd_t *cmd);
+
+/*
+ * Select button at specified video frame coordinates.
+ */
+dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y);
+
+/*
+ * Activate ("press") button at specified video frame coordinates.
+ */
+dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y);
+
+
+/*********************************************************************
+ * languages                                                         *
+ *********************************************************************/
+
+/* 
+ * The language codes expected by these functions are two character
+ * codes as defined in ISO639.
+ */
+
+/*
+ * Set which menu language we should use per default.
+ */
+dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *self,
+					   char *code);
+
+/*
+ * Set which audio language we should use per default.
+ */
+dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *self,
+					    char *code);
+
+/*
+ * Set which spu language we should use per default.
+ */
+dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *self,
+					  char *code);
+
+
+/*********************************************************************
+ * obtaining stream attributes                                       *
+ *********************************************************************/
+
+/*
+ * Return a string describing the title of the DVD.
+ * This is an ID string encoded on the disc by the author. In many cases
+ * this is a descriptive string such as `THE_MATRIX' but sometimes is sigularly
+ * uninformative such as `PDVD-011421'. Some DVD authors even forget to set this,
+ * so you may also read the default of the authoring software they used, like
+ * `DVDVolume'.
+ */
+dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, const char **title_str);
+
+/*
+ * Get video aspect code.
+ * The aspect code does only change on VTS boundaries.
+ * See the DVDNAV_VTS_CHANGE event.
+ * 
+ * 0 -- 4:3, 2 -- 16:9
+ */
+uint8_t dvdnav_get_video_aspect(dvdnav_t *self);
+
+/*
+ * Get video scaling permissions.
+ * The scaling permission does only change on VTS boundaries.
+ * See the DVDNAV_VTS_CHANGE event.
+ *
+ * bit0 set = deny letterboxing, bit1 set = deny pan&scan
+ */
+uint8_t dvdnav_get_video_scale_permission(dvdnav_t *self);
+
+/*
+ * Converts a *logical* audio stream id into language code
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Returns the format of *logical* audio stream 'stream'
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_format(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Returns number of channelsn in *logical* audio stream 'stream'
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_audio_stream_channels(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Converts a *logical* subpicture stream id into country code 
+ * (returns 0xffff if no such stream).
+ */
+uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream);
+
+/*
+ * Converts a *physical* (MPEG) audio stream id into a logical stream number.
+ */
+int8_t dvdnav_get_audio_logical_stream(dvdnav_t *self, uint8_t audio_num);
+
+#define HAVE_GET_AUDIO_ATTR
+/*
+ * Get audio attr
+ */
+dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *self, uint8_t audio_mum, audio_attr_t *audio_attr);
+
+/*
+ * Converts a *physical* (MPEG) subpicture stream id into a logical stream number.
+ */
+int8_t dvdnav_get_spu_logical_stream(dvdnav_t *self, uint8_t subp_num);
+
+#define HAVE_GET_SPU_ATTR
+/*
+ * Get spu attr
+ */
+dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *self, uint8_t audio_mum, subp_attr_t *subp_attr);
+
+/*
+ * Get active audio stream.
+ */
+int8_t dvdnav_get_active_audio_stream(dvdnav_t *self);
+
+/*
+ * Get active spu stream.
+ */
+int8_t dvdnav_get_active_spu_stream(dvdnav_t *self);
+
+/* 
+ * Get the set of user operations that are currently prohibited.
+ * There are potentially new restrictions right after 
+ * DVDNAV_CHANNEL_HOP and DVDNAV_NAV_PACKET.
+ */
+user_ops_t dvdnav_get_restrictions(dvdnav_t *self);
+
+
+/*********************************************************************
+ * multiple angles                                                   *
+ *********************************************************************/
+
+/*
+ * The libdvdnav library abstracts away the difference between seamless and
+ * non-seamless angles. From the point of view of the programmer you just set the
+ * angle number and all is well in the world. You will always see only the
+ * selected angle coming from the get_next_block functions.
+ *
+ * Note:
+ * It is quite possible that some tremendously strange DVD feature might change the
+ * angle number from under you. Generally you should always view the results from
+ * dvdnav_get_angle_info() as definitive only up to the next time you call
+ * dvdnav_get_next_block().
+ */
+
+/*
+ * Sets the current angle. If you try to follow a non existant angle
+ * the call fails.
+ */
+dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int32_t angle);
+
+/*
+ * Returns the current angle and number of angles present.
+ */
+dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *self, int32_t *current_angle,
+				      int32_t *number_of_angles);
+
+/*********************************************************************
+ * domain queries                                                    *
+ *********************************************************************/
+
+/*
+ * Are we in the First Play domain?
+ */
+int8_t dvdnav_is_domain_fp(dvdnav_t *self);
+
+/*
+ * Are we in the Video management Menu domain?
+ */
+int8_t dvdnav_is_domain_vmgm(dvdnav_t *self);
+
+/*
+ * Are we in the Video Title Menu domain?
+ */
+int8_t dvdnav_is_domain_vtsm(dvdnav_t *self);
+
+/*
+ * Are we in the Video Title Set domain?
+ */
+int8_t dvdnav_is_domain_vts(dvdnav_t *self);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DVDNAV_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav_events.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,239 @@
+/* 
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+/*
+ * This header defines events and event types 
+ */
+
+#ifndef DVDNAV_EVENTS_H_INCLUDED
+#define DVDNAV_EVENTS_H_INCLUDED
+
+/*
+ * DVDNAV_BLOCK_OK
+ *
+ * A regular data block from the DVD has been returned.
+ * This one should be demuxed and decoded for playback.
+ */
+#define DVDNAV_BLOCK_OK			 0
+
+
+/*
+ * DVDNAV_NOP
+ *
+ * Just ignore this.
+ */
+#define DVDNAV_NOP			 1
+
+
+/*
+ * DVDNAV_STILL_FRAME
+ *
+ * We have reached a still frame. The player application should wait
+ * the amount of time specified by the still's length while still handling
+ * user input to make menus and other interactive stills work.
+ * The last delivered frame should be kept showing.
+ * Once the still has timed out, call dvdnav_skip_still().
+ * A length of 0xff means an infinite still which has to be skipped
+ * indirectly by some user interaction.
+ */
+#define DVDNAV_STILL_FRAME		 2
+
+typedef struct {
+  /* The length (in seconds) the still frame should be displayed for,
+   * or 0xff if infinite. */
+  int length;
+} dvdnav_still_event_t;
+
+
+/*
+ * DVDNAV_SPU_STREAM_CHANGE
+ *
+ * Inform the SPU decoding/overlaying engine to switch SPU channels.
+ */
+#define DVDNAV_SPU_STREAM_CHANGE	 3
+
+typedef struct {
+  /* The physical (MPEG) stream number for widescreen SPU display.
+   * Use this, if you blend the SPU on an anamorphic image before
+   * unsqueezing it. */
+  int physical_wide;
+
+  /* The physical (MPEG) stream number for letterboxed display.
+   * Use this, if you blend the SPU on an anamorphic image after
+   * unsqueezing it. */
+  int physical_letterbox;
+
+  /* The physical (MPEG) stream number for pan&scan display.
+   * Use this, if you blend the SPU on an anamorphic image after
+   * unsqueezing it the pan&scan way. */
+  int physical_pan_scan;
+  
+  /* The logical (DVD) stream number. */
+  int logical;
+} dvdnav_spu_stream_change_event_t;
+
+
+/*
+ * DVDNAV_AUDIO_STREAM_CHANGE
+ *
+ * Inform the audio decoder to switch channels.
+ */
+#define DVDNAV_AUDIO_STREAM_CHANGE	 4
+
+typedef struct {
+  /* The physical (MPEG) stream number. */
+  int physical;
+
+  /* The logical (DVD) stream number. */
+  int logical;
+} dvdnav_audio_stream_change_event_t;
+
+
+/*
+ * DVDNAV_VTS_CHANGE
+ *
+ * Some status information like video aspect and video scale permissions do
+ * not change inside a VTS. Therefore this event can be used to query such
+ * information only when necessary and update the decoding/displaying
+ * accordingly.
+ */
+#define DVDNAV_VTS_CHANGE		 5
+
+typedef struct {
+  int old_vtsN;                 /* the old VTS number */
+  dvd_read_domain_t old_domain; /* the old domain */
+  int new_vtsN;                 /* the new VTS number */
+  dvd_read_domain_t new_domain; /* the new domain */
+} dvdnav_vts_change_event_t;
+
+
+/*
+ * DVDNAV_CELL_CHANGE
+ *
+ * Some status information like the current Title and Part numbers do not
+ * change inside a cell. Therefore this event can be used to query such
+ * information only when necessary and update the decoding/displaying
+ * accordingly.
+ * Some useful information for accurate time display is also reported
+ * together with this event.
+ */
+#define DVDNAV_CELL_CHANGE		 6
+
+typedef struct {
+  int     cellN;       /* the new cell number */
+  int     pgN;         /* the current program number */
+  int64_t cell_length; /* the length of the current cell in PTS ticks */
+  int64_t pg_length;   /* the length of the current program in PTS ticks */
+  int64_t pgc_length;  /* the length of the current program chain in PTS ticks */
+  int64_t cell_start;  /* the start time of the current cell relatively to the PGC in PTS ticks */
+  int64_t pg_start;    /* the start time of the current PG relatively to the PGC in PTS ticks */
+} dvdnav_cell_change_event_t;
+
+
+/*
+ * DVDNAV_NAV_PACKET
+ *
+ * NAV packets are useful for various purposes. They define the button
+ * highlight areas and VM commands of DVD menus, so they should in any
+ * case be sent to the SPU decoder/overlaying engine for the menus to work.
+ * NAV packets also provide a way to detect PTS discontinuities, because
+ * they carry the start and end PTS values for the current VOBU.
+ * (pci.vobu_s_ptm and pci.vobu_e_ptm) Whenever the start PTS of the
+ * current NAV does not match the end PTS of the previous NAV, a PTS
+ * discontinuity has occured.
+ * NAV packets can also be used for time display, because they are
+ * timestamped relatively to the current Cell.
+ */
+#define DVDNAV_NAV_PACKET		 7
+
+
+/*
+ * DVDNAV_STOP
+ *
+ * Applications should end playback here. A subsequent dvdnav_get_next_block()
+ * call will restart the VM from the beginning of the DVD.
+ */
+#define DVDNAV_STOP			 8
+
+
+/*
+ * DVDNAV_HIGHLIGHT
+ *
+ * The current button highlight changed. Inform the overlaying engine to
+ * highlight a different button. Please note, that at the moment only mode 1
+ * highlights are reported this way. That means, when the button highlight
+ * has been moved around by some function call, you will receive an event
+ * telling you the new button. But when a button gets activated, you have
+ * to handle the mode 2 highlighting (that is some different colour the
+ * button turns to on activation) in your application.
+ */
+#define DVDNAV_HIGHLIGHT		 9
+
+typedef struct {
+  /* highlight mode: 0 - hide, 1 - show, 2 - activate, currently always 1 */
+  int display;
+
+  /* FIXME: these fields are currently not set */
+  uint32_t palette;     /* The CLUT entries for the highlight palette 
+			   (4-bits per entry -> 4 entries) */
+  uint16_t sx,sy,ex,ey; /* The start/end x,y positions */
+  uint32_t pts;         /* Highlight PTS to match with SPU */
+
+  /* button number for the SPU decoder/overlaying engine */
+  uint32_t buttonN;
+} dvdnav_highlight_event_t;
+
+
+/*
+ * DVDNAV_SPU_CLUT_CHANGE
+ *
+ * Inform the SPU decoder/overlaying engine to update its colour lookup table.
+ * The CLUT is given as 16 uint32_t's in the buffer.
+ */
+#define DVDNAV_SPU_CLUT_CHANGE		10
+
+
+/*
+ * DVDNAV_HOP_CHANNEL
+ *
+ * A non-seamless operation has been performed. Applications can drop all
+ * their internal fifo's content, which will speed up the response.
+ */
+#define DVDNAV_HOP_CHANNEL		12
+
+
+/*
+ * DVDNAV_WAIT
+ *
+ * We have reached a point in DVD playback, where timing is critical.
+ * Player application with internal fifos can introduce state
+ * inconsistencies, because libdvdnav is always the fifo's length
+ * ahead in the stream compared to what the application sees.
+ * Such applications should wait until their fifos are empty
+ * when they receive this type of event.
+ * Once this is achieved, call dvdnav_skip_wait().
+ */
+#define DVDNAV_WAIT			13
+
+
+#endif /* DVDNAV_EVENTS_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dvdnav_internal.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,195 @@
+/* 
+ * Copyright (C) 2001-2004 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifndef DVDNAV_INTERNAL_H_INCLUDED
+#define DVDNAV_INTERNAL_H_INCLUDED
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef WIN32
+
+/* pthread_mutex_* wrapper for win32 */
+#include <windows.h>
+#include <process.h>
+typedef CRITICAL_SECTION pthread_mutex_t;
+#define pthread_mutex_init(a, b) InitializeCriticalSection(a)
+#define pthread_mutex_lock(a)    EnterCriticalSection(a)
+#define pthread_mutex_unlock(a)  LeaveCriticalSection(a)
+#define pthread_mutex_destroy(a)
+
+/* replacement gettimeofday implementation */
+#include <sys/timeb.h>
+static inline int _private_gettimeofday( struct timeval *tv, void *tz )
+{
+  struct timeb t;
+  ftime( &t );
+  tv->tv_sec = t.time;
+  tv->tv_usec = t.millitm * 1000;
+  return 0;
+}
+#define gettimeofday(TV, TZ) _private_gettimeofday((TV), (TZ))
+#include <io.h> /* read() */
+#define lseek64 _lseeki64
+
+#else
+
+#include <pthread.h>
+
+#endif /* WIN32 */
+
+/* where should libdvdnav write its messages (stdout/stderr) */
+#define MSG_OUT stdout
+
+/* Maximum length of an error string */
+#define MAX_ERR_LEN 255
+
+/* Use the POSIX PATH_MAX if available */
+#ifdef PATH_MAX
+#define MAX_PATH_LEN PATH_MAX
+#else
+#define MAX_PATH_LEN 255 /* Arbitrary */
+#endif
+
+#ifndef DVD_VIDEO_LB_LEN
+#define DVD_VIDEO_LB_LEN 2048
+#endif
+
+typedef struct read_cache_s read_cache_t;
+
+/*
+ * These are defined here because they are
+ * not in ifo_types.h, they maybe one day 
+ */
+
+#ifndef audio_status_t
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int available     : 1;
+  unsigned int zero1         : 4;
+  unsigned int stream_number : 3;
+  uint8_t zero2;
+#else
+  uint8_t zero2;
+  unsigned int stream_number : 3;
+  unsigned int zero1         : 4;  
+  unsigned int available     : 1;
+#endif
+} ATTRIBUTE_PACKED audio_status_t;
+#endif
+
+#ifndef spu_status_t
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+  unsigned int available               : 1;
+  unsigned int zero1                   : 2;
+  unsigned int stream_number_4_3       : 5;
+  unsigned int zero2                   : 3;
+  unsigned int stream_number_wide      : 5;
+  unsigned int zero3                   : 3;
+  unsigned int stream_number_letterbox : 5;
+  unsigned int zero4                   : 3;
+  unsigned int stream_number_pan_scan  : 5;
+#else
+  unsigned int stream_number_pan_scan  : 5;
+  unsigned int zero4                   : 3;
+  unsigned int stream_number_letterbox : 5;
+  unsigned int zero3                   : 3;
+  unsigned int stream_number_wide      : 5;
+  unsigned int zero2                   : 3;
+  unsigned int stream_number_4_3       : 5;
+  unsigned int zero1                   : 2;
+  unsigned int available               : 1;
+#endif
+} ATTRIBUTE_PACKED spu_status_t;
+#endif
+
+typedef struct dvdnav_vobu_s {
+  int32_t vobu_start;  /* Logical Absolute. MAX needed is 0x300000 */
+  int32_t vobu_length;
+  int32_t blockN;      /* Relative offset */
+  int32_t vobu_next;   /* Relative offset */
+} dvdnav_vobu_t;  
+   
+/** The main DVDNAV type **/
+
+struct dvdnav_s {
+  /* General data */
+  char        path[MAX_PATH_LEN]; /* Path to DVD device/dir */
+  dvd_file_t *file;               /* Currently opened file */
+ 
+  /* Position data */
+  vm_position_t position_next;
+  vm_position_t position_current;
+  dvdnav_vobu_t vobu;  
+
+  /* NAV data */
+  pci_t pci;
+  dsi_t dsi;
+  uint32_t last_cmd_nav_lbn;      /* detects when a command is issued on an already left NAV */
+  
+  /* Flags */
+  int skip_still;                 /* Set when skipping a still */
+  int sync_wait;                  /* applications should wait till they are in sync with us */
+  int sync_wait_skip;             /* Set when skipping wait state */
+  int spu_clut_changed;           /* The SPU CLUT changed */ 
+  int started;                    /* vm_start has been called? */
+  int use_read_ahead;             /* 1 - use read-ahead cache, 0 - don't */
+  int pgc_based;                  /* positioning works PGC based instead of PG based */
+  int cur_cell_time;              /* time expired since the beginning of the current cell, read from the dsi */
+  
+  /* VM */
+  vm_t *vm;
+  pthread_mutex_t vm_lock;
+
+  /* Read-ahead cache */
+  read_cache_t *cache;
+
+  /* Errors */
+  char err_str[MAX_ERR_LEN];
+};
+
+/** HELPER FUNCTIONS **/
+
+/* converts a dvd_time_t to PTS ticks */
+int64_t dvdnav_convert_time(dvd_time_t *time);
+
+/** USEFUL MACROS **/
+
+#ifdef __GNUC__
+#define printerrf(format, args...) \
+	do { if (this) snprintf(this->err_str, MAX_ERR_LEN, format, ## args); } while (0)
+#else
+#ifdef _MSC_VER
+#define printerrf(str) \
+	do { if (this) snprintf(this->err_str, MAX_ERR_LEN, str); } while (0)
+#else
+#define printerrf(...) \
+	do { if (this) snprintf(this->err_str, MAX_ERR_LEN, __VA_ARGS__); } while (0)
+#endif /* WIN32 */
+#endif
+#define printerr(str) \
+	do { if (this) strncpy(this->err_str, str, MAX_ERR_LEN - 1); } while (0)
+
+#endif /* DVDNAV_INTERNAL_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/highlight.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,497 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/time.h>
+#include "nav_types.h"
+#include "dvd_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "vm/vmcmd.h"
+#include "dvdnav_internal.h"
+#include "dvdnav.h"
+
+/*
+#define BUTTON_TESTING
+*/
+
+#ifdef BUTTON_TESTING
+
+#include "nav_print.h"
+
+static void print_time(dvd_time_t *dtime) {
+  const char *rate;
+
+  assert((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa);
+  assert((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa);
+  assert((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa);
+  assert((dtime->frame_u&0xf) < 0xa);
+
+  fprintf(MSG_OUT,"%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;
+  }
+  fprintf(MSG_OUT," @ %s fps", rate);
+}
+
+static void nav_print_PCI_GI(pci_gi_t *pci_gi) {
+  int32_t i;
+
+  fprintf(MSG_OUT,"libdvdnav: pci_gi:\n");
+  fprintf(MSG_OUT,"libdvdnav: nv_pck_lbn    0x%08x\n", pci_gi->nv_pck_lbn);
+  fprintf(MSG_OUT,"libdvdnav: vobu_cat      0x%04x\n", pci_gi->vobu_cat);
+  fprintf(MSG_OUT,"libdvdnav: vobu_uop_ctl  0x%08x\n", *(uint32_t*)&pci_gi->vobu_uop_ctl);
+  fprintf(MSG_OUT,"libdvdnav: vobu_s_ptm    0x%08x\n", pci_gi->vobu_s_ptm);
+  fprintf(MSG_OUT,"libdvdnav: vobu_e_ptm    0x%08x\n", pci_gi->vobu_e_ptm);
+  fprintf(MSG_OUT,"libdvdnav: vobu_se_e_ptm 0x%08x\n", pci_gi->vobu_se_e_ptm);
+  fprintf(MSG_OUT,"libdvdnav: e_eltm        ");
+  print_time(&pci_gi->e_eltm);
+  fprintf(MSG_OUT,"\n");
+
+  fprintf(MSG_OUT,"libdvdnav: vobu_isrc     \"");
+  for(i = 0; i < 32; i++) {
+    char c = pci_gi->vobu_isrc[i];
+    if((c >= ' ') && (c <= '~'))
+      fprintf(MSG_OUT,"%c", c);
+    else
+      fprintf(MSG_OUT,".");
+  }
+  fprintf(MSG_OUT,"\"\n");
+}
+
+static void nav_print_NSML_AGLI(nsml_agli_t *nsml_agli) {
+  int32_t i, j = 0;
+
+  for(i = 0; i < 9; i++)
+    j |= nsml_agli->nsml_agl_dsta[i];
+  if(j == 0)
+    return;
+
+  fprintf(MSG_OUT,"libdvdnav: nsml_agli:\n");
+  for(i = 0; i < 9; i++)
+    if(nsml_agli->nsml_agl_dsta[i])
+      fprintf(MSG_OUT,"libdvdnav: nsml_agl_c%d_dsta  0x%08x\n", i + 1,
+             nsml_agli->nsml_agl_dsta[i]);
+}
+
+static void nav_print_HL_GI(hl_gi_t *hl_gi, int32_t *btngr_ns, int32_t *btn_ns) {
+
+  if((hl_gi->hli_ss & 0x03) == 0)
+    return;
+
+  fprintf(MSG_OUT,"libdvdnav: hl_gi:\n");
+  fprintf(MSG_OUT,"libdvdnav: hli_ss        0x%01x\n", hl_gi->hli_ss & 0x03);
+  fprintf(MSG_OUT,"libdvdnav: hli_s_ptm     0x%08x\n", hl_gi->hli_s_ptm);
+  fprintf(MSG_OUT,"libdvdnav: hli_e_ptm     0x%08x\n", hl_gi->hli_e_ptm);
+  fprintf(MSG_OUT,"libdvdnav: btn_se_e_ptm  0x%08x\n", hl_gi->btn_se_e_ptm);
+
+  *btngr_ns = hl_gi->btngr_ns;
+  fprintf(MSG_OUT,"libdvdnav: btngr_ns      %d\n",  hl_gi->btngr_ns);
+  fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 1, hl_gi->btngr1_dsp_ty);
+  fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 2, hl_gi->btngr2_dsp_ty);
+  fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 3, hl_gi->btngr3_dsp_ty);
+
+  fprintf(MSG_OUT,"libdvdnav: btn_ofn       %d\n", hl_gi->btn_ofn);
+  *btn_ns = hl_gi->btn_ns;
+  fprintf(MSG_OUT,"libdvdnav: btn_ns        %d\n", hl_gi->btn_ns);
+  fprintf(MSG_OUT,"libdvdnav: nsl_btn_ns    %d\n", hl_gi->nsl_btn_ns);
+  fprintf(MSG_OUT,"libdvdnav: fosl_btnn     %d\n", hl_gi->fosl_btnn);
+  fprintf(MSG_OUT,"libdvdnav: foac_btnn     %d\n", hl_gi->foac_btnn);
+}
+
+static void nav_print_BTN_COLIT(btn_colit_t *btn_colit) {
+  int32_t i, j;
+
+  j = 0;
+  for(i = 0; i < 6; i++)
+    j |= btn_colit->btn_coli[i/2][i&1];
+  if(j == 0)
+    return;
+
+  fprintf(MSG_OUT,"libdvdnav: btn_colit:\n");
+  for(i = 0; i < 3; i++)
+    for(j = 0; j < 2; j++)
+      fprintf(MSG_OUT,"libdvdnav: btn_cqoli %d  %s_coli:  %08x\n",
+             i, (j == 0) ? "sl" : "ac",
+             btn_colit->btn_coli[i][j]);
+}
+
+static void nav_print_BTNIT(btni_t *btni_table, int32_t btngr_ns, int32_t btn_ns) {
+  int32_t i, j, k;
+
+  fprintf(MSG_OUT,"libdvdnav: btnit:\n");
+  fprintf(MSG_OUT,"libdvdnav: btngr_ns: %i\n", btngr_ns);
+  fprintf(MSG_OUT,"libdvdnav: 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];
+
+        fprintf(MSG_OUT,"libdvdnav: group %d btni %d:  ", i+1, j+1);
+        fprintf(MSG_OUT,"btn_coln %d, auto_action_mode %d\n",
+               btni->btn_coln, btni->auto_action_mode);
+        fprintf(MSG_OUT,"libdvdnav: coords   (%d, %d) .. (%d, %d)\n",
+               btni->x_start, btni->y_start, btni->x_end, btni->y_end);
+
+        fprintf(MSG_OUT,"libdvdnav: up %d, ", btni->up);
+        fprintf(MSG_OUT,"down %d, ", btni->down);
+        fprintf(MSG_OUT,"left %d, ", btni->left);
+        fprintf(MSG_OUT,"right %d\n", btni->right);
+        for(k = 0; k < 8; k++) {
+          fprintf(MSG_OUT, "libdvdnav: %02x ", btni->cmd.bytes[k]);
+        }
+        fprintf(MSG_OUT, "| ");
+#ifdef TRACE
+        vm_print_mnemonic(&btni->cmd);
+#endif
+        fprintf(MSG_OUT, "\n");
+      }
+    }
+  }
+}
+
+static void nav_print_HLI(hli_t *hli) {
+  int32_t btngr_ns = 0, btn_ns = 0;
+
+  fprintf(MSG_OUT,"libdvdnav: hli:\n");
+  nav_print_HL_GI(&hli->hl_gi, & btngr_ns, & btn_ns);
+  nav_print_BTN_COLIT(&hli->btn_colit);
+  nav_print_BTNIT(hli->btnit, btngr_ns, btn_ns);
+}
+
+void nav_print_PCI(pci_t *pci) {
+  fprintf(MSG_OUT,"libdvdnav: pci packet:\n");
+  nav_print_PCI_GI(&pci->pci_gi);
+  nav_print_NSML_AGLI(&pci->nsml_agli);
+  nav_print_HLI(&pci->hli);
+}
+
+#endif
+
+
+/* Highlighting API calls */
+
+dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *this, int32_t *button) {
+  /* Simply return the appropriate value based on the SPRM */
+  if(((*button) = this->position_current.button) == -1)
+    (*button) = this->vm->state.HL_BTNN_REG >> 10;
+  
+  return DVDNAV_STATUS_OK;
+}
+
+static btni_t *get_current_button(dvdnav_t *this, pci_t *pci) {
+  int32_t button = 0;
+
+  if(!pci->hli.hl_gi.hli_ss) {
+    printerr("Not in a menu.");
+    return NULL;
+  }
+  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) {
+    printerr("This NAV has already been left.");
+    return NULL;
+  }
+
+  button = this->vm->state.HL_BTNN_REG >> 10;
+#ifdef BUTTON_TESTING
+  nav_print_PCI(pci);
+#endif
+  
+  return &(pci->hli.btnit[button-1]);
+}
+
+static dvdnav_status_t button_auto_action(dvdnav_t *this, pci_t *pci) {
+  if (get_current_button(this, pci)->auto_action_mode)
+    return dvdnav_button_activate(this, pci);
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *this, pci_t *pci) {
+  btni_t *button_ptr;
+  
+  if(!(button_ptr = get_current_button(this, pci)))
+    return DVDNAV_STATUS_ERR;
+
+  dvdnav_button_select(this, pci, button_ptr->up);
+  return button_auto_action(this, pci);
+}
+
+dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *this, pci_t *pci) {
+  btni_t *button_ptr;
+  
+  if(!(button_ptr = get_current_button(this, pci)))
+    return DVDNAV_STATUS_ERR;
+
+  dvdnav_button_select(this, pci, button_ptr->down);
+  return button_auto_action(this, pci);
+}
+
+dvdnav_status_t dvdnav_right_button_select(dvdnav_t *this, pci_t *pci) {
+  btni_t *button_ptr;
+  
+  if(!(button_ptr = get_current_button(this, pci)))
+    return DVDNAV_STATUS_ERR;
+
+  dvdnav_button_select(this, pci, button_ptr->right);
+  return button_auto_action(this, pci);
+}
+
+dvdnav_status_t dvdnav_left_button_select(dvdnav_t *this, pci_t *pci) {
+  btni_t *button_ptr;
+  
+  if(!(button_ptr = get_current_button(this, pci)))
+    return DVDNAV_STATUS_ERR;
+
+  dvdnav_button_select(this, pci, button_ptr->left);
+  return button_auto_action(this, pci);
+}
+
+dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode, 
+					  dvdnav_highlight_area_t *highlight) {
+  btni_t *button_ptr;
+
+#ifdef BUTTON_TESTING
+  fprintf(MSG_OUT, "libdvdnav: Button get_highlight_area %i\n", button);
+#endif
+  
+  if(!nav_pci->hli.hl_gi.hli_ss)
+    return DVDNAV_STATUS_ERR;
+  if((button <= 0) || (button > nav_pci->hli.hl_gi.btn_ns))
+    return DVDNAV_STATUS_ERR;
+
+
+  button_ptr = &nav_pci->hli.btnit[button-1];
+
+  highlight->sx = button_ptr->x_start;
+  highlight->sy = button_ptr->y_start;
+  highlight->ex = button_ptr->x_end;
+  highlight->ey = button_ptr->y_end;
+  if(button_ptr->btn_coln != 0) {
+    highlight->palette = nav_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode];
+  } else {
+    highlight->palette = 0;
+  }
+  highlight->pts = nav_pci->hli.hl_gi.hli_s_ptm;
+  highlight->buttonN = button;
+#ifdef BUTTON_TESTING
+  fprintf(MSG_OUT, "libdvdnav: highlight: Highlight area is (%u,%u)-(%u,%u), display = %i, button = %u\n",
+               button_ptr->x_start, button_ptr->y_start,
+               button_ptr->x_end, button_ptr->y_end,
+               1,
+               button);
+#endif
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_button_activate(dvdnav_t *this, pci_t *pci) {
+  int32_t button;
+  btni_t *button_ptr = NULL;
+
+  if(!pci->hli.hl_gi.hli_ss) {
+    printerr("Not in a menu.");
+    return DVDNAV_STATUS_ERR;
+  }
+  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) {
+    printerr("This NAV has already been left.");
+    return DVDNAV_STATUS_ERR;
+  }
+  pthread_mutex_lock(&this->vm_lock); 
+
+  button = this->vm->state.HL_BTNN_REG >> 10;
+
+  if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) {
+    /* Special code to handle still menus with no buttons.
+     * The navigation is expected to report to the application that a STILL is
+     * underway. In turn, the application is supposed to report to the user
+     * that the playback is paused. The user is then expected to undo the pause,
+     * ie: hit play. At that point, the navigation should release the still and
+     * go to the next Cell.
+     * Explanation by Mathieu Lacage <mathieu_lacage@realmagic.fr>
+     * Code added by jcdutton.
+     */
+    if (this->position_current.still != 0) {
+      /* In still, but no buttons. */
+      vm_get_next_cell(this->vm);
+      this->position_current.still = 0;
+      this->sync_wait = 0;
+      this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn;
+      pthread_mutex_unlock(&this->vm_lock);
+      /* clear error message */
+      printerr("");
+      return DVDNAV_STATUS_OK;
+    }
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  button_ptr = get_current_button(this, pci);
+  /* Finally, make the VM execute the appropriate code and probably
+   * scedule a jump */
+#ifdef BUTTON_TESTING
+  fprintf(MSG_OUT, "libdvdnav: Evaluating Button Activation commands.\n");
+#endif
+  if(vm_exec_cmd(this->vm, &(button_ptr->cmd)) == 1) {
+    /* Command caused a jump */
+    this->vm->hop_channel++;
+    this->position_current.still = 0;
+    this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn;
+  }
+  
+  pthread_mutex_unlock(&this->vm_lock); 
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *this, int32_t button, vm_cmd_t *cmd)
+{
+  pthread_mutex_lock(&this->vm_lock);
+  /* make the VM execute the appropriate code and probably
+   * schedule a jump */
+#ifdef BUTTON_TESTING
+  fprintf(MSG_OUT, "libdvdnav: dvdnav_button_activate_cmd: Evaluating Button Activation commands.\n");
+#endif
+  if(button > 0) {
+    this->vm->state.HL_BTNN_REG = (button << 10);
+    if(vm_exec_cmd(this->vm, cmd) == 1) {
+      /* Command caused a jump */
+      this->vm->hop_channel++;
+    }
+  }
+  /* Always remove still, because some still menus have no buttons. */
+  this->position_current.still = 0;
+  this->sync_wait = 0;
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}  
+
+dvdnav_status_t dvdnav_button_select(dvdnav_t *this, pci_t *pci, int32_t button) {
+  if(!pci->hli.hl_gi.hli_ss) {
+    printerr("Not in a menu.");
+    return DVDNAV_STATUS_ERR;
+  }
+  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) {
+    printerr("This NAV has already been left.");
+    return DVDNAV_STATUS_ERR;
+  }
+ 
+#ifdef BUTTON_TESTING
+  fprintf(MSG_OUT, "libdvdnav: Button select %i\n", button); 
+#endif
+  
+  if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) {
+    printerr("Button does not exist.");
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  this->vm->state.HL_BTNN_REG = (button << 10);
+  this->position_current.button = -1; /* Force Highligh change */
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *this, pci_t *pci, 
+						  int32_t button) {
+  /* A trivial function */
+  if(dvdnav_button_select(this, pci, button) != DVDNAV_STATUS_ERR)
+    return dvdnav_button_activate(this, pci);
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_mouse_select(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) {
+  int32_t button, cur_button;
+  int32_t best,dist,d;
+  int32_t mx,my,dx,dy;
+
+  if(!pci->hli.hl_gi.hli_ss) {
+    printerr("Not in a menu.");
+    return DVDNAV_STATUS_ERR;
+  }
+  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) {
+    printerr("This NAV has already been left.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  cur_button = this->vm->state.HL_BTNN_REG >> 10;
+
+  best = 0;
+  dist = 0x08000000; /* >> than  (720*720)+(567*567); */
+  
+  /* Loop through all buttons */
+  for(button = 1; button <= pci->hli.hl_gi.btn_ns; button++) {
+    btni_t *button_ptr = &(pci->hli.btnit[button-1]);
+
+    if((x >= button_ptr->x_start) && (x <= button_ptr->x_end) &&
+       (y >= button_ptr->y_start) && (y <= button_ptr->y_end)) {
+      mx = (button_ptr->x_start + button_ptr->x_end)/2;
+      my = (button_ptr->y_start + button_ptr->y_end)/2;
+      dx = mx - x;
+      dy = my - y;
+      d = (dx*dx) + (dy*dy);
+      /* If the mouse is within the button and the mouse is closer
+       * to the center of this button then it is the best choice. */
+      if(d < dist) {
+        dist = d;
+        best = button;
+      }
+    }
+  }
+  /* As an efficiency measure, only re-select the button
+   * if it is different to the previously selected one. */
+  if (best != 0 && best != cur_button)
+    dvdnav_button_select(this, pci, best);
+
+  /* return DVDNAV_STATUS_OK only if we actually found a matching button */
+  return best ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) {
+  /* A trivial function */
+  if(dvdnav_mouse_select(this, pci, x,y) != DVDNAV_STATUS_ERR)
+    return dvdnav_button_activate(this, pci);
+  return DVDNAV_STATUS_ERR;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/Makefile.am	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,19 @@
+include $(top_srcdir)/misc/Makefile.common
+
+includedir = ${prefix}/include/libdvdread
+
+AM_CPPFLAGS = -DDVDNAV_COMPILE -I$(top_srcdir)/src/vm
+
+lib_LTLIBRARIES = libdvdread.la
+
+libdvdread_la_SOURCES = dvd_reader.c nav_read.c ifo_read.c \
+	dvd_input.c dvd_udf.c md5.c nav_print.c ifo_print.c bitreader.c \
+	bswap.h dvd_input.h dvdread_internal.h dvd_udf.h md5.h bitreader.h
+
+libdvdread_la_LIBADD = $(DYNAMIC_LD_LIBS)
+
+libdvdread_la_LDFLAGS = -version-info $(DVDNAV_LT_CURRENT):$(DVDNAV_LT_REVISION):$(DVDNAV_LT_AGE) \
+	-export-symbols-regex "(^dvd.*|^nav.*|^ifo.*|^DVD.*|^UDF.*)"
+
+include_HEADERS = dvd_reader.h nav_read.h ifo_read.h \
+	nav_print.h ifo_print.h ifo_types.h nav_types.h dvd_udf.h bitreader.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/bitreader.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,116 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "bitreader.h"
+
+int dvdread_getbits_init(getbits_state_t *state, uint8_t *start) {
+  if ((state == NULL) || (start == NULL)) return 0;
+  state->start = start;
+  state->bit_position = 0;
+  state->byte_position = 0;
+  state->byte = start[0];
+  return 1;
+}
+
+/* Non-optimized getbits. */
+/* This can easily be optimized for particular platforms. */
+uint32_t dvdread_getbits(getbits_state_t *state, uint32_t number_of_bits) {
+  uint32_t result=0;
+  uint8_t byte=0;
+  if (number_of_bits > 32) {
+    printf("Number of bits > 32 in getbits\n");
+    abort();
+  }
+
+  if ((state->bit_position) > 0) {  /* Last getbits left us in the middle of a byte. */
+    if (number_of_bits > (8-state->bit_position)) { /* this getbits will span 2 or more bytes. */
+      byte = state->byte;
+      byte = byte >> (state->bit_position);
+      result = byte;
+      number_of_bits -= (8-state->bit_position);
+      state->bit_position = 0;
+      state->byte_position++;
+      state->byte = state->start[state->byte_position];
+    } else {
+      byte=state->byte;
+      state->byte = state->byte << number_of_bits;
+      byte = byte >> (8 - number_of_bits);
+      result = byte;
+      state->bit_position += number_of_bits; /* Here it is impossible for bit_position > 8 */
+      if (state->bit_position == 8) {
+        state->bit_position = 0;
+        state->byte_position++;
+        state->byte = state->start[state->byte_position];
+      }
+      number_of_bits = 0;
+    }
+  }
+  if ((state->bit_position) == 0) {
+    while (number_of_bits > 7) {
+      result = (result << 8) + state->byte;
+      state->byte_position++;
+      state->byte = state->start[state->byte_position];
+      number_of_bits -= 8;
+    }
+    if (number_of_bits > 0) { /* number_of_bits < 8 */
+      byte = state->byte;
+      state->byte = state->byte << number_of_bits;
+      state->bit_position += number_of_bits; /* Here it is impossible for bit_position > 7 */
+      byte = byte >> (8 - number_of_bits);
+      result = (result << number_of_bits) + byte;
+      number_of_bits = 0;
+    }
+  }
+
+  return result;
+}
+
+#if 0  /* TODO: optimized versions not yet used */
+
+/* WARNING: This function can only be used on a byte boundary.
+            No checks are made that we are in fact on a byte boundary.
+ */
+uint16_t dvdread_get16bits(getbits_state_t *state) {
+  uint16_t result;
+  state->byte_position++;
+  result = (state->byte << 8) + state->start[state->byte_position++];
+  state->byte = state->start[state->byte_position];
+  return result;
+}
+
+/* WARNING: This function can only be used on a byte boundary.
+            No checks are made that we are in fact on a byte boundary.
+ */
+uint32_t dvdread_get32bits(getbits_state_t *state) {
+  uint32_t result;
+  state->byte_position++;
+  result = (state->byte << 8) + state->start[state->byte_position++];
+  result = (result << 8) + state->start[state->byte_position++];
+  result = (result << 8) + state->start[state->byte_position++];
+  state->byte = state->start[state->byte_position];
+  return result;
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/bitreader.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,40 @@
+#ifndef BITREADER_H_INCLUDED
+#define BITREADER_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
+ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+  uint8_t *start;
+  uint32_t byte_position;
+  uint32_t bit_position;
+  uint8_t byte;
+} getbits_state_t;
+
+int dvdread_getbits_init(getbits_state_t *state, uint8_t *start);
+uint32_t dvdread_getbits(getbits_state_t *state, uint32_t number_of_bits);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* BITREADER_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/bswap.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,104 @@
+#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__) || defined(__GLIBC__)
+#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(__APPLE__)
+#include <libkern/OSByteOrder.h>
+#define B2N_16(x) x = OSSwapBigToHostInt16(x)
+#define B2N_32(x) x = OSSwapBigToHostInt32(x)
+#define B2N_64(x) x = OSSwapBigToHostInt64(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__) || defined(WIN32) || defined(__CYGWIN__) || defined(__BEOS__)
+#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) & 0xff00000000000000ULL) >> 56) | \
+      (((x) & 0x00ff000000000000ULL) >> 40) | \
+      (((x) & 0x0000ff0000000000ULL) >> 24) | \
+      (((x) & 0x000000ff00000000ULL) >>  8) | \
+      (((x) & 0x00000000ff000000ULL) <<  8) | \
+      (((x) & 0x0000000000ff0000ULL) << 24) | \
+      (((x) & 0x000000000000ff00ULL) << 40) | \
+      (((x) & 0x00000000000000ffULL) << 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/libdvdread/dvd_input.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,367 @@
+/*
+ * 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 */
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#else
+/* Only needed on MINGW at the moment */
+#include "../../msvc/contrib/dlfcn.c"
+#endif
+
+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(*dev));
+  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(*dev));
+  if(dev == NULL) {
+    fprintf(stderr, "libdvdread: Could not allocate memory.\n");
+    return NULL;
+  }
+  
+  /* Open the device */
+#ifndef WIN32
+  dev->fd = open(target, O_RDONLY);
+#else
+  dev->fd = open(target, O_RDONLY | O_BINARY);
+#endif
+  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 */
+
+#ifdef __APPLE__
+  #define CSS_LIB "libdvdcss.2.dylib"
+#elif defined(WIN32)
+  #define CSS_LIB "libdvdcss.dll"
+#else
+  #define CSS_LIB "libdvdcss.so.2"
+#endif
+  dvdcss_library = dlopen(CSS_LIB, 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 %s, "
+	      "this shouldn't happen !\n", CSS_LIB);
+      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 ? *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/libdvdread/dvd_input.h	Sun Jun 01 08:39:07 2008 +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/libdvdread/dvd_reader.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2001-2004 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>
+
+/* misc win32 helpers */
+#ifdef WIN32
+/* replacement gettimeofday implementation */
+#include <sys/timeb.h>
+static inline int _private_gettimeofday( struct timeval *tv, void *tz )
+{
+  struct timeb t;
+  ftime( &t );
+  tv->tv_sec = t.time;
+  tv->tv_usec = t.millitm * 1000;
+  return 0;
+}
+#define gettimeofday(TV, TZ) _private_gettimeofday((TV), (TZ))
+#include <io.h> /* read() */
+#define lseek64 _lseeki64
+#endif
+ 
+#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;
+};
+
+#define TITLES_MAX 9
+
+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[ TITLES_MAX ];
+    dvd_input_t title_devs[ TITLES_MAX ];
+
+    /* Calculated at open-time, size in blocks. */
+    ssize_t filesize;
+};
+
+int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
+                     size_t block_count, unsigned char *data, 
+                     int encrypted );
+
+/**
+ * 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 NULL;
+    }
+
+    dvd = (dvd_reader_t *) malloc( sizeof( dvd_reader_t ) );
+    if( !dvd ) {
+        dvdinput_close(dev);
+        return NULL;
+    }
+    dvd->isImageFile = 1;
+    dvd->dev = dev;
+    dvd->path_root = NULL;
+    
+    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 NULL;
+    dvd->isImageFile = 0;
+    dvd->dev = 0;
+    dvd->path_root = strdup( path_root );
+    if(!dvd->path_root) {
+      free(dvd);
+      return 0;
+    }
+
+    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 *ppath )
+{
+    struct stat fileinfo;
+    int ret;
+    int have_css;
+	dvd_reader_t *ret_val = NULL;
+    char *dev_name = NULL;
+	char *path;
+
+#ifdef _MSC_VER
+	int len;
+#endif
+
+    if( ppath == NULL )
+      return 0;
+
+	path = strdup(ppath);
+    if( path == NULL )
+      return 0;
+	
+    /* Try to open libdvdcss or fall back to standard functions */
+    have_css = dvdinput_setup();
+
+#ifdef _MSC_VER
+	/* Strip off the trailing \ if it is not a drive */
+	len = strlen(path);
+	if ((len > 1) && 
+		(path[len - 1] == '\\')  && 
+		(path[len - 2] != ':'))
+	{
+		path[len-1] = '\0';
+	}
+#endif
+    
+    ret = stat( path, &fileinfo );
+
+    if( ret < 0 ) {
+
+        /* maybe "host:port" url? try opening it with acCeSS library */
+        if( strchr(path,':') ) {
+			ret_val = DVDOpenImageFile( path, have_css );
+			free(path);
+	        return ret_val;
+        }
+      
+	/* If we can't stat the file, give up */
+	fprintf( stderr, "libdvdread: Can't stat %s\n", path );
+	perror("");
+	free(path);
+	return NULL;
+    }
+
+    /* 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)
+	ret_val = DVDOpenImageFile( sun_block2char( path ), have_css );
+#elif defined(SYS_BSD)
+	ret_val = DVDOpenImageFile( bsd_block2char( path ), have_css );
+#else
+	ret_val = DVDOpenImageFile( path, have_css );
+#endif
+
+	free(path);
+	return ret_val;
+
+    } 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 ) ) ) {
+		free(path);	
+		return NULL;
+	}
+
+#ifndef WIN32 /* don't have fchdir, and getcwd( NULL, ... ) is strange */
+              /* Also WIN32 does not have symlinks, so we don't need this bit of code. */
+
+	/* 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 = malloc(PATH_MAX+1);
+		if(!new_path) {
+		  free(path);
+		  return NULL;
+		}
+		getcwd(new_path, PATH_MAX );
+		fchdir( cdir );
+		close( cdir );
+		    free( path_copy );
+		    path_copy = new_path;
+	    }
+	}
+#endif	
+	/**
+	 * 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 ) > TITLES_MAX ) {
+	    if( !strcasecmp( &(path_copy[ strlen( path_copy ) - TITLES_MAX ]), 
+			     "/video_ts" ) ) {
+	      path_copy[ strlen( path_copy ) - TITLES_MAX ] = '\0';
+	    }
+	}
+	
+	if(path_copy[0] == '\0') {
+	    path_copy[0] = '/';
+	    path_copy[1] = '\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 );
+	}
+#elif defined(_MSC_VER)
+    auth_drive = DVDOpenImageFile( path, have_css );
+#endif
+
+#ifndef _MSC_VER
+	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 );
+	}
+#else
+	if( !auth_drive ) {
+	    fprintf( stderr, "libdvdread: Device %s inaccessible, "
+		     "CSS authentication not available.\n", dev_name );
+	}
+#endif
+
+	free( dev_name );
+	free( path_copy );
+
+        /**
+         * If we've opened a drive, just use that.
+         */
+	if( auth_drive ) {
+		free(path);
+		return auth_drive;
+	}
+
+        /**
+         * Otherwise, we now try to open the directory tree instead.
+         */
+        ret_val = DVDOpenPath( path );
+		free( path );
+		return ret_val;
+    }
+
+    /* If it's none of the above, screw it. */
+    fprintf( stderr, "libdvdread: Could not open %s\n", path );
+	free( path );
+    return NULL;
+}
+
+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 ) {
+      fprintf( stderr, "libdvdnav:DVDOpenFileUDF:UDFFindFile %s failed\n", filename );
+      return NULL;
+    }
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) {
+      fprintf( stderr, "libdvdnav:DVDOpenFileUDF:malloc failed\n" );
+      return NULL;
+    }
+    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 );
+            closedir( dir );
+            return 0;
+        }
+    }
+
+    closedir( dir );
+    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 ) ) {
+      fprintf( stderr, "libdvdnav:DVDOpenFilePath:findDVDFile %s failed\n", filename );
+      return NULL;
+    }
+
+    dev = dvdinput_open( full_path );
+    if( !dev ) {
+      fprintf( stderr, "libdvdnav:DVDOpenFilePath:dvdinput_open %s failed\n", full_path );
+      return NULL;
+    }
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) {
+      fprintf( stderr, "libdvdnav:DVDOpenFilePath:dvd_file malloc failed\n" );
+      dvdinput_close(dev);
+      return NULL;
+    }
+    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 NULL;
+    }
+    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 NULL;
+
+    dvd_file = (dvd_file_t *) malloc( sizeof( dvd_file_t ) );
+    if( !dvd_file ) return NULL;
+    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 NULL;
+    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 NULL;
+        }
+
+        dev = dvdinput_open( full_path );
+        if( dev == NULL ) {
+            free( dvd_file );
+            return NULL;
+        }
+
+        if( stat( full_path, &fileinfo ) < 0 ) {
+            fprintf( stderr, "libdvdread: Can't stat() %s.\n", filename );
+            dvdinput_close(dev);
+            free( dvd_file );
+            return NULL;
+        }
+        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 < TITLES_MAX; ++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 NULL;
+        }
+    }
+
+    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 < TITLES_MAX; ++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;
+   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;
+   }
+
+   ret = dvdinput_read( device->dev, (char *) data, 
+			 (int) block_count, encrypted );
+   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 )
+{
+    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 < TITLES_MAX; ++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( i + 1 >= TITLES_MAX || !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;
+    /* 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 ) {
+	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 {
+	dvdinput_title( dvd_file->title_devs[ 0 ], (int)dvd_file->lb_start );
+      }*/
+    }
+    
+    if( dvd_file->dvd->isImageFile ) {
+	ret = DVDReadBlocksUDF( dvd_file, (uint32_t)offset, 
+				block_count, data, DVDINPUT_READ_DECRYPT );
+    } else {
+	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;
+}
+
+int DVDFileSeekForce(dvd_file_t *dvd_file, int offset, int force_size)
+{
+    /* Check arguments. */
+    if( dvd_file == NULL || offset <= 0 )
+        return -1;
+
+    if( dvd_file->dvd->isImageFile ) {
+        if( force_size < 0 )
+            force_size = (offset - 1) / DVD_VIDEO_LB_LEN + 1;
+        if( dvd_file->filesize < force_size ) {
+            dvd_file->filesize = force_size;
+            fprintf(stderr, "libdvdread: Ignored size of file indicated in UDF.\n");
+        }
+    }
+
+    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_base, *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_base = (unsigned char *) malloc( numsec * DVD_VIDEO_LB_LEN + 2048 );
+    secbuf = (unsigned char *)(((uintptr_t)secbuf_base & ~((uintptr_t)2047)) + 2048);
+    if( !secbuf_base ) {
+	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_base );
+        return ret < 0 ? ret : 0;
+    }
+
+    memcpy( data, &(secbuf[ seek_byte ]), byte_size );
+    free( secbuf_base );
+
+    DVDFileSeekForce(dvd_file, dvd_file->seek_pos + byte_size, -1);
+    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;
+    int nr_of_files = 0;
+
+    /* 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_base = malloc( file_size + 2048 );
+	    char *buffer = (unsigned char *)(((uintptr_t)buffer_base & ~((uintptr_t)2047)) + 2048);
+	    
+	    if( buffer_base == NULL ) {
+	        DVDCloseFile( dvd_file );
+		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 %zd bytes"
+			 ", wanted %zd\n", bytes_read, file_size );
+		DVDCloseFile( dvd_file );
+		free( buffer_base );
+		return -1;
+	    }
+	    
+	    md5_process_bytes( buffer, file_size,  &ctx );
+	    
+	    DVDCloseFile( dvd_file );
+	    free( buffer_base );
+	    nr_of_files++;
+	}
+    }
+    md5_finish_ctx( &ctx, discid );
+    if(!nr_of_files)
+      return -1;
+    
+    return 0;
+}
+
+
+int DVDISOVolumeInfo( dvd_reader_t *dvd,
+		      char *volid, unsigned int volid_size,
+		      unsigned char *volsetid, unsigned int volsetid_size )
+{
+  unsigned char *buffer, *buffer_base;
+  int ret;
+
+  /* Check arguments. */
+  if( dvd == NULL )
+    return 0;
+  
+  if( dvd->dev == NULL ) {
+    /* No block access, so no ISO... */
+    return -1;
+  }
+  
+  buffer_base = malloc( DVD_VIDEO_LB_LEN + 2048 );
+  buffer = (unsigned char *)(((uintptr_t)buffer_base & ~((uintptr_t)2047)) + 2048);
+
+  if( buffer_base == 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" );
+    free( buffer_base );
+    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);
+  }
+  free( buffer_base );
+  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/libdvdread/dvd_reader.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,275 @@
+#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
+ */
+
+#ifdef _MSC_VER
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+#include <sys/types.h>
+#include <inttypes.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);
+ */
+int32_t DVDFileSeek( dvd_file_t *, int32_t );
+
+/**
+ * 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 );
+
+int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
+
+/**
+ * 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/libdvdread/dvd_udf.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,967 @@
+/*
+ * 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/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;
+  /* needed for proper freeing */
+  uint8_t *data_base;
+};
+
+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;
+
+void FreeUDFCache(void *cache)
+{
+  struct udf_cache *c = (struct udf_cache *)cache;
+  if(c == NULL)
+    return;
+
+  if(c->lbs) {
+    int n;
+    for(n = 0; n < c->lb_num; n++)
+      free(c->lbs[n].data_base);
+    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;
+  void *tmp;
+
+  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_base = ((uint8_t **)data)[0];
+       c->lbs[n].data = ((uint8_t **)data)[1];
+       c->lbs[n].lb = nr;
+       return 1;
+      }
+    }
+    c->lb_num++;
+    tmp = 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(tmp == NULL) {
+      if(c->lbs) free(c->lbs);
+      c->lb_num = 0;
+      return 0;
+    }
+    c->lbs = tmp;
+    c->lbs[n].data_base = ((uint8_t **)data)[0];
+    c->lbs[n].data = ((uint8_t **)data)[1];
+    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++;
+    tmp = 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(tmp == NULL) {
+      if(c->maps) free(c->maps);
+      c->map_num = 0;
+      return 0;
+    }
+    c->maps = tmp;
+    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_base[DVD_VIDEO_LB_LEN + 2048];
+    uint8_t *LogBlock = (uint8_t *)(((uintptr_t)LogBlock_base & ~((uintptr_t)2047)) + 2048);
+    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;
+      memcpy(File, &tmpmap.file, sizeof(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 );
+           memcpy(&tmpmap.file, File, sizeof(tmpmap.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_base[ 2 * DVD_VIDEO_LB_LEN + 2048];
+    uint8_t *directory = (uint8_t *)(((uintptr_t)directory_base & ~((uintptr_t)2047)) + 2048);
+    uint32_t lbnum;
+    uint16_t TagID;
+    uint8_t filechar;
+    unsigned int p;
+    uint8_t *cached_dir_base = NULL, *cached_dir;
+    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_base = malloc(dir_lba * DVD_VIDEO_LB_LEN + 2048)) == NULL)
+            return 0;
+
+          cached_dir = (uint8_t *)(((uintptr_t)cached_dir_base & ~((uintptr_t)2047)) + 2048);
+          if( DVDReadLBUDF( device, lbnum, dir_lba, cached_dir, 0) <= 0 ) {
+            free(cached_dir_base);
+            cached_dir_base = NULL;
+            cached_dir = NULL;
+          }
+          /*
+          if(cached_dir) {
+            fprintf(stderr, "malloc dir: %d\n",  dir_lba * DVD_VIDEO_LB_LEN);
+          }
+          */
+          {
+            uint8_t *data[2];
+            data[0] = cached_dir_base;
+            data[1] = cached_dir;
+            SetUDFCache(device, LBUDFCache, lbnum, data);
+          }
+      } 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_base[ DVD_VIDEO_LB_LEN + 2048 ];
+  uint8_t *Anchor = (uint8_t *)(((uintptr_t)Anchor_base & ~((uintptr_t)2047)) + 2048);
+  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_base[ DVD_VIDEO_LB_LEN + 2048 ];
+    uint8_t *LogBlock = (uint8_t *)(((uintptr_t)LogBlock_base & ~((uintptr_t)2047)) + 2048);
+    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_base[ DVD_VIDEO_LB_LEN + 2048 ];
+    uint8_t *LogBlock = (uint8_t *)(((uintptr_t)LogBlock_base & ~((uintptr_t)2047)) + 2048);
+    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';
+    strncat(tokenline, filename, MAX_UDF_FILE_NAME_LEN - 1);
+
+    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_base[DVD_VIDEO_LB_LEN + 2048];
+  uint8_t *pvd_buf = (uint8_t *)(((uintptr_t)pvd_buf_base & ~((uintptr_t)2047)) + 2048);
+  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/libdvdread/dvd_udf.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,62 @@
+#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);
+void *GetUDFCacheHandle(dvd_reader_t *device);
+void SetUDFCacheHandle(dvd_reader_t *device, void *cache);
+
+#ifdef __cplusplus
+};
+#endif
+#endif /* DVD_UDF_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/dvdread_internal.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,15 @@
+#ifndef DVDREAD_INTERNAL_H
+#define DVDREAD_INTERNAL_H
+
+#ifdef _MSC_VER
+#include <unistd.h>
+#endif /* _MSC_VER */
+
+#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/libdvdread/ifo_print.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,1248 @@
+/* 
+ * 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
+ *
+ * $Id$
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "ifo_types.h"
+#include "ifo_read.h"
+#include "ifo_print.h"
+#include "vmcmd.h"
+
+/* Put this in some other file / package?  It's used in nav_print too. */
+static void ifo_print_time(int level, dvd_time_t *dtime) {
+  const char *rate;
+  assert((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa);
+  assert((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa);
+  assert((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa);
+  assert((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 ifo_print_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("| ");
+#if 0
+  //disabled call of dvdnav function
+  vm_print_mnemonic(command);
+#endif
+  printf("\n");
+}
+
+static void ifo_print_video_attributes(int level, 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->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:
+    printf("not specified, ");
+    break;
+  default:
+    printf("(please send a bug report), ");
+  }
+  
+  printf("U%x, ", attr->unknown1);
+  assert(!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, ");
+  }
+  
+  {
+    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 ifo_print_audio_attributes(int level, 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_extension == 0
+     && attr->unknown1 == 0
+     && attr->unknown3 == 0) {
+    printf("-- Unspecified --");
+    return;
+  }
+  
+  switch(attr->audio_format) {
+  case 0:
+    printf("ac3 ");
+    if(attr->quantization != 3)
+      printf("(please send a bug report) ac3 quant/drc not 3 (%d)", attr->quantization);
+    break;
+  case 1:
+    printf("(please send a bug report) ");
+    break;
+  case 2:
+    printf("mpeg1 ");
+  case 3:
+    printf("mpeg2ext ");
+    switch(attr->quantization) {
+      case 0:
+        printf("no drc ");
+        break;
+      case 1:
+        printf("drc ");
+        break;
+      default:
+        printf("(please send a bug report) mpeg reserved quant/drc  (%d)", attr->quantization);  
+    }
+    break;
+  case 4:
+    printf("lpcm ");
+    switch(attr->quantization) {
+      case 0:
+        printf("16bit ");
+        break;
+      case 1:
+        printf("20bit ");
+        break;
+      case 2:
+        printf("24bit ");
+        break;
+      case 3:
+        printf("(please send a bug report) lpcm reserved quant/drc  (%d)", attr->quantization);
+      break;
+    }
+    break;
+  case 5:
+    printf("(please send a bug report) ");
+    break;
+  case 6:
+    printf("dts ");
+    if(attr->quantization != 3)
+      printf("(please send a bug report) dts quant/drc not 3 (%d)", attr->quantization);
+    break;
+  default:
+    printf("(please send a bug report) ");
+  }
+  
+  if(attr->multichannel_extension)
+    printf("multichannel_extension ");
+  
+  switch(attr->lang_type) {
+  case 0:
+    // not specified
+    assert(attr->lang_code == 0 || attr->lang_code == 0xffff);
+    break;
+  case 1:
+    printf("%c%c ", attr->lang_code>>8, attr->lang_code & 0xff);
+    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->lang_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->unknown1);
+  printf("%d ", attr->unknown3);
+}
+
+static void ifo_print_subp_attributes(int level, subp_attr_t *attr) {
+  
+  if(attr->type == 0
+     && attr->lang_code == 0
+     && attr->zero1 == 0
+     && attr->zero2 == 0
+     && attr->lang_extension== 0) {
+    printf("-- Unspecified --");
+    return;
+  }
+  
+  printf("type %02x ", attr->type);
+  
+  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 ", 0xff & (unsigned)(attr->lang_code >> 8), 
+	   0xff & (unsigned)(attr->lang_code & 0xff));
+  }
+  
+  printf("%d ", attr->zero1);
+  printf("%d ", attr->zero2);
+
+  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 ifo_print_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 ifo_print_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 (Region Code=%02x)\n", vmgi_mat->vmg_category, ((vmgi_mat->vmg_category >> 16) & 0xff) ^0xff);
+  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: ");
+  ifo_print_video_attributes(5, &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);
+    ifo_print_audio_attributes(5, &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);
+    ifo_print_subp_attributes(5, &vmgi_mat->vmgm_subp_attr);
+    printf("\n");
+  }
+}
+
+
+void ifo_print_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: ");
+  ifo_print_video_attributes(5, &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);
+    ifo_print_audio_attributes(5, &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);
+    ifo_print_subp_attributes(5, &vtsi_mat->vtsm_subp_attr);
+    printf("\n");
+  }
+  
+  printf("Video attributes of VTS_VOBS: ");
+  ifo_print_video_attributes(5, &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);
+    ifo_print_audio_attributes(5, &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);
+    ifo_print_subp_attributes(5, &vtsi_mat->vts_subp_attr[i]);
+    printf("\n");
+  }
+}
+
+
+static void ifo_print_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++) {
+    ifo_print_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++) {
+    ifo_print_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++) {
+    ifo_print_cmd(i, &cmd_tbl->cell_cmds[i]);
+  }
+}
+
+
+static void ifo_print_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 ifo_print_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);
+
+    ifo_print_time(5, &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].playback_mode)
+      printf("only still VOBUs ");
+    if(cell_playback[i].restricted)
+      printf("restricted cell ");
+    if(cell_playback[i].unknown2)
+      printf("Unknown 0x%x ", cell_playback[i].unknown2);
+    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 ifo_print_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 ifo_print_PGC(pgc_t *pgc) {
+  int i;
+  
+  if (!pgc) {
+    printf("None\n");
+    return;
+  }
+  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: ");
+  ifo_print_time(5, &pgc->playback_time); printf("\n");
+
+  /* If no programs/no time then does this mean anything? */
+  printf("Prohibited user operations: ");
+  ifo_print_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: 4:3=%d, Wide=%d, Letterbox=%d, Pan-Scan=%d\n", 
+	     i, pgc->subp_control[i],
+	     (pgc->subp_control[i] >>24) & 0x1f,
+	     (pgc->subp_control[i] >>16) & 0x1f,
+	     (pgc->subp_control[i] >>8) & 0x1f,
+	     (pgc->subp_control[i] ) & 0x1f);
+    }
+  }
+  
+  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);
+    printf("PG Playback mode %02x\n", pgc->pg_playback_mode);
+  }
+  
+  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. */
+  ifo_print_PGC_COMMAND_TBL(pgc->command_tbl);
+  ifo_print_PGC_PROGRAM_MAP(pgc->program_map, pgc->nr_of_programs);
+  ifo_print_CELL_PLAYBACK(pgc->cell_playback, pgc->nr_of_cells);
+  ifo_print_CELL_POSITION(pgc->cell_position, pgc->nr_of_cells);
+}
+
+
+void ifo_print_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: (%02x)\n", 
+	   *(uint8_t *)&(tt_srpt->title[i].pb_ty));
+    printf("\t\t%s\n",
+       tt_srpt->title[i].pb_ty.multi_or_random_pgc_title ? "Random or Shuffle" : "Sequencial");
+    if (tt_srpt->title[i].pb_ty.jlc_exists_in_cell_cmd) printf("\t\tJump/Link/Call exists in cell cmd\n"); 
+    if (tt_srpt->title[i].pb_ty.jlc_exists_in_prepost_cmd) printf("\t\tJump/Link/Call exists in pre/post cmd\n");
+    if (tt_srpt->title[i].pb_ty.jlc_exists_in_button_cmd) printf("\t\tJump/Link/Call exists in button cmd\n");
+    if (tt_srpt->title[i].pb_ty.jlc_exists_in_tt_dom) printf("\t\tJump/Link/Call exists in tt_dom cmd\n");
+    printf("\t\tTitle or time play:%d\n", tt_srpt->title[i].pb_ty.title_or_time_play);
+    printf("\t\tChapter search or play:%d\n", tt_srpt->title[i].pb_ty.chapter_search_or_play);
+
+    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 ifo_print_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++) {
+    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 );
+    }
+  }
+}
+
+
+static void hexdump(uint8_t *ptr, int len) {
+  while(len--)
+    printf("%02x ", *ptr++);
+}
+
+void ifo_print_PTL_MAIT(ptl_mait_t *ptl_mait) {
+  int i, j;
+  
+  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("Country code: %c%c\n", 
+	   ptl_mait->countries[i].country_code >> 8,
+	   ptl_mait->countries[i].country_code & 0xff);
+    /*
+      printf("Start byte: %04x %i\n", 
+      ptl_mait->countries[i].pf_ptl_mai_start_byte, 
+      ptl_mait->countries[i].pf_ptl_mai_start_byte);
+    */
+    /* This seems to be pointing at a array with 8 2byte fields per VTS
+       ? and one extra for the menu? always an odd number of VTSs on
+       all the dics I tested so it might be padding to even also.
+       If it is for the menu it probably the first entry.  */
+    for(j=0;j<8;j++) {
+      hexdump( (uint8_t *)ptl_mait->countries - PTL_MAIT_COUNTRY_SIZE 
+	       + ptl_mait->countries[i].pf_ptl_mai_start_byte
+	       + j*(ptl_mait->nr_of_vtss+1)*2, (ptl_mait->nr_of_vtss+1)*2);
+      printf("\n");
+    }
+  }
+}
+
+void ifo_print_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 (number matches title PGC number.)\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 ifo_print_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 ifo_print_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]);
+  }
+}
+
+const char *ifo_print_menu_name(int type) {
+  const char *menu_name;
+  menu_name="";
+  switch (type) {
+  case 2:
+    menu_name="Title";
+    break;
+  case 3:
+    menu_name = "Root";
+    break;
+  case 4:
+    menu_name = "Sub-Picture";
+    break;
+  case 5:
+    menu_name = "Audio";
+    break;
+  case 6:
+    menu_name = "Angle";
+    break;
+  case 7:
+    menu_name = "PTT (Chapter)";
+    break;
+  default:
+    menu_name = "Unknown";
+    break;
+  }
+  return &menu_name[0];
+}
+
+/* pgc_type=1 for menu, 0 for title. */
+void ifo_print_PGCIT(pgcit_t *pgcit, int pgc_type) {
+  int i;
+  
+  printf("\nNumber of Program Chains: %3i\n", pgcit->nr_of_pgci_srp);
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    printf("\nProgram (PGC): %3i\n", i + 1);
+    if (pgc_type) {
+       printf("PGC Category: Entry PGC %d, Menu Type=0x%02x:%s (Entry id 0x%02x), ",
+            pgcit->pgci_srp[i].entry_id >> 7,
+            pgcit->pgci_srp[i].entry_id & 0xf,
+            ifo_print_menu_name(pgcit->pgci_srp[i].entry_id & 0xf),
+            pgcit->pgci_srp[i].entry_id);
+    } else {
+       printf("PGC Category: %s VTS_TTN:0x%02x (Entry id 0x%02x), ",
+            pgcit->pgci_srp[i].entry_id >> 7 ? "At Start of" : "During",
+            pgcit->pgci_srp[i].entry_id & 0xf,
+            pgcit->pgci_srp[i].entry_id);
+    }
+    printf("Parental ID mask 0x%04x\n", pgcit->pgci_srp[i].ptl_id_mask);
+    ifo_print_PGC(pgcit->pgci_srp[i].pgc);
+  }
+}
+
+
+void ifo_print_PGCI_UT(pgci_ut_t *pgci_ut) {
+  int i, menu;
+  
+  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 Unit %d\n", i+1);
+    printf("\nMenu Language Code: %c%c\n",
+	   pgci_ut->lu[i].lang_code >> 8,
+	   pgci_ut->lu[i].lang_code & 0xff);
+  
+    menu = pgci_ut->lu[i].exists;
+    printf("Menu Existence: %02x: ", menu);
+    if (menu == 0) {
+      printf("No menus ");
+    }
+    if (menu & 0x80) {
+      printf("Root ");
+      menu^=0x80;
+    }
+    if (menu & 0x40) {
+      printf("Sub-Picture ");
+      menu^=0x40;
+    }
+    if (menu & 0x20) {
+      printf("Audio ");
+      menu^=0x20;
+    }
+    if (menu & 0x10) {
+      printf("Angle ");
+      menu^=0x10;
+    }
+    if (menu & 0x08) {
+      printf("PTT ");
+      menu^=0x08;
+    }
+    if (menu > 0) {
+      printf("Unknown extra menus ");
+      menu^=0x08;
+    }
+    printf("\n");
+    ifo_print_PGCIT(pgci_ut->lu[i].pgcit, 1);
+  }
+}
+
+
+static void ifo_print_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: ");
+  ifo_print_video_attributes(5, &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);
+    ifo_print_audio_attributes(5, &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);
+    ifo_print_subp_attributes(5, &vts_attributes->vtsm_subp_attr);
+    printf("\n");
+  }
+   
+  printf("Video attributes of VTSTT_VOBS: ");
+  ifo_print_video_attributes(5, &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);
+    ifo_print_audio_attributes(5, &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);    
+    ifo_print_subp_attributes(5, &vts_attributes->vtstt_subp_attr[i]);
+    printf("\n");
+  }
+}
+
+
+void ifo_print_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);
+    ifo_print_VTS_ATTRIBUTES(&vts_atrt->vts[i]);
+  }
+}
+
+
+void ifo_print(dvd_reader_t *dvd, int title) {
+  ifo_handle_t *ifohandle;
+  printf("Local ifo_print\n");
+  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");
+    ifo_print_VMGI_MAT(ifohandle->vmgi_mat);
+
+    printf("\nFirst Play PGC\n--------------\n");
+    if(ifohandle->first_play_pgc)
+      ifo_print_PGC(ifohandle->first_play_pgc);
+    else
+      printf("No First Play PGC present\n");
+
+    printf("\nTitle Track search pointer table\n");
+    printf(  "------------------------------------------------\n");
+    ifo_print_TT_SRPT(ifohandle->tt_srpt);
+
+    printf("\nMenu PGCI Unit table\n");
+    printf(  "--------------------\n");
+    if(ifohandle->pgci_ut) {
+      ifo_print_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) {
+      ifo_print_PTL_MAIT(ifohandle->ptl_mait);
+    } else {
+      printf("No Parental Management Information present\n");
+    }
+
+    printf("\nVideo Title Set Attribute Table\n");
+    printf(  "-------------------------------\n");
+    ifo_print_VTS_ATRT(ifohandle->vts_atrt);
+    
+    printf("\nText Data Manager Information\n");
+    printf(  "-----------------------------\n");
+    if(ifohandle->txtdt_mgi) {
+      //ifo_print_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) {
+      ifo_print_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) {
+      ifo_print_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");
+    ifo_print_VTSI_MAT(ifohandle->vtsi_mat);
+
+    printf("\nPart of Title Track search pointer table\n");
+    printf(  "----------------------------------------------\n");
+    ifo_print_VTS_PTT_SRPT(ifohandle->vts_ptt_srpt);
+
+    printf("\nPGCI Unit table\n");
+    printf(  "--------------------\n");
+    ifo_print_PGCIT(ifohandle->vts_pgcit, 0);
+
+    printf("\nMenu PGCI Unit table\n");
+    printf(  "--------------------\n");
+    if(ifohandle->pgci_ut) {
+      ifo_print_PGCI_UT(ifohandle->pgci_ut);
+    } else {
+      printf("No Menu PGCI Unit table present\n");
+    }
+
+    printf("\nVTS Time Map table\n");
+    printf(  "-----------------\n");
+    if(ifohandle->vts_tmapt) {
+      ifo_print_VTS_TMAPT(ifohandle->vts_tmapt);
+    } else {
+      printf("No VTS Time Map table present\n");
+    }
+
+    printf("\nMenu Cell Adress table\n");
+    printf(  "-----------------\n");
+    if(ifohandle->menu_c_adt) {
+      ifo_print_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) {
+      ifo_print_VOBU_ADMAP(ifohandle->menu_vobu_admap);
+    } else {
+      printf("No Menu VOBU address map present\n");
+    }
+
+    printf("\nCell Adress table\n");
+    printf(  "-----------------\n");
+    ifo_print_C_ADT(ifohandle->vts_c_adt);
+
+    printf("\nVideo Title Set VOBU address map\n");
+    printf(  "-----------------\n");
+    ifo_print_VOBU_ADMAP(ifohandle->vts_vobu_admap);
+  } 
+
+  ifoClose(ifohandle);
+}
+
+/*
+ * $Log$
+ * Revision 1.3  2004/10/22 11:36:19  jcdutton
+ * Stop seg fault when using ifo_dump.
+ *
+ * Revision 1.2  2004/09/27 12:24:01  jcdutton
+ * Add extra info to printout when using ifo_dump.
+ *
+ * Revision 1.1  2004/01/11 21:43:13  mroi
+ * big build system changes
+ *  * cleaned up all Makefiles and added a Makefile.common
+ *  * added relchk script
+ *  * moved libdvdread files to a dvdread subdir
+ *  * moved DVD VM to a vm subdir
+ *  * removed unused code in read_cache.c
+ *
+ * Revision 1.4  2004/01/01 15:13:13  jcdutton
+ * Put ifo_print.c and .h back in.
+ *
+ * Revision 1.7  2003/04/28 15:17:17  jcdutton
+ * Update ifodump to work with new libdvdnav cvs, instead of needing libdvdread.
+ *
+ * Revision 1.6  2003/04/05 22:49:04  jcdutton
+ * Update with more info from the latest libdvdread.
+ *
+ * Revision 1.5  2003/04/05 13:03:49  jcdutton
+ * Small updates.
+ *
+ * Revision 1.4  2003/04/01 08:01:03  jcdutton
+ * Add VTS Time map display. Requires libdvdread 0.9.4. The same version that comes with xine.
+ *
+ * Revision 1.3  2003/03/14 15:49:18  mroi
+ * adjust to new libdvdread version
+ *
+ * Revision 1.2  2002/08/30 05:12:33  jcdutton
+ * Minor update now that I know what PGC Entry IDs are for.
+ *
+ * Revision 1.1.1.1  2002/08/28 09:48:35  jcdutton
+ * Initial import into CVS.
+ *
+ *
+ *
+ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/ifo_print.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,56 @@
+/*
+ * 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
+ *
+ * $Id$
+ *
+ */
+
+#ifndef IFO_PRINT_H_INCLUDED
+#define IFO_PRINT_H_INCLUDED
+
+#include <inttypes.h>
+#ifdef DVDNAV_COMPILE
+#  include "ifo_types.h"
+#else
+#  include <libdvdread/ifo_types.h> /*  Only for vm_cmd_t  */
+#endif
+
+void ifo_print(dvd_reader_t *dvd, int title);
+
+#endif /* IFO_PRINT_H_INCLUDED */
+
+/*
+ * $Log$
+ * Revision 1.1  2004/01/11 21:43:13  mroi
+ * big build system changes
+ *  * cleaned up all Makefiles and added a Makefile.common
+ *  * added relchk script
+ *  * moved libdvdread files to a dvdread subdir
+ *  * moved DVD VM to a vm subdir
+ *  * removed unused code in read_cache.c
+ *
+ * Revision 1.4  2004/01/01 15:13:13  jcdutton
+ * Put ifo_print.c and .h back in.
+ *
+ * Revision 1.2  2003/04/28 15:17:17  jcdutton
+ * Update ifodump to work with new libdvdnav cvs, instead of needing libdvdread.
+ *
+ * Revision 1.1.1.1  2002/08/28 09:48:35  jcdutton
+ * Initial import into CVS.
+ *
+ *
+ *
+ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/ifo_read.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,2213 @@
+/*
+ * 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"
+#include "bitreader.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 DVDFileSeekForce_( dvd_file_t *dvd_file, uint32_t offset, int force_size ) {
+  return (DVDFileSeekForce(dvd_file, (int)offset, force_size) == (int)offset);
+}
+
+static inline int DVDFileSeek_( dvd_file_t *dvd_file, uint32_t offset ) {
+  return (DVDFileSeek(dvd_file, (int)offset) == (int)offset);
+}
+
+static void read_video_attr(video_attr_t *va) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(video_attr_t)];
+
+  memcpy(buf, va, sizeof(video_attr_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  va->mpeg_version = dvdread_getbits(&state, 2);
+  va->video_format = dvdread_getbits(&state, 2);
+  va->display_aspect_ratio = dvdread_getbits(&state, 2);
+  va->permitted_df = dvdread_getbits(&state, 2);
+  va->line21_cc_1 = dvdread_getbits(&state, 1);
+  va->line21_cc_2 = dvdread_getbits(&state, 1);
+  va->unknown1 = dvdread_getbits(&state, 1);
+  va->bit_rate = dvdread_getbits(&state, 1);
+  va->picture_size = dvdread_getbits(&state, 2);
+  va->letterboxed = dvdread_getbits(&state, 1);
+  va->film_mode = dvdread_getbits(&state, 1);
+}
+
+static void read_audio_attr(audio_attr_t *aa) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(audio_attr_t)];
+  
+  memcpy(buf, aa, sizeof(audio_attr_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  aa->audio_format = dvdread_getbits(&state, 3);
+  aa->multichannel_extension = dvdread_getbits(&state, 1);
+  aa->lang_type = dvdread_getbits(&state, 2);
+  aa->application_mode = dvdread_getbits(&state, 2);
+  aa->quantization = dvdread_getbits(&state, 2);
+  aa->sample_frequency = dvdread_getbits(&state, 2);
+  aa->unknown1 = dvdread_getbits(&state, 1);
+  aa->channels = dvdread_getbits(&state, 3);
+  aa->lang_code = dvdread_getbits(&state, 16);
+  aa->lang_extension = dvdread_getbits(&state, 8);
+  aa->code_extension = dvdread_getbits(&state, 8);
+  aa->unknown3 = dvdread_getbits(&state, 8);
+  aa->app_info.karaoke.unknown4 = dvdread_getbits(&state, 1);
+  aa->app_info.karaoke.channel_assignment = dvdread_getbits(&state, 3);
+  aa->app_info.karaoke.version = dvdread_getbits(&state, 2);
+  aa->app_info.karaoke.mc_intro = dvdread_getbits(&state, 1);
+  aa->app_info.karaoke.mode = dvdread_getbits(&state, 1);
+}
+
+static void read_multichannel_ext(multichannel_ext_t *me) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(multichannel_ext_t)];
+  
+  memcpy(buf, me, sizeof(multichannel_ext_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  me->zero1 = dvdread_getbits(&state, 7);
+  me->ach0_gme = dvdread_getbits(&state, 1);
+  me->zero2 = dvdread_getbits(&state, 7);
+  me->ach1_gme = dvdread_getbits(&state, 1);
+  me->zero3 = dvdread_getbits(&state, 4);
+  me->ach2_gv1e = dvdread_getbits(&state, 1);
+  me->ach2_gv2e = dvdread_getbits(&state, 1);
+  me->ach2_gm1e = dvdread_getbits(&state, 1);
+  me->ach2_gm2e = dvdread_getbits(&state, 1);
+  me->zero4 = dvdread_getbits(&state, 4);
+  me->ach3_gv1e = dvdread_getbits(&state, 1);
+  me->ach3_gv2e = dvdread_getbits(&state, 1);
+  me->ach3_gmAe = dvdread_getbits(&state, 1);
+  me->ach3_se2e = dvdread_getbits(&state, 1);
+  me->zero5 = dvdread_getbits(&state, 4);
+  me->ach4_gv1e = dvdread_getbits(&state, 1);
+  me->ach4_gv2e = dvdread_getbits(&state, 1);
+  me->ach4_gmBe = dvdread_getbits(&state, 1);
+  me->ach4_seBe = dvdread_getbits(&state, 1);
+}
+
+static void read_subp_attr(subp_attr_t *sa) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(subp_attr_t)];
+  
+  memcpy(buf, sa, sizeof(subp_attr_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  sa->code_mode = dvdread_getbits(&state, 3);
+  sa->zero1 = dvdread_getbits(&state, 3);
+  sa->type = dvdread_getbits(&state, 2);
+  sa->zero2 = dvdread_getbits(&state, 8);
+  sa->lang_code = dvdread_getbits(&state, 16);
+  sa->lang_extension = dvdread_getbits(&state, 8);
+  sa->code_extension = dvdread_getbits(&state, 8);
+}
+
+static void read_user_ops(user_ops_t *uo) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(user_ops_t)];
+  
+  memcpy(buf, uo, sizeof(user_ops_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  uo->zero                           = dvdread_getbits(&state, 7);
+  uo->video_pres_mode_change         = dvdread_getbits(&state, 1);
+  uo->karaoke_audio_pres_mode_change = dvdread_getbits(&state, 1);
+  uo->angle_change                   = dvdread_getbits(&state, 1);
+  uo->subpic_stream_change           = dvdread_getbits(&state, 1);
+  uo->audio_stream_change            = dvdread_getbits(&state, 1);
+  uo->pause_on                       = dvdread_getbits(&state, 1);
+  uo->still_off                      = dvdread_getbits(&state, 1);
+  uo->button_select_or_activate      = dvdread_getbits(&state, 1);
+  uo->resume                         = dvdread_getbits(&state, 1);
+  uo->chapter_menu_call              = dvdread_getbits(&state, 1);
+  uo->angle_menu_call                = dvdread_getbits(&state, 1);
+  uo->audio_menu_call                = dvdread_getbits(&state, 1);
+  uo->subpic_menu_call               = dvdread_getbits(&state, 1);
+  uo->root_menu_call                 = dvdread_getbits(&state, 1);
+  uo->title_menu_call                = dvdread_getbits(&state, 1);
+  uo->backward_scan                  = dvdread_getbits(&state, 1);
+  uo->forward_scan                   = dvdread_getbits(&state, 1);
+  uo->next_pg_search                 = dvdread_getbits(&state, 1);
+  uo->prev_or_top_pg_search          = dvdread_getbits(&state, 1);
+  uo->time_or_chapter_search         = dvdread_getbits(&state, 1);
+  uo->go_up                          = dvdread_getbits(&state, 1);
+  uo->stop                           = dvdread_getbits(&state, 1);
+  uo->title_play                     = dvdread_getbits(&state, 1);
+  uo->chapter_search_or_play         = dvdread_getbits(&state, 1);
+  uo->title_or_time_play             = dvdread_getbits(&state, 1);
+}
+
+static void read_pgci_srp(pgci_srp_t *ps) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(pgci_srp_t)];
+  
+  memcpy(buf, ps, sizeof(pgci_srp_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  ps->entry_id                       = dvdread_getbits(&state, 8);
+  ps->block_mode                     = dvdread_getbits(&state, 2);
+  ps->block_type                     = dvdread_getbits(&state, 2);
+  ps->unknown1                       = dvdread_getbits(&state, 4);
+  ps->ptl_id_mask                    = dvdread_getbits(&state, 16);
+  ps->pgc_start_byte                 = dvdread_getbits(&state, 32);
+}
+
+static void read_cell_playback(cell_playback_t *cp) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(cell_playback_t)];
+  
+  memcpy(buf, cp, sizeof(cell_playback_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  cp->block_mode                      = dvdread_getbits(&state, 2);
+  cp->block_type                      = dvdread_getbits(&state, 2);
+  cp->seamless_play                   = dvdread_getbits(&state, 1);
+  cp->interleaved                     = dvdread_getbits(&state, 1);
+  cp->stc_discontinuity               = dvdread_getbits(&state, 1);
+  cp->seamless_angle                  = dvdread_getbits(&state, 1);
+  cp->playback_mode                   = dvdread_getbits(&state, 1);
+  cp->restricted                      = dvdread_getbits(&state, 1);
+  cp->unknown2                        = dvdread_getbits(&state, 6);
+  cp->still_time                      = dvdread_getbits(&state, 8);
+  cp->cell_cmd_nr                     = dvdread_getbits(&state, 8);
+  
+  cp->playback_time.hour              = dvdread_getbits(&state, 8);
+  cp->playback_time.minute            = dvdread_getbits(&state, 8);
+  cp->playback_time.second            = dvdread_getbits(&state, 8);
+  cp->playback_time.frame_u           = dvdread_getbits(&state, 8);
+  
+  cp->first_sector                    = dvdread_getbits(&state, 32);
+  cp->first_ilvu_end_sector           = dvdread_getbits(&state, 32);
+  cp->last_vobu_start_sector          = dvdread_getbits(&state, 32);
+  cp->last_sector                     = dvdread_getbits(&state, 32);
+}
+
+static void read_playback_type(playback_type_t *pt) {
+  getbits_state_t state;
+  uint8_t buf[sizeof(playback_type_t)];
+  
+  memcpy(buf, pt, sizeof(playback_type_t));
+  if (!dvdread_getbits_init(&state, buf)) abort();
+  pt->zero_1                          = dvdread_getbits(&state, 1);
+  pt->multi_or_random_pgc_title       = dvdread_getbits(&state, 1);
+  pt->jlc_exists_in_cell_cmd          = dvdread_getbits(&state, 1);
+  pt->jlc_exists_in_prepost_cmd       = dvdread_getbits(&state, 1);
+  pt->jlc_exists_in_button_cmd        = dvdread_getbits(&state, 1);
+  pt->jlc_exists_in_tt_dom            = dvdread_getbits(&state, 1);
+  pt->chapter_search_or_play          = dvdread_getbits(&state, 1);
+  pt->title_or_time_play              = dvdread_getbits(&state, 1);
+}
+
+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 NULL;
+
+  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 NULL;
+  }
+
+  /* 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), ifoRead_FP_PGC() failed.\n");
+      ifoClose(ifofile);
+      return NULL;
+    }
+
+    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), ifoRead_VTS_ATRT() failed.\n");
+      ifoClose(ifofile);
+      return NULL;
+    }
+
+    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 NULL;
+    }
+
+
+    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 NULL;
+    }
+
+    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 NULL;
+}
+
+
+ifo_handle_t *ifoOpenVMGI(dvd_reader_t *dvd) {
+  ifo_handle_t *ifofile;
+
+  ifofile = (ifo_handle_t *)malloc(sizeof(ifo_handle_t));
+  if(!ifofile)
+    return NULL;
+
+  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 NULL;
+  }
+
+  if(ifoRead_VMG(ifofile))
+    return ifofile;
+
+  fprintf(stderr, "libdvdread,ifoOpenVMGI(): Invalid main menu IFO (VIDEO_TS.IFO).\n");
+  ifoClose(ifofile);
+  return NULL;
+}
+
+
+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 NULL;
+
+  memset(ifofile, 0, sizeof(ifo_handle_t));
+  
+  if(title <= 0 || title > 99) {
+    fprintf(stderr, "libdvdread: ifoOpenVTSI invalid title (%d).\n", title);
+    free(ifofile);
+    return NULL;
+  }
+    
+  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 NULL;
+  }
+
+  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 NULL;
+}
+
+
+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);
+  ifoFree_VTS_TMAPT(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);
+  read_video_attr(&vmgi_mat->vmgm_video_attr);
+  read_audio_attr(&vmgi_mat->vmgm_audio_attr);
+  read_subp_attr(&vmgi_mat->vmgm_subp_attr);
+
+
+  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;
+  }
+
+  read_video_attr(&vtsi_mat->vtsm_video_attr);
+  read_video_attr(&vtsi_mat->vts_video_attr);
+  read_audio_attr(&vtsi_mat->vtsm_audio_attr);
+  for(i=0; i<8; i++)
+    read_audio_attr(&vtsi_mat->vts_audio_attr[i]);
+  read_subp_attr(&vtsi_mat->vtsm_subp_attr);
+  for(i=0; i<32; i++)
+    read_subp_attr(&vtsi_mat->vts_subp_attr[i]);
+  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);
+
+
+  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++) {
+    read_multichannel_ext(&vtsi_mat->vts_mu_audio_attr[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++) {
+    read_cell_playback(&cell_playback[i]);
+    /* 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;
+
+  read_user_ops(&pgc->prohibited_ops);
+  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->nr_of_programs>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->nr_of_cells>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->nr_of_cells>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++) {
+    read_playback_type(&tt_srpt->title[i].pb_ty);
+    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(!DVDFileSeekForce_(ifofile->file, sector * DVD_BLOCK_LEN, sector))
+    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_SRP_SIZE);
+    ptr += PGCI_SRP_SIZE;
+    read_pgci_srp(&pgcit->pgci_srp[i]);
+    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);
+      }
+      goto fail;
+    }
+    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);
+      }
+      goto fail;
+    }
+  }
+
+  return 1;
+fail:
+  free(pgcit->pgci_srp);
+  pgcit->pgci_srp = NULL;
+  return 0;
+}
+
+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;
+
+  read_video_attr(&vts_attributes->vtsm_vobs_attr);
+  read_video_attr(&vts_attributes->vtstt_vobs_video_attr);
+  read_audio_attr(&vts_attributes->vtsm_audio_attr);
+  for(i=0; i<8; i++)
+    read_audio_attr(&vts_attributes->vtstt_audio_attr[i]);
+  read_subp_attr(&vts_attributes->vtsm_subp_attr);
+  for(i=0; i<32; i++)
+    read_subp_attr(&vts_attributes->vtstt_subp_attr[i]);
+  B2N_32(vts_attributes->last_byte);
+  B2N_32(vts_attributes->vts_cat);
+  
+  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/libdvdread/ifo_read.h	Sun Jun 01 08:39:07 2008 +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 "ifo_types.h"
+#include "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/libdvdread/ifo_types.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,747 @@
+#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 "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 8U
+
+
+/**
+ * Video Attributes.
+ */
+typedef struct {
+  unsigned char mpeg_version         : 2;
+  unsigned char video_format         : 2;
+  unsigned char display_aspect_ratio : 2;
+  unsigned char permitted_df         : 2;
+  
+  unsigned char line21_cc_1          : 1;
+  unsigned char line21_cc_2          : 1;
+  unsigned char unknown1             : 1;
+  unsigned char bit_rate             : 1;
+  
+  unsigned char picture_size         : 2;
+  unsigned char letterboxed          : 1;
+  unsigned char film_mode            : 1;
+} ATTRIBUTE_PACKED video_attr_t;
+
+/**
+ * Audio Attributes.
+ */
+typedef struct {
+  unsigned char audio_format           : 3;
+  unsigned char multichannel_extension : 1;
+  unsigned char lang_type              : 2;
+  unsigned char application_mode       : 2;
+  
+  unsigned char quantization           : 2;
+  unsigned char sample_frequency       : 2;
+  unsigned char unknown1               : 1;
+  unsigned char channels               : 3;
+  uint16_t lang_code;
+  uint8_t  lang_extension;
+  uint8_t  code_extension;
+  uint8_t unknown3;
+  union {
+    struct ATTRIBUTE_PACKED {
+      unsigned char unknown4           : 1;
+      unsigned char channel_assignment : 3;
+      unsigned char version            : 2;
+      unsigned char mc_intro           : 1; /* probably 0: true, 1:false */
+      unsigned char mode               : 1; /* Karaoke mode 0: solo 1: duet */
+    } karaoke;
+    struct ATTRIBUTE_PACKED {
+      unsigned char unknown5           : 4;
+      unsigned char dolby_encoded      : 1; /* suitable for surround decoding */
+      unsigned char unknown6           : 3;
+    } surround;
+  } app_info;
+} ATTRIBUTE_PACKED audio_attr_t;
+
+
+/**
+ * MultiChannel Extension
+ */
+typedef struct {
+  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;
+  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
+   */
+  unsigned char code_mode : 3;
+  unsigned char zero1     : 3;
+  unsigned char type      : 2;
+  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 8U
+
+/**
+ * PGC Program Map
+ */
+typedef uint8_t pgc_program_map_t; 
+
+/**
+ * Cell Playback Information.
+ */
+typedef struct {
+  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;
+  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 {
+  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 */
+} 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 236U
+
+/**
+ * Program Chain Information Search Pointer.
+ */
+typedef struct {
+  uint8_t  entry_id;
+  unsigned int block_mode : 2;
+  unsigned int block_type : 2;
+  unsigned int unknown1   : 4;
+  uint16_t ptl_id_mask;
+  uint32_t pgc_start_byte;
+  pgc_t *pgc;
+} ATTRIBUTE_PACKED pgci_srp_t;
+#define PGCI_SRP_SIZE 8U
+
+/**
+ * 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 8U
+
+/**
+ * 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 8U
+
+/**
+ * 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 8U
+
+/**
+ * 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 8U
+
+/**
+ * VOBU Address Map.
+ */
+typedef struct {
+  uint32_t last_byte;
+  uint32_t *vobu_start_sectors;
+} ATTRIBUTE_PACKED vobu_admap_t;
+#define VOBU_ADMAP_SIZE 4U
+
+
+
+
+/**
+ * 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 {
+  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 */
+} 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 8U
+
+
+/**
+ * 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 8U
+
+/**
+ * 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 8U
+
+/**
+ * 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 542U
+#define VTS_ATTRIBUTES_MIN_SIZE 356U
+
+/**
+ * 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 8U
+
+/**
+ * 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 8U
+
+/**
+ * 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 20U
+
+
+/**
+ * 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 8U
+
+
+/**
+ * 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 4U
+
+/**
+ * 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 8U
+
+
+#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/libdvdread/md5.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,411 @@
+/* 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>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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/libdvdread/md5.h	Sun Jun 01 08:39:07 2008 +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/libdvdread/nav_print.c	Sun Jun 01 08:39:07 2008 +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/libdvdread/nav_print.h	Sun Jun 01 08:39:07 2008 +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 "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/libdvdread/nav_read.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,265 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "bswap.h"
+#include "nav_types.h"
+#include "nav_read.h"
+#include "dvdread_internal.h"
+#include "bitreader.h"
+
+#define getbits_init dvdread_getbits_init
+#define getbits dvdread_getbits
+
+void navRead_PCI(pci_t *pci, unsigned char *buffer) {
+  int32_t i, j;
+  getbits_state_t state;
+  if (!getbits_init(&state, buffer)) abort(); /* Passed NULL pointers */
+
+  /* pci pci_gi */
+  pci->pci_gi.nv_pck_lbn = getbits(&state, 32 );
+  pci->pci_gi.vobu_cat = getbits(&state, 16 );
+  pci->pci_gi.zero1 = getbits(&state, 16 );
+  pci->pci_gi.vobu_uop_ctl.zero = getbits(&state, 7 );
+  pci->pci_gi.vobu_uop_ctl.video_pres_mode_change         = getbits(&state, 1 );
+
+  pci->pci_gi.vobu_uop_ctl.karaoke_audio_pres_mode_change = getbits(&state, 1 ); 
+  pci->pci_gi.vobu_uop_ctl.angle_change                   = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.subpic_stream_change           = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.audio_stream_change            = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.pause_on                       = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.still_off                      = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.button_select_or_activate      = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.resume                         = getbits(&state, 1 );
+
+  pci->pci_gi.vobu_uop_ctl.chapter_menu_call              = getbits(&state, 1 ); 
+  pci->pci_gi.vobu_uop_ctl.angle_menu_call                = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.audio_menu_call                = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.subpic_menu_call               = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.root_menu_call                 = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.title_menu_call                = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.backward_scan                  = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.forward_scan                   = getbits(&state, 1 );
+
+  pci->pci_gi.vobu_uop_ctl.next_pg_search                 = getbits(&state, 1 ); 
+  pci->pci_gi.vobu_uop_ctl.prev_or_top_pg_search          = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.time_or_chapter_search         = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.go_up                          = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.stop                           = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.title_play                     = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.chapter_search_or_play         = getbits(&state, 1 );
+  pci->pci_gi.vobu_uop_ctl.title_or_time_play             = getbits(&state, 1 );
+  pci->pci_gi.vobu_s_ptm = getbits(&state, 32 ); 
+  pci->pci_gi.vobu_e_ptm = getbits(&state, 32 ); 
+  pci->pci_gi.vobu_se_e_ptm = getbits(&state, 32 ); 
+  pci->pci_gi.e_eltm.hour   = getbits(&state, 8 );
+  pci->pci_gi.e_eltm.minute = getbits(&state, 8 );
+  pci->pci_gi.e_eltm.second = getbits(&state, 8 );
+  pci->pci_gi.e_eltm.frame_u = getbits(&state, 8 );
+  for(i = 0; i < 32; i++)
+    pci->pci_gi.vobu_isrc[i] = getbits(&state, 8 );
+
+  /* pci nsml_agli */
+  for(i = 0; i < 9; i++)
+    pci->nsml_agli.nsml_agl_dsta[i] = getbits(&state, 32 );
+
+  /* pci hli hli_gi */
+  pci->hli.hl_gi.hli_ss = getbits(&state, 16 );
+  pci->hli.hl_gi.hli_s_ptm = getbits(&state, 32 ); 
+  pci->hli.hl_gi.hli_e_ptm = getbits(&state, 32 );
+  pci->hli.hl_gi.btn_se_e_ptm = getbits(&state, 32 );
+  pci->hli.hl_gi.zero1 = getbits(&state, 2 );
+  pci->hli.hl_gi.btngr_ns = getbits(&state, 2 );
+  pci->hli.hl_gi.zero2 = getbits(&state, 1 );
+  pci->hli.hl_gi.btngr1_dsp_ty = getbits(&state, 3 );
+  pci->hli.hl_gi.zero3 = getbits(&state, 1 );
+  pci->hli.hl_gi.btngr2_dsp_ty = getbits(&state, 3 );
+  pci->hli.hl_gi.zero4 = getbits(&state, 1 );
+  pci->hli.hl_gi.btngr3_dsp_ty = getbits(&state, 3 );
+  pci->hli.hl_gi.btn_ofn = getbits(&state, 8 );
+  pci->hli.hl_gi.btn_ns = getbits(&state, 8 );
+  pci->hli.hl_gi.nsl_btn_ns = getbits(&state, 8 ); 
+  pci->hli.hl_gi.zero5 = getbits(&state, 8 );
+  pci->hli.hl_gi.fosl_btnn = getbits(&state, 8 );
+  pci->hli.hl_gi.foac_btnn = getbits(&state, 8 );
+
+  /* pci hli btn_colit */
+  for(i = 0; i < 3; i++)
+    for(j = 0; j < 2; j++)
+      pci->hli.btn_colit.btn_coli[i][j] = getbits(&state, 32 ); 
+
+  /* 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++) {
+    pci->hli.btnit[i].btn_coln = getbits(&state, 2 );
+    pci->hli.btnit[i].x_start = getbits(&state, 10 );
+    pci->hli.btnit[i].zero1 = getbits(&state, 2 );
+    pci->hli.btnit[i].x_end = getbits(&state, 10 );
+
+    pci->hli.btnit[i].auto_action_mode = getbits(&state, 2 );
+    pci->hli.btnit[i].y_start = getbits(&state, 10 );
+    pci->hli.btnit[i].zero2 = getbits(&state, 2 );
+    pci->hli.btnit[i].y_end = getbits(&state, 10 );
+
+    pci->hli.btnit[i].zero3 = getbits(&state, 2 );
+    pci->hli.btnit[i].up = getbits(&state, 6 );
+    pci->hli.btnit[i].zero4 = getbits(&state, 2 );
+    pci->hli.btnit[i].down = getbits(&state, 6 );
+    pci->hli.btnit[i].zero5 = getbits(&state, 2 );
+    pci->hli.btnit[i].left = getbits(&state, 6 );
+    pci->hli.btnit[i].zero6 = getbits(&state, 2 );
+    pci->hli.btnit[i].right = getbits(&state, 6 );
+    /* pci vm_cmd */
+    for(j = 0; j < 8; j++)
+      pci->hli.btnit[i].cmd.bytes[j] = getbits(&state, 8 );
+  }
+
+
+
+#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;
+  getbits_state_t state;
+  if (!getbits_init(&state, buffer)) abort(); /* Passed NULL pointers */
+
+  /* dsi dsi gi */
+  dsi->dsi_gi.nv_pck_scr = getbits(&state, 32 );
+  dsi->dsi_gi.nv_pck_lbn = getbits(&state, 32 );
+  dsi->dsi_gi.vobu_ea = getbits(&state, 32 );
+  dsi->dsi_gi.vobu_1stref_ea = getbits(&state, 32 );
+  dsi->dsi_gi.vobu_2ndref_ea = getbits(&state, 32 );
+  dsi->dsi_gi.vobu_3rdref_ea = getbits(&state, 32 );
+  dsi->dsi_gi.vobu_vob_idn = getbits(&state, 16 );
+  dsi->dsi_gi.zero1 = getbits(&state, 8 );
+  dsi->dsi_gi.vobu_c_idn = getbits(&state, 8 );
+  dsi->dsi_gi.c_eltm.hour   = getbits(&state, 8 );
+  dsi->dsi_gi.c_eltm.minute = getbits(&state, 8 );
+  dsi->dsi_gi.c_eltm.second = getbits(&state, 8 );
+  dsi->dsi_gi.c_eltm.frame_u = getbits(&state, 8 );
+
+  /* dsi sml pbi */
+  dsi->sml_pbi.category = getbits(&state, 16 );
+  dsi->sml_pbi.ilvu_ea = getbits(&state, 32 );
+  dsi->sml_pbi.ilvu_sa = getbits(&state, 32 );
+  dsi->sml_pbi.size = getbits(&state, 16 );
+  dsi->sml_pbi.vob_v_s_s_ptm = getbits(&state, 32 );
+  dsi->sml_pbi.vob_v_e_e_ptm = getbits(&state, 32 );
+  for(i = 0; i < 8; i++) {
+    dsi->sml_pbi.vob_a[i].stp_ptm1 = getbits(&state, 32 );
+    dsi->sml_pbi.vob_a[i].stp_ptm2 = getbits(&state, 32 );
+    dsi->sml_pbi.vob_a[i].gap_len1 = getbits(&state, 32 );
+    dsi->sml_pbi.vob_a[i].gap_len2 = getbits(&state, 32 );
+  }
+
+  /* dsi sml agli */
+  for(i = 0; i < 9; i++) {
+    dsi->sml_agli.data[ i ].address = getbits(&state, 32 );
+    dsi->sml_agli.data[ i ].size = getbits(&state, 16 );
+  }
+
+  /* dsi vobu sri */
+  dsi->vobu_sri.next_video = getbits(&state, 32 );
+  for(i = 0; i < 19; i++)
+    dsi->vobu_sri.fwda[i] = getbits(&state, 32 );
+  dsi->vobu_sri.next_vobu = getbits(&state, 32 );
+  dsi->vobu_sri.prev_vobu = getbits(&state, 32 );
+  for(i = 0; i < 19; i++)
+    dsi->vobu_sri.bwda[i] = getbits(&state, 32 );
+  dsi->vobu_sri.prev_video = getbits(&state, 32 );
+
+  /* dsi synci */
+  for(i = 0; i < 8; i++)
+    dsi->synci.a_synca[i] = getbits(&state, 16 );
+  for(i = 0; i < 32; i++)
+    dsi->synci.sp_synca[i] = getbits(&state, 32 );
+
+  
+  /* Asserts */
+
+  /* dsi dsi gi */
+  CHECK_VALUE(dsi->dsi_gi.zero1 == 0);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdvdread/nav_read.h	Sun Jun 01 08:39:07 2008 +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 "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/libdvdread/nav_types.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,273 @@
+#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 "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 */
+  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 */
+  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 {
+  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 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 zero3            : 2;  /**< reserved */
+  unsigned int up               : 6;  /**< button index when pressing up */
+  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 */
+  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 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/navigation.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,198 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/time.h>
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+/* Navigation API calls */
+
+dvdnav_status_t dvdnav_still_skip(dvdnav_t *this) {
+  this->position_current.still = 0;
+  this->skip_still = 1;
+  this->sync_wait = 0;
+  this->sync_wait_skip = 1;
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_wait_skip(dvdnav_t *this) {
+  this->sync_wait = 0;
+  this->sync_wait_skip = 1;
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *this, int32_t *titles) {
+  if (!this->vm->vmgi) {
+    printerr("Bad VM state.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  (*titles) = vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts;
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *this, int32_t title, int32_t *parts) {
+  if (!this->vm->vmgi) {
+    printerr("Bad VM state.");
+    return DVDNAV_STATUS_ERR;
+  }
+  if ((title < 1) || (title > vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts) ) {
+    printerr("Passed a title number out of range.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  (*parts) = vm_get_vmgi(this->vm)->tt_srpt->title[title-1].nr_of_ptts;
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_current_title_info(dvdnav_t *this, int32_t *title, int32_t *part) {
+  int32_t retval;
+  
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->vtsi || !this->vm->vmgi) {
+    printerr("Bad VM state.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->started) {
+    printerr("Virtual DVD machine not started.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if ( (this->vm->state.domain == VTSM_DOMAIN)
+      || (this->vm->state.domain == VMGM_DOMAIN) ) {
+    /* Get current Menu ID: into *part. */
+    if(! vm_get_current_menu(this->vm, part)) {
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    if (*part > -1) {
+      *title = 0;
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_OK;
+    }
+  }
+  if (this->vm->state.domain == VTS_DOMAIN) {
+    retval = vm_get_current_title_part(this->vm, title, part);
+    pthread_mutex_unlock(&this->vm_lock);
+    return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
+  }
+  printerr("Not in a title or menu.");
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_title_play(dvdnav_t *this, int32_t title) {
+  return dvdnav_part_play(this, title, 1);
+}
+
+dvdnav_status_t dvdnav_part_play(dvdnav_t *this, int32_t title, int32_t part) {
+  int32_t retval;
+
+  pthread_mutex_lock(&this->vm_lock);
+  if (!this->vm->vmgi) {
+    printerr("Bad VM state.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (!this->started) {
+    /* don't report an error but be nice */
+    vm_start(this->vm);
+    this->started = 1;
+  }
+  if (!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if((title < 1) || (title > this->vm->vmgi->tt_srpt->nr_of_srpts)) {
+    printerr("Title out of range.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if((part < 1) || (part > this->vm->vmgi->tt_srpt->title[title-1].nr_of_ptts)) {
+    printerr("Part out of range.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  retval = vm_jump_title_part(this->vm, title, part);
+  if (retval)
+    this->vm->hop_channel++;
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *this, int32_t title,
+					   int32_t part, int32_t parts_to_play) {
+  /* FIXME: Implement auto-stop */
+ if (dvdnav_part_play(this, title, part) == DVDNAV_STATUS_OK)
+   printerr("Not implemented yet.");
+ return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_time_play(dvdnav_t *this, int32_t title,
+				 uint64_t time) {
+  /* FIXME: Implement */
+  printerr("Not implemented yet.");
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_stop(dvdnav_t *this) {
+  pthread_mutex_lock(&this->vm_lock);
+  this->vm->stopped = 1;
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_go_up(dvdnav_t *this) {
+  /* A nice easy function... delegate to the VM */
+  pthread_mutex_lock(&this->vm_lock);
+  vm_jump_up(this->vm);
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/read_cache.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ *               2001-2004 the dvdnav project
+ *
+ * This file is part of libdvdnav, a DVD navigation library.
+ *
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+/*
+ * There was a multithreaded read ahead cache in here for some time, but
+ * it had only been used for a short time. If you want to have a look at it,
+ * search the CVS attic.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <time.h>
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+#include "read_cache.h"
+
+#define READ_CACHE_CHUNKS 10
+
+/* all cache chunks must be memory aligned to allow use of raw devices */
+#define ALIGNMENT 2048
+
+#define READ_AHEAD_SIZE_MIN 4
+#define READ_AHEAD_SIZE_MAX 512
+
+typedef struct read_cache_chunk_s {
+  uint8_t     *cache_buffer;
+  uint8_t     *cache_buffer_base;  /* used in malloc and free for alignment */
+  int32_t      cache_start_sector; /* -1 means cache invalid */
+  int32_t      cache_read_count;   /* this many sectors are already read */
+  size_t       cache_block_count;  /* this many sectors will go in this chunk */
+  size_t       cache_malloc_size;
+  int          cache_valid;
+  int          usage_count;  /* counts how many buffers where issued from this chunk */
+} read_cache_chunk_t;
+
+struct read_cache_s {
+  read_cache_chunk_t  chunk[READ_CACHE_CHUNKS];
+  int                 current;
+  int                 freeing;  /* is set to one when we are about to dispose the cache */
+  uint32_t            read_ahead_size;
+  int                 read_ahead_incr;
+  int                 last_sector;
+  pthread_mutex_t     lock;
+
+  /* Bit of strange cross-linking going on here :) -- Gotta love C :) */
+  dvdnav_t           *dvd_self;
+};
+
+/*
+#define READ_CACHE_TRACE 0
+*/
+
+#ifdef __GNUC__
+# if READ_CACHE_TRACE
+#  define dprintf(fmt, args...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt,  __func__ , ## args)
+# else
+#  define dprintf(fmt, args...) /* Nowt */
+# endif
+#else
+# if READ_CACHE_TRACE
+#  define dprintf(fmt, ...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt,  __func__ , __VA_ARGS__)
+# else
+#ifdef _MSC_VER
+#  define dprintf(fmt, str) /* Nowt */
+#else
+#  define dprintf(fmt, ...) /* Nowt */
+#endif /* _MSC_VER */
+# endif
+#endif
+
+
+read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) {
+  read_cache_t *self;
+  int i;
+
+  self = (read_cache_t *)malloc(sizeof(read_cache_t));
+
+  if(self) {
+    self->current = 0;
+    self->freeing = 0;
+    self->dvd_self = dvd_self;
+    self->last_sector = 0;
+    self->read_ahead_size = READ_AHEAD_SIZE_MIN;
+    self->read_ahead_incr = 0;
+    pthread_mutex_init(&self->lock, NULL);
+    dvdnav_read_cache_clear(self);
+    for (i = 0; i < READ_CACHE_CHUNKS; i++) {
+      self->chunk[i].cache_buffer = NULL;
+      self->chunk[i].usage_count = 0;
+    }
+  }
+
+  return self;
+}
+
+void dvdnav_read_cache_free(read_cache_t* self) {
+  dvdnav_t *tmp;
+  int i;
+
+  pthread_mutex_lock(&self->lock);
+  self->freeing = 1;
+  for (i = 0; i < READ_CACHE_CHUNKS; i++)
+    if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) {
+      free(self->chunk[i].cache_buffer_base);
+      self->chunk[i].cache_buffer = NULL;
+    }
+  pthread_mutex_unlock(&self->lock);
+
+  for (i = 0; i < READ_CACHE_CHUNKS; i++)
+    if (self->chunk[i].cache_buffer) return;
+
+  /* all buffers returned, free everything */
+  tmp = self->dvd_self;
+  pthread_mutex_destroy(&self->lock);
+  free(self);
+  free(tmp);
+}
+
+/* This function MUST be called whenever self->file changes. */
+void dvdnav_read_cache_clear(read_cache_t *self) {
+  int i;
+
+  if(!self)
+   return;
+
+  pthread_mutex_lock(&self->lock);
+  for (i = 0; i < READ_CACHE_CHUNKS; i++)
+    self->chunk[i].cache_valid = 0;
+  pthread_mutex_unlock(&self->lock);
+}
+
+/* This function is called just after reading the NAV packet. */
+void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) {
+  int i, use;
+
+  if(!self)
+    return;
+
+  if(!self->dvd_self->use_read_ahead)
+    return;
+
+  pthread_mutex_lock(&self->lock);
+
+  /* find a free cache chunk that best fits the required size */
+  use = -1;
+  for (i = 0; i < READ_CACHE_CHUNKS; i++)
+    if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
+        self->chunk[i].cache_malloc_size >= block_count &&
+        (use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size))
+      use = i;
+
+  if (use == -1) {
+    /* we haven't found a cache chunk, so we try to reallocate an existing one */
+    for (i = 0; i < READ_CACHE_CHUNKS; i++)
+      if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
+          (use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size))
+        use = i;
+    if (use >= 0) {
+      self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base,
+        block_count * DVD_VIDEO_LB_LEN + ALIGNMENT);
+      self->chunk[use].cache_buffer =
+        (uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
+      dprintf("pre_cache DVD read realloc happened\n");
+      self->chunk[use].cache_malloc_size = block_count;
+    } else {
+      /* we still haven't found a cache chunk, let's allocate a new one */
+      for (i = 0; i < READ_CACHE_CHUNKS; i++)
+        if (!self->chunk[i].cache_buffer) {
+	  use = i;
+	  break;
+	}
+      if (use >= 0) {
+        /* We start with a sensible figure for the first malloc of 500 blocks.
+         * Some DVDs I have seen venture to 450 blocks.
+         * This is so that fewer realloc's happen if at all.
+         */
+	self->chunk[i].cache_buffer_base =
+	  malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT);
+	self->chunk[i].cache_buffer =
+	  (uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
+	self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500;
+	dprintf("pre_cache DVD read malloc %d blocks\n",
+	  (block_count > 500 ? block_count : 500 ));
+      }
+    }
+  }
+
+  if (use >= 0) {
+    self->chunk[use].cache_start_sector = sector;
+    self->chunk[use].cache_block_count = block_count;
+    self->chunk[use].cache_read_count = 0;
+    self->chunk[use].cache_valid = 1;
+    self->current = use;
+  } else {
+    dprintf("pre_caching was impossible, no cache chunk available\n");
+  }
+  pthread_mutex_unlock(&self->lock);
+}
+
+int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) {
+  int i, use;
+  int start;
+  int size;
+  int incr;
+  uint8_t *read_ahead_buf;
+  int32_t res;
+
+  if(!self)
+    return 0;
+
+  use = -1;
+
+  if(self->dvd_self->use_read_ahead) {
+    /* first check, if sector is in current chunk */
+    read_cache_chunk_t cur = self->chunk[self->current];
+    if (cur.cache_valid && sector >= cur.cache_start_sector &&
+        sector <= (cur.cache_start_sector + cur.cache_read_count) &&
+        sector + block_count <= cur.cache_start_sector + cur.cache_block_count)
+      use = self->current;
+    else
+      for (i = 0; i < READ_CACHE_CHUNKS; i++)
+        if (self->chunk[i].cache_valid &&
+            sector >= self->chunk[i].cache_start_sector &&
+            sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) &&
+            sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count)
+            use = i;
+  }
+
+  if (use >= 0) {
+    read_cache_chunk_t *chunk;
+    
+    /* Increment read-ahead size if sector follows the last sector */
+    if (sector == (self->last_sector + 1)) {
+      if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX)
+        self->read_ahead_incr++;
+    } else {
+      self->read_ahead_size = READ_AHEAD_SIZE_MIN;
+      self->read_ahead_incr = 0;
+    }
+    self->last_sector = sector;
+
+    /* The following resources need to be protected by a mutex :
+     *   self->chunk[*].cache_buffer
+     *   self->chunk[*].cache_malloc_size
+     *   self->chunk[*].usage_count
+     */
+    pthread_mutex_lock(&self->lock);
+    chunk = &self->chunk[use];
+    read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN;
+    *buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN;
+    chunk->usage_count++;
+    pthread_mutex_unlock(&self->lock);
+
+    dprintf("libdvdnav: sector=%d, start_sector=%d, last_sector=%d\n", sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count);
+
+    /* read_ahead_size */
+    incr = self->read_ahead_incr >> 1;
+    if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) {
+      self->read_ahead_size = READ_AHEAD_SIZE_MAX;
+    } else {
+      self->read_ahead_size += incr;
+    }
+
+    /* real read size */
+    start = chunk->cache_start_sector + chunk->cache_read_count;
+    if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) {
+      size = chunk->cache_block_count - chunk->cache_read_count;
+    } else {
+      size = self->read_ahead_size;
+      /* ensure that the sector we want will be read */
+      if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size)
+        size = sector - chunk->cache_start_sector - chunk->cache_read_count;
+    }
+    dprintf("libdvdnav: read_ahead_size=%d, size=%d\n", self->read_ahead_size, size);
+
+    if (size)
+      chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file,
+                                               start,
+                                               size,
+                                               read_ahead_buf);
+
+    res = DVD_VIDEO_LB_LEN * block_count;
+
+  } else {
+
+    if (self->dvd_self->use_read_ahead)
+      dprintf("cache miss on sector %d\n", sector);
+
+    res = DVDReadBlocks(self->dvd_self->file,
+                        sector,
+                        block_count,
+                        *buf) * DVD_VIDEO_LB_LEN;
+  }
+
+  return res;
+
+}
+
+dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) {
+  read_cache_t *cache;
+  int i;
+
+  if (!self)
+    return DVDNAV_STATUS_ERR;
+
+  cache = self->cache;
+  if (!cache)
+    return DVDNAV_STATUS_ERR;
+
+  pthread_mutex_lock(&cache->lock);
+  for (i = 0; i < READ_CACHE_CHUNKS; i++) {
+    if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer &&
+        buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) {
+      cache->chunk[i].usage_count--;
+    }
+  }
+  pthread_mutex_unlock(&cache->lock);
+
+  if (cache->freeing)
+    /* when we want to dispose the cache, try freeing it now */
+    dvdnav_read_cache_free(cache);
+
+  return DVDNAV_STATUS_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/read_cache.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,49 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifndef __DVDNAV_READ_CACHE_H
+#define __DVDNAV_READ_CACHE_H
+
+/* Opaque cache type -- defined in dvdnav_internal.h */
+/* typedef struct read_cache_s read_cache_t; */
+
+/* EXPERIMENTAL: Setting the following to 1 will use an experimental multi-threaded
+ *               read-ahead cache. 
+ */
+#define _MULTITHREAD_ 0
+
+/* Constructor/destructors */
+read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self);
+void dvdnav_read_cache_free(read_cache_t* self);
+
+/* This function MUST be called whenever self->file changes. */
+void dvdnav_read_cache_clear(read_cache_t *self);
+/* This function is called just after reading the NAV packet. */
+void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count);
+/* This function will do the cache read.
+ * The buffer handed in must be malloced to take one dvd block.
+ * On a cache hit, a different buffer will be returned though.
+ * Those buffers must _never_ be freed. */
+int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf);
+
+#endif /* __DVDNAV_READ_CACHE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remap.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,265 @@
+/*
+ * This file is part of libdvdnav, a DVD navigation library.
+ *
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef _MSC_VER 
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#else
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 255
+#endif
+#endif /* _MSC_VER */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sys/time.h>
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+struct block_s {
+    int domain;
+    int title;
+    int program;
+    unsigned long start_block;
+    unsigned long end_block;
+};
+
+struct remap_s {
+    char *title;
+    int maxblocks;
+    int nblocks;
+    int debug;
+    struct block_s *blocks;
+};
+
+static remap_t* remap_new( char *title) {
+    remap_t *map = malloc( sizeof(remap_t));
+    map->title = strdup(title);
+    map->maxblocks = 0;
+    map->nblocks = 0;
+    map->blocks = NULL;
+    map->debug = 0;
+    return map;
+}
+
+static int compare_block( block_t *a, block_t *b) {
+    /* returns -1 if a precedes b, 1 if a follows b, and 0 if a and b overlap */
+    if (a->domain < b->domain) {
+	return -1;
+    } else if (a->domain > b->domain) {
+	return 1;
+    }
+
+    if (a->title < b->title) {
+	return -1;
+    } else if (a->title > b->title) {
+	return 1;
+    }
+
+    if (a->program < b->program) {
+	return -1;
+    } else if (a->program > b->program) {
+	return 1;
+    }
+
+    if (a->end_block < b->start_block) {
+	return -1;
+    } else if (a->start_block > b->end_block) {
+	/*
+	 * if a->start_block == b->end_block then the two regions
+	 * aren't strictly overlapping, but they should be merged 
+	 * anyway since there are zero blocks between them
+	 */
+	return 1;
+    }
+
+    return 0;
+}
+
+static block_t *findblock( remap_t *map, block_t *key) {
+    int lb = 0;
+    int ub = map->nblocks - 1;
+    int mid;
+    int res;
+
+    while (lb <= ub) {
+	mid = lb + (ub - lb)/2;
+	res = compare_block( key, &map->blocks[mid]);
+	if (res < 0) {
+	    ub = mid-1;
+	} else if (res > 0) {
+	    lb = mid+1;
+	} else {
+	    return &map->blocks[mid];
+	}
+    }
+    return NULL;
+}
+
+static void mergeblock( block_t *b, block_t tmp) {
+    if (tmp.start_block < b->start_block) b->start_block = tmp.start_block;
+    if (tmp.end_block > b->end_block) b->end_block = tmp.end_block;
+}
+
+static void remap_add_node( remap_t *map, block_t block) {
+    block_t *b;
+    int n;
+    b = findblock( map, &block);
+    if (b) {
+	/* overlaps an existing block */
+	mergeblock( b, block);
+    } else {
+        /* new block */
+	if (map->nblocks >= map->maxblocks) {
+	    map->maxblocks += 20;
+	    map->blocks = realloc( map->blocks, sizeof( block_t)*map->maxblocks);
+	}
+	n = map->nblocks++;
+	while (n > 0 && compare_block( &block, &map->blocks[ n-1]) < 0) {
+	    map->blocks[ n] = map->blocks[ n-1];
+	    n--;
+	}
+	map->blocks[ n] = block;
+    }
+}
+
+static int parseblock(char *buf, int *dom, int *tt, int *pg, 
+		      unsigned long *start, unsigned long *end) {
+    long tmp;
+    char *tok;
+    char *epos;
+    char *marker[]={"domain", "title", "program", "start", "end"};
+    int st = 0;
+    tok = strtok( buf, " ");
+    while (st < 5) {
+        if (strcmp(tok, marker[st])) return -st-1000;
+        tok = strtok( NULL, " ");
+        if (!tok) return -st-2000;
+        tmp = strtol( tok, &epos, 0);
+        if (*epos != 0 && *epos != ',') return -st-3000;
+        switch (st) {
+	    case 0:
+		*dom = (int)tmp;
+		break;
+	    case 1:
+		*tt = (int)tmp;
+		break;
+	    case 2:
+		*pg = (int)tmp;
+		break;
+	    case 3:
+		*start = tmp;
+		break;
+	    case 4:
+		*end = tmp;
+		break;
+	} 
+	st++;
+        tok = strtok( NULL, " ");
+    }
+    return st;
+}
+
+remap_t* remap_loadmap( char *title) {
+    char buf[160];
+    char fname[MAXPATHLEN];
+    char *home;
+    int res;
+    FILE *fp;
+    block_t tmp;
+    remap_t *map;
+
+    /* Build the map filename */
+    home = getenv("HOME"); assert(home);
+    snprintf(fname, sizeof(fname), "%s/.dvdnav/%s.map", home, title);
+
+    /* Open the map file */
+    fp = fopen( fname, "r");
+    if (!fp) {
+	fprintf(MSG_OUT, "libdvdnav: Unable to find map file '%s'\n", fname);
+	return NULL;
+    }
+
+    /* Load the map file */
+    map = remap_new( title);
+    while (fgets( buf, sizeof(buf), fp) != NULL) {
+        if (buf[0] == '\n' || buf[0] == '#' || buf[0] == 0) continue;
+        if (strncasecmp( buf, "debug", 5) == 0) {
+	    map->debug = 1;
+	} else {
+	    res = parseblock( buf, 
+		&tmp.domain, &tmp.title, &tmp.program, &tmp.start_block, &tmp.end_block);
+	    if (res != 5) {
+		fprintf(MSG_OUT, "libdvdnav: Ignoring map line (%d): %s\n", res, buf);
+		continue;
+	    }
+	    remap_add_node( map, tmp);
+	}
+    }
+    fclose(fp);
+
+    if (map->nblocks == 0 && map->debug == 0) {
+        free(map);
+        return NULL;
+    }
+    return map;
+}
+
+unsigned long remap_block( 
+	remap_t *map, int domain, int title, int program, 
+	unsigned long cblock, unsigned long offset) 
+{
+    block_t key;
+    block_t *b;
+
+    if (map->debug) {
+	fprintf(MSG_OUT, "libdvdnav: %s: domain %d, title %d, program %d, start %lx, next %lx\n",
+	    map->title, domain, title, program, cblock, cblock+offset);
+    }
+
+    key.domain = domain;
+    key.title = title;
+    key.program = program;
+    key.start_block = key.end_block = cblock + offset;
+    b = findblock( map, &key);
+    
+    if (b) {
+       if (map->debug) {
+	   fprintf(MSG_OUT, "libdvdnav: Redirected to %lx\n", b->end_block);
+       }
+       return b->end_block - cblock;
+    }
+    return offset;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/remap.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,33 @@
+/* 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ */
+
+#ifndef __REMAP__H
+#define __REMAP__H
+typedef struct block_s block_t;
+
+typedef struct remap_s remap_t;
+
+remap_t* remap_loadmap( char *title);
+
+unsigned long remap_block( 
+	remap_t *map, int domain, int title, int program, 
+	unsigned long cblock, unsigned long offset);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/searching.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+/*
+#define LOG_DEBUG
+*/
+
+/* Searching API calls */
+
+/* Scan the ADMAP for a particular block number. */
+/* Return placed in vobu. */
+/* Returns error status */
+/* FIXME: Maybe need to handle seeking outside current cell. */
+static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, uint32_t *vobu) {
+  vobu_admap_t *admap = NULL;
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block);
+#endif
+  *vobu = -1;
+
+  /* Search through the VOBU_ADMAP for the nearest VOBU
+   * to the target block */
+  switch(domain) {
+  case FP_DOMAIN:
+  case VMGM_DOMAIN:
+    admap = this->vm->vmgi->menu_vobu_admap;
+    break;
+  case VTSM_DOMAIN:
+    admap = this->vm->vtsi->menu_vobu_admap;
+    break;
+  case VTS_DOMAIN:
+    admap = this->vm->vtsi->vts_vobu_admap;
+    break;
+  default:
+    fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n");
+  }
+  if(admap) {
+    uint32_t address = 0;
+    uint32_t vobu_start, next_vobu;
+    int admap_entries = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)/VOBU_ADMAP_SIZE;
+
+    /* Search through ADMAP for best sector */
+    vobu_start = SRI_END_OF_CELL;
+    /* FIXME: Implement a faster search algorithm */
+    while(address < admap_entries) {
+      next_vobu = admap->vobu_start_sectors[address];
+
+      /* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */
+
+      if(vobu_start <= seekto_block && next_vobu > seekto_block)
+        break;
+      vobu_start = next_vobu;
+      address++;
+    }
+    *vobu = vobu_start;
+    return DVDNAV_STATUS_OK;
+  }
+  fprintf(MSG_OUT, "libdvdnav: admap not located\n");
+  return DVDNAV_STATUS_ERR;
+}
+
+/* FIXME: right now, this function does not use the time tables but interpolates
+   only the cell times */
+dvdnav_status_t dvdnav_time_search(dvdnav_t *this,
+				   uint64_t time) {
+  
+  uint64_t target = time;
+  uint64_t length = 0;
+  uint32_t first_cell_nr, last_cell_nr, cell_nr;
+  int32_t found;
+  cell_playback_t *cell;
+  dvd_state_t *state;
+
+  if(this->position_current.still != 0) {
+    printerr("Cannot seek in a still frame.");
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock);
+  state = &(this->vm->state);
+  if(!state->pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  
+  this->cur_cell_time = 0;  
+  if (this->pgc_based) {
+    first_cell_nr = 1;
+    last_cell_nr = state->pgc->nr_of_cells;
+  } else {
+    /* Find start cell of program. */
+    first_cell_nr = state->pgc->program_map[state->pgN-1];
+    /* Find end cell of program */
+    if(state->pgN < state->pgc->nr_of_programs)
+      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+    else
+      last_cell_nr = state->pgc->nr_of_cells;
+  }
+
+  found = 0;
+  for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
+    cell =  &(state->pgc->cell_playback[cell_nr-1]);
+    if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL)
+      continue;
+    length = dvdnav_convert_time(&cell->playback_time);
+    if (target >= length) {
+      target -= length;
+    } else {
+      /* FIXME: there must be a better way than interpolation */
+      target = target * (cell->last_sector - cell->first_sector + 1) / length;
+      target += cell->first_sector;
+      
+      found = 1;
+      break;
+    }
+  }
+
+  if(found) {
+    uint32_t vobu;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
+	    cell_nr, first_cell_nr, last_cell_nr);
+#endif
+    if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) {
+      uint32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
+      
+      if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
+#ifdef LOG_DEBUG
+        fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
+          state->cellN, state->blockN, target, vobu, start);
+#endif
+        this->vm->hop_channel += HOP_SEEK;
+        pthread_mutex_unlock(&this->vm_lock);
+        return DVDNAV_STATUS_OK;
+      }
+    }
+  }
+  
+  fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
+  printerr("Error when seeking.");
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_sector_search(dvdnav_t *this,
+				     uint64_t offset, int32_t origin) {
+  uint32_t target = 0;
+  uint32_t length = 0;
+  uint32_t first_cell_nr, last_cell_nr, cell_nr;
+  int32_t found;
+  cell_playback_t *cell;
+  dvd_state_t *state;
+  dvdnav_status_t result;
+
+  if(this->position_current.still != 0) {
+    printerr("Cannot seek in a still frame.");
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  result = dvdnav_get_position(this, &target, &length);
+  if(!result) {
+    return DVDNAV_STATUS_ERR;
+  }
+ 
+  pthread_mutex_lock(&this->vm_lock);
+  state = &(this->vm->state);
+  if(!state->pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length); 
+  fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
+#endif
+
+  switch(origin) {
+   case SEEK_SET:
+    if(offset >= length) {
+      printerr("Request to seek behind end.");
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    target = offset;
+    break;
+   case SEEK_CUR:
+    if(target + offset >= length) {
+      printerr("Request to seek behind end.");
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    target += offset;
+    break;
+   case SEEK_END:
+    if(length < offset) {
+      printerr("Request to seek before start.");
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+    target = length - offset;
+    break;
+   default:
+    /* Error occured */
+    printerr("Illegal seek mode.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  this->cur_cell_time = 0;
+  if (this->pgc_based) {
+    first_cell_nr = 1;
+    last_cell_nr = state->pgc->nr_of_cells;
+  } else {
+    /* Find start cell of program. */
+    first_cell_nr = state->pgc->program_map[state->pgN-1];
+    /* Find end cell of program */
+    if(state->pgN < state->pgc->nr_of_programs)
+      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+    else
+      last_cell_nr = state->pgc->nr_of_cells;
+  }
+
+  found = 0;
+  for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
+    cell =  &(state->pgc->cell_playback[cell_nr-1]);
+    if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL)
+      continue;
+    length = cell->last_sector - cell->first_sector + 1;
+    if (target >= length) {
+      target -= length;
+    } else {
+      /* convert the target sector from Cell-relative to absolute physical sector */
+      target += cell->first_sector;
+      found = 1;
+      break;
+    }
+  }
+
+  if(found) {
+    int32_t vobu;
+#ifdef LOG_DEBUG
+    fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
+	    cell_nr, first_cell_nr, last_cell_nr);
+#endif
+    if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) {
+      int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
+      
+      if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
+#ifdef LOG_DEBUG
+        fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
+          state->cellN, state->blockN, target, vobu, start);
+#endif
+        this->vm->hop_channel += HOP_SEEK;
+        pthread_mutex_unlock(&this->vm_lock);
+        return DVDNAV_STATUS_OK;
+      }
+    }
+  }
+  
+  fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
+  fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", target); 
+  printerr("Error when seeking.");
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) {
+  int32_t title, old_part;
+  
+  if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK)
+    return dvdnav_part_play(this, title, part);
+  return DVDNAV_STATUS_ERR;
+}
+
+dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) {
+  pthread_mutex_lock(&this->vm_lock);
+  if(!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: previous chapter\n");
+#endif
+  if (!vm_jump_prev_pg(this->vm)) {
+    fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n");
+    printerr("Skip to previous chapter failed.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  this->cur_cell_time = 0;
+  this->position_current.still = 0;
+  this->vm->hop_channel++;
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: previous chapter done\n");
+#endif
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) {
+  pthread_mutex_lock(&this->vm_lock);
+  if(!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: top chapter\n");
+#endif
+  if (!vm_jump_top_pg(this->vm)) {
+    fprintf(MSG_OUT, "libdvdnav: top chapter failed.\n");
+    printerr("Skip to top chapter failed.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  this->cur_cell_time = 0;
+  this->position_current.still = 0;
+  this->vm->hop_channel++;
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: top chapter done\n");
+#endif
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) {
+  vm_t *try_vm;
+
+  pthread_mutex_lock(&this->vm_lock);
+  if(!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: next chapter\n");
+#endif
+  /* make a copy of current VM and try to navigate the copy to the next PG */
+  try_vm = vm_new_copy(this->vm);
+  if (!vm_jump_next_pg(try_vm) || try_vm->stopped) {
+    vm_free_copy(try_vm);
+    /* next_pg failed, try to jump at least to the next cell */
+    try_vm = vm_new_copy(this->vm);
+    vm_get_next_cell(try_vm);
+    if (try_vm->stopped) {
+      vm_free_copy(try_vm);
+      fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n");
+      printerr("Skip to next chapter failed.");
+      pthread_mutex_unlock(&this->vm_lock);
+      return DVDNAV_STATUS_ERR;
+    }
+  }
+  this->cur_cell_time = 0;
+  /* merge changes on success */
+  vm_merge(this->vm, try_vm);
+  vm_free_copy(try_vm);
+  this->position_current.still = 0;
+  this->vm->hop_channel++;
+#ifdef LOG_DEBUG
+  fprintf(MSG_OUT, "libdvdnav: next chapter done\n");
+#endif
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) {
+  vm_t *try_vm;
+  
+  pthread_mutex_lock(&this->vm_lock);
+  if(!this->vm->state.pgc) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  this->cur_cell_time = 0;
+  /* make a copy of current VM and try to navigate the copy to the menu */
+  try_vm = vm_new_copy(this->vm);
+  if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) {
+    /* Try resume */
+    if (vm_jump_resume(try_vm) && !try_vm->stopped) {
+        /* merge changes on success */
+        vm_merge(this->vm, try_vm);
+        vm_free_copy(try_vm);
+        this->position_current.still = 0;
+        this->vm->hop_channel++;
+        pthread_mutex_unlock(&this->vm_lock); 
+        return DVDNAV_STATUS_OK;
+    }
+  }
+  if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root;
+ 
+  if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) {
+    /* merge changes on success */
+    vm_merge(this->vm, try_vm);
+    vm_free_copy(try_vm);
+    this->position_current.still = 0;
+    this->vm->hop_channel++;
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_OK;
+  } else {
+    vm_free_copy(try_vm);
+    printerr("No such menu or menu not reachable.");
+    pthread_mutex_unlock(&this->vm_lock); 
+    return DVDNAV_STATUS_ERR;
+  }
+}
+
+dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos,
+				    uint32_t *len) {
+  uint32_t cur_sector;
+  int32_t cell_nr, first_cell_nr, last_cell_nr;
+  cell_playback_t *cell;
+  dvd_state_t *state;
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  pthread_mutex_lock(&this->vm_lock);
+  state = &(this->vm->state);
+  if(!state->pgc || this->vm->stopped) {
+    printerr("No current PGC.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+  if (this->position_current.hop_channel  != this->vm->hop_channel ||
+      this->position_current.domain       != state->domain         ||
+      this->position_current.vts          != state->vtsN           ||
+      this->position_current.cell_restart != state->cell_restart) {
+    printerr("New position not yet determined.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  /* Get current sector */
+  cur_sector = this->vobu.vobu_start + this->vobu.blockN;
+
+  if (this->pgc_based) {
+    first_cell_nr = 1;
+    last_cell_nr = state->pgc->nr_of_cells;
+  } else {
+    /* Find start cell of program. */
+    first_cell_nr = state->pgc->program_map[state->pgN-1];
+    /* Find end cell of program */
+    if(state->pgN < state->pgc->nr_of_programs)
+      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
+    else
+      last_cell_nr = state->pgc->nr_of_cells;
+  }
+
+  *pos = -1;
+  *len = 0;
+  for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) {
+    cell = &(state->pgc->cell_playback[cell_nr-1]);
+    if (cell_nr == state->cellN) {
+      /* the current sector is in this cell,
+       * pos is length of PG up to here + sector's offset in this cell */
+      *pos = *len + cur_sector - cell->first_sector;
+    }
+    *len += cell->last_sector - cell->first_sector + 1;
+  }
+  
+  assert((signed)*pos != -1);
+
+  pthread_mutex_unlock(&this->vm_lock);
+
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this,
+					     uint32_t *pos,
+					     uint32_t *len) {
+  uint32_t cur_sector;
+  uint32_t first_cell_nr;
+  uint32_t last_cell_nr;
+  cell_playback_t *first_cell;
+  cell_playback_t *last_cell;
+  dvd_state_t *state;
+
+  state = &(this->vm->state);
+  if(!state->pgc) {
+    printerr("No current PGC.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  /* Get current sector */
+  cur_sector = this->vobu.vobu_start + this->vobu.blockN;
+
+  /* Now find first and last cells in title. */
+  first_cell_nr = state->pgc->program_map[0];
+  first_cell = &(state->pgc->cell_playback[first_cell_nr-1]);
+  last_cell_nr = state->pgc->nr_of_cells;
+  last_cell = &(state->pgc->cell_playback[last_cell_nr-1]);
+  
+  *pos = cur_sector - first_cell->first_sector;
+  *len = last_cell->last_sector - first_cell->first_sector;
+  
+  return DVDNAV_STATUS_OK;
+}
+
+uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration) {
+  int32_t retval=0;
+  uint16_t parts, i;
+  title_info_t *ptitle = NULL;
+  ptt_info_t *ptt = NULL;
+  ifo_handle_t *ifo;
+  pgc_t *pgc;
+  cell_playback_t *cell;
+  uint64_t length, *tmp=NULL;
+
+  *times = NULL;
+  *duration = 0;
+  pthread_mutex_lock(&this->vm_lock);
+  if(!this->vm->vmgi) {
+    printerr("Bad VM state or missing VTSI.");
+    goto fail;
+  }
+  if(!this->started) {
+    /* don't report an error but be nice */
+    vm_start(this->vm);
+    this->started = 1;
+  }
+  ifo = vm_get_title_ifo(this->vm, title);
+  if(!ifo || !ifo->vts_pgcit) {
+    printerr("Couldn't open IFO for chosen title, exit.");
+    goto fail;
+  }
+  
+  ptitle = &this->vm->vmgi->tt_srpt->title[title-1];
+  parts = ptitle->nr_of_ptts;
+  ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt;
+  
+  tmp = calloc(1, sizeof(uint64_t)*parts);
+  if(!tmp)
+    goto fail;
+ 
+  length = 0;
+  for(i=0; i<parts; i++) {
+    uint32_t cellnr, endcellnr;
+    pgc = ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc;
+    if(ptt[i].pgn > pgc->nr_of_programs) {
+      printerr("WRONG part number.");
+      goto fail;
+    }
+ 
+    cellnr = pgc->program_map[ptt[i].pgn-1];
+    if(ptt[i].pgn < pgc->nr_of_programs)
+      endcellnr = pgc->program_map[ptt[i].pgn];
+    else
+      endcellnr = 0;
+ 
+    do {
+      cell = &pgc->cell_playback[cellnr-1];
+      if(!(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK &&
+           cell->block_mode != BLOCK_MODE_FIRST_CELL
+      ))
+      {
+        tmp[i] = length + dvdnav_convert_time(&cell->playback_time);
+        length = tmp[i];
+      }
+      cellnr++;
+    } while(cellnr < endcellnr);
+  }
+  *duration = length;
+  vm_ifo_close(ifo);
+  retval = parts;
+  *times = tmp;
+
+fail:
+  pthread_mutex_unlock(&this->vm_lock);
+  if(!retval && tmp)
+    free(tmp);
+  return retval;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/settings.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,97 @@
+/* 
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/time.h>
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "remap.h"
+#include "vm/decoder.h"
+#include "vm/vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+/* Characteristics/setting API calls */
+
+dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *this, int32_t *region) {
+  (*region) = this->vm->state.registers.SPRM[20];
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *this, int32_t mask) {
+  pthread_mutex_lock(&this->vm_lock);
+  this->vm->state.registers.SPRM[20] = (mask & 0xff);
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *this, int32_t use_readahead) {
+  this->use_read_ahead = use_readahead;
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *this, int32_t *flag) {
+  (*flag) = this->use_read_ahead;
+  return DVDNAV_STATUS_OK;
+}
+
+static dvdnav_status_t set_language_register(dvdnav_t *this, char *code, int reg) {
+  if(!code[0] || !code[1]) {
+    printerr("Passed illegal language code.");
+    return DVDNAV_STATUS_ERR;
+  }
+  
+  pthread_mutex_lock(&this->vm_lock);
+  this->vm->state.registers.SPRM[reg] = (code[0] << 8) | code[1];
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *this, char *code) {
+  return set_language_register(this, code, 0);
+}
+
+dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *this, char *code) {
+  return set_language_register(this, code, 16);
+}
+
+dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *this, char *code) {
+  return set_language_register(this, code, 18);
+}
+
+dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *this, int32_t pgc) {
+  this->pgc_based = pgc;
+  return DVDNAV_STATUS_OK;
+}
+
+dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *this, int32_t *flag) {
+  (*flag) = this->pgc_based;
+  return DVDNAV_STATUS_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/Makefile.am	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,16 @@
+include $(top_srcdir)/misc/Makefile.common
+
+includedir = ${prefix}/include/dvdnav
+
+AM_CPPFLAGS = -DDVDNAV_COMPILE $(THREAD_CFLAGS) \
+	-I$(top_srcdir)/src -I$(top_srcdir)/src/libdvdread
+
+noinst_LTLIBRARIES = libdvdvm.la
+
+libdvdvm_la_SOURCES = decoder.c vm.c vmcmd.c
+
+libdvdvm_la_LDFLAGS = $(THREAD_LIBS)
+
+include_HEADERS = 
+
+noinst_HEADERS = decoder.h vm.h vmcmd.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/decoder.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ *               2002-2004 the dvdnav project
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>  /* For memset */
+#include <sys/time.h>
+#include "nav_types.h"
+#include "ifo_types.h" /* vm_cmd_t */
+
+#include "dvd_types.h"
+#include "remap.h"
+#include "decoder.h"
+#include "vm.h"
+#include "vmcmd.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+uint32_t vm_getbits(command_t *command, int32_t start, int32_t count) {
+  uint64_t result = 0;
+  uint64_t bit_mask = 0;
+  uint64_t examining = 0;
+  int32_t  bits;
+  
+  if (count == 0) return 0;
+
+  if ( ((start - count) < -1) ||
+       (count > 32) ||
+       (start > 63) ||
+       (count < 0) ||
+       (start < 0) ) {
+    fprintf(MSG_OUT, "libdvdnav: Bad call to vm_getbits. Parameter out of range\n");
+    abort();
+  }
+  /* all ones, please */
+  bit_mask = ~bit_mask;
+  bit_mask >>= 63 - start;
+  bits = start + 1 - count;
+  examining = ((bit_mask >> bits) << bits );
+  command->examined |= examining;
+  result = (command->instruction & bit_mask) >> bits;
+  return (uint32_t) result;
+}
+
+static uint16_t get_GPRM(registers_t* registers, uint8_t reg) {
+  if (registers->GPRM_mode[reg] & 0x01) {
+    struct timeval current_time, time_offset;
+    uint16_t result;
+    /* Counter mode */
+    /* fprintf(MSG_OUT, "libdvdnav: Getting counter %d\n",reg);*/
+    gettimeofday(&current_time, NULL);
+    time_offset.tv_sec = current_time.tv_sec - registers->GPRM_time[reg].tv_sec;
+    time_offset.tv_usec = current_time.tv_usec - registers->GPRM_time[reg].tv_usec;
+    if (time_offset.tv_usec < 0) { 
+      time_offset.tv_sec--; 
+      time_offset.tv_usec += 1000000;
+    }
+    result = (uint16_t) (time_offset.tv_sec & 0xffff);
+    registers->GPRM[reg]=result;
+    return result; 
+
+  } else {
+    /* Register mode */
+    return registers->GPRM[reg];
+  }
+  
+}
+
+static void set_GPRM(registers_t* registers, uint8_t reg, uint16_t value) {
+  if (registers->GPRM_mode[reg] & 0x01) {
+    struct timeval current_time;
+    /* Counter mode */
+    /* fprintf(MSG_OUT, "libdvdnav: Setting counter %d\n",reg); */
+    gettimeofday(&current_time, NULL);
+    registers->GPRM_time[reg] = current_time;
+    registers->GPRM_time[reg].tv_sec -= value;
+  }
+  registers->GPRM[reg] = value;
+}
+
+/* Eval register code, can either be system or general register.
+   SXXX_XXXX, where S is 1 if it is system register. */
+static uint16_t eval_reg(command_t* command, uint8_t reg) {
+  if(reg & 0x80) {
+    if ((reg & 0x1f) == 20) {
+      fprintf(MSG_OUT, "libdvdnav: Suspected RCE Region Protection!!!\n");
+    }
+    return command->registers->SPRM[reg & 0x1f]; /*  FIXME max 24 not 32 */
+  } else {
+    return get_GPRM(command->registers, reg & 0x0f) ;
+  }
+}
+
+/* Eval register or immediate data.
+   AAAA_AAAA BBBB_BBBB, if immediate use all 16 bits for data else use
+   lower eight bits for the system or general purpose register. */
+static uint16_t eval_reg_or_data(command_t* command, int32_t imm, int32_t start) {
+  if(imm) { /*  immediate */
+    return vm_getbits(command, start, 16);
+  } else {
+    return eval_reg(command, vm_getbits(command, (start - 8), 8));
+  }
+}
+
+/* Eval register or immediate data.
+   xBBB_BBBB, if immediate use all 7 bits for data else use
+   lower four bits for the general purpose register number. */
+/* Evaluates gprm or data depending on bit, data is in byte n */
+static uint16_t eval_reg_or_data_2(command_t* command, 
+				   int32_t imm, int32_t start) {
+  if(imm) /* immediate */
+    return vm_getbits(command, (start - 1), 7);
+  else
+    return get_GPRM(command->registers, (vm_getbits(command, (start - 4), 4)) );
+}
+
+
+/* Compare data using operation, return result from comparison. 
+   Helper function for the different if functions. */
+static int32_t eval_compare(uint8_t operation, uint16_t data1, uint16_t data2) {
+  switch(operation) {
+    case 1:
+      return data1 & data2;
+    case 2:
+      return data1 == data2;
+    case 3:
+      return data1 != data2;
+    case 4:
+      return data1 >= data2;
+    case 5:
+      return data1 >  data2;
+    case 6:
+      return data1 <= data2;
+    case 7:
+      return data1 <  data2;
+  }
+  fprintf(MSG_OUT, "libdvdnav: eval_compare: Invalid comparison code\n");
+  return 0;
+}
+
+
+/* Evaluate if version 1.
+   Has comparison data in byte 3 and 4-5 (immediate or register) */
+static int32_t eval_if_version_1(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(command, vm_getbits(command, 39, 8)), 
+                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 31));
+  }
+  return 1;
+}
+
+/* Evaluate if version 2.
+   This version only compares register which are in byte 6 and 7 */
+static int32_t eval_if_version_2(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(command, vm_getbits(command, 15, 8)), 
+                            eval_reg(command, vm_getbits(command, 7, 8)));
+  }
+  return 1;
+}
+
+/* Evaluate if version 3.
+   Has comparison data in byte 2 and 6-7 (immediate or register) */
+static int32_t eval_if_version_3(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(command, vm_getbits(command, 47, 8)), 
+                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 15));
+  }
+  return 1;
+}
+
+/* Evaluate if version 4.
+   Has comparison data in byte 1 and 4-5 (immediate or register) 
+   The register in byte 1 is only the lowe nibble (4 bits) */
+static int32_t eval_if_version_4(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  if(op) {
+    return eval_compare(op, eval_reg(command, vm_getbits(command, 51, 4)), 
+                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 31));
+  }
+  return 1;
+}
+
+/* Evaluate special instruction.... returns the new row/line number,
+   0 if no new row and 256 if Break. */
+static int32_t eval_special_instruction(command_t* command, int32_t cond) {
+  int32_t line, level;
+  
+  switch(vm_getbits(command, 51, 4)) {
+    case 0: /*  NOP */
+      line = 0;
+      return cond ? line : 0;
+    case 1: /*  Goto line */
+      line = vm_getbits(command, 7, 8);
+      return cond ? line : 0;
+    case 2: /*  Break */
+      /*  max number of rows < 256, so we will end this set */
+      line = 256;
+      return cond ? 256 : 0;
+    case 3: /*  Set temporary parental level and goto */
+      line = vm_getbits(command, 7, 8); 
+      level = vm_getbits(command, 11, 4);
+      if(cond) {
+	/*  This always succeeds now, if we want real parental protection */
+	/*  we need to ask the user and have passwords and stuff. */
+	command->registers->SPRM[13] = level;
+      }
+      return cond ? line : 0;
+  }
+  return 0;
+}
+
+/* Evaluate link by subinstruction.
+   Return 1 if link, or 0 if no link
+   Actual link instruction is in return_values parameter */
+static int32_t eval_link_subins(command_t* command, int32_t cond, link_t *return_values) {
+  uint16_t button = vm_getbits(command, 15, 6);
+  uint8_t  linkop = vm_getbits(command, 4, 5);
+  
+  if(linkop > 0x10)
+    return 0;    /*  Unknown Link by Sub-Instruction command */
+
+  /*  Assumes that the link_cmd_t enum has the same values as the LinkSIns codes */
+  return_values->command = linkop;
+  return_values->data1 = button;
+  return cond;
+}
+
+
+/* Evaluate link instruction.
+   Return 1 if link, or 0 if no link
+   Actual link instruction is in return_values parameter */
+static int32_t eval_link_instruction(command_t* command, int32_t cond, link_t *return_values) {
+  uint8_t op = vm_getbits(command, 51, 4);
+  
+  switch(op) {
+    case 1:
+	return eval_link_subins(command, cond, return_values);
+    case 4:
+	return_values->command = LinkPGCN;
+	return_values->data1   = vm_getbits(command, 14, 15);
+	return cond;
+    case 5:
+	return_values->command = LinkPTTN;
+	return_values->data1 = vm_getbits(command, 9, 10);
+	return_values->data2 = vm_getbits(command, 15, 6);
+	return cond;
+    case 6:
+	return_values->command = LinkPGN;
+	return_values->data1 = vm_getbits(command, 6, 7);
+	return_values->data2 = vm_getbits(command, 15, 6);
+	return cond;
+    case 7:
+	return_values->command = LinkCN;
+	return_values->data1 = vm_getbits(command, 7, 8);
+	return_values->data2 = vm_getbits(command, 15, 6);
+	return cond;
+  }
+  return 0;
+}
+
+
+/* Evaluate a jump instruction.
+   returns 1 if jump or 0 if no jump
+   actual jump instruction is in return_values parameter */
+static int32_t eval_jump_instruction(command_t* command, int32_t cond, link_t *return_values) {
+  
+  switch(vm_getbits(command, 51, 4)) {
+    case 1:
+      return_values->command = Exit;
+      return cond;
+    case 2:
+      return_values->command = JumpTT;
+      return_values->data1 = vm_getbits(command, 22, 7);
+      return cond;
+    case 3:
+      return_values->command = JumpVTS_TT;
+      return_values->data1 = vm_getbits(command, 22, 7);
+      return cond;
+    case 5:
+      return_values->command = JumpVTS_PTT;
+      return_values->data1 = vm_getbits(command, 22, 7);
+      return_values->data2 = vm_getbits(command, 41, 10);
+      return cond;
+    case 6:
+      switch(vm_getbits(command, 23, 2)) {
+        case 0:
+          return_values->command = JumpSS_FP;
+          return cond;
+        case 1:
+          return_values->command = JumpSS_VMGM_MENU;
+          return_values->data1 =  vm_getbits(command, 19, 4);
+          return cond;
+        case 2:
+          return_values->command = JumpSS_VTSM;
+          return_values->data1 =  vm_getbits(command, 31, 8);
+          return_values->data2 =  vm_getbits(command, 39, 8);
+          return_values->data3 =  vm_getbits(command, 19, 4);
+          return cond;
+        case 3:
+          return_values->command = JumpSS_VMGM_PGC;
+          return_values->data1 =  vm_getbits(command, 46, 15);
+          return cond;
+        }
+      break;
+    case 8:
+      switch(vm_getbits(command, 23, 2)) {
+        case 0:
+          return_values->command = CallSS_FP;
+          return_values->data1 = vm_getbits(command, 31, 8);
+          return cond;
+        case 1:
+          return_values->command = CallSS_VMGM_MENU;
+          return_values->data1 = vm_getbits(command, 19, 4);
+          return_values->data2 = vm_getbits(command, 31, 8);
+          return cond;
+        case 2:
+          return_values->command = CallSS_VTSM;
+          return_values->data1 = vm_getbits(command, 19, 4);
+          return_values->data2 = vm_getbits(command, 31, 8);
+          return cond;
+        case 3:
+          return_values->command = CallSS_VMGM_PGC;
+          return_values->data1 = vm_getbits(command, 46, 15);
+          return_values->data2 = vm_getbits(command, 31, 8);
+          return cond;
+      }
+      break;
+  }
+  return 0;
+}
+
+/* Evaluate a set sytem register instruction 
+   May contain a link so return the same as eval_link */
+static int32_t eval_system_set(command_t* command, int32_t cond, link_t *return_values) {
+  int32_t i;
+  uint16_t data, data2;
+  
+  switch(vm_getbits(command, 59, 4)) {
+    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
+      for(i = 1; i <= 3; i++) {
+        if(vm_getbits(command, 63 - ((2 + i)*8), 1)) {
+          data = eval_reg_or_data_2(command, vm_getbits(command, 60, 1), (47 - (i*8)));
+          if(cond) {
+            command->registers->SPRM[i] = data;
+          }
+        }
+      }
+      break;
+    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
+      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+      data2 = vm_getbits(command, 23, 8); /*  ?? size */
+      if(cond) {
+	command->registers->SPRM[9] = data; /*  time */
+	command->registers->SPRM[10] = data2; /*  pgcN */
+      }
+      break;
+    case 3: /*  Mode: Counter / Register + Set */
+      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+      data2 = vm_getbits(command, 19, 4);
+      if(vm_getbits(command, 23, 1)) {
+	command->registers->GPRM_mode[data2] |= 1; /* Set bit 0 */
+      } else {
+	command->registers->GPRM_mode[data2] &= ~ 0x01; /* Reset bit 0 */
+      }
+      if(cond) {
+        set_GPRM(command->registers, data2, data);
+      }
+      break;
+    case 6: /*  Set system reg 8 (Highlighted button) */
+      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31); /*  Not system reg!! */
+      if(cond) {
+	command->registers->SPRM[8] = data;
+      }
+      break;
+  }
+  if(vm_getbits(command, 51, 4)) {
+    return eval_link_instruction(command, cond, return_values);
+  }
+  return 0;
+}
+
+
+/* Evaluate set operation
+   Sets the register given to the value indicated by op and data.
+   For the swap case the contents of reg is stored in reg2.
+*/
+static void eval_set_op(command_t* command, int32_t op, int32_t reg, int32_t reg2, int32_t data) {
+  static const int32_t shortmax = 0xffff;
+  int32_t     tmp; 
+  switch(op) {
+    case 1:
+      set_GPRM(command->registers, reg, data);
+      break;
+    case 2: /* SPECIAL CASE - SWAP! */
+      set_GPRM(command->registers, reg2, get_GPRM(command->registers, reg));
+      set_GPRM(command->registers, reg, data);
+      break;
+    case 3:
+      tmp = get_GPRM(command->registers, reg) + data;
+      if(tmp > shortmax) tmp = shortmax;
+      set_GPRM(command->registers, reg, (uint16_t)tmp);
+      break;
+    case 4:
+      tmp = get_GPRM(command->registers, reg) - data;
+      if(tmp < 0) tmp = 0;
+      set_GPRM(command->registers, reg, (uint16_t)tmp);
+      break;
+    case 5:
+      tmp = get_GPRM(command->registers, reg) * data;
+      if(tmp > shortmax) tmp = shortmax;
+      set_GPRM(command->registers, reg, (uint16_t)tmp);
+      break;
+    case 6:
+      if (data != 0) {
+        set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) / data) );
+      } else {
+        set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */
+      }
+      break;
+    case 7:
+      if (data != 0) {
+        set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) % data) );
+      } else {
+        set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */
+      }
+      break;
+    case 8: /* SPECIAL CASE - RND! Return numbers between 1 and data. */
+      set_GPRM(command->registers, reg, 1 + ((uint16_t) ((float) data * rand()/(RAND_MAX+1.0))) );
+      break;
+    case 9:
+      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) & data) );
+      break;
+    case 10:
+      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) | data) );
+      break;
+    case 11:
+      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) ^ data) );
+      break;
+  }
+}
+
+/* Evaluate set instruction, combined with either Link or Compare. */
+static void eval_set_version_1(command_t* command, int32_t cond) {
+  uint8_t  op   = vm_getbits(command, 59, 4);
+  uint8_t  reg  = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */
+  uint8_t  reg2 = vm_getbits(command, 19, 4);
+  uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31);
+
+  if(cond) {
+    eval_set_op(command, op, reg, reg2, data);
+  }
+}
+
+
+/* Evaluate set instruction, combined with both Link and Compare. */
+static void eval_set_version_2(command_t* command, int32_t cond) {
+  uint8_t  op   = vm_getbits(command, 59, 4);
+  uint8_t  reg  = vm_getbits(command, 51, 4);
+  uint8_t  reg2 = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */
+  uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+
+  if(cond) {
+    eval_set_op(command, op, reg, reg2, data);
+  }
+}
+
+
+/* Evaluate a command
+   returns row number of goto, 0 if no goto, -1 if link.
+   Link command in return_values */
+static int32_t eval_command(uint8_t *bytes, registers_t* registers, link_t *return_values) {
+  int32_t cond, res = 0;
+  command_t command;
+  command.instruction =( (uint64_t) bytes[0] << 56 ) |
+        ( (uint64_t) bytes[1] << 48 ) |
+        ( (uint64_t) bytes[2] << 40 ) |
+        ( (uint64_t) bytes[3] << 32 ) |
+        ( (uint64_t) bytes[4] << 24 ) |
+        ( (uint64_t) bytes[5] << 16 ) |
+        ( (uint64_t) bytes[6] <<  8 ) |
+          (uint64_t) bytes[7] ;
+  command.examined = 0;
+  command.registers = registers;
+  memset(return_values, 0, sizeof(link_t));
+
+  switch(vm_getbits(&command, 63, 3)) { /* three first old_bits */
+    case 0: /*  Special instructions */
+      cond = eval_if_version_1(&command);
+      res = eval_special_instruction(&command, cond);
+      if(res == -1) {
+	fprintf(MSG_OUT, "libdvdnav: Unknown Instruction!\n");
+	abort();
+      }
+      break;
+    case 1: /*  Link/jump instructions */
+      if(vm_getbits(&command, 60, 1)) {
+        cond = eval_if_version_2(&command);
+        res = eval_jump_instruction(&command, cond, return_values);
+      } else {
+        cond = eval_if_version_1(&command);
+        res = eval_link_instruction(&command, cond, return_values);
+      }
+      if(res)
+	res = -1;
+      break;
+    case 2: /*  System set instructions */
+      cond = eval_if_version_2(&command);
+      res = eval_system_set(&command, cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 3: /*  Set instructions, either Compare or Link may be used */
+      cond = eval_if_version_3(&command);
+      eval_set_version_1(&command, cond);
+      if(vm_getbits(&command, 51, 4)) {
+	res = eval_link_instruction(&command, cond, return_values);
+      }
+      if(res)
+	res = -1;
+      break;
+    case 4: /*  Set, Compare -> Link Sub-Instruction */
+      eval_set_version_2(&command, /*True*/ 1);
+      cond = eval_if_version_4(&command);
+      res = eval_link_subins(&command, cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 5: /*  Compare -> (Set and Link Sub-Instruction) */
+      /* FIXME: These are wrong. Need to be updated from vmcmd.c */
+      cond = eval_if_version_4(&command);
+      eval_set_version_2(&command, cond);
+      res = eval_link_subins(&command, cond, return_values);
+      if(res)
+	res = -1;
+      break;
+    case 6: /*  Compare -> Set, allways Link Sub-Instruction */
+      /* FIXME: These are wrong. Need to be updated from vmcmd.c */
+      cond = eval_if_version_4(&command);
+      eval_set_version_2(&command, cond);
+      res = eval_link_subins(&command, /*True*/ 1, return_values);
+      if(res)
+	res = -1;
+      break;
+    default: /* Unknown command */
+      fprintf(MSG_OUT, "libdvdnav: WARNING: Unknown Command=%x\n", vm_getbits(&command, 63, 3));
+      abort();
+  }
+  /*  Check if there are bits not yet examined */
+
+  if(command.instruction & ~ command.examined) {
+    fprintf(MSG_OUT, "libdvdnav: decoder.c: [WARNING, unknown bits:");
+    fprintf(MSG_OUT, " %08"PRIx64, (command.instruction & ~ command.examined) );
+    fprintf(MSG_OUT, "]\n");
+  }
+
+  return res;
+}
+
+/* Evaluate a set of commands in the given register set (which is modified) */
+int32_t vmEval_CMD(vm_cmd_t commands[], int32_t num_commands, 
+	       registers_t *registers, link_t *return_values) {
+  int32_t i = 0;
+  int32_t total = 0;
+  
+#ifdef TRACE
+  /*  DEBUG */
+  fprintf(MSG_OUT, "libdvdnav: Registers before transaction\n");
+  vm_print_registers( registers );
+  fprintf(MSG_OUT, "libdvdnav: Full list of commands to execute\n");
+  for(i = 0; i < num_commands; i++)
+    vm_print_cmd(i, &commands[i]);
+  fprintf(MSG_OUT, "libdvdnav: --------------------------------------------\n");
+  fprintf(MSG_OUT, "libdvdnav: Single stepping commands\n");
+#endif
+
+  i = 0; 
+  while(i < num_commands && total < 100000) {
+    int32_t line;
+    
+#ifdef TRACE
+    vm_print_cmd(i, &commands[i]);
+#endif
+
+    line = eval_command(&commands[i].bytes[0], registers, return_values);
+    
+    if (line < 0) { /*  Link command */
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: Registers after transaction\n");
+      vm_print_registers( registers );
+      fprintf(MSG_OUT, "libdvdnav: eval: Doing Link/Jump/Call\n"); 
+#endif
+      return 1;
+    }
+    
+    if (line > 0) /*  Goto command */
+      i = line - 1;
+    else /*  Just continue on the next line */
+      i++;
+
+    total++;
+  }
+  
+  memset(return_values, 0, sizeof(link_t));
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: Registers after transaction\n");
+  vm_print_registers( registers );
+#endif
+  return 0;
+}
+
+#ifdef TRACE
+
+static char *linkcmd2str(link_cmd_t cmd) {
+  switch(cmd) {
+  case LinkNoLink:
+    return "LinkNoLink";
+  case LinkTopC:
+    return "LinkTopC";
+  case LinkNextC:
+    return "LinkNextC";
+  case LinkPrevC:
+    return "LinkPrevC";
+  case LinkTopPG:
+    return "LinkTopPG";
+  case LinkNextPG:
+    return "LinkNextPG";
+  case LinkPrevPG:
+    return "LinkPrevPG";
+  case LinkTopPGC:
+    return "LinkTopPGC";
+  case LinkNextPGC:
+    return "LinkNextPGC";
+  case LinkPrevPGC:
+    return "LinkPrevPGC";
+  case LinkGoUpPGC:
+    return "LinkGoUpPGC";
+  case LinkTailPGC:
+    return "LinkTailPGC";
+  case LinkRSM:
+    return "LinkRSM";
+  case LinkPGCN:
+    return "LinkPGCN";
+  case LinkPTTN:
+    return "LinkPTTN";
+  case LinkPGN:
+    return "LinkPGN";
+  case LinkCN:
+    return "LinkCN";
+  case Exit:
+    return "Exit";
+  case JumpTT:
+    return "JumpTT";
+  case JumpVTS_TT:
+    return "JumpVTS_TT";
+  case JumpVTS_PTT:
+    return "JumpVTS_PTT";
+  case JumpSS_FP:
+    return "JumpSS_FP";
+  case JumpSS_VMGM_MENU:
+    return "JumpSS_VMGM_MENU";
+  case JumpSS_VTSM:
+    return "JumpSS_VTSM";
+  case JumpSS_VMGM_PGC:
+    return "JumpSS_VMGM_PGC";
+  case CallSS_FP:
+    return "CallSS_FP";
+  case CallSS_VMGM_MENU:
+    return "CallSS_VMGM_MENU";
+  case CallSS_VTSM:
+    return "CallSS_VTSM";
+  case CallSS_VMGM_PGC:
+    return "CallSS_VMGM_PGC";
+  case PlayThis:
+    return "PlayThis";
+  }
+  return "*** (bug)";
+}
+
+void vm_print_link(link_t value) {
+  char *cmd = linkcmd2str(value.command);
+    
+  switch(value.command) {
+  case LinkNoLink:
+  case LinkTopC:
+  case LinkNextC:
+  case LinkPrevC:
+  case LinkTopPG:
+  case LinkNextPG:
+  case LinkPrevPG:
+  case LinkTopPGC:
+  case LinkNextPGC:
+  case LinkPrevPGC:
+  case LinkGoUpPGC:
+  case LinkTailPGC:
+  case LinkRSM:
+    fprintf(MSG_OUT, "libdvdnav: %s (button %d)\n", cmd, value.data1);
+    break;
+  case LinkPGCN:
+  case JumpTT:
+  case JumpVTS_TT:
+  case JumpSS_VMGM_MENU: /*  == 2 -> Title Menu */
+  case JumpSS_VMGM_PGC:
+    fprintf(MSG_OUT, "libdvdnav: %s %d\n", cmd, value.data1);
+    break;
+  case LinkPTTN:
+  case LinkPGN:
+  case LinkCN:
+    fprintf(MSG_OUT, "libdvdnav: %s %d (button %d)\n", cmd, value.data1, value.data2);
+    break;
+  case Exit:
+  case JumpSS_FP:
+  case PlayThis: /*  Humm.. should we have this at all.. */
+    fprintf(MSG_OUT, "libdvdnav: %s\n", cmd);
+    break;
+  case JumpVTS_PTT:
+    fprintf(MSG_OUT, "libdvdnav: %s %d:%d\n", cmd, value.data1, value.data2);
+    break;
+  case JumpSS_VTSM:
+    fprintf(MSG_OUT, "libdvdnav: %s vts %d title %d menu %d\n", 
+	    cmd, value.data1, value.data2, value.data3);
+    break;
+  case CallSS_FP:
+    fprintf(MSG_OUT, "libdvdnav: %s resume cell %d\n", cmd, value.data1);
+    break;
+  case CallSS_VMGM_MENU: /*  == 2 -> Title Menu */
+  case CallSS_VTSM:
+    fprintf(MSG_OUT, "libdvdnav: %s %d resume cell %d\n", cmd, value.data1, value.data2);
+    break;
+  case CallSS_VMGM_PGC:
+    fprintf(MSG_OUT, "libdvdnav: %s %d resume cell %d\n", cmd, value.data1, value.data2);
+    break;
+  }
+ }
+
+void vm_print_registers( registers_t *registers ) {
+  int32_t i;
+  fprintf(MSG_OUT, "libdvdnav:    #   ");
+  for(i = 0; i < 24; i++)
+    fprintf(MSG_OUT, " %2d |", i);
+  fprintf(MSG_OUT, "\nlibdvdnav: SRPMS: ");
+  for(i = 0; i < 24; i++)
+    fprintf(MSG_OUT, "%04x|", registers->SPRM[i]);
+  fprintf(MSG_OUT, "\nlibdvdnav: GRPMS: ");
+  for(i = 0; i < 16; i++)
+    fprintf(MSG_OUT, "%04x|", get_GPRM(registers, i) );
+  fprintf(MSG_OUT, "\nlibdvdnav: Gmode: ");
+  for(i = 0; i < 16; i++)
+    fprintf(MSG_OUT, "%04x|", registers->GPRM_mode[i]);
+  fprintf(MSG_OUT, "\nlibdvdnav: Gtime: ");
+  for(i = 0; i < 16; i++)
+    fprintf(MSG_OUT, "%04lx|", registers->GPRM_time[i].tv_sec & 0xffff);
+  fprintf(MSG_OUT, "\n");
+}
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/decoder.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifndef DECODER_H_INCLUDED
+#define DECODER_H_INCLUDED
+
+/* link command types */
+typedef enum {
+  LinkNoLink  = 0,
+
+  LinkTopC    = 1,
+  LinkNextC   = 2,
+  LinkPrevC   = 3,
+
+  LinkTopPG   = 5,
+  LinkNextPG  = 6,
+  LinkPrevPG  = 7,
+
+  LinkTopPGC  = 9,
+  LinkNextPGC = 10,
+  LinkPrevPGC = 11,
+  LinkGoUpPGC = 12,
+  LinkTailPGC = 13,
+
+  LinkRSM     = 16,
+
+  LinkPGCN,
+  LinkPTTN,
+  LinkPGN,
+  LinkCN,
+
+  Exit,
+
+  JumpTT, /* 22 */
+  JumpVTS_TT,
+  JumpVTS_PTT,
+
+  JumpSS_FP,
+  JumpSS_VMGM_MENU,
+  JumpSS_VTSM,
+  JumpSS_VMGM_PGC,
+
+  CallSS_FP, /* 29 */
+  CallSS_VMGM_MENU,
+  CallSS_VTSM,
+  CallSS_VMGM_PGC,
+
+  PlayThis
+} link_cmd_t;
+
+/* a link's data set */
+typedef struct {
+  link_cmd_t command;
+  uint16_t   data1;
+  uint16_t   data2;
+  uint16_t   data3;
+} link_t;
+
+/* the VM registers */
+typedef struct {
+  uint16_t SPRM[24];
+  uint16_t GPRM[16];
+  uint8_t  GPRM_mode[16];  /* Need to have some thing to indicate normal/counter mode for every GPRM */
+  struct timeval GPRM_time[16]; /* For counter mode */
+} registers_t;
+
+/* a VM command data set */
+typedef struct {
+  uint64_t instruction;
+  uint64_t examined;
+  registers_t *registers;
+} command_t;
+
+/* the big VM function, executing the given commands and writing
+ * the link where to continue, the return value indicates if a jump
+ * has been performed */
+int32_t vmEval_CMD(vm_cmd_t commands[], int32_t num_commands, 
+	       registers_t *registers, link_t *return_values);
+
+/* extracts some bits from the command */
+uint32_t vm_getbits(command_t* command, int32_t start, int32_t count);
+
+#ifdef TRACE
+/* for debugging: prints a link in readable form */
+void vm_print_link(link_t value);
+
+/* for debugging: dumps VM registers */
+void vm_print_registers( registers_t *registers );
+#endif
+
+#endif /* DECODER_H_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/vm.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,1886 @@
+/*
+ * Copyright (C) 2000, 2001 Håkan Hjort
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ *               2002-2004 the dvdnav project
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "ifo_read.h"
+#include "dvd_types.h"
+
+#include "decoder.h"
+#include "remap.h"
+#include "vm.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+#ifdef _MSC_VER
+#include <io.h>   /* read() */
+#endif /* _MSC_VER */
+
+/*
+#define STRICT
+*/
+
+/* Local prototypes */
+
+/* get_XYZ returns a value.
+ * set_XYZ sets state using passed parameters.
+ *         returns success/failure.
+ */
+
+/* Play */
+static link_t play_PGC(vm_t *vm);
+static link_t play_PGC_PG(vm_t *vm, int pgN);
+static link_t play_PGC_post(vm_t *vm);
+static link_t play_PG(vm_t *vm);
+static link_t play_Cell(vm_t *vm);
+static link_t play_Cell_post(vm_t *vm);
+
+/* Process link - returns 1 if a hop has been performed */
+static int process_command(vm_t *vm,link_t link_values);
+
+/* Set */
+static int  set_TT(vm_t *vm, int tt);
+static int  set_PTT(vm_t *vm, int tt, int ptt);
+static int  set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn);
+static int  set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part);
+static int  set_FP_PGC(vm_t *vm);
+static int  set_MENU(vm_t *vm, int menu);
+static int  set_PGCN(vm_t *vm, int pgcN);
+static int  set_PGN(vm_t *vm); /* Set PGN based on (vm->state).CellN */
+static void set_RSMinfo(vm_t *vm, int cellN, int blockN);
+
+/* Get */
+static int get_TT(vm_t *vm, int vtsN, int vts_ttn);
+static int get_ID(vm_t *vm, int id);
+static int get_PGCN(vm_t *vm);
+
+static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang);
+static pgcit_t* get_PGCIT(vm_t *vm);
+
+
+/* Helper functions */
+
+#ifdef TRACE
+static void vm_print_current_domain_state(vm_t *vm) {
+  switch((vm->state).domain) {
+    case VTS_DOMAIN:
+      fprintf(MSG_OUT, "libdvdnav: Video Title Domain: -\n");
+      break;
+
+    case VTSM_DOMAIN:
+      fprintf(MSG_OUT, "libdvdnav: Video Title Menu Domain: -\n");
+      break;
+
+    case VMGM_DOMAIN:
+      fprintf(MSG_OUT, "libdvdnav: Video Manager Menu Domain: -\n");
+      break;
+
+    case FP_DOMAIN: 
+      fprintf(MSG_OUT, "libdvdnav: First Play Domain: -\n");
+      break;
+
+    default:
+      fprintf(MSG_OUT, "libdvdnav: Unknown Domain: -\n");
+      break;
+  }
+  fprintf(MSG_OUT, "libdvdnav: VTS:%d PGC:%d PG:%u CELL:%u BLOCK:%u VTS_TTN:%u TTN:%u TT_PGCN:%u\n", 
+                   (vm->state).vtsN,
+                   get_PGCN(vm),
+                   (vm->state).pgN,
+                   (vm->state).cellN,
+                   (vm->state).blockN,
+                   (vm->state).VTS_TTN_REG,
+                   (vm->state).TTN_REG,
+                   (vm->state).TT_PGCN_REG);
+}
+#endif
+
+static void dvd_read_name(char *name, const char *device) {
+    /* Because we are compiling with _FILE_OFFSET_BITS=64
+     * all off_t are 64bit.
+     */
+    off_t off;
+    int fd, i;
+    uint8_t data[DVD_VIDEO_LB_LEN];
+
+    /* Read DVD name */
+    fd = open(device, O_RDONLY);
+    if (fd > 0) { 
+      off = lseek( fd, 32 * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET );
+      if( off == ( 32 * (off_t) DVD_VIDEO_LB_LEN ) ) {
+        off = read( fd, data, DVD_VIDEO_LB_LEN ); 
+        close(fd);
+        if (off == ( (off_t) DVD_VIDEO_LB_LEN )) {
+          fprintf(MSG_OUT, "libdvdnav: DVD Title: ");
+          for(i=25; i < 73; i++ ) {
+            if((data[i] == 0)) break;
+            if((data[i] > 32) && (data[i] < 127)) {
+              fprintf(MSG_OUT, "%c", data[i]);
+            } else {
+              fprintf(MSG_OUT, " ");
+            }
+          }
+          strncpy(name, &data[25], 48);
+          name[48] = 0;
+          fprintf(MSG_OUT, "\nlibdvdnav: DVD Serial Number: ");
+          for(i=73; i < 89; i++ ) {
+            if((data[i] == 0)) break;
+            if((data[i] > 32) && (data[i] < 127)) {
+              fprintf(MSG_OUT, "%c", data[i]);
+            } else {
+              fprintf(MSG_OUT, " ");
+            } 
+          }
+          fprintf(MSG_OUT, "\nlibdvdnav: DVD Title (Alternative): ");
+          for(i=89; i < 128; i++ ) {
+            if((data[i] == 0)) break;
+            if((data[i] > 32) && (data[i] < 127)) {
+              fprintf(MSG_OUT, "%c", data[i]);
+            } else {
+              fprintf(MSG_OUT, " ");
+            }
+          }
+          fprintf(MSG_OUT, "\n");
+        } else {
+          fprintf(MSG_OUT, "libdvdnav: Can't read name block. Probably not a DVD-ROM device.\n");
+        }
+      } else {
+        fprintf(MSG_OUT, "libdvdnav: Can't seek to block %u\n", 32 );
+      }
+      close(fd);
+    } else {
+    fprintf(MSG_OUT, "NAME OPEN FAILED\n");
+  }
+}
+
+static int ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN) {
+  if((vm->state).vtsN == vtsN) {
+    return 1; /*  We alread have it */
+  }
+  
+  if(vm->vtsi != NULL)
+    ifoClose(vm->vtsi);
+  
+  vm->vtsi = ifoOpenVTSI(dvd, vtsN);
+  if(vm->vtsi == NULL) {
+    fprintf(MSG_OUT, "libdvdnav: ifoOpenVTSI failed\n");
+    return 0;
+  }
+  if(!ifoRead_VTS_PTT_SRPT(vm->vtsi)) {
+    fprintf(MSG_OUT, "libdvdnav: ifoRead_VTS_PTT_SRPT failed\n");
+    return 0;
+  }
+  if(!ifoRead_PGCIT(vm->vtsi)) {
+    fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCIT failed\n");
+    return 0;
+  }
+  if(!ifoRead_PGCI_UT(vm->vtsi)) {
+    fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCI_UT failed\n");
+    return 0;
+  }
+  if(!ifoRead_VOBU_ADMAP(vm->vtsi)) {
+    fprintf(MSG_OUT, "libdvdnav: ifoRead_VOBU_ADMAP vtsi failed\n");
+    return 0;
+  }
+  if(!ifoRead_TITLE_VOBU_ADMAP(vm->vtsi)) {
+    fprintf(MSG_OUT, "libdvdnav: ifoRead_TITLE_VOBU_ADMAP vtsi failed\n");
+    return 0;
+  }
+  (vm->state).vtsN = vtsN;
+  
+  return 1;
+}
+
+
+/* Initialisation & Destruction */
+
+vm_t* vm_new_vm() {
+  return (vm_t*)calloc(sizeof(vm_t), sizeof(char));
+}
+
+void vm_free_vm(vm_t *vm) {
+  vm_stop(vm);
+  free(vm);
+}
+
+
+/* IFO Access */
+
+ifo_handle_t *vm_get_vmgi(vm_t *vm) {
+  return vm->vmgi;
+}
+
+ifo_handle_t *vm_get_vtsi(vm_t *vm) {
+  return vm->vtsi;
+}
+
+
+/* Reader Access */
+
+dvd_reader_t *vm_get_dvd_reader(vm_t *vm) {
+  return vm->dvd;
+}
+
+
+/* Basic Handling */
+
+int vm_start(vm_t *vm) {
+  /* Set pgc to FP (First Play) pgc */
+  set_FP_PGC(vm);
+  process_command(vm, play_PGC(vm));
+  return !vm->stopped;
+}
+
+void vm_stop(vm_t *vm) {
+  if(vm->vmgi) {
+    ifoClose(vm->vmgi);
+    vm->vmgi=NULL;
+  }
+  if(vm->vtsi) {
+    ifoClose(vm->vtsi);
+    vm->vtsi=NULL;
+  }
+  if(vm->dvd) {
+    DVDClose(vm->dvd);
+    vm->dvd=NULL;
+  }
+  vm->stopped = 1;
+}
+ 
+int vm_reset(vm_t *vm, const char *dvdroot) {
+  /*  Setup State */
+  memset((vm->state).registers.SPRM, 0, sizeof((vm->state).registers.SPRM));
+  memset((vm->state).registers.GPRM, 0, sizeof((vm->state).registers.GPRM));
+  memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode));
+  memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode));
+  memset((vm->state).registers.GPRM_time, 0, sizeof((vm->state).registers.GPRM_time));
+  (vm->state).registers.SPRM[0]  = ('e'<<8)|'n'; /* Player Menu Languange code */
+  (vm->state).AST_REG            = 15;           /* 15 why? */
+  (vm->state).SPST_REG           = 62;           /* 62 why? */
+  (vm->state).AGL_REG            = 1;
+  (vm->state).TTN_REG            = 1;
+  (vm->state).VTS_TTN_REG        = 1;
+  /* (vm->state).TT_PGCN_REG        = 0 */
+  (vm->state).PTTN_REG           = 1;
+  (vm->state).HL_BTNN_REG        = 1 << 10;
+  (vm->state).PTL_REG            = 15;           /* Parental Level */
+  (vm->state).registers.SPRM[12] = ('U'<<8)|'S'; /* Parental Management Country Code */
+  (vm->state).registers.SPRM[16] = ('e'<<8)|'n'; /* Initial Language Code for Audio */
+  (vm->state).registers.SPRM[18] = ('e'<<8)|'n'; /* Initial Language Code for Spu */
+  (vm->state).registers.SPRM[20] = 0x1;          /* Player Regional Code Mask. Region free! */
+  (vm->state).registers.SPRM[14] = 0x100;        /* Try Pan&Scan */
+   
+  (vm->state).pgN                = 0;
+  (vm->state).cellN              = 0;
+  (vm->state).cell_restart       = 0;
+
+  (vm->state).domain             = FP_DOMAIN;
+  (vm->state).rsm_vtsN           = 0;
+  (vm->state).rsm_cellN          = 0;
+  (vm->state).rsm_blockN         = 0;
+  
+  (vm->state).vtsN               = -1;
+  
+  if (vm->dvd && dvdroot) {
+    /* a new dvd device has been requested */
+    vm_stop(vm);
+  }
+  if (!vm->dvd) {
+    vm->dvd = DVDOpen(dvdroot);
+    if(!vm->dvd) {
+      fprintf(MSG_OUT, "libdvdnav: vm: failed to open/read the DVD\n");
+      return 0;
+    }
+    dvd_read_name(vm->dvd_name, dvdroot);
+    vm->map  = remap_loadmap(vm->dvd_name);
+    vm->vmgi = ifoOpenVMGI(vm->dvd);
+    if(!vm->vmgi) {
+      fprintf(MSG_OUT, "libdvdnav: vm: failed to read VIDEO_TS.IFO\n");
+      return 0;
+    }
+    if(!ifoRead_FP_PGC(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_FP_PGC failed\n");
+      return 0;
+    }
+    if(!ifoRead_TT_SRPT(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_TT_SRPT failed\n");
+      return 0;
+    }
+    if(!ifoRead_PGCI_UT(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PGCI_UT failed\n");
+      return 0;
+    }
+    if(!ifoRead_PTL_MAIT(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PTL_MAIT failed\n");
+      /* return 0; Not really used for now.. */
+    }
+    if(!ifoRead_VTS_ATRT(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VTS_ATRT failed\n");
+      /* return 0; Not really used for now.. */
+    }
+    if(!ifoRead_VOBU_ADMAP(vm->vmgi)) {
+      fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VOBU_ADMAP vgmi failed\n");
+      /* return 0; Not really used for now.. */
+    }
+    /* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */
+  }
+  if (vm->vmgi) {
+    int i, mask;
+    fprintf(MSG_OUT, "libdvdnav: DVD disk reports itself with Region mask 0x%08x. Regions:",
+      vm->vmgi->vmgi_mat->vmg_category);
+    for (i = 1, mask = 1; i <= 8; i++, mask <<= 1)
+      if (((vm->vmgi->vmgi_mat->vmg_category >> 16) & mask) == 0)
+        fprintf(MSG_OUT, " %d", i);
+    fprintf(MSG_OUT, "\n");
+  }
+  return 1;
+}
+
+
+/* copying and merging */
+
+vm_t *vm_new_copy(vm_t *source) {
+  vm_t *target = vm_new_vm();
+  int vtsN;
+  int pgcN = get_PGCN(source);
+  int pgN  = (source->state).pgN;
+  
+  assert(pgcN);
+  
+  memcpy(target, source, sizeof(vm_t));
+  
+  /* open a new vtsi handle, because the copy might switch to another VTS */
+  target->vtsi = NULL;
+  vtsN = (target->state).vtsN;
+  if (vtsN > 0) {
+    (target->state).vtsN = 0;
+    if (!ifoOpenNewVTSI(target, target->dvd, vtsN))
+      assert(0);
+  
+    /* restore pgc pointer into the new vtsi */
+    if (!set_PGCN(target, pgcN))
+      assert(0);
+    (target->state).pgN = pgN;
+  }
+  
+  return target;
+}
+
+void vm_merge(vm_t *target, vm_t *source) {
+  if(target->vtsi)
+    ifoClose(target->vtsi);
+  memcpy(target, source, sizeof(vm_t));
+  memset(source, 0, sizeof(vm_t));
+}
+
+void vm_free_copy(vm_t *vm) {
+  if(vm->vtsi)
+    ifoClose(vm->vtsi);
+  free(vm);
+}
+
+
+/* regular playback */
+
+void vm_position_get(vm_t *vm, vm_position_t *position) {
+  position->button = (vm->state).HL_BTNN_REG >> 10;
+  position->vts = (vm->state).vtsN; 
+  position->domain = (vm->state).domain; 
+  position->spu_channel = (vm->state).SPST_REG;
+  position->audio_channel = (vm->state).AST_REG;
+  position->angle_channel = (vm->state).AGL_REG;
+  position->hop_channel = vm->hop_channel; /* Increases by one on each hop */
+  position->cell = (vm->state).cellN;
+  position->cell_restart = (vm->state).cell_restart;
+  position->cell_start = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector;
+  position->still = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].still_time;
+  position->block = (vm->state).blockN;
+
+  /* handle PGC stills at PGC end */
+  if ((vm->state).cellN == (vm->state).pgc->nr_of_cells)
+    position->still += (vm->state).pgc->still_time;
+  /* still already determined */
+  if (position->still)
+    return;
+  /* This is a rough fix for some strange still situations on some strange DVDs.
+   * There are discs (like the German "Back to the Future" RC2) where the only
+   * indication of a still is a cell playback time higher than the time the frames
+   * in this cell actually take to play (like 1 frame with 1 minute playback time).
+   * On the said BTTF disc, for these cells last_sector and last_vobu_start_sector
+   * are equal and the cells are very short, so we abuse these conditions to
+   * detect such discs. I consider these discs broken, so the fix is somewhat
+   * broken, too. */
+  if (((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector ==
+       (vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_vobu_start_sector) &&
+      ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector -
+       (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector < 1024)) {
+    int time;
+    int size = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector -
+	       (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector;
+    time  = ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour   >> 4  ) * 36000;
+    time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour   & 0x0f) * 3600;
+    time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute >> 4  ) * 600;
+    time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute & 0x0f) * 60;
+    time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second >> 4  ) * 10;
+    time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second & 0x0f) * 1;
+    if (!time || size / time > 30)
+      /* datarate is too high, it might be a very short, but regular cell */
+      return;
+    if (time > 0xff) time = 0xff;
+    position->still = time;
+  }
+}
+
+void vm_get_next_cell(vm_t *vm) {
+  process_command(vm, play_Cell_post(vm));
+}
+
+
+/* Jumping */
+
+int vm_jump_pg(vm_t *vm, int pg) {
+  (vm->state).pgN = pg;
+  process_command(vm, play_PG(vm));
+  return 1;
+}
+
+int vm_jump_cell_block(vm_t *vm, int cell, int block) {
+  (vm->state).cellN = cell;
+  process_command(vm, play_Cell(vm));
+  /* play_Cell can jump to a different cell in case of angles */
+  if ((vm->state).cellN == cell)
+    (vm->state).blockN = block;
+  return 1;
+}
+
+int vm_jump_title_part(vm_t *vm, int title, int part) {
+  link_t link;
+  
+  if(!set_PTT(vm, title, part))
+    return 0;
+  /* Some DVDs do not want us to jump directly into a title and have
+   * PGC pre commands taking us back to some menu. Since we do not like that,
+   * we do not execute PGC pre commands that would do a jump. */
+  /* process_command(vm, play_PGC_PG(vm, (vm->state).pgN)); */
+  link = play_PGC_PG(vm, (vm->state).pgN);
+  if (link.command != PlayThis)
+    /* jump occured -> ignore it and play the PG anyway */
+    process_command(vm, play_PG(vm));
+  else
+    process_command(vm, link);
+  return 1;
+}
+
+int vm_jump_top_pg(vm_t *vm) {
+  process_command(vm, play_PG(vm));
+  return 1;
+}
+
+int vm_jump_next_pg(vm_t *vm) {
+  if((vm->state).pgN >= (vm->state).pgc->nr_of_programs) {
+    /* last program -> move to TailPGC */
+    process_command(vm, play_PGC_post(vm));
+    return 1;
+  } else {
+    vm_jump_pg(vm, (vm->state).pgN + 1);
+    return 1;
+  }
+}
+
+int vm_jump_prev_pg(vm_t *vm) {
+  if ((vm->state).pgN <= 1) {
+    /* first program -> move to last program of previous PGC */
+    if ((vm->state).pgc->prev_pgc_nr && set_PGCN(vm, (vm->state).pgc->prev_pgc_nr)) {
+      process_command(vm, play_PGC(vm));
+      vm_jump_pg(vm, (vm->state).pgc->nr_of_programs);
+      return 1;
+    }
+    return 0;
+  } else {
+    vm_jump_pg(vm, (vm->state).pgN - 1);
+    return 1;
+  }
+}
+
+int vm_jump_up(vm_t *vm) {
+  if((vm->state).pgc->goup_pgc_nr && set_PGCN(vm, (vm->state).pgc->goup_pgc_nr)) {
+    process_command(vm, play_PGC(vm));
+    return 1;
+  }
+  return 0;
+}
+
+int vm_jump_menu(vm_t *vm, DVDMenuID_t menuid) {
+  domain_t old_domain = (vm->state).domain;
+  
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    set_RSMinfo(vm, 0, (vm->state).blockN);
+    /* FALL THROUGH */
+  case VTSM_DOMAIN:
+  case VMGM_DOMAIN:
+    switch(menuid) {
+    case DVD_MENU_Title:
+    case DVD_MENU_Escape:
+      (vm->state).domain = VMGM_DOMAIN;
+      break;
+    case DVD_MENU_Root:
+    case DVD_MENU_Subpicture:
+    case DVD_MENU_Audio:
+    case DVD_MENU_Angle:
+    case DVD_MENU_Part:
+      (vm->state).domain = VTSM_DOMAIN;
+      break;
+    }
+    if(get_PGCIT(vm) && set_MENU(vm, menuid)) {
+      process_command(vm, play_PGC(vm));
+      return 1;  /* Jump */
+    } else {
+      (vm->state).domain = old_domain;
+    }
+    break;
+  case FP_DOMAIN: /* FIXME XXX $$$ What should we do here? */
+    break;
+  }
+  
+  return 0;
+}
+
+int vm_jump_resume(vm_t *vm) {
+  link_t link_values = { LinkRSM, 0, 0, 0 };
+
+  if (!(vm->state).rsm_vtsN) /* Do we have resume info? */
+    return 0;
+  if (!process_command(vm, link_values))
+    return 0;
+  return 1;
+}
+
+int vm_exec_cmd(vm_t *vm, vm_cmd_t *cmd) {
+  link_t link_values;
+  
+  if(vmEval_CMD(cmd, 1, &(vm->state).registers, &link_values))
+    return process_command(vm, link_values);
+  else
+    return 0; /*  It updated some state thats all... */
+}
+
+
+/* getting information */
+
+int vm_get_current_menu(vm_t *vm, int *menuid) {
+  pgcit_t* pgcit;
+  int pgcn;
+  pgcn = (vm->state).pgcN;
+  pgcit = get_PGCIT(vm);
+  if(pgcit==NULL) return 0;
+  *menuid = pgcit->pgci_srp[pgcn - 1].entry_id & 0xf ;
+  return 1;
+}
+
+int vm_get_current_title_part(vm_t *vm, int *title_result, int *part_result) {
+  vts_ptt_srpt_t *vts_ptt_srpt;
+  int title, part = 0, vts_ttn;
+  int found;
+  int16_t pgcN, pgN;
+
+  vts_ptt_srpt = vm->vtsi->vts_ptt_srpt;
+  pgcN = get_PGCN(vm);
+  pgN = vm->state.pgN;
+
+  found = 0;
+  for (vts_ttn = 0; (vts_ttn < vts_ptt_srpt->nr_of_srpts) && !found; vts_ttn++) {
+    for (part = 0; (part < vts_ptt_srpt->title[vts_ttn].nr_of_ptts) && !found; part++) {
+      if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgcn == pgcN) {
+	if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgn  == pgN) {
+	  found = 1;
+          break;
+	}
+	if (part > 0 && vts_ptt_srpt->title[vts_ttn].ptt[part].pgn > pgN &&
+	    vts_ptt_srpt->title[vts_ttn].ptt[part - 1].pgn < pgN) {
+	  part--;
+	  found = 1;
+	  break;
+	}
+      }
+    }
+    if (found) break;
+  }
+  vts_ttn++;
+  part++;
+  
+  if (!found) {
+    fprintf(MSG_OUT, "libdvdnav: chapter NOT FOUND!\n");
+    return 0;
+  }
+
+  title = get_TT(vm, vm->state.vtsN, vts_ttn);
+
+#ifdef TRACE
+  if (title) {
+    fprintf(MSG_OUT, "libdvdnav: ************ this chapter FOUND!\n");
+    fprintf(MSG_OUT, "libdvdnav: VTS_PTT_SRPT - Title %3i part %3i: PGC: %3i PG: %3i\n",
+             title, part,
+             vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgcn ,
+             vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgn );
+  }
+#endif
+  *title_result = title;
+  *part_result = part;
+  return 1;
+}
+
+/* Return the substream id for 'logical' audio stream audioN.
+ * 0 <= audioN < 8
+ */
+int vm_get_audio_stream(vm_t *vm, int audioN) {
+  int streamN = -1;
+
+  if((vm->state).domain != VTS_DOMAIN)
+    audioN = 0;
+  
+  if(audioN < 8) {
+    /* Is there any control info for this logical stream */ 
+    if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
+      streamN = ((vm->state).pgc->audio_control[audioN] >> 8) & 0x07;  
+    }
+  }
+  
+  if((vm->state).domain != VTS_DOMAIN && streamN == -1)
+    streamN = 0;
+  
+  /* FIXME: Should also check in vtsi/vmgi status what kind of stream
+   * it is (ac3/lpcm/dts/sdds...) to find the right (sub)stream id */
+  return streamN;
+}
+
+/* Return the substream id for 'logical' subpicture stream subpN and given mode.
+ * 0 <= subpN < 32
+ * mode == 0 - widescreen
+ * mode == 1 - letterbox
+ * mode == 2 - pan&scan
+ */
+int vm_get_subp_stream(vm_t *vm, int subpN, int mode) {
+  int streamN = -1;
+  int source_aspect = vm_get_video_aspect(vm);
+  
+  if((vm->state).domain != VTS_DOMAIN)
+    subpN = 0;
+  
+  if(subpN < 32) { /* a valid logical stream */
+    /* Is this logical stream present */ 
+    if((vm->state).pgc->subp_control[subpN] & (1<<31)) {
+      if(source_aspect == 0) /* 4:3 */	     
+	streamN = ((vm->state).pgc->subp_control[subpN] >> 24) & 0x1f;  
+      if(source_aspect == 3) /* 16:9 */
+        switch (mode) {
+	case 0:
+	  streamN = ((vm->state).pgc->subp_control[subpN] >> 16) & 0x1f;
+	  break;
+	case 1:
+	  streamN = ((vm->state).pgc->subp_control[subpN] >> 8) & 0x1f;
+	  break;
+	case 2:
+	  streamN = (vm->state).pgc->subp_control[subpN] & 0x1f;
+	}
+    }
+  }
+  
+  if((vm->state).domain != VTS_DOMAIN && streamN == -1)
+    streamN = 0;
+
+  /* FIXME: Should also check in vtsi/vmgi status what kind of stream it is. */
+  return streamN;
+}
+
+int vm_get_audio_active_stream(vm_t *vm) {
+  int audioN;
+  int streamN;
+  audioN = (vm->state).AST_REG ;
+  streamN = vm_get_audio_stream(vm, audioN);
+  
+  /* If no such stream, then select the first one that exists. */
+  if(streamN == -1) {
+    for(audioN = 0; audioN < 8; audioN++) {
+      if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
+        if ((streamN = vm_get_audio_stream(vm, audioN)) >= 0)
+          break;
+      }
+    }
+  }
+
+  return streamN;
+}
+
+int vm_get_subp_active_stream(vm_t *vm, int mode) {
+  int subpN;
+  int streamN;
+  subpN = (vm->state).SPST_REG & ~0x40;
+  streamN = vm_get_subp_stream(vm, subpN, mode);
+  
+  /* If no such stream, then select the first one that exists. */
+  if(streamN == -1) {
+    for(subpN = 0; subpN < 32; subpN++) {
+      if((vm->state).pgc->subp_control[subpN] & (1<<31)) {
+        if ((streamN = vm_get_subp_stream(vm, subpN, mode)) >= 0)
+          break;
+      }
+    }
+  }
+
+  if((vm->state).domain == VTS_DOMAIN && !((vm->state).SPST_REG & 0x40))
+    /* Bit 7 set means hide, and only let Forced display show */
+    return (streamN | 0x80);
+  else
+    return streamN;
+}
+
+void vm_get_angle_info(vm_t *vm, int *current, int *num_avail) {
+  *num_avail = 1;
+  *current = 1;
+  
+  if((vm->state).domain == VTS_DOMAIN) {
+    title_info_t *title;
+    /* TTN_REG does not allways point to the correct title.. */
+    if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts)
+      return;
+    title = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1];
+    if(title->title_set_nr != (vm->state).vtsN || 
+       title->vts_ttn != (vm->state).VTS_TTN_REG)
+      return; 
+    *num_avail = title->nr_of_angles;
+    *current = (vm->state).AGL_REG;
+  }
+}
+
+#if 0
+/* currently unused */
+void vm_get_audio_info(vm_t *vm, int *current, int *num_avail) {
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_audio_streams;
+    *current = (vm->state).AST_REG;
+    break;
+  case VTSM_DOMAIN:
+    *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_audio_streams; /*  1 */
+    *current = 1;
+    break;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    *num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_audio_streams; /*  1 */
+    *current = 1;
+    break;
+  }
+}
+
+/* currently unused */
+void vm_get_subp_info(vm_t *vm, int *current, int *num_avail) {
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_subp_streams;
+    *current = (vm->state).SPST_REG;
+    break;
+  case VTSM_DOMAIN:
+    *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_subp_streams; /*  1 */
+    *current = 0x41;
+    break;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    *num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_subp_streams; /*  1 */
+    *current = 0x41;
+    break;
+  }
+}
+
+/* currently unused */
+void vm_get_video_res(vm_t *vm, int *width, int *height) {
+  video_attr_t attr = vm_get_video_attr(vm);
+  
+  if(attr.video_format != 0) 
+    *height = 576;
+  else
+    *height = 480;
+  switch(attr.picture_size) {
+  case 0:
+    *width = 720;
+    break;
+  case 1:
+    *width = 704;
+    break;
+  case 2:
+    *width = 352;
+    break;
+  case 3:
+    *width = 352;
+    *height /= 2;
+    break;
+  }
+}
+#endif
+
+int vm_get_video_aspect(vm_t *vm) {
+  int aspect = vm_get_video_attr(vm).display_aspect_ratio;
+  
+  assert(aspect == 0 || aspect == 3);
+  (vm->state).registers.SPRM[14] &= ~(0x3 << 10);
+  (vm->state).registers.SPRM[14] |= aspect << 10;
+  
+  return aspect;
+}
+
+int vm_get_video_scale_permission(vm_t *vm) {
+  return vm_get_video_attr(vm).permitted_df;
+}
+
+video_attr_t vm_get_video_attr(vm_t *vm) {
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    return vm->vtsi->vtsi_mat->vts_video_attr;
+  case VTSM_DOMAIN:
+    return vm->vtsi->vtsi_mat->vtsm_video_attr;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    return vm->vmgi->vmgi_mat->vmgm_video_attr;
+  default:
+    abort();
+  }
+}
+
+audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN) {
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    return vm->vtsi->vtsi_mat->vts_audio_attr[streamN];
+  case VTSM_DOMAIN:
+    return vm->vtsi->vtsi_mat->vtsm_audio_attr;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    return vm->vmgi->vmgi_mat->vmgm_audio_attr;
+  default:
+    abort();
+  }
+}
+
+subp_attr_t vm_get_subp_attr(vm_t *vm, int streamN) {
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    return vm->vtsi->vtsi_mat->vts_subp_attr[streamN];
+  case VTSM_DOMAIN:
+    return vm->vtsi->vtsi_mat->vtsm_subp_attr;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    return vm->vmgi->vmgi_mat->vmgm_subp_attr;
+  default:
+    abort();
+  }
+}
+
+
+/* Playback control */
+
+static link_t play_PGC(vm_t *vm) {
+  link_t link_values;
+  
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_PGC:");
+  if((vm->state).domain != FP_DOMAIN) {
+    fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
+  } else {
+    fprintf(MSG_OUT, " first_play_pgc\n");
+  }
+#endif
+
+  /* This must be set before the pre-commands are executed because they
+   * might contain a CallSS that will save resume state */
+
+  /* FIXME: This may be only a temporary fix for something... */
+  (vm->state).pgN = 1;
+  (vm->state).cellN = 0;
+  (vm->state).blockN = 0;
+
+  /* eval -> updates the state and returns either 
+     - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
+     - just play video i.e first PG
+       (This is what happens if you fall of the end of the pre_cmds)
+     - or an error (are there more cases?) */
+  if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) {
+    if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds, 
+		  (vm->state).pgc->command_tbl->nr_of_pre, 
+		  &(vm->state).registers, &link_values)) {
+      /*  link_values contains the 'jump' return value */
+      return link_values;
+    } else {
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n");
+#endif
+    }
+  }
+  return play_PG(vm);
+}  
+
+static link_t play_PGC_PG(vm_t *vm, int pgN) {    
+  link_t link_values;
+  
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_PGC_PG:");
+  if((vm->state).domain != FP_DOMAIN) {
+    fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
+  } else {
+    fprintf(MSG_OUT, " first_play_pgc\n");
+  }
+#endif
+
+  /*  This must be set before the pre-commands are executed because they
+   *  might contain a CallSS that will save resume state */
+
+  /* FIXME: This may be only a temporary fix for something... */
+  (vm->state).pgN = pgN;
+  (vm->state).cellN = 0;
+  (vm->state).blockN = 0;
+
+  /* eval -> updates the state and returns either 
+     - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
+     - just play video i.e first PG
+       (This is what happens if you fall of the end of the pre_cmds)
+     - or an error (are there more cases?) */
+  if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) {
+    if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds, 
+		  (vm->state).pgc->command_tbl->nr_of_pre, 
+		  &(vm->state).registers, &link_values)) {
+      /*  link_values contains the 'jump' return value */
+      return link_values;
+    } else {
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n");
+#endif
+    }
+  }
+  return play_PG(vm);
+}  
+
+static link_t play_PGC_post(vm_t *vm) {
+  link_t link_values;
+
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_PGC_post:\n");
+#endif
+  
+  /* eval -> updates the state and returns either 
+     - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
+     - just go to next PGC
+       (This is what happens if you fall of the end of the post_cmds)
+     - or an error (are there more cases?) */
+  if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_post &&
+     vmEval_CMD((vm->state).pgc->command_tbl->post_cmds,
+		(vm->state).pgc->command_tbl->nr_of_post, 
+		&(vm->state).registers, &link_values)) {
+    return link_values;
+  }
+  
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: ** Fell of the end of the pgc, continuing in NextPGC\n");
+#endif
+  /* Should end up in the STOP_DOMAIN if next_pgc is 0. */
+  if(!set_PGCN(vm, (vm->state).pgc->next_pgc_nr)) {
+    link_values.command = Exit;
+    return link_values;
+  }
+  return play_PGC(vm);
+}
+
+static link_t play_PG(vm_t *vm) {
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i)\n", (vm->state).pgN);
+#endif
+  
+  assert((vm->state).pgN > 0);
+  if((vm->state).pgN > (vm->state).pgc->nr_of_programs) {
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i) > pgc->nr_of_programs (%i)\n", 
+	    (vm->state).pgN, (vm->state).pgc->nr_of_programs );
+#endif
+    assert((vm->state).pgN == (vm->state).pgc->nr_of_programs + 1); 
+    return play_PGC_post(vm);
+  }
+  
+  (vm->state).cellN = (vm->state).pgc->program_map[(vm->state).pgN - 1];
+  
+  return play_Cell(vm);
+}
+
+static link_t play_Cell(vm_t *vm) {
+  static const link_t play_this = {PlayThis, /* Block in Cell */ 0, 0, 0};
+
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_Cell: (vm->state).cellN (%i)\n", (vm->state).cellN);
+#endif
+  
+  assert((vm->state).cellN > 0);
+  if((vm->state).cellN > (vm->state).pgc->nr_of_cells) {
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav: (vm->state).cellN (%i) > pgc->nr_of_cells (%i)\n", 
+	    (vm->state).cellN, (vm->state).pgc->nr_of_cells );
+#endif
+    assert((vm->state).cellN == (vm->state).pgc->nr_of_cells + 1); 
+    return play_PGC_post(vm);
+  }
+  
+  /* Multi angle/Interleaved */
+  switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) {
+  case 0: /*  Normal */
+    assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0);
+    break;
+  case 1: /*  The first cell in the block */
+    switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) {
+    case 0: /*  Not part of a block */
+      assert(0);
+      break;
+    case 1: /*  Angle block */
+      /* Loop and check each cell instead? So we don't get outside the block? */
+      (vm->state).cellN += (vm->state).AGL_REG - 1;
+#ifdef STRICT
+      assert((vm->state).cellN <= (vm->state).pgc->nr_of_cells);
+      assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0);
+      assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1);
+#else
+      if (!((vm->state).cellN <= (vm->state).pgc->nr_of_cells) ||
+          !((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0) ||
+	  !((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1)) {
+	fprintf(MSG_OUT, "libdvdnav: Invalid angle block\n");
+	(vm->state).cellN -= (vm->state).AGL_REG - 1;
+      }
+#endif
+      break;
+    case 2: /*  ?? */
+    case 3: /*  ?? */
+    default:
+      fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n",
+	      (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode,
+	      (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type);
+      assert(0);
+    }
+    break;
+  case 2: /*  Cell in the block */
+  case 3: /*  Last cell in the block */
+  /* These might perhaps happen for RSM or LinkC commands? */
+  default:
+    fprintf(MSG_OUT, "libdvdnav: Cell is in block but did not enter at first cell!\n");
+  }
+  
+  /* Updates (vm->state).pgN and PTTN_REG */
+  if(!set_PGN(vm)) {
+    /* Should not happen */
+    assert(0);
+    return play_PGC_post(vm);
+  }
+  (vm->state).cell_restart++;
+  (vm->state).blockN = 0;
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: Cell should restart here\n");
+#endif
+  return play_this;
+}
+
+static link_t play_Cell_post(vm_t *vm) {
+  cell_playback_t *cell;
+  
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: play_Cell_post: (vm->state).cellN (%i)\n", (vm->state).cellN);
+#endif
+  
+  cell = &(vm->state).pgc->cell_playback[(vm->state).cellN - 1];
+  
+  /* Still time is already taken care of before we get called. */
+  
+  /* Deal with a Cell command, if any */
+  if(cell->cell_cmd_nr != 0) {
+    link_t link_values;
+    
+/*  These asserts are now not needed.
+ *  Some DVDs have no cell commands listed in the PGC,
+ *  but the Cell itself points to a cell command that does not exist.
+ *  For this situation, just ignore the cell command and continue.
+ *
+ *  assert((vm->state).pgc->command_tbl != NULL);
+ *  assert((vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr);
+ */
+
+    if ((vm->state).pgc->command_tbl != NULL &&
+        (vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr) {
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: Cell command present, executing\n");
+#endif
+      if(vmEval_CMD(&(vm->state).pgc->command_tbl->cell_cmds[cell->cell_cmd_nr - 1], 1,
+		    &(vm->state).registers, &link_values)) {
+        return link_values;
+      } else {
+#ifdef TRACE
+        fprintf(MSG_OUT, "libdvdnav: Cell command didn't do a Jump, Link or Call\n");
+#endif
+      }
+    } else {
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: Invalid Cell command\n");
+#endif
+    }
+  }
+  
+  /* Where to continue after playing the cell... */
+  /* Multi angle/Interleaved */
+  switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) {
+  case 0: /*  Normal */
+    assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0);
+    (vm->state).cellN++;
+    break;
+  case 1: /*  The first cell in the block */
+  case 2: /*  A cell in the block */
+  case 3: /*  The last cell in the block */
+  default:
+    switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) {
+    case 0: /*  Not part of a block */
+      assert(0);
+      break;
+    case 1: /*  Angle block */
+      /* Skip the 'other' angles */
+      (vm->state).cellN++;
+      while((vm->state).cellN <= (vm->state).pgc->nr_of_cells &&
+	    (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode >= 2) {
+	(vm->state).cellN++;
+      }
+      break;
+    case 2: /*  ?? */
+    case 3: /*  ?? */
+    default:
+      fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n",
+	      (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode,
+	      (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type);
+      assert(0);
+    }
+    break;
+  }
+  
+  /* Figure out the correct pgN for the new cell */ 
+  if(!set_PGN(vm)) {
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav: last cell in this PGC\n");
+#endif
+    return play_PGC_post(vm);
+  }
+  return play_Cell(vm);
+}
+
+
+/* link processing */
+
+static int process_command(vm_t *vm, link_t link_values) {
+  
+  while(link_values.command != PlayThis) {
+    
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav: Before printout starts:\n");
+    vm_print_link(link_values);
+    fprintf(MSG_OUT, "libdvdnav: Link values %i %i %i %i\n", link_values.command, 
+	    link_values.data1, link_values.data2, link_values.data3);
+    vm_print_current_domain_state(vm);
+    fprintf(MSG_OUT, "libdvdnav: Before printout ends.\n");
+#endif
+    
+    switch(link_values.command) {
+    case LinkNoLink:
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      return 0;  /* no actual jump */
+
+    case LinkTopC:
+      /* Restart playing from the beginning of the current Cell. */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_Cell(vm);
+      break;
+    case LinkNextC:
+      /* Link to Next Cell */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      (vm->state).cellN += 1;
+      link_values = play_Cell(vm);
+      break;
+    case LinkPrevC:
+      /* Link to Previous Cell */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((vm->state).cellN > 1);
+      (vm->state).cellN -= 1;
+      link_values = play_Cell(vm);
+      break;
+      
+    case LinkTopPG:
+      /* Link to Top of current Program */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_PG(vm);
+      break;
+    case LinkNextPG:
+      /* Link to Next Program */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      (vm->state).pgN += 1;
+      link_values = play_PG(vm);
+      break;
+    case LinkPrevPG:
+      /* Link to Previous Program */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((vm->state).pgN > 1);
+      (vm->state).pgN -= 1;
+      link_values = play_PG(vm);
+      break;
+
+    case LinkTopPGC:
+      /* Restart playing from beginning of current Program Chain */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_PGC(vm);
+      break;
+    case LinkNextPGC:
+      /* Link to Next Program Chain */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((vm->state).pgc->next_pgc_nr != 0);
+      if(set_PGCN(vm, (vm->state).pgc->next_pgc_nr))
+	link_values = play_PGC(vm);
+      else
+	link_values.command = Exit;
+      break;
+    case LinkPrevPGC:
+      /* Link to Previous Program Chain */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((vm->state).pgc->prev_pgc_nr != 0);
+      if(set_PGCN(vm, (vm->state).pgc->prev_pgc_nr))
+	link_values = play_PGC(vm);
+      else
+	link_values.command = Exit;
+      break;
+    case LinkGoUpPGC:
+      /* Link to GoUp Program Chain */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      assert((vm->state).pgc->goup_pgc_nr != 0);
+      if(set_PGCN(vm, (vm->state).pgc->goup_pgc_nr))
+	link_values = play_PGC(vm);
+      else
+	link_values.command = Exit;
+      break;
+    case LinkTailPGC:
+      /* Link to Tail of Program Chain */
+      /* BUTTON number:data1 */
+      if(link_values.data1 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data1 << 10;
+      link_values = play_PGC_post(vm);
+    break;
+
+    case LinkRSM:
+      {
+	/* Link to Resume point */
+	int i;
+	
+	/* Check and see if there is any rsm info!! */
+	if (!(vm->state).rsm_vtsN) {
+	  fprintf(MSG_OUT, "libdvdnav: trying to resume without any resume info set\n");
+	  link_values.command = Exit;
+	  break;
+	}
+	
+	(vm->state).domain = VTS_DOMAIN;
+	if (!ifoOpenNewVTSI(vm, vm->dvd, (vm->state).rsm_vtsN))
+	  assert(0);
+	set_PGCN(vm, (vm->state).rsm_pgcN);
+	
+	/* These should never be set in SystemSpace and/or MenuSpace */ 
+	/* (vm->state).TTN_REG = rsm_tt; ?? */
+	/* (vm->state).TT_PGCN_REG = (vm->state).rsm_pgcN; ?? */
+	for(i = 0; i < 5; i++) {
+	  (vm->state).registers.SPRM[4 + i] = (vm->state).rsm_regs[i];
+	}
+	
+	if(link_values.data1 != 0)
+	  (vm->state).HL_BTNN_REG = link_values.data1 << 10;
+	
+	if((vm->state).rsm_cellN == 0) {
+	  assert((vm->state).cellN); /*  Checking if this ever happens */
+	  (vm->state).pgN = 1;
+	  link_values = play_PG(vm);
+	} else { 
+	  /* (vm->state).pgN = ?? this gets the right value in set_PGN() below */
+	  (vm->state).cellN = (vm->state).rsm_cellN;
+	  link_values.command = PlayThis;
+	  link_values.data1 = (vm->state).rsm_blockN & 0xffff;
+	  link_values.data2 = (vm->state).rsm_blockN >> 16;
+	  if(!set_PGN(vm)) {
+	    /* Were at the end of the PGC, should not happen for a RSM */
+	    assert(0);
+	    link_values.command = LinkTailPGC;
+	    link_values.data1 = 0;  /* No button */
+	  }
+	}
+      }
+      break;
+    case LinkPGCN:
+      /* Link to Program Chain Number:data1 */
+      if(!set_PGCN(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case LinkPTTN:
+      /* Link to Part of current Title Number:data1 */
+      /* BUTTON number:data2 */
+      /* PGC Pre-Commands are not executed */
+      assert((vm->state).domain == VTS_DOMAIN);
+      if(link_values.data2 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data2 << 10;
+      if(!set_VTS_PTT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG, link_values.data1))
+	assert(0);
+      link_values = play_PG(vm);
+      break;
+    case LinkPGN:
+      /* Link to Program Number:data1 */
+      /* BUTTON number:data2 */
+      if(link_values.data2 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data2 << 10;
+      /* Update any other state, PTTN perhaps? */
+      (vm->state).pgN = link_values.data1;
+      link_values = play_PG(vm);
+      break;
+    case LinkCN:
+      /* Link to Cell Number:data1 */
+      /* BUTTON number:data2 */
+      if(link_values.data2 != 0)
+	(vm->state).HL_BTNN_REG = link_values.data2 << 10;
+      /* Update any other state, pgN, PTTN perhaps? */
+      (vm->state).cellN = link_values.data1;
+      link_values = play_Cell(vm);
+      break;
+      
+    case Exit:
+      vm->stopped = 1;
+      return 0;
+      
+    case JumpTT:
+      /* Jump to VTS Title Domain */
+      /* Only allowed from the First Play domain(PGC) */
+      /* or the Video Manager domain (VMG) */
+      /* Stop SPRM9 Timer */
+      /* Set SPRM1 and SPRM2 */
+      assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
+      if(set_TT(vm, link_values.data1))
+        link_values = play_PGC(vm);
+      else
+	link_values.command = Exit;
+      break;
+    case JumpVTS_TT:
+      /* Jump to Title:data1 in same VTS Title Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Title Set Domain(VTS) */
+      /* Stop SPRM9 Timer */
+      /* Set SPRM1 and SPRM2 */
+      assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */
+      if(!set_VTS_TT(vm, (vm->state).vtsN, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case JumpVTS_PTT:
+      /* Jump to Part:data2 of Title:data1 in same VTS Title Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Title Set Domain(VTS) */
+      /* Stop SPRM9 Timer */
+      /* Set SPRM1 and SPRM2 */
+      assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */
+      if(!set_VTS_PTT(vm, (vm->state).vtsN, link_values.data1, link_values.data2))
+	assert(0);
+      link_values = play_PGC_PG(vm, (vm->state).pgN);
+      break;
+      
+    case JumpSS_FP:
+      /* Jump to First Play Domain */
+      /* Only allowed from the VTS Menu Domain(VTSM) */
+      /* or the Video Manager domain (VMG) */
+      /* Stop SPRM9 Timer and any GPRM counters */
+      assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN); /* ?? */
+      if (!set_FP_PGC(vm))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case JumpSS_VMGM_MENU:
+      /* Jump to Video Manger domain - Title Menu:data1 or any PGC in VMG */
+      /* Allowed from anywhere except the VTS Title domain */
+      /* Stop SPRM9 Timer and any GPRM counters */
+      assert((vm->state).domain != VTS_DOMAIN); /* ?? */
+      (vm->state).domain = VMGM_DOMAIN;
+      if(!set_MENU(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case JumpSS_VTSM:
+      /* Jump to a menu in Video Title domain, */
+      /* or to a Menu is the current VTS */
+      /* Stop SPRM9 Timer and any GPRM counters */
+      /* ifoOpenNewVTSI:data1 */
+      /* VTS_TTN_REG:data2 */
+      /* get_MENU:data3 */ 
+      if(link_values.data1 != 0) {
+	if (link_values.data1 != (vm->state).vtsN) {
+	  /* the normal case */
+	  assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
+	  (vm->state).domain = VTSM_DOMAIN;
+	  if (!ifoOpenNewVTSI(vm, vm->dvd, link_values.data1))  /* Also sets (vm->state).vtsN */
+	    assert(0);
+	} else {
+	  /* This happens on some discs like "Captain Scarlet & the Mysterons" or
+	   * the German RC2 of "Anatomie" in VTSM. */
+	  assert((vm->state).domain == VTSM_DOMAIN ||
+	    (vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
+	  (vm->state).domain = VTSM_DOMAIN;
+	}
+      } else {
+	/*  This happens on 'The Fifth Element' region 2. */
+	assert((vm->state).domain == VTSM_DOMAIN);
+      }
+      /*  I don't know what title is supposed to be used for. */
+      /*  Alien or Aliens has this != 1, I think. */
+      /* assert(link_values.data2 == 1); */
+      (vm->state).VTS_TTN_REG = link_values.data2;
+      /* TTN_REG (SPRM4), VTS_TTN_REG (SPRM5), TT_PGCN_REG (SPRM6) are linked, */
+      /* so if one changes, the others must change to match it. */
+      (vm->state).TTN_REG     = get_TT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG);
+      if(!set_MENU(vm, link_values.data3))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case JumpSS_VMGM_PGC:
+      /* set_PGCN:data1 */
+      /* Stop SPRM9 Timer and any GPRM counters */
+      assert((vm->state).domain != VTS_DOMAIN); /* ?? */
+      (vm->state).domain = VMGM_DOMAIN;
+      if(!set_PGCN(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+      
+    case CallSS_FP:
+      /* set_RSMinfo:data1 */
+      assert((vm->state).domain == VTS_DOMAIN); /* ?? */
+      /* Must be called before domain is changed */
+      set_RSMinfo(vm, link_values.data1, /* We dont have block info */ 0);
+      set_FP_PGC(vm);
+      link_values = play_PGC(vm);
+      break;
+    case CallSS_VMGM_MENU:
+      /* set_MENU:data1 */ 
+      /* set_RSMinfo:data2 */
+      assert((vm->state).domain == VTS_DOMAIN); /* ?? */
+      /* Must be called before domain is changed */
+      set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);      
+      (vm->state).domain = VMGM_DOMAIN;
+      if(!set_MENU(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case CallSS_VTSM:
+      /* set_MENU:data1 */ 
+      /* set_RSMinfo:data2 */
+      assert((vm->state).domain == VTS_DOMAIN); /* ?? */
+      /* Must be called before domain is changed */
+      set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);
+      (vm->state).domain = VTSM_DOMAIN;
+      if(!set_MENU(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case CallSS_VMGM_PGC:
+      /* set_PGC:data1 */
+      /* set_RSMinfo:data2 */
+      assert((vm->state).domain == VTS_DOMAIN); /* ?? */
+      /* Must be called before domain is changed */
+      set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);
+      (vm->state).domain = VMGM_DOMAIN;
+      if(!set_PGCN(vm, link_values.data1))
+	assert(0);
+      link_values = play_PGC(vm);
+      break;
+    case PlayThis:
+      /* Should never happen. */
+      assert(0);
+      break;
+    }
+
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav: After printout starts:\n");
+    vm_print_current_domain_state(vm);
+    fprintf(MSG_OUT, "libdvdnav: After printout ends.\n");
+#endif
+    
+  }
+  (vm->state).blockN = link_values.data1 | (link_values.data2 << 16);
+  return 1;
+}
+
+
+/* Set functions */
+
+static int set_TT(vm_t *vm, int tt) {  
+  return set_PTT(vm, tt, 1);
+}
+
+static int set_PTT(vm_t *vm, int tt, int ptt) {
+  assert(tt <= vm->vmgi->tt_srpt->nr_of_srpts);
+  return set_VTS_PTT(vm, vm->vmgi->tt_srpt->title[tt - 1].title_set_nr,
+		     vm->vmgi->tt_srpt->title[tt - 1].vts_ttn, ptt);
+}
+
+static int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn) {
+  return set_VTS_PTT(vm, vtsN, vts_ttn, 1);
+}
+
+static int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part) {
+  int pgcN, pgN, res;
+  
+  (vm->state).domain = VTS_DOMAIN;
+
+  if (vtsN != (vm->state).vtsN)
+    if (!ifoOpenNewVTSI(vm, vm->dvd, vtsN))  /* Also sets (vm->state).vtsN */
+      return 0;
+  
+  if ((vts_ttn < 1) || (vts_ttn > vm->vtsi->vts_ptt_srpt->nr_of_srpts) ||
+      (part < 1) || (part > vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].nr_of_ptts) ) {
+    return 0;
+  }
+  
+  pgcN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgcn;
+  pgN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgn;
+ 
+  (vm->state).TT_PGCN_REG = pgcN;
+  (vm->state).PTTN_REG    = part;
+  (vm->state).TTN_REG     = get_TT(vm, vtsN, vts_ttn);
+  assert( (vm->state.TTN_REG) != 0 );
+  (vm->state).VTS_TTN_REG = vts_ttn;
+  (vm->state).vtsN        = vtsN;  /* Not sure about this one. We can get to it easily from TTN_REG */
+  /* Any other registers? */
+  
+  res = set_PGCN(vm, pgcN);   /* This clobber's state.pgN (sets it to 1), but we don't want clobbering here. */
+  (vm->state).pgN = pgN;
+  return res;
+}
+
+static int set_FP_PGC(vm_t *vm) {  
+  (vm->state).domain = FP_DOMAIN;
+  if (!vm->vmgi->first_play_pgc) {
+    return set_PGCN(vm, 1);
+  }
+  (vm->state).pgc = vm->vmgi->first_play_pgc;
+  (vm->state).pgcN = vm->vmgi->vmgi_mat->first_play_pgc;
+  return 1;
+}
+
+
+static int set_MENU(vm_t *vm, int menu) {
+  assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN);
+  return set_PGCN(vm, get_ID(vm, menu));
+}
+
+static int set_PGCN(vm_t *vm, int pgcN) {
+  pgcit_t *pgcit;
+  
+  pgcit = get_PGCIT(vm);
+  assert(pgcit != NULL);  /* ?? Make this return -1 instead */
+
+  if(pgcN < 1 || pgcN > pgcit->nr_of_pgci_srp) {
+#ifdef TRACE
+    fprintf(MSG_OUT, "libdvdnav:  ** No such pgcN = %d\n", pgcN);
+#endif
+    return 0;
+  }
+  
+  (vm->state).pgc = pgcit->pgci_srp[pgcN - 1].pgc;
+  (vm->state).pgcN = pgcN;
+  (vm->state).pgN = 1;
+ 
+  if((vm->state).domain == VTS_DOMAIN)
+    (vm->state).TT_PGCN_REG = pgcN;
+
+  return 1;
+}
+
+/* Figure out the correct pgN from the cell and update (vm->state). */ 
+static int set_PGN(vm_t *vm) {
+  int new_pgN = 0;
+  
+  while(new_pgN < (vm->state).pgc->nr_of_programs 
+	&& (vm->state).cellN >= (vm->state).pgc->program_map[new_pgN])
+    new_pgN++;
+  
+  if(new_pgN == (vm->state).pgc->nr_of_programs) /* We are at the last program */
+    if((vm->state).cellN > (vm->state).pgc->nr_of_cells)
+      return 0; /* We are past the last cell */
+  
+  (vm->state).pgN = new_pgN;
+  
+  if((vm->state).domain == VTS_DOMAIN) {
+    playback_type_t *pb_ty;
+    if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts)
+      return 0; /* ?? */
+    pb_ty = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1].pb_ty;
+    if(pb_ty->multi_or_random_pgc_title == /* One_Sequential_PGC_Title */ 0) {
+      int dummy, part;
+      vm_get_current_title_part(vm, &dummy, &part);
+      (vm->state).PTTN_REG = part;
+    } else {
+      /* FIXME: Handle RANDOM or SHUFFLE titles. */
+      fprintf(MSG_OUT, "libdvdnav: RANDOM or SHUFFLE titles are NOT handled yet.\n");
+    }
+  }
+  return 1;
+}
+
+/* Must be called before domain is changed (set_PGCN()) */
+static void set_RSMinfo(vm_t *vm, int cellN, int blockN) {
+  int i;
+  
+  if(cellN) {
+    (vm->state).rsm_cellN = cellN;
+    (vm->state).rsm_blockN = blockN;
+  } else {
+    (vm->state).rsm_cellN = (vm->state).cellN;
+    (vm->state).rsm_blockN = blockN;
+  }
+  (vm->state).rsm_vtsN = (vm->state).vtsN;
+  (vm->state).rsm_pgcN = get_PGCN(vm);
+  
+  /* assert((vm->state).rsm_pgcN == (vm->state).TT_PGCN_REG);  for VTS_DOMAIN */
+  
+  for(i = 0; i < 5; i++) {
+    (vm->state).rsm_regs[i] = (vm->state).registers.SPRM[4 + i];
+  }
+}
+
+
+/* Get functions */
+
+/* Searches the TT tables, to find the current TT.
+ * returns the current TT.
+ * returns 0 if not found.
+ */
+static int get_TT(vm_t *vm, int vtsN, int vts_ttn) {
+  int i;
+  int tt=0;
+
+  for(i = 1; i <= vm->vmgi->tt_srpt->nr_of_srpts; i++) {
+    if( vm->vmgi->tt_srpt->title[i - 1].title_set_nr == vtsN && 
+        vm->vmgi->tt_srpt->title[i - 1].vts_ttn == vts_ttn) {
+      tt=i;
+      break;
+    }
+  }
+  return tt;
+}
+
+/* Search for entry_id match of the PGC Category in the current VTS PGCIT table.
+ * Return pgcN based on entry_id match.
+ */
+static int get_ID(vm_t *vm, int id) {
+  int pgcN, i;
+  pgcit_t *pgcit;
+  
+  /* Relies on state to get the correct pgcit. */
+  pgcit = get_PGCIT(vm);
+  assert(pgcit != NULL);
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: ** Searching for menu (0x%x) entry PGC\n", id);
+#endif
+
+  /* Force high bit set. */
+  id |=0x80;
+
+  /* Get menu/title */
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    if( (pgcit->pgci_srp[i].entry_id) == id) {
+      pgcN = i + 1;
+#ifdef TRACE
+      fprintf(MSG_OUT, "libdvdnav: Found menu.\n");
+#endif
+      return pgcN;
+    }
+  }
+#ifdef TRACE
+  fprintf(MSG_OUT, "libdvdnav: ** No such id/menu (0x%02x) entry PGC\n", id & 0x7f);
+  for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
+    if ( (pgcit->pgci_srp[i].entry_id & 0x80) == 0x80) {
+      fprintf(MSG_OUT, "libdvdnav: Available menus: 0x%x\n",
+                     pgcit->pgci_srp[i].entry_id & 0x7f);
+    }
+  }
+#endif
+  return 0; /*  error */
+}
+
+/* FIXME: we have a pgcN member in the vm's state now, so this should be obsolete */
+static int get_PGCN(vm_t *vm) {
+  pgcit_t *pgcit;
+  int pgcN = 1;
+
+  pgcit = get_PGCIT(vm);
+  
+  if (pgcit) {
+    while(pgcN <= pgcit->nr_of_pgci_srp) {
+      if(pgcit->pgci_srp[pgcN - 1].pgc == (vm->state).pgc) {
+	assert((vm->state).pgcN == pgcN);
+	return pgcN;
+      }
+      pgcN++;
+    }
+  }
+  fprintf(MSG_OUT, "libdvdnav: get_PGCN failed. Was trying to find pgcN in domain %d\n", 
+         (vm->state).domain);
+  return 0; /*  error */
+}
+
+static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang) {
+  int i;
+  
+  if(h == NULL || h->pgci_ut == NULL) {
+    fprintf(MSG_OUT, "libdvdnav: *** pgci_ut handle is NULL ***\n");
+    return NULL; /*  error? */
+  }
+  
+  i = 0;
+  while(i < h->pgci_ut->nr_of_lus
+	&& h->pgci_ut->lu[i].lang_code != lang)
+    i++;
+  if(i == h->pgci_ut->nr_of_lus) {
+    fprintf(MSG_OUT, "libdvdnav: Language '%c%c' not found, using '%c%c' instead\n",
+	    (char)(lang >> 8), (char)(lang & 0xff),
+ 	    (char)(h->pgci_ut->lu[0].lang_code >> 8),
+	    (char)(h->pgci_ut->lu[0].lang_code & 0xff));
+    fprintf(MSG_OUT, "libdvdnav: Menu Languages available: ");
+    for(i = 0; i < h->pgci_ut->nr_of_lus; i++) {
+      fprintf(MSG_OUT, "%c%c ",
+ 	    (char)(h->pgci_ut->lu[i].lang_code >> 8),
+	    (char)(h->pgci_ut->lu[i].lang_code & 0xff));
+    }
+    fprintf(MSG_OUT, "\n");
+    i = 0; /*  error? */
+  }
+  
+  return h->pgci_ut->lu[i].pgcit;
+}
+
+/* Uses state to decide what to return */
+static pgcit_t* get_PGCIT(vm_t *vm) {
+  pgcit_t *pgcit = NULL;
+  
+  switch ((vm->state).domain) {
+  case VTS_DOMAIN:
+    if(!vm->vtsi) return NULL;
+    pgcit = vm->vtsi->vts_pgcit;
+    break;
+  case VTSM_DOMAIN:
+    if(!vm->vtsi) return NULL;
+    pgcit = get_MENU_PGCIT(vm, vm->vtsi, (vm->state).registers.SPRM[0]);
+    break;
+  case VMGM_DOMAIN:
+  case FP_DOMAIN:
+    pgcit = get_MENU_PGCIT(vm, vm->vmgi, (vm->state).registers.SPRM[0]);
+    break;
+  default:
+    abort();
+  }
+  
+  return pgcit;
+}
+
+//return the ifo_handle_t describing required title, used to 
+//identify chapters
+ifo_handle_t *vm_get_title_ifo(vm_t *vm, uint32_t title)
+{
+  ifo_handle_t *ifo = NULL;
+  uint8_t titleset_nr;
+  if((title < 1) || (title > vm->vmgi->tt_srpt->nr_of_srpts))
+    return NULL;
+  titleset_nr = vm->vmgi->tt_srpt->title[title-1].title_set_nr;
+  ifo = ifoOpen(vm->dvd, titleset_nr);
+  return ifo;
+}
+
+void vm_ifo_close(ifo_handle_t *ifo)
+{
+  ifoClose(ifo);
+}
+
+/* Debug functions */
+
+#ifdef TRACE
+void vm_position_print(vm_t *vm, vm_position_t *position) {
+  fprintf(MSG_OUT, "libdvdnav: But=%x Spu=%x Aud=%x Ang=%x Hop=%x vts=%x dom=%x cell=%x cell_restart=%x cell_start=%x still=%x block=%x\n",
+  position->button,
+  position->spu_channel,
+  position->audio_channel,
+  position->angle_channel,
+  position->hop_channel,
+  position->vts,
+  position->domain,
+  position->cell,
+  position->cell_restart,
+  position->cell_start,
+  position->still,
+  position->block);
+}
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/vm.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2000, 2001 Håkan Hjort
+ * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifndef VM_H_INCLUDED
+#define VM_H_INCLUDED
+
+/* DOMAIN enum */
+
+typedef enum {
+  FP_DOMAIN   = 1,
+  VTS_DOMAIN  = 2,
+  VMGM_DOMAIN = 4,
+  VTSM_DOMAIN = 8
+} domain_t;  
+
+/**
+ * State: SPRM, GPRM, Domain, pgc, pgN, cellN, ?
+ */
+typedef struct {
+  registers_t registers;
+  
+  domain_t  domain;
+  int       vtsN;         /* 0 is vmgm? */
+  pgc_t    *pgc;          /* either this or 'int pgcN' is enough? */
+  int       pgcN;         /* but provide pgcN for quick lookup */
+  int       pgN;          /* is this needed? can allways fid pgN from cellN? */
+  int       cellN;
+  int32_t   cell_restart; /* get cell to restart */
+  int       blockN;
+  
+  /* Resume info */
+  int      rsm_vtsN;
+  int      rsm_blockN;    /* of nav_packet */
+  uint16_t rsm_regs[5];   /* system registers 4-8 */
+  int      rsm_pgcN;
+  int      rsm_cellN;
+} dvd_state_t;
+
+typedef struct vm_position_s {
+  int16_t  button;        /* Button highlighted */
+  int32_t  vts;           /* vts number to use */
+  domain_t domain;        /* domain to use */
+  int32_t  spu_channel;   /* spu channel to use */
+  int32_t  angle_channel; /* angle channel to use */
+  int32_t  audio_channel; /* audio channel to use */
+  int32_t  hop_channel;   /* channel hopping. E.g menu button pressed */
+#if 0
+  /* currently unused */
+  int32_t  title;         /* title number */
+  int32_t  chapter;       /* chapter number */
+#endif
+  int32_t  cell;          /* cell number */
+  int32_t  cell_restart;  /* get cell to restart */
+  int32_t  cell_start;    /* sector number of start of current cell in use */
+  int32_t  still;         /* is cell still */
+  int32_t  block;         /* block number within cell in use */
+} vm_position_t;
+
+typedef struct {
+  dvd_reader_t *dvd;
+  ifo_handle_t *vmgi;
+  ifo_handle_t *vtsi;
+  dvd_state_t   state;
+  int32_t       hop_channel;
+  char          dvd_name[50];
+  remap_t      *map;
+  int           stopped;
+} vm_t;
+
+/* magic number for seeking hops */
+#define HOP_SEEK 0x1000
+
+
+/*  Audio stream number */
+#define AST_REG      registers.SPRM[1]
+/*  Subpicture stream number */
+#define SPST_REG     registers.SPRM[2]
+/*  Angle number */
+#define AGL_REG      registers.SPRM[3]
+/*  Title Track Number */
+#define TTN_REG      registers.SPRM[4]
+/*  VTS Title Track Number */
+#define VTS_TTN_REG  registers.SPRM[5]
+/*  PGC Number for this Title Track */
+#define TT_PGCN_REG  registers.SPRM[6]
+/*  Current Part of Title (PTT) number for (One_Sequential_PGC_Title) */
+#define PTTN_REG     registers.SPRM[7]
+/*  Highlighted Button Number (btn nr 1 == value 1024) */
+#define HL_BTNN_REG  registers.SPRM[8]
+/*  Parental Level */
+#define PTL_REG      registers.SPRM[13]
+
+/* Initialisation & destruction */
+vm_t *vm_new_vm(void);
+void  vm_free_vm(vm_t *vm);
+
+/* IFO access */
+ifo_handle_t *vm_get_vmgi(vm_t *vm);
+ifo_handle_t *vm_get_vtsi(vm_t *vm);
+
+/* Reader Access */
+dvd_reader_t *vm_get_dvd_reader(vm_t *vm);
+
+/* Basic Handling */
+int  vm_start(vm_t *vm);
+void vm_stop(vm_t *vm);
+int  vm_reset(vm_t *vm, const char *dvdroot);
+
+/* copying and merging  - useful for try-running an operation */
+vm_t *vm_new_copy(vm_t *vm);
+void  vm_merge(vm_t *target, vm_t *source);
+void  vm_free_copy(vm_t *vm);
+
+/* regular playback */
+void vm_position_get(vm_t *vm, vm_position_t *position);
+void vm_get_next_cell(vm_t *vm);
+
+/* Jumping - all these return 1, if a hop has been performed */
+int vm_jump_pg(vm_t *vm, int pg);
+int vm_jump_cell_block(vm_t *vm, int cell, int block);
+int vm_jump_title_part(vm_t *vm, int title, int part);
+int vm_jump_top_pg(vm_t *vm);
+int vm_jump_next_pg(vm_t *vm);
+int vm_jump_prev_pg(vm_t *vm);
+int vm_jump_up(vm_t *vm);
+int vm_jump_menu(vm_t *vm, DVDMenuID_t menuid);
+int vm_jump_resume(vm_t *vm);
+int vm_exec_cmd(vm_t *vm, vm_cmd_t *cmd);
+
+/* getting information */
+int vm_get_current_menu(vm_t *vm, int *menuid);
+int vm_get_current_title_part(vm_t *vm, int *title_result, int *part_result);
+int vm_get_audio_stream(vm_t *vm, int audioN);
+int vm_get_subp_stream(vm_t *vm, int subpN, int mode);
+int vm_get_audio_active_stream(vm_t *vm);
+int vm_get_subp_active_stream(vm_t *vm, int mode);
+void vm_get_angle_info(vm_t *vm, int *current, int *num_avail);
+#if 0
+/* currently unused */
+void vm_get_audio_info(vm_t *vm, int *current, int *num_avail);
+void vm_get_subp_info(vm_t *vm, int *current, int *num_avail);
+void vm_get_video_res(vm_t *vm, int *width, int *height);
+#endif
+int  vm_get_video_aspect(vm_t *vm);
+int  vm_get_video_scale_permission(vm_t *vm);
+video_attr_t vm_get_video_attr(vm_t *vm);
+audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN);
+subp_attr_t  vm_get_subp_attr(vm_t *vm, int streamN);
+ifo_handle_t *vm_get_title_ifo(vm_t *vm, uint32_t title);
+void vm_ifo_close(ifo_handle_t *ifo);
+
+/* Uncomment for VM command tracing */
+/* #define TRACE */
+#ifdef TRACE
+/* Debug */
+void vm_position_print(vm_t *vm, vm_position_t *position);
+#endif
+
+
+#endif /* VM_HV_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/vmcmd.c	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ *               2002-2004 the dvdnav project
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sys/time.h>
+
+#include "dvd_types.h"
+#include "nav_types.h"
+#include "ifo_types.h"
+#include "decoder.h"
+#include "remap.h"
+#include "vm.h"
+#include "vmcmd.h"
+#include "dvdnav.h"
+#include "dvdnav_internal.h"
+
+/*  freebsd compatibility */
+#ifndef PRIu8
+#define PRIu8 "d"
+#endif
+
+/*  freebsd compatibility */
+#ifndef PRIu16
+#define PRIu16 "d"
+#endif
+
+static const char cmp_op_table[][4] = {
+  "", "&", "==", "!=", ">=", ">", "<=", "<"
+};
+static const char set_op_table[][4] = {
+  "", "=", "<->", "+=", "-=", "*=", "/=", "%=", "rnd", "&=", "|=", "^="
+};
+
+static const char link_table[][16] = {
+  "LinkNoLink",  "LinkTopC",    "LinkNextC",   "LinkPrevC",
+  "",            "LinkTopPG",   "LinkNextPG",  "LinkPrevPG",
+  "",            "LinkTopPGC",  "LinkNextPGC", "LinkPrevPGC",
+  "LinkGoUpPGC", "LinkTailPGC", "",            "",
+  "RSM"
+};
+
+static const char *const system_reg_table[] = {
+  "Menu Description Language Code",
+  "Audio Stream Number",
+  "Sub-picture Stream Number",
+  "Angle Number",
+  "Title Track Number",
+  "VTS Title Track Number",
+  "VTS PGC Number",
+  "PTT Number for One_Sequential_PGC_Title",
+  "Highlighted Button Number",
+  "Navigation Timer",
+  "Title PGC Number for Navigation Timer",
+  "Audio Mixing Mode for Karaoke",
+  "Country Code for Parental Management",
+  "Parental Level",
+  "Player Configurations for Video",
+  "Player Configurations for Audio",
+  "Initial Language Code for Audio",
+  "Initial Language Code Extension for Audio",
+  "Initial Language Code for Sub-picture",
+  "Initial Language Code Extension for Sub-picture",
+  "Player Regional Code",
+  "Reserved 21",
+  "Reserved 22",
+  "Reserved 23"
+};
+
+static const char system_reg_abbr_table[][8] = {
+  "",
+  "ASTN",
+  "SPSTN",
+  "AGLN",
+  "TTN",
+  "VTS_TTN",
+  "TT_PGCN",
+  "PTTN",
+  "HL_BTNN",
+  "NVTMR",
+  "NV_PGCN",
+  "",
+  "CC_PLT",
+  "PLT",
+  "",
+  "",
+  "",
+  "",
+  "",
+  "",
+  "",
+  "",
+  "",
+  "",
+};
+
+static void print_system_reg(uint16_t reg) {
+  if(reg < sizeof(system_reg_abbr_table) / sizeof(system_reg_abbr_table[0]))
+    fprintf(MSG_OUT, "%s (SRPM:%d)", system_reg_table[reg], reg);
+  else
+    fprintf(MSG_OUT, " WARNING: Unknown system register ( reg=%d ) ", reg);
+}
+
+static void print_g_reg(uint8_t reg) {
+    if(reg < 16)
+      fprintf(MSG_OUT, "g[%" PRIu8 "]", reg);
+    else
+      fprintf(MSG_OUT, " WARNING: Unknown general register ");
+}
+
+static void print_reg(uint8_t reg) {
+  if(reg & 0x80)
+    print_system_reg(reg & 0x7f);
+  else
+    print_g_reg(reg & 0x7f);
+}
+
+static void print_cmp_op(uint8_t op) {
+  if(op < sizeof(cmp_op_table) / sizeof(cmp_op_table[0]))
+    fprintf(MSG_OUT, " %s ", cmp_op_table[op]);
+  else
+    fprintf(MSG_OUT, " WARNING: Unknown compare op ");
+}
+
+static void print_set_op(uint8_t op) {
+  if(op < sizeof(set_op_table) / sizeof(cmp_op_table[0]))
+    fprintf(MSG_OUT, " %s ", set_op_table[op]);
+  else
+    fprintf(MSG_OUT, " WARNING: Unknown set op ");
+}
+
+static void print_reg_or_data(command_t* command, int immediate, int start) {
+  if(immediate) {
+    uint32_t i = vm_getbits(command, start, 16);
+    
+    fprintf(MSG_OUT, "0x%x", i);
+    if(isprint(i & 0xff) && isprint((i>>8) & 0xff))
+      fprintf(MSG_OUT, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff));
+  } else {
+    print_reg(vm_getbits(command, start - 8, 8));
+  }
+}
+
+static void print_reg_or_data_2(command_t* command, int immediate, int start) {
+  if(immediate)
+    fprintf(MSG_OUT, "0x%x", vm_getbits(command, start - 1, 7));
+  else
+    fprintf(MSG_OUT, "g[%" PRIu8 "]", vm_getbits(command, start - 4, 4));
+}
+
+static void print_reg_or_data_3(command_t* command, int immediate, int start) {
+  if(immediate) {
+    uint32_t i = vm_getbits(command, start, 16);
+    
+    fprintf(MSG_OUT, "0x%x", i);
+    if(isprint(i & 0xff) && isprint((i>>8) & 0xff))
+      fprintf(MSG_OUT, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff));
+  } else {
+    print_reg(vm_getbits(command, start, 8));
+  }
+}
+
+
+static void print_if_version_1(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  
+  if(op) {
+    fprintf(MSG_OUT, "if (");
+    print_g_reg(vm_getbits(command,39,8));
+    print_cmp_op(op);
+    print_reg_or_data(command, vm_getbits(command, 55,1), 31);
+    fprintf(MSG_OUT, ") ");
+  }
+}
+
+static void print_if_version_2(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  
+  if(op) {
+    fprintf(MSG_OUT, "if (");
+    print_reg(vm_getbits(command, 15, 8));
+    print_cmp_op(op);
+    print_reg(vm_getbits(command, 7, 8));
+    fprintf(MSG_OUT, ") ");
+  }
+}
+
+static void print_if_version_3(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  
+  if(op) {
+    fprintf(MSG_OUT, "if (");
+    print_g_reg(vm_getbits(command, 43, 4));
+    print_cmp_op(op);
+    print_reg_or_data(command, vm_getbits(command, 55, 1), 15);
+    fprintf(MSG_OUT, ") ");
+  }
+}
+
+static void print_if_version_4(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+
+  if(op) {
+    fprintf(MSG_OUT, "if (");
+    print_g_reg(vm_getbits(command, 51, 4));
+    print_cmp_op(op);
+    print_reg_or_data(command, vm_getbits(command, 55, 1), 31);
+    fprintf(MSG_OUT, ") ");
+  }
+}
+
+static void print_if_version_5(command_t* command) {
+  uint8_t op = vm_getbits(command, 54, 3);
+  int set_immediate = vm_getbits(command, 60, 1);
+  
+  if(op) {
+    if (set_immediate) {
+      fprintf(MSG_OUT, "if (");
+      print_g_reg(vm_getbits(command, 31, 8));
+      print_cmp_op(op);
+      print_reg(vm_getbits(command, 23, 8));
+      fprintf(MSG_OUT, ") ");
+    } else {
+      fprintf(MSG_OUT, "if (");
+      print_g_reg(vm_getbits(command, 39, 8));
+      print_cmp_op(op);
+      print_reg_or_data(command, vm_getbits(command, 55, 1), 31);
+      fprintf(MSG_OUT, ") ");
+    }
+  }
+}
+
+static void print_special_instruction(command_t* command) {
+  uint8_t op = vm_getbits(command, 51, 4);
+  
+  switch(op) {
+    case 0: /*  NOP */
+      fprintf(MSG_OUT, "Nop");
+      break;
+    case 1: /*  Goto line */
+      fprintf(MSG_OUT, "Goto %" PRIu8, vm_getbits(command, 7, 8));
+      break;
+    case 2: /*  Break */
+      fprintf(MSG_OUT, "Break");
+      break;
+    case 3: /*  Parental level */
+      fprintf(MSG_OUT, "SetTmpPML %" PRIu8 ", Goto %" PRIu8, 
+	      vm_getbits(command, 11, 4), vm_getbits(command, 7, 8));
+      break;
+    default:
+      fprintf(MSG_OUT, "WARNING: Unknown special instruction (%i)", 
+	      vm_getbits(command, 51, 4));
+  }
+}
+
+static void print_linksub_instruction(command_t* command) {
+  uint32_t linkop = vm_getbits(command, 7, 8);
+  uint32_t button = vm_getbits(command, 15, 6);
+  
+  if(linkop < sizeof(link_table)/sizeof(link_table[0]))
+    fprintf(MSG_OUT, "%s (button %" PRIu8 ")", link_table[linkop], button);
+  else
+    fprintf(MSG_OUT, "WARNING: Unknown linksub instruction (%i)", linkop);
+}
+
+static void print_link_instruction(command_t* command, int optional) {
+  uint8_t op = vm_getbits(command, 51, 4);
+  
+  if(optional && op)
+    fprintf(MSG_OUT, ", ");
+  
+  switch(op) {
+    case 0:
+      if(!optional)
+      fprintf(MSG_OUT, "WARNING: NOP (link)!");
+      break;
+    case 1:
+      print_linksub_instruction(command);
+      break;
+    case 4:
+      fprintf(MSG_OUT, "LinkPGCN %" PRIu16, vm_getbits(command, 14, 15));
+      break;
+    case 5:
+      fprintf(MSG_OUT, "LinkPTT %" PRIu16 " (button %" PRIu8 ")", 
+	      vm_getbits(command, 9, 10), vm_getbits(command, 15, 6));
+      break;
+    case 6:
+      fprintf(MSG_OUT, "LinkPGN %" PRIu8 " (button %" PRIu8 ")", 
+	      vm_getbits(command, 6, 7), vm_getbits(command, 15, 6));
+      break;
+    case 7:
+      fprintf(MSG_OUT, "LinkCN %" PRIu8 " (button %" PRIu8 ")", 
+	      vm_getbits(command, 7, 8), vm_getbits(command, 15, 6));
+      break;
+    default:
+      fprintf(MSG_OUT, "WARNING: Unknown link instruction");
+  }
+}
+
+static void print_jump_instruction(command_t* command) {
+  switch(vm_getbits(command, 51, 4)) {
+    case 1:
+      fprintf(MSG_OUT, "Exit");
+      break;
+    case 2:
+      fprintf(MSG_OUT, "JumpTT %" PRIu8, vm_getbits(command, 22, 7));
+      break;
+    case 3:
+      fprintf(MSG_OUT, "JumpVTS_TT %" PRIu8, vm_getbits(command, 22, 7));
+      break;
+    case 5:
+      fprintf(MSG_OUT, "JumpVTS_PTT %" PRIu8 ":%" PRIu16, 
+	      vm_getbits(command, 22, 7), vm_getbits(command, 41, 10));
+      break;
+    case 6:
+      switch(vm_getbits(command, 23, 2)) {
+        case 0:
+          fprintf(MSG_OUT, "JumpSS FP");
+          break;
+        case 1:
+          fprintf(MSG_OUT, "JumpSS VMGM (menu %" PRIu8 ")", vm_getbits(command, 19, 4));
+          break;
+        case 2:
+          fprintf(MSG_OUT, "JumpSS VTSM (vts %" PRIu8 ", title %" PRIu8 
+		  ", menu %" PRIu8 ")", vm_getbits(command, 30, 7), vm_getbits(command, 38, 7), vm_getbits(command, 19, 4));
+          break;
+        case 3:
+          fprintf(MSG_OUT, "JumpSS VMGM (pgc %" PRIu8 ")", vm_getbits(command, 46, 15));
+          break;
+        }
+      break;
+    case 8:
+      switch(vm_getbits(command, 23, 2)) {
+        case 0:
+          fprintf(MSG_OUT, "CallSS FP (rsm_cell %" PRIu8 ")",
+              vm_getbits(command, 31, 8));
+          break;
+        case 1:
+          fprintf(MSG_OUT, "CallSS VMGM (menu %" PRIu8 
+		  ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8));
+          break;
+        case 2:
+          fprintf(MSG_OUT, "CallSS VTSM (menu %" PRIu8 
+		  ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8));
+          break;
+        case 3:
+          fprintf(MSG_OUT, "CallSS VMGM (pgc %" PRIu8 ", rsm_cell %" PRIu8 ")", 
+		  vm_getbits(command, 46, 15), vm_getbits(command, 31, 8));
+          break;
+      }
+      break;
+    default:
+      fprintf(MSG_OUT, "WARNING: Unknown Jump/Call instruction");
+  }
+}
+
+static void print_system_set(command_t* command) {
+  int i;
+/* FIXME: What about SPRM11 ? Karaoke */
+/*        Surely there must be some system set command for that ? */
+  
+  switch(vm_getbits(command, 59, 4)) {
+    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
+      for(i = 1; i <= 3; i++) {
+        if(vm_getbits(command, 47 - (i*8), 1)) {
+          print_system_reg(i);
+          fprintf(MSG_OUT, " = ");
+          print_reg_or_data_2(command, vm_getbits(command, 60, 1), 47 - (i*8) );
+          fprintf(MSG_OUT, " ");
+        }
+      }
+      break;
+    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
+      print_system_reg(9);
+      fprintf(MSG_OUT, " = ");
+      print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+      fprintf(MSG_OUT, " ");
+      print_system_reg(10);
+      fprintf(MSG_OUT, " = %" PRIu16, vm_getbits(command, 30, 15)); /*  ?? */
+      break;
+    case 3: /*  Mode: Counter / Register + Set */
+      fprintf(MSG_OUT, "SetMode ");
+      if(vm_getbits(command, 23, 1))
+	fprintf(MSG_OUT, "Counter ");
+      else
+	fprintf(MSG_OUT, "Register ");
+      print_g_reg(vm_getbits(command, 19, 4));
+      print_set_op(0x1); /*  '=' */
+      print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+      break;
+    case 6: /*  Set system reg 8 (Highlighted button) */
+      print_system_reg(8);
+      if(vm_getbits(command, 60, 1)) /*  immediate */
+        fprintf(MSG_OUT, " = 0x%x (button no %d)", vm_getbits(command, 31, 16), vm_getbits(command, 31, 6));
+      else
+        fprintf(MSG_OUT, " = g[%" PRIu8 "]", vm_getbits(command, 19, 4));
+      break;
+    default:
+      fprintf(MSG_OUT, "WARNING: Unknown system set instruction (%i)", 
+	      vm_getbits(command, 59, 4));
+  }
+}
+
+static void print_set_version_1(command_t* command) {
+  uint8_t set_op = vm_getbits(command, 59, 4);
+  
+  if(set_op) {
+    print_g_reg(vm_getbits(command, 35, 4));
+    print_set_op(set_op);
+    print_reg_or_data(command, vm_getbits(command, 60, 1), 31);
+  } else {
+    fprintf(MSG_OUT, "NOP");
+  }
+}
+
+static void print_set_version_2(command_t* command) {
+  uint8_t set_op = vm_getbits(command, 59, 4);
+  
+  if(set_op) {
+    print_g_reg(vm_getbits(command, 51, 4));
+    print_set_op(set_op);
+    print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
+  } else {
+    fprintf(MSG_OUT, "NOP");
+  }
+}
+
+static void print_set_version_3(command_t* command) {
+  uint8_t set_op = vm_getbits(command, 59, 4);
+  
+  if(set_op) {
+    print_g_reg(vm_getbits(command, 51, 4));
+    print_set_op(set_op);
+    print_reg_or_data_3(command, vm_getbits(command, 60, 1), 47);
+  } else {
+    fprintf(MSG_OUT, "NOP");
+  }
+}
+
+
+void vm_print_mnemonic(vm_cmd_t *vm_command)  {
+  command_t command;
+  command.instruction =( (uint64_t) vm_command->bytes[0] << 56 ) |
+        ( (uint64_t) vm_command->bytes[1] << 48 ) |
+        ( (uint64_t) vm_command->bytes[2] << 40 ) |
+        ( (uint64_t) vm_command->bytes[3] << 32 ) |
+        ( (uint64_t) vm_command->bytes[4] << 24 ) |
+        ( (uint64_t) vm_command->bytes[5] << 16 ) |
+        ( (uint64_t) vm_command->bytes[6] <<  8 ) |
+          (uint64_t) vm_command->bytes[7] ;
+  command.examined = 0; 
+
+  switch(vm_getbits(&command,63,3)) { /* three first bits */
+    case 0: /*  Special instructions */
+      print_if_version_1(&command);
+      print_special_instruction(&command);
+      break;
+    case 1: /*  Jump/Call or Link instructions */
+      if(vm_getbits(&command,60,1)) {
+        print_if_version_2(&command);
+        print_jump_instruction(&command);
+      } else {
+        print_if_version_1(&command);
+        print_link_instruction(&command, 0); /*  must be pressent */
+      }
+      break;
+    case 2: /*  Set System Parameters instructions */
+      print_if_version_2(&command);
+      print_system_set(&command);
+      print_link_instruction(&command, 1); /*  either 'if' or 'link' */
+      break;
+    case 3: /*  Set General Parameters instructions */
+      print_if_version_3(&command);
+      print_set_version_1(&command);
+      print_link_instruction(&command, 1); /*  either 'if' or 'link' */
+      break;
+    case 4: /*  Set, Compare -> LinkSub instructions */
+      print_set_version_2(&command);
+      fprintf(MSG_OUT, ", ");
+      print_if_version_4(&command);
+      print_linksub_instruction(&command);
+      break;
+    case 5: /*  Compare -> (Set and LinkSub) instructions */
+      print_if_version_5(&command);
+      fprintf(MSG_OUT, "{ ");
+      print_set_version_3(&command);
+      fprintf(MSG_OUT, ", ");
+      print_linksub_instruction(&command);
+      fprintf(MSG_OUT, " }");
+      break;
+    case 6: /*  Compare -> Set, always LinkSub instructions */
+      print_if_version_5(&command);
+      fprintf(MSG_OUT, "{ ");
+      print_set_version_3(&command);
+      fprintf(MSG_OUT, " } ");
+      print_linksub_instruction(&command);
+      break;
+    default:
+      fprintf(MSG_OUT, "WARNING: Unknown instruction type (%i)", vm_getbits(&command, 63, 3));
+  }
+  /*  Check if there still are bits set that were not examined */
+  
+  if(command.instruction & ~ command.examined) {
+    fprintf(MSG_OUT, " libdvdnav: vmcmd.c: [WARNING, unknown bits:");
+    fprintf(MSG_OUT, " %08"PRIx64, (command.instruction & ~ command.examined) );
+    fprintf(MSG_OUT, "]");
+  }
+}
+
+void vm_print_cmd(int row, vm_cmd_t *vm_command) {
+  int i;
+
+  fprintf(MSG_OUT, "(%03d) ", row + 1);
+  for(i = 0; i < 8; i++)
+    fprintf(MSG_OUT, "%02x ", vm_command->bytes[i]);
+  fprintf(MSG_OUT, "| ");
+
+  vm_print_mnemonic(vm_command);
+  fprintf(MSG_OUT, "\n");
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm/vmcmd.h	Sun Jun 01 08:39:07 2008 +0000
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2000, 2001 Martin Norbäck, Håkan Hjort
+ * 
+ * This file is part of libdvdnav, a DVD navigation library. It is modified
+ * from a file originally part of the Ogle DVD player.
+ * 
+ * libdvdnav is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdvdnav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+ *
+ * $Id$
+ *
+ */
+
+#ifndef VMCMD_H_INCLUDED
+#define VMCMD_H_INCLUDED
+
+void vm_print_mnemonic(vm_cmd_t *command);
+void vm_print_cmd(int row, vm_cmd_t *command);
+
+#endif /* VMCMD_H_INCLUDED */