bool module_renderer::ReadITProject(const uint8_t * lpStream, const uint32_t dwMemLength) //----------------------------------------------------------------------- { UINT i,n,nsmp; uint32_t id,len,size; uint32_t dwMemPos = 0; uint32_t version; ASSERT_CAN_READ(12); // Check file ID memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); if(id != ITP_FILE_ID) return false; dwMemPos += sizeof(uint32_t); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); version = id; dwMemPos += sizeof(uint32_t); // bad_max supported version if(version > ITP_VERSION) { return false; } m_nType = MOD_TYPE_IT; // Song name // name string length memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); len = id; dwMemPos += sizeof(uint32_t); // name string ASSERT_CAN_READ(len); if (len <= MAX_SAMPLENAME) { assign_without_padding(this->song_name, reinterpret_cast<const char *>(lpStream + dwMemPos), len); dwMemPos += len; } else return false; // Song comments // comment string length ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); dwMemPos += sizeof(uint32_t); if(id > UINT16_MAX) return false; // allocate and copy comment string ASSERT_CAN_READ(id); if(id > 0) { ReadMessage(lpStream + dwMemPos, id - 1, leCR); } dwMemPos += id; // Song global config ASSERT_CAN_READ(5*4); // m_dwSongFlags memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_dwSongFlags = (id & SONG_FILE_FLAGS); dwMemPos += sizeof(uint32_t); if(!(m_dwSongFlags & SONG_ITPROJECT)) return false; // m_nDefaultGlobalVolume memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nDefaultGlobalVolume = id; dwMemPos += sizeof(uint32_t); // m_nSamplePreAmp memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nSamplePreAmp = id; dwMemPos += sizeof(uint32_t); // m_nDefaultSpeed memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nDefaultSpeed = id; dwMemPos += sizeof(uint32_t); // m_nDefaultTempo memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nDefaultTempo = id; dwMemPos += sizeof(uint32_t); // Song channels data ASSERT_CAN_READ(2*4); // m_nChannels memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nChannels = (modplug::tracker::chnindex_t)id; dwMemPos += sizeof(uint32_t); if(m_nChannels > 127) return false; // channel name string length (=MAX_CHANNELNAME) memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); len = id; dwMemPos += sizeof(uint32_t); if(len > MAX_CHANNELNAME) return false; // Channels' data for(i=0; i<m_nChannels; i++){ ASSERT_CAN_READ(3*4 + len); // ChnSettings[i].nPan memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); ChnSettings[i].nPan = id; dwMemPos += sizeof(uint32_t); // ChnSettings[i].dwFlags memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); ChnSettings[i].dwFlags = id; dwMemPos += sizeof(uint32_t); // ChnSettings[i].nVolume memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); ChnSettings[i].nVolume = id; dwMemPos += sizeof(uint32_t); // ChnSettings[i].szName memcpy(&ChnSettings[i].szName[0],lpStream+dwMemPos,len); SetNullTerminator(ChnSettings[i].szName); dwMemPos += len; } // Song mix plugins // size of mix plugins data ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); dwMemPos += sizeof(uint32_t); // mix plugins ASSERT_CAN_READ(id); dwMemPos += LoadMixPlugins(lpStream+dwMemPos, id); // Song midi config // midi cfg data length ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); dwMemPos += sizeof(uint32_t); // midi cfg ASSERT_CAN_READ(id); if (id <= sizeof(m_MidiCfg)) { memcpy(&m_MidiCfg, lpStream + dwMemPos, id); SanitizeMacros(); dwMemPos += id; } // Song Instruments // m_nInstruments ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); m_nInstruments = (modplug::tracker::instrumentindex_t)id; if(m_nInstruments > MAX_INSTRUMENTS) return false; dwMemPos += sizeof(uint32_t); // path string length (=_MAX_PATH) ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); len = id; if(len > _MAX_PATH) return false; dwMemPos += sizeof(uint32_t); // instruments' paths for(i=0; i<m_nInstruments; i++){ ASSERT_CAN_READ(len); memcpy(&m_szInstrumentPath[i][0],lpStream+dwMemPos,len); SetNullTerminator(m_szInstrumentPath[i]); dwMemPos += len; } // Song Orders // size of order array (=MAX_ORDERS) ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); size = id; if(size > MAX_ORDERS) return false; dwMemPos += sizeof(uint32_t); // order data ASSERT_CAN_READ(size); Order.ReadAsByte(lpStream+dwMemPos, size, dwMemLength-dwMemPos); dwMemPos += size; // Song Patterns ASSERT_CAN_READ(3*4); // number of patterns (=MAX_PATTERNS) memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); size = id; dwMemPos += sizeof(uint32_t); if(size > MAX_PATTERNS) return false; // m_nPatternNames memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); const modplug::tracker::patternindex_t numNamedPats = id; dwMemPos += sizeof(uint32_t); // pattern name string length (=MAX_PATTERNNAME) memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); const uint32_t patNameLen = id; dwMemPos += sizeof(uint32_t); // m_lpszPatternNames ASSERT_CAN_READ(numNamedPats * patNameLen); char *patNames = (char *)(lpStream + dwMemPos); dwMemPos += numNamedPats * patNameLen; // modcommand data length ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); n = id; if(n != 6) return false; dwMemPos += sizeof(uint32_t); for(modplug::tracker::patternindex_t npat=0; npat<size; npat++) { // Patterns[npat].GetNumRows() ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); if(id > MAX_PATTERN_ROWS) return false; const modplug::tracker::rowindex_t nRows = id; dwMemPos += sizeof(uint32_t); // Try to allocate & read only sized patterns if(nRows) { // Allocate pattern if(Patterns.Insert(npat, nRows)) { dwMemPos += m_nChannels * Patterns[npat].GetNumRows() * n; continue; } if(npat < numNamedPats && patNameLen > 0) { Patterns[npat].SetName(patNames, patNameLen); patNames += patNameLen; } // Pattern data long datasize = m_nChannels * Patterns[npat].GetNumRows() * n; //if (streamPos+datasize<=dwMemLength) { if(Patterns[npat].ReadITPdata(lpStream, dwMemPos, datasize, dwMemLength)) { ErrorBox(IDS_ERR_FILEOPEN, NULL); return false; } //memcpy(Patterns[npat],lpStream+streamPos,datasize); //streamPos += datasize; //} } } // Load embeded samples ITSAMPLESTRUCT pis; // Read original number of samples ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); if(id > MAX_SAMPLES) return false; m_nSamples = (modplug::tracker::sampleindex_t)id; dwMemPos += sizeof(uint32_t); // Read number of embeded samples ASSERT_CAN_READ(4); memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); if(id > MAX_SAMPLES) return false; n = id; dwMemPos += sizeof(uint32_t); // Read samples for(i=0; i<n; i++){ ASSERT_CAN_READ(4 + sizeof(ITSAMPLESTRUCT) + 4); // Sample id number memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); nsmp = id; dwMemPos += sizeof(uint32_t); if(nsmp < 1 || nsmp >= MAX_SAMPLES) return false; // Sample struct memcpy(&pis,lpStream+dwMemPos,sizeof(ITSAMPLESTRUCT)); dwMemPos += sizeof(ITSAMPLESTRUCT); // Sample length memcpy(&id,lpStream+dwMemPos,sizeof(uint32_t)); len = id; dwMemPos += sizeof(uint32_t); if(dwMemPos >= dwMemLength || len > dwMemLength - dwMemPos) return false; // Copy sample struct data (ut-oh... this code looks very familiar!) if(pis.id == LittleEndian(IT_IMPS)) { modsample_t *pSmp = &Samples[nsmp]; memcpy(pSmp->legacy_filename, pis.filename, 12); pSmp->flags = 0; pSmp->length = 0; pSmp->loop_start = pis.loopbegin; pSmp->loop_end = pis.loopend; pSmp->sustain_start = pis.susloopbegin; pSmp->sustain_end = pis.susloopend; pSmp->c5_samplerate = pis.C5Speed; if(!pSmp->c5_samplerate) pSmp->c5_samplerate = 8363; if(pis.C5Speed < 256) pSmp->c5_samplerate = 256; pSmp->default_volume = pis.vol << 2; if(pSmp->default_volume > 256) pSmp->default_volume = 256; pSmp->global_volume = pis.gvl; if(pSmp->global_volume > 64) pSmp->global_volume = 64; if(pis.flags & 0x10) pSmp->flags |= CHN_LOOP; if(pis.flags & 0x20) pSmp->flags |= CHN_SUSTAINLOOP; if(pis.flags & 0x40) pSmp->flags |= CHN_PINGPONGLOOP; if(pis.flags & 0x80) pSmp->flags |= CHN_PINGPONGSUSTAIN; pSmp->default_pan = (pis.dfp & 0x7F) << 2; if(pSmp->default_pan > 256) pSmp->default_pan = 256; if(pis.dfp & 0x80) pSmp->flags |= CHN_PANNING; pSmp->vibrato_type = autovibit2xm[pis.vit & 7]; pSmp->vibrato_rate = pis.vis; pSmp->vibrato_depth = pis.vid & 0x7F; pSmp->vibrato_sweep = pis.vir; if(pis.length){ pSmp->length = pis.length; if (pSmp->length > MAX_SAMPLE_LENGTH) pSmp->length = MAX_SAMPLE_LENGTH; UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U; if (pis.flags & 2){ flags += 5; if (pis.flags & 4) flags |= RSF_STEREO; pSmp->flags |= CHN_16BIT; } else{ if (pis.flags & 4) flags |= RSF_STEREO; } // Read sample data ReadSample(&Samples[nsmp], flags, (LPSTR)(lpStream+dwMemPos), len); dwMemPos += len; memcpy(m_szNames[nsmp], pis.name, 26); } } } // Load instruments CMappedFile f; LPBYTE lpFile; for(modplug::tracker::instrumentindex_t i = 0; i < m_nInstruments; i++) { if(m_szInstrumentPath[i][0] == '\0' || !f.Open(m_szInstrumentPath[i])) continue; len = f.GetLength(); lpFile = f.Lock(len); if(!lpFile) { f.Close(); continue; } ReadInstrumentFromFile(i+1, lpFile, len); f.Unlock(); f.Close(); } // Extra info data __int32 fcode = 0; const uint8_t * ptr = lpStream + bad_min(dwMemPos, dwMemLength); if (dwMemPos <= dwMemLength - 4) { fcode = (*((__int32 *)ptr)); } // Embed instruments' header [v1.01] if(version >= 0x00000101 && m_dwSongFlags & SONG_ITPEMBEDIH && fcode == 'EBIH') { // jump embeded instrument header tag ptr += sizeof(__int32); // set first instrument's header as current i = 1; // parse file while( uintptr_t(ptr - lpStream) <= dwMemLength - 4 && i <= m_nInstruments ) { fcode = (*((__int32 *)ptr)); // read field code switch( fcode ) { case 'MPTS': goto mpts; //:) // reached end of instrument headers case 'SEP@': case 'MPTX': ptr += sizeof(__int32); // jump code i++; // switch to next instrument break; default: ptr += sizeof(__int32); // jump field code ReadExtendedInstrumentProperty(Instruments[i], fcode, ptr, lpStream + dwMemLength); break; } } } //HACK: if we fail on i <= m_nInstruments above, arrive here without having set fcode as appropriate, // hence the code duplication. if ( (uintptr_t)(ptr - lpStream) <= dwMemLength - 4 ) { fcode = (*((__int32 *)ptr)); } // Song extensions mpts: if( fcode == 'MPTS' ) LoadExtendedSongProperties(MOD_TYPE_IT, ptr, lpStream, dwMemLength); m_nMaxPeriod = 0xF000; m_nMinPeriod = 8; if(m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 17, 2, 50)) { SetModFlag(MSF_COMPATIBLE_PLAY, false); SetModFlag(MSF_MIDICC_BUGEMULATION, true); SetModFlag(MSF_OLDVOLSWING, true); } return true; }
OPENMPT_NAMESPACE_BEGIN // Version changelog: // v1.03: - Relative unicode instrument paths instead of absolute ANSI paths // - Per-path variable string length // - Embedded samples are IT-compressed // (rev. 3249) // v1.02: Explicitely updated format to use new instrument flags representation (rev. 483) // v1.01: Added option to embed instrument headers bool CSoundFile::ReadITProject(FileReader &file, ModLoadingFlags loadFlags) //------------------------------------------------------------------------- { #ifndef MPT_EXTERNAL_SAMPLES // Doesn't really make sense to support this format when there's no support for external files... MPT_UNREFERENCED_PARAMETER(file); MPT_UNREFERENCED_PARAMETER(loadFlags); return false; #else // MPT_EXTERNAL_SAMPLES enum ITPSongFlags { ITP_EMBEDMIDICFG = 0x00001, // Embed macros in file ITP_ITOLDEFFECTS = 0x00004, // Old Impulse Tracker effect implementations ITP_ITCOMPATGXX = 0x00008, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) ITP_LINEARSLIDES = 0x00010, // Linear slides vs. Amiga slides ITP_EXFILTERRANGE = 0x08000, // Cutoff Filter has double frequency range (up to ~10Khz) ITP_ITPROJECT = 0x20000, // Is a project file ITP_ITPEMBEDIH = 0x40000, // Embed instrument headers in project file }; uint32 version; FileReader::off_t size; file.Rewind(); // Check file ID if(!file.CanRead(12 + 4 + 24 + 4) || file.ReadUint32LE() != MAGIC4BE('.','i','t','p') // Magic bytes || (version = file.ReadUint32LE()) > 0x00000103 // Format version || version < 0x00000100) { return false; } else if(loadFlags == onlyVerifyHeader) { return true; } InitializeGlobals(MOD_TYPE_IT); m_playBehaviour.reset(); file.ReadString<mpt::String::maybeNullTerminated>(m_songName, file.ReadUint32LE()); // Song comments m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR); // Song global config const uint32 songFlags = file.ReadUint32LE(); if(!(songFlags & ITP_ITPROJECT)) { return false; } if(songFlags & ITP_EMBEDMIDICFG) m_SongFlags.set(SONG_EMBEDMIDICFG); if(songFlags & ITP_ITOLDEFFECTS) m_SongFlags.set(SONG_ITOLDEFFECTS); if(songFlags & ITP_ITCOMPATGXX) m_SongFlags.set(SONG_ITCOMPATGXX); if(songFlags & ITP_LINEARSLIDES) m_SongFlags.set(SONG_LINEARSLIDES); if(songFlags & ITP_EXFILTERRANGE) m_SongFlags.set(SONG_EXFILTERRANGE); m_nDefaultGlobalVolume = file.ReadUint32LE(); m_nSamplePreAmp = file.ReadUint32LE(); m_nDefaultSpeed = std::max(uint32(1), file.ReadUint32LE()); m_nDefaultTempo.Set(std::max(uint32(32), file.ReadUint32LE())); m_nChannels = static_cast<CHANNELINDEX>(file.ReadUint32LE()); if(m_nChannels == 0 || m_nChannels > MAX_BASECHANNELS) { return false; } // channel name string length (=MAX_CHANNELNAME) size = file.ReadUint32LE(); // Channels' data for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) { ChnSettings[chn].nPan = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(256)); ChnSettings[chn].dwFlags.reset(); uint32 flags = file.ReadUint32LE(); if(flags & 0x100) ChnSettings[chn].dwFlags.set(CHN_MUTE); if(flags & 0x800) ChnSettings[chn].dwFlags.set(CHN_SURROUND); ChnSettings[chn].nVolume = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(64)); file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, size); } // Song mix plugins { FileReader plugChunk = file.ReadChunk(file.ReadUint32LE()); LoadMixPlugins(plugChunk); } // MIDI Macro config file.ReadStructPartial(m_MidiCfg, file.ReadUint32LE()); m_MidiCfg.Sanitize(); // Song Instruments m_nInstruments = static_cast<INSTRUMENTINDEX>(file.ReadUint32LE()); if(m_nInstruments >= MAX_INSTRUMENTS) { return false; } // Instruments' paths if(version <= 0x00000102) { size = file.ReadUint32LE(); // path string length } std::vector<mpt::PathString> instrPaths(GetNumInstruments()); for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++) { if(version > 0x00000102) { size = file.ReadUint32LE(); // path string length } std::string path; file.ReadString<mpt::String::maybeNullTerminated>(path, size); if(version <= 0x00000102) { instrPaths[ins] = mpt::PathString::FromLocaleSilent(path); } else { instrPaths[ins] = mpt::PathString::FromUTF8(path); } } // Song Orders size = file.ReadUint32LE(); Order.ReadAsByte(file, size, size, 0xFF, 0xFE); // Song Patterns const PATTERNINDEX numPats = static_cast<PATTERNINDEX>(file.ReadUint32LE()); const PATTERNINDEX numNamedPats = static_cast<PATTERNINDEX>(file.ReadUint32LE()); size_t patNameLen = file.ReadUint32LE(); // Size of each pattern name FileReader pattNames = file.ReadChunk(numNamedPats * patNameLen); // modcommand data length size = file.ReadUint32LE(); if(size != 6) { return false; } for(PATTERNINDEX pat = 0; pat < numPats; pat++) { const ROWINDEX numRows = file.ReadUint32LE(); FileReader patternChunk = file.ReadChunk(numRows * size * GetNumChannels()); // Allocate pattern if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows)) { pattNames.Skip(patNameLen); continue; } if(pat < numNamedPats) { char patName[32]; pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen); Patterns[pat].SetName(patName); } // Pattern data size_t numCommands = GetNumChannels() * numRows; if(patternChunk.CanRead(sizeof(MODCOMMAND_ORIGINAL) * numCommands)) { ModCommand *target = Patterns[pat].GetpModCommand(0, 0); while(numCommands-- != 0) { STATIC_ASSERT(sizeof(MODCOMMAND_ORIGINAL) == 6); MODCOMMAND_ORIGINAL data; patternChunk.ReadStruct(data); if(data.command >= MAX_EFFECTS) data.command = CMD_NONE; if(data.volcmd >= MAX_VOLCMDS) data.volcmd = VOLCMD_NONE; if(data.note > NOTE_MAX && data.note < NOTE_MIN_SPECIAL) data.note = NOTE_NONE; *(target++) = data; } } } // Load embedded samples // Read original number of samples m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint32LE()); LimitMax(m_nSamples, SAMPLEINDEX(MAX_SAMPLES - 1)); // Read number of embedded samples uint32 embeddedSamples = file.ReadUint32LE(); // Read samples for(uint32 smp = 0; smp < embeddedSamples; smp++) { SAMPLEINDEX realSample = static_cast<SAMPLEINDEX>(file.ReadUint32LE()); ITSample sampleHeader; file.ReadConvertEndianness(sampleHeader); FileReader sampleData = file.ReadChunk(file.ReadUint32LE()); if(realSample >= 1 && realSample <= GetNumSamples() && !memcmp(sampleHeader.id, "IMPS", 4) && (loadFlags & loadSampleData)) { sampleHeader.ConvertToMPT(Samples[realSample]); mpt::String::Read<mpt::String::nullTerminated>(m_szNames[realSample], sampleHeader.name); // Read sample data sampleHeader.GetSampleFormat().ReadSample(Samples[realSample], sampleData); } } // Load instruments for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++) { if(instrPaths[ins].empty()) continue; if(!file.GetFileName().empty()) { instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(file.GetFileName().GetPath()); } #ifdef MODPLUG_TRACKER else if(GetpModDoc() != nullptr) { instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(GetpModDoc()->GetPathNameMpt().GetPath()); } #endif // MODPLUG_TRACKER InputFile f(instrPaths[ins]); FileReader file = GetFileReader(f); if(!ReadInstrumentFromFile(ins + 1, file, true)) { AddToLog(LogWarning, MPT_USTRING("Unable to open instrument: ") + instrPaths[ins].ToUnicode()); } } // Extra info data uint32 code = file.ReadUint32LE(); // Embed instruments' header [v1.01] if(version >= 0x00000101 && (songFlags & ITP_ITPEMBEDIH) && code == MAGIC4BE('E', 'B', 'I', 'H')) { code = file.ReadUint32LE(); INSTRUMENTINDEX ins = 1; while(ins <= GetNumInstruments() && file.CanRead(4)) { if(code == MAGIC4BE('M', 'P', 'T', 'S')) { break; } else if(code == MAGIC4BE('S', 'E', 'P', '@') || code == MAGIC4BE('M', 'P', 'T', 'X')) { // jump code - switch to next instrument ins++; } else { ReadExtendedInstrumentProperty(Instruments[ins], code, file); } code = file.ReadUint32LE(); } } // Song extensions if(code == MAGIC4BE('M', 'P', 'T', 'S')) { file.SkipBack(4); LoadExtendedSongProperties(file); } m_nMaxPeriod = 0xF000; m_nMinPeriod = 8; // Before OpenMPT 1.20.01.09, the MIDI macros were always read from the file, even if the "embed" flag was not set. if(m_dwLastSavedWithVersion >= MAKE_VERSION_NUMERIC(1,20,01,09) && !m_SongFlags[SONG_EMBEDMIDICFG]) { m_MidiCfg.Reset(); } else if(!m_MidiCfg.IsMacroDefaultSetupUsed()) { m_SongFlags.set(SONG_EMBEDMIDICFG); } m_madeWithTracker = "OpenMPT " + MptVersion::ToStr(m_dwLastSavedWithVersion); return true; #endif // MPT_EXTERNAL_SAMPLES }