OSDN Git Service

Add VC++ project files.
[timidity41/timidity41.git] / libunimod / load_uni.c
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 *
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 BOOL 
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 BOOL 
128 UNI_Init (void)
129 {
130   return 1;
131 }
132
133 void 
134 UNI_Cleanup (void)
135 {
136   _mm_free (wh);
137   s = NULL;
138 }
139
140 static UBYTE *
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 
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 
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 
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 
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 BOOL 
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 CHAR *
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: */