1 /* MikMod sound library
2 (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for
3 complete list.
4
5 This library is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of
8 the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA.
19 */
20
21 /*==============================================================================
22
23 $Id$
24
25 UNIMOD (libmikmod's and APlayer's internal module format) loader
26
27 ==============================================================================*/
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <string.h>
34
35 #include "unimod_priv.h"
36
37 /*========== Module structure */
38
39 typedef struct UNIHEADER
40 {
41 CHAR id[4];
42 UBYTE numchn;
43 UWORD numpos;
44 UWORD reppos;
45 UWORD numpat;
46 UWORD numtrk;
47 UWORD numins;
48 UWORD numsmp;
49 UBYTE initspeed;
50 UBYTE inittempo;
51 UBYTE initvolume;
52 UBYTE flags;
53 UBYTE numvoices;
54
55 UBYTE positions[256];
56 UBYTE panning[32];
57 }
58 UNIHEADER;
59
60 typedef struct UNISMP05
61 {
62 UWORD c2spd;
63 UWORD transpose;
64 UBYTE volume;
65 UBYTE panning;
66 ULONG length;
67 ULONG loopstart;
68 ULONG loopend;
69 UWORD flags;
70 CHAR *samplename;
71 UBYTE vibtype;
72 UBYTE vibsweep;
73 UBYTE vibdepth;
74 UBYTE vibrate;
75 }
76 UNISMP05;
77
78 /*========== Loader variables */
79
80 static UWORD universion;
81 static UNIHEADER mh;
82
83 #define UNI_SMPINCR 64
84 static UNISMP05 *wh = NULL, *s = NULL;
85
86 /*========== Loader code */
87
88 static char *
readstring(void)89 readstring (void)
90 {
91 char *s = NULL;
92 UWORD len;
93
94 len = _mm_read_I_UWORD (modreader);
95 if (len)
96 {
97 s = _mm_malloc (len + 1);
98 _mm_read_UBYTES (s, len, modreader);
99 s[len] = 0;
100 }
101 return s;
102 }
103
104 static BOOL
UNI_Test(void)105 UNI_Test (void)
106 {
107 char id[6];
108
109 if (!_mm_read_UBYTES (id, 6, modreader))
110 return 0;
111
112 /* UNIMod created by MikCvt */
113 if (!(memcmp (id, "UN0", 3)))
114 {
115 if ((id[3] >= '4') && (id[3] <= '6'))
116 return 1;
117 }
118 /* UNIMod created by APlayer */
119 if (!(memcmp (id, "APUN\01", 5)))
120 {
121 if ((id[5] >= 1) && (id[5] <= 4))
122 return 1;
123 }
124 return 0;
125 }
126
127 static BOOL
UNI_Init(void)128 UNI_Init (void)
129 {
130 return 1;
131 }
132
133 static void
UNI_Cleanup(void)134 UNI_Cleanup (void)
135 {
136 _mm_free (wh);
137 s = NULL;
138 }
139
140 static UBYTE *
readtrack(void)141 readtrack (void)
142 {
143 UBYTE *t;
144 UWORD len;
145 int cur = 0, chunk;
146
147 if (universion >= 6)
148 len = _mm_read_M_UWORD (modreader);
149 else
150 len = _mm_read_I_UWORD (modreader);
151
152 if (!len)
153 return NULL;
154 if (!(t = _mm_malloc (len)))
155 return NULL;
156 _mm_read_UBYTES (t, len, modreader);
157
158 /* Check if the track is correct */
159 while (1)
160 {
161 chunk = t[cur++];
162 if (!chunk)
163 break;
164 chunk = (chunk & 0x1f) - 1;
165 while (chunk > 0)
166 {
167 int opcode, oplen;
168
169 if (cur >= len)
170 {
171 free (t);
172 return NULL;
173 }
174 opcode = t[cur];
175
176 /* Remap opcodes */
177 if (universion <= 5)
178 {
179 if (opcode > 29)
180 {
181 free (t);
182 return NULL;
183 }
184 switch (opcode)
185 {
186 /* UNI_NOTE .. UNI_S3MEFFECTQ are the same */
187 case 25:
188 opcode = UNI_S3MEFFECTT;
189 break;
190 case 26:
191 opcode = UNI_XMEFFECTA;
192 break;
193 case 27:
194 opcode = UNI_XMEFFECTG;
195 break;
196 case 28:
197 opcode = UNI_XMEFFECTH;
198 break;
199 case 29:
200 opcode = UNI_XMEFFECTP;
201 break;
202 }
203 }
204 else
205 {
206 if (opcode > UNI_ITEFFECTP)
207 {
208 /* APlayer < 1.03 does not have ITEFFECTT */
209 if (universion < 0x103)
210 opcode++;
211 /* APlayer < 1.02 does not have ITEFFECTZ */
212 if ((opcode > UNI_ITEFFECTY) && (universion < 0x102))
213 opcode++;
214 }
215 }
216
217 if ((!opcode) || (opcode >= UNI_LAST))
218 {
219 free (t);
220 return NULL;
221 }
222 oplen = unioperands[opcode] + 1;
223 cur += oplen;
224 chunk -= oplen;
225 }
226 if ((chunk < 0) || (cur >= len))
227 {
228 free (t);
229 return NULL;
230 }
231 }
232 return t;
233 }
234
235 static BOOL
loadsmp6(void)236 loadsmp6 (void)
237 {
238 int t;
239 SAMPLE *s;
240
241 s = of.samples;
242 for (t = 0; t < of.numsmp; t++, s++)
243 {
244 int flags;
245
246 flags = _mm_read_M_UWORD (modreader);
247 s->flags = 0;
248 if (flags & 0x0100)
249 s->flags |= SF_REVERSE;
250 if (flags & 0x0004)
251 s->flags |= SF_STEREO;
252 if (flags & 0x0002)
253 s->flags |= SF_SIGNED;
254 if (flags & 0x0001)
255 s->flags |= SF_16BITS;
256 /* convert flags */
257 if (universion >= 0x102)
258 {
259 if (flags & 0x0800)
260 s->flags |= SF_UST_LOOP;
261 if (flags & 0x0400)
262 s->flags |= SF_OWNPAN;
263 if (flags & 0x0200)
264 s->flags |= SF_SUSTAIN;
265 if (flags & 0x0080)
266 s->flags |= SF_BIDI;
267 if (flags & 0x0040)
268 s->flags |= SF_LOOP;
269 if (flags & 0x0020)
270 s->flags |= SF_ITPACKED;
271 if (flags & 0x0010)
272 s->flags |= SF_DELTA;
273 if (flags & 0x0008)
274 s->flags |= SF_BIG_ENDIAN;
275 }
276 else
277 {
278 if (flags & 0x400)
279 s->flags |= SF_UST_LOOP;
280 if (flags & 0x200)
281 s->flags |= SF_OWNPAN;
282 if (flags & 0x080)
283 s->flags |= SF_SUSTAIN;
284 if (flags & 0x040)
285 s->flags |= SF_BIDI;
286 if (flags & 0x020)
287 s->flags |= SF_LOOP;
288 if (flags & 0x010)
289 s->flags |= SF_BIG_ENDIAN;
290 if (flags & 0x008)
291 s->flags |= SF_DELTA;
292 }
293
294 s->speed = _mm_read_M_ULONG (modreader);
295 s->volume = _mm_read_UBYTE (modreader);
296 s->panning = _mm_read_M_UWORD (modreader);
297 s->length = _mm_read_M_ULONG (modreader);
298 s->loopstart = _mm_read_M_ULONG (modreader);
299 s->loopend = _mm_read_M_ULONG (modreader);
300 s->susbegin = _mm_read_M_ULONG (modreader);
301 s->susend = _mm_read_M_ULONG (modreader);
302 s->globvol = _mm_read_UBYTE (modreader);
303 s->vibflags = _mm_read_UBYTE (modreader);
304 s->vibtype = _mm_read_UBYTE (modreader);
305 s->vibsweep = _mm_read_UBYTE (modreader);
306 s->vibdepth = _mm_read_UBYTE (modreader);
307 s->vibrate = _mm_read_UBYTE (modreader);
308
309 s->samplename = readstring ();
310
311 if (_mm_eof (modreader))
312 {
313 _mm_errno = MMERR_LOADING_SAMPLEINFO;
314 return 0;
315 }
316 }
317 return 1;
318 }
319
320 static BOOL
loadinstr6(void)321 loadinstr6 (void)
322 {
323 int t, w;
324 INSTRUMENT *i;
325
326 i = of.instruments;
327 for (t = 0; t < of.numins; t++, i++)
328 {
329 i->flags = _mm_read_UBYTE (modreader);
330 i->nnatype = _mm_read_UBYTE (modreader);
331 i->dca = _mm_read_UBYTE (modreader);
332 i->dct = _mm_read_UBYTE (modreader);
333 i->globvol = _mm_read_UBYTE (modreader);
334 i->panning = _mm_read_M_UWORD (modreader);
335 i->pitpansep = _mm_read_UBYTE (modreader);
336 i->pitpancenter = _mm_read_UBYTE (modreader);
337 i->rvolvar = _mm_read_UBYTE (modreader);
338 i->rpanvar = _mm_read_UBYTE (modreader);
339 i->volfade = _mm_read_M_UWORD (modreader);
340
341 #define UNI_LoadEnvelope6(name) \
342 i->name##flg=_mm_read_UBYTE(modreader); \
343 i->name##pts=_mm_read_UBYTE(modreader); \
344 i->name##susbeg=_mm_read_UBYTE(modreader); \
345 i->name##susend=_mm_read_UBYTE(modreader); \
346 i->name##beg=_mm_read_UBYTE(modreader); \
347 i->name##end=_mm_read_UBYTE(modreader); \
348 for(w=0;w<(universion>=0x100?32:i->name##pts);w++) { \
349 i->name##env[w].pos=_mm_read_M_SWORD(modreader); \
350 i->name##env[w].val=_mm_read_M_SWORD(modreader); \
351 }
352
353 UNI_LoadEnvelope6 (vol);
354 UNI_LoadEnvelope6 (pan);
355 UNI_LoadEnvelope6 (pit);
356 #undef UNI_LoadEnvelope6
357
358 if (universion == 0x103)
359 _mm_read_M_UWORDS (i->samplenumber, 120, modreader);
360 else
361 for (w = 0; w < 120; w++)
362 i->samplenumber[w] = _mm_read_UBYTE (modreader);
363 _mm_read_UBYTES (i->samplenote, 120, modreader);
364
365 i->insname = readstring ();
366
367 if (_mm_eof (modreader))
368 {
369 _mm_errno = MMERR_LOADING_SAMPLEINFO;
370 return 0;
371 }
372 }
373 return 1;
374 }
375
376 static BOOL
loadinstr5(void)377 loadinstr5 (void)
378 {
379 INSTRUMENT *i;
380 int t;
381 UWORD wavcnt = 0;
382 UBYTE vibtype, vibsweep, vibdepth, vibrate;
383
384 i = of.instruments;
385 for (of.numsmp = t = 0; t < of.numins; t++, i++)
386 {
387 int u, numsmp;
388
389 numsmp = _mm_read_UBYTE (modreader);
390
391 memset (i->samplenumber, 0xff, INSTNOTES * sizeof (UWORD));
392 for (u = 0; u < 96; u++)
393 i->samplenumber[u] = of.numsmp + _mm_read_UBYTE (modreader);
394
395 #define UNI_LoadEnvelope5(name) \
396 i->name##flg=_mm_read_UBYTE(modreader); \
397 i->name##pts=_mm_read_UBYTE(modreader); \
398 i->name##susbeg=_mm_read_UBYTE(modreader); \
399 i->name##susend=i->name##susbeg; \
400 i->name##beg=_mm_read_UBYTE(modreader); \
401 i->name##end=_mm_read_UBYTE(modreader); \
402 for(u=0;u<12;u++) { \
403 i->name##env[u].pos=_mm_read_I_SWORD(modreader); \
404 i->name##env[u].val=_mm_read_I_SWORD(modreader); \
405 }
406
407 UNI_LoadEnvelope5 (vol);
408 UNI_LoadEnvelope5 (pan);
409 #undef UNI_LoadEnvelope5
410
411 vibtype = _mm_read_UBYTE (modreader);
412 vibsweep = _mm_read_UBYTE (modreader);
413 vibdepth = _mm_read_UBYTE (modreader);
414 vibrate = _mm_read_UBYTE (modreader);
415
416 i->volfade = _mm_read_I_UWORD (modreader);
417 i->insname = readstring ();
418
419 for (u = 0; u < numsmp; u++, s++, of.numsmp++)
420 {
421 /* Allocate more room for sample information if necessary */
422 if (of.numsmp + u == wavcnt)
423 {
424 wavcnt += UNI_SMPINCR;
425 if (!(wh = realloc (wh, wavcnt * sizeof (UNISMP05))))
426 {
427 _mm_errno = MMERR_OUT_OF_MEMORY;
428 return 0;
429 }
430 s = wh + (wavcnt - UNI_SMPINCR);
431 }
432
433 s->c2spd = _mm_read_I_UWORD (modreader);
434 s->transpose = _mm_read_SBYTE (modreader);
435 s->volume = _mm_read_UBYTE (modreader);
436 s->panning = _mm_read_UBYTE (modreader);
437 s->length = _mm_read_I_ULONG (modreader);
438 s->loopstart = _mm_read_I_ULONG (modreader);
439 s->loopend = _mm_read_I_ULONG (modreader);
440 s->flags = _mm_read_I_UWORD (modreader);
441 s->samplename = readstring ();
442
443 s->vibtype = vibtype;
444 s->vibsweep = vibsweep;
445 s->vibdepth = vibdepth;
446 s->vibrate = vibrate;
447
448 if (_mm_eof (modreader))
449 {
450 free (wh);
451 wh = NULL;
452 _mm_errno = MMERR_LOADING_SAMPLEINFO;
453 return 0;
454 }
455 }
456 }
457
458 /* sanity check */
459 if (!of.numsmp)
460 {
461 if (wh)
462 {
463 free (wh);
464 wh = NULL;
465 }
466 _mm_errno = MMERR_LOADING_SAMPLEINFO;
467 return 0;
468 }
469 return 1;
470 }
471
472 static BOOL
loadsmp5(void)473 loadsmp5 (void)
474 {
475 int t, u;
476 SAMPLE *q;
477 INSTRUMENT *d;
478
479 q = of.samples;
480 s = wh;
481 for (u = 0; u < of.numsmp; u++, q++, s++)
482 {
483 q->samplename = s->samplename;
484
485 q->length = s->length;
486 q->loopstart = s->loopstart;
487 q->loopend = s->loopend;
488 q->volume = s->volume;
489 q->speed = s->c2spd;
490 q->panning = s->panning;
491 q->vibtype = s->vibtype;
492 q->vibsweep = s->vibsweep;
493 q->vibdepth = s->vibdepth;
494 q->vibrate = s->vibrate;
495
496 /* convert flags */
497 q->flags = 0;
498 if (s->flags & 128)
499 q->flags |= SF_REVERSE;
500 if (s->flags & 64)
501 q->flags |= SF_SUSTAIN;
502 if (s->flags & 32)
503 q->flags |= SF_BIDI;
504 if (s->flags & 16)
505 q->flags |= SF_LOOP;
506 if (s->flags & 8)
507 q->flags |= SF_BIG_ENDIAN;
508 if (s->flags & 4)
509 q->flags |= SF_DELTA;
510 if (s->flags & 2)
511 q->flags |= SF_SIGNED;
512 if (s->flags & 1)
513 q->flags |= SF_16BITS;
514 }
515
516 d = of.instruments;
517 s = wh;
518 for (u = 0; u < of.numins; u++, d++)
519 for (t = 0; t < INSTNOTES; t++)
520 d->samplenote[t] = (d->samplenumber[t] >= of.numsmp) ?
521 255 : (t + s[d->samplenumber[t]].transpose);
522
523 free (wh);
524 wh = NULL;
525
526 return 1;
527 }
528
529 static BOOL
UNI_Load(BOOL curious)530 UNI_Load (BOOL curious)
531 {
532 int t;
533 char *modtype, *oldtype = NULL;
534 INSTRUMENT *d;
535 SAMPLE *q;
536
537 /* read module header */
538 _mm_read_UBYTES (mh.id, 4, modreader);
539 if (mh.id[3] != 'N')
540 universion = mh.id[3] - '0';
541 else
542 universion = 0x100;
543
544 if (universion >= 6)
545 {
546 if (universion == 6)
547 _mm_read_UBYTE (modreader);
548 else
549 universion = _mm_read_M_UWORD (modreader);
550
551 mh.flags = _mm_read_M_UWORD (modreader);
552 mh.numchn = _mm_read_UBYTE (modreader);
553 mh.numvoices = _mm_read_UBYTE (modreader);
554 mh.numpos = _mm_read_M_UWORD (modreader);
555 mh.numpat = _mm_read_M_UWORD (modreader);
556 mh.numtrk = _mm_read_M_UWORD (modreader);
557 mh.numins = _mm_read_M_UWORD (modreader);
558 mh.numsmp = _mm_read_M_UWORD (modreader);
559 mh.reppos = _mm_read_M_UWORD (modreader);
560 mh.initspeed = _mm_read_UBYTE (modreader);
561 mh.inittempo = _mm_read_UBYTE (modreader);
562 mh.initvolume = _mm_read_UBYTE (modreader);
563
564 mh.flags &= (UF_XMPERIODS | UF_LINEAR | UF_INST | UF_NNA);
565 }
566 else
567 {
568 mh.numchn = _mm_read_UBYTE (modreader);
569 mh.numpos = _mm_read_I_UWORD (modreader);
570 mh.reppos = (universion == 5) ? _mm_read_I_UWORD (modreader) : 0;
571 mh.numpat = _mm_read_I_UWORD (modreader);
572 mh.numtrk = _mm_read_I_UWORD (modreader);
573 mh.numins = _mm_read_I_UWORD (modreader);
574 mh.initspeed = _mm_read_UBYTE (modreader);
575 mh.inittempo = _mm_read_UBYTE (modreader);
576 _mm_read_UBYTES (mh.positions, 256, modreader);
577 _mm_read_UBYTES (mh.panning, 32, modreader);
578 mh.flags = _mm_read_UBYTE (modreader);
579
580 mh.flags &= (UF_XMPERIODS | UF_LINEAR);
581 mh.flags |= UF_INST | UF_NOWRAP;
582 }
583
584 /* set module parameters */
585 of.flags = mh.flags;
586 of.numchn = mh.numchn;
587 of.numpos = mh.numpos;
588 of.numpat = mh.numpat;
589 of.numtrk = mh.numtrk;
590 of.numins = mh.numins;
591 of.reppos = mh.reppos;
592 of.initspeed = mh.initspeed;
593 of.inittempo = mh.inittempo;
594
595 of.songname = readstring ();
596 if (universion < 0x102)
597 oldtype = readstring ();
598 if (oldtype)
599 {
600 int len = strlen (oldtype) + 20;
601 if (!(modtype = _mm_malloc (len)))
602 return 0;
603 #ifdef HAVE_SNPRINTF
604 snprintf (modtype, len, "%s (was %s)", (universion >= 0x100) ? "APlayer" : "MikCvt2", oldtype);
605 #else
606 sprintf (modtype, "%s (was %s)", (universion >= 0x100) ? "APlayer" : "MikCvt2", oldtype);
607 #endif
608 }
609 else
610 {
611 if (!(modtype = _mm_malloc (10)))
612 return 0;
613 #ifdef HAVE_SNPRINTF
614 snprintf (modtype, 10, "%s", (universion >= 0x100) ? "APlayer" : "MikCvt3");
615 #else
616 sprintf (modtype, "%s", (universion >= 0x100) ? "APlayer" : "MikCvt3");
617 #endif
618 }
619 of.modtype = strdup (modtype);
620 free (modtype);
621 free (oldtype);
622 of.comment = readstring ();
623
624 if (universion >= 6)
625 {
626 of.numvoices = mh.numvoices;
627 of.initvolume = mh.initvolume;
628 }
629
630 if (_mm_eof (modreader))
631 {
632 _mm_errno = MMERR_LOADING_HEADER;
633 return 0;
634 }
635
636 /* positions */
637 if (!AllocPositions (of.numpos))
638 return 0;
639 if (universion >= 6)
640 {
641 if (universion >= 0x100)
642 _mm_read_M_UWORDS (of.positions, of.numpos, modreader);
643 else
644 for (t = 0; t < of.numpos; t++)
645 of.positions[t] = _mm_read_UBYTE (modreader);
646 _mm_read_M_UWORDS (of.panning, of.numchn, modreader);
647 _mm_read_UBYTES (of.chanvol, of.numchn, modreader);
648 }
649 else
650 {
651 if ((mh.numpos > 256) || (mh.numchn > 32))
652 {
653 _mm_errno = MMERR_LOADING_HEADER;
654 return 0;
655 }
656 for (t = 0; t < of.numpos; t++)
657 of.positions[t] = mh.positions[t];
658 for (t = 0; t < of.numchn; t++)
659 of.panning[t] = mh.panning[t];
660 }
661
662 /* instruments and samples */
663 if (universion >= 6)
664 {
665 of.numsmp = mh.numsmp;
666 if (!AllocSamples ())
667 return 0;
668 if (!loadsmp6 ())
669 return 0;
670
671 if (of.flags & UF_INST)
672 {
673 if (!AllocInstruments ())
674 return 0;
675 if (!loadinstr6 ())
676 return 0;
677 }
678 }
679 else
680 {
681 if (!AllocInstruments ())
682 return 0;
683 if (!loadinstr5 ())
684 return 0;
685 if (!AllocSamples ())
686 {
687 if (wh)
688 {
689 free (wh);
690 wh = NULL;
691 }
692 return 0;
693 }
694 if (!loadsmp5 ())
695 return 0;
696
697 /* check if the original file had no instruments */
698 if (of.numsmp == of.numins)
699 {
700 for (t = 0, d = of.instruments; t < of.numins; t++, d++)
701 {
702 int u;
703
704 if ((d->volpts) || (d->panpts) || (d->globvol != 64))
705 break;
706 for (u = 0; u < 96; u++)
707 if ((d->samplenumber[u] != t) || (d->samplenote[u] != u))
708 break;
709 if (u != 96)
710 break;
711 }
712 if (t == of.numins)
713 {
714 of.flags &= ~UF_INST;
715 of.flags &= ~UF_NOWRAP;
716 for (t = 0, d = of.instruments, q = of.samples; t < of.numins; t++, d++, q++)
717 {
718 q->samplename = d->insname;
719 d->insname = NULL;
720 }
721 }
722 }
723 }
724
725 /* patterns */
726 if (!AllocPatterns ())
727 return 0;
728 if (universion >= 6)
729 {
730 _mm_read_M_UWORDS (of.pattrows, of.numpat, modreader);
731 _mm_read_M_UWORDS (of.patterns, of.numpat * of.numchn, modreader);
732 }
733 else
734 {
735 _mm_read_I_UWORDS (of.pattrows, of.numpat, modreader);
736 _mm_read_I_UWORDS (of.patterns, of.numpat * of.numchn, modreader);
737 }
738
739 /* tracks */
740 if (!AllocTracks ())
741 return 0;
742 for (t = 0; t < of.numtrk; t++)
743 if (!(of.tracks[t] = readtrack ()))
744 {
745 _mm_errno = MMERR_LOADING_TRACK;
746 return 0;
747 }
748
749 return 1;
750 }
751
752 static CHAR *
UNI_LoadTitle(void)753 UNI_LoadTitle (void)
754 {
755 UBYTE ver;
756 int posit[3] =
757 {304, 306, 26};
758
759 _mm_fseek (modreader, 3, SEEK_SET);
760 ver = _mm_read_UBYTE (modreader);
761 if (ver == 'N')
762 ver = '6';
763
764 _mm_fseek (modreader, posit[ver - '4'], SEEK_SET);
765 return readstring ();
766 }
767
768 /*========== Loader information */
769
770 MLOADER load_uni =
771 {
772 NULL,
773 "UNI",
774 "APUN (APlayer) and UNI (MikMod)",
775 UNI_Init,
776 UNI_Test,
777 UNI_Load,
778 UNI_Cleanup,
779 UNI_LoadTitle
780 };
781
782 /* ex:set ts=4: */
783