1 /*
2     Code generation for the VM language
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 <assert.h>
25 
26 #include "dvdauthor.h"
27 #include "da-internal.h"
28 #include "dvdvm.h"
29 
30 
31 struct vm_statement *dvd_vm_parsed_cmd;
32 
33 /* arbitrary implementation limits--should be adequate, given
34   the restrictions on the length of instruction sequences */
35 #define MAXLABELS 200
36 #define MAXGOTOS 200
37 
38 struct dvdlabel {
39     char *lname; /* label name */
40     unsigned char *code;
41       /* pointer into buf where label is defined or where goto instruction needs fixup */
42 };
43 
44 static struct dvdlabel labels[MAXLABELS];
45 static struct dvdlabel gotos[MAXGOTOS];
46 static int numlabels=0, numgotos=0;
47 
negatecompare(int compareop)48 static int negatecompare(int compareop)
49   /* returns the comparison with the opposite result. Assumes the op isn't BC ("&"). */
50   {
51     return compareop ^ 1 ^ ((compareop & 4) >> 1);
52       /* EQ <=> NE, GE <=> LT, GT <=> LE */
53   } /*negatecompare*/
54 
swapcompare(int compareop)55 static int swapcompare(int compareop)
56   /* returns the equivalent comparison with the operands swapped. */
57   {
58     if (compareop < 4) /* BC, EQ, NE unchanged */
59         return compareop;
60     else
61         return compareop ^ 3; /* GE <=> LT, GT <=> LE */
62   } /*swapcompare*/
63 
compile_usesreg(const struct vm_statement * cs,int target)64 static bool compile_usesreg(const struct vm_statement *cs, int target)
65   /* does cs reference the specified register. */
66   {
67     while (cs)
68       {
69         if (cs->op == VM_VAL)
70             return cs->i1 == target - 256;
71         if (compile_usesreg(cs->param, target))
72             return true;
73         cs = cs->next;
74       } /*while*/
75     return false;
76   } /*compile_usesreg*/
77 
nexttarget(int t)78 static int nexttarget(int t)
79   /* returns the next register after t in the range I have reserved. Will fail
80     if it's all used, or if I haven't got a reserved range. */
81   {
82     if (!allowallreg)
83       {
84         if (t < 13)
85             return 13;
86         t++;
87         if (t < 16)
88             return t;
89       } /*if*/
90     fprintf(stderr,"ERR:  Expression is too complicated, ran out of registers\n");
91     exit(1);
92   } /*nexttarget*/
93 
94 // like nexttarget, but takes a VM_VAL argument
nextval(int t)95 static int nextval(int t)
96   {
97     if (t < -128)
98         return nexttarget(t + 256) - 256; /* it's a register, return next available register */
99     else
100         return nexttarget(-1) - 256; /* not a register, return first available register */
101   } /*nextval*/
102 
compileop(unsigned char * buf,int target,int op,int val)103 static unsigned char *compileop(unsigned char *buf, int target, int op, int val)
104   /* compiles a command to set the target GPRM to the result of the specified operation
105     on it and the specified value. */
106   {
107     if (op == VM_VAL && target == val + 256)
108         return buf; /* setting register to its same value => noop */
109     write8(buf, val >= 0 ? 0x70 : 0x60, 0x00, 0x00, target, val >= 0 ? (val >> 8) : 0x00, val, 0x00, 0x00);
110       /* target op= val (op to be filled in below) */
111     switch(op)
112       {
113     case VM_VAL: /* simple assignment */
114         buf[0] |= 1;
115     break;
116     case VM_ADD:
117         buf[0] |= 3;
118     break;
119     case VM_SUB:
120         buf[0] |= 4;
121     break;
122     case VM_MUL:
123         buf[0] |= 5;
124     break;
125     case VM_DIV:
126         buf[0] |= 6;
127     break;
128     case VM_MOD:
129         buf[0] |= 7;
130     break;
131     case VM_RND:
132         buf[0] |= 8;
133     break;
134     case VM_AND:
135         buf[0] |= 9;
136     break;
137     case VM_OR:
138         buf[0] |= 10;
139     break;
140     case VM_XOR:
141         buf[0] |= 11;
142     break;
143     default:
144         fprintf(stderr, "ERR:  Unknown op in compileop: %d\n", op);
145         exit(1);
146       } /*switch*/
147     return buf + 8;
148   } /*compileop*/
149 
issprmval(const struct vm_statement * v)150 static int issprmval(const struct vm_statement *v)
151   /* is operand v a reference to an SPRM value. */
152   {
153     return v->op == VM_VAL && v->i1 >= -128 && v->i1 < 0;
154   } /*issprmval*/
155 
compileexpr(unsigned char * buf,int target,struct vm_statement * cs)156 static unsigned char *compileexpr(unsigned char *buf, int target, struct vm_statement *cs)
157   /* generates code to put the the value of an expression cs into GPRM target.
158     Returns pointer to after generated code. */
159   {
160     struct vm_statement *v, **vp;
161     bool isassoc, canusesprm;
162     if (cs->op == VM_VAL) /* simple value reference */
163         return compileop(buf, target, VM_VAL, cs->i1); /* assign value to target */
164 
165     isassoc = /* associative operator--just so happens these are also commutative */
166             cs->op == VM_ADD
167         ||
168             cs->op == VM_MUL
169         ||
170             cs->op == VM_AND
171         ||
172             cs->op == VM_OR
173         ||
174             cs->op == VM_XOR;
175     canusesprm = cs->op == VM_AND || cs->op == VM_OR || cs->op == VM_XOR;
176       /* operations where the source may be an SPRM (also VM_VAL, but that was already dealt with) */
177 
178     if (isassoc)
179       {
180       /* if one of the source operands is the destination register, move it to the front */
181         for (vp = &cs->param->next; *vp; vp = &(vp[0]->next))
182             if (vp[0]->op == VM_VAL && vp[0]->i1 == target - 256)
183               {
184                 v = *vp;
185                 *vp = v->next; /* take out from its place in chain */
186                 v->next = cs->param;
187                 cs->param = v; /* and put the VM_VAL op on front of chain */
188                 break;
189               } /*if*/
190       } /*if*/
191 
192     if (compile_usesreg(cs->param->next, target))
193       {
194       /* cannot evaluate cs->param directly into target, because target is used
195         in evaluation */
196         const int t2 = nexttarget(target); /* need another register */
197         buf = compileexpr(buf, t2, cs); /* evaluate expr into t2 */
198         write8(buf, 0x61, 0x00, 0x00, target, 0x00, t2, 0x00, 0x00);
199           /* and then move value of t2 to target */
200         buf += 8;
201         if (t2 == 15)
202           /* just zapped a reserved register whose value might be misinterpreted elsewhere */
203           {
204             write8(buf, 0x71, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00);
205               /* g15 = 0 */
206             buf += 8;
207           } /*if*/
208         return buf;
209       } /*if*/
210 
211     if (isassoc && cs->param->op == VM_VAL && cs->param->i1 != target - 256)
212       {
213         // if the first param is a value, then try to move a complex operation farther up or an SPRM access (if SPRM ops are not allowed)
214         for (vp = &cs->param->next; *vp; vp = &(vp[0]->next))
215             if (vp[0]->op != VM_VAL || canusesprm < issprmval(vp[0]))
216               {
217                 v = *vp;
218                 *vp = v->next; /* take out from its place in chain */
219                 v->next = cs->param;
220                 cs->param = v; /* and put the SPRM/non-VM_VAL op on front of chain */
221                 break;
222               } /*if*/
223       } /*if*/
224 
225     // special case -- rnd where the parameter is a reg or value
226     if (cs->op == VM_RND && cs->param->op == VM_VAL)
227       {
228         assert(cs->param->next == 0); /* only one operand */
229         return compileop(buf, target, cs->op, cs->param->i1);
230       } /*if*/
231 
232     buf = compileexpr(buf, target, cs->param); /* use target for first/only operand */
233     if (cs->op == VM_RND)
234       {
235         assert(cs->param->next == 0); /* only one operand */
236         return compileop(buf, target, cs->op, target - 256);
237           /* operand from target, result into target */
238       }
239     else /* all other operators take two operands */
240       {
241         for (v = cs->param->next; v; v = v->next) /* process chain of operations */
242           {
243             if (v->op == VM_VAL && canusesprm >= issprmval(v))
244                 buf = compileop(buf, target, cs->op, v->i1);
245                   /* can simply put function value straight into target */
246             else
247               {
248                 const int t2 = nexttarget(target);
249                 buf = compileexpr(buf, t2, v); /* put value of v into t2 */
250                 buf = compileop(buf, target, cs->op, t2 - 256);
251                   /* then value of that and target operated on by op into target */
252                 if (t2 == 15)
253                   {
254                   /* just zapped a reserved register whose value might be misinterpreted elsewhere */
255                     write8(buf, 0x71, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00);
256                       /* g15 = 0 */
257                     buf += 8;
258                   } /*if*/
259               } /*if*/
260           } /*for*/
261       } /*if*/
262     return buf;
263   } /*compileexpr*/
264 
compilebool(const unsigned char * obuf,unsigned char * buf,struct vm_statement * cs,const unsigned char * iftrue,const unsigned char * iffalse)265 static unsigned char *compilebool
266   (
267     const unsigned char *obuf, /* start of buffer, for calculating instruction numbers */
268     unsigned char *buf, /* where to insert compiled instructions */
269     struct vm_statement *cs, /* expression to compile */
270     const unsigned char *iftrue, /* branch target for true */
271     const unsigned char *iffalse /* branch target for false */
272  )
273   /* compiles an expression that returns a true/false result, branching to iftrue or
274     iffalse depending. Returns pointer to after generated code. */
275   {
276     switch (cs->op)
277       {
278     case VM_EQ:
279     case VM_NE:
280     case VM_GTE:
281     case VM_GT:
282     case VM_LTE:
283     case VM_LT:
284       { /* the two operands are cs->param and cs->param->next */
285         int r1, r2, op;
286         op = cs->op - VM_EQ + 2;
287           /* convert to comparison encoding that can be inserted directly into instruction */
288         if (cs->param->op == VM_VAL)
289             r1 = cs->param->i1; /* value already here */
290         else /* cs->param is something more complex */
291           {
292             r1 = nextval(0); /* temporary place to put it */
293             buf = compileexpr(buf, r1 & 15, cs->param); /* put it there */
294           } /*if*/
295       /* at this point, r1 is literal/register containing first operand */
296         if (cs->param->next->op == VM_VAL && (r1 < 0 || cs->param->next->i1 < 0))
297           /* if one operand is a register and the other is a simple literal or register,
298             I can combine them directly */
299             r2 = cs->param->next->i1;
300         else /* not so simple */
301           {
302             r2 = nextval(r1);
303             buf = compileexpr(buf, r2 & 15, cs->param->next);
304           } /*if*/
305       /* at this point, r2 is literal/register containing second operand */
306         if (r1 >= 0)
307           {
308           /* literal value--swap with r2 */
309             const int t = r1;
310             r1 = r2;
311             r2 = t;
312             op = swapcompare(op);
313           } /*if*/
314         if (iffalse > iftrue)
315           {
316           /* make false branch the one earlier in buffer, in the hope I can fall through to it */
317           /* really only worth doing this if iffalse - buf == 8 */
318             const unsigned char * const t = iftrue;
319             iftrue = iffalse;
320             iffalse = t;
321             op = negatecompare(op);
322           } /*if*/
323         if (r2 >= 0)
324             write8(buf, 0x00, 0x81 | (op << 4), 0x00, r1, r2 >> 8, r2,0x00, 0x00);
325               /* if r1 op r2 then goto true branch */
326         else /* r1 and r2 both registers */
327             write8(buf, 0x00, 0x01 | (op << 4), 0x00, r1, 0x00, r2, 0x00, 0x00);
328               /* if r1 op r2 then goto true branch */
329         buf[7] = (iftrue - obuf) / 8 + 1; /* branch target instr nr */
330         buf += 8;
331         if (iffalse > buf)
332           {
333           /* can't fallthrough for false branch */
334             write8(buf, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
335               /* goto false branch */
336             buf[7] = (iffalse - obuf) / 8 + 1; /* branch target */
337             buf += 8;
338           } /*if*/
339       } /*case*/
340     break;
341 
342     case VM_LOR:
343     case VM_LAND:
344       {
345         const int op = cs->op;
346         cs = cs->param;
347         while (cs->next) /* process chain of operands, doing short-cut evaluation */
348           {
349           /* compile this operand, branching to next or to final true/false destination
350             as appropriate */
351             unsigned char * n = buf + 8;
352               /* where to continue chain--assume it'll compile to one instruction to begin with */
353             while (true) /* should loop no more than twice */
354               {
355                 unsigned char * const nn = compilebool
356                   (
357                     /*obuf =*/ obuf,
358                     /*buf =*/ buf,
359                     /*cs =*/ cs,
360                     /*iftrue =*/
361                         op == VM_LAND ?
362                             n /* continue AND-chain as long as conditions are true */
363                         :
364                             iftrue, /* no need to continue OR-chain on true */
365                     /*iffalse =*/
366                         op == VM_LOR ?
367                             n /* continue OR-chain as long as conditions are false */
368                         :
369                             iffalse /* no need to continue AND-chain on false */
370                   );
371                 if (nn == n)
372                     break;
373               /* too many instructions generated to fit */
374                 n = nn; /* try again leaving this much room */
375               } /*while*/
376             buf = n;
377             cs = cs->next;
378           } /*while*/
379         buf = compilebool(obuf, buf, cs, iftrue, iffalse);
380       }
381     break;
382 
383     case VM_NOT:
384         return compilebool(obuf, buf, cs->param, iffalse, iftrue);
385           /* re-enter myself with the operands swapped */
386 
387     default:
388         fprintf(stderr, "ERR:  Unknown bool op: %d\n", cs->op);
389         exit(1);
390       } /*switch*/
391     return buf;
392   } /*compilebool*/
393 
394 // NOTE: curgroup is passed separately from curpgc, because in FPC, curpgc==NULL, but curgroup!=NULL
compilecs(const unsigned char * obuf,unsigned char * buf,const struct workset * ws,const struct pgcgroup * curgroup,const struct pgc * curpgc,const struct vm_statement * cs,vtypes ismenu)395 static unsigned char *compilecs
396   (
397     const unsigned char *obuf,
398     unsigned char *buf,
399     const struct workset *ws,
400     const struct pgcgroup *curgroup,
401     const struct pgc *curpgc,
402     const struct vm_statement *cs,
403     vtypes ismenu /* needed to decide what kinds of jumps/calls are allowed */
404   )
405   /* compiles a parse tree into naive VM instructions: no optimization of conditionals,
406     and no fixup of gotos; these tasks are left to caller. */
407   {
408     bool lastif = false;
409     while (cs)
410       {
411         if (cs->op != VM_NOP)
412             lastif = false; /* no need for dummy target for last branch, I'll be providing a real one */
413           /* actually check for VM_NOP is unnecessary so long as no construct will
414             generate one */
415         switch (cs->op)
416           {
417         case VM_SET: /* cs->i1 is destination, cs->param is source */
418             switch (cs->i1)
419               {
420             case 0:
421             case 1:
422             case 2:
423             case 3:
424             case 4:
425             case 5:
426             case 6:
427             case 7:
428             case 8:
429             case 9:
430             case 10:
431             case 11:
432             case 12:
433             case 13:
434             case 14:
435             case 15: // set GPRM
436                 buf = compileexpr(buf, cs->i1, cs->param);
437             break;
438 
439             case 32 + 0:
440             case 32 + 1:
441             case 32 + 2:
442             case 32 + 3:
443             case 32 + 4:
444             case 32 + 5:
445             case 32 + 6:
446             case 32 + 7:
447             case 32 + 8:
448             case 32 + 9:
449             case 32 + 10:
450             case 32 + 11:
451             case 32 + 12:
452             case 32 + 13:
453             case 32 + 14:
454             case 32 + 15: // set GPRM, counter mode
455                 if (cs->param->op == VM_VAL)
456                   { // we can set counters to system registers
457                     const int v = cs->param->i1; /* reg/literal value to set */
458                     if (v < 0)
459                         write8(buf, 0x43, 0x00, 0x00, v,0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
460                           /* SetGPRMMD indirect */
461                     else
462                         write8(buf, 0x53, 0x00, v / 256, v, 0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
463                           /* SetGPRMMD direct */
464                     buf += 8;
465                   }
466                 else /* not so simple */
467                   {
468                     const int r = nexttarget(0); /* temporary place to put value */
469                     buf = compileexpr(buf, r, cs->param); /* put it there */
470                     write8(buf, 0x43, 0x00, 0x00, r, 0x00, 0x80 | (cs->i1 - 32), 0x00, 0x00);
471                       /* SetGPRMMD indirect to r */
472                     buf += 8;
473                   } /*if*/
474             break;
475 
476             case 128 + 1: // audio
477             case 128 + 2: // subtitle
478             case 128 + 3: // angle
479                 if (cs->param->op == VM_VAL && !(cs->param->i1 >= -128 && cs->param->i1 < 0))
480                   {
481                     const int v = cs->param->i1;
482                     write8(buf, v < 0 ? 0x41 : 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
483                       /* SetSTN indirect/direct */
484                     buf[(cs->i1 - 128) + 2] = 128 | v; // doesn't matter whether this is a register or a value
485                       /* put new value/regnr into audio/subpicture/angle field as appropriate */
486                     buf += 8;
487                   }
488                 else /* complex expression */
489                   {
490                     const int r = nexttarget(0);
491                     buf = compileexpr(buf, r, cs->param);
492                     write8(buf, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
493                       /* SetSTN indirect */
494                     buf[(cs->i1 - 128) + 2] = 128 | r;
495                       /* put regnr containing new value into audio/subpicture/angle field as appropriate */
496                     buf += 8;
497                   } /*if*/
498             break;
499 
500             case 128 + 8: // button
501                 if (cs->param->op == VM_VAL && !(cs->param->i1 >= -128 && cs->param->i1 < 0))
502                   {
503                     const int v = cs->param->i1;
504                     if (v > 0 && (v & 1023) != 0)
505                         fprintf
506                           (
507                             stderr,
508                             "WARN: Button value is %d, but it should be a multiple of 1024\n"
509                               "WARN: (button #1=1024, #2=2048, etc)\n",
510                             v
511                           );
512                     if (v < 0)
513                         write8(buf, 0x46, 0x00, 0x00, 0x00, 0x00, v, 0x00, 0x00);
514                           /* SetHL_BTNN indirect */
515                     else
516                         write8(buf, 0x56, 0x00, 0x00, 0x00, v / 256, v, 0x00, 0x00);
517                           /* SetHL_BTNN direct */
518                     buf += 8;
519                   }
520                 else /* complex expression */
521                   {
522                     const int r = nexttarget(0);
523                     buf = compileexpr(buf, r, cs->param);
524                     write8(buf, 0x46, 0x00, 0x00, 0x00, 0x00, r, 0x00, 0x00);
525                       /* SetHL_BTNN indirect */
526                     buf += 8;
527                   } /*if*/
528                 break;
529 
530             default:
531                 fprintf(stderr, "ERR:  Cannot set SPRM %d\n", cs->i1 - 128);
532                 return 0;
533               } /*switch*/
534             break;
535 
536         case VM_IF: /* if-statement */
537           {
538             unsigned char * iftrue = buf + 8; /* initially try putting true branch here */
539             const unsigned char * iffalse = buf + 16; /* initially try putting false branch here */
540             unsigned char * end = buf + 16; /* initially assuming code will end here */
541             while (true) /* should loop no more than twice */
542               {
543                 unsigned char *lp, *ib, *e;
544                 lp = compilecs(obuf, iftrue, ws, curgroup, curpgc, cs->param->next->param, ismenu);
545                   /* the if-true part */
546                 if (cs->param->next->next)
547                   {
548                   /* there's an else-part */
549                     e = compilecs(obuf, lp + 8, ws, curgroup, curpgc, cs->param->next->next, ismenu);
550                       /* compile the else-part, leaving room for following instr */
551                     write8(lp, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, (e - obuf ) / 8 + 1);
552                       /* insert a goto at the end of the if-true part to branch over the else-part */
553                     lp += 8; /* include in true branch */
554                   }
555                 else
556                     e = lp; /* code ends with true branch */
557                 ib = compilebool(obuf, buf, cs->param, iftrue, iffalse);
558                   /* put condition test at start */
559                 if (!lp)
560                     return 0;
561               /* at this point, ib is just after the condition test, lp is just after
562                 the true branch, and e is just after the end of all the code */
563                 if (ib == iftrue && lp == iffalse)
564                     break; /* all fitted nicely */
565               /* didn't leave enough room for pieces next to each other, try again */
566                 iftrue = ib; /* enough room for condition code */
567                 iffalse = lp; /* enough room for true branch */
568                 end = e;
569               } /*while*/
570             buf = end;
571             lastif = true; // make sure reference statement is generated
572           }
573         break;
574 
575         case VM_LABEL:
576           {
577             int i;
578             for (i = 0; i < numlabels; i++)
579                 if (!strcasecmp(labels[i].lname, cs->s1))
580                   {
581                     fprintf(stderr, "ERR:  Duplicate label '%s'\n", cs->s1);
582                     return 0;
583                   } /*if; for*/
584             if (numlabels == MAXLABELS)
585               {
586                 fprintf(stderr, "ERR:  Too many labels\n");
587                 return 0;
588               } /*if*/
589             labels[numlabels].lname = cs->s1;
590             labels[numlabels].code = buf; /* where label points to */
591             numlabels++;
592             lastif = true; // make sure reference statement is generated
593           }
594         break;
595 
596         case VM_GOTO:
597             if (numgotos == MAXGOTOS)
598               {
599                 fprintf(stderr, "ERR:  Too many gotos\n");
600                 return 0;
601               } /*if*/
602             gotos[numgotos].lname = cs->s1;
603             gotos[numgotos].code = buf; /* point to instruction so it can be fixed up later */
604             numgotos++;
605             write8(buf, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
606             buf += 8;
607         break;
608 
609         case VM_BREAK:
610             write8(buf, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
611             buf += 8;
612         break;
613 
614         case VM_EXIT:
615             write8(buf, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
616             buf += 8;
617         break;
618 
619         case VM_LINK:
620             write8(buf, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, cs->i1);
621             buf += 8;
622         break;
623 
624         case VM_JUMP:
625           {
626             int i1 = cs->i1; /* if nonzero, 1 for VMGM, or titleset nr + 1 */
627             int i2 = cs->i2; /* menu number or menu entry ID + 120 or title number + 128 */
628           /* cs->i3 is PGC number if nonzero and less than 65536; or chapter number + 65536;
629             or program number + 131072; or cell number + 196608 */
630 
631           /* check for various disallowed combinations */
632             if (i1 == 1 && ismenu == VTYPE_VMGM)
633               {
634                 //  VMGM    VMGM    NOPGC   NOCH
635                 //  VMGM    VMGM    NOPGC   CHXX
636                 //  VMGM    VMGM    MPGC    NOCH
637                 //  VMGM    VMGM    MPGC    CHXX
638                 //  VMGM    VMGM    MEPGC   NOCH
639                 //  VMGM    VMGM    MEPGC   CHXX
640                 //  VMGM    VMGM    TPGC    NOCH
641                 //  VMGM    VMGM    TPGC    CHXX
642                 i1 = 0; /* no need to explicitly specify VMGM */
643               } /*if*/
644             if
645               (
646                     (
647                         i2 > 0 && i2 < 128 /* jump to non-entry menu */
648                     ||
649                         i2 == 0 && i1 == 1 /* jump to VMGM */
650                     )
651                 &&
652                     ismenu == VTYPE_VTS
653               )
654               {
655                 //  VTS     NONE    MPGC    NOCH
656                 //  VTS     VMGM    MPGC    NOCH
657                 //  VTS     TS      MPGC    NOCH
658                 //  VTS     NONE    MPGC    CHXX
659                 //  VTS     VMGM    MPGC    CHXX
660                 //  VTS     TS      MPGC    CHXX
661                 //  VTS     NONE    MEPGC   NOCH
662                 //  VTS     VMGM    MEPGC   NOCH
663                 //  VTS     TS      MEPGC   NOCH
664                 //  VTS     NONE    MEPGC   CHXX
665                 //  VTS     VMGM    MEPGC   CHXX
666                 //  VTS     TS      MEPGC   CHXX
667                 //  VTS     VMGM    NOPGC   NOCH
668                 //  VTS     VMGM    NOPGC   CHXX
669 
670                 fprintf(stderr, "ERR:  Cannot jump to a menu from a title, use 'call' instead\n");
671                 return 0;
672               } /*if*/
673             if
674               (
675                     i2 > 0
676                 &&
677                     i2 < 128 /* jump to non-entry menu */
678                 &&
679                     (cs->i3 & 65535) /* PGC/chapter/cell/program specified */
680                 &&
681                     ismenu != VTYPE_VTS
682               )
683               {
684                 //  VMGM    NONE    MPGC    CHXX
685                 //  VMGM    TS      MPGC    CHXX
686                 //  VMGM    NONE    MEPGC   CHXX
687                 //  VMGM    TS      MEPGC   CHXX
688                 //  VTSM    NONE    MPGC    CHXX
689                 //  VTSM    VMGM    MPGC    CHXX
690                 //  VTSM    TS      MPGC    CHXX
691                 //  VTSM    NONE    MEPGC   CHXX
692                 //  VTSM    VMGM    MEPGC   CHXX
693                 //  VTSM    TS      MEPGC   CHXX
694                 fprintf(stderr, "ERR:  Cannot specify chapter when jumping to another menu\n");
695                 return 0;
696               } /*if*/
697             if (i1 /*VMGM/titleset*/ && !i2 /*no PGC*/)
698               {
699                 //  VTSM    VMGM    NOPGC   CHXX
700                 //  VTS     TS      NOPGC   CHXX
701                 //  VTSM    TS      NOPGC   CHXX
702                 //  VMGM    TS      NOPGC   CHXX
703                 //  VTS     TS      NOPGC   NOCH
704                 //  VTSM    TS      NOPGC   NOCH
705                 //  VMGM    TS      NOPGC   NOCH
706                 fprintf(stderr, "ERR:  Cannot omit menu/title if specifying vmgm/titleset\n");
707                 return 0;
708               } /*if*/
709             if
710               (
711                     !i1 /*same VMGM/titleset*/
712                 &&
713                     !i2 /*same PGC*/
714                 &&
715                     !(cs->i3 & 65535) /*no PGC/chapter/cell/program*/
716               )
717               {
718                 //  VTS     NONE    NOPGC   NOCH
719                 //  VTSM    NONE    NOPGC   NOCH
720                 //  VMGM    NONE    NOPGC   NOCH
721                 fprintf(stderr, "ERR:  Nop jump statement\n");
722                 return 0;
723               } /*if*/
724             if
725               (
726                     i2 == 121 /*jump to FPC*/
727                 &&
728                     (
729                         i1 >= 2 /*titleset*/
730                     ||
731                         (i1 == 0 /*current VMGM/titleset*/ && ismenu != VTYPE_VMGM)
732                     )
733               )
734               {
735                 fprintf(stderr, "ERR:  VMGM must be specified with FPC\n");
736                 return 0;
737               } /*if*/
738 
739             // *** ACTUAL COMPILING
740             if
741               (
742                     i1 >= 2 /*titleset*/
743                 &&
744                     i2 >= 120
745                 &&
746                     i2 < 128 /*entry PGC*/
747               )
748               {
749                 //  VTSM    TS      MEPGC   NOCH
750                 //  VMGM    TS      MEPGC   NOCH
751                 if (i2 == 120) /* "default" entry means "root" */
752                     i2 = 123;
753                 write8(buf, 0x30, 0x06, 0x00, 0x01, i1 - 1, 0x80 + (i2 - 120), 0x00, 0x00); buf += 8; // JumpSS VTSM vts 1 menu
754               }
755             else if
756               (
757                   i1 >= 2 /*jump to titleset*/
758               ||
759                   i1 == 1 /*jump to VMGM*/ && i2 >= 128 /*title*/
760               ||
761                   ismenu == VTYPE_VMGM && i2 >= 128 /*title*/ && (cs->i3 & 65535) != 0 /*chapter/program/cell*/
762               )
763               {
764                 //  VMGM    TS      TPGC    CHXX
765                 //  VTSM    TS      MPGC    NOCH
766                 //  VMGM    TS      MPGC    NOCH
767                 //  VTS     TS      TPGC    NOCH
768                 //  VTSM    TS      TPGC    NOCH
769                 //  VMGM    TS      TPGC    NOCH
770                 //  VTS     TS      TPGC    CHXX
771                 //  VTSM    TS      TPGC    CHXX
772                 //  VTS     VMGM    TPGC    NOCH
773                 //  VTSM    VMGM    TPGC    NOCH
774                 //  VTS     VMGM    TPGC    CHXX
775                 //  VTSM    VMGM    TPGC    CHXX
776                 //  VMGM    NONE    TPGC    CHXX
777                 if (jumppad)
778                   {
779                     if (!i1)
780                         i1 = 1; /* make VMGM explicit */
781                     write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, i1, 0x00, 0x00);
782                       /* g15 = i2 << 8 | i1 */
783                     buf += 8;
784                     write8(buf, 0x71, 0x00, 0x00, 0x0E, 0x00, cs->i3, 0x00, 0x00);
785                       /* g14 = cs->i3 */
786                     buf += 8;
787                     write8(buf, 0x30, ismenu != VTYPE_VTS ? 0x06 : 0x08, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00);
788                       /* JumpSS/CallSS VMGM menu entry title */
789                     buf += 8;
790                   }
791                 else
792                   {
793                   /* JumpTT allows directly jumping from VMGM to any titleset, but that
794                     requires global title numbers, not titleset-local title numbers,
795                     and I only work these out later when putting together the final VMGM,
796                     which is why I can't handle it here without the jumppad */
797                     fprintf(stderr, "ERR:  That form of jumping is not allowed\n");
798                     return 0;
799                   } /*if*/
800               }
801             else  if (i1 == 1 /*jump to VMGM*/ || i2 == 121 /*jump to FPC*/)
802               {
803                 //  VTSM    VMGM    NOPGC   NOCH
804                 //  VTSM    VMGM    MPGC    NOCH
805                 //  VTSM    VMGM    MEPGC   NOCH
806                 // cannot error check jumps to the vmgm menu
807                 if (!i2 || i2 == 120)
808                     i2 = 122; /* must be title menu */
809                 if (i2 < 120)
810                     write8(buf, 0x30, 0x06, 0x00, i2, 0x00, 0xC0, 0x00, 0x00); // JumpSS VMGM pgcn
811                 else
812                     write8(buf, 0x30, 0x06, 0x00, 0x00, 0x00, i2 == 121 ? 0 : (0x40 + i2 - 120), 0x00, 0x00); // JumpSS FP or JumpSS VMGM menu
813                 buf += 8;
814               }
815             else if (!i1 && !i2 && (cs->i3 & 65535))
816               {
817                 int numc;
818                 const char *des;
819 
820                 //  VTS     NONE    NOPGC   CHXX
821                 //  VTSM    NONE    NOPGC   CHXX
822                 //  VMGM    NONE    NOPGC   CHXX
823                 if (curpgc == 0)
824                   {
825                     fprintf(stderr, "ERR:  Cannot jump to a chapter from FPC\n");
826                     return 0;
827                   } /*if*/
828                 if (cs->i3 >> 16 == 1 && ismenu != VTYPE_VTS)
829                   {
830                     fprintf(stderr, "ERR:  Menus do not have chapters\n");
831                     return 0;
832                   } /*if*/
833                 switch (cs->i3 >> 16)
834                   {
835                 case 0:
836                     numc = curgroup->numpgcs;
837                     des = "pgc";
838                 break;
839                 case 1:
840                     numc = curpgc->numchapters;
841                     des = "chapter";
842                 break;
843                 case 2:
844                     numc = curpgc->numprograms;
845                     des = "program";
846                 break;
847                 case 3:
848                     numc = curpgc->numcells;
849                     des = "cell";
850                 break;
851                 default: /* shouldn't occur! */
852                     numc = 0;
853                     des = "<err>";
854                 break;
855                   } /*switch*/
856                 if ((cs->i3 & 65535) > numc)
857                   {
858                     fprintf(stderr, "ERR:  Cannot jump to %s %d, only %d exist\n", des, cs->i3 & 65535, numc);
859                     return 0;
860                   } /*if*/
861                 write8(buf, 0x20, 0x04 + (cs->i3 >> 16), 0x00, 0x00, 0x00, 0x00, 0x00, cs->i3); // LinkPGCN pgcn, LinkPTTN pttn, LinkPGCN pgn, or LinkCN cn
862                 buf += 8;
863               }
864             else if (i2 < 128) /* menu */
865               {
866                 //  VTSM    NONE    MPGC    NOCH
867                 //  VMGM    NONE    MPGC    NOCH
868                 //  VTSM    NONE    MEPGC   NOCH
869                 //  VMGM    NONE    MEPGC   NOCH
870                 if (!curgroup)
871                   {
872                     fprintf(stderr,"ERR:  Cannot jump to menu; none exist\n");
873                     return 0;
874                   }
875                 else if (i2 >= 120 && i2 < 128) /* menu entry */
876                   {
877                     int i;
878                     for (i = 0; i < curgroup->numpgcs; i++)
879                         if (curgroup->pgcs[i]->entries & (1 << (i2 - 120)))
880                           {
881                             i2 = i + 1;
882                             break;
883                           } /*if; for*/
884                     if (i2 >= 120)
885                       {
886                         fprintf(stderr, "ERR:  Cannot find PGC with entry %s\n", entries[i2 - 120]);
887                         return 0;
888                       } /*if*/
889                   }
890                 else /* non-entry menu */
891                   {
892                     if (i2 > curgroup->numpgcs)
893                       {
894                         fprintf(stderr, "ERR:  Cannot jump to menu PGC #%d, only %d exist\n", i2, curgroup->numpgcs);
895                         return 0;
896                       } /*if*/
897                   } /*if*/
898                 if (ismenu == VTYPE_VMGM)
899                   {
900                     // In case we are jumping from FP to VMGM, we need to use a JumpSS
901                     // instruction
902                     write8(buf, 0x30, 0x06, 0x00, i2 & 127, 0x00, 0xc0, 0x00, 0x00); // JumpSS VMGM pgcn
903                   }
904                 else
905                     write8(buf, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, i2 & 127); // LinkPGCN pgcn
906                 buf += 8;
907               }
908             else
909               {
910                 //  VMGM    NONE    TPGC    NOCH
911                 //  VTS     NONE    TPGC    NOCH
912                 //  VTSM    NONE    TPGC    NOCH
913                 //  VTS     NONE    TPGC    CHXX
914                 //  VTSM    NONE    TPGC    CHXX
915                 if (ismenu < VTYPE_VMGM) /* VTS or VTSM */
916                   {
917                     if (i2 - 128 > ws->titles->numpgcs) /* title nr */
918                       {
919                         fprintf
920                           (
921                             stderr,
922                             "ERR:  Cannot jump to title #%d, only %d exist\n",
923                             i2 - 128,
924                             ws->titles->numpgcs
925                           );
926                         return 0;
927                       } /*if*/
928                     if ((cs->i3 & 65535) != 0 && (cs->i3 & 65535) > ws->titles->pgcs[i2 - 128 - 1]->numchapters)
929                       {
930                         fprintf
931                           (
932                             stderr,
933                             "ERR:  Cannot jump to chapter %d of title %d, only %d exist\n",
934                             cs->i3 & 65535,
935                             i2 - 128,
936                             ws->titles->pgcs[i2 - 128 - 1]->numchapters
937                           );
938                         return 0;
939                       } /*if*/
940                   } /*if*/
941                 write8
942                   (
943                     buf,
944                     0x30,
945                     ismenu == VTYPE_VMGM ?
946                         0x02 /*JumpTT*/
947                     : (cs->i3 & 65535) != 0 ?
948                         0x05 /*JumpVTS_PTT*/
949                     :
950                         0x03 /*JumpVTS_TT*/,
951                     0x00,
952                     cs->i3, /* chapter if present */
953                     0x00,
954                     i2 - 128, /* title nr */
955                     0x00,
956                     0x00
957                   );
958                 buf += 8;
959               } /*if*/
960           }
961         break;
962 
963         case VM_CALL:
964           {
965           /* cs->i1 if nonzero is 1 for VMGM, or titleset nr + 1 */
966             int i2 = cs->i2; /* menu number or menu entry ID + 120 or title number + 128 */
967           /* cs->i3 is chapter number if specified, else zero */
968             int i4 = cs->i4; /* resume cell if specified, else zero */
969 
970             // CALL's from <post> MUST have a resume cell
971             if (!i4)
972                 i4 = 1; /* resume from start if not specified */
973             if (ismenu != VTYPE_VTS)
974               {
975                 //  VTSM    NONE    NOPGC   NOCH
976                 //  VMGM    NONE    NOPGC   NOCH
977                 //  VTSM    VMGM    NOPGC   NOCH
978                 //  VMGM    VMGM    NOPGC   NOCH
979                 //  VTSM    TS      NOPGC   NOCH
980                 //  VMGM    TS      NOPGC   NOCH
981                 //  VTSM    NONE    NOPGC   CHXX
982                 //  VMGM    NONE    NOPGC   CHXX
983                 //  VTSM    VMGM    NOPGC   CHXX
984                 //  VMGM    VMGM    NOPGC   CHXX
985                 //  VTSM    TS      NOPGC   CHXX
986                 //  VMGM    TS      NOPGC   CHXX
987                 //  VTSM    NONE    MPGC    NOCH
988                 //  VMGM    NONE    MPGC    NOCH
989                 //  VTSM    VMGM    MPGC    NOCH
990                 //  VMGM    VMGM    MPGC    NOCH
991                 //  VTSM    TS      MPGC    NOCH
992                 //  VMGM    TS      MPGC    NOCH
993                 //  VTSM    NONE    MPGC    CHXX
994                 //  VMGM    NONE    MPGC    CHXX
995                 //  VTSM    VMGM    MPGC    CHXX
996                 //  VMGM    VMGM    MPGC    CHXX
997                 //  VTSM    TS      MPGC    CHXX
998                 //  VMGM    TS      MPGC    CHXX
999                 //  VTSM    NONE    MEPGC   NOCH
1000                 //  VMGM    NONE    MEPGC   NOCH
1001                 //  VTSM    VMGM    MEPGC   NOCH
1002                 //  VMGM    VMGM    MEPGC   NOCH
1003                 //  VTSM    TS      MEPGC   NOCH
1004                 //  VMGM    TS      MEPGC   NOCH
1005                 //  VTSM    NONE    MEPGC   CHXX
1006                 //  VMGM    NONE    MEPGC   CHXX
1007                 //  VTSM    VMGM    MEPGC   CHXX
1008                 //  VMGM    VMGM    MEPGC   CHXX
1009                 //  VTSM    TS      MEPGC   CHXX
1010                 //  VMGM    TS      MEPGC   CHXX
1011                 //  VTSM    NONE    TPGC    NOCH
1012                 //  VMGM    NONE    TPGC    NOCH
1013                 //  VTSM    VMGM    TPGC    NOCH
1014                 //  VMGM    VMGM    TPGC    NOCH
1015                 //  VTSM    TS      TPGC    NOCH
1016                 //  VMGM    TS      TPGC    NOCH
1017                 //  VTSM    NONE    TPGC    CHXX
1018                 //  VMGM    NONE    TPGC    CHXX
1019                 //  VTSM    VMGM    TPGC    CHXX
1020                 //  VMGM    VMGM    TPGC    CHXX
1021                 //  VTSM    TS      TPGC    CHXX
1022                 //  VMGM    TS      TPGC    CHXX
1023                 fprintf(stderr, "ERR:  Cannot 'call' a menu from another menu, use 'jump' instead\n");
1024                 return 0;
1025               } /*if*/
1026             if (i2 == 0 || i2 >= 128) /* title nr or no menu/title */
1027               {
1028                 //  VTS NONE    NOPGC   NOCH
1029                 //  VTS VMGM    NOPGC   NOCH
1030                 //  VTS TS      NOPGC   NOCH
1031                 //  VTS NONE    NOPGC   CHXX
1032                 //  VTS VMGM    NOPGC   CHXX
1033                 //  VTS TS      NOPGC   CHXX
1034                 //  VTS NONE    TPGC    NOCH
1035                 //  VTS VMGM    TPGC    NOCH
1036                 //  VTS TS      TPGC    NOCH
1037                 //  VTS NONE    TPGC    CHXX
1038                 //  VTS VMGM    TPGC    CHXX
1039                 //  VTS TS      TPGC    CHXX
1040 
1041                 fprintf(stderr, "ERR:  Cannot 'call' another title, use 'jump' instead\n");
1042                 return 0;
1043               } /*if*/
1044             if (cs->i3 != 0) /* chapter/cell/program */
1045               {
1046                 //  VTS NONE    MPGC    CHXX
1047                 //  VTS VMGM    MPGC    CHXX
1048                 //  VTS TS      MPGC    CHXX
1049                 //  VTS NONE    MEPGC   CHXX
1050                 //  VTS VMGM    MEPGC   CHXX
1051                 //  VTS TS      MEPGC   CHXX
1052                 fprintf(stderr, "ERR:  Cannot 'call' a chapter within a menu\n");
1053                 return 0;
1054               } /*if*/
1055             if
1056               (
1057                     i2 == 121 /*FPC*/
1058                 &&
1059                     (
1060                         cs->i1 >= 2 /*titleset*/
1061                     ||
1062                         cs->i1 == 0 /*no VMGM/titleset*/ && ismenu != VTYPE_VMGM
1063                     )
1064               )
1065               {
1066                 fprintf(stderr, "ERR:  VMGM must be specified with FPC\n");
1067                 return 0;
1068               } /*if*/
1069 
1070             if (cs->i1 >= 2) /*titleset*/
1071               {
1072                 //  VTS TS  MPGC    NOCH
1073                 //  VTS TS  MEPGC   NOCH
1074                 if (jumppad)
1075                   {
1076                     write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, cs->i1, 0x00, 0x00); buf += 8;
1077                     write8(buf, 0x71, 0x00, 0x00, 0x0E, 0x00, cs->i3, 0x00, 0x00); buf += 8;
1078                     write8(buf, 0x30, 0x08, 0x00, 0x00, i4, 0x42, 0x00, 0x00); buf += 8;
1079                   }
1080                 else
1081                   {
1082                     fprintf(stderr, "ERR:  Cannot call to a menu in another titleset\n");
1083                     return 0;
1084                   } /*if*/
1085               }
1086             else if (cs->i1 == 0 && i2 < 120) /* non-entry menu in current VMGM/titleset */
1087               {
1088                 //  VTS NONE    MPGC    NOCH
1089                 if (jumppad)
1090                   {
1091                     write8(buf, 0x71, 0x00, 0x00, 0x0F, i2, 0x00, 0x00, 0x00); buf += 8;
1092                     write8(buf, 0x30, 0x08, 0x00, 0x00, i4, 0x87, 0x00, 0x00); buf += 8;
1093                   }
1094                 else
1095                   {
1096                     fprintf(stderr, "ERR:  Cannot call to a specific menu PGC, only an entry\n");
1097                     return 0;
1098                   } /*if*/
1099               }
1100             else if (cs->i1 == 1) /* jump to VMGM */
1101               {
1102                 //  VTS VMGM    MPGC    NOCH
1103                 //  VTS VMGM    MEPGC   NOCH
1104                 // we cannot provide error checking when jumping to a VMGM
1105                 if (i2 == 120) /* "default" entry means "title" */
1106                     i2 = 122;
1107                 if (i2 < 120)
1108                     write8(buf, 0x30, 0x08, 0x00, i2, i4, 0xC0, 0x00, 0x00);
1109                 else
1110                     write8(buf, 0x30, 0x08, 0x00, 0x00, i4, i2 == 121 ? 0 : (0x40 + i2 - 120), 0x00, 0x00);
1111                 buf += 8;
1112               }
1113             else
1114               {
1115                 int i, j;
1116                 //  VTS NONE    MEPGC   NOCH
1117                 if (i2 == 120)
1118                     i2 = 127; /* "default" means chapter menu? */
1119                 for (j = 0; j < ws->menus->numgroups; j++)
1120                   {
1121                     const struct pgcgroup * const pg = ws->menus->groups[j].pg;
1122                     for (i = 0; i < pg->numpgcs; i++)
1123                         if (pg->pgcs[i]->entries & (1 << (i2 - 120)))
1124                             goto foundpgc;
1125                   } /*for*/
1126                 fprintf(stderr, "ERR:  Cannot find PGC with entry %s\n", entries[i2 - 120]);
1127                 return 0;
1128             foundpgc:
1129                 write8(buf, 0x30, 0x08, 0x00, 0x00, i4 /*rsm cell*/, 0x80 + i2 - 120 /*VTSM menu*/, 0x00, 0x00);
1130                   /* CallSS VTSM menu, rsm_cell */
1131                 buf += 8;
1132               } /*if*/
1133           }
1134         break;
1135 
1136         case VM_NOP:
1137           /* nothing to do */
1138         break;
1139 
1140         default:
1141             fprintf(stderr,"ERR:  Unsupported VM opcode %d\n",cs->op);
1142             return 0;
1143           } /*switch*/
1144         cs = cs->next;
1145       } /*while*/
1146     if (lastif)
1147       {
1148       /* need target for last branch */
1149         write8(buf, 0, 0, 0, 0, 0, 0, 0, 0); /* NOP */
1150         buf += 8;
1151       } /*if*/
1152     return buf;
1153   } /*compilecs*/
1154 
extractif(const unsigned char * b)1155 static unsigned int extractif(const unsigned char *b)
1156   /* extracts the common bits from a conditional instruction into a format
1157     that can be reapplied to another instruction. */
1158   {
1159     switch (b[0] >> 4)
1160       {
1161     case 0:
1162     case 1:
1163     case 2:
1164         return
1165                 (b[1] >> 4) << 24 /* the comparison op and direct-operand-2 flag */
1166             |
1167                 b[3] << 16 /* operand 1 for the comparison */
1168             |
1169                 b[4] << 8 /* operand 2, high byte */
1170             |
1171                 b[5]; /* operand 2, low byte */
1172 
1173     default:
1174         fprintf(stderr, "ERR:  Unhandled extractif scenario (%x), file bug\n", b[0]);
1175         exit(1);
1176       } /*switch*/
1177   } /*extractif*/
1178 
negateif(unsigned int ifs)1179 static unsigned int negateif(unsigned int ifs)
1180   /* negates the comparison op part of a value returned from extractif. */
1181   {
1182     return
1183             ifs & 0x8ffffff /* remove comparison op, leave direct flag and operands */
1184         |
1185             negatecompare((ifs >> 24) & 7) << 24; /* replace with opposite comparison */
1186   } /*negateif*/
1187 
applyif(unsigned char * b,unsigned int ifs)1188 static void applyif(unsigned char *b,unsigned int ifs)
1189   /* inserts the bits extracted by extractif into a new instruction. */
1190   {
1191     switch (b[0] >> 4)
1192       {
1193     case 0:
1194     case 1:
1195     case 2:
1196         b[5] = ifs;
1197         b[4] = ifs >> 8;
1198         b[3] = ifs >> 16;
1199         b[1] |= (ifs >> 24) << 4;
1200     break;
1201 
1202     case 3:
1203     case 4:
1204     case 5:
1205         b[7] = ifs; /* assume ifs >> 8 & 255 is zero! */
1206         b[6] = ifs >> 16;
1207         b[1] |= (ifs >> 24) << 4;
1208     break;
1209 
1210     case 6:
1211     case 7:
1212         b[7] = ifs;
1213         b[6] = ifs >> 8;
1214         b[2] = ifs >> 16;
1215         b[1] = (ifs >> 24) << 4;
1216     break;
1217 
1218     default:
1219         fprintf(stderr,"ERR:  Unhandled applyif scenario (%x), file bug\n", b[0]);
1220         exit(1);
1221       } /*switch*/
1222   } /*applyif*/
1223 
ifcombinable(unsigned char b0,unsigned char b1,unsigned char b8)1224 static bool ifcombinable(unsigned char b0 /* actually caller always passes 0 */, unsigned char b1, unsigned char b8)
1225   /* can the instruction whose first two bytes are b0 and b1 have its condition
1226     combined with the one whose first byte is b8. */
1227   {
1228     int iftype = -1;
1229     switch (b0 >> 4)
1230       {
1231     case 0:
1232     case 1:
1233     case 2:
1234     case 6:
1235     case 7:
1236         iftype = b1 >> 7; /* 1 if 2nd operand is immediate, 0 if it's a register */
1237     break;
1238     case 3:
1239     case 4:
1240     case 5:
1241         iftype = 0; /* 2nd operand always register */
1242     break;
1243     default:
1244         return false;
1245       } /*switch*/
1246     switch (b8 >> 4)
1247       {
1248     case 0:
1249     case 1:
1250     case 2:
1251     case 6:
1252     case 7:
1253         return true; /* can take both immediate and register 2nd operands */
1254     case 3:
1255     case 4:
1256     case 5:
1257         return iftype == 0; /* can only take register 2nd operand */
1258     default:
1259         return false;
1260       } /*switch*/
1261   } /*ifcombinable*/
1262 
isreferenced(const unsigned char * buf,const unsigned char * end,int linenum)1263 static bool isreferenced(const unsigned char *buf, const unsigned char *end, int linenum)
1264   /* checks if there are any branches with destination linenum. */
1265   {
1266     const unsigned char *b;
1267     bool referenced;
1268     for (b = buf;;)
1269       {
1270         if (b == end)
1271           {
1272             referenced = false;
1273             break;
1274           } /*if*/
1275         if (b[0] == 0 && (b[1] & 15) == 1 && b[7] == linenum)
1276           /* check for goto -- fixme: should also check for SetTmpPML if I ever implement that */
1277           {
1278             referenced = true;
1279             break;
1280           } /*if*/
1281         b += 8;
1282       } /*for*/
1283     return
1284         referenced;
1285   } /*isreferenced*/
1286 
deleteinstruction(const unsigned char * obuf,unsigned char * buf,unsigned char ** end,unsigned char * b)1287 static void deleteinstruction
1288   (
1289     const unsigned char *obuf, /* start of instruction buffer */
1290     unsigned char *buf, /* start of area where branch targets need adjusting */
1291     unsigned char **end, /* pointer to next free part of buffer, to be updated */
1292     unsigned char *b /* instruction to be deleted from buffer */
1293   )
1294   /* deletes an instruction from the buffer, and moves up the following ones,
1295     adjusting branches over the deleted instruction as appropriate. */
1296   {
1297     unsigned char *b2;
1298     const int linenum = (b - obuf) / 8 + 1;
1299     for (b2 = buf; b2 < *end; b2 += 8) /* adjust branches to following instructions */
1300         if (b2[0] == 0 && (b2[1] & 15) == 1 && b2[7] > linenum)
1301             b2[7]--;
1302     memmove(b, b + 8, *end - (b + 8));
1303     *end -= 8;
1304     memset(*end, 0, 8); // clean up tracks (so pgc structure is not polluted)
1305   } /*deleteinstruction*/
1306 
dumpcode(const char * descr,const unsigned char * obuf,unsigned char * buf,const unsigned char * end)1307 static void dumpcode
1308   (
1309     const char * descr,
1310     const unsigned char *obuf, /* start of buffer for computing instruction numbers for branches */
1311     unsigned char *buf, /* where to insert new compiled code */
1312     const unsigned char *end /* points to after last instruction generated */
1313   )
1314   /* dumps out compiled code for debugging. */
1315   {
1316 #ifdef VM_DEBUG
1317     const unsigned int nrlines = (end - buf) / 8;
1318     unsigned int i, j;
1319     fprintf(stderr, "* %s:\n", descr);
1320     for (i = 0; i < nrlines; ++i)
1321       {
1322         fprintf(stderr, " %3d:", i + (buf - obuf) + 1);
1323         for (j = 0; j < 8; ++j)
1324           {
1325             fprintf(stderr, " %02X", buf[i * 8 + j]);
1326           } /*for*/
1327         fputs("\n", stderr);
1328       } /*for*/
1329 #endif
1330   } /*dumpcode*/
1331 
vm_optimize(const unsigned char * obuf,unsigned char * buf,unsigned char ** end)1332 void vm_optimize(const unsigned char *obuf, unsigned char *buf, unsigned char **end)
1333   /* does various peephole optimizations on the part of obuf from buf to *end.
1334     *end will be updated if unnecessary instructions are removed. */
1335   {
1336     unsigned char *b;
1337  again:
1338     for (b = buf; b < *end; b += 8)
1339       {
1340         const int curline = (b - obuf) / 8 + 1;
1341         // if
1342         // 1. this is a jump over one statement
1343         // 2. we can combine the statement with the if
1344         // 3. there are no references to the statement
1345         // then
1346         // combine statement with if, negate if, and replace statement with nop
1347         if
1348           (
1349                 b[0] == 0
1350             &&
1351                 (b[1] & 0x70) != 0 /* conditional */
1352             &&
1353                 (b[1] & 15) == 1 /* cmd = goto */
1354             &&
1355                 b[7] == curline + 2 // step 1
1356             &&
1357                 (b[9] & 0x70) == 0 /* second instr not conditional */
1358             &&
1359                 (
1360                     (b[8] & 15) == 0 /* not a set */
1361                 ||
1362                     (b[9] & 15) == 0 /* not a link */
1363                 ) /* not set-and-link in one */
1364             &&
1365                 ifcombinable(b[0], b[1], b[8]) // step 2
1366             &&
1367                 !isreferenced(buf, *end, curline + 1) // step 3
1368           )
1369           {
1370             const unsigned int ifs = negateif(extractif(b));
1371             memcpy(b, b + 8, 8); // move statement
1372             memset(b + 8, 0, 8); // replace with nop
1373             applyif(b, ifs);
1374             dumpcode("vm_optimize: jump over one => inverse conditional", obuf, buf, *end);
1375             goto again;
1376           } /*if*/
1377         // 1. this is a NOP instruction
1378         // 2. there are more instructions after this OR there are no references here
1379         // then
1380         // delete instruction, fix goto labels
1381         if
1382           (
1383                 b[0] == 0
1384             &&
1385                 b[1] == 0
1386             &&
1387                 b[2] == 0
1388             &&
1389                 b[3] == 0
1390             &&
1391                 b[4] == 0
1392             &&
1393                 b[5] == 0
1394             &&
1395                 b[6] == 0
1396             &&
1397                 b[7] == 0 /* it's a NOP */
1398             &&
1399                 (
1400                     b + 8 != *end /* more instructions after this */
1401                 ||
1402                     !isreferenced(buf, *end, curline) /* no references here */
1403                 )
1404           )
1405           {
1406             deleteinstruction(obuf, buf, end, b);
1407             dumpcode("vm_optimize: remove nop", obuf, buf, *end);
1408             goto again;
1409           } /*if*/
1410         // if
1411         // 1. the prev instruction is an UNCONDITIONAL jump/goto
1412         // 2. there are no references to the statement
1413         // then
1414         // delete instruction, fix goto labels
1415         if
1416           (
1417                 b > buf
1418             &&
1419                 (b[-8] >> 4) <= 3
1420             &&
1421                 (b[-7] & 0x70) == 0
1422             &&
1423                 (b[-7] & 15) != 0 /* previous was unconditional transfer */
1424             &&
1425                 !isreferenced(buf, *end, curline) /* no references here */
1426            /* fixme: should also remove in the case where jump was to this instruction */
1427           )
1428           {
1429           /* remove dead code */
1430             deleteinstruction(obuf, buf, end, b);
1431             dumpcode("vm_optimize: remove dead code", obuf, buf, *end);
1432             goto again;
1433           } /*if*/
1434         // if
1435         // 1. this instruction sets subtitle/angle/audio
1436         // 2. the next instruction sets subtitle/angle/audio
1437         // 3. they both set them the same way (i.e. immediate/indirect)
1438         // 4. there are no references to the second instruction
1439         // then
1440         // combine
1441         if
1442           (
1443                 b + 8 != *end
1444             &&
1445                 (b[0] & 0xEF) == 0x41 /* SetSTN */
1446             &&
1447                 b[1] == 0 // step 1
1448             &&
1449                 b[0] == b[8]
1450             &&
1451                 b[1] == b[9] // step 2 & 3
1452             &&
1453                 !isreferenced(buf, *end, curline + 1)
1454           )
1455           {
1456             if (b[8 + 3])
1457                 b[3] = b[8 + 3];
1458             if (b[8 + 4])
1459                 b[4] = b[8 + 4];
1460             if (b[8 + 5])
1461                 b[5] = b[8 + 5];
1462             deleteinstruction(obuf, buf, end, b + 8);
1463             dumpcode("vm_optimize: merge setting of subtitle/angle/audio", obuf, buf, *end);
1464             goto again;
1465           } /*if*/
1466         // if
1467         // 1. this instruction sets the button directly
1468         // 2. the next instruction is a link command (not NOP, not PGCN)
1469         // 3. there are no references to the second instruction
1470         // then
1471         // combine
1472         if
1473           (
1474                 b + 8 != *end
1475             &&
1476                 b[0] == 0x56
1477             &&
1478                 b[1] == 0x00
1479             &&
1480                 b[8] == 0x20
1481             &&
1482                 (
1483                     (b[8 + 1] & 0xf) == 5
1484                 ||
1485                     (b[8 + 1] & 0xf) == 6
1486                 ||
1487                     (b[8 + 1] & 0xf) == 7
1488                 ||
1489                         (b[8 + 1] & 0xf) == 1
1490                     &&
1491                         (b[8 + 7] & 0x1f) != 0
1492                 )
1493             &&
1494                 !isreferenced(buf, *end, curline + 1)
1495           )
1496           {
1497             if (b[8 + 6] == 0)
1498                 b[8 + 6] = b[4];
1499             deleteinstruction(obuf, buf, end, b);
1500             dumpcode("vm_optimize: merge set button and link", obuf, buf, *end);
1501             goto again;
1502           } /*if*/
1503         // if
1504         // 1. this instruction sets a GPRM/SPRM register
1505         // 2. the next instruction is a link command (not NOP)
1506         // 3. there are no references to the second instruction
1507         // then
1508         // combine
1509         if
1510           (
1511                 b + 8 != *end
1512             &&
1513                 ((b[0] & 0xE0) == 0x40 || (b[0] & 0xE0) == 0x60)
1514             &&
1515                 (b[1] & 0x7f) == 0x00
1516             &&
1517                 b[8] == 0x20
1518             &&
1519                 (
1520                     (b[8 + 1] & 0x7f) == 4
1521                 ||
1522                     (b[8 + 1] & 0x7f) == 5
1523                 ||
1524                     (b[8 + 1] & 0x7f) == 6
1525                 ||
1526                     (b[8 + 1] & 0x7f) == 7
1527                 ||
1528                         (b[8 + 1] & 0x7f) == 1
1529                     &&
1530                         (b[8 + 7] & 0x1f) != 0
1531                 )
1532             &&
1533                 !isreferenced(buf, *end, curline + 1)
1534           )
1535           {
1536             b[1] = b[8 + 1];
1537             b[6] = b[8 + 6];
1538             b[7] = b[8 + 7];
1539             deleteinstruction(obuf, buf, end, b + 8);
1540             dumpcode("vm_optimize: merge set register and link", obuf, buf, *end);
1541             goto again;
1542           } /*if*/
1543       } /*for*/
1544   } /*vm_optimize*/
1545 
vm_compile(const unsigned char * obuf,unsigned char * buf,const struct workset * ws,const struct pgcgroup * curgroup,const struct pgc * curpgc,const struct vm_statement * cs,vtypes ismenu)1546 unsigned char *vm_compile
1547   (
1548     const unsigned char *obuf, /* start of buffer for computing instruction numbers for branches */
1549     unsigned char *buf, /* where to insert new compiled code */
1550     const struct workset *ws,
1551     const struct pgcgroup *curgroup,
1552     const struct pgc *curpgc,
1553     const struct vm_statement *cs,
1554     vtypes ismenu
1555   )
1556   /* compiles the parse tree cs into actual VM instructions with optimization,
1557     and fixes up all the gotos. */
1558   {
1559     unsigned char *end;
1560     int i, j;
1561     numlabels = 0;
1562     numgotos = 0;
1563     end = compilecs(obuf, buf, ws, curgroup, curpgc, cs, ismenu);
1564     if (!end) /* error */
1565         return end;
1566     // fix goto references
1567     for (i = 0; i < numgotos; i++)
1568       {
1569         for (j = 0; j < numlabels; j++)
1570             if (!strcasecmp(gotos[i].lname,labels[j].lname))
1571                 break;
1572         if (j == numlabels)
1573           {
1574             fprintf(stderr, "ERR:  Cannot find label %s\n", gotos[i].lname);
1575             return 0;
1576           } /*if*/
1577         gotos[i].code[7] = (labels[j].code - obuf) / 8 + 1;
1578       } /*for*/
1579     dumpcode("vm_compile: before vm_optimize", obuf, buf, end);
1580     vm_optimize(obuf, buf, &end);
1581     dumpcode("vm_compile: after vm_optimize", obuf, buf, end);
1582     return end;
1583   } /*vm_compile*/
1584 
dvdvmerror(const char * s)1585 void dvdvmerror(const char *s)
1586   /* reports a parse error. */
1587   {
1588     extern char *dvdvmtext;
1589     fprintf(stderr, "ERR:  Parse error '%s' on token '%s'\n", s, dvdvmtext);
1590     exit(1);
1591   } /*dvdvmerror*/
1592 
vm_parse(const char * b)1593 struct vm_statement *vm_parse(const char *b)
1594   /* parses a VM source string and returns the constructed parse tree. */
1595   {
1596     if (b)
1597       {
1598         const char * const cmd = strdup(b);
1599         dvdvm_buffer_state buf = dvdvm_scan_string(cmd);
1600         dvd_vm_parsed_cmd = 0;
1601         if (dvdvmparse())
1602           {
1603             fprintf(stderr, "ERR:  Parser failed on code '%s'.\n", b);
1604             exit(1);
1605           } /*if*/
1606         if (!dvd_vm_parsed_cmd)
1607           {
1608             fprintf(stderr, "ERR:  Nothing parsed from '%s'\n", b);
1609             exit(1);
1610           } /*if*/
1611         dvdvm_delete_buffer(buf);
1612         free((void *)cmd);
1613         return dvd_vm_parsed_cmd;
1614       }
1615     else
1616       {
1617         // pieces of code in dvdauthor rely on a non-null vm_statement
1618         // meaning something significant.  so if we parse an empty string,
1619         // we should return SOMETHING not null.
1620         // also, since the xml parser strips whitespace, we treat a
1621         // NULL string as an empty string
1622         struct vm_statement * const v = statement_new();
1623         v->op = VM_NOP;
1624         return v;
1625       } /*if*/
1626   } /*vm_parse*/
1627