#include "stdafx.h"
#include "Loaders.h"
#include "ChunkReader.h"
#if defined(MPT_WITH_ZLIB)
#include <zlib.h>
#elif defined(MPT_WITH_MINIZ)
#include <miniz/miniz.h>
#endif
#ifdef MPT_ALL_LOGGING
#define J2B_LOG
#endif
OPENMPT_NAMESPACE_BEGIN
static const VibratoType j2bAutoVibratoTrans[] =
{
VIB_SINE, VIB_SQUARE, VIB_RAMP_UP, VIB_RAMP_DOWN, VIB_RANDOM,
};
struct J2BFileHeader
{
enum : uint32 {
magicDEADBEAF = 0xAFBEADDEu,
magicDEADBABE = 0xBEBAADDEu
};
char signature[4]; uint32le deadbeaf; uint32le fileLength; uint32le crc32; uint32le packedLength; uint32le unpackedLength; };
MPT_BINARY_STRUCT(J2BFileHeader, 24)
struct AMFFRiffChunk
{
enum ChunkIdentifiers
{
idRIFF = MagicLE("RIFF"),
idAMFF = MagicLE("AMFF"),
idAM__ = MagicLE("AM "),
idMAIN = MagicLE("MAIN"),
idINIT = MagicLE("INIT"),
idORDR = MagicLE("ORDR"),
idPATT = MagicLE("PATT"),
idINST = MagicLE("INST"),
idSAMP = MagicLE("SAMP"),
idAI__ = MagicLE("AI "),
idAS__ = MagicLE("AS "),
};
uint32le id; uint32le length;
size_t GetLength() const
{
return length;
}
ChunkIdentifiers GetID() const
{
return static_cast<ChunkIdentifiers>(id.get());
}
};
MPT_BINARY_STRUCT(AMFFRiffChunk, 8)
struct AMFFMainChunk
{
enum MainFlags
{
amigaSlides = 0x01,
};
char songname[64];
uint8le flags;
uint8le channels;
uint8le speed;
uint8le tempo;
uint16le minPeriod; uint16le maxPeriod; uint8le globalvolume;
};
MPT_BINARY_STRUCT(AMFFMainChunk, 73)
struct AMFFEnvelope
{
enum EnvelopeFlags
{
envEnabled = 0x01,
envSustain = 0x02,
envLoop = 0x04,
};
struct EnvPoint
{
uint16le tick;
uint8le value; };
uint8le envFlags; uint8le envNumPoints; uint8le envSustainPoints; uint8le envLoopStarts; uint8le envLoopEnds; EnvPoint volEnv[10];
EnvPoint panEnv[10];
void ConvertEnvelope(uint8 flags, uint8 numPoints, uint8 sustainPoint, uint8 loopStart, uint8 loopEnd, const EnvPoint (&points)[10], InstrumentEnvelope &mptEnv) const
{
mptEnv.resize(std::min(numPoints, static_cast<uint8>(10)));
mptEnv.nSustainStart = mptEnv.nSustainEnd = sustainPoint;
mptEnv.nLoopStart = loopStart;
mptEnv.nLoopEnd = loopEnd;
for(uint32 i = 0; i < mptEnv.size(); i++)
{
mptEnv[i].tick = points[i].tick >> 4;
if(i == 0)
mptEnv[0].tick = 0;
else if(mptEnv[i].tick < mptEnv[i - 1].tick)
mptEnv[i].tick = mptEnv[i - 1].tick + 1;
mptEnv[i].value = Clamp<uint8, uint8>(points[i].value, 0, 64);
}
mptEnv.dwFlags.set(ENV_ENABLED, (flags & AMFFEnvelope::envEnabled) != 0);
mptEnv.dwFlags.set(ENV_SUSTAIN, (flags & AMFFEnvelope::envSustain) && mptEnv.nSustainStart <= mptEnv.size());
mptEnv.dwFlags.set(ENV_LOOP, (flags & AMFFEnvelope::envLoop) && mptEnv.nLoopStart <= mptEnv.nLoopEnd && mptEnv.nLoopStart <= mptEnv.size());
}
void ConvertToMPT(ModInstrument &mptIns) const
{
ConvertEnvelope(envFlags & 0x0F, envNumPoints & 0x0F, envSustainPoints & 0x0F, envLoopStarts & 0x0F, envLoopEnds & 0x0F, volEnv, mptIns.VolEnv);
ConvertEnvelope(envFlags >> 4, envNumPoints >> 4, envSustainPoints >> 4, envLoopStarts >> 4, envLoopEnds >> 4, panEnv, mptIns.PanEnv);
}
};
MPT_BINARY_STRUCT(AMFFEnvelope::EnvPoint, 3)
MPT_BINARY_STRUCT(AMFFEnvelope, 65)
struct AMFFInstrumentHeader
{
uint8le unknown; uint8le index; char name[28];
uint8le numSamples;
uint8le sampleMap[120];
uint8le vibratoType;
uint16le vibratoSweep;
uint16le vibratoDepth;
uint16le vibratoRate;
AMFFEnvelope envelopes;
uint16le fadeout;
void ConvertToMPT(ModInstrument &mptIns, SAMPLEINDEX baseSample)
{
mpt::String::Read<mpt::String::maybeNullTerminated>(mptIns.name, name);
STATIC_ASSERT(CountOf(sampleMap) <= CountOf(mptIns.Keyboard));
for(size_t i = 0; i < CountOf(sampleMap); i++)
{
mptIns.Keyboard[i] = sampleMap[i] + baseSample + 1;
}
mptIns.nFadeOut = fadeout << 5;
envelopes.ConvertToMPT(mptIns);
}
};
MPT_BINARY_STRUCT(AMFFInstrumentHeader, 225)
struct AMFFSampleHeader
{
enum SampleFlags
{
smp16Bit = 0x04,
smpLoop = 0x08,
smpPingPong = 0x10,
smpPanning = 0x20,
smpExists = 0x80,
};
uint32le id; uint32le chunkSize; char name[28];
uint8le pan;
uint8le volume;
uint16le flags;
uint32le length;
uint32le loopStart;
uint32le loopEnd;
uint32le sampleRate;
uint32le reserved1;
uint32le reserved2;
void ConvertToMPT(AMFFInstrumentHeader &instrHeader, ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.nPan = pan * 4;
mptSmp.nVolume = volume * 4;
mptSmp.nGlobalVol = 64;
mptSmp.nLength = length;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nC5Speed = sampleRate;
if(instrHeader.vibratoType < mpt::size(j2bAutoVibratoTrans))
mptSmp.nVibType = j2bAutoVibratoTrans[instrHeader.vibratoType];
mptSmp.nVibSweep = static_cast<uint8>(instrHeader.vibratoSweep);
mptSmp.nVibRate = static_cast<uint8>(instrHeader.vibratoRate / 16);
mptSmp.nVibDepth = static_cast<uint8>(instrHeader.vibratoDepth / 4);
if((mptSmp.nVibRate | mptSmp.nVibDepth) != 0)
{
mptSmp.nVibSweep = 255 - mptSmp.nVibSweep;
}
if(flags & AMFFSampleHeader::smp16Bit)
mptSmp.uFlags.set(CHN_16BIT);
if(flags & AMFFSampleHeader::smpLoop)
mptSmp.uFlags.set(CHN_LOOP);
if(flags & AMFFSampleHeader::smpPingPong)
mptSmp.uFlags.set(CHN_PINGPONGLOOP);
if(flags & AMFFSampleHeader::smpPanning)
mptSmp.uFlags.set(CHN_PANNING);
}
SampleIO GetSampleFormat() const
{
return SampleIO(
(flags & AMFFSampleHeader::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::signedPCM);
}
};
MPT_BINARY_STRUCT(AMFFSampleHeader, 64)
struct AMEnvelope
{
struct EnvPoint
{
uint16le tick;
uint16le value;
};
uint16le flags;
uint8le numPoints; uint8le sustainPoint;
uint8le loopStart;
uint8le loopEnd;
EnvPoint values[10];
uint16le fadeout;
void ConvertToMPT(InstrumentEnvelope &mptEnv, EnvelopeType envType) const
{
if(numPoints == 0xFF || numPoints == 0)
return;
mptEnv.resize(std::min(numPoints + 1, 10));
mptEnv.nSustainStart = mptEnv.nSustainEnd = sustainPoint;
mptEnv.nLoopStart = loopStart;
mptEnv.nLoopEnd = loopEnd;
for(uint32 i = 0; i < mptEnv.size(); i++)
{
mptEnv[i].tick = values[i].tick >> 4;
if(i == 0)
mptEnv[i].tick = 0;
else if(mptEnv[i].tick < mptEnv[i - 1].tick)
mptEnv[i].tick = mptEnv[i - 1].tick + 1;
const uint16 val = values[i].value;
switch(envType)
{
case ENV_VOLUME: default:
mptEnv[i].value = (uint8)((val + 1) >> 9);
break;
case ENV_PITCH: mptEnv[i].value = (uint8)((((int16)val) + 0x1001) >> 7);
break;
case ENV_PANNING: mptEnv[i].value = (uint8)((((int16)val) + 0x8001) >> 10);
break;
}
Limit(mptEnv[i].value, uint8(ENVELOPE_MIN), uint8(ENVELOPE_MAX));
}
mptEnv.dwFlags.set(ENV_ENABLED, (flags & AMFFEnvelope::envEnabled) != 0);
mptEnv.dwFlags.set(ENV_SUSTAIN, (flags & AMFFEnvelope::envSustain) && mptEnv.nSustainStart <= mptEnv.size());
mptEnv.dwFlags.set(ENV_LOOP, (flags & AMFFEnvelope::envLoop) && mptEnv.nLoopStart <= mptEnv.nLoopEnd && mptEnv.nLoopStart <= mptEnv.size());
}
};
MPT_BINARY_STRUCT(AMEnvelope::EnvPoint, 4)
MPT_BINARY_STRUCT(AMEnvelope, 48)
struct AMInstrumentHeader
{
uint32le headSize; uint8le unknown1; uint8le index; char name[32];
uint8le sampleMap[128];
uint8le vibratoType;
uint16le vibratoSweep;
uint16le vibratoDepth;
uint16le vibratoRate;
uint8le unknown2[7];
AMEnvelope volEnv;
AMEnvelope pitchEnv;
AMEnvelope panEnv;
uint16le numSamples;
void ConvertToMPT(ModInstrument &mptIns, SAMPLEINDEX baseSample)
{
mpt::String::Read<mpt::String::maybeNullTerminated>(mptIns.name, name);
STATIC_ASSERT(CountOf(sampleMap) <= CountOf(mptIns.Keyboard));
for(uint8 i = 0; i < CountOf(sampleMap); i++)
{
mptIns.Keyboard[i] = sampleMap[i] + baseSample + 1;
}
mptIns.nFadeOut = volEnv.fadeout << 5;
volEnv.ConvertToMPT(mptIns.VolEnv, ENV_VOLUME);
pitchEnv.ConvertToMPT(mptIns.PitchEnv, ENV_PITCH);
panEnv.ConvertToMPT(mptIns.PanEnv, ENV_PANNING);
if(numSamples == 0)
{
MemsetZero(mptIns.Keyboard);
}
}
};
MPT_BINARY_STRUCT(AMInstrumentHeader, 326)
struct AMSampleHeader
{
uint32le headSize; char name[32];
uint16le pan;
uint16le volume;
uint16le flags;
uint16le unknown; uint32le length;
uint32le loopStart;
uint32le loopEnd;
uint32le sampleRate;
void ConvertToMPT(AMInstrumentHeader &instrHeader, ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.nPan = std::min<uint16>(pan, 32767) * 256 / 32767;
mptSmp.nVolume = std::min<uint16>(volume, 32767) * 256 / 32767;
mptSmp.nGlobalVol = 64;
mptSmp.nLength = length;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nC5Speed = sampleRate;
if(instrHeader.vibratoType < CountOf(j2bAutoVibratoTrans))
mptSmp.nVibType = j2bAutoVibratoTrans[instrHeader.vibratoType];
mptSmp.nVibSweep = static_cast<uint8>(instrHeader.vibratoSweep);
mptSmp.nVibRate = static_cast<uint8>(instrHeader.vibratoRate / 16);
mptSmp.nVibDepth = static_cast<uint8>(instrHeader.vibratoDepth / 4);
if((mptSmp.nVibRate | mptSmp.nVibDepth) != 0)
{
mptSmp.nVibSweep = 255 - mptSmp.nVibSweep;
}
if(flags & AMFFSampleHeader::smp16Bit)
mptSmp.uFlags.set(CHN_16BIT);
if(flags & AMFFSampleHeader::smpLoop)
mptSmp.uFlags.set(CHN_LOOP);
if(flags & AMFFSampleHeader::smpPingPong)
mptSmp.uFlags.set(CHN_PINGPONGLOOP);
if(flags & AMFFSampleHeader::smpPanning)
mptSmp.uFlags.set(CHN_PANNING);
}
SampleIO GetSampleFormat() const
{
return SampleIO(
(flags & AMFFSampleHeader::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::signedPCM);
}
};
MPT_BINARY_STRUCT(AMSampleHeader, 60)
static bool ConvertAMPattern(FileReader chunk, PATTERNINDEX pat, bool isAM, CSoundFile &sndFile)
{
static const EffectCommand amEffTrans[] =
{
CMD_ARPEGGIO, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_TONEPORTAMENTO,
CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL, CMD_TREMOLO,
CMD_PANNING8, CMD_OFFSET, CMD_VOLUMESLIDE, CMD_POSITIONJUMP,
CMD_VOLUME, CMD_PATTERNBREAK, CMD_MODCMDEX, CMD_TEMPO,
CMD_GLOBALVOLUME, CMD_GLOBALVOLSLIDE, CMD_KEYOFF, CMD_SETENVPOSITION,
CMD_CHANNELVOLUME, CMD_CHANNELVOLSLIDE, CMD_PANNINGSLIDE, CMD_RETRIG,
CMD_TREMOR, CMD_XFINEPORTAUPDOWN,
};
enum
{
rowDone = 0, channelMask = 0x1F, volFlag = 0x20, noteFlag = 0x40, effectFlag = 0x80, dataFlag = 0xE0, };
if(chunk.NoBytesLeft())
{
return false;
}
ROWINDEX numRows = Clamp(static_cast<ROWINDEX>(chunk.ReadUint8()) + 1, ROWINDEX(1), MAX_PATTERN_ROWS);
if(!sndFile.Patterns.Insert(pat, numRows))
return false;
const CHANNELINDEX channels = sndFile.GetNumChannels();
if(channels == 0)
return false;
ROWINDEX row = 0;
while(row < numRows && chunk.CanRead(1))
{
const uint8 flags = chunk.ReadUint8();
if(flags == rowDone)
{
row++;
continue;
}
ModCommand &m = *sndFile.Patterns[pat].GetpModCommand(row, std::min<CHANNELINDEX>((flags & channelMask), channels - 1));
if(flags & dataFlag)
{
if(flags & effectFlag) {
m.param = chunk.ReadUint8();
uint8 command = chunk.ReadUint8();
if(command < CountOf(amEffTrans))
{
m.command = amEffTrans[command];
} else
{
#ifdef J2B_LOG
MPT_LOG(LogDebug, "J2B", mpt::uformat(U_("J2B: Unknown command: 0x%1, param 0x%2"))(mpt::ufmt::HEX0<2>(command), mpt::ufmt::HEX0<2>(m.param)));
#endif
m.command = CMD_NONE;
}
switch(m.command)
{
case CMD_ARPEGGIO:
if(m.param == 0) m.command = CMD_NONE;
break;
case CMD_VOLUME:
if(m.volcmd == VOLCMD_NONE)
{
m.volcmd = VOLCMD_VOLUME;
m.vol = Clamp(m.param, uint8(0), uint8(64));
m.command = CMD_NONE;
m.param = 0;
}
break;
case CMD_TONEPORTAVOL:
case CMD_VIBRATOVOL:
case CMD_VOLUMESLIDE:
case CMD_GLOBALVOLSLIDE:
case CMD_PANNINGSLIDE:
if (m.param & 0xF0) m.param &= 0xF0;
break;
case CMD_PANNING8:
if(m.param <= 0x80) m.param = MIN(m.param << 1, 0xFF);
else if(m.param == 0xA4) {m.command = CMD_S3MCMDEX; m.param = 0x91;}
break;
case CMD_PATTERNBREAK:
m.param = ((m.param >> 4) * 10) + (m.param & 0x0F);
break;
case CMD_MODCMDEX:
m.ExtendedMODtoS3MEffect();
break;
case CMD_TEMPO:
if(m.param <= 0x1F) m.command = CMD_SPEED;
break;
case CMD_XFINEPORTAUPDOWN:
switch(m.param & 0xF0)
{
case 0x10:
m.command = CMD_PORTAMENTOUP;
break;
case 0x20:
m.command = CMD_PORTAMENTODOWN;
break;
}
m.param = (m.param & 0x0F) | 0xE0;
break;
}
}
if (flags & noteFlag) {
m.instr = chunk.ReadUint8();
m.note = chunk.ReadUint8();
if(m.note == 0x80) m.note = NOTE_KEYOFF;
else if(m.note > 0x80) m.note = NOTE_FADE; }
if (flags & volFlag) {
m.volcmd = VOLCMD_VOLUME;
m.vol = chunk.ReadUint8();
if(isAM)
{
m.vol = m.vol * 64 / 127;
}
}
}
}
return true;
}
struct AMFFRiffChunkFormat
{
uint32le format;
};
MPT_BINARY_STRUCT(AMFFRiffChunkFormat, 4)
static bool ValidateHeader(const AMFFRiffChunk &fileHeader)
{
if(fileHeader.id != AMFFRiffChunk::idRIFF)
{
return false;
}
if(fileHeader.GetLength() < 8 + sizeof(AMFFMainChunk))
{
return false;
}
return true;
}
static bool ValidateHeader(const AMFFRiffChunkFormat &formatHeader)
{
if(formatHeader.format != AMFFRiffChunk::idAMFF && formatHeader.format != AMFFRiffChunk::idAM__)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderAM(MemoryFileReader file, const uint64 *pfilesize)
{
AMFFRiffChunk fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
AMFFRiffChunkFormat formatHeader;
if(!file.ReadStruct(formatHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(formatHeader))
{
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
AMFFRiffChunk fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
AMFFRiffChunkFormat formatHeader;
if(!file.ReadStruct(formatHeader))
{
return false;
}
if(!ValidateHeader(formatHeader))
{
return false;
}
bool isAM;
uint32 format = formatHeader.format;
if(format == AMFFRiffChunk::idAMFF)
isAM = false; else if(format == AMFFRiffChunk::idAM__)
isAM = true; else
return false;
ChunkReader chunkFile(file);
AMFFRiffChunk::ChunkIdentifiers mainChunkID = isAM ? AMFFRiffChunk::idINIT : AMFFRiffChunk::idMAIN;
ChunkReader::ChunkList<AMFFRiffChunk> chunks;
if(loadFlags == onlyVerifyHeader)
chunks = chunkFile.ReadChunksUntil<AMFFRiffChunk>(isAM ? 2 : 1, mainChunkID);
else
chunks = chunkFile.ReadChunks<AMFFRiffChunk>(isAM ? 2 : 1);
FileReader chunkMain(chunks.GetChunk(mainChunkID));
AMFFMainChunk mainChunk;
if(!chunkMain.IsValid()
|| !chunkMain.ReadStruct(mainChunk)
|| mainChunk.channels < 1
|| !chunkMain.CanRead(mainChunk.channels))
{
return false;
} else if(loadFlags == onlyVerifyHeader)
{
return true;
}
InitializeGlobals(MOD_TYPE_J2B);
m_SongFlags = SONG_ITOLDEFFECTS | SONG_ITCOMPATGXX;
m_SongFlags.set(SONG_LINEARSLIDES, !(mainChunk.flags & AMFFMainChunk::amigaSlides));
m_nChannels = MIN(mainChunk.channels, MAX_BASECHANNELS);
m_nDefaultSpeed = mainChunk.speed;
m_nDefaultTempo.Set(mainChunk.tempo);
m_nDefaultGlobalVolume = mainChunk.globalvolume * 2;
m_modFormat.formatName = isAM ? UL_("Galaxy Sound System (new version)") : UL_("Galaxy Sound System (old version)");
m_modFormat.type = U_("j2b");
m_modFormat.charset = mpt::CharsetCP437;
mpt::String::Read<mpt::String::maybeNullTerminated>(m_songName, mainChunk.songname);
for(CHANNELINDEX nChn = 0; nChn < m_nChannels; nChn++)
{
ChnSettings[nChn].Reset();
uint8 pan = chunkMain.ReadUint8();
if(isAM)
{
if(pan > 128)
ChnSettings[nChn].dwFlags = CHN_MUTE;
else
ChnSettings[nChn].nPan = pan * 2;
} else
{
if(pan >= 128)
ChnSettings[nChn].dwFlags = CHN_MUTE;
else
ChnSettings[nChn].nPan = static_cast<uint16>(std::min(pan * 4, 256));
}
}
if(chunks.ChunkExists(AMFFRiffChunk::idORDR))
{
FileReader chunk(chunks.GetChunk(AMFFRiffChunk::idORDR));
uint8 numOrders = chunk.ReadUint8() + 1;
ReadOrderFromFile<uint8>(Order(), chunk, numOrders, 0xFF, 0xFE);
}
if(loadFlags & loadPatternData)
{
PATTERNINDEX maxPattern = 0;
auto pattChunks = chunks.GetAllChunks(AMFFRiffChunk::idPATT);
Patterns.ResizeArray(static_cast<PATTERNINDEX>(pattChunks.size()));
for(auto chunk : pattChunks)
{
PATTERNINDEX pat = chunk.ReadUint8();
size_t patternSize = chunk.ReadUint32LE();
ConvertAMPattern(chunk.ReadChunk(patternSize), pat, isAM, *this);
maxPattern = std::max(maxPattern, pat);
}
for(PATTERNINDEX pat = 0; pat < maxPattern; pat++)
{
if(!Patterns.IsValidPat(pat))
Patterns.Insert(pat, 64);
}
}
if(!isAM)
{
auto instChunks = chunks.GetAllChunks(AMFFRiffChunk::idINST);
for(auto chunk : instChunks)
{
AMFFInstrumentHeader instrHeader;
if(!chunk.ReadStruct(instrHeader))
{
continue;
}
const INSTRUMENTINDEX instr = instrHeader.index + 1;
if(instr >= MAX_INSTRUMENTS)
continue;
ModInstrument *pIns = AllocateInstrument(instr);
if(pIns == nullptr)
{
continue;
}
instrHeader.ConvertToMPT(*pIns, m_nSamples);
for(size_t samples = 0; samples < instrHeader.numSamples; samples++)
{
AMFFSampleHeader sampleHeader;
if(m_nSamples + 1 >= MAX_SAMPLES || !chunk.ReadStruct(sampleHeader))
{
continue;
}
const SAMPLEINDEX smp = ++m_nSamples;
if(sampleHeader.id != AMFFRiffChunk::idSAMP)
{
continue;
}
mpt::String::Read<mpt::String::maybeNullTerminated>(m_szNames[smp], sampleHeader.name);
sampleHeader.ConvertToMPT(instrHeader, Samples[smp]);
if(loadFlags & loadSampleData)
sampleHeader.GetSampleFormat().ReadSample(Samples[smp], chunk);
else
chunk.Skip(Samples[smp].GetSampleSizeInBytes());
}
}
} else
{
auto instChunks = chunks.GetAllChunks(AMFFRiffChunk::idRIFF);
for(ChunkReader chunk : instChunks)
{
if(chunk.ReadUint32LE() != AMFFRiffChunk::idAI__)
{
continue;
}
AMFFRiffChunk instChunk;
if(!chunk.ReadStruct(instChunk) || instChunk.id != AMFFRiffChunk::idINST)
{
continue;
}
AMInstrumentHeader instrHeader;
if(!chunk.ReadStruct(instrHeader))
{
continue;
}
MPT_ASSERT(instrHeader.headSize + 4 == sizeof(instrHeader));
const INSTRUMENTINDEX instr = instrHeader.index + 1;
if(instr >= MAX_INSTRUMENTS)
continue;
ModInstrument *pIns = AllocateInstrument(instr);
if(pIns == nullptr)
{
continue;
}
instrHeader.ConvertToMPT(*pIns, m_nSamples);
auto sampleChunks = chunk.ReadChunks<AMFFRiffChunk>(2).GetAllChunks(AMFFRiffChunk::idRIFF);
MPT_ASSERT(sampleChunks.size() == instrHeader.numSamples);
for(auto sampleChunk : sampleChunks)
{
if(sampleChunk.ReadUint32LE() != AMFFRiffChunk::idAS__ || m_nSamples + 1 >= MAX_SAMPLES)
{
continue;
}
if((instrHeader.numSamples--) == 0)
{
break;
}
const SAMPLEINDEX smp = ++m_nSamples;
AMFFRiffChunk sampleHeaderChunk;
if(!sampleChunk.ReadStruct(sampleHeaderChunk) || sampleHeaderChunk.id != AMFFRiffChunk::idSAMP)
{
break;
}
FileReader sampleFileChunk = sampleChunk.ReadChunk(sampleHeaderChunk.length);
AMSampleHeader sampleHeader;
if(!sampleFileChunk.ReadStruct(sampleHeader))
{
break;
}
mpt::String::Read<mpt::String::maybeNullTerminated>(m_szNames[smp], sampleHeader.name);
sampleHeader.ConvertToMPT(instrHeader, Samples[smp]);
if(loadFlags & loadSampleData)
{
sampleFileChunk.Seek(sampleHeader.headSize + 4);
sampleHeader.GetSampleFormat().ReadSample(Samples[smp], sampleFileChunk);
}
}
}
}
return true;
}
static bool ValidateHeader(const J2BFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.signature, "MUSE", 4)
|| (fileHeader.deadbeaf != J2BFileHeader::magicDEADBEAF && fileHeader.deadbeaf != J2BFileHeader::magicDEADBABE) )
{
return false;
}
if(fileHeader.packedLength == 0)
{
return false;
}
if(fileHeader.fileLength != fileHeader.packedLength + sizeof(J2BFileHeader))
{
return false;
}
return true;
}
static bool ValidateHeaderFileSize(const J2BFileHeader &fileHeader, uint64 filesize)
{
if(filesize != fileHeader.fileLength)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderJ2B(MemoryFileReader file, const uint64 *pfilesize)
{
J2BFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
if(pfilesize)
{
if(!ValidateHeaderFileSize(fileHeader, *pfilesize))
{
return ProbeFailure;
}
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadJ2B(FileReader &file, ModLoadingFlags loadFlags)
{
#if !defined(MPT_WITH_ZLIB) && !defined(MPT_WITH_MINIZ)
MPT_UNREFERENCED_PARAMETER(file);
MPT_UNREFERENCED_PARAMETER(loadFlags);
return false;
#else
file.Rewind();
J2BFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(fileHeader.fileLength != file.GetLength()
|| fileHeader.packedLength != file.BytesLeft()
)
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
FileReader::PinnedRawDataView filePackedView = file.GetPinnedRawDataView(fileHeader.packedLength);
#ifndef MPT_BUILD_FUZZER
if(fileHeader.crc32 != crc32(0, mpt::byte_cast<const Bytef*>(filePackedView.data()), static_cast<uint32>(filePackedView.size())))
{
return false;
}
#endif
uLongf destSize = fileHeader.unpackedLength;
std::vector<Bytef> amFileData(destSize);
int retVal = uncompress(amFileData.data(), &destSize, mpt::byte_cast<const Bytef*>(filePackedView.data()), static_cast<uint32>(filePackedView.size()));
bool result = false;
#ifndef MPT_BUILD_FUZZER
if(destSize == fileHeader.unpackedLength && retVal == Z_OK)
#endif
{
FileReader amFile(mpt::as_span(amFileData));
result = ReadAM(amFile, loadFlags);
}
return result;
#endif
}
OPENMPT_NAMESPACE_END