From: <sv...@op...> - 2024-05-09 21:59:47
|
Author: sagamusix Date: Thu May 9 23:59:39 2024 New Revision: 20747 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=20747 Log: [Imp] XM: Improve detection of MPT-made files, and tell them apart from files created with PlayerPRO. Modified: trunk/OpenMPT/soundlib/Load_xm.cpp Modified: trunk/OpenMPT/soundlib/Load_xm.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_xm.cpp Thu May 9 19:41:22 2024 (r20746) +++ trunk/OpenMPT/soundlib/Load_xm.cpp Thu May 9 23:59:39 2024 (r20747) @@ -368,9 +368,10 @@ verFT2Generic = 0x20, // "FastTracker v2.00", but FastTracker has NOT been ruled out verOther = 0x40, // Something we don't know, testing for DigiTrakker. verFT2Clone = 0x80, // NOT FT2: itype changed between instruments, or \0 found in song title - verDigiTrakker = 0x100, // Probably DigiTrakker - verUNMO3 = 0x200, // TODO: UNMO3-ed XMs are detected as MPT 1.16 - verEmptyOrders = 0x400, // Allow empty order list like in OpenMPT (FT2 just plays pattern 0 if the order list is empty according to the header) + verPlayerPRO = 0x100, // Could be PlayerPRO + verDigiTrakker = 0x200, // Probably DigiTrakker + verUNMO3 = 0x400, // TODO: UNMO3-ed XMs are detected as MPT 1.16 + verEmptyOrders = 0x800, // Allow empty order list like in OpenMPT (FT2 just plays pattern 0 if the order list is empty according to the header) }; DECLARE_FLAGSET(TrackerVersions) @@ -605,13 +606,30 @@ if(!memcmp(fileHeader.trackerName, "FastTracker v2.00 ", 20) && fileHeader.size == 276) { + const std::string_view songName{fileHeader.songName, sizeof(fileHeader.songName)}; if(fileHeader.version < 0x0104) + { madeWith = verFT2Generic | verConfirmed; - else if(memchr(fileHeader.songName, '\0', 20) != nullptr) + } else if(const auto firstNull = songName.find('\0'); firstNull != std::string_view::npos) + { // FT2 pads the song title with spaces, some other trackers use null chars - madeWith = verFT2Clone | verNewModPlug | verEmptyOrders; - else - madeWith = verFT2Generic | verNewModPlug; + // PlayerPRO filles the remaining buffer after the null terminator with space characters. + // PlayerPRO does not support song restart position. + if(fileHeader.restartPos) + madeWith = verFT2Clone | verNewModPlug | verEmptyOrders; + else if(firstNull == songName.size() - 1) + madeWith = verFT2Clone | verNewModPlug | verPlayerPRO | verEmptyOrders; + else if(songName.find_first_not_of(' ', firstNull + 1) == std::string_view::npos) + madeWith = verPlayerPRO | verConfirmed; + else + madeWith = verFT2Clone | verNewModPlug | verEmptyOrders; + } else + { + if(fileHeader.restartPos) + madeWith = verFT2Generic | verNewModPlug; + else + madeWith = verFT2Generic | verNewModPlug | verPlayerPRO; + } } else if(!memcmp(fileHeader.trackerName, "FastTracker v 2.00 ", 20)) { // MPT 1.0 (exact version to be determined later) @@ -672,10 +690,8 @@ m_SongFlags.reset(); m_SongFlags.set(SONG_LINEARSLIDES, (fileHeader.flags & XMFileHeader::linearSlides) != 0); m_SongFlags.set(SONG_EXFILTERRANGE, (fileHeader.flags & XMFileHeader::extendedFilterRange) != 0); - if(m_SongFlags[SONG_EXFILTERRANGE] && madeWith == (verFT2Generic | verNewModPlug)) - { - madeWith = verFT2Clone | verNewModPlug | verConfirmed; - } + if(m_SongFlags[SONG_EXFILTERRANGE] && madeWith[verNewModPlug]) + madeWith = verFT2Clone | verNewModPlug | verConfirmed | verEmptyOrders; ReadOrderFromFile<uint8>(Order(), file, fileHeader.orders); if(fileHeader.orders == 0 && !madeWith[verEmptyOrders]) @@ -695,9 +711,11 @@ // In case of XM versions < 1.04, we need to memorize the sample flags for all samples, as they are not stored immediately after the sample headers. std::vector<SampleIO> sampleFlags; uint8 sampleReserved = 0; - int instrType = -1; + int16 lastInstrType = -1, lastSampleReserved = -1; + int64 lastSampleHeaderSize = -1; bool unsupportedSamples = false; bool anyADPCM = false; + bool instrumentWithSamplesEncountered = false; // Reading instruments for(INSTRUMENTINDEX instr = 1; instr <= m_nInstruments; instr++) @@ -743,11 +761,23 @@ else if(madeWith[verFT2Clone | verFT2Generic] && instrHeader.size != 33) { // Sure isn't FT2. - // Note: FT2 NORMALLY writes shdr=40 for all samples, but sometimes it - // just happens to write random garbage there instead. Surprise! - // Note: 4-mat's eternity.xm has an instrument header size of 29. madeWith = verUnknown; } + if(instrHeader.size != 33) + { + madeWith.reset(verPlayerPRO); + } else if(instrHeader.sampleHeaderSize > sizeof(XMSample) && madeWith[verPlayerPRO]) + { + // Older PlayerPRO versions appear to write garbage in the sampleHeaderSize field, and it's different for each sample. + // Note: FT2 NORMALLY writes sampleHeaderSize=40 for all samples, but for any instruments before the first + // instrument that has numSamples != 0, sampleHeaderSize will be uninitialized. It will always be the same + // value, though. + // Note: 4-mat's eternity.xm has an instrument header size of 29 (without the previously described condition). + // Another module with that size is funky_dumbass.xm. Mysterious! + if(instrumentWithSamplesEncountered || (lastSampleHeaderSize != -1 && instrHeader.sampleHeaderSize != lastSampleHeaderSize)) + madeWith = verPlayerPRO | verConfirmed; + lastSampleHeaderSize = instrHeader.sampleHeaderSize; + } } if(AllocateInstrument(instr) == nullptr) @@ -757,10 +787,10 @@ instrHeader.ConvertToMPT(*Instruments[instr]); - if(instrType == -1) + if(lastInstrType == -1) { - instrType = instrHeader.type; - } else if(instrType != instrHeader.type && madeWith[verFT2Generic]) + lastInstrType = instrHeader.type; + } else if(lastInstrType != instrHeader.type && madeWith[verFT2Generic]) { // FT2 writes some random junk for the instrument type field, // but it's always the SAME junk for every instrument saved. @@ -770,11 +800,24 @@ if(instrHeader.numSamples > 0) { + instrumentWithSamplesEncountered = true; + // Yep, there are some samples associated with this instrument. + + // If MIDI settings are present, this is definitely not an old MPT or PlayerPRO. if((instrHeader.instrument.midiEnabled | instrHeader.instrument.midiChannel | instrHeader.instrument.midiProgram | instrHeader.instrument.muteComputer) != 0) - { - // Definitely not an old MPT. - madeWith.reset(verOldModPlug | verNewModPlug); + madeWith.reset(verOldModPlug | verNewModPlug | verPlayerPRO); + if(instrHeader.size != 263 || instrHeader.type != 0) + madeWith.reset(verPlayerPRO); + if(!madeWith[verConfirmed] && madeWith[verPlayerPRO]) + { + // Note: Earlier (?) PlayerPRO versions do not seem to set the loop points to 0xFF (george_megas_-_q.xm) + if((!(instrHeader.instrument.volFlags & XMInstrument::envLoop) && instrHeader.instrument.volLoopStart == 0xFF && instrHeader.instrument.volLoopEnd == 0xFF) + || (!(instrHeader.instrument.panFlags & XMInstrument::envLoop) && instrHeader.instrument.panLoopStart == 0xFF && instrHeader.instrument.panLoopEnd == 0xFF)) + { + madeWith.set(verConfirmed); + madeWith.reset(verNewModPlug); + } } // Read sample headers @@ -809,6 +852,18 @@ sampleSize[sample] = sampleHeader.length; sampleReserved |= sampleHeader.reserved; + if(sampleHeader.reserved != 0 && sampleHeader.reserved != 0xAD) + madeWith.reset(verOldModPlug | verNewModPlug | verOpenMPT); + + if(lastSampleReserved == -1) + lastSampleReserved = sampleHeader.reserved; + else if(lastSampleReserved != sampleHeader.reserved) + madeWith.reset(verPlayerPRO); + if(sampleHeader.pan != 128) + madeWith.reset(verPlayerPRO); + if((sampleHeader.finetune & 0x0F) && sampleHeader.finetune != 127) + madeWith.reset(verPlayerPRO); + if(sample < sampleSlots.size()) { SAMPLEINDEX mptSample = sampleSlots[sample]; @@ -817,6 +872,13 @@ instrHeader.instrument.ApplyAutoVibratoToMPT(Samples[mptSample]); m_szNames[mptSample] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name); + if(madeWith[verFT2Generic | verFT2Clone] && madeWith[verNewModPlug | verPlayerPRO] && !madeWith[verConfirmed] + && (sampleHeader.reserved > 22 || std::find_if(std::begin(sampleHeader.name) + sampleHeader.reserved, std::end(sampleHeader.name), [](char c) { return c != ' '; }) != std::end(sampleHeader.name))) + { + // FT2 stores the sample name length here (it just copies the entire Pascal string, but that string might have ended with spaces even before space-padding it in the file, so we cannot do an exact length comparison) + madeWith.reset(verFT2Generic); + madeWith.set(verFT2Clone | verConfirmed); + } if((sampleHeader.flags & 3) == 3 && madeWith[verNewModPlug]) madeWith.set(verModPlugBidiFlag); @@ -878,6 +940,7 @@ { m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR); madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read midi config: "MIDI" @@ -888,6 +951,7 @@ m_MidiCfg.Sanitize(); hasMidiConfig = true; madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read pattern names: "PNAM" @@ -902,6 +966,7 @@ Patterns[pat].SetName(patName); } madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read channel names: "CNAM" @@ -913,6 +978,7 @@ file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, MAX_CHANNELNAME); } madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read mix plugins information @@ -923,6 +989,7 @@ if(file.GetPosition() != oldPos) { madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } } @@ -932,10 +999,17 @@ { m_dwLastSavedWithVersion = MPT_V("1.11"); madeWithTracker = U_("ModPlug Tracker 1.0 - 1.11"); - } else if(madeWith[verNewModPlug]) + } else if(madeWith[verNewModPlug] && !madeWith[verPlayerPRO]) { m_dwLastSavedWithVersion = MPT_V("1.16"); madeWithTracker = U_("ModPlug Tracker 1.0 - 1.16"); + } else if(madeWith[verNewModPlug] && madeWith[verPlayerPRO]) + { + m_dwLastSavedWithVersion = MPT_V("1.16"); + madeWithTracker = U_("ModPlug Tracker 1.0 - 1.16 / PlayerPRO"); + } else if(!madeWith[verNewModPlug] && madeWith[verPlayerPRO]) + { + madeWithTracker = U_("PlayerPRO"); } } @@ -982,7 +1056,7 @@ if(madeWithTracker.empty()) { - if(madeWith[verDigiTrakker] && sampleReserved == 0 && (instrType ? instrType : -1) == -1) + if(madeWith[verDigiTrakker] && sampleReserved == 0 && (lastInstrType ? lastInstrType : -1) == -1) { madeWithTracker = U_("DigiTrakker"); } else if(madeWith[verFT2Generic]) |