]> git.sesse.net Git - vlc/blob - plugins/dvd/dvd_ifo.c
*** empty log message ***
[vlc] / plugins / dvd / dvd_ifo.c
1 /*****************************************************************************
2  * dvd_ifo.c: Functions for ifo parsing
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: dvd_ifo.c,v 1.2 2001/02/08 08:08:03 stef Exp $
6  *
7  * Author: Stéphane Borel <stef@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <malloc.h>
32
33 #include "common.h"
34
35 #include "intf_msg.h"
36 #include "dvd_ifo.h"
37 #include "input_dvd.h"
38
39 /*
40  * IFO Management.
41  */
42
43 /*****************************************************************************
44  * IfoFindVMG : When reading directly on a device, finds the offset to the
45  * beginning of video_ts.ifo.
46  *****************************************************************************/
47 static int IfoFindVMG( ifo_t* p_ifo )
48 {
49     char    psz_ifo_start[12] = "DVDVIDEO-VMG";
50     char    psz_test[12];
51
52     read( p_ifo->i_fd, psz_test, 12 );
53
54     while( strncmp( psz_test, psz_ifo_start, 12 ) != 0 )
55     {
56         /* The start of ifo file is on a sector boundary */
57         p_ifo->i_pos = lseek64( p_ifo->i_fd,
58                               p_ifo->i_pos + DVD_LB_SIZE,
59                               SEEK_SET );
60         read( p_ifo->i_fd, psz_test, 12 );
61     }
62     p_ifo->i_off = p_ifo->i_pos;
63
64 //fprintf( stderr, "VMG Off : %lld\n", (long long)(p_ifo->i_off) );
65
66     return 0;
67 }
68
69 /*****************************************************************************
70  * IfoFindVTS : beginning of vts_*.ifo.
71  *****************************************************************************/
72 static int IfoFindVTS( ifo_t* p_ifo )
73 {
74     char    psz_ifo_start[12] = "DVDVIDEO-VTS";
75     char    psz_test[12];
76
77     read( p_ifo->i_fd, psz_test, 12 );
78
79     while( strncmp( psz_test, psz_ifo_start, 12 ) != 0 )
80     {
81         /* The start of ifo file is on a sector boundary */
82         p_ifo->i_pos = lseek64( p_ifo->i_fd,
83                               p_ifo->i_pos + DVD_LB_SIZE,
84                               SEEK_SET );
85         read( p_ifo->i_fd, psz_test, 12 );
86     }
87     p_ifo->i_off = p_ifo->i_pos;
88
89 //fprintf( stderr, "VTS Off : %lld\n", (long long)(p_ifo->i_off) );
90
91     return 0;
92 }
93
94 /*****************************************************************************
95  * IfoInit : Creates an ifo structure and prepares for parsing directly
96  * on DVD device.
97  *****************************************************************************/
98 ifo_t IfoInit( int i_fd )
99 {
100     ifo_t       ifo;
101     
102     /* If we are here the dvd device has already been opened */
103     ifo.i_fd = i_fd;
104     /* No data at the beginning of the disk
105      * 512000 bytes is just another value :) */
106     ifo.i_pos = lseek64( ifo.i_fd, 250 *DVD_LB_SIZE, SEEK_SET );
107     /* FIXME : use udf filesystem to find the beginning of the file */
108     IfoFindVMG( &ifo );
109     
110     return ifo;
111 }
112
113 /*****************************************************************************
114  * IfoEnd : Frees all the memory allocated to ifo structures
115  *****************************************************************************/
116 void IfoEnd( ifo_t* p_ifo )
117 {
118     int     i,j;
119
120     /* Free structures from video title sets */
121     for( j=0 ; j<p_ifo->vmg.mat.i_tts_nb ; j++ )
122     {
123         free( p_ifo->p_vts[j].vobu_admap.pi_vobu_ssector );
124         free( p_ifo->p_vts[j].c_adt.p_cell_inf );
125         free( p_ifo->p_vts[j].m_vobu_admap.pi_vobu_ssector );
126         free( p_ifo->p_vts[j].m_c_adt.p_cell_inf );
127         for( i=0 ; i<p_ifo->p_vts[j].tmap_ti.i_nb ; i++ )
128         {
129             free( p_ifo->p_vts[j].tmap_ti.p_tmap[i].pi_sector );
130         }
131         free( p_ifo->p_vts[j].tmap_ti.pi_sbyte );
132         free( p_ifo->p_vts[j].tmap_ti.p_tmap );
133         free( p_ifo->p_vts[j].pgci_ti.p_srp );
134         for( i=0 ; i<p_ifo->p_vts[j].pgci_ut.i_lu_nb ; i++ )
135         {
136             free( p_ifo->p_vts[j].pgci_ut.p_pgci_inf[i].p_srp );
137         }
138         free( p_ifo->p_vts[j].pgci_ut.p_pgci_inf );
139         free( p_ifo->p_vts[j].pgci_ut.p_lu );
140     }
141
142     free( p_ifo->p_vts );
143
144     /* Free structures from video manager */
145     free( p_ifo->vmg.vobu_admap.pi_vobu_ssector );
146     free( p_ifo->vmg.c_adt.p_cell_inf );
147     for( i=0 ; i<p_ifo->vmg.pgci_ut.i_lu_nb ; i++ )
148     {
149         free( p_ifo->vmg.pgci_ut.p_pgci_inf[i].p_srp );
150     }
151     free( p_ifo->vmg.pgci_ut.p_pgci_inf );
152     free( p_ifo->vmg.pgci_ut.p_lu );
153     for( i=1 ; i<=8 ; i++ )
154     {
155         free( p_ifo->vmg.ptl_mait.p_ptl_mask->ppi_ptl_mask[i] );
156     }
157     free( p_ifo->vmg.ptl_mait.p_ptl_desc );
158     free( p_ifo->vmg.ptl_mait.p_ptl_mask );
159     free( p_ifo->vmg.vts_atrt.pi_vts_atrt_sbyte );
160     free( p_ifo->vmg.vts_atrt.p_vts_atrt );
161     free( p_ifo->vmg.pgc.p_cell_pos_inf );
162     free( p_ifo->vmg.pgc.p_cell_play_inf );
163     free( p_ifo->vmg.pgc.prg_map.pi_entry_cell );
164     free( p_ifo->vmg.pgc.com_tab.p_cell_com );
165     free( p_ifo->vmg.pgc.com_tab.p_post_com );
166     free( p_ifo->vmg.pgc.com_tab.p_pre_com );
167
168     return;
169 }
170
171 /*
172  * Macros to process ifo files
173  */
174  
175 #define GET( p_field , i_len )                                              \
176     {                                                                       \
177         read( p_ifo->i_fd , (p_field) , (i_len) );                          \
178 /*fprintf(stderr, "Pos : %lld Val : %llx\n",                                  \
179                                 (long long)(p_ifo->i_pos - i_start),        \
180                                 (long long)*(p_field) );    */                \
181         p_ifo->i_pos += i_len;                                              \
182     }
183
184 #define GETC( p_field )                                                     \
185     {                                                                       \
186         read( p_ifo->i_fd , (p_field) , 1 );                                \
187 /*fprintf(stderr, "Pos : %lld Value : %d\n",                                  \
188                                 (long long)(p_ifo->i_pos - i_start),        \
189                                           *(p_field) );*/                     \
190         p_ifo->i_pos += 1;                                                  \
191     }
192
193 #define GETS( p_field )                                                     \
194     {                                                                       \
195         read( p_ifo->i_fd , (p_field) , 2 );                                \
196         *(p_field) = ntohs( *(p_field) );                                   \
197 /*fprintf(stderr, "Pos : %lld Value : %d\n",                                  \
198                                 (long long)(p_ifo->i_pos - i_start),        \
199                                           *(p_field) );*/                     \
200         p_ifo->i_pos += 2;                                                  \
201     }
202
203 #define GETL( p_field )                                                     \
204     {                                                                       \
205         read( p_ifo->i_fd , (p_field) , 4 );                                \
206         *(p_field) = ntohl( *(p_field) );                                   \
207 /*fprintf(stderr, "Pos : %lld Value : %d\n",                                  \
208                                 (long long)(p_ifo->i_pos - i_start),        \
209                                           *(p_field) );*/                     \
210         p_ifo->i_pos += 4;                                                  \
211     }
212
213 #define GETLL( p_field )                                                    \
214     {                                                                       \
215         read( p_ifo->i_fd , (p_field) , 8 );                                \
216         *(p_field) = ntoh64( *(p_field) );                                  \
217 /*fprintf(stderr, "Pos : %lld Value : %lld\n",                                \
218                                 (long long)(p_ifo->i_pos - i_start),        \
219                                             *(p_field) );*/                   \
220         p_ifo->i_pos += 8;                                                  \
221     }
222
223 #define FLUSH( i_len )                                                      \
224     {                                                                       \
225 /*fprintf(stderr, "Pos : %lld\n", (long long)(p_ifo->i_pos - i_start));*/       \
226         p_ifo->i_pos = lseek64( p_ifo->i_fd ,                               \
227                               p_ifo->i_pos + (i_len), SEEK_SET );           \
228     }
229
230 /*
231  * Function common to Video Manager and Video Title set Processing
232  */
233
234 /*****************************************************************************
235  * ReadPGC : Fills the Program Chain structure.
236  *****************************************************************************/
237 #define GETCOMMAND( p_com )                                                 \
238     {                                                                       \
239         read( p_ifo->i_fd , (p_com) , 8 );                                  \
240 /*fprintf(stderr, "Pos : %lld Type : %d direct : %d cmd : %d dircmp : %d cmp : %d subcmd : %d v0 : %d v2 : %d v4 : %d\n",                                  \
241                                 (long long)(p_ifo->i_pos - i_start),        \
242                                 (int)((p_com)->i_type),                     \
243                                 (int)((p_com)->i_direct),                   \
244                                 (int)((p_com)->i_cmd),                      \
245                                 (int)((p_com)->i_dir_cmp),                  \
246                                 (int)((p_com)->i_cmp),                      \
247                                 (int)((p_com)->i_sub_cmd),                  \
248                                 (int)((p_com)->i_v0),                       \
249                                 (int)((p_com)->i_v2),                       \
250                                 (int)((p_com)->i_v4) );           */          \
251         p_ifo->i_pos += 8;                                                  \
252     }
253
254 static pgc_t ReadPGC( ifo_t* p_ifo )
255 {
256     pgc_t   pgc;
257     int     i;
258     off64_t   i_start = p_ifo->i_pos;
259
260 //fprintf( stderr, "PGC\n" );
261
262     FLUSH(2);
263     GETC( &pgc.i_prg_nb );
264     GETC( &pgc.i_cell_nb );
265     GETL( &pgc.i_play_time );
266     GETL( &pgc.i_prohibited_user_op );
267     for( i=0 ; i<8 ; i++ )
268     {
269         GETS( &pgc.pi_audio_status[i] );
270     }
271     for( i=0 ; i<32 ; i++ )
272     {
273         GETL( &pgc.pi_subpic_status[i] );
274     }
275     GETS( &pgc.i_next_pgc_nb );
276     GETS( &pgc.i_prev_pgc_nb );
277     GETS( &pgc.i_goup_pgc_nb );
278     GETC( &pgc.i_still_time );
279     GETC( &pgc.i_play_mode );
280     for( i=0 ; i<16 ; i++ )
281     {
282         GETL( &pgc.pi_yuv_color[i] );
283         /* FIXME : We have to erase the extra bit */
284     }
285     GETS( &pgc.i_com_tab_sbyte );
286     GETS( &pgc.i_prg_map_sbyte );
287     GETS( &pgc.i_cell_play_inf_sbyte );
288     GETS( &pgc.i_cell_pos_inf_sbyte );
289
290     /* Parsing of pgc_com_tab_t */
291     if( pgc.i_com_tab_sbyte )
292     {
293         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start
294                             + pgc.i_com_tab_sbyte, SEEK_SET );
295         GETS( &pgc.com_tab.i_pre_com_nb );
296         GETS( &pgc.com_tab.i_post_com_nb );
297         GETS( &pgc.com_tab.i_cell_com_nb );
298         FLUSH( 2 );
299         if( pgc.com_tab.i_pre_com_nb )
300         {
301             pgc.com_tab.p_pre_com =
302                       malloc(pgc.com_tab.i_pre_com_nb *sizeof(ifo_command_t));
303             if( pgc.com_tab.p_pre_com == NULL )
304             {
305                 intf_ErrMsg( "Out of memory" );
306                 p_ifo->b_error = 1;
307                 return pgc;
308             }
309             for( i=0 ; i<pgc.com_tab.i_pre_com_nb ; i++ )
310             {
311                 GETCOMMAND( &pgc.com_tab.p_pre_com[i] );
312             }
313         }
314         if( pgc.com_tab.i_post_com_nb )
315         {
316             pgc.com_tab.p_post_com =
317                       malloc(pgc.com_tab.i_post_com_nb *sizeof(ifo_command_t));
318             if( pgc.com_tab.p_post_com == NULL )
319             {
320                 intf_ErrMsg( "Out of memory" );
321                 p_ifo->b_error = 1;
322                 return pgc;
323             }
324             for( i=0 ; i<pgc.com_tab.i_post_com_nb ; i++ )
325             {
326                 GETCOMMAND( &pgc.com_tab.p_post_com[i] );
327             }
328         }
329         if( pgc.com_tab.i_cell_com_nb )
330         {
331             pgc.com_tab.p_cell_com =
332                       malloc(pgc.com_tab.i_cell_com_nb *sizeof(ifo_command_t));
333             if( pgc.com_tab.p_cell_com == NULL )
334             {
335                 intf_ErrMsg( "Out of memory" );
336                 p_ifo->b_error = 1;
337                 return pgc;
338             }
339             for( i=0 ; i<pgc.com_tab.i_cell_com_nb ; i++ )
340             {
341                 GETCOMMAND( &pgc.com_tab.p_cell_com[i] );
342             }
343         }
344     }
345     /* Parsing of pgc_prg_map_t */
346     if( pgc.i_prg_map_sbyte )
347     {
348         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start
349                             + pgc.i_prg_map_sbyte, SEEK_SET );
350         pgc.prg_map.pi_entry_cell = malloc( pgc.i_prg_nb *sizeof(u8) );
351         if( pgc.prg_map.pi_entry_cell == NULL )
352         {
353             intf_ErrMsg( "Out of memory" );
354             p_ifo->b_error = 1;
355             return pgc;
356         }
357         GET( pgc.prg_map.pi_entry_cell, pgc.i_prg_nb );
358         /* FIXME : check endianness here */
359     }
360     /* Parsing of cell_play_inf_t */
361     if( pgc.i_cell_play_inf_sbyte )
362     {
363         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start
364                             + pgc.i_cell_play_inf_sbyte, SEEK_SET );
365         pgc.p_cell_play_inf = malloc( pgc.i_cell_nb *sizeof(cell_play_inf_t) );
366         if( pgc.p_cell_play_inf == NULL )
367         {
368             intf_ErrMsg( "Out of memory" );
369             p_ifo->b_error = 1;
370             return pgc;
371         }
372         for( i=0 ; i<pgc.i_cell_nb ; i++ )
373         {
374             GETS( &pgc.p_cell_play_inf[i].i_cat );
375             GETC( &pgc.p_cell_play_inf[i].i_still_time );
376             GETC( &pgc.p_cell_play_inf[i].i_com_nb );
377             GETL( &pgc.p_cell_play_inf[i].i_play_time );
378             GETL( &pgc.p_cell_play_inf[i].i_entry_sector );
379             GETL( &pgc.p_cell_play_inf[i].i_first_ilvu_vobu_esector );
380             GETL( &pgc.p_cell_play_inf[i].i_lvobu_ssector );
381             GETL( &pgc.p_cell_play_inf[i].i_lsector );
382         }
383     }
384     /* Parsing of cell_pos_inf_map */
385     if( pgc.i_cell_pos_inf_sbyte )
386     {
387         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start
388                             + pgc.i_cell_pos_inf_sbyte, SEEK_SET );
389         pgc.p_cell_pos_inf = malloc( pgc.i_cell_nb *sizeof(cell_pos_inf_t) );
390         if( pgc.p_cell_play_inf == NULL )
391         {
392             intf_ErrMsg( "Out of memory" );
393             p_ifo->b_error = 1;
394             return pgc;
395         }
396         for( i=0 ; i<pgc.i_cell_nb ; i++ )
397         {
398             GETS( &pgc.p_cell_pos_inf[i].i_vob_id );
399             FLUSH( 1 );
400             GETC( &pgc.p_cell_pos_inf[i].i_cell_id );
401         }
402     } 
403
404     return pgc;
405 }
406
407 /*****************************************************************************
408  * ReadUnit : Fills Menu Language Unit Table/ PGC Info Table
409  *****************************************************************************/
410 static pgci_inf_t ReadUnit( ifo_t* p_ifo )
411 {
412     pgci_inf_t      inf;
413     int             i;
414     off64_t         i_start = p_ifo->i_pos;
415
416 //fprintf( stderr, "Unit\n" );
417
418     GETS( &inf.i_srp_nb );
419     FLUSH( 2 );
420     GETL( &inf.i_lu_ebyte );
421     inf.p_srp = malloc( inf.i_srp_nb *sizeof(pgci_srp_t) );
422     if( inf.p_srp == NULL )
423     {
424         intf_ErrMsg( "Out of memory" );
425         p_ifo->b_error = 1;
426         return inf;
427     }
428     for( i=0 ; i<inf.i_srp_nb ; i++ )
429     {
430         GETC( &inf.p_srp[i].i_pgc_cat_mask );
431         GETC( &inf.p_srp[i].i_pgc_cat );
432         GETS( &inf.p_srp[i].i_par_mask );
433         GETL( &inf.p_srp[i].i_pgci_sbyte );
434     }
435     for( i=0 ; i<inf.i_srp_nb ; i++ )
436     {
437         p_ifo->i_pos = lseek64( p_ifo->i_fd,
438                          i_start + inf.p_srp[i].i_pgci_sbyte,
439                          SEEK_SET );
440         inf.p_srp[i].pgc = ReadPGC( p_ifo );
441     }
442
443     return inf;
444 }
445
446 /*****************************************************************************
447  * ReadUnitTable : Fills the PGCI Unit structure.
448  *****************************************************************************/
449 static pgci_ut_t ReadUnitTable( ifo_t* p_ifo )
450 {
451     pgci_ut_t       pgci;
452     int             i;
453     off64_t         i_start = p_ifo->i_pos;
454
455 //fprintf( stderr, "Unit Table\n" );
456
457     GETS( &pgci.i_lu_nb );
458     FLUSH( 2 );
459     GETL( &pgci.i_ebyte );
460     pgci.p_lu = malloc( pgci.i_lu_nb *sizeof(pgci_lu_t) );
461     if( pgci.p_lu == NULL )
462     {
463         intf_ErrMsg( "Out of memory" );
464         p_ifo->b_error = 1;
465         return pgci;
466     }
467     for( i=0 ; i<pgci.i_lu_nb ; i++ )
468     {
469         GET( pgci.p_lu[i].ps_lang_code, 2 );
470         FLUSH( 1 );
471         GETC( &pgci.p_lu[i].i_existence_mask );
472         GETL( &pgci.p_lu[i].i_lu_sbyte );
473     }
474     pgci.p_pgci_inf = malloc( pgci.i_lu_nb *sizeof(pgci_inf_t) );
475     if( pgci.p_pgci_inf == NULL )
476     {
477         intf_ErrMsg( "Out of memory" );
478         p_ifo->b_error = 1;
479         return pgci;
480     }
481     for( i=0 ; i<pgci.i_lu_nb ; i++ )
482     {
483         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start +
484                                 pgci.p_lu[i].i_lu_sbyte,
485                                 SEEK_SET );
486         pgci.p_pgci_inf[i] = ReadUnit( p_ifo );
487     }
488
489     return pgci;
490 }
491
492 /*****************************************************************************
493  * ReadCellInf : Fills the Cell Information structure.
494  *****************************************************************************/
495 static c_adt_t ReadCellInf( ifo_t* p_ifo )
496 {
497     c_adt_t         c_adt;
498     int             i, i_max;
499     off64_t         i_start = p_ifo->i_pos;
500
501 //fprintf( stderr, "CELL ADD\n" );
502
503     GETS( &c_adt.i_vob_nb );
504     FLUSH( 2 );
505     GETL( &c_adt.i_ebyte );
506     i_max = ( i_start + c_adt.i_ebyte + 1 - p_ifo->i_pos ) / sizeof(cell_inf_t);
507     c_adt.p_cell_inf = malloc( i_max *sizeof(cell_inf_t) );
508     if( c_adt.p_cell_inf == NULL )
509     {
510         intf_ErrMsg( "Out of memory" );
511         p_ifo->b_error = 1;
512         return c_adt;
513     }
514     for( i=0 ; i<i_max ; i++ )
515     {
516         GETS( &c_adt.p_cell_inf[i].i_vob_id );
517         GETC( &c_adt.p_cell_inf[i].i_cell_id );
518         FLUSH( 1 );
519         GETL( &c_adt.p_cell_inf[i].i_ssector );
520         GETL( &c_adt.p_cell_inf[i].i_esector );
521     }
522     
523     return c_adt;
524 }
525
526 /*****************************************************************************
527  * ReadMap : Fills the VOBU Map structure.
528  *****************************************************************************/
529 static vobu_admap_t ReadMap( ifo_t* p_ifo )
530 {
531     vobu_admap_t        map;
532     int                 i, i_max;
533     off64_t             i_start = p_ifo->i_pos;
534     
535 //fprintf( stderr, "VOBU ADMAP\n" );
536
537     GETL( &map.i_ebyte );
538     i_max = ( i_start + map.i_ebyte + 1 - p_ifo->i_pos ) / sizeof(u32);
539     map.pi_vobu_ssector = malloc( i_max *sizeof(u32) );
540     for( i=0 ; i<i_max ; i++ )
541     {
542         GETL( &map.pi_vobu_ssector[i] );
543     }
544
545     return map;
546 }
547  
548 /*
549  * Video Manager Information Processing.
550  * This is what is contained in video_ts.ifo.
551  */
552
553 /*****************************************************************************
554  * ReadVMGInfMat : Fills the Management Information structure.
555  *****************************************************************************/
556 static vmgi_mat_t ReadVMGInfMat( ifo_t* p_ifo )
557 {
558     vmgi_mat_t  mat;
559     int         i;
560 //    off64_t     i_start = p_ifo->i_pos;
561
562 //fprintf( stderr, "VMGI\n" );
563
564     GET( mat.psz_id , 12 );
565     mat.psz_id[12] = '\0';
566     GETL( &mat.i_lsector );
567     FLUSH( 12 );
568     GETL( &mat.i_i_lsector );
569     FLUSH( 1 );
570     GETC( &mat.i_spec_ver );
571     GETL( &mat.i_cat );
572     GETS( &mat.i_vol_nb );
573     GETS( &mat.i_vol );
574     GETC( &mat.i_disc_side );
575     FLUSH( 19 );
576     GETS( &mat.i_tts_nb );
577     GET( mat.ps_provider_id, 32 );
578     GETLL( &mat.i_pos_code );
579     FLUSH( 24 );
580     GETL( &mat.i_i_mat_ebyte );
581     GETL( &mat.i_fp_pgc_sbyte );
582     FLUSH( 56 );
583     GETL( &mat.i_vobs_ssector );
584     GETL( &mat.i_ptt_srpt_ssector );
585     GETL( &mat.i_pgci_ut_ssector );
586     GETL( &mat.i_ptl_mait_ssector );
587     GETL( &mat.i_vts_atrt_ssector );
588     GETL( &mat.i_txtdt_mg_ssector );
589     GETL( &mat.i_c_adt_ssector );
590     GETL( &mat.i_vobu_admap_ssector );
591     FLUSH( 32 );
592     GETS( &mat.i_video_atrt );
593     FLUSH( 1 );
594     GETC( &mat.i_audio_nb );
595 //fprintf( stderr, "vmgi audio nb : %d\n", mat.i_audio_nb );
596     for( i=0 ; i < 8 ; i++ )
597     {
598         GETLL( &mat.pi_audio_atrt[i] );
599     }
600     FLUSH( 17 );
601     GETC( &mat.i_subpic_nb );
602 //fprintf( stderr, "vmgi subpic nb : %d\n", mat.i_subpic_nb );
603     for( i=0 ; i < mat.i_subpic_nb ; i++ )
604     {
605         GET( &mat.pi_subpic_atrt[i], 6 );
606         /* FIXME : take care of endianness */
607     }
608
609     return mat;
610 }
611
612 /*****************************************************************************
613  * ReadVMGTitlePointer : Fills the Part Of Title Search Pointer structure.
614  *****************************************************************************/
615 static vmg_ptt_srpt_t ReadVMGTitlePointer( ifo_t* p_ifo )
616 {
617     vmg_ptt_srpt_t  ptr;
618     int             i;
619 //    off64_t         i_start = p_ifo->i_pos;
620
621 //fprintf( stderr, "PTR\n" );
622
623     GETS( &ptr.i_ttu_nb );
624     FLUSH( 2 );
625     GETL( &ptr.i_ebyte );
626     /* Parsing of tts */
627     ptr.p_tts = malloc( ptr.i_ttu_nb *sizeof(tts_t) );
628     if( ptr.p_tts == NULL )
629     {
630         intf_ErrMsg( "Out of memory" );
631         p_ifo->b_error = 1;
632         return ptr;
633     }
634     for( i=0 ; i<ptr.i_ttu_nb ; i++ )
635     {
636         GETC( &ptr.p_tts[i].i_play_type );
637         GETC( &ptr.p_tts[i].i_angle_nb );
638         GETS( &ptr.p_tts[i].i_ptt_nb );
639         GETS( &ptr.p_tts[i].i_parental_id );
640         GETC( &ptr.p_tts[i].i_tts_nb );
641         GETC( &ptr.p_tts[i].i_vts_ttn );
642         GETL( &ptr.p_tts[i].i_ssector );
643     }
644
645     return ptr;
646 }
647
648 /*****************************************************************************
649  * ReadParentalInf : Fills the Parental Management structure.
650  *****************************************************************************/
651 static vmg_ptl_mait_t ReadParentalInf( ifo_t* p_ifo )
652 {
653     vmg_ptl_mait_t  par;
654     int             i, j, k;
655     off64_t         i_start = p_ifo->i_pos;
656
657 //fprintf( stderr, "PTL\n" );
658
659     GETS( &par.i_country_nb );
660     GETS( &par.i_vts_nb );
661     GETL( &par.i_ebyte );
662     par.p_ptl_desc = malloc( par.i_country_nb *sizeof(vmg_ptl_mai_desc_t) );
663     if( par.p_ptl_desc == NULL )
664     {
665         intf_ErrMsg( "Out of memory" );
666         p_ifo->b_error = 1;
667         return par;
668     }
669     for( i=0 ; i<par.i_country_nb ; i++ )
670     {
671         GET( par.p_ptl_desc[i].ps_country_code, 2 );
672         FLUSH( 2 );
673         GETS( &par.p_ptl_desc[i].i_ptl_mai_sbyte );
674         FLUSH( 2 );
675     }
676     par.p_ptl_mask = malloc( par.i_country_nb *sizeof(vmg_ptl_mask_t) );
677     if( par.p_ptl_mask == NULL )
678     {
679         intf_ErrMsg( "Out of memory" );
680         p_ifo->b_error = 1;
681         return par;
682     }
683     for( i=0 ; i<par.i_country_nb ; i++ )
684     {
685         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start +
686                          par.p_ptl_desc[i].i_ptl_mai_sbyte, SEEK_SET );
687         for( j=1 ; j<=8 ; j++ )
688         {
689             par.p_ptl_mask[i].ppi_ptl_mask[j] =
690                                     malloc( par.i_vts_nb *sizeof(u16) );
691             if( par.p_ptl_mask[i].ppi_ptl_mask[j] == NULL )
692             {
693                 intf_ErrMsg( "Out of memory" );
694                 p_ifo->b_error = 1;
695                 return par;
696             }        
697             for( k=0 ; k<par.i_vts_nb ; k++ )
698             {
699                 GETS( &par.p_ptl_mask[i].ppi_ptl_mask[j][k] );
700             }
701         }
702     }
703
704     return par;
705 }
706
707 /*****************************************************************************
708  * ReadVTSAttr : Fills the structure about VTS attributes.
709  *****************************************************************************/
710 static vmg_vts_atrt_t ReadVTSAttr( ifo_t* p_ifo )
711 {
712     vmg_vts_atrt_t  atrt;
713     int             i, j;
714     off64_t         i_start = p_ifo->i_pos;
715
716 //fprintf( stderr, "VTS ATTR\n" );
717
718     GETS( &atrt.i_vts_nb );
719     FLUSH( 2 );
720     GETL( &atrt.i_ebyte );
721     atrt.pi_vts_atrt_sbyte = malloc( atrt.i_vts_nb *sizeof(u32) );
722     if( atrt.pi_vts_atrt_sbyte == NULL )
723     {
724         intf_ErrMsg( "Out of memory" );
725         p_ifo->b_error = 1;
726         return atrt;
727     }
728     for( i=0 ; i<atrt.i_vts_nb ; i++ )
729     {
730         GETL( &atrt.pi_vts_atrt_sbyte[i] );
731     }
732     atrt.p_vts_atrt = malloc( atrt.i_vts_nb *sizeof(vts_atrt_t) );
733     if( atrt.p_vts_atrt == NULL )
734     {
735         intf_ErrMsg( "Out of memory" );
736         p_ifo->b_error = 1;
737         return atrt;
738     }
739     for( i=0 ; i<atrt.i_vts_nb ; i++ )
740     {
741         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start +
742                                 atrt.pi_vts_atrt_sbyte[i],
743                                 SEEK_SET );
744         GETL( &atrt.p_vts_atrt[i].i_ebyte );
745         GETL( &atrt.p_vts_atrt[i].i_cat_app_type );
746         GETS( &atrt.p_vts_atrt[i].i_vtsm_video_atrt );
747         FLUSH( 1 );
748         GETC( &atrt.p_vts_atrt[i].i_vtsm_audio_nb );
749 //fprintf( stderr, "m audio nb : %d\n", atrt.p_vts_atrt[i].i_vtsm_audio_nb );
750         for( j=0 ; j<8 ; j++ )
751         {
752             GETLL( &atrt.p_vts_atrt[i].pi_vtsm_audio_atrt[j] );
753         }
754         FLUSH( 17 );
755         GETC( &atrt.p_vts_atrt[i].i_vtsm_subpic_nb );
756 //fprintf( stderr, "m subp nb : %d\n", atrt.p_vts_atrt[i].i_vtsm_subpic_nb );
757         for( j=0 ; j<28 ; j++ )
758         {
759             GET( &atrt.p_vts_atrt[i].pi_vtsm_subpic_atrt[j], 6 );
760             /* FIXME : Fix endianness issue here */
761         }
762         FLUSH( 2 );
763         GETS( &atrt.p_vts_atrt[i].i_vtstt_video_atrt );
764         FLUSH( 1 );
765         GETL( &atrt.p_vts_atrt[i].i_vtstt_audio_nb );
766 //fprintf( stderr, "tt audio nb : %d\n", atrt.p_vts_atrt[i].i_vtstt_audio_nb );
767         for( j=0 ; j<8 ; j++ )
768         {
769             GETLL( &atrt.p_vts_atrt[i].pi_vtstt_audio_atrt[j] );
770         }
771         FLUSH( 17 );
772         GETC( &atrt.p_vts_atrt[i].i_vtstt_subpic_nb );
773 //fprintf( stderr, "tt subp nb : %d\n", atrt.p_vts_atrt[i].i_vtstt_subpic_nb );
774         for( j=0 ; j<28/*atrt.p_vts_atrt[i].i_vtstt_subpic_nb*/ ; j++ )
775         {
776             GET( &atrt.p_vts_atrt[i].pi_vtstt_subpic_atrt[j], 6 );
777             /* FIXME : Fix endianness issue here */
778         }
779     }
780
781     return atrt;
782 }
783                            
784 /*****************************************************************************
785  * ReadVMG : Parse video_ts.ifo file to fill the Video Manager structure.
786  *****************************************************************************/
787 static vmg_t ReadVMG( ifo_t* p_ifo )
788 {
789     vmg_t       vmg;
790
791     p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off, SEEK_SET);
792     vmg.mat = ReadVMGInfMat( p_ifo );
793     p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off + 
794                               vmg.mat.i_fp_pgc_sbyte, SEEK_SET );
795     vmg.pgc = ReadPGC( p_ifo );
796     if( vmg.mat.i_ptt_srpt_ssector )
797     {
798         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
799                         vmg.mat.i_ptt_srpt_ssector *DVD_LB_SIZE,
800                         SEEK_SET );
801         vmg.ptt_srpt = ReadVMGTitlePointer( p_ifo );
802     }
803     if( vmg.mat.i_pgci_ut_ssector )
804     {
805         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
806                         vmg.mat.i_pgci_ut_ssector *DVD_LB_SIZE,
807                         SEEK_SET );
808         vmg.pgci_ut = ReadUnitTable( p_ifo );
809     }
810     if( vmg.mat.i_ptl_mait_ssector )
811     {
812         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
813                         vmg.mat.i_ptl_mait_ssector *DVD_LB_SIZE,
814                         SEEK_SET );
815         vmg.ptl_mait = ReadParentalInf( p_ifo );
816     }
817     if( vmg.mat.i_vts_atrt_ssector )
818     {
819         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
820                         vmg.mat.i_vts_atrt_ssector *DVD_LB_SIZE,
821                         SEEK_SET );
822         vmg.vts_atrt = ReadVTSAttr( p_ifo );
823     }
824     if( vmg.mat.i_c_adt_ssector )
825     {
826         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
827                         vmg.mat.i_c_adt_ssector *DVD_LB_SIZE,
828                         SEEK_SET );
829         vmg.c_adt = ReadCellInf( p_ifo );
830     }
831     if( vmg.mat.i_vobu_admap_ssector )
832     {
833         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
834                         vmg.mat.i_vobu_admap_ssector *DVD_LB_SIZE,
835                         SEEK_SET );
836         vmg.vobu_admap = ReadMap( p_ifo );
837     }
838     return vmg;
839 }
840
841 /*
842  * Video Title Set Information Processing.
843  * This is what is contained in vts_*.ifo.
844  */
845
846 /*****************************************************************************
847  * ReadVTSInfMat : Fills the Title Set Information structure.
848  *****************************************************************************/
849 static vtsi_mat_t ReadVTSInfMat( ifo_t* p_ifo )
850 {
851     vtsi_mat_t  mat;
852     int         i;
853 //    off64_t     i_start = p_ifo->i_pos;
854
855 //fprintf( stderr, "VTSI\n" );
856
857     GET( mat.psz_id , 12 );
858     mat.psz_id[12] = '\0';
859     GETL( &mat.i_lsector );
860     FLUSH( 12 );
861     GETL( &mat.i_i_lsector );
862     FLUSH( 1 );
863     GETC( &mat.i_spec_ver );
864     GETL( &mat.i_cat );
865     FLUSH( 90 );
866     GETL( &mat.i_mat_ebyte );
867     FLUSH( 60 );
868     GETL( &mat.i_m_vobs_ssector );
869     GETL( &mat.i_tt_vobs_ssector );
870     GETL( &mat.i_ptt_srpt_ssector );
871     GETL( &mat.i_pgcit_ssector );
872     GETL( &mat.i_m_pgci_ut_ssector );
873     GETL( &mat.i_tmap_ti_ssector );
874     GETL( &mat.i_m_c_adt_ssector );
875     GETL( &mat.i_m_vobu_admap_ssector );
876     GETL( &mat.i_c_adt_ssector );
877     GETL( &mat.i_vobu_admap_ssector );
878     FLUSH( 24 );
879     GETS( &mat.i_m_video_atrt );
880     FLUSH( 1 );
881     GETC( &mat.i_m_audio_nb );
882     for( i=0 ; i<8 ; i++ )
883     {
884         GETLL( &mat.pi_m_audio_atrt[i] );
885     }
886     FLUSH( 17 );
887     GETC( &mat.i_m_subpic_nb );
888     for( i=0 ; i<28 ; i++ )
889     {
890         GET( &mat.pi_m_subpic_atrt[i], 6 );
891         /* FIXME : take care of endianness */
892     }
893     FLUSH( 2 );
894     GETS( &mat.i_video_atrt );
895     FLUSH( 1 );
896     GETC( &mat.i_audio_nb );
897 //fprintf( stderr, "vtsi audio nb : %d\n", mat.i_audio_nb );
898     for( i=0 ; i<8 ; i++ )
899     {
900         GETLL( &mat.pi_audio_atrt[i] );
901     }
902     FLUSH( 17 );
903     GETC( &mat.i_subpic_nb );
904 //fprintf( stderr, "vtsi subpic nb : %d\n", mat.i_subpic_nb );
905     for( i=0 ; i<mat.i_subpic_nb ; i++ )
906     {
907         GET( &mat.pi_subpic_atrt[i], 6 );
908         /* FIXME : take care of endianness */
909     }
910
911     return mat;
912 }
913
914 /*****************************************************************************
915  * ReadVTSTitlePointer : Fills the Part Of Title Search Pointer structure.
916  *****************************************************************************/
917 static vts_ptt_srpt_t ReadVTSTitlePointer( ifo_t* p_ifo )
918 {
919     vts_ptt_srpt_t  ptr;
920     int             i;
921     off64_t         i_start = p_ifo->i_pos;
922
923 //fprintf( stderr, "PTR\n" );
924
925     GETS( &ptr.i_ttu_nb );
926     FLUSH( 2 );
927     GETL( &ptr.i_ebyte );
928     ptr.pi_ttu_sbyte = malloc( ptr.i_ttu_nb *sizeof(u32) );
929     if( ptr.pi_ttu_sbyte == NULL )
930     {
931         intf_ErrMsg( "Out of memory" );
932         p_ifo->b_error = 1;
933         return ptr;
934     }
935     for( i=0 ; i<ptr.i_ttu_nb ; i++ )
936     {
937         GETL( &ptr.pi_ttu_sbyte[i] );
938     }
939     /* Parsing of tts */
940     ptr.p_ttu = malloc( ptr.i_ttu_nb *sizeof(ttu_t) );
941     if( ptr.p_ttu == NULL )
942     {
943         intf_ErrMsg( "Out of memory" );
944         p_ifo->b_error = 1;
945         return ptr;
946     }
947     for( i=0 ; i<ptr.i_ttu_nb ; i++ )
948     {
949         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_start +
950                         ptr.pi_ttu_sbyte[i], SEEK_SET );
951         GETS( &ptr.p_ttu[i].i_pgc_nb );
952         GETS( &ptr.p_ttu[i].i_prg_nb );
953     }
954
955     return ptr;
956 }
957
958 /*****************************************************************************
959  * ReadVTSTimeMap : Fills the time map table
960  *****************************************************************************/
961 static vts_tmap_ti_t ReadVTSTimeMap( ifo_t* p_ifo )
962 {
963     vts_tmap_ti_t   tmap;
964     int             i,j;
965 //    off64_t         i_start = p_ifo->i_pos;
966
967 //fprintf( stderr, "TMAP\n" );
968
969     GETS( &tmap.i_nb );
970     FLUSH( 2 );
971     GETL( &tmap.i_ebyte );
972     tmap.pi_sbyte = malloc( tmap.i_nb *sizeof(u32) );
973     if( tmap.pi_sbyte == NULL )
974     {
975         intf_ErrMsg( "Out of memory" );
976         p_ifo->b_error = 1;
977         return tmap;
978     }
979     for( i=0 ; i<tmap.i_nb ; i++ )
980     {    
981         GETL( &tmap.pi_sbyte[i] );
982     }
983     tmap.p_tmap = malloc( tmap.i_nb *sizeof(tmap_t) );
984     if( tmap.p_tmap == NULL )
985     {
986         intf_ErrMsg( "Out of memory" );
987         p_ifo->b_error = 1;
988         return tmap;
989     }
990     for( i=0 ; i<tmap.i_nb ; i++ )
991     {    
992         GETC( &tmap.p_tmap[i].i_time_unit );
993         FLUSH( 1 );
994         GETS( &tmap.p_tmap[i].i_entry_nb );
995         tmap.p_tmap[i].pi_sector =
996                     malloc( tmap.p_tmap[i].i_entry_nb *sizeof(u32) );
997         if( tmap.p_tmap[i].pi_sector == NULL )
998         {
999             intf_ErrMsg( "Out of memory" );
1000             p_ifo->b_error = 1;
1001             return tmap;
1002         }
1003         for( j=0 ; j<tmap.p_tmap[i].i_entry_nb ; j++ )
1004         {
1005             GETL( &tmap.p_tmap[i].pi_sector[j] );
1006         }
1007     }
1008
1009     return tmap;
1010 }
1011     
1012
1013 /*****************************************************************************
1014  * ReadVTS : Parse vts*.ifo files to fill the Video Title Set structure.
1015  *****************************************************************************/
1016 static vts_t ReadVTS( ifo_t* p_ifo )
1017 {
1018     vts_t       vts;
1019
1020     vts.i_pos = p_ifo->i_pos;
1021
1022     vts.mat = ReadVTSInfMat( p_ifo );
1023     if( vts.mat.i_ptt_srpt_ssector )
1024     {
1025         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1026                         vts.mat.i_ptt_srpt_ssector *DVD_LB_SIZE,
1027                         SEEK_SET );
1028         vts.ptt_srpt = ReadVTSTitlePointer( p_ifo );
1029     }
1030     if( vts.mat.i_m_pgci_ut_ssector )
1031     {
1032         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1033                         vts.mat.i_m_pgci_ut_ssector *DVD_LB_SIZE,
1034                         SEEK_SET );
1035         vts.pgci_ut = ReadUnitTable( p_ifo );
1036     }
1037     if( vts.mat.i_pgcit_ssector )
1038     {
1039         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1040                         vts.mat.i_pgcit_ssector *DVD_LB_SIZE,
1041                         SEEK_SET );
1042         vts.pgci_ti = ReadUnit( p_ifo );
1043     }
1044     if( vts.mat.i_tmap_ti_ssector )
1045     {
1046         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1047                         vts.mat.i_tmap_ti_ssector *DVD_LB_SIZE,
1048                         SEEK_SET );
1049         vts.tmap_ti = ReadVTSTimeMap( p_ifo );
1050     }
1051     if( vts.mat.i_m_c_adt_ssector )
1052     {
1053         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1054                         vts.mat.i_m_c_adt_ssector *DVD_LB_SIZE,
1055                         SEEK_SET );
1056         vts.m_c_adt = ReadCellInf( p_ifo );
1057     }
1058     if( vts.mat.i_m_vobu_admap_ssector )
1059     {
1060         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1061                         vts.mat.i_m_vobu_admap_ssector *DVD_LB_SIZE,
1062                         SEEK_SET );
1063         vts.m_vobu_admap = ReadMap( p_ifo );
1064     }
1065     if( vts.mat.i_c_adt_ssector )
1066     {
1067         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1068                         vts.mat.i_c_adt_ssector *DVD_LB_SIZE,
1069                         SEEK_SET );
1070         vts.c_adt = ReadCellInf( p_ifo );
1071     }
1072     if( vts.mat.i_vobu_admap_ssector )
1073     {
1074         p_ifo->i_pos = lseek64( p_ifo->i_fd, p_ifo->i_off +
1075                         vts.mat.i_vobu_admap_ssector *DVD_LB_SIZE,
1076                         SEEK_SET );
1077         vts.vobu_admap = ReadMap( p_ifo );
1078     }
1079
1080     return vts;
1081 }
1082
1083 /*
1084  * DVD Information Management
1085  */
1086
1087 /*****************************************************************************
1088  * IfoRead : Function that fills structure and calls specified functions
1089  * to do it.
1090  *****************************************************************************/
1091 void IfoRead( ifo_t* p_ifo )
1092 {
1093     int     i;
1094     off64_t i_off;
1095
1096     p_ifo->vmg = ReadVMG( p_ifo );
1097     p_ifo->p_vts = malloc( p_ifo->vmg.mat.i_tts_nb *sizeof(vts_t) );
1098     if( p_ifo->p_vts == NULL )
1099     {
1100         intf_ErrMsg( "Out of memory" );
1101         p_ifo->b_error = 1;
1102         return;
1103     }
1104     for( i=0 ; i<1/*p_ifo->vmg.mat.i_tts_nb*/ ; i++ )
1105     {
1106
1107         intf_WarnMsg( 3, "######### VTS %d #############\n", i+1 );
1108
1109         i_off = p_ifo->vmg.ptt_srpt.p_tts[i].i_ssector *DVD_LB_SIZE;
1110         p_ifo->i_pos = lseek64( p_ifo->i_fd, i_off, SEEK_SET );
1111         /* FIXME : use udf filesystem to avoid this */
1112         IfoFindVTS( p_ifo );
1113         p_ifo->p_vts[i] = ReadVTS( p_ifo );
1114     }
1115     return; 
1116 }
1117
1118 /*
1119  * IFO virtual machine : a set of commands that give the behaviour of the dvd
1120  */
1121 #if 0
1122 /*****************************************************************************
1123  * CommandRead : translates the command strings in ifo into command
1124  * structures.
1125  *****************************************************************************/
1126 void CommandRead( ifo_command_t com )
1127 {
1128     u8*     pi_code = (u8*)(&com);
1129
1130     switch( com.i_type )
1131     {
1132         case 0:                                     /* Goto */
1133             if( !pi_code[1] )
1134             {
1135                 fprintf( stderr, "NOP\n" );
1136             }
1137             else if( cmd.i_cmp )
1138             {
1139                 
1140             }
1141             break;
1142         case 1:                                     /* Lnk */
1143             break;
1144         case 2:                                     /* SetSystem */
1145             break;
1146         case 3:                                     /* Set */
1147             break;
1148         case 4:                                     /* */
1149             break;
1150         case 5:                                     /* */
1151             break;
1152         case 6:                                     /* */
1153             break;
1154         default:
1155             fprintf( stderr, "Unknown Command\n" );
1156             break;
1157     }
1158
1159     return;
1160 }
1161
1162 /*****************************************************************************
1163  * IfoGoto
1164  *****************************************************************************/
1165 static void IfoGoto( ifo_command_t cmd )
1166 {
1167     
1168
1169     return;
1170 }
1171
1172 /*****************************************************************************
1173  * IfoLnk
1174  *****************************************************************************/
1175 static void IfoLnk( ifo_t* p_ifo )
1176 {
1177     return;
1178 }
1179
1180 /*****************************************************************************
1181  * IfoJmp
1182  *****************************************************************************/
1183 static void IfoJmp( ifo_t* p_ifo )
1184 {
1185     return;
1186 }
1187 #endif