From: <sv...@op...> - 2024-05-11 15:32:10
|
Author: sagamusix Date: Sat May 11 17:31:57 2024 New Revision: 20759 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=20759 Log: Merged revision(s) 20744 from trunk/OpenMPT: [Mod] XM: All MPT versions up to v1.11 set both normal and pingpong loop flags for pingpong-looped samples, not just MPT 1.09. Apart from this, a file made with MPT 1.06 or newer will be bit-identical when re-saved in MPT 1.16. Make the version reporting more precise. ........ Merged revision(s) 20747-20748 from trunk/OpenMPT: [Imp] XM: Improve detection of MPT-made files, and tell them apart from files created with PlayerPRO. ........ [Var] XM: Document more cases of unusal instrument header sizes. ........ Modified: branches/OpenMPT-1.31/ (props changed) branches/OpenMPT-1.31/soundlib/Load_xm.cpp Modified: branches/OpenMPT-1.31/soundlib/Load_xm.cpp ============================================================================== --- branches/OpenMPT-1.31/soundlib/Load_xm.cpp Sat May 11 17:30:11 2024 (r20758) +++ branches/OpenMPT-1.31/soundlib/Load_xm.cpp Sat May 11 17:31:57 2024 (r20759) @@ -359,19 +359,20 @@ enum TrackerVersions { - verUnknown = 0x00, // Probably not made with MPT - verOldModPlug = 0x01, // Made with MPT Alpha / Beta - verNewModPlug = 0x02, // Made with MPT (not Alpha / Beta) - verModPlug1_09 = 0x04, // Made with MPT 1.09 or possibly other version - verOpenMPT = 0x08, // Made with OpenMPT - verConfirmed = 0x10, // We are very sure that we found the correct tracker version. - - 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) + verUnknown = 0x00, // Probably not made with MPT + verOldModPlug = 0x01, // Made with MPT Alpha / Beta + verNewModPlug = 0x02, // Made with MPT (not Alpha / Beta) + verModPlugBidiFlag = 0x04, // MPT up to v1.11 sets both normal loop and pingpong loop flags + verOpenMPT = 0x08, // Made with OpenMPT + verConfirmed = 0x10, // We are very sure that we found the correct tracker version. + + 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 + 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) @@ -606,13 +607,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) @@ -673,10 +691,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]) @@ -696,9 +712,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++) @@ -744,11 +762,25 @@ 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. + // 4-mat's eternity.xm has an empty instruments with a header size of 29. + // Another module using that size is funky_dumbass.xm. Mysterious! + // Note: This may happen when the XM Commenter by Aka (XMC.EXE) adds empty instruments at the end of the list, + // which would explain the latter case, but in eternity.xm the empty slots are not at the end of the list. 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. + if(instrumentWithSamplesEncountered || (lastSampleHeaderSize != -1 && instrHeader.sampleHeaderSize != lastSampleHeaderSize)) + madeWith = verPlayerPRO | verConfirmed; + lastSampleHeaderSize = instrHeader.sampleHeaderSize; + } } if(AllocateInstrument(instr) == nullptr) @@ -758,24 +790,38 @@ 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. + // Note: This may happen when running an FT2-made XM through PutInst and adding new instrument slots. madeWith.reset(verFT2Generic); madeWith.set(verFT2Clone); } 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 @@ -810,6 +856,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]; @@ -818,12 +876,16 @@ instrHeader.instrument.ApplyAutoVibratoToMPT(Samples[mptSample]); m_szNames[mptSample] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name); - - if((sampleHeader.flags & 3) == 3 && madeWith[verNewModPlug]) + 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))) { - // MPT 1.09 and maybe newer / older versions set both loop flags for bidi loops. - madeWith.set(verModPlug1_09); + // 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); } if(sampleFlags.back().GetEncoding() == SampleIO::ADPCM) anyADPCM = true; @@ -882,6 +944,7 @@ { m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR); madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read midi config: "MIDI" @@ -892,6 +955,7 @@ m_MidiCfg.Sanitize(); hasMidiConfig = true; madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read pattern names: "PNAM" @@ -906,6 +970,7 @@ Patterns[pat].SetName(patName); } madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read channel names: "CNAM" @@ -917,6 +982,7 @@ file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, MAX_CHANNELNAME); } madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } // Read mix plugins information @@ -927,19 +993,27 @@ if(file.GetPosition() != oldPos) { madeWith.set(verConfirmed); + madeWith.reset(verPlayerPRO); } } if(madeWith[verConfirmed]) { - if(madeWith[verModPlug1_09]) + if(madeWith[verModPlugBidiFlag]) { - m_dwLastSavedWithVersion = MPT_V("1.09.00.00"); - madeWithTracker = U_("ModPlug Tracker 1.09"); - } else if(madeWith[verNewModPlug]) + m_dwLastSavedWithVersion = MPT_V("1.11"); + madeWithTracker = U_("ModPlug Tracker 1.0 - 1.11"); + } else if(madeWith[verNewModPlug] && !madeWith[verPlayerPRO]) { m_dwLastSavedWithVersion = MPT_V("1.16.00.00"); - madeWithTracker = U_("ModPlug Tracker 1.10 - 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"); } } @@ -986,7 +1060,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]) |