#include "stdafx.h"
#include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
struct C67SampleHeader
{
uint32le unknown; uint32le length;
uint32le loopStart;
uint32le loopEnd;
};
MPT_BINARY_STRUCT(C67SampleHeader, 16)
struct C67FileHeader
{
uint8 speed;
uint8 restartPos;
char sampleNames[32][13];
C67SampleHeader samples[32];
char fmInstrNames[32][13];
uint8 fmInstr[32][11];
uint8 orders[256];
};
MPT_BINARY_STRUCT(C67FileHeader, 1954)
static bool ValidateHeader(const C67FileHeader &fileHeader)
{
if(fileHeader.speed < 1 || fileHeader.speed > 15)
return false;
for(auto ord : fileHeader.orders)
{
if(ord >= 128 && ord != 0xFF)
return false;
}
bool anyNonSilent = false;
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
if(fileHeader.sampleNames[smp][12] != 0
|| fileHeader.samples[smp].unknown != 0
|| fileHeader.samples[smp].length > 0xFFFFF
|| fileHeader.fmInstrNames[smp][12] != 0
|| (fileHeader.fmInstr[smp][0] & 0xF0) || (fileHeader.fmInstr[smp][5] & 0xFC) || (fileHeader.fmInstr[smp][10] & 0xFC)) {
return false;
}
if(fileHeader.samples[smp].length != 0 && fileHeader.samples[smp].loopEnd < 0xFFFFF)
{
if(fileHeader.samples[smp].loopEnd > fileHeader.samples[smp].length
|| fileHeader.samples[smp].loopStart > fileHeader.samples[smp].loopEnd)
{
return false;
}
}
if(!anyNonSilent && (fileHeader.samples[smp].length != 0 || memcmp(fileHeader.fmInstr[smp], "\0\0\0\0\0\0\0\0\0\0\0", 11)))
{
anyNonSilent = true;
}
}
return anyNonSilent;
}
static uint64 GetHeaderMinimumAdditionalSize(const C67FileHeader &)
{
return 1024; }
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderC67(MemoryFileReader file, const uint64 *pfilesize)
{
C67FileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
}
static void TranslateVolume(ModCommand &m, uint8 volume, bool isFM)
{
static const uint8 fmVolume[16] =
{
0x08, 0x10, 0x18, 0x20, 0x28, 0x2C, 0x30, 0x34,
0x36, 0x38, 0x3A, 0x3C, 0x3D, 0x3E, 0x3F, 0x40,
};
volume &= 0x0F;
m.volcmd = VOLCMD_VOLUME;
m.vol = isFM ? fmVolume[volume] : (4u + volume * 4u);
}
bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags)
{
C67FileHeader fileHeader;
file.Rewind();
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
{
return false;
}
uint32le patOffsets[128], patLengths[128];
file.ReadArray(patOffsets);
file.ReadArray(patLengths);
for(PATTERNINDEX pat = 0; pat < 128; pat++)
{
if(patOffsets[pat] > 0xFFFFFF
|| patLengths[pat] < 3 || patLengths[pat] > 0x1000 || !file.LengthIsAtLeast(2978 + patOffsets[pat] + patLengths[pat]))
{
return false;
}
}
InitializeGlobals(MOD_TYPE_S3M);
InitializeChannels();
m_modFormat.formatName = U_("CDFM");
m_modFormat.type = U_("c67");
m_modFormat.madeWithTracker = U_("Composer 670");
m_modFormat.charset = mpt::CharsetCP437;
m_nDefaultSpeed = fileHeader.speed;
m_nDefaultTempo.Set(143);
Order().SetRestartPos(fileHeader.restartPos);
m_nSamples = 64;
m_nChannels = 4 + 9;
m_playBehaviour.set(kOPLBeatingOscillators);
for(CHANNELINDEX chn = 0; chn < 4; chn++)
{
ChnSettings[chn].nPan = (chn & 1) ? 192 : 64;
}
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
ModSample &mptSmp = Samples[smp + 1];
mptSmp.Initialize(MOD_TYPE_S3M);
mpt::String::Read<mpt::String::nullTerminated>(m_szNames[smp + 1], fileHeader.sampleNames[smp]);
mptSmp.nLength = fileHeader.samples[smp].length;
if(fileHeader.samples[smp].loopEnd <= fileHeader.samples[smp].length)
{
mptSmp.nLoopStart = fileHeader.samples[smp].loopStart;
mptSmp.nLoopEnd = fileHeader.samples[smp].loopEnd;
mptSmp.uFlags = CHN_LOOP;
}
mptSmp.nC5Speed = 8287;
}
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
ModSample &mptSmp = Samples[smp + 33];
mptSmp.Initialize(MOD_TYPE_S3M);
mpt::String::Read<mpt::String::nullTerminated>(m_szNames[smp + 33], fileHeader.fmInstrNames[smp]);
const auto &fm = fileHeader.fmInstr[smp];
OPLPatch patch{{}};
patch[0] = fm[1]; patch[1] = fm[6];
patch[2] = fm[2]; patch[3] = fm[7];
patch[4] = fm[3]; patch[5] = fm[8];
patch[6] = fm[4]; patch[7] = fm[9];
patch[8] = fm[5]; patch[9] = fm[10];
patch[10] = fm[0];
mptSmp.SetAdlib(true, patch);
}
ReadOrderFromArray<uint8>(Order(), fileHeader.orders, 256, 0xFF);
Patterns.ResizeArray(128);
for(PATTERNINDEX pat = 0; pat < 128; pat++)
{
file.Seek(2978 + patOffsets[pat]);
FileReader patChunk = file.ReadChunk(patLengths[pat]);
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
{
continue;
}
CPattern &pattern = Patterns[pat];
ROWINDEX row = 0;
while(row < 64 && patChunk.CanRead(1))
{
uint8 cmd = patChunk.ReadUint8();
if(cmd <= 0x0C)
{
ModCommand &m = *pattern.GetpModCommand(row, cmd);
uint8 data[2];
patChunk.ReadArray(data);
uint8 note = data[0], instrVol = data[1];
bool fmChn = (cmd >= 4);
m.note = NOTE_MIN + (fmChn ? 12 : 36) + (note & 0x0F) + ((note >> 4) & 0x07) * 12;
m.instr = (fmChn ? 33 : 1) + (instrVol >> 4) + ((note & 0x80) >> 3);
TranslateVolume(m, instrVol, fmChn);
} else if(cmd >= 0x20 && cmd <= 0x2C)
{
TranslateVolume(*pattern.GetpModCommand(row, cmd - 0x20), patChunk.ReadUint8(), cmd >= 0x24);
} else if(cmd == 0x40)
{
row += patChunk.ReadUint8();
} else if(cmd == 0x60)
{
if(row > 0)
{
pattern.GetpModCommand(row - 1, 0)->command = CMD_PATTERNBREAK;
}
break;
} else
{
return false;
}
}
}
if(loadFlags & loadSampleData)
{
for(SAMPLEINDEX smp = 1; smp <= 32; smp++)
{
SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::unsignedPCM).ReadSample(Samples[smp], file);
}
}
return true;
}
OPENMPT_NAMESPACE_END