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,