Attached patch for removing the hacky overload of "struct AD" which is also used for File operations, resulting in 64-bit to 32-bit conversions and more.

Additionally, libdvdread assumed all blocks fit inside one AD chain, in contiguous order, which is generally true for all DVDs. (Since files are limited to 1GB in size). I have extended this to correctly handle the UDF AD chaining as the ECMA_167 specifications detail.

All 32-bit filesize references have been changed to 64-bit.

UDFFindFile() no longer return a "block number" but a reference to a "UDF_FILE". This should be freed by calling UDFFileFree(). All calls from dvd_reader.c have been updated.

An additional function UDFFileBlockPos() has added to translate between a file's block offset, into correct disk block. (Walking the AD chains to find correct block, as well as, File start block and partition block number).

Tested only against title_info.c since I need all patches for complete testing.


--
Jorgen Lundman       | <[EMAIL PROTECTED]>
Unix Administrator   | +81 (0)3 -5456-2687 ext 1017 (work)
Shibuya-ku, Tokyo    | +81 (0)90-5578-8500          (cell)
Japan                | +81 (0)3 -3375-1767          (home)
Index: dvd_reader.c
===================================================================
--- dvd_reader.c        (revision 1157)
+++ dvd_reader.c        (working copy)
@@ -102,7 +102,7 @@
   int css_title;
 
   /* Information required for an image file. */
-  uint32_t lb_start;
+  UDF_FILE udf_file;
   uint32_t seek_pos;
 
   /* Information required for a directory path drive. */
@@ -111,6 +111,10 @@
 
   /* Calculated at open-time, size in blocks. */
   ssize_t filesize;
+
+  /* Size of file in bytes. */
+  uint64_t filebytes;
+
 };
 
 int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
@@ -159,7 +163,9 @@
   struct timeval all_s, all_e;
   struct timeval t_s, t_e;
   char filename[ MAX_UDF_FILE_NAME_LEN ];
-  uint32_t start, len;
+  UDF_FILE udf_file;
+  uint32_t start;
+  uint64_t len;
   int title;
 
   char *nokeys_str = getenv("DVDREAD_NOKEYS");
@@ -179,9 +185,10 @@
     } else {
       sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, 0 );
     }
-    start = UDFFindFile( dvd, filename, &len );
-    if( start != 0 && len != 0 ) {
+    udf_file = UDFFindFile( dvd, filename, &len );
+    if( udf_file != NULL && len != 0 ) {
       /* Perform CSS key cracking for this title. */
+      start = UDFFileBlockPos(udf_file, 0);
       fprintf( stderr, "libdvdread: Get key for %s at 0x%08x\n",
                filename, start );
       if( dvdinput_title( dvd->dev, (int)start ) < 0 ) {
@@ -192,14 +199,18 @@
                (long int) t_e.tv_sec - t_s.tv_sec );
     }
 
+    UDFFreeFile(dvd, udf_file);
+    udf_file = NULL;
+
     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;
+    udf_file = UDFFindFile( dvd, filename, &len );
+    if( udf_file == 0 || len == 0 ) break;
 
     /* Perform CSS key cracking for this title. */
+    start = UDFFileBlockPos(udf_file, 0);
     fprintf( stderr, "libdvdread: Get key for %s at 0x%08x\n",
              filename, start );
     if( dvdinput_title( dvd->dev, (int)start ) < 0 ) {
@@ -216,6 +227,9 @@
   fprintf( stderr, "libdvdread: Elapsed time %ld\n",
            (long int) all_e.tv_sec - all_s.tv_sec );
 
+  UDFFreeFile(dvd, udf_file);
+  udf_file = NULL;
+
   return 0;
 }
 
@@ -565,11 +579,12 @@
  */
 static dvd_file_t *DVDOpenFileUDF( dvd_reader_t *dvd, char *filename )
 {
-  uint32_t start, len;
+  UDF_FILE udf_file;
+  uint64_t len;
   dvd_file_t *dvd_file;
 
-  start = UDFFindFile( dvd, filename, &len );
-  if( !start ) {
+  udf_file = UDFFindFile( dvd, filename, &len );
+  if( !udf_file ) {
     fprintf( stderr, "libdvdnav:DVDOpenFileUDF:UDFFindFile %s failed\n", 
filename );
     return NULL;
   }
@@ -580,11 +595,12 @@
     return NULL;
   }
   dvd_file->dvd = dvd;
-  dvd_file->lb_start = start;
+  dvd_file->udf_file = udf_file;
   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;
+  dvd_file->filebytes = len;
 
   return dvd_file;
 }
@@ -676,7 +692,7 @@
     return NULL;
   }
   dvd_file->dvd = dvd;
