diff options
Diffstat (limited to 'apps/plugins/mikmod/load_umx.c')
-rw-r--r-- | apps/plugins/mikmod/load_umx.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/apps/plugins/mikmod/load_umx.c b/apps/plugins/mikmod/load_umx.c new file mode 100644 index 0000000000..1a0535affd --- /dev/null +++ b/apps/plugins/mikmod/load_umx.c | |||
@@ -0,0 +1,476 @@ | |||
1 | /* MikMod sound library | ||
2 | * (c) 2003-2004 Raphael Assenat and others - see file | ||
3 | * AUTHORS for 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 | /* Epic Games Unreal UMX container loading for libmikmod | ||
22 | * Written by O. Sezer <sezero@users.sourceforge.net> | ||
23 | * | ||
24 | * Records data type/offset info in its Test() function, then acts | ||
25 | * as a middle-man, forwarding calls to the real loader units. It | ||
26 | * requires that the MREADER implementation in use always respects | ||
27 | * its iobase fields. Like all other libmikmod loaders, this code | ||
28 | * is not reentrant yet. | ||
29 | * | ||
30 | * UPKG parsing partially based on Unreal Media Ripper (UMR) v0.3 | ||
31 | * by Andy Ward <wardwh@swbell.net>, with additional updates | ||
32 | * by O. Sezer - see git repo at https://github.com/sezero/umr/ | ||
33 | * | ||
34 | * The cheaper way, i.e. linear search of music object like libxmp | ||
35 | * and libmodplug does, is possible. With this however we're using | ||
36 | * the embedded offset, size and object type directly from the umx | ||
37 | * file, and I feel safer with it. | ||
38 | */ | ||
39 | |||
40 | #ifdef HAVE_CONFIG_H | ||
41 | #include "config.h" | ||
42 | #endif | ||
43 | |||
44 | #include <stddef.h> | ||
45 | #include <stdio.h> | ||
46 | #include <string.h> | ||
47 | |||
48 | #include "mikmod_internals.h" | ||
49 | |||
50 | |||
51 | /*========== upkg defs */ | ||
52 | |||
53 | typedef SLONG fci_t; /* FCompactIndex */ | ||
54 | |||
55 | #define UPKG_HDR_TAG 0x9e2a83c1 | ||
56 | |||
57 | struct _genhist { /* for upkg versions >= 68 */ | ||
58 | SLONG export_count; | ||
59 | SLONG name_count; | ||
60 | }; | ||
61 | |||
62 | struct upkg_hdr { | ||
63 | ULONG tag; /* UPKG_HDR_TAG */ | ||
64 | SLONG file_version; | ||
65 | ULONG pkg_flags; | ||
66 | SLONG name_count; /* number of names in name table (>= 0) */ | ||
67 | SLONG name_offset; /* offset to name table (>= 0) */ | ||
68 | SLONG export_count; /* num. exports in export table (>= 0) */ | ||
69 | SLONG export_offset; /* offset to export table (>= 0) */ | ||
70 | SLONG import_count; /* num. imports in export table (>= 0) */ | ||
71 | SLONG import_offset; /* offset to import table (>= 0) */ | ||
72 | |||
73 | /* number of GUIDs in heritage table (>= 1) and table's offset: | ||
74 | * only with versions < 68. */ | ||
75 | SLONG heritage_count; | ||
76 | SLONG heritage_offset; | ||
77 | /* with versions >= 68: a GUID, a dword for generation count | ||
78 | * and export_count and name_count dwords for each generation: */ | ||
79 | ULONG guid[4]; | ||
80 | SLONG generation_count; | ||
81 | #define UPKG_HDR_SIZE 64 /* 64 bytes up until here */ | ||
82 | /*struct _genhist *gen;*/ | ||
83 | }; | ||
84 | /* compile time assert for upkg_hdr size */ | ||
85 | /*typedef int _check_hdrsize[2 * (offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE) - 1];*/ | ||
86 | typedef int _check_hdrsize[2 * (sizeof(struct upkg_hdr) == UPKG_HDR_SIZE) - 1]; | ||
87 | |||
88 | /*========== Supported content types */ | ||
89 | |||
90 | #define UMUSIC_IT 0 | ||
91 | #define UMUSIC_S3M 1 | ||
92 | #define UMUSIC_XM 2 | ||
93 | #define UMUSIC_MOD 3 | ||
94 | |||
95 | static const char *mustype[] = { | ||
96 | "IT", "S3M", "XM", "MOD", | ||
97 | NULL | ||
98 | }; | ||
99 | |||
100 | /*========== UPKG parsing */ | ||
101 | |||
102 | /* decode an FCompactIndex. | ||
103 | * original documentation by Tim Sweeney was at | ||
104 | * http://unreal.epicgames.com/Packages.htm | ||
105 | * also see Unreal Wiki: | ||
106 | * http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details | ||
107 | */ | ||
108 | static fci_t get_fci (const char *in, int *pos) | ||
109 | { | ||
110 | SLONG a; | ||
111 | int size; | ||
112 | |||
113 | size = 1; | ||
114 | a = in[0] & 0x3f; | ||
115 | |||
116 | if (in[0] & 0x40) { | ||
117 | size++; | ||
118 | a |= (in[1] & 0x7f) << 6; | ||
119 | |||
120 | if (in[1] & 0x80) { | ||
121 | size++; | ||
122 | a |= (in[2] & 0x7f) << 13; | ||
123 | |||
124 | if (in[2] & 0x80) { | ||
125 | size++; | ||
126 | a |= (in[3] & 0x7f) << 20; | ||
127 | |||
128 | if (in[3] & 0x80) { | ||
129 | size++; | ||
130 | a |= (in[4] & 0x3f) << 27; | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | |||
136 | if (in[0] & 0x80) | ||
137 | a = -a; | ||
138 | |||
139 | *pos += size; | ||
140 | |||
141 | return a; | ||
142 | } | ||
143 | |||
144 | static int get_objtype (SLONG ofs, int type) | ||
145 | { | ||
146 | char sig[16]; | ||
147 | _retry: | ||
148 | _mm_fseek(modreader, ofs, SEEK_SET); | ||
149 | _mm_read_UBYTES(sig, 16, modreader); | ||
150 | if (type == UMUSIC_IT) { | ||
151 | if (memcmp(sig, "IMPM", 4) == 0) | ||
152 | return UMUSIC_IT; | ||
153 | return -1; | ||
154 | } | ||
155 | if (type == UMUSIC_XM) { | ||
156 | if (memcmp(sig, "Extended Module:", 16) != 0) | ||
157 | return -1; | ||
158 | _mm_read_UBYTES(sig, 16, modreader); | ||
159 | if (sig[0] != ' ') return -1; | ||
160 | _mm_read_UBYTES(sig, 16, modreader); | ||
161 | if (sig[5] != 0x1a) return -1; | ||
162 | return UMUSIC_XM; | ||
163 | } | ||
164 | |||
165 | _mm_fseek(modreader, ofs + 44, SEEK_SET); | ||
166 | _mm_read_UBYTES(sig, 4, modreader); | ||
167 | if (type == UMUSIC_S3M) { | ||
168 | if (memcmp(sig, "SCRM", 4) == 0) | ||
169 | return UMUSIC_S3M; | ||
170 | /*return -1;*/ | ||
171 | /* SpaceMarines.umx and Starseek.umx from Return to NaPali | ||
172 | * report as "s3m" whereas the actual music format is "it" */ | ||
173 | type = UMUSIC_IT; | ||
174 | goto _retry; | ||
175 | } | ||
176 | |||
177 | _mm_fseek(modreader, ofs + 1080, SEEK_SET); | ||
178 | _mm_read_UBYTES(sig, 4, modreader); | ||
179 | if (type == UMUSIC_MOD) { | ||
180 | if (memcmp(sig, "M.K.", 4) == 0 || memcmp(sig, "M!K!", 4) == 0) | ||
181 | return UMUSIC_MOD; | ||
182 | return -1; | ||
183 | } | ||
184 | |||
185 | return -1; | ||
186 | } | ||
187 | |||
188 | static int read_export (const struct upkg_hdr *hdr, | ||
189 | SLONG *ofs, SLONG *objsize) | ||
190 | { | ||
191 | char buf[40]; | ||
192 | int idx = 0, t; | ||
193 | |||
194 | _mm_fseek(modreader, *ofs, SEEK_SET); | ||
195 | if (!_mm_read_UBYTES(buf, 40, modreader)) | ||
196 | return -1; | ||
197 | |||
198 | if (hdr->file_version < 40) idx += 8; /* 00 00 00 00 00 00 00 00 */ | ||
199 | if (hdr->file_version < 60) idx += 16; /* 81 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 */ | ||
200 | get_fci(&buf[idx], &idx); /* skip junk */ | ||
201 | t = get_fci(&buf[idx], &idx); /* type_name */ | ||
202 | if (hdr->file_version > 61) idx += 4; /* skip export size */ | ||
203 | *objsize = get_fci(&buf[idx], &idx); | ||
204 | *ofs += idx; /* offset for real data */ | ||
205 | |||
206 | return t; /* return type_name index */ | ||
207 | } | ||
208 | |||
209 | static int read_typname(const struct upkg_hdr *hdr, | ||
210 | int idx, char *out) | ||
211 | { | ||
212 | int i, s; | ||
213 | long l; | ||
214 | char buf[64]; | ||
215 | |||
216 | if (idx >= hdr->name_count) return -1; | ||
217 | buf[63] = '\0'; | ||
218 | for (i = 0, l = 0; i <= idx; i++) { | ||
219 | _mm_fseek(modreader, hdr->name_offset + l, SEEK_SET); | ||
220 | _mm_read_UBYTES(buf, 63, modreader); | ||
221 | if (hdr->file_version >= 64) { | ||
222 | s = *(signed char *)buf; /* numchars *including* terminator */ | ||
223 | if (s <= 0 || s > 64) return -1; | ||
224 | l += s + 5; /* 1 for buf[0], 4 for int32_t name_flags */ | ||
225 | } else { | ||
226 | l += (long)strlen(buf); | ||
227 | l += 5; /* 1 for terminator, 4 for int32_t name_flags */ | ||
228 | } | ||
229 | } | ||
230 | |||
231 | strcpy(out, (hdr->file_version >= 64)? &buf[1] : buf); | ||
232 | return 0; | ||
233 | } | ||
234 | |||
235 | static int probe_umx (const struct upkg_hdr *hdr, | ||
236 | SLONG *ofs, SLONG *objsize) | ||
237 | { | ||
238 | int i, idx, t; | ||
239 | SLONG s, pos; | ||
240 | long fsiz; | ||
241 | char buf[64]; | ||
242 | |||
243 | idx = 0; | ||
244 | _mm_fseek(modreader, 0, SEEK_END); | ||
245 | fsiz = _mm_ftell(modreader); | ||
246 | |||
247 | /* Find the offset and size of the first IT, S3M or XM | ||
248 | * by parsing the exports table. The umx files should | ||
249 | * have only one export. Kran32.umx from Unreal has two, | ||
250 | * but both pointing to the same music. */ | ||
251 | if (hdr->export_offset >= fsiz) return -1; | ||
252 | memset(buf, 0, 64); | ||
253 | _mm_fseek(modreader, hdr->export_offset, SEEK_SET); | ||
254 | _mm_read_UBYTES(buf, 64, modreader); | ||
255 | |||
256 | get_fci(&buf[idx], &idx); /* skip class_index */ | ||
257 | get_fci(&buf[idx], &idx); /* skip super_index */ | ||
258 | if (hdr->file_version >= 60) idx += 4; /* skip int32 package_index */ | ||
259 | get_fci(&buf[idx], &idx); /* skip object_name */ | ||
260 | idx += 4; /* skip int32 object_flags */ | ||
261 | |||
262 | s = get_fci(&buf[idx], &idx); /* get serial_size */ | ||
263 | if (s <= 0) return -1; | ||
264 | pos = get_fci(&buf[idx],&idx); /* get serial_offset */ | ||
265 | if (pos < 0 || pos > fsiz - 40) return -1; | ||
266 | |||
267 | if ((t = read_export(hdr, &pos, &s)) < 0) return -1; | ||
268 | if (s <= 0 || s > fsiz - pos) return -1; | ||
269 | |||
270 | if (read_typname(hdr, t, buf) < 0) return -1; | ||
271 | for (i = 0; mustype[i] != NULL; i++) { | ||
272 | if (!strcasecmp(buf, mustype[i])) { | ||
273 | t = i; | ||
274 | break; | ||
275 | } | ||
276 | } | ||
277 | if (mustype[i] == NULL) return -1; | ||
278 | if ((t = get_objtype(pos, t)) < 0) return -1; | ||
279 | |||
280 | *ofs = pos; | ||
281 | *objsize = s; | ||
282 | return t; | ||
283 | } | ||
284 | |||
285 | static SLONG probe_header (void *header) | ||
286 | { | ||
287 | struct upkg_hdr *hdr; | ||
288 | unsigned char *p; | ||
289 | ULONG *swp; | ||
290 | int i; | ||
291 | |||
292 | /* byte swap the header - all members are 32 bit LE values */ | ||
293 | p = (unsigned char *) header; | ||
294 | swp = (ULONG *) header; | ||
295 | for (i = 0; i < UPKG_HDR_SIZE/4; i++, p += 4) { | ||
296 | swp[i] = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); | ||
297 | } | ||
298 | |||
299 | hdr = (struct upkg_hdr *) header; | ||
300 | if (hdr->tag != UPKG_HDR_TAG) { | ||
301 | return -1; | ||
302 | } | ||
303 | if (hdr->name_count < 0 || | ||
304 | hdr->name_offset < 0 || | ||
305 | hdr->export_count < 0 || | ||
306 | hdr->export_offset < 0 || | ||
307 | hdr->import_count < 0 || | ||
308 | hdr->import_offset < 0 ) { | ||
309 | return -1; | ||
310 | } | ||
311 | |||
312 | switch (hdr->file_version) { | ||
313 | case 35: case 37: /* Unreal beta - */ | ||
314 | case 40: case 41: /* 1998 */ | ||
315 | case 61:/* Unreal */ | ||
316 | case 62:/* Unreal Tournament */ | ||
317 | case 63:/* Return to NaPali */ | ||
318 | case 64:/* Unreal Tournament */ | ||
319 | case 66:/* Unreal Tournament */ | ||
320 | case 68:/* Unreal Tournament */ | ||
321 | case 69:/* Tactical Ops */ | ||
322 | case 83:/* Mobile Forces */ | ||
323 | return 0; | ||
324 | } | ||
325 | |||
326 | return -1; | ||
327 | } | ||
328 | |||
329 | static int process_upkg (SLONG *ofs, SLONG *objsize) | ||
330 | { | ||
331 | char header[UPKG_HDR_SIZE]; | ||
332 | |||
333 | if (!_mm_read_UBYTES(header, UPKG_HDR_SIZE, modreader)) | ||
334 | return -1; | ||
335 | if (probe_header(header) < 0) | ||
336 | return -1; | ||
337 | |||
338 | return probe_umx((struct upkg_hdr *)header, ofs, objsize); | ||
339 | } | ||
340 | |||
341 | /*========== Loader vars */ | ||
342 | |||
343 | typedef struct _umx_info { | ||
344 | int type; | ||
345 | SLONG ofs, size; | ||
346 | MLOADER* loader; | ||
347 | } umx_info; | ||
348 | |||
349 | static umx_info *umx_data = NULL; | ||
350 | |||
351 | /*========== Loader code */ | ||
352 | |||
353 | /* Without Test() being called first, Load[Title] is never called. | ||
354 | * A Test() is always followed by either a Load() or a LoadTitle(). | ||
355 | * A Load() is always followed by Cleanup() regardless of success. | ||
356 | * | ||
357 | * Therefore, in between Test() and LoadTitle() or Load()/Cleanup(), | ||
358 | * we must remember the type and the offset of the umx music data, | ||
359 | * and always clear it when returning from LoadTitle() or Cleanup(). | ||
360 | */ | ||
361 | |||
362 | static int UMX_Test(void) | ||
363 | { | ||
364 | int type; | ||
365 | SLONG ofs = 0, size = 0; | ||
366 | |||
367 | if (umx_data) { | ||
368 | #ifdef MIKMOD_DEBUG | ||
369 | fprintf(stderr, "UMX_Test called while a previous instance is active\n"); | ||
370 | #endif | ||
371 | MikMod_free(umx_data); | ||
372 | umx_data = NULL; | ||
373 | } | ||
374 | |||
375 | _mm_fseek(modreader, 0, SEEK_SET); | ||
376 | type = process_upkg(&ofs, &size); | ||
377 | if (type < 0 || type > UMUSIC_MOD) | ||
378 | return 0; | ||
379 | |||
380 | umx_data = (umx_info*) MikMod_calloc(1, sizeof(umx_info)); | ||
381 | if (!umx_data) return 0; | ||
382 | |||
383 | umx_data->type = type; | ||
384 | umx_data->ofs = ofs; | ||
385 | umx_data->size = size; | ||
386 | switch (type) { | ||
387 | case UMUSIC_IT: | ||
388 | umx_data->loader = &load_it; | ||
389 | break; | ||
390 | case UMUSIC_S3M: | ||
391 | umx_data->loader = &load_s3m; | ||
392 | break; | ||
393 | case UMUSIC_XM: | ||
394 | umx_data->loader = &load_xm; | ||
395 | break; | ||
396 | case UMUSIC_MOD: | ||
397 | umx_data->loader = &load_mod; | ||
398 | break; | ||
399 | } | ||
400 | |||
401 | return 1; | ||
402 | } | ||
403 | |||
404 | static int UMX_Init(void) | ||
405 | { | ||
406 | if (!umx_data || !umx_data->loader) | ||
407 | return 0; | ||
408 | |||
409 | if (umx_data->loader->Init) | ||
410 | return umx_data->loader->Init(); | ||
411 | |||
412 | return 1; | ||
413 | } | ||
414 | |||
415 | static void UMX_Cleanup(void) | ||
416 | { | ||
417 | if (!umx_data) return; | ||
418 | |||
419 | if (umx_data->loader && umx_data->loader->Cleanup) | ||
420 | umx_data->loader->Cleanup(); | ||
421 | |||
422 | MikMod_free(umx_data); | ||
423 | umx_data = NULL; | ||
424 | } | ||
425 | |||
426 | static int UMX_Load(int curious) | ||
427 | { | ||
428 | if (!umx_data || !umx_data->loader) | ||
429 | return 0; | ||
430 | |||
431 | _mm_fseek(modreader, umx_data->ofs, SEEK_SET); | ||
432 | /* set reader iobase to the umx object offset */ | ||
433 | _mm_iobase_revert(modreader); | ||
434 | _mm_iobase_setcur(modreader); | ||
435 | |||
436 | return umx_data->loader->Load(curious); | ||
437 | } | ||
438 | |||
439 | static CHAR *UMX_LoadTitle(void) | ||
440 | { | ||
441 | CHAR *title; | ||
442 | |||
443 | if (!umx_data) return NULL; | ||
444 | |||
445 | if (!umx_data->loader) { | ||
446 | title = NULL; | ||
447 | } | ||
448 | else { | ||
449 | _mm_fseek(modreader, umx_data->ofs, SEEK_SET); | ||
450 | /* set reader iobase to the umx object offset */ | ||
451 | _mm_iobase_revert(modreader); | ||
452 | _mm_iobase_setcur(modreader); | ||
453 | |||
454 | title = umx_data->loader->LoadTitle(); | ||
455 | } | ||
456 | |||
457 | MikMod_free(umx_data); | ||
458 | umx_data = NULL; | ||
459 | |||
460 | return title; | ||
461 | } | ||
462 | |||
463 | /*========== Loader information */ | ||
464 | |||
465 | MIKMODAPI MLOADER load_umx = { | ||
466 | NULL, | ||
467 | "UMX", | ||
468 | "UMX (Unreal UMX container)", | ||
469 | UMX_Init, | ||
470 | UMX_Test, | ||
471 | UMX_Load, | ||
472 | UMX_Cleanup, | ||
473 | UMX_LoadTitle | ||
474 | }; | ||
475 | |||
476 | /* ex:set ts=8: */ | ||