forked from len0rd/rockbox
mikmod: Upgrade mikmod core from v3.2.0 to v3.3.11
* Get rid of the non-functional GT2 loader * Add the UMX loader * Add HQ mixer routines (and make it configurable) * Allow samplerate to be configured at run/playtime * Support >64KHz mixing/playback * Correctly restore non-boost status (The diff to upstream is much smaller now too!) Change-Id: Iaa4ac901ba9cd4123bb225656976e78271353a72
This commit is contained in:
parent
8c7780bafc
commit
b4e70422a3
43 changed files with 5072 additions and 2802 deletions
476
apps/plugins/mikmod/load_umx.c
Normal file
476
apps/plugins/mikmod/load_umx.c
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/* MikMod sound library
|
||||
* (c) 2003-2004 Raphael Assenat and others - see file
|
||||
* AUTHORS for complete list.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/* Epic Games Unreal UMX container loading for libmikmod
|
||||
* Written by O. Sezer <sezero@users.sourceforge.net>
|
||||
*
|
||||
* Records data type/offset info in its Test() function, then acts
|
||||
* as a middle-man, forwarding calls to the real loader units. It
|
||||
* requires that the MREADER implementation in use always respects
|
||||
* its iobase fields. Like all other libmikmod loaders, this code
|
||||
* is not reentrant yet.
|
||||
*
|
||||
* UPKG parsing partially based on Unreal Media Ripper (UMR) v0.3
|
||||
* by Andy Ward <wardwh@swbell.net>, with additional updates
|
||||
* by O. Sezer - see git repo at https://github.com/sezero/umr/
|
||||
*
|
||||
* The cheaper way, i.e. linear search of music object like libxmp
|
||||
* and libmodplug does, is possible. With this however we're using
|
||||
* the embedded offset, size and object type directly from the umx
|
||||
* file, and I feel safer with it.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mikmod_internals.h"
|
||||
|
||||
|
||||
/*========== upkg defs */
|
||||
|
||||
typedef SLONG fci_t; /* FCompactIndex */
|
||||
|
||||
#define UPKG_HDR_TAG 0x9e2a83c1
|
||||
|
||||
struct _genhist { /* for upkg versions >= 68 */
|
||||
SLONG export_count;
|
||||
SLONG name_count;
|
||||
};
|
||||
|
||||
struct upkg_hdr {
|
||||
ULONG tag; /* UPKG_HDR_TAG */
|
||||
SLONG file_version;
|
||||
ULONG pkg_flags;
|
||||
SLONG name_count; /* number of names in name table (>= 0) */
|
||||
SLONG name_offset; /* offset to name table (>= 0) */
|
||||
SLONG export_count; /* num. exports in export table (>= 0) */
|
||||
SLONG export_offset; /* offset to export table (>= 0) */
|
||||
SLONG import_count; /* num. imports in export table (>= 0) */
|
||||
SLONG import_offset; /* offset to import table (>= 0) */
|
||||
|
||||
/* number of GUIDs in heritage table (>= 1) and table's offset:
|
||||
* only with versions < 68. */
|
||||
SLONG heritage_count;
|
||||
SLONG heritage_offset;
|
||||
/* with versions >= 68: a GUID, a dword for generation count
|
||||
* and export_count and name_count dwords for each generation: */
|
||||
ULONG guid[4];
|
||||
SLONG generation_count;
|
||||
#define UPKG_HDR_SIZE 64 /* 64 bytes up until here */
|
||||
/*struct _genhist *gen;*/
|
||||
};
|
||||
/* compile time assert for upkg_hdr size */
|
||||
/*typedef int _check_hdrsize[2 * (offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE) - 1];*/
|
||||
typedef int _check_hdrsize[2 * (sizeof(struct upkg_hdr) == UPKG_HDR_SIZE) - 1];
|
||||
|
||||
/*========== Supported content types */
|
||||
|
||||
#define UMUSIC_IT 0
|
||||
#define UMUSIC_S3M 1
|
||||
#define UMUSIC_XM 2
|
||||
#define UMUSIC_MOD 3
|
||||
|
||||
static const char *mustype[] = {
|
||||
"IT", "S3M", "XM", "MOD",
|
||||
NULL
|
||||
};
|
||||
|
||||
/*========== UPKG parsing */
|
||||
|
||||
/* decode an FCompactIndex.
|
||||
* original documentation by Tim Sweeney was at
|
||||
* http://unreal.epicgames.com/Packages.htm
|
||||
* also see Unreal Wiki:
|
||||
* http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details
|
||||
*/
|
||||
static fci_t get_fci (const char *in, int *pos)
|
||||
{
|
||||
SLONG a;
|
||||
int size;
|
||||
|
||||
size = 1;
|
||||
a = in[0] & 0x3f;
|
||||
|
||||
if (in[0] & 0x40) {
|
||||
size++;
|
||||
a |= (in[1] & 0x7f) << 6;
|
||||
|
||||
if (in[1] & 0x80) {
|
||||
size++;
|
||||
a |= (in[2] & 0x7f) << 13;
|
||||
|
||||
if (in[2] & 0x80) {
|
||||
size++;
|
||||
a |= (in[3] & 0x7f) << 20;
|
||||
|
||||
if (in[3] & 0x80) {
|
||||
size++;
|
||||
a |= (in[4] & 0x3f) << 27;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in[0] & 0x80)
|
||||
a = -a;
|
||||
|
||||
*pos += size;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
static int get_objtype (SLONG ofs, int type)
|
||||
{
|
||||
char sig[16];
|
||||
_retry:
|
||||
_mm_fseek(modreader, ofs, SEEK_SET);
|
||||
_mm_read_UBYTES(sig, 16, modreader);
|
||||
if (type == UMUSIC_IT) {
|
||||
if (memcmp(sig, "IMPM", 4) == 0)
|
||||
return UMUSIC_IT;
|
||||
return -1;
|
||||
}
|
||||
if (type == UMUSIC_XM) {
|
||||
if (memcmp(sig, "Extended Module:", 16) != 0)
|
||||
return -1;
|
||||
_mm_read_UBYTES(sig, 16, modreader);
|
||||
if (sig[0] != ' ') return -1;
|
||||
_mm_read_UBYTES(sig, 16, modreader);
|
||||
if (sig[5] != 0x1a) return -1;
|
||||
return UMUSIC_XM;
|
||||
}
|
||||
|
||||
_mm_fseek(modreader, ofs + 44, SEEK_SET);
|
||||
_mm_read_UBYTES(sig, 4, modreader);
|
||||
if (type == UMUSIC_S3M) {
|
||||
if (memcmp(sig, "SCRM", 4) == 0)
|
||||
return UMUSIC_S3M;
|
||||
/*return -1;*/
|
||||
/* SpaceMarines.umx and Starseek.umx from Return to NaPali
|
||||
* report as "s3m" whereas the actual music format is "it" */
|
||||
type = UMUSIC_IT;
|
||||
goto _retry;
|
||||
}
|
||||
|
||||
_mm_fseek(modreader, ofs + 1080, SEEK_SET);
|
||||
_mm_read_UBYTES(sig, 4, modreader);
|
||||
if (type == UMUSIC_MOD) {
|
||||
if (memcmp(sig, "M.K.", 4) == 0 || memcmp(sig, "M!K!", 4) == 0)
|
||||
return UMUSIC_MOD;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int read_export (const struct upkg_hdr *hdr,
|
||||
SLONG *ofs, SLONG *objsize)
|
||||
{
|
||||
char buf[40];
|
||||
int idx = 0, t;
|
||||
|
||||
_mm_fseek(modreader, *ofs, SEEK_SET);
|
||||
if (!_mm_read_UBYTES(buf, 40, modreader))
|
||||
return -1;
|
||||
|
||||
if (hdr->file_version < 40) idx += 8; /* 00 00 00 00 00 00 00 00 */
|
||||
if (hdr->file_version < 60) idx += 16; /* 81 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 */
|
||||
get_fci(&buf[idx], &idx); /* skip junk */
|
||||
t = get_fci(&buf[idx], &idx); /* type_name */
|
||||
if (hdr->file_version > 61) idx += 4; /* skip export size */
|
||||
*objsize = get_fci(&buf[idx], &idx);
|
||||
*ofs += idx; /* offset for real data */
|
||||
|
||||
return t; /* return type_name index */
|
||||
}
|
||||
|
||||
static int read_typname(const struct upkg_hdr *hdr,
|
||||
int idx, char *out)
|
||||
{
|
||||
int i, s;
|
||||
long l;
|
||||
char buf[64];
|
||||
|
||||
if (idx >= hdr->name_count) return -1;
|
||||
buf[63] = '\0';
|
||||
for (i = 0, l = 0; i <= idx; i++) {
|
||||
_mm_fseek(modreader, hdr->name_offset + l, SEEK_SET);
|
||||
_mm_read_UBYTES(buf, 63, modreader);
|
||||
if (hdr->file_version >= 64) {
|
||||
s = *(signed char *)buf; /* numchars *including* terminator */
|
||||
if (s <= 0 || s > 64) return -1;
|
||||
l += s + 5; /* 1 for buf[0], 4 for int32_t name_flags */
|
||||
} else {
|
||||
l += (long)strlen(buf);
|
||||
l += 5; /* 1 for terminator, 4 for int32_t name_flags */
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(out, (hdr->file_version >= 64)? &buf[1] : buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int probe_umx (const struct upkg_hdr *hdr,
|
||||
SLONG *ofs, SLONG *objsize)
|
||||
{
|
||||
int i, idx, t;
|
||||
SLONG s, pos;
|
||||
long fsiz;
|
||||
char buf[64];
|
||||
|
||||
idx = 0;
|
||||
_mm_fseek(modreader, 0, SEEK_END);
|
||||
fsiz = _mm_ftell(modreader);
|
||||
|
||||
/* Find the offset and size of the first IT, S3M or XM
|
||||
* by parsing the exports table. The umx files should
|
||||
* have only one export. Kran32.umx from Unreal has two,
|
||||
* but both pointing to the same music. */
|
||||
if (hdr->export_offset >= fsiz) return -1;
|
||||
memset(buf, 0, 64);
|
||||
_mm_fseek(modreader, hdr->export_offset, SEEK_SET);
|
||||
_mm_read_UBYTES(buf, 64, modreader);
|
||||
|
||||
get_fci(&buf[idx], &idx); /* skip class_index */
|
||||
get_fci(&buf[idx], &idx); /* skip super_index */
|
||||
if (hdr->file_version >= 60) idx += 4; /* skip int32 package_index */
|
||||
get_fci(&buf[idx], &idx); /* skip object_name */
|
||||
idx += 4; /* skip int32 object_flags */
|
||||
|
||||
s = get_fci(&buf[idx], &idx); /* get serial_size */
|
||||
if (s <= 0) return -1;
|
||||
pos = get_fci(&buf[idx],&idx); /* get serial_offset */
|
||||
if (pos < 0 || pos > fsiz - 40) return -1;
|
||||
|
||||
if ((t = read_export(hdr, &pos, &s)) < 0) return -1;
|
||||
if (s <= 0 || s > fsiz - pos) return -1;
|
||||
|
||||
if (read_typname(hdr, t, buf) < 0) return -1;
|
||||
for (i = 0; mustype[i] != NULL; i++) {
|
||||
if (!strcasecmp(buf, mustype[i])) {
|
||||
t = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mustype[i] == NULL) return -1;
|
||||
if ((t = get_objtype(pos, t)) < 0) return -1;
|
||||
|
||||
*ofs = pos;
|
||||
*objsize = s;
|
||||
return t;
|
||||
}
|
||||
|
||||
static SLONG probe_header (void *header)
|
||||
{
|
||||
struct upkg_hdr *hdr;
|
||||
unsigned char *p;
|
||||
ULONG *swp;
|
||||
int i;
|
||||
|
||||
/* byte swap the header - all members are 32 bit LE values */
|
||||
p = (unsigned char *) header;
|
||||
swp = (ULONG *) header;
|
||||
for (i = 0; i < UPKG_HDR_SIZE/4; i++, p += 4) {
|
||||
swp[i] = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
||||
}
|
||||
|
||||
hdr = (struct upkg_hdr *) header;
|
||||
if (hdr->tag != UPKG_HDR_TAG) {
|
||||
return -1;
|
||||
}
|
||||
if (hdr->name_count < 0 ||
|
||||
hdr->name_offset < 0 ||
|
||||
hdr->export_count < 0 ||
|
||||
hdr->export_offset < 0 ||
|
||||
hdr->import_count < 0 ||
|
||||
hdr->import_offset < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (hdr->file_version) {
|
||||
case 35: case 37: /* Unreal beta - */
|
||||
case 40: case 41: /* 1998 */
|
||||
case 61:/* Unreal */
|
||||
case 62:/* Unreal Tournament */
|
||||
case 63:/* Return to NaPali */
|
||||
case 64:/* Unreal Tournament */
|
||||
case 66:/* Unreal Tournament */
|
||||
case 68:/* Unreal Tournament */
|
||||
case 69:/* Tactical Ops */
|
||||
case 83:/* Mobile Forces */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int process_upkg (SLONG *ofs, SLONG *objsize)
|
||||
{
|
||||
char header[UPKG_HDR_SIZE];
|
||||
|
||||
if (!_mm_read_UBYTES(header, UPKG_HDR_SIZE, modreader))
|
||||
return -1;
|
||||
if (probe_header(header) < 0)
|
||||
return -1;
|
||||
|
||||
return probe_umx((struct upkg_hdr *)header, ofs, objsize);
|
||||
}
|
||||
|
||||
/*========== Loader vars */
|
||||
|
||||
typedef struct _umx_info {
|
||||
int type;
|
||||
SLONG ofs, size;
|
||||
MLOADER* loader;
|
||||
} umx_info;
|
||||
|
||||
static umx_info *umx_data = NULL;
|
||||
|
||||
/*========== Loader code */
|
||||
|
||||
/* Without Test() being called first, Load[Title] is never called.
|
||||
* A Test() is always followed by either a Load() or a LoadTitle().
|
||||
* A Load() is always followed by Cleanup() regardless of success.
|
||||
*
|
||||
* Therefore, in between Test() and LoadTitle() or Load()/Cleanup(),
|
||||
* we must remember the type and the offset of the umx music data,
|
||||
* and always clear it when returning from LoadTitle() or Cleanup().
|
||||
*/
|
||||
|
||||
static int UMX_Test(void)
|
||||
{
|
||||
int type;
|
||||
SLONG ofs = 0, size = 0;
|
||||
|
||||
if (umx_data) {
|
||||
#ifdef MIKMOD_DEBUG
|
||||
fprintf(stderr, "UMX_Test called while a previous instance is active\n");
|
||||
#endif
|
||||
MikMod_free(umx_data);
|
||||
umx_data = NULL;
|
||||
}
|
||||
|
||||
_mm_fseek(modreader, 0, SEEK_SET);
|
||||
type = process_upkg(&ofs, &size);
|
||||
if (type < 0 || type > UMUSIC_MOD)
|
||||
return 0;
|
||||
|
||||
umx_data = (umx_info*) MikMod_calloc(1, sizeof(umx_info));
|
||||
if (!umx_data) return 0;
|
||||
|
||||
umx_data->type = type;
|
||||
umx_data->ofs = ofs;
|
||||
umx_data->size = size;
|
||||
switch (type) {
|
||||
case UMUSIC_IT:
|
||||
umx_data->loader = &load_it;
|
||||
break;
|
||||
case UMUSIC_S3M:
|
||||
umx_data->loader = &load_s3m;
|
||||
break;
|
||||
case UMUSIC_XM:
|
||||
umx_data->loader = &load_xm;
|
||||
break;
|
||||
case UMUSIC_MOD:
|
||||
umx_data->loader = &load_mod;
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int UMX_Init(void)
|
||||
{
|
||||
if (!umx_data || !umx_data->loader)
|
||||
return 0;
|
||||
|
||||
if (umx_data->loader->Init)
|
||||
return umx_data->loader->Init();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void UMX_Cleanup(void)
|
||||
{
|
||||
if (!umx_data) return;
|
||||
|
||||
if (umx_data->loader && umx_data->loader->Cleanup)
|
||||
umx_data->loader->Cleanup();
|
||||
|
||||
MikMod_free(umx_data);
|
||||
umx_data = NULL;
|
||||
}
|
||||
|
||||
static int UMX_Load(int curious)
|
||||
{
|
||||
if (!umx_data || !umx_data->loader)
|
||||
return 0;
|
||||
|
||||
_mm_fseek(modreader, umx_data->ofs, SEEK_SET);
|
||||
/* set reader iobase to the umx object offset */
|
||||
_mm_iobase_revert(modreader);
|
||||
_mm_iobase_setcur(modreader);
|
||||
|
||||
return umx_data->loader->Load(curious);
|
||||
}
|
||||
|
||||
static CHAR *UMX_LoadTitle(void)
|
||||
{
|
||||
CHAR *title;
|
||||
|
||||
if (!umx_data) return NULL;
|
||||
|
||||
if (!umx_data->loader) {
|
||||
title = NULL;
|
||||
}
|
||||
else {
|
||||
_mm_fseek(modreader, umx_data->ofs, SEEK_SET);
|
||||
/* set reader iobase to the umx object offset */
|
||||
_mm_iobase_revert(modreader);
|
||||
_mm_iobase_setcur(modreader);
|
||||
|
||||
title = umx_data->loader->LoadTitle();
|
||||
}
|
||||
|
||||
MikMod_free(umx_data);
|
||||
umx_data = NULL;
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
/*========== Loader information */
|
||||
|
||||
MIKMODAPI MLOADER load_umx = {
|
||||
NULL,
|
||||
"UMX",
|
||||
"UMX (Unreal UMX container)",
|
||||
UMX_Init,
|
||||
UMX_Test,
|
||||
UMX_Load,
|
||||
UMX_Cleanup,
|
||||
UMX_LoadTitle
|
||||
};
|
||||
|
||||
/* ex:set ts=8: */
|
||||
Loading…
Add table
Add a link
Reference in a new issue