ref: 09ef04f7d153b286cea2d5f517c31359cd56aaf4
parent: 2f4495e7dc0bb2547d918b974f56945517cac21e
author: Olav Sørensen <olav.sorensen@live.no>
date: Wed Sep 9 17:22:00 EDT 2020
Support loading XMs with >32 chans and instrs. with >16 samps. ...but discard the extra channels and samples! Also did some other fixes.
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -136,17 +136,18 @@
// these temporarily read to, then copied to real struct if load was OK (should not need to be volatile'd)
static int16_t pattLensTmp[MAX_PATTERNS];
+static uint32_t extraSampleLengths[32-MAX_SMP_PER_INST]; // ModPlug Tracker & OpenMPT supports up to 32 samples per instrument in .XM
static tonTyp *pattTmp[MAX_PATTERNS];
-static instrTyp *instrTmp[1 + MAX_INST];
+static instrTyp *instrTmp[1+MAX_INST];
static songTyp songTmp;
static void setupLoadedModule(void);
static void freeTmpModule(void);
-static bool loadInstrHeader(FILE *f, uint16_t i);
-static bool loadInstrSample(FILE *f, uint16_t i);
+static bool loadInstrHeader(FILE *f, uint16_t i, bool externalThreadFlag);
+static bool loadInstrSample(FILE *f, uint16_t i, bool externalThreadFlag);
void unpackPatt(uint8_t *dst, uint8_t *src, uint16_t len, int32_t antChn);
static bool tmpPatternEmpty(uint16_t nr);
-static bool loadPatterns(FILE *f, uint16_t antPtn);
+static bool loadPatterns(FILE *f, uint16_t antPtn, bool externalThreadFlag);
void checkSampleRepeat(sampleTyp *s);
@@ -160,13 +161,11 @@
if (instrTmp[nr] != NULL)
return false; // already allocated
- p = (instrTyp *)malloc(sizeof (instrTyp));
+ p = (instrTyp *)calloc(1, sizeof (instrTyp));
if (p == NULL)
return false;
- memset(p, 0, sizeof (instrTyp));
-
- for (int8_t i = 0; i < 16; i++) // set standard sample pan/vol
+ for (int8_t i = 0; i < MAX_SMP_PER_INST; i++) // set standard sample pan/vol
{
p->samp[i].pan = 128;
p->samp[i].vol = 64;
@@ -213,7 +212,7 @@
return modFormat;
}
-static bool loadMusicMOD(FILE *f, uint32_t fileLength, bool fromExternalThread)
+static bool loadMusicMOD(FILE *f, uint32_t fileLength, bool externalThreadFlag)
{
char ID[16];
bool mightBeSTK, lateSTKVerFlag, veryLateSTKVerFlag;
@@ -226,7 +225,7 @@
songMOD15HeaderTyp h_MOD15;
int16_t (*showMsg)(int16_t, const char *, const char *);
- showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
veryLateSTKVerFlag = false; // "DFJ SoundTracker III" nad later
lateSTKVerFlag = false; // "TJC SoundTracker II" and later
@@ -318,7 +317,7 @@
}
if (modFormat == FORMAT_MK && songTmp.len == 129)
- songTmp.len = 127; // fixes a specific copy of beatwave.mod
+ songTmp.len = 127; // fixes a specific copy of beatwave.mod (FIXME: Do we want to keep this digsuting hack?)
if (songTmp.antChn == 0 || songTmp.len < 1 || songTmp.len > 128 || (mightBeSTK && songTmp.repS > 220))
{
@@ -735,7 +734,7 @@
return (uint8_t)CLAMP(bpm, 32, 255); // result can be slightly off, but close enough...
}
-static bool loadMusicSTM(FILE *f, uint32_t fileLength, bool fromExternalThread)
+static bool loadMusicSTM(FILE *f, uint32_t fileLength, bool externalThreadFlag)
{
uint8_t typ, tempo;
int16_t i, j, k, ap, tmp;
@@ -745,7 +744,7 @@
songSTMHeaderTyp h_STM;
int16_t (*showMsg)(int16_t, const char *, const char *);
- showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
rewind(f);
@@ -752,12 +751,12 @@
// start loading STM
if (fread(&h_STM, 1, sizeof (h_STM), f) != sizeof (h_STM))
- return loadMusicMOD(f, fileLength, fromExternalThread); // file is not a .stm, try to load as .mod
+ return loadMusicMOD(f, fileLength, externalThreadFlag); // file is not a .stm, try to load as .mod
if (memcmp(h_STM.sig, "!Scream!", 8) && memcmp(h_STM.sig, "BMOD2STM", 8) &&
memcmp(h_STM.sig, "WUZAMOD!", 8) && memcmp(h_STM.sig, "SWavePro", 8))
{
- return loadMusicMOD(f, fileLength, fromExternalThread); // file is not a .stm, try to load as .mod
+ return loadMusicMOD(f, fileLength, externalThreadFlag); // file is not a .stm, try to load as .mod
}
loadedFormat = FORMAT_STM;
@@ -1007,7 +1006,7 @@
return channels;
}
-static bool loadMusicS3M(FILE *f, uint32_t dataLength, bool fromExternalThread)
+static bool loadMusicS3M(FILE *f, uint32_t dataLength, bool externalThreadFlag)
{
int8_t *tmpSmp;
uint8_t ha[2048];
@@ -1022,7 +1021,7 @@
songS3MinstrHeaderTyp h_S3MInstr;
int16_t (*showMsg)(int16_t, const char *, const char *);
- showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
rewind(f);
@@ -1029,10 +1028,10 @@
// start loading S3M
if (fread(&h_S3M, 1, sizeof (h_S3M), f) != sizeof (h_S3M))
- return loadMusicSTM(f, dataLength, fromExternalThread); // not a .s3m, try loading as .stm
+ return loadMusicSTM(f, dataLength, externalThreadFlag); // not a .s3m, try loading as .stm
if (memcmp(h_S3M.id, "SCRM", 4))
- return loadMusicSTM(f, dataLength, fromExternalThread); // not a .s3m, try loading as .stm
+ return loadMusicSTM(f, dataLength, externalThreadFlag); // not a .s3m, try loading as .stm
loadedFormat = FORMAT_S3M;
@@ -1651,7 +1650,7 @@
return false;
}
-bool doLoadMusic(bool fromExternalThread)
+bool doLoadMusic(bool externalThreadFlag)
{
char tmpText[128];
int16_t k;
@@ -1661,7 +1660,7 @@
FILE *f;
int16_t (*showMsg)(int16_t, const char *, const char *);
- showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
linearFreqTable = false;
@@ -1686,10 +1685,10 @@
// start loading
if (fread(&h, 1, sizeof (h), f) != sizeof (h))
- return loadMusicS3M(f, filelength, fromExternalThread); // not a .xm file, try to load as .s3m
+ return loadMusicS3M(f, filelength, externalThreadFlag); // not a .xm file, try to load as .s3m
if (memcmp(h.sig, "Extended Module: ", 17))
- return loadMusicS3M(f, filelength, fromExternalThread); // not a .xm file, try to load as .s3m
+ return loadMusicS3M(f, filelength, externalThreadFlag); // not a .xm file, try to load as .s3m
loadedFormat = FORMAT_XM;
@@ -1700,7 +1699,7 @@
const int32_t major = (h.ver >> 8) & 0x0F;
const int32_t minor = h.ver & 0xFF;
- sprintf(tmpText, "Error loading .xm: Unsupported file version (v%01X.%02X)", major, minor);
+ sprintf(tmpText, "Error loading XM: Unsupported file version (v%01X.%02X).", major, minor);
showMsg(0, "System message", tmpText);
moduleFailedToLoad = true;
@@ -1709,29 +1708,26 @@
if (h.len > MAX_ORDERS)
{
- showMsg(0, "System message", "Error loading .xm: The song has more than 256 orders!");
+ showMsg(0, "System message", "Error loading XM: The song has more than 256 orders!");
goto xmLoadError;
}
if (h.antPtn > MAX_PATTERNS)
{
- showMsg(0, "System message", "Error loading .xm: The song has more than 256 patterns!");
+ showMsg(0, "System message", "Error loading XM: The song has more than 256 patterns!");
goto xmLoadError;
}
- if (h.antChn == 0 || h.antChn > MAX_VOICES)
+ if (h.antChn == 0)
{
- showMsg(0, "System message", "Error loading .xm: Incompatible amount of channels!");
+ showMsg(0, "System message", "Error loading XM: This file is corrupt.");
goto xmLoadError;
}
- if (h.antInstrs > MAX_INST)
- showMsg(0, "System message", "This module has over 128 instruments! Only the first 128 will be loaded.");
-
fseek(f, 60 + h.headerSize, SEEK_SET);
if (filelength != 336 && feof(f)) // 336 in length at this point = empty XM
{
- showMsg(0, "System message", "Error loading .xm: The module is empty!");
+ showMsg(0, "System message", "Error loading XM: The module is empty!");
goto xmLoadError;
}
@@ -1768,62 +1764,81 @@
else
memcpy(songTmp.songTab, h.songTab, songTmp.len);
+ // some strange XMs have the order list padded with 0xFF, remove them!
+ for (int16_t j = 255; j >= 0; j--)
+ {
+ if (songTmp.songTab[j] != 0xFF)
+ break;
+
+ if (songTmp.len > j)
+ songTmp.len = j;
+ }
+
+ // even though XM supports 256 orders, FT2 supports only 255...
+ if (songTmp.len > 0xFF)
+ songTmp.len = 0xFF;
+
if (songTmp.ver < 0x0104)
{
- // old FT2 format
+ // XM v1.02 and XM v1.03
for (i = 1; i <= h.antInstrs; i++)
{
- if (!loadInstrHeader(f, i))
- {
- showMsg(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
+ if (!loadInstrHeader(f, i, externalThreadFlag))
goto xmLoadError;
- }
}
- if (!loadPatterns(f, h.antPtn))
- {
- // error message is shown inside loadPattern()
+ if (!loadPatterns(f, h.antPtn, externalThreadFlag))
goto xmLoadError;
- }
for (i = 1; i <= h.antInstrs; i++)
{
- if (!loadInstrSample(f, i))
- {
- showMsg(0, "System message", "Not enough memory!");
+ if (!loadInstrSample(f, i, externalThreadFlag))
goto xmLoadError;
- }
}
}
else
{
- // current FT2 format
+ // XM v1.04 (latest version)
- if (!loadPatterns(f, h.antPtn))
- {
- // error message is shown inside loadPattern()
+ if (!loadPatterns(f, h.antPtn, externalThreadFlag))
goto xmLoadError;
- }
for (i = 1; i <= h.antInstrs; i++)
{
- if (!loadInstrHeader(f, i))
- {
- showMsg(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
- goto xmLoadError;
- }
-
- if (!loadInstrSample(f, i))
- {
- showMsg(0, "System message", "Not enough memory!");
- goto xmLoadError;
- }
+ if (!loadInstrHeader(f, i, externalThreadFlag)) goto xmLoadError;
+ if (!loadInstrSample(f, i, externalThreadFlag)) goto xmLoadError;
}
}
fclose(f);
+ /* We support loading XMs with up to 32 samples per instrument (ModPlug/OpenMPT),
+ ** but only the first 16 will be loaded. Now make sure we set the number of samples
+ ** back to max 16 in the headers before loading is done.
+ */
+ bool instrHasMoreThan16Samples = false;
+ for (i = 1; i <= MAX_INST; i++)
+ {
+ if (instrTmp[i] != NULL && instrTmp[i]->antSamp > MAX_SMP_PER_INST)
+ {
+ instrHasMoreThan16Samples = true;
+ instrTmp[i]->antSamp = MAX_SMP_PER_INST;
+ }
+ }
+
+ if (songTmp.antChn > MAX_VOICES)
+ {
+ songTmp.antChn = MAX_VOICES;
+ showMsg(0, "System message", "Warning: This XM contains >32 channels. The extra channels will be discarded!");
+ }
+
+ if (h.antInstrs > MAX_INST)
+ showMsg(0, "System message", "Warning: This XM contains >128 instruments. The extra instruments will be discarded!");
+
+ if (instrHasMoreThan16Samples)
+ showMsg(0, "System message", "Warning: This XM contains instrument(s) with >16 samples. The extra samples will be discarded!");
+
moduleLoaded = true;
return true;
@@ -1908,9 +1923,9 @@
return false;
}
-static void freeTmpModule(void)
+static void freeTmpModule(void) // called on module load error
{
- uint16_t i;
+ int32_t i, j;
// free all patterns
for (i = 0; i < MAX_PATTERNS; i++)
@@ -1927,10 +1942,10 @@
{
if (instrTmp[i] != NULL)
{
- for (uint8_t j = 0; j < MAX_SMP_PER_INST; j++)
+ for (j = 0; j < MAX_SMP_PER_INST; j++)
{
- if (instrTmp[i]->samp[j].pek != NULL)
- free(instrTmp[i]->samp[j].pek);
+ if (instrTmp[i]->samp[j].origPek != NULL)
+ free(instrTmp[i]->samp[j].origPek);
}
free(instrTmp[i]);
@@ -1939,7 +1954,7 @@
}
}
-static bool loadInstrHeader(FILE *f, uint16_t i)
+static bool loadInstrHeader(FILE *f, uint16_t i, bool externalThreadFlag)
{
int8_t k;
uint8_t j;
@@ -1948,24 +1963,38 @@
instrTyp *ins;
sampleHeaderTyp *src;
sampleTyp *s;
+ int16_t (*showMsg)(int16_t, const char *, const char *);
- memset(&ih, 0, INSTR_HEADER_SIZE);
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
+ memset(extraSampleLengths, 0, sizeof (extraSampleLengths));
+ memset(&ih, 0, sizeof (ih));
fread(&ih.instrSize, 4, 1, f);
readSize = ih.instrSize;
- if (readSize < 4 || readSize > INSTR_HEADER_SIZE)
+
+ // yes, some XMs can have a header size of 0, and it usually means 263 bytes (INSTR_HEADER_SIZE)
+ if (readSize == 0 || readSize > INSTR_HEADER_SIZE)
readSize = INSTR_HEADER_SIZE;
+ if (readSize < 4)
+ {
+ showMsg(0, "System message", "Error loading XM: This file is corrupt (or not supported)!");
+ return false;
+ }
+
// load instrument data into temp buffer
fread(ih.name, readSize-4, 1, f); // -4 = skip ih.instrSize
// FT2 bugfix: skip instrument header data if instrSize is above INSTR_HEADER_SIZE
if (ih.instrSize > INSTR_HEADER_SIZE)
- fseek(f, ih.instrSize - INSTR_HEADER_SIZE, SEEK_CUR);
+ fseek(f, ih.instrSize-INSTR_HEADER_SIZE, SEEK_CUR);
- if (ih.antSamp > MAX_SMP_PER_INST)
+ if (ih.antSamp < 0 || ih.antSamp > 32)
+ {
+ showMsg(0, "System message", "Error loading XM: This file is corrupt (or not supported)!");
return false;
+ }
if (i <= MAX_INST)
{
@@ -1987,7 +2016,10 @@
if (i <= MAX_INST)
{
if (!allocateTmpInstr(i))
+ {
+ showMsg(0, "System message", "Not enough memory!");
return false;
+ }
// copy instrument header elements to our instrument struct
@@ -2028,8 +2060,8 @@
for (j = 0; j < 96; j++)
{
- if (ins->ta[j] > 15)
- ins->ta[j] = 15;
+ if (ins->ta[j] >= MAX_SMP_PER_INST)
+ ins->ta[j] = MAX_SMP_PER_INST-1;
}
if (ins->envVPAnt > 12) ins->envVPAnt = 12;
@@ -2050,12 +2082,30 @@
}
}
- if (fread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
+ int32_t sampleHeadersToRead = ih.antSamp;
+ if (sampleHeadersToRead > MAX_SMP_PER_INST)
+ sampleHeadersToRead = MAX_SMP_PER_INST;
+
+ if (fread(ih.samp, sampleHeadersToRead * sizeof (sampleHeaderTyp), 1, f) != 1)
+ {
+ showMsg(0, "System message", "General I/O error during loading!");
return false;
+ }
+ // if instrument contains more than 16 sample headers (unsupported), skip them
+ if (ih.antSamp > MAX_SMP_PER_INST) // can only be 0..32 at this point
+ {
+ const int32_t samplesToSkip = ih.antSamp-MAX_SMP_PER_INST;
+ for (j = 0; j < samplesToSkip; j++)
+ {
+ fread(&extraSampleLengths[j], 4, 1, f); // used for skipping data in loadInstrSample()
+ fseek(f, sizeof (sampleHeaderTyp)-4, SEEK_CUR);
+ }
+ }
+
if (i <= MAX_INST)
{
- for (j = 0; j < ih.antSamp; j++)
+ for (j = 0; j < sampleHeadersToRead; j++)
{
s = &instrTmp[i]->samp[j];
src = &ih.samp[j];
@@ -2105,17 +2155,23 @@
if (s->repL == 0) s->typ &= ~3; // non-FT2 fix: force loop off if looplen is 0
}
-static bool loadInstrSample(FILE *f, uint16_t i)
+static bool loadInstrSample(FILE *f, uint16_t i, bool externalThreadFlag)
{
int8_t *newPtr;
uint16_t j, k;
int32_t l, bytesToSkip;
sampleTyp *s;
+ int16_t (*showMsg)(int16_t, const char *, const char *);
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
+
if (i > MAX_INST || instrTmp[i] == NULL)
return true; // yes, let's just pretend they got loaded
k = instrTmp[i]->antSamp;
+ if (k > MAX_SMP_PER_INST)
+ k = MAX_SMP_PER_INST;
+
for (j = 0; j < k; j++)
{
s = &instrTmp[i]->samp[j];
@@ -2147,14 +2203,17 @@
s->pek = NULL;
s->origPek = (int8_t *)malloc(l + LOOP_FIX_LEN);
if (s->origPek == NULL)
+ {
+ showMsg(0, "System message", "Not enough memory!");
return false;
+ }
s->pek = s->origPek + SMP_DAT_OFFSET;
- int32_t bytesRead = (int32_t)fread(s->pek, 1, l, f);
+ const int32_t bytesRead = (int32_t)fread(s->pek, 1, l, f);
if (bytesRead < l)
{
- int32_t bytesToClear = l - bytesRead;
+ const int32_t bytesToClear = l - bytesRead;
memset(&s->pek[bytesRead], 0, bytesToClear);
}
@@ -2192,6 +2251,16 @@
fixSample(s);
}
+ if (instrTmp[i]->antSamp > MAX_SMP_PER_INST)
+ {
+ const int32_t samplesToSkip = instrTmp[i]->antSamp-MAX_SMP_PER_INST;
+ for (i = 0; i < samplesToSkip; i++)
+ {
+ if (extraSampleLengths[i] > 0)
+ fseek(f, extraSampleLengths[i], SEEK_CUR);
+ }
+ }
+
return true;
}
@@ -2198,17 +2267,21 @@
void unpackPatt(uint8_t *dst, uint8_t *src, uint16_t len, int32_t antChn)
{
uint8_t note, data;
- int32_t srcEnd, srcIdx;
+ int32_t srcEnd, srcIdx, j;
if (dst == NULL)
return;
- srcEnd = len * TRACK_WIDTH;
+ srcEnd = len * (sizeof (tonTyp) * antChn);
srcIdx = 0;
+ int32_t numChannels = antChn;
+ if (numChannels > MAX_VOICES)
+ numChannels = MAX_VOICES;
+
for (int32_t i = 0; i < len; i++)
{
- for (int32_t j = 0; j < antChn; j++)
+ for (j = 0; j < numChannels; j++)
{
if (srcIdx >= srcEnd)
return; // error!
@@ -2245,8 +2318,35 @@
srcIdx += sizeof (tonTyp);
}
- // skip unused channels
- dst += sizeof (tonTyp) * (MAX_VOICES - antChn);
+ // if more than 32 channels, skip rest of the channels for this row
+ for (; j < antChn; j++)
+ {
+ if (srcIdx >= srcEnd)
+ return; // error!
+
+ note = *src++;
+ if (note & 0x80)
+ {
+ if (note & 0x01) src++;
+ if (note & 0x02) src++;
+ if (note & 0x04) src++;
+ if (note & 0x08) src++;
+ if (note & 0x10) src++;
+ }
+ else
+ {
+ src++;
+ src++;
+ src++;
+ src++;
+ }
+
+ srcIdx += sizeof (tonTyp);
+ }
+
+ // if song has <32 channels, align pointer to next row (skip unused channels)
+ if (antChn < MAX_VOICES)
+ dst += sizeof (tonTyp) * (MAX_VOICES - antChn);
}
}
@@ -2279,15 +2379,17 @@
memset(&p[(i * MAX_VOICES) + antChn], 0, sizeof (tonTyp) * (MAX_VOICES - antChn));
}
-static bool loadPatterns(FILE *f, uint16_t antPtn)
+static bool loadPatterns(FILE *f, uint16_t antPtn, bool externalThreadFlag)
{
bool pattLenWarn;
uint8_t tmpLen;
uint16_t i;
patternHeaderTyp ph;
+ int16_t (*showMsg)(int16_t, const char *, const char *);
- pattLenWarn = false;
+ showMsg = externalThreadFlag ? okBoxThreadSafe : okBox;
+ pattLenWarn = false;
for (i = 0; i < antPtn; i++)
{
if (fread(&ph.patternHeaderSize, 4, 1, f) != 1)
@@ -2337,7 +2439,7 @@
pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
if (pattTmp[i] == NULL)
{
- okBoxThreadSafe(0, "System message", "Not enough memory!");
+ showMsg(0, "System message", "Not enough memory!");
return false;
}
@@ -2345,7 +2447,6 @@
goto pattCorrupt;
unpackPatt((uint8_t *)pattTmp[i], packedPattData, pattLensTmp[i], songTmp.antChn);
-
clearUnusedChannels(pattTmp[i], pattLensTmp[i], songTmp.antChn);
}
@@ -2362,12 +2463,12 @@
}
if (pattLenWarn)
- okBoxThreadSafe(0, "System message", "This module contains pattern(s) with a length above 256! They will be truncated.");
+ showMsg(0, "System message", "This module contains pattern(s) with a length above 256! They will be truncated.");
return true;
pattCorrupt:
- okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
+ showMsg(0, "System message", "Error loading XM: This file is corrupt!");
return false;
}