1 /*
2 * Load_itp.cpp
3 * ------------
4 * Purpose: Impulse Tracker Project (ITP) module loader
5 * Notes : Despite its name, ITP is not a format supported by Impulse Tracker.
6 * In fact, it's a format invented by the OpenMPT team to allow people to work
7 * with the IT format, but keeping the instrument files with big samples separate
8 * from the pattern data, to keep the work files small and handy.
9 * The design of the format is quite flawed, though, so it was superseded by
10 * extra functionality in the MPTM format in OpenMPT 1.24.
11 * Authors: OpenMPT Devs
12 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
13 */
14
15
16 #include "stdafx.h"
17 #include "../common/version.h"
18 #include "Loaders.h"
19 #include "ITTools.h"
20 #ifdef MODPLUG_TRACKER
21 // For loading external instruments
22 #include "../mptrack/Moddoc.h"
23 #endif // MODPLUG_TRACKER
24 #ifdef MPT_EXTERNAL_SAMPLES
25 #include "../common/mptFileIO.h"
26 #endif // MPT_EXTERNAL_SAMPLES
27
28 OPENMPT_NAMESPACE_BEGIN
29
30 // Version changelog:
31 // v1.03: - Relative unicode instrument paths instead of absolute ANSI paths
32 // - Per-path variable string length
33 // - Embedded samples are IT-compressed
34 // (rev. 3249)
35 // v1.02: Explicitly updated format to use new instrument flags representation (rev. 483)
36 // v1.01: Added option to embed instrument headers
37
38
39 struct ITPModCommand
40 {
41 uint8le note;
42 uint8le instr;
43 uint8le volcmd;
44 uint8le command;
45 uint8le vol;
46 uint8le param;
operator ModCommandITPModCommand47 operator ModCommand() const
48 {
49 ModCommand result;
50 result.note = (ModCommand::IsNote(note) || ModCommand::IsSpecialNote(note)) ? static_cast<ModCommand::NOTE>(note.get()) : static_cast<ModCommand::NOTE>(NOTE_NONE);
51 result.instr = instr;
52 result.command = (command < MAX_EFFECTS) ? static_cast<EffectCommand>(command.get()) : CMD_NONE;
53 result.volcmd = (volcmd < MAX_VOLCMDS) ? static_cast<VolumeCommand>(volcmd.get()) : VOLCMD_NONE;
54 result.vol = vol;
55 result.param = param;
56 return result;
57 }
58 };
59
60 MPT_BINARY_STRUCT(ITPModCommand, 6)
61
62
63 struct ITPHeader
64 {
65 uint32le magic;
66 uint32le version;
67 };
68
69 MPT_BINARY_STRUCT(ITPHeader, 8)
70
71
ValidateHeader(const ITPHeader & hdr)72 static bool ValidateHeader(const ITPHeader &hdr)
73 {
74 if(hdr.magic != MagicBE(".itp"))
75 {
76 return false;
77 }
78 if(hdr.version < 0x00000100 || hdr.version > 0x00000103)
79 {
80 return false;
81 }
82 return true;
83 }
84
85
GetHeaderMinimumAdditionalSize(const ITPHeader & hdr)86 static uint64 GetHeaderMinimumAdditionalSize(const ITPHeader &hdr)
87 {
88 return 76 + (hdr.version <= 0x102 ? 4 : 0);
89 }
90
91
ProbeFileHeaderITP(MemoryFileReader file,const uint64 * pfilesize)92 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderITP(MemoryFileReader file, const uint64 *pfilesize)
93 {
94 ITPHeader hdr;
95 if(!file.ReadStruct(hdr))
96 {
97 return ProbeWantMoreData;
98 }
99 if(!ValidateHeader(hdr))
100 {
101 return ProbeFailure;
102 }
103 return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(hdr));
104 }
105
106
ReadITP(FileReader & file,ModLoadingFlags loadFlags)107 bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags)
108 {
109 #if !defined(MPT_EXTERNAL_SAMPLES) && !defined(MPT_FUZZ_TRACKER)
110 // Doesn't really make sense to support this format when there's no support for external files...
111 MPT_UNREFERENCED_PARAMETER(file);
112 MPT_UNREFERENCED_PARAMETER(loadFlags);
113 return false;
114 #else // !MPT_EXTERNAL_SAMPLES && !MPT_FUZZ_TRACKER
115
116 enum ITPSongFlags
117 {
118 ITP_EMBEDMIDICFG = 0x00001, // Embed macros in file
119 ITP_ITOLDEFFECTS = 0x00004, // Old Impulse Tracker effect implementations
120 ITP_ITCOMPATGXX = 0x00008, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects)
121 ITP_LINEARSLIDES = 0x00010, // Linear slides vs. Amiga slides
122 ITP_EXFILTERRANGE = 0x08000, // Cutoff Filter has double frequency range (up to ~10Khz)
123 ITP_ITPROJECT = 0x20000, // Is a project file
124 ITP_ITPEMBEDIH = 0x40000, // Embed instrument headers in project file
125 };
126
127 file.Rewind();
128
129 ITPHeader hdr;
130 if(!file.ReadStruct(hdr))
131 {
132 return false;
133 }
134 if(!ValidateHeader(hdr))
135 {
136 return false;
137 }
138 if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(hdr))))
139 {
140 return false;
141 }
142 if(loadFlags == onlyVerifyHeader)
143 {
144 return true;
145 }
146
147 const uint32 version = hdr.version;
148
149 InitializeGlobals(MOD_TYPE_IT);
150 m_playBehaviour.reset();
151 file.ReadSizedString<uint32le, mpt::String::maybeNullTerminated>(m_songName);
152
153 // Song comments
154 m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR);
155
156 // Song global config
157 const uint32 songFlags = file.ReadUint32LE();
158 if(!(songFlags & ITP_ITPROJECT))
159 {
160 return false;
161 }
162 m_SongFlags.set(SONG_IMPORTED);
163 if(songFlags & ITP_ITOLDEFFECTS)
164 m_SongFlags.set(SONG_ITOLDEFFECTS);
165 if(songFlags & ITP_ITCOMPATGXX)
166 m_SongFlags.set(SONG_ITCOMPATGXX);
167 if(songFlags & ITP_LINEARSLIDES)
168 m_SongFlags.set(SONG_LINEARSLIDES);
169 if(songFlags & ITP_EXFILTERRANGE)
170 m_SongFlags.set(SONG_EXFILTERRANGE);
171
172 m_nDefaultGlobalVolume = file.ReadUint32LE();
173 m_nSamplePreAmp = file.ReadUint32LE();
174 m_nDefaultSpeed = std::max(uint32(1), file.ReadUint32LE());
175 m_nDefaultTempo.Set(std::max(uint32(32), file.ReadUint32LE()));
176 m_nChannels = static_cast<CHANNELINDEX>(file.ReadUint32LE());
177 if(m_nChannels == 0 || m_nChannels > MAX_BASECHANNELS)
178 {
179 return false;
180 }
181
182 // channel name string length (=MAX_CHANNELNAME)
183 uint32 size = file.ReadUint32LE();
184
185 // Channels' data
186 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
187 {
188 ChnSettings[chn].nPan = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(256));
189 ChnSettings[chn].dwFlags.reset();
190 uint32 flags = file.ReadUint32LE();
191 if(flags & 0x100) ChnSettings[chn].dwFlags.set(CHN_MUTE);
192 if(flags & 0x800) ChnSettings[chn].dwFlags.set(CHN_SURROUND);
193 ChnSettings[chn].nVolume = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(64));
194 file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, size);
195 }
196
197 // Song mix plugins
198 {
199 FileReader plugChunk = file.ReadChunk(file.ReadUint32LE());
200 LoadMixPlugins(plugChunk);
201 }
202
203 // MIDI Macro config
204 file.ReadStructPartial<MIDIMacroConfigData>(m_MidiCfg, file.ReadUint32LE());
205 m_MidiCfg.Sanitize();
206
207 // Song Instruments
208 m_nInstruments = static_cast<INSTRUMENTINDEX>(file.ReadUint32LE());
209 if(m_nInstruments >= MAX_INSTRUMENTS)
210 {
211 m_nInstruments = 0;
212 return false;
213 }
214
215 // Instruments' paths
216 if(version <= 0x102)
217 {
218 size = file.ReadUint32LE(); // path string length
219 }
220
221 std::vector<mpt::PathString> instrPaths(GetNumInstruments());
222 for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
223 {
224 if(version > 0x102)
225 {
226 size = file.ReadUint32LE(); // path string length
227 }
228 std::string path;
229 file.ReadString<mpt::String::maybeNullTerminated>(path, size);
230 #ifdef MODPLUG_TRACKER
231 if(version <= 0x102)
232 {
233 instrPaths[ins] = mpt::PathString::FromLocaleSilent(path);
234 } else
235 #endif // MODPLUG_TRACKER
236 {
237 instrPaths[ins] = mpt::PathString::FromUTF8(path);
238 }
239 #ifdef MODPLUG_TRACKER
240 if(const auto fileName = file.GetOptionalFileName(); fileName.has_value())
241 {
242 instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(fileName->GetPath());
243 } else if(GetpModDoc() != nullptr)
244 {
245 instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(GetpModDoc()->GetPathNameMpt().GetPath());
246 }
247 #endif // MODPLUG_TRACKER
248 }
249
250 // Song Orders
251 size = file.ReadUint32LE();
252 ReadOrderFromFile<uint8>(Order(), file, size, 0xFF, 0xFE);
253
254 // Song Patterns
255 const PATTERNINDEX numPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
256 const PATTERNINDEX numNamedPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
257 size_t patNameLen = file.ReadUint32LE(); // Size of each pattern name
258 FileReader pattNames = file.ReadChunk(numNamedPats * patNameLen);
259
260 // modcommand data length
261 size = file.ReadUint32LE();
262 if(size != sizeof(ITPModCommand))
263 {
264 return false;
265 }
266
267 if(loadFlags & loadPatternData)
268 Patterns.ResizeArray(numPats);
269 for(PATTERNINDEX pat = 0; pat < numPats; pat++)
270 {
271 const ROWINDEX numRows = file.ReadUint32LE();
272 FileReader patternChunk = file.ReadChunk(numRows * size * GetNumChannels());
273
274 // Allocate pattern
275 if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows))
276 {
277 pattNames.Skip(patNameLen);
278 continue;
279 }
280
281 if(pat < numNamedPats)
282 {
283 char patName[32];
284 pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen);
285 Patterns[pat].SetName(patName);
286 }
287
288 // Pattern data
289 size_t numCommands = GetNumChannels() * numRows;
290
291 if(patternChunk.CanRead(sizeof(ITPModCommand) * numCommands))
292 {
293 ModCommand *target = Patterns[pat].GetpModCommand(0, 0);
294 while(numCommands-- != 0)
295 {
296 ITPModCommand data;
297 patternChunk.ReadStruct(data);
298 *(target++) = data;
299 }
300 }
301 }
302
303 // Load embedded samples
304
305 // Read original number of samples
306 m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint32LE());
307 LimitMax(m_nSamples, SAMPLEINDEX(MAX_SAMPLES - 1));
308
309 // Read number of embedded samples - at most as many as there are real samples in a valid file
310 uint32 embeddedSamples = file.ReadUint32LE();
311 if(embeddedSamples > m_nSamples)
312 {
313 return false;
314 }
315
316 // Read samples
317 for(uint32 smp = 0; smp < embeddedSamples && file.CanRead(8 + sizeof(ITSample)); smp++)
318 {
319 uint32 realSample = file.ReadUint32LE();
320 ITSample sampleHeader;
321 file.ReadStruct(sampleHeader);
322 FileReader sampleData = file.ReadChunk(file.ReadUint32LE());
323
324 if((loadFlags & loadSampleData)
325 && realSample >= 1 && realSample <= GetNumSamples()
326 && Samples[realSample].pData.pSample == nullptr
327 && !memcmp(sampleHeader.id, "IMPS", 4))
328 {
329 sampleHeader.ConvertToMPT(Samples[realSample]);
330 m_szNames[realSample] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name);
331
332 // Read sample data
333 sampleHeader.GetSampleFormat().ReadSample(Samples[realSample], sampleData);
334 }
335 }
336
337 // Load instruments
338 for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
339 {
340 if(instrPaths[ins].empty())
341 continue;
342
343 #ifdef MPT_EXTERNAL_SAMPLES
344 InputFile f(instrPaths[ins], SettingCacheCompleteFileBeforeLoading());
345 FileReader instrFile = GetFileReader(f);
346 if(!ReadInstrumentFromFile(ins + 1, instrFile, true))
347 {
348 AddToLog(LogWarning, U_("Unable to open instrument: ") + instrPaths[ins].ToUnicode());
349 }
350 #else
351 AddToLog(LogWarning, MPT_UFORMAT("Loading external instrument {} ('{}') failed: External instruments are not supported.")(ins + 1, instrPaths[ins].ToUnicode()));
352 #endif // MPT_EXTERNAL_SAMPLES
353 }
354
355 // Extra info data
356 uint32 code = file.ReadUint32LE();
357
358 // Embed instruments' header [v1.01]
359 if(version >= 0x101 && (songFlags & ITP_ITPEMBEDIH) && code == MagicBE("EBIH"))
360 {
361 code = file.ReadUint32LE();
362
363 INSTRUMENTINDEX ins = 1;
364 while(ins <= GetNumInstruments() && file.CanRead(4))
365 {
366 if(code == MagicBE("MPTS"))
367 {
368 break;
369 } else if(code == MagicBE("SEP@") || code == MagicBE("MPTX"))
370 {
371 // jump code - switch to next instrument
372 ins++;
373 } else
374 {
375 ReadExtendedInstrumentProperty(Instruments[ins], code, file);
376 }
377
378 code = file.ReadUint32LE();
379 }
380 }
381
382 // Song extensions
383 if(code == MagicBE("MPTS"))
384 {
385 file.SkipBack(4);
386 LoadExtendedSongProperties(file, true);
387 }
388
389 m_nMaxPeriod = 0xF000;
390 m_nMinPeriod = 8;
391
392 // Before OpenMPT 1.20.01.09, the MIDI macros were always read from the file, even if the "embed" flag was not set.
393 if(m_dwLastSavedWithVersion >= MPT_V("1.20.01.09") && !(songFlags & ITP_EMBEDMIDICFG))
394 {
395 m_MidiCfg.Reset();
396 }
397
398 m_modFormat.formatName = U_("Impulse Tracker Project");
399 m_modFormat.type = U_("itp");
400 m_modFormat.madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion);
401 m_modFormat.charset = mpt::Charset::Windows1252;
402
403 return true;
404 #endif // MPT_EXTERNAL_SAMPLES
405 }
406
407 OPENMPT_NAMESPACE_END
408