From 31daa956d62b39429cb6638ed3fb549ac488833a Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sat, 31 Dec 2016 20:39:57 -0600 Subject: SCI: Implement bounds-checked reads of game resources --- engines/sci/sound/audio.cpp | 32 +++-- engines/sci/sound/drivers/adlib.cpp | 61 ++++---- engines/sci/sound/drivers/amigamac.cpp | 2 +- engines/sci/sound/drivers/cms.cpp | 20 +-- engines/sci/sound/drivers/fb01.cpp | 39 +++--- engines/sci/sound/drivers/fmtowns.cpp | 20 +-- engines/sci/sound/drivers/midi.cpp | 248 +++++++++++++++++---------------- engines/sci/sound/midiparser_sci.cpp | 14 +- engines/sci/sound/music.cpp | 8 +- engines/sci/sound/sync.cpp | 8 +- 10 files changed, 231 insertions(+), 221 deletions(-) (limited to 'engines/sci/sound') diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index 4fb9a58003..e470b315cc 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -365,15 +365,15 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 if (audioCompressionType) { #if (defined(USE_MAD) || defined(USE_VORBIS) || defined(USE_FLAC)) // Compressed audio made by our tool - byte *compressedData = (byte *)malloc(audioRes->size); + byte *compressedData = (byte *)malloc(audioRes->size()); assert(compressedData); // We copy over the compressed data in our own buffer. We have to do // this, because ResourceManager may free the original data late. All // other compression types already decompress completely into an // additional buffer here. MP3/OGG/FLAC decompression works on-the-fly // instead. - memcpy(compressedData, audioRes->data, audioRes->size); - Common::SeekableReadStream *compressedStream = new Common::MemoryReadStream(compressedData, audioRes->size, DisposeAfterUse::YES); + audioRes->unsafeCopyDataTo(compressedData); + Common::SeekableReadStream *compressedStream = new Common::MemoryReadStream(compressedData, audioRes->size(), DisposeAfterUse::YES); switch (audioCompressionType) { case MKTAG('M','P','3',' '): @@ -401,13 +401,13 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 // SCI1.1 Common::MemoryReadStream headerStream(audioRes->_header, audioRes->_headerSize, DisposeAfterUse::NO); - if (readSOLHeader(&headerStream, audioRes->_headerSize, size, _audioRate, audioFlags, audioRes->size)) { - Common::MemoryReadStream dataStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + if (readSOLHeader(&headerStream, audioRes->_headerSize, size, _audioRate, audioFlags, audioRes->size())) { + Common::MemoryReadStream dataStream(audioRes->toStream()); data = readSOLAudio(&dataStream, size, audioFlags, flags); } - } else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKTAG('R','I','F','F')) { + } else if (audioRes->size() > 4 && audioRes->getUint32BEAt(0) == MKTAG('R','I','F','F')) { // WAVE detected - Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->getUnsafeDataAt(0), audioRes->size(), DisposeAfterUse::NO); // Calculate samplelen from WAVE header int waveSize = 0, waveRate = 0; @@ -420,9 +420,9 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 waveStream->seek(0, SEEK_SET); audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES); - } else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKTAG('F','O','R','M')) { + } else if (audioRes->size() > 4 && audioRes->getUint32BEAt(0) == MKTAG('F','O','R','M')) { // AIFF detected - Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->getUnsafeDataAt(0), audioRes->size(), DisposeAfterUse::NO); Audio::RewindableAudioStream *rewindStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); audioSeekStream = dynamic_cast(rewindStream); @@ -430,10 +430,14 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 warning("AIFF file is not seekable"); delete rewindStream; } - } else if (audioRes->size > 14 && READ_BE_UINT16(audioRes->data) == 1 && READ_BE_UINT16(audioRes->data + 2) == 1 - && READ_BE_UINT16(audioRes->data + 4) == 5 && READ_BE_UINT32(audioRes->data + 10) == 0x00018051) { + } else if (audioRes->size() > 14 && + audioRes->getUint16BEAt(0) == 1 && + audioRes->getUint16BEAt(2) == 1 && + audioRes->getUint16BEAt(4) == 5 && + audioRes->getUint32BEAt(10) == 0x00018051) { + // Mac snd detected - Common::SeekableReadStream *sndStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + Common::SeekableReadStream *sndStream = new Common::MemoryReadStream(audioRes->getUnsafeDataAt(0), audioRes->size(), DisposeAfterUse::NO); audioSeekStream = Audio::makeMacSndStream(sndStream, DisposeAfterUse::YES); if (!audioSeekStream) @@ -441,10 +445,10 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 } else { // SCI1 raw audio - size = audioRes->size; + size = audioRes->size(); data = (byte *)malloc(size); assert(data); - memcpy(data, audioRes->data, size); + audioRes->unsafeCopyDataTo(data); flags = Audio::FLAG_UNSIGNED; _audioRate = 11025; } diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index 4f557be95e..b8e90802b2 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -31,6 +31,7 @@ #include "sci/resource.h" #include "sci/sound/drivers/mididriver.h" +#include "sci/util.h" namespace Sci { @@ -50,7 +51,7 @@ public: kRhythmKeys = 62 }; - MidiDriver_AdLib(Audio::Mixer *mixer) :_playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0), _isOpen(false) { } + MidiDriver_AdLib(Audio::Mixer *mixer) :_playSwitch(true), _masterVolume(15), _rhythmKeyMap(), _opl(0), _isOpen(false) { } virtual ~MidiDriver_AdLib() { } // MidiDriver @@ -70,10 +71,10 @@ public: void setVolume(byte volume); void playSwitch(bool play); - bool loadResource(const byte *data, uint size); + bool loadResource(const SciSpan &data); virtual uint32 property(int prop, uint32 param); - bool useRhythmChannel() const { return _rhythmKeyMap != NULL; } + bool useRhythmChannel() const { return _rhythmKeyMap; } private: enum ChannelID { @@ -139,13 +140,13 @@ private: int _masterVolume; Channel _channels[MIDI_CHANNELS]; AdLibVoice _voices[kVoices]; - byte *_rhythmKeyMap; + Common::SpanOwner > _rhythmKeyMap; Common::Array _patches; Common::TimerManager::TimerProc _adlibTimerProc; void *_adlibTimerParam; - void loadInstrument(const byte *ins); + void loadInstrument(const SciSpan &ins); void voiceOn(int voice, int note, int velocity); void voiceOff(int voice); void setPatch(int voice, int patch); @@ -255,7 +256,7 @@ int MidiDriver_AdLib::openAdLib(bool isSCI0) { void MidiDriver_AdLib::close() { delete _opl; - delete[] _rhythmKeyMap; + _rhythmKeyMap.clear(); } void MidiDriver_AdLib::setVolume(byte volume) { @@ -346,12 +347,12 @@ void MidiDriver_AdLib::onTimer() { } } -void MidiDriver_AdLib::loadInstrument(const byte *ins) { +void MidiDriver_AdLib::loadInstrument(const SciSpan &ins) { AdLibPatch patch; // Set data for the operators for (int i = 0; i < 2; i++) { - const byte *op = ins + i * 13; + const byte *op = ins.getUnsafeDataAt(i * 13, 13); patch.op[i].kbScaleLevel = op[0] & 0x3; patch.op[i].frequencyMult = op[1] & 0xf; patch.op[i].attackRate = op[3] & 0xf; @@ -589,7 +590,7 @@ void MidiDriver_AdLib::voiceOn(int voice, int note, int velocity) { _voices[voice].age = 0; - if ((channel == 9) && _rhythmKeyMap) { + if (channel == 9 && _rhythmKeyMap) { patch = CLIP(note, 27, 88) + 101; } else { patch = _channels[channel].patch; @@ -616,7 +617,7 @@ void MidiDriver_AdLib::setNote(int voice, int note, bool key) { float delta; int bend = _channels[channel].pitchWheel; - if ((channel == 9) && _rhythmKeyMap) { + if (channel == 9 && _rhythmKeyMap) { note = _rhythmKeyMap[CLIP(note, 27, 88) - 27]; } @@ -756,30 +757,32 @@ void MidiDriver_AdLib::playSwitch(bool play) { renewNotes(-1, play); } -bool MidiDriver_AdLib::loadResource(const byte *data, uint size) { - if ((size != 1344) && (size != 2690) && (size != 5382)) { - error("ADLIB: Unsupported patch format (%i bytes)", size); +bool MidiDriver_AdLib::loadResource(const SciSpan &data) { + const uint32 size = data.size(); + if (size != 1344 && size != 2690 && size != 5382) { + error("ADLIB: Unsupported patch format (%u bytes)", size); return false; } for (int i = 0; i < 48; i++) - loadInstrument(data + (28 * i)); + loadInstrument(data.subspan(28 * i)); if (size == 1344) { byte dummy[28] = {0}; // Only 48 instruments, add dummies for (int i = 0; i < 48; i++) - loadInstrument(dummy); + loadInstrument(SciSpan(dummy, sizeof(dummy))); } else if (size == 2690) { for (int i = 48; i < 96; i++) - loadInstrument(data + 2 + (28 * i)); + loadInstrument(data.subspan(2 + (28 * i))); } else { // SCI1.1 and later - for (int i = 48; i < 190; i++) - loadInstrument(data + (28 * i)); - _rhythmKeyMap = new byte[kRhythmKeys]; - memcpy(_rhythmKeyMap, data + 5320, kRhythmKeys); + for (int i = 48; i < 190; i++) { + loadInstrument(data.subspan(28 * i)); + } + + _rhythmKeyMap->allocateFromSpan(data.subspan(5320, kRhythmKeys)); } return true; @@ -802,11 +805,11 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) { assert(resMan != NULL); // Load up the patch.003 file, parse out the instruments - Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), 0); + Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), false); bool ok = false; if (res) { - ok = static_cast(_driver)->loadResource(res->data, res->size); + ok = static_cast(_driver)->loadResource(*res); } else { // Early SCI0 games have the sound bank embedded in the AdLib driver @@ -819,13 +822,13 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) { // Note: Funseeker's Guide also has another version of adl.drv, 8803 bytes. // This isn't supported, but it's not really used anywhere, as that demo // doesn't have sound anyway. - if ((size == 5684) || (size == 5720) || (size == 5727)) { - byte *buf = new byte[patchSize]; - - if (f.seek(0x45a) && (f.read(buf, patchSize) == patchSize)) - ok = static_cast(_driver)->loadResource(buf, patchSize); - - delete[] buf; + if (size == 5684 || size == 5720 || size == 5727) { + ok = f.seek(0x45a); + if (ok) { + Common::SpanOwner > patchData; + patchData->allocateFromStream(f, patchSize); + ok = static_cast(_driver)->loadResource(*patchData); + } } } } diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp index f5daab726e..b8b6a32214 100644 --- a/engines/sci/sound/drivers/amigamac.cpp +++ b/engines/sci/sound/drivers/amigamac.cpp @@ -610,7 +610,7 @@ int MidiDriver_AmigaMac::open() { return Common::kUnknownError; } - Common::MemoryReadStream stream(resource->data, resource->size); + Common::MemoryReadStream stream(resource->toStream()); if (_isSci1) { if (!loadInstrumentsSCI1(stream)) diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp index a222090fc8..8b92432cb9 100644 --- a/engines/sci/sound/drivers/cms.cpp +++ b/engines/sci/sound/drivers/cms.cpp @@ -29,6 +29,7 @@ #include "common/system.h" #include "sci/resource.h" +#include "sci/util.h" namespace Sci { @@ -72,7 +73,7 @@ private: bool _playSwitch; uint16 _masterVolume; - uint8 *_patchData; + Common::SpanOwner > _patchData; struct Channel { Channel() @@ -96,7 +97,7 @@ private: struct Voice { Voice() : channel(0xFF), note(0xFF), sustained(0xFF), ticks(0), - turnOffTicks(0), patchDataPtr(0), patchDataIndex(0), + turnOffTicks(0), patchDataPtr(), patchDataIndex(0), amplitudeTimer(0), amplitudeModifier(0), turnOff(false), velocity(0) { } @@ -106,7 +107,7 @@ private: uint8 sustained; uint16 ticks; uint16 turnOffTicks; - const uint8 *patchDataPtr; + SciSpan patchDataPtr; uint8 patchDataIndex; uint8 amplitudeTimer; uint8 amplitudeModifier; @@ -172,12 +173,11 @@ int MidiDriver_CMS::open() { return MERR_ALREADY_OPEN; assert(_resMan); - Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), 0); + Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), false); if (!res) return -1; - _patchData = new uint8[res->size]; - memcpy(_patchData, res->data, res->size); + _patchData->allocateFromSpan(*res); for (uint i = 0; i < ARRAYSIZE(_channel); ++i) _channel[i] = Channel(); @@ -218,9 +218,9 @@ int MidiDriver_CMS::open() { void MidiDriver_CMS::close() { _mixer->stopHandle(_mixerSoundHandle); - delete[] _patchData; + _patchData.clear(); delete _cms; - _cms = 0; + _cms = nullptr; } void MidiDriver_CMS::send(uint32 b) { @@ -295,7 +295,7 @@ void MidiDriver_CMS::voiceOn(int voiceNr, int note, int velocity) { voice.amplitudeTimer = 0; voice.ticks = 0; voice.turnOffTicks = 0; - voice.patchDataPtr = _patchData + READ_LE_UINT16(&_patchData[_channel[voice.channel].patch * 2]); + voice.patchDataPtr = _patchData->subspan(_patchData->getUint16LEAt(_channel[voice.channel].patch * 2)); if (velocity) velocity = _velocityTable[(velocity >> 3)]; voice.velocity = velocity; @@ -798,7 +798,7 @@ public: _driver->setTimerCallback(0, 0); _driver->close(); delete _driver; - _driver = 0; + _driver = nullptr; } bool hasRhythmChannel() const { return false; } diff --git a/engines/sci/sound/drivers/fb01.cpp b/engines/sci/sound/drivers/fb01.cpp index db9f7558e2..3f3f581ee2 100644 --- a/engines/sci/sound/drivers/fb01.cpp +++ b/engines/sci/sound/drivers/fb01.cpp @@ -24,6 +24,7 @@ #include "sci/resource.h" #include "sci/sound/drivers/mididriver.h" +#include "sci/util.h" #include "common/file.h" #include "common/system.h" @@ -71,8 +72,8 @@ private: void setVoiceParam(byte voice, byte param, byte value); void setSystemParam(byte sysChan, byte param, byte value); - void sendVoiceData(byte instrument, const byte *data); - void sendBanks(const byte *data, int size); + void sendVoiceData(byte instrument, const SciSpan &data); + void sendBanks(const SciSpan &data); void storeVoiceData(byte instrument, byte bank, byte index); void initVoices(); @@ -442,22 +443,22 @@ void MidiPlayer_Fb01::setTimerCallback(void *timer_param, Common::TimerManager:: _driver->setTimerCallback(this, midiTimerCallback); } -void MidiPlayer_Fb01::sendBanks(const byte *data, int size) { - if (size < 3072) +void MidiPlayer_Fb01::sendBanks(const SciSpan &data) { + if (data.size() < 3072) error("Failed to read FB-01 patch"); // SSCI sends bank dumps containing 48 instruments at once. We cannot do that // due to the limited maximum SysEx length. Instead we send the instruments // one by one and store them in the banks. for (int i = 0; i < 48; i++) { - sendVoiceData(0, data + i * 64); + sendVoiceData(0, data.subspan(i * 64)); storeVoiceData(0, 0, i); } // Send second bank if available - if ((size >= 6146) && (READ_BE_UINT16(data + 3072) == 0xabcd)) { + if (data.size() >= 6146 && data.getUint16BEAt(3072) == 0xabcd) { for (int i = 0; i < 48; i++) { - sendVoiceData(0, data + 3074 + i * 64); + sendVoiceData(0, data.subspan(3074 + i * 64)); storeVoiceData(0, 1, i); } } @@ -479,10 +480,10 @@ int MidiPlayer_Fb01::open(ResourceManager *resMan) { // Turn off memory protection setSystemParam(0, 0x21, 0); - Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 2), 0); + Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 2), false); if (res) { - sendBanks(res->data, res->size); + sendBanks(*res); } else { // Early SCI0 games have the sound bank embedded in the IMF driver. // Note that these games didn't actually support the FB-01 as a device, @@ -494,27 +495,23 @@ int MidiPlayer_Fb01::open(ResourceManager *resMan) { Common::File f; if (f.open("IMF.DRV")) { - int size = f.size(); - byte *buf = new byte[size]; - - f.read(buf, size); + Common::SpanOwner > buf; + buf->allocateFromStream(f); // Search for start of sound bank - int offset; - for (offset = 0; offset < size; ++offset) { - if (!strncmp((char *)buf + offset, "SIERRA ", 7)) + uint offset; + for (offset = 0; offset < buf->size() - 7; ++offset) { + if (!strncmp((const char *)buf->getUnsafeDataAt(offset, 7), "SIERRA ", 7)) break; } // Skip to voice data offset += 0x20; - if (offset >= size) + if (offset >= buf->size()) error("Failed to locate start of FB-01 sound bank"); - sendBanks(buf + offset, size - offset); - - delete[] buf; + sendBanks(buf->subspan(offset)); } else error("Failed to open IMF.DRV"); } @@ -553,7 +550,7 @@ void MidiPlayer_Fb01::setSystemParam(byte sysChan, byte param, byte value) { sysEx(_sysExBuf, 6); } -void MidiPlayer_Fb01::sendVoiceData(byte instrument, const byte *data) { +void MidiPlayer_Fb01::sendVoiceData(byte instrument, const SciSpan &data) { _sysExBuf[2] = 0x00; _sysExBuf[3] = 0x08 | instrument; _sysExBuf[4] = 0x00; diff --git a/engines/sci/sound/drivers/fmtowns.cpp b/engines/sci/sound/drivers/fmtowns.cpp index f6dbac2a67..270843c396 100644 --- a/engines/sci/sound/drivers/fmtowns.cpp +++ b/engines/sci/sound/drivers/fmtowns.cpp @@ -101,7 +101,7 @@ public: ~MidiDriver_FMTowns(); int open(); - void loadInstruments(const uint8 *data); + void loadInstruments(const SciSpan &data); bool isOpen() const { return _isOpen; } void close(); @@ -461,14 +461,18 @@ int MidiDriver_FMTowns::open() { return 0; } -void MidiDriver_FMTowns::loadInstruments(const uint8 *data) { - if (data) { - data += 6; - for (int i = 0; i < 128; i++) { - _intf->callback(5, 0, i, data); - data += 48; +void MidiDriver_FMTowns::loadInstruments(const SciSpan &data) { + enum { + fmDataSize = 48 + }; + + if (data.size()) { + SciSpan instrumentData = data.subspan(6); + for (int i = 0; i < 128; i++, instrumentData += fmDataSize) { + _intf->callback(5, 0, i, instrumentData.getUnsafeDataAt(0, fmDataSize)); } } + _intf->callback(70, 3); property(MIDI_PROP_MASTER_VOLUME, _masterVolume); } @@ -622,7 +626,7 @@ int MidiPlayer_FMTowns::open(ResourceManager *resMan) { if (_townsDriver) { result = _townsDriver->open(); if (!result && _version == SCI_VERSION_1_LATE) - _townsDriver->loadInstruments((resMan->findResource(ResourceId(kResourceTypePatch, 8), true))->data); + _townsDriver->loadInstruments(*resMan->findResource(ResourceId(kResourceTypePatch, 8), false)); } return result; } diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp index 7b2b102284..b9035b07ea 100644 --- a/engines/sci/sound/drivers/midi.cpp +++ b/engines/sci/sound/drivers/midi.cpp @@ -70,18 +70,18 @@ public: void playSwitch(bool play); private: - bool isMt32GmPatch(const byte *data, int size); - void readMt32GmPatch(const byte *data, int size); - void readMt32Patch(const byte *data, int size); + bool isMt32GmPatch(const SciSpan &data); + void readMt32GmPatch(const SciSpan &data); + void readMt32Patch(const SciSpan &data); void readMt32DrvData(); - void mapMt32ToGm(byte *data, size_t size); + void mapMt32ToGm(const SciSpan &data); uint8 lookupGmInstrument(const char *iname); uint8 lookupGmRhythmKey(const char *iname); uint8 getGmInstrument(const Mt32ToGmMap &Mt32Ins); - void sendMt32SysEx(const uint32 addr, Common::SeekableReadStream *str, int len, bool noDelay); - void sendMt32SysEx(const uint32 addr, const byte *buf, int len, bool noDelay); + void sendMt32SysEx(const uint32 addr, Common::SeekableReadStream &data, const int len, bool noDelay); + void sendMt32SysEx(const uint32 addr, const SciSpan &data, bool noDelay); void setMt32Volume(byte volume); void resetMt32(); @@ -382,8 +382,9 @@ int MidiPlayer_Midi::getVolume() { void MidiPlayer_Midi::setReverb(int8 reverb) { assert(reverb < kReverbConfigNr); - if (_hasReverb && (_reverb != reverb)) - sendMt32SysEx(0x100001, _reverbConfig[reverb], 3, true); + if (_hasReverb && _reverb != reverb) { + sendMt32SysEx(0x100001, SciSpan(_reverbConfig[reverb], 3), true); + } _reverb = reverb; } @@ -398,7 +399,9 @@ void MidiPlayer_Midi::playSwitch(bool play) { } } -bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size) { +bool MidiPlayer_Midi::isMt32GmPatch(const SciSpan &data) { + uint32 size = data.size(); + // WORKAROUND: Some Mac games (e.g. LSL5) may have an extra byte at the // end, so compensate for that here - bug #6725. if (size == 16890) @@ -419,21 +422,21 @@ bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size) { // First, check for a GM patch. The presence of MIDI data after the // initial 1153 + 2 bytes indicates a GM patch - if (READ_LE_UINT16(data + 1153) + 1155 == size) + if (data.getUint16LEAt(1153) + 1155U == size) isMt32Gm = true; // Now check for a regular MT-32 patch. Check readMt32Patch() below for // more info. // 491 = 20 + 20 + 20 + 2 + 1 + 11 + 3 * 11 + 256 + 128 byte timbresNr = data[491]; - int pos = 492 + 246 * timbresNr; + uint pos = 492 + 246 * timbresNr; // Patches 49-96 - if ((size >= (pos + 386)) && (READ_BE_UINT16(data + pos) == 0xabcd)) + if (size >= pos + 386 && data.getUint16BEAt(pos) == 0xabcd) pos += 386; // 256 + 128 + 2 // Rhythm key map + partial reserve - if ((size >= (pos + 267)) && (READ_BE_UINT16(data + pos) == 0xdcba)) + if (size >= pos + 267 && data.getUint16BEAt(pos) == 0xdcba) pos += 267; // 256 + 9 + 2 if (size == pos) @@ -445,7 +448,7 @@ bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size) { return isMt32Gm; } -void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, Common::SeekableReadStream *str, int len, bool noDelay = false) { +void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, Common::SeekableReadStream &stream, int len, bool noDelay = false) { if (len + 8 > kMaxSysExSize) { warning("SysEx message exceed maximum size; ignoring"); return; @@ -457,8 +460,7 @@ void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, Common::SeekableReadStrea _sysExBuf[5] = (addr >> 8) & 0xff; _sysExBuf[6] = addr & 0xff; - for (int i = 0; i < len; i++) - _sysExBuf[7 + i] = str->readByte(); + stream.read(_sysExBuf + 7, len); for (int i = 4; i < 7 + len; i++) chk -= _sysExBuf[i]; @@ -471,81 +473,82 @@ void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, Common::SeekableReadStrea sysEx(_sysExBuf, len + 8); } -void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, const byte *buf, int len, bool noDelay = false) { - Common::MemoryReadStream *str = new Common::MemoryReadStream(buf, len); - sendMt32SysEx(addr, str, len, noDelay); - delete str; +void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, const SciSpan &buf, bool noDelay = false) { + Common::MemoryReadStream stream(buf.toStream()); + sendMt32SysEx(addr, stream, buf.size(), noDelay); } -void MidiPlayer_Midi::readMt32Patch(const byte *data, int size) { + +void MidiPlayer_Midi::readMt32Patch(const SciSpan &data) { // MT-32 patch contents: - // - 20 bytes unkown - // - 20 bytes before-SysEx message - // - 20 bytes goodbye SysEx message - // - 2 bytes volume - // - 1 byte reverb - // - 11 bytes reverb Sysex message - // - 3 * 11 reverb data - // - 256 + 128 bytes patches 1-48 + // - 0-19 after-SysEx message + // - 20-39 before-SysEx message + // - 40-59 goodbye SysEx message + // - 60-61 volume + // - 62 reverb + // - 63-73 reverb Sysex message + // - 74-106 [3 * 11] reverb data + // - 107-490 [256 + 128] patches 1-48 // --> total: 491 bytes - // - 1 byte number of timbres (64 max) - // - 246 * timbres timbre data - // - 2 bytes flag (0xabcd) - // - 256 + 128 bytes patches 49-96 - // - 2 bytes flag (0xdcba) - // - 256 bytes rhythm key map - // - 9 bytes partial reserve + // - 491 number of timbres (64 max) + // - 492..n [246 * number of timbres] timbre data + // - n-n+1 flag (0xabcd) + // - n+2-n+385 [256 + 128] patches 49-96 + // - n+386-n+387 flag (0xdcba) + // - n+388-n+643 rhythm key map + // - n+644-n+652 partial reserve - Common::MemoryReadStream *str = new Common::MemoryReadStream(data, size); + Common::MemoryReadStream stream(data.toStream()); // Send before-SysEx text - str->seek(20); - sendMt32SysEx(0x200000, str, 20); + stream.seek(20); + sendMt32SysEx(0x200000, stream, 20); // Save goodbye message - str->read(_goodbyeMsg, 20); + assert(sizeof(_goodbyeMsg) == 20); + stream.read(_goodbyeMsg, 20); - byte volume = CLIP(str->readUint16LE(), 0, 100); + const uint8 volume = MIN(stream.readUint16LE(), 100); setMt32Volume(volume); // Reverb default only used in (roughly) SCI0/SCI01 - byte reverb = str->readByte(); + byte reverb = stream.readByte(); _hasReverb = true; // Skip reverb SysEx message - str->seek(11, SEEK_CUR); + stream.seek(11, SEEK_CUR); // Read reverb data (stored vertically - patch #3117434) for (int j = 0; j < 3; ++j) { for (int i = 0; i < kReverbConfigNr; i++) { - _reverbConfig[i][j] = str->readByte(); + _reverbConfig[i][j] = stream.readByte(); } } // Patches 1-48 - sendMt32SysEx(0x50000, str, 256); - sendMt32SysEx(0x50200, str, 128); + sendMt32SysEx(0x50000, stream, 256); + sendMt32SysEx(0x50200, stream, 128); // Timbres - byte timbresNr = str->readByte(); + const uint8 timbresNr = stream.readByte(); for (int i = 0; i < timbresNr; i++) - sendMt32SysEx(0x80000 + (i << 9), str, 246); + sendMt32SysEx(0x80000 + (i << 9), stream, 246); - uint16 flag = str->readUint16BE(); + uint16 flag = stream.readUint16BE(); - if (!str->eos() && (flag == 0xabcd)) { + if (!stream.eos() && flag == 0xabcd) { // Patches 49-96 - sendMt32SysEx(0x50300, str, 256); - sendMt32SysEx(0x50500, str, 128); - flag = str->readUint16BE(); + sendMt32SysEx(0x50300, stream, 256); + sendMt32SysEx(0x50500, stream, 128); + flag = stream.readUint16BE(); } - if (!str->eos() && (flag == 0xdcba)) { + if (!stream.eos() && flag == 0xdcba) { // Rhythm key map - sendMt32SysEx(0x30110, str, 256); + sendMt32SysEx(0x30110, stream, 256); // Partial reserve - sendMt32SysEx(0x100004, str, 9); + sendMt32SysEx(0x100004, stream, 9); } // Reverb for SCI0 @@ -553,16 +556,15 @@ void MidiPlayer_Midi::readMt32Patch(const byte *data, int size) { setReverb(reverb); // Send after-SysEx text - str->seek(0); - sendMt32SysEx(0x200000, str, 20); + stream.seek(0); + sendMt32SysEx(0x200000, stream, 20); // Send the mystery SysEx - sendMt32SysEx(0x52000a, (const byte *)"\x16\x16\x16\x16\x16\x16", 6); - - delete str; + Common::MemoryReadStream mystery((const byte *)"\x16\x16\x16\x16\x16\x16", 6); + sendMt32SysEx(0x52000a, mystery, 6); } -void MidiPlayer_Midi::readMt32GmPatch(const byte *data, int size) { +void MidiPlayer_Midi::readMt32GmPatch(const SciSpan &data) { // GM patch contents: // - 128 bytes patch map // - 128 bytes key shift @@ -573,21 +575,21 @@ void MidiPlayer_Midi::readMt32GmPatch(const byte *data, int size) { // - 512 bytes velocity map // --> total: 1153 bytes - memcpy(_patchMap, data, 128); - memcpy(_keyShift, data + 128, 128); - memcpy(_volAdjust, data + 256, 128); - memcpy(_percussionMap, data + 384, 128); + data.subspan(0, sizeof(_patchMap)).unsafeCopyDataTo(_patchMap); + data.subspan(128, sizeof(_keyShift)).unsafeCopyDataTo(_keyShift); + data.subspan(256, sizeof(_volAdjust)).unsafeCopyDataTo(_volAdjust); + data.subspan(384, sizeof(_percussionMap)).unsafeCopyDataTo(_percussionMap); _channels[MIDI_RHYTHM_CHANNEL].volAdjust = data[512]; - memcpy(_velocityMapIdx, data + 513, 128); - memcpy(_velocityMap, data + 641, 512); + data.subspan(513, sizeof(_velocityMapIdx)).unsafeCopyDataTo(_velocityMapIdx); + data.subspan(641, sizeof(_velocityMap)).unsafeCopyDataTo(_velocityMap); - uint16 midiSize = READ_LE_UINT16(data + 1153); + uint16 midiSize = data.getUint16LEAt(1153); if (midiSize > 0) { - if (size < midiSize + 1155) + if (data.size() < midiSize + 1155U) error("Failed to read MIDI data"); - const byte *midi = data + 1155; + const SciSpan midi = data.subspan(1155, midiSize); byte command = 0; uint i = 0; @@ -599,15 +601,16 @@ void MidiPlayer_Midi::readMt32GmPatch(const byte *data, int size) { switch (command & 0xf0) { case 0xf0: { - const byte *sysExEnd = (const byte *)memchr(midi + i, 0xf7, midiSize - i); + const byte *sysExStart = midi.getUnsafeDataAt(i, midiSize - i); + const byte *sysExEnd = (const byte *)memchr(sysExStart, 0xf7, midiSize - i); if (!sysExEnd) error("Failed to find end of sysEx"); - int len = sysExEnd - (midi + i); - sysEx(midi + i, len); + int len = sysExEnd - sysExStart; + sysEx(sysExStart, len); - i += len + 1; // One more for the 0x7f + i += len + 1; // One more for the 0xf7 break; } case 0x80: @@ -656,13 +659,13 @@ void MidiPlayer_Midi::readMt32DrvData() { f.seek(-2, SEEK_CUR); // Send before-SysEx text - sendMt32SysEx(0x200000, &f, 20); + sendMt32SysEx(0x200000, f, 20); if (size != 2271) { // Send after-SysEx text (SSCI sends this before every song). // There aren't any SysEx calls in old drivers, so this can // be sent right after the before-SysEx text. - sendMt32SysEx(0x200000, &f, 20); + sendMt32SysEx(0x200000, f, 20); } else { // Skip the after-SysEx text in the newer patch version, we'll send // it after the SysEx messages are sent. @@ -696,14 +699,14 @@ void MidiPlayer_Midi::readMt32DrvData() { f.skip(2235); // skip driver code // Patches 1-48 - sendMt32SysEx(0x50000, &f, 256); - sendMt32SysEx(0x50200, &f, 128); + sendMt32SysEx(0x50000, f, 256); + sendMt32SysEx(0x50200, f, 128); setReverb(reverb); // Send the after-SysEx text f.seek(0x3d); - sendMt32SysEx(0x200000, &f, 20); + sendMt32SysEx(0x200000, f, 20); } else { byte reverbSysEx[13]; // This old driver should have a full reverb SysEx @@ -775,11 +778,11 @@ uint8 MidiPlayer_Midi::getGmInstrument(const Mt32ToGmMap &Mt32Ins) { return Mt32Ins.gmInstr; } -void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { +void MidiPlayer_Midi::mapMt32ToGm(const SciSpan &data) { // FIXME: Clean this up int memtimbres, patches; uint8 group, number, keyshift, /*finetune,*/ bender_range; - uint8 *patchpointer; + SciSpan patchpointer; uint32 pos; int i; @@ -791,10 +794,10 @@ void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { for (i = 0; i < 128; i++) _percussionMap[i] = Mt32PresetRhythmKeymap[i]; - memtimbres = *(data + 0x1eb); + memtimbres = data[0x1eb]; pos = 0x1ec + memtimbres * 0xf6; - if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xabcd)) { + if (data.size() > pos && data.getUint16BEAt(pos) == 0xabcd) { patches = 96; pos += 2 + 8 * 48; } else { @@ -807,18 +810,18 @@ void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { debugC(kDebugLevelSound, "\n[MT32-to-GM] Mapping patches.."); for (i = 0; i < patches; i++) { - char name[11]; + Common::String name; if (i < 48) - patchpointer = data + 0x6b + 8 * i; + patchpointer = data.subspan(0x6b + 8 * i); else - patchpointer = data + 0x1ec + 8 * (i - 48) + memtimbres * 0xf6 + 2; + patchpointer = data.subspan(0x1ec + 8 * (i - 48) + memtimbres * 0xf6 + 2); - group = *patchpointer; - number = *(patchpointer + 1); - keyshift = *(patchpointer + 2); - //finetune = *(patchpointer + 3); - bender_range = *(patchpointer + 4); + group = patchpointer[0]; + number = patchpointer[1]; + keyshift = patchpointer[2]; + //finetune = patchpointer[3]; + bender_range = patchpointer[4]; debugCN(kDebugLevelSound, " [%03d] ", i); @@ -832,10 +835,9 @@ void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { break; case 2: if (number < memtimbres) { - strncpy(name, (const char *)data + 0x1ec + number * 0xf6, 10); - name[10] = 0; - _patchMap[i] = lookupGmInstrument(name); - debugCN(kDebugLevelSound, "%s -> ", name); + name = data.getStringAt(0x1ec + number * 0xf6, 10); + _patchMap[i] = lookupGmInstrument(name.c_str()); + debugCN(kDebugLevelSound, "%s -> ", name.c_str()); } else { _patchMap[i] = 0xff; debugCN(kDebugLevelSound, "[Invalid] -> "); @@ -865,21 +867,19 @@ void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { _pitchBendRange[i] = CLIP(bender_range, 0, 24); } - if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xdcba)) { + if (data.size() > pos && data.getUint16BEAt(pos) == 0xdcba) { debugC(kDebugLevelSound, "\n[MT32-to-GM] Mapping percussion.."); for (i = 0; i < 64; i++) { - number = *(data + pos + 4 * i + 2); + number = data[pos + 4 * i + 2]; byte ins = i + 24; debugCN(kDebugLevelSound, " [%03d] ", ins); if (number < 64) { - char name[11]; - strncpy(name, (const char *)data + 0x1ec + number * 0xf6, 10); - name[10] = 0; - debugCN(kDebugLevelSound, "%s -> ", name); - _percussionMap[ins] = lookupGmRhythmKey(name); + Common::String name = data.getStringAt(0x1ec + number * 0xf6, 10); + debugCN(kDebugLevelSound, "%s -> ", name.c_str()); + _percussionMap[ins] = lookupGmRhythmKey(name.c_str()); } else { if (number < 94) { debugCN(kDebugLevelSound, "%s -> ", Mt32RhythmTimbreMaps[number - 64].name); @@ -897,17 +897,19 @@ void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) { debugC(kDebugLevelSound, "%s", GmPercussionNames[_percussionMap[ins]]); #endif - _percussionVelocityScale[ins] = *(data + pos + 4 * i + 3) * 127 / 100; + _percussionVelocityScale[ins] = data[pos + 4 * i + 3] * 127 / 100; } } } void MidiPlayer_Midi::setMt32Volume(byte volume) { - sendMt32SysEx(0x100016, &volume, 1); + Common::MemoryReadStream s(&volume, 1); + sendMt32SysEx(0x100016, s, 1); } void MidiPlayer_Midi::resetMt32() { - sendMt32SysEx(0x7f0000, (const byte *)"\x01\x00", 2, true); + Common::MemoryReadStream s((const byte *)"\x01\x00", 2); + sendMt32SysEx(0x7f0000, s, 2, true); // This seems to require a longer delay than usual g_system->delayMillis(150); @@ -937,11 +939,11 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { _percussionVelocityScale[i] = 127; } - Resource *res = NULL; + Resource *res = nullptr; if (g_sci && g_sci->_features->useAltWinGMSound()) { - res = resMan->findResource(ResourceId(kResourceTypePatch, 4), 0); - if (!(res && isMt32GmPatch(res->data, res->size))) { + res = resMan->findResource(ResourceId(kResourceTypePatch, 4), false); + if (!res || !isMt32GmPatch(*res)) { // Don't do any mapping when a Windows alternative track is selected // and no MIDI patch is available _useMT32Track = false; @@ -953,15 +955,15 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { // MT-32 resetMt32(); - res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0); + res = resMan->findResource(ResourceId(kResourceTypePatch, 1), false); if (res) { - if (isMt32GmPatch(res->data, res->size)) { - readMt32GmPatch(res->data, res->size); + if (isMt32GmPatch(*res)) { + readMt32GmPatch(*res); // Note that _goodbyeMsg is not zero-terminated memcpy(_goodbyeMsg, " ScummVM ", 20); } else { - readMt32Patch(res->data, res->size); + readMt32Patch(*res); } } else { // Early SCI0 games have the sound bank embedded in the MT-32 driver @@ -969,22 +971,22 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { } } else { // General MIDI - res = resMan->findResource(ResourceId(kResourceTypePatch, 4), 0); + res = resMan->findResource(ResourceId(kResourceTypePatch, 4), false); - if (res && isMt32GmPatch(res->data, res->size)) { + if (res && isMt32GmPatch(*res)) { // There is a GM patch - readMt32GmPatch(res->data, res->size); + readMt32GmPatch(*res); if (g_sci && g_sci->_features->useAltWinGMSound()) { // Always use the GM track if an alternative GM Windows soundtrack is selected _useMT32Track = false; } else { // Detect the format of patch 1, so that we know what play mask to use - res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0); + res = resMan->findResource(ResourceId(kResourceTypePatch, 1), false); if (!res) _useMT32Track = false; else - _useMT32Track = !isMt32GmPatch(res->data, res->size); + _useMT32Track = !isMt32GmPatch(*res); // Check if the songs themselves have a GM track if (!_useMT32Track) { @@ -1013,17 +1015,17 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { _velocityMap[3][i] = 0x20 + (i - 1) / 2; } - res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0); + res = resMan->findResource(ResourceId(kResourceTypePatch, 1), false); if (res) { - if (!isMt32GmPatch(res->data, res->size)) { - mapMt32ToGm(res->data, res->size); + if (!isMt32GmPatch(*res)) { + mapMt32ToGm(*res); } else { if (getSciVersion() < SCI_VERSION_3) { error("MT-32 patch has wrong type"); } else { // Happens in the SCI3 interactive demo of Lighthouse - warning("TODO: Ignoring new SCI3 type of MT-32 patch for now (size = %d)", res->size); + warning("TODO: Ignoring new SCI3 type of MT-32 patch for now (size = %lu)", res->size()); } } } else { @@ -1053,7 +1055,7 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { void MidiPlayer_Midi::close() { if (_isMt32) { // Send goodbye message - sendMt32SysEx(0x200000, _goodbyeMsg, 20, true); + sendMt32SysEx(0x200000, SciSpan(_goodbyeMsg, 20), true); } _driver->close(); diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp index 21f95f17e0..20688ca351 100644 --- a/engines/sci/sound/midiparser_sci.cpp +++ b/engines/sci/sound/midiparser_sci.cpp @@ -129,7 +129,7 @@ byte MidiParser_SCI::midiGetNextChannel(long ticker) { if (_track->channels[i].time == -1) // channel ended continue; SoundResource::Channel *curChannel = &_track->channels[i]; - if (curChannel->curPos >= curChannel->size) + if (curChannel->curPos >= curChannel->data.size()) continue; next = curChannel->data[curChannel->curPos]; // when the next event should occur if (next == 0xF8) // 0xF8 means 240 ticks delay @@ -151,7 +151,7 @@ byte *MidiParser_SCI::midiMixChannels() { _track->channels[i].time = 0; _track->channels[i].prev = 0; _track->channels[i].curPos = 0; - totalSize += _track->channels[i].size; + totalSize += _track->channels[i].data.size(); } byte *outData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data @@ -228,9 +228,9 @@ byte *MidiParser_SCI::midiMixChannels() { // certain channels from that data. byte *MidiParser_SCI::midiFilterChannels(int channelMask) { SoundResource::Channel *channel = &_track->channels[0]; - byte *channelData = channel->data; - byte *channelDataEnd = channel->data + channel->size; - byte *outData = new byte[channel->size + 5]; + SciSpan::const_iterator channelData = channel->data.cbegin(); + SciSpan::const_iterator channelDataEnd = channel->data.cend(); + byte *outData = new byte[channel->data.size() + 5]; byte curChannel = 15, curByte, curDelta; byte command = 0, lastCommand = 0; int delta = 0; @@ -239,7 +239,7 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) { _mixedData = outData; - while (channelData < channelDataEnd) { + while (channelData != channelDataEnd) { curDelta = *channelData++; if (curDelta == 0xF8) { delta += 240; @@ -804,7 +804,7 @@ byte MidiParser_SCI::getSongReverb() { for (int i = 0; i < _track->channelCount; i++) { SoundResource::Channel &channel = _track->channels[i]; // Peek ahead in the control channel to get the default reverb setting - if (channel.number == 15 && channel.size >= 7) + if (channel.number == 15 && channel.data.size() >= 7) return channel.data[6]; } } diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 488bd0360c..f78f1466af 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -345,16 +345,16 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) { if (track) { // Play digital sample if (track->digitalChannelNr != -1) { - byte *channelData = track->channels[track->digitalChannelNr].data; + const SciSpan &channelData = track->channels[track->digitalChannelNr].data; delete pSnd->pStreamAud; byte flags = Audio::FLAG_UNSIGNED; // Amiga SCI1 games had signed sound data if (_soundVersion >= SCI_VERSION_1_EARLY && g_sci->getPlatform() == Common::kPlatformAmiga) flags = 0; int endPart = track->digitalSampleEnd > 0 ? (track->digitalSampleSize - track->digitalSampleEnd) : 0; - pSnd->pStreamAud = Audio::makeRawStream(channelData + track->digitalSampleStart, - track->digitalSampleSize - track->digitalSampleStart - endPart, - track->digitalSampleRate, flags, DisposeAfterUse::NO); + const uint size = track->digitalSampleSize - track->digitalSampleStart - endPart; + pSnd->pStreamAud = Audio::makeRawStream(channelData.getUnsafeDataAt(track->digitalSampleStart), + size, track->digitalSampleRate, flags, DisposeAfterUse::NO); assert(pSnd->pStreamAud); delete pSnd->pLoopStream; pSnd->pLoopStream = 0; diff --git a/engines/sci/sound/sync.cpp b/engines/sci/sound/sync.cpp index 4e75dab725..90567e8c90 100644 --- a/engines/sci/sound/sync.cpp +++ b/engines/sci/sound/sync.cpp @@ -50,14 +50,14 @@ void Sync::start(const ResourceId id, const reg_t syncObjAddr) { } void Sync::next(const reg_t syncObjAddr) { - if (_resource && (_offset < _resource->size - 1)) { + if (_resource && (_offset < _resource->size() - 1)) { int16 syncCue = -1; - int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset); + int16 syncTime = _resource->getInt16SEAt(_offset); _offset += 2; - if ((syncTime != -1) && (_offset < _resource->size - 1)) { - syncCue = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset); + if ((syncTime != -1) && (_offset < _resource->size() - 1)) { + syncCue = _resource->getInt16SEAt(_offset); _offset += 2; } -- cgit v1.2.3