View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000752 | OpenMPT | File Format Support | public | 2016-02-27 02:56 | 2016-03-18 19:04 |
Reporter | Revenant | Assigned To | Saga Musix | ||
Priority | normal | Severity | minor | Reproducibility | N/A |
Status | resolved | Resolution | fixed | ||
Product Version | OpenMPT 1.25.04.00 / libopenmpt 0.2-beta16 (upgrade first) | ||||
Target Version | OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) | Fixed in Version | OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) | ||
Summary | 0000752: ProTracker 3.6 | ||||
Description | For some reason or another, ProTracker 3.6x supports saving modules inside of IFF containers. Aside from the very small amount of extra metadata, the format is basically identical, so I wrote a patch to load them (with the regular .mod loader still doing nearly all of the actual work). There are a whopping 15 of these on Modland, but I attached 3 as examples. (Also not really related but I noticed that the option to associate .st26 files was missing from the install script so I threw that in too) | ||||
Tags | No tags attached. | ||||
Attached Files | pt36.patch (8,483 bytes)
diff --git OpenMPT/installer/filetypes.iss OpenMPT/installer/filetypes.iss index d9826e4..057a170 100644 --- OpenMPT/installer/filetypes.iss +++ OpenMPT/installer/filetypes.iss @@ -40,7 +40,9 @@ Name: "associate_exotic\mtm"; Description: "MultiTracker Modules (MTM)"; Name: "associate_exotic\okt"; Description: "Oktalyzer (OKT)"; Name: "associate_exotic\plm"; Description: "Disorder Tracker 2 (PLM)"; Name: "associate_exotic\psm"; Description: "Epic Megagames MASI (PSM)"; +Name: "associate_exotic\pt36"; Description: "ProTracker 3.6 (PT36)"; Name: "associate_exotic\ptm"; Description: "PolyTracker (PTM)"; +Name: "associate_exotic\st26"; Description: "SoundTracker 2.6 (ST26)"; Name: "associate_exotic\stm"; Description: "Scream Tracker 2 (STM)"; Name: "associate_exotic\ult"; Description: "UltraTracker (ULT)"; Name: "associate_exotic\umx"; Description: "Unreal Music (UMX)"; @@ -83,7 +85,9 @@ Root: HKCR; Subkey: ".mtm"; ValueType: string; ValueName: ""; ValueData: "OpenMP Root: HKCR; Subkey: ".okt"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\okt Root: HKCR; Subkey: ".plm"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\plm Root: HKCR; Subkey: ".psm"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\psm +Root: HKCR; Subkey: ".pt36"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\pt36 Root: HKCR; Subkey: ".ptm"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\ptm +Root: HKCR; Subkey: ".st26"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\st26 Root: HKCR; Subkey: ".stm"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\stm Root: HKCR; Subkey: ".ult"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\ult Root: HKCR; Subkey: ".umx"; ValueType: string; ValueName: ""; ValueData: "OpenMPTFile"; Flags: uninsdeletevalue; Tasks: associate_exotic\umx diff --git OpenMPT/libopenmpt/foo_openmpt.cpp OpenMPT/libopenmpt/foo_openmpt.cpp index 814a8ae..ac7ce34 100644 --- OpenMPT/libopenmpt/foo_openmpt.cpp +++ OpenMPT/libopenmpt/foo_openmpt.cpp @@ -295,6 +295,7 @@ DECLARE_FILE_TYPE("OpenMPT compatible module files", "*.m15" ";" "*.stk" ";" "*.st26" ";" + "*.pt36" ";" "*.ice" ";" "*.wow" ";" "*.ult" ";" diff --git OpenMPT/mptrack/Mptrack.cpp OpenMPT/mptrack/Mptrack.cpp index 4ba9aac..5aff1aa 100644 --- OpenMPT/mptrack/Mptrack.cpp +++ OpenMPT/mptrack/Mptrack.cpp @@ -1377,7 +1377,7 @@ void CTrackApp::OpenModulesDialog(std::vector<mpt::PathString> &files) ";*.mo3" #endif "|" - "ProTracker Modules (*.mod,*.nst)|*.mod;mod.*;*.mdz;*.nst;*.m15;*.stk|" + "ProTracker Modules (*.mod,*.nst)|*.mod;mod.*;*.mdz;*.nst;*.m15;*.stk;*.pt36|" "ScreamTracker Modules (*.s3m,*.stm)|*.s3m;*.stm;*.s3z|" "FastTracker Modules (*.xm)|*.xm;*.xmz|" "Impulse Tracker Modules (*.it)|*.it;*.itz|" diff --git OpenMPT/soundlib/Load_mod.cpp OpenMPT/soundlib/Load_mod.cpp index 02793ff..1aa8ab7 100644 --- OpenMPT/soundlib/Load_mod.cpp +++ OpenMPT/soundlib/Load_mod.cpp @@ -334,6 +334,29 @@ struct PACKED MODSampleHeader STATIC_ASSERT(sizeof(MODSampleHeader) == 30); +struct PACKED PT36IffChunk +{ + // IFF chunk names + enum ChunkIdentifiers + { + idVERS = MAGIC4BE('V','E','R','S'), + idINFO = MAGIC4BE('I','N','F','O'), + idCMNT = MAGIC4BE('C','M','N','T'), + idPTDT = MAGIC4BE('P','T','D','T'), + }; + + uint32 signature; // IFF chunk name + uint32 chunksize; // chunk size without header + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesBE(signature); + SwapBytesBE(chunksize); + } +}; + +STATIC_ASSERT(sizeof(PT36IffChunk) == 8); #ifdef NEEDS_PRAGMA_PACK #pragma pack(pop) @@ -490,6 +513,7 @@ bool CSoundFile::ReadMod(FileReader &file, ModLoadingFlags loadFlags) // Check MOD Magic if(IsMagic(magic, "M.K.") // ProTracker and compatible || IsMagic(magic, "M!K!") // ProTracker (64+ patterns) + || IsMagic(magic, "PATT") // ProTracker 3.6 || IsMagic(magic, "NSMS") // kingdomofpleasure.mod by bee hunter || IsMagic(magic, "LARD")) // judgement_day_gvine.mod by 4-mat { @@ -1388,6 +1412,89 @@ bool CSoundFile::ReadICE(FileReader &file, ModLoadingFlags loadFlags) } + +// ProTracker 3.6 version of the MOD format +// Basically just a normal ProTracker mod with different magic, wrapped in an IFF file. +// The "PTDT" chunk is passed to the normal MOD loader. +bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) +//------------------------------------------------------------------- +{ + file.Rewind(); + if(!file.ReadMagic("FORM")) + { + return false; + } + + file.Skip(4); + if(!file.ReadMagic("MODL")) + { + return false; + } + + bool ok = false; + std::string title, message; + std::string version = "3.6"; + + // Go through IFF chunks... + PT36IffChunk iffHead; + if(!file.ReadConvertEndianness(iffHead)) + { + return false; + } + // first chunk includes "MODL" magic in size + iffHead.chunksize -= 4; + + do + { + // all chunk sizes include chunk header + iffHead.chunksize -= 8; + + FileReader chunk = file.ReadChunk(iffHead.chunksize); + if(!chunk.IsValid()) + { + break; + } + + switch(iffHead.signature) + { + case PT36IffChunk::idVERS: + chunk.Skip(4); + if(chunk.ReadMagic("PT")) + { + chunk.ReadString<mpt::String::maybeNullTerminated>(version, iffHead.chunksize - 6); + } + break; + + case PT36IffChunk::idINFO: + chunk.ReadString<mpt::String::maybeNullTerminated>(title, iffHead.chunksize); + break; + + case PT36IffChunk::idCMNT: + chunk.ReadString<mpt::String::maybeNullTerminated>(message, iffHead.chunksize); + break; + + case PT36IffChunk::idPTDT: + ok = ReadMod(chunk, loadFlags); + break; + } + } while(file.CanRead(sizeof(PT36IffChunk)) && file.ReadConvertEndianness(iffHead)); + + if(ok) + { + if(!title.empty()) + m_songName = title; + + // "message" chunk seems to only be used to store the artist name, despite being pretty long + if(message != "UNNAMED AUTHOR") + m_songArtist = mpt::ToUnicode(mpt::CharsetISO8859_1, message); + + m_madeWithTracker = "ProTracker "; + m_madeWithTracker += version; + } + + return ok; +} + #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveMod(const mpt::PathString &filename) const diff --git OpenMPT/soundlib/Sndfile.cpp OpenMPT/soundlib/Sndfile.cpp index 8930a6e..6672dfe 100644 --- OpenMPT/soundlib/Sndfile.cpp +++ OpenMPT/soundlib/Sndfile.cpp @@ -316,7 +316,8 @@ bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags) && !ReadMod(file, loadFlags) && !Read669(file, loadFlags) && !ReadICE(file, loadFlags) - && !ReadM15(file, loadFlags)) + && !ReadM15(file, loadFlags) + && !ReadPT36(file, loadFlags)) { m_nType = MOD_TYPE_NONE; m_ContainerType = MOD_CONTAINERTYPE_NONE; diff --git OpenMPT/soundlib/Sndfile.h OpenMPT/soundlib/Sndfile.h index 4223d23..6ab175d 100644 --- OpenMPT/soundlib/Sndfile.h +++ OpenMPT/soundlib/Sndfile.h @@ -619,6 +619,7 @@ public: bool ReadMod(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadM15(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadICE(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadPT36(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMed(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); diff --git OpenMPT/soundlib/Tables.cpp OpenMPT/soundlib/Tables.cpp index f446311..93ec4b3 100644 --- OpenMPT/soundlib/Tables.cpp +++ OpenMPT/soundlib/Tables.cpp @@ -59,6 +59,7 @@ static const ModFormatInfo modFormatInfo[] = #endif { MOD_TYPE_MPT, "OpenMPT", "mptm" }, { MOD_TYPE_STM, "ScreamTracker 2", "stm" }, + { MOD_TYPE_MOD, "ProTracker", "pt36" }, { MOD_TYPE_MOD, "NoiseTracker", "nst" }, { MOD_TYPE_MOD, "Soundtracker", "m15" }, { MOD_TYPE_MOD, "Soundtracker", "stk" }, pt36info.patch (3,172 bytes)
diff --git OpenMPT/soundlib/Load_mod.cpp OpenMPT/soundlib/Load_mod.cpp index 26e12a2..757a3c5 100644 --- OpenMPT/soundlib/Load_mod.cpp +++ OpenMPT/soundlib/Load_mod.cpp @@ -358,6 +358,50 @@ struct PACKED PT36IffChunk STATIC_ASSERT(sizeof(PT36IffChunk) == 8); +struct PACKED PT36InfoChunk +{ + char name[32]; + uint16 numSamples; // unused + uint16 numOrders; // unused + uint16 numPatterns; // unused + uint16 volume; + uint16 tempo; + uint16 flags; // unused + uint16 dateDay; + uint16 dateMonth; + uint16 dateYear; + uint16 dateHour; + uint16 dateMinute; + uint16 dateSecond; + uint16 playtimeHour; // unused + uint16 playtimeMinute; // unused + uint16 playtimeSecond; // unused + uint16 playtimeMsecond; // unused + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesBE(numSamples); + SwapBytesBE(numOrders); + SwapBytesBE(numPatterns); + SwapBytesBE(volume); + SwapBytesBE(tempo); + SwapBytesBE(flags); + SwapBytesBE(dateDay); + SwapBytesBE(dateMonth); + SwapBytesBE(dateYear); + SwapBytesBE(dateHour); + SwapBytesBE(dateMinute); + SwapBytesBE(dateSecond); + SwapBytesBE(playtimeHour); + SwapBytesBE(playtimeMinute); + SwapBytesBE(playtimeSecond); + SwapBytesBE(playtimeMsecond); + } +}; + +STATIC_ASSERT(sizeof(PT36InfoChunk) == 64); + #ifdef NEEDS_PRAGMA_PACK #pragma pack(pop) #endif @@ -1441,9 +1485,10 @@ bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) return false; } - bool ok = false; + bool ok = false, infoOk = false; std::string title, message; std::string version = "3.6"; + PT36InfoChunk info; // Go through IFF chunks... PT36IffChunk iffHead; @@ -1480,7 +1525,7 @@ bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) break; case PT36IffChunk::idINFO: - chunk.ReadString<mpt::String::maybeNullTerminated>(title, iffHead.chunksize); + infoOk = chunk.ReadConvertEndianness(info); break; case PT36IffChunk::idCMNT: @@ -1493,11 +1538,26 @@ bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) } } while(file.CanRead(sizeof(PT36IffChunk)) && file.ReadConvertEndianness(iffHead)); - if(ok) + // both an info chunk and a module are required + if(ok && infoOk) { + m_nSamplePreAmp = MIN(64, info.volume); + m_nDefaultTempo.Set(info.tempo); + + mpt::String::Read<mpt::String::maybeNullTerminated>(title, info.name); if(!title.empty()) m_songName = title; + FileHistory mptHistory; + MemsetZero(mptHistory.loadDate); + mptHistory.loadDate.tm_year = info.dateYear; + mptHistory.loadDate.tm_mon = Clamp(info.dateMonth, 1, 12) - 1; + mptHistory.loadDate.tm_mday = Clamp(info.dateDay, 1, 31); + mptHistory.loadDate.tm_hour = Clamp(info.dateHour, 0, 23); + mptHistory.loadDate.tm_min = Clamp(info.dateMinute, 0, 59); + mptHistory.loadDate.tm_sec = Clamp(info.dateSecond, 0, 59); + m_FileHistory.push_back(mptHistory); + // "message" chunk seems to only be used to store the artist name, despite being pretty long if(message != "UNNAMED AUTHOR") m_songArtist = mpt::ToUnicode(mpt::CharsetISO8859_1, message); | ||||
Has the bug occurred in previous versions? | |||||
Tested code revision (in case you know it) | |||||
Merged with some modifications in r6024. |
|
Sure, my real name is Devin Acker. If you also want a web address there's http://revenant1.net (which is just a redirect to my github profile for now until I finish designing a new site proper). |
|
One more patch... This handles the "INFO" chunk more correctly, based on the format description here: The document also describes an alternate module format based on pattern/instrument/sample chunks, but these were never actually implemented, so the loader ignores them (as well as INFO chunk fields which are not really useful for the regular format). |
|
Applied in r6143 with some slight tweaks. |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2016-02-27 02:56 | Revenant | New Issue | |
2016-02-27 02:56 | Revenant | File Added: pt36.patch | |
2016-02-27 02:56 | Revenant | File Added: Protracker 3.6.rar | |
2016-02-27 15:39 | Saga Musix | Assigned To | => Saga Musix |
2016-02-27 15:39 | Saga Musix | Status | new => assigned |
2016-03-03 01:29 | Saga Musix | Note Added: 0002271 | |
2016-03-03 01:29 | Saga Musix | Status | assigned => feedback |
2016-03-03 01:30 | Saga Musix | Product Version | => OpenMPT 1.25.04.00 / libopenmpt 0.2-beta16 (upgrade first) |
2016-03-03 01:30 | Saga Musix | Fixed in Version | => OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) |
2016-03-03 01:30 | Saga Musix | Target Version | => OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) |
2016-03-05 03:56 | Revenant | Note Added: 0002273 | |
2016-03-05 03:56 | Revenant | Status | feedback => assigned |
2016-03-05 10:19 | Saga Musix | Status | assigned => resolved |
2016-03-05 10:19 | Saga Musix | Resolution | open => fixed |
2016-03-18 01:47 | Revenant | Note Added: 0002290 | |
2016-03-18 01:47 | Revenant | Status | resolved => feedback |
2016-03-18 01:47 | Revenant | Resolution | fixed => reopened |
2016-03-18 01:52 | Saga Musix | Status | feedback => assigned |
2016-03-18 05:26 | Revenant | File Added: pt36info.patch | |
2016-03-18 05:27 | Revenant | Note Edited: 0002290 | |
2016-03-18 19:04 | Saga Musix | Note Added: 0002296 | |
2016-03-18 19:04 | Saga Musix | Status | assigned => resolved |
2016-03-18 19:04 | Saga Musix | Resolution | reopened => fixed |