-  dvd_file->lb_start = 0;
+  dvd_file->udf_file = NULL;
   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 ) );
@@ -690,6 +706,7 @@
   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 ];
+  dvd_file->filebytes = fileinfo.st_size;
 
   return dvd_file;
 }
@@ -697,7 +714,8 @@
 static dvd_file_t *DVDOpenVOBUDF( dvd_reader_t *dvd, int title, int menu )
 {
   char filename[ MAX_UDF_FILE_NAME_LEN ];
-  uint32_t start, len;
+  UDF_FILE udf_file;
+  uint64_t len;
   dvd_file_t *dvd_file;
 
   if( title == 0 ) {
@@ -705,18 +723,19 @@
   } else {
     sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, menu ? 0 : 1 );
   }
-  start = UDFFindFile( dvd, filename, &len );
-  if( start == 0 ) return NULL;
+  udf_file = UDFFindFile( dvd, filename, &len );
+  if( udf_file == NULL ) 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->udf_file = udf_file;
   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;
+  dvd_file->filebytes = len;
 
   /* Calculate the complete file size for every file in the VOBS */
   if( !menu ) {
@@ -755,11 +774,12 @@
   if( !dvd_file ) return NULL;
   dvd_file->dvd = dvd;
   /*Hack*/ dvd_file->css_title = title << 1 | menu;
-  dvd_file->lb_start = 0;
+  dvd_file->udf_file = NULL;
   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;
+  dvd_file->filebytes = 0;
 
   if( menu ) {
     dvd_input_t dev;
@@ -884,6 +904,9 @@
       }
     }
 
+    if (dvd_file->udf_file)
+      UDFFreeFile(NULL, dvd_file->udf_file);
+
     free( dvd_file );
     dvd_file = 0;
   }
@@ -922,7 +945,8 @@
                              size_t block_count, unsigned char *data,
                              int encrypted )
 {
-  return UDFReadBlocksRaw( dvd_file->dvd, dvd_file->lb_start + offset,
+  return UDFReadBlocksRaw( dvd_file->dvd,
+                           UDFFileBlockPos(dvd_file->udf_file, offset),
                            block_count, data, encrypted );
 }
 
@@ -1014,7 +1038,7 @@
   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 );
+      dvdinput_title( dvd_file->dvd->dev, 
(int)UDFFileBlockPos(dvd_file->udf_file, 0) );
     }
     /* Here each vobu has it's own dvdcss handle, so no need to update
     else {
@@ -1046,6 +1070,15 @@
   return offset;
 }
 
+uint64_t DVDFileSize64( dvd_file_t *dvd_file )
+{
+  /* Check arguments. */
+  if( dvd_file == NULL )
+    return -1;
+
+  return dvd_file->filebytes;
+}
+
 int DVDFileSeekForce(dvd_file_t *dvd_file, int offset, int force_size)
 {
   /* Check arguments. */
Index: dvd_reader.h
===================================================================
--- dvd_reader.h        (revision 1157)
+++ dvd_reader.h        (working copy)
@@ -196,6 +196,11 @@
 ssize_t DVDFileSize( dvd_file_t * );
 
 /**
+ * Define separate 64-bit version to be back-ward compatible.
+ */
+uint64_t DVDFileSize64( 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).
Index: dvd_udf.c
===================================================================
--- dvd_udf.c   (revision 1157)
+++ dvd_udf.c   (working copy)
@@ -88,6 +88,10 @@
   uint32_t Length;
 };
 
+/* Previously, dvdread re-used AD to carry up the filesize, resulting in
+ * a 64bit->32bit problem. This is acceptable on most DVD images (1GB VOB 
files)
+ * but not desirable. Now it has changed to use a FileAD when it is actually
+ * used for files, and AD otherwise.   - Lund */
 struct AD {
   uint32_t Location;
   uint32_t Length;
@@ -95,6 +99,25 @@
   uint16_t Partition;
 };
 
+/* Previously dvdread would assume files only had one AD chain, and since they
+ * are 1GB or less, this is most problably true. However, now we handle chains
+ * for large files. ECMA_167 does not specify the maximum number of chains, is
+ * it as many as can fit in a 2048 block? (minus ID 266 size), or some other
+ * limit. For now, I have assumed that;
+ * a 4.4GB file uses 5 AD chains. A BluRay disk can store 50GB of data, so the
+ * largest file should be 50 GB. So the maximum number of chains should be
+ * around 62. We could also change it to be allocated as needed.
+ */
+
+#define UDF_MAX_AD_CHAINS 50
+
+struct FileAD {
+    uint64_t Length;
+    uint32_t num_AD;
+    uint32_t Partition_Start;
+    struct AD AD_chain[UDF_MAX_AD_CHAINS];
+};
+
 struct extent_ad {
   uint32_t location;
   uint32_t length;
@@ -119,7 +142,7 @@
 
 struct icbmap {
   uint32_t lbn;
-  struct AD file;
+  struct FileAD file;
   uint8_t filetype;
 };
 
@@ -142,6 +165,17 @@
   PartitionCache, RootICBCache, LBUDFCache, MapCache, AVDPCache, PVDCache
 } UDFCacheType;
 
+/*
+ * Release an allocated FileAD data. Technically you can just call free()
+ * but it is clean to stay modular. (Should we one day record further data.
+ */
+void UDFFreeFile(dvd_reader_t *device, struct FileAD *File)
+{
+  free(File);
+}
+
+
+
 void FreeUDFCache(void *cache)
 {
   struct udf_cache *c = (struct udf_cache *)cache;
@@ -329,6 +363,15 @@
                   | ((uint32_t)data[(p) + 1] << 8)      \
                   | ((uint32_t)data[(p) + 2] << 16)     \
                   | ((uint32_t)data[(p) + 3] << 24))
+#define GETN8(p) ((uint64_t)data[p]                 \
+                  | ((uint64_t)data[(p) + 1] << 8)  \
+                  | ((uint64_t)data[(p) + 2] << 16) \
+                  | ((uint64_t)data[(p) + 3] << 24) \
+                  | ((uint64_t)data[(p) + 4] << 32) \
+                  | ((uint64_t)data[(p) + 5] << 40) \
+                  | ((uint64_t)data[(p) + 6] << 48) \
+                  | ((uint64_t)data[(p) + 7] << 56))
+
 /* This is wrong with regard to endianess */
 #define GETN(p, n, target) memcpy(target, &data[p], n)
 
@@ -429,38 +472,65 @@
 }
 
 static int UDFFileEntry( uint8_t *data, uint8_t *FileType,
-                         struct Partition *partition, struct AD *ad )
+                         struct Partition *partition, struct FileAD *fad )
 {
   uint16_t flags;
   uint32_t L_EA, L_AD;
   unsigned int p;
+  unsigned int curr_ad;
 
   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 */
+  fad->Length = GETN4( 56 ); /* Was 4 bytes at 60, changed to 64-bit. */
 
   L_EA = GETN4( 168 );
   L_AD = GETN4( 172 );
   p = 176 + L_EA;
+  curr_ad = 0;
+
+  /* Function changed to record all AD chains, not just the last one! */
   while( p < 176 + L_EA + L_AD ) {
+    struct AD *ad;
+
+    if (curr_ad >= UDF_MAX_AD_CHAINS) return 0;
+
+    ad =  &fad->AD_chain[curr_ad];
+    ad->Partition = partition->Number;
+    ad->Flags = 0;
+    // Increase AD chain ptr
+    curr_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 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;
+      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;
+      p += L_AD;
+      break;
     }
   }
   return 0;
@@ -489,7 +559,7 @@
  * 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 )
+                      struct Partition *partition, struct FileAD *File )
 {
   uint8_t LogBlock_base[DVD_VIDEO_LB_LEN + 2048];
   uint8_t *LogBlock = (uint8_t *)(((uintptr_t)LogBlock_base & 
~((uintptr_t)2047)) + 2048);
@@ -530,7 +600,7 @@
  * 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,
+static int UDFScanDir( dvd_reader_t *device, struct FileAD Dir, char *FileName,
                        struct Partition *partition, struct AD *FileICB,
                        int cache_file_info)
 {
@@ -548,13 +618,13 @@
   int in_cache = 0;
 
   /* Scan dir for ICB of file */
-  lbnum = partition->Start + Dir.Location;
+  lbnum = partition->Start + Dir.AD_chain[0].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;
+      dir_lba = (Dir.AD_chain[0].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);
@@ -582,15 +652,17 @@
 
     p = 0;
 
-    while( p < Dir.Length ) {
+    while( p < Dir.AD_chain[0].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;
+          struct FileAD tmpFile;
 
+          memset(&tmpFile, 0, sizeof(tmpFile));
+
           if( !strcasecmp( FileName, filename ) ) {
             memcpy(FileICB, &tmpICB, sizeof(tmpICB));
             found = 1;
@@ -617,11 +689,11 @@
     return 0;
 
   p = 0;
-  while( p < Dir.Length ) {
+  while( p < Dir.AD_chain[0].Length ) {
     if( p > DVD_VIDEO_LB_LEN ) {
       ++lbnum;
       p -= DVD_VIDEO_LB_LEN;
-      Dir.Length -= DVD_VIDEO_LB_LEN;
+      Dir.AD_chain[0].Length -= DVD_VIDEO_LB_LEN;
       if( DVDReadLBUDF( device, lbnum, 2, directory, 0 ) <= 0 ) {
         return 0;
       }
@@ -768,23 +840,89 @@
   return part->valid;
 }
 
-uint32_t UDFFindFile( dvd_reader_t *device, char *filename,
-                      uint32_t *filesize )
+/*
+ * Translate between File logic block, and actual disk-block, taking into
+ * account for partition start, file block start, and chain position.
+ *
+ * The API users will refer to block 0 as start of file and going up
+ * we need to convert that to actual disk block;
+ * partition_start + file_start + offset
+ * but keep in mind that file_start is chained, and not contiguous.
+ *
+ * We return "0" as error, since a File can not start at physical block 0
+ *
+ */
+uint32_t UDFFileBlockPos(struct FileAD *File, uint32_t file_block)
 {
+  uint32_t result, i, offset;
+
+  if (!File) return 0;
+  // Look through the chain to see where this block would belong.
+  for (i = 0, offset = 0;
+       i < File->num_AD;
+       i++) {
+
+    // Is "file_block" inside this chain? Then use this chain.
+    if (file_block < (offset +
+                      (File->AD_chain[i].Length /  DVD_VIDEO_LB_LEN)))
+      break;
+
+    offset += (File->AD_chain[i].Length /  DVD_VIDEO_LB_LEN);
+
+  }
+
+  // Beyond out chains of AD? We could return error, or just fall back to
+  // old behavior in case someone relies on the broken way.
+  if (i >= File->num_AD)
+    i = 0;
+
+  // Compute actual block number
+  result = File->Partition_Start
+    + File->AD_chain[i].Location
+    + file_block
+    - offset;
+
+
+#ifdef DEBUG
+  if (!(file_block % 10000))
+    fprintf(stderr, "libdvdread: File block %d -> %d (chain %d + offset %d : 
%d)\r\n",
+            file_block, result,
+            i,
+            file_block - offset,
+            (File->AD_chain[i].Length / DVD_VIDEO_LB_LEN));
+#endif
+  return result;
+
+}
+
+/*
+ * Find a File/Directory based on name.
+ *  in: DVD Device *
+ *  in: filename
+ * out: allocated UDF_FILE*, call UDFFreeFile() to release.
+ * out: Filesize. (Note: 64-bit)
+ * out: NULL to indicate error, File Not Found.
+ */
+UDF_FILE UDFFindFile( dvd_reader_t *device, char *filename,
+                      uint64_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;
+  struct AD RootICB, ICB;
+  struct FileAD File;
   char tokenline[ MAX_UDF_FILE_NAME_LEN ];
   char *token;
   uint8_t filetype;
+  struct FileAD *result;
 
   *filesize = 0;
   tokenline[0] = '\0';
   strncat(tokenline, filename, MAX_UDF_FILE_NAME_LEN - 1);
   memset(&ICB, 0, sizeof(ICB));
+  memset(&File, 0, sizeof(File));
 
   if(!(GetUDFCache(device, PartitionCache, 0, &partition) &&
        GetUDFCache(device, RootICBCache, 0, &RootICB))) {
@@ -808,17 +946,17 @@
 
     /* Sanity checks. */
     if( TagID != 256 )
-      return 0;
+      return NULL;
     if( RootICB.Partition != 0 )
-      return 0;
+      return NULL;
     SetUDFCache(device, RootICBCache, 0, &RootICB);
   }
 
   /* Find root dir */
   if( !UDFMapICB( device, RootICB, &filetype, &partition, &File ) )
-    return 0;
+    return NULL;
   if( filetype != 4 )
-    return 0;  /* Root dir should be dir */
+    return NULL;  /* Root dir should be dir */
   {
     int cache_file_info = 0;
     /* Tokenize filepath */
@@ -837,14 +975,23 @@
   }
 
   /* Sanity check. */
-  if( File.Partition != 0 )
-    return 0;
+  if( File.AD_chain[0].Partition != 0 )
+    return NULL;
   *filesize = File.Length;
   /* Hack to not return partition.Start for empty files. */
-  if( !File.Location )
-    return 0;
-  else
-    return partition.Start + File.Location;
+  if( !File.AD_chain[0].Location )
+    return NULL;
+
+  /* Allocate a new UDF_FILE and return it. */
+  result = (struct FileAD *) malloc(sizeof(*result));
+  if (!result) return NULL;
+
+  memcpy(result, &File, sizeof(*result));
+
+  result->Partition_Start = partition.Start;
+
+  return result;
+
 }
 
 
Index: dvd_udf.h
===================================================================
--- dvd_udf.h   (revision 1157)
+++ dvd_udf.h   (working copy)
@@ -39,6 +39,8 @@
 extern "C" {
 #endif
 
+typedef struct FileAD *UDF_FILE;
+
 /**
  * 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
@@ -46,7 +48,9 @@
  * '/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 );
+UDF_FILE UDFFindFile( dvd_reader_t *device, char *filename, uint64_t *size );
+void     UDFFreeFile    ( dvd_reader_t *device, UDF_FILE );
+uint32_t UDFFileBlockPos( UDF_FILE, uint32_t file_block);
 
 void FreeUDFCache(void *cache);
 int UDFGetVolumeIdentifier(dvd_reader_t *device,
_______________________________________________
DVDnav-discuss mailing list
[email protected]
https://lists.mplayerhq.hu/mailman/listinfo/dvdnav-discuss

Reply via email to