1 /*$Id: pdixtract.c,v 1.5 2003/02/27 15:08:50 smurf Exp $*/
2 /* Convert Pinnacle Disk Images (i.e. .pdi file sets) to ISO9660 files */
3 /* or extract files from the file sets */
4
5 /*
6 pdixtract is distributed in the hope that it will be useful, but WITHOUT ANY
7 WARRANTY. No author or distributor accepts responsibility to anyone for the
8 consequences of using it or for whether it serves any particular purpose or
9 works at all, unless he says so in writing. Refer to the GNU General Public
10 License (the "GPL") for full details.
11
12 Everyone is granted permission to copy, modify and redistribute pdixtract,
13 but only under the conditions described in the GPL. A copy of this license
14 is supposed to have been given to you along with pdixtract so you can know
15 your rights and responsibilities. It should be in a file named COPYLEFT,
16 or, if there is no file named COPYLEFT, a file named COPYING. Among other
17 things, the copyright notice and this notice must be preserved on all
18 copies.
19
20 We explicitly state here what we believe is already implied by the GPL: if
21 the pdixtract program is distributed as a separate set of sources and a
22 separate executable file which are aggregated on a storage medium together
23 with another program, this in itself does not bring the other program under
24 the GPL, nor does the mere fact that such a program or the procedures for
25 constructing it invoke the pdixtract executable bring any other part of the
26 program under the GPL.
27 */
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35
36 #if defined(__WINDOWS__) || defined(__NT__)
37 #include <io.h>
38 #include <direct.h>
39 #define MAKE_DIR(x) mkdir(x)
40 #define PATH_SEP '\\'
41 #define STRCMP(a,b) strcmpi(a,b)
42 #define STRNCMP(a,b,l) strnicmp(a,b,l)
43 #else
44 #define MAKE_DIR(x) mkdir(x,0775)
45 #define STRCMP(a,b) strcasecmp(a,b)
46 #define STRNCMP(a,b,l) strncasecmp(a,b,l)
47 #define PATH_SEP '/'
48 #endif
49
50 #ifndef O_BINARY
51 #define O_BINARY 0
52 #endif
53
54 #define DEBUGP(a) /* fprintf a */
55 #define PDI_HEADER 0x130
56 #define PDI_FIRST_DESC (0x22800+PDI_HEADER)
57 #define PDI_DIR_MAX 20480
58 #define MAX_FILES 400
59 #define BUFFSIZE 20*1024*1024
60
61 /*
62 * The isofs filesystem constants/structures
63 */
64
65 /* This part borrowed from the bsd386 isofs */
66 #define ISODCL(from, to) (to - from + 1)
67
68 struct iso_directory_record {
69 unsigned char length [ISODCL (1, 1)]; /* length of descriptor */
70 unsigned char ext_attr_length [ISODCL (2, 2)]; /* length of extended attributes, 0 in .pdi-files */
71 unsigned char extent [ISODCL (3, 10)]; /* position of file in image in multiple of sector size, 32Bit-LSB, 32Bit-MSB */
72 unsigned char size [ISODCL (11, 18)]; /* size of file, 32Bit-LSB, 32Bit-MSB */
73 unsigned char date [ISODCL (19, 25)]; /* Year-1900, month, day, hour,min,sec */
74 unsigned char flags [ISODCL (26, 26)]; /* 00 for files */
75 unsigned char file_unit_size [ISODCL (27, 27)]; /* 00 in .pdi */
76 unsigned char interleave [ISODCL (28, 28)]; /* 00 in .pdi */
77 unsigned char volume_sequence_number [ISODCL (29, 32)]; /* 01 in .pdi, 16Bit-LSB, 16Bit-MSB */
78 unsigned char name_len [ISODCL (33, 33)]; /* length of name */
79 char name [1];
80 };
81
82 struct filedesc {
83 char *name;
84 size_t size; /* size in extents */
85 size_t extent;
86 int hits; /* flag for individual file selection */
87 };
88
89 char buffer[BUFFSIZE];
90 struct filedesc files[MAX_FILES];
91
92 char id[]="$Id: pdixtract.c,v 1.5 2003/02/27 15:08:50 smurf Exp $";
93
94 /* append [ext] to [in] if not already present */
95 char *
file_with_ext(const char * in,const char * ext)96 file_with_ext(const char*in,const char*ext){
97 size_t in_l=strlen(in);
98 size_t ext_l=strlen(ext);
99 char * out;
100 if(STRCMP(in+in_l-ext_l,ext)){
101 out=malloc(in_l+ext_l+1);
102 memcpy(out,in,in_l);
103 memcpy(out+in_l,ext,ext_l+1);
104 return out;
105 }
106 return strdup(in);
107 }
108
109 /* errexit is called after a fatal error that prevents us from continuing */
errexit(const char * err)110 void errexit(const char*err){
111 perror(err);
112 exit(1);
113 }
114
115
116
117 /* helper for qsort, compares extents of 2 filedescs */
118 int
filedesc_cmp(struct filedesc * a,struct filedesc * b)119 filedesc_cmp(struct filedesc*a,struct filedesc*b){
120 return a->extent-b->extent;
121 }
122
123 /* read directory from first pdi-file into files, returns no of files */
124 int
pdi_read_dir(int fin)125 pdi_read_dir(int fin){
126 size_t off=0; /* offset into buffer for current dir-record */
127 size_t size,extent;
128 size_t fdscno=0;
129 struct iso_directory_record *dirp;
130 lseek(fin,PDI_FIRST_DESC,SEEK_SET);
131 if(read(fin,buffer,PDI_DIR_MAX)<0)
132 errexit("during read of directory");
133 off=0;
134 printf("[extent ] File Name Size Date Time\n");
135 while(buffer[off]){
136 dirp=(struct iso_directory_record*)&buffer[off];
137 if(!dirp->flags[0]&&dirp->name[0]&&dirp->name_len[0]){
138 size=dirp->size[0]+0x100*dirp->size[1]+
139 0x10000*dirp->size[2]+0x1000000*dirp->size[3];
140 extent=dirp->extent[0]+0x100*dirp->extent[1]+
141 0x10000*dirp->extent[2]+0x1000000*dirp->extent[3];
142 printf("[%7i] %12s %10i %2i.%2i.%4i %02i:%02i:%02i\n",
143 extent,dirp->name,size,
144 dirp->date[2],dirp->date[1],dirp->date[0]+1900,
145 dirp->date[3],dirp->date[4],dirp->date[5]);
146 files[fdscno].size=size/0x800;
147 files[fdscno].name=strdup(dirp->name);
148 files[fdscno].extent=extent;
149 files[fdscno].hits=1;
150 fdscno++;
151 }
152 off+=dirp->length[0];
153 if(!buffer[off]){ /* next directory record to large for current extent */
154 off=(off+0x7ff)&0xfffff800; /* skip to next extent */
155 }
156 }
157 // qsort(files,fdscno,sizeof(struct filedesc),filedesc_cmp);
158 qsort(files,fdscno,sizeof(struct filedesc),
159 (int (*) (const void *,const void *))filedesc_cmp);
160 return fdscno;
161 }
162
163 void
usage()164 usage(){
165 fprintf(stderr,
166 "usage: pdixtract [options] <pdi-file-name>[.pdi] [files...]\n"
167 "\n"
168 "options:\n"
169 "-l list only (directory mode)\n"
170 "-i <iso> convert .pdi-file-set into .iso-image <iso>[.iso]\n"
171 /*
172 "-n <name> (in combination with -i) set the iso volume name\n"
173 */
174 "-r remove .pdi files as soon as possible\n"
175 "-d <dir> extract files to directory <dir>\n"
176 "-m <dir> create directory <dir> and extract files int it\n"
177 "-v create VIDEO_TS directory and extract files into it\n"
178 "-x same as -r -m <pdi-file-name> -v\n"
179 "\n"
180 "a trailing '*' may be used as wildcard for file selection,\n"
181 "e.g. pdixtract -v movie vts_01*\n"
182 "extracts Video Title Set 1 from movie.pdi* to dir VIDEO_TS\n"
183 );
184 exit(1);
185 }
186
187 /* global data describing the input data */
188 int current_segment=0; /* number of current input file */
189 int delete_source=0; /* flag for deletion of un-needed sources */
190 char current_file[0x300]; /* file name of current input */
191 char *infile; /* name of very first input file */
192 int fin; /* os file descriptor of current input */
193 struct {
194 size_t first_extent; /* first whole extent in current file */
195 size_t no_extents; /* number of whole extents in current file */
196 size_t off; /* offset to first extent in current file */
197 size_t size; /* size of current file in bytes */
198 } pdi_segment[6];
199
200 /* open a new .pdi segment and prepare it for reading */
201 void
load_new_source()202 load_new_source(){
203 long l;
204 close(fin);
205 if(delete_source&&unlink(current_file))
206 perror(current_file); /* failed deletion is not fatal */
207 current_segment++;
208 snprintf(current_file,sizeof(current_file)-1,
209 "%s%02i",infile,current_segment);
210 fin=open(current_file,O_RDONLY+O_BINARY);
211 if(fin<0)
212 errexit(current_file);
213 fprintf(stderr,"(%s)",current_file);
214 /* now check files' size */
215 l=lseek(fin,0,SEEK_END);
216 if(l<0)
217 errexit(current_file);
218 pdi_segment[current_segment].size=l;
219 pdi_segment[current_segment].off=
220 (pdi_segment[current_segment-1].size-pdi_segment[current_segment-1].off)%0x800;
221 pdi_segment[current_segment].first_extent=
222 pdi_segment[current_segment-1].first_extent+
223 (pdi_segment[current_segment-1].size-pdi_segment[current_segment-1].off)/0x800;
224 if(pdi_segment[current_segment].off){
225 pdi_segment[current_segment].off=0x800-pdi_segment[current_segment].off;
226 pdi_segment[current_segment].first_extent++;
227 }
228 pdi_segment[current_segment].no_extents=(l-pdi_segment[current_segment].off)/0x800;
229 DEBUGP((stderr,"\nseg %i:off(%i) first(%i) no(%i) size(%i)\n",
230 current_segment,
231 pdi_segment[current_segment].off,
232 pdi_segment[current_segment].first_extent,
233 pdi_segment[current_segment].no_extents,
234 pdi_segment[current_segment].size));
235 }
236 /* write no_extents blocks of 0x800 to fout, starting with first_extent */
237 /* all requested extents are contained in current input */
238 void
copy_extents(int fout,size_t first_extent,long no_extents)239 copy_extents(int fout,size_t first_extent, long no_extents){
240 DEBUGP((stderr,"copy_segments: first=%i, no=%li\n",
241 first_extent,no_extents));
242 if(0>lseek(fin,
243 pdi_segment[current_segment].off +
244 (first_extent-pdi_segment[current_segment].first_extent)*0x800,
245 SEEK_SET))
246 errexit("during skip of .pdi-data");
247 no_extents*=0x800;
248 while(no_extents>BUFFSIZE){
249 if(BUFFSIZE>read(fin,buffer,BUFFSIZE))
250 errexit("during read of extents");
251 if(BUFFSIZE>write(fout,buffer,BUFFSIZE))
252 errexit("during write of extents");
253 no_extents-=BUFFSIZE;
254 putc('.',stderr);
255 }
256 if(no_extents>read(fin,buffer,no_extents))
257 errexit("during read of extents");
258 if(no_extents>write(fout,buffer,no_extents))
259 errexit("during write of extents");
260 }
261 /* write no_extents blocks of 0x800 to fout, starting with first_extent */
262 /* manage segment boundaries */
263 void
write_to_file(int fout,size_t first_extent,size_t no_extents)264 write_to_file(int fout,size_t first_extent, size_t no_extents)
265 {
266 DEBUGP((stderr,"[%i..%i]",first_extent,first_extent+no_extents-1));
267 /* skip all input files, that do not contain parts of desired output */
268 while(pdi_segment[current_segment].first_extent+
269 pdi_segment[current_segment].no_extents+1<
270 first_extent)
271 load_new_source();
272 while(no_extents){
273 char small_buffer[0x800];
274 size_t extents_from_this_segment;
275 long bytes_in_buffer;
276 if(first_extent+no_extents <=
277 pdi_segment[current_segment].first_extent+pdi_segment[current_segment].no_extents)
278 extents_from_this_segment=no_extents;
279 else
280 extents_from_this_segment=
281 pdi_segment[current_segment].first_extent+
282 pdi_segment[current_segment].no_extents-first_extent;
283 if (extents_from_this_segment){ /* this file contains whole segments */
284 copy_extents(fout,first_extent,extents_from_this_segment);
285 first_extent+=extents_from_this_segment;
286 if(!(no_extents-=extents_from_this_segment))
287 return;
288 }
289 if(0>lseek(fin,
290 pdi_segment[current_segment].off + pdi_segment[current_segment].no_extents*0x800,
291 SEEK_SET))
292 errexit("during skip of .pdi-data");
293 bytes_in_buffer=read(fin,small_buffer,0x800);
294 if(bytes_in_buffer<0)
295 errexit("reading partial extent, first part");
296 load_new_source();
297 if(0>lseek(fin, 0, SEEK_SET))
298 errexit("during rewind of .pdi-data");
299 if(bytes_in_buffer){
300 bytes_in_buffer=read(fin,small_buffer+bytes_in_buffer,0x800-bytes_in_buffer);
301 if(bytes_in_buffer<(long)pdi_segment[current_segment].off)
302 errexit("reading partial extent, second part");
303 if(0x800>write(fout,small_buffer,0x800))
304 errexit("during write of segmented extent");
305 first_extent++;
306 no_extents--;
307 }
308 }
309 }
310
main(int argc,char ** argv)311 int main(int argc,char **argv){
312 int fout=-1; /* handle to output file */
313 size_t fdscno; /* count of output files */
314 size_t i;
315 long l;
316 char outname[999];
317
318 /* option flags */
319 int list_only=0;
320 int make_directory=0;
321 int make_VIDEO_TS=0;
322 char* dest_dir=0;
323 char* iso_name=0;
324 int argi=1;
325 char*iso_vol_name=0;
326 char*list_file=0;
327 int list_format=0;
328
329 fprintf(stderr,"%s\n\n",id);
330 /* option parsing */
331 while(argc>argi&&argv[argi][0]=='-'){
332 switch(argv[argi][1]){
333 case 'l': case 'L': list_only=1; break;
334 case 'r': case 'R': delete_source=1; break;
335 case 'm': case 'M': make_directory=1;
336 case 'd': case 'D': dest_dir=argv[++argi]; break;
337 case 'i': case 'I': iso_name=file_with_ext(argv[++argi],".iso"); break;
338 case 'n': case 'N': iso_vol_name=argv[++argi]; break;
339 case 'v': case 'V': make_VIDEO_TS=1; break;
340 case 'g': case 'G': /* GUI options */
341 switch(argv[argi][2]) {
342 case 'b': case 'B': list_format++; /* format 3: <name> <size>\n */
343 case 'f': case 'F': list_format++; /* format 2: <name>\n<size>\n */
344 case 's': case 'S': list_format++; /* format 1: <name>,<size>\n */
345 case 'l': case 'L': /* format 0: <name>\n */
346 list_only=1;list_file=argv[++argi]; break;
347 default: usage();
348 }
349 break;
350 case 'x': case 'X': if(argc-argi!=2) usage();
351 make_directory=1; make_VIDEO_TS=1; delete_source=1;
352 dest_dir=strdup(argv[argi+1]);
353 l=strlen(dest_dir)-4;
354 if(l>0&&!STRCMP(dest_dir+l,".pdi"))
355 dest_dir[l]=0; /* strip .pdi extension */
356 break;
357 default: usage();
358 }
359 argi++;
360 }
361
362 if(argc==argi)
363 usage();
364 if(argc-argi>1&&(iso_name||list_only)){
365 fprintf(stderr,"individual file selection may be used only in file extraction mode\n");
366 usage();
367 }
368 infile=file_with_ext(argv[argi],".pdi");
369 fin=open(infile,O_RDONLY+O_BINARY);
370 if(fin<0)
371 errexit(infile);
372 fdscno=pdi_read_dir(fin);
373
374 if(list_file){ /* generate listing for PDIgui */
375 FILE*f=fopen(list_file,"wt");
376 char* list_formats[]={"%s\n","%s,%i\n","%s\n%i\n","%s %i\n"};
377 if(!f)errexit(list_file);
378 for(i=0;i<fdscno;i++)
379 fprintf(f,list_formats[list_format],files[i].name,files[i].size*0x800);
380 fclose(f);
381 }
382 if(list_only)
383 exit(0);
384 if(!fdscno){
385 fprintf(stderr,"no files found in %s, exiting.\n",infile);
386 exit(0);
387 }
388 if(argc-argi>1){ /* there are file arguments specified */
389 /* now filter directory files through arguments */
390 int warning=0;
391 while(++argi<argc){
392 int found=0;
393 l=strlen(argv[argi]);
394 if(l&&argv[argi][--l]=='*'){ /* wild card match */
395 for(i=0;i<fdscno;i++)
396 if(!STRNCMP(argv[argi],files[i].name,l))
397 found=files[i].hits++;
398 }
399 else
400 for(i=0;i<fdscno;i++)
401 if(!STRCMP(argv[argi],files[i].name))
402 found=files[i].hits++;
403 if(!found){
404 fprintf(stderr,"Warning: file %s not found in .pdi set\n",argv[argi]);
405 warning++;
406 }
407 }
408 if(warning&&delete_source){
409 fprintf(stderr,"%i warnings issued during file selection.\n"
410 "cowardly clearing delete source flag\n",warning);
411 delete_source=0;
412 }
413 l=0;
414 for(i=0;i<fdscno;i++)
415 l+=--files[i].hits;
416 if(!l){
417 fprintf(stderr,"no files to extract, exiting.\n");
418 exit(0);
419 }
420 }
421 if(make_directory&&MAKE_DIR(dest_dir))
422 perror(dest_dir);
423 if(make_VIDEO_TS){
424 if(dest_dir){
425 snprintf(outname,sizeof(outname)-1,"%s%cVIDEO_TS",dest_dir,PATH_SEP);
426 dest_dir=strdup(outname);
427 }
428 else
429 dest_dir="VIDEO_TS";
430 if(MAKE_DIR(dest_dir))
431 perror(dest_dir);
432 }
433
434 pdi_segment[0].off=PDI_HEADER;
435 l=lseek(fin,0,SEEK_END);
436 if(l<0)
437 errexit(current_file);
438 pdi_segment[0].size=l;
439 pdi_segment[0].first_extent=0;
440 pdi_segment[0].no_extents=(l-PDI_HEADER)/0x800;
441
442 if(iso_name){ /* copy directory structure to iso */
443 fdscno--;
444 files[0].size=files[fdscno].extent+files[fdscno].size;
445 files[0].extent=0;
446 files[0].name=iso_name;
447 fdscno=1;
448 }
449 strncpy(current_file,infile,sizeof(current_file)-1);
450 for(i=0;i<fdscno;i++)
451 if(files[i].hits){
452 if(dest_dir)
453 snprintf(outname,sizeof(outname)-1,"%s%c%s",dest_dir,PATH_SEP,files[i].name);
454 else
455 strncpy(outname,files[i].name,sizeof(outname)-1);
456 fprintf(stderr,"writing %s (%i extents) ",outname,files[i].size);
457 fout=open(outname,O_TRUNC+O_WRONLY+O_CREAT+O_BINARY,S_IWUSR+S_IRUSR);
458 if(fout<0)
459 errexit(files[i].name);
460 write_to_file(fout,files[i].extent,files[i].size);
461 close(fout);
462 putc('\n',stderr);
463 }
464 close(fin);
465 if(delete_source&&unlink(current_file))
466 perror(current_file);
467 return 0;
468 }
469
470 /* $Log: pdixtract.c,v $
471 * Revision 1.5 2003/02/27 15:08:50 smurf
472 * added different list formats to support PDIgui
473 *
474 * Revision 1.4 2003/02/25 19:25:00 smurf
475 * added -i option to extract iso image
476 * added -x "all in wonder" in options
477 * added support for extracting individual files
478 *
479 * Revision 1.3 2003/02/20 12:59:10 smurf
480 * improved error reporting
481 * checked for shadowed directory entries (reported by JJF007)
482 * added command line switches:
483 * -v create VIDEO_TS
484 * -x (all in wonder) is -r -m <pdi-file-name> -v
485 *
486 * Revision 1.2 2003/02/18 17:39:03 smurf
487 * Added new command-line switches:
488 * -l list only
489 * -r remove source files after extraction
490 * -d <dir> extract files into <dir> instead of current directory
491 * -m <dir> create directory <dir> and extract files into <dir>
492 *
493 * Revision 1.1 2003/02/17 15:08:50 smurf
494 * Initial revision
495 *
496 * */
497