2
* dvdudf: parse and read the UDF volume information of a DVD Video
3
* Copyright (C) 1999 Christian Wolff for convergence integrated media GmbH
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
20
* The author can be reached at scarabaeus@convergence.de,
21
* the project's page is at http://linuxtv.org/dvd/
23
* Removed all CSS related stuff to be able to publish "regionset"
24
* by Mirko D�lle <cooper@linvdr.org>
27
/* This is needed for 64 bit file seek */
28
// Since older libraries don't seem to support this, i did a workaround
29
#define _LARGEFILE64_SOURCE 1
36
#include <sys/ioctl.h>
37
#include <sys/types.h>
40
#if defined(__OpenBSD__)
41
# include <sys/dvdio.h>
42
#elif defined(__linux__)
43
# include <linux/cdrom.h>
45
# error "Need the DVD ioctls"
51
#define u8 unsigned char
55
#define u16 unsigned short
59
#define u32 unsigned long
63
#define u64 unsigned long long
67
#define NULL ((void *)0)
70
// Maximum length of filenames for UDF
71
#define MAX_FILE_LEN 2048
73
// default name for split udf image files
74
#define SPLITNAME "DVDVIDEO"
76
FILE* dvdromfile = NULL; // CD/DVD-ROM block device or image file
78
char Filename[PATH_MAX];
80
struct DVD_Video_Disc {
83
long int *segmentlength;
85
int phoony_region_mask;
106
// for direct data access, LSB first
107
#define GETN1(p) ((u8)data[p])
108
#define GETN2(p) ((u16)data[p] | ((u16)data[(p) + 1] << 8))
109
#define GETN3(p) ((u32)data[p] | ((u32)data[(p) + 1] << 8) | ((u32)data[(p) + 2] << 16))
110
#define GETN4(p) ((u32)data[p] | ((u32)data[(p) + 1] << 8) | ((u32)data[(p) + 2] << 16) | ((u32)data[(p) + 3] << 24))
111
#define GETN(p, n, target) memcpy(target, &data[p], n)
119
// searches for <file> in directory <path>, ignoring case
120
// returns 0 and full filename in <filename>
121
// or -1 on file not found
122
// or -2 on path not found
123
int udf_find_file(const char *path, const char *file, char *filename)
129
printf("find file %s / %s\n", path, file);
131
if ((dir = opendir(path)) == NULL) return -2;
132
while ((ent = readdir(dir)) != NULL) {
133
if (!strcasecmp(ent->d_name, file)) {
134
sprintf(filename, "%s%s%s", path, ((path[strlen(path)-1] == '/') ? "" : "/"), ent->d_name);
143
// updates the segment length table of a DVD Video image directory
144
// stores the length of DVDVIDEO.000 and following into an array
145
// returns 0 on success, >0 on error
146
int udf_update_segments(void)
149
char filename[PATH_MAX];
151
if (disc.currentsegment >= 0) fclose(dvdromfile);
152
disc.currentsegment = -1;
156
sprintf(file, "%s.%03d", Filename, disc.segments);
157
if (!(err = udf_find_file(Path, file, filename))) {
158
if ((dvdromfile = fopen(filename, "r")) == NULL) {
159
err = 1; // file not readable
161
if (disc.segments >= disc.segtablelen) {
162
disc.segtablelen += 100;
163
disc.segmentlength = (long int *)realloc(disc.segmentlength, disc.segtablelen * sizeof(long int));
164
if (disc.segmentlength == NULL) return 1;
166
fseek(dvdromfile, 0L, SEEK_END);
167
disc.segmentlength[disc.segments++] = ftell(dvdromfile)/DVD_VIDEO_LB_LEN;
176
// reads absolute Logical Block of the disc
177
// returns number of read bytes on success, 0 or negative error number on error
178
int UDFReadLB(unsigned long int lb_number, unsigned int block_count, unsigned char *data)
183
u64 pos; // 64 bit position
185
int result, segment, segblocks, numread;
187
char filename[PATH_MAX];
188
if (disc.segments > 1) { // split image file
191
while (block_count && (segment < disc.segments)) {
192
if (disc.segmentlength[segment] <= lb_number) { // that's not our segment yet
193
lb_number -= disc.segmentlength[segment++]; // skip to next segment
194
} else { // our segment
195
if (disc.currentsegment != segment) { // segment not open, yet?
196
if (disc.currentsegment >= 0) fclose(dvdromfile);
197
disc.currentsegment = -1;
198
sprintf(file, "%s.%03d", Filename, segment);
199
if (udf_find_file(Path, file, filename)) return 0;
200
if ((dvdromfile = fopen(filename, "r")) == NULL) return 0;
201
disc.currentsegment = segment; // remember open segment number
203
if (fseek(dvdromfile, lb_number * DVD_VIDEO_LB_LEN, SEEK_SET) < 0) break; // position not found
204
segblocks = disc.segmentlength[segment] - lb_number; // remaining blocks in segment
205
if (block_count < segblocks) segblocks = block_count; // more than requested?
206
if ((numread = fread(&data[result], segblocks * DVD_VIDEO_LB_LEN, 1, dvdromfile))) { // read requested blocks or up to end of segment
207
result += segblocks * DVD_VIDEO_LB_LEN; // add to overall number of read bytes
208
block_count -= segblocks; // segments done
209
lb_number += segblocks; // next position
210
} else { // read error
212
result = ferror(dvdromfile);
213
if (result > 0) result = -result;
220
} else if (dvdromfile != NULL) { // block device or one image file
222
pos = (fpos64_t)lb_number * (fpos64_t)DVD_VIDEO_LB_LEN;
223
if (fsetpos64(dvdromfile, &pos) < 0) return 0; // position not found
225
pos = (u64)lb_number * (u64)DVD_VIDEO_LB_LEN;
226
fseek(dvdromfile, 0, SEEK_SET);
227
while (pos > LONG_MAX) {
228
if (fseek(dvdromfile, LONG_MAX, SEEK_CUR)) return 0; // position not found
231
if (fseek(dvdromfile, (long int)pos, SEEK_CUR)) return 0; // position not found
233
if ((result = fread(data, block_count * DVD_VIDEO_LB_LEN, 1, dvdromfile)) <= 0) {
234
result = ferror(dvdromfile);
235
clearerr(dvdromfile);
236
return ((result > 0) ? -result : result); // make it negative!
237
} else return result * block_count * DVD_VIDEO_LB_LEN;
241
int Unicodedecode(u8 *data, int len, char *target)
244
if ((data[0] == 8) || (data[0] == 16)) do {
245
if (data[0] == 16) p++; // ignore MSB of unicode16
247
target[i++] = data[p++];
254
int UDFEntity(u8 *data, u8 *Flags, char *Identifier)
257
strncpy(Identifier, &data[1], 5);
261
int UDFDescriptor(u8 *data, u16 *TagID)
264
// TODO: check CRC 'n stuff
268
int UDFExtentAD(u8 *data, u32 *Length, u32 *Location)
271
*Location = GETN4(4);
275
int UDFShortAD(u8 *data, struct AD *ad)
277
ad->Length = GETN4(0);
278
ad->Flags = ad->Length >> 30;
279
ad->Length &= 0x3FFFFFFF;
280
ad->Location = GETN4(4);
281
ad->Partition = partition.Number; // use number of current partition
285
int UDFLongAD(u8 *data, struct AD *ad)
287
ad->Length = GETN4(0);
288
ad->Flags = ad->Length >> 30;
289
ad->Length &= 0x3FFFFFFF;
290
ad->Location = GETN4(4);
291
ad->Partition = GETN2(8);
296
int UDFExtAD(u8 *data, struct AD *ad)
298
ad->Length = GETN4(0);
299
ad->Flags = ad->Length >> 30;
300
ad->Length &= 0x3FFFFFFF;
301
ad->Location = GETN4(12);
302
ad->Partition = GETN2(16);
307
int UDFICB(u8 *data, u8 *FileType, u16 *Flags)
309
*FileType = GETN1(11);
315
int UDFPartition(u8 *data, u16 *Flags, u16 *Number, char *Contents, u32 *Start, u32 *Length)
319
GETN(24, 32, Contents);
321
*Length = GETN4(192);
325
// reads the volume descriptor and checks the parameters
326
// returns 0 on OK, 1 on error
327
int UDFLogVolume(u8 *data, char *VolumeDescriptor)
329
u32 lbsize, MT_L, N_PM;
333
Unicodedecode(&data[84], 128, VolumeDescriptor);
334
lbsize = GETN4(212); // should be 2048
335
MT_L = GETN4(264); // should be 6
336
N_PM = GETN4(268); // should be 1
337
if (lbsize != DVD_VIDEO_LB_LEN) return 1;
341
for (i = 0; i < N_PM; i++) {
345
sequence = GETN2(p + 2);
347
*Partition1 = GETN2(p + 4);
358
int UDFFileEntry(u8 *data, u8 *FileType, struct AD *ad)
365
UDFICB(&data[16], &filetype, &flags);
366
*FileType = filetype;
370
while (p < 176 + L_EA + L_AD) {
371
switch (flags & 0x0007) {
372
case 0: UDFShortAD(&data[p], ad); p += 8; break;
373
case 1: UDFLongAD(&data[p], ad); p += 16; break;
374
case 2: UDFExtAD(&data[p], ad); p += 20; break;
377
case 8: UDFShortAD(&data[p], ad); break;
378
case 16: UDFLongAD(&data[p], ad); break;
379
case 20: UDFExtAD(&data[p], ad); break;
383
default: p += L_AD; break;
389
int UDFFileIdentifier(u8 *data, u8 *FileCharacteristics, char *FileName, struct AD *FileICB)
394
*FileCharacteristics = GETN1(18);
396
UDFLongAD(&data[20], FileICB);
398
if (L_FI) Unicodedecode(&data[38 + L_IU], L_FI, FileName);
399
else FileName[0] = '\0';
400
return 4 * ((38 + L_FI + L_IU + 3) / 4);
403
// Maps ICB to FileAD
404
// ICB: Location of ICB of directory to scan
405
// FileType: Type of the file
406
// File: Location of file the ICB is pointing to
407
// return 1 on success, 0 on error;
408
int UDFMapICB(struct AD ICB, u8 *FileType, struct AD *File)
410
u8 LogBlock[DVD_VIDEO_LB_LEN];
414
lbnum = partition.Start + ICB.Location;
416
if (UDFReadLB(lbnum++, 1, LogBlock) <= 0) TagID = 0;
417
else UDFDescriptor(LogBlock, &TagID);
419
UDFFileEntry(LogBlock, FileType, File);
421
printf("Found File entry type %d at LB %ld, %ld bytes long\n", *FileType, File->Location, File->Length);
425
} while ((lbnum <= partition.Start + ICB.Location + (ICB.Length-1) / DVD_VIDEO_LB_LEN) && (TagID != 261));
429
// Dir: Location of directory to scan
430
// FileName: Name of file to look for
431
// FileICB: Location of ICB of the found file
432
// return 1 on success, 0 on error;
433
int UDFScanDir(struct AD Dir, char *FileName, struct AD *FileICB)
435
u8 LogBlock[DVD_VIDEO_LB_LEN];
439
char filename[MAX_FILE_LEN];
442
// Scan dir for ICB of file
443
lbnum = partition.Start + Dir.Location;
445
if (UDFReadLB(lbnum++, 1, LogBlock) <= 0) TagID = 0;
448
while (p < DVD_VIDEO_LB_LEN) {
449
UDFDescriptor(&LogBlock[p], &TagID);
451
p += UDFFileIdentifier(&LogBlock[p], &filechar, filename, FileICB);
453
printf("Found ICB for file '%s' at LB %ld, %ld bytes long\n", filename, FileICB->Location, FileICB->Length);
455
if (!strcasecmp(FileName, filename)) return 1;
456
} else p = DVD_VIDEO_LB_LEN;
459
} while (lbnum <= partition.Start + Dir.Location + (Dir.Length-1) / DVD_VIDEO_LB_LEN);
463
// looks for partition on the disc
464
// partnum: number of the partition, starting at 0
465
// part: structure to fill with the partition information
466
// return 1 if partition found, 0 on error;
467
int UDFFindPartition(int partnum, struct Partition *part)
469
u8 LogBlock[DVD_VIDEO_LB_LEN], Anchor[DVD_VIDEO_LB_LEN];
470
u32 lbnum, MVDS_location, MVDS_length;
473
//char Identifier[6];
475
int i, terminate, volvalid;
481
if (UDFReadLB(lbnum++, 1, LogBlock) <= 0) strcpy(Identifier, "");
482
else UDFEntity(LogBlock, &Flags, Identifier);
484
printf("Looking for NSR02 at LB %ld, found %s\n", lbnum-1, Identifier);
486
} while ((lbnum <= 256) && strcmp("NSR02", Identifier));
488
if (strcmp("NSR02", Identifier)) printf("Could not recognize volume. Bad.\n");
489
else printf("Found %s at LB %ld. Good.\n", Identifier, lbnum-1);
495
lbnum = 256; // try #1, prime anchor
497
while (1) { // loop da loop
498
if (UDFReadLB(lbnum, 1, Anchor) > 0) {
499
UDFDescriptor(Anchor, &TagID);
501
if (TagID != 2) { // not an anchor?
502
if (terminate) return 0; // final try failed
503
if (lastsector) { // we already found the last sector
504
lbnum = lastsector; // try #3, alternative backup anchor
505
terminate = 1; // but that's just about enough, then!
507
// TODO: find last sector of the disc (this is optional)
508
if (lastsector) lbnum = lastsector - 256; // try #2, backup anchor
509
else return 0; // unable to find last sector
511
} else break; // it is an anchor! continue...
513
UDFExtentAD(&Anchor[16], &MVDS_length, &MVDS_location); // main volume descriptor
515
printf("MVDS at LB %ld thru %ld\n", MVDS_location, MVDS_location + (MVDS_length-1) / DVD_VIDEO_LB_LEN);
520
part->VolumeDesc[0] = '\0';
523
// Find Volume Descriptor
524
lbnum = MVDS_location;
526
if (UDFReadLB(lbnum++, 1, LogBlock) <= 0) TagID = 0;
527
else UDFDescriptor(LogBlock, &TagID);
529
printf("Looking for Descripors at LB %ld, found %d\n", lbnum-1, TagID);
531
if ((TagID == 5) && (!part->valid)) { // Partition Descriptor
533
printf("Partition Descriptor at LB %ld\n", lbnum-1);
535
UDFPartition(LogBlock, &part->Flags, &part->Number, part->Contents, &part->Start, &part->Length);
536
part->valid = (partnum == part->Number);
538
printf("Partition %d at LB %ld thru %ld\n", part->Number, part->Start, part->Start+part->Length-1);
540
} else if ((TagID == 6) && (!volvalid)) { // Logical Volume Descriptor
542
printf("Logical Volume Descriptor at LB %ld\n", lbnum-1);
544
if (UDFLogVolume(LogBlock, part->VolumeDesc)) {
545
//TODO: sector size wrong!
548
printf("Logical Volume Descriptor: %s\n", part->VolumeDesc); // name of the disc
551
} while ((lbnum <= MVDS_location + (MVDS_length-1) / DVD_VIDEO_LB_LEN) && (TagID != 8) && ((!part->valid) || (!volvalid)));
552
if ((!part->valid) || (!volvalid)) UDFExtentAD(&Anchor[24], &MVDS_length, &MVDS_location); // backup volume descriptor
553
} while (i-- && ((!part->valid) || (!volvalid)));
554
return (part->valid); // we only care for the partition, not the volume
557
// looks for a file on the UDF disc/imagefile
558
// filename has to be the absolute pathname on the UDF filesystem, starting with /
559
// filesize will be set to the size of the file in bytes, on success
560
// returns absolute LB number, or 0 on error
561
unsigned long int UDFFindFile(char *filename, unsigned long int *filesize)
563
u8 LogBlock[DVD_VIDEO_LB_LEN];
566
struct AD RootICB, File, ICB;
567
char tokenline[MAX_FILE_LEN];
571
int Partition = 0; // Partition number to look for the file,
572
// 0 is this is the standard location for DVD Video
576
strcat(tokenline, filename);
579
if (!UDFFindPartition(Partition, &partition)) return 0;
581
lbnum = partition.Start;
583
if (UDFReadLB(lbnum++, 1, LogBlock) <= 0) TagID = 0;
584
else UDFDescriptor(LogBlock, &TagID);
586
printf("Found TagID %d at LB %ld\n", TagID, lbnum-1);
588
if (TagID == 256) { // File Set Descriptor
589
UDFLongAD(&LogBlock[400], &RootICB);
591
} while ((lbnum < partition.Start + partition.Length) && (TagID != 8) && (TagID != 256));
592
if (TagID!=256) return 0;
593
if (RootICB.Partition != Partition) return 0;
596
if (!UDFMapICB(RootICB, &filetype, &File)) return 0;
597
if (filetype != 4) return 0; // root dir should be dir
599
printf("Root Dir found at %ld\n", File.Location);
603
token = strtok(tokenline, "/");
604
while (token != NULL) {
606
printf("looking for token %s\n", token);
608
if (!UDFScanDir(File, token, &ICB)) return 0;
609
if (!UDFMapICB(ICB, &filetype, &File)) return 0;
610
token = strtok(NULL, "/");
612
*filesize = File.Length;
613
return partition.Start + File.Location;
618
// DVD Copy Management:
619
// RPC - Region Playback Control
621
// Query RPC status of the drive
622
// type: 0=NONE (no drive region setting)
623
// 1=SET (drive region is set
624
// 2=LAST CHANCE (drive region is set, only one change remains)
625
// 3=PERM (region set permanently, may be reset by vendor)
626
// vra: number of vendor resets available
627
// ucca: number of user controlled changes available
628
// region_mask: the bit of the drive's region is set to zero, all other 7 bits to one
629
// rpc_scheme: 0=unknown, 1=RPC Phase II, others reserved
630
// returns 0 on success, <0 on error
631
int UDFRPCGet(int *type, int *vra, int *ucca, int *region_mask, int *rpc_scheme)
638
*region_mask = disc.phoony_region_mask;
641
#ifdef DVD_LU_SEND_RPC_STATE
642
if (dvdromfile==NULL) return 0;
643
ai.type = DVD_LU_SEND_RPC_STATE;
644
if ((ret = ioctl(fileno(dvdromfile), DVD_AUTH, &ai)) < 0) return ret;
645
*type = ai.lrpcs.type;
647
*ucca = ai.lrpcs.ucca;
648
*region_mask = ai.lrpcs.region_mask;
649
*rpc_scheme = ai.lrpcs.rpc_scheme;
657
// Set new Region for drive
658
// region_mask: the bit of the new drive's region is set to zero, all other 7 bits to one
659
int UDFRPCSet(int region_mask)
663
disc.phoony_region_mask = region_mask;
665
#ifdef DVD_HOST_SEND_RPC_STATE
666
if (dvdromfile == NULL) return 0;
667
ai.type = DVD_HOST_SEND_RPC_STATE;
668
ai.hrpcs.pdrc = region_mask & 0xFF;
669
if ((ret = ioctl(fileno(dvdromfile), DVD_AUTH, &ai)) < 0) return ret;
677
// open block device or image file
678
// returns fileno() of the file on success, or -1 on error
679
int UDFOpenDisc(char *filename)
681
struct stat filestat;
683
char tempfilename[PATH_MAX];
684
if (filename == NULL) return -1;
686
disc.currentsegment = -1;
687
disc.segmentlength = NULL;
688
disc.segtablelen = 0;
689
disc.phoony_region_mask = 0x00;
690
if (strlen(Filename) < (PATH_MAX-4)) {
691
strcpy(tempfilename, filename);
693
strncpy(tempfilename, filename, PATH_MAX-4);
695
if (stat(tempfilename, &filestat) < 0) {
696
if (tempfilename[strlen(tempfilename)-1] != '.') strcat(tempfilename, ".");
697
strcat(tempfilename, "000");
699
if (stat(tempfilename, &filestat) >= 0) {
700
if (S_ISDIR(filestat.st_mode)) { // directory? dir/DVDVIDEO.000 obligatory
702
printf("is directory, looking for %s\n", SPLITNAME ".000");
704
if (tempfilename[strlen(tempfilename)-1] != '/') strcat(tempfilename, "/");
705
strcat(tempfilename, SPLITNAME ".000");
706
if (stat(tempfilename, &filestat) < 0) return -1; // file not found
708
if (S_ISBLK(filestat.st_mode)) { // block device?
710
printf("is block device.\n");
713
strcpy(Filename, tempfilename);
714
} else if (S_ISREG(filestat.st_mode)) { // image file? tempfilename.000 for split image obligatory
715
i = strlen(tempfilename);
716
while ((--i >= 0) && (tempfilename[i] != '/'));
717
if ((i >= 0) && (tempfilename[i] == '/')) { // slash in name?
718
strncpy(Path, tempfilename, i + 1); // separate there
719
strcpy(Filename, &tempfilename[i + 1]);
721
strcpy(Path, "./"); // in local directory
722
strcpy(Filename, tempfilename);
724
if (strncmp(&Filename[strlen(Filename)-4], ".000", 4)) { // does Filename end on 000?
725
strcpy(tempfilename, Path);
726
strcat(tempfilename, Filename);
727
strcat(tempfilename, ".000"); // or do we have an Filename.000?
728
if (stat(tempfilename, &filestat) >= 0) {
729
disc.segments = 0; // then it might be split file
731
disc.segments = 1; // else one image file
733
} else { // ends on 000, chop that off
734
i = strlen(Filename)-4;
735
Filename[i] = '\0'; // cut off extension
738
if (!disc.segments) {
739
strcpy(tempfilename, Path);
740
strcat(tempfilename, Filename);
741
strcat(tempfilename, ".001"); // do we have a second segment?
742
if (stat(tempfilename, &filestat) >= 0) {
744
printf("is split image files.\n");
746
udf_update_segments();
747
if (disc.segments == 1) disc.segmentlength[disc.segments++]=0; // dummy segment
750
strcat(Filename, ".000");
753
if (disc.segments == 1) {
755
printf("is one image file.\n");
761
printf("stat failed. block device?\n");
764
strcpy(Filename, tempfilename);
766
if (disc.segments <= 1) {
767
strcpy(tempfilename, Path);
768
strcat(tempfilename, Filename);
769
if ((dvdromfile = fopen(tempfilename, "r")) != NULL) {
770
return fileno(dvdromfile);
778
// closes previously opened block device or image file
779
// returns 0 on success, or -1 on error
780
int UDFCloseDisc(void)