1 /*
2 * Load_mod.cpp
3 * ------------
4 * Purpose: MOD / NST (ProTracker / NoiseTracker), M15 / STK (Ultimate Soundtracker / Soundtracker) and ST26 (SoundTracker 2.6 / Ice Tracker) module loader / saver
5 * Notes : "2000 LOC for processing MOD files?!" you say? Well, this file also contains loaders for some formats that are almost identical to MOD, and extensive
6 * heuristics for more or less broken MOD files and files saved with tons of different trackers, to allow for the most optimal playback.
7 * Authors: Olivier Lapicque
8 * OpenMPT Devs
9 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
10 */
11
12
13 #include "stdafx.h"
14 #include "Loaders.h"
15 #include "Tables.h"
16 #ifndef MODPLUG_NO_FILESAVE
17 #include "mpt/io/base.hpp"
18 #include "mpt/io/io.hpp"
19 #include "mpt/io/io_stdstream.hpp"
20 #include "../common/mptFileIO.h"
21 #endif
22 #ifdef MPT_EXTERNAL_SAMPLES
23 // For loading external data in Startrekker files
24 #include "../common/mptPathString.h"
25 #endif // MPT_EXTERNAL_SAMPLES
26
27 OPENMPT_NAMESPACE_BEGIN
28
ConvertModCommand(ModCommand & m)29 void CSoundFile::ConvertModCommand(ModCommand &m)
30 {
31 switch(m.command)
32 {
33 case 0x00: if(m.param) m.command = CMD_ARPEGGIO; break;
34 case 0x01: m.command = CMD_PORTAMENTOUP; break;
35 case 0x02: m.command = CMD_PORTAMENTODOWN; break;
36 case 0x03: m.command = CMD_TONEPORTAMENTO; break;
37 case 0x04: m.command = CMD_VIBRATO; break;
38 case 0x05: m.command = CMD_TONEPORTAVOL; break;
39 case 0x06: m.command = CMD_VIBRATOVOL; break;
40 case 0x07: m.command = CMD_TREMOLO; break;
41 case 0x08: m.command = CMD_PANNING8; break;
42 case 0x09: m.command = CMD_OFFSET; break;
43 case 0x0A: m.command = CMD_VOLUMESLIDE; break;
44 case 0x0B: m.command = CMD_POSITIONJUMP; break;
45 case 0x0C: m.command = CMD_VOLUME; break;
46 case 0x0D: m.command = CMD_PATTERNBREAK; m.param = ((m.param >> 4) * 10) + (m.param & 0x0F); break;
47 case 0x0E: m.command = CMD_MODCMDEX; break;
48 case 0x0F:
49 // For a very long time, this code imported 0x20 as CMD_SPEED for MOD files, but this seems to contradict
50 // pretty much the majority of other MOD player out there.
51 // 0x20 is Speed: Impulse Tracker, Scream Tracker, old ModPlug
52 // 0x20 is Tempo: ProTracker, XMPlay, Imago Orpheus, Cubic Player, ChibiTracker, BeRoTracker, DigiTrakker, DigiTrekker, Disorder Tracker 2, DMP, Extreme's Tracker, ...
53 if(m.param < 0x20)
54 m.command = CMD_SPEED;
55 else
56 m.command = CMD_TEMPO;
57 break;
58
59 // Extension for XM extended effects
60 case 'G' - 55: m.command = CMD_GLOBALVOLUME; break; //16
61 case 'H' - 55: m.command = CMD_GLOBALVOLSLIDE; break;
62 case 'K' - 55: m.command = CMD_KEYOFF; break;
63 case 'L' - 55: m.command = CMD_SETENVPOSITION; break;
64 case 'P' - 55: m.command = CMD_PANNINGSLIDE; break;
65 case 'R' - 55: m.command = CMD_RETRIG; break;
66 case 'T' - 55: m.command = CMD_TREMOR; break;
67 case 'W' - 55: m.command = CMD_DUMMY; break;
68 case 'X' - 55: m.command = CMD_XFINEPORTAUPDOWN; break;
69 case 'Y' - 55: m.command = CMD_PANBRELLO; break; // 34
70 case 'Z' - 55: m.command = CMD_MIDI; break; // 35
71 case '\\' - 56: m.command = CMD_SMOOTHMIDI; break; // 36 - note: this is actually displayed as "-" in FT2, but seems to be doing nothing.
72 case 37: m.command = CMD_SMOOTHMIDI; break; // BeRoTracker uses this for smooth MIDI macros for some reason; in old OpenMPT versions this was reserved for the unimplemented "velocity" command
73 case '#' + 3: m.command = CMD_XPARAM; break; // 38
74 default: m.command = CMD_NONE;
75 }
76 }
77
78 #ifndef MODPLUG_NO_FILESAVE
79
ModSaveCommand(uint8 & command,uint8 & param,bool toXM,bool compatibilityExport) const80 void CSoundFile::ModSaveCommand(uint8 &command, uint8 ¶m, bool toXM, bool compatibilityExport) const
81 {
82 switch(command)
83 {
84 case CMD_NONE: command = param = 0; break;
85 case CMD_ARPEGGIO: command = 0; break;
86 case CMD_PORTAMENTOUP:
87 if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_MPT))
88 {
89 if ((param & 0xF0) == 0xE0) { command = 0x0E; param = ((param & 0x0F) >> 2) | 0x10; break; }
90 else if ((param & 0xF0) == 0xF0) { command = 0x0E; param &= 0x0F; param |= 0x10; break; }
91 }
92 command = 0x01;
93 break;
94 case CMD_PORTAMENTODOWN:
95 if(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_MPT))
96 {
97 if ((param & 0xF0) == 0xE0) { command = 0x0E; param= ((param & 0x0F) >> 2) | 0x20; break; }
98 else if ((param & 0xF0) == 0xF0) { command = 0x0E; param &= 0x0F; param |= 0x20; break; }
99 }
100 command = 0x02;
101 break;
102 case CMD_TONEPORTAMENTO: command = 0x03; break;
103 case CMD_VIBRATO: command = 0x04; break;
104 case CMD_TONEPORTAVOL: command = 0x05; break;
105 case CMD_VIBRATOVOL: command = 0x06; break;
106 case CMD_TREMOLO: command = 0x07; break;
107 case CMD_PANNING8:
108 command = 0x08;
109 if(GetType() & MOD_TYPE_S3M)
110 {
111 if(param <= 0x80)
112 {
113 param = mpt::saturate_cast<uint8>(param * 2);
114 }
115 else if(param == 0xA4) // surround
116 {
117 if(compatibilityExport || !toXM)
118 {
119 command = param = 0;
120 }
121 else
122 {
123 command = 'X' - 55;
124 param = 91;
125 }
126 }
127 }
128 break;
129 case CMD_OFFSET: command = 0x09; break;
130 case CMD_VOLUMESLIDE: command = 0x0A; break;
131 case CMD_POSITIONJUMP: command = 0x0B; break;
132 case CMD_VOLUME: command = 0x0C; break;
133 case CMD_PATTERNBREAK: command = 0x0D; param = ((param / 10) << 4) | (param % 10); break;
134 case CMD_MODCMDEX: command = 0x0E; break;
135 case CMD_SPEED: command = 0x0F; param = std::min(param, uint8(0x1F)); break;
136 case CMD_TEMPO: command = 0x0F; param = std::max(param, uint8(0x20)); break;
137 case CMD_GLOBALVOLUME: command = 'G' - 55; break;
138 case CMD_GLOBALVOLSLIDE: command = 'H' - 55; break;
139 case CMD_KEYOFF: command = 'K' - 55; break;
140 case CMD_SETENVPOSITION: command = 'L' - 55; break;
141 case CMD_PANNINGSLIDE: command = 'P' - 55; break;
142 case CMD_RETRIG: command = 'R' - 55; break;
143 case CMD_TREMOR: command = 'T' - 55; break;
144 case CMD_DUMMY: command = 'W' - 55; break;
145 case CMD_XFINEPORTAUPDOWN: command = 'X' - 55;
146 if(compatibilityExport && param >= 0x30) // X1x and X2x are legit, everything above are MPT extensions, which don't belong here.
147 param = 0; // Don't set command to 0 to indicate that there *was* some X command here...
148 break;
149 case CMD_PANBRELLO:
150 if(compatibilityExport)
151 command = param = 0;
152 else
153 command = 'Y' - 55;
154 break;
155 case CMD_MIDI:
156 if(compatibilityExport)
157 command = param = 0;
158 else
159 command = 'Z' - 55;
160 break;
161 case CMD_SMOOTHMIDI: //rewbs.smoothVST: 36
162 if(compatibilityExport)
163 command = param = 0;
164 else
165 command = '\\' - 56;
166 break;
167 case CMD_XPARAM: //rewbs.XMfixes - XParam is 38
168 if(compatibilityExport)
169 command = param = 0;
170 else
171 command = '#' + 3;
172 break;
173 case CMD_S3MCMDEX:
174 switch(param & 0xF0)
175 {
176 case 0x10: command = 0x0E; param = (param & 0x0F) | 0x30; break;
177 case 0x20: command = 0x0E; param = (param & 0x0F) | 0x50; break;
178 case 0x30: command = 0x0E; param = (param & 0x0F) | 0x40; break;
179 case 0x40: command = 0x0E; param = (param & 0x0F) | 0x70; break;
180 case 0x90:
181 if(compatibilityExport)
182 command = param = 0;
183 else
184 command = 'X' - 55;
185 break;
186 case 0xB0: command = 0x0E; param = (param & 0x0F) | 0x60; break;
187 case 0xA0:
188 case 0x50:
189 case 0x70:
190 case 0x60: command = param = 0; break;
191 default: command = 0x0E; break;
192 }
193 break;
194 default:
195 command = param = 0;
196 }
197
198 // Don't even think about saving XM effects in MODs...
199 if(command > 0x0F && !toXM)
200 {
201 command = param = 0;
202 }
203 }
204
205 #endif // MODPLUG_NO_FILESAVE
206
207
208 // File Header
209 struct MODFileHeader
210 {
211 uint8be numOrders;
212 uint8be restartPos;
213 uint8be orderList[128];
214 };
215
216 MPT_BINARY_STRUCT(MODFileHeader, 130)
217
218
219 // Sample Header
220 struct MODSampleHeader
221 {
222 char name[22];
223 uint16be length;
224 uint8be finetune;
225 uint8be volume;
226 uint16be loopStart;
227 uint16be loopLength;
228
229 // Convert an MOD sample header to OpenMPT's internal sample header.
ConvertToMPTMODSampleHeader230 void ConvertToMPT(ModSample &mptSmp, bool is4Chn) const
231 {
232 mptSmp.Initialize(MOD_TYPE_MOD);
233 mptSmp.nLength = length * 2;
234 mptSmp.nFineTune = MOD2XMFineTune(finetune & 0x0F);
235 mptSmp.nVolume = 4u * std::min(volume.get(), uint8(64));
236
237 SmpLength lStart = loopStart * 2;
238 SmpLength lLength = loopLength * 2;
239 // See if loop start is incorrect as words, but correct as bytes (like in Soundtracker modules)
240 if(lLength > 2 && (lStart + lLength > mptSmp.nLength)
241 && (lStart / 2 + lLength <= mptSmp.nLength))
242 {
243 lStart /= 2;
244 }
245
246 if(mptSmp.nLength == 2)
247 {
248 mptSmp.nLength = 0;
249 }
250
251 if(mptSmp.nLength)
252 {
253 mptSmp.nLoopStart = lStart;
254 mptSmp.nLoopEnd = lStart + lLength;
255
256 if(mptSmp.nLoopStart >= mptSmp.nLength)
257 {
258 mptSmp.nLoopStart = mptSmp.nLength - 1;
259 }
260 if(mptSmp.nLoopStart > mptSmp.nLoopEnd || mptSmp.nLoopEnd < 4 || mptSmp.nLoopEnd - mptSmp.nLoopStart < 4)
261 {
262 mptSmp.nLoopStart = 0;
263 mptSmp.nLoopEnd = 0;
264 }
265
266 // Fix for most likely broken sample loops. This fixes super_sufm_-_new_life.mod (M.K.) which has a long sample which is looped from 0 to 4.
267 // This module also has notes outside of the Amiga frequency range, so we cannot say that it should be played using ProTracker one-shot loops.
268 // On the other hand, "Crew Generation" by Necros (6CHN) has a sample with a similar loop, which is supposed to be played.
269 // To be able to correctly play both modules, we will draw a somewhat arbitrary line here and trust the loop points in MODs with more than
270 // 4 channels, even if they are tiny and at the very beginning of the sample.
271 if(mptSmp.nLoopEnd <= 8 && mptSmp.nLoopStart == 0 && mptSmp.nLength > mptSmp.nLoopEnd && is4Chn)
272 {
273 mptSmp.nLoopEnd = 0;
274 }
275 if(mptSmp.nLoopEnd > mptSmp.nLoopStart)
276 {
277 mptSmp.uFlags.set(CHN_LOOP);
278 }
279 }
280 }
281
282 // Convert OpenMPT's internal sample header to a MOD sample header.
ConvertToMODMODSampleHeader283 SmpLength ConvertToMOD(const ModSample &mptSmp)
284 {
285 SmpLength writeLength = mptSmp.HasSampleData() ? mptSmp.nLength : 0;
286 // If the sample size is odd, we have to add a padding byte, as all sample sizes in MODs are even.
287 if((writeLength % 2u) != 0)
288 {
289 writeLength++;
290 }
291 LimitMax(writeLength, SmpLength(0x1FFFE));
292
293 length = static_cast<uint16>(writeLength / 2u);
294
295 if(mptSmp.RelativeTone < 0)
296 {
297 finetune = 0x08;
298 } else if(mptSmp.RelativeTone > 0)
299 {
300 finetune = 0x07;
301 } else
302 {
303 finetune = XM2MODFineTune(mptSmp.nFineTune);
304 }
305 volume = static_cast<uint8>(mptSmp.nVolume / 4u);
306
307 loopStart = 0;
308 loopLength = 1;
309 if(mptSmp.uFlags[CHN_LOOP] && (mptSmp.nLoopStart + 2u) < writeLength)
310 {
311 const SmpLength loopEnd = Clamp(mptSmp.nLoopEnd, (mptSmp.nLoopStart & ~1) + 2u, writeLength) & ~1;
312 loopStart = static_cast<uint16>(mptSmp.nLoopStart / 2u);
313 loopLength = static_cast<uint16>((loopEnd - (mptSmp.nLoopStart & ~1)) / 2u);
314 }
315
316 return writeLength;
317 }
318
319 // Compute a "rating" of this sample header by counting invalid header data to ultimately reject garbage files.
GetInvalidByteScoreMODSampleHeader320 uint32 GetInvalidByteScore() const
321 {
322 return ((volume > 64) ? 1 : 0)
323 + ((finetune > 15) ? 1 : 0)
324 + ((loopStart > length * 2) ? 1 : 0);
325 }
326
327 // Suggested threshold for rejecting invalid files based on cumulated score returned by GetInvalidByteScore
328 static constexpr uint32 INVALID_BYTE_THRESHOLD = 40;
329
330 // This threshold is used for files where the file magic only gives a
331 // fragile result which alone would lead to too many false positives.
332 // In particular, the files from Inconexia demo by Iguana
333 // (https://www.pouet.net/prod.php?which=830) which have 3 \0 bytes in
334 // the file magic tend to cause misdetection of random files.
335 static constexpr uint32 INVALID_BYTE_FRAGILE_THRESHOLD = 1;
336
337 // Retrieve the internal sample format flags for this sample.
GetSampleFormatMODSampleHeader338 static SampleIO GetSampleFormat()
339 {
340 return SampleIO(
341 SampleIO::_8bit,
342 SampleIO::mono,
343 SampleIO::bigEndian,
344 SampleIO::signedPCM);
345 }
346 };
347
348 MPT_BINARY_STRUCT(MODSampleHeader, 30)
349
350 // Pattern data of a 4-channel MOD file
351 using MODPatternData = std::array<std::array<std::array<uint8, 4>, 4>, 64>;
352
353 // Synthesized StarTrekker instruments
354 struct AMInstrument
355 {
356 char am[2]; // "AM"
357 char zero[4];
358 uint16be startLevel; // Start level
359 uint16be attack1Level; // Attack 1 level
360 uint16be attack1Speed; // Attack 1 speed
361 uint16be attack2Level; // Attack 2 level
362 uint16be attack2Speed; // Attack 2 speed
363 uint16be sustainLevel; // Sustain level
364 uint16be decaySpeed; // Decay speed
365 uint16be sustainTime; // Sustain time
366 uint16be nt; // ?
367 uint16be releaseSpeed; // Release speed
368 uint16be waveform; // Waveform
369 int16be pitchFall; // Pitch fall
370 uint16be vibAmp; // Vibrato amplitude
371 uint16be vibSpeed; // Vibrato speed
372 uint16be octave; // Base frequency
373
ConvertToMPTAMInstrument374 void ConvertToMPT(ModSample &sample, ModInstrument &ins, mpt::fast_prng &rng) const
375 {
376 sample.nLength = waveform == 3 ? 1024 : 32;
377 sample.nLoopStart = 0;
378 sample.nLoopEnd = sample.nLength;
379 sample.uFlags.set(CHN_LOOP);
380 sample.nVolume = 256; // prelude.mod has volume 0 in sample header
381 sample.nVibDepth = mpt::saturate_cast<uint8>(vibAmp * 2);
382 sample.nVibRate = static_cast<uint8>(vibSpeed);
383 sample.nVibType = VIB_SINE;
384 sample.RelativeTone = static_cast<int8>(-12 * octave);
385 if(sample.AllocateSample())
386 {
387 int8 *p = sample.sample8();
388 for(SmpLength i = 0; i < sample.nLength; i++)
389 {
390 switch(waveform)
391 {
392 default:
393 case 0: p[i] = ModSinusTable[i * 2]; break; // Sine
394 case 1: p[i] = static_cast<int8>(-128 + i * 8); break; // Saw
395 case 2: p[i] = i < 16 ? -128 : 127; break; // Square
396 case 3: p[i] = mpt::random<int8>(rng); break; // Noise
397 }
398 }
399 }
400
401 InstrumentEnvelope &volEnv = ins.VolEnv;
402 volEnv.dwFlags.set(ENV_ENABLED);
403 volEnv.reserve(6);
404 volEnv.push_back(0, static_cast<EnvelopeNode::value_t>(startLevel / 4));
405
406 const struct
407 {
408 uint16 level, speed;
409 } points[] = {{startLevel, 0}, {attack1Level, attack1Speed}, {attack2Level, attack2Speed}, {sustainLevel, decaySpeed}, {sustainLevel, sustainTime}, {0, releaseSpeed}};
410
411 for(uint8 i = 1; i < std::size(points); i++)
412 {
413 int duration = std::min(points[i].speed, uint16(256));
414 // Sustain time is already in ticks, no need to compute the segment duration.
415 if(i != 4)
416 {
417 if(duration == 0)
418 {
419 volEnv.dwFlags.set(ENV_LOOP);
420 volEnv.nLoopStart = volEnv.nLoopEnd = static_cast<uint8>(volEnv.size() - 1);
421 break;
422 }
423
424 // Startrekker increments / decrements the envelope level by the stage speed
425 // until it reaches the next stage level.
426 int a, b;
427 if(points[i].level > points[i - 1].level)
428 {
429 a = points[i].level - points[i - 1].level;
430 b = 256 - points[i - 1].level;
431 } else
432 {
433 a = points[i - 1].level - points[i].level;
434 b = points[i - 1].level;
435 }
436 // Release time is again special.
437 if(i == 5)
438 b = 256;
439 else if(b == 0)
440 b = 1;
441 duration = std::max((256 * a) / (duration * b), 1);
442 }
443 if(duration > 0)
444 {
445 volEnv.push_back(volEnv.back().tick + static_cast<EnvelopeNode::tick_t>(duration), static_cast<EnvelopeNode::value_t>(points[i].level / 4));
446 }
447 }
448
449 if(pitchFall)
450 {
451 InstrumentEnvelope &pitchEnv = ins.PitchEnv;
452 pitchEnv.dwFlags.set(ENV_ENABLED);
453 pitchEnv.reserve(2);
454 pitchEnv.push_back(0, ENVELOPE_MID);
455 // cppcheck false-positive
456 // cppcheck-suppress zerodiv
457 pitchEnv.push_back(static_cast<EnvelopeNode::tick_t>(1024 / abs(pitchFall)), pitchFall > 0 ? ENVELOPE_MIN : ENVELOPE_MAX);
458 }
459 }
460 };
461
462 MPT_BINARY_STRUCT(AMInstrument, 36)
463
464 struct PT36IffChunk
465 {
466 // IFF chunk names
467 enum ChunkIdentifiers
468 {
469 idVERS = MagicBE("VERS"),
470 idINFO = MagicBE("INFO"),
471 idCMNT = MagicBE("CMNT"),
472 idPTDT = MagicBE("PTDT"),
473 };
474
475 uint32be signature; // IFF chunk name
476 uint32be chunksize; // chunk size without header
477 };
478
479 MPT_BINARY_STRUCT(PT36IffChunk, 8)
480
481 struct PT36InfoChunk
482 {
483 char name[32];
484 uint16be numSamples;
485 uint16be numOrders;
486 uint16be numPatterns;
487 uint16be volume;
488 uint16be tempo;
489 uint16be flags;
490 uint16be dateDay;
491 uint16be dateMonth;
492 uint16be dateYear;
493 uint16be dateHour;
494 uint16be dateMinute;
495 uint16be dateSecond;
496 uint16be playtimeHour;
497 uint16be playtimeMinute;
498 uint16be playtimeSecond;
499 uint16be playtimeMsecond;
500 };
501
502 MPT_BINARY_STRUCT(PT36InfoChunk, 64)
503
504
505 // Check if header magic equals a given string.
IsMagic(const char * magic1,const char (& magic2)[5])506 static bool IsMagic(const char *magic1, const char (&magic2)[5])
507 {
508 return std::memcmp(magic1, magic2, 4) == 0;
509 }
510
511
ReadSample(FileReader & file,MODSampleHeader & sampleHeader,ModSample & sample,mpt::charbuf<MAX_SAMPLENAME> & sampleName,bool is4Chn)512 static uint32 ReadSample(FileReader &file, MODSampleHeader &sampleHeader, ModSample &sample, mpt::charbuf<MAX_SAMPLENAME> &sampleName, bool is4Chn)
513 {
514 file.ReadStruct(sampleHeader);
515 sampleHeader.ConvertToMPT(sample, is4Chn);
516
517 sampleName = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
518 // Get rid of weird characters in sample names.
519 for(auto &c : sampleName.buf)
520 {
521 if(c > 0 && c < ' ')
522 {
523 c = ' ';
524 }
525 }
526 // Check for invalid values
527 return sampleHeader.GetInvalidByteScore();
528 }
529
530
531 // Count malformed bytes in MOD pattern data
CountMalformedMODPatternData(const MODPatternData & patternData,const bool allow31Samples)532 static uint32 CountMalformedMODPatternData(const MODPatternData &patternData, const bool allow31Samples)
533 {
534 const uint8 mask = allow31Samples ? 0xE0 : 0xF0;
535 uint32 malformedBytes = 0;
536 for(const auto &row : patternData)
537 {
538 for(const auto &data : row)
539 {
540 if(data[0] & mask)
541 malformedBytes++;
542 }
543 }
544 return malformedBytes;
545 }
546
547
548 // Check if number of malformed bytes in MOD pattern data exceeds some threshold
549 template <typename TFileReader>
ValidateMODPatternData(TFileReader & file,const uint32 threshold,const bool allow31Samples)550 static bool ValidateMODPatternData(TFileReader &file, const uint32 threshold, const bool allow31Samples)
551 {
552 MODPatternData patternData;
553 if(!file.Read(patternData))
554 return false;
555 return CountMalformedMODPatternData(patternData, allow31Samples) <= threshold;
556 }
557
558
559 // Parse the order list to determine how many patterns are used in the file.
GetNumPatterns(FileReader & file,ModSequence & Order,ORDERINDEX numOrders,SmpLength totalSampleLen,CHANNELINDEX & numChannels,SmpLength wowSampleLen,bool validateHiddenPatterns)560 static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERINDEX numOrders, SmpLength totalSampleLen, CHANNELINDEX &numChannels, SmpLength wowSampleLen, bool validateHiddenPatterns)
561 {
562 PATTERNINDEX numPatterns = 0; // Total number of patterns in file (determined by going through the whole order list) with pattern number < 128
563 PATTERNINDEX officialPatterns = 0; // Number of patterns only found in the "official" part of the order list (i.e. order positions < claimed order length)
564 PATTERNINDEX numPatternsIllegal = 0; // Total number of patterns in file, also counting in "invalid" pattern indexes >= 128
565
566 for(ORDERINDEX ord = 0; ord < 128; ord++)
567 {
568 PATTERNINDEX pat = Order[ord];
569 if(pat < 128 && numPatterns <= pat)
570 {
571 numPatterns = pat + 1;
572 if(ord < numOrders)
573 {
574 officialPatterns = numPatterns;
575 }
576 }
577 if(pat >= numPatternsIllegal)
578 {
579 numPatternsIllegal = pat + 1;
580 }
581 }
582
583 // Remove the garbage patterns past the official order end now that we don't need them anymore.
584 Order.resize(numOrders);
585
586 const size_t patternStartOffset = file.GetPosition();
587 const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset;
588
589 if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == (file.GetLength() & ~1))
590 {
591 // Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files.
592 // We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end
593 // (e.g. ponylips.mod, MD5 c039af363b1d99a492dafc5b5f9dd949, SHA1 1bee1941c47bc6f913735ce0cf1880b248b8fc93)
594 file.Seek(patternStartOffset + numPatterns * 4 * 256);
595 if(ValidateMODPatternData(file, 16, true))
596 numChannels = 8;
597 file.Seek(patternStartOffset);
598 } else if(numPatterns != officialPatterns && validateHiddenPatterns)
599 {
600 // Fix SoundTracker modules where "hidden" patterns should be ignored.
601 // razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b)
602 // and captain_fizz.mod (MD5 55bd89fe5a8e345df65438dbfc2df94e, SHA1 9e0e8b7dc67939885435ea8d3ff4be7704207a43)
603 // seem to have the "correct" file size when only taking the "official" patterns into account,
604 // but they only play correctly when also loading the inofficial patterns.
605 // On the other hand, the SoundTracker module
606 // wolf1.mod (MD5 a4983d7a432d324ce8261b019257f4ed, SHA1 aa6b399d02546bcb6baf9ec56a8081730dea3f44),
607 // wolf3.mod (MD5 af60840815aa9eef43820a7a04417fa6, SHA1 24d6c2e38894f78f6c5c6a4b693a016af8fa037b)
608 // and jean_baudlot_-_bad_dudes_vs_dragonninja-dragonf.mod (MD5 fa48e0f805b36bdc1833f6b82d22d936, SHA1 39f2f8319f4847fe928b9d88eee19d79310b9f91)
609 // only play correctly if we ignore the hidden patterns.
610 // Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data.
611 // If that is the case, we assume it's part of the sample data and only consider the "official" patterns.
612 file.Seek(patternStartOffset + officialPatterns * 1024);
613 if(!ValidateMODPatternData(file, 64, true))
614 numPatterns = officialPatterns;
615 file.Seek(patternStartOffset);
616 }
617
618 #ifdef MPT_BUILD_DEBUG
619 // Check if the "hidden" patterns in the order list are actually real, i.e. if they are saved in the file.
620 // OpenMPT did this check in the past, but no other tracker appears to do this.
621 // Interestingly, (broken) variants of the ProTracker modules
622 // "killing butterfly" (MD5 bd676358b1dbb40d40f25435e845cf6b, SHA1 9df4ae21214ff753802756b616a0cafaeced8021),
623 // "quartex" by Reflex (MD5 35526bef0fb21cb96394838d94c14bab, SHA1 116756c68c7b6598dcfbad75a043477fcc54c96c),
624 // seem to have the "correct" file size when only taking the "official" patterns into account, but they only play
625 // correctly when also loading the inofficial patterns.
626 // See also the above check for ambiguities with SoundTracker modules.
627 // Keep this assertion in the code to find potential other broken MODs.
628 if(numPatterns != officialPatterns && sizeWithoutPatterns + officialPatterns * numChannels * 256 == file.GetLength())
629 {
630 MPT_ASSERT(false);
631 //numPatterns = officialPatterns;
632 } else
633 #endif
634 if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == file.GetLength())
635 {
636 // Even those illegal pattern indexes (> 128) appear to be valid... What a weird file!
637 // e.g. NIETNU.MOD, where the end of the order list is filled with FF rather than 00, and the file actually contains 256 patterns.
638 numPatterns = numPatternsIllegal;
639 } else if(numPatternsIllegal >= 0xFF)
640 {
641 // Patterns FE and FF are used with S3M semantics (e.g. some MODs written with old OpenMPT versions)
642 Order.Replace(0xFE, Order.GetIgnoreIndex());
643 Order.Replace(0xFF, Order.GetInvalidPatIndex());
644 }
645
646 return numPatterns;
647 }
648
649
ReadMODPatternEntry(FileReader & file,ModCommand & m)650 void CSoundFile::ReadMODPatternEntry(FileReader &file, ModCommand &m)
651 {
652 ReadMODPatternEntry(file.ReadArray<uint8, 4>(), m);
653 }
654
655
ReadMODPatternEntry(const std::array<uint8,4> data,ModCommand & m)656 void CSoundFile::ReadMODPatternEntry(const std::array<uint8, 4> data, ModCommand &m)
657 {
658 // Read Period
659 uint16 period = (((static_cast<uint16>(data[0]) & 0x0F) << 8) | data[1]);
660 size_t note = NOTE_NONE;
661 if(period > 0 && period != 0xFFF)
662 {
663 note = std::size(ProTrackerPeriodTable) + 23 + NOTE_MIN;
664 for(size_t i = 0; i < std::size(ProTrackerPeriodTable); i++)
665 {
666 if(period >= ProTrackerPeriodTable[i])
667 {
668 if(period != ProTrackerPeriodTable[i] && i != 0)
669 {
670 uint16 p1 = ProTrackerPeriodTable[i - 1];
671 uint16 p2 = ProTrackerPeriodTable[i];
672 if(p1 - period < (period - p2))
673 {
674 note = i + 23 + NOTE_MIN;
675 break;
676 }
677 }
678 note = i + 24 + NOTE_MIN;
679 break;
680 }
681 }
682 }
683 m.note = static_cast<ModCommand::NOTE>(note);
684 // Read Instrument
685 m.instr = (data[2] >> 4) | (data[0] & 0x10);
686 // Read Effect
687 m.command = data[2] & 0x0F;
688 m.param = data[3];
689 }
690
691
692 struct MODMagicResult
693 {
694 const mpt::uchar *madeWithTracker = nullptr;
695 uint32 invalidByteThreshold = MODSampleHeader::INVALID_BYTE_THRESHOLD;
696 uint16 patternDataOffset = 1084;
697 CHANNELINDEX numChannels = 0;
698 bool isNoiseTracker = false;
699 bool isStartrekker = false;
700 bool isGenericMultiChannel = false;
701 bool setMODVBlankTiming = false;
702 };
703
704
CheckMODMagic(const char magic[4],MODMagicResult & result)705 static bool CheckMODMagic(const char magic[4], MODMagicResult &result)
706 {
707 if(IsMagic(magic, "M.K.") // ProTracker and compatible
708 || IsMagic(magic, "M!K!") // ProTracker (>64 patterns)
709 || IsMagic(magic, "PATT") // ProTracker 3.6
710 || IsMagic(magic, "NSMS") // kingdomofpleasure.mod by bee hunter
711 || IsMagic(magic, "LARD")) // judgement_day_gvine.mod by 4-mat
712 {
713 result.madeWithTracker = UL_("Generic ProTracker or compatible");
714 result.numChannels = 4;
715 } else if(IsMagic(magic, "M&K!") // "His Master's Noise" musicdisk
716 || IsMagic(magic, "FEST") // "His Master's Noise" musicdisk
717 || IsMagic(magic, "N.T."))
718 {
719 result.madeWithTracker = UL_("NoiseTracker");
720 result.isNoiseTracker = true;
721 result.numChannels = 4;
722 } else if(IsMagic(magic, "OKTA")
723 || IsMagic(magic, "OCTA"))
724 {
725 // Oktalyzer
726 result.madeWithTracker = UL_("Oktalyzer");
727 result.numChannels = 8;
728 } else if(IsMagic(magic, "CD81")
729 || IsMagic(magic, "CD61"))
730 {
731 // Octalyser on Atari STe/Falcon
732 result.madeWithTracker = UL_("Octalyser (Atari)");
733 result.numChannels = magic[2] - '0';
734 } else if(IsMagic(magic, "M\0\0\0") || IsMagic(magic, "8\0\0\0"))
735 {
736 // Inconexia demo by Iguana, delta samples (https://www.pouet.net/prod.php?which=830)
737 result.madeWithTracker = UL_("Inconexia demo (delta samples)");
738 result.invalidByteThreshold = MODSampleHeader::INVALID_BYTE_FRAGILE_THRESHOLD;
739 result.numChannels = (magic[0] == '8') ? 8 : 4;
740 } else if(!memcmp(magic, "FA0", 3) && magic[3] >= '4' && magic[3] <= '8')
741 {
742 // Digital Tracker on Atari Falcon
743 result.madeWithTracker = UL_("Digital Tracker");
744 result.numChannels = magic[3] - '0';
745 // Digital Tracker MODs contain four bytes (00 40 00 00) right after the magic bytes which don't seem to do anything special.
746 result.patternDataOffset = 1088;
747 } else if((!memcmp(magic, "FLT", 3) || !memcmp(magic, "EXO", 3)) && magic[3] >= '4' && magic[3] <= '9')
748 {
749 // FLTx / EXOx - Startrekker by Exolon / Fairlight
750 result.madeWithTracker = UL_("Startrekker");
751 result.isStartrekker = true;
752 result.setMODVBlankTiming = true;
753 result.numChannels = magic[3] - '0';
754 } else if(magic[0] >= '1' && magic[0] <= '9' && !memcmp(magic + 1, "CHN", 3))
755 {
756 // xCHN - Many trackers
757 result.madeWithTracker = UL_("Generic MOD-compatible Tracker");
758 result.isGenericMultiChannel = true;
759 result.numChannels = magic[0] - '0';
760 } else if(magic[0] >= '1' && magic[0] <= '9' && magic[1] >= '0' && magic[1] <= '9'
761 && (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2)))
762 {
763 // xxCN / xxCH - Many trackers
764 result.madeWithTracker = UL_("Generic MOD-compatible Tracker");
765 result.isGenericMultiChannel = true;
766 result.numChannels = (magic[0] - '0') * 10 + magic[1] - '0';
767 } else if(!memcmp(magic, "TDZ", 3) && magic[3] >= '1' && magic[3] <= '9')
768 {
769 // TDZx - TakeTracker (only TDZ1-TDZ3 should exist, but historically this code only supported 4-9 channels, so we keep those for the unlikely case that they were actually used for something)
770 result.madeWithTracker = UL_("TakeTracker");
771 result.numChannels = magic[3] - '0';
772 } else
773 {
774 return false;
775 }
776 return true;
777 }
778
779
ProbeFileHeaderMOD(MemoryFileReader file,const uint64 * pfilesize)780 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMOD(MemoryFileReader file, const uint64 *pfilesize)
781 {
782 if(!file.CanRead(1080 + 4))
783 {
784 return ProbeWantMoreData;
785 }
786 file.Seek(1080);
787 char magic[4];
788 file.ReadArray(magic);
789 MODMagicResult modMagicResult;
790 if(!CheckMODMagic(magic, modMagicResult))
791 {
792 return ProbeFailure;
793 }
794
795 file.Seek(20);
796 uint32 invalidBytes = 0;
797 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
798 {
799 MODSampleHeader sampleHeader;
800 file.ReadStruct(sampleHeader);
801 invalidBytes += sampleHeader.GetInvalidByteScore();
802 }
803 if(invalidBytes > modMagicResult.invalidByteThreshold)
804 {
805 return ProbeFailure;
806 }
807
808 MPT_UNREFERENCED_PARAMETER(pfilesize);
809 return ProbeSuccess;
810 }
811
812
ReadMOD(FileReader & file,ModLoadingFlags loadFlags)813 bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
814 {
815 char magic[4];
816 if(!file.Seek(1080) || !file.ReadArray(magic))
817 {
818 return false;
819 }
820
821 InitializeGlobals(MOD_TYPE_MOD);
822
823 MODMagicResult modMagicResult;
824 if(!CheckMODMagic(magic, modMagicResult)
825 || modMagicResult.numChannels < 1
826 || modMagicResult.numChannels > MAX_BASECHANNELS)
827 {
828 return false;
829 }
830
831 if(loadFlags == onlyVerifyHeader)
832 {
833 return true;
834 }
835
836 m_nChannels = modMagicResult.numChannels;
837
838 bool isNoiseTracker = modMagicResult.isNoiseTracker;
839 bool isStartrekker = modMagicResult.isStartrekker;
840 bool isGenericMultiChannel = modMagicResult.isGenericMultiChannel;
841 bool isInconexia = IsMagic(magic, "M\0\0\0") || IsMagic(magic, "8\0\0\0");
842 // A loop length of zero will freeze ProTracker, so assume that modules having such a value were not meant to be played on Amiga. Fixes LHS_MI.MOD
843 bool hasRepLen0 = false;
844 // Empty sample slots typically should have a default volume of 0 in ProTracker
845 bool hasEmptySampleWithVolume = false;
846 if(modMagicResult.setMODVBlankTiming)
847 {
848 m_playBehaviour.set(kMODVBlankTiming);
849 }
850
851 // Startrekker 8 channel mod (needs special treatment, see below)
852 const bool isFLT8 = isStartrekker && m_nChannels == 8;
853 // Only apply VBlank tests to M.K. (ProTracker) modules.
854 const bool isMdKd = IsMagic(magic, "M.K.");
855 // Adjust finetune values for modules saved with "His Master's Noisetracker"
856 const bool isHMNT = IsMagic(magic, "M&K!") || IsMagic(magic, "FEST");
857 bool maybeWOW = isMdKd;
858
859 // Reading song title
860 file.Seek(0);
861 file.ReadString<mpt::String::spacePadded>(m_songName, 20);
862
863 // Load Sample Headers
864 SmpLength totalSampleLen = 0, wowSampleLen = 0;
865 m_nSamples = 31;
866 uint32 invalidBytes = 0;
867 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
868 {
869 MODSampleHeader sampleHeader;
870 invalidBytes += ReadSample(file, sampleHeader, Samples[smp], m_szNames[smp], m_nChannels == 4);
871 totalSampleLen += Samples[smp].nLength;
872
873 if(isHMNT)
874 Samples[smp].nFineTune = -static_cast<int8>(sampleHeader.finetune << 3);
875 else if(Samples[smp].nLength > 65535)
876 isNoiseTracker = false;
877
878 if(sampleHeader.length && !sampleHeader.loopLength)
879 hasRepLen0 = true;
880 else if(!sampleHeader.length && sampleHeader.volume == 64)
881 hasEmptySampleWithVolume = true;
882
883 if(maybeWOW)
884 {
885 // Some WOW files rely on sample length 1 being counted as well
886 wowSampleLen += sampleHeader.length * 2;
887 // WOW files are converted 669 files, which don't support finetune or default volume
888 if(sampleHeader.finetune)
889 maybeWOW = false;
890 else if(sampleHeader.length > 0 && sampleHeader.volume != 64)
891 maybeWOW = false;
892 }
893 }
894 // If there is too much binary garbage in the sample headers, reject the file.
895 if(invalidBytes > modMagicResult.invalidByteThreshold)
896 {
897 return false;
898 }
899
900 // Read order information
901 MODFileHeader fileHeader;
902 file.ReadStruct(fileHeader);
903
904 file.Seek(modMagicResult.patternDataOffset);
905
906 if(fileHeader.restartPos > 0)
907 maybeWOW = false;
908 if(!maybeWOW)
909 wowSampleLen = 0;
910
911 ReadOrderFromArray(Order(), fileHeader.orderList);
912
913 ORDERINDEX realOrders = fileHeader.numOrders;
914 if(realOrders > 128)
915 {
916 // beatwave.mod by Sidewinder claims to have 129 orders. (MD5: 8a029ac498d453beb929db9a73c3c6b4, SHA1: f7b76fb9f477b07a2e78eb10d8624f0df262cde7 - the version from ModArchive, not ModLand)
917 realOrders = 128;
918 } else if(realOrders == 0)
919 {
920 // Is this necessary?
921 realOrders = 128;
922 while(realOrders > 1 && Order()[realOrders - 1] == 0)
923 {
924 realOrders--;
925 }
926 }
927
928 // Get number of patterns (including some order list sanity checks)
929 PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, wowSampleLen, false);
930 if(maybeWOW && GetNumChannels() == 8)
931 {
932 // M.K. with 8 channels = Mod's Grave
933 modMagicResult.madeWithTracker = UL_("Mod's Grave");
934 isGenericMultiChannel = true;
935 }
936
937 if(isFLT8)
938 {
939 // FLT8 has only even order items, so divide by two.
940 for(auto &pat : Order())
941 {
942 pat /= 2u;
943 }
944 }
945
946 // Restart position sanity checks
947 realOrders--;
948 Order().SetRestartPos(fileHeader.restartPos);
949
950 // (Ultimate) Soundtracker didn't have a restart position, but instead stored a default tempo in this value.
951 // The default value for this is 0x78 (120 BPM). This is probably the reason why some M.K. modules
952 // have this weird restart position. I think I've read somewhere that NoiseTracker actually writes 0x78 there.
953 // M.K. files that have restart pos == 0x78: action's batman by DJ Uno, VALLEY.MOD, WormsTDC.MOD, ZWARTZ.MOD
954 // Files that have an order list longer than 0x78 with restart pos = 0x78: my_shoe_is_barking.mod, papermix.mod
955 // - in both cases it does not appear like the restart position should be used.
956 MPT_ASSERT(fileHeader.restartPos != 0x78 || fileHeader.restartPos + 1u >= realOrders);
957 if(fileHeader.restartPos > realOrders || (fileHeader.restartPos == 0x78 && m_nChannels == 4))
958 {
959 Order().SetRestartPos(0);
960 }
961
962 m_nDefaultSpeed = 6;
963 m_nDefaultTempo.Set(125);
964 m_nMinPeriod = 14 * 4;
965 m_nMaxPeriod = 3424 * 4;
966 // Prevent clipping based on number of channels... If all channels are playing at full volume, "256 / #channels"
967 // is the maximum possible sample pre-amp without getting distortion (Compatible mix levels given).
968 // The more channels we have, the less likely it is that all of them are used at the same time, though, so cap at 32...
969 m_nSamplePreAmp = Clamp(256 / m_nChannels, 32, 128);
970 m_SongFlags.reset(); // SONG_ISAMIGA will be set conditionally
971
972 // Setup channel pan positions and volume
973 SetupMODPanning();
974
975 // Before loading patterns, apply some heuristics:
976 // - Scan patterns to check if file could be a NoiseTracker file in disguise.
977 // In this case, the parameter of Dxx commands needs to be ignored.
978 // - Use the same code to find notes that would be out-of-range on Amiga.
979 // - Detect 7-bit panning and whether 8xx / E8x commands should be interpreted as panning at all.
980 bool onlyAmigaNotes = true;
981 bool fix7BitPanning = false;
982 uint8 maxPanning = 0; // For detecting 8xx-as-sync
983 const uint8 ENABLE_MOD_PANNING_THRESHOLD = 0x30;
984 if(!isNoiseTracker)
985 {
986 bool leftPanning = false, extendedPanning = false; // For detecting 800-880 panning
987 isNoiseTracker = isMdKd;
988 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
989 {
990 uint16 patternBreaks = 0;
991
992 for(uint32 i = 0; i < 256; i++)
993 {
994 ModCommand m;
995 ReadMODPatternEntry(file, m);
996 if(!m.IsAmigaNote())
997 {
998 isNoiseTracker = onlyAmigaNotes = false;
999 }
1000 if((m.command > 0x06 && m.command < 0x0A)
1001 || (m.command == 0x0E && m.param > 0x01)
1002 || (m.command == 0x0F && m.param > 0x1F)
1003 || (m.command == 0x0D && ++patternBreaks > 1))
1004 {
1005 isNoiseTracker = false;
1006 }
1007 if(m.command == 0x08)
1008 {
1009 maxPanning = std::max(maxPanning, m.param);
1010 if(m.param < 0x80)
1011 leftPanning = true;
1012 else if(m.param > 0x8F && m.param != 0xA4)
1013 extendedPanning = true;
1014 } else if(m.command == 0x0E && (m.param & 0xF0) == 0x80)
1015 {
1016 maxPanning = std::max(maxPanning, static_cast<uint8>((m.param & 0x0F) << 4));
1017 }
1018 }
1019 }
1020 fix7BitPanning = leftPanning && !extendedPanning && maxPanning >= ENABLE_MOD_PANNING_THRESHOLD;
1021 }
1022 file.Seek(modMagicResult.patternDataOffset);
1023
1024 const CHANNELINDEX readChannels = (isFLT8 ? 4 : m_nChannels); // 4 channels per pattern in FLT8 format.
1025 if(isFLT8) numPatterns++; // as one logical pattern consists of two real patterns in FLT8 format, the highest pattern number has to be increased by one.
1026 bool hasTempoCommands = false, definitelyCIA = false; // for detecting VBlank MODs
1027 // Heuristic for rejecting E0x commands that are most likely not intended to actually toggle the Amiga LED filter, like in naen_leijasi_ptk.mod by ilmarque
1028 bool filterState = false;
1029 int filterTransitions = 0;
1030
1031 // Reading patterns
1032 Patterns.ResizeArray(numPatterns);
1033 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1034 {
1035 ModCommand *rowBase = nullptr;
1036
1037 if(isFLT8)
1038 {
1039 // FLT8: Only create "even" patterns and either write to channel 1 to 4 (even patterns) or 5 to 8 (odd patterns).
1040 PATTERNINDEX actualPattern = pat / 2u;
1041 if((pat % 2u) == 0 && !Patterns.Insert(actualPattern, 64))
1042 {
1043 break;
1044 }
1045 rowBase = Patterns[actualPattern].GetpModCommand(0, (pat % 2u) == 0 ? 0 : 4);
1046 } else
1047 {
1048 if(!Patterns.Insert(pat, 64))
1049 {
1050 break;
1051 }
1052 rowBase = Patterns[pat].GetpModCommand(0, 0);
1053 }
1054
1055 if(rowBase == nullptr || !(loadFlags & loadPatternData))
1056 {
1057 break;
1058 }
1059
1060 // For detecting PT1x mode
1061 std::vector<ModCommand::INSTR> lastInstrument(GetNumChannels(), 0);
1062 std::vector<uint8> instrWithoutNoteCount(GetNumChannels(), 0);
1063
1064 for(ROWINDEX row = 0; row < 64; row++, rowBase += m_nChannels)
1065 {
1066 // If we have more than one Fxx command on this row and one can be interpreted as speed
1067 // and the other as tempo, we can be rather sure that it is not a VBlank mod.
1068 bool hasSpeedOnRow = false, hasTempoOnRow = false;
1069
1070 for(CHANNELINDEX chn = 0; chn < readChannels; chn++)
1071 {
1072 ModCommand &m = rowBase[chn];
1073 ReadMODPatternEntry(file, m);
1074
1075 if(m.command || m.param)
1076 {
1077 if(isStartrekker && m.command == 0x0E)
1078 {
1079 // No support for Startrekker assembly macros
1080 m.command = CMD_NONE;
1081 m.param = 0;
1082 } else if(isStartrekker && m.command == 0x0F && m.param > 0x1F)
1083 {
1084 // Startrekker caps speed at 31 ticks per row
1085 m.param = 0x1F;
1086 }
1087 ConvertModCommand(m);
1088 }
1089
1090 // Perform some checks for our heuristics...
1091 if(m.command == CMD_TEMPO)
1092 {
1093 hasTempoOnRow = true;
1094 if(m.param < 100)
1095 hasTempoCommands = true;
1096 } else if(m.command == CMD_SPEED)
1097 {
1098 hasSpeedOnRow = true;
1099 } else if(m.command == CMD_PATTERNBREAK && isNoiseTracker)
1100 {
1101 m.param = 0;
1102 } else if(m.command == CMD_PANNING8 && fix7BitPanning)
1103 {
1104 // Fix MODs with 7-bit + surround panning
1105 if(m.param == 0xA4)
1106 {
1107 m.command = CMD_S3MCMDEX;
1108 m.param = 0x91;
1109 } else
1110 {
1111 m.param = mpt::saturate_cast<ModCommand::PARAM>(m.param * 2);
1112 }
1113 } else if(m.command == CMD_MODCMDEX && m.param < 0x10)
1114 {
1115 // Count LED filter transitions
1116 bool newState = !(m.param & 0x01);
1117 if(newState != filterState)
1118 {
1119 filterState = newState;
1120 filterTransitions++;
1121 }
1122 }
1123 if(m.note == NOTE_NONE && m.instr > 0 && !isFLT8)
1124 {
1125 if(lastInstrument[chn] > 0 && lastInstrument[chn] != m.instr)
1126 {
1127 // Arbitrary threshold for enabling sample swapping: 4 consecutive "sample swaps" in one pattern.
1128 if(++instrWithoutNoteCount[chn] >= 4)
1129 {
1130 m_playBehaviour.set(kMODSampleSwap);
1131 }
1132 }
1133 } else if(m.note != NOTE_NONE)
1134 {
1135 instrWithoutNoteCount[chn] = 0;
1136 }
1137 if(m.instr != 0)
1138 {
1139 lastInstrument[chn] = m.instr;
1140 }
1141 }
1142 if(hasSpeedOnRow && hasTempoOnRow)
1143 definitelyCIA = true;
1144 }
1145 }
1146
1147 if(onlyAmigaNotes && !hasRepLen0 && (IsMagic(magic, "M.K.") || IsMagic(magic, "M!K!") || IsMagic(magic, "PATT")))
1148 {
1149 // M.K. files that don't exceed the Amiga note limit (fixes mod.mothergoose)
1150 m_SongFlags.set(SONG_AMIGALIMITS);
1151 // Need this for professionaltracker.mod by h0ffman (SHA1: 9a7c52cbad73ed2a198ee3fa18d3704ea9f546ff)
1152 m_SongFlags.set(SONG_PT_MODE);
1153 m_playBehaviour.set(kMODSampleSwap);
1154 m_playBehaviour.set(kMODOutOfRangeNoteDelay);
1155 m_playBehaviour.set(kMODTempoOnSecondTick);
1156 // Arbitrary threshold for deciding that 8xx effects are only used as sync markers
1157 if(maxPanning < ENABLE_MOD_PANNING_THRESHOLD)
1158 {
1159 m_playBehaviour.set(kMODIgnorePanning);
1160 if(fileHeader.restartPos != 0x7F)
1161 {
1162 // Don't enable these hacks for ScreamTracker modules (restart position = 0x7F), to fix e.g. sample 10 in BASIC001.MOD (SHA1: 11298a5620e677beaa50bd4ed00c3710b75c81af)
1163 // Note: restart position = 0x7F can also be found in ProTracker modules, e.g. professionaltracker.mod by h0ffman
1164 m_playBehaviour.set(kMODOneShotLoops);
1165 }
1166 }
1167 } else if(!onlyAmigaNotes && fileHeader.restartPos == 0x7F && isMdKd && fileHeader.restartPos + 1u >= realOrders)
1168 {
1169 modMagicResult.madeWithTracker = UL_("Scream Tracker");
1170 }
1171
1172 if(onlyAmigaNotes && !isGenericMultiChannel && filterTransitions < 7)
1173 {
1174 m_SongFlags.set(SONG_ISAMIGA);
1175 }
1176 if(isGenericMultiChannel || isMdKd)
1177 {
1178 m_playBehaviour.set(kFT2MODTremoloRampWaveform);
1179 }
1180 if(isInconexia)
1181 {
1182 m_playBehaviour.set(kMODIgnorePanning);
1183 }
1184
1185 // Reading samples
1186 if(loadFlags & loadSampleData)
1187 {
1188 file.Seek(modMagicResult.patternDataOffset + (readChannels * 64 * 4) * numPatterns);
1189 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
1190 {
1191 ModSample &sample = Samples[smp];
1192 if(sample.nLength)
1193 {
1194 SampleIO::Encoding encoding = SampleIO::signedPCM;
1195 if(isInconexia)
1196 encoding = SampleIO::deltaPCM;
1197 else if(file.ReadMagic("ADPCM"))
1198 encoding = SampleIO::ADPCM;
1199
1200 SampleIO sampleIO(
1201 SampleIO::_8bit,
1202 SampleIO::mono,
1203 SampleIO::littleEndian,
1204 encoding);
1205
1206 // Fix sample 6 in MOD.shorttune2, which has a replen longer than the sample itself.
1207 // ProTracker reads beyond the end of the sample when playing. Normally samples are
1208 // adjacent in PT's memory, so we simply read into the next sample in the file.
1209 // On the other hand, the loop points in Purple Motions's SOUL-O-M.MOD are completely broken and shouldn't be treated like this.
1210 // As it was most likely written in Scream Tracker, it has empty sample slots with a default volume of 64, which we use for
1211 // rejecting this quirk for that file.
1212 FileReader::off_t nextSample = file.GetPosition() + sampleIO.CalculateEncodedSize(sample.nLength);
1213 if(isMdKd && onlyAmigaNotes && !hasEmptySampleWithVolume)
1214 sample.nLength = std::max(sample.nLength, sample.nLoopEnd);
1215
1216 sampleIO.ReadSample(sample, file);
1217 file.Seek(nextSample);
1218 }
1219 }
1220 }
1221
1222 #if defined(MPT_EXTERNAL_SAMPLES) || defined(MPT_BUILD_FUZZER)
1223 // Detect Startrekker files with external synth instruments.
1224 // Note: Synthesized AM samples may overwrite existing samples (e.g. sample 1 in fa.worse face.mod),
1225 // hence they are loaded here after all regular samples have been loaded.
1226 if((loadFlags & loadSampleData) && isStartrekker)
1227 {
1228 #ifdef MPT_EXTERNAL_SAMPLES
1229 std::optional<InputFile> amFile;
1230 FileReader amData;
1231 if(file.GetOptionalFileName())
1232 {
1233 mpt::PathString filename = file.GetOptionalFileName().value();
1234 // Find instrument definition file
1235 const mpt::PathString exts[] = {P_(".nt"), P_(".NT"), P_(".as"), P_(".AS")};
1236 for(const auto &ext : exts)
1237 {
1238 mpt::PathString infoName = filename + ext;
1239 char stMagic[16];
1240 if(infoName.IsFile())
1241 {
1242 amFile.emplace(infoName, SettingCacheCompleteFileBeforeLoading());
1243 if(amFile->IsValid() && (amData = GetFileReader(*amFile)).IsValid() && amData.ReadArray(stMagic))
1244 {
1245 if(!memcmp(stMagic, "ST1.2 ModuleINFO", 16))
1246 modMagicResult.madeWithTracker = UL_("Startrekker 1.2");
1247 else if(!memcmp(stMagic, "ST1.3 ModuleINFO", 16))
1248 modMagicResult.madeWithTracker = UL_("Startrekker 1.3");
1249 else if(!memcmp(stMagic, "AudioSculpture10", 16))
1250 modMagicResult.madeWithTracker = UL_("AudioSculpture 1.0");
1251 else
1252 continue;
1253
1254 if(amData.Seek(144))
1255 {
1256 // Looks like a valid instrument definition file!
1257 m_nInstruments = 31;
1258 break;
1259 }
1260 }
1261 }
1262 }
1263 }
1264 #elif defined(MPT_BUILD_FUZZER)
1265 // For fuzzing this part of the code, just take random data from patterns
1266 FileReader amData = file.GetChunkAt(1084, 31 * 120);
1267 m_nInstruments = 31;
1268 #endif
1269
1270 for(SAMPLEINDEX smp = 1; smp <= m_nInstruments; smp++)
1271 {
1272 // For Startrekker AM synthesis, we need instrument envelopes.
1273 ModInstrument *ins = AllocateInstrument(smp, smp);
1274 if(ins == nullptr)
1275 {
1276 break;
1277 }
1278 ins->name = m_szNames[smp];
1279
1280 AMInstrument am;
1281 // Allow partial reads for fa.worse face.mod
1282 if(amData.ReadStructPartial(am) && !memcmp(am.am, "AM", 2) && am.waveform < 4)
1283 {
1284 am.ConvertToMPT(Samples[smp], *ins, AccessPRNG());
1285 }
1286
1287 // This extra padding is probably present to have identical block sizes for AM and FM instruments.
1288 amData.Skip(120 - sizeof(AMInstrument));
1289 }
1290 }
1291 #endif // MPT_EXTERNAL_SAMPLES || MPT_BUILD_FUZZER
1292
1293 // Fix VBlank MODs. Arbitrary threshold: 9 minutes (enough for Guitar Slinger...).
1294 // Basically, this just converts all tempo commands into speed commands
1295 // for MODs which are supposed to have VBlank timing (instead of CIA timing).
1296 // There is no perfect way to do this, since both MOD types look the same,
1297 // but the most reliable way is to simply check for extremely long songs
1298 // (as this would indicate that e.g. a F30 command was really meant to set
1299 // the ticks per row to 48, and not the tempo to 48 BPM).
1300 // In the pattern loader above, a second condition is used: Only tempo commands
1301 // below 100 BPM are taken into account. Furthermore, only M.K. (ProTracker)
1302 // modules are checked.
1303 if(isMdKd && hasTempoCommands && !definitelyCIA)
1304 {
1305 const double songTime = GetLength(eNoAdjust).front().duration;
1306 if(songTime >= 540.0)
1307 {
1308 m_playBehaviour.set(kMODVBlankTiming);
1309 if(GetLength(eNoAdjust, GetLengthTarget(songTime)).front().targetReached)
1310 {
1311 // This just makes things worse, song is at least as long as in CIA mode (e.g. in "Stary Hallway" by Neurodancer)
1312 // Obviously we should keep using CIA timing then...
1313 m_playBehaviour.reset(kMODVBlankTiming);
1314 } else
1315 {
1316 modMagicResult.madeWithTracker = UL_("ProTracker (VBlank)");
1317 }
1318 }
1319 }
1320
1321 std::transform(std::begin(magic), std::end(magic), std::begin(magic), [](unsigned char c) -> unsigned char { return (c < ' ') ? ' ' : c; });
1322 m_modFormat.formatName = MPT_UFORMAT("ProTracker MOD ({})")(mpt::ToUnicode(mpt::Charset::ASCII, std::string(std::begin(magic), std::end(magic))));
1323 m_modFormat.type = U_("mod");
1324 if(modMagicResult.madeWithTracker)
1325 m_modFormat.madeWithTracker = modMagicResult.madeWithTracker;
1326 m_modFormat.charset = mpt::Charset::ISO8859_1;
1327
1328 return true;
1329 }
1330
1331
1332 // Check if a name string is valid (i.e. doesn't contain binary garbage data)
1333 template <size_t N>
CountInvalidChars(const char (& name)[N])1334 static uint32 CountInvalidChars(const char (&name)[N])
1335 {
1336 uint32 invalidChars = 0;
1337 for(int8 c : name) // char can be signed or unsigned
1338 {
1339 // Check for any Extended ASCII and control characters
1340 if(c != 0 && c < ' ')
1341 invalidChars++;
1342 }
1343 return invalidChars;
1344 }
1345
1346
1347 // We'll have to do some heuristic checks to find out whether this is an old Ultimate Soundtracker module
1348 // or if it was made with the newer Soundtracker versions.
1349 // Thanks for Fraggie for this information! (https://www.un4seen.com/forum/?topic=14471.msg100829#msg100829)
1350 enum STVersions
1351 {
1352 UST1_00, // Ultimate Soundtracker 1.0-1.21 (K. Obarski)
1353 UST1_80, // Ultimate Soundtracker 1.8-2.0 (K. Obarski)
1354 ST2_00_Exterminator, // SoundTracker 2.0 (The Exterminator), D.O.C. Sountracker II (Unknown/D.O.C.)
1355 ST_III, // Defjam Soundtracker III (Il Scuro/Defjam), Alpha Flight SoundTracker IV (Alpha Flight), D.O.C. SoundTracker IV (Unknown/D.O.C.), D.O.C. SoundTracker VI (Unknown/D.O.C.)
1356 ST_IX, // D.O.C. SoundTracker IX (Unknown/D.O.C.)
1357 MST1_00, // Master Soundtracker 1.0 (Tip/The New Masters)
1358 ST2_00, // SoundTracker 2.0, 2.1, 2.2 (Unknown/D.O.C.)
1359 };
1360
1361
1362
1363 struct M15FileHeaders
1364 {
1365 char songname[20];
1366 MODSampleHeader sampleHeaders[15];
1367 MODFileHeader fileHeader;
1368 };
1369
1370 MPT_BINARY_STRUCT(M15FileHeaders, 20 + 15 * 30 + 130)
1371
1372
ValidateHeader(const M15FileHeaders & fileHeaders)1373 static bool ValidateHeader(const M15FileHeaders &fileHeaders)
1374 {
1375 // In theory, sample and song names should only ever contain printable ASCII chars and null.
1376 // However, there are quite a few SoundTracker modules in the wild with random
1377 // characters. To still be able to distguish them from other formats, we just reject
1378 // files with *too* many bogus characters. Arbitrary threshold: 48 bogus characters in total
1379 // or more than 5 invalid characters just in the title alone.
1380 uint32 invalidChars = CountInvalidChars(fileHeaders.songname);
1381 if(invalidChars > 5)
1382 {
1383 return false;
1384 }
1385
1386 SmpLength totalSampleLen = 0;
1387 uint8 allVolumes = 0;
1388
1389 for(SAMPLEINDEX smp = 0; smp < 15; smp++)
1390 {
1391 const MODSampleHeader &sampleHeader = fileHeaders.sampleHeaders[smp];
1392
1393 invalidChars += CountInvalidChars(sampleHeader.name);
1394
1395 // Sanity checks - invalid character count adjusted for ata.mod (MD5 937b79b54026fa73a1a4d3597c26eace, SHA1 3322ca62258adb9e0ae8e9afe6e0c29d39add874)
1396 if(invalidChars > 48
1397 || sampleHeader.volume > 64
1398 || sampleHeader.finetune != 0
1399 || sampleHeader.length > 32768)
1400 {
1401 return false;
1402 }
1403
1404 totalSampleLen += sampleHeader.length;
1405 allVolumes |= sampleHeader.volume;
1406 }
1407
1408 // Reject any files with no (or only silent) samples at all, as this might just be a random binary file (e.g. ID3 tags with tons of padding)
1409 if(totalSampleLen == 0 || allVolumes == 0)
1410 {
1411 return false;
1412 }
1413
1414 // Sanity check: No more than 128 positions. ST's GUI limits tempo to [1, 220].
1415 // There are some mods with a tempo of 0 (explora3-death.mod) though, so ignore the lower limit.
1416 if(fileHeaders.fileHeader.numOrders > 128 || fileHeaders.fileHeader.restartPos > 220)
1417 {
1418 return false;
1419 }
1420
1421 uint8 maxPattern = *std::max_element(std::begin(fileHeaders.fileHeader.orderList), std::end(fileHeaders.fileHeader.orderList));
1422 // Sanity check: 64 patterns max.
1423 if(maxPattern > 63)
1424 {
1425 return false;
1426 }
1427
1428 // No playable song, and lots of null values => most likely a sparse binary file but not a module
1429 if(fileHeaders.fileHeader.restartPos == 0 && fileHeaders.fileHeader.numOrders == 0 && maxPattern == 0)
1430 {
1431 return false;
1432 }
1433
1434 return true;
1435 }
1436
1437
1438 template <typename TFileReader>
ValidateFirstM15Pattern(TFileReader & file)1439 static bool ValidateFirstM15Pattern(TFileReader &file)
1440 {
1441 // threshold is chosen as: [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much]
1442 return ValidateMODPatternData(file, 512 / 64 * 2, false);
1443 }
1444
1445
ProbeFileHeaderM15(MemoryFileReader file,const uint64 * pfilesize)1446 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderM15(MemoryFileReader file, const uint64 *pfilesize)
1447 {
1448 M15FileHeaders fileHeaders;
1449 if(!file.ReadStruct(fileHeaders))
1450 {
1451 return ProbeWantMoreData;
1452 }
1453 if(!ValidateHeader(fileHeaders))
1454 {
1455 return ProbeFailure;
1456 }
1457 if(!file.CanRead(sizeof(MODPatternData)))
1458 {
1459 return ProbeWantMoreData;
1460 }
1461 if(!ValidateFirstM15Pattern(file))
1462 {
1463 return ProbeFailure;
1464 }
1465 MPT_UNREFERENCED_PARAMETER(pfilesize);
1466 return ProbeSuccess;
1467 }
1468
1469
ReadM15(FileReader & file,ModLoadingFlags loadFlags)1470 bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
1471 {
1472 file.Rewind();
1473
1474 M15FileHeaders fileHeaders;
1475 if(!file.ReadStruct(fileHeaders))
1476 {
1477 return false;
1478 }
1479 if(!ValidateHeader(fileHeaders))
1480 {
1481 return false;
1482 }
1483 if(!ValidateFirstM15Pattern(file))
1484 {
1485 return false;
1486 }
1487
1488 char songname[20];
1489 std::memcpy(songname, fileHeaders.songname, 20);
1490
1491 InitializeGlobals(MOD_TYPE_MOD);
1492 m_playBehaviour.reset(kMODOneShotLoops);
1493 m_playBehaviour.set(kMODIgnorePanning);
1494 m_playBehaviour.set(kMODSampleSwap); // untested
1495 m_nChannels = 4;
1496
1497 STVersions minVersion = UST1_00;
1498
1499 bool hasDiskNames = true;
1500 SmpLength totalSampleLen = 0;
1501 m_nSamples = 15;
1502
1503 file.Seek(20);
1504 for(SAMPLEINDEX smp = 1; smp <= 15; smp++)
1505 {
1506 MODSampleHeader sampleHeader;
1507 ReadSample(file, sampleHeader, Samples[smp], m_szNames[smp], true);
1508
1509 totalSampleLen += Samples[smp].nLength;
1510
1511 if(m_szNames[smp][0] && ((memcmp(m_szNames[smp].buf, "st-", 3) && memcmp(m_szNames[smp].buf, "ST-", 3)) || m_szNames[smp][5] != ':'))
1512 {
1513 // Ultimate Soundtracker 1.8 and D.O.C. SoundTracker IX always have sample names containing disk names.
1514 hasDiskNames = false;
1515 }
1516
1517 // Loop start is always in bytes, not words, so don't trust the auto-fix magic in the sample header conversion (fixes loop of "st-01:asia" in mod.drag 10)
1518 if(sampleHeader.loopLength > 1)
1519 {
1520 Samples[smp].nLoopStart = sampleHeader.loopStart;
1521 Samples[smp].nLoopEnd = sampleHeader.loopStart + sampleHeader.loopLength * 2;
1522 Samples[smp].SanitizeLoops();
1523 }
1524
1525 // UST only handles samples up to 9999 bytes. Master Soundtracker 1.0 and SoundTracker 2.0 introduce 32KB samples.
1526 if(sampleHeader.length > 4999 || sampleHeader.loopStart > 9999)
1527 minVersion = std::max(minVersion, MST1_00);
1528 }
1529
1530 MODFileHeader fileHeader;
1531 file.ReadStruct(fileHeader);
1532
1533 ReadOrderFromArray(Order(), fileHeader.orderList);
1534 PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), fileHeader.numOrders, totalSampleLen, m_nChannels, 0, true);
1535
1536 // Most likely just a file with lots of NULs at the start
1537 if(fileHeader.restartPos == 0 && fileHeader.numOrders == 0 && numPatterns <= 1)
1538 {
1539 return false;
1540 }
1541
1542 // Let's see if the file is too small (including some overhead for broken files like sll7.mod or ghostbus.mod)
1543 if(file.BytesLeft() + 65536 < numPatterns * 64u * 4u * 4u + totalSampleLen)
1544 return false;
1545
1546 if(loadFlags == onlyVerifyHeader)
1547 return true;
1548
1549 // Now we can be pretty sure that this is a valid Soundtracker file. Set up default song settings.
1550 // explora3-death.mod has a tempo of 0
1551 if(!fileHeader.restartPos)
1552 fileHeader.restartPos = 0x78;
1553 // jjk55 by Jesper Kyd has a weird tempo set, but it needs to be ignored.
1554 if(!memcmp(songname, "jjk55", 6))
1555 fileHeader.restartPos = 0x78;
1556 // Sample 7 in echoing.mod won't "loop" correctly if we don't convert the VBlank tempo.
1557 m_nDefaultTempo.Set(125);
1558 if(fileHeader.restartPos != 0x78)
1559 {
1560 // Convert to CIA timing
1561 m_nDefaultTempo = TEMPO((709379.0 * 125.0 / 50.0) / ((240 - fileHeader.restartPos) * 122.0));
1562 if(minVersion > UST1_80)
1563 {
1564 // D.O.C. SoundTracker IX re-introduced the variable tempo after some other versions dropped it.
1565 minVersion = std::max(minVersion, hasDiskNames ? ST_IX : MST1_00);
1566 } else
1567 {
1568 // Ultimate Soundtracker 1.8 adds variable tempo
1569 minVersion = std::max(minVersion, hasDiskNames ? UST1_80 : ST2_00_Exterminator);
1570 }
1571 }
1572 m_nMinPeriod = 113 * 4;
1573 m_nMaxPeriod = 856 * 4;
1574 m_nSamplePreAmp = 64;
1575 m_SongFlags.set(SONG_PT_MODE);
1576 m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, songname);
1577
1578 // Setup channel pan positions and volume
1579 SetupMODPanning();
1580
1581 FileReader::off_t patOffset = file.GetPosition();
1582
1583 // Scan patterns to identify Ultimate Soundtracker modules.
1584 uint32 illegalBytes = 0, totalNumDxx = 0;
1585 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1586 {
1587 const bool patternInUse = mpt::contains(Order(), pat);
1588 uint8 numDxx = 0;
1589 uint8 emptyCmds = 0;
1590 MODPatternData patternData;
1591 file.ReadArray(patternData);
1592 if(patternInUse)
1593 {
1594 illegalBytes += CountMalformedMODPatternData(patternData, false);
1595 // Reject files that contain a lot of illegal pattern data.
1596 // STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3)
1597 // has one illegal byte, so we only reject after an arbitrary threshold has been passed.
1598 // This also allows to play some rather damaged files like
1599 // crockets.mod (MD5 995ed9f44cab995a0eeb19deb52e2a8b, SHA1 6c79983c3b7d55c9bc110b625eaa07ce9d75f369)
1600 // but naturally we cannot recover the broken data.
1601
1602 // We only check patterns that are actually being used in the order list, because some bad rips of the
1603 // "operation wolf" soundtrack have 15 patterns for several songs, but the last few patterns are just garbage.
1604 // Apart from those hidden patterns, the files play fine.
1605 // Example: operation wolf - wolf1.mod (MD5 739acdbdacd247fbefcac7bc2d8abe6b, SHA1 e6b4813daacbf95f41ce9ec3b22520a2ae07eed8)
1606 if(illegalBytes > 512)
1607 return false;
1608 }
1609 for(ROWINDEX row = 0; row < 64; row++)
1610 {
1611 for(CHANNELINDEX chn = 0; chn < 4; chn++)
1612 {
1613 const auto &data = patternData[row][chn];
1614 const uint8 eff = data[2] & 0x0F, param = data[3];
1615 // Check for empty space between the last Dxx command and the beginning of another pattern
1616 if(emptyCmds != 0 && !memcmp(data.data(), "\0\0\0\0", 4))
1617 {
1618 emptyCmds++;
1619 if(emptyCmds > 32)
1620 {
1621 // Since there is a lot of empty space after the last Dxx command,
1622 // we assume it's supposed to be a pattern break effect.
1623 minVersion = ST2_00;
1624 }
1625 } else
1626 {
1627 emptyCmds = 0;
1628 }
1629
1630 switch(eff)
1631 {
1632 case 1:
1633 case 2:
1634 if(param > 0x1F && minVersion == UST1_80)
1635 {
1636 // If a 1xx / 2xx effect has a parameter greater than 0x20, it is assumed to be UST.
1637 minVersion = hasDiskNames ? UST1_80 : UST1_00;
1638 } else if(eff == 1 && param > 0 && param < 0x03)
1639 {
1640 // This doesn't look like an arpeggio.
1641 minVersion = std::max(minVersion, ST2_00_Exterminator);
1642 } else if(eff == 1 && (param == 0x37 || param == 0x47) && minVersion <= ST2_00_Exterminator)
1643 {
1644 // This suspiciously looks like an arpeggio.
1645 // Catch sleepwalk.mod by Karsten Obarski, which has a default tempo of 125 rather than 120 in the header, so gets mis-identified as a later tracker version.
1646 minVersion = hasDiskNames ? UST1_80 : UST1_00;
1647 }
1648 break;
1649 case 0x0B:
1650 minVersion = ST2_00;
1651 break;
1652 case 0x0C:
1653 case 0x0D:
1654 case 0x0E:
1655 minVersion = std::max(minVersion, ST2_00_Exterminator);
1656 if(eff == 0x0D)
1657 {
1658 emptyCmds = 1;
1659 if(param == 0 && row == 0)
1660 {
1661 // Fix a possible tracking mistake in Blood Money title - who wants to do a pattern break on the first row anyway?
1662 break;
1663 }
1664 numDxx++;
1665 }
1666 break;
1667 case 0x0F:
1668 minVersion = std::max(minVersion, ST_III);
1669 break;
1670 }
1671 }
1672 }
1673
1674 if(numDxx > 0 && numDxx < 3)
1675 {
1676 // Not many Dxx commands in one pattern means they were probably pattern breaks
1677 minVersion = ST2_00;
1678 }
1679 totalNumDxx += numDxx;
1680 }
1681
1682 // If there is a huge number of Dxx commands, this is extremely unlikely to be a SoundTracker 2.0 module
1683 if(totalNumDxx > numPatterns + 32u && minVersion == ST2_00)
1684 minVersion = MST1_00;
1685
1686 file.Seek(patOffset);
1687
1688 // Reading patterns
1689 if(loadFlags & loadPatternData)
1690 Patterns.ResizeArray(numPatterns);
1691 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1692 {
1693 MODPatternData patternData;
1694 file.ReadArray(patternData);
1695
1696 if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
1697 {
1698 continue;
1699 }
1700
1701 uint8 autoSlide[4] = {0, 0, 0, 0};
1702 for(ROWINDEX row = 0; row < 64; row++)
1703 {
1704 PatternRow rowBase = Patterns[pat].GetpModCommand(row, 0);
1705 for(CHANNELINDEX chn = 0; chn < 4; chn++)
1706 {
1707 ModCommand &m = rowBase[chn];
1708 ReadMODPatternEntry(patternData[row][chn], m);
1709
1710 if(!m.param || m.command == 0x0E)
1711 {
1712 autoSlide[chn] = 0;
1713 }
1714 if(m.command || m.param)
1715 {
1716 if(autoSlide[chn] != 0)
1717 {
1718 if(autoSlide[chn] & 0xF0)
1719 {
1720 m.volcmd = VOLCMD_VOLSLIDEUP;
1721 m.vol = autoSlide[chn] >> 4;
1722 } else
1723 {
1724 m.volcmd = VOLCMD_VOLSLIDEDOWN;
1725 m.vol = autoSlide[chn] & 0x0F;
1726 }
1727 }
1728 if(m.command == 0x0D)
1729 {
1730 if(minVersion != ST2_00)
1731 {
1732 // Dxy is volume slide in some Soundtracker versions, D00 is a pattern break in the latest versions.
1733 m.command = 0x0A;
1734 } else
1735 {
1736 m.param = 0;
1737 }
1738 } else if(m.command == 0x0C)
1739 {
1740 // Volume is sent as-is to the chip, which ignores the highest bit.
1741 m.param &= 0x7F;
1742 } else if(m.command == 0x0E && (m.param > 0x01 || minVersion < ST_IX))
1743 {
1744 // Import auto-slides as normal slides and fake them using volume column slides.
1745 m.command = 0x0A;
1746 autoSlide[chn] = m.param;
1747 } else if(m.command == 0x0F)
1748 {
1749 // Only the low nibble is evaluated in Soundtracker.
1750 m.param &= 0x0F;
1751 }
1752
1753 if(minVersion <= UST1_80)
1754 {
1755 // UST effects
1756 switch(m.command)
1757 {
1758 case 0:
1759 // jackdance.mod by Karsten Obarski has 0xy arpeggios...
1760 if(m.param < 0x03)
1761 {
1762 m.command = CMD_NONE;
1763 } else
1764 {
1765 m.command = CMD_ARPEGGIO;
1766 }
1767 break;
1768 case 1:
1769 m.command = CMD_ARPEGGIO;
1770 break;
1771 case 2:
1772 if(m.param & 0x0F)
1773 {
1774 m.command = CMD_PORTAMENTOUP;
1775 m.param &= 0x0F;
1776 } else if(m.param >> 4)
1777 {
1778 m.command = CMD_PORTAMENTODOWN;
1779 m.param >>= 4;
1780 }
1781 break;
1782 default:
1783 m.command = CMD_NONE;
1784 break;
1785 }
1786 } else
1787 {
1788 ConvertModCommand(m);
1789 }
1790 } else
1791 {
1792 autoSlide[chn] = 0;
1793 }
1794 }
1795 }
1796 }
1797
1798 const mpt::uchar *madeWithTracker = UL_("");
1799 switch(minVersion)
1800 {
1801 case UST1_00:
1802 madeWithTracker = UL_("Ultimate Soundtracker 1.0-1.21");
1803 break;
1804 case UST1_80:
1805 madeWithTracker = UL_("Ultimate Soundtracker 1.8-2.0");
1806 break;
1807 case ST2_00_Exterminator:
1808 madeWithTracker = UL_("SoundTracker 2.0 / D.O.C. SoundTracker II");
1809 break;
1810 case ST_III:
1811 madeWithTracker = UL_("Defjam Soundtracker III / Alpha Flight SoundTracker IV / D.O.C. SoundTracker IV / VI");
1812 break;
1813 case ST_IX:
1814 madeWithTracker = UL_("D.O.C. SoundTracker IX");
1815 break;
1816 case MST1_00:
1817 madeWithTracker = UL_("Master Soundtracker 1.0");
1818 break;
1819 case ST2_00:
1820 madeWithTracker = UL_("SoundTracker 2.0 / 2.1 / 2.2");
1821 break;
1822 }
1823
1824 m_modFormat.formatName = U_("Soundtracker");
1825 m_modFormat.type = U_("stk");
1826 m_modFormat.madeWithTracker = madeWithTracker;
1827 m_modFormat.charset = mpt::Charset::ISO8859_1;
1828
1829 // Reading samples
1830 if(loadFlags & loadSampleData)
1831 {
1832 for(SAMPLEINDEX smp = 1; smp <= 15; smp++)
1833 {
1834 // Looped samples in (Ultimate) Soundtracker seem to ignore all sample data before the actual loop start.
1835 // This avoids the clicks in the first sample of pretend.mod by Karsten Obarski.
1836 file.Skip(Samples[smp].nLoopStart);
1837 Samples[smp].nLength -= Samples[smp].nLoopStart;
1838 Samples[smp].nLoopEnd -= Samples[smp].nLoopStart;
1839 Samples[smp].nLoopStart = 0;
1840 MODSampleHeader::GetSampleFormat().ReadSample(Samples[smp], file);
1841 }
1842 }
1843
1844 return true;
1845 }
1846
1847
ProbeFileHeaderICE(MemoryFileReader file,const uint64 * pfilesize)1848 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderICE(MemoryFileReader file, const uint64 *pfilesize)
1849 {
1850 if(!file.CanRead(1464 + 4))
1851 {
1852 return ProbeWantMoreData;
1853 }
1854 file.Seek(1464);
1855 char magic[4];
1856 file.ReadArray(magic);
1857 if(!IsMagic(magic, "MTN\0") && !IsMagic(magic, "IT10"))
1858 {
1859 return ProbeFailure;
1860 }
1861 file.Seek(20);
1862 uint32 invalidBytes = 0;
1863 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
1864 {
1865 MODSampleHeader sampleHeader;
1866 if(!file.ReadStruct(sampleHeader))
1867 {
1868 return ProbeWantMoreData;
1869 }
1870 invalidBytes += sampleHeader.GetInvalidByteScore();
1871 }
1872 if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD)
1873 {
1874 return ProbeFailure;
1875 }
1876 const auto [numOrders, numTracks] = file.ReadArray<uint8, 2>();
1877 if(numOrders > 128)
1878 {
1879 return ProbeFailure;
1880 }
1881 uint8 tracks[128 * 4];
1882 file.ReadArray(tracks);
1883 for(auto track : tracks)
1884 {
1885 if(track > numTracks)
1886 {
1887 return ProbeFailure;
1888 }
1889 }
1890 MPT_UNREFERENCED_PARAMETER(pfilesize);
1891 return ProbeSuccess;
1892 }
1893
1894
1895 // SoundTracker 2.6 / Ice Tracker variation of the MOD format
1896 // The only real difference to other SoundTracker formats is the way patterns are stored:
1897 // Every pattern consists of four independent, re-usable tracks.
ReadICE(FileReader & file,ModLoadingFlags loadFlags)1898 bool CSoundFile::ReadICE(FileReader &file, ModLoadingFlags loadFlags)
1899 {
1900 char magic[4];
1901 if(!file.Seek(1464) || !file.ReadArray(magic))
1902 {
1903 return false;
1904 }
1905
1906 InitializeGlobals(MOD_TYPE_MOD);
1907 m_playBehaviour.reset(kMODOneShotLoops);
1908 m_playBehaviour.set(kMODIgnorePanning);
1909 m_playBehaviour.set(kMODSampleSwap); // untested
1910
1911 if(IsMagic(magic, "MTN\0"))
1912 {
1913 m_modFormat.formatName = U_("MnemoTroN SoundTracker");
1914 m_modFormat.type = U_("st26");
1915 m_modFormat.madeWithTracker = U_("SoundTracker 2.6");
1916 m_modFormat.charset = mpt::Charset::ISO8859_1;
1917 } else if(IsMagic(magic, "IT10"))
1918 {
1919 m_modFormat.formatName = U_("Ice Tracker");
1920 m_modFormat.type = U_("ice");
1921 m_modFormat.madeWithTracker = U_("Ice Tracker 1.0 / 1.1");
1922 m_modFormat.charset = mpt::Charset::ISO8859_1;
1923 } else
1924 {
1925 return false;
1926 }
1927
1928 // Reading song title
1929 file.Seek(0);
1930 file.ReadString<mpt::String::spacePadded>(m_songName, 20);
1931
1932 // Load Samples
1933 m_nSamples = 31;
1934 uint32 invalidBytes = 0;
1935 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
1936 {
1937 MODSampleHeader sampleHeader;
1938 invalidBytes += ReadSample(file, sampleHeader, Samples[smp], m_szNames[smp], true);
1939 }
1940 if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD)
1941 {
1942 return false;
1943 }
1944
1945 const auto [numOrders, numTracks] = file.ReadArray<uint8, 2>();
1946 if(numOrders > 128)
1947 {
1948 return false;
1949 }
1950
1951 uint8 tracks[128 * 4];
1952 file.ReadArray(tracks);
1953 for(auto track : tracks)
1954 {
1955 if(track > numTracks)
1956 {
1957 return false;
1958 }
1959 }
1960
1961 if(loadFlags == onlyVerifyHeader)
1962 {
1963 return true;
1964 }
1965
1966 // Now we can be pretty sure that this is a valid MOD file. Set up default song settings.
1967 m_nChannels = 4;
1968 m_nInstruments = 0;
1969 m_nDefaultSpeed = 6;
1970 m_nDefaultTempo.Set(125);
1971 m_nMinPeriod = 14 * 4;
1972 m_nMaxPeriod = 3424 * 4;
1973 m_nSamplePreAmp = 64;
1974 m_SongFlags.set(SONG_PT_MODE | SONG_IMPORTED);
1975
1976 // Setup channel pan positions and volume
1977 SetupMODPanning();
1978
1979 // Reading patterns
1980 Order().resize(numOrders);
1981 uint8 speed[2] = {0, 0}, speedPos = 0;
1982 Patterns.ResizeArray(numOrders);
1983 for(PATTERNINDEX pat = 0; pat < numOrders; pat++)
1984 {
1985 Order()[pat] = pat;
1986 if(!Patterns.Insert(pat, 64))
1987 continue;
1988
1989 for(CHANNELINDEX chn = 0; chn < 4; chn++)
1990 {
1991 file.Seek(1468 + tracks[pat * 4 + chn] * 64u * 4u);
1992 ModCommand *m = Patterns[pat].GetpModCommand(0, chn);
1993
1994 for(ROWINDEX row = 0; row < 64; row++, m += 4)
1995 {
1996 ReadMODPatternEntry(file, *m);
1997
1998 if((m->command || m->param)
1999 && !(m->command == 0x0E && m->param >= 0x10) // Exx only sets filter
2000 && !(m->command >= 0x05 && m->command <= 0x09)) // These don't exist in ST2.6
2001 {
2002 ConvertModCommand(*m);
2003 } else
2004 {
2005 m->command = CMD_NONE;
2006 }
2007 }
2008 }
2009
2010 // Handle speed command with both nibbles set - this enables auto-swing (alternates between the two nibbles)
2011 auto m = Patterns[pat].begin();
2012 for(ROWINDEX row = 0; row < 64; row++)
2013 {
2014 for(CHANNELINDEX chn = 0; chn < 4; chn++, m++)
2015 {
2016 if(m->command == CMD_SPEED || m->command == CMD_TEMPO)
2017 {
2018 m->command = CMD_SPEED;
2019 speedPos = 0;
2020 if(m->param & 0xF0)
2021 {
2022 if((m->param >> 4) != (m->param & 0x0F) && (m->param & 0x0F) != 0)
2023 {
2024 // Both nibbles set
2025 speed[0] = m->param >> 4;
2026 speed[1] = m->param & 0x0F;
2027 speedPos = 1;
2028 }
2029 m->param >>= 4;
2030 }
2031 }
2032 }
2033 if(speedPos)
2034 {
2035 Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, speed[speedPos - 1]).Row(row));
2036 speedPos++;
2037 if(speedPos == 3)
2038 speedPos = 1;
2039 }
2040 }
2041 }
2042
2043 // Reading samples
2044 if(loadFlags & loadSampleData)
2045 {
2046 file.Seek(1468 + numTracks * 64u * 4u);
2047 for(SAMPLEINDEX smp = 1; smp <= 31; smp++) if(Samples[smp].nLength)
2048 {
2049 SampleIO(
2050 SampleIO::_8bit,
2051 SampleIO::mono,
2052 SampleIO::littleEndian,
2053 SampleIO::signedPCM)
2054 .ReadSample(Samples[smp], file);
2055 }
2056 }
2057
2058 return true;
2059 }
2060
2061
2062
2063 struct PT36Header
2064 {
2065 char magicFORM[4]; // "FORM"
2066 uint32be size;
2067 char magicMODL[4]; // "MODL"
2068 };
2069
2070 MPT_BINARY_STRUCT(PT36Header, 12)
2071
2072
ValidateHeader(const PT36Header & fileHeader)2073 static bool ValidateHeader(const PT36Header &fileHeader)
2074 {
2075 if(std::memcmp(fileHeader.magicFORM, "FORM", 4))
2076 {
2077 return false;
2078 }
2079 if(std::memcmp(fileHeader.magicMODL, "MODL", 4))
2080 {
2081 return false;
2082 }
2083 return true;
2084 }
2085
2086
ProbeFileHeaderPT36(MemoryFileReader file,const uint64 * pfilesize)2087 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPT36(MemoryFileReader file, const uint64 *pfilesize)
2088 {
2089 PT36Header fileHeader;
2090 if(!file.ReadStruct(fileHeader))
2091 {
2092 return ProbeWantMoreData;
2093 }
2094 if(!ValidateHeader(fileHeader))
2095 {
2096 return ProbeFailure;
2097 }
2098 MPT_UNREFERENCED_PARAMETER(pfilesize);
2099 return ProbeSuccess;
2100 }
2101
2102
2103 // ProTracker 3.6 version of the MOD format
2104 // Basically just a normal ProTracker mod with different magic, wrapped in an IFF file.
2105 // The "PTDT" chunk is passed to the normal MOD loader.
ReadPT36(FileReader & file,ModLoadingFlags loadFlags)2106 bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags)
2107 {
2108 file.Rewind();
2109
2110 PT36Header fileHeader;
2111 if(!file.ReadStruct(fileHeader))
2112 {
2113 return false;
2114 }
2115 if(!ValidateHeader(fileHeader))
2116 {
2117 return false;
2118 }
2119
2120 bool ok = false, infoOk = false;
2121 FileReader commentChunk;
2122 mpt::ustring version;
2123 PT36InfoChunk info;
2124 MemsetZero(info);
2125
2126 // Go through IFF chunks...
2127 PT36IffChunk iffHead;
2128 if(!file.ReadStruct(iffHead))
2129 {
2130 return false;
2131 }
2132 // First chunk includes "MODL" magic in size
2133 iffHead.chunksize -= 4;
2134
2135 do
2136 {
2137 // All chunk sizes include chunk header
2138 iffHead.chunksize -= 8;
2139 if(loadFlags == onlyVerifyHeader && iffHead.signature == PT36IffChunk::idPTDT)
2140 {
2141 return true;
2142 }
2143
2144 FileReader chunk = file.ReadChunk(iffHead.chunksize);
2145 if(!chunk.IsValid())
2146 {
2147 break;
2148 }
2149
2150 switch(iffHead.signature)
2151 {
2152 case PT36IffChunk::idVERS:
2153 chunk.Skip(4);
2154 if(chunk.ReadMagic("PT") && iffHead.chunksize > 6)
2155 {
2156 chunk.ReadString<mpt::String::maybeNullTerminated>(version, mpt::Charset::ISO8859_1, iffHead.chunksize - 6);
2157 }
2158 break;
2159
2160 case PT36IffChunk::idINFO:
2161 infoOk = chunk.ReadStruct(info);
2162 break;
2163
2164 case PT36IffChunk::idCMNT:
2165 commentChunk = chunk;
2166 break;
2167
2168 case PT36IffChunk::idPTDT:
2169 ok = ReadMOD(chunk, loadFlags);
2170 break;
2171 }
2172 } while(file.ReadStruct(iffHead));
2173
2174 if(version.empty())
2175 {
2176 version = U_("3.6");
2177 }
2178
2179 // both an info chunk and a module are required
2180 if(ok && infoOk)
2181 {
2182 bool vblank = (info.flags & 0x100) == 0;
2183 m_playBehaviour.set(kMODVBlankTiming, vblank);
2184 if(info.volume != 0)
2185 m_nSamplePreAmp = std::min(uint16(64), static_cast<uint16>(info.volume));
2186 if(info.tempo != 0 && !vblank)
2187 m_nDefaultTempo.Set(info.tempo);
2188
2189 if(info.name[0])
2190 m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, info.name);
2191
2192 if(mpt::is_in_range(info.dateMonth, 1, 12) && mpt::is_in_range(info.dateDay, 1, 31) && mpt::is_in_range(info.dateHour, 0, 23)
2193 && mpt::is_in_range(info.dateMinute, 0, 59) && mpt::is_in_range(info.dateSecond, 0, 59))
2194 {
2195 FileHistory mptHistory;
2196 mptHistory.loadDate.tm_year = info.dateYear;
2197 mptHistory.loadDate.tm_mon = info.dateMonth - 1;
2198 mptHistory.loadDate.tm_mday = info.dateDay;
2199 mptHistory.loadDate.tm_hour = info.dateHour;
2200 mptHistory.loadDate.tm_min = info.dateMinute;
2201 mptHistory.loadDate.tm_sec = info.dateSecond;
2202 m_FileHistory.push_back(mptHistory);
2203 }
2204 }
2205 if(ok)
2206 {
2207 if(commentChunk.IsValid())
2208 {
2209 std::string author;
2210 commentChunk.ReadString<mpt::String::maybeNullTerminated>(author, 32);
2211 if(author != "UNNAMED AUTHOR")
2212 m_songArtist = mpt::ToUnicode(mpt::Charset::ISO8859_1, author);
2213 if(!commentChunk.NoBytesLeft())
2214 {
2215 m_songMessage.ReadFixedLineLength(commentChunk, commentChunk.BytesLeft(), 40, 0);
2216 }
2217 }
2218
2219 m_modFormat.madeWithTracker = U_("ProTracker ") + version;
2220 }
2221 m_SongFlags.set(SONG_PT_MODE);
2222 m_playBehaviour.set(kMODIgnorePanning);
2223 m_playBehaviour.set(kMODOneShotLoops);
2224 m_playBehaviour.reset(kMODSampleSwap);
2225
2226 return ok;
2227 }
2228
2229
2230 #ifndef MODPLUG_NO_FILESAVE
2231
SaveMod(std::ostream & f) const2232 bool CSoundFile::SaveMod(std::ostream &f) const
2233 {
2234 if(m_nChannels == 0)
2235 {
2236 return false;
2237 }
2238
2239 // Write song title
2240 {
2241 char name[20];
2242 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = m_songName;
2243 mpt::IO::Write(f, name);
2244 }
2245
2246 std::vector<SmpLength> sampleLength(32, 0);
2247 std::vector<SAMPLEINDEX> sampleSource(32, 0);
2248
2249 if(GetNumInstruments())
2250 {
2251 INSTRUMENTINDEX lastIns = std::min(INSTRUMENTINDEX(31), GetNumInstruments());
2252 for(INSTRUMENTINDEX ins = 1; ins <= lastIns; ins++) if (Instruments[ins])
2253 {
2254 // Find some valid sample associated with this instrument.
2255 for(auto smp : Instruments[ins]->Keyboard)
2256 {
2257 if(smp > 0 && smp <= GetNumSamples())
2258 {
2259 sampleSource[ins] = smp;
2260 break;
2261 }
2262 }
2263 }
2264 } else
2265 {
2266 for(SAMPLEINDEX i = 1; i <= 31; i++)
2267 {
2268 sampleSource[i] = i;
2269 }
2270 }
2271
2272 // Write sample headers
2273 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
2274 {
2275 MODSampleHeader sampleHeader;
2276 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, sampleHeader.name) = m_szNames[sampleSource[smp]];
2277 sampleLength[smp] = sampleHeader.ConvertToMOD(sampleSource[smp] <= GetNumSamples() ? GetSample(sampleSource[smp]) : ModSample(MOD_TYPE_MOD));
2278 mpt::IO::Write(f, sampleHeader);
2279 }
2280
2281 // Write order list
2282 MODFileHeader fileHeader;
2283 MemsetZero(fileHeader);
2284
2285 PATTERNINDEX writePatterns = 0;
2286 uint8 writtenOrders = 0;
2287 for(ORDERINDEX ord = 0; ord < Order().GetLength() && writtenOrders < 128; ord++)
2288 {
2289 // Ignore +++ and --- patterns in order list, as well as high patterns (MOD officially only supports up to 128 patterns)
2290 if(ord == Order().GetRestartPos())
2291 {
2292 fileHeader.restartPos = writtenOrders;
2293 }
2294 if(Order()[ord] < 128)
2295 {
2296 fileHeader.orderList[writtenOrders++] = static_cast<uint8>(Order()[ord]);
2297 if(writePatterns <= Order()[ord])
2298 {
2299 writePatterns = Order()[ord] + 1;
2300 }
2301 }
2302 }
2303 fileHeader.numOrders = writtenOrders;
2304 mpt::IO::Write(f, fileHeader);
2305
2306 // Write magic bytes
2307 char modMagic[4];
2308 CHANNELINDEX writeChannels = std::min(CHANNELINDEX(99), GetNumChannels());
2309 if(writeChannels == 4)
2310 {
2311 // ProTracker may not load files with more than 64 patterns correctly if we do not specify the M!K! magic.
2312 if(writePatterns <= 64)
2313 memcpy(modMagic, "M.K.", 4);
2314 else
2315 memcpy(modMagic, "M!K!", 4);
2316 } else if(writeChannels < 10)
2317 {
2318 memcpy(modMagic, "0CHN", 4);
2319 modMagic[0] += static_cast<char>(writeChannels);
2320 } else
2321 {
2322 memcpy(modMagic, "00CH", 4);
2323 modMagic[0] += static_cast<char>(writeChannels / 10u);
2324 modMagic[1] += static_cast<char>(writeChannels % 10u);
2325 }
2326 mpt::IO::Write(f, modMagic);
2327
2328 // Write patterns
2329 bool invalidInstruments = false;
2330 std::vector<uint8> events;
2331 for(PATTERNINDEX pat = 0; pat < writePatterns; pat++)
2332 {
2333 if(!Patterns.IsValidPat(pat))
2334 {
2335 // Invent empty pattern
2336 events.assign(writeChannels * 64 * 4, 0);
2337 mpt::IO::Write(f, events);
2338 continue;
2339 }
2340
2341 for(ROWINDEX row = 0; row < 64; row++)
2342 {
2343 if(row >= Patterns[pat].GetNumRows())
2344 {
2345 // Invent empty row
2346 events.assign(writeChannels * 4, 0);
2347 mpt::IO::Write(f, events);
2348 continue;
2349 }
2350 PatternRow rowBase = Patterns[pat].GetRow(row);
2351
2352 events.resize(writeChannels * 4);
2353 size_t eventByte = 0;
2354 for(CHANNELINDEX chn = 0; chn < writeChannels; chn++, eventByte += 4)
2355 {
2356 const ModCommand &m = rowBase[chn];
2357 uint8 command = m.command, param = m.param;
2358 ModSaveCommand(command, param, false, true);
2359
2360 if(m.volcmd == VOLCMD_VOLUME && !command && !param)
2361 {
2362 // Maybe we can save some volume commands...
2363 command = 0x0C;
2364 param = std::min(m.vol, uint8(64));
2365 }
2366
2367 uint16 period = 0;
2368 // Convert note to period
2369 if(m.note >= 24 + NOTE_MIN && m.note < std::size(ProTrackerPeriodTable) + 24 + NOTE_MIN)
2370 {
2371 period = ProTrackerPeriodTable[m.note - 24 - NOTE_MIN];
2372 }
2373
2374 const uint8 instr = (m.instr > 31) ? 0 : m.instr;
2375 if(m.instr > 31)
2376 invalidInstruments = true;
2377
2378 events[eventByte + 0] = ((period >> 8) & 0x0F) | (instr & 0x10);
2379 events[eventByte + 1] = period & 0xFF;
2380 events[eventByte + 2] = ((instr & 0x0F) << 4) | (command & 0x0F);
2381 events[eventByte + 3] = param;
2382 }
2383 mpt::IO::WriteRaw(f, mpt::as_span(events));
2384 }
2385 }
2386
2387 if(invalidInstruments)
2388 {
2389 AddToLog(LogWarning, U_("Warning: This track references sample slots higher than 31. Such samples cannot be saved in the MOD format, and thus the notes will not sound correct. Use the Cleanup tool to rearrange and remove unused samples."));
2390 }
2391
2392 //Check for unsaved patterns
2393 for(PATTERNINDEX pat = writePatterns; pat < Patterns.Size(); pat++)
2394 {
2395 if(Patterns.IsValidPat(pat))
2396 {
2397 AddToLog(LogWarning, U_("Warning: This track contains at least one pattern after the highest pattern number referred to in the sequence. Such patterns are not saved in the MOD format."));
2398 break;
2399 }
2400 }
2401
2402 // Writing samples
2403 for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
2404 {
2405 if(sampleLength[smp] == 0)
2406 {
2407 continue;
2408 }
2409 const ModSample &sample = Samples[sampleSource[smp]];
2410
2411 const mpt::IO::Offset sampleStart = mpt::IO::TellWrite(f);
2412 const size_t writtenBytes = MODSampleHeader::GetSampleFormat().WriteSample(f, sample, sampleLength[smp]);
2413
2414 const int8 silence = 0;
2415
2416 // Write padding byte if the sample size is odd.
2417 if((writtenBytes % 2u) != 0)
2418 {
2419 mpt::IO::Write(f, silence);
2420 }
2421
2422 if(!sample.uFlags[CHN_LOOP] && writtenBytes >= 2)
2423 {
2424 // First two bytes of oneshot samples have to be 0 due to PT's one-shot loop
2425 const mpt::IO::Offset sampleEnd = mpt::IO::TellWrite(f);
2426 mpt::IO::SeekAbsolute(f, sampleStart);
2427 mpt::IO::Write(f, silence);
2428 mpt::IO::Write(f, silence);
2429 mpt::IO::SeekAbsolute(f, sampleEnd);
2430 }
2431 }
2432
2433 return true;
2434 }
2435
2436 #endif // MODPLUG_NO_FILESAVE
2437
2438
2439 OPENMPT_NAMESPACE_END
2440