ref: 5dca43d1a307ba6d2288c4be9ff7d3f705aaabc4
dir: /src/modloaders/ft2_load_s3m.c/
/* Scream Tracker 3 (or compatible) S3M loader
**
** 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 <stdint.h>
#include <stdbool.h>
#include "../ft2_header.h"
#include "../ft2_module_loader.h"
#include "../ft2_sample_ed.h"
#include "../ft2_tables.h"
#include "../ft2_sysreqs.h"
#ifdef _MSC_VER // please don't mess with these structs!
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct s3mSmpHdr_t
{
uint8_t type, junk1[12], offsetInFileH;
uint16_t offsetInFile;
int32_t length, loopStart, loopEnd;
uint8_t volume, junk2, packFlag, flags;
int32_t midCFreq, junk3;
uint16_t junk4;
uint8_t junk5[6];
char name[28], ID[4];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
s3mSmpHdr_t;
typedef struct s3mHdr_t
{
char name[28];
uint8_t junk1, type;
uint16_t junk2;
int16_t numOrders, numSamples, numPatterns;
uint16_t flags, junk3, version;
char ID[4];
uint8_t junk4, speed, BPM, junk5, junk6[12], chnSettings[32];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
s3mHdr_t;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
static uint8_t pattBuff[12288];
static int8_t countS3MChannels(uint16_t antPtn);
bool loadS3M(FILE *f, uint32_t filesize)
{
uint8_t alastnfo[32], alastefx[32], alastvibnfo[32], s3mLastGInstr[32];
int16_t ii, kk, tmp;
int32_t patternOffsets[256], sampleOffsets[256], j, k;
note_t tmpNote;
sample_t *s;
s3mHdr_t hdr;
s3mSmpHdr_t smpHdr;
tmpLinearPeriodsFlag = false; // use Amiga periods
if (filesize < sizeof (hdr))
{
loaderMsgBox("Error: This file is either not a module, or is not supported.");
return false;
}
memset(&hdr, 0, sizeof (hdr));
if (fread(&hdr, 1, sizeof (hdr), f) != sizeof (hdr))
{
loaderMsgBox("Error: This file is either not a module, or is not supported.");
return false;
}
if (hdr.numSamples > MAX_INST || hdr.numOrders > MAX_ORDERS || hdr.numPatterns > MAX_PATTERNS ||
hdr.type != 16 || hdr.version < 1 || hdr.version > 2)
{
loaderMsgBox("Error loading .s3m: Incompatible module!");
return false;
}
memset(songTmp.orders, 255, 256); // pad by 255
if (fread(songTmp.orders, hdr.numOrders, 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
songTmp.songLength = hdr.numOrders;
// remove pattern separators (254)
k = 0;
j = 0;
for (int32_t i = 0; i < songTmp.songLength; i++)
{
if (songTmp.orders[i] != 254)
songTmp.orders[j++] = songTmp.orders[i];
else
k++;
}
if (k <= songTmp.songLength)
songTmp.songLength -= (uint16_t)k;
else
songTmp.songLength = 1;
for (int32_t i = 1; i < songTmp.songLength; i++)
{
if (songTmp.orders[i] == 255)
{
songTmp.songLength = (uint16_t)i;
break;
}
}
// clear unused song table entries
if (songTmp.songLength < 255)
memset(&songTmp.orders[songTmp.songLength], 0, 255-songTmp.songLength);
memcpy(songTmp.name, hdr.name, 20);
songTmp.BPM = hdr.BPM;
songTmp.speed = hdr.speed;
// load sample offsets
for (int32_t i = 0; i < hdr.numSamples; i++)
{
uint16_t offset;
if (fread(&offset, 2, 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
sampleOffsets[i] = offset << 4;
}
// load pattern offsets
for (int32_t i = 0; i < hdr.numPatterns; i++)
{
uint16_t offset;
if (fread(&offset, 2, 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
patternOffsets[i] = offset << 4;
}
// *** PATTERNS ***
k = 0;
for (int32_t i = 0; i < hdr.numPatterns; i++)
{
if (patternOffsets[i] == 0)
continue; // empty pattern
memset(alastnfo, 0, sizeof (alastnfo));
memset(alastefx, 0, sizeof (alastefx));
memset(alastvibnfo, 0, sizeof (alastvibnfo));
memset(s3mLastGInstr, 0, sizeof (s3mLastGInstr));
fseek(f, patternOffsets[i], SEEK_SET);
if (feof(f))
continue;
if (fread(&j, 2, 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
if (j > 0 && j <= 12288)
{
if (!allocateTmpPatt(i, 64))
{
loaderMsgBox("Not enough memory!");
return false;
}
fread(pattBuff, j, 1, f);
k = 0;
kk = 0;
while (k < j && kk < 64)
{
uint8_t bits = pattBuff[k++];
if (bits == 0)
{
kk++;
}
else
{
ii = bits & 31;
memset(&tmpNote, 0, sizeof (tmpNote));
// note and sample
if (bits & 32)
{
tmpNote.note = pattBuff[k++];
tmpNote.instr = pattBuff[k++];
if (tmpNote.instr > MAX_INST)
tmpNote.instr = 0;
if (tmpNote.note == 254) tmpNote.note = NOTE_OFF;
else if (tmpNote.note == 255) tmpNote.note = 0;
else
{
tmpNote.note = 1 + (tmpNote.note & 0xF) + (tmpNote.note >> 4) * 12;
if (tmpNote.note > 96)
tmpNote.note = 0;
}
}
// volume column
if (bits & 64)
{
tmpNote.vol = pattBuff[k++];
if (tmpNote.vol <= 64)
tmpNote.vol += 0x10;
else
tmpNote.vol = 0;
}
// effect
if (bits & 128)
{
tmpNote.efx = pattBuff[k++];
tmpNote.efxData = pattBuff[k++];
if (tmpNote.efxData > 0)
{
alastnfo[ii] = tmpNote.efxData;
if (tmpNote.efx == 8 || tmpNote.efx == 21)
alastvibnfo[ii] = tmpNote.efxData; // H/U
}
// in ST3, a lot of effects directly share the same memory!
if (tmpNote.efxData == 0 && tmpNote.efx != 7) // G
{
uint8_t efx = tmpNote.efx;
if (efx == 8 || efx == 21) // H/U
tmpNote.efxData = alastvibnfo[ii];
else if ((efx >= 4 && efx <= 12) || (efx >= 17 && efx <= 19)) // D/E/F/I/J/K/L/Q/R/S
tmpNote.efxData = alastnfo[ii];
/* If effect data is zero and effect type was the same as last one, clear out
** data if it's not J or S (those have no memory in the equivalent XM effects).
** Also goes for extra fine pitch slides and fine volume slides,
** since they get converted to other effects.
*/
if (efx == alastefx[ii] && tmpNote.efx != 10 && tmpNote.efx != 19) // J/S
{
uint8_t nfo = tmpNote.efxData;
bool extraFinePitchSlides = (efx == 5 || efx == 6) && ((nfo & 0xF0) == 0xE0);
bool fineVolSlides = (efx == 4 || efx == 11) &&
((nfo > 0xF0) || (((nfo & 0xF) == 0xF) && ((nfo & 0xF0) > 0)));
if (!extraFinePitchSlides && !fineVolSlides)
tmpNote.efxData = 0;
}
}
if (tmpNote.efx > 0)
alastefx[ii] = tmpNote.efx;
switch (tmpNote.efx)
{
case 1: // A
{
tmpNote.efx = 0xF;
if (tmpNote.efxData == 0)
{
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
else if (tmpNote.efxData > 0x1F)
{
tmpNote.efxData = 0x1F;
}
}
break;
case 2: tmpNote.efx = 0xB; break; // B
case 3: tmpNote.efx = 0xD; break; // C
case 4: // D
{
if (tmpNote.efxData > 0xF0) // fine slide up
{
tmpNote.efx = 0xE;
tmpNote.efxData = 0xB0 | (tmpNote.efxData & 0xF);
}
else if ((tmpNote.efxData & 0x0F) == 0x0F && (tmpNote.efxData & 0xF0) > 0) // fine slide down
{
tmpNote.efx = 0xE;
tmpNote.efxData = 0xA0 | (tmpNote.efxData >> 4);
}
else
{
tmpNote.efx = 0xA;
if (tmpNote.efxData & 0x0F) // on D/K, last nybble has first priority in ST3
tmpNote.efxData &= 0x0F;
}
}
break;
case 5: // E
case 6: // F
{
if ((tmpNote.efxData & 0xF0) >= 0xE0)
{
// convert to fine slide
if ((tmpNote.efxData & 0xF0) == 0xE0)
tmp = 0x21;
else
tmp = 0xE;
tmpNote.efxData &= 0x0F;
if (tmpNote.efx == 0x05)
tmpNote.efxData |= 0x20;
else
tmpNote.efxData |= 0x10;
tmpNote.efx = (uint8_t)tmp;
if (tmpNote.efx == 0x21 && tmpNote.efxData == 0)
{
tmpNote.efx = 0;
}
}
else
{
// convert to normal 1xx/2xx slide
tmpNote.efx = 7 - tmpNote.efx;
}
}
break;
case 7: // G
{
tmpNote.efx = 0x03;
// fix illegal slides (to new instruments)
if (tmpNote.instr != 0 && tmpNote.instr != s3mLastGInstr[ii])
tmpNote.instr = s3mLastGInstr[ii];
}
break;
case 11: // K
{
if (tmpNote.efxData > 0xF0) // fine slide up
{
tmpNote.efx = 0xE;
tmpNote.efxData = 0xB0 | (tmpNote.efxData & 0xF);
// if volume column is unoccupied, set to vibrato
if (tmpNote.vol == 0)
tmpNote.vol = 0xB0;
}
else if ((tmpNote.efxData & 0x0F) == 0x0F && (tmpNote.efxData & 0xF0) > 0) // fine slide down
{
tmpNote.efx = 0xE;
tmpNote.efxData = 0xA0 | (tmpNote.efxData >> 4);
// if volume column is unoccupied, set to vibrato
if (tmpNote.vol == 0)
tmpNote.vol = 0xB0;
}
else
{
tmpNote.efx = 0x6;
if (tmpNote.efxData & 0x0F) // on D/K, last nybble has first priority in ST3
tmpNote.efxData &= 0x0F;
}
}
break;
case 8: tmpNote.efx = 0x04; break; // H
case 9: tmpNote.efx = 0x1D; break; // I
case 10: tmpNote.efx = 0x00; break; // J
case 12: tmpNote.efx = 0x05; break; // L
case 15: tmpNote.efx = 0x09; break; // O
case 17: tmpNote.efx = 0x1B; break; // Q
case 18: tmpNote.efx = 0x07; break; // R
case 19: // S
{
tmpNote.efx = 0xE;
tmp = tmpNote.efxData >> 4;
tmpNote.efxData &= 0x0F;
if (tmp == 0x1) tmpNote.efxData |= 0x30;
else if (tmp == 0x2) tmpNote.efxData |= 0x50;
else if (tmp == 0x3) tmpNote.efxData |= 0x40;
else if (tmp == 0x4) tmpNote.efxData |= 0x70;
// ignore S8x becuase it's not compatible with FT2 panning
else if (tmp == 0xB) tmpNote.efxData |= 0x60;
else if (tmp == 0xC) // Note Cut
{
tmpNote.efxData |= 0xC0;
if (tmpNote.efxData == 0xC0)
{
// EC0 does nothing in ST3 but cuts voice in FT2, remove effect
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
}
else if (tmp == 0xD) // Note Delay
{
tmpNote.efxData |= 0xD0;
if (tmpNote.note == 0 || tmpNote.note == NOTE_OFF)
{
// EDx without a note does nothing in ST3 but retrigs in FT2, remove effect
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
else if (tmpNote.efxData == 0xD0)
{
// ED0 prevents note/smp/vol from updating in ST3, remove everything
tmpNote.note = 0;
tmpNote.instr = 0;
tmpNote.vol = 0;
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
}
else if (tmp == 0xE) tmpNote.efxData |= 0xE0;
else if (tmp == 0xF) tmpNote.efxData |= 0xF0;
else
{
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
}
break;
case 20: // T
{
tmpNote.efx = 0x0F;
if (tmpNote.efxData < 0x21) // Txx with a value lower than 33 (0x21) does nothing in ST3, remove effect
{
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
}
break;
case 22: // V
{
tmpNote.efx = 0x10;
if (tmpNote.efxData > 0x40)
{
// Vxx > 0x40 does nothing in ST3
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
}
break;
default:
{
tmpNote.efx = 0;
tmpNote.efxData = 0;
}
break;
}
}
if (tmpNote.instr != 0 && tmpNote.efx != 0x3)
s3mLastGInstr[ii] = tmpNote.instr;
patternTmp[i][(kk * MAX_CHANNELS) + ii] = tmpNote;
}
}
if (tmpPatternEmpty((uint16_t)i))
{
if (patternTmp[i] != NULL)
{
free(patternTmp[i]);
patternTmp[i] = NULL;
}
}
}
}
// *** SAMPLES ***
bool adlibInsWarn = false;
for (int32_t i = 0; i < hdr.numSamples; i++)
{
if (sampleOffsets[i] == 0)
continue;
fseek(f, sampleOffsets[i], SEEK_SET);
if (fread(&smpHdr, 1, sizeof (smpHdr), f) != sizeof (smpHdr))
{
loaderMsgBox("Not enough memory!");
return false;
}
memcpy(songTmp.instrName[1+i], smpHdr.name, 22);
if (smpHdr.type == 2)
{
adlibInsWarn = true;
}
else if (smpHdr.type == 1)
{
int32_t offsetInFile = ((smpHdr.offsetInFileH << 16) | smpHdr.offsetInFile) << 4;
if ((smpHdr.flags & (255-1-2-4)) != 0 || smpHdr.packFlag != 0)
{
loaderMsgBox("Error loading .s3m: Incompatible module!");
return false;
}
else if (offsetInFile > 0 && smpHdr.length > 0)
{
if (!allocateTmpInstr((int16_t)(1 + i)))
{
loaderMsgBox("Not enough memory!");
return false;
}
setNoEnvelope(instrTmp[1 + i]);
s = &instrTmp[1+i]->smp[0];
if (smpHdr.midCFreq > 65535) // ST3 (and OpenMPT) does this
smpHdr.midCFreq = 65535;
memcpy(s->name, smpHdr.name, 22);
// non-FT2: fixes "miracle man.s3m" and other broken S3Ms
if (offsetInFile+smpHdr.length > (int32_t)filesize)
smpHdr.length = filesize - offsetInFile;
bool hasLoop = !!(smpHdr.flags & 1);
bool stereoSample = !!(smpHdr.flags & 2);
bool sample16Bit = !!(smpHdr.flags & 4);
if (stereoSample)
smpHdr.length <<= 1;
int32_t lengthInFile = smpHdr.length;
s->length = smpHdr.length;
s->volume = smpHdr.volume;
s->loopStart = smpHdr.loopStart;
s->loopLength = smpHdr.loopEnd - smpHdr.loopStart;
tuneSample(s, smpHdr.midCFreq, tmpLinearPeriodsFlag);
if (sample16Bit)
{
s->flags |= SAMPLE_16BIT;
lengthInFile <<= 1;
}
if (!allocateSmpData(s, s->length, sample16Bit))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (s->loopLength <= 1 || s->loopStart+s->loopLength > s->length || s->loopLength == 0)
{
s->loopStart = 0;
s->loopLength = 0;
hasLoop = false;
}
if (hasLoop)
s->flags |= LOOP_FWD;
fseek(f, offsetInFile, SEEK_SET);
if (hdr.version == 1)
{
fseek(f, lengthInFile, SEEK_CUR); // sample not supported
}
else
{
if (fread(s->dataPtr, SAMPLE_LENGTH_BYTES(s), 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
if (sample16Bit)
conv16BitSample(s->dataPtr, s->length, stereoSample);
else
conv8BitSample(s->dataPtr, s->length, stereoSample);
// if stereo sample: reduce memory footprint after sample was downmixed to mono
if (stereoSample)
{
s->length >>= 1;
reallocateSmpData(s, s->length, sample16Bit);
}
}
}
}
}
songTmp.numChannels = countS3MChannels(hdr.numPatterns);
if (adlibInsWarn)
loaderMsgBox("Warning: The module contains unsupported AdLib instruments!");
if (!(config.dontShowAgainFlags & DONT_SHOW_IMPORT_WARNING_FLAG))
loaderSysReq(6, "System message", "Loading of this format is not fully supported and can have issues.");
return true;
}
static int8_t countS3MChannels(uint16_t antPtn)
{
int32_t channels = 0;
for (int32_t i = 0; i < antPtn; i++)
{
if (patternTmp[i] == NULL)
continue;
note_t *p = patternTmp[i];
for (int32_t j = 0; j < 64; j++)
{
for (int32_t k = 0; k < MAX_CHANNELS; k++, p++)
{
if (p->note == 0 && p->instr == 0 && p->vol == 0 && p->efx == 0 && p->efxData == 0)
continue;
if (k > channels)
channels = k;
}
}
}
channels++;
return (int8_t)channels;
}