1 /*
2 dvdauthor -- generation of PGCs within IFO files
3 */
4 /*
5 * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or (at
10 * your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301 USA.
21 */
22
23 #include "compat.h"
24 #include <errno.h>
25 #include <assert.h>
26
27 #include "dvdauthor.h"
28 #include "da-internal.h"
29
30
31
32 #define MAXCELLS 4096
33 #define MAXPGCSIZE (236+128*8+256+MAXCELLS*(24+4))
34 #define BUFFERPAD (MAXPGCSIZE+1024)
35
36 static int bigwritebuflen=0;
37 static unsigned char *bigwritebuf=0;
38
39
40 /*
41 titleset X: 2-255
42 vmgm: 1
43 menu Y: 1-119
44 menu entry Y: 120-127
45 title Y: 129-255
46 chapter Z: 1-255
47
48 need 3 bytes plus jump command
49
50 for VMGM menu:
51 [vmgm | titleset X] -> g[15] low
52 [menu [entry] Y | title Y] -> g[15] high
53 [chapter Z] -> g[14] low
54
55 for VTSM root menu:
56 [menu [entry] Y | title Y] -> g[15] high
57 [chapter Z] -> g[15] low
58
59 */
60
genjumppad(unsigned char * buf,vtypes ismenu,int entry,const struct workset * ws,const struct pgcgroup * curgroup)61 static int genjumppad(unsigned char *buf, vtypes ismenu, int entry, const struct workset *ws, const struct pgcgroup *curgroup)
62 /* generates the jumppad if the user wants it. The code is put into buf, and the function
63 result is the number of bytes generated. */
64 {
65 unsigned char *cbuf = buf;
66 int i, j, k;
67 if (jumppad && ismenu == VTYPE_VTSM && entry == 7 /* root menu? */)
68 {
69 // *** VTSM jumppad
70 write8(cbuf,0x61,0x00,0x00,0x0E,0x00,0x0F,0x00,0x00); cbuf+=8; // g[14]=g[15];
71 write8(cbuf,0x71,0x00,0x00,0x0F,0x00,0x00,0x00,0x00); cbuf+=8; // g[15]=0;
72 // menu entry jumptable
73 for (i = 2; i < 8; i++)
74 {
75 for (j = 0; j < curgroup->numpgcs; j++)
76 if (curgroup->pgcs[j]->entries & (1 << i))
77 {
78 write8(cbuf,0x20,0xA4,0x00,0x0E,i+120,0x00,0x00,j+1); cbuf+=8; // if g[14]==0xXX00 then LinkPGCN XX
79 } /*if; for*/
80 } /*for*/
81 // menu jumptable
82 for (i = 0; i < curgroup->numpgcs; i++)
83 {
84 write8(cbuf,0x20,0xA4,0x00,0x0E,i+1,0x00,0x00,i+1); cbuf+=8; // if g[14]==0xXX00 then LinkPGCN XX
85 } /*for*/
86 // title/chapter jumptable
87 for (i = 0; i < ws->titles->numpgcs; i++)
88 {
89 write8(cbuf,0x71,0x00,0x00,0x0D,i+129,0,0x00,0x00); cbuf+=8; // g[13]=(i+1)*256;
90 write8(cbuf,0x30,0x23,0x00,0x00,0x00,i+1,0x0E,0x0D); cbuf+=8; // if g[15]==g[13] then JumpSS VTSM i+1, ROOT
91 for (j = 0; j < ws->titles->pgcs[i]->numchapters; j++)
92 {
93 write8(cbuf,0x71,0x00,0x00,0x0D,i+129,j+1,0x00,0x00); cbuf+=8; // g[13]=(i+1)*256;
94 write8(cbuf,0x30,0x25,0x00,j+1,0x00,i+1,0x0E,0x0D); cbuf+=8; // if g[15]==g[13] then JumpSS VTSM i+1, ROOT
95 } /*for*/
96 } /*for*/
97 }
98 else if (jumppad && ismenu == VTYPE_VMGM && entry == 2 /* title menu */)
99 {
100 // *** VMGM jumppad
101 // remap all VMGM TITLE X -> TITLESET X TITLE Y
102 k = 129;
103 for (i = 0; i < ws->titlesets->numvts; i++)
104 for (j = 0; j < ws->titlesets->vts[i].numtitles; j++)
105 {
106 write8(cbuf, 0x71, 0xA0, 0x0F, 0x0F, j + 129, i + 2, k, 1);
107 /* if g15 == k << 8 | 1 then g15 = j + 129 << 8 | i + 2 */
108 cbuf += 8;
109 k++;
110 } /*for; for*/
111 // move TITLE out of g[15] into g[14] (to mate up with CHAPTER)
112 // then put title/chapter into g[15], and leave titleset in g[14]
113 write8(cbuf,0x63,0x00,0x00,0x0E,0x00,0x0F,0x00,0x00); cbuf+=8; // g[14]+=g[15]
114 write8(cbuf,0x79,0x00,0x00,0x0F,0x00,0xFF,0x00,0x00); cbuf+=8; // g[15]&=255
115 write8(cbuf,0x64,0x00,0x00,0x0E,0x00,0x0F,0x00,0x00); cbuf+=8; // g[14]-=g[15]
116 write8(cbuf,0x62,0x00,0x00,0x0E,0x00,0x0F,0x00,0x00); cbuf+=8; // g[14]<->g[15]
117 // For each titleset, delegate to the appropriate submenu
118 for (i = 0; i < ws->titlesets->numvts; i++)
119 {
120 write8(cbuf,0x71,0x00,0x00,0x0D,0x00,i+2,0x00,0x00); cbuf+=8; // g[13]=(i+2)*256;
121 write8(cbuf,0x30,0x26,0x00,0x01,i+1,0x87,0x0E,0x0D); cbuf+=8; // if g[14]==g[13] then JumpSS VTSM i+1, ROOT
122 } /*for*/
123 // set g[15]=0 so we don't leak dirty registers to other PGC's
124 write8(cbuf,0x71,0x00,0x00,0x0F,0x00,0x00,0x00,0x00); cbuf+=8; // g[15]=0;
125 } /*if*/
126 return cbuf-buf;
127 } /*genjumppad*/
128
jumppgc(unsigned char * buf,int pgc,vtypes ismenu,int entry,const struct workset * ws,const struct pgcgroup * curgroup)129 static int jumppgc(unsigned char *buf,int pgc,vtypes ismenu,int entry,const struct workset *ws,const struct pgcgroup *curgroup)
130 /* generates a command table for a PGC containing a jumppad. Returns the number of bytes output. */
131 {
132 int base = 0xEC; /* put command table immediately after PGC header */
133 int ncmd, offs;
134 offs = base + 8; /* room for command table header */
135 offs += genjumppad(buf + offs, ismenu, entry, ws, curgroup);
136 if (pgc > 0)
137 write8(buf + offs, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, pgc); // LinkPGCN pgc
138 else
139 write8(buf + offs, 0x30, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // JumpSS FP
140 offs += 8;
141 ncmd = (offs - base) / 8 - 1;
142 if (ncmd > 128)
143 {
144 fprintf
145 (
146 stderr,
147 "ERR: Too many titlesets/titles/menus/etc for jumppad to handle."
148 " Reduce complexity and/or disable\njumppad.\n"
149 );
150 exit(1);
151 } /*if*/
152 buf[0xE5] = base; /* offset within PGC to command table, low byte */
153 buf[base + 1] = ncmd; /* number of pre commands, low byte */
154 write2(buf + base + 6, 7 + 8 * ncmd); /* end address relative to command table */
155 return offs;
156 } /*jumppgc*/
157
genpgc(unsigned char * buf,const struct workset * ws,const struct pgcgroup * group,int pgc,vtypes ismenu,int entry)158 static int genpgc(unsigned char *buf,const struct workset *ws,const struct pgcgroup *group,int pgc,vtypes ismenu,int entry)
159 /* generates a PGC entry for an IFO file in buf. */
160 {
161 const struct vobgroup *va = (ismenu != VTYPE_VTS ? ws->menus->mg_vg : ws->titles->pg_vg);
162 const struct pgc * const thispgc = group->pgcs[pgc];
163 int i, j, d;
164
165 /* buf[0], buf[1] don't seem to be used */
166
167 // PGC header starts at buf[16]
168 buf[2] = thispgc->numprograms;
169 buf[3] = thispgc->numcells;
170 write4(buf + 4, buildtimeeven(va, getptsspan(thispgc))); /* BCD playback time + frame rate */
171 /* buf[8 .. 11] -- prohibited user ops -- none for now */
172 for (i = 0; i < va->numaudiotracks; i++)
173 {
174 if (va->ad[i].aid)
175 buf[12 + i * 2] = 0x80 | (va->ad[i].aid - 1);
176 /* PGC_AST_CTL, audio stream control: actual stream IDs corresponding to
177 each stream described in IFO */
178 } /*for*/
179 for (i = 0; i < va->numsubpicturetracks; i++)
180 {
181 int m;
182 bool e;
183 e = false;
184 for (m = 0; m < 4; m++)
185 if (thispgc->subpmap[i][m] & 128)
186 {
187 buf[28 + i * 4 + m] = thispgc->subpmap[i][m] & 127;
188 /* PGC_SPST_CTL, subpicture stream control: actual stream IDs corresponding
189 to each stream described in IFO */
190 e = true;
191 } /*if; for*/
192 if (e)
193 buf[28 + i * 4] |= 0x80; /* set stream-available flag */
194 } /*for*/
195 buf[163] = thispgc->pauselen; // PGC stilltime
196 for (i = 0; i < 16; i++) /* colour lookup table (0, Y, Cr, Cb) */
197 write4
198 (
199 buf + 164 + i * 4,
200 thispgc->colors->color[i] < COLOR_UNUSED ? thispgc->colors->color[i] : 0x108080
201 );
202
203 d = 0xEC; /* start assembling commands here */
204 // command table
205 {
206 unsigned char *cd = buf + d + 8, *preptr, *postptr, *cellptr;
207 preptr = cd; /* start of pre commands */
208 cd += genjumppad(cd, ismenu, entry, ws, group);
209 if (thispgc->prei)
210 {
211 cd = vm_compile(preptr, cd, ws, thispgc->pgcgroup, thispgc, thispgc->prei, ismenu);
212 if (!cd)
213 {
214 fprintf(stderr, "ERR: in %s pgc %d, <pre>\n", pstypes[ismenu], pgc);
215 exit(1);
216 } /*if*/
217 }
218 else if (thispgc->numbuttons)
219 {
220 write8(cd, 0x56, 0x00, 0x00, 0x00, 4 * 1, 0x00, 0x00, 0x00); cd += 8;
221 // set active button to be #1
222 } /*if*/
223
224 postptr = cd; /* start of post commands */
225 if (thispgc->numbuttons && !allowallreg)
226 {
227 /* transfer sequence for button instructions too long to fit in PCI packet */
228 /* enter with g15 = button number */
229 write8(cd, 0x61, 0x00, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x00); // g[14]=g[15]
230 write8(cd + 8, 0x71, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00); // g[15]=0
231 cd += 8 * (thispgc->numbuttons + 2);
232 /* transfer to individual button sequences--reserve space to be filled in below */
233 } /*if*/
234 if (thispgc->posti)
235 {
236 cd = vm_compile(postptr, cd, ws, thispgc->pgcgroup, thispgc, thispgc->posti, ismenu);
237 if (!cd)
238 {
239 fprintf(stderr, "ERR: in %s pgc %d, <post>\n", pstypes[ismenu], pgc);
240 exit(1);
241 } /*if*/
242 }
243 else
244 {
245 write8(cd, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // exit
246 cd += 8;
247 } /*if*/
248 if (thispgc->numbuttons && !allowallreg)
249 {
250 /* compile and insert all the button instructions that wouldn't fit
251 in the PCI packets in the VOB */
252 unsigned char *buttonptr = cd;
253 for (i = 0; i < thispgc->numbuttons; i++)
254 {
255 const struct button * const thisbutton = &thispgc->buttons[i];
256 unsigned char * cdd = vm_compile(postptr, cd, ws, thispgc->pgcgroup, thispgc, thisbutton->commands, ismenu);
257 if (!cdd)
258 {
259 fprintf(stderr, "ERR: in %s pgc %d, button %s\n", pstypes[ismenu], pgc, thisbutton->name);
260 exit(1);
261 } /*if*/
262 if (cdd == cd + 8)
263 {
264 // the button fits in one command; assume it went in the vob itself
265 memset(postptr + i * 8 + 16, 0, 8); // nop
266 memset(cd, 0, 8);
267 // reset the just compiled command, since it was test written
268 // to part of the pgc structure
269 }
270 else
271 {
272 write8(postptr + i * 8 + 16, 0x00, 0xa1, 0x00, 0x0E, 0x00, i + 1, 0x00, (cd - postptr) / 8 + 1);
273 /* if g14 == i + 1 then goto (cd - postptr) / 8 + 1 */
274 /* insert entry in transfer table */
275 cd = cdd;
276 } /*if*/
277 } /*for*/
278 if (cd == buttonptr)
279 memset(postptr, 0, 16);
280 /* no button transfer sequences generated, nop the register transfer */
281 } /*if*/
282
283 vm_optimize(postptr, postptr, &cd);
284 /* tidy up all the code I just output */
285
286 cellptr = cd;
287 for (i = 0; i < thispgc->numsources; i++)
288 for (j = 0; j < thispgc->sources[i]->numcells; j++)
289 {
290 const struct cell * const thiscell = &thispgc->sources[i]->cells[j];
291 if (thiscell->commands)
292 {
293 unsigned char *cdd = vm_compile(cellptr, cd, ws, thispgc->pgcgroup, thispgc, thiscell->commands, ismenu);
294 if (!cdd)
295 {
296 fprintf(stderr, "ERR: in %s pgc %d, <cell>\n", pstypes[ismenu], pgc);
297 exit(1);
298 } /*if*/
299 if (cdd != cd + 8)
300 {
301 fprintf(stderr, "ERR: Cell command can only compile to one VM instruction.\n");
302 exit(1);
303 } /*if*/
304 cd = cdd;
305 } /*if*/
306 } /*for; for*/
307
308 write2(buf + 228, d); /* offset to commands */
309 if (cd - (buf + d) - 8 > 128 * 8) // can only have 128 commands
310 {
311 fprintf(stderr, "ERR: Can only have 128 commands for pre, post, and cell commands.\n");
312 exit(1);
313 } /*if*/
314 /* fill in command table header */
315 write2(buf + d, (postptr - preptr) / 8); /* nr pre commands */
316 write2(buf + d + 2, (cellptr - postptr) / 8); /* nr post commands */
317 write2(buf + d + 4, (cd - cellptr) / 8); /* nr cell commands */
318 write2(buf + d + 6, cd - (buf + d) - 1); /* end address relative to command table */
319 d = cd - buf;
320 }
321
322 if (thispgc->numsources)
323 {
324 /* fill in program map, cell playback info table and cell position info table */
325 int cellline;
326 bool notseamless;
327 write2(buf + 230, d); /* offset to program map */
328 j = 1;
329 for (i = 0; i < thispgc->numsources; i++)
330 { /* generate the program map */
331 int k;
332 const struct source * const thissource = thispgc->sources[i];
333 for (k = 0; k < thissource->numcells; k++)
334 {
335 if (thissource->cells[k].scellid == thissource->cells[k].ecellid)
336 continue; /* empty cell */
337 if (thissource->cells[k].ischapter != CELL_NEITHER)
338 buf[d++] = j; /* this cell starts a program (possibly a chapter as well) */
339 j += thissource->cells[k].ecellid - thissource->cells[k].scellid;
340 } /*for*/
341 } /*for*/
342 d += d & 1; /* round up to word boundary */
343
344 write2(buf + 232, d); /* offset to cell playback information table */
345 j = -1;
346 cellline = 1;
347 notseamless = true; /* first cell never seamless, otherwise a/v sync problems will occur */
348 for (i = 0; i < thispgc->numsources; i++)
349 { /* generate the cell playback information table */
350 int k, l, m, firsttime;
351 const struct source * const thissource = thispgc->sources[i];
352 for (k = 0; k < thissource->numcells; k++)
353 for (l = thissource->cells[k].scellid; l < thissource->cells[k].ecellid; l++)
354 {
355 const int id = thissource->vob->vobid * 256 + l;
356 int vi;
357 for (m = 0; m < va->numaudiotracks; m++)
358 if
359 (
360 j >= 0
361 &&
362 getaudch(va, m) >= 0
363 &&
364 calcaudiogap(va, j, id, getaudch(va, m))
365 )
366 notseamless = true;
367 buf[d] =
368 (notseamless ? 0 : 8 /* seamless multiplex */)
369 |
370 (j + 1 != id ? 2 /* SCR discontinuity */ : 0);
371 // if this is the first cell of the source, then set 'STC_discontinuity', otherwise set 'seamless presentation'
372 notseamless = false; /* initial assumption for next cell */
373 j = id;
374
375 vi = findcellvobu(thissource->vob, l);
376 firsttime = thissource->vob->vobu[vi].sectpts[0];
377 write4(buf + 8 + d, thissource->vob->vobu[vi].sector);
378 /* first VOBU start sector */
379 /* first ILVU end sector not supported for now */
380 vi = findcellvobu(thissource->vob, l + 1) - 1;
381 if (l == thissource->cells[k].ecellid - 1)
382 {
383 if (thissource->cells[k].pauselen > 0)
384 {
385 buf[2 + d] = thissource->cells[k].pauselen; /* cell still time */
386 notseamless = true; // cells with stilltime are not seamless
387 } /*if*/
388 if (thissource->cells[k].commands)
389 {
390 buf[3 + d] = cellline++; /* cell command nr */
391 notseamless = true; // cells with commands are not seamless
392 } /*if*/
393 } /*if*/
394 write4(buf + 4 + d, buildtimeeven(va, thissource->vob->vobu[vi]. sectpts[1] - firsttime));
395 /* cell playback time + frame rate */
396 write4(buf + 16 + d, thissource->vob->vobu[vi].sector);
397 /* last VOBU start sector */
398 write4(buf + 20 + d, thissource->vob->vobu[vi].lastsector);
399 /* last VOBU end sector */
400 d += 24;
401 } /*for; for*/
402 } /*for*/
403
404 write2(buf + 234, d); /* offset to cell position information table */
405 for (i = 0; i < thispgc->numsources; i++)
406 { /* build the cell position information table */
407 int j,k;
408 const struct source * const thissource = thispgc->sources[i];
409 for (j = 0; j < thissource->numcells; j++)
410 for (k = thissource->cells[j].scellid; k < thissource->cells[j].ecellid; k++)
411 { /* put in IDs of all nonempty cells */
412 buf[1 + d] = thissource->vob->vobid; /* VOB ID low byte */
413 buf[3 + d] = k; /* cell ID */
414 d += 4;
415 } /*for*/
416 } /*for*/
417 } /*if thispgc->numsources*/
418
419 return d;
420 } /*genpgc*/
421
createpgcgroup(const struct workset * ws,vtypes ismenu,const struct pgcgroup * va,unsigned char * buf)422 static int createpgcgroup(const struct workset *ws,vtypes ismenu,const struct pgcgroup *va,unsigned char *buf /* where to put generated table */)
423 /* generates a VMGM_LU, VTSM_LU or VTS_PGCI table and all associated PGCs. Returns -1 if there wasn't enough space. */
424 {
425 int len, i, pgcidx, nrtitles;
426 len = va->numpgcs + va->numentries;
427 /* duplicate each PGC for each additional menu entry type it may have ... */
428 for (i = 0; i < va->numpgcs; i++)
429 if (va->pgcs[i]->entries)
430 len--; /* ... beyond the first one */
431 write2(buf, len); /* nr language units (VMGM_LU, VTSM_LU)/program chains (VTS_PGCI) */
432 len = len * 8 + 8; /* total length of VMGM_LU/VTSM_LU/VTS_PGCI */
433 pgcidx = 8;
434 nrtitles = 0;
435 for (i = 0; i < va->numpgcs; i++)
436 { /* generate the PGCs and put in their offsets */
437 int j = 0;
438 if (buf + len + BUFFERPAD - bigwritebuf > bigwritebuflen)
439 return -1; /* caller needs to give me more space */
440 if (ismenu == VTYPE_VTS)
441 {
442 if (va->pgcs[i]->entries == 0)
443 {
444 ++nrtitles;
445 buf[pgcidx] = 0x80 | nrtitles; /* title type & nr for VTS_PGC */
446 } /*if*/
447 }
448 else
449 {
450 buf[pgcidx] = 0;
451 for (j = 2; j < 8; j++)
452 if (va->pgcs[i]->entries & (1 << j))
453 {
454 buf[pgcidx] = 0x80 | j; /* menu type for VMGM_LU/VTSM_LU */
455 break;
456 } /*if; for*/
457 } /*if*/
458 write4(buf + pgcidx + 4, len); /* offset to VMGM_PGC/VTSM_PGC/VTS_PGC */
459 len += genpgc(buf + len, ws, va, i, ismenu, j); /* put it there */
460 pgcidx += 8;
461 } /*for*/
462 for (i = 2; i < 8; i++)
463 {
464 if (va->allentries & (1 << i))
465 {
466 int j;
467 if (buf + len + BUFFERPAD - bigwritebuf > bigwritebuflen)
468 return -1; /* caller needs to give me more space */
469 for (j = 0; j < va->numpgcs; j++)
470 if (va->pgcs[j]->entries & (1 << i))
471 break;
472 if (j < va->numpgcs) // this can be false if jumppad is set
473 {
474 // is this the first entry for this pgc? if so, it was already
475 // triggered via the PGC itself, so skip writing the extra
476 // entry here
477 if ((va->pgcs[j]->entries & ((1 << i) - 1)) == 0)
478 continue;
479 } /*if*/
480 j++;
481 buf[pgcidx] = 0x80 | i; /* menu type */
482 write4(buf + pgcidx + 4, len); /* offset to VMGM_PGC/VTSM_PGC */
483 len += jumppgc(buf + len, j, ismenu, i, ws, va);
484 pgcidx += 8;
485 } /*if*/
486 } /*for*/
487 write4(buf + 4, len - 1);
488 /* end address (last byte of last PGC in this LU) relative to VMGM_LU/VTSM_LU */
489 return len;
490 } /*createpgcgroup*/
491
CreatePGC(FILE * h,const struct workset * ws,vtypes ismenu)492 int CreatePGC(FILE *h, const struct workset *ws, vtypes ismenu)
493 {
494 unsigned char *buf;
495 int len,ph,i;
496 bool in_it;
497
498 in_it = bigwritebuflen == 0; /* don't do first allocation if already got a buffer from last time */
499 retry: /* come back here if buffer wasn't big enough to generate a PGC group */
500 if (in_it)
501 {
502 if (bigwritebuflen == 0)
503 bigwritebuflen = 128 * 1024; /* first call */
504 else
505 bigwritebuflen <<= 1; /* previous write failed, try twice the size */
506 if (bigwritebuf)
507 free(bigwritebuf);
508 bigwritebuf = malloc(bigwritebuflen);
509 } /*if*/
510 in_it = true;
511 buf = bigwritebuf;
512 memset(buf, 0, bigwritebuflen);
513 if (ismenu != VTYPE_VTS) /* create VMGM_PGCI_UT/VTSM_PGCI_UT structure */
514 {
515 buf[1] = ws->menus->numgroups; // # of language units
516 ph = 8 + 8 * ws->menus->numgroups; /* VMGM_LU/VTSM_LU structures start here */
517
518 for (i = 0; i < ws->menus->numgroups; i++)
519 {
520 unsigned char * const plu = buf + 8 + 8 * i;
521 const struct langgroup * const lg = &ws->menus->groups[i];
522 memcpy(plu, lg->lang, 2); /* ISO639 language code */
523 if (ismenu == VTYPE_VTSM)
524 plu[3] = lg->pg->allentries;
525 else /* ismenu = VTYPE_VMGM */
526 plu[3] = 0x80; // menu system contains entry for title
527 write4(plu + 4, ph);
528 len = createpgcgroup(ws, ismenu, lg->pg, buf + ph);
529 if (len < 0)
530 goto retry;
531 ph += len;
532 } /*for*/
533 write4(buf + 4, ph - 1);
534 /* end address (last byte of last PGC in last LU) relative to VMGM_PGCI_UT/VTSM_PGCI_UT */
535 }
536 else
537 {
538 /* generate VTS_PGCI structure */
539 len = createpgcgroup(ws, VTYPE_VTS, ws->titles, buf);
540 if (len < 0)
541 goto retry;
542 ph = len;
543 } /*if*/
544
545 assert(ph <= bigwritebuflen);
546 ph = (ph + 2047) & (-2048);
547 if (h)
548 {
549 (void)fwrite(buf, 1, ph, h);
550 if (errno != 0)
551 {
552 fprintf
553 (
554 stderr,
555 "\nERR: Error %d -- %s -- writing output PGC\n",
556 errno,
557 strerror(errno)
558 );
559 exit(1);
560 } /*if*/
561 } /*if*/
562 return ph / 2048;
563 } /*CreatePGC*/
564