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