ref: 4f4c60b34d48ac94175de4939c2fe07e73c348c5
dir: /src/modloaders/pt2_load_mod31.c/
/* 31-sample MOD loader (Amiga, PC, etc.)
**
** Note: Data sanitation is done in the last stage
** of module loading, so you don't need to do that here.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // isdigit()
#include <stdint.h>
#include <stdbool.h>
#include "../pt2_header.h"
#include "../pt2_paula.h" // PAULA_VOICES
#include "../pt2_config.h"
#include "../pt2_structs.h"
#include "../pt2_replayer.h"
#include "../pt2_textout.h"
#include "../pt2_visuals.h"
enum // 31-sample .MOD types
{
FORMAT_UNKNOWN,
FORMAT_PT, // ProTracker or compatible
FORMAT_FLT, // Startrekker (4 channels)
FORMAT_FT2, // FT2 (or other trackers, multichannel)
FORMAT_NT, // NoiseTracker
FORMAT_HMNT // His Master's NoiseTracker (special one)
};
static int32_t realSampleLengths[MOD_SAMPLES];
static uint8_t getMod31Type(uint8_t *buffer, uint32_t filesize, uint8_t *numChannels); // 0 = not detected
bool detectMod31(uint8_t *buffer, uint32_t filesize);
module_t *loadMod31(uint8_t *buffer, uint32_t filesize)
{
module_t *m = createEmptyMod();
if (m == NULL)
{
statusOutOfMemory();
goto loadError;
}
uint8_t numChannels;
uint8_t modFormat = getMod31Type(buffer, filesize, &numChannels);
if (modFormat == FORMAT_UNKNOWN || numChannels > PAULA_VOICES)
{
displayErrorMsg("UNSUPPORTED MOD !");
goto loadError;
}
uint8_t *p = buffer;
memcpy(m->header.name, p, 20); p += 20;
// read sample headers
moduleSample_t *s = m->samples;
for (int32_t i = 0; i < MOD_SAMPLES; i++, s++)
{
memcpy(s->text, p, 22); p += 22;
realSampleLengths[i] = ((p[0] << 8) | p[1]) * 2; p += 2;
s->length = (realSampleLengths[i] > config.maxSampleLength) ? config.maxSampleLength : realSampleLengths[i];
s->fineTune = *p++ & 0xF;
s->volume = *p++;
s->loopStart = ((p[0] << 8) | p[1]) * 2; p += 2;
s->loopLength = ((p[0] << 8) | p[1]) * 2; p += 2;
// fix for poorly converted STK (< v2.5) -> PT/NT modules
if (s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
{
if ((s->loopStart/2) + s->loopLength <= s->length)
s->loopStart /= 2;
}
}
m->header.numOrders = *p++;
p++; // skip uninteresting field (127 in PT MOds, "restart pos" in PC MODs)
if (modFormat == FORMAT_PT && m->header.numOrders == 129)
m->header.numOrders = 127; // fixes a specific copy of beatwave.mod (XXX: hackish...)
if (m->header.numOrders == 0 || m->header.numOrders > 129)
{
displayErrorMsg("NOT A MOD FILE !");
goto loadError;
}
// read orders and count number of patterns
int32_t numPatterns = 0;
for (int32_t i = 0; i < MOD_ORDERS; i++)
{
m->header.order[i] = *p++;
if (m->header.order[i] > numPatterns)
numPatterns = m->header.order[i];
}
numPatterns++;
if (numPatterns > MAX_PATTERNS)
{
displayErrorMsg("UNSUPPORTED MOD !");
goto loadError;
}
p += 4; // skip magic ID (already handled)
// load pattern data
for (int32_t i = 0; i < numPatterns; i++)
{
note_t *note = m->patterns[i];
for (int32_t j = 0; j < MOD_ROWS; j++)
{
for (int32_t k = 0; k < numChannels; k++, note++, p += 4)
{
note->period = ((p[0] & 0x0F) << 8) | p[1];
note->sample = ((p[0] & 0xF0) | (p[2] >> 4)) & 31;
note->command = p[2] & 0x0F;
note->param = p[3];
}
if (numChannels < PAULA_VOICES)
note += PAULA_VOICES-numChannels;
}
}
if (modFormat != FORMAT_PT) // pattern command conversion for non-PT formats
{
for (int32_t i = 0; i < numPatterns; i++)
{
note_t *note = m->patterns[i];
for (int32_t j = 0; j < MOD_ROWS*4; j++, note++)
{
if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
{
// any Dxx == D00 in NT/HMNT
if (note->command == 0xD)
note->param = 0;
// effect F with param 0x00 does nothing in NT/HMNT
if (note->command == 0xF && note->param == 0)
note->command = 0;
}
else if (modFormat == FORMAT_FLT) // Startrekker (4 channels)
{
if (note->command == 0xE) // remove unsupported "assembly macros" command
{
note->command = 0;
note->param = 0;
}
// Startrekker is always in vblank mode, and limits speed to 0x1F
if (note->command == 0xF && note->param > 0x1F)
note->param = 0x1F;
}
// remove E8x (Karplus-Strong is only supported for ProTracker .MODs)
if (note->command == 0xE && (note->param >> 4) == 0x8)
{
note->command = 0;
note->param = 0;
}
// remove EFx (Invert Loop is only supported for ProTracker .MODs)
if (note->command == 0xE && (note->param >> 4) == 0xF)
{
note->command = 0;
note->param = 0;
}
}
}
}
// load sample data
s = m->samples;
for (int32_t i = 0; i < MOD_SAMPLES; i++, s++)
{
/* We don't support loading samples bigger than 65534 bytes in our PT2 clone,
** so skip overflown data in .MOD file if present.
*/
int32_t bytesToSkip = 0;
if (realSampleLengths[i] > config.maxSampleLength)
bytesToSkip = realSampleLengths[i] - config.maxSampleLength;
memcpy(&m->sampleData[s->offset], p, s->length); p += s->length;
if (bytesToSkip > 0)
p += bytesToSkip;
}
m->header.initialTempo = 125;
return m;
loadError:
if (m != NULL)
{
for (int32_t i = 0; i < MAX_PATTERNS; i++)
{
if (m->patterns[i] != NULL)
free(m->patterns[i]);
}
if (m->sampleData != NULL)
free(m->sampleData);
free(m);
}
return NULL;
}
static uint8_t getMod31Type(uint8_t *buffer, uint32_t filesize, uint8_t *numChannels) // 0 = not detected
{
const uint8_t *id = &buffer[1080];
#define ID(s) !memcmp(s, id, 4)
*numChannels = 0;
if (buffer == NULL || filesize < 1084+1024)
return FORMAT_UNKNOWN;
*numChannels = 4;
if (ID("M.K.") || ID("M!K!") || ID("NSMS") || ID("LARD") || ID("PATT"))
{
return FORMAT_PT; // ProTracker (or compatible)
}
else if (ID("FLT4"))
{
return FORMAT_FLT; // Startrekker (4 channels)
}
else if (ID("N.T."))
{
return FORMAT_NT; // NoiseTracker
}
else if (ID("M&K!") || ID("FEST"))
{
return FORMAT_HMNT; // His Master's NoiseTracker
}
else if (isdigit(id[0]) && (id[1] == 'C' && id[2] == 'H' && id[3] == 'N'))
{
*numChannels = id[0] - '0';
return FORMAT_FT2; // Fasttracker II 1..9 channels (or other trackers)
}
else if (isdigit(id[0]) && isdigit(id[1]) && id[1] == 'C' && id[2] == 'H')
{
*numChannels = ((id[0] - '0') * 10) + (id[1] - '0');
return FORMAT_FT2; // Fasttracker II 10+ channels (or other trackers)
}
return FORMAT_UNKNOWN;
}
bool detectMod31(uint8_t *buffer, uint32_t filesize)
{
uint8_t junk;
if (buffer == NULL || filesize < 1084+1024)
return FORMAT_UNKNOWN;
return getMod31Type(buffer, filesize, &junk) != FORMAT_UNKNOWN;
}