diff options
75 files changed, 3166 insertions, 1212 deletions
@@ -2,6 +2,9 @@ For a more comprehensive changelog of the latest experimental code, see: https://github.com/scummvm/scummvm/commits/ 1.4.0 (????-??-??) + SCUMM: + - Implemented PC Speaker support for SCUMM v5 games. + SDL ports: - Added support for OpenGL (GSoC Task). diff --git a/audio/decoders/aac.cpp b/audio/decoders/aac.cpp index 874062a702..50325dc9f0 100644 --- a/audio/decoders/aac.cpp +++ b/audio/decoders/aac.cpp @@ -28,74 +28,34 @@ #ifdef USE_FAAD #include "common/debug.h" +#include "common/memstream.h" #include "common/stream.h" #include "common/textconsole.h" #include "common/util.h" #include "audio/audiostream.h" +#include "audio/decoders/codec.h" +#include "audio/decoders/raw.h" #include <neaacdec.h> namespace Audio { -class AACStream : public AudioStream { +class AACDecoder : public Codec { public: - AACStream(Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeStream, - Common::SeekableReadStream *extraData, + AACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData); - ~AACStream(); + ~AACDecoder(); - int readBuffer(int16 *buffer, const int numSamples); - - bool endOfData() const { return _inBufferPos >= _inBufferSize && !_remainingSamples; } - bool isStereo() const { return _channels == 2; } - int getRate() const { return _rate; } + AudioStream *decodeFrame(Common::SeekableReadStream &stream); private: NeAACDecHandle _handle; byte _channels; unsigned long _rate; - - byte *_inBuffer; - uint32 _inBufferSize; - uint32 _inBufferPos; - - int16 *_remainingSamples; - uint32 _remainingSamplesSize; - uint32 _remainingSamplesPos; - - void init(Common::SeekableReadStream *extraData); }; -AACStream::AACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, - Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { - - _remainingSamples = 0; - _inBufferPos = 0; - - init(extraData); - - // Copy all the data to a pointer so it can be passed through - // (At least MPEG-4 chunks shouldn't be large) - _inBufferSize = stream->size(); - _inBuffer = new byte[_inBufferSize]; - stream->read(_inBuffer, _inBufferSize); - - if (disposeStream == DisposeAfterUse::YES) - delete stream; - - if (disposeExtraData == DisposeAfterUse::YES) - delete extraData; -} - -AACStream::~AACStream() { - NeAACDecClose(_handle); - delete[] _inBuffer; - delete[] _remainingSamples; -} - -void AACStream::init(Common::SeekableReadStream *extraData) { +AACDecoder::AACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { // Open the library _handle = NeAACDecOpen(); @@ -117,59 +77,55 @@ void AACStream::init(Common::SeekableReadStream *extraData) { if (err < 0) error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err)); -} - -int AACStream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - assert((numSamples % _channels) == 0); + if (disposeExtraData == DisposeAfterUse::YES) + delete extraData; +} - // Dip into our remaining samples pool if it's available - if (_remainingSamples) { - samples = MIN<int>(numSamples, _remainingSamplesSize - _remainingSamplesPos); +AACDecoder::~AACDecoder() { + NeAACDecClose(_handle); +} - memcpy(buffer, _remainingSamples + _remainingSamplesPos, samples * 2); - _remainingSamplesPos += samples; +AudioStream *AACDecoder::decodeFrame(Common::SeekableReadStream &stream) { + // read everything into a buffer + uint32 inBufferPos = 0; + uint32 inBufferSize = stream.size(); + byte *inBuffer = new byte[inBufferSize]; + stream.read(inBuffer, inBufferSize); - if (_remainingSamplesPos == _remainingSamplesSize) { - delete[] _remainingSamples; - _remainingSamples = 0; - } - } + QueuingAudioStream *audioStream = makeQueuingAudioStream(_rate, _channels == 2); // Decode until we have enough samples (or there's no more left) - while (samples < numSamples && !endOfData()) { + while (inBufferPos < inBufferSize) { NeAACDecFrameInfo frameInfo; - uint16 *decodedSamples = (uint16 *)NeAACDecDecode(_handle, &frameInfo, _inBuffer + _inBufferPos, _inBufferSize - _inBufferPos); + void *decodedSamples = NeAACDecDecode(_handle, &frameInfo, inBuffer + inBufferPos, inBufferSize - inBufferPos); if (frameInfo.error != 0) error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error)); - int decodedSampleSize = frameInfo.samples; - int copySamples = (decodedSampleSize > (numSamples - samples)) ? (numSamples - samples) : decodedSampleSize; + byte *buffer = (byte *)malloc(frameInfo.samples * 2); + memcpy(buffer, decodedSamples, frameInfo.samples * 2); - memcpy(buffer + samples, decodedSamples, copySamples * 2); - samples += copySamples; + byte flags = FLAG_16BITS; - // Copy leftover samples for use in a later readBuffer() call - if (copySamples != decodedSampleSize) { - _remainingSamplesSize = decodedSampleSize - copySamples; - _remainingSamples = new int16[_remainingSamplesSize]; - _remainingSamplesPos = 0; - memcpy(_remainingSamples, decodedSamples + copySamples, _remainingSamplesSize * 2); - } + if (_channels == 2) + flags |= FLAG_STEREO; - _inBufferPos += frameInfo.bytesconsumed; +#ifdef SCUMM_LITTLE_ENDIAN + flags |= FLAG_LITTLE_ENDIAN; +#endif + + audioStream->queueBuffer(buffer, frameInfo.samples * 2, DisposeAfterUse::YES, flags); + + inBufferPos += frameInfo.bytesconsumed; } - return samples; + return audioStream; } // Factory function -AudioStream *makeAACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, - Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { - - return new AACStream(stream, disposeStream, extraData, disposeExtraData); +Codec *makeAACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { + return new AACDecoder(extraData, disposeExtraData); } } // End of namespace Audio diff --git a/audio/decoders/aac.h b/audio/decoders/aac.h index efcbcc6f42..c5085fadaa 100644 --- a/audio/decoders/aac.h +++ b/audio/decoders/aac.h @@ -43,23 +43,19 @@ namespace Common { namespace Audio { -class AudioStream; +class Codec; /** - * Create a new AudioStream from the AAC data of an MPEG-4 file in the given stream. + * Create a new Codec for decoding AAC data of an MPEG-4 file in the given stream. * * @note This should *only* be called by our QuickTime/MPEG-4 decoder since it relies * on the MPEG-4 extra data. If you want to decode a file using AAC, go use * makeQuickTimeStream() instead! - * @param stream the SeekableReadStream from which to read the AAC data - * @param disposeStream whether to delete the stream after use * @param extraData the SeekableReadStream from which to read the AAC extra data * @param disposeExtraData whether to delete the extra data stream after use - * @return a new AudioStream, or NULL, if an error occurred + * @return a new Codec, or NULL, if an error occurred */ -AudioStream *makeAACStream( - Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeStream, +Codec *makeAACDecoder( Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO); diff --git a/audio/decoders/codec.h b/audio/decoders/codec.h new file mode 100644 index 0000000000..93b6878dee --- /dev/null +++ b/audio/decoders/codec.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AUDIO_DECODERS_CODEC_H +#define AUDIO_DECODERS_CODEC_H + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; + +class Codec { +public: + Codec() {} + virtual ~Codec() {} + + virtual AudioStream *decodeFrame(Common::SeekableReadStream &data) = 0; +}; + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/qdm2.cpp b/audio/decoders/qdm2.cpp index a178c363b5..ec2911ef20 100644 --- a/audio/decoders/qdm2.cpp +++ b/audio/decoders/qdm2.cpp @@ -28,7 +28,9 @@ #ifdef AUDIO_QDM2_H #include "audio/audiostream.h" +#include "audio/decoders/codec.h" #include "audio/decoders/qdm2data.h" +#include "audio/decoders/raw.h" #include "common/array.h" #include "common/debug.h" @@ -150,19 +152,14 @@ struct RDFTContext { FFTContext fft; }; -class QDM2Stream : public AudioStream { +class QDM2Stream : public Codec { public: - QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); + QDM2Stream(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData); ~QDM2Stream(); - bool isStereo() const { return _channels == 2; } - bool endOfData() const { return _stream->pos() >= _stream->size() && _outputSamples.size() == 0 && _subPacket == 0; } - int getRate() const { return _sampleRate; } - int readBuffer(int16 *buffer, const int numSamples); + AudioStream *decodeFrame(Common::SeekableReadStream &stream); private: - Common::SeekableReadStream *_stream; - // Parameters from codec header, do not change during playback uint8 _channels; uint16 _sampleRate; @@ -204,7 +201,6 @@ private: // I/O data uint8 *_compressedData; float _outputBuffer[1024]; - Common::Array<int16> _outputSamples; // Synthesis filter int16 ff_mpa_synth_window[512]; @@ -285,7 +281,7 @@ private: void qdm2_fft_tone_synthesizer(uint8 sub_packet); void qdm2_calculate_fft(int channel); void qdm2_synthesis_filter(uint8 index); - int qdm2_decodeFrame(Common::SeekableReadStream *in); + bool qdm2_decodeFrame(Common::SeekableReadStream &in, QueuingAudioStream *audioStream); }; // Fix compilation for non C99-compliant compilers, like MSVC @@ -1711,7 +1707,7 @@ void QDM2Stream::initVlc(void) { } } -QDM2Stream::QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) { +QDM2Stream::QDM2Stream(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { uint32 tmp; int32 tmp_s; int tmp_val; @@ -1719,7 +1715,6 @@ QDM2Stream::QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadS debug(1, "QDM2Stream::QDM2Stream() Call"); - _stream = stream; _compressedData = NULL; _subPacket = 0; _superBlockStart = 0; @@ -1906,11 +1901,13 @@ QDM2Stream::QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadS initNoiseSamples(); _compressedData = new uint8[_packetSize]; + + if (disposeExtraData == DisposeAfterUse::YES) + delete extraData; } QDM2Stream::~QDM2Stream() { delete[] _compressedData; - delete _stream; } static int qdm2_get_vlc(GetBitContext *gb, VLC *vlc, int flag, int depth) { @@ -3158,30 +3155,30 @@ void QDM2Stream::qdm2_synthesis_filter(uint8 index) _outputBuffer[_channels * i + ch] += (float)(samples[_channels * sub_sampling * i + ch] >> (sizeof(int16)*8-16)); } -int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { - debug(1, "QDM2Stream::qdm2_decodeFrame in->pos(): %d in->size(): %d", in->pos(), in->size()); +bool QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream &in, QueuingAudioStream *audioStream) { + debug(1, "QDM2Stream::qdm2_decodeFrame in.pos(): %d in.size(): %d", in.pos(), in.size()); int ch, i; const int frame_size = (_sFrameSize * _channels); // If we're in any packet but the first, seek back to the first if (_subPacket == 0) - _superBlockStart = in->pos(); + _superBlockStart = in.pos(); else - in->seek(_superBlockStart); + in.seek(_superBlockStart); // select input buffer - if (in->eos() || in->pos() >= in->size()) { + if (in.eos() || in.pos() >= in.size()) { debug(1, "QDM2Stream::qdm2_decodeFrame End of Input Stream"); - return 0; + return false; } - if ((in->size() - in->pos()) < _packetSize) { - debug(1, "QDM2Stream::qdm2_decodeFrame Insufficient Packet Data in Input Stream Found: %d Need: %d", in->size() - in->pos(), _packetSize); - return 0; + if ((in.size() - in.pos()) < _packetSize) { + debug(1, "QDM2Stream::qdm2_decodeFrame Insufficient Packet Data in Input Stream Found: %d Need: %d", in.size() - in.pos(), _packetSize); + return false; } - if (!in->eos()) { - in->read(_compressedData, _packetSize); + if (!in.eos()) { + in.read(_compressedData, _packetSize); debug(1, "QDM2Stream::qdm2_decodeFrame constructed input data"); } @@ -3190,7 +3187,7 @@ int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { memset(&_outputBuffer[frame_size], 0, frame_size * sizeof(float)); debug(1, "QDM2Stream::qdm2_decodeFrame cleared outputBuffer"); - if (!in->eos()) { + if (!in.eos()) { // decode block of QDM2 compressed data debug(1, "QDM2Stream::qdm2_decodeFrame decode block of QDM2 compressed data"); if (_subPacket == 0) { @@ -3218,7 +3215,7 @@ int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { if (!_hasErrors && _subPacketListC[0].packet != NULL) { error("QDM2 : has errors, and C list is not empty"); - return 0; + return false; } } @@ -3236,6 +3233,12 @@ int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { debug(1, "QDM2Stream::qdm2_decodeFrame clip and convert output float[] to 16bit signed samples"); } + if (frame_size == 0) + return false; + + // Prepare a buffer for queuing + uint16 *outputBuffer = (uint16 *)malloc(frame_size * 2); + for (i = 0; i < frame_size; i++) { int value = (int)_outputBuffer[i]; @@ -3244,34 +3247,35 @@ int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) { else if (value < -SOFTCLIP_THRESHOLD) value = (value < -HARDCLIP_THRESHOLD) ? -32767 : -_softclipTable[-value - SOFTCLIP_THRESHOLD]; - _outputSamples.push_back(value); + outputBuffer[i] = value; } - return frame_size; -} -int QDM2Stream::readBuffer(int16 *buffer, const int numSamples) { - debug(1, "QDM2Stream::readBuffer numSamples: %d", numSamples); - int32 decodedSamples = _outputSamples.size(); - int32 i; + // Queue the translated buffer to our stream + byte flags = FLAG_16BITS; - while (decodedSamples < numSamples) { - i = qdm2_decodeFrame(_stream); - if (i == 0) - break; // Out Of Decode Frames... - decodedSamples += i; - } + if (_channels == 2) + flags |= FLAG_STEREO; + +#ifdef SCUMM_LITTLE_ENDIAN + flags |= FLAG_LITTLE_ENDIAN; +#endif + + audioStream->queueBuffer((byte *)outputBuffer, frame_size * 2, DisposeAfterUse::YES, flags); + + return true; +} - if (decodedSamples > numSamples) - decodedSamples = numSamples; +AudioStream *QDM2Stream::decodeFrame(Common::SeekableReadStream &stream) { + QueuingAudioStream *audioStream = makeQueuingAudioStream(_sampleRate, _channels == 2); - for (i = 0; i < decodedSamples; i++) - buffer[i] = _outputSamples.remove_at(0); + while (qdm2_decodeFrame(stream, audioStream)) + ; - return decodedSamples; + return audioStream; } -AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) { - return new QDM2Stream(stream, extraData); +Codec *makeQDM2Decoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { + return new QDM2Stream(extraData, disposeExtraData); } } // End of namespace Audio diff --git a/audio/decoders/qdm2.h b/audio/decoders/qdm2.h index c0ec647bfd..f0793e3c1e 100644 --- a/audio/decoders/qdm2.h +++ b/audio/decoders/qdm2.h @@ -26,22 +26,25 @@ #ifndef AUDIO_QDM2_H #define AUDIO_QDM2_H +#include "common/types.h" + namespace Common { class SeekableReadStream; } namespace Audio { -class AudioStream; +class Codec; /** - * Create a new AudioStream from the QDM2 data in the given stream. + * Create a new Codec from the QDM2 data in the given stream. * - * @param stream the SeekableReadStream from which to read the FLAC data - * @param extraData the QuickTime extra data stream - * @return a new AudioStream, or NULL, if an error occurred + * @param extraData the QuickTime extra data stream + * @param disposeExtraData the QuickTime extra data stream + * @return a new Codec, or NULL, if an error occurred */ -AudioStream *makeQDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData); +Codec *makeQDM2Decoder(Common::SeekableReadStream *extraData, + DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO); } // End of namespace Audio diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 0ad2821cd5..a39fedc1d6 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -30,6 +30,7 @@ #include "common/textconsole.h" #include "audio/audiostream.h" +#include "audio/decoders/codec.h" #include "audio/decoders/quicktime.h" #include "audio/decoders/quicktime_intern.h" @@ -86,6 +87,9 @@ void QuickTimeAudioDecoder::init() { // Make sure the bits per sample transfers to the sample size if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's')) _tracks[_audioTrackIndex]->sampleSize = (entry->_bitsPerSample / 8) * entry->_channels; + + // Initialize the codec (if necessary) + entry->initCodec(); } } } @@ -217,6 +221,9 @@ void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) { Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0]; _audStream = Audio::makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2); + // Reinitialize the codec + entry->initCodec(); + // First, we need to track down what audio sample we need Audio::Timestamp curAudioTime = where.convertToFramerate(_tracks[_audioTrackIndex]->timeScale); uint32 sample = curAudioTime.totalNumberOfFrames(); @@ -266,6 +273,11 @@ QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc(Common::QuickTimeParser: _samplesPerFrame = 0; _bytesPerFrame = 0; _bitsPerSample = 0; + _codec = 0; +} + +QuickTimeAudioDecoder::AudioSampleDesc::~AudioSampleDesc() { + delete _codec; } bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const { @@ -313,7 +325,12 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S if (!stream) return 0; - if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ')) { + if (_codec) { + // If we've loaded a codec, make sure we use first + AudioStream *audioStream = _codec->decodeFrame(*stream); + delete stream; + return audioStream; + } else if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ')) { // Fortunately, most of the audio used in Myst videos is raw... uint16 flags = 0; if (_codecTag == MKTAG('r', 'a', 'w', ' ')) @@ -330,24 +347,32 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S } else if (_codecTag == MKTAG('i', 'm', 'a', '4')) { // Riven uses this codec (as do some Myst ME videos) return makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), kADPCMApple, _sampleRate, _channels, 34); - } else if (_codecTag == MKTAG('m', 'p', '4', 'a')) { - // The 7th Guest iOS uses an MPEG-4 codec -#ifdef USE_FAAD - if (_parentTrack->objectTypeMP4 == 0x40) - return makeAACStream(stream, DisposeAfterUse::YES, _parentTrack->extraData); -#endif -#ifdef AUDIO_QDM2_H - } else if (_codecTag == MKTAG('Q', 'D', 'M', '2')) { - // Myst ME uses this codec for many videos - return makeQDM2Stream(stream, _parentTrack->extraData); -#endif } error("Unsupported audio codec"); - return NULL; } +void QuickTimeAudioDecoder::AudioSampleDesc::initCodec() { + delete _codec; _codec = 0; + + switch (_codecTag) { + case MKTAG('Q', 'D', 'M', '2'): +#ifdef AUDIO_QDM2_H + _codec = makeQDM2Decoder(_parentTrack->extraData); +#endif + break; + case MKTAG('m', 'p', '4', 'a'): +#ifdef USE_FAAD + if (_parentTrack->objectTypeMP4 == 0x40) + _codec = makeAACDecoder(_parentTrack->extraData); +#endif + break; + default: + break; + } +} + /** * A wrapper around QuickTimeAudioDecoder that implements the RewindableAudioStream API */ diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h index f288d5604b..7ce06b85fe 100644 --- a/audio/decoders/quicktime_intern.h +++ b/audio/decoders/quicktime_intern.h @@ -45,6 +45,7 @@ namespace Common { namespace Audio { class AudioStream; +class Codec; class QueuingAudioStream; class QuickTimeAudioDecoder : public Common::QuickTimeParser { @@ -68,10 +69,12 @@ protected: class AudioSampleDesc : public Common::QuickTimeParser::SampleDesc { public: AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); + ~AudioSampleDesc(); bool isAudioCodecSupported() const; uint32 getAudioChunkSampleCount(uint chunk) const; AudioStream *createAudioStream(Common::SeekableReadStream *stream) const; + void initCodec(); // TODO: Make private in the long run uint16 _bitsPerSample; @@ -79,6 +82,8 @@ protected: uint32 _sampleRate; uint32 _samplesPerFrame; uint32 _bytesPerFrame; + + Codec *_codec; }; // Common::QuickTimeParser API diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.cpp b/audio/softsynth/fmtowns_pc98/towns_midi.cpp index e415a0dda5..d69ed73ee6 100644 --- a/audio/softsynth/fmtowns_pc98/towns_midi.cpp +++ b/audio/softsynth/fmtowns_pc98/towns_midi.cpp @@ -464,9 +464,10 @@ int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef * s->currentLevel = t; s->modWheelLast = s->modWheelState; t = getEffectModLevel(t, s->modWheelState); - if (t != d->phase) + if (t != d->phase) { d->phase = t; - retFlags |= 1; + retFlags |= 1; + } } if (--s->stepCounter) diff --git a/backends/platform/gph/devices/gp2x/mmuhack/readme.txt b/backends/platform/gph/devices/gp2x/mmuhack/README index bea49d7d6d..6db7d81845 100644 --- a/backends/platform/gph/devices/gp2x/mmuhack/readme.txt +++ b/backends/platform/gph/devices/gp2x/mmuhack/README @@ -1,3 +1,10 @@ +PLEASE NOTE: + +The binary object 'mmuhack.o' is stored in the source tree as it is very awkward to +build it manually each time and would require the use of 2 toolchains to do so. + +Notes on how to rebuild from the included source can be found below. + About ----- @@ -107,4 +114,3 @@ Credits Original idea/implementation: Squidge (this whole thing is also known as squidgehack) Kernel module: NK Documentation: notaz - diff --git a/backends/platform/openpandora/build/PXML.xml b/backends/platform/openpandora/build/PXML.xml index f4d2e2a595..a87c49e2b8 100755 --- a/backends/platform/openpandora/build/PXML.xml +++ b/backends/platform/openpandora/build/PXML.xml @@ -1,34 +1,55 @@ <?xml version="1.0" encoding="UTF-8"?> <PXML xmlns="http://openpandora.org/namespaces/PXML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="PXML_schema.xsd"> - - <application id="scummvm.djwillis.0001" appdata="scummvm"> - - <title lang="en_US">ScummVM</title> - - <exec command="./runscummvm.sh"/> - <icon src="icon/scummvm.png"/> - - <previewpics> - <pic src="icon/preview-pic.png"/> - </previewpics> - - <info name="ScummVM Documentation" type="text/html" src="docs/index.html"/> - - <description lang="en_US">Point & click game interpreter.</description> - - <author name="DJWillis" website="http://www.scummvm.org/"/> - - <version major="1" minor="1" release="1" build="1"/><!--This programs version--> - <osversion major="1" minor="0" release="0" build="0"/><!--The minimum OS version required--> - - <categories> - <category name="Game"><!--category like "Games", "Graphics", "Internet" etc--> - <subcategory name="Adventure Games"/><!--subcategory, like "Board Games", "Strategy", "First Person Shooters"--> - </category> - </categories> - - <clockspeed frequency="500"/><!--Frequency in Hz--> - - </application> - + <!-- This is the package, in our case ScummVM --> + <package id="scummvm.djwillis.0001"> + <author name="DJWillis" website="http://www.scummvm.org/"/> + <!-- version type can be alpha, beta or release, set to release in branch --> + <version major="1" minor="4" release="0" build="1" type="alpha"/> + <!-- Both title and titles are needed --> + <title lang="en_US">ScummVM</title> + <titles> + <title lang="en_US">ScummVM</title> + </titles> + <descriptions> + <description lang="en_US"> + ScummVM is a program which allows you to run certain classic graphical point-and-click adventure games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! + + ScummVM supports many adventure games, including LucasArts SCUMM games (such as Monkey Island 1-3, Day of the Tentacle, Sam & Max, ...), many of Sierra's AGI and SCI games (such as King's Quest 1-6, Space Quest 1-5, ...), Discworld 1 and 2, Simon the Sorcerer 1 and 2, Beneath A Steel Sky, Lure of the Temptress, Broken Sword 1 and 2, Flight of the Amazon Queen, Gobliiins 1-3, The Legend of Kyrandia 1-3, many of Humongous Entertainment's children's SCUMM games (including Freddi Fish and Putt Putt games) and many more. + </description> + </descriptions> + <icon src="icon/scummvm.png"/> + </package> + + <!-- This is the application, the ScummVM binary --> + <application id="scummvm.djwillis.0001" appdata="scummvm"> + <exec command="./runscummvm.sh"/> + <author name="DJWillis" website="http://www.scummvm.org/"/> + <!-- version type can be alpha, beta or release, set to release in branch --> + <version major="1" minor="4" release="0" build="1" type="alpha"/> + <!-- Both title and titles are needed --> + <title lang="en_US">ScummVM</title> + <titles> + <title lang="en_US">ScummVM</title> + </titles> + <descriptions> + <description lang="en_US"> + ScummVM is a program which allows you to run certain classic graphical point-and-click adventure games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! + + ScummVM supports many adventure games, including LucasArts SCUMM games (such as Monkey Island 1-3, Day of the Tentacle, Sam & Max, ...), many of Sierra's AGI and SCI games (such as King's Quest 1-6, Space Quest 1-5, ...), Discworld 1 and 2, Simon the Sorcerer 1 and 2, Beneath A Steel Sky, Lure of the Temptress, Broken Sword 1 and 2, Flight of the Amazon Queen, Gobliiins 1-3, The Legend of Kyrandia 1-3, many of Humongous Entertainment's children's SCUMM games (including Freddi Fish and Putt Putt games) and many more. + </description> + </descriptions> + <licenses> + <license name="GPLv2" url="http://www.gnu.org/licenses/gpl-2.0.html" sourcecodeurl="http://www.scummvm.org"/> + </licenses> + <icon src="icon/scummvm.png"/> + <previewpics> + <pic src="icon/preview-pic.png"/> + </previewpics> + <info name="ScummVM Documentation" type="text/html" src="docs/index.html"/> + <categories> + <category name="Game"> + <subcategory name="AdventureGame"/> + </category> + </categories> + </application> </PXML> diff --git a/backends/platform/openpandora/build/PXML_schema.xsd b/backends/platform/openpandora/build/PXML_schema.xsd new file mode 100644 index 0000000000..335efe5002 --- /dev/null +++ b/backends/platform/openpandora/build/PXML_schema.xsd @@ -0,0 +1,341 @@ +<?xml version="1.0" encoding="utf-8"?> +<xs:schema targetNamespace="http://openpandora.org/namespaces/PXML" xmlns="http://openpandora.org/namespaces/PXML" xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified"> + + + <!-- declare some simpleTypes for later usage --> + + <!-- Specify params allows with the 'x11' entry in exec --> + <xs:simpleType name="x11Param"> + <xs:restriction base="xs:string"> + <xs:enumeration value="req" /> + <xs:enumeration value="stop" /> + <xs:enumeration value="ignore" /> + </xs:restriction> + </xs:simpleType> + + <!-- Specify the valid documentation formats in the <info> block --> + <xs:simpleType name="docType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="text/html" /> + <xs:enumeration value="text/plain" /> + </xs:restriction> + </xs:simpleType> + + <!-- Make sure that version numbers only consist of letters, numbers and + as well as - --> + <xs:simpleType name="versionNumber"> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:pattern value="[a-zA-Z0-9+-]*" /> + </xs:restriction> + </xs:simpleType> + + <!-- Specify what is valid as release type --> + <xs:simpleType name="releaseType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="alpha" /> + <xs:enumeration value="beta" /> + <xs:enumeration value="release" /> + </xs:restriction> + </xs:simpleType> + + <!-- Specify what makes an email address "valid" --> + <xs:simpleType name="emailAddress"> + <xs:restriction base="xs:string"> + <xs:pattern value="[^@]+@[^\.]+\..+"/> + </xs:restriction> + </xs:simpleType> + + <!-- some restrictions regarding file names that are eg not allowed/possible when using sd cards formated as fat32 --> + <xs:simpleType name="dumbPath"> + <xs:restriction base="xs:normalizedString"> + <xs:pattern value="[^?>:]+" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="dumbFolderName"> + <xs:restriction base="xs:normalizedString"> + <xs:pattern value="[^?>:/]+" /> + </xs:restriction> + </xs:simpleType> + + <!-- Specify lang codes --> + <xs:simpleType name="isoLangcode"> + <xs:restriction base="xs:string"> + <xs:minLength value="2"/> + <xs:pattern value="[a-zA-Z]{2,3}(_[a-zA-Z0-9]{2,3})*" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="isoLangcode_en_US"> + <xs:restriction base="xs:string"> + <xs:enumeration value="en_US" /> + </xs:restriction> + </xs:simpleType> + + <!-- Definition of all allowed categories following the FDO specs --> + <xs:simpleType name="fdoCategory"> + <xs:restriction base="xs:string"> + <xs:pattern value="AudioVideo|Audio|Video|Development|Education|Game|Graphics|Network|Office|Settings|System|Utility"/> + </xs:restriction> + </xs:simpleType> + <!-- Definition of all allowed subcategories following the FDO specs (should be based upon the given main categories, but would significantly increase complexity of the schema) --> + <xs:simpleType name="fdoSubCategory"> + <xs:restriction base="xs:string"> + <xs:pattern value="Building|Debugger|IDE|GUIDesigner|Profiling|RevisionControl|Translation|Calendar|ContactManagement|Database|Dictionary|Chart|Email|Finance|FlowChart|PDA|ProjectManagement|Presentation|Spreadsheet|WordProcessor|2DGraphics|VectorGraphics|RasterGraphics|3DGraphics|Scanning|OCR|Photography|Publishing|Viewer|TextTools|DesktopSettings|HardwareSettings|Printing|PackageManager|Dialup|InstantMessaging|Chat|IRCClient|FileTransfer|HamRadio|News|P2P|RemoteAccess|Telephony|TelephonyTools|VideoConference|WebBrowser|WebDevelopment|Midi|Mixer|Sequencer|Tuner|TV|AudioVideoEditing|Player|Recorder|DiscBurning|ActionGame|AdventureGame|ArcadeGame|BoardGame|BlocksGame|CardGame|KidsGame|LogicGame|RolePlaying|Simulation|SportsGame|StrategyGame|Art|Construction|Music|Languages|Science|ArtificialIntelligence|Astronomy|Biology|Chemistry|ComputerScience|DataVisualization|Economy|Electricity|Geography|Geology|Geoscience|History|ImageProcessing|Literature|Math|NumericalAnalysis|MedicalSoftware|Physics|Robotics|Sports|ParallelComputing|Amusement|Archiving|Compression|Electronics|Emulator|Engineering|FileTools|FileManager|TerminalEmulator|Filesystem|Monitor|Security|Accessibility|Calculator|Clock|TextEditor|Documentation|Core|KDE|GNOME|GTK|Qt|Motif|Java|ConsoleOnly"/> + </xs:restriction> + </xs:simpleType> + + <!-- Create some way to enforce entries to be nonempty --> + <xs:simpleType name="nonempty_token"> + <xs:restriction base="xs:token"> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="nonempty_string"> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="nonempty_normalizedString"> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + + + + <!-- declare some complexTypes for later usage --> + + <!-- type used for file associations --> + <xs:complexType name="association_data"> + <xs:attribute name="name" use="required" type="nonempty_normalizedString" /> + <xs:attribute name="filetype" use="required" type="nonempty_token" /> + <xs:attribute name="exec" use="required" type="nonempty_token" /> + </xs:complexType> + + <!-- type used for author info --> + <xs:complexType name="author_data"> + <xs:attribute name="name" use="required" type="nonempty_normalizedString" /> + <xs:attribute name="website" use="optional" type="xs:anyURI" /> + <xs:attribute name="email" use="optional" type="emailAddress" /> + </xs:complexType> + + <!-- type used for version informations (full entry as well as os version) --> + <xs:complexType name="app_version_info"> + <xs:attribute name="major" use="required" type="versionNumber" /> + <xs:attribute name="minor" use="required" type="versionNumber" /> + <xs:attribute name="release" use="required" type="versionNumber" /> + <xs:attribute name="build" use="required" type="versionNumber" /> + <xs:attribute name="type" use="optional" type="releaseType" /> + </xs:complexType> + <xs:complexType name="os_version_info"> + <xs:attribute name="major" use="required" type="versionNumber" /> + <xs:attribute name="minor" use="required" type="versionNumber" /> + <xs:attribute name="release" use="required" type="versionNumber" /> + <xs:attribute name="build" use="required" type="versionNumber" /> + </xs:complexType> + + <!-- type used for exec entries --> + <xs:complexType name="exec_params"> + <xs:attribute name="command" use="required" type="nonempty_token" /> + <xs:attribute name="arguments" use="optional" type="nonempty_token" /> + <xs:attribute name="background" use="optional" type="xs:boolean" /> + <xs:attribute name="startdir" use="optional" type="dumbPath" /> + <xs:attribute name="standalone" use="optional" type="xs:boolean" /> + <xs:attribute name="x11" use="optional" type="x11Param" /> + </xs:complexType> + + <!-- type used for tiles or descriptions, once in 'normal' version, once enforcing usage of en_US --> + <xs:complexType name="title_or_description"> + <xs:simpleContent> + <xs:extension base="nonempty_string"> + <xs:attribute name="lang" use="required" type="isoLangcode" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="title_or_description_enUS"> + <xs:simpleContent> + <xs:extension base="nonempty_string"> + <xs:attribute name="lang" use="required" type="isoLangcode_en_US" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <!-- type used for referencing images --> + <xs:complexType name="image_entry"> + <xs:attribute name="src" use="required" type="dumbPath" /> + </xs:complexType> + + <!-- type for referencing manuals/readme docs --> + <xs:complexType name="information_entry"> + <xs:attribute name="name" use="required" type="nonempty_normalizedString" /> + <xs:attribute name="type" use="required" type="docType" /> + <xs:attribute name="src" use="required" type="dumbPath" /> + </xs:complexType> + + <!-- type used for the license information --> + <xs:complexType name="license_info"> + <xs:attribute name="name" use="required" type="nonempty_normalizedString" /> + <xs:attribute name="url" use="optional" type="xs:anyURI" /> + <xs:attribute name="sourcecodeurl" use="optional" type="xs:anyURI" /> + </xs:complexType> + + + + <!-- Combine the symple and complex types into the "real" PXML specification --> + + <xs:element name="PXML"> + <xs:complexType> + <xs:sequence> + <!-- specify the <package> tag with info about the complete package, information providable: + author + version + title(s) + description(s) + icon + --> + <xs:element name="package" minOccurs="1" maxOccurs="1"> + <xs:complexType> + <xs:all> + <!--Author info--> + <xs:element name="author" type="author_data" minOccurs="1" /> + <!--App version info--> + <xs:element name="version" type="app_version_info" minOccurs="1" /> + <!--Title--> + <xs:element name="titles" minOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="title" type="title_or_description_enUS" minOccurs="1" maxOccurs="1" /> + <xs:element name="title" type="title_or_description" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Description--> + <xs:element name="descriptions" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="title" type="title_or_description_enUS" minOccurs="0" maxOccurs="1" /> + <xs:element name="description" type="title_or_description" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Icon--> + <xs:element name="icon" type="image_entry" minOccurs="0" /> + </xs:all> + <!--Package ID--> + <xs:attribute name="id" use="required" type="dumbFolderName" /> + </xs:complexType> + </xs:element> + <!-- specify the <application> tag with info about a single program + executable call + author + version (of the application) + osversion (min OS version supported) + title(s) (allowing compatibility to <HF6, too!) + description(s) (allowing compatibility to <HF6, too!) + icon + license + preview pictures + info/manual/readme entry + categories + associations to file types + clockspeed + --> + <xs:element name="application" minOccurs="1" maxOccurs="unbounded"> + <xs:complexType> + <xs:all> + <!--Execution params --> + <xs:element name="exec" type="exec_params" minOccurs="1" /> + <!--Author info--> + <xs:element name="author" type="author_data" minOccurs="1" /> + <!--App version info--> + <xs:element name="version" type="app_version_info" minOccurs="1" /> + <!--OS Version info--> + <xs:element name="osversion" type="os_version_info" minOccurs="0" /> + <!--Title--> + <!-- via <titles> element, used for HF6+ --> + <xs:element name="titles" minOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="title" type="title_or_description_enUS" minOccurs="1" maxOccurs="1" /> + <xs:element name="title" type="title_or_description" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Title--> + <!-- via <title> element, only one for en_US allowed, meant for backwards compatibility with libpnd from <HF6 --> + <xs:element name="title" type="title_or_description_enUS" minOccurs="0" /> + <!--Description--> + <!-- via <descriptions> element, used for HF6+ --> + <xs:element name="descriptions" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="description" type="title_or_description_enUS" minOccurs="1" maxOccurs="1" /> + <xs:element name="description" type="title_or_description" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Description--> + <!-- via <description> element, only one for en_US allowed, meant for backwards compatibility with libpnd from <HF6 --> + <xs:element name="description" type="title_or_description_enUS" minOccurs="0" /> + <!--Icon--> + <xs:element name="icon" type="image_entry" minOccurs="0" /> + <!--License--> + <xs:element name="licenses" minOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="license" type="license_info" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Preview pics--> + <xs:element name="previewpics" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="pic" type="image_entry" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Info (aka manual or readme entry)--> + <xs:element name="info" type="information_entry" minOccurs="0" /> + <!--Categories--> + <xs:element name="categories" minOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="category" minOccurs="1" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="subcategory" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:attribute name="name" type="fdoSubCategory" /> + </xs:complexType> + </xs:element> + </xs:sequence> + <xs:attribute name="name" use="required" type="fdoCategory" /> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Associations--> + <xs:element name="associations" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="association" type="association_data" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <!--Clockspeed--> + <xs:element name="clockspeed" minOccurs="0"> + <xs:complexType> + <xs:attribute name="frequency" use="required" type="xs:positiveInteger" /> + </xs:complexType> + </xs:element> + </xs:all> + <!--AppID--> + <xs:attribute name="id" use="required" type="dumbFolderName" /> + <xs:attribute name="appdata" use="optional" type="dumbFolderName" /> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema>
\ No newline at end of file diff --git a/backends/platform/openpandora/build/pnd_make.sh b/backends/platform/openpandora/build/pnd_make.sh index b19de87bb4..0c03e8154d 100755 --- a/backends/platform/openpandora/build/pnd_make.sh +++ b/backends/platform/openpandora/build/pnd_make.sh @@ -1,65 +1,321 @@ -#!/bin/sh +#!/bin/bash +# +# pnd_make.sh +# +# This script is meant to ease generation of a pnd file. Please consult the output +# when running --help for a list of available parameters and an explaination of +# those. +# +# Required tools when running the script: +# bash +# echo, cat, mv, rm +# mkisofs or mksquashfs (the latter when using the -c param!) +# xmllint (optional, only for validation of the PXML against the schema) -######adjust path of genpxml.sh if you want to use that "feture"##### -TEMP=`getopt -o p:d:x:i:c -- "$@"` +PXML_schema=$(dirname ${0})/PXML_schema.xsd +GENPXML_PATH=$(dirname ${0})/genpxml.sh -if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi +# useful functions ... +black='\E[30m' +red='\E[31m' +green='\E[32m' +yellow='\E[33m' +blue='\E[34m' +magenta='\E[35m' +cyan='\E[36m' +white='\E[37m' -eval set -- "$TEMP" -while true ; do - case "$1" in - -p) echo "PNDNAME set to $2" ;PNDNAME=$2;shift 2;; - -d) echo "FOLDER set to $2" ;FOLDER=$2;shift 2 ;; - -x) echo "PXML set to $2" ;PXML=$2;shift 2 ;; - -i) echo "ICON set to $2" ;ICON=$2;shift 2 ;; - -c) echo "-c set, will create compressed squasfs image instead of iso $2" ;SQUASH=1;shift 1 ;; - --) shift ; break ;; - *) echo "Error while parsing arguments! $2" ; exit 1 ;; - esac +check_for_tool() +{ + which $1 &> /dev/null + if [ "$?" -ne "0" ]; + then + cecho "ERROR: Could not find the program '$1'. Please make sure +that it is available in your PATH since it is required to complete your request." $red + exit 1 + fi +} + +cecho () # Color-echo. Argument $1 = message, Argument $2 = color +{ + local default_msg="No message passed." # Doesn't really need to be a local variable. + message=${1:-$default_msg} # Defaults to default message. + color=${2:-$black} # Defaults to black, if not specified. + echo -e "$color$message" + tput sgr0 # Reset to normal. + return +} + + +print_help() +{ + cat << EOSTREAM +pnd_make.sh - A script to package "something" into a PND. + +Usage: + $(basename ${0}) {--directory|-d} <folder> {--pndname|-p} <file> [{--compress-squashfs|-c}] + [{--genpxml} <file>] [{--icon|-i} <file>] [{--pxml|-x} <file>] + [{--schema|-s} <file>] [{--help|-h}] + + +Switches: + --compress-squashfs / -c Define whether or not the pnd should be compressed using + squashfs. If this parameter is selected, a compressed pnd + will be created. + + --directory / -d Sets the folder that is to be used for the resulting pnd + to <folder>. This option is mandatory for the script to + function correctly. + + --genpxml Sets the script used for generating a PXML file (if none + is available already) to <file>. Please make sure to either + provide a full path or prefix a script in the current folder + with './' so that the script can actually be executed. If + this variable is not specified, $GENPXML_PATH + will be used. + + --help / -h Displays this help text. + + --icon / -i Sets the icon that will be appended in the pnd to <file>. + + --pndname / -p Sets the output filename of the resulting pnd to <file>. + This option is mandatory for the script to function + correctly. + + --pxml / -x Sets the PXML file that is to be used to <file>. If you + neither provide a PXML file or set this entry to 'guess', + an existing 'PXML.xml' in your selected '--directory' + will be used, or the script $GENPXML_PATH + will be called to try to generate a basic PXML file for you. + + --schema / -s Sets the schema file, that is to be used for validation, + to <file. If this is not defined, the script will try to + use the file '$PXML_schema'. If this fails, + a warning is issued. + +If you select the option to create a compressed squashfs, a version >=4.0 of squashfs +is required to be available in your PATH. +EOSTREAM +} + + +# Parse command line parameters +while [ "${1}" != "" ]; do + if [ "${1}" = "--compress-squashfs" ] || [ "${1}" = "-c" ]; + then + SQUASH=1 + shift 1 + elif [ "${1}" = "--directory" ] || [ "${1}" = "-d" ]; + then + FOLDER=$2 + shift 2 + elif [ "${1}" = "--genpxml" ]; + then + GENPXML_PATH=$2 + shift 2 + elif [ "${1}" = "--help" ] || [ "${1}" = "-h" ]; + then + print_help + exit 0 + elif [ "${1}" = "--icon" ] || [ "${1}" = "-i" ]; + then + ICON=$2 + shift 2 + elif [ "${1}" = "--pndname" ] || [ "${1}" = "-p" ]; + then + PNDNAME=$2 + shift 2 + elif [ "${1}" = "--pxml" ] || [ "${1}" = "-x" ]; + then + PXML=$2 + shift 2 + elif [ "${1}" = "--schema" ] || [ "${1}" = "-f" ] + then + PXML_schema=$2 + shift 2 + else + cecho "ERROR: '$1' is not a known argument. Printing --help and aborting." $red + print_help + exit 1 + fi done -rnd=$RANDOM; # random number for genpxml and index$rnd.xml -#generate pxml if guess or empty -if [ ! $PXML ] || [ $PXML = "guess" ] && [ $PNDNAME ] && [ $FOLDER ]; then - PXMLtxt=$(/home/user/libpnd/pandora-libraries/testdata/scripts/genpxml.sh $FOLDER $ICON) - PXML=$FOLDER/PXML.xml - echo "$PXMLtxt" > $FOLDER/PXML.xml +# Generate a PXML if the param is set to Guess or it is empty. +if [ ! $PXML ] || [ $PXML = "guess" ] && [ $PNDNAME ] && [ $FOLDER ]; +then + if [ -f $FOLDER/PXML.xml ]; # use the already existing PXML.xml file if there is one... + then + PXML=$FOLDER/PXML.xml + PXML_ALREADY_EXISTING="true" + else + if [ -f $GENPXML_PATH ]; + then + $GENPXML_PATH --src $FOLDER --dest $FOLDER --author $USER + if [ -f $FOLDER/PXML.xml ]; + then + PXML_GENERATED="true" + else + cecho "ERROR: Generating a PXML file using '$GENPXML_PATH' failed. +Please generate a PXML file manually." $red + exit 1 + fi + else + cecho "ERROR: Could not find '$GENPXML_PATH' for generating a PXML file." $red + exit 1 + fi + fi fi -#check arguments -if [ ! $PNDNAME ] || [ ! $FOLDER ] || [ ! $PXML ]; then - echo " Usage: pnd_make.sh -p your.pnd -d folder/containing/your/app/ -x - your.pxml (or \"guess\" to try to generate it from the folder) -i icon.png" + +# Probe if required variables were set +echo -e +cecho "Checking if all required variables were set." $green +if [ ! $PNDNAME ] || [ ! $FOLDER ] || [ ! $PXML ]; +then + echo -e + cecho "ERROR: Not all required options were set! Please see the --help information below." $red + echo -e + print_help exit 1 +else + echo "PNDNAME set to '$PNDNAME'." +fi +# Check if the selected folder actually exists +if [ ! -d $FOLDER ]; +then + echo -e + cecho "ERROR: '$FOLDER' doesn't exist or is not a folder." $red + exit 1 +else + echo "FOLDER set to '$FOLDER'." +fi +# Check if the selected PXML file actually exists +if [ ! -f $PXML ]; +then + echo -e + cecho "ERROR: '$PXML' doesn't exist or is not a file." $red + exit 1 +else + if [ $PXML_ALREADY_EXISTING ]; + then + echo "You have not explicitly specified a PXML to use, but an existing file was +found. Will be using this one." + elif [ $PXML_GENERATED ]; + then + echo "A PXML file was generated for you using '$GENPXML_PATH'. This file will +not be removed at the end of this script because you might want to review it, adjust +single entries and rerun the script to generate a pnd with a PXML file with all the +information you want to have listed." + fi + echo "PXML set to '$PXML'." fi -if [ ! -d $FOLDER ]; then echo "$FOLDER doesnt exist"; exit 1; fi #check if folder actually exists -if [ ! -f $PXML ]; then echo "$PXML doesnt exist"; exit 1; fi #check if pxml actually exists -#make iso from folder -if [ ! $SQUASH ]; then - mkisofs -o $PNDNAME.iso -R $FOLDER +# Print the other variables: +if [ $ICON ]; +then + if [ ! -f $ICON ] + then + cecho "WARNING: '$ICON' doesn't exist, will not append the selected icon to the pnd." $red + else + echo "ICON set to '$ICON'." + USE_ICON="true" + fi +fi +if [ $SQUASH ]; +then + echo "Will use a squashfs for '$PNDNAME'." +fi + + +# Validate the PXML file (if xmllint is available) +# Errors and problems in this section will be shown but are not fatal. +echo -e +cecho "Trying to validate '$PXML' now. Will be using '$PXML_schema' to do so." $green +which xmllint &> /dev/null +if [ "$?" -ne "0" ]; +then + VALIDATED=false + cecho "WARNING: Could not find 'xmllint'. Validity check of '$PXML' is not possible!" $red else - if [ $(mksquashfs -version | awk '{if ($3 >= 4) print 1}') = 1 ]; then - echo "your squashfs version is older then version 4, please upgrade to 4.0 or later" + if [ ! -f "$PXML_schema" ]; + then + VALIDATED=false + cecho "WARNING: Could not find '$PXML_schema'. If you want to validate your +PXML file please make sure to provide a schema using the --schema option." $red + else + xmllint --noout --schema $PXML_schema $PXML + if [ "$?" -ne "0" ]; then VALIDATED=false; else VALIDATED=true; fi + fi +fi +# Print some message at the end about the validation in case the user missed the output above +if [ $VALIDATED = "false" ] +then + cecho "WARNING: Could not successfully validate '$PXML'. Please check the output +above. This does not mean that your pnd will be broken. Either you are not following the strict +syntax required for validation or you don't have all files/programs required for validating." $red +else + cecho "Your file '$PXML' was validated successfully. The resulting pnd should +work nicely with libpnd." $green +fi + + +# Make iso from folder +echo -e +cecho "Creating an iso file based on '$FOLDER'." $green +if [ $SQUASH ]; +then + check_for_tool mksquashfs + if [ $(mksquashfs -version | awk 'BEGIN{r=0} $3>=4{r=1} END{print r}') -eq 0 ]; + then + cecho "ERROR: Your squashfs version is older then version 4, please upgrade to 4.0 or later" $red exit 1 fi - mksquashfs -no-recovery -nopad $FOLDER $PNDNAME.iso + mksquashfs $FOLDER $PNDNAME.iso -nopad -no-recovery +else + check_for_tool mkisofs + mkisofs -o $PNDNAME.iso -R $FOLDER +fi + +# Check that the iso file was actually created before continuing +if [ ! -f $PNDNAME.iso ]; +then + cecho "ERROR: The temporary file '$PNDNAME.iso' could not be created. +Please check the output above for any errors and retry after fixing them. Aborting." $red + exit 1 fi -#append pxml to iso -cat $PNDNAME.iso $PXML > $PNDNAME + + +# Append pxml to iso +echo -e +cecho "Appending '$PXML' to the created iso file." $green +cat $PNDNAME.iso $PXML > $PNDNAME rm $PNDNAME.iso #cleanup -#append icon if specified -if [ $ICON ]; then # check if we want to add an icon - if [ ! -f $ICON ]; then #does the icon actually exist? - echo "$ICON doesnt exist" - else # yes + +# Append icon if specified and available +if [ $USE_ICON ]; +then + echo -e + cecho "Appending the icon '$ICON' to the pnd." $green mv $PNDNAME $PNDNAME.tmp cat $PNDNAME.tmp $ICON > $PNDNAME # append icon rm $PNDNAME.tmp #cleanup - fi fi -if [ $PXML = "guess" ];then rm $FOLDER/PXML.xml; fi #cleanup + +# Final message +echo -e +if [ -f $PNDNAME ]; +then + cecho "Successfully finished creating the pnd '$PNDNAME'." $green +else + cecho "There seems to have been a problem and '$PNDNAME' was not created. Please check +the output above for any error messages. A possible cause for this is that there was +not enough space available." $red + exit 1 +fi + + +#if [ $PXML = "guess" ];then rm $FOLDER/PXML.xml; fi #cleanup diff --git a/backends/platform/openpandora/build/runscummvm.sh b/backends/platform/openpandora/build/runscummvm.sh index 48d24a2b81..c641235219 100755 --- a/backends/platform/openpandora/build/runscummvm.sh +++ b/backends/platform/openpandora/build/runscummvm.sh @@ -11,4 +11,5 @@ mkdir saves mkdir runtime cd runtime -../bin/scummvm --fullscreen --gfx-mode=2x --config=../scummvm.config +../bin/scummvm --fullscreen --gfx-mode=2x --config=../scummvm.config --themepath=../data + diff --git a/backends/platform/openpandora/op-bundle.mk b/backends/platform/openpandora/op-bundle.mk index 163f4711ce..089430f43c 100755 --- a/backends/platform/openpandora/op-bundle.mk +++ b/backends/platform/openpandora/op-bundle.mk @@ -75,11 +75,10 @@ endif $(CP) $(libloc)/../arm-angstrom-linux-gnueabi/usr/lib/libFLAC.so.8.2.0 $(bundle_name)/scummvm/lib/libFLAC.so.8 - $(srcdir)/backends/platform/openpandora/build/pnd_make.sh -p $(bundle_name).pnd -d $(bundle_name)/scummvm -x $(bundle_name)/scummvm/data/PXML.xml -i $(bundle_name)/scummvm/icon/scummvm.png + $(srcdir)/backends/platform/openpandora/build/pnd_make.sh -p $(bundle_name).pnd -c -d $(bundle_name)/scummvm -x $(bundle_name)/scummvm/data/PXML.xml -i $(bundle_name)/scummvm/icon/scummvm.png $(CP) $(srcdir)/backends/platform/openpandora/build/README-PND.txt $(bundle_name) tar -cvjf $(bundle_name)-pnd.tar.bz2 $(bundle_name).pnd $(bundle_name)/README-PND.txt rm -R ./$(bundle_name) -# rm $(bundle_name).pnd .PHONY: op-bundle op-pnd diff --git a/backends/platform/sdl/ps3/ps3.cpp b/backends/platform/sdl/ps3/ps3.cpp index 16722ccdb7..33586ce693 100644 --- a/backends/platform/sdl/ps3/ps3.cpp +++ b/backends/platform/sdl/ps3/ps3.cpp @@ -21,7 +21,7 @@ */ #define FORBIDDEN_SYMBOL_EXCEPTION_mkdir -#define FORBIDDEN_SYMBOL_EXCEPTION_time_h //On IRIX, sys/stat.h includes sys/time.h +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h // sys/stat.h includes sys/time.h #define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h #include "common/scummsys.h" diff --git a/common/system.h b/common/system.h index 15fbe386b1..9b833c5b1a 100644 --- a/common/system.h +++ b/common/system.h @@ -154,9 +154,9 @@ protected: #if defined(USE_TASKBAR) /** - * No default value is provided for _savefileManager by OSystem. + * No default value is provided for _taskbarManager by OSystem. * - * @note _savefileManager is deleted by the OSystem destructor. + * @note _taskbarManager is deleted by the OSystem destructor. */ Common::TaskbarManager *_taskbarManager; #endif @@ -121,7 +121,6 @@ add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes add_engine tsage "Ringworld: Revenge Of The Patriarch" no add_engine tucker "Bud Tucker in Double Trouble" yes - # # Default settings # @@ -2065,6 +2064,7 @@ if test -n "$_host"; then gamecube) _backend="wii" _build_scalers=no + _vkeybd=yes _mt32emu=no _port_mk="backends/platform/wii/wii.mk" add_line_to_config_mk 'GAMECUBE = 1' @@ -2279,6 +2279,7 @@ if test -n "$_host"; then wii) _backend="wii" _build_scalers=no + _vkeybd=yes _port_mk="backends/platform/wii/wii.mk" add_line_to_config_mk 'GAMECUBE = 0' add_line_to_config_h '#define AUDIO_REVERSE_STEREO' diff --git a/devtools/create_project/msvc10/create_project.vcxproj b/devtools/create_project/msvc10/create_project.vcxproj index 3d7f8fdd3d..40c515f26b 100644 --- a/devtools/create_project/msvc10/create_project.vcxproj +++ b/devtools/create_project/msvc10/create_project.vcxproj @@ -59,11 +59,11 @@ </Link> <PostBuildEvent> <Command>@echo off -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc10\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc9\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc8\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\codeblocks\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\iphone\</Command> +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc10\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc9\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc8\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\codeblocks\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\iphone\"</Command> </PostBuildEvent> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -84,10 +84,12 @@ xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\iphone\</Command> <TargetMachine>MachineX86</TargetMachine> </Link> <PostBuildEvent> - <Command>xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc10\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc9\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\msvc8\ -xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\codeblocks\</Command> + <Command>@echo off +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc10\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc9\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\msvc8\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\codeblocks\" +xcopy /Y "$(TargetPath)" "$(SolutionDir)\..\..\..\dists\iphone\"</Command> </PostBuildEvent> <PreBuildEvent> <Command> @@ -120,4 +122,4 @@ xcopy /Y $(TargetPath) $(SolutionDir)\..\..\..\dists\codeblocks\</Command> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/dists/redhat/scummvm.spec b/dists/redhat/scummvm.spec index 777e0afc8a..cd51f45c0a 100644 --- a/dists/redhat/scummvm.spec +++ b/dists/redhat/scummvm.spec @@ -68,6 +68,8 @@ install -m644 -D dists/engine-data/queen.tbl %{buildroot}%{_datadir}/scummvm/que install -m644 -D dists/engine-data/sky.cpt %{buildroot}%{_datadir}/scummvm/sky.cpt install -m644 -D dists/engine-data/drascula.dat %{buildroot}%{_datadir}/scummvm/drascula.dat install -m644 -D dists/engine-data/teenagent.dat %{buildroot}%{_datadir}/scummvm/teenagent.dat +install -m644 -D dists/engine-data/hugo.dat %{buildroot}%{_datadir}/scummvm/hugo.dat +install -m644 -D dists/engine-data/toon.dat %{buildroot}%{_datadir}/scummvm/toon.dat desktop-file-install --vendor scummvm --dir=%{buildroot}/%{_datadir}/applications dists/scummvm.desktop %clean @@ -105,6 +107,8 @@ fi %{_datadir}/scummvm/lure.dat %{_datadir}/scummvm/drascula.dat %{_datadir}/scummvm/teenagent.dat +%{_datadir}/scummvm/hugo.dat +%{_datadir}/scummvm/toon.dat %{_mandir}/man6/scummvm.6* #------------------------------------------------------------------------------ diff --git a/dists/redhat/scummvm.spec.in b/dists/redhat/scummvm.spec.in index 13ce600d02..838a05411a 100644 --- a/dists/redhat/scummvm.spec.in +++ b/dists/redhat/scummvm.spec.in @@ -68,6 +68,8 @@ install -m644 -D dists/engine-data/queen.tbl %{buildroot}%{_datadir}/scummvm/que install -m644 -D dists/engine-data/sky.cpt %{buildroot}%{_datadir}/scummvm/sky.cpt install -m644 -D dists/engine-data/drascula.dat %{buildroot}%{_datadir}/scummvm/drascula.dat install -m644 -D dists/engine-data/teenagent.dat %{buildroot}%{_datadir}/scummvm/teenagent.dat +install -m644 -D dists/engine-data/hugo.dat %{buildroot}%{_datadir}/scummvm/hugo.dat +install -m644 -D dists/engine-data/toon.dat %{buildroot}%{_datadir}/scummvm/toon.dat desktop-file-install --vendor scummvm --dir=%{buildroot}/%{_datadir}/applications dists/scummvm.desktop %clean @@ -105,6 +107,8 @@ fi %{_datadir}/scummvm/lure.dat %{_datadir}/scummvm/drascula.dat %{_datadir}/scummvm/teenagent.dat +%{_datadir}/scummvm/hugo.dat +%{_datadir}/scummvm/toon.dat %{_mandir}/man6/scummvm.6* #------------------------------------------------------------------------------ diff --git a/engines/dreamweb/dreamweb.cpp b/engines/dreamweb/dreamweb.cpp index eebadfddae..85cb2c1983 100644 --- a/engines/dreamweb/dreamweb.cpp +++ b/engines/dreamweb/dreamweb.cpp @@ -591,7 +591,7 @@ uint8 DreamWebEngine::modifyChar(uint8 c) const { return 'Z' + 4; case 154: return 'Z' + 6; - case 255: + case 225: return 'A' - 1; case 153: return 'Z' + 5; diff --git a/engines/lastexpress/data/snd.cpp b/engines/lastexpress/data/snd.cpp index 28d20df9bd..6845be8808 100644 --- a/engines/lastexpress/data/snd.cpp +++ b/engines/lastexpress/data/snd.cpp @@ -38,7 +38,7 @@ namespace LastExpress { #pragma region Sound filters tables -static const int filterData[1424] = { +static const int stepTable[1424] = { 0, 0, 0, 0, 128, 256, 384, 512, 0, 0, 0, 0, 128, 256, 384, 512, 0, 0, 0, 0, 192, 320, 448, 576, 0, 0, 0, 0, 192, 320, 448, 576, 64, 64, 64, 64, 256, 384, 512, 640, @@ -152,7 +152,7 @@ static const int filterData[1424] = { 4224, 4352, 4480, 4608, 4096, 4096, 4096, 4096, 4288, 4416, 4544, 4672, 4096, 4096, 4096, 4096, 4288, 4416, 4544, 4672, 4160, 4160, 4160, 4160, 4352, 4480, 4608, - 4.6, 4160, 4160, 4160, 4160, 4352, 4480, 4608, 4736, + 4736, 4160, 4160, 4160, 4160, 4352, 4480, 4608, 4736, 4224, 4224, 4224, 4224, 4416, 4544, 4672, 4800, 4224, 4224, 4224, 4224, 4416, 4544, 4672, 4800, 4288, 4288, 4288, 4288, 4480, 4608, 4736, 4864, 4288, 4288, 4288, @@ -195,7 +195,7 @@ static const int filterData[1424] = { 5632 }; -static const int filterData2[1424] = { +static const int imaTable[1424] = { 0, 2, 4, 6, 7, 9, 11, 13, 0, -2, -4, -6, -7, -9, -11, -13, 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15, 1, 3, 5, 7, 10, 12, 14, 16, -1, -3, -5, @@ -352,16 +352,20 @@ static const int p2s[17] = { 0, 1, 1, 3, 1, 5, 3, 7, 1, 9, 5, 11, 3, 13, 7, 15, // Last Express ADPCM is similar to MS IMA mono, but inverts its nibbles // and does not have the 4 byte per channel requirement -class LastExpress_ADPCMStream : public Audio::Ima_ADPCMStream { +class LastExpress_ADPCMStream : public Audio::ADPCMStream { public: LastExpress_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, uint32 blockSize, int32 filterId) : - Audio::Ima_ADPCMStream(stream, disposeAfterUse, size, 44100, 1, blockSize) { + Audio::ADPCMStream(stream, disposeAfterUse, size, 44100, 1, blockSize) { _currentFilterId = -1; _nextFilterId = filterId; } int readBuffer(int16 *buffer, const int numSamples) { int samples = 0; + // Temporary data + int step = 0; + int sample = 0; + byte idx = 0; assert(numSamples % 2 == 0); @@ -369,15 +373,37 @@ public: if (_blockPos[0] == _blockAlign) { // read block header _status.ima_ch[0].last = _stream->readSint16LE(); - _status.ima_ch[0].stepIndex = _stream->readSint16LE(); + _status.ima_ch[0].stepIndex = _stream->readSint16LE() << 6; _blockPos[0] = 4; + + // Get current filter + _currentFilterId = _nextFilterId; + _nextFilterId = -1; + + // No filter: skip decoding + if (_currentFilterId == -1) + break; + + // Compute step adjustment + _stepAdjust1 = p1s[_currentFilterId]; + _stepAdjust2 = p2s[_currentFilterId]; } for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { byte data = _stream->readByte(); _blockPos[0]++; - buffer[samples] = decodeIMA((data >> 4) & 0x0f); - buffer[samples + 1] = decodeIMA(data & 0x0f); + + // First nibble + idx = data >> 4; + step = stepTable[idx + _status.ima_ch[0].stepIndex / 4]; + sample = CLIP(imaTable[idx + _status.ima_ch[0].stepIndex / 4] + _status.ima_ch[0].last, -32767, 32767); + buffer[samples] = (_stepAdjust2 * sample) >> _stepAdjust1; + + // Second nibble + idx = data & 0xF; + _status.ima_ch[0].stepIndex = stepTable[idx + step / 4]; + _status.ima_ch[0].last = CLIP(imaTable[idx + step / 4] + sample, -32767, 32767); + buffer[samples + 1] = (_stepAdjust2 * _status.ima_ch[0].last) >> _stepAdjust1; } } @@ -387,41 +413,10 @@ public: void setFilterId(int32 filterId) { _nextFilterId = filterId; } private: - int32 _currentFilterId; - int32 _nextFilterId; // the sound filter id, -1 for none - - /** - * Sound filter - * - * @param [in] data If non-null, the input data - * @param [in,out] buffer If non-null, the output buffer. - * @param p1 The first filter input. - * @param p2 The second filter input. - */ - static void soundFilter(byte *data, int16 *buffer, int p1, int p2) { - int data1, data2, data1p, data2p; - byte idx; - - data2 = data[0]; - data1 = data[1] << 6; - - data += 2; - - for (int count = 0; count < 735; count++) { - idx = data[count] >> 4; - - data1p = filterData[idx + data1]; - data2p = CLIP(filterData2[idx + data1] + data2, -32767, 32767); - - buffer[2 * count] = (p2 * data2p) >> p1; - - idx = data[count] & 0xF; - - data1 = filterData[idx + data1p]; - data2 = CLIP(filterData2[idx + data1p] + data2p, -32767, 32767); - buffer[2 * count + 1] = (p2 * data2) >> p1; - } - } + int32 _currentFilterId; + int32 _nextFilterId; // the sound filter id, -1 for none + int32 _stepAdjust1; + int32 _stepAdjust2; }; ////////////////////////////////////////////////////////////////////////// diff --git a/engines/lastexpress/game/savegame.cpp b/engines/lastexpress/game/savegame.cpp index ebada5dd4e..57c18b5697 100644 --- a/engines/lastexpress/game/savegame.cpp +++ b/engines/lastexpress/game/savegame.cpp @@ -45,12 +45,12 @@ namespace LastExpress { static const struct { const char *saveFile; } gameInfo[6] = { - {"blue.egg"}, - {"red.egg"}, - {"green.egg"}, - {"purple.egg"}, - {"teal.egg"}, - {"gold.egg"} + {"lastexpress-blue.egg"}, + {"lastexpress-red.egg"}, + {"lastexpress-green.egg"}, + {"lastexpress-purple.egg"}, + {"lastexpress-teal.egg"}, + {"lastexpress-gold.egg"} }; ////////////////////////////////////////////////////////////////////////// diff --git a/engines/lastexpress/shared.h b/engines/lastexpress/shared.h index 7b640c773a..d60a498447 100644 --- a/engines/lastexpress/shared.h +++ b/engines/lastexpress/shared.h @@ -84,29 +84,26 @@ enum SoundFlag { }; enum SoundState { - kSoundState0 = 0, - kSoundState1 = 1, - kSoundState2 = 2 + kSoundStateNone = 0, + kSoundState1 = 1, + kSoundState2 = 2 }; enum SoundStatus { + kSoundStatusClear0 = 0x10, + kSoundStatusFilter = 0x1F, kSoundStatus_20 = 0x20, kSoundStatus_40 = 0x40, + kSoundStatusCached = 0x80, kSoundStatus_180 = 0x180, kSoundStatusClosed = 0x200, kSoundStatus_400 = 0x400, - + kSoundStatusClear4 = 0x800, kSoundStatus_8000 = 0x8000, kSoundStatus_20000 = 0x20000, kSoundStatus_100000 = 0x100000, kSoundStatus_20000000 = 0x20000000, kSoundStatus_40000000 = 0x40000000, - - kSoundStatusClear0 = 0x10, - kSoundStatusFilter = 0x1F, - kSoundStatusCached = 0x80, - kSoundStatusClear3 = 0x200, - kSoundStatusClear4 = 0x800, kSoundStatusClearAll = 0xFFFFFFE0 }; diff --git a/engines/lastexpress/sound/entry.cpp b/engines/lastexpress/sound/entry.cpp index 87d8ccdb30..44cc68a57b 100644 --- a/engines/lastexpress/sound/entry.cpp +++ b/engines/lastexpress/sound/entry.cpp @@ -88,8 +88,8 @@ void SoundEntry::close() { _status.status |= kSoundStatusClosed; // Loop until ready - while (!(_status.status1 & 4) && !(getSoundQueue()->getFlag() & 8) && (getSoundQueue()->getFlag() & 1)) - ; // empty loop body + //while (!(_status.status1 & 4) && !(getSoundQueue()->getFlag() & 8) && (getSoundQueue()->getFlag() & 1)) + // ; // empty loop body // The original game remove the entry from the cache here, // but since we are called from within an iterator loop @@ -290,7 +290,7 @@ bool SoundEntry::updateSound() { } } } - //if (status.status2 & 0x40 && !((uint32)_status.status & 0x180) && v1->soundBuffer) + //if (status.status2 & 0x40 && !((uint32)_status.status & 0x180) && v1->soundBuffer) // Sound_FillSoundBuffer(v1); } result = true; diff --git a/engines/lastexpress/sound/queue.cpp b/engines/lastexpress/sound/queue.cpp index 0a6442ceed..33b4c06793 100644 --- a/engines/lastexpress/sound/queue.cpp +++ b/engines/lastexpress/sound/queue.cpp @@ -102,80 +102,41 @@ void SoundQueue::removeFromQueue(Common::String filename) { } void SoundQueue::updateQueue() { - //Common::StackLock locker(_mutex); - - //warning("[Sound::updateQueue] Not implemented"); - - int maxPriority = 0; - Common::List<SoundEntry *>::iterator lsnd; - SoundEntry *msnd; - - bool loopedPlaying; - - loopedPlaying = 0; - //++g_sound_flag; + Common::StackLock locker(_mutex); - for (lsnd = _soundList.begin(); lsnd != _soundList.end(); ++lsnd) { - if ((*lsnd)->getType() == kSoundType1) - break; - } + ++_flag; - if (getSoundState() & 1) { - if (!(*lsnd) || getFlags()->flag_3 || (*lsnd && (*lsnd)->getTime() > getSound()->getLoopingSoundDuration())) { + if (getSoundState() & kSoundState1) { + SoundEntry *entry = getEntry(kSoundType1); + if (!entry || getFlags()->flag_3 || (entry && entry->getTime() > getSound()->getLoopingSoundDuration())) { getSound()->playLoopingSound(0x45); } else { if (getSound()->getData1() && getSound()->getData2() >= getSound()->getData1()) { - (*lsnd)->update(getSound()->getData0()); + entry->update(getSound()->getData0()); getSound()->setData1(0); } } } - msnd = NULL; - - for (lsnd = _soundList.begin(); lsnd != _soundList.end(); ++lsnd) { - if ((*lsnd)->getStatus().status2 & 0x1) { // Sound is stopped - // original code - //if ((*lsnd)->soundBuffer) - // Sound_RemoveSoundDataFromCache(*lsnd); - //if ((*lsnd)->archive) { - // Archive_SetStatusNotLoaded((*lsnd)->archive); - // (*lsnd)->archive = 0; - // (*lsnd)->field_28 = 3; - //} - - if (_soundList.size() < 6) { - if ((*lsnd)->getStatus().status1 & 0x1F) { - int pri = (*lsnd)->getPriority() + ((*lsnd)->getStatus().status1 & 0x1F); - - if (pri > maxPriority) { - msnd = *lsnd; - maxPriority = pri; - } - } - } - } + for (Common::List<SoundEntry *>::iterator it = _soundList.begin(); it != _soundList.end(); ++it) { + SoundEntry *entry = *it; - if (!(*lsnd)->updateSound() && !((*lsnd)->getStatus().status3 & 0x8)) { - if (msnd == *lsnd) { - maxPriority = 0; - msnd = 0; - } - if (*lsnd) { - (*lsnd)->close(); - SAFE_DELETE(*lsnd); - lsnd = _soundList.reverse_erase(lsnd); - } + // Original removes the entry data from the cache and sets the archive as not loaded + // and if the sound data buffer is not full, loads a new entry to be played based on + // its priority and filter id + + if (!entry->updateSound() && !(entry->getStatus().status3 & 0x8)) { + entry->close(); + SAFE_DELETE(entry); + it = _soundList.reverse_erase(it); } } - - // We don't need this - //if (msnd) - // msnd->updateEntryInternal(); + // Original update the current entry, loading another set of samples to be decoded getFlags()->flag_3 = 0; - //--g_sound_flag; + + --_flag; } void SoundQueue::resetQueue() { @@ -209,17 +170,10 @@ void SoundQueue::resetQueue(SoundType type1, SoundType type2) { } void SoundQueue::clearQueue() { - _flag |= 4; - - // FIXME: Wait a while for a flag to be set - //for (int i = 0; i < 3000000; i++) - // if (_flag & 8) - // break; + Common::StackLock locker(_mutex); _flag |= 8; - Common::StackLock locker(_mutex); - for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) { SoundEntry *entry = (*i); @@ -240,7 +194,7 @@ void SoundQueue::clearStatus() { Common::StackLock locker(_mutex); for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) - (*i)->setStatus((*i)->getStatus().status | kSoundStatusClear3); + (*i)->setStatus((*i)->getStatus().status | kSoundStatusClosed); } ////////////////////////////////////////////////////////////////////////// diff --git a/engines/lastexpress/sound/sound.cpp b/engines/lastexpress/sound/sound.cpp index c04b6d361f..2f7bb4a601 100644 --- a/engines/lastexpress/sound/sound.cpp +++ b/engines/lastexpress/sound/sound.cpp @@ -1305,8 +1305,8 @@ void SoundManager::playLoopingSound(int param) { int partNumber = 1; int fnameLen = 6; - if (_queue->getSoundState() & 1 && param >= 0x45 && param <= 0x46) { - if (_queue->getSoundState() & 2) { + if (_queue->getSoundState() & kSoundState1 && param >= 0x45 && param <= 0x46) { + if (_queue->getSoundState() & kSoundState2) { strcpy(tmp, "STEAM.SND"); _loopingSoundDuration = 32767; diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp index f9d18ff7ff..65073bd970 100644 --- a/engines/mohawk/livingbooks.cpp +++ b/engines/mohawk/livingbooks.cpp @@ -204,9 +204,12 @@ Common::Error MohawkEngine_LivingBooks::run() { break; case Common::EVENT_LBUTTONDOWN: - for (uint16 i = 0; i < _items.size(); i++) - if (_items[i]->contains(event.mouse)) - found = _items[i]; + for (Common::List<LBItem *>::const_iterator i = _orderedItems.begin(); i != _orderedItems.end(); ++i) { + if ((*i)->contains(event.mouse)) { + found = *i; + break; + } + } if (found) found->handleMouseDown(event.mouse); @@ -341,6 +344,7 @@ void MohawkEngine_LivingBooks::destroyPage() { delete _page; assert(_items.empty()); + assert(_orderedItems.empty()); _page = NULL; _notifyEvents.clear(); @@ -567,6 +571,7 @@ void MohawkEngine_LivingBooks::updatePage() { case kLBDelayedEventDestroy: _items.remove_at(i); i--; + _orderedItems.remove(delayedEvent.item); delete delayedEvent.item; _page->itemDestroyed(delayedEvent.item); if (_focus == delayedEvent.item) @@ -613,6 +618,8 @@ void MohawkEngine_LivingBooks::removeArchive(Archive *archive) { void MohawkEngine_LivingBooks::addItem(LBItem *item) { _items.push_back(item); + _orderedItems.push_front(item); + item->_iterator = _orderedItems.begin(); } void MohawkEngine_LivingBooks::removeItems(const Common::Array<LBItem *> &items) { @@ -626,6 +633,7 @@ void MohawkEngine_LivingBooks::removeItems(const Common::Array<LBItem *> &items) break; } assert(found); + _orderedItems.erase(items[i]->_iterator); } } @@ -1319,8 +1327,13 @@ void MohawkEngine_LivingBooks::handleNotify(NotifyEvent &event) { if (getGameType() == GType_LIVINGBOOKSV1) { debug(2, "kLBNotifyChangeMode: %d", event.param); quitGame(); - } else { - debug(2, "kLBNotifyChangeMode: mode %d, page %d.%d", + break; + } + + debug(2, "kLBNotifyChangeMode: v2 type %d", event.param); + switch (event.param) { + case 1: + debug(2, "kLBNotifyChangeMode:, mode %d, page %d.%d", event.newMode, event.newPage, event.newSubpage); // TODO: what is entry.newUnknown? if (!event.newMode) @@ -1331,6 +1344,13 @@ void MohawkEngine_LivingBooks::handleNotify(NotifyEvent &event) { error("kLBNotifyChangeMode failed to move to mode %d, page %d.%d", event.newMode, event.newPage, event.newSubpage); } + break; + case 3: + debug(2, "kLBNotifyChangeMode: new cursor '%s'", event.newCursor.c_str()); + _cursor->setCursor(event.newCursor); + break; + default: + error("unknown v2 kLBNotifyChangeMode type %d", event.param); } break; @@ -2084,16 +2104,32 @@ LBScriptEntry *LBItem::parseScriptEntry(uint16 type, uint16 &size, Common::Memor } if (type == kLBNotifyScript && entry->opcode == kLBNotifyChangeMode && _vm->getGameType() != GType_LIVINGBOOKSV1) { - if (size < 8) { - error("%d unknown bytes in notify entry kLBNotifyChangeMode", size); + switch (entry->param) { + case 1: + if (size < 8) + error("%d unknown bytes in notify entry kLBNotifyChangeMode", size); + entry->newUnknown = stream->readUint16(); + entry->newMode = stream->readUint16(); + entry->newPage = stream->readUint16(); + entry->newSubpage = stream->readUint16(); + debug(4, "kLBNotifyChangeMode: unknown %04x, mode %d, page %d.%d", + entry->newUnknown, entry->newMode, entry->newPage, entry->newSubpage); + size -= 8; + break; + case 3: + { + Common::String newCursor = _vm->readString(stream); + entry->newCursor = newCursor; + if (size < newCursor.size() + 1) + error("failed to read newCursor in notify entry"); + size -= newCursor.size() + 1; + debug(4, "kLBNotifyChangeMode: new cursor '%s'", newCursor.c_str()); + } + break; + default: + // the original engine also does something when param==2 (but not a notify) + error("unknown v2 kLBNotifyChangeMode type %d", entry->param); } - entry->newUnknown = stream->readUint16(); - entry->newMode = stream->readUint16(); - entry->newPage = stream->readUint16(); - entry->newSubpage = stream->readUint16(); - debug(4, "kLBNotifyChangeMode: unknown %04x, mode %d, page %d.%d", - entry->newUnknown, entry->newMode, entry->newPage, entry->newSubpage); - size -= 8; } if (entry->opcode == kLBOpSendExpression) { if (size < 4) @@ -2577,6 +2613,7 @@ void LBItem::runScript(uint event, uint16 data, uint16 from) { notifyEvent.newMode = entry->newMode; notifyEvent.newPage = entry->newPage; notifyEvent.newSubpage = entry->newSubpage; + notifyEvent.newCursor = entry->newCursor; _vm->addNotifyEvent(notifyEvent); } else _vm->addNotifyEvent(NotifyEvent(entry->opcode, entry->param)); diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h index ad2fe56a52..27e703a578 100644 --- a/engines/mohawk/livingbooks.h +++ b/engines/mohawk/livingbooks.h @@ -133,7 +133,9 @@ enum { kLBEventMouseUp = 5, kLBEventPhaseMain = 6, kLBEventNotified = 7, + kLBEventDragStart = 8, kLBEventDragMove = 9, + kLBEventDragEnd = 0xa, kLBEventRolloverBegin = 0xb, kLBEventRolloverMove = 0xc, kLBEventRolloverEnd = 0xd, @@ -271,6 +273,7 @@ struct LBScriptEntry { uint16 newMode; uint16 newPage; uint16 newSubpage; + Common::String newCursor; // kLBEventNotified uint16 matchFrom; @@ -405,6 +408,8 @@ public: uint16 getSoundPriority() { return _soundMode; } bool isAmbient() { return _isAmbient; } + Common::List<LBItem *>::iterator _iterator; + protected: MohawkEngine_LivingBooks *_vm; LBPage *_page; @@ -608,6 +613,7 @@ struct NotifyEvent { uint16 newMode; uint16 newPage; uint16 newSubpage; + Common::String newCursor; }; enum DelayedEventType { @@ -667,7 +673,7 @@ public: GUI::Debugger *getDebugger() { return _console; } void addArchive(Archive *archive); - void removeArchive(Archive *Archive); + void removeArchive(Archive *archive); void addItem(LBItem *item); void removeItems(const Common::Array<LBItem *> &items); @@ -714,6 +720,7 @@ private: uint16 _phase; LBPage *_page; Common::Array<LBItem *> _items; + Common::List<LBItem *> _orderedItems; Common::Queue<DelayedEvent> _eventQueue; LBItem *_focus; void destroyPage(); diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp index e9ef2516e2..80b5fe9660 100644 --- a/engines/mohawk/livingbooks_code.cpp +++ b/engines/mohawk/livingbooks_code.cpp @@ -686,8 +686,8 @@ struct CodeCommandInfo { CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = { { "eval", &LBCode::cmdEval }, { "random", &LBCode::cmdRandom }, - { "stringLen", 0 }, - { "substring", 0 }, + { "stringLen", &LBCode::cmdStringLen }, + { "substring", &LBCode::cmdSubstring }, { "max", 0 }, { "min", 0 }, { "abs", 0 }, @@ -861,6 +861,31 @@ void LBCode::cmdRandom(const Common::Array<LBValue> ¶ms) { _stack.push(_vm->_rnd->getRandomNumberRng(min, max)); } +void LBCode::cmdStringLen(const Common::Array<LBValue> ¶ms) { + if (params.size() != 1) + error("incorrect number of parameters (%d) to stringLen", params.size()); + + const Common::String &string = params[0].toString(); + _stack.push(string.size()); +} + +void LBCode::cmdSubstring(const Common::Array<LBValue> ¶ms) { + if (params.size() != 3) + error("incorrect number of parameters (%d) to substring", params.size()); + + const Common::String &string = params[0].toString(); + uint begin = params[1].toInt(); + uint end = params[2].toInt(); + if (begin == 0) + error("invalid substring call (%d to %d)", begin, end); + if (begin > end || end > string.size()) { + _stack.push(Common::String()); + return; + } + Common::String substring(string.c_str() + (begin - 1), end - begin + 1); + _stack.push(substring); +} + void LBCode::cmdGetRect(const Common::Array<LBValue> ¶ms) { if (params.size() < 2) { _stack.push(getRectFromParams(params)); @@ -1156,8 +1181,8 @@ bool LBCode::parseCodeSymbol(const Common::String &name, uint &pos, Common::Arra // first, check whether the name matches a known function for (uint i = 0; i < 2; i++) { byte cmdToken; - CodeCommandInfo *cmdInfo; - uint cmdCount; + CodeCommandInfo *cmdInfo = NULL; + uint cmdCount = 0; switch (i) { case 0: diff --git a/engines/mohawk/livingbooks_code.h b/engines/mohawk/livingbooks_code.h index 9c58ed7a46..79c9af94f7 100644 --- a/engines/mohawk/livingbooks_code.h +++ b/engines/mohawk/livingbooks_code.h @@ -222,6 +222,8 @@ public: void cmdUnimplemented(const Common::Array<LBValue> ¶ms); void cmdEval(const Common::Array<LBValue> ¶ms); void cmdRandom(const Common::Array<LBValue> ¶ms); + void cmdStringLen(const Common::Array<LBValue> ¶ms); + void cmdSubstring(const Common::Array<LBValue> ¶ms); void cmdGetRect(const Common::Array<LBValue> ¶ms); void cmdTopLeft(const Common::Array<LBValue> ¶ms); void cmdBottomRight(const Common::Array<LBValue> ¶ms); diff --git a/engines/saga/actor.h b/engines/saga/actor.h index a4f475660d..d9d4b70168 100644 --- a/engines/saga/actor.h +++ b/engines/saga/actor.h @@ -650,7 +650,7 @@ private: public: #ifdef ACTOR_DEBUG #ifndef SAGA_DEBUG - you must also define SAGA_DEBUG + #error You must also define SAGA_DEBUG #endif //path debug - use with care struct DebugPoint { diff --git a/engines/saga/detection.cpp b/engines/saga/detection.cpp index 7a98fe4164..2f1b61eed8 100644 --- a/engines/saga/detection.cpp +++ b/engines/saga/detection.cpp @@ -67,9 +67,6 @@ int SagaEngine::getGameId() const { return _gameDescription->gameId; } uint32 SagaEngine::getFeatures() const { uint32 result = _gameDescription->features; - if (_gf_wyrmkeep) - result |= GF_WYRMKEEP; - return result; } diff --git a/engines/saga/detection_tables.h b/engines/saga/detection_tables.h index ab73fcba6e..a29d835a54 100644 --- a/engines/saga/detection_tables.h +++ b/engines/saga/detection_tables.h @@ -221,7 +221,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NOSPEECH }, GID_ITE, - GF_WYRMKEEP | GF_SCENE_SUBSTITUTES | GF_MONO_MUSIC | GF_LE_VOICES, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITEWINDEMO_GameFonts), @@ -247,7 +247,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NOSPEECH }, GID_ITE, - GF_WYRMKEEP | GF_LE_VOICES, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITEWINDEMO_GameFonts), @@ -273,7 +273,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NONE }, GID_ITE, - GF_WYRMKEEP | GF_SCENE_SUBSTITUTES, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITEWINDEMO_GameFonts), @@ -299,7 +299,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NONE }, GID_ITE, - GF_WYRMKEEP | GF_8BIT_UNSIGNED_PCM, + GF_8BIT_UNSIGNED_PCM, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITEWINDEMO_GameFonts), @@ -356,7 +356,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NONE }, GID_ITE, - GF_WYRMKEEP, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITEWINDEMO_GameFonts), @@ -388,7 +388,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NONE }, GID_ITE, - GF_WYRMKEEP, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITE_GameFonts), @@ -418,7 +418,7 @@ static const SAGAGameDescription gameDescriptions[] = { GUIO_NONE }, GID_ITE, - GF_WYRMKEEP, + 0, ITE_DEFAULT_SCENE, &ITE_Resources, ARRAYSIZE(ITE_GameFonts), diff --git a/engines/saga/introproc_ite.cpp b/engines/saga/introproc_ite.cpp index 87fd48e2d2..9248f2b530 100644 --- a/engines/saga/introproc_ite.cpp +++ b/engines/saga/introproc_ite.cpp @@ -179,21 +179,22 @@ enum { EventColumns *Scene::ITEQueueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]) { int game; Common::Language lang; + bool hasWyrmkeepCredits = (Common::File::exists("credit3n.dlt") || // PC + Common::File::exists("credit3m.dlt")); // Mac // The assumption here is that all WyrmKeep versions have the same // credits, regardless of which operating system they're for. lang = _vm->getLanguage(); - if (_vm->getFeatures() & GF_WYRMKEEP) { + if (hasWyrmkeepCredits) game = kITEWyrmKeep; - } else if (_vm->getPlatform() == Common::kPlatformMacintosh) { + else if (_vm->getPlatform() == Common::kPlatformMacintosh) game = kITEMac; - } else if (_vm->getFeatures() & GF_EXTRA_ITE_CREDITS) { + else if (_vm->getFeatures() & GF_EXTRA_ITE_CREDITS) game = kITEPCCD; - } else { + else game = kITEPC; - } int line_spacing = 0; int paragraph_spacing; @@ -303,6 +304,11 @@ int Scene::SC_ITEIntroAnimProc(int param, void *refCon) { int Scene::ITEIntroAnimProc(int param) { Event event; EventColumns *eventColumns; + bool isMac = _vm->getPlatform() == Common::kPlatformMacintosh; + bool isMultiCD = _vm->getPlatform() == Common::kPlatformUnknown; + bool hasWyrmkeepCredits = (Common::File::exists("credit3n.dlt") || // PC + Common::File::exists("credit3m.dlt")); // Mac + bool isDemo = Common::File::exists("scriptsd.rsc"); switch (param) { case SCENE_BEGIN:{ @@ -324,19 +330,10 @@ int Scene::ITEIntroAnimProc(int param) { // playback int lastAnim; - if (_vm->getFeatures() & GF_WYRMKEEP) { - if (_vm->getPlatform() == Common::kPlatformMacintosh) { - lastAnim = 3; - } else { - lastAnim = 2; - } - } else { - if (_vm->getPlatform() == Common::kPlatformMacintosh) { - lastAnim = 4; - } else { - lastAnim = 5; - } - } + if (hasWyrmkeepCredits || isMultiCD || isDemo) + lastAnim = isMac ? 3 : 2; + else + lastAnim = isMac ? 4 : 5; for (int i = 0; i < lastAnim; i++) _vm->_anim->link(i, i+1); diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp index 21f3cc489e..49d3f91d77 100644 --- a/engines/saga/music.cpp +++ b/engines/saga/music.cpp @@ -287,7 +287,12 @@ void Music::play(uint32 resourceId, MusicFlags flags) { if (_vm->isBigEndian()) musicFlags &= ~Audio::FLAG_LITTLE_ENDIAN; - if (_vm->getFeatures() & GF_MONO_MUSIC) + // The newer ITE Mac demo version contains a music file, but it has mono music. + // This is the only music file that is about 7MB, whereas all the other ones + // are much larger. Thus, we use this simple heuristic to determine if we got + // mono music in the ITE demos or not. + if (!strcmp(_digitalMusicContext->fileName(), "musicd.rsc") && + _digitalMusicContext->fileSize() < 8000000) musicFlags &= ~Audio::FLAG_STEREO; audioStream = Audio::makeRawStream(musicStream, 11025, musicFlags, DisposeAfterUse::YES); @@ -368,10 +373,12 @@ void Music::play(uint32 resourceId, MusicFlags flags) { void Music::pause() { _player->pause(); + _player->setVolume(0); } void Music::resume() { _player->resume(); + _player->setVolume(_vm->_musicVolume); } void Music::stop() { diff --git a/engines/saga/resource.cpp b/engines/saga/resource.cpp index 72b021309c..1b0dfa2f22 100644 --- a/engines/saga/resource.cpp +++ b/engines/saga/resource.cpp @@ -162,12 +162,6 @@ bool Resource::createContexts() { uint16 voiceFileAddType; }; - - // If the Wyrmkeep credits file is found, set the Wyrmkeep version flag to true - if (Common::File::exists("credit3n.dlt")) { - _vm->_gf_wyrmkeep = true; - } - for (const ADGameFileDescription *gameFileDescription = _vm->getFilesDescriptions(); gameFileDescription->fileName; gameFileDescription++) { addContext(gameFileDescription->fileName, gameFileDescription->fileType); diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index d168605e99..6e272d37c0 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -72,9 +72,8 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc) _readingSpeed = 0; _copyProtection = false; - _gf_wyrmkeep = false; _musicWasPlaying = false; - + _hasITESceneSubstitutes = false; _sndRes = NULL; _sound = NULL; @@ -211,9 +210,9 @@ Common::Error SagaEngine::run() { _subtitlesEnabled = ConfMan.getBool("subtitles"); _readingSpeed = getTalkspeed(); _copyProtection = ConfMan.getBool("copy_protection"); - _gf_wyrmkeep = false; _musicWasPlaying = false; _isIHNMDemo = Common::File::exists("music.res"); + _hasITESceneSubstitutes = Common::File::exists("boarhall.bbm"); if (_readingSpeed > 3) _readingSpeed = 0; diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 23258e1277..336883680a 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -137,16 +137,12 @@ enum GameFileTypes { }; enum GameFeatures { - GF_WYRMKEEP = 1 << 0, - GF_ITE_FLOPPY = 1 << 1, - GF_SCENE_SUBSTITUTES = 1 << 2, + GF_ITE_FLOPPY = 1 << 0, #if 0 - GF_OLD_ITE_DOS = 1 << 3, // Currently unused + GF_OLD_ITE_DOS = 1 << 1, // Currently unused #endif - GF_MONO_MUSIC = 1 << 4, - GF_EXTRA_ITE_CREDITS = 1 << 5, - GF_LE_VOICES = 1 << 6, - GF_8BIT_UNSIGNED_PCM = 1 << 7 + GF_EXTRA_ITE_CREDITS = 1 << 2, + GF_8BIT_UNSIGNED_PCM = 1 << 3 }; enum VerbTypeIds { @@ -532,9 +528,9 @@ public: int _readingSpeed; bool _copyProtection; - bool _gf_wyrmkeep; bool _musicWasPlaying; bool _isIHNMDemo; + bool _hasITESceneSubstitutes; SndRes *_sndRes; Sound *_sound; diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp index 66ee8f4504..61e62d5626 100644 --- a/engines/saga/scene.cpp +++ b/engines/saga/scene.cpp @@ -451,7 +451,7 @@ void Scene::changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionTy // This is used for latter ITE demos where all places on world map except // Tent Faire are substituted with LBM picture and short description - if (_vm->getFeatures() & GF_SCENE_SUBSTITUTES) { + if (_vm->_hasITESceneSubstitutes) { for (int i = 0; i < ARRAYSIZE(sceneSubstitutes); i++) { if (sceneSubstitutes[i].sceneId == sceneNumber) { Surface bbmBuffer; diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp index 2433c93e93..add34e22a2 100644 --- a/engines/saga/sndres.cpp +++ b/engines/saga/sndres.cpp @@ -262,9 +262,12 @@ bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buff buffer.flags |= Audio::FLAG_UNSIGNED; buffer.flags &= ~Audio::FLAG_16BITS; } else { - // Voice files in newer ITE demo versions are OKI ADPCM (VOX) encoded - if (!uncompressedSound && !scumm_stricmp(context->fileName(), "voicesd.rsc")) + // Voice files in newer ITE demo versions are OKI ADPCM (VOX) encoded. + // These are LE in all the Windows and Mac demos + if (!uncompressedSound && !scumm_stricmp(context->fileName(), "voicesd.rsc")) { resourceType = kSoundVOX; + buffer.flags |= Audio::FLAG_LITTLE_ENDIAN; + } } } buffer.buffer = NULL; @@ -272,8 +275,6 @@ bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buff // Check for LE sounds if (!context->isBigEndian()) buffer.flags |= Audio::FLAG_LITTLE_ENDIAN; - if ((context->fileType() & GAME_VOICEFILE) && (_vm->getFeatures() & GF_LE_VOICES)) - buffer.flags |= Audio::FLAG_LITTLE_ENDIAN; // Older Mac versions of ITE were Macbinary packed int soundOffset = (context->fileType() & GAME_MACBINARY) ? 36 : 0; diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 6641f243e6..3b18a1f68d 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -3150,6 +3150,15 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + + // Slater & Charlie Go Camping - English DOS/Windows (Sierra Originals) + {"slater", "", { + {"resource.000", 0, "d7b4cc8e2c0b3a4768f8dfb5de27f206", 2256126}, + {"resource.map", 0, "21f85414124dc23e54544a5536dc35cd", 4044}, + {"resource.msg", 0, "c44f51fb955eae266fecf360ebcd5ad2", 1132}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + // Space Quest 1 VGA Remake - English Amiga (from www.back2roots.org) // SCI interpreter version 1.000.510 (just a guess) {"sq1sci", "SCI", { diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 9f10691767..7b8db22e3f 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -730,6 +730,10 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { case 8: { // Dup const char *rawString = 0; uint32 size = 0; + reg_t stringHandle; + // We allocate the new string first because if the StringTable needs to + // grow, our rawString pointer will be invalidated + SciString *dupString = s->_segMan->allocateString(&stringHandle); if (argv[1].segment == s->_segMan->getStringSegmentId()) { SciString *string = s->_segMan->lookupString(argv[1]); @@ -741,8 +745,6 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { size = string.size() + 1; } - reg_t stringHandle; - SciString *dupString = s->_segMan->allocateString(&stringHandle); dupString->setSize(size); for (uint32 i = 0; i < size; i++) diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index eaae64dc77..b38bd3b674 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -49,35 +49,28 @@ void ScummEngine::loadCJKFont() { _newLineCharacter = 0; if (_game.version <= 5 && _game.platform == Common::kPlatformFMTowns && _language == Common::JA_JPN) { // FM-TOWNS v3 / v5 Kanji -#ifdef DISABLE_TOWNS_DUAL_LAYER_MODE +#if defined(DISABLE_TOWNS_DUAL_LAYER_MODE) || !defined(USE_RGB_COLOR) GUIErrorMessage("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build"); error("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build"); #else // use FM-TOWNS font rom, since game files don't have kanji font resources - _cjkFont = Graphics::FontSJIS::createFont(Common::kPlatformFMTowns); + _cjkFont = Graphics::FontSJIS::createFont(_game.platform); if (!_cjkFont) error("SCUMM::Font: Could not open file 'FMT_FNT.ROM'"); _textSurfaceMultiplier = 2; _useCJKMode = true; #endif } else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine && _language == Common::JA_JPN) { - int numChar = 3418; - _2byteWidth = 12; - _2byteHeight = 12; +#ifdef USE_RGB_COLOR // use PC-Engine System Card, since game files don't have kanji font resources - if (!fp.open("pce.cdbios")) { - error("SCUMM::Font: Could not open System Card pce.cdbios"); - } else { - _useCJKMode = true; - debug(2, "Loading PC-Engine System Card"); - - // A 0x200 byte header can be present at the beginning of the syscard. Seek past it too. - fp.seek((fp.size() & 0x200) ? 0x30200 : 0x30000); + _cjkFont = Graphics::FontSJIS::createFont(_game.platform); + if (!_cjkFont) + error("SCUMM::Font: Could not open file 'pce.cdbios'"); - _2byteFontPtr = new byte[_2byteWidth * _2byteHeight * numChar / 8]; - fp.read(_2byteFontPtr, _2byteWidth * _2byteHeight * numChar / 8); - fp.close(); - } + _cjkFont->setDrawingMode(Graphics::FontSJIS::kShadowMode); + _2byteWidth = _2byteHeight = 12; + _useCJKMode = true; +#endif } else if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { int numChar = 1413; _2byteWidth = 16; @@ -161,71 +154,16 @@ void ScummEngine::loadCJKFont() { } } -static int SJIStoPCEChunk(int f, int s) { //converts sjis code to pce font offset - // rangeTbl maps SJIS char-codes to the PCE System Card font rom. - // Each pair {<upperBound>,<lowerBound>} in the array represents a SJIS range. - const int rangeCnt = 45; - static const uint16 rangeTbl[rangeCnt][2] = { - // Symbols - {0x8140,0x817E},{0x8180,0x81AC}, - // 0-9 - {0x824F,0x8258}, - // Latin upper - {0x8260,0x8279}, - // Latin lower - {0x8281,0x829A}, - // Kana - {0x829F,0x82F1},{0x8340,0x837E},{0x8380,0x8396}, - // Greek upper - {0x839F,0x83B6}, - // Greek lower - {0x83BF,0x83D6}, - // Cyrillic upper - {0x8440,0x8460}, - // Cyrillic lower - {0x8470,0x847E},{0x8480,0x8491}, - // Kanji - {0x889F,0x88FC}, - {0x8940,0x897E},{0x8980,0x89FC}, - {0x8A40,0x8A7E},{0x8A80,0x8AFC}, - {0x8B40,0x8B7E},{0x8B80,0x8BFC}, - {0x8C40,0x8C7E},{0x8C80,0x8CFC}, - {0x8D40,0x8D7E},{0x8D80,0x8DFC}, - {0x8E40,0x8E7E},{0x8E80,0x8EFC}, - {0x8F40,0x8F7E},{0x8F80,0x8FFC}, - {0x9040,0x907E},{0x9080,0x90FC}, - {0x9140,0x917E},{0x9180,0x91FC}, - {0x9240,0x927E},{0x9280,0x92FC}, - {0x9340,0x937E},{0x9380,0x93FC}, - {0x9440,0x947E},{0x9480,0x94FC}, - {0x9540,0x957E},{0x9580,0x95FC}, - {0x9640,0x967E},{0x9680,0x96FC}, - {0x9740,0x977E},{0x9780,0x97FC}, - {0x9840,0x9872} - }; - - int ch = (f << 8) | (s & 0xFF); - int offset = 0; - for (int i = 0; i < rangeCnt; ++i) { - if (ch >= rangeTbl[i][0] && ch <= rangeTbl[i][1]) - return offset + ch - rangeTbl[i][0]; - offset += rangeTbl[i][1] - rangeTbl[i][0] + 1; - } - - debug(4, "Invalid Char: 0x%x", ch); - return 0; -} - byte *ScummEngine::get2byteCharPtr(int idx) { + if (_game.platform == Common::kPlatformFMTowns || _game.platform == Common::kPlatformPCEngine) + return 0; + switch (_language) { case Common::KO_KOR: idx = ((idx % 256) - 0xb0) * 94 + (idx / 256) - 0xa1; break; case Common::JA_JPN: - if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) { - idx = SJIStoPCEChunk((idx % 256), (idx / 256)); - return _2byteFontPtr + (_2byteWidth * _2byteHeight / 8) * idx; - } else if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { + if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { // init pointer to charset resource if (_2byteFontPtr[0] == 0xFF) { int charsetId = 5; @@ -314,7 +252,7 @@ CharsetRenderer::~CharsetRenderer() { CharsetRendererCommon::CharsetRendererCommon(ScummEngine *vm) : CharsetRenderer(vm), _bytesPerPixel(0), _fontHeight(0), _numChars(0) { - _shadowMode = kNoShadowMode; + _shadowMode = false; _shadowColor = 0; } @@ -362,17 +300,9 @@ void CharsetRendererV3::setCurID(int32 id) { } int CharsetRendererCommon::getFontHeight() { - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - static const uint8 sjisFontHeightM1[] = { 0, 8, 9, 8, 9, 8, 9, 0, 0, 0 }; - static const uint8 sjisFontHeightM2[] = { 0, 8, 9, 9, 9, 8, 9, 9, 9, 8 }; - static const uint8 sjisFontHeightI4[] = { 0, 8, 9, 9, 9, 8, 8, 8, 8, 8 }; - const uint8 *htbl = (_vm->_game.id == GID_MONKEY) ? sjisFontHeightM1 : ((_vm->_game.id == GID_INDY4) ? sjisFontHeightI4 : sjisFontHeightM2); - return (_vm->_game.version == 3) ? 8 : htbl[_curId]; - } else { - return MAX(_vm->_2byteHeight + 1, _fontHeight); - } - } else + if (_vm->_useCJKMode) + return MAX(_vm->_2byteHeight + 1, _fontHeight); + else return _fontHeight; } @@ -380,57 +310,16 @@ int CharsetRendererCommon::getFontHeight() { int CharsetRendererClassic::getCharWidth(uint16 chr) { int spacing = 0; - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - if ((chr & 0xff00) == 0xfd00) { - chr &= 0xff; - } else if (chr >= 256) { - spacing = 8; - } else if (useTownsFontRomCharacter(chr)) { - spacing = 4; - } + if (_vm->_useCJKMode && chr >= 0x80) + return _vm->_2byteWidth / 2; - if (spacing) { - if (_vm->_game.id == GID_MONKEY) { - spacing++; - if (_curId == 2) - spacing++; - } else if (_vm->_game.id != GID_INDY4 && _curId == 1) { - spacing++; - } - } - - } else if (chr >= 0x80) { - return _vm->_2byteWidth / 2; - } - } - - if (!spacing) { - int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - if (offs) { - spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; - } - } + int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + if (offs) + spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; return spacing; } -bool CharsetRendererClassic::useTownsFontRomCharacter(uint16 chr) { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform != Common::kPlatformFMTowns || !_vm->_useCJKMode) - return false; - - if (chr < 128) { - if (((_vm->_game.id == GID_MONKEY2 && _curId != 0) || (_vm->_game.id == GID_INDY4 && _curId != 3)) && (chr > 31 && chr != 94 && chr != 95 && chr != 126 && chr != 127)) - return true; - return false; - } - return true; -#else - return false; -#endif -} - int CharsetRenderer::getStringWidth(int arg, const byte *text) { int pos = 0; int width = 1; @@ -608,22 +497,51 @@ void CharsetRenderer::addLinebreaks(int a, byte *str, int pos, int maxwidth) { int CharsetRendererV3::getCharWidth(uint16 chr) { int spacing = 0; - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - if (chr >= 256) - spacing = 8; - else if (chr >= 128) - spacing = 4; - } else if (chr & 0x80) { - spacing = _vm->_2byteWidth / 2; + if (_vm->_useCJKMode && (chr & 0x80)) + spacing = _vm->_2byteWidth / 2; + + if (!spacing) + spacing = *(_widthTable + chr); + + return spacing; +} + +void CharsetRendererV3::enableShadow(bool enable) { + _shadowColor = 0; + _shadowMode = enable; +} + +void CharsetRendererV3::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { + int y, x; + byte bits = 0; + uint8 col = _color; + int pitch = s.pitch - width * bitDepth; + byte *dst2 = dst + s.pitch; + + for (y = 0; y < height && y + drawTop < s.h; y++) { + for (x = 0; x < width; x++) { + if ((x % 8) == 0) + bits = *src++; + if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) { + if (_shadowMode) + dst[1] = dst2[0] = dst2[1] = _shadowColor; + dst[0] = col; + } + dst += bitDepth; + dst2 += bitDepth; } - } - if (!spacing) { - spacing = *(_widthTable + chr); + dst += pitch; + dst2 += pitch; } +} - return spacing; +int CharsetRendererV3::getDrawWidthIntern(uint16 chr) { + return getCharWidth(chr); +} + +int CharsetRendererV3::getDrawHeightIntern(uint16) { + return 8; } void CharsetRendererV3::setColor(byte color) { @@ -662,43 +580,6 @@ void CharsetRendererPCE::setColor(byte color) { } #endif -void CharsetRendererCommon::enableShadow(bool enable) { - if (enable) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _shadowColor = 8; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - _shadowColor = _vm->_game.version == 5 ? _vm->_townsCharsetColorMap[0] : 0x88; - if (_vm->_cjkFont) { - if (_vm->_game.version == 5) { - if (((_vm->_game.id == GID_MONKEY) && (_curId == 2 || _curId == 4 || _curId == 6)) || - ((_vm->_game.id == GID_MONKEY2) && (_curId != 1 && _curId != 5 && _curId != 9)) || - ((_vm->_game.id == GID_INDY4) && (_curId == 2 || _curId == 3 || _curId == 4))) { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kOutlineMode); - } else { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); - } - _vm->_cjkFont->toggleFlippedMode((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && _curId == 3); - } else { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kShadowMode); - } - } -#endif - _shadowMode = kFMTOWNSShadowMode; - } else { - _shadowColor = 0; - _shadowMode = kNormalShadowMode; - } - } else { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_cjkFont) { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); - _vm->_cjkFont->toggleFlippedMode(false); - } -#endif - _shadowMode = kNoShadowMode; - } -} - void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { // WORKAROUND for bug #1509509: Indy3 Mac does not show black // characters (such as in the grail diary) if ignoreCharsetMask @@ -721,33 +602,19 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { if (chr == '@') return; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_useCJKMode && chr > 127) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - charPtr = 0; - width = _vm->_cjkFont->getCharWidth(chr); - height = _vm->_cjkFont->getFontHeight(); - } else { - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - charPtr = _vm->get2byteCharPtr(chr); - } - } else -#endif - { - charPtr = _fontPtr + chr * 8; - width = getCharWidth(chr); - height = 8; - } + charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8; + width = getDrawWidthIntern(chr); + height = getDrawHeightIntern(chr); + setDrawCharIntern(chr); + + origWidth = width; + origHeight = height; // Clip at the right side (to avoid drawing "outside" the screen bounds). if (_left + origWidth > _right + 1) return; - origWidth = width; - origHeight = height; - - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { width++; height++; } @@ -769,30 +636,17 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { _textScreenID = vs->number; } - if ( -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - (_vm->_game.platform != Common::kPlatformFMTowns) && -#endif - (ignoreCharsetMask || !vs->hasTwoBuffers)) { + if ((ignoreCharsetMask || !vs->hasTwoBuffers)) { dst = vs->getPixels(_left, drawTop); - if (charPtr) - drawBits1(*vs, dst, charPtr, drawTop, origWidth, origHeight, vs->format.bytesPerPixel); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - else if (_vm->_cjkFont) - _vm->_cjkFont->drawChar(*vs, chr, _left, drawTop, _color, _shadowColor); -#endif + drawBits1(*vs, dst, charPtr, drawTop, origWidth, origHeight, vs->format.bytesPerPixel); } else { dst = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier); - if (charPtr) - drawBits1(_vm->_textSurface, dst, charPtr, drawTop, origWidth, origHeight, _vm->_textSurface.format.bytesPerPixel, (_vm->_textSurfaceMultiplier == 2 && !is2byte)); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - else if (_vm->_cjkFont) - _vm->_cjkFont->drawChar(_vm->_textSurface, chr, _left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier, _color, _shadowColor); -#endif - if (is2byte) { - origWidth /= _vm->_textSurfaceMultiplier; - height /= _vm->_textSurfaceMultiplier; - } + drawBits1(_vm->_textSurface, dst, charPtr, drawTop, origWidth, origHeight, _vm->_textSurface.format.bytesPerPixel); + } + + if (is2byte) { + origWidth /= _vm->_textSurfaceMultiplier; + height /= _vm->_textSurfaceMultiplier; } if (_str.left > _left) @@ -802,7 +656,7 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { if (_str.right < _left) { _str.right = _left; - if (_shadowMode != kNoShadowMode) + if (_shadowMode) _str.right++; } @@ -811,30 +665,12 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { } void CharsetRendererV3::drawChar(int chr, Graphics::Surface &s, int x, int y) { - const byte *charPtr; - byte *dst; - int width, height; - int is2byte = (chr >= 0x80 && _vm->_useCJKMode) ? 1 : 0; - if (is2byte) { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _vm->_cjkFont->drawChar(s, chr, x * _vm->_textSurfaceMultiplier, y * _vm->_textSurfaceMultiplier, _color, _shadowColor); - return; - } - else -#endif - { - charPtr = _vm->get2byteCharPtr(chr); - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - } - } else { - charPtr = _fontPtr + chr * 8; -// width = height = 8; - width = getCharWidth(chr); - height = 8; - } - dst = (byte *)s.pixels + y * s.pitch + x; + const byte *charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8; + int width = getDrawWidthIntern(chr); + int height = getDrawHeightIntern(chr); + setDrawCharIntern(chr); + + byte *dst = (byte *)s.pixels + y * s.pitch + x; drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); } @@ -853,29 +689,6 @@ void CharsetRenderer::translateColor() { } } -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE -void CharsetRenderer::processTownsCharsetColors(uint8 bytesPerPixel) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - for (int i = 0; i < (1 << bytesPerPixel); i++) { - uint8 c = _vm->_charsetColorMap[i]; - - if (c > 16) { - uint8 t = (_vm->_currentPalette[c * 3] < 32) ? 4 : 12; - t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 2 : 10); - t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 1 : 9); - c = t; - } - - if (c == 0) - c = _vm->_townsOverrideShadowColor; - - c = ((c & 0x0f) << 4) | (c & 0x0f); - _vm->_townsCharsetColorMap[i] = c; - } - } -} -#endif - void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) { static const SaveLoadEntry charsetRendererEntries[] = { MKLINE_OLD(CharsetRenderer, _curId, sleByte, VER(73), VER(73)), @@ -893,10 +706,7 @@ void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) { } void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { - int width, height, origWidth, origHeight; - int offsX, offsY; VirtScreen *vs; - const byte *charPtr; bool is2byte = (chr >= 256 && _vm->_useCJKMode); assertRange(1, _curId, _vm->_numCharsets - 1, "charset"); @@ -911,64 +721,7 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { _vm->_charsetColorMap[1] = _color; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - processTownsCharsetColors(_bytesPerPixel); - bool noSjis = false; - - if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_useCJKMode) { - if ((chr & 0x00ff) == 0x00fd) { - chr >>= 8; - noSjis = true; - } - } - - if (useTownsFontRomCharacter(chr) && !noSjis) { - charPtr = 0; - _vm->_cjkChar = chr; - enableShadow(true); - - width = getCharWidth(chr); - // For whatever reason MI1 uses a different font width - // for alignment calculation and for drawing when - // charset 2 is active. This fixes some subtle glitches. - if (_vm->_game.id == GID_MONKEY && _curId == 2) - width--; - origWidth = width; - - origHeight = height = getFontHeight(); - offsX = offsY = 0; - } else if (_vm->_useCJKMode && (chr >= 128) && !noSjis) { - enableShadow(true); - origWidth = width = _vm->_2byteWidth; - origHeight = height = _vm->_2byteHeight; - charPtr = _vm->get2byteCharPtr(chr); - offsX = offsY = 0; - if (_shadowMode != kNoShadowMode) { - width++; - height++; - } - } else -#endif - { - uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - assert(charOffs < 0x14000); - if (!charOffs) - return; - charPtr = _fontPtr + charOffs; - - width = origWidth = charPtr[0]; - height = origHeight = charPtr[1]; - - if (_disableOffsX) { - offsX = 0; - } else { - offsX = (signed char)charPtr[2]; - } - - offsY = (signed char)charPtr[3]; - - charPtr += 4; // Skip over char header - } + prepareDraw(chr); if (_firstChar) { _str.left = 0; @@ -977,12 +730,12 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { _str.bottom = 0; } - _top += offsY; - _left += offsX; + _top += _offsY; + _left += _offsX; - if (_left + origWidth > _right + 1 || _left < 0) { - _left += origWidth; - _top -= offsY; + if (_left + _origWidth > _right + 1 || _left < 0) { + _left += _origWidth; + _top -= _offsY; return; } @@ -1004,33 +757,29 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { int drawTop = _top - vs->topline; - _vm->markRectAsDirty(vs->number, _left, _left + width, drawTop, drawTop + height); + _vm->markRectAsDirty(vs->number, _left, _left + _width, drawTop, drawTop + _height); // This check for kPlatformFMTowns and kMainVirtScreen is at least required for the chat with // the navigator's head in front of the ghost ship in Monkey Island 1 - if (!ignoreCharsetMask -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - || (_vm->_game.platform == Common::kPlatformFMTowns && vs->number == kMainVirtScreen) -#endif - ) { + if (!ignoreCharsetMask || (_vm->_game.platform == Common::kPlatformFMTowns && vs->number == kMainVirtScreen)) { _hasMask = true; _textScreenID = vs->number; } - printCharIntern(is2byte, charPtr, origWidth, origHeight, width, height, vs, ignoreCharsetMask); + printCharIntern(is2byte, _charPtr, _origWidth, _origHeight, _width, _height, vs, ignoreCharsetMask); - _left += origWidth; + _left += _origWidth; if (_str.right < _left) { _str.right = _left; - if (_vm->_game.platform != Common::kPlatformFMTowns && _shadowMode != kNoShadowMode) + if (_vm->_game.platform != Common::kPlatformFMTowns && _shadowMode) _str.right++; } - if (_str.bottom < _top + origHeight) - _str.bottom = _top + origHeight; + if (_str.bottom < _top + _origHeight) + _str.bottom = _top + _origHeight; - _top -= offsY; + _top -= _offsY; } void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask) { @@ -1068,11 +817,7 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, } else { Graphics::Surface dstSurface; Graphics::Surface backSurface; - if ((ignoreCharsetMask || !vs->hasTwoBuffers) -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - && (_vm->_game.platform != Common::kPlatformFMTowns) -#endif - ) { + if ((ignoreCharsetMask || !vs->hasTwoBuffers)) { dstSurface = *vs; dstPtr = vs->getPixels(_left, drawTop); } else { @@ -1091,16 +836,7 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, drawTop = _top - _vm->_screenTop; } -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (!charPtr && _vm->_cjkFont) { - _vm->_cjkFont->drawChar(dstSurface, _vm->_cjkChar, _left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier, _vm->_townsCharsetColorMap[1], _shadowColor); - } else -#endif - if (is2byte) { - drawBits1(dstSurface, dstPtr, charPtr, drawTop, origWidth, origHeight, dstSurface.format.bytesPerPixel); - } else { - drawBitsN(dstSurface, dstPtr, charPtr, *_fontPtr, drawTop, origWidth, origHeight, _vm->_textSurfaceMultiplier == 2); - } + drawBitsN(dstSurface, dstPtr, charPtr, *_fontPtr, drawTop, origWidth, origHeight); if (_blitAlso && vs->hasTwoBuffers) { // FIXME: Revisiting this code, I think the _blitAlso mode is likely broken @@ -1139,54 +875,34 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, } } -void CharsetRendererClassic::drawChar(int chr, Graphics::Surface &s, int x, int y) { - const byte *charPtr; - byte *dst; - int width, height; - int is2byte = (chr >= 0x80 && _vm->_useCJKMode) ? 1 : 0; - - if (is2byte) { - enableShadow(true); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _vm->_cjkFont->drawChar(s, chr, x * _vm->_textSurfaceMultiplier, y * _vm->_textSurfaceMultiplier, _color, _shadowColor); - return; - } else -#endif - { - charPtr = _vm->get2byteCharPtr(chr); - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - } - } else { - uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - assert(charOffs < 0x10000); - if (!charOffs) - return; - charPtr = _fontPtr + charOffs; +void CharsetRendererClassic::prepareDraw(uint16 chr) { + uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + assert(charOffs < 0x14000); + if (!charOffs) + return; + _charPtr = _fontPtr + charOffs; - width = charPtr[0]; - height = charPtr[1]; + _width = _origWidth = _charPtr[0]; + _height = _origHeight = _charPtr[1]; - charPtr += 4; // Skip over char header + if (_disableOffsX) { + _offsX = 0; + } else { + _offsX = (signed char)_charPtr[2]; } - dst = (byte *)s.pixels + y * s.pitch + x; + _offsY = (signed char)_charPtr[3]; - if (is2byte) { - drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); - } else { - drawBitsN(s, dst, charPtr, *_fontPtr, y, width, height); - } + _charPtr += 4; // Skip over char header } -void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height, -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - bool scale2x) { -#else - bool) { -#endif +void CharsetRendererClassic::drawChar(int chr, Graphics::Surface &s, int x, int y) { + prepareDraw(chr); + byte *dst = (byte *)s.pixels + y * s.pitch + x; + drawBitsN(s, dst, _charPtr, *_fontPtr, y, _width, _height); +} +void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) { int y, x; int color; byte numbits, bits; @@ -1198,38 +914,13 @@ void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, co numbits = 8; byte *cmap = _vm->_charsetColorMap; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - byte *dst2 = dst; - - if (_vm->_game.platform == Common::kPlatformFMTowns) - cmap = _vm->_townsCharsetColorMap; - if (scale2x) { - dst2 += s.pitch; - pitch <<= 1; - } -#endif - for (y = 0; y < height && y + drawTop < s.h; y++) { for (x = 0; x < width; x++) { color = (bits >> (8 - bpp)) & 0xFF; - if (color && y + drawTop >= 0) { + if (color && y + drawTop >= 0) *dst = cmap[color]; - -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (scale2x) - dst[1] = dst2[0] = dst2[1] = dst[0]; -#endif - } dst++; - -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (scale2x) { - dst++; - dst2 += 2; - } -#endif - bits <<= bpp; numbits -= bpp; if (numbits == 0) { @@ -1238,52 +929,93 @@ void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, co } } dst += pitch; + } +} + +CharsetRendererTownsV3::CharsetRendererTownsV3(ScummEngine *vm) : CharsetRendererV3(vm), _sjisCurChar(0) { +} + +int CharsetRendererTownsV3::getCharWidth(uint16 chr) { + int spacing = 0; + + if (_vm->_useCJKMode) { + if (chr >= 256) + spacing = 8; + else if (chr >= 128) + spacing = 4; + } + + if (!spacing) + spacing = *(_widthTable + chr); + + return spacing; +} + +int CharsetRendererTownsV3::getFontHeight() { + return _vm->_useCJKMode ? 8 : _fontHeight; +} + +void CharsetRendererTownsV3::enableShadow(bool enable) { + _shadowColor = 8; + _shadowMode = enable; + #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - dst2 += pitch; + _shadowColor = 0x88; +#ifdef USE_RGB_COLOR + if (_vm->_cjkFont) + _vm->_cjkFont->setDrawingMode(enable ? Graphics::FontSJIS::kFMTownsShadowMode : Graphics::FontSJIS::kDefaultMode); +#endif #endif - } } -void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, +void CharsetRendererTownsV3::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - bool scale2x) { +#ifdef USE_RGB_COLOR + if (_sjisCurChar) { + assert(_vm->_cjkFont); + _vm->_cjkFont->drawChar(_vm->_textSurface, _sjisCurChar, _left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier, _color, _shadowColor); + return; + } +#endif + + dst = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier); + int sfPitch = _vm->_textSurface.pitch; + int sfHeight = _vm->_textSurface.h; + bool scale2x = (_vm->_textSurfaceMultiplier == 2 && !(_sjisCurChar >= 256 && _vm->_useCJKMode)); #else - bool) { + int sfPitch = s.pitch; + int sfHeight = s.h; #endif int y, x; byte bits = 0; uint8 col = _color; - int pitch = s.pitch - width * bitDepth; - byte *dst2 = dst + s.pitch; + int pitch = sfPitch - width * bitDepth; + byte *dst2 = dst + sfPitch; #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE byte *dst3 = dst2; byte *dst4 = dst2; if (scale2x) { - dst3 = dst2 + s.pitch; - dst4 = dst3 + s.pitch; + dst3 = dst2 + sfPitch; + dst4 = dst3 + sfPitch; pitch <<= 1; } - if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_game.version == 5) - col = _vm->_townsCharsetColorMap[1]; #endif - for (y = 0; y < height && y + drawTop < s.h; y++) { + for (y = 0; y < height && y + drawTop < sfHeight; y++) { for (x = 0; x < width; x++) { if ((x % 8) == 0) bits = *src++; if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) { if (bitDepth == 2) { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { WRITE_UINT16(dst + 2, _vm->_16BitPalette[_shadowColor]); - WRITE_UINT16(dst + s.pitch, _vm->_16BitPalette[_shadowColor]); - if (_shadowMode != kFMTOWNSShadowMode) - WRITE_UINT16(dst + s.pitch + 2, _vm->_16BitPalette[_shadowColor]); + WRITE_UINT16(dst + sfPitch, _vm->_16BitPalette[_shadowColor]); } WRITE_UINT16(dst, _vm->_16BitPalette[_color]); } else { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (scale2x) { dst[2] = dst[3] = dst2[2] = dst2[3] = _shadowColor; @@ -1292,8 +1024,6 @@ void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, con #endif { dst[1] = dst2[0] = _shadowColor; - if (_shadowMode != kFMTOWNSShadowMode) - dst2[1] = _shadowColor; } } dst[0] = col; @@ -1324,31 +1054,64 @@ void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, con #endif } } +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +int CharsetRendererTownsV3::getDrawWidthIntern(uint16 chr) { +#ifdef USE_RGB_COLOR + if (_vm->_useCJKMode && chr > 127) { + assert(_vm->_cjkFont); + return _vm->_cjkFont->getCharWidth(chr); + } +#endif + return CharsetRendererV3::getDrawWidthIntern(chr); +} +int CharsetRendererTownsV3::getDrawHeightIntern(uint16 chr) { #ifdef USE_RGB_COLOR -void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scalex) { + if (_vm->_useCJKMode && chr > 127) { + assert(_vm->_cjkFont); + return _vm->_cjkFont->getFontHeight(); + } +#endif + return CharsetRendererV3::getDrawHeightIntern(chr); +} + +void CharsetRendererTownsV3::setDrawCharIntern(uint16 chr) { + _sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0; +} +#endif + +#ifdef USE_RGB_COLOR +void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { + if (_sjisCurChar) { + assert(_vm->_cjkFont); + uint16 col1 = _color; + uint16 col2 = _shadowColor; + + if (s.format.bytesPerPixel == 2) { + col1 = _vm->_16BitPalette[col1]; + col2 = _vm->_16BitPalette[col2]; + } + + _vm->_cjkFont->drawChar(dst, _sjisCurChar, s.pitch, s.format.bytesPerPixel, col1, col2, -1, -1); + return; + } + int y, x; - int bitCount = 0; byte bits = 0; - const bool resetLineBitCount = (_vm->_language != Common::JA_JPN || width != 12); - for (y = 0; y < height && y + drawTop < s.h; y++) { - if (resetLineBitCount) - bitCount = 0; + int bitCount = 0; for (x = 0; x < width; x++) { if ((bitCount % 8) == 0) bits = *src++; if ((bits & revBitMask(bitCount % 8)) && y + drawTop >= 0) { if (bitDepth == 2) { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) WRITE_UINT16(dst + s.pitch + 2, _vm->_16BitPalette[_shadowColor]); - } WRITE_UINT16(dst, _vm->_16BitPalette[_color]); } else { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) *(dst + s.pitch + 1) = _shadowColor; - } *dst = _color; } } @@ -1359,6 +1122,22 @@ void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const dst += s.pitch - width * bitDepth; } } + +int CharsetRendererPCE::getDrawWidthIntern(uint16 chr) { + if (_vm->_useCJKMode && chr > 127) + return _vm->_2byteWidth; + return CharsetRendererV3::getDrawWidthIntern(chr); +} + +int CharsetRendererPCE::getDrawHeightIntern(uint16 chr) { + if (_vm->_useCJKMode && chr > 127) + return _vm->_2byteHeight; + return CharsetRendererV3::getDrawHeightIntern(chr); +} + +void CharsetRendererPCE::setDrawCharIntern(uint16 chr) { + _sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0; +} #endif #ifdef ENABLE_SCUMM_7_8 @@ -1533,7 +1312,7 @@ void CharsetRendererNES::printChar(int chr, bool ignoreCharsetMask) { if (_str.right < _left) { _str.right = _left; - if (_shadowMode != kNoShadowMode) + if (_shadowMode) _str.right++; } @@ -1556,7 +1335,203 @@ void CharsetRendererNES::drawChar(int chr, Graphics::Surface &s, int x, int y) { drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); } -void CharsetRendererNES::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scalex) { +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +CharsetRendererTownsClassic::CharsetRendererTownsClassic(ScummEngine *vm) : CharsetRendererClassic(vm), _sjisCurChar(0) { +} + +int CharsetRendererTownsClassic::getCharWidth(uint16 chr) { + int spacing = 0; + + if (_vm->_useCJKMode) { + if ((chr & 0xff00) == 0xfd00) { + chr &= 0xff; + } else if (chr >= 256) { + spacing = 8; + } else if (useFontRomCharacter(chr)) { + spacing = 4; + } + + if (spacing) { + if (_vm->_game.id == GID_MONKEY) { + spacing++; + if (_curId == 2) + spacing++; + } else if (_vm->_game.id != GID_INDY4 && _curId == 1) { + spacing++; + } + } + } + + if (!spacing) { + int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + if (offs) + spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; + } + + return spacing; +} + +int CharsetRendererTownsClassic::getFontHeight() { + static const uint8 sjisFontHeightM1[] = { 0, 8, 9, 8, 9, 8, 9, 0, 0, 0 }; + static const uint8 sjisFontHeightM2[] = { 0, 8, 9, 9, 9, 8, 9, 9, 9, 8 }; + static const uint8 sjisFontHeightI4[] = { 0, 8, 9, 9, 9, 8, 8, 8, 8, 8 }; + const uint8 *htbl = (_vm->_game.id == GID_MONKEY) ? sjisFontHeightM1 : ((_vm->_game.id == GID_INDY4) ? sjisFontHeightI4 : sjisFontHeightM2); + return _vm->_useCJKMode ? htbl[_curId] : _fontHeight; +} + +void CharsetRendererTownsClassic::drawBitsN(const Graphics::Surface&, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) { + if (_sjisCurChar) { + assert(_vm->_cjkFont); + _vm->_cjkFont->drawChar(_vm->_textSurface, _sjisCurChar, _left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier, _vm->_townsCharsetColorMap[1], _shadowColor); + return; + } + + bool scale2x = (_vm->_textSurfaceMultiplier == 2); + dst = (byte *)_vm->_textSurface.pixels + (_top - _vm->_screenTop) * _vm->_textSurface.pitch * _vm->_textSurfaceMultiplier + _left * _vm->_textSurfaceMultiplier; + + int y, x; + int color; + byte numbits, bits; + + int pitch = _vm->_textSurface.pitch - width; + + assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8); + bits = *src++; + numbits = 8; + byte *cmap = _vm->_charsetColorMap; + byte *dst2 = dst; + + if (_vm->_game.platform == Common::kPlatformFMTowns) + cmap = _vm->_townsCharsetColorMap; + if (scale2x) { + dst2 += _vm->_textSurface.pitch; + pitch <<= 1; + } + + for (y = 0; y < height && y + drawTop < _vm->_textSurface.h; y++) { + for (x = 0; x < width; x++) { + color = (bits >> (8 - bpp)) & 0xFF; + + if (color && y + drawTop >= 0) { + *dst = cmap[color]; + if (scale2x) + dst[1] = dst2[0] = dst2[1] = dst[0]; + } + dst++; + + if (scale2x) { + dst++; + dst2 += 2; + } + + bits <<= bpp; + numbits -= bpp; + if (numbits == 0) { + bits = *src++; + numbits = 8; + } + } + dst += pitch; + dst2 += pitch; + } +} + +void CharsetRendererTownsClassic::prepareDraw(uint16 chr) { + processCharsetColors(); + bool noSjis = false; + + if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_useCJKMode) { + if ((chr & 0x00ff) == 0x00fd) { + chr >>= 8; + noSjis = true; + } + } + + if (useFontRomCharacter(chr) && !noSjis) { + setupShadowMode(); + _charPtr = 0; + _sjisCurChar = chr; + + _width = getCharWidth(chr); + // For whatever reason MI1 uses a different font width + // for alignment calculation and for drawing when + // charset 2 is active. This fixes some subtle glitches. + if (_vm->_game.id == GID_MONKEY && _curId == 2) + _width--; + _origWidth = _width; + + _origHeight = _height = getFontHeight(); + _offsX = _offsY = 0; + } else if (_vm->_useCJKMode && (chr >= 128) && !noSjis) { + setupShadowMode(); + _origWidth = _width = _vm->_2byteWidth; + _origHeight = _height = _vm->_2byteHeight; + _charPtr = _vm->get2byteCharPtr(chr); + _offsX = _offsY = 0; + if (_shadowMode) { + _width++; + _height++; + } + } else { + _sjisCurChar = 0; + CharsetRendererClassic::prepareDraw(chr); + } +} + +void CharsetRendererTownsClassic::setupShadowMode() { + _shadowMode = true; + _shadowColor = _vm->_townsCharsetColorMap[0]; + assert(_vm->_cjkFont); + + if (((_vm->_game.id == GID_MONKEY) && (_curId == 2 || _curId == 4 || _curId == 6)) || + ((_vm->_game.id == GID_MONKEY2) && (_curId != 1 && _curId != 5 && _curId != 9)) || + ((_vm->_game.id == GID_INDY4) && (_curId == 2 || _curId == 3 || _curId == 4))) { + _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kOutlineMode); + } else { + _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); + } + + _vm->_cjkFont->toggleFlippedMode((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && _curId == 3); +} + +bool CharsetRendererTownsClassic::useFontRomCharacter(uint16 chr) { + if (!_vm->_useCJKMode) + return false; + + // Some SCUMM 5 games contain hard coded logic to determine whether to use + // the SCUMM fonts or the FM-Towns font rom to draw a character. For the other + // games we will simply check for a character greater 127. + if (chr < 128) { + if (((_vm->_game.id == GID_MONKEY2 && _curId != 0) || (_vm->_game.id == GID_INDY4 && _curId != 3)) && (chr > 31 && chr != 94 && chr != 95 && chr != 126 && chr != 127)) + return true; + return false; + } + return true; +} + +void CharsetRendererTownsClassic::processCharsetColors() { + for (int i = 0; i < (1 << _bytesPerPixel); i++) { + uint8 c = _vm->_charsetColorMap[i]; + + if (c > 16) { + uint8 t = (_vm->_currentPalette[c * 3] < 32) ? 4 : 12; + t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 2 : 10); + t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 1 : 9); + c = t; + } + + if (c == 0) + c = _vm->_townsOverrideShadowColor; + + c = ((c & 0x0f) << 4) | (c & 0x0f); + _vm->_townsCharsetColorMap[i] = c; + } +} +#endif +#endif + +void CharsetRendererNES::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { for (int i = 0; i < 8; i++) { byte c0 = src[i]; byte c1 = src[i + 8]; diff --git a/engines/scumm/charset.h b/engines/scumm/charset.h index 4c657b475e..b80db73a58 100644 --- a/engines/scumm/charset.h +++ b/engines/scumm/charset.h @@ -25,6 +25,7 @@ #include "common/scummsys.h" #include "common/rect.h" #include "graphics/sjis.h" +#include "scumm/scumm.h" #include "scumm/gfx.h" #include "scumm/saveload.h" @@ -78,10 +79,6 @@ public: void addLinebreaks(int a, byte *str, int pos, int maxwidth); void translateColor(); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - void processTownsCharsetColors(uint8 bytesPerPixel); -#endif - virtual void setCurID(int32 id) = 0; int getCurID() { return _curId; } @@ -101,31 +98,26 @@ protected: int _fontHeight; int _numChars; - enum ShadowMode { - kNoShadowMode, - kFMTOWNSShadowMode, - kNormalShadowMode - }; byte _shadowColor; - ShadowMode _shadowMode; - - void enableShadow(bool enable); - virtual void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); - + bool _shadowMode; public: CharsetRendererCommon(ScummEngine *vm); void setCurID(int32 id); - int getFontHeight(); + virtual int getFontHeight(); }; class CharsetRendererClassic : public CharsetRendererCommon { protected: - void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height, bool scale2x = false); + virtual void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height); + void printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask); + virtual void prepareDraw(uint16 chr); - void printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask); + int _width, _height, _origWidth, _origHeight; + int _offsX, _offsY; + const byte *_charPtr; public: CharsetRendererClassic(ScummEngine *vm) : CharsetRendererCommon(vm) {} @@ -134,18 +126,34 @@ public: void drawChar(int chr, Graphics::Surface &s, int x, int y); int getCharWidth(uint16 chr); +}; + +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +class CharsetRendererTownsClassic : public CharsetRendererClassic { +public: + CharsetRendererTownsClassic(ScummEngine *vm); + + int getCharWidth(uint16 chr); + int getFontHeight(); - // Some SCUMM 5 games contain hard coded logic to determine whether to use - // the SCUMM fonts or the FM-Towns font rom to draw a character. For the other - // games we will simply check for a character greater 127. - bool useTownsFontRomCharacter(uint16 chr); +private: + void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height); + void prepareDraw(uint16 chr); + void setupShadowMode(); + bool useFontRomCharacter(uint16 chr); + void processCharsetColors(); + + uint16 _sjisCurChar; }; +#endif +#endif class CharsetRendererNES : public CharsetRendererCommon { protected: byte *_trTable; - void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); public: CharsetRendererNES(ScummEngine *vm) : CharsetRendererCommon(vm) {} @@ -160,6 +168,12 @@ public: class CharsetRendererV3 : public CharsetRendererCommon { protected: + virtual void enableShadow(bool enable); + virtual void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); + virtual int getDrawWidthIntern(uint16 chr); + virtual int getDrawHeightIntern(uint16 chr); + virtual void setDrawCharIntern(uint16 chr) {} + const byte *_widthTable; public: @@ -169,16 +183,40 @@ public: void drawChar(int chr, Graphics::Surface &s, int x, int y); void setCurID(int32 id); void setColor(byte color); + virtual int getCharWidth(uint16 chr); +}; + +class CharsetRendererTownsV3 : public CharsetRendererV3 { +public: + CharsetRendererTownsV3(ScummEngine *vm); + int getCharWidth(uint16 chr); + int getFontHeight(); + +private: + void enableShadow(bool enable); + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE + int getDrawWidthIntern(uint16 chr); + int getDrawHeightIntern(uint16 chr); + void setDrawCharIntern(uint16 chr); +#endif + uint16 _sjisCurChar; }; #ifdef USE_RGB_COLOR class CharsetRendererPCE : public CharsetRendererV3 { -protected: - void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); +private: + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); + + int getDrawWidthIntern(uint16 chr); + int getDrawHeightIntern(uint16 chr); + void setDrawCharIntern(uint16 chr); + + uint16 _sjisCurChar; public: - CharsetRendererPCE(ScummEngine *vm) : CharsetRendererV3(vm) {} + CharsetRendererPCE(ScummEngine *vm) : CharsetRendererV3(vm), _sjisCurChar(0) {} void setColor(byte color); }; diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index e510c46cf2..11901f7565 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -243,11 +243,11 @@ static const GameSettings gameVariantsTable[] = { {"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO_NOSPEECH | GUIO_NOMIDI}, - {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, - {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, + {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, + {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, - {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE}, - {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, + {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE}, + {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, {"atlantis", "FM-TOWNS", 0, GID_INDY4, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, {"tentacle", "", 0, GID_TENTACLE, 6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO_NONE}, diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 317ef36cb9..451721cf70 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -44,30 +44,31 @@ namespace Scumm { //////////////////////////////////////// IMuseInternal::IMuseInternal() : -_native_mt32(false), -_enable_gs(false), -_sc55(false), -_midi_adlib(NULL), -_midi_native(NULL), -_sysex(NULL), -_paused(false), -_initialized(false), -_tempoFactor(0), -_player_limit(ARRAYSIZE(_players)), -_recycle_players(false), -_queue_end(0), -_queue_pos(0), -_queue_sound(0), -_queue_adding(0), -_queue_marker(0), -_queue_cleared(0), -_master_volume(0), -_music_volume(0), -_trigger_count(0), -_snm_trigger_index(0) { - memset(_channel_volume,0,sizeof(_channel_volume)); - memset(_channel_volume_eff,0,sizeof(_channel_volume_eff)); - memset(_volchan_table,0,sizeof(_volchan_table)); + _native_mt32(false), + _enable_gs(false), + _sc55(false), + _midi_adlib(NULL), + _midi_native(NULL), + _sysex(NULL), + _paused(false), + _initialized(false), + _tempoFactor(0), + _player_limit(ARRAYSIZE(_players)), + _recycle_players(false), + _queue_end(0), + _queue_pos(0), + _queue_sound(0), + _queue_adding(0), + _queue_marker(0), + _queue_cleared(0), + _master_volume(0), + _music_volume(0), + _trigger_count(0), + _snm_trigger_index(0), + _pcSpeaker(false) { + memset(_channel_volume, 0, sizeof(_channel_volume)); + memset(_channel_volume_eff, 0, sizeof(_channel_volume_eff)); + memset(_volchan_table, 0, sizeof(_volchan_table)); } IMuseInternal::~IMuseInternal() { @@ -119,7 +120,7 @@ byte *IMuseInternal::findStartOfSound(int sound, int ct) { // Check for old-style headers first, like 'RO' int trFlag = (kMThd | kFORM); - if (ptr[0] == 'R' && ptr[1] == 'O'&& ptr[2] != 'L') + if (ptr[0] == 'R' && ptr[1] == 'O' && ptr[2] != 'L') return ct == trFlag ? ptr : 0; if (ptr[4] == 'S' && ptr[5] == 'O') return ct == trFlag ? ptr + 4 : 0; @@ -153,22 +154,22 @@ bool IMuseInternal::isMT32(int sound) { uint32 tag = READ_BE_UINT32(ptr); switch (tag) { - case MKTAG('A','D','L',' '): - case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects - case MKTAG('S','P','K',' '): + case MKTAG('A', 'D', 'L', ' '): + case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects + case MKTAG('S', 'P', 'K', ' '): return false; - case MKTAG('A','M','I',' '): - case MKTAG('R','O','L',' '): + case MKTAG('A', 'M', 'I', ' '): + case MKTAG('R', 'O', 'L', ' '): return true; - case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2 + case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 return true; - case MKTAG('G','M','D',' '): + case MKTAG('G', 'M', 'D', ' '): return false; - case MKTAG('M','I','D','I'): // Occurs in Sam & Max + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max // HE games use Roland music if (ptr[8] == 'H' && ptr[9] == 'S') return true; @@ -195,20 +196,20 @@ bool IMuseInternal::isMIDI(int sound) { uint32 tag = READ_BE_UINT32(ptr); switch (tag) { - case MKTAG('A','D','L',' '): - case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects - case MKTAG('S','P','K',' '): + case MKTAG('A', 'D', 'L', ' '): + case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects + case MKTAG('S', 'P', 'K', ' '): return false; - case MKTAG('A','M','I',' '): - case MKTAG('R','O','L',' '): + case MKTAG('A', 'M', 'I', ' '): + case MKTAG('R', 'O', 'L', ' '): return true; - case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2 + case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 return true; - case MKTAG('G','M','D',' '): - case MKTAG('M','I','D','I'): // Occurs in Sam & Max + case MKTAG('G', 'M', 'D', ' '): + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max return true; } @@ -381,7 +382,8 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { for (i = 0; i < ARRAYSIZE(_parts); ++i) _parts[i].saveLoadWithSerializer(ser); - { // Load/save the instrument definitions, which were revamped with V11. + { + // Load/save the instrument definitions, which were revamped with V11. Part *part = &_parts[0]; if (ser->getVersion() >= VER(11)) { for (i = ARRAYSIZE(_parts); i; --i, ++part) { @@ -467,6 +469,10 @@ uint32 IMuseInternal::property(int prop, uint32 value) { case IMuse::PROP_GAME_ID: _game_id = value; break; + + case IMuse::PROP_PC_SPEAKER: + _pcSpeaker = (value != 0); + break; } return 0; @@ -522,7 +528,7 @@ void IMuseInternal::stopAllSounds() { int IMuseInternal::getSoundStatus(int sound) const { Common::StackLock lock(_mutex, "IMuseInternal::getSoundStatus()"); - return getSoundStatus_internal (sound, true); + return getSoundStatus_internal(sound, true); } int IMuseInternal::getMusicTimer() { @@ -565,7 +571,7 @@ bool IMuseInternal::startSound_internal(int sound, int offset) { int i; ImTrigger *trigger = _snm_triggers; for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) { - if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal (trigger->sound,true)) + if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal(trigger->sound, true)) return false; } @@ -663,9 +669,7 @@ int IMuseInternal::getSoundStatus_internal(int sound, bool ignoreFadeouts) const return (sound == -1) ? 0 : get_queue_sound_status(sound); } -int32 IMuseInternal::doCommand_internal - (int a, int b, int c, int d, int e, int f, int g, int h) -{ +int32 IMuseInternal::doCommand_internal(int a, int b, int c, int d, int e, int f, int g, int h) { int args[8]; args[0] = a; args[1] = b; @@ -733,7 +737,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) { } return -1; case 13: - return getSoundStatus_internal (a[1], true); + return getSoundStatus_internal(a[1], true); case 14: // Sam and Max: Parameter fade player = findActivePlayer(a[1]); @@ -779,8 +783,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) { a[0] = 0; for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { if (_snm_triggers[i].sound == a[1] && _snm_triggers[i].id && - (a[3] == -1 || _snm_triggers[i].id == a[3])) - { + (a[3] == -1 || _snm_triggers[i].id == a[3])) { ++a[0]; } } @@ -1002,9 +1005,9 @@ int IMuseInternal::get_queue_sound_status(int sound) const { i = (i + 1) % ARRAYSIZE(_cmd_queue); } - for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) { + for (i = 0; i < ARRAYSIZE(_deferredCommands); ++i) { if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 && - _deferredCommands[i].b == sound) { + _deferredCommands[i].b == sound) { return 2; } } @@ -1213,7 +1216,7 @@ int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, // NOTE: We ONLY do this if the sound that will trigger the command is actually // playing. Otherwise, there's a problem when exiting and re-entering the // Bumpusville mansion. Ref Bug #780918. - if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1],true) && getSoundStatus_internal(sound,true)) + if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1], true) && getSoundStatus_internal(sound, true)) stopSound_internal(trig->command[1]); return 0; } @@ -1246,8 +1249,7 @@ int32 IMuseInternal::ImFireAllTriggers(int sound) { return (count > 0) ? 0 : -1; } -int IMuseInternal::set_channel_volume(uint chan, uint vol) -{ +int IMuseInternal::set_channel_volume(uint chan, uint vol) { if (chan >= 8 || vol > 127) return -1; @@ -1427,7 +1429,7 @@ void IMuseInternal::initMT32(MidiDriver *midi) { // Display a welcome message on MT-32 displays. memcpy(&buffer[0], "\x41\x10\x16\x12\x20\x00\x00", 7); memcpy(&buffer[7], " ", 20); - memcpy(buffer + 7 +(20 - len) / 2, info, len); + memcpy(buffer + 7 + (20 - len) / 2, info, len); byte checksum = 0; for (int i = 4; i < 27; ++i) checksum -= buffer[i]; @@ -1473,9 +1475,9 @@ void IMuseInternal::initGM(MidiDriver *midi) { // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation for (i = 0; i < 16; ++i) { - midi->send(( 127 << 16) | (0 << 8) | (0xB0 | i)); - midi->send(( 1 << 16) | (32 << 8) | (0xB0 | i)); - midi->send(( 0 << 16) | (0 << 8) | (0xC0 | i)); + midi->send((127 << 16) | (0 << 8) | (0xB0 | i)); + midi->send((1 << 16) | (32 << 8) | (0xB0 | i)); + midi->send((0 << 16) | (0 << 8) | (0xC0 | i)); } debug(2, "GS Program Change: CM-64/32L Map Selected"); @@ -1496,7 +1498,7 @@ void IMuseInternal::initGM(MidiDriver *midi) { // Set Channels 1-16 Reverb to 64, which is the // equivalent of MT-32 default Reverb Level 5 for (i = 0; i < 16; ++i) - midi->send(( 64 << 16) | (91 << 8) | (0xB0 | i)); + midi->send((64 << 16) | (91 << 8) | (0xB0 | i)); debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64"); // Set Channels 1-16 Pitch Bend Sensitivity to @@ -1637,8 +1639,8 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { hipart = NULL; for (i = 32, part = _parts; i; i--, part++) { if (part->_player && part->_player->getMidiDriver() == midi && - !part->_percussion && part->_on && - !part->_mc && part->_pri_eff >= hipri) { + !part->_percussion && part->_on && + !part->_mc && part->_pri_eff >= hipri) { hipri = part->_pri_eff; hipart = part; } @@ -1668,16 +1670,19 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { } } -void IMuseInternal::setGlobalAdLibInstrument(byte slot, byte *data) { +void IMuseInternal::setGlobalInstrument(byte slot, byte *data) { if (slot < 32) { - _global_adlib_instruments[slot].adlib(data); + if (_pcSpeaker) + _global_instruments[slot].pcspk(data); + else + _global_instruments[slot].adlib(data); } } -void IMuseInternal::copyGlobalAdLibInstrument(byte slot, Instrument *dest) { +void IMuseInternal::copyGlobalInstrument(byte slot, Instrument *dest) { if (slot >= 32) return; - _global_adlib_instruments[slot].copy_to(dest); + _global_instruments[slot].copy_to(dest); } diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 8014b13409..23449e470b 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -37,7 +37,7 @@ class Player; class ScummEngine; class Serializer; -typedef void (*sysexfunc) (Player *, const byte *, uint16); +typedef void (*sysexfunc)(Player *, const byte *, uint16); /** * iMuse implementation interface. @@ -55,7 +55,8 @@ public: PROP_GS, PROP_LIMIT_PLAYERS, PROP_RECYCLE_PLAYERS, - PROP_GAME_ID + PROP_GAME_ID, + PROP_PC_SPEAKER }; public: @@ -66,7 +67,7 @@ public: virtual int32 doCommand(int numargs, int args[]) = 0; virtual int clear_queue() = 0; virtual uint32 property(int prop, uint32 value) = 0; - virtual void addSysexHandler (byte mfgID, sysexfunc handler) = 0; + virtual void addSysexHandler(byte mfgID, sysexfunc handler) = 0; public: virtual void startSoundWithNoteOffset(int sound, int offset) = 0; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 6a7b9fc7d9..3b0d36e119 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -135,7 +135,7 @@ struct ImTrigger { int sound; byte id; uint16 expire; - int command [8]; + int command[8]; ImTrigger() { memset(this, 0, sizeof(ImTrigger)); } }; @@ -153,12 +153,12 @@ struct CommandQueue { ////////////////////////////////////////////////// class Player : public MidiDriver_BASE { -/* - * External SysEx handler functions shall each be defined in - * a separate file. This header file shall be included at the - * top of the file immediately following this special #define: - * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction - */ + /* + * External SysEx handler functions shall each be defined in + * a separate file. This header file shall be included at the + * top of the file immediately following this special #define: + * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction + */ #ifdef SYSEX_CALLBACK_FUNCTION friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16); #endif @@ -244,7 +244,7 @@ public: void clear(); void clearLoop(); void fixAfterLoad(); - Part * getActivePart(uint8 part); + Part *getActivePart(uint8 part); uint getBeatIndex(); int8 getDetune() const { return _detune; } byte getEffectiveVolume() const { return _vol_eff; } @@ -252,7 +252,7 @@ public: MidiDriver *getMidiDriver() const { return _midi; } int getParam(int param, byte chan); int8 getPan() const { return _pan; } - Part * getPart(uint8 part); + Part *getPart(uint8 part); byte getPriority() const { return _priority; } uint getTicksPerBeat() const { return TICKS_PER_BEAT; } int8 getTranspose() const { return _transpose; } @@ -342,6 +342,7 @@ struct Part : public Serializable { void off(); void set_instrument(uint b); void set_instrument(byte *data); + void set_instrument_pcspk(byte *data); void load_global_instrument(byte b); void set_transpose(int8 transpose); @@ -375,12 +376,12 @@ class IMuseInternal : public IMuse { friend class Player; friend struct Part; -/* - * External SysEx handler functions shall each be defined in - * a separate file. This header file shall be included at the - * top of the file immediately following this special #define: - * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction - */ + /* + * External SysEx handler functions shall each be defined in + * a separate file. This header file shall be included at the + * top of the file immediately following this special #define: + * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction + */ #ifdef SYSEX_CALLBACK_FUNCTION friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16); #endif @@ -433,7 +434,8 @@ protected: Player _players[8]; Part _parts[32]; - Instrument _global_adlib_instruments[32]; + bool _pcSpeaker; + Instrument _global_instruments[32]; CommandQueue _cmd_queue[64]; DeferredCommand _deferredCommands[4]; @@ -449,8 +451,8 @@ protected: enum ChunkType { kMThd = 1, kFORM = 2, - kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player. - kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however. + kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player. + kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however. }; byte *findStartOfSound(int sound, int ct = (kMThd | kFORM)); @@ -498,8 +500,8 @@ protected: int setImuseMasterVolume(uint vol); void reallocateMidiChannels(MidiDriver *midi); - void setGlobalAdLibInstrument(byte slot, byte *data); - void copyGlobalAdLibInstrument(byte slot, Instrument *dest); + void setGlobalInstrument(byte slot, byte *data); + void copyGlobalInstrument(byte slot, Instrument *dest); bool isNativeMT32() { return _native_mt32; } protected: diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp index 5df8407a96..73e7704469 100644 --- a/engines/scumm/imuse/imuse_part.cpp +++ b/engines/scumm/imuse/imuse_part.cpp @@ -193,14 +193,18 @@ void Part::set_onoff(bool on) { } } -void Part::set_instrument(byte * data) { - _instrument.adlib(data); +void Part::set_instrument(byte *data) { + if (_se->_pcSpeaker) + _instrument.pcspk(data); + else + _instrument.adlib(data); + if (clearToTransmit()) _instrument.send(_mc); } void Part::load_global_instrument(byte slot) { - _player->_se->copyGlobalAdLibInstrument(slot, &_instrument); + _player->_se->copyGlobalInstrument(slot, &_instrument); if (clearToTransmit()) _instrument.send(_mc); } @@ -234,7 +238,7 @@ void Part::noteOn(byte note, byte velocity) { // should be implemented as a class static var. As it is, using // a function level static var in most cases is arcane and evil. static byte prev_vol_eff = 128; - if (_vol_eff != prev_vol_eff){ + if (_vol_eff != prev_vol_eff) { mc->volume(_vol_eff); prev_vol_eff = _vol_eff; } diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index 61b9cad2cb..73be2174cd 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -79,7 +79,7 @@ Player::Player() : _isMT32(false), _isMIDI(false), _se(0), - _vol_chan(0){ + _vol_chan(0) { } Player::~Player() { @@ -133,7 +133,7 @@ bool Player::isFadingOut() const { int i; for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) { if (_parameterFaders[i].param == ParameterFader::pfVolume && - _parameterFaders[i].end == 0) { + _parameterFaders[i].end == 0) { return true; } } @@ -371,11 +371,13 @@ void Player::sysEx(const byte *p, uint16 len) { if (a != IMUSE_SYSEX_ID) { if (a == ROLAND_SYSEX_ID) { // Roland custom instrument definition. - part = getPart(p[0] & 0x0F); - if (part) { - part->_instrument.roland(p - 1); - if (part->clearToTransmit()) - part->_instrument.send(part->_mc); + if (_isMIDI || _isMT32) { + part = getPart(p[0] & 0x0F); + if (part) { + part->_instrument.roland(p - 1); + if (part->clearToTransmit()) + part->_instrument.send(part->_mc); + } } } else if (a == YM2612_SYSEX_ID) { // FM-TOWNS custom instrument definition @@ -399,13 +401,13 @@ void Player::sysEx(const byte *p, uint16 len) { if (!_scanning) { for (a = 0; a < len + 1 && a < 19; ++a) { - sprintf((char *)&buf[a*3], " %02X", p[a]); + sprintf((char *)&buf[a * 3], " %02X", p[a]); } // next for if (a < len + 1) { - buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.'; + buf[a * 3] = buf[a * 3 + 1] = buf[a * 3 + 2] = '.'; ++a; } // end if - buf[a*3] = '\0'; + buf[a * 3] = '\0'; debugC(DEBUG_IMUSE, "[%02d] SysEx:%s", _id, buf); } @@ -814,7 +816,7 @@ int Player::query_part_param(int param, byte chan) { return part->_vol; case 16: // FIXME: Need to know where this occurs... -error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan); + error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan); // In old versions of the code, this used to return part->_program. // This was changed in revision 2.29 of imuse.cpp (where this code used // to reside). @@ -845,9 +847,8 @@ void Player::onTimer() { uint beat_index = target_tick / TICKS_PER_BEAT + 1; uint tick_index = target_tick % TICKS_PER_BEAT; - if (_loop_counter &&(beat_index > _loop_from_beat || - (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) - { + if (_loop_counter && (beat_index > _loop_from_beat || + (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) { _loop_counter--; jump(_track_index, _loop_to_beat, _loop_to_tick); } @@ -891,15 +892,15 @@ int Player::addParameterFader(int param, int target, int time) { // target = target * 128 / 100; break; - case 127: - { // FIXME? I *think* this clears all parameter faders. - ParameterFader *ptr = &_parameterFaders[0]; - int i; - for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) - ptr->param = 0; - return 0; - } - break; + case 127: { + // FIXME? I *think* this clears all parameter faders. + ParameterFader *ptr = &_parameterFaders[0]; + int i; + for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) + ptr->param = 0; + return 0; + } + break; default: debug(0, "Player::addParameterFader(%d, %d, %d): Unknown parameter", param, target, time); @@ -1085,7 +1086,7 @@ void Player::saveLoadWithSerializer(Serializer *ser) { } ser->saveLoadEntries(this, playerEntries); ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders), - sizeof(ParameterFader), parameterFaderEntries); + sizeof(ParameterFader), parameterFaderEntries); return; } diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp index 955700fc2b..11bb4e7605 100644 --- a/engines/scumm/imuse/instrument.cpp +++ b/engines/scumm/imuse/instrument.cpp @@ -114,14 +114,15 @@ roland_to_gm_map[] = { // { "trickle4 ", ??? } }; +// This emulates the percussion bank setup LEC used with the MT-32, +// where notes 24 - 34 were assigned instruments without reverb. +// It also fixes problems on GS devices that map sounds to these +// notes by default. const byte Instrument::_gmRhythmMap[35] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, - 65, 48, 56}; - // This emulates the percussion bank setup LEC used with the MT-32, - // where notes 24 - 34 were assigned instruments without reverb. - // It also fixes problems on GS devices that map sounds to these - // notes by default. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, + 65, 48, 56 +}; class Instrument_Program : public InstrumentInternal { private: @@ -136,15 +137,16 @@ public: void copy_to(Instrument *dest) { dest->program(_program, _mt32); } bool is_valid() { return (_program < 128) && - ((_native_mt32 == _mt32) || _native_mt32 - ? (MidiDriver::_gmToMt32[_program] < 128) - : (MidiDriver::_mt32ToGm[_program] < 128)); } + ((_native_mt32 == _mt32) || _native_mt32 + ? (MidiDriver::_gmToMt32[_program] < 128) + : (MidiDriver::_mt32ToGm[_program] < 128)); + } }; class Instrument_AdLib : public InstrumentInternal { private: -#include "common/pack-start.h" // START STRUCT PACKING +#include "common/pack-start.h" // START STRUCT PACKING struct AdLibInstrument { byte flags_1; @@ -159,13 +161,17 @@ private: byte waveform_2; byte feedback; byte flags_a; - struct { byte a,b,c,d,e,f,g,h; } extra_a; + struct { + byte a, b, c, d, e, f, g, h; + } extra_a; byte flags_b; - struct { byte a,b,c,d,e,f,g,h; } extra_b; + struct { + byte a, b, c, d, e, f, g, h; + } extra_b; byte duration; } PACKED_STRUCT; -#include "common/pack-end.h" // END STRUCT PACKING +#include "common/pack-end.h" // END STRUCT PACKING AdLibInstrument _instrument; @@ -181,7 +187,7 @@ public: class Instrument_Roland : public InstrumentInternal { private: -#include "common/pack-start.h" // START STRUCT PACKING +#include "common/pack-start.h" // START STRUCT PACKING struct RolandInstrument { byte roland_id; @@ -242,11 +248,11 @@ private: byte checksum; } PACKED_STRUCT; -#include "common/pack-end.h" // END STRUCT PACKING +#include "common/pack-end.h" // END STRUCT PACKING RolandInstrument _instrument; - char _instrument_name [11]; + char _instrument_name[11]; uint8 getEquivalentGM(); @@ -259,6 +265,19 @@ public: bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); } }; +class Instrument_PcSpk : public InstrumentInternal { +public: + Instrument_PcSpk(const byte *data); + Instrument_PcSpk(Serializer *s); + void saveOrLoad(Serializer *s); + void send(MidiChannel *mc); + void copy_to(Instrument *dest) { dest->pcspk((byte *)&_instrument); } + bool is_valid() { return true; } + +private: + byte _instrument[23]; +}; + //////////////////////////////////////// // // Instrument class members @@ -299,7 +318,15 @@ void Instrument::roland(const byte *instrument) { _instrument = new Instrument_Roland(instrument); } -void Instrument::saveOrLoad (Serializer *s) { +void Instrument::pcspk(const byte *instrument) { + clear(); + if (!instrument) + return; + _type = itPcSpk; + _instrument = new Instrument_PcSpk(instrument); +} + +void Instrument::saveOrLoad(Serializer *s) { if (s->isSaving()) { s->saveByte(_type); if (_instrument) @@ -319,6 +346,9 @@ void Instrument::saveOrLoad (Serializer *s) { case itRoland: _instrument = new Instrument_Roland(s); break; + case itPcSpk: + _instrument = new Instrument_PcSpk(s); + break; default: warning("No known instrument classification #%d", (int)_type); _type = itNone; @@ -333,8 +363,8 @@ void Instrument::saveOrLoad (Serializer *s) { //////////////////////////////////////// Instrument_Program::Instrument_Program(byte program, bool mt32) : -_program (program), -_mt32 (mt32) { + _program(program), + _mt32(mt32) { if (program > 127) _program = 255; } @@ -413,7 +443,7 @@ Instrument_Roland::Instrument_Roland(const byte *data) { Instrument_Roland::Instrument_Roland(Serializer *s) { _instrument_name[0] = '\0'; if (!s->isSaving()) - saveOrLoad (s); + saveOrLoad(s); else memset(&_instrument, 0, sizeof(_instrument)); } @@ -470,4 +500,32 @@ uint8 Instrument_Roland::getEquivalentGM() { return 255; } +//////////////////////////////////////// +// +// Instrument_PcSpk class members +// +//////////////////////////////////////// + +Instrument_PcSpk::Instrument_PcSpk(const byte *data) { + memcpy(_instrument, data, sizeof(_instrument)); +} + +Instrument_PcSpk::Instrument_PcSpk(Serializer *s) { + if (!s->isSaving()) + saveOrLoad(s); + else + memset(_instrument, 0, sizeof(_instrument)); +} + +void Instrument_PcSpk::saveOrLoad(Serializer *s) { + if (s->isSaving()) + s->saveBytes(_instrument, sizeof(_instrument)); + else + s->loadBytes(_instrument, sizeof(_instrument)); +} + +void Instrument_PcSpk::send(MidiChannel *mc) { + mc->sysEx_customInstrument('SPK ', (byte *)&_instrument); +} + } // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h index 79cbd49032..a855c64155 100644 --- a/engines/scumm/imuse/instrument.h +++ b/engines/scumm/imuse/instrument.h @@ -51,10 +51,11 @@ public: itNone = 0, itProgram = 1, itAdLib = 2, - itRoland = 3 + itRoland = 3, + itPcSpk = 4 }; - Instrument() : _type (0), _instrument (0) { } + Instrument() : _type(0), _instrument(0) { } ~Instrument() { delete _instrument; } static void nativeMT32(bool native); static const byte _gmRhythmMap[35]; @@ -70,6 +71,7 @@ public: void program(byte program, bool mt32); void adlib(const byte *instrument); void roland(const byte *instrument); + void pcspk(const byte *instrument); byte getType() { return _type; } bool isValid() { return (_instrument ? _instrument->is_valid() : false); } diff --git a/engines/scumm/imuse/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp new file mode 100644 index 0000000000..01e2ab3b7d --- /dev/null +++ b/engines/scumm/imuse/pcspk.cpp @@ -0,0 +1,835 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "scumm/imuse/pcspk.h" + +#include "common/util.h" + +namespace Scumm { + +PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) { +} + +PcSpkDriver::~PcSpkDriver() { + close(); +} + +int PcSpkDriver::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + for (uint i = 0; i < 6; ++i) + _channels[i].init(this, i); + _activeChannel = 0; + _effectTimer = 0; + _randBase = 1; + + // We need to take care we only send note frequencies, when the internal + // settings actually changed, thus we need some extra state to keep track + // of that. + _lastActiveChannel = 0; + _lastActiveOut = 0; + + // We set the output sound type to music here to allow sound volume + // adjustment. The drawback here is that we can not control the music and + // sfx separately here. But the AdLib output has the same issue so it + // should not be that bad. + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + return 0; +} + +void PcSpkDriver::close() { + if (!_isOpen) + return; + _isOpen = false; + + _mixer->stopHandle(_mixerSoundHandle); +} + +void PcSpkDriver::send(uint32 d) { + assert((d & 0x0F) < 6); + _channels[(d & 0x0F)].send(d); +} + +void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + assert(channel < 6); + if (type == 'SPK ') + _channels[channel].sysEx_customInstrument(type, instr); +} + +MidiChannel *PcSpkDriver::allocateChannel() { + for (uint i = 0; i < 6; ++i) { + if (_channels[i].allocate()) + return &_channels[i]; + } + + return 0; +} + +void PcSpkDriver::generateSamples(int16 *buf, int len) { + _pcSpk.readBuffer(buf, len); +} + +void PcSpkDriver::onTimer() { + if (!_activeChannel) + return; + + for (uint i = 0; i < 6; ++i) { + OutputChannel &out = _channels[i]._out; + + if (!out.active) + continue; + + if (out.length == 0 || --out.length != 0) { + if (out.unkB && out.unkC) { + out.unkA += out.unkB; + if (out.instrument) + out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4; + } + + ++_effectTimer; + if (_effectTimer > 3) { + _effectTimer = 0; + + if (out.effectEnvelopeA.state) + updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA); + if (out.effectEnvelopeB.state) + updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB); + } + } else { + out.active = 0; + updateNote(); + return; + } + } + + if (_activeChannel->_tl) { + output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE); + } else { + _pcSpk.stop(); + _lastActiveChannel = 0; + _lastActiveOut = 0; + } +} + +void PcSpkDriver::updateNote() { + uint8 priority = 0; + _activeChannel = 0; + for (uint i = 0; i < 6; ++i) { + if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) { + priority = _channels[i]._priority; + _activeChannel = &_channels[i]; + } + } + + if (_activeChannel == 0 || _activeChannel->_tl == 0) { + _pcSpk.stop(); + _lastActiveChannel = 0; + _lastActiveOut = 0; + } else { + output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7)); + } +} + +void PcSpkDriver::output(uint16 out) { + byte v1 = (out >> 7) & 0xFF; + byte v2 = (out >> 2) & 0x1E; + + byte shift = _outputTable1[v1]; + uint16 indexBase = _outputTable2[v1] << 5; + uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift; + + // Only output in case the active channel changed or the frequency changed. + // This is not faithful to the original. Since our timings differ we would + // get distorted sound otherwise though. + if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) { + _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1); + _lastActiveChannel = _activeChannel; + _lastActiveOut = out; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) { + _owner = owner; + _channel = channel; + _allocated = false; + memset(&_out, 0, sizeof(_out)); +} + +bool PcSpkDriver::MidiChannel_PcSpk::allocate() { + if (_allocated) + return false; + + memset(&_out, 0, sizeof(_out)); + memset(_instrument, 0, sizeof(_instrument)); + _out.effectDefA.envelope = &_out.effectEnvelopeA; + _out.effectDefB.envelope = &_out.effectEnvelopeB; + + _allocated = true; + return true; +} + +MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() { + return _owner; +} + +byte PcSpkDriver::MidiChannel_PcSpk::getNumber() { + return _channel; +} + +void PcSpkDriver::MidiChannel_PcSpk::release() { + _out.active = 0; + _allocated = false; + _owner->updateNote(); +} + +void PcSpkDriver::MidiChannel_PcSpk::send(uint32 b) { + uint8 type = b & 0xF0; + uint8 p1 = (b >> 8) & 0xFF; + uint8 p2 = (b >> 16) & 0xFF; + + switch (type) { + case 0x80: + noteOff(p1); + break; + + case 0x90: + if (p2) + noteOn(p1, p2); + else + noteOff(p1); + break; + + case 0xB0: + controlChange(p1, p2); + break; + + case 0xE0: + pitchBend((p1 | (p2 << 7)) - 0x2000); + break; + + default: + break; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) { + if (!_allocated) + return; + + if (_sustain) { + if (_out.note == note) + _out.sustainNoteOff = 1; + } else { + if (_out.note == note) { + _out.active = 0; + _owner->updateNote(); + } + } +} + +void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) { + if (!_allocated) + return; + + _out.note = note; + _out.sustainNoteOff = 0; + _out.length = _instrument[0]; + + if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData)) + _out.instrument = _owner->_outInstrumentData + _instrument[4] * 256; + else + _out.instrument = 0; + + _out.unkA = 0; + _out.unkB = _instrument[1]; + _out.unkC = _instrument[2]; + _out.unkE = 0; + _out.unk60 = 0; + _out.active = 1; + + // In case we get a note on event on the last active channel, we reset the + // last active channel, thus we assure the frequency is correctly set, even + // when the same note was sent. + if (_owner->_lastActiveChannel == this) { + _owner->_lastActiveChannel = 0; + _owner->_lastActiveOut = 0; + } + _owner->updateNote(); + + _out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4)); + if (_out.unkC > 63) + _out.unkC = 63; + + if ((_instrument[5] & 0x80) != 0) + _owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6); + + if ((_instrument[14] & 0x80) != 0) + _owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15); +} + +void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) { + // Nothing to implement here, the iMuse code takes care of passing us the + // instrument data. +} + +void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) { + _pitchBend = (bend * _pitchBendFactor) >> 6; +} + +void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) { + switch (control) { + case 1: + if (_out.effectEnvelopeA.state && _out.effectDefA.useModWheel) + _out.effectEnvelopeA.modWheelState = (value >> 2); + if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel) + _out.effectEnvelopeB.modWheelState = (value >> 2); + break; + + case 7: + _tl = value; + if (_owner->_activeChannel == this) { + if (_tl == 0) { + _owner->_lastActiveChannel = 0; + _owner->_lastActiveOut = 0; + _owner->_pcSpk.stop(); + } else { + _owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE); + } + } + break; + + case 64: + _sustain = value; + if (!value && _out.sustainNoteOff) { + _out.active = 0; + _owner->updateNote(); + } + break; + + case 123: + _out.active = 0; + _owner->updateNote(); + break; + + default: + break; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) { + _pitchBendFactor = value; +} + +void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) { + _priority = value; +} + +void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) { + memcpy(_instrument, instr, sizeof(_instrument)); +} + +uint8 PcSpkDriver::getEffectModifier(uint16 level) { + uint8 base = level / 32; + uint8 index = level % 32; + + if (index == 0) + return 0; + + return (base * (index + 1)) >> 5; +} + +int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) { + if (!mod) { + return 0; + } else if (mod == 31) { + return level; + } else if (level < -63 || level > 63) { + return (mod * (level + 1)) >> 6; + } else if (mod < 0) { + if (level < 0) + return getEffectModifier(((-level) << 5) - mod); + else + return -getEffectModifier((level << 5) - mod); + } else { + if (level < 0) + return -getEffectModifier(((-level) << 5) + mod); + else + return getEffectModifier(((-level) << 5) + mod); + } +} + +int16 PcSpkDriver::getRandMultipy(int16 input) { + if (_randBase & 1) + _randBase = (_randBase >> 1) ^ 0xB8; + else + _randBase >>= 1; + + return (_randBase * input) >> 8; +} + +void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) { + def.phase = 0; + def.useModWheel = flags & 0x40; + env.loop = flags & 0x20; + def.type = flags & 0x1F; + + env.modWheelSensitivity = 31; + if (def.useModWheel) + env.modWheelState = chan._modWheel >> 2; + else + env.modWheelState = 31; + + switch (def.type) { + case 0: + env.maxLevel = 767; + env.startLevel = 383; + break; + + case 1: + env.maxLevel = 31; + env.startLevel = 15; + break; + + case 2: + env.maxLevel = 63; + env.startLevel = chan._out.unkB; + break; + + case 3: + env.maxLevel = 63; + env.startLevel = chan._out.unkC; + break; + + case 4: + env.maxLevel = 3; + env.startLevel = chan._instrument[4]; + break; + + case 5: + env.maxLevel = 62; + env.startLevel = 31; + env.modWheelState = 0; + break; + + case 6: + env.maxLevel = 31; + env.startLevel = 0; + env.modWheelSensitivity = 0; + break; + + default: + break; + } + + startEffect(env, data); +} + +void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) { + env.state = 1; + env.currentLevel = 0; + env.modWheelLast = 31; + env.duration = data[0] * 63; + + env.stateTargetLevels[0] = data[1]; + env.stateTargetLevels[1] = data[3]; + env.stateTargetLevels[2] = data[5]; + env.stateTargetLevels[3] = data[6]; + + env.stateModWheelLevels[0] = data[2]; + env.stateModWheelLevels[1] = data[4]; + env.stateModWheelLevels[2] = 0; + env.stateModWheelLevels[3] = data[7]; + + initNextEnvelopeState(env); +} + +void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) { + uint8 lastState = env.state - 1; + + uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)]; + if (env.stateTargetLevels[lastState] & 0x80) + stepCount = getRandMultipy(stepCount); + if (!stepCount) + stepCount = 1; + + env.stateNumSteps = env.stateStepCounter = stepCount; + + int16 totalChange = 0; + if (lastState != 2) { + totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31); + if (env.stateModWheelLevels[lastState] & 0x80) + totalChange = getRandMultipy(totalChange); + + if (totalChange + env.startLevel > env.maxLevel) + totalChange = env.maxLevel - env.startLevel; + else if (totalChange + env.startLevel < 0) + totalChange = -env.startLevel; + + totalChange -= env.currentLevel; + } + + env.changePerStep = totalChange / stepCount; + if (totalChange < 0) { + totalChange = -totalChange; + env.dir = -1; + } else { + env.dir = 1; + } + env.changePerStepRem = totalChange % stepCount; + env.changeCountRem = 0; +} + +void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) { + if (advanceEffectEnvelope(env, def) & 1) { + switch (def.type) { + case 0: case 1: + chan._out.unk60 = def.phase << 4; + break; + + case 2: + chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1]; + break; + + case 3: + chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2]; + break; + + case 4: + if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData)) + chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256; + else + chan._out.instrument = 0; + break; + + case 5: + env.modWheelState = (def.phase & 0xFF); + break; + + case 6: + env.modWheelSensitivity = (def.phase & 0xFF); + break; + + default: + break; + } + } +} + +uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) { + if (env.duration != 0) { + env.duration -= 17; + if (env.duration <= 0) { + env.state = 0; + return 0; + } + } + + uint8 changedFlags = 0; + int16 newLevel = env.currentLevel + env.changePerStep; + env.changeCountRem += env.changePerStepRem; + if (env.changeCountRem >= env.stateNumSteps) { + env.changeCountRem -= env.stateNumSteps; + newLevel += env.dir; + } + + if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) { + env.currentLevel = newLevel; + env.modWheelLast = env.modWheelState; + + int16 newPhase = getEffectModLevel(newLevel, env.modWheelState); + if (def.phase != newPhase) { + changedFlags |= 1; + def.phase = newPhase; + } + } + + --env.stateStepCounter; + if (!env.stateStepCounter) { + ++env.state; + if (env.state > 4) { + if (env.loop) { + env.state = 1; + changedFlags |= 2; + } else { + env.state = 0; + return changedFlags; + } + } + + initNextEnvelopeState(env); + } + + return changedFlags; +} + +const byte PcSpkDriver::_outInstrumentData[1024] = { + 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15, + 0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D, + 0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43, + 0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57, + 0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67, + 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B, + 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D, + 0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, + 0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B, + 0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B, + 0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49, + 0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33, + 0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B, + 0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03, + 0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB, + 0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3, + 0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD, + 0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9, + 0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99, + 0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C, + 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85, + 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83, + 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, + 0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95, + 0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5, + 0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7, + 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD, + 0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5, + 0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE, + 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB, + 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99, + 0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE, + 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6, + 0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F, + 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B, + 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32, + 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD, + 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC, + 0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0, + 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22, + 0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA, + 0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49, + 0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80, + 0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6, + 0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66, + 0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F, + 0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72, + 0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77, + 0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9, + 0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5, + 0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF, + 0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D, + 0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A, + 0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44, + 0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE, + 0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F, + 0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1, + 0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04, + 0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68, + 0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76 +}; + +const byte PcSpkDriver::_outputTable1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 +}; + +const byte PcSpkDriver::_outputTable2[] = { + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7 +}; + +const uint16 PcSpkDriver::_effectEnvStepTable[] = { + 1, 2, 4, 5, + 6, 7, 8, 9, + 10, 12, 14, 16, + 18, 21, 24, 30, + 36, 50, 64, 82, + 100, 136, 160, 192, + 240, 276, 340, 460, + 600, 860, 1200, 1600 +}; + +const uint16 PcSpkDriver::_frequencyTable[] = { + 0x8E84, 0x8E00, 0x8D7D, 0x8CFA, + 0x8C78, 0x8BF7, 0x8B76, 0x8AF5, + 0x8A75, 0x89F5, 0x8976, 0x88F7, + 0x8879, 0x87FB, 0x877D, 0x8700, + 0x8684, 0x8608, 0x858C, 0x8511, + 0x8496, 0x841C, 0x83A2, 0x8328, + 0x82AF, 0x8237, 0x81BF, 0x8147, + 0x80D0, 0x8059, 0x7FE3, 0x7F6D, + 0x7EF7, 0x7E82, 0x7E0D, 0x7D99, + 0x7D25, 0x7CB2, 0x7C3F, 0x7BCC, + 0x7B5A, 0x7AE8, 0x7A77, 0x7A06, + 0x7995, 0x7925, 0x78B5, 0x7846, + 0x77D7, 0x7768, 0x76FA, 0x768C, + 0x761F, 0x75B2, 0x7545, 0x74D9, + 0x746D, 0x7402, 0x7397, 0x732C, + 0x72C2, 0x7258, 0x71EF, 0x7186, + 0x711D, 0x70B5, 0x704D, 0x6FE5, + 0x6F7E, 0x6F17, 0x6EB0, 0x6E4A, + 0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5, + 0x6C51, 0x6BED, 0x6B8A, 0x6B26, + 0x6AC4, 0x6A61, 0x69FF, 0x699D, + 0x693C, 0x68DB, 0x687A, 0x681A, + 0x67BA, 0x675A, 0x66FA, 0x669B, + 0x663D, 0x65DF, 0x6581, 0x6523, + 0x64C6, 0x6469, 0x640C, 0x63B0, + 0x6354, 0x62F8, 0x629D, 0x6242, + 0x61E7, 0x618D, 0x6133, 0x60D9, + 0x6080, 0x6027, 0x5FCE, 0x5F76, + 0x5F1E, 0x5EC6, 0x5E6E, 0x5E17, + 0x5DC1, 0x5D6A, 0x5D14, 0x5CBE, + 0x5C68, 0x5C13, 0x5BBE, 0x5B6A, + 0x5B15, 0x5AC1, 0x5A6E, 0x5A1A, + 0x59C7, 0x5974, 0x5922, 0x58CF, + 0x587D, 0x582C, 0x57DA, 0x5789, + 0x5739, 0x56E8, 0x5698, 0x5648, + 0x55F9, 0x55A9, 0x555A, 0x550B, + 0x54BD, 0x546F, 0x5421, 0x53D3, + 0x5386, 0x5339, 0x52EC, 0x52A0, + 0x5253, 0x5207, 0x51BC, 0x5170, + 0x5125, 0x50DA, 0x5090, 0x5046, + 0x4FFB, 0x4FB2, 0x4F68, 0x4F1F, + 0x4ED6, 0x4E8D, 0x4E45, 0x4DFC, + 0x4DB5, 0x4D6D, 0x4D25, 0x4CDE, + 0x4C97, 0x4C51, 0x4C0A, 0x4BC4, + 0x4B7E, 0x4B39, 0x4AF3, 0x4AAE, + 0x4A69, 0x4A24, 0x49E0, 0x499C, + 0x4958, 0x4914, 0x48D1, 0x488E, + 0x484B, 0x4808, 0x47C6, 0x4783 +}; + +} // End of namespace Scumm + diff --git a/engines/scumm/imuse/pcspk.h b/engines/scumm/imuse/pcspk.h new file mode 100644 index 0000000000..e77ac8c1bf --- /dev/null +++ b/engines/scumm/imuse/pcspk.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SCUMM_IMUSE_PCSPK_H +#define SCUMM_IMUSE_PCSPK_H + +#include "audio/softsynth/emumidi.h" +#include "audio/softsynth/pcspk.h" + +namespace Scumm { + +class PcSpkDriver : public MidiDriver_Emulated { +public: + PcSpkDriver(Audio::Mixer *mixer); + ~PcSpkDriver(); + + virtual int open(); + virtual void close(); + + virtual void send(uint32 d); + virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); + + virtual MidiChannel *allocateChannel(); + virtual MidiChannel *getPercussionChannel() { return 0; } + + bool isStereo() const { return _pcSpk.isStereo(); } + int getRate() const { return _pcSpk.getRate(); } +protected: + void generateSamples(int16 *buf, int len); + void onTimer(); + +private: + Audio::PCSpeaker _pcSpk; + int _effectTimer; + uint8 _randBase; + + void updateNote(); + void output(uint16 out); + + static uint8 getEffectModifier(uint16 level); + int16 getEffectModLevel(int16 level, int8 mod); + int16 getRandMultipy(int16 input); + + struct EffectEnvelope { + uint8 state; + int16 currentLevel; + int16 duration; + int16 maxLevel; + int16 startLevel; + uint8 loop; + uint8 stateTargetLevels[4]; + uint8 stateModWheelLevels[4]; + uint8 modWheelSensitivity; + uint8 modWheelState; + uint8 modWheelLast; + int16 stateNumSteps; + int16 stateStepCounter; + int16 changePerStep; + int8 dir; + int16 changePerStepRem; + int16 changeCountRem; + }; + + struct EffectDefinition { + int16 phase; + uint8 type; + uint8 useModWheel; + EffectEnvelope *envelope; + }; + + struct OutputChannel { + uint8 active; + uint8 note; + uint8 sustainNoteOff; + uint8 length; + const uint8 *instrument; + uint8 unkA; + uint8 unkB; + uint8 unkC; + int16 unkE; + EffectEnvelope effectEnvelopeA; + EffectDefinition effectDefA; + EffectEnvelope effectEnvelopeB; + EffectDefinition effectDefB; + int16 unk60; + }; + + struct MidiChannel_PcSpk : public MidiChannel { + virtual MidiDriver *device(); + virtual byte getNumber(); + virtual void release(); + + virtual void send(uint32 b); + virtual void noteOff(byte note); + virtual void noteOn(byte note, byte velocity); + virtual void programChange(byte program); + virtual void pitchBend(int16 bend); + virtual void controlChange(byte control, byte value); + virtual void pitchBendFactor(byte value); + virtual void priority(byte value); + virtual void sysEx_customInstrument(uint32 type, const byte *instr); + + void init(PcSpkDriver *owner, byte channel); + bool allocate(); + + PcSpkDriver *_owner; + bool _allocated; + byte _channel; + + OutputChannel _out; + uint8 _instrument[23]; + uint8 _programNr; + uint8 _priority; + uint8 _tl; + uint8 _modWheel; + uint8 _sustain; + uint8 _pitchBendFactor; + int16 _pitchBend; + }; + + void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data); + void startEffect(EffectEnvelope &env, const byte *data); + void initNextEnvelopeState(EffectEnvelope &env); + void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def); + uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def); + + MidiChannel_PcSpk _channels[6]; + MidiChannel_PcSpk *_activeChannel; + + MidiChannel_PcSpk *_lastActiveChannel; + uint16 _lastActiveOut; + + static const byte _outInstrumentData[1024]; + static const byte _outputTable1[]; + static const byte _outputTable2[]; + static const uint16 _effectEnvStepTable[]; + static const uint16 _frequencyTable[]; +}; + +} // End of namespace Scumm + +#endif + diff --git a/engines/scumm/imuse/sysex_samnmax.cpp b/engines/scumm/imuse/sysex_samnmax.cpp index 4c4219e7bb..a4f525da56 100644 --- a/engines/scumm/imuse/sysex_samnmax.cpp +++ b/engines/scumm/imuse/sysex_samnmax.cpp @@ -53,8 +53,7 @@ void sysexHandler_SamNMax(Player *player, const byte *msg, uint16 len) { // something magical is supposed to happen.... for (a = 0; a < ARRAYSIZE(se->_snm_triggers); ++a) { if (se->_snm_triggers[a].sound == player->_id && - se->_snm_triggers[a].id == *p) - { + se->_snm_triggers[a].id == *p) { se->_snm_triggers[a].sound = se->_snm_triggers[a].id = 0; se->doCommand(8, se->_snm_triggers[a].command); break; diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp index c3bec93a60..ec64800b20 100644 --- a/engines/scumm/imuse/sysex_scumm.cpp +++ b/engines/scumm/imuse/sysex_scumm.cpp @@ -54,7 +54,8 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { // BYTE 00: Channel # // BYTE 02: BIT 01(0x01): Part on?(1 = yes) // BIT 02(0x02): Reverb? (1 = yes) [bug #1088045] - // BYTE 04: Priority adjustment [guessing] + // BYTE 03: Priority adjustment(upper 4 bits) + // BYTE 04: Priority adjustment(lower 4 bits) // BYTE 05: Volume(upper 4 bits) [guessing] // BYTE 06: Volume(lower 4 bits) [guessing] // BYTE 07: Pan(upper 4 bits) [bug #1088045] @@ -73,8 +74,8 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { if (part) { part->set_onoff(p[2] & 0x01); part->effectLevel((p[2] & 0x02) ? 127 : 0); - part->set_pri(p[4]); - part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F)); + part->set_pri((p[3] << 4) | p[4]); + part->volume((p[5] & 0x0F) << 4 | (p[6] & 0x0F)); part->set_pan((p[7] & 0x0F) << 4 | (p[8] & 0x0F)); part->_percussion = player->_isMIDI ? ((p[9] & 0x08) > 0) : false; part->set_transpose((p[9] & 0x0F) << 4 | (p[10] & 0x0F)); @@ -91,8 +92,16 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { // 0 is a valid program number. MI2 tests show that in such // cases, a regular program change message always seems to follow // anyway. - if (player->_isMIDI) - part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), player->_isMT32); + if (player->_isMIDI) { + part->_instrument.program((p[15] & 0x0F) << 4 | (p[16] & 0x0F), player->_isMT32); + } else if (se->_pcSpeaker) { + // FIXME/HACK: This is only needed here, since when we use the following line: + // se->copyGlobalInstrument((p[15] & 0x0F) << 4 |(p[16] & 0x0F), &part->_instrument); + // We would not get any instrument for PC Speaker. Because we don't default to an + // "empty" instrument in case the global instrument specified is not set up. + byte empty[23] = {0}; + part->_instrument.pcspk(empty); + } part->sendAll(); } } @@ -113,11 +122,10 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { ++p; // Skip hardware type part = player->getPart(a); if (part) { - if (len == 62) { + if (len == 62 || len == 48) { player->decode_sysex_bytes(p, buf, len - 2); part->set_instrument((byte *)buf); } else { - // SPK tracks have len == 48 here, and are not supported part->programChange(254); // Must be invalid, but not 255 (which is reserved) } } @@ -127,7 +135,8 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { p += 2; // Skip hardware type and... whatever came right before it a = *p++; player->decode_sysex_bytes(p, buf, len - 3); - se->setGlobalAdLibInstrument(a, buf); + if (len == 63 || len == 49) + se->setGlobalInstrument(a, buf); break; case 33: // Parameter adjust @@ -185,10 +194,9 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { case 80: // Loop player->decode_sysex_bytes(p + 1, buf, len - 1); - player->setLoop - (READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), - READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), - READ_BE_UINT16(buf + 8)); + player->setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), + READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), + READ_BE_UINT16(buf + 8)); break; case 81: // End loop diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 1a60564a9e..99ffdf7f21 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -27,6 +27,7 @@ MODULE_OBJS := \ imuse/imuse_part.o \ imuse/imuse_player.o \ imuse/instrument.o \ + imuse/pcspk.o \ imuse/sysex_samnmax.o \ imuse/sysex_scumm.o \ input.o \ diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 7b136dc36d..0f01e39459 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -71,6 +71,7 @@ #include "scumm/he/cup_player_he.h" #include "scumm/util.h" #include "scumm/verbs.h" +#include "scumm/imuse/pcspk.h" #include "backends/audiocd/audiocd.h" @@ -283,8 +284,9 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _16BitPalette = NULL; #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE _townsScreen = 0; +#ifdef USE_RGB_COLOR _cjkFont = 0; - _cjkChar = 0; +#endif #endif _shadowPalette = NULL; _shadowPaletteSize = 0; @@ -633,8 +635,10 @@ ScummEngine::~ScummEngine() { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE delete _townsScreen; +#ifdef USE_RGB_COLOR delete _cjkFont; #endif +#endif delete _debugger; @@ -1350,13 +1354,23 @@ void ScummEngine::setupCharsetRenderer() { _charset = new CharsetRendererPCE(this); else #endif + if (_game.platform == Common::kPlatformFMTowns) + _charset = new CharsetRendererTownsV3(this); + else _charset = new CharsetRendererV3(this); #ifdef ENABLE_SCUMM_7_8 } else if (_game.version == 8) { _charset = new CharsetRendererNut(this); #endif } else { - _charset = new CharsetRendererClassic(this); +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE + if (_game.platform == Common::kPlatformFMTowns) + _charset = new CharsetRendererTownsClassic(this); + else +#endif +#endif + _charset = new CharsetRendererClassic(this); } } @@ -1857,14 +1871,16 @@ void ScummEngine::setupMusic(int midi) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; - if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS) + if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS && _musicType != MDT_PCSPK) nativeMidiDriver = MidiDriver::createMidi(dev); if (nativeMidiDriver != NULL && _native_mt32) nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - bool multi_midi = ConfMan.getBool("multi_midi") && _musicType != MDT_NONE && (midi & MDT_ADLIB); + bool multi_midi = ConfMan.getBool("multi_midi") && _musicType != MDT_NONE && _musicType != MDT_PCSPK && (midi & MDT_ADLIB); if (_musicType == MDT_ADLIB || _musicType == MDT_TOWNS || multi_midi) { adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); + } else if (_musicType == MDT_PCSPK) { + adlibMidiDriver = new PcSpkDriver(_mixer); } _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver); @@ -1893,6 +1909,8 @@ void ScummEngine::setupMusic(int midi) { _imuse->property(IMuse::PROP_LIMIT_PLAYERS, 1); _imuse->property(IMuse::PROP_RECYCLE_PLAYERS, 1); } + if (_musicType == MDT_PCSPK) + _imuse->property(IMuse::PROP_PC_SPEAKER, 1); } } } diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 01bde90e1c..e503af750d 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -46,9 +46,10 @@ /* This disables the dual layer mode which is used in FM-Towns versions * of SCUMM games and which emulates the behavior of the original code. * The only purpose is code size reduction for certain backends. - * SCUMM 3 (FM-Towns) games will run in normal (DOS VGA) mode, which should - * work just fine in most situations. Some glitches might occur. SCUMM 5 games - * will not work without dual layer (and 16 bit color) support. + * SCUMM 3 (FM-Towns) games will run in English in normal (DOS VGA) mode, + * which should work just fine in most situations. Some glitches might + * occur. Japanese mode and SCUMM 5 FM-Towns games will not work without + * dual layer (and 16 bit color) support. */ #define DISABLE_TOWNS_DUAL_LAYER_MODE #endif @@ -345,6 +346,7 @@ class ResourceManager; class ScummEngine : public Engine { friend class ScummDebugger; friend class CharsetRenderer; + friend class CharsetRendererTownsClassic; friend class ResourceManager; public: @@ -1326,14 +1328,17 @@ public: // Exists both in V7 and in V72HE: byte VAR_NUM_GLOBAL_OBJS; +#ifdef USE_RGB_COLOR + // FM-Towns / PC-Engine specific + Graphics::FontSJIS *_cjkFont; +#endif + // FM-Towns specific #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE public: bool towns_isRectInStringBox(int x1, int y1, int x2, int y2); byte _townsPaletteFlags; - byte _townsCharsetColorMap[16]; - Graphics::FontSJIS *_cjkFont; - uint16 _cjkChar; + byte _townsCharsetColorMap[16]; protected: void towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, int srcX, int srcY, int w, int h); diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 27e43b3740..544abe6b1d 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -1137,8 +1137,8 @@ int ScummEngine::readSoundResource(ResId idx) { break; case MKTAG('S','P','K',' '): pri = -1; -// if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR) -// pri = 11; + if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR) + pri = 11; break; } diff --git a/engines/sword25/gfx/image/vectorimage.cpp b/engines/sword25/gfx/image/vectorimage.cpp index 45d43c465e..81f4fc2ad5 100644 --- a/engines/sword25/gfx/image/vectorimage.cpp +++ b/engines/sword25/gfx/image/vectorimage.cpp @@ -247,6 +247,9 @@ VectorImage::VectorImage(const byte *pFileData, uint fileSize, bool &success, co return; } + // readout SWF size + flashRectToBSRect(bs); + // Get frame rate and frame count /* uint32 frameRate = */ bs.getUInt16(); diff --git a/engines/sword25/kernel/inputpersistenceblock.cpp b/engines/sword25/kernel/inputpersistenceblock.cpp index 2d45dfb640..cdce539c31 100644 --- a/engines/sword25/kernel/inputpersistenceblock.cpp +++ b/engines/sword25/kernel/inputpersistenceblock.cpp @@ -55,8 +55,8 @@ void InputPersistenceBlock::read(int16 &value) { void InputPersistenceBlock::read(signed int &value) { if (checkMarker(SINT_MARKER)) { - rawRead(&value, sizeof(signed int)); - value = convertEndianessFromStorageToSystem(value); + value = (int32)READ_LE_UINT32(_iter); + _iter += 4; } else { value = 0; } @@ -64,8 +64,8 @@ void InputPersistenceBlock::read(signed int &value) { void InputPersistenceBlock::read(uint &value) { if (checkMarker(UINT_MARKER)) { - rawRead(&value, sizeof(uint)); - value = convertEndianessFromStorageToSystem(value); + value = READ_LE_UINT32(_iter); + _iter += 4; } else { value = 0; } @@ -73,8 +73,10 @@ void InputPersistenceBlock::read(uint &value) { void InputPersistenceBlock::read(float &value) { if (checkMarker(FLOAT_MARKER)) { - rawRead(&value, sizeof(float)); - value = convertEndianessFromStorageToSystem(value); + uint32 tmp[1]; + tmp[0] = READ_LE_UINT32(_iter); + value = ((float *)tmp)[0]; + _iter += 4; } else { value = 0.0f; } @@ -82,12 +84,11 @@ void InputPersistenceBlock::read(float &value) { void InputPersistenceBlock::read(bool &value) { if (checkMarker(BOOL_MARKER)) { - uint uintBool; - rawRead(&uintBool, sizeof(float)); - uintBool = convertEndianessFromStorageToSystem(uintBool); + uint uintBool = READ_LE_UINT32(_iter); + _iter += 4; value = uintBool == 0 ? false : true; } else { - value = 0.0f; + value = false; } } @@ -117,13 +118,6 @@ void InputPersistenceBlock::readByteArray(Common::Array<byte> &value) { } } -void InputPersistenceBlock::rawRead(void *destPtr, size_t size) { - if (checkBlockSize(size)) { - memcpy(destPtr, &*_iter, size); - _iter += size; - } -} - bool InputPersistenceBlock::checkBlockSize(int size) { if (_data.end() - _iter >= size) { return true; diff --git a/engines/sword25/kernel/inputpersistenceblock.h b/engines/sword25/kernel/inputpersistenceblock.h index 7e68137246..2518d7e32c 100644 --- a/engines/sword25/kernel/inputpersistenceblock.h +++ b/engines/sword25/kernel/inputpersistenceblock.h @@ -69,7 +69,6 @@ public: private: bool checkMarker(byte marker); bool checkBlockSize(int size); - void rawRead(void *destPtr, size_t size); Common::Array<byte> _data; Common::Array<byte>::const_iterator _iter; diff --git a/engines/sword25/kernel/outputpersistenceblock.cpp b/engines/sword25/kernel/outputpersistenceblock.cpp index cf28ea401f..e29d956e5f 100644 --- a/engines/sword25/kernel/outputpersistenceblock.cpp +++ b/engines/sword25/kernel/outputpersistenceblock.cpp @@ -43,19 +43,23 @@ OutputPersistenceBlock::OutputPersistenceBlock() { void OutputPersistenceBlock::write(signed int value) { writeMarker(SINT_MARKER); - value = convertEndianessFromSystemToStorage(value); + value = TO_LE_32(value); rawWrite(&value, sizeof(value)); } void OutputPersistenceBlock::write(uint value) { writeMarker(UINT_MARKER); - value = convertEndianessFromSystemToStorage(value); + value = TO_LE_32(value); rawWrite(&value, sizeof(value)); } void OutputPersistenceBlock::write(float value) { writeMarker(FLOAT_MARKER); - value = convertEndianessFromSystemToStorage(value); + uint32 tmp[1]; + + ((float *)tmp)[0] = value; + tmp[0] = TO_LE_32(tmp[0]); + rawWrite(&value, sizeof(value)); } @@ -63,7 +67,7 @@ void OutputPersistenceBlock::write(bool value) { writeMarker(BOOL_MARKER); uint uintBool = value ? 1 : 0; - uintBool = convertEndianessFromSystemToStorage(uintBool); + uintBool = TO_LE_32(uintBool); rawWrite(&uintBool, sizeof(uintBool)); } diff --git a/engines/sword25/kernel/persistenceblock.h b/engines/sword25/kernel/persistenceblock.h index d8440faa50..8ac3e84a41 100644 --- a/engines/sword25/kernel/persistenceblock.h +++ b/engines/sword25/kernel/persistenceblock.h @@ -64,48 +64,6 @@ protected: BLOCK_MARKER }; - // ----------------------------------------------------------------------------- - // Endianess Conversions - // ----------------------------------------------------------------------------- - // - // Everything is stored in Little Endian - // Big Endian Systems will need to be byte swapped during both saving and reading of saved values - // - - template<typename T> - static T convertEndianessFromSystemToStorage(T value) { - if (isBigEndian()) - reverseByteOrder(&value); - return value; - } - - template<typename T> - static T convertEndianessFromStorageToSystem(T value) { - if (isBigEndian()) - reverseByteOrder(&value); - return value; - } - -private: - static bool isBigEndian() { - uint dummy = 1; - byte *dummyPtr = reinterpret_cast<byte *>(&dummy); - return dummyPtr[0] == 0; - } - - template<typename T> - static void swap(T &one, T &two) { - T temp = one; - one = two; - two = temp; - } - - static void reverseByteOrder(void *ptr) { - // Reverses the byte order of the 32-bit word pointed to by Ptr - byte *charPtr = static_cast<byte *>(ptr); - swap(charPtr[0], charPtr[3]); - swap(charPtr[1], charPtr[2]); - } }; #define CTASSERT(ex) typedef char ctassert_type[(ex) ? 1 : -1] diff --git a/engines/toon/character.cpp b/engines/toon/character.cpp index 06c6e21d21..022214157a 100644 --- a/engines/toon/character.cpp +++ b/engines/toon/character.cpp @@ -596,7 +596,8 @@ int32 Character::getId() { void Character::save(Common::WriteStream *stream) { debugC(1, kDebugCharacter, "save(stream)"); - stream->writeSint32LE(_flags); + // we have to save visibility too, put in flags to not invalidate old savegames. + stream->writeSint32LE(_flags | ((_visible == false) ? 0x100 : 0)); stream->writeSint32LE(_x); stream->writeSint32LE(_y); stream->writeSint32LE(_z); @@ -633,6 +634,12 @@ void Character::load(Common::ReadStream *stream) { if (_sceneAnimationId > -1) { setAnimationInstance(_vm->getSceneAnimation(_sceneAnimationId)->_animInstance); } + + // "not visible" flag. + if (_flags & 0x100) { + _flags &= ~0x100; + setVisible(false); + } } void Character::setAnimScript(int32 animScriptId) { diff --git a/engines/toon/movie.cpp b/engines/toon/movie.cpp index 2318eaaac7..7637f4e62f 100644 --- a/engines/toon/movie.cpp +++ b/engines/toon/movie.cpp @@ -94,7 +94,7 @@ void Movie::play(Common::String video, int32 flags) { _vm->getAudioManager()->setMusicVolume(0); _decoder->loadFile(video.c_str()); playVideo(isFirstIntroVideo); - _vm->flushPalette(false); + _vm->flushPalette(true); if (flags & 1) _vm->getAudioManager()->setMusicVolume(_vm->getAudioManager()->isMusicMuted() ? 0 : 255); _decoder->close(); @@ -103,7 +103,6 @@ void Movie::play(Common::String video, int32 flags) { bool Movie::playVideo(bool isFirstIntroVideo) { debugC(1, kDebugMovie, "playVideo(isFirstIntroVideo: %d)", isFirstIntroVideo); - while (!_vm->shouldQuit() && !_decoder->endOfVideo()) { if (_decoder->needsUpdate()) { const Graphics::Surface *frame = _decoder->decodeNextFrame(); diff --git a/engines/toon/picture.cpp b/engines/toon/picture.cpp index 0257964fb5..295e304765 100644 --- a/engines/toon/picture.cpp +++ b/engines/toon/picture.cpp @@ -29,16 +29,14 @@ namespace Toon { -bool Picture::loadPicture(Common::String file, bool totalPalette /*= false*/) { - debugC(1, kDebugPicture, "loadPicture(%s, %d)", file.c_str(), (totalPalette) ? 1 : 0); +bool Picture::loadPicture(Common::String file) { + debugC(1, kDebugPicture, "loadPicture(%s)", file.c_str()); uint32 size = 0; uint8 *fileData = _vm->resources()->getFileData(file, &size); if (!fileData) return false; - _useFullPalette = totalPalette; - uint32 compId = READ_BE_UINT32(fileData); switch (compId) { @@ -57,6 +55,8 @@ bool Picture::loadPicture(Common::String file, bool totalPalette /*= false*/) { // do we have a palette ? _paletteEntries = (dstsize & 0x7ff) / 3; + _useFullPalette = (_paletteEntries == 256); + // _useFullPalette = true; if (_paletteEntries) { _palette = new uint8[_paletteEntries * 3]; memcpy(_palette, _data + dstsize - (dstsize & 0x7ff), _paletteEntries * 3); @@ -70,7 +70,8 @@ bool Picture::loadPicture(Common::String file, bool totalPalette /*= false*/) { uint32 decSize = READ_LE_UINT32(fileData + 10); _data = new uint8[decSize + 100]; _paletteEntries = READ_LE_UINT16(fileData + 14) / 3; - + _useFullPalette = (_paletteEntries == 256); + if (_paletteEntries) { _palette = new uint8[_paletteEntries * 3]; memcpy(_palette, fileData + 16, _paletteEntries * 3); diff --git a/engines/toon/picture.h b/engines/toon/picture.h index 23edbc91da..ee0e006702 100644 --- a/engines/toon/picture.h +++ b/engines/toon/picture.h @@ -38,7 +38,7 @@ class Picture { public: Picture(ToonEngine *vm); ~Picture(); - bool loadPicture(Common::String file, bool totalPalette = false); + bool loadPicture(Common::String file); void setupPalette(); void draw(Graphics::Surface &surface, int32 x, int32 y, int32 dx, int32 dy); void drawWithRectList(Graphics::Surface& surface, int32 x, int32 y, int32 dx, int32 dy, Common::Array<Common::Rect>& rectArray); diff --git a/engines/toon/toon.cpp b/engines/toon/toon.cpp index 26639d71f7..cff6c24469 100644 --- a/engines/toon/toon.cpp +++ b/engines/toon/toon.cpp @@ -614,7 +614,7 @@ struct MainMenuEntry { bool ToonEngine::showMainmenu(bool &loadedGame) { Picture *mainmenuPicture = new Picture(this); - mainmenuPicture->loadPicture("TITLESCR.CPS", true); + mainmenuPicture->loadPicture("TITLESCR.CPS"); mainmenuPicture->setupPalette(); flushPalette(false); @@ -690,6 +690,11 @@ bool ToonEngine::showMainmenu(bool &loadedGame) { } } + if (_needPaletteFlush) { + flushPalette(false); + _needPaletteFlush = false; + } + parseInput(); copyToVirtualScreen(true); _system->delayMillis(17); @@ -1547,7 +1552,7 @@ void ToonEngine::clickEvent() { return; } } else { - if (!_drew->walkTo(_mouseX, _mouseY)) { + if (!_drew->walkTo(_mouseX + _gameState->_currentScrollValue, _mouseY)) { // walk was canceled ? return; } @@ -2600,7 +2605,7 @@ int32 ToonEngine::showInventory() { delete _inventoryPicture; _inventoryPicture = new Picture(this); fadeOut(5); - _inventoryPicture->loadPicture("SACK128.CPS", true); + _inventoryPicture->loadPicture("SACK128.CPS"); _inventoryPicture->setupPalette(); dirtyAllScreen(); @@ -2786,7 +2791,7 @@ void ToonEngine::showCutaway(Common::String cutawayPicture) { if (cutawayPicture == "") { cutawayPicture = Common::String(_gameState->_locations[_gameState->_currentScene]._cutaway) + ".CPS"; } - _currentCutaway->loadPicture(cutawayPicture, false); + _currentCutaway->loadPicture(cutawayPicture); _currentCutaway->setupPalette(); _oldScrollValue = _gameState->_currentScrollValue; _gameState->_currentScrollValue = 0; @@ -3418,7 +3423,7 @@ void ToonEngine::viewInventoryItem(Common::String str, int32 lineId, int32 itemD fadeOut(5); Picture *pic = new Picture(this); - pic->loadPicture(str, false); + pic->loadPicture(str); pic->setupPalette(); dirtyAllScreen(); flushPalette(); diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h index 8b80edf89d..f9ced562c2 100644 --- a/engines/tsage/detection_tables.h +++ b/engines/tsage/detection_tables.h @@ -24,7 +24,7 @@ namespace tSage { static const tSageGameDescription gameDescriptions[] = { - // Ringworld CD and First Wave versions + // Ringworld English CD and First Wave versions { { "ring", @@ -38,6 +38,20 @@ static const tSageGameDescription gameDescriptions[] = { GType_Ringworld, GF_CD | GF_ALT_REGIONS }, + // Ringworld Spanish CD + { + { + "ring", + "CD", + AD_ENTRY1s("ring.rlb", "cb8bba91b30cd172712371d7123bd763", 7427980), + Common::ES_ESP, + Common::kPlatformPC, + ADGF_UNSTABLE, + Common::GUIO_NOSPEECH | Common::GUIO_NOSFX + }, + GType_Ringworld, + GF_CD | GF_ALT_REGIONS + }, // Ringworld English Floppy version { { diff --git a/graphics/sjis.cpp b/graphics/sjis.cpp index 10c780b156..be078a4da9 100644 --- a/graphics/sjis.cpp +++ b/graphics/sjis.cpp @@ -40,10 +40,27 @@ FontSJIS *FontSJIS::createFont(const Common::Platform platform) { // Try the font ROM of the specified platform if (platform == Common::kPlatformFMTowns) { ret = new FontTowns(); - if (ret && ret->loadData()) - return ret; + if (ret) { + if (ret->loadData()) + return ret; + } delete ret; - } + } else if (platform == Common::kPlatformPCEngine) { + ret = new FontPCEngine(); + if (ret) { + if (ret->loadData()) + return ret; + } + delete ret; + } // TODO: PC98 font rom support + /* else if (platform == Common::kPlatformPC98) { + ret = new FontPC98(); + if (ret) { + if (ret->loadData()) + return ret; + } + delete ret; + }*/ // Try ScummVM's font. ret = new FontSjisSVM(platform); @@ -59,15 +76,21 @@ void FontSJIS::drawChar(Graphics::Surface &dst, uint16 ch, int x, int y, uint32 } FontSJISBase::FontSJISBase() - : _drawMode(kDefaultMode), _flippedMode(false), _fontWidth(16), _fontHeight(16) { + : _drawMode(kDefaultMode), _flippedMode(false), _fontWidth(16), _fontHeight(16), _bitPosNewLineMask(0) { } void FontSJISBase::setDrawingMode(DrawingMode mode) { - _drawMode = mode; + if (hasFeature(1 << mode)) + _drawMode = mode; + else + warning("Unsupported drawing mode selected"); } void FontSJISBase::toggleFlippedMode(bool enable) { - _flippedMode = enable; + if (hasFeature(kFeatFlipped)) + _flippedMode = enable; + else + warning("Flipped mode unsupported by this font"); } uint FontSJISBase::getFontHeight() const { @@ -98,26 +121,30 @@ uint FontSJISBase::getMaxFontWidth() const { uint FontSJISBase::getCharWidth(uint16 ch) const { if (isASCII(ch)) - return (_drawMode == kOutlineMode) ? 10 : (_drawMode == kDefaultMode ? 8 : 9); + return ((_drawMode == kOutlineMode) ? 10 : (_drawMode == kDefaultMode ? 8 : 9)); else return getMaxFontWidth(); } template<typename Color> void FontSJISBase::blitCharacter(const uint8 *glyph, const int w, const int h, uint8 *dst, int pitch, Color c) const { + uint8 bitPos = 0; + uint8 mask = 0; + for (int y = 0; y < h; ++y) { Color *d = (Color *)dst; dst += pitch; - uint8 mask = 0; + bitPos &= _bitPosNewLineMask; for (int x = 0; x < w; ++x) { - if (!(x % 8)) + if (!(bitPos % 8)) mask = *glyph++; if (mask & 0x80) *d = c; ++d; + ++bitPos; mask <<= 1; } } @@ -176,9 +203,6 @@ const uint8 *FontSJISBase::flipCharacter(const uint8 *glyph, const int w) const 0x0F, 0x8F, 0x4F, 0xC7, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x97, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF }; - // TODO: This code looks like it will only work with 16 pixel wide - // characters we should really take care that we only call it on these - // or we fix this to support a generic width. for (int i = 0; i < w; i++) { _tempGlyph[i] = flipData[glyph[(w * 2 - 1) - i]]; _tempGlyph[(w * 2 - 1) - i] = flipData[glyph[i]]; @@ -225,9 +249,6 @@ void FontSJISBase::drawChar(void *dst, uint16 ch, int pitch, int bpp, uint32 c1, } #ifndef DISABLE_FLIPPED_MODE - // TODO: This code inside flopCharater looks like it will only work with - // 16 pixel wide characters we should really take care that we only call - // it on these or we fix it to support a generic width. if (_flippedMode) glyphSource = flipCharacter(glyphSource, width); #endif @@ -303,7 +324,7 @@ const uint8 *FontTowns::getCharData(uint16 ch) const { uint8 f = ch & 0xFF; uint8 s = ch >> 8; - // copied from scumm\charset.cpp + // moved from scumm\charset.cpp enum { KANA = 0, KANJI = 1, @@ -392,6 +413,98 @@ const uint8 *FontTowns::getCharData(uint16 ch) const { } } +bool FontTowns::hasFeature(int feat) const { + static const int features = kFeatDefault | kFeatOutline | kFeatShadow | kFeatFMTownsShadow | kFeatFlipped; + return (features & feat) ? true : false; +} + +// PC-Engine ROM font + +bool FontPCEngine::loadData() { + Common::SeekableReadStream *data = SearchMan.createReadStreamForMember("pce.cdbios"); + if (!data) + return false; + + data->seek((data->size() & 0x200) ? 0x30200 : 0x30000); + data->read(_fontData12x12, kFont12x12Chars * 18); + + _fontWidth = _fontHeight = 12; + _bitPosNewLineMask = _fontWidth & 7; + + bool retValue = !data->err(); + delete data; + return retValue; +} + +const uint8 *FontPCEngine::getCharData(uint16 ch) const { + // Converts sjis code to pce font offset + // (moved from scumm\charset.cpp). + // rangeTbl maps SJIS char-codes to the PCE System Card font rom. + // Each pair {<upperBound>,<lowerBound>} in the array represents a SJIS range. + const int rangeCnt = 45; + static const uint16 rangeTbl[rangeCnt][2] = { + // Symbols + {0x8140,0x817E},{0x8180,0x81AC}, + // 0-9 + {0x824F,0x8258}, + // Latin upper + {0x8260,0x8279}, + // Latin lower + {0x8281,0x829A}, + // Kana + {0x829F,0x82F1},{0x8340,0x837E},{0x8380,0x8396}, + // Greek upper + {0x839F,0x83B6}, + // Greek lower + {0x83BF,0x83D6}, + // Cyrillic upper + {0x8440,0x8460}, + // Cyrillic lower + {0x8470,0x847E},{0x8480,0x8491}, + // Kanji + {0x889F,0x88FC}, + {0x8940,0x897E},{0x8980,0x89FC}, + {0x8A40,0x8A7E},{0x8A80,0x8AFC}, + {0x8B40,0x8B7E},{0x8B80,0x8BFC}, + {0x8C40,0x8C7E},{0x8C80,0x8CFC}, + {0x8D40,0x8D7E},{0x8D80,0x8DFC}, + {0x8E40,0x8E7E},{0x8E80,0x8EFC}, + {0x8F40,0x8F7E},{0x8F80,0x8FFC}, + {0x9040,0x907E},{0x9080,0x90FC}, + {0x9140,0x917E},{0x9180,0x91FC}, + {0x9240,0x927E},{0x9280,0x92FC}, + {0x9340,0x937E},{0x9380,0x93FC}, + {0x9440,0x947E},{0x9480,0x94FC}, + {0x9540,0x957E},{0x9580,0x95FC}, + {0x9640,0x967E},{0x9680,0x96FC}, + {0x9740,0x977E},{0x9780,0x97FC}, + {0x9840,0x9872} + }; + + ch = (ch << 8) | (ch >> 8); + int offset = 0; + for (int i = 0; i < rangeCnt; ++i) { + if (ch >= rangeTbl[i][0] && ch <= rangeTbl[i][1]) { + return _fontData12x12 + 18 * (offset + ch - rangeTbl[i][0]); + break; + } + offset += rangeTbl[i][1] - rangeTbl[i][0] + 1; + } + + debug(4, "Invalid Char: 0x%x", ch); + return 0; +} + +bool FontPCEngine::hasFeature(int feat) const { + // Outline mode not supported due to use of _bitPosNewLineMask. This could be implemented, + // but is not needed for any particular target at the moment. + // Flipped mode is also not supported since the hard coded table (taken from SCUMM 5 FM-TOWNS) + // is set up for font sizes of 8/16. This mode is also not required at the moment, since + // there aren't any SCUMM 5 PC-Engine games. + static const int features = kFeatDefault | kFeatShadow | kFeatFMTownsShadow; + return (features & feat) ? true : false; +} + // ScummVM SJIS font FontSjisSVM::FontSjisSVM(const Common::Platform platform) @@ -464,6 +577,15 @@ const uint8 *FontSjisSVM::getCharData(uint16 c) const { return getCharDataDefault(c); } +bool FontSjisSVM::hasFeature(int feat) const { + // Flipped mode is not supported since the hard coded table (taken from SCUMM 5 FM-TOWNS) + // is set up for font sizes of 8/16. This mode is also not required at the moment, since + // there aren't any SCUMM 5 PC-Engine games. + static const int features16 = kFeatDefault | kFeatOutline | kFeatShadow | kFeatFMTownsShadow | kFeatFlipped; + static const int features12 = kFeatDefault | kFeatOutline | kFeatShadow | kFeatFMTownsShadow; + return (((_fontWidth == 12) ? features12 : features16) & feat) ? true : false; +} + const uint8 *FontSjisSVM::getCharDataPCE(uint16 c) const { if (isASCII(c)) return 0; diff --git a/graphics/sjis.h b/graphics/sjis.h index 62e68013da..4b54da53b4 100644 --- a/graphics/sjis.h +++ b/graphics/sjis.h @@ -75,7 +75,7 @@ public: virtual bool loadData() = 0; /** - * Enable drawing with outline or shadow. + * Enable drawing with outline or shadow if supported by the Font. * * After changing outline state, getFontHeight and getMaxFontWidth / getCharWidth might return * different values! @@ -90,11 +90,17 @@ public: virtual void setDrawingMode(DrawingMode mode) {} /** - * Enable flipped character drawing (e.g. in the MI1 circus scene after Guybrush gets shot out of the cannon). + * Enable flipped character drawing if supported by the Font (e.g. in the MI1 circus scene after Guybrush gets shot out of the cannon). */ virtual void toggleFlippedMode(bool enable) {} /** + * Set spacing between characters and lines. This affects font height / char width + */ + virtual void setCharSpacing(int spacing) {} + virtual void setLineSpacing(int spacing) {} + + /** * Returns the height of the font. */ virtual uint getFontHeight() const = 0; @@ -162,16 +168,27 @@ protected: DrawingMode _drawMode; bool _flippedMode; int _fontWidth, _fontHeight; - + uint8 _bitPosNewLineMask; + bool isASCII(uint16 ch) const; virtual const uint8 *getCharData(uint16 c) const = 0; + + enum DrawingFeature { + kFeatDefault = 1 << 0, + kFeatOutline = 1 << 1, + kFeatShadow = 1 << 2, + kFeatFMTownsShadow = 1 << 3, + kFeatFlipped = 1 << 4 + }; + + virtual bool hasFeature(int feat) const = 0; }; /** * FM-TOWNS ROM based SJIS compatible font. * - * This is used in KYRA and SCI. + * This is used in KYRA, SCUMM and SCI. */ class FontTowns : public FontSJISBase { public: @@ -189,6 +206,31 @@ private: uint8 _fontData8x16[kFont8x16Chars * 32]; virtual const uint8 *getCharData(uint16 c) const; + + bool hasFeature(int feat) const; +}; + +/** + * PC-Engine System Card based SJIS compatible font. + * + * This is used in LOOM. + */ +class FontPCEngine : public FontSJISBase { +public: + /** + * Loads the ROM data from "pce.cdbios". + */ + bool loadData(); +private: + enum { + kFont12x12Chars = 3418 + }; + + uint8 _fontData12x12[kFont12x12Chars * 18]; + + virtual const uint8 *getCharData(uint16 c) const; + + bool hasFeature(int feat) const; }; /** @@ -215,6 +257,8 @@ private: virtual const uint8 *getCharData(uint16 c) const; + bool hasFeature(int feat) const; + const uint8 *getCharDataPCE(uint16 c) const; const uint8 *getCharDataDefault(uint16 c) const; |