1 /*
2  * Load_xm.cpp
3  * -----------
4  * Purpose: XM (FastTracker II) module loader / saver
5  * Notes  : (currently none)
6  * Authors: Olivier Lapicque
7  *          OpenMPT Devs
8  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
9  */
10 
11 
12 #include "stdafx.h"
13 #include "Loaders.h"
14 #include "../common/version.h"
15 #include "XMTools.h"
16 #include "mod_specifications.h"
17 #ifndef MODPLUG_NO_FILESAVE
18 #include "mpt/io/base.hpp"
19 #include "mpt/io/io.hpp"
20 #include "mpt/io/io_stdstream.hpp"
21 #include "../common/mptFileIO.h"
22 #endif
23 #include "OggStream.h"
24 #include <algorithm>
25 #ifdef MODPLUG_TRACKER
26 #include "../mptrack/TrackerSettings.h"	// For super smooth ramping option
27 #endif // MODPLUG_TRACKER
28 #include "mpt/audio/span.hpp"
29 
30 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
31 #include <sstream>
32 #endif
33 
34 #if defined(MPT_WITH_VORBIS)
35 #if MPT_COMPILER_CLANG
36 #pragma clang diagnostic push
37 #pragma clang diagnostic ignored "-Wreserved-id-macro"
38 #endif // MPT_COMPILER_CLANG
39 #include <vorbis/codec.h>
40 #if MPT_COMPILER_CLANG
41 #pragma clang diagnostic pop
42 #endif // MPT_COMPILER_CLANG
43 #endif
44 
45 #if defined(MPT_WITH_VORBISFILE)
46 #if MPT_COMPILER_CLANG
47 #pragma clang diagnostic push
48 #pragma clang diagnostic ignored "-Wreserved-id-macro"
49 #endif // MPT_COMPILER_CLANG
50 #include <vorbis/vorbisfile.h>
51 #if MPT_COMPILER_CLANG
52 #pragma clang diagnostic pop
53 #endif // MPT_COMPILER_CLANG
54 #include "openmpt/soundbase/Copy.hpp"
55 #endif
56 
57 #ifdef MPT_WITH_STBVORBIS
58 #include <stb_vorbis/stb_vorbis.c>
59 #include "openmpt/soundbase/Copy.hpp"
60 #endif // MPT_WITH_STBVORBIS
61 
62 
63 OPENMPT_NAMESPACE_BEGIN
64 
65 
66 
67 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
68 
VorbisfileFilereaderRead(void * ptr,size_t size,size_t nmemb,void * datasource)69 static size_t VorbisfileFilereaderRead(void *ptr, size_t size, size_t nmemb, void *datasource)
70 {
71 	FileReader &file = *reinterpret_cast<FileReader*>(datasource);
72 	return file.ReadRaw(mpt::span(mpt::void_cast<std::byte*>(ptr), size * nmemb)).size() / size;
73 }
74 
VorbisfileFilereaderSeek(void * datasource,ogg_int64_t offset,int whence)75 static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int whence)
76 {
77 	FileReader &file = *reinterpret_cast<FileReader*>(datasource);
78 	switch(whence)
79 	{
80 	case SEEK_SET:
81 		{
82 			if(!mpt::in_range<FileReader::off_t>(offset))
83 			{
84 				return -1;
85 			}
86 			return file.Seek(mpt::saturate_cast<FileReader::off_t>(offset)) ? 0 : -1;
87 		}
88 		break;
89 	case SEEK_CUR:
90 		{
91 			if(offset < 0)
92 			{
93 				if(offset == std::numeric_limits<ogg_int64_t>::min())
94 				{
95 					return -1;
96 				}
97 				if(!mpt::in_range<FileReader::off_t>(0-offset))
98 				{
99 					return -1;
100 				}
101 				return file.SkipBack(mpt::saturate_cast<FileReader::off_t>(0 - offset)) ? 0 : -1;
102 			} else
103 			{
104 				if(!mpt::in_range<FileReader::off_t>(offset))
105 				{
106 					return -1;
107 				}
108 				return file.Skip(mpt::saturate_cast<FileReader::off_t>(offset)) ? 0 : -1;
109 			}
110 		}
111 		break;
112 	case SEEK_END:
113 		{
114 			if(!mpt::in_range<FileReader::off_t>(offset))
115 			{
116 				return -1;
117 			}
118 			if(!mpt::in_range<FileReader::off_t>(file.GetLength() + offset))
119 			{
120 				return -1;
121 			}
122 			return file.Seek(mpt::saturate_cast<FileReader::off_t>(file.GetLength() + offset)) ? 0 : -1;
123 		}
124 		break;
125 	default:
126 		return -1;
127 	}
128 }
129 
VorbisfileFilereaderTell(void * datasource)130 static long VorbisfileFilereaderTell(void *datasource)
131 {
132 	FileReader &file = *reinterpret_cast<FileReader*>(datasource);
133 	FileReader::off_t result = file.GetPosition();
134 	if(!mpt::in_range<long>(result))
135 	{
136 		return -1;
137 	}
138 	return static_cast<long>(result);
139 }
140 
141 #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE
142 
143 
144 // Allocate samples for an instrument
AllocateXMSamples(CSoundFile & sndFile,SAMPLEINDEX numSamples)145 static std::vector<SAMPLEINDEX> AllocateXMSamples(CSoundFile &sndFile, SAMPLEINDEX numSamples)
146 {
147 	LimitMax(numSamples, SAMPLEINDEX(32));
148 
149 	std::vector<SAMPLEINDEX> foundSlots;
150 	foundSlots.reserve(numSamples);
151 
152 	for(SAMPLEINDEX i = 0; i < numSamples; i++)
153 	{
154 		SAMPLEINDEX candidateSlot = sndFile.GetNumSamples() + 1;
155 
156 		if(candidateSlot >= MAX_SAMPLES)
157 		{
158 			// If too many sample slots are needed, try to fill some empty slots first.
159 			for(SAMPLEINDEX j = 1; j <= sndFile.GetNumSamples(); j++)
160 			{
161 				if(sndFile.GetSample(j).HasSampleData())
162 				{
163 					continue;
164 				}
165 
166 				if(!mpt::contains(foundSlots, j))
167 				{
168 					// Empty sample slot that is not occupied by the current instrument. Yay!
169 					candidateSlot = j;
170 
171 					// Remove unused sample from instrument sample assignments
172 					for(INSTRUMENTINDEX ins = 1; ins <= sndFile.GetNumInstruments(); ins++)
173 					{
174 						if(sndFile.Instruments[ins] == nullptr)
175 						{
176 							continue;
177 						}
178 						for(auto &sample : sndFile.Instruments[ins]->Keyboard)
179 						{
180 							if(sample == candidateSlot)
181 							{
182 								sample = 0;
183 							}
184 						}
185 					}
186 					break;
187 				}
188 			}
189 		}
190 
191 		if(candidateSlot >= MAX_SAMPLES)
192 		{
193 			// Still couldn't find any empty sample slots, so look out for existing but unused samples.
194 			std::vector<bool> usedSamples;
195 			SAMPLEINDEX unusedSampleCount = sndFile.DetectUnusedSamples(usedSamples);
196 
197 			if(unusedSampleCount > 0)
198 			{
199 				sndFile.RemoveSelectedSamples(usedSamples);
200 				// Remove unused samples from instrument sample assignments
201 				for(INSTRUMENTINDEX ins = 1; ins <= sndFile.GetNumInstruments(); ins++)
202 				{
203 					if(sndFile.Instruments[ins] == nullptr)
204 					{
205 						continue;
206 					}
207 					for(auto &sample : sndFile.Instruments[ins]->Keyboard)
208 					{
209 						if(sample < usedSamples.size() && !usedSamples[sample])
210 						{
211 							sample = 0;
212 						}
213 					}
214 				}
215 
216 				// New candidate slot is first unused sample slot.
217 				candidateSlot = static_cast<SAMPLEINDEX>(std::find(usedSamples.begin() + 1, usedSamples.end(), false) - usedSamples.begin());
218 			} else
219 			{
220 				// No unused sampel slots: Give up :(
221 				break;
222 			}
223 		}
224 
225 		if(candidateSlot < MAX_SAMPLES)
226 		{
227 			foundSlots.push_back(candidateSlot);
228 			if(candidateSlot > sndFile.GetNumSamples())
229 			{
230 				sndFile.m_nSamples = candidateSlot;
231 			}
232 		}
233 	}
234 
235 	return foundSlots;
236 }
237 
238 
239 // Read .XM patterns
ReadXMPatterns(FileReader & file,const XMFileHeader & fileHeader,CSoundFile & sndFile)240 static void ReadXMPatterns(FileReader &file, const XMFileHeader &fileHeader, CSoundFile &sndFile)
241 {
242 	// Reading patterns
243 	sndFile.Patterns.ResizeArray(fileHeader.patterns);
244 	for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++)
245 	{
246 		FileReader::off_t curPos = file.GetPosition();
247 		uint32 headerSize = file.ReadUint32LE();
248 		file.Skip(1);	// Pack method (= 0)
249 
250 		ROWINDEX numRows = 64;
251 
252 		if(fileHeader.version == 0x0102)
253 		{
254 			numRows = file.ReadUint8() + 1;
255 		} else
256 		{
257 			numRows = file.ReadUint16LE();
258 		}
259 
260 		// A packed size of 0 indicates a completely empty pattern.
261 		const uint16 packedSize = file.ReadUint16LE();
262 
263 		if(numRows == 0)
264 			numRows = 64;
265 		else if(numRows > MAX_PATTERN_ROWS)
266 			numRows = MAX_PATTERN_ROWS;
267 
268 		file.Seek(curPos + headerSize);
269 		FileReader patternChunk = file.ReadChunk(packedSize);
270 
271 		if(!sndFile.Patterns.Insert(pat, numRows) || packedSize == 0)
272 		{
273 			continue;
274 		}
275 
276 		enum PatternFlags
277 		{
278 			isPackByte		= 0x80,
279 			allFlags		= 0xFF,
280 
281 			notePresent		= 0x01,
282 			instrPresent	= 0x02,
283 			volPresent		= 0x04,
284 			commandPresent	= 0x08,
285 			paramPresent	= 0x10,
286 		};
287 
288 		for(auto &m : sndFile.Patterns[pat])
289 		{
290 			uint8 info = patternChunk.ReadUint8();
291 
292 			uint8 vol = 0;
293 			if(info & isPackByte)
294 			{
295 				// Interpret byte as flag set.
296 				if(info & notePresent) m.note = patternChunk.ReadUint8();
297 			} else
298 			{
299 				// Interpret byte as note, read all other pattern fields as well.
300 				m.note = info;
301 				info = allFlags;
302 			}
303 
304 			if(info & instrPresent) m.instr = patternChunk.ReadUint8();
305 			if(info & volPresent) vol = patternChunk.ReadUint8();
306 			if(info & commandPresent) m.command = patternChunk.ReadUint8();
307 			if(info & paramPresent) m.param = patternChunk.ReadUint8();
308 
309 			if(m.note == 97)
310 			{
311 				m.note = NOTE_KEYOFF;
312 			} else if(m.note > 0 && m.note < 97)
313 			{
314 				m.note += 12;
315 			} else
316 			{
317 				m.note = NOTE_NONE;
318 			}
319 
320 			if(m.command | m.param)
321 			{
322 				CSoundFile::ConvertModCommand(m);
323 			} else
324 			{
325 				m.command = CMD_NONE;
326 			}
327 
328 			if(m.instr == 0xFF)
329 			{
330 				m.instr = 0;
331 			}
332 
333 			if(vol >= 0x10 && vol <= 0x50)
334 			{
335 				m.volcmd = VOLCMD_VOLUME;
336 				m.vol = vol - 0x10;
337 			} else if (vol >= 0x60)
338 			{
339 				// Volume commands 6-F translation.
340 				static constexpr ModCommand::VOLCMD volEffTrans[] =
341 				{
342 					VOLCMD_VOLSLIDEDOWN, VOLCMD_VOLSLIDEUP, VOLCMD_FINEVOLDOWN, VOLCMD_FINEVOLUP,
343 					VOLCMD_VIBRATOSPEED, VOLCMD_VIBRATODEPTH, VOLCMD_PANNING, VOLCMD_PANSLIDELEFT,
344 					VOLCMD_PANSLIDERIGHT, VOLCMD_TONEPORTAMENTO,
345 				};
346 
347 				m.volcmd = volEffTrans[(vol - 0x60) >> 4];
348 				m.vol = vol & 0x0F;
349 
350 				if(m.volcmd == VOLCMD_PANNING)
351 				{
352 					m.vol *= 4;	// FT2 does indeed not scale panning symmetrically.
353 				}
354 			}
355 		}
356 	}
357 }
358 
359 
360 enum TrackerVersions
361 {
362 	verUnknown		= 0x00,		// Probably not made with MPT
363 	verOldModPlug	= 0x01,		// Made with MPT Alpha / Beta
364 	verNewModPlug	= 0x02,		// Made with MPT (not Alpha / Beta)
365 	verModPlug1_09	= 0x04,		// Made with MPT 1.09 or possibly other version
366 	verOpenMPT		= 0x08,		// Made with OpenMPT
367 	verConfirmed	= 0x10,		// We are very sure that we found the correct tracker version.
368 
369 	verFT2Generic	= 0x20,		// "FastTracker v2.00", but FastTracker has NOT been ruled out
370 	verOther		= 0x40,		// Something we don't know, testing for DigiTrakker.
371 	verFT2Clone		= 0x80,		// NOT FT2: itype changed between instruments, or \0 found in song title
372 	verDigiTrakker	= 0x100,	// Probably DigiTrakker
373 	verUNMO3		= 0x200,	// TODO: UNMO3-ed XMs are detected as MPT 1.16
374 	verEmptyOrders	= 0x400,	// Allow empty order list like in OpenMPT (FT2 just plays pattern 0 if the order list is empty according to the header)
375 };
DECLARE_FLAGSET(TrackerVersions)376 DECLARE_FLAGSET(TrackerVersions)
377 
378 
379 static bool ValidateHeader(const XMFileHeader &fileHeader)
380 {
381 	if(fileHeader.channels == 0
382 		|| fileHeader.channels > MAX_BASECHANNELS
383 		|| std::memcmp(fileHeader.signature, "Extended Module: ", 17)
384 		)
385 	{
386 		return false;
387 	}
388 	return true;
389 }
390 
391 
GetHeaderMinimumAdditionalSize(const XMFileHeader & fileHeader)392 static uint64 GetHeaderMinimumAdditionalSize(const XMFileHeader &fileHeader)
393 {
394 	return fileHeader.orders + 4 * (fileHeader.patterns + fileHeader.instruments);
395 }
396 
397 
ProbeFileHeaderXM(MemoryFileReader file,const uint64 * pfilesize)398 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderXM(MemoryFileReader file, const uint64 *pfilesize)
399 {
400 	XMFileHeader fileHeader;
401 	if(!file.ReadStruct(fileHeader))
402 	{
403 		return ProbeWantMoreData;
404 	}
405 	if(!ValidateHeader(fileHeader))
406 	{
407 		return ProbeFailure;
408 	}
409 	return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
410 }
411 
412 
ReadSampleData(ModSample & sample,SampleIO sampleFlags,FileReader & sampleChunk,bool & isOXM)413 static bool ReadSampleData(ModSample &sample, SampleIO sampleFlags, FileReader &sampleChunk, bool &isOXM)
414 {
415 	bool unsupportedSample = false;
416 
417 	bool isOGG = false;
418 	if(sampleChunk.CanRead(8))
419 	{
420 		isOGG = true;
421 		sampleChunk.Skip(4);
422 		// In order to avoid false-detecting PCM as OggVorbis as much as possible,
423 		// we parse and verify the complete sample data and only assume OggVorbis,
424 		// if all Ogg checksums are correct a no single byte of non-Ogg data exists.
425 		// The fast-path for regular PCM will only check "OggS" magic and do no other work after failing that check.
426 		while(!sampleChunk.EndOfFile())
427 		{
428 			if(!Ogg::ReadPage(sampleChunk))
429 			{
430 				isOGG = false;
431 				break;
432 			}
433 		}
434 	}
435 	isOXM = isOXM || isOGG;
436 	sampleChunk.Rewind();
437 	if(isOGG)
438 	{
439 		uint32 originalSize = sampleChunk.ReadInt32LE();
440 		FileReader sampleData = sampleChunk.ReadChunk(sampleChunk.BytesLeft());
441 
442 		sample.uFlags.set(CHN_16BIT, sampleFlags.GetBitDepth() >= 16);
443 		sample.uFlags.set(CHN_STEREO, sampleFlags.GetChannelFormat() != SampleIO::mono);
444 		sample.nLength = originalSize / (sample.uFlags[CHN_16BIT] ? 2 : 1) / (sample.uFlags[CHN_STEREO] ? 2 : 1);
445 
446 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
447 
448 		ov_callbacks callbacks = {
449 			&VorbisfileFilereaderRead,
450 			&VorbisfileFilereaderSeek,
451 			NULL,
452 			&VorbisfileFilereaderTell
453 		};
454 		OggVorbis_File vf;
455 		MemsetZero(vf);
456 		if(ov_open_callbacks(&sampleData, &vf, nullptr, 0, callbacks) == 0)
457 		{
458 			if(ov_streams(&vf) == 1)
459 			{ // we do not support chained vorbis samples
460 				vorbis_info *vi = ov_info(&vf, -1);
461 				if(vi && vi->rate > 0 && vi->channels > 0)
462 				{
463 					sample.AllocateSample();
464 					SmpLength offset = 0;
465 					int channels = vi->channels;
466 					int current_section = 0;
467 					long decodedSamples = 0;
468 					bool eof = false;
469 					while(!eof && offset < sample.nLength && sample.HasSampleData())
470 					{
471 						float **output = nullptr;
472 						long ret = ov_read_float(&vf, &output, 1024, &current_section);
473 						if(ret == 0)
474 						{
475 							eof = true;
476 						} else if(ret < 0)
477 						{
478 							// stream error, just try to continue
479 						} else
480 						{
481 							decodedSamples = ret;
482 							LimitMax(decodedSamples, mpt::saturate_cast<long>(sample.nLength - offset));
483 							if(decodedSamples > 0 && channels == sample.GetNumChannels())
484 							{
485 								if(sample.uFlags[CHN_16BIT])
486 								{
487 									CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
488 								} else
489 								{
490 									CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
491 								}
492 							}
493 							offset += decodedSamples;
494 						}
495 					}
496 				} else
497 				{
498 					unsupportedSample = true;
499 				}
500 			} else
501 			{
502 				unsupportedSample = true;
503 			}
504 			ov_clear(&vf);
505 		} else
506 		{
507 			unsupportedSample = true;
508 		}
509 
510 #elif defined(MPT_WITH_STBVORBIS)
511 
512 		// NOTE/TODO: stb_vorbis does not handle inferred negative PCM sample
513 		// position at stream start. (See
514 		// <https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-132000A.2>).
515 		// This means that, for remuxed and re-aligned/cutted (at stream start)
516 		// Vorbis files, stb_vorbis will include superfluous samples at the
517 		// beginning. OXM files with this property are yet to be spotted in the
518 		// wild, thus, this behaviour is currently not problematic.
519 
520 		int consumed = 0, error = 0;
521 		stb_vorbis *vorb = nullptr;
522 		FileReader::PinnedView sampleDataView = sampleData.GetPinnedView();
523 		const std::byte* data = sampleDataView.data();
524 		std::size_t dataLeft = sampleDataView.size();
525 		vorb = stb_vorbis_open_pushdata(mpt::byte_cast<const unsigned char*>(data), mpt::saturate_cast<int>(dataLeft), &consumed, &error, nullptr);
526 		sampleData.Skip(consumed);
527 		data += consumed;
528 		dataLeft -= consumed;
529 		if(vorb)
530 		{
531 			// Header has been read, proceed to reading the sample data
532 			sample.AllocateSample();
533 			SmpLength offset = 0;
534 			while((error == VORBIS__no_error || (error == VORBIS_need_more_data && dataLeft > 0))
535 				&& offset < sample.nLength && sample.HasSampleData())
536 			{
537 				int channels = 0, decodedSamples = 0;
538 				float **output;
539 				consumed = stb_vorbis_decode_frame_pushdata(vorb, mpt::byte_cast<const unsigned char*>(data), mpt::saturate_cast<int>(dataLeft), &channels, &output, &decodedSamples);
540 				sampleData.Skip(consumed);
541 				data += consumed;
542 				dataLeft -= consumed;
543 				LimitMax(decodedSamples, mpt::saturate_cast<int>(sample.nLength - offset));
544 				if(decodedSamples > 0 && channels == sample.GetNumChannels())
545 				{
546 					if(sample.uFlags[CHN_16BIT])
547 					{
548 						CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
549 					} else
550 					{
551 						CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
552 					}
553 				}
554 				offset += decodedSamples;
555 				error = stb_vorbis_get_error(vorb);
556 			}
557 			stb_vorbis_close(vorb);
558 		} else
559 		{
560 			unsupportedSample = true;
561 		}
562 
563 #else // !VORBIS
564 
565 		unsupportedSample = true;
566 
567 #endif // VORBIS
568 
569 	} else
570 	{
571 		sampleFlags.ReadSample(sample, sampleChunk);
572 	}
573 
574 	return !unsupportedSample;
575 }
576 
577 
ReadXM(FileReader & file,ModLoadingFlags loadFlags)578 bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags)
579 {
580 	file.Rewind();
581 
582 	XMFileHeader fileHeader;
583 	if(!file.ReadStruct(fileHeader))
584 	{
585 		return false;
586 	}
587 	if(!ValidateHeader(fileHeader))
588 	{
589 		return false;
590 	}
591 	if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
592 	{
593 		return false;
594 	} else if(loadFlags == onlyVerifyHeader)
595 	{
596 		return true;
597 	}
598 
599 	InitializeGlobals(MOD_TYPE_XM);
600 	InitializeChannels();
601 	m_nMixLevels = MixLevels::Compatible;
602 
603 	FlagSet<TrackerVersions> madeWith(verUnknown);
604 	mpt::ustring madeWithTracker;
605 	bool isMadTracker = false;
606 
607 	if(!memcmp(fileHeader.trackerName, "FastTracker v2.00   ", 20) && fileHeader.size == 276)
608 	{
609 		if(fileHeader.version < 0x0104)
610 			madeWith = verFT2Generic | verConfirmed;
611 		else if(memchr(fileHeader.songName, '\0', 20) != nullptr)
612 			// FT2 pads the song title with spaces, some other trackers use null chars
613 			madeWith = verFT2Clone | verNewModPlug | verEmptyOrders;
614 		else
615 			madeWith = verFT2Generic | verNewModPlug;
616 	} else if(!memcmp(fileHeader.trackerName, "FastTracker v 2.00  ", 20))
617 	{
618 		// MPT 1.0 (exact version to be determined later)
619 		madeWith = verOldModPlug;
620 	} else
621 	{
622 		// Something else!
623 		madeWith = verUnknown | verConfirmed;
624 
625 		madeWithTracker = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.trackerName));
626 
627 		if(!memcmp(fileHeader.trackerName, "OpenMPT ", 8))
628 		{
629 			madeWith = verOpenMPT | verConfirmed | verEmptyOrders;
630 		} else if(!memcmp(fileHeader.trackerName, "MilkyTracker ", 12))
631 		{
632 			// MilkyTracker prior to version 0.90.87 doesn't set a version string.
633 			// Luckily, starting with v0.90.87, MilkyTracker also implements the FT2 panning scheme.
634 			if(memcmp(fileHeader.trackerName + 12, "        ", 8))
635 			{
636 				m_nMixLevels = MixLevels::CompatibleFT2;
637 			}
638 		} else if(!memcmp(fileHeader.trackerName, "Fasttracker II clone", 20))
639 		{
640 			// 8bitbubsy's FT2 clone should be treated exactly like FT2
641 			madeWith = verFT2Generic | verConfirmed;
642 		} else if(!memcmp(fileHeader.trackerName, "MadTracker 2.0\0", 15))
643 		{
644 			// Fix channel 2 in m3_cha.xm
645 			m_playBehaviour.reset(kFT2PortaNoNote);
646 			// Fix arpeggios in kragle_-_happy_day.xm
647 			m_playBehaviour.reset(kFT2Arpeggio);
648 			isMadTracker = true;
649 		} else if(!memcmp(fileHeader.trackerName, "Skale Tracker\0", 14) || !memcmp(fileHeader.trackerName, "Sk@le Tracker\0", 14))
650 		{
651 			m_playBehaviour.reset(kFT2ST3OffsetOutOfRange);
652 			// Fix arpeggios in KAPTENFL.XM
653 			m_playBehaviour.reset(kFT2Arpeggio);
654 		} else if(!memcmp(fileHeader.trackerName, "*Converted ", 11))
655 		{
656 			madeWith = verDigiTrakker;
657 		}
658 	}
659 
660 	m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName);
661 
662 	m_nMinPeriod = 1;
663 	m_nMaxPeriod = 31999;
664 
665 	Order().SetRestartPos(fileHeader.restartPos);
666 	m_nChannels = fileHeader.channels;
667 	m_nInstruments = std::min(static_cast<uint16>(fileHeader.instruments), static_cast<uint16>(MAX_INSTRUMENTS - 1));
668 	if(fileHeader.speed)
669 		m_nDefaultSpeed = fileHeader.speed;
670 	if(fileHeader.tempo)
671 		m_nDefaultTempo = Clamp(TEMPO(fileHeader.tempo, 0), ModSpecs::xmEx.GetTempoMin(), ModSpecs::xmEx.GetTempoMax());
672 
673 	m_SongFlags.reset();
674 	m_SongFlags.set(SONG_LINEARSLIDES, (fileHeader.flags & XMFileHeader::linearSlides) != 0);
675 	m_SongFlags.set(SONG_EXFILTERRANGE, (fileHeader.flags & XMFileHeader::extendedFilterRange) != 0);
676 	if(m_SongFlags[SONG_EXFILTERRANGE] && madeWith == (verFT2Generic | verNewModPlug))
677 	{
678 		madeWith = verFT2Clone | verNewModPlug | verConfirmed;
679 	}
680 
681 	ReadOrderFromFile<uint8>(Order(), file, fileHeader.orders);
682 	if(fileHeader.orders == 0 && !madeWith[verEmptyOrders])
683 	{
684 		// Fix lamb_-_dark_lighthouse.xm, which only contains one pattern and an empty order list
685 		Order().assign(1, 0);
686 	}
687 	file.Seek(fileHeader.size + 60);
688 
689 	if(fileHeader.version >= 0x0104)
690 	{
691 		ReadXMPatterns(file, fileHeader, *this);
692 	}
693 
694 	bool isOXM = false;
695 
696 	// 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.
697 	std::vector<SampleIO> sampleFlags;
698 	uint8 sampleReserved = 0;
699 	int instrType = -1;
700 	bool unsupportedSamples = false;
701 
702 	// Reading instruments
703 	for(INSTRUMENTINDEX instr = 1; instr <= m_nInstruments; instr++)
704 	{
705 		// First, try to read instrument header length...
706 		uint32 headerSize = file.ReadUint32LE();
707 		if(headerSize == 0)
708 		{
709 			headerSize = sizeof(XMInstrumentHeader);
710 		}
711 
712 		// Now, read the complete struct.
713 		file.SkipBack(4);
714 		XMInstrumentHeader instrHeader;
715 		file.ReadStructPartial(instrHeader, headerSize);
716 
717 		// Time for some version detection stuff.
718 		if(madeWith == verOldModPlug)
719 		{
720 			madeWith.set(verConfirmed);
721 			if(instrHeader.size == 245)
722 			{
723 				// ModPlug Tracker Alpha
724 				m_dwLastSavedWithVersion = MPT_V("1.00.00.A5");
725 				madeWithTracker = U_("ModPlug Tracker 1.0 alpha");
726 			} else if(instrHeader.size == 263)
727 			{
728 				// ModPlug Tracker Beta (Beta 1 still behaves like Alpha, but Beta 3.3 does it this way)
729 				m_dwLastSavedWithVersion = MPT_V("1.00.00.B3");
730 				madeWithTracker = U_("ModPlug Tracker 1.0 beta");
731 			} else
732 			{
733 				// WTF?
734 				madeWith = (verUnknown | verConfirmed);
735 			}
736 		} else if(instrHeader.numSamples == 0)
737 		{
738 			// Empty instruments make tracker identification pretty easy!
739 			if(instrHeader.size == 263 && instrHeader.sampleHeaderSize == 0 && madeWith[verNewModPlug])
740 				madeWith.set(verConfirmed);
741 			else if(instrHeader.size != 29 && madeWith[verDigiTrakker])
742 				madeWith.reset(verDigiTrakker);
743 			else if(madeWith[verFT2Clone | verFT2Generic] && instrHeader.size != 33)
744 			{
745 				// Sure isn't FT2.
746 				// Note: FT2 NORMALLY writes shdr=40 for all samples, but sometimes it
747 				// just happens to write random garbage there instead. Surprise!
748 				// Note: 4-mat's eternity.xm has an instrument header size of 29.
749 				madeWith = verUnknown;
750 			}
751 		}
752 
753 		if(AllocateInstrument(instr) == nullptr)
754 		{
755 			continue;
756 		}
757 
758 		instrHeader.ConvertToMPT(*Instruments[instr]);
759 
760 		if(instrType == -1)
761 		{
762 			instrType = instrHeader.type;
763 		} else if(instrType != instrHeader.type && madeWith[verFT2Generic])
764 		{
765 			// FT2 writes some random junk for the instrument type field,
766 			// but it's always the SAME junk for every instrument saved.
767 			madeWith.reset(verFT2Generic);
768 			madeWith.set(verFT2Clone);
769 		}
770 
771 		if(instrHeader.numSamples > 0)
772 		{
773 			// Yep, there are some samples associated with this instrument.
774 			if((instrHeader.instrument.midiEnabled | instrHeader.instrument.midiChannel | instrHeader.instrument.midiProgram | instrHeader.instrument.muteComputer) != 0)
775 			{
776 				// Definitely not an old MPT.
777 				madeWith.reset(verOldModPlug | verNewModPlug);
778 			}
779 
780 			// Read sample headers
781 			std::vector<SAMPLEINDEX> sampleSlots = AllocateXMSamples(*this, instrHeader.numSamples);
782 
783 			// Update sample assignment map
784 			for(size_t k = 0 + 12; k < 96 + 12; k++)
785 			{
786 				if(Instruments[instr]->Keyboard[k] < sampleSlots.size())
787 				{
788 					Instruments[instr]->Keyboard[k] = sampleSlots[Instruments[instr]->Keyboard[k]];
789 				}
790 			}
791 
792 			if(fileHeader.version >= 0x0104)
793 			{
794 				sampleFlags.clear();
795 			}
796 			// Need to memorize those if we're going to skip any samples...
797 			std::vector<uint32> sampleSize(instrHeader.numSamples);
798 
799 			// Early versions of Sk@le Tracker set instrHeader.sampleHeaderSize = 0 (IFULOVE.XM)
800 			// cybernostra weekend has instrHeader.sampleHeaderSize = 0x12, which would leave out the sample name, but FT2 still reads the name.
801 			MPT_ASSERT(instrHeader.sampleHeaderSize == 0 || instrHeader.sampleHeaderSize == sizeof(XMSample));
802 
803 			for(SAMPLEINDEX sample = 0; sample < instrHeader.numSamples; sample++)
804 			{
805 				XMSample sampleHeader;
806 				file.ReadStruct(sampleHeader);
807 
808 				sampleFlags.push_back(sampleHeader.GetSampleFormat());
809 				sampleSize[sample] = sampleHeader.length;
810 				sampleReserved |= sampleHeader.reserved;
811 
812 				if(sample < sampleSlots.size())
813 				{
814 					SAMPLEINDEX mptSample = sampleSlots[sample];
815 
816 					sampleHeader.ConvertToMPT(Samples[mptSample]);
817 					instrHeader.instrument.ApplyAutoVibratoToMPT(Samples[mptSample]);
818 
819 					m_szNames[mptSample] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
820 
821 					if((sampleHeader.flags & 3) == 3 && madeWith[verNewModPlug])
822 					{
823 						// MPT 1.09 and maybe newer / older versions set both loop flags for bidi loops.
824 						madeWith.set(verModPlug1_09);
825 					}
826 				}
827 			}
828 
829 			// Read samples
830 			if(fileHeader.version >= 0x0104)
831 			{
832 				for(SAMPLEINDEX sample = 0; sample < instrHeader.numSamples; sample++)
833 				{
834 					// Sample 15 in dirtysex.xm by J/M/T/M is a 16-bit sample with an odd size of 0x18B according to the header, while the real sample size would be 0x18A.
835 					// Always read as many bytes as specified in the header, even if the sample reader would probably read less bytes.
836 					FileReader sampleChunk = file.ReadChunk(sampleFlags[sample].GetEncoding() != SampleIO::ADPCM ? sampleSize[sample] : (16 + (sampleSize[sample] + 1) / 2));
837 					if(sample < sampleSlots.size() && (loadFlags & loadSampleData))
838 					{
839 						if(!ReadSampleData(Samples[sampleSlots[sample]], sampleFlags[sample], sampleChunk, isOXM))
840 						{
841 							unsupportedSamples = true;
842 						}
843 					}
844 				}
845 			}
846 		}
847 	}
848 
849 	if(sampleReserved == 0 && madeWith[verNewModPlug] && memchr(fileHeader.songName, '\0', sizeof(fileHeader.songName)) != nullptr)
850 	{
851 		// Null-terminated song name: Quite possibly MPT. (could really be an MPT-made file resaved in FT2, though)
852 		madeWith.set(verConfirmed);
853 	}
854 
855 	if(fileHeader.version < 0x0104)
856 	{
857 		// Load Patterns and Samples (Version 1.02 and 1.03)
858 		if(loadFlags & (loadPatternData | loadSampleData))
859 		{
860 			ReadXMPatterns(file, fileHeader, *this);
861 		}
862 
863 		if(loadFlags & loadSampleData)
864 		{
865 			for(SAMPLEINDEX sample = 1; sample <= GetNumSamples(); sample++)
866 			{
867 				sampleFlags[sample - 1].ReadSample(Samples[sample], file);
868 			}
869 		}
870 	}
871 
872 	if(unsupportedSamples)
873 	{
874 		AddToLog(LogWarning, U_("Some compressed samples could not be loaded because they use an unsupported codec."));
875 	}
876 
877 	// Read song comments: "text"
878 	if(file.ReadMagic("text"))
879 	{
880 		m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR);
881 		madeWith.set(verConfirmed);
882 	}
883 
884 	// Read midi config: "MIDI"
885 	bool hasMidiConfig = false;
886 	if(file.ReadMagic("MIDI"))
887 	{
888 		file.ReadStructPartial<MIDIMacroConfigData>(m_MidiCfg, file.ReadUint32LE());
889 		m_MidiCfg.Sanitize();
890 		hasMidiConfig = true;
891 		madeWith.set(verConfirmed);
892 	}
893 
894 	// Read pattern names: "PNAM"
895 	if(file.ReadMagic("PNAM"))
896 	{
897 		const PATTERNINDEX namedPats = std::min(static_cast<PATTERNINDEX>(file.ReadUint32LE() / MAX_PATTERNNAME), Patterns.Size());
898 
899 		for(PATTERNINDEX pat = 0; pat < namedPats; pat++)
900 		{
901 			char patName[MAX_PATTERNNAME];
902 			file.ReadString<mpt::String::maybeNullTerminated>(patName, MAX_PATTERNNAME);
903 			Patterns[pat].SetName(patName);
904 		}
905 		madeWith.set(verConfirmed);
906 	}
907 
908 	// Read channel names: "CNAM"
909 	if(file.ReadMagic("CNAM"))
910 	{
911 		const CHANNELINDEX namedChans = std::min(static_cast<CHANNELINDEX>(file.ReadUint32LE() / MAX_CHANNELNAME), GetNumChannels());
912 		for(CHANNELINDEX chn = 0; chn < namedChans; chn++)
913 		{
914 			file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, MAX_CHANNELNAME);
915 		}
916 		madeWith.set(verConfirmed);
917 	}
918 
919 	// Read mix plugins information
920 	if(file.CanRead(8))
921 	{
922 		FileReader::off_t oldPos = file.GetPosition();
923 		LoadMixPlugins(file);
924 		if(file.GetPosition() != oldPos)
925 		{
926 			madeWith.set(verConfirmed);
927 		}
928 	}
929 
930 	if(madeWith[verConfirmed])
931 	{
932 		if(madeWith[verModPlug1_09])
933 		{
934 			m_dwLastSavedWithVersion = MPT_V("1.09.00.00");
935 			madeWithTracker = U_("ModPlug Tracker 1.09");
936 		} else if(madeWith[verNewModPlug])
937 		{
938 			m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
939 			madeWithTracker = U_("ModPlug Tracker 1.10 - 1.16");
940 		}
941 	}
942 
943 	if(!memcmp(fileHeader.trackerName, "OpenMPT ", 8))
944 	{
945 		// Hey, I know this tracker!
946 		std::string mptVersion(fileHeader.trackerName + 8, 12);
947 		m_dwLastSavedWithVersion = Version::Parse(mpt::ToUnicode(mpt::Charset::ASCII, mptVersion));
948 		madeWith = verOpenMPT | verConfirmed;
949 
950 		if(m_dwLastSavedWithVersion < MPT_V("1.22.07.19"))
951 			m_nMixLevels = MixLevels::Compatible;
952 		else
953 			m_nMixLevels = MixLevels::CompatibleFT2;
954 	}
955 
956 	if(m_dwLastSavedWithVersion && !madeWith[verOpenMPT])
957 	{
958 		m_nMixLevels = MixLevels::Original;
959 		m_playBehaviour.reset();
960 	}
961 
962 	if(madeWith[verFT2Generic])
963 	{
964 		m_nMixLevels = MixLevels::CompatibleFT2;
965 
966 		if(!hasMidiConfig)
967 		{
968 			// FT2 allows typing in arbitrary unsupported effect letters such as Zxx.
969 			// Prevent these commands from being interpreted as filter commands by erasing the default MIDI Config.
970 			m_MidiCfg.ClearZxxMacros();
971 		}
972 
973 		if(fileHeader.version >= 0x0104	// Old versions of FT2 didn't have (smooth) ramping. Disable it for those versions where we can be sure that there should be no ramping.
974 #ifdef MODPLUG_TRACKER
975 			&& TrackerSettings::Instance().autoApplySmoothFT2Ramping
976 #endif // MODPLUG_TRACKER
977 			)
978 		{
979 			// apply FT2-style super-soft volume ramping
980 			m_playBehaviour.set(kFT2VolumeRamping);
981 		}
982 	}
983 
984 	if(madeWithTracker.empty())
985 	{
986 		if(madeWith[verDigiTrakker] && sampleReserved == 0 && (instrType ? instrType : -1) == -1)
987 		{
988 			madeWithTracker = U_("DigiTrakker");
989 		} else if(madeWith[verFT2Generic])
990 		{
991 			madeWithTracker = U_("FastTracker 2 or compatible");
992 		} else
993 		{
994 			madeWithTracker = U_("Unknown");
995 		}
996 	}
997 
998 	bool isOpenMPTMade = false; // specific for OpenMPT 1.17+
999 	if(GetNumInstruments())
1000 	{
1001 		isOpenMPTMade = LoadExtendedInstrumentProperties(file);
1002 	}
1003 
1004 	LoadExtendedSongProperties(file, true, &isOpenMPTMade);
1005 
1006 	if(isOpenMPTMade && m_dwLastSavedWithVersion < MPT_V("1.17.00.00"))
1007 	{
1008 		// Up to OpenMPT 1.17.02.45 (r165), it was possible that the "last saved with" field was 0
1009 		// when saving a file in OpenMPT for the first time.
1010 		m_dwLastSavedWithVersion = MPT_V("1.17.00.00");
1011 	}
1012 
1013 	if(m_dwLastSavedWithVersion >= MPT_V("1.17.00.00"))
1014 	{
1015 		madeWithTracker = U_("OpenMPT ") + m_dwLastSavedWithVersion.ToUString();
1016 	}
1017 
1018 	// We no longer allow any --- or +++ items in the order list now.
1019 	if(m_dwLastSavedWithVersion && m_dwLastSavedWithVersion < MPT_V("1.22.02.02"))
1020 	{
1021 		if(!Patterns.IsValidPat(0xFE))
1022 			Order().RemovePattern(0xFE);
1023 		if(!Patterns.IsValidPat(0xFF))
1024 			Order().Replace(0xFF, Order.GetInvalidPatIndex());
1025 	}
1026 
1027 	m_modFormat.formatName = MPT_UFORMAT("FastTracker 2 v{}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF));
1028 	m_modFormat.madeWithTracker = std::move(madeWithTracker);
1029 	m_modFormat.charset = (m_dwLastSavedWithVersion || isMadTracker) ? mpt::Charset::Windows1252 : mpt::Charset::CP437;
1030 	if(isOXM)
1031 	{
1032 		m_modFormat.originalFormatName = std::move(m_modFormat.formatName);
1033 		m_modFormat.formatName = U_("OggMod FastTracker 2");
1034 		m_modFormat.type = U_("oxm");
1035 		m_modFormat.originalType = U_("xm");
1036 	} else
1037 	{
1038 		m_modFormat.type = U_("xm");
1039 	}
1040 
1041 	return true;
1042 }
1043 
1044 
1045 #ifndef MODPLUG_NO_FILESAVE
1046 
1047 
SaveXM(std::ostream & f,bool compatibilityExport)1048 bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport)
1049 {
1050 
1051 	bool addChannel = false; // avoid odd channel count for FT2 compatibility
1052 
1053 	XMFileHeader fileHeader;
1054 	MemsetZero(fileHeader);
1055 
1056 	memcpy(fileHeader.signature, "Extended Module: ", 17);
1057 	mpt::String::WriteBuf(mpt::String::spacePadded, fileHeader.songName) = m_songName;
1058 	fileHeader.eof = 0x1A;
1059 	const std::string openMptTrackerName = mpt::ToCharset(GetCharsetFile(), Version::Current().GetOpenMPTVersionString());
1060 	mpt::String::WriteBuf(mpt::String::spacePadded, fileHeader.trackerName) = openMptTrackerName;
1061 
1062 	// Writing song header
1063 	fileHeader.version = 0x0104;					// XM Format v1.04
1064 	fileHeader.size = sizeof(XMFileHeader) - 60;	// minus everything before this field
1065 	fileHeader.restartPos = Order().GetRestartPos();
1066 
1067 	fileHeader.channels = m_nChannels;
1068 	if((m_nChannels % 2u) && m_nChannels < 32)
1069 	{
1070 		// Avoid odd channel count for FT2 compatibility
1071 		fileHeader.channels++;
1072 		addChannel = true;
1073 	} else if(compatibilityExport && fileHeader.channels > 32)
1074 	{
1075 		fileHeader.channels = 32;
1076 	}
1077 
1078 	// Find out number of orders and patterns used.
1079 	// +++ and --- patterns are not taken into consideration as FastTracker does not support them.
1080 
1081 	const ORDERINDEX trimmedLength = Order().GetLengthTailTrimmed();
1082 	std::vector<uint8> orderList(trimmedLength);
1083 	const ORDERINDEX orderLimit = compatibilityExport ? 256 : uint16_max;
1084 	ORDERINDEX numOrders = 0;
1085 	PATTERNINDEX numPatterns = Patterns.GetNumPatterns();
1086 	bool changeOrderList = false;
1087 	for(ORDERINDEX ord = 0; ord < trimmedLength; ord++)
1088 	{
1089 		PATTERNINDEX pat = Order()[ord];
1090 		if(pat == Order.GetIgnoreIndex() || pat == Order.GetInvalidPatIndex() || pat > uint8_max)
1091 		{
1092 			changeOrderList = true;
1093 		} else if(numOrders < orderLimit)
1094 		{
1095 			orderList[numOrders++] = static_cast<uint8>(pat);
1096 			if(pat >= numPatterns)
1097 				numPatterns = pat + 1;
1098 		}
1099 	}
1100 	if(changeOrderList)
1101 	{
1102 		AddToLog(LogWarning, U_("Skip and stop order list items (+++ and ---) are not saved in XM files."));
1103 	}
1104 	orderList.resize(compatibilityExport ? 256 : numOrders);
1105 
1106 	fileHeader.orders = numOrders;
1107 	fileHeader.patterns = numPatterns;
1108 	fileHeader.size += static_cast<uint32>(orderList.size());
1109 
1110 	uint16 writeInstruments;
1111 	if(m_nInstruments > 0)
1112 		fileHeader.instruments = writeInstruments = m_nInstruments;
1113 	else
1114 		fileHeader.instruments = writeInstruments = m_nSamples;
1115 
1116 	if(m_SongFlags[SONG_LINEARSLIDES]) fileHeader.flags |= XMFileHeader::linearSlides;
1117 	if(m_SongFlags[SONG_EXFILTERRANGE] && !compatibilityExport) fileHeader.flags |= XMFileHeader::extendedFilterRange;
1118 	fileHeader.flags = fileHeader.flags;
1119 
1120 	// Fasttracker 2 will happily accept any tempo faster than 255 BPM. XMPlay does also support this, great!
1121 	fileHeader.tempo = mpt::saturate_cast<uint16>(m_nDefaultTempo.GetInt());
1122 	fileHeader.speed = static_cast<uint16>(Clamp(m_nDefaultSpeed, 1u, 31u));
1123 
1124 	mpt::IO::Write(f, fileHeader);
1125 
1126 	// Write processed order list
1127 	mpt::IO::Write(f, orderList);
1128 
1129 	// Writing patterns
1130 
1131 #define ASSERT_CAN_WRITE(x) \
1132 	if(len > s.size() - x) /*Buffer running out? Make it larger.*/ \
1133 		s.resize(s.size() + 10 * 1024, 0);
1134 	std::vector<uint8> s(64 * 64 * 5, 0);
1135 
1136 	for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1137 	{
1138 		uint8 patHead[9] = { 0 };
1139 		patHead[0] = 9;
1140 
1141 		if(!Patterns.IsValidPat(pat))
1142 		{
1143 			// There's nothing to write... chicken out.
1144 			patHead[5] = 64;
1145 			mpt::IO::Write(f, patHead);
1146 			continue;
1147 		}
1148 
1149 		const uint16 numRows = mpt::saturate_cast<uint16>(Patterns[pat].GetNumRows());
1150 		patHead[5] = static_cast<uint8>(numRows & 0xFF);
1151 		patHead[6] = static_cast<uint8>(numRows >> 8);
1152 
1153 		auto p = Patterns[pat].cbegin();
1154 		size_t len = 0;
1155 		// Empty patterns are always loaded as 64-row patterns in FT2, regardless of their real size...
1156 		bool emptyPattern = true;
1157 
1158 		for(size_t j = m_nChannels * numRows; j > 0; j--, p++)
1159 		{
1160 			// Don't write more than 32 channels
1161 			if(compatibilityExport && m_nChannels - ((j - 1) % m_nChannels) > 32) continue;
1162 
1163 			uint8 note = p->note;
1164 			uint8 command = p->command, param = p->param;
1165 			ModSaveCommand(command, param, true, compatibilityExport);
1166 
1167 			if (note >= NOTE_MIN_SPECIAL) note = 97; else
1168 			if ((note <= 12) || (note > 96+12)) note = 0; else
1169 			note -= 12;
1170 			uint8 vol = 0;
1171 			if (p->volcmd != VOLCMD_NONE)
1172 			{
1173 				switch(p->volcmd)
1174 				{
1175 				case VOLCMD_VOLUME:			vol = 0x10 + p->vol; break;
1176 				case VOLCMD_VOLSLIDEDOWN:	vol = 0x60 + (p->vol & 0x0F); break;
1177 				case VOLCMD_VOLSLIDEUP:		vol = 0x70 + (p->vol & 0x0F); break;
1178 				case VOLCMD_FINEVOLDOWN:	vol = 0x80 + (p->vol & 0x0F); break;
1179 				case VOLCMD_FINEVOLUP:		vol = 0x90 + (p->vol & 0x0F); break;
1180 				case VOLCMD_VIBRATOSPEED:	vol = 0xA0 + (p->vol & 0x0F); break;
1181 				case VOLCMD_VIBRATODEPTH:	vol = 0xB0 + (p->vol & 0x0F); break;
1182 				case VOLCMD_PANNING:		vol = 0xC0 + (p->vol / 4); if (vol > 0xCF) vol = 0xCF; break;
1183 				case VOLCMD_PANSLIDELEFT:	vol = 0xD0 + (p->vol & 0x0F); break;
1184 				case VOLCMD_PANSLIDERIGHT:	vol = 0xE0 + (p->vol & 0x0F); break;
1185 				case VOLCMD_TONEPORTAMENTO:	vol = 0xF0 + (p->vol & 0x0F); break;
1186 				}
1187 				// Those values are ignored in FT2. Don't save them, also to avoid possible problems with other trackers (or MPT itself)
1188 				if(compatibilityExport && p->vol == 0)
1189 				{
1190 					switch(p->volcmd)
1191 					{
1192 					case VOLCMD_VOLUME:
1193 					case VOLCMD_PANNING:
1194 					case VOLCMD_VIBRATODEPTH:
1195 					case VOLCMD_TONEPORTAMENTO:
1196 					case VOLCMD_PANSLIDELEFT:	// Doesn't have memory, but does weird things with zero param.
1197 						break;
1198 					default:
1199 						// no memory here.
1200 						vol = 0;
1201 					}
1202 				}
1203 			}
1204 
1205 			// no need to fix non-empty patterns
1206 			if(!p->IsEmpty())
1207 				emptyPattern = false;
1208 
1209 			// Apparently, completely empty patterns are loaded as empty 64-row patterns in FT2, regardless of their original size.
1210 			// We have to avoid this, so we add a "break to row 0" command in the last row.
1211 			if(j == 1 && emptyPattern && numRows != 64)
1212 			{
1213 				command = 0x0D;
1214 				param = 0;
1215 			}
1216 
1217 			if ((note) && (p->instr) && (vol > 0x0F) && (command) && (param))
1218 			{
1219 				s[len++] = note;
1220 				s[len++] = p->instr;
1221 				s[len++] = vol;
1222 				s[len++] = command;
1223 				s[len++] = param;
1224 			} else
1225 			{
1226 				uint8 b = 0x80;
1227 				if (note) b |= 0x01;
1228 				if (p->instr) b |= 0x02;
1229 				if (vol >= 0x10) b |= 0x04;
1230 				if (command) b |= 0x08;
1231 				if (param) b |= 0x10;
1232 				s[len++] = b;
1233 				if (b & 1) s[len++] = note;
1234 				if (b & 2) s[len++] = p->instr;
1235 				if (b & 4) s[len++] = vol;
1236 				if (b & 8) s[len++] = command;
1237 				if (b & 16) s[len++] = param;
1238 			}
1239 
1240 			if(addChannel && (j % m_nChannels == 1 || m_nChannels == 1))
1241 			{
1242 				ASSERT_CAN_WRITE(1);
1243 				s[len++] = 0x80;
1244 			}
1245 
1246 			ASSERT_CAN_WRITE(5);
1247 		}
1248 
1249 		if(emptyPattern && numRows == 64)
1250 		{
1251 			// Be smart when saving empty patterns!
1252 			len = 0;
1253 		}
1254 
1255 		// Reaching the limits of file format?
1256 		if(len > uint16_max)
1257 		{
1258 			AddToLog(LogWarning, MPT_UFORMAT("Warning: File format limit was reached. Some pattern data may not get written to file. (pattern {})")(pat));
1259 			len = uint16_max;
1260 		}
1261 
1262 		patHead[7] = static_cast<uint8>(len & 0xFF);
1263 		patHead[8] = static_cast<uint8>(len >> 8);
1264 		mpt::IO::Write(f, patHead);
1265 		if(len) mpt::IO::WriteRaw(f, s.data(), len);
1266 	}
1267 
1268 #undef ASSERT_CAN_WRITE
1269 
1270 	// Check which samples are referenced by which instruments (for assigning unreferenced samples to instruments)
1271 	std::vector<bool> sampleAssigned(GetNumSamples() + 1, false);
1272 	for(INSTRUMENTINDEX ins = 1; ins <= GetNumInstruments(); ins++)
1273 	{
1274 		if(Instruments[ins] != nullptr)
1275 		{
1276 			Instruments[ins]->GetSamples(sampleAssigned);
1277 		}
1278 	}
1279 
1280 	// Writing instruments
1281 	for(INSTRUMENTINDEX ins = 1; ins <= writeInstruments; ins++)
1282 	{
1283 		XMInstrumentHeader insHeader;
1284 		std::vector<SAMPLEINDEX> samples;
1285 
1286 		if(GetNumInstruments())
1287 		{
1288 			if(Instruments[ins] != nullptr)
1289 			{
1290 				// Convert instrument
1291 				insHeader.ConvertToXM(*Instruments[ins], compatibilityExport);
1292 
1293 				samples = insHeader.instrument.GetSampleList(*Instruments[ins], compatibilityExport);
1294 				if(samples.size() > 0 && samples[0] <= GetNumSamples())
1295 				{
1296 					// Copy over auto-vibrato settings of first sample
1297 					insHeader.instrument.ApplyAutoVibratoToXM(Samples[samples[0]], GetType());
1298 				}
1299 
1300 				std::vector<SAMPLEINDEX> additionalSamples;
1301 
1302 				// Try to save "instrument-less" samples as well by adding those after the "normal" samples of our sample.
1303 				// We look for unassigned samples directly after the samples assigned to our current instrument, so if
1304 				// e.g. sample 1 is assigned to instrument 1 and samples 2 to 10 aren't assigned to any instrument,
1305 				// we will assign those to sample 1. Any samples before the first referenced sample are going to be lost,
1306 				// but hey, I wrote this mostly for preserving instrument texts in existing modules, where we shouldn't encounter this situation...
1307 				for(auto smp : samples)
1308 				{
1309 					while(++smp <= GetNumSamples()
1310 						&& !sampleAssigned[smp]
1311 						&& insHeader.numSamples < (compatibilityExport ? 16 : 32))
1312 					{
1313 						sampleAssigned[smp] = true;			// Don't want to add this sample again.
1314 						additionalSamples.push_back(smp);
1315 						insHeader.numSamples++;
1316 					}
1317 				}
1318 
1319 				samples.insert(samples.end(), additionalSamples.begin(), additionalSamples.end());
1320 			} else
1321 			{
1322 				MemsetZero(insHeader);
1323 			}
1324 		} else
1325 		{
1326 			// Convert samples to instruments
1327 			MemsetZero(insHeader);
1328 			insHeader.numSamples = 1;
1329 			insHeader.instrument.ApplyAutoVibratoToXM(Samples[ins], GetType());
1330 			samples.push_back(ins);
1331 		}
1332 
1333 		insHeader.Finalise();
1334 		size_t insHeaderSize = insHeader.size;
1335 		mpt::IO::WritePartial(f, insHeader, insHeaderSize);
1336 
1337 		std::vector<SampleIO> sampleFlags(samples.size());
1338 
1339 		// Write Sample Headers
1340 		for(SAMPLEINDEX smp = 0; smp < samples.size(); smp++)
1341 		{
1342 			XMSample xmSample;
1343 			if(samples[smp] <= GetNumSamples())
1344 			{
1345 				xmSample.ConvertToXM(Samples[samples[smp]], GetType(), compatibilityExport);
1346 			} else
1347 			{
1348 				MemsetZero(xmSample);
1349 			}
1350 			sampleFlags[smp] = xmSample.GetSampleFormat();
1351 
1352 			mpt::String::WriteBuf(mpt::String::spacePadded, xmSample.name) = m_szNames[samples[smp]];
1353 
1354 			mpt::IO::Write(f, xmSample);
1355 		}
1356 
1357 		// Write Sample Data
1358 		for(SAMPLEINDEX smp = 0; smp < samples.size(); smp++)
1359 		{
1360 			if(samples[smp] <= GetNumSamples())
1361 			{
1362 				sampleFlags[smp].WriteSample(f, Samples[samples[smp]]);
1363 			}
1364 		}
1365 	}
1366 
1367 	if(!compatibilityExport)
1368 	{
1369 		// Writing song comments
1370 		if(!m_songMessage.empty())
1371 		{
1372 			uint32 size = mpt::saturate_cast<uint32>(m_songMessage.length());
1373 			mpt::IO::WriteRaw(f, "text", 4);
1374 			mpt::IO::WriteIntLE<uint32>(f, size);
1375 			mpt::IO::WriteRaw(f, m_songMessage.c_str(), size);
1376 		}
1377 		// Writing midi cfg
1378 		if(!m_MidiCfg.IsMacroDefaultSetupUsed())
1379 		{
1380 			mpt::IO::WriteRaw(f, "MIDI", 4);
1381 			mpt::IO::WriteIntLE<uint32>(f, sizeof(MIDIMacroConfigData));
1382 			mpt::IO::Write(f, static_cast<MIDIMacroConfigData &>(m_MidiCfg));
1383 		}
1384 		// Writing Pattern Names
1385 		const PATTERNINDEX numNamedPats = Patterns.GetNumNamedPatterns();
1386 		if(numNamedPats > 0)
1387 		{
1388 			mpt::IO::WriteRaw(f, "PNAM", 4);
1389 			mpt::IO::WriteIntLE<uint32>(f, numNamedPats * MAX_PATTERNNAME);
1390 			for(PATTERNINDEX pat = 0; pat < numNamedPats; pat++)
1391 			{
1392 				char name[MAX_PATTERNNAME];
1393 				mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = Patterns[pat].GetName();
1394 				mpt::IO::Write(f, name);
1395 			}
1396 		}
1397 		// Writing Channel Names
1398 		{
1399 			CHANNELINDEX numNamedChannels = 0;
1400 			for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1401 			{
1402 				if (ChnSettings[chn].szName[0]) numNamedChannels = chn + 1;
1403 			}
1404 			// Do it!
1405 			if(numNamedChannels)
1406 			{
1407 				mpt::IO::WriteRaw(f, "CNAM", 4);
1408 				mpt::IO::WriteIntLE<uint32>(f, numNamedChannels * MAX_CHANNELNAME);
1409 				for(CHANNELINDEX chn = 0; chn < numNamedChannels; chn++)
1410 				{
1411 					char name[MAX_CHANNELNAME];
1412 					mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = ChnSettings[chn].szName;
1413 					mpt::IO::Write(f, name);
1414 				}
1415 			}
1416 		}
1417 
1418 		//Save hacked-on extra info
1419 		SaveMixPlugins(&f);
1420 		if(GetNumInstruments())
1421 		{
1422 			SaveExtendedInstrumentProperties(writeInstruments, f);
1423 		}
1424 		SaveExtendedSongProperties(f);
1425 	}
1426 
1427 	return true;
1428 }
1429 
1430 #endif // MODPLUG_NO_FILESAVE
1431 
1432 
1433 OPENMPT_NAMESPACE_END
1434