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 &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(version, iffHead.chunksize - 6); + } + break; + + case PT36IffChunk::idINFO: + chunk.ReadString(title, iffHead.chunksize); + break; + + case PT36IffChunk::idCMNT: + chunk.ReadString(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" },