aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS3
-rw-r--r--NEWS10
-rw-r--r--audio/decoders/quicktime.cpp2
-rw-r--r--audio/decoders/quicktime_intern.h2
-rw-r--r--audio/midiparser.h1
-rw-r--r--audio/midiparser_qt.cpp496
-rw-r--r--audio/midiparser_qt.h134
-rw-r--r--audio/module.mk1
-rw-r--r--common/forbidden.h25
-rw-r--r--common/macresman.cpp4
-rw-r--r--common/quicktime.cpp6
-rw-r--r--common/quicktime.h5
-rw-r--r--common/str.cpp2
-rw-r--r--common/util.cpp5
-rw-r--r--common/util.h13
-rwxr-xr-xdevtools/credits.pl3
-rw-r--r--engines/cge/events.cpp2
-rw-r--r--engines/groovie/detection.cpp99
-rw-r--r--engines/groovie/groovie.cpp19
-rw-r--r--engines/groovie/module.mk1
-rw-r--r--engines/groovie/music.cpp54
-rw-r--r--engines/groovie/music.h12
-rw-r--r--engines/groovie/stuffit.cpp537
-rw-r--r--engines/groovie/stuffit.h43
-rw-r--r--engines/hugo/parser.cpp2
-rw-r--r--engines/mohawk/console.cpp2
-rw-r--r--engines/mohawk/dialogs.cpp12
-rw-r--r--engines/mohawk/myst.cpp78
-rw-r--r--engines/mohawk/myst.h19
-rw-r--r--engines/mohawk/myst_areas.cpp43
-rw-r--r--engines/mohawk/myst_graphics.cpp283
-rw-r--r--engines/mohawk/myst_graphics.h10
-rw-r--r--engines/mohawk/myst_scripts.cpp118
-rw-r--r--engines/mohawk/myst_scripts.h8
-rw-r--r--engines/mohawk/myst_stacks/channelwood.cpp7
-rw-r--r--engines/mohawk/myst_stacks/demo.cpp4
-rw-r--r--engines/mohawk/myst_stacks/intro.cpp6
-rw-r--r--engines/mohawk/myst_stacks/mechanical.cpp280
-rw-r--r--engines/mohawk/myst_stacks/mechanical.h10
-rw-r--r--engines/mohawk/myst_stacks/myst.cpp175
-rw-r--r--engines/mohawk/myst_stacks/myst.h16
-rw-r--r--engines/mohawk/myst_stacks/preview.cpp32
-rw-r--r--engines/mohawk/myst_stacks/preview.h3
-rw-r--r--engines/mohawk/myst_stacks/selenitic.cpp8
-rw-r--r--engines/mohawk/myst_stacks/slides.cpp2
-rw-r--r--engines/mohawk/myst_stacks/stoneship.cpp14
-rw-r--r--engines/mohawk/riven.cpp10
-rw-r--r--engines/mohawk/riven_external.cpp6
-rw-r--r--engines/mohawk/video.cpp14
-rw-r--r--engines/mohawk/video.h4
-rw-r--r--engines/pegasus/neighborhood/norad/pressuredoor.cpp5
-rw-r--r--engines/pegasus/neighborhood/tsa/fulltsa.cpp7
-rw-r--r--engines/pegasus/pegasus.cpp4
-rw-r--r--engines/queen/journal.cpp2
-rw-r--r--engines/saga/music.cpp59
-rw-r--r--engines/saga/music.h1
-rw-r--r--engines/scumm/imuse/imuse.cpp13
-rw-r--r--engines/scumm/imuse/imuse.h2
-rw-r--r--engines/scumm/imuse/imuse_internal.h2
-rw-r--r--engines/scumm/module.mk3
-rw-r--r--engines/scumm/music.h6
-rw-r--r--engines/scumm/player_mac.cpp415
-rw-r--r--engines/scumm/player_mac.h133
-rw-r--r--engines/scumm/player_v3m.cpp202
-rw-r--r--engines/scumm/player_v3m.h54
-rw-r--r--engines/scumm/player_v5m.cpp246
-rw-r--r--engines/scumm/player_v5m.h57
-rw-r--r--engines/scumm/saveload.cpp10
-rw-r--r--engines/scumm/saveload.h2
-rw-r--r--engines/scumm/script.cpp6
-rw-r--r--engines/scumm/scumm.cpp8
-rw-r--r--engines/scumm/sound.cpp246
-rw-r--r--engines/sword25/util/lua/llex.cpp2
-rw-r--r--engines/tinsel/detection_tables.h7
-rw-r--r--engines/tinsel/graphics.cpp215
-rw-r--r--engines/tinsel/music.cpp9
-rw-r--r--engines/tinsel/palette.cpp6
-rw-r--r--engines/tinsel/scene.cpp3
-rw-r--r--engines/tinsel/sound.cpp138
-rw-r--r--engines/tinsel/sound.h10
-rw-r--r--engines/tinsel/tinlib.cpp2
-rw-r--r--engines/tinsel/tinsel.cpp4
-rw-r--r--engines/tinsel/tinsel.h2
-rw-r--r--engines/touche/menu.cpp2
-rw-r--r--engines/wintermute/base/base_keyboard_state.cpp7
-rw-r--r--engines/wintermute/base/base_persistence_manager.cpp10
-rw-r--r--engines/wintermute/base/gfx/osystem/base_render_osystem.cpp101
-rw-r--r--engines/wintermute/base/gfx/osystem/base_render_osystem.h30
-rw-r--r--engines/wintermute/base/gfx/osystem/render_ticket.cpp103
-rw-r--r--engines/wintermute/base/gfx/osystem/render_ticket.h63
-rw-r--r--engines/wintermute/module.mk1
-rw-r--r--engines/wintermute/ui/ui_tiled_image.h1
-rw-r--r--gui/credits.h3
-rw-r--r--gui/themes/translations.datbin392733 -> 415352 bytes
-rw-r--r--gui/widgets/list.cpp2
-rw-r--r--po/be_BY.po3140
-rw-r--r--test/common/bitstream.h152
-rw-r--r--video/qt_decoder.cpp167
-rw-r--r--video/qt_decoder.h6
-rw-r--r--video/video_decoder.cpp46
-rw-r--r--video/video_decoder.h26
101 files changed, 7544 insertions, 842 deletions
diff --git a/AUTHORS b/AUTHORS
index 9e0c399642..e39400f22f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -411,6 +411,9 @@ Other contributions
Basque:
Mikel Iturbe Urretxa
+ Belarusian:
+ Ivan Lukyanov
+
Catalan:
Jordi Vilalta Prat
diff --git a/NEWS b/NEWS
index 29cde45154..9affe57b6a 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,7 @@ For a more comprehensive changelog of the latest experimental code, see:
- Rewrote VideoDecoder subsystem.
- Added Galician translation.
- Added Finnish translation.
+ - Added Belarusian translation.
Cine:
- Improved audio support for Amiga and AtariST versions of Future Wars.
@@ -28,10 +29,19 @@ For a more comprehensive changelog of the latest experimental code, see:
protection, this extra line can be toggled by the ScummVM copy protection
command line option.
+ SAGA:
+ - Added music support for the Macintosh version of I Have No Mouth and, I
+ Must Scream.
+
SCUMM:
- Implemented Monkey Island 2 Macintosh's audio driver. Now we properly
support its sample based audio output. The same output is also used for
the m68k Macintosh version of Indiana Jones and the Fate of Atlantis.
+ - Improved music support for the Macintosh version of Monkey Island 1. It
+ now uses the original instruments, rather than approximating them with
+ General MIDI instruments, and should sound a lot closer to the original.
+ - Added sound and music support for the Macintosh version of Loom.
+ - Handle double-clicking in the Macintosh version of Loom.
1.5.0 (2012-07-27)
New Games:
diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp
index 0588650ec6..787b547495 100644
--- a/audio/decoders/quicktime.cpp
+++ b/audio/decoders/quicktime.cpp
@@ -134,7 +134,7 @@ void QuickTimeAudioDecoder::init() {
_audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i]));
}
-Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) {
+Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_AUDIO) {
debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format));
diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h
index f1ab037d89..bb5ff0cf5c 100644
--- a/audio/decoders/quicktime_intern.h
+++ b/audio/decoders/quicktime_intern.h
@@ -131,7 +131,7 @@ protected:
};
// Common::QuickTimeParser API
- virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format);
+ virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
void init();
diff --git a/audio/midiparser.h b/audio/midiparser.h
index a4dbf174e1..bb9749b97f 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -394,6 +394,7 @@ public:
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
+ static MidiParser *createParser_QT();
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
};
diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp
new file mode 100644
index 0000000000..6214d28f95
--- /dev/null
+++ b/audio/midiparser_qt.cpp
@@ -0,0 +1,496 @@
+/* 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 "audio/midiparser_qt.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+
+bool MidiParser_QT::loadMusic(byte *data, uint32 size) {
+ if (size < 8)
+ return false;
+
+ Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+
+ // Attempt to detect what format we have
+ bool result;
+ if (READ_BE_UINT32(data + 4) == MKTAG('m', 'u', 's', 'i'))
+ result = loadFromTune(stream);
+ else
+ result = loadFromContainerStream(stream);
+
+ if (!result) {
+ delete stream;
+ return false;
+ }
+
+ return true;
+}
+
+void MidiParser_QT::unloadMusic() {
+ MidiParser::unloadMusic();
+ close();
+
+ // Unlike those lesser formats, we *do* hold track data
+ for (uint i = 0; i < _trackInfo.size(); i++)
+ free(_trackInfo[i].data);
+
+ _trackInfo.clear();
+}
+
+bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ unloadMusic();
+
+ // a tune starts off with a sample description
+ stream->readUint32BE(); // header size
+
+ if (stream->readUint32BE() != MKTAG('m', 'u', 's', 'i'))
+ return false;
+
+ stream->readUint32BE(); // reserved
+ stream->readUint16BE(); // reserved
+ stream->readUint16BE(); // index
+
+ stream->readUint32BE(); // flags, ignore
+
+ MIDITrackInfo trackInfo;
+ trackInfo.size = stream->size() - stream->pos();
+ assert(trackInfo.size > 0);
+
+ trackInfo.data = (byte *)malloc(trackInfo.size);
+ stream->read(trackInfo.data, trackInfo.size);
+
+ trackInfo.timeScale = 600; // the default
+ _trackInfo.push_back(trackInfo);
+
+ initCommon();
+ return true;
+}
+
+bool MidiParser_QT::loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ unloadMusic();
+
+ if (!parseStream(stream, disposeAfterUse))
+ return false;
+
+ initFromContainerTracks();
+ return true;
+}
+
+bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) {
+ unloadMusic();
+
+ if (!parseFile(fileName))
+ return false;
+
+ initFromContainerTracks();
+ return true;
+}
+
+void MidiParser_QT::parseNextEvent(EventInfo &info) {
+ uint32 delta = 0;
+
+ while (_queuedEvents.empty())
+ delta += readNextEvent();
+
+ info = _queuedEvents.pop();
+ info.delta = delta;
+}
+
+uint32 MidiParser_QT::readNextEvent() {
+ if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) {
+ // Manually insert end of track when we reach the end
+ EventInfo info;
+ info.event = 0xFF;
+ info.ext.type = 0x2F;
+ _queuedEvents.push(info);
+ return 0;
+ }
+
+ uint32 control = readUint32();
+
+ switch (control >> 28) {
+ case 0x0:
+ case 0x1:
+ // Rest
+ // We handle this by recursively adding up all the rests into the
+ // next event's delta
+ return readNextEvent() + (control & 0xFFFFFF);
+ case 0x2:
+ case 0x3:
+ // Note event
+ handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF);
+ break;
+ case 0x4:
+ case 0x5:
+ // Controller
+ handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF);
+ break;
+ case 0x6:
+ case 0x7:
+ // Marker
+ // Used for editing only, so we don't need to care about this
+ break;
+ case 0x9: {
+ // Extended note event
+ uint32 extra = readUint32();
+ handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF);
+ break;
+ }
+ case 0xA: {
+ // Extended controller
+ uint32 extra = readUint32();
+ handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF);
+ break;
+ }
+ case 0xB:
+ // Knob
+ error("Encountered knob event in QuickTime MIDI");
+ break;
+ case 0x8:
+ case 0xC:
+ case 0xD:
+ case 0xE:
+ // Reserved
+ readUint32();
+ break;
+ case 0xF:
+ // General
+ handleGeneralEvent(control);
+ break;
+ }
+
+ return 0;
+}
+
+void MidiParser_QT::handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length) {
+ byte channel = getChannel(part);
+
+ EventInfo info;
+ info.event = 0x90 | channel;
+ info.basic.param1 = pitch;
+ info.basic.param2 = velocity;
+ info.length = (velocity == 0) ? 0 : length;
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart) {
+ byte channel = getChannel(part);
+ EventInfo info;
+
+ if (control == 0) {
+ // "Bank select"
+ // QuickTime docs don't list this, but IHNM Mac calls this anyway
+ // We have to ignore this.
+ return;
+ } else if (control == 32) {
+ // Pitch bend
+ info.event = 0xE0 | channel;
+
+ // Actually an 8.8 fixed point number
+ int16 value = (int16)((intPart << 8) | fracPart);
+
+ if (value < -0x200 || value > 0x1FF) {
+ warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value);
+ value = CLIP<int16>(value, -0x200, 0x1FF);
+ }
+
+ // Now convert the value to 'normal' MIDI values
+ value += 0x200;
+ value *= 16;
+
+ // param1 holds the low 7 bits, param2 holds the high 7 bits
+ info.basic.param1 = value & 0x7F;
+ info.basic.param2 = value >> 7;
+
+ _partMap[part].pitchBend = value;
+ } else {
+ // Regular controller
+ info.event = 0xB0 | channel;
+ info.basic.param1 = control;
+ info.basic.param2 = intPart;
+
+ // TODO: Parse more controls to hold their status
+ switch (control) {
+ case 7:
+ _partMap[part].volume = intPart;
+ break;
+ case 10:
+ _partMap[part].pan = intPart;
+ break;
+ }
+ }
+
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::handleGeneralEvent(uint32 control) {
+ uint32 part = (control >> 16) & 0xFFF;
+ uint32 dataSize = ((control & 0xFFFF) - 2) * 4;
+ byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF;
+
+ switch (subType) {
+ case 1:
+ // Note Request
+ // Currently we're only using the GM number from the request
+ assert(dataSize == 84);
+
+ // We have to remap channels because GM needs percussion to be on the
+ // percussion channel but QuickTime can have that anywhere.
+ definePart(part, READ_BE_UINT32(_position._playPos + 80));
+ break;
+ case 5: // Tune Difference
+ case 8: // MIDI Channel
+ case 10: // No-op
+ case 11: // Used Notes
+ // Should be safe to skip these
+ break;
+ default:
+ warning("Unhandled general event %d", subType);
+ }
+
+ _position._playPos += dataSize + 4;
+}
+
+void MidiParser_QT::definePart(uint32 part, uint32 instrument) {
+ if (_partMap.contains(part))
+ warning("QuickTime MIDI part %d being redefined", part);
+
+ PartStatus partStatus;
+ partStatus.instrument = instrument;
+ partStatus.volume = 127;
+ partStatus.pan = 64;
+ partStatus.pitchBend = 0x2000;
+ _partMap[part] = partStatus;
+}
+
+byte MidiParser_QT::getChannel(uint32 part) {
+ // If we already mapped it, just go with it
+ if (!_channelMap.contains(part)) {
+ byte newChannel = findFreeChannel(part);
+ _channelMap[part] = newChannel;
+ setupPart(part);
+ }
+
+ return _channelMap[part];
+}
+
+byte MidiParser_QT::findFreeChannel(uint32 part) {
+ if (_partMap[part].instrument != 0x4001) {
+ // Normal Instrument -> First Free Channel
+ if (allChannelsAllocated())
+ deallocateFreeChannel();
+
+ for (int i = 0; i < 16; i++)
+ if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion
+ return i;
+
+ // Can't actually get here
+ }
+
+ // Drum Kit -> Percussion Channel
+ deallocateChannel(9);
+ return 9;
+}
+
+void MidiParser_QT::deallocateFreeChannel() {
+ for (int i = 0; i < 16; i++) {
+ if (i != 9 && !_activeNotes[i]) {
+ // TODO: Improve this by looking for the channel with the longest
+ // time since the last note.
+ deallocateChannel(i);
+ return;
+ }
+ }
+
+ error("Exceeded QuickTime MIDI channel polyphony");
+}
+
+void MidiParser_QT::deallocateChannel(byte channel) {
+ for (ChannelMap::iterator it = _channelMap.begin(); it != _channelMap.end(); it++) {
+ if (it->_value == channel) {
+ _channelMap.erase(it);
+ return;
+ }
+ }
+}
+
+bool MidiParser_QT::isChannelAllocated(byte channel) const {
+ for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
+ if (it->_value == channel)
+ return true;
+
+ return false;
+}
+
+bool MidiParser_QT::allChannelsAllocated() const {
+ // Less than 15? We definitely have room
+ if (_channelMap.size() < 15)
+ return false;
+
+ // 15? One of the allocated channels might be the percussion one
+ if (_channelMap.size() == 15)
+ for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
+ if (it->_value == 9)
+ return false;
+
+ // 16 -> definitely all allocated
+ return true;
+}
+
+void MidiParser_QT::setupPart(uint32 part) {
+ PartStatus &status = _partMap[part];
+ byte channel = _channelMap[part];
+ EventInfo info;
+
+ // First, the program change
+ if (channel != 9) {
+ // 9 is always percussion
+ info.event = 0xC0 | channel;
+ info.basic.param1 = status.instrument;
+ _queuedEvents.push(info);
+ }
+
+ // Volume
+ info.event = 0xB0 | channel;
+ info.basic.param1 = 7;
+ info.basic.param2 = status.volume;
+ _queuedEvents.push(info);
+
+ // Pan
+ info.event = 0xB0 | channel;
+ info.basic.param1 = 10;
+ info.basic.param2 = status.pan;
+ _queuedEvents.push(info);
+
+ // Pitch Bend
+ info.event = 0xE0 | channel;
+ info.basic.param1 = status.pitchBend & 0x7F;
+ info.basic.param2 = status.pitchBend >> 7;
+ _queuedEvents.push(info);
+}
+
+void MidiParser_QT::resetTracking() {
+ MidiParser::resetTracking();
+ _channelMap.clear();
+ _queuedEvents.clear();
+ _partMap.clear();
+}
+
+Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
+ if (track->codecType == CODEC_TYPE_MIDI) {
+ debug(0, "MIDI Codec FourCC '%s'", tag2str(format));
+
+ _fd->readUint32BE(); // flags, ignore
+ descSize -= 4;
+
+ MIDISampleDesc *entry = new MIDISampleDesc(track, format);
+ entry->_requestSize = descSize;
+ entry->_requestData = (byte *)malloc(descSize);
+ _fd->read(entry->_requestData, descSize);
+ return entry;
+ }
+
+ return 0;
+}
+
+MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) :
+ Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
+}
+
+void MidiParser_QT::initFromContainerTracks() {
+ const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks;
+
+ for (uint32 i = 0; i < tracks.size(); i++) {
+ if (tracks[i]->codecType == CODEC_TYPE_MIDI) {
+ assert(tracks[i]->sampleDescs.size() == 1);
+
+ if (tracks[i]->editCount != 1)
+ warning("Unhandled QuickTime MIDI edit lists, things may go awry");
+
+ MIDITrackInfo trackInfo;
+ trackInfo.data = readWholeTrack(tracks[i], trackInfo.size);
+ trackInfo.timeScale = tracks[i]->timeScale;
+ _trackInfo.push_back(trackInfo);
+ }
+ }
+
+ initCommon();
+}
+
+void MidiParser_QT::initCommon() {
+ // Now we have all our info needed in _trackInfo from whatever container
+ // form, we can fill in the MidiParser tracks.
+
+ _numTracks = _trackInfo.size();
+ assert(_numTracks > 0);
+
+ for (uint32 i = 0; i < _trackInfo.size(); i++)
+ MidiParser::_tracks[i] = _trackInfo[i].data;
+
+ _ppqn = _trackInfo[0].timeScale;
+ resetTracking();
+ setTempo(1000000);
+ setTrack(0);
+}
+
+byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) {
+ // This just goes through all chunks and appends them together
+
+ Common::MemoryWriteStreamDynamic output;
+ uint32 curSample = 0;
+
+ // Read in the note request data first
+ MIDISampleDesc *entry = (MIDISampleDesc *)track->sampleDescs[0];
+ output.write(entry->_requestData, entry->_requestSize);
+
+ for (uint i = 0; i < track->chunkCount; i++) {
+ _fd->seek(track->chunkOffsets[i]);
+
+ uint32 sampleCount = 0;
+
+ for (uint32 j = 0; j < track->sampleToChunkCount; j++)
+ if (i >= track->sampleToChunk[j].first)
+ sampleCount = track->sampleToChunk[j].count;
+
+ for (uint32 j = 0; j < sampleCount; j++, curSample++) {
+ uint32 size = (track->sampleSize != 0) ? track->sampleSize : track->sampleSizes[curSample];
+
+ byte *data = new byte[size];
+ _fd->read(data, size);
+ output.write(data, size);
+ delete[] data;
+ }
+ }
+
+ trackSize = output.size();
+ return output.getData();
+}
+
+uint32 MidiParser_QT::readUint32() {
+ uint32 value = READ_BE_UINT32(_position._playPos);
+ _position._playPos += 4;
+ return value;
+}
+
+MidiParser *MidiParser::createParser_QT() {
+ return new MidiParser_QT();
+}
diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h
new file mode 100644
index 0000000000..d6d0f40a48
--- /dev/null
+++ b/audio/midiparser_qt.h
@@ -0,0 +1,134 @@
+/* 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_MIDIPARSER_QT_H
+#define AUDIO_MIDIPARSER_QT_H
+
+#include "audio/midiparser.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/queue.h"
+#include "common/quicktime.h"
+
+/**
+ * The QuickTime Music version of MidiParser.
+ *
+ * QuickTime Music is actually a superset of MIDI. It has its own custom
+ * instruments and supports more than 15 non-percussion channels. It also
+ * has custom control changes and a more advanced pitch bend (which we
+ * convert to GM pitch bend as best as possible). We then use the fallback
+ * GM instrument that each QuickTime instrument definition has to provide.
+ *
+ * Furthermore, Apple's documentation on this is terrible. You know
+ * documentation is bad when it contradicts itself three times on the same
+ * subject (like about setting the GM instrument field to percussion).
+ *
+ * This is as close to a proper QuickTime Music parser as we can currently
+ * implement using our MidiParser interface.
+ */
+class MidiParser_QT : public MidiParser, public Common::QuickTimeParser {
+public:
+ MidiParser_QT() {}
+ ~MidiParser_QT() {}
+
+ // MidiParser
+ bool loadMusic(byte *data, uint32 size);
+ void unloadMusic();
+
+ /**
+ * Load the MIDI from a 'Tune' resource
+ */
+ bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+ /**
+ * Load the MIDI from a QuickTime stream
+ */
+ bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+ /**
+ * Load the MIDI from a QuickTime file
+ */
+ bool loadFromContainerFile(const Common::String &fileName);
+
+protected:
+ // MidiParser
+ void parseNextEvent(EventInfo &info);
+ void resetTracking();
+
+ // QuickTimeParser
+ SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
+
+private:
+ struct MIDITrackInfo {
+ byte *data;
+ uint32 size;
+ uint32 timeScale;
+ };
+
+ struct PartStatus {
+ uint32 instrument;
+ byte volume;
+ byte pan;
+ uint16 pitchBend;
+ };
+
+ class MIDISampleDesc : public SampleDesc {
+ public:
+ MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
+ ~MIDISampleDesc() {}
+
+ byte *_requestData;
+ uint32 _requestSize;
+ };
+
+ uint32 readNextEvent();
+ void handleGeneralEvent(uint32 control);
+ void handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart);
+ void handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length);
+
+ void definePart(uint32 part, uint32 instrument);
+ void setupPart(uint32 part);
+
+ byte getChannel(uint32 part);
+ bool isChannelAllocated(byte channel) const;
+ byte findFreeChannel(uint32 part);
+ void deallocateFreeChannel();
+ void deallocateChannel(byte channel);
+ bool allChannelsAllocated() const;
+
+ byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize);
+
+ Common::Array<MIDITrackInfo> _trackInfo;
+ Common::Queue<EventInfo> _queuedEvents;
+
+ typedef Common::HashMap<uint, PartStatus> PartMap;
+ PartMap _partMap;
+
+ typedef Common::HashMap<uint, byte> ChannelMap;
+ ChannelMap _channelMap;
+
+ void initFromContainerTracks();
+ void initCommon();
+ uint32 readUint32();
+};
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index e3aa0aaa81..4e1c031c83 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
audiostream.o \
fmopl.o \
mididrv.o \
+ midiparser_qt.o \
midiparser_smf.o \
midiparser_xmidi.o \
midiparser.o \
diff --git a/common/forbidden.h b/common/forbidden.h
index eec80bba59..9050114442 100644
--- a/common/forbidden.h
+++ b/common/forbidden.h
@@ -333,11 +333,21 @@
#define isalpha(a) FORBIDDEN_SYMBOL_REPLACEMENT
#endif
+ #ifndef FORBIDDEN_SYMBOL_EXCEPTION_iscntrl
+ #undef iscntrl
+ #define iscntrl(a) FORBIDDEN_SYMBOL_REPLACEMENT
+ #endif
+
#ifndef FORBIDDEN_SYMBOL_EXCEPTION_isdigit
#undef isdigit
#define isdigit(a) FORBIDDEN_SYMBOL_REPLACEMENT
#endif
+ #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isgraph
+ #undef isgraph
+ #define isgraph(a) FORBIDDEN_SYMBOL_REPLACEMENT
+ #endif
+
#ifndef FORBIDDEN_SYMBOL_EXCEPTION_isnumber
#undef isnumber
#define isnumber(a) FORBIDDEN_SYMBOL_REPLACEMENT
@@ -348,6 +358,16 @@
#define islower(a) FORBIDDEN_SYMBOL_REPLACEMENT
#endif
+ #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isprint
+ #undef isprint
+ #define isprint(a) FORBIDDEN_SYMBOL_REPLACEMENT
+ #endif
+
+ #ifndef FORBIDDEN_SYMBOL_EXCEPTION_ispunct
+ #undef ispunct
+ #define ispunct(a) FORBIDDEN_SYMBOL_REPLACEMENT
+ #endif
+
#ifndef FORBIDDEN_SYMBOL_EXCEPTION_isspace
#undef isspace
#define isspace(a) FORBIDDEN_SYMBOL_REPLACEMENT
@@ -358,6 +378,11 @@
#define isupper(a) FORBIDDEN_SYMBOL_REPLACEMENT
#endif
+ #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isxdigit
+ #undef isxdigit
+ #define isxdigit(a) FORBIDDEN_SYMBOL_REPLACEMENT
+ #endif
+
#endif // FORBIDDEN_SYMBOL_EXCEPTION_ctype_h
#ifndef FORBIDDEN_SYMBOL_EXCEPTION_mkdir
diff --git a/common/macresman.cpp b/common/macresman.cpp
index f2f020c6de..00562f746a 100644
--- a/common/macresman.cpp
+++ b/common/macresman.cpp
@@ -360,8 +360,8 @@ bool MacResManager::load(SeekableReadStream &stream) {
_mapLength = stream.readUint32BE();
// do sanity check
- if (_dataOffset >= (uint32)stream.size() || _mapOffset >= (uint32)stream.size() ||
- _dataLength + _mapLength > (uint32)stream.size()) {
+ if (stream.eos() || _dataOffset >= (uint32)stream.size() || _mapOffset >= (uint32)stream.size() ||
+ _dataLength + _mapLength > (uint32)stream.size()) {
_resForkOffset = -1;
_mode = kResForkNone;
return false;
diff --git a/common/quicktime.cpp b/common/quicktime.cpp
index 173d3c6a97..5b3659b0d5 100644
--- a/common/quicktime.cpp
+++ b/common/quicktime.cpp
@@ -165,6 +165,8 @@ void QuickTimeParser::initParseTable() {
{ &QuickTimeParser::readWAVE, MKTAG('w', 'a', 'v', 'e') },
{ &QuickTimeParser::readESDS, MKTAG('e', 's', 'd', 's') },
{ &QuickTimeParser::readSMI, MKTAG('S', 'M', 'I', ' ') },
+ { &QuickTimeParser::readDefault, MKTAG('g', 'm', 'h', 'd') },
+ { &QuickTimeParser::readLeaf, MKTAG('g', 'm', 'i', 'n') },
{ 0, 0 }
};
@@ -477,6 +479,8 @@ int QuickTimeParser::readHDLR(Atom atom) {
track->codecType = CODEC_TYPE_VIDEO;
else if (type == MKTAG('s', 'o', 'u', 'n'))
track->codecType = CODEC_TYPE_AUDIO;
+ else if (type == MKTAG('m', 'u', 's', 'i'))
+ track->codecType = CODEC_TYPE_MIDI;
_fd->readUint32BE(); // component manufacture
_fd->readUint32BE(); // component flags
@@ -540,7 +544,7 @@ int QuickTimeParser::readSTSD(Atom atom) {
_fd->readUint16BE(); // reserved
_fd->readUint16BE(); // index
- track->sampleDescs[i] = readSampleDesc(track, format);
+ track->sampleDescs[i] = readSampleDesc(track, format, size - 16);
debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), track->codecType);
diff --git a/common/quicktime.h b/common/quicktime.h
index 641718e13a..caa92578b1 100644
--- a/common/quicktime.h
+++ b/common/quicktime.h
@@ -120,7 +120,8 @@ protected:
enum CodecType {
CODEC_TYPE_MOV_OTHER,
CODEC_TYPE_VIDEO,
- CODEC_TYPE_AUDIO
+ CODEC_TYPE_AUDIO,
+ CODEC_TYPE_MIDI
};
struct Track {
@@ -161,7 +162,7 @@ protected:
byte objectTypeMP4;
};
- virtual SampleDesc *readSampleDesc(Track *track, uint32 format) = 0;
+ virtual SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize) = 0;
uint32 _timeScale;
uint32 _duration;
diff --git a/common/str.cpp b/common/str.cpp
index 84805082ac..8210ca6bb8 100644
--- a/common/str.cpp
+++ b/common/str.cpp
@@ -764,7 +764,7 @@ String tag2string(uint32 tag) {
str[4] = '\0';
// Replace non-printable chars by dot
for (int i = 0; i < 4; ++i) {
- if (!isprint((unsigned char)str[i]))
+ if (!Common::isPrint(str[i]))
str[i] = '.';
}
return String(str);
diff --git a/common/util.cpp b/common/util.cpp
index 4d9ff11c5c..3d40fffff5 100644
--- a/common/util.cpp
+++ b/common/util.cpp
@@ -26,6 +26,7 @@
#define FORBIDDEN_SYMBOL_EXCEPTION_islower
#define FORBIDDEN_SYMBOL_EXCEPTION_isspace
#define FORBIDDEN_SYMBOL_EXCEPTION_isupper
+#define FORBIDDEN_SYMBOL_EXCEPTION_isprint
#include "common/util.h"
@@ -144,4 +145,8 @@ bool isUpper(int c) {
return isupper((byte)c);
}
+bool isPrint(int c) {
+ ENSURE_ASCII_CHAR(c);
+ return isprint((byte)c);
+}
} // End of namespace Common
diff --git a/common/util.h b/common/util.h
index 78340980d5..4ca1c42929 100644
--- a/common/util.h
+++ b/common/util.h
@@ -165,6 +165,17 @@ bool isSpace(int c);
*/
bool isUpper(int c);
-} // End of namespace Common
+/**
+ * Test whether the given character is printable. This includes the space
+ * character (' ').
+ *
+ * If the parameter is outside the range of a signed or unsigned char, then
+ * false is returned.
+ *
+ * @param c the character to test
+ * @return true if the character is printable, false otherwise.
+ */
+bool isPrint(int c);
+} // End of namespace Common
#endif
diff --git a/devtools/credits.pl b/devtools/credits.pl
index f5fc9d64eb..3c97e6ab0b 100755
--- a/devtools/credits.pl
+++ b/devtools/credits.pl
@@ -940,6 +940,9 @@ begin_credits("Credits");
begin_section("Basque");
add_person("Mikel Iturbe Urretxa", "", "");
end_section();
+ begin_section("Belarusian");
+ add_person("Ivan Lukyanov", "", "");
+ end_section();
begin_section("Catalan");
add_person("Jordi Vilalta Prat", "jvprat", "");
end_section();
diff --git a/engines/cge/events.cpp b/engines/cge/events.cpp
index 1530c870ef..89802058f4 100644
--- a/engines/cge/events.cpp
+++ b/engines/cge/events.cpp
@@ -184,7 +184,7 @@ void Mouse::on() {
step(0);
if (_busy)
_busy->step(0);
- }
+ }
}
void Mouse::off() {
diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp
index 895686b5e0..e06dace0d7 100644
--- a/engines/groovie/detection.cpp
+++ b/engines/groovie/detection.cpp
@@ -137,6 +137,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 1
},
+ // The 11th Hour Macintosh English
+ {
+ {
+ "11h", "",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 1
+ },
+
+ // The 11th Hour Macintosh English (Installed)
+ {
+ {
+ "11h", "Installed",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 1
+ },
+
// The 11th Hour DOS Demo English
{
{
@@ -159,6 +189,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 2
},
+ // The Making of The 11th Hour Macintosh English
+ {
+ {
+ "11h", "Making Of",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 2
+ },
+
+ // The Making of The 11th Hour Macintosh English (Installed)
+ {
+ {
+ "11h", "Making Of (Installed)",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 2
+ },
+
// Clandestiny Trailer DOS English
{
{
@@ -170,6 +230,36 @@ static const GroovieGameDescription gameDescriptions[] = {
kGroovieV2, 3
},
+ // Clandestiny Trailer Macintosh English
+ {
+ {
+ "clandestiny", "Trailer",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 3
+ },
+
+ // Clandestiny Trailer Macintosh English (Installed)
+ {
+ {
+ "clandestiny", "Trailer (Installed)",
+ {
+ { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 },
+ { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE,
+ GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT)
+ },
+ kGroovieV2, 3
+ },
+
// Clandestiny DOS English
{
{
@@ -207,6 +297,11 @@ static const GroovieGameDescription gameDescriptions[] = {
{AD_TABLE_END_MARKER, kGroovieT7G, 0}
};
+static const char *directoryGlobs[] = {
+ "MIDI",
+ 0
+};
+
class GroovieMetaEngine : public AdvancedMetaEngine {
public:
GroovieMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(GroovieGameDescription), groovieGames) {
@@ -222,6 +317,10 @@ public:
// replaced with an according explanation.
_flags = kADFlagUseExtraAsHint;
_guioptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT);
+
+ // Need MIDI directory to detect 11H Mac Installed
+ _maxScanDepth = 2;
+ _directoryGlobs = directoryGlobs;
}
const char *getName() const {
diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp
index 726e7cbede..16358bfa28 100644
--- a/engines/groovie/groovie.cpp
+++ b/engines/groovie/groovie.cpp
@@ -30,6 +30,7 @@
#include "groovie/music.h"
#include "groovie/resource.h"
#include "groovie/roq.h"
+#include "groovie/stuffit.h"
#include "groovie/vdx.h"
#include "common/config-manager.h"
@@ -56,6 +57,7 @@ GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) :
SearchMan.addSubDirectoryMatching(gameDataDir, "groovie");
SearchMan.addSubDirectoryMatching(gameDataDir, "media");
SearchMan.addSubDirectoryMatching(gameDataDir, "system");
+ SearchMan.addSubDirectoryMatching(gameDataDir, "MIDI");
_modeSpeed = kGroovieSpeedNormal;
if (ConfMan.hasKey("t7g_speed")) {
@@ -93,6 +95,15 @@ GroovieEngine::~GroovieEngine() {
}
Common::Error GroovieEngine::run() {
+ if (_gameDescription->version == kGroovieV2 && getPlatform() == Common::kPlatformMacintosh) {
+ // Load the Mac installer with the lowest priority (in case the user has installed
+ // the game and has the MIDI folder present; faster to just load them)
+ Common::Archive *archive = createStuffItArchive("The 11th Hour Installer");
+
+ if (archive)
+ SearchMan.add("The 11th Hour Installer", archive);
+ }
+
_script = new Script(this, _gameDescription->version);
// Initialize the graphics
@@ -160,10 +171,10 @@ Common::Error GroovieEngine::run() {
// Create the music player
switch (getPlatform()) {
case Common::kPlatformMacintosh:
- // TODO: The 11th Hour Mac uses QuickTime MIDI files
- // Right now, since the XMIDI are present and it is still detected as
- // the DOS version, we don't have to do anything here.
- _musicPlayer = new MusicPlayerMac(this);
+ if (_gameDescription->version == kGroovieT7G)
+ _musicPlayer = new MusicPlayerMac_t7g(this);
+ else
+ _musicPlayer = new MusicPlayerMac_v2(this);
break;
case Common::kPlatformIOS:
_musicPlayer = new MusicPlayerIOS(this);
diff --git a/engines/groovie/module.mk b/engines/groovie/module.mk
index 1e89ff66f5..b47eed912b 100644
--- a/engines/groovie/module.mk
+++ b/engines/groovie/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
roq.o \
saveload.o \
script.o \
+ stuffit.o \
vdx.o
# This module can be built as a plugin
diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp
index af929d439b..95637fc407 100644
--- a/engines/groovie/music.cpp
+++ b/engines/groovie/music.cpp
@@ -678,9 +678,9 @@ void MusicPlayerXMI::setTimbreMT(byte channel, const Timbre &timbre) {
}
-// MusicPlayerMac
+// MusicPlayerMac_t7g
-MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) {
+MusicPlayerMac_t7g::MusicPlayerMac_t7g(GroovieEngine *vm) : MusicPlayerMidi(vm) {
// Create the parser
_midiParser = MidiParser::createParser_SMF();
@@ -701,7 +701,7 @@ MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) {
assert(_vm->_macResFork);
}
-bool MusicPlayerMac::load(uint32 fileref, bool loop) {
+bool MusicPlayerMac_t7g::load(uint32 fileref, bool loop) {
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
// First try for compressed MIDI
@@ -722,7 +722,7 @@ bool MusicPlayerMac::load(uint32 fileref, bool loop) {
return loadParser(file, loop);
}
-Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadStream *stream) {
+Common::SeekableReadStream *MusicPlayerMac_t7g::decompressMidi(Common::SeekableReadStream *stream) {
// Initialize an output buffer of the given size
uint32 size = stream->readUint32BE();
byte *output = (byte *)malloc(size);
@@ -768,6 +768,52 @@ Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadS
return new Common::MemoryReadStream(output, size, DisposeAfterUse::YES);
}
+// MusicPlayerMac_v2
+
+MusicPlayerMac_v2::MusicPlayerMac_v2(GroovieEngine *vm) : MusicPlayerMidi(vm) {
+ // Create the parser
+ _midiParser = MidiParser::createParser_QT();
+
+ // Create the driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ _driver = MidiDriver::createMidi(dev);
+ assert(_driver);
+
+ _driver->open(); // TODO: Handle return value != 0 (indicating an error)
+
+ // Set the parser's driver
+ _midiParser->setMidiDriver(this);
+
+ // Set the timer rate
+ _midiParser->setTimerRate(_driver->getBaseTempo());
+}
+
+bool MusicPlayerMac_v2::load(uint32 fileref, bool loop) {
+ debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
+
+ // Find correct filename
+ ResInfo info;
+ _vm->_resMan->getResInfo(fileref, info);
+ uint len = info.filename.size();
+ if (len < 4)
+ return false; // This shouldn't actually occur
+
+ // Remove the extension and add ".mov"
+ info.filename.deleteLastChar();
+ info.filename.deleteLastChar();
+ info.filename.deleteLastChar();
+ info.filename += "mov";
+
+ Common::SeekableReadStream *file = SearchMan.createReadStreamForMember(info.filename);
+
+ if (!file) {
+ warning("Could not find file '%s'", info.filename.c_str());
+ return false;
+ }
+
+ return loadParser(file, loop);
+}
+
MusicPlayerIOS::MusicPlayerIOS(GroovieEngine *vm) : MusicPlayer(vm) {
vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this, "groovieMusic");
}
diff --git a/engines/groovie/music.h b/engines/groovie/music.h
index cc852aa8dc..92e9c8b487 100644
--- a/engines/groovie/music.h
+++ b/engines/groovie/music.h
@@ -150,9 +150,9 @@ private:
void setTimbreMT(byte channel, const Timbre &timbre);
};
-class MusicPlayerMac : public MusicPlayerMidi {
+class MusicPlayerMac_t7g : public MusicPlayerMidi {
public:
- MusicPlayerMac(GroovieEngine *vm);
+ MusicPlayerMac_t7g(GroovieEngine *vm);
protected:
bool load(uint32 fileref, bool loop);
@@ -161,6 +161,14 @@ private:
Common::SeekableReadStream *decompressMidi(Common::SeekableReadStream *stream);
};
+class MusicPlayerMac_v2 : public MusicPlayerMidi {
+public:
+ MusicPlayerMac_v2(GroovieEngine *vm);
+
+protected:
+ bool load(uint32 fileref, bool loop);
+};
+
class MusicPlayerIOS : public MusicPlayer {
public:
MusicPlayerIOS(GroovieEngine *vm);
diff --git a/engines/groovie/stuffit.cpp b/engines/groovie/stuffit.cpp
new file mode 100644
index 0000000000..37f12585e7
--- /dev/null
+++ b/engines/groovie/stuffit.cpp
@@ -0,0 +1,537 @@
+/* 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.
+ *
+ */
+
+// Based on the StuffIt code in ResidualVM
+// StuffIt parsing based on http://code.google.com/p/theunarchiver/wiki/StuffItFormat
+// Compression 14 based on libxad (http://sourceforge.net/projects/libxad/)
+
+#include "groovie/stuffit.h"
+
+#include "common/archive.h"
+#include "common/bitstream.h"
+#include "common/debug.h"
+#include "common/hash-str.h"
+#include "common/hashmap.h"
+#include "common/memstream.h"
+#include "common/substream.h"
+
+namespace Groovie {
+
+struct SIT14Data;
+
+class StuffItArchive : public Common::Archive {
+public:
+ StuffItArchive();
+ ~StuffItArchive();
+
+ bool open(const Common::String &filename);
+ void close();
+ bool isOpen() const { return _stream != 0; }
+
+ // Common::Archive API implementation
+ bool hasFile(const Common::String &name) const;
+ int listMembers(Common::ArchiveMemberList &list) const;
+ const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
+ Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+
+private:
+ struct FileEntry {
+ byte compression;
+ uint32 uncompressedSize;
+ uint32 compressedSize;
+ uint32 offset;
+ };
+
+ Common::SeekableReadStream *_stream;
+
+ typedef Common::HashMap<Common::String, FileEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
+ FileMap _map;
+
+ // Decompression Functions
+ Common::SeekableReadStream *decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const;
+
+ // Decompression Helpers
+ void update14(uint16 first, uint16 last, byte *code, uint16 *freq) const;
+ void readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const;
+};
+
+StuffItArchive::StuffItArchive() : Common::Archive() {
+ _stream = 0;
+}
+
+StuffItArchive::~StuffItArchive() {
+ close();
+}
+
+// Some known values of StuffIt FourCC's
+// 11H Mac in particular uses ST46
+static const uint32 s_magicNumbers[] = {
+ MKTAG('S', 'I', 'T', '!'), MKTAG('S', 'T', '6', '5'), MKTAG('S', 'T', '5', '0'),
+ MKTAG('S', 'T', '6', '0'), MKTAG('S', 'T', 'i', 'n'), MKTAG('S', 'T', 'i', '2'),
+ MKTAG('S', 'T', 'i', '3'), MKTAG('S', 'T', 'i', '4'), MKTAG('S', 'T', '4', '6')
+};
+
+bool StuffItArchive::open(const Common::String &filename) {
+ close();
+
+ _stream = SearchMan.createReadStreamForMember(filename);
+
+ if (!_stream)
+ return false;
+
+ uint32 tag = _stream->readUint32BE();
+
+ // Check all the possible FourCC's
+ bool found = false;
+ for (int i = 0; i < ARRAYSIZE(s_magicNumbers); i++) {
+ if (tag == s_magicNumbers[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ // Didn't find one, let's bail out
+ if (!found) {
+ close();
+ return false;
+ }
+
+ /* uint16 fileCount = */ _stream->readUint16BE();
+ /* uint32 archiveSize = */ _stream->readUint32BE();
+
+ // Some sort of second magic number
+ if (_stream->readUint32BE() != MKTAG('r', 'L', 'a', 'u')) {
+ close();
+ return false;
+ }
+
+ /* byte version = */ _stream->readByte(); // meaning not clear
+
+ _stream->skip(7); // unknown
+
+ while (_stream->pos() < _stream->size() && !_stream->eos()) {
+ byte resForkCompression = _stream->readByte();
+ byte dataForkCompression = _stream->readByte();
+
+ byte fileNameLength = _stream->readByte();
+ Common::String name;
+
+ for (byte i = 0; i < fileNameLength; i++)
+ name += (char)_stream->readByte();
+
+ // Skip remaining bytes
+ _stream->skip(63 - fileNameLength);
+
+ /* uint32 fileType = */ _stream->readUint32BE();
+ /* uint32 fileCreator = */ _stream->readUint32BE();
+ /* uint16 finderFlags = */ _stream->readUint16BE();
+ /* uint32 creationDate = */ _stream->readUint32BE();
+ /* uint32 modificationDate = */ _stream->readUint32BE();
+ uint32 resForkUncompressedSize = _stream->readUint32BE();
+ uint32 dataForkUncompressedSize = _stream->readUint32BE();
+ uint32 resForkCompressedSize = _stream->readUint32BE();
+ uint32 dataForkCompressedSize = _stream->readUint32BE();
+ /* uint16 resForkCRC = */ _stream->readUint16BE();
+ /* uint16 dataForkCRC = */ _stream->readUint16BE();
+ _stream->skip(6); // unknown
+ /* uint16 headerCRC = */ _stream->readUint16BE();
+
+ // Ignore directories for now
+ if (dataForkCompression == 32 || dataForkCompression == 33)
+ continue;
+
+ if (dataForkUncompressedSize != 0) {
+ // We have a data fork
+
+ FileEntry entry;
+ entry.compression = dataForkCompression;
+ entry.uncompressedSize = dataForkUncompressedSize;
+ entry.compressedSize = dataForkCompressedSize;
+ entry.offset = _stream->pos() + resForkCompressedSize;
+ _map[name] = entry;
+
+ debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression);
+ }
+
+ if (resForkUncompressedSize != 0) {
+ // We have a resource fork
+
+ // Add a .rsrc extension so we know it's the resource fork
+ name += ".rsrc";
+
+ FileEntry entry;
+ entry.compression = resForkCompression;
+ entry.uncompressedSize = resForkUncompressedSize;
+ entry.compressedSize = resForkCompressedSize;
+ entry.offset = _stream->pos();
+ _map[name] = entry;
+
+ debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression);
+ }
+
+ // Go to the next entry
+ _stream->skip(dataForkCompressedSize + resForkCompressedSize);
+ }
+
+ return true;
+}
+
+void StuffItArchive::close() {
+ delete _stream; _stream = 0;
+ _map.clear();
+}
+
+bool StuffItArchive::hasFile(const Common::String &name) const {
+ return _map.contains(name);
+}
+
+int StuffItArchive::listMembers(Common::ArchiveMemberList &list) const {
+ for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++)
+ list.push_back(getMember(it->_key));
+
+ return _map.size();
+}
+
+const Common::ArchiveMemberPtr StuffItArchive::getMember(const Common::String &name) const {
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *StuffItArchive::createReadStreamForMember(const Common::String &name) const {
+ if (!_stream || !_map.contains(name))
+ return 0;
+
+ const FileEntry &entry = _map[name];
+
+ if (entry.compression & 0xF0)
+ error("Unhandled StuffIt encryption");
+
+ Common::SeekableSubReadStream subStream(_stream, entry.offset, entry.offset + entry.compressedSize);
+
+ // We currently only support type 14 compression
+ switch (entry.compression) {
+ case 0: // Uncompressed
+ return subStream.readStream(subStream.size());
+ case 14: // Installer
+ return decompress14(&subStream, entry.uncompressedSize);
+ default:
+ error("Unhandled StuffIt compression %d", entry.compression);
+ }
+
+ return 0;
+}
+
+void StuffItArchive::update14(uint16 first, uint16 last, byte *code, uint16 *freq) const {
+ uint16 i, j;
+
+ while (last - first > 1) {
+ i = first;
+ j = last;
+
+ do {
+ while (++i < last && code[first] > code[i])
+ ;
+
+ while (--j > first && code[first] < code[j])
+ ;
+
+ if (j > i) {
+ SWAP(code[i], code[j]);
+ SWAP(freq[i], freq[j]);
+ }
+ } while (j > i);
+
+ if (first != j) {
+ SWAP(code[first], code[j]);
+ SWAP(freq[first], freq[j]);
+
+ i = j + 1;
+
+ if (last - i <= j - first) {
+ update14(i, last, code, freq);
+ last = j;
+ } else {
+ update14(first, j, code, freq);
+ first = i;
+ }
+ } else {
+ ++first;
+ }
+ }
+}
+
+struct SIT14Data {
+ byte code[308];
+ byte codecopy[308];
+ uint16 freq[308];
+ uint32 buff[308];
+
+ byte var1[52];
+ uint16 var2[52];
+ uint16 var3[75 * 2];
+
+ byte var4[76];
+ uint32 var5[75];
+ byte var6[1024];
+ uint16 var7[308 * 2];
+ byte var8[0x4000];
+
+ byte window[0x40000];
+};
+
+// Realign to a byte boundary
+#define ALIGN_BITS(b) \
+ if (b->pos() & 7) \
+ b->skip(8 - (b->pos() & 7))
+
+void StuffItArchive::readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const {
+ uint32 i, l, n;
+ uint32 k = bits->getBit();
+ uint32 j = bits->getBits(2) + 2;
+ uint32 o = bits->getBits(3) + 1;
+ uint32 size = 1 << j;
+ uint32 m = size - 1;
+ k = k ? (m - 1) : 0xFFFFFFFF;
+
+ if (bits->getBits(2) & 1) { // skip 1 bit!
+ // requirements for this call: dat->buff[32], dat->code[32], dat->freq[32*2]
+ readTree14(bits, dat, size, dat->freq);
+
+ for (i = 0; i < codesize; ) {
+ l = 0;
+
+ do {
+ l = dat->freq[l + bits->getBit()];
+ n = size << 1;
+ } while (n > l);
+
+ l -= n;
+
+ if (k != l) {
+ if (l == m) {
+ l = 0;
+
+ do {
+ l = dat->freq[l + bits->getBit()];
+ n = size << 1;
+ } while (n > l);
+
+ l += 3 - n;
+
+ while (l--) {
+ dat->code[i] = dat->code[i - 1];
+ ++i;
+ }
+ } else {
+ dat->code[i++] = l + o;
+ }
+ } else {
+ dat->code[i++] = 0;
+ }
+ }
+ } else {
+ for (i = 0; i < codesize; ) {
+ l = bits->getBits(j);
+
+ if (k != l) {
+ if (l == m) {
+ l = bits->getBits(j) + 3;
+
+ while (l--) {
+ dat->code[i] = dat->code[i - 1];
+ ++i;
+ }
+ } else {
+ dat->code[i++] = l+o;
+ }
+ } else {
+ dat->code[i++] = 0;
+ }
+ }
+ }
+
+ for (i = 0; i < codesize; ++i) {
+ dat->codecopy[i] = dat->code[i];
+ dat->freq[i] = i;
+ }
+
+ update14(0, codesize, dat->codecopy, dat->freq);
+
+ for (i = 0; i < codesize && !dat->codecopy[i]; ++i)
+ ; // find first nonempty
+
+ for (j = 0; i < codesize; ++i, ++j) {
+ if (i)
+ j <<= (dat->codecopy[i] - dat->codecopy[i - 1]);
+
+ k = dat->codecopy[i]; m = 0;
+
+ for (l = j; k--; l >>= 1)
+ m = (m << 1) | (l & 1);
+
+ dat->buff[dat->freq[i]] = m;
+ }
+
+ for (i = 0; i < (uint32)codesize * 2; ++i)
+ result[i] = 0;
+
+ j = 2;
+
+ for (i = 0; i < codesize; ++i) {
+ l = 0;
+ m = dat->buff[i];
+
+ for (k = 0; k < dat->code[i]; ++k) {
+ l += (m & 1);
+
+ if (dat->code[i] - 1 <= (int32)k) {
+ result[l] = codesize * 2 + i;
+ } else {
+ if (!result[l]) {
+ result[l] = j;
+ j += 2;
+ }
+
+ l = result[l];
+ }
+
+ m >>= 1;
+ }
+ }
+
+ ALIGN_BITS(bits);
+}
+
+#define OUTPUT_VAL(x) \
+ out.writeByte(x); \
+ dat->window[j++] = x; \
+ j &= 0x3FFFF
+
+Common::SeekableReadStream *StuffItArchive::decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const {
+ byte *dst = (byte *)malloc(uncompressedSize);
+ Common::MemoryWriteStream out(dst, uncompressedSize);
+
+ Common::BitStream *bits = new Common::BitStream8LSB(src);
+
+ uint32 i, j, k, l, m, n;
+
+ SIT14Data *dat = new SIT14Data();
+
+ // initialization
+ for (i = k = 0; i < 52; ++i) {
+ dat->var2[i] = k;
+ k += (1 << (dat->var1[i] = ((i >= 4) ? ((i - 4) >> 2) : 0)));
+ }
+
+ for (i = 0; i < 4; ++i)
+ dat->var8[i] = i;
+
+ for (m = 1, l = 4; i < 0x4000; m <<= 1) // i is 4
+ for (n = l+4; l < n; ++l)
+ for (j = 0; j < m; ++j)
+ dat->var8[i++] = l;
+
+ for (i = 0, k = 1; i < 75; ++i) {
+ dat->var5[i] = k;
+ k += (1 << (dat->var4[i] = (i >= 3 ? ((i - 3) >> 2) : 0)));
+ }
+
+ for (i = 0; i < 4; ++i)
+ dat->var6[i] = i - 1;
+
+ for (m = 1, l = 3; i < 0x400; m <<= 1) // i is 4
+ for (n = l + 4; l < n; ++l)
+ for (j = 0; j < m; ++j)
+ dat->var6[i++] = l;
+
+ m = bits->getBits(16); // number of blocks
+ j = 0; // window position
+
+ while (m-- && !bits->eos()) {
+ bits->getBits(16); // skip crunched block size
+ bits->getBits(16);
+ n = bits->getBits(16); // number of uncrunched bytes
+ n |= bits->getBits(16) << 16;
+ readTree14(bits, dat, 308, dat->var7);
+ readTree14(bits, dat, 75, dat->var3);
+
+ while (n && !bits->eos()) {
+ for (i = 0; i < 616;)
+ i = dat->var7[i + bits->getBit()];
+
+ i -= 616;
+
+ if (i < 0x100) {
+ OUTPUT_VAL(i);
+ --n;
+ } else {
+ i -= 0x100;
+ k = dat->var2[i]+4;
+ i = dat->var1[i];
+
+ if (i)
+ k += bits->getBits(i);
+
+ for (i = 0; i < 150;)
+ i = dat->var3[i + bits->getBit()];
+
+ i -= 150;
+ l = dat->var5[i];
+ i = dat->var4[i];
+
+ if (i)
+ l += bits->getBits(i);
+
+ n -= k;
+ l = j + 0x40000 - l;
+
+ while (k--) {
+ l &= 0x3FFFF;
+ OUTPUT_VAL(dat->window[l]);
+ l++;
+ }
+ }
+ }
+
+ ALIGN_BITS(bits);
+ }
+
+ delete dat;
+ delete bits;
+
+ return new Common::MemoryReadStream(dst, uncompressedSize, DisposeAfterUse::YES);
+}
+
+#undef OUTPUT_VAL
+#undef ALIGN_BITS
+
+Common::Archive *createStuffItArchive(const Common::String &fileName) {
+ StuffItArchive *archive = new StuffItArchive();
+
+ if (!archive->open(fileName)) {
+ delete archive;
+ return 0;
+ }
+
+ return archive;
+}
+
+} // End of namespace Groovie
diff --git a/engines/groovie/stuffit.h b/engines/groovie/stuffit.h
new file mode 100644
index 0000000000..44f593dbea
--- /dev/null
+++ b/engines/groovie/stuffit.h
@@ -0,0 +1,43 @@
+/* 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 GROOVIE_STUFFIT_H
+#define GROOVIE_STUFFIT_H
+
+namespace Common {
+class Archive;
+class String;
+}
+
+namespace Groovie {
+
+/**
+ * This factory method creates an Archive instance corresponding to the content
+ * of the StuffIt compressed file.
+ *
+ * May return 0 in case of a failure.
+ */
+Common::Archive *createStuffItArchive(const Common::String &fileName);
+
+} // End of namespace Groovie
+
+#endif
diff --git a/engines/hugo/parser.cpp b/engines/hugo/parser.cpp
index 5fdb2026a7..2585c64fd8 100644
--- a/engines/hugo/parser.cpp
+++ b/engines/hugo/parser.cpp
@@ -235,7 +235,7 @@ void Parser::charHandler() {
if (_cmdLineIndex >= kMaxLineSize) {
//MessageBeep(MB_ICONASTERISK);
warning("STUB: MessageBeep() - Command line too long");
- } else if (isprint(static_cast<unsigned char>(c))) {
+ } else if (Common::isPrint(c)) {
_cmdLine[_cmdLineIndex++] = c;
_cmdLine[_cmdLineIndex] = '\0';
}
diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp
index a7a650d8ed..fc957e895e 100644
--- a/engines/mohawk/console.cpp
+++ b/engines/mohawk/console.cpp
@@ -75,7 +75,7 @@ bool MystConsole::Cmd_ChangeCard(int argc, const char **argv) {
}
_vm->_sound->stopSound();
- _vm->changeToCard((uint16)atoi(argv[1]), true);
+ _vm->changeToCard((uint16)atoi(argv[1]), kTransitionCopy);
return false;
}
diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp
index 4461a30ad4..5f5a3b3800 100644
--- a/engines/mohawk/dialogs.cpp
+++ b/engines/mohawk/dialogs.cpp
@@ -137,12 +137,6 @@ void MystOptionsDialog::open() {
void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
- case kZipCmd:
- _vm->_gameState->_globals.zipMode = _zipModeCheckbox->getState();
- break;
- case kTransCmd:
- _vm->_gameState->_globals.transitions = _transitionsCheckbox->getState();
- break;
case kDropCmd:
_vm->_needsPageDrop = true;
close();
@@ -155,8 +149,10 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui
_vm->_needsShowDemoMenu = true;
close();
break;
- case GUI::kCloseCmd:
- close();
+ case GUI::kOKCmd:
+ _vm->_gameState->_globals.zipMode = _zipModeCheckbox->getState();
+ _vm->_gameState->_globals.transitions = _transitionsCheckbox->getState();
+ GUI::OptionsDialog::handleCommand(sender, cmd, data);
break;
default:
GUI::OptionsDialog::handleCommand(sender, cmd, data);
diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp
index 9c0e642203..8140817eb3 100644
--- a/engines/mohawk/myst.cpp
+++ b/engines/mohawk/myst.cpp
@@ -523,7 +523,7 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS
_video->playMovieBlockingCentered(wrapMovieFilename(flyby, kMasterpieceOnly));
}
- changeToCard(card, true);
+ changeToCard(card, kTransitionCopy);
if (linkDstSound)
_sound->playSoundBlocking(linkDstSound);
@@ -549,7 +549,7 @@ void MohawkEngine_Myst::drawCardBackground() {
_gfx->copyImageToBackBuffer(getCardBackgroundId(), Common::Rect(0, 0, 544, 332));
}
-void MohawkEngine_Myst::changeToCard(uint16 card, bool updateScreen) {
+void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) {
debug(2, "changeToCard(%d)", card);
_scriptParser->disablePersistentScripts();
@@ -629,9 +629,11 @@ void MohawkEngine_Myst::changeToCard(uint16 card, bool updateScreen) {
}
// Make sure the screen is updated
- if (updateScreen) {
- _gfx->copyBackBufferToScreen(Common::Rect(544, 333));
- _system->updateScreen();
+ if (transition != kNoTransition) {
+ if (!_gameState->_globals.transitions)
+ transition = kTransitionCopy;
+
+ _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0);
}
// Make sure we have the right cursor showing
@@ -1179,41 +1181,41 @@ bool MohawkEngine_Myst::canSaveGameStateCurrently() {
}
void MohawkEngine_Myst::dropPage() {
- uint16 page = _gameState->_globals.heldPage;
+ uint16 page = _gameState->_globals.heldPage;
bool whitePage = page == 13;
bool bluePage = page - 1 < 6;
- bool redPage = page - 7 < 6;
-
- // Play drop page sound
- _sound->replaceSoundMyst(800);
-
- // Drop page
- _gameState->_globals.heldPage = 0;
-
- // Redraw page area
- if (whitePage && _gameState->_globals.currentAge == 2) {
- redrawArea(41);
- } else if (bluePage) {
- if (page == 6) {
- if (_gameState->_globals.currentAge == 2)
- redrawArea(24);
- } else {
- redrawArea(103);
- }
- } else if (redPage) {
- if (page == 12) {
- if (_gameState->_globals.currentAge == 2)
- redrawArea(25);
- } else if (page == 10) {
- if (_gameState->_globals.currentAge == 1)
- redrawArea(35);
- } else {
- redrawArea(102);
- }
- }
-
- setMainCursor(kDefaultMystCursor);
- checkCursorHints();
+ bool redPage = page - 7 < 6;
+
+ // Play drop page sound
+ _sound->replaceSoundMyst(800);
+
+ // Drop page
+ _gameState->_globals.heldPage = 0;
+
+ // Redraw page area
+ if (whitePage && _gameState->_globals.currentAge == 2) {
+ redrawArea(41);
+ } else if (bluePage) {
+ if (page == 6) {
+ if (_gameState->_globals.currentAge == 2)
+ redrawArea(24);
+ } else {
+ redrawArea(103);
+ }
+ } else if (redPage) {
+ if (page == 12) {
+ if (_gameState->_globals.currentAge == 2)
+ redrawArea(25);
+ } else if (page == 10) {
+ if (_gameState->_globals.currentAge == 1)
+ redrawArea(35);
+ } else {
+ redrawArea(102);
+ }
+ }
+
+ setMainCursor(kDefaultMystCursor);
+ checkCursorHints();
}
} // End of namespace Mohawk
diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h
index 30770f7ec9..a268c19737 100644
--- a/engines/mohawk/myst.h
+++ b/engines/mohawk/myst.h
@@ -75,6 +75,23 @@ enum {
kStoneshipStack // Stoneship Age
};
+// Transitions
+enum TransitionType {
+ kTransitionLeftToRight = 0,
+ kTransitionRightToLeft = 1,
+ kTransitionSlideToLeft = 2,
+ kTransitionSlideToRight = 3,
+ kTransitionDissolve = 4,
+ kTransitionTopToBottom = 5,
+ kTransitionBottomToTop = 6,
+ kTransitionSlideToTop = 7,
+ kTransitionSlideToBottom= 8,
+ kTransitionPartToRight = 9,
+ kTransitionPartToLeft = 10,
+ kTransitionCopy = 11,
+ kNoTransition = 999
+};
+
const uint16 kMasterpieceOnly = 0xFFFF;
struct MystCondition {
@@ -154,7 +171,7 @@ public:
void reloadSaveList();
void changeToStack(uint16 stack, uint16 card, uint16 linkSrcSound, uint16 linkDstSound);
- void changeToCard(uint16 card, bool updateScreen);
+ void changeToCard(uint16 card, TransitionType transition);
uint16 getCurCard() { return _curCard; }
uint16 getCurStack() { return _curStack; }
void setMainCursor(uint16 cursor);
diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp
index a54b67bef4..12a2c7f44c 100644
--- a/engines/mohawk/myst_areas.cpp
+++ b/engines/mohawk/myst_areas.cpp
@@ -70,10 +70,30 @@ MystResource::~MystResource() {
}
void MystResource::handleMouseUp() {
- if (_dest != 0)
- _vm->changeToCard(_dest, true);
- else
+ if (_dest == 0) {
warning("Movement type resource with null destination at position (%d, %d), (%d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom);
+ return;
+ }
+
+ uint16 opcode;
+
+ switch (type) {
+ case kMystForwardArea:
+ opcode = 6;
+ break;
+ case kMystLeftArea:
+ opcode = 8;
+ break;
+ case kMystRightArea:
+ opcode = 7;
+ break;
+ default:
+ opcode = 48;
+ break;
+ }
+
+ _vm->_scriptParser->setInvokingResource(this);
+ _vm->_scriptParser->runOpcode(opcode, 0);
}
bool MystResource::canBecomeActive() {
@@ -202,22 +222,23 @@ VideoHandle MystResourceType6::playMovie() {
// Check if the video is already running
VideoHandle handle = _vm->_video->findVideoHandle(_videoFile);
- if (_direction != 1)
- warning("Playing QT movies backwards is not implemented");
-
// If the video is not running, play it
if (handle == NULL_VID_HANDLE || _vm->_video->endOfVideo(handle)) {
- if (_playBlocking) {
- _vm->_video->playMovieBlocking(_videoFile, _left, _top);
- handle = NULL_VID_HANDLE;
- } else {
- handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop);
+ handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop);
+ if (_direction == -1) {
+ _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle));
+ _vm->_video->setVideoRate(handle, -1);
}
} else {
// Resume the video
_vm->_video->pauseMovie(handle, false);
}
+ if (_playBlocking) {
+ _vm->_video->waitUntilMovieEnds(handle);
+ handle = NULL_VID_HANDLE;
+ }
+
return handle;
}
diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp
index 2df0f7e6ba..6a292c66e2 100644
--- a/engines/mohawk/myst_graphics.cpp
+++ b/engines/mohawk/myst_graphics.cpp
@@ -209,15 +209,15 @@ void MystGraphics::copyBackBufferToScreen(Common::Rect r) {
_vm->_system->copyRectToScreen(_backBuffer->getBasePtr(r.left, r.top), _backBuffer->pitch, r.left, r.top, r.width(), r.height());
}
-void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, uint16 delay) {
+void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay) {
// Do not artificially delay during transitions
int oldEnableDrawingTimeSimulation = _enableDrawingTimeSimulation;
_enableDrawingTimeSimulation = 0;
switch (type) {
- case 0: {
- debugC(kDebugScript, "Left to Right");
+ case kTransitionLeftToRight: {
+ debugC(kDebugView, "Left to Right");
uint16 step = (rect.right - rect.left) / steps;
Common::Rect area = rect;
@@ -239,8 +239,8 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u
}
}
break;
- case 1: {
- debugC(kDebugScript, "Right to Left");
+ case kTransitionRightToLeft: {
+ debugC(kDebugView, "Right to Left");
uint16 step = (rect.right - rect.left) / steps;
Common::Rect area = rect;
@@ -262,8 +262,25 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u
}
}
break;
- case 5: {
- debugC(kDebugScript, "Top to Bottom");
+ case kTransitionSlideToLeft:
+ debugC(kDebugView, "Slide to left");
+ transitionSlideToLeft(rect, steps, delay);
+ break;
+ case kTransitionSlideToRight:
+ debugC(kDebugView, "Slide to right");
+ transitionSlideToRight(rect, steps, delay);
+ break;
+ case kTransitionDissolve: {
+ debugC(kDebugView, "Dissolve");
+
+ for (int16 step = 0; step < 8; step++) {
+ simulatePreviousDrawDelay(rect);
+ transitionDissolve(rect, step);
+ }
+ }
+ break;
+ case kTransitionTopToBottom: {
+ debugC(kDebugView, "Top to Bottom");
uint16 step = (rect.bottom - rect.top) / steps;
Common::Rect area = rect;
@@ -285,8 +302,8 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u
}
}
break;
- case 6: {
- debugC(kDebugScript, "Bottom to Top");
+ case kTransitionBottomToTop: {
+ debugC(kDebugView, "Bottom to Top");
uint16 step = (rect.bottom - rect.top) / steps;
Common::Rect area = rect;
@@ -308,18 +325,260 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u
}
}
break;
- default:
- warning("Unknown Update Direction");
+ case kTransitionSlideToTop:
+ debugC(kDebugView, "Slide to top");
+ transitionSlideToTop(rect, steps, delay);
+ break;
+ case kTransitionSlideToBottom:
+ debugC(kDebugView, "Slide to bottom");
+ transitionSlideToBottom(rect, steps, delay);
+ break;
+ case kTransitionPartToRight: {
+ debugC(kDebugView, "Partial left to right");
+
+ transitionPartialToRight(rect, 75, 3);
+ }
+ break;
+ case kTransitionPartToLeft: {
+ debugC(kDebugView, "Partial right to left");
- //TODO: Replace minimal implementation
+ transitionPartialToLeft(rect, 75, 3);
+ }
+ break;
+ case kTransitionCopy:
copyBackBufferToScreen(rect);
_vm->_system->updateScreen();
break;
+ default:
+ error("Unknown transition %d", type);
}
_enableDrawingTimeSimulation = oldEnableDrawingTimeSimulation;
}
+void MystGraphics::transitionDissolve(Common::Rect rect, uint step) {
+ static const bool pattern[][4][4] = {
+ {
+ { true, false, false, false },
+ { false, false, false, false },
+ { false, false, true, false },
+ { false, false, false, false }
+ },
+ {
+ { false, false, true, false },
+ { false, false, false, false },
+ { true, false, false, false },
+ { false, false, false, false }
+ },
+ {
+ { false, false, false, false },
+ { false, true, false, false },
+ { false, false, false, false },
+ { false, false, false, true }
+ },
+ {
+ { false, false, false, false },
+ { false, false, false, true },
+ { false, false, false, false },
+ { false, true, false, false }
+ },
+ {
+ { false, false, false, false },
+ { false, false, true, false },
+ { false, true, false, false },
+ { false, false, false, false }
+ },
+ {
+ { false, true, false, false },
+ { false, false, false, false },
+ { false, false, false, false },
+ { false, false, true, false }
+ },
+ {
+ { false, false, false, false },
+ { true, false, false, false },
+ { false, false, false, true },
+ { false, false, false, false }
+ },
+ {
+ { false, false, false, true },
+ { false, false, false, false },
+ { false, false, false, false },
+ { true, false, false, false }
+ }
+ };
+
+ rect.clip(_viewport);
+
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+
+ for (uint16 y = rect.top; y < rect.bottom; y++) {
+ const bool *linePattern = pattern[step][y % 4];
+
+ if (!linePattern[0] && !linePattern[1] && !linePattern[2] && !linePattern[3])
+ continue;
+
+ for (uint16 x = rect.left; x < rect.right; x++) {
+ if (linePattern[x % 4]) {
+ if (_pixelFormat.bytesPerPixel == 2) {
+ uint16 *dst = (uint16 *)screen->getBasePtr(x, y);
+ *dst = *(const uint16 *)_backBuffer->getBasePtr(x, y);
+ } else {
+ uint32 *dst = (uint32 *)screen->getBasePtr(x, y);
+ *dst = *(const uint32 *)_backBuffer->getBasePtr(x, y);
+ }
+ }
+ }
+ }
+
+ _vm->_system->unlockScreen();
+ _vm->_system->updateScreen();
+}
+
+void MystGraphics::transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = (rect.right - rect.left) / steps;
+ Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom);
+ Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.right = dstRect.left + step * stepWidth;
+ srcRect.left = srcRect.right - step * stepWidth;
+
+ _vm->_system->delayMillis(delay);
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+ if (dstRect.right != rect.right) {
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+ }
+}
+
+void MystGraphics::transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = (rect.right - rect.left) / steps;
+ Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom);
+ Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.left = dstRect.right - step * stepWidth;
+ srcRect.right = srcRect.left + step * stepWidth;
+
+ _vm->_system->delayMillis(delay);
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+ if (dstRect.left != rect.left) {
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+ }
+}
+
+void MystGraphics::transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 delay) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = (rect.bottom - rect.top) / steps;
+ Common::Rect srcRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom);
+ Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.right, rect.top);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.bottom = dstRect.top + step * stepWidth;
+ srcRect.top = srcRect.bottom - step * stepWidth;
+
+ _vm->_system->delayMillis(delay);
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+
+ if (dstRect.bottom < rect.bottom) {
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+ }
+}
+
+void MystGraphics::transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = (rect.bottom - rect.top) / steps;
+ Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.right, rect.top);
+ Common::Rect dstRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.top = dstRect.bottom - step * stepWidth;
+ srcRect.bottom = srcRect.top + step * stepWidth;
+
+ _vm->_system->delayMillis(delay);
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+
+ if (dstRect.top > rect.top) {
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+ }
+}
+
+void MystGraphics::transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = width / steps;
+ Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom);
+ Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.right = dstRect.left + step * stepWidth;
+ srcRect.left = srcRect.right - step * stepWidth;
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+}
+
+void MystGraphics::transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps) {
+ rect.clip(_viewport);
+
+ uint32 stepWidth = width / steps;
+ Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom);
+ Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom);
+
+ for (uint step = 1; step <= steps; step++) {
+ dstRect.left = dstRect.right - step * stepWidth;
+ srcRect.right = srcRect.left + step * stepWidth;
+
+ simulatePreviousDrawDelay(dstRect);
+ _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top),
+ _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
+ _vm->_system->updateScreen();
+ }
+
+ copyBackBufferToScreen(rect);
+ _vm->_system->updateScreen();
+}
+
void MystGraphics::drawRect(Common::Rect rect, RectState state) {
rect.clip(_viewport);
diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h
index de8fe521e6..4bbc8d5b8c 100644
--- a/engines/mohawk/myst_graphics.h
+++ b/engines/mohawk/myst_graphics.h
@@ -48,7 +48,7 @@ public:
void copyImageToScreen(uint16 image, Common::Rect dest);
void copyImageToBackBuffer(uint16 image, Common::Rect dest);
void copyBackBufferToScreen(Common::Rect r);
- void runTransition(uint16 type, Common::Rect rect, uint16 steps, uint16 delay);
+ void runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay);
void drawRect(Common::Rect rect, RectState state);
void drawLine(const Common::Point &p1, const Common::Point &p2, uint32 color);
void enableDrawingTimeSimulation(bool enable);
@@ -60,7 +60,13 @@ protected:
MohawkEngine *getVM() { return (MohawkEngine *)_vm; }
void simulatePreviousDrawDelay(const Common::Rect &dest);
void copyBackBufferToScreenWithSaturation(int16 saturation);
-
+ void transitionDissolve(Common::Rect rect, uint step);
+ void transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay);
+ void transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay);
+ void transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 delay);
+ void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay);
+ void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps);
+ void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps);
private:
MohawkEngine_Myst *_vm;
MystBitmap *_bmpDecoder;
diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp
index 107a8b03e9..c1b75df4cf 100644
--- a/engines/mohawk/myst_scripts.cpp
+++ b/engines/mohawk/myst_scripts.cpp
@@ -100,18 +100,18 @@ void MystScriptParser::setupCommonOpcodes() {
// "Standard" Opcodes
OPCODE(0, o_toggleVar);
OPCODE(1, o_setVar);
- OPCODE(2, o_changeCardSwitch);
+ OPCODE(2, o_changeCardSwitch4);
OPCODE(3, o_takePage);
OPCODE(4, o_redrawCard);
// Opcode 5 Not Present
- OPCODE(6, o_goToDest);
- OPCODE(7, o_goToDest);
- OPCODE(8, o_goToDest);
+ OPCODE(6, o_goToDestForward);
+ OPCODE(7, o_goToDestLeft);
+ OPCODE(8, o_goToDestRight);
OPCODE(9, o_triggerMovie);
OPCODE(10, o_toggleVarNoRedraw);
// Opcode 11 Not Present
- OPCODE(12, o_changeCardSwitch);
- OPCODE(13, o_changeCardSwitch);
+ OPCODE(12, o_changeCardSwitchLtR);
+ OPCODE(13, o_changeCardSwitchRtL);
OPCODE(14, o_drawAreaState);
OPCODE(15, o_redrawAreaForVar);
OPCODE(16, o_changeCardDirectional);
@@ -120,7 +120,7 @@ void MystScriptParser::setupCommonOpcodes() {
OPCODE(19, o_enableAreas);
OPCODE(20, o_disableAreas);
OPCODE(21, o_directionalUpdate);
- OPCODE(22, o_goToDest);
+ OPCODE(22, o_goToDestUp);
OPCODE(23, o_toggleAreasActivation);
OPCODE(24, o_playSound);
// Opcode 25 is unused; original calls replaceSoundMyst
@@ -145,6 +145,7 @@ void MystScriptParser::setupCommonOpcodes() {
OPCODE(44, o_restoreMainCursor);
// Opcode 45 Not Present
OPCODE(46, o_soundWaitStop);
+ OPCODE(48, o_goToDest);
OPCODE(51, o_exitMap);
// Opcodes 47 to 99 Not Present
@@ -273,7 +274,7 @@ void MystScriptParser::animatedUpdate(uint16 argc, uint16 *argv, uint16 delay) {
while (argsRead < argc) {
Common::Rect rect = Common::Rect(argv[argsRead], argv[argsRead + 1], argv[argsRead + 2], argv[argsRead + 3]);
- uint16 kind = argv[argsRead + 4];
+ TransitionType kind = static_cast<TransitionType>(argv[argsRead + 4]);
uint16 steps = argv[argsRead + 5];
debugC(kDebugScript, "\trect.left: %d", rect.left);
@@ -323,16 +324,41 @@ void MystScriptParser::o_setVar(uint16 op, uint16 var, uint16 argc, uint16 *argv
_vm->redrawArea(var);
}
-void MystScriptParser::o_changeCardSwitch(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
- // Opcodes 2, 12, and 13 are the same
+void MystScriptParser::o_changeCardSwitch4(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
uint16 value = getVar(var);
debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value);
if (value)
- _vm->changeToCard(argv[value -1 ], true);
+ _vm->changeToCard(argv[value -1 ], kTransitionDissolve);
else if (_invokingResource != NULL)
- _vm->changeToCard(_invokingResource->getDest(), true);
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve);
+ else
+ warning("Missing invokingResource in altDest call");
+}
+
+void MystScriptParser::o_changeCardSwitchLtR(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ uint16 value = getVar(var);
+
+ debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value);
+
+ if (value)
+ _vm->changeToCard(argv[value -1 ], kTransitionLeftToRight);
+ else if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionLeftToRight);
+ else
+ warning("Missing invokingResource in altDest call");
+}
+
+void MystScriptParser::o_changeCardSwitchRtL(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ uint16 value = getVar(var);
+
+ debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value);
+
+ if (value)
+ _vm->changeToCard(argv[value -1 ], kTransitionRightToLeft);
+ else if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionRightToLeft);
else
warning("Missing invokingResource in altDest call");
}
@@ -373,10 +399,47 @@ void MystScriptParser::o_goToDest(uint16 op, uint16 var, uint16 argc, uint16 *ar
debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
if (_invokingResource != NULL)
- _vm->changeToCard(_invokingResource->getDest(), true);
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionCopy);
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+}
+
+void MystScriptParser::o_goToDestForward(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve);
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+}
+
+void MystScriptParser::o_goToDestLeft(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToRight);
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+}
+
+void MystScriptParser::o_goToDestRight(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToLeft);
else
warning("Opcode %d: Missing invokingResource", op);
}
+
+void MystScriptParser::o_goToDestUp(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest(), kTransitionTopToBottom);
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+}
+
void MystScriptParser::o_triggerMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Trigger Type 6 Resource Movie..", op);
// TODO: If movie has sound, pause background music
@@ -427,7 +490,7 @@ void MystScriptParser::o_changeCardDirectional(uint16 op, uint16 var, uint16 arg
debugC(kDebugScript, "\tcardId: %d", cardId);
debugC(kDebugScript, "\tdirectonal update data size: %d", directionalUpdateDataSize);
- _vm->changeToCard(cardId, false);
+ _vm->changeToCard(cardId, kNoTransition);
animatedUpdate(directionalUpdateDataSize, &argv[2], 0);
}
@@ -440,23 +503,23 @@ void MystScriptParser::o_changeCardPush(uint16 op, uint16 var, uint16 argc, uint
debugC(kDebugScript, "Opcode %d: Jump to Card Id, Storing Current Card Id", op);
_savedCardId = _vm->getCurCard();
- uint16 cardId = argv[0];
- // argv[1] is not used in the original engine
+ uint16 cardId = argv[0];
+ TransitionType transition = static_cast<TransitionType>(argv[1]);
debugC(kDebugScript, "\tCurrent CardId: %d", _savedCardId);
debugC(kDebugScript, "\tJump to CardId: %d", cardId);
- _vm->changeToCard(cardId, true);
+ _vm->changeToCard(cardId, transition);
}
void MystScriptParser::o_changeCardPop(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Return To Stored Card Id", op);
debugC(kDebugScript, "\tCardId: %d", _savedCardId);
- // argv[0] is not used in the original engine
+ TransitionType transition = static_cast<TransitionType>(argv[0]);
- _vm->changeToCard(_savedCardId, true);
+ _vm->changeToCard(_savedCardId, transition);
}
void MystScriptParser::o_enableAreas(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -752,14 +815,11 @@ void MystScriptParser::o_changeCard(uint16 op, uint16 var, uint16 argc, uint16 *
debugC(kDebugScript, "Opcode %d: Change Card", op);
uint16 cardId = argv[0];
-
- // Argument 1 if present is not used
- // uint16 u0 = argv[1];
+ TransitionType transition = static_cast<TransitionType>(argv[1]);
debugC(kDebugScript, "\tTarget Card: %d", cardId);
- //debugC(kDebugScript, "\tu0: %d", u0); // Unused data
- _vm->changeToCard(cardId, true);
+ _vm->changeToCard(cardId, transition);
}
void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -767,7 +827,7 @@ void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc,
uint16 imageId = argv[0];
uint16 cardId = argv[1];
- // argv[2] is not used in the original engine
+ TransitionType transition = static_cast<TransitionType>(argv[2]);
debugC(kDebugScript, "\timageId: %d", imageId);
debugC(kDebugScript, "\tcardId: %d", cardId);
@@ -775,7 +835,7 @@ void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc,
_vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333));
_vm->_system->updateScreen();
- _vm->changeToCard(cardId, true);
+ _vm->changeToCard(cardId, transition);
}
void MystScriptParser::o_changeMainCursor(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -850,7 +910,7 @@ void MystScriptParser::o_changeCardPlaySoundDirectional(uint16 op, uint16 var, u
if (soundId)
_vm->_sound->replaceSoundMyst(soundId);
- _vm->changeToCard(cardId, false);
+ _vm->changeToCard(cardId, kNoTransition);
animatedUpdate(dataSize, &argv[4], delayBetweenSteps);
}
@@ -901,12 +961,12 @@ void MystScriptParser::o_quit(uint16 op, uint16 var, uint16 argc, uint16 *argv)
void MystScriptParser::showMap() {
if (_vm->getCurCard() != getMap()) {
_savedMapCardId = _vm->getCurCard();
- _vm->changeToCard(getMap(), true);
+ _vm->changeToCard(getMap(), kTransitionCopy);
}
}
void MystScriptParser::o_exitMap(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
- _vm->changeToCard(_savedMapCardId, true);
+ _vm->changeToCard(_savedMapCardId, kTransitionCopy);
}
} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_scripts.h b/engines/mohawk/myst_scripts.h
index ccb76e0dc8..b75da0801a 100644
--- a/engines/mohawk/myst_scripts.h
+++ b/engines/mohawk/myst_scripts.h
@@ -86,10 +86,16 @@ public:
// Common opcodes
DECLARE_OPCODE(o_toggleVar);
DECLARE_OPCODE(o_setVar);
- DECLARE_OPCODE(o_changeCardSwitch);
+ DECLARE_OPCODE(o_changeCardSwitch4);
+ DECLARE_OPCODE(o_changeCardSwitchLtR);
+ DECLARE_OPCODE(o_changeCardSwitchRtL);
DECLARE_OPCODE(o_takePage);
DECLARE_OPCODE(o_redrawCard);
DECLARE_OPCODE(o_goToDest);
+ DECLARE_OPCODE(o_goToDestForward);
+ DECLARE_OPCODE(o_goToDestLeft);
+ DECLARE_OPCODE(o_goToDestRight);
+ DECLARE_OPCODE(o_goToDestUp);
DECLARE_OPCODE(o_triggerMovie);
DECLARE_OPCODE(o_toggleVarNoRedraw);
DECLARE_OPCODE(o_drawAreaState);
diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp
index 069281f5dc..63ba5f7c85 100644
--- a/engines/mohawk/myst_stacks/channelwood.cpp
+++ b/engines/mohawk/myst_stacks/channelwood.cpp
@@ -341,7 +341,7 @@ void Channelwood::o_drawImageChangeCardAndVolume(uint16 op, uint16 var, uint16 a
_vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333));
_vm->_system->updateScreen();
- _vm->changeToCard(cardId, true);
+ _vm->changeToCard(cardId, kTransitionPartToLeft);
if (argc == 3) {
uint16 volume = argv[2];
@@ -476,9 +476,12 @@ void Channelwood::o_stairsDoorToggle(uint16 op, uint16 var, uint16 argc, uint16
MystResourceType6 *movie = static_cast<MystResourceType6 *>(_invokingResource);
if (_state.stairsUpperDoorState) {
- // TODO: Play backwards
+ // Close door, play the open movie backwards
+ movie->setDirection(-1);
movie->playMovie();
} else {
+ // Open door
+ movie->setDirection(1);
movie->playMovie();
}
}
diff --git a/engines/mohawk/myst_stacks/demo.cpp b/engines/mohawk/myst_stacks/demo.cpp
index 29a12571fd..9f393ea401 100644
--- a/engines/mohawk/myst_stacks/demo.cpp
+++ b/engines/mohawk/myst_stacks/demo.cpp
@@ -104,14 +104,14 @@ void Demo::returnToMenu_run() {
switch (_returnToMenuStep){
case 0:
_vm->_gfx->fadeToBlack();
- _vm->changeToCard(2003, false);
+ _vm->changeToCard(2003, kNoTransition);
_vm->_gfx->fadeFromBlack();
_returnToMenuStep++;
break;
case 1:
_vm->_gfx->fadeToBlack();
- _vm->changeToCard(2001, false);
+ _vm->changeToCard(2001, kNoTransition);
_vm->_gfx->fadeFromBlack();
_vm->_cursor->showCursor();
diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp
index 545b97d956..71733227ac 100644
--- a/engines/mohawk/myst_stacks/intro.cpp
+++ b/engines/mohawk/myst_stacks/intro.cpp
@@ -127,9 +127,9 @@ void Intro::introMovies_run() {
break;
default:
if (_vm->getFeatures() & GF_DEMO)
- _vm->changeToCard(2001, true);
+ _vm->changeToCard(2001, kTransitionRightToLeft);
else
- _vm->changeToCard(2, true);
+ _vm->changeToCard(2, kTransitionRightToLeft);
}
}
@@ -148,7 +148,7 @@ void Intro::mystLinkBook_run() {
_vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333));
}
} else if (!_linkBookMovie->isPlaying()) {
- _vm->changeToCard(5, true);
+ _vm->changeToCard(5, kTransitionRightToLeft);
}
}
diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp
index 79de03308c..ce6f902851 100644
--- a/engines/mohawk/myst_stacks/mechanical.cpp
+++ b/engines/mohawk/myst_stacks/mechanical.cpp
@@ -39,8 +39,17 @@ Mechanical::Mechanical(MohawkEngine_Myst *vm) :
MystScriptParser(vm), _state(vm->_gameState->_mechanical) {
setupOpcodes();
+ _elevatorGoingMiddle = false;
+
_mystStaircaseState = false;
_fortressPosition = 0;
+ _fortressRotationSpeed = 0;
+ _fortressSimulationSpeed = 0;
+ _gearsWereRunning = false;
+
+ _fortressRotationShortMovieWorkaround = false;
+ _fortressRotationShortMovieCount = 0;
+ _fortressRotationShortMovieLast = 0;
}
Mechanical::~Mechanical() {
@@ -74,9 +83,9 @@ void Mechanical::setupOpcodes() {
OPCODE(121, o_elevatorWindowMovie);
OPCODE(122, o_elevatorGoMiddle);
OPCODE(123, o_elevatorTopMovie);
- OPCODE(124, opcode_124);
+ OPCODE(124, o_fortressRotationSetPosition);
OPCODE(125, o_mystStaircaseMovie);
- OPCODE(126, opcode_126);
+ OPCODE(126, o_elevatorWaitTimeout);
OPCODE(127, o_crystalEnterYellow);
OPCODE(128, o_crystalLeaveYellow);
OPCODE(129, o_crystalEnterGreen);
@@ -103,7 +112,6 @@ void Mechanical::setupOpcodes() {
void Mechanical::disablePersistentScripts() {
_fortressSimulationRunning = false;
_elevatorRotationLeverMoving = false;
- _elevatorGoingMiddle = false;
_birdSinging = false;
_fortressRotationRunning = false;
}
@@ -599,30 +607,33 @@ void Mechanical::elevatorGoMiddle_run() {
_vm->_gfx->copyBackBufferToScreen(Common::Rect(10, 137, 61, 165));
_vm->_system->updateScreen();
}
- } else if (_elevatorInCabin) {
+ } else {
_elevatorTooLate = true;
-
- // Elevator going to middle animation
- _vm->_cursor->hideCursor();
- _vm->_sound->playSoundBlocking(11120);
- _vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333));
- _vm->_sound->replaceSoundMyst(12120);
- _vm->_gfx->runTransition(2, Common::Rect(177, 0, 370, 333), 25, 0);
- _vm->_sound->playSoundBlocking(13120);
- _vm->_sound->replaceSoundMyst(8120);
- _vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333));
- _vm->_system->delayMillis(500);
- _vm->_sound->replaceSoundMyst(9120);
- static uint16 moviePos[2] = { 3540, 5380 };
- o_elevatorWindowMovie(121, 0, 2, moviePos);
- _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333));
- _vm->_sound->replaceSoundMyst(10120);
- _vm->_cursor->showCursor();
-
_elevatorGoingMiddle = false;
- _elevatorPosition = 1;
- _vm->changeToCard(6327, true);
+ if (_elevatorInCabin) {
+
+ // Elevator going to middle animation
+ _vm->_cursor->hideCursor();
+ _vm->_sound->playSoundBlocking(11120);
+ _vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333));
+ _vm->_sound->replaceSoundMyst(12120);
+ _vm->_gfx->runTransition(kTransitionSlideToLeft, Common::Rect(177, 0, 370, 333), 25, 0);
+ _vm->_sound->playSoundBlocking(13120);
+ _vm->_sound->replaceSoundMyst(8120);
+ _vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333));
+ _vm->_system->delayMillis(500);
+ _vm->_sound->replaceSoundMyst(9120);
+ static uint16 moviePos[2] = { 3540, 5380 };
+ o_elevatorWindowMovie(121, 0, 2, moviePos);
+ _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333));
+ _vm->_sound->replaceSoundMyst(10120);
+ _vm->_cursor->showCursor();
+
+ _elevatorPosition = 1;
+
+ _vm->changeToCard(6327, kTransitionRightToLeft);
+ }
}
}
}
@@ -638,16 +649,18 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 *
_vm->_video->waitUntilMovieEnds(window);
}
-void Mechanical::opcode_124(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
- varUnusedCheck(op, var);
+void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Set fortress position", op);
- if (argc == 0) {
- // Used by Card 6156 (Fortress Rotation Controls)
- // Called when Red Exit Button Pressed to raise Elevator
+ VideoHandle gears = _fortressRotationGears->playMovie();
+ uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames();
+
+ // Myst ME short movie workaround, explained in o_fortressRotation_init
+ if (_fortressRotationShortMovieWorkaround) {
+ moviePosition += 3600 * _fortressRotationShortMovieCount;
+ }
- // TODO: Fill in Code...
- } else
- unknown(op, var, argc, argv);
+ _fortressPosition = (moviePosition + 900) / 1800 % 4;
}
void Mechanical::o_mystStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -656,17 +669,14 @@ void Mechanical::o_mystStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16
_vm->_video->playMovieBlocking(_vm->wrapMovieFilename("sstairs", kMechanicalStack), 199, 108);
}
-void Mechanical::opcode_126(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
- varUnusedCheck(op, var);
+void Mechanical::o_elevatorWaitTimeout(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Wait for the elevator to go middle", op);
- if (argc == 0) {
- // Used by Card 6120 (Fortress Elevator)
- // Called when Red Exit Button Pressed to raise Elevator and
- // exit is clicked...
-
- // TODO: Fill in Code...
- } else
- unknown(op, var, argc, argv);
+ // Wait while the elevator times out
+ while (_elevatorGoingMiddle) {
+ runPersistentScripts();
+ _vm->skippableWait(10);
+ }
}
void Mechanical::o_crystalEnterYellow(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -776,8 +786,76 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin
}
void Mechanical::fortressRotation_run() {
- // Used for Card 6156 (Fortress Rotation Controls)
- // TODO: Fill in function...
+ VideoHandle gears = _fortressRotationGears->playMovie();
+
+ double oldRate = _vm->_video->getVideoRate(gears).toDouble();
+
+ uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames();
+
+ // Myst ME short movie workaround, explained in o_fortressRotation_init
+ if (_fortressRotationShortMovieWorkaround) {
+ // Detect if we just looped
+ if (ABS<int32>(_fortressRotationShortMovieLast - 3680) < 50
+ && ABS<int32>(moviePosition) < 50) {
+ _fortressRotationShortMovieCount++;
+ }
+
+ _fortressRotationShortMovieLast = moviePosition;
+
+ // Simulate longer movie
+ moviePosition += 3600 * _fortressRotationShortMovieCount;
+ }
+
+ int32 positionInQuarter = 900 - (moviePosition + 900) % 1800;
+
+ // Are the gears moving?
+ if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressRotationBrake) {
+
+ double newRate = oldRate;
+ if (_fortressRotationBrake && (double)_fortressRotationBrake * 0.2 > oldRate) {
+ newRate += 0.1;
+ }
+
+ // Don't let the gears get stuck between two fortress positions
+ if (ABS<double>(oldRate) <= 0.05) {
+ if (oldRate <= 0.0) {
+ newRate += oldRate;
+ } else {
+ newRate -= oldRate;
+ }
+ } else {
+ if (oldRate <= 0.0) {
+ newRate += 0.05;
+ } else {
+ newRate -= 0.05;
+ }
+ }
+
+ // Adjust speed accordingly to acceleration lever
+ newRate += (double) (positionInQuarter / 1500.0)
+ * (double) (9 - _fortressRotationSpeed) / 9.0;
+
+ newRate = CLIP<double>(newRate, -2.5, 2.5);
+
+ _vm->_video->setVideoRate(gears, Common::Rational(newRate * 1000.0, 1000));
+
+ _gearsWereRunning = true;
+ } else if (_gearsWereRunning) {
+ // The fortress has stopped. Set its new position
+ _fortressPosition = (moviePosition + 900) / 1800 % 4;
+
+ _vm->_video->setVideoRate(gears, 0);
+
+ if (!_fortressRotationShortMovieWorkaround) {
+ _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600));
+ } else {
+ _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600));
+ }
+
+ _vm->_sound->playSoundBlocking(_fortressRotationSounds[_fortressPosition]);
+
+ _gearsWereRunning = false;
+ }
}
void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -785,6 +863,11 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin
_fortressRotationGears = static_cast<MystResourceType6 *>(_invokingResource);
+ VideoHandle gears = _fortressRotationGears->playMovie();
+ _vm->_video->setVideoLooping(gears, true);
+ _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600));
+ _vm->_video->setVideoRate(gears, 0);
+
_fortressRotationSounds[0] = argv[0];
_fortressRotationSounds[1] = argv[1];
_fortressRotationSounds[2] = argv[2];
@@ -792,12 +875,113 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin
_fortressRotationBrake = 0;
+ // WORKAROUND for the tower rotation bug in Myst ME.
+ // The original engine only allowed to visit two out of the three small islands,
+ // preventing the game from being fully completable.
+ // The fortress rotation is computed from the current position in the movie
+ // hcgears.mov. The version of this movie that shipped with the ME edition is
+ // too short to allow to visit all the islands.
+ // ScummVM simulates a longer movie by counting the number of times the movie
+ // looped and adding that time to the current movie position.
+ // Hence allowing the fortress position to be properly computed.
+ uint32 movieDuration = _vm->_video->getDuration(gears).convertToFramerate(600).totalNumberOfFrames();
+ if (movieDuration == 3680) {
+ _fortressRotationShortMovieWorkaround = true;
+ _fortressRotationShortMovieCount = 0;
+ _fortressRotationShortMovieLast = 0;
+ }
+
_fortressRotationRunning = true;
+ _gearsWereRunning = false;
}
void Mechanical::fortressSimulation_run() {
- // Used for Card 6044 (Fortress Rotation Simulator)
- // TODO: Fill in function...
+ if (_fortressSimulationInit) {
+ // Init sequence
+ _vm->_sound->replaceBackgroundMyst(_fortressSimulationStartSound1, 65535);
+ _vm->skippableWait(5000);
+ _vm->_sound->replaceSoundMyst(_fortressSimulationStartSound2);
+
+ // Update movie while the sound is playing
+ VideoHandle startup = _fortressSimulationStartup->playMovie();
+ while (_vm->_sound->isPlaying(_fortressSimulationStartSound2)) {
+ if (_vm->_video->updateMovies())
+ _vm->_system->updateScreen();
+
+ _vm->_system->delayMillis(10);
+ }
+ _vm->_sound->replaceBackgroundMyst(_fortressSimulationStartSound1, 65535);
+ _vm->_video->waitUntilMovieEnds(startup);
+ _vm->_sound->stopBackgroundMyst();
+ _vm->_sound->replaceSoundMyst(_fortressSimulationStartSound2);
+
+
+ Common::Rect src = Common::Rect(0, 0, 176, 176);
+ Common::Rect dst = Common::Rect(187, 3, 363, 179);
+ _vm->_gfx->copyImageSectionToBackBuffer(6046, src, dst);
+ _vm->_gfx->copyBackBufferToScreen(dst);
+ _vm->_system->updateScreen();
+
+ _fortressSimulationStartup->pauseMovie(true);
+ VideoHandle holo = _fortressSimulationHolo->playMovie();
+ _vm->_video->setVideoLooping(holo, true);
+ _vm->_video->setVideoRate(holo, 0);
+
+ _vm->_cursor->showCursor();
+
+ _fortressSimulationInit = false;
+ } else {
+ VideoHandle holo = _fortressSimulationHolo->playMovie();
+
+ double oldRate = _vm->_video->getVideoRate(holo).toDouble();
+
+ uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(holo), 600).totalNumberOfFrames();
+
+ int32 positionInQuarter = 900 - (moviePosition + 900) % 1800;
+
+ // Are the gears moving?
+ if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressSimulationBrake) {
+
+ double newRate = oldRate;
+ if (_fortressSimulationBrake && (double)_fortressSimulationBrake * 0.2 > oldRate) {
+ newRate += 0.1;
+ }
+
+ // Don't let the gears get stuck between two fortress positions
+ if (ABS<double>(oldRate) <= 0.05) {
+ if (oldRate <= 0.0) {
+ newRate += oldRate;
+ } else {
+ newRate -= oldRate;
+ }
+ } else {
+ if (oldRate <= 0.0) {
+ newRate += 0.05;
+ } else {
+ newRate -= 0.05;
+ }
+ }
+
+ // Adjust speed accordingly to acceleration lever
+ newRate += (double) (positionInQuarter / 1500.0)
+ * (double) (9 - _fortressSimulationSpeed) / 9.0;
+
+ newRate = CLIP<double>(newRate, -2.5, 2.5);
+
+ _vm->_video->setVideoRate(holo, Common::Rational(newRate * 1000.0, 1000));
+
+ _gearsWereRunning = true;
+ } else if (_gearsWereRunning) {
+ // The fortress has stopped. Set its new position
+ uint16 simulationPosition = (moviePosition + 900) / 1800 % 4;
+
+ _vm->_video->setVideoRate(holo, 0);
+ _vm->_video->seekToTime(holo, Audio::Timestamp(0, 1800 * simulationPosition, 600));
+ _vm->_sound->playSoundBlocking( _fortressRotationSounds[simulationPosition]);
+
+ _gearsWereRunning = false;
+ }
+ }
}
void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -816,6 +1000,10 @@ void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, u
_fortressSimulationBrake = 0;
_fortressSimulationRunning = true;
+ _gearsWereRunning = false;
+ _fortressSimulationInit = true;
+
+ _vm->_cursor->hideCursor();
}
void Mechanical::o_fortressSimulationStartup_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
diff --git a/engines/mohawk/myst_stacks/mechanical.h b/engines/mohawk/myst_stacks/mechanical.h
index 3bd7f2d71b..7f3d5143e4 100644
--- a/engines/mohawk/myst_stacks/mechanical.h
+++ b/engines/mohawk/myst_stacks/mechanical.h
@@ -80,9 +80,9 @@ private:
DECLARE_OPCODE(o_elevatorWindowMovie);
DECLARE_OPCODE(o_elevatorGoMiddle);
DECLARE_OPCODE(o_elevatorTopMovie);
- DECLARE_OPCODE(opcode_124);
+ DECLARE_OPCODE(o_fortressRotationSetPosition);
DECLARE_OPCODE(o_mystStaircaseMovie);
- DECLARE_OPCODE(opcode_126);
+ DECLARE_OPCODE(o_elevatorWaitTimeout);
DECLARE_OPCODE(o_crystalEnterYellow);
DECLARE_OPCODE(o_crystalEnterGreen);
DECLARE_OPCODE(o_crystalEnterRed);
@@ -104,13 +104,19 @@ private:
bool _mystStaircaseState; // 76
bool _fortressRotationRunning;
+ bool _gearsWereRunning;
uint16 _fortressRotationSpeed; // 78
uint16 _fortressRotationBrake; // 80
uint16 _fortressPosition; // 82
uint16 _fortressRotationSounds[4]; // 86 to 92
MystResourceType6 *_fortressRotationGears; // 172
+ bool _fortressRotationShortMovieWorkaround;
+ uint32 _fortressRotationShortMovieCount;
+ uint32 _fortressRotationShortMovieLast;
+
bool _fortressSimulationRunning;
+ bool _fortressSimulationInit; // 94
uint16 _fortressSimulationSpeed; // 96
uint16 _fortressSimulationBrake; // 98
uint16 _fortressSimulationStartSound1; // 102
diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp
index c1ddc74c82..f17d765c99 100644
--- a/engines/mohawk/myst_stacks/myst.cpp
+++ b/engines/mohawk/myst_stacks/myst.cpp
@@ -51,6 +51,8 @@ Myst::Myst(MohawkEngine_Myst *vm) :
_dockVaultState = 0;
_cabinDoorOpened = 0;
_cabinMatchState = 2;
+ _cabinGaugeMovie = NULL_VID_HANDLE;
+ _cabinFireMovie = NULL_VID_HANDLE;
_matchBurning = false;
_tree = 0;
_treeAlcove = 0;
@@ -189,7 +191,7 @@ void Myst::setupOpcodes() {
OPCODE(215, o_gulls2_init);
OPCODE(216, o_treeCard_init);
OPCODE(217, o_treeEntry_init);
- OPCODE(218, opcode_218);
+ OPCODE(218, o_boilerMovies_init);
OPCODE(219, o_rocketSliders_init);
OPCODE(220, o_rocketLinkVideo_init);
OPCODE(221, o_greenBook_init);
@@ -202,7 +204,7 @@ void Myst::setupOpcodes() {
OPCODE(303, NOP);
OPCODE(304, o_treeCard_exit);
OPCODE(305, o_treeEntry_exit);
- OPCODE(306, NOP);
+ OPCODE(306, o_boiler_exit);
OPCODE(307, o_generatorControlRoom_exit);
OPCODE(308, NOP);
OPCODE(309, NOP);
@@ -608,6 +610,8 @@ uint16 Myst::getVar(uint16 var) {
return 1;
case 302: // Green Book Opened Before Flag
return _state.greenBookOpenedBefore;
+ case 303: // Library Bookcase status changed
+ return _libraryBookcaseChanged;
case 304: // Tower Rotation Map Initialized
return _towerRotationMapInitialized;
case 305: // Cabin Boiler Lit
@@ -1041,7 +1045,7 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
// No page or white page
if (!_globals.heldPage || _globals.heldPage == 13) {
- _vm->changeToCard(cardIdBookCover, true);
+ _vm->changeToCard(cardIdBookCover, kTransitionDissolve);
return;
}
@@ -1083,7 +1087,7 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
// Wrong book
if (bookVar != var) {
- _vm->changeToCard(cardIdBookCover, true);
+ _vm->changeToCard(cardIdBookCover, kTransitionDissolve);
return;
}
@@ -1109,9 +1113,9 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
else
_globals.currentAge = 10;
- _vm->changeToCard(cardIdLose, true);
+ _vm->changeToCard(cardIdLose, kTransitionDissolve);
} else {
- _vm->changeToCard(cardIdBookCover, true);
+ _vm->changeToCard(cardIdBookCover, kTransitionDissolve);
}
}
@@ -1298,7 +1302,7 @@ void Myst::imagerValidation_run() {
if (_imagerValidationStep == 11) {
_imagerValidationStep = 0;
- _vm->changeToCard(_imagerValidationCard, true);
+ _vm->changeToCard(_imagerValidationCard, kTransitionBottomToTop);
} else {
_startTime = time + 100;
}
@@ -1473,10 +1477,10 @@ void Myst::o_cabinSafeHandleMove(uint16 op, uint16 var, uint16 argc, uint16 *arg
if (soundId)
_vm->_sound->replaceSoundMyst(soundId);
- _vm->changeToCard(4103, false);
+ _vm->changeToCard(4103, kNoTransition);
Common::Rect screenRect = Common::Rect(544, 333);
- _vm->_gfx->runTransition(0, screenRect, 2, 5);
+ _vm->_gfx->runTransition(kTransitionLeftToRight, screenRect, 2, 5);
}
_tempVar = 1;
} else {
@@ -1869,17 +1873,50 @@ void Myst::o_boilerLightPilot(uint16 op, uint16 var, uint16 argc, uint16 *argv)
_state.cabinPilotLightLit = 1;
_vm->redrawArea(98);
+ boilerFireUpdate(false);
+
// Put out match
_matchGoOutTime = _vm->_system->getMillis();
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(8098, 49152);
- if (_state.cabinValvePosition > 12)
+ if (_state.cabinValvePosition > 12) {
+ // Compute the speed of the gauge to synchronize it with the next tree move
+ uint32 delay = treeNextMoveDelay(_state.cabinValvePosition);
+ Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay);
+ boilerResetGauge(rate);
+
_state.treeLastMoveTime = _vm->_system->getMillis();
+ }
+ }
+}
- // TODO: Complete. Play movies
+Common::Rational Myst::boilerComputeGaugeRate(uint16 pressure, uint32 delay) {
+ Common::Rational rate = Common::Rational(2088, delay);
+ if (pressure < 12)
+ return -rate;
+ else
+ return rate;
+}
+
+void Myst::boilerResetGauge(const Common::Rational &rate) {
+ if (_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ if (_vm->getCurCard() == 4098) {
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96);
+ } else {
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136);
+ }
}
+
+ Audio::Timestamp goTo;
+ if (rate > 0)
+ goTo = Audio::Timestamp(0, 0, 600);
+ else
+ goTo = _vm->_video->getDuration(_cabinGaugeMovie);
+
+ _vm->_video->seekToTime(_cabinGaugeMovie, goTo);
+ _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
}
void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -1893,7 +1930,12 @@ void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(8098, 49152);
- // TODO: Play movies
+ if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ uint16 delay = treeNextMoveDelay(_state.cabinValvePosition);
+ Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay);
+ _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
+ }
+
} else if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(4098, _state.cabinValvePosition << 10);
}
@@ -1903,7 +1945,8 @@ void Myst::boilerPressureIncrease_run() {
if (!_vm->_sound->isPlaying(5098) && _state.cabinValvePosition < 25) {
_state.cabinValvePosition++;
if (_state.cabinValvePosition == 1) {
- // TODO: Play fire movie
+ // Set fire to high
+ boilerFireUpdate(false);
// Draw fire
_vm->redrawArea(305);
@@ -1927,7 +1970,8 @@ void Myst::boilerPressureDecrease_run() {
if (!_vm->_sound->isPlaying(5098) && _state.cabinValvePosition > 0) {
_state.cabinValvePosition--;
if (_state.cabinValvePosition == 0) {
- // TODO: Play fire movie
+ // Set fire to low
+ boilerFireUpdate(false);
// Draw fire
_vm->redrawArea(305);
@@ -1961,7 +2005,12 @@ void Myst::o_boilerDecreasePressureStop(uint16 op, uint16 var, uint16 argc, uint
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(8098, 49152);
- // TODO: Play movies
+ if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ uint16 delay = treeNextMoveDelay(_state.cabinValvePosition);
+ Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay);
+ _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
+ }
+
} else {
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(4098, _state.cabinValvePosition << 10);
@@ -2067,6 +2116,11 @@ void Myst::tree_run() {
// Check if alcove is accessible
treeSetAlcoveAccessible();
+ if (_cabinGaugeMovie != NULL_VID_HANDLE) {
+ Common::Rational rate = boilerComputeGaugeRate(pressure, delay);
+ boilerResetGauge(rate);
+ }
+
_state.treeLastMoveTime = time;
}
}
@@ -2968,7 +3022,12 @@ void Myst::clockReset() {
_vm->_system->delayMillis(1000);
_vm->_sound->replaceSoundMyst(7113);
- // TODO: Play cl1wggat backwards
+ // Gear closing movie
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225);
+ _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle));
+ _vm->_video->setVideoRate(handle, -1);
+ _vm->_video->waitUntilMovieEnds(handle);
+
// Redraw gear
_state.gearsOpen = 0;
_vm->redrawArea(40);
@@ -2980,16 +3039,9 @@ void Myst::clockReset() {
void Myst::clockResetWeight() {
_clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0);
- if (!(_vm->getFeatures() & GF_ME)) {
- // Set video bounds, weight going up
- _vm->_video->setVideoBounds(_clockWeightVideo,
- Audio::Timestamp(0, 2214 * 2 - _clockWeightPosition, 600),
- Audio::Timestamp(0, 2214 * 2, 600));
- } else {
- //FIXME: Needs QT backwards playing, for now just display the weight up
- warning("Weight going back up not implemented");
- _vm->_video->drawVideoFrame(_clockWeightVideo, Audio::Timestamp(0, 0, 600));
- }
+ // Play the movie backwards, weight going up
+ _vm->_video->seekToTime(_clockWeightVideo, Audio::Timestamp(0, _clockWeightPosition, 600));
+ _vm->_video->setVideoRate(_clockWeightVideo, -1);
// Reset position
_clockWeightPosition = 0;
@@ -3246,7 +3298,7 @@ void Myst::libraryBookcaseTransform_run(void) {
if (_state.libraryBookcaseDoor) {
_vm->_gfx->copyImageSectionToBackBuffer(11179, Common::Rect(0, 0, 106, 81), Common::Rect(0, 72, 106, 153));
- _vm->_gfx->runTransition(6, Common::Rect(0, 72, 106, 153), 5, 10);
+ _vm->_gfx->runTransition(kTransitionBottomToTop, Common::Rect(0, 72, 106, 153), 5, 10);
_vm->_sound->playSoundBlocking(7348);
_vm->_sound->replaceBackgroundMyst(4348, 16384);
} else {
@@ -3510,22 +3562,60 @@ void Myst::o_treeEntry_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
treeSetAlcoveAccessible();
}
-void Myst::opcode_218(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
- varUnusedCheck(op, var);
+void Myst::o_boilerMovies_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Boiler movies init", op);
- // Used for Card 4097 (Cabin Boiler)
- // TODO: Fill in logic
- if (false) {
- _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244);
- _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 138);
+ boilerFireInit();
+ boilerGaugeInit();
+}
+
+void Myst::boilerFireInit() {
+ if (_vm->getCurCard() == 4098) {
+ _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279, true);
+ _vm->_video->pauseMovie(_cabinFireMovie, true);
+
+ _vm->redrawArea(305);
+ boilerFireUpdate(true);
+ } else {
+ if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition >= 1) {
+ _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244, true);
+ }
+ }
+}
+
+void Myst::boilerFireUpdate(bool init) {
+ uint position = _vm->_video->getTime(_cabinFireMovie);
+
+ if (_state.cabinPilotLightLit == 1) {
+ if (_state.cabinValvePosition == 0) {
+ if (position > (uint)Audio::Timestamp(0, 200, 600).msecs() || init) {
+ _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600));
+ _vm->_video->pauseMovie(_cabinFireMovie, false);
+ }
+ } else {
+ if (position < (uint)Audio::Timestamp(0, 200, 600).msecs() || init) {
+ _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600));
+ _vm->_video->pauseMovie(_cabinFireMovie, false);
+ }
+ }
}
+}
- // Used for Card 4098 (Cabin Boiler)
- // TODO: Fill in logic
- if (false) {
- _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279);
- _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 97);
+void Myst::boilerGaugeInit() {
+ if (_vm->getCurCard() == 4098) {
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96);
+ } else {
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136);
}
+
+ Audio::Timestamp frame;
+
+ if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition > 12)
+ frame = _vm->_video->getDuration(_cabinGaugeMovie);
+ else
+ frame = Audio::Timestamp(0, 0, 600);
+
+ _vm->_video->drawVideoFrame(_cabinGaugeMovie, frame);
}
void Myst::o_rocketSliders_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -3648,6 +3738,13 @@ void Myst::o_treeEntry_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
_treeAlcove = 0;
}
+void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ debugC(kDebugScript, "Opcode %d: Exit boiler card", op);
+
+ _cabinGaugeMovie = NULL_VID_HANDLE;
+ _cabinFireMovie = NULL_VID_HANDLE;
+}
+
void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Generator room exit", op);
diff --git a/engines/mohawk/myst_stacks/myst.h b/engines/mohawk/myst_stacks/myst.h
index e9bff08cb4..de88843d59 100644
--- a/engines/mohawk/myst_stacks/myst.h
+++ b/engines/mohawk/myst_stacks/myst.h
@@ -43,7 +43,7 @@ public:
virtual void disablePersistentScripts();
virtual void runPersistentScripts();
-private:
+protected:
void setupOpcodes();
uint16 getVar(uint16 var);
void toggleVar(uint16 var);
@@ -52,7 +52,7 @@ private:
virtual uint16 getMap() { return 9934; }
void towerRotationMap_run();
- void libraryBookcaseTransform_run();
+ virtual void libraryBookcaseTransform_run();
void generatorControlRoom_run();
void opcode_212_run();
void libraryCombinationBook_run();
@@ -174,7 +174,7 @@ private:
DECLARE_OPCODE(o_gulls2_init);
DECLARE_OPCODE(o_treeCard_init);
DECLARE_OPCODE(o_treeEntry_init);
- DECLARE_OPCODE(opcode_218);
+ DECLARE_OPCODE(o_boilerMovies_init);
DECLARE_OPCODE(o_rocketSliders_init);
DECLARE_OPCODE(o_rocketLinkVideo_init);
DECLARE_OPCODE(o_greenBook_init);
@@ -183,6 +183,7 @@ private:
DECLARE_OPCODE(o_bookAddSpecialPage_exit);
DECLARE_OPCODE(o_treeCard_exit);
DECLARE_OPCODE(o_treeEntry_exit);
+ DECLARE_OPCODE(o_boiler_exit);
DECLARE_OPCODE(o_generatorControlRoom_exit);
@@ -259,6 +260,9 @@ private:
uint16 _cabinMatchState; // 60
uint32 _matchGoOutTime; // 144
+ VideoHandle _cabinFireMovie; // 240
+ VideoHandle _cabinGaugeMovie; // 244
+
bool _boilerPressureIncreasing;
bool _boilerPressureDecreasing;
bool _basementPressureIncreasing;
@@ -317,6 +321,12 @@ private:
Common::Point towerRotationMapComputeCoords(const Common::Point &center, uint16 angle);
void towerRotationMapDrawLine(const Common::Point &center, const Common::Point &end);
+ void boilerFireInit();
+ void boilerFireUpdate(bool init);
+ void boilerGaugeInit();
+ Common::Rational boilerComputeGaugeRate(uint16 pressure, uint32 delay);
+ void boilerResetGauge(const Common::Rational &rate);
+
void treeSetAlcoveAccessible();
uint32 treeNextMoveDelay(uint16 pressure);
diff --git a/engines/mohawk/myst_stacks/preview.cpp b/engines/mohawk/myst_stacks/preview.cpp
index 0b8dcf897a..75e870281e 100644
--- a/engines/mohawk/myst_stacks/preview.cpp
+++ b/engines/mohawk/myst_stacks/preview.cpp
@@ -60,6 +60,7 @@ void Preview::setupOpcodes() {
OVERRIDE_OPCODE(199, o_speechStop);
// "Init" Opcodes
+ OVERRIDE_OPCODE(209, o_libraryBookcaseTransformDemo_init);
OPCODE(298, o_speech_init);
OPCODE(299, o_library_init);
}
@@ -139,7 +140,7 @@ void Preview::speech_run() {
break;
case 1: // Open book
if (_currentCue >= 1) {
- _vm->changeToCard(3001, true);
+ _vm->changeToCard(3001, kTransitionDissolve);
_speechStep++;
}
@@ -147,7 +148,7 @@ void Preview::speech_run() {
case 2: // Go to Myst
if (_currentCue >= 2) {
_vm->_gfx->fadeToBlack();
- _vm->changeToCard(3002, false);
+ _vm->changeToCard(3002, kNoTransition);
_vm->_gfx->fadeFromBlack();
_speechStep++;
@@ -164,7 +165,7 @@ void Preview::speech_run() {
if (_currentCue >= 4) {
_library->drawConditionalDataToScreen(0);
- _vm->changeToCard(3003, true);
+ _vm->changeToCard(3003, kTransitionDissolve);
_speechNextTime = time + 2000;
_speechStep++;
@@ -181,7 +182,7 @@ void Preview::speech_run() {
if (time < _speechNextTime)
break;
- _vm->changeToCard(3004, true);
+ _vm->changeToCard(3004, kTransitionDissolve);
_speechNextTime = time + 2000;
_speechStep++;
break;
@@ -190,7 +191,7 @@ void Preview::speech_run() {
break;
_vm->_gfx->fadeToBlack();
- _vm->changeToCard(3005, false);
+ _vm->changeToCard(3005, kNoTransition);
_vm->_gfx->fadeFromBlack();
_speechNextTime = time + 1000;
_speechStep++;
@@ -205,7 +206,7 @@ void Preview::speech_run() {
if (time < _speechNextTime)
break;
- _vm->changeToCard(3006 + _speechStep - 7, true);
+ _vm->changeToCard(3006 + _speechStep - 7, kTransitionDissolve);
_speechNextTime = time + 2000;
_speechStep++;
break;
@@ -213,7 +214,7 @@ void Preview::speech_run() {
if (time < _speechNextTime)
break;
- _vm->changeToCard(4329, true);
+ _vm->changeToCard(4329, kTransitionDissolve);
_speechRunning = false;
_globals.currentAge = 2;
@@ -241,5 +242,22 @@ void Preview::o_library_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
_library = static_cast<MystResourceType8 *>(_invokingResource);
}
+void Preview::o_libraryBookcaseTransformDemo_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (_libraryBookcaseChanged) {
+ MystResourceType7 *resource = static_cast<MystResourceType7 *>(_invokingResource);
+ _libraryBookcaseMovie = static_cast<MystResourceType6 *>(resource->getSubResource(getVar(303)));
+ _libraryBookcaseSoundId = argv[0];
+ _libraryBookcaseMoving = true;
+ }
+}
+
+void Preview::libraryBookcaseTransform_run() {
+ if (_libraryBookcaseChanged)
+ _state.libraryBookcaseDoor = !_state.libraryBookcaseDoor;
+
+ Myst::libraryBookcaseTransform_run();
+}
+
+
} // End of namespace MystStacks
} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_stacks/preview.h b/engines/mohawk/myst_stacks/preview.h
index 1e4ff3efb4..706220e8ed 100644
--- a/engines/mohawk/myst_stacks/preview.h
+++ b/engines/mohawk/myst_stacks/preview.h
@@ -51,6 +51,7 @@ private:
DECLARE_OPCODE(o_stayHere);
DECLARE_OPCODE(o_speechStop);
+ DECLARE_OPCODE(o_libraryBookcaseTransformDemo_init);
DECLARE_OPCODE(o_speech_init);
DECLARE_OPCODE(o_library_init);
@@ -65,6 +66,8 @@ private:
void speech_run();
void speechUpdateCue();
+
+ void libraryBookcaseTransform_run();
};
} // End of namespace MystStacks
diff --git a/engines/mohawk/myst_stacks/selenitic.cpp b/engines/mohawk/myst_stacks/selenitic.cpp
index 1473742259..a941b14eaa 100644
--- a/engines/mohawk/myst_stacks/selenitic.cpp
+++ b/engines/mohawk/myst_stacks/selenitic.cpp
@@ -729,11 +729,11 @@ void Selenitic::o_mazeRunnerDoorButton(uint16 op, uint16 var, uint16 argc, uint1
uint16 cardIdEntry = argv[1];
if (_mazeRunnerPosition == 288) {
- _vm->changeToCard(cardIdEntry, false);
+ _vm->changeToCard(cardIdEntry, kNoTransition);
_vm->_sound->replaceSoundMyst(cardIdEntry);
animatedUpdate(argv[2], &argv[3], 10);
} else if (_mazeRunnerPosition == 289) {
- _vm->changeToCard(cardIdExit, false);
+ _vm->changeToCard(cardIdExit, kNoTransition);
_vm->_sound->replaceSoundMyst(cardIdExit);
animatedUpdate(argv[2], &argv[3], 10);
}
@@ -895,9 +895,9 @@ void Selenitic::o_soundLockButton(uint16 op, uint16 var, uint16 argc, uint16 *ar
uint16 cardIdClosed = argv[0];
uint16 cardIdOpen = argv[1];
- _vm->changeToCard(cardIdClosed, true);
+ _vm->changeToCard(cardIdClosed, kTransitionDissolve);
- _vm->changeToCard(cardIdOpen, false);
+ _vm->changeToCard(cardIdOpen, kNoTransition);
_vm->_sound->replaceSoundMyst(argv[2]);
animatedUpdate(argv[4], &argv[5], argv[3]);
diff --git a/engines/mohawk/myst_stacks/slides.cpp b/engines/mohawk/myst_stacks/slides.cpp
index c0bb400db1..720926904a 100644
--- a/engines/mohawk/myst_stacks/slides.cpp
+++ b/engines/mohawk/myst_stacks/slides.cpp
@@ -63,7 +63,7 @@ void Slides::runPersistentScripts() {
// Used on Cards...
if (_vm->_system->getMillis() > _nextCardTime) {
_vm->_gfx->fadeToBlack();
- _vm->changeToCard(_nextCardID, false);
+ _vm->changeToCard(_nextCardID, kNoTransition);
_vm->_gfx->fadeFromBlack();
}
}
diff --git a/engines/mohawk/myst_stacks/stoneship.cpp b/engines/mohawk/myst_stacks/stoneship.cpp
index ef228e62f3..1359685302 100644
--- a/engines/mohawk/myst_stacks/stoneship.cpp
+++ b/engines/mohawk/myst_stacks/stoneship.cpp
@@ -440,9 +440,9 @@ void Stoneship::o_drawerOpenSirius(uint16 op, uint16 var, uint16 argc, uint16 *a
drawer->drawConditionalDataToScreen(0, 0);
}
- uint16 transition = 5;
+ TransitionType transition = kTransitionTopToBottom;
if (argc == 2 && argv[1])
- transition = 11;
+ transition = kTransitionCopy;
_vm->_gfx->runTransition(transition, drawer->getRect(), 25, 5);
}
@@ -579,7 +579,7 @@ void Stoneship::o_drawerOpenAchenar(uint16 op, uint16 var, uint16 argc, uint16 *
MystResourceType8 *drawer = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]);
drawer->drawConditionalDataToScreen(0, 0);
- _vm->_gfx->runTransition(5, drawer->getRect(), 25, 5);
+ _vm->_gfx->runTransition(kTransitionTopToBottom, drawer->getRect(), 25, 5);
}
void Stoneship::o_hologramPlayback(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -692,9 +692,9 @@ void Stoneship::o_chestValveVideos(uint16 op, uint16 var, uint16 argc, uint16 *a
_vm->_sound->resumeBackgroundMyst();
} else {
// Valve opening
- // TODO: Play backwards
VideoHandle valve = _vm->_video->playMovie(movie, 97, 267);
- _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600));
+ _vm->_video->seekToTime(valve, Audio::Timestamp(0, 350, 600));
+ _vm->_video->setVideoRate(valve, -1);
_vm->_video->waitUntilMovieEnds(valve);
}
}
@@ -777,7 +777,7 @@ void Stoneship::o_cloudOrbLeave(uint16 op, uint16 var, uint16 argc, uint16 *argv
_cloudOrbMovie->pauseMovie(true);
_vm->_sound->replaceSoundMyst(_cloudOrbStopSound);
- _vm->_gfx->runTransition(5, _invokingResource->getRect(), 4, 0);
+ _vm->_gfx->runTransition(kTransitionTopToBottom, _invokingResource->getRect(), 4, 0);
}
void Stoneship::o_drawerCloseOpened(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -794,7 +794,7 @@ void Stoneship::drawerClose(uint16 drawer) {
_vm->drawResourceImages();
MystResource *res = _vm->_resources[drawer];
- _vm->_gfx->runTransition(6, res->getRect(), 25, 5);
+ _vm->_gfx->runTransition(kTransitionBottomToTop, res->getRect(), 25, 5);
}
void Stoneship::o_hologramDisplay_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
index 32613c6185..71aa371073 100644
--- a/engines/mohawk/riven.cpp
+++ b/engines/mohawk/riven.cpp
@@ -177,7 +177,7 @@ Common::Error MohawkEngine_Riven::run() {
}
} else {
// Otherwise, start us off at aspit's card 1 (the main menu)
- changeToStack(aspit);
+ changeToStack(aspit);
changeToCard(1);
}
@@ -830,7 +830,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) {
} else if (sunnerTime < vm->getTotalPlayTime()) {
VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(1, 3));
- timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(2, 15) * 1000;
+ timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -868,7 +868,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) {
VideoHandle handle = vm->_video->playMovieRiven(movie);
- timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 10) * 1000;
+ timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -898,7 +898,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) {
} else if (sunnerTime < vm->getTotalPlayTime()) {
VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(3, 5));
- timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
+ timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -932,7 +932,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) {
vm->_video->activateMLST(mlstID, vm->getCurCard());
VideoHandle handle = vm->_video->playMovieRiven(mlstID);
- timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
+ timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp
index 337a57e3e1..384e89a4cf 100644
--- a/engines/mohawk/riven_external.cpp
+++ b/engines/mohawk/riven_external.cpp
@@ -1467,7 +1467,7 @@ static void catherineViewerIdleTimer(MohawkEngine_Riven *vm) {
VideoHandle videoHandle = vm->_video->playMovieRiven(30);
// Reset the timer
- vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle) + vm->_rnd->getRandomNumber(60) * 1000);
+ vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle).msecs() + vm->_rnd->getRandomNumber(60) * 1000);
}
void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) {
@@ -1507,7 +1507,7 @@ void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) {
_vm->_video->activateMLST(cathMovie, _vm->getCurCard());
VideoHandle videoHandle = _vm->_video->playMovieRiven(30);
- timeUntilNextMovie = _vm->_video->getDuration(videoHandle) + _vm->_rnd->getRandomNumber(60) * 1000;
+ timeUntilNextMovie = _vm->_video->getDuration(videoHandle).msecs() + _vm->_rnd->getRandomNumber(60) * 1000;
} else {
// Otherwise, just redraw the imager
timeUntilNextMovie = _vm->_rnd->getRandomNumberRng(10, 20) * 1000;
@@ -2335,7 +2335,7 @@ static void rebelPrisonWindowTimer(MohawkEngine_Riven *vm) {
VideoHandle handle = vm->_video->playMovieRiven(movie);
// Ensure the next video starts after this one ends
- uint32 timeUntilNextVideo = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(38, 58) * 1000;
+ uint32 timeUntilNextVideo = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000;
// Save the time in case we leave the card and return
vm->_vars["rvillagetime"] = timeUntilNextVideo + vm->getTotalPlayTime();
diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp
index b1b99722d5..8b0130d711 100644
--- a/engines/mohawk/video.cpp
+++ b/engines/mohawk/video.cpp
@@ -493,9 +493,9 @@ uint32 VideoManager::getTime(VideoHandle handle) {
return _videoStreams[handle]->getTime();
}
-uint32 VideoManager::getDuration(VideoHandle handle) {
+Audio::Timestamp VideoManager::getDuration(VideoHandle handle) {
assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getDuration().msecs();
+ return _videoStreams[handle]->getDuration();
}
bool VideoManager::endOfVideo(VideoHandle handle) {
@@ -536,6 +536,16 @@ void VideoManager::setVideoLooping(VideoHandle handle, bool loop) {
_videoStreams[handle].loop = loop;
}
+Common::Rational VideoManager::getVideoRate(VideoHandle handle) const {
+ assert(handle != NULL_VID_HANDLE);
+ return _videoStreams[handle]->getRate();
+}
+
+void VideoManager::setVideoRate(VideoHandle handle, const Common::Rational &rate) {
+ assert(handle != NULL_VID_HANDLE);
+ _videoStreams[handle]->setRate(rate);
+}
+
void VideoManager::pauseMovie(VideoHandle handle, bool pause) {
assert(handle != NULL_VID_HANDLE);
_videoStreams[handle]->pauseVideo(pause);
diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h
index 6d2783936d..2c4c827aa8 100644
--- a/engines/mohawk/video.h
+++ b/engines/mohawk/video.h
@@ -101,12 +101,14 @@ public:
int getCurFrame(VideoHandle handle);
uint32 getFrameCount(VideoHandle handle);
uint32 getTime(VideoHandle handle);
- uint32 getDuration(VideoHandle videoHandle);
+ Audio::Timestamp getDuration(VideoHandle videoHandle);
bool endOfVideo(VideoHandle handle);
void setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end);
void drawVideoFrame(VideoHandle handle, Audio::Timestamp time);
void seekToTime(VideoHandle handle, Audio::Timestamp time);
void setVideoLooping(VideoHandle handle, bool loop);
+ Common::Rational getVideoRate(VideoHandle handle) const;
+ void setVideoRate(VideoHandle handle, const Common::Rational &rate);
void waitUntilMovieEnds(VideoHandle videoHandle);
void delayUntilMovieEnds(VideoHandle videoHandle);
void pauseMovie(VideoHandle videoHandle, bool pause);
diff --git a/engines/pegasus/neighborhood/norad/pressuredoor.cpp b/engines/pegasus/neighborhood/norad/pressuredoor.cpp
index d1378567d3..a12e971d10 100644
--- a/engines/pegasus/neighborhood/norad/pressuredoor.cpp
+++ b/engines/pegasus/neighborhood/norad/pressuredoor.cpp
@@ -323,7 +323,8 @@ void PressureDoor::receiveNotification(Notification *notification, const Notific
_robotState = kRobotDead;
_levelsMovie.stop();
_levelsMovie.setSegment((kNormalSubRoomPressure + kPressureBase) * _levelsScale,
- (GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
+ (GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale + 1);
+ _levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale);
_pressureCallBack.setCallBackFlag(kPressureDroppingFlag);
_pressureCallBack.scheduleCallBack(kTriggerAtStart, 0, 0);
_typeMovie.stop();
@@ -335,7 +336,7 @@ void PressureDoor::receiveNotification(Notification *notification, const Notific
_downButton.setCurrentFrameIndex(1);
_gameState = kGameOver;
allowInput(false);
- _levelsMovie.setRate(Common::Rational(0x5555, 0x10000) - 1); // Should match door tracker.
+ _levelsMovie.setRate(Common::Rational(-4, 3)); // Should match door tracker.
break;
case kRobotDead:
allowInput(true);
diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.cpp b/engines/pegasus/neighborhood/tsa/fulltsa.cpp
index b598841b45..9b843da5d6 100644
--- a/engines/pegasus/neighborhood/tsa/fulltsa.cpp
+++ b/engines/pegasus/neighborhood/tsa/fulltsa.cpp
@@ -622,6 +622,13 @@ void RipTimer::draw(const Common::Rect &updateRect) {
}
void RipTimer::timeChanged(const TimeValue newTime) {
+ // WORKAROUND: If the timer isn't running, don't run the following code.
+ // Fixes use of the code when it shouldn't be running (since this is an
+ // IdlerAnimation, this is called on useIdleTime() but this specific
+ // timer only makes sense when used as an actual timer).
+ if (!isRunning())
+ return;
+
Common::Rect bounds;
getBounds(bounds);
diff --git a/engines/pegasus/pegasus.cpp b/engines/pegasus/pegasus.cpp
index 502a79ec25..98f0553783 100644
--- a/engines/pegasus/pegasus.cpp
+++ b/engines/pegasus/pegasus.cpp
@@ -1428,6 +1428,10 @@ void PegasusEngine::switchGameMode(const GameMode newMode, const GameMode oldMod
}
bool PegasusEngine::canSwitchGameMode(const GameMode newMode, const GameMode oldMode) {
+ // WORKAROUND: Don't allow game mode switches when the interface is not set up.
+ // Prevents segfaults when pressing 'i' when in the space chase.
+ if (!g_interface)
+ return false;
if (newMode == kModeInventoryPick && oldMode == kModeBiochipPick)
return false;
if (newMode == kModeBiochipPick && oldMode == kModeInventoryPick)
diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp
index db06d540f5..474f72eca5 100644
--- a/engines/queen/journal.cpp
+++ b/engines/queen/journal.cpp
@@ -559,7 +559,7 @@ void Journal::updateTextField(uint16 ascii, int keycode) {
}
break;
default:
- if (isprint((char)ascii) &&
+ if (Common::isPrint((char)ascii) &&
_textField.textCharsCount < (sizeof(_textField.text) - 1) &&
_vm->display()->textWidth(_textField.text) < _textField.w) {
_textField.text[_textField.textCharsCount] = (char)ascii;
diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp
index 13850a0b6d..0eebf3f175 100644
--- a/engines/saga/music.cpp
+++ b/engines/saga/music.cpp
@@ -30,6 +30,7 @@
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
+#include "audio/midiparser_qt.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
#include "common/file.h"
@@ -76,19 +77,14 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) {
}
// Check if the game is using XMIDI or SMF music
- if (vm->getGameId() == GID_IHNM && vm->isMacResources()) {
- // Just set an XMIDI parser for Mac IHNM for now
+ if (!memcmp(buffer->getBuffer(), "FORM", 4)) {
_parser = MidiParser::createParser_XMIDI();
+ // ITE had MT32 mapped instruments
+ _isGM = (vm->getGameId() != GID_ITE);
} else {
- if (!memcmp(buffer->getBuffer(), "FORM", 4)) {
- _parser = MidiParser::createParser_XMIDI();
- // ITE had MT32 mapped instruments
- _isGM = (vm->getGameId() != GID_ITE);
- } else {
- _parser = MidiParser::createParser_SMF();
- // ITE with standalone MIDI files is General MIDI
- _isGM = (vm->getGameId() == GID_ITE);
- }
+ _parser = MidiParser::createParser_SMF();
+ // ITE with standalone MIDI files is General MIDI
+ _isGM = (vm->getGameId() == GID_ITE);
}
if (!_parser->loadMusic(buffer->getBuffer(), buffer->size()))
@@ -107,6 +103,27 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) {
_isPlaying = true;
}
+void MusicDriver::playQuickTime(const Common::String &musicName, bool loop) {
+ // IHNM Mac uses QuickTime MIDI
+ _parser = MidiParser::createParser_QT();
+ _isGM = true;
+
+ if (!((MidiParser_QT *)_parser)->loadFromContainerFile(musicName))
+ error("MusicDriver::playQuickTime(): Failed to load file '%s'", musicName.c_str());
+
+ _parser->setTrack(0);
+ _parser->setMidiDriver(this);
+ _parser->setTimerRate(_driver->getBaseTempo());
+ _parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+ _parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
+
+ // Handle music looping
+ _parser->property(MidiParser::mpAutoLoop, loop);
+// _isLooping = loop;
+
+ _isPlaying = true;
+}
+
void MusicDriver::pause() {
_isPlaying = false;
}
@@ -343,31 +360,19 @@ void Music::play(uint32 resourceId, MusicFlags flags) {
// Load MIDI/XMI resource data
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
- // Load the external music file for Mac IHNM
-#if 0
- Common::File musicFile;
- char musicFileName[40];
- sprintf(musicFileName, "Music/Music%02x", resourceId);
- musicFile.open(musicFileName);
- resourceSize = musicFile.size();
- resourceData = new byte[resourceSize];
- musicFile.read(resourceData, resourceSize);
- musicFile.close();
-
- // TODO: The Mac music format is unsupported (QuickTime MIDI)
- // so stop here
-#endif
- return;
+ // Load the external music file for Mac IHNM
+ _player->playQuickTime(Common::String::format("Music/Music%02x", resourceId), flags & MUSIC_LOOP);
} else {
if (_currentMusicBuffer == &_musicBuffer[1]) {
_currentMusicBuffer = &_musicBuffer[0];
} else {
_currentMusicBuffer = &_musicBuffer[1];
}
+
_vm->_resource->loadResource(_musicContext, resourceId, *_currentMusicBuffer);
+ _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP));
}
- _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP));
setVolume(_vm->_musicVolume);
}
diff --git a/engines/saga/music.h b/engines/saga/music.h
index 5a4e662af4..081fab21f6 100644
--- a/engines/saga/music.h
+++ b/engines/saga/music.h
@@ -46,6 +46,7 @@ public:
MusicDriver();
void play(SagaEngine *vm, ByteArray *buffer, bool loop);
+ void playQuickTime(const Common::String &musicName, bool loop);
virtual void pause();
virtual void resume();
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 016ba89e7b..b69ce552bc 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -363,7 +363,7 @@ void IMuseInternal::pause(bool paused) {
_paused = paused;
}
-int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
+int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad) {
Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()");
const SaveLoadEntry mainEntries[] = {
MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)),
@@ -440,7 +440,16 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
for (i = 0; i < 8; ++i)
ser->saveLoadEntries(0, volumeFaderEntries);
- if (ser->isLoading()) {
+ // Normally, we have to fix up the data structures after loading a
+ // saved game. But there are cases where we don't. For instance, The
+ // Macintosh version of Monkey Island 1 used to convert the Mac0 music
+ // resources to General MIDI and play it through iMUSE as a rough
+ // approximation. Now it has its own player, but old savegame still
+ // have the iMUSE data in them. We have to skip that data, using a
+ // dummy iMUSE object, but since the resource is no longer recognizable
+ // to iMUSE, the fixup fails hard. So yes, this is a bit of a hack.
+
+ if (ser->isLoading() && fixAfterLoad) {
// Load all sounds that we need
fix_players_after_load(scumm);
fix_parts_after_load();
diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h
index 23449e470b..cce5309229 100644
--- a/engines/scumm/imuse/imuse.h
+++ b/engines/scumm/imuse/imuse.h
@@ -62,7 +62,7 @@ public:
public:
virtual void on_timer(MidiDriver *midi) = 0;
virtual void pause(bool paused) = 0;
- virtual int save_or_load(Serializer *ser, ScummEngine *scumm) = 0;
+ virtual int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0;
virtual bool get_sound_active(int sound) const = 0;
virtual int32 doCommand(int numargs, int args[]) = 0;
virtual int clear_queue() = 0;
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 846e2d7545..6be564a517 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -518,7 +518,7 @@ protected:
public:
// IMuse interface
void pause(bool paused);
- int save_or_load(Serializer *ser, ScummEngine *scumm);
+ int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true);
bool get_sound_active(int sound) const;
int32 doCommand(int numargs, int args[]);
uint32 property(int prop, uint32 value);
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 8499c9bad3..28884d7f78 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -36,6 +36,7 @@ MODULE_OBJS := \
object.o \
palette.o \
player_apple2.o \
+ player_mac.o \
player_mod.o \
player_nes.o \
player_pce.o \
@@ -47,7 +48,9 @@ MODULE_OBJS := \
player_v2base.o \
player_v2cms.o \
player_v3a.o \
+ player_v3m.o \
player_v4a.o \
+ player_v5m.o \
resource_v2.o \
resource_v3.o \
resource_v4.o \
diff --git a/engines/scumm/music.h b/engines/scumm/music.h
index a527c77b72..9fd14d830e 100644
--- a/engines/scumm/music.h
+++ b/engines/scumm/music.h
@@ -24,6 +24,7 @@
#define SCUMM_MUSIC_H
#include "common/scummsys.h"
+#include "engines/scumm/saveload.h"
namespace Scumm {
@@ -78,6 +79,11 @@ public:
* @return the music timer
*/
virtual int getMusicTimer() { return 0; }
+
+ /**
+ * Save or load the music state.
+ */
+ virtual void saveLoadWithSerializer(Serializer *ser) {}
};
} // End of namespace Scumm
diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
new file mode 100644
index 0000000000..c16c85bff3
--- /dev/null
+++ b/engines/scumm/player_mac.cpp
@@ -0,0 +1,415 @@
+/* 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 "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/player_mac.h"
+#include "scumm/resource.h"
+#include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
+
+namespace Scumm {
+
+Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds)
+ : _vm(scumm),
+ _mixer(mixer),
+ _sampleRate(_mixer->getOutputRate()),
+ _soundPlaying(-1),
+ _numberOfChannels(numberOfChannels),
+ _channelMask(channelMask),
+ _fadeNoteEnds(fadeNoteEnds) {
+ assert(scumm);
+ assert(mixer);
+}
+
+void Player_Mac::init() {
+ _channel = new Player_Mac::Channel[_numberOfChannels];
+
+ int i;
+
+ for (i = 0; i < _numberOfChannels; i++) {
+ _channel[i]._looped = false;
+ _channel[i]._length = 0;
+ _channel[i]._data = NULL;
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = false;
+ _channel[i]._instrument._data = NULL;
+ _channel[i]._instrument._size = 0;
+ _channel[i]._instrument._rate = 0;
+ _channel[i]._instrument._loopStart = 0;
+ _channel[i]._instrument._loopEnd = 0;
+ _channel[i]._instrument._baseFreq = 0;
+ _channel[i]._instrument._pos = 0;
+ _channel[i]._instrument._subPos = 0;
+ }
+
+ _pitchTable[116] = 1664510;
+ _pitchTable[117] = 1763487;
+ _pitchTable[118] = 1868350;
+ _pitchTable[119] = 1979447;
+ _pitchTable[120] = 2097152;
+ _pitchTable[121] = 2221855;
+ _pitchTable[122] = 2353973;
+ _pitchTable[123] = 2493948;
+ _pitchTable[124] = 2642246;
+ _pitchTable[125] = 2799362;
+ _pitchTable[126] = 2965820;
+ _pitchTable[127] = 3142177;
+ for (i = 115; i >= 0; --i) {
+ _pitchTable[i] = _pitchTable[i + 12] / 2;
+ }
+
+ setMusicVolume(255);
+
+ if (!checkMusicAvailable()) {
+ return;
+ }
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_Mac::~Player_Mac() {
+ Common::StackLock lock(_mutex);
+ _mixer->stopHandle(_soundHandle);
+ stopAllSounds_Internal();
+ delete[] _channel;
+}
+
+void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
+ Common::StackLock lock(_mutex);
+ if (ser->getVersion() < VER(94)) {
+ if (_vm->_game.id == GID_MONKEY && ser->isLoading()) {
+ IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+ dummyImuse->save_or_load(ser, _vm, false);
+ delete dummyImuse;
+ }
+ } else {
+ static const SaveLoadEntry musicEntries[] = {
+ MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)),
+ MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)),
+ MKEND()
+ };
+
+ static const SaveLoadEntry channelEntries[] = {
+ MKLINE(Channel, _pos, sleUint16, VER(94)),
+ MKLINE(Channel, _pitchModifier, sleInt32, VER(94)),
+ MKLINE(Channel, _velocity, sleUint8, VER(94)),
+ MKLINE(Channel, _remaining, sleUint32, VER(94)),
+ MKLINE(Channel, _notesLeft, sleUint8, VER(94)),
+ MKEND()
+ };
+
+ static const SaveLoadEntry instrumentEntries[] = {
+ MKLINE(Instrument, _pos, sleUint32, VER(94)),
+ MKLINE(Instrument, _subPos, sleUint32, VER(94)),
+ MKEND()
+ };
+
+ uint32 mixerSampleRate = _sampleRate;
+ int i;
+
+ ser->saveLoadEntries(this, musicEntries);
+
+ if (ser->isLoading() && _soundPlaying != -1) {
+ const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying);
+ assert(ptr);
+ loadMusic(ptr);
+ }
+
+ ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries);
+ for (i = 0; i < _numberOfChannels; i++) {
+ ser->saveLoadEntries(&_channel[i], instrumentEntries);
+ }
+
+ if (ser->isLoading()) {
+ // If necessary, adjust the channel data to fit the
+ // current sample rate.
+ if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) {
+ double mult = (double)_sampleRate / (double)mixerSampleRate;
+ for (i = 0; i < _numberOfChannels; i++) {
+ _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult);
+ _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult);
+ }
+ }
+ _sampleRate = mixerSampleRate;
+ }
+ }
+}
+
+void Player_Mac::setMusicVolume(int vol) {
+ debug(5, "Player_Mac::setMusicVolume(%d)", vol);
+}
+
+void Player_Mac::stopAllSounds_Internal() {
+ if (_soundPlaying != -1) {
+ _vm->_res->unlock(rtSound, _soundPlaying);
+ }
+ _soundPlaying = -1;
+ for (int i = 0; i < _numberOfChannels; i++) {
+ // The channel data is managed by the resource manager, so
+ // don't delete that.
+ delete[] _channel[i]._instrument._data;
+ _channel[i]._instrument._data = NULL;
+
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = false;
+ }
+}
+
+void Player_Mac::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::stopAllSounds()");
+ stopAllSounds_Internal();
+}
+
+void Player_Mac::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::stopSound(%d)", nr);
+
+ if (nr == _soundPlaying) {
+ stopAllSounds();
+ }
+}
+
+void Player_Mac::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::startSound(%d)", nr);
+
+ stopAllSounds_Internal();
+
+ const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+ assert(ptr);
+
+ if (!loadMusic(ptr)) {
+ return;
+ }
+
+ _vm->_res->lock(rtSound, nr);
+ _soundPlaying = nr;
+}
+
+bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+ uint16 soundType = stream->readUint16BE();
+ if (soundType != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType);
+ return false;
+ }
+ uint16 typeCount = stream->readUint16BE();
+ if (typeCount != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount);
+ return false;
+ }
+ uint16 dataType = stream->readUint16BE();
+ if (dataType != 5) {
+ warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType);
+ return false;
+ }
+
+ stream->readUint32BE(); // initialization option
+
+ uint16 cmdCount = stream->readUint16BE();
+ if (cmdCount != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount);
+ return false;
+ }
+ uint16 command = stream->readUint16BE();
+ if (command != 0x8050 && command != 0x8051) {
+ warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command);
+ return false;
+ }
+
+ stream->readUint16BE(); // 0
+ uint32 soundHeaderOffset = stream->readUint32BE();
+
+ stream->seek(soundHeaderOffset);
+
+ uint32 soundDataOffset = stream->readUint32BE();
+ uint32 size = stream->readUint32BE();
+ uint32 rate = stream->readUint32BE() >> 16;
+ uint32 loopStart = stream->readUint32BE();
+ uint32 loopEnd = stream->readUint32BE();
+ byte encoding = stream->readByte();
+ byte baseFreq = stream->readByte();
+
+ if (encoding != 0) {
+ warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding);
+ return false;
+ }
+
+ stream->skip(soundDataOffset);
+
+ byte *data = new byte[size];
+ stream->read(data, size);
+
+ _instrument._data = data;
+ _instrument._size = size;
+ _instrument._rate = rate;
+ _instrument._loopStart = loopStart;
+ _instrument._loopEnd = loopEnd;
+ _instrument._baseFreq = baseFreq;
+
+ return true;
+}
+
+int Player_Mac::getMusicTimer() {
+ return 0;
+}
+
+int Player_Mac::getSoundStatus(int nr) const {
+ return _soundPlaying == nr;
+}
+
+uint32 Player_Mac::durationToSamples(uint16 duration) {
+ // The correct formula should be:
+ //
+ // (duration * 473 * _sampleRate) / (4 * 480 * 480)
+ //
+ // But that's likely to cause integer overflow, so we do it in two
+ // steps and hope that the rounding error won't be noticeable.
+ //
+ // The original code is a bit unclear on if it should be 473 or 437,
+ // but since the comments indicated 473 I'm assuming 437 was a typo.
+ uint32 samples = (duration * _sampleRate) / (4 * 480);
+ samples = (samples * 473) / 480;
+ return samples;
+}
+
+int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
+ if (note > 0) {
+ const int pitchIdx = note + 60 - instrument->_baseFreq;
+ // I don't want to use floating-point arithmetics here, but I
+ // ran into overflow problems with the church music in Monkey
+ // Island. It's only once per note, so it should be ok.
+ double mult = (double)instrument->_rate / (double)_sampleRate;
+ return (int)(mult * _pitchTable[pitchIdx]);
+ } else {
+ return 0;
+ }
+}
+
+int Player_Mac::readBuffer(int16 *data, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ memset(data, 0, numSamples * 2);
+ if (_soundPlaying == -1) {
+ return numSamples;
+ }
+
+ bool notesLeft = false;
+
+ for (int i = 0; i < _numberOfChannels; i++) {
+ if (!(_channelMask & (1 << i))) {
+ continue;
+ }
+
+ uint samplesLeft = numSamples;
+ int16 *ptr = data;
+
+ while (samplesLeft > 0) {
+ int generated;
+ if (_channel[i]._remaining == 0) {
+ uint32 samples;
+ int pitchModifier;
+ byte velocity;
+ if (getNextNote(i, samples, pitchModifier, velocity)) {
+ _channel[i]._remaining = samples;
+ _channel[i]._pitchModifier = pitchModifier;
+ _channel[i]._velocity = velocity;
+
+ } else {
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = samplesLeft;
+ }
+ }
+ generated = MIN<uint32>(_channel[i]._remaining, samplesLeft);
+ if (_channel[i]._velocity != 0) {
+ _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds);
+ }
+ ptr += generated;
+ samplesLeft -= generated;
+ _channel[i]._remaining -= generated;
+ }
+
+ if (_channel[i]._notesLeft) {
+ notesLeft = true;
+ }
+ }
+
+ if (!notesLeft) {
+ stopAllSounds_Internal();
+ }
+
+ return numSamples;
+}
+
+void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) {
+ int samplesLeft = numSamples;
+ while (samplesLeft) {
+ _subPos += pitchModifier;
+ while (_subPos >= 0x10000) {
+ _subPos -= 0x10000;
+ _pos++;
+ if (_pos >= _loopEnd) {
+ _pos = _loopStart;
+ }
+ }
+
+ int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255;
+
+ if (fadeNoteEnds) {
+ // Fade out the last 100 samples on each note. Even at
+ // low output sample rates this is just a fraction of a
+ // second, but it gets rid of distracting "pops" at the
+ // end when the sample would otherwise go abruptly from
+ // something to nothing. This was particularly
+ // noticeable on the distaff notes in Loom.
+ //
+ // The reason it's conditional is that Monkey Island
+ // appears to have a "hold current note" command, and
+ // if we fade out the current note in that case we
+ // will actually introduce new "pops".
+
+ remainingSamplesOnNote--;
+ if (remainingSamplesOnNote < 100) {
+ newSample = (newSample * remainingSamplesOnNote) / 100;
+ }
+ }
+
+ int sample = *data + newSample;
+ if (sample > 32767) {
+ sample = 32767;
+ } else if (sample < -32768) {
+ sample = -32768;
+ }
+
+ *data++ = sample;
+ samplesLeft--;
+ }
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
new file mode 100644
index 0000000000..09307b4e57
--- /dev/null
+++ b/engines/scumm/player_mac.h
@@ -0,0 +1,133 @@
+/* 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_PLAYER_MAC_H
+#define SCUMM_PLAYER_MAC_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/saveload.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#define RES_SND MKTAG('s', 'n', 'd', ' ')
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm Macintosh music driver, base class.
+ */
+class Player_Mac : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds);
+ virtual ~Player_Mac();
+
+ void init();
+
+ // MusicEngine API
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+ virtual bool isStereo() const { return false; }
+ virtual bool endOfData() const { return false; }
+ virtual int getRate() const { return _sampleRate; }
+
+ virtual void saveLoadWithSerializer(Serializer *ser);
+
+private:
+ Common::Mutex _mutex;
+ Audio::Mixer *const _mixer;
+ Audio::SoundHandle _soundHandle;
+ uint32 _sampleRate;
+ int _soundPlaying;
+
+ void stopAllSounds_Internal();
+
+ struct Instrument {
+ byte *_data;
+ uint32 _size;
+ uint32 _rate;
+ uint32 _loopStart;
+ uint32 _loopEnd;
+ byte _baseFreq;
+
+ uint _pos;
+ uint _subPos;
+
+ void newNote() {
+ _pos = 0;
+ _subPos = 0;
+ }
+
+ void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds);
+ };
+
+ int _pitchTable[128];
+ int _numberOfChannels;
+ int _channelMask;
+ bool _fadeNoteEnds;
+
+ virtual bool checkMusicAvailable() { return false; }
+ virtual bool loadMusic(const byte *ptr) { return false; }
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; }
+
+protected:
+ struct Channel {
+ virtual ~Channel() {}
+
+ Instrument _instrument;
+ bool _looped;
+ uint32 _length;
+ const byte *_data;
+
+ uint _pos;
+ int _pitchModifier;
+ byte _velocity;
+ uint32 _remaining;
+
+ bool _notesLeft;
+
+ bool loadInstrument(Common::SeekableReadStream *stream);
+ };
+
+ ScummEngine *const _vm;
+ Channel *_channel;
+
+ uint32 durationToSamples(uint16 duration);
+ int noteToPitchModifier(byte note, Instrument *instrument);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
new file mode 100644
index 0000000000..e61463128a
--- /dev/null
+++ b/engines/scumm/player_v3m.cpp
@@ -0,0 +1,202 @@
+/* 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.
+ *
+ */
+
+/*
+ We have the following information from Lars Christensen (lechimp) and
+ Jamieson Christian (jamieson630):
+
+ RESOURCE DATA
+ LE 2 bytes Resource size
+ 2 bytes Unknown
+ 2 bytes 'so'
+ 14 bytes Unknown
+ BE 2 bytes Instrument for Stream 1
+ BE 2 bytes Instrument for Stream 2
+ BE 2 bytes Instrument for Stream 3
+ BE 2 bytes Instrument for Stream 4
+ BE 2 bytes Instrument for Stream 5
+ BE 2 bytes Offset to Stream 1
+ BE 2 bytes Offset to Stream 2
+ BE 2 bytes Offset to Stream 3
+ BE 2 bytes Offset to Stream 4
+ BE 2 bytes Offset to Stream 5
+ ? bytes The streams
+
+ STREAM DATA
+ BE 2 bytes Unknown (always 1?)
+ 2 bytes Unknown (always 0?)
+ BE 2 bytes Number of events in stream
+ ? bytes Stream data
+
+ Each stream event is exactly 3 bytes, therefore one can
+ assert that numEvents == (streamSize - 6) / 3. The
+ polyphony of a stream appears to be 1; in other words, only
+ one note at a time can be playing in each stream. The next
+ event is not executed until the current note (or rest) is
+ finished playing; therefore, note duration also serves as the
+ time delta between events.
+
+ FOR EACH EVENTS
+ BE 2 bytes Note duration
+ 1 byte Note number to play (0 = rest/silent)
+
+ Oh, and quick speculation -- Stream 1 may be used for a
+ single-voice interleaved version of the music, where Stream 2-
+ 5 represent a version of the music in up to 4-voice
+ polyphony, one voice per stream. I postulate thus because
+ the first stream of the Mac Loom theme music contains
+ interleaved voices, whereas the second stream seemed to
+ contain only the pizzicato bottom-end harp. Stream 5, in this
+ example, is empty, so if my speculation is correct, this
+ particular musical number supports 3-voice polyphony at
+ most. I must check out Streams 3 and 4 to see what they
+ contain.
+
+ ==========
+
+ The instruments appear to be identified by their resource IDs:
+
+ 1000 Dual Harp
+ 10895 harp1
+ 11445 strings1
+ 11548 silent
+ 13811 staff1
+ 15703 brass1
+ 16324 flute1
+ 25614 accordian 1
+ 28110 f horn1
+ 29042 bassoon1
+*/
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/player_v3m.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
+ : Player_Mac(scumm, mixer, 5, 0x1E, true) {
+ assert(_vm->_game.id == GID_LOOM);
+
+ // Channel 0 seems to be what was played on low-end macs, that couldn't
+ // handle multi-channel music and play the game at the same time. I'm
+ // not sure if stream 4 is ever used, but let's use it just in case.
+}
+
+// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows
+// version, the UTF-8 version, and just plain without in case the file system
+// can't handle exotic characters like that.
+
+static const char *loomFileNames[] = {
+ "Loom\xAA",
+ "Loom\x99",
+ "Loom\xE2\x84\xA2",
+ "Loom"
+};
+
+bool Player_V3M::checkMusicAvailable() {
+ Common::MacResManager resource;
+
+ for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+ if (resource.exists(loomFileNames[i])) {
+ return true;
+ }
+ }
+
+ GUI::MessageDialog dialog(_(
+ "Could not find the 'Loom' Macintosh executable to read the\n"
+ "instruments from. Music will be disabled."), _("OK"));
+ dialog.runModal();
+ return false;
+}
+
+bool Player_V3M::loadMusic(const byte *ptr) {
+ Common::MacResManager resource;
+ bool found = false;
+
+ for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+ if (resource.open(loomFileNames[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+
+ assert(ptr[4] == 's' && ptr[5] == 'o');
+
+ uint i;
+ for (i = 0; i < 5; i++) {
+ int instrument = READ_BE_UINT16(ptr + 20 + 2 * i);
+ int offset = READ_BE_UINT16(ptr + 30 + 2 * i);
+
+ _channel[i]._looped = false;
+ _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3;
+ _channel[i]._data = ptr + offset + 6;
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = true;
+
+ Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
+ if (_channel[i].loadInstrument(stream)) {
+ debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
+ } else {
+ resource.close();
+ return false;
+ }
+ }
+
+ resource.close();
+ return true;
+}
+
+bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
+ _channel[ch]._instrument.newNote();
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ if (!_channel[ch]._looped) {
+ _channel[ch]._notesLeft = false;
+ return false;
+ }
+ _channel[ch]._pos = 0;
+ }
+ uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+ byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+ samples = durationToSamples(duration);
+ if (note > 0) {
+ pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+ velocity = 127;
+ } else {
+ pitchModifier = 0;
+ velocity = 0;
+ }
+ _channel[ch]._pos += 3;
+ return true;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h
new file mode 100644
index 0000000000..359bab32a9
--- /dev/null
+++ b/engines/scumm/player_v3m.h
@@ -0,0 +1,54 @@
+/* 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_PLAYER_V3M_H
+#define SCUMM_PLAYER_V3M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/player_mac.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V3 Macintosh music driver.
+ */
+class Player_V3M : public Player_Mac {
+public:
+ Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
+
+ virtual bool checkMusicAvailable();
+ virtual bool loadMusic(const byte *ptr);
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
new file mode 100644
index 0000000000..500f3bbc40
--- /dev/null
+++ b/engines/scumm/player_v5m.cpp
@@ -0,0 +1,246 @@
+/* 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.
+ *
+ */
+
+/*
+ From Markus Magnuson (superqult) we got this information:
+ Mac0
+ ---
+ 4 bytes - 'SOUN'
+ BE 4 bytes - block length
+
+ 4 bytes - 'Mac0'
+ BE 4 bytes - (blockLength - 27)
+ 28 bytes - ???
+
+ do this three times (once for each channel):
+ 4 bytes - 'Chan'
+ BE 4 bytes - channel length
+ 4 bytes - instrument name (e.g. 'MARI')
+
+ do this for ((chanLength-24)/4) times:
+ 2 bytes - note duration
+ 1 byte - note value
+ 1 byte - note velocity
+
+ 4 bytes - ???
+ 4 bytes - 'Loop'/'Done'
+ 4 bytes - ???
+
+ 1 byte - 0x09
+ ---
+
+ The instruments presumably correspond to the snd resource names in the
+ Monkey Island executable:
+
+ Instruments
+ "MARI" - MARIMBA
+ "PLUC" - PLUCK
+ "HARM" - HARMONIC
+ "PIPE" - PIPEORGAN
+ "TROM" - TROMBONE
+ "STRI" - STRINGS
+ "HORN" - HORN
+ "VIBE" - VIBES
+ "SHAK" - SHAKUHACHI
+ "PANP" - PANPIPE
+ "WHIS" - WHISTLE
+ "ORGA" - ORGAN3
+ "BONG" - BONGO
+ "BASS" - BASS
+
+ ---
+
+ Note values <= 1 are silent.
+*/
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/player_v5m.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
+ : Player_Mac(scumm, mixer, 3, 0x07, false) {
+ assert(_vm->_game.id == GID_MONKEY);
+}
+
+// Try both with and without underscore in the filename, because hfsutils may
+// turn the space into an underscore. At least, it did for me.
+
+static const char *monkeyIslandFileNames[] = {
+ "Monkey Island",
+ "Monkey_Island"
+};
+
+bool Player_V5M::checkMusicAvailable() {
+ Common::MacResManager resource;
+
+ for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+ if (resource.exists(monkeyIslandFileNames[i])) {
+ return true;
+ }
+ }
+
+ GUI::MessageDialog dialog(_(
+ "Could not find the 'Monkey Island' Macintosh executable to read the\n"
+ "instruments from. Music will be disabled."), _("OK"));
+ dialog.runModal();
+ return false;
+}
+
+bool Player_V5M::loadMusic(const byte *ptr) {
+ Common::MacResManager resource;
+ bool found = false;
+ uint i;
+
+ for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+ if (resource.open(monkeyIslandFileNames[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+
+ ptr += 8;
+ // TODO: Decipher the unknown bytes in the header. For now, skip 'em
+ ptr += 28;
+
+ Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
+
+ // Load the three channels and their instruments
+ for (i = 0; i < 3; i++) {
+ assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n'));
+ uint32 len = READ_BE_UINT32(ptr + 4);
+ uint32 instrument = READ_BE_UINT32(ptr + 8);
+
+ _channel[i]._length = len - 20;
+ _channel[i]._data = ptr + 12;
+ _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p'));
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = true;
+
+ for (uint j = 0; j < idArray.size(); j++) {
+ Common::String name = resource.getResName(RES_SND, idArray[j]);
+ if (instrument == READ_BE_UINT32(name.c_str())) {
+ debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str());
+ Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]);
+
+ if (!_channel[i].loadInstrument(stream)) {
+ resource.close();
+ return false;
+ }
+
+ break;
+ }
+ }
+
+ ptr += len;
+ }
+
+ resource.close();
+
+ // The last note of each channel is just zeroes. We will adjust this
+ // note so that all the channels end at the same time.
+
+ uint32 samples[3];
+ uint32 maxSamples = 0;
+ for (i = 0; i < 3; i++) {
+ samples[i] = 0;
+ for (uint j = 0; j < _channel[i]._length; j += 4) {
+ samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j]));
+ }
+ if (samples[i] > maxSamples) {
+ maxSamples = samples[i];
+ }
+ }
+
+ for (i = 0; i < 3; i++) {
+ _lastNoteSamples[i] = maxSamples - samples[i];
+ }
+
+ return true;
+}
+
+bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ if (!_channel[ch]._looped) {
+ _channel[ch]._notesLeft = false;
+ return false;
+ }
+ // FIXME: Jamieson630: The jump seems to be happening
+ // too quickly! There should maybe be a pause after
+ // the last Note Off? But I couldn't find one in the
+ // MI1 Lookout music, where I was hearing problems.
+ _channel[ch]._pos = 0;
+ }
+ uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+ byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+ samples = durationToSamples(duration);
+
+ if (note != 1) {
+ _channel[ch]._instrument.newNote();
+ }
+
+ if (note > 1) {
+ pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+ velocity = _channel[ch]._data[_channel[ch]._pos + 3];
+ } else if (note == 1) {
+ // This is guesswork, but Monkey Island uses two different
+ // "special" note values: 0, which is clearly a rest, and 1
+ // which is... I thought at first it was a "soft" key off, to
+ // fade out the note, but listening to the music in a Mac
+ // emulator (which unfortunately doesn't work all that well),
+ // I hear no trace of fading out.
+ //
+ // It could mean "change the volume on the current note", but
+ // I can't hear that either, and it always seems to use the
+ // exact same velocity on this note.
+ //
+ // So it appears it really just is a "hold the current note",
+ // but why? Couldn't they just have made the original note
+ // longer?
+
+ pitchModifier = _channel[ch]._pitchModifier;
+ velocity = _channel[ch]._velocity;
+ } else {
+ pitchModifier = 0;
+ velocity = 0;
+ }
+
+ _channel[ch]._pos += 4;
+
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ samples = _lastNoteSamples[ch];
+ }
+ return true;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h
new file mode 100644
index 0000000000..b2079ee331
--- /dev/null
+++ b/engines/scumm/player_v5m.h
@@ -0,0 +1,57 @@
+/* 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_PLAYER_V5M_H
+#define SCUMM_PLAYER_V5M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/player_mac.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V5 Macintosh music driver.
+ */
+class Player_V5M : public Player_Mac {
+public:
+ Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer);
+
+ virtual bool checkMusicAvailable();
+ virtual bool loadMusic(const byte *ptr);
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
+
+private:
+ uint32 _lastNoteSamples[3];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 72896e097a..3453e53a18 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -1477,9 +1477,13 @@ void ScummEngine::saveOrLoad(Serializer *s) {
}
- // Save/load FM-Towns audio status
- if (_townsPlayer)
- _townsPlayer->saveLoadWithSerializer(s);
+ //
+ // Save/load music engine status
+ //
+ if (_musicEngine) {
+ _musicEngine->saveLoadWithSerializer(s);
+ }
+
//
// Save/load the charset renderer state
diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h
index a640bc1e17..4bfa7d0e71 100644
--- a/engines/scumm/saveload.h
+++ b/engines/scumm/saveload.h
@@ -47,7 +47,7 @@ namespace Scumm {
* only saves/loads those which are valid for the version of the savegame
* which is being loaded/saved currently.
*/
-#define CURRENT_VER 93
+#define CURRENT_VER 94
/**
* An auxillary macro, used to specify savegame versions. We use this instead
diff --git a/engines/scumm/script.cpp b/engines/scumm/script.cpp
index d8c4948ea8..8587fb8092 100644
--- a/engines/scumm/script.cpp
+++ b/engines/scumm/script.cpp
@@ -1366,9 +1366,15 @@ void ScummEngine::runInputScript(int clickArea, int val, int mode) {
// Clicks are handled differently in Indy3 mac: param 2 of the
// input script is set to 0 for normal clicks, and to 1 for double clicks.
+ // The EGA DOS version of Loom also checks that the second click happens
+ // close enough to the first one, but that seems like overkill.
uint32 time = _system->getMillis();
args[2] = (time < _lastInputScriptTime + 500); // 500 ms double click delay
_lastInputScriptTime = time;
+ } else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformMacintosh) {
+ uint32 time = _system->getMillis();
+ VAR(52) = (time < _lastInputScriptTime + 500); // 500 ms double click delay
+ _lastInputScriptTime = time;
}
if (verbScript)
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 6a4fe238a1..3afeeda13d 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -61,7 +61,9 @@
#include "scumm/player_v2cms.h"
#include "scumm/player_v2a.h"
#include "scumm/player_v3a.h"
+#include "scumm/player_v3m.h"
#include "scumm/player_v4a.h"
+#include "scumm/player_v5m.h"
#include "scumm/resource.h"
#include "scumm/he/resource_he.h"
#include "scumm/scumm_v0.h"
@@ -1819,6 +1821,12 @@ void ScummEngine::setupMusic(int midi) {
#endif
} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) {
_musicEngine = new Player_V4A(this, _mixer);
+ } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) {
+ _musicEngine = new Player_V3M(this, _mixer);
+ ((Player_V3M *)_musicEngine)->init();
+ } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
+ _musicEngine = new Player_V5M(this, _mixer);
+ ((Player_V5M *)_musicEngine)->init();
} else if (_game.id == GID_MANIAC && _game.version == 1) {
_musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK);
} else if (_game.version <= 2) {
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index a1cecfa0b3..2fe16c5441 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -346,29 +346,6 @@ void Sound::playSound(int soundID) {
warning("Scumm::Sound::playSound: encountered audio resoure with chunk type 'SOUN' and sound type %d", type);
}
}
- else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) {
- // Mac version of Loom uses yet another sound format
- /*
- playSound #9 (room 70)
- 000000: 55 00 00 45 73 6f 00 64 01 00 00 00 00 00 00 00 |U..Eso.d........|
- 000010: 00 05 00 8e 2a 8f 2d 1c 2a 8f 2a 8f 2d 1c 00 28 |....*.-.*.*.-..(|
- 000020: 00 31 00 3a 00 43 00 4c 00 01 00 00 00 01 00 64 |.1.:.C.L.......d|
- 000030: 5a 00 01 00 00 00 01 00 64 00 00 01 00 00 00 01 |Z.......d.......|
- 000040: 00 64 5a 00 01 00 00 00 01 00 64 5a 00 01 00 00 |.dZ.......dZ....|
- 000050: 00 01 00 64 00 00 00 00 00 00 00 07 00 00 00 64 |...d...........d|
- 000060: 64 00 00 4e 73 6f 00 64 01 00 00 00 00 00 00 00 |d..Nso.d........|
- 000070: 00 05 00 89 3d 57 2d 1c 3d 57 3d 57 2d 1c 00 28 |....=W-.=W=W-..(|
- playSound #16 (room 69)
- 000000: dc 00 00 a5 73 6f 00 64 01 00 00 00 00 00 00 00 |....so.d........|
- 000010: 00 05 00 00 2a 8f 03 e8 03 e8 03 e8 03 e8 00 28 |....*..........(|
- 000020: 00 79 00 7f 00 85 00 d6 00 01 00 00 00 19 01 18 |.y..............|
- 000030: 2f 00 18 00 01 18 32 00 18 00 01 18 36 00 18 00 |/.....2.....6...|
- 000040: 01 18 3b 00 18 00 01 18 3e 00 18 00 01 18 42 00 |..;.....>.....B.|
- 000050: 18 00 01 18 47 00 18 00 01 18 4a 00 18 00 01 18 |....G.....J.....|
- 000060: 4e 00 10 00 01 18 53 00 10 00 01 18 56 00 10 00 |N.....S.....V...|
- 000070: 01 18 5a 00 10 00 02 28 5f 00 01 00 00 00 00 00 |..Z....(_.......|
- */
- }
else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) {
// Sound format as used in Indy3 EGA Mac.
// It seems to be closely related to the Amiga format, see player_v3a.cpp
@@ -414,8 +391,7 @@ void Sound::playSound(int soundID) {
}
else {
- if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA
- || (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) {
+ if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) {
// Works around the fact that in some places in MonkeyEGA/VGA,
// the music is never explicitly stopped.
// Rather it seems that starting a new music is supposed to
@@ -1086,9 +1062,6 @@ void Sound::saveLoadWithSerializer(Serializer *ser) {
#pragma mark --- Sound resource handling ---
#pragma mark -
-static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size);
-
-
/*
* TODO: The way we handle sound/music resources really is one huge hack.
* We probably should reconsider how we do this, and maybe come up with a
@@ -1208,11 +1181,9 @@ int ScummEngine::readSoundResource(ResId idx) {
case MKTAG('M','a','c','0'):
_fileHandle->seek(-12, SEEK_CUR);
total_size = _fileHandle->readUint32BE() - 8;
- ptr = (byte *)calloc(total_size, 1);
+ ptr = _res->createResource(rtSound, idx, total_size);
_fileHandle->read(ptr, total_size);
//dumpResource("sound-", idx, ptr);
- convertMac0Resource(_res, idx, ptr, total_size);
- free(ptr);
return 1;
case MKTAG('M','a','c','1'):
@@ -1445,219 +1416,6 @@ static byte *writeVLQ(byte *ptr, int value) {
return ptr;
}
-static byte Mac0ToGMInstrument(uint32 type, int &transpose) {
- transpose = 0;
- switch (type) {
- case MKTAG('M','A','R','I'): return 12;
- case MKTAG('P','L','U','C'): return 45;
- case MKTAG('H','A','R','M'): return 22;
- case MKTAG('P','I','P','E'): return 19;
- case MKTAG('T','R','O','M'): transpose = -12; return 57;
- case MKTAG('S','T','R','I'): return 48;
- case MKTAG('H','O','R','N'): return 60;
- case MKTAG('V','I','B','E'): return 11;
- case MKTAG('S','H','A','K'): return 77;
- case MKTAG('P','A','N','P'): return 75;
- case MKTAG('W','H','I','S'): return 76;
- case MKTAG('O','R','G','A'): return 17;
- case MKTAG('B','O','N','G'): return 115;
- case MKTAG('B','A','S','S'): transpose = -24; return 35;
- default:
- error("Unknown Mac0 instrument %s found", tag2str(type));
- }
-}
-
-static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size) {
- /*
- From Markus Magnuson (superqult) we got this information:
- Mac0
- ---
- 4 bytes - 'SOUN'
- BE 4 bytes - block length
-
- 4 bytes - 'Mac0'
- BE 4 bytes - (blockLength - 27)
- 28 bytes - ???
-
- do this three times (once for each channel):
- 4 bytes - 'Chan'
- BE 4 bytes - channel length
- 4 bytes - instrument name (e.g. 'MARI')
-
- do this for ((chanLength-24)/4) times:
- 2 bytes - note duration
- 1 byte - note value
- 1 byte - note velocity
-
- 4 bytes - ???
- 4 bytes - 'Loop'/'Done'
- 4 bytes - ???
-
- 1 byte - 0x09
- ---
-
- Instruments (General Midi):
- "MARI" - Marimba (12)
- "PLUC" - Pizzicato Strings (45)
- "HARM" - Harmonica (22)
- "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109)
- "TROM" - Trombone (57)
- "STRI" - String Ensemble (48 or 49)
- "HORN" - French Horn? (60) or English Horn? (69)
- "VIBE" - Vibraphone (11)
- "SHAK" - Shakuhachi? (77)
- "PANP" - Pan Flute (75)
- "WHIS" - Whistle (78) / Bottle (76)
- "ORGA" - Drawbar Organ (16; but could also be 17-20)
- "BONG" - Woodblock? (115)
- "BASS" - Bass (32-39)
-
-
- Now the task could be to convert this into MIDI, to be fed into iMuse.
- Or we do something similiar to what is done in Player_V3, assuming
- we can identify SFX in the MI datafiles for each of the instruments
- listed above.
- */
-
-#if 0
- byte *ptr = _res->createResource(rtSound, idx, size);
- memcpy(ptr, src_ptr, size);
-#else
- const int ppqn = 480;
- byte *ptr, *start_ptr;
-
- int total_size = 0;
- total_size += kMIDIHeaderSize; // Header
- total_size += 7; // Tempo META
- total_size += 3 * 3; // Three program change mesages
- total_size += 22; // Possible jump SysEx
- total_size += 5; // EOT META
-
- int i, len;
- byte track_instr[3];
- byte *track_data[3];
- int track_len[3];
- int track_transpose[3];
- bool looped = false;
-
- src_ptr += 8;
- // TODO: Decipher the unknown bytes in the header. For now, skip 'em
- src_ptr += 28;
-
- // Parse the three channels
- for (i = 0; i < 3; i++) {
- assert(READ_BE_UINT32(src_ptr) == MKTAG('C','h','a','n'));
- len = READ_BE_UINT32(src_ptr + 4);
- track_len[i] = len - 24;
- track_instr[i] = Mac0ToGMInstrument(READ_BE_UINT32(src_ptr + 8), track_transpose[i]);
- track_data[i] = src_ptr + 12;
- src_ptr += len;
- looped = (READ_BE_UINT32(src_ptr - 8) == MKTAG('L','o','o','p'));
-
- // For each note event, we need up to 6 bytes for the
- // Note On (3 VLQ, 3 event), and 6 bytes for the Note
- // Off (3 VLQ, 3 event). So 12 bytes total.
- total_size += 12 * track_len[i];
- }
- assert(*src_ptr == 0x09);
-
- // Create sound resource
- start_ptr = res->createResource(rtSound, idx, total_size);
-
- // Insert MIDI header
- ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
-
- // Write a tempo change Meta event
- // 473 / 4 Hz, convert to micro seconds.
- uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473;
- memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4;
- *ptr++ = (byte)((dw >> 16) & 0xFF);
- *ptr++ = (byte)((dw >> 8) & 0xFF);
- *ptr++ = (byte)(dw & 0xFF);
-
- // Insert program change messages
- *ptr++ = 0; // VLQ
- *ptr++ = 0xC0;
- *ptr++ = track_instr[0];
- *ptr++ = 0; // VLQ
- *ptr++ = 0xC1;
- *ptr++ = track_instr[1];
- *ptr++ = 0; // VLQ
- *ptr++ = 0xC2;
- *ptr++ = track_instr[2];
-
- // And now, the actual composition. Please turn all cell phones
- // and pagers off during the performance. Thank you.
- uint16 nextTime[3] = { 1, 1, 1 };
- int stage[3] = { 0, 0, 0 };
-
- while (track_len[0] | track_len[1] | track_len[2]) {
- int best = -1;
- uint16 bestTime = 0xFFFF;
- for (i = 0; i < 3; ++i) {
- if (track_len[i] && nextTime[i] < bestTime) {
- bestTime = nextTime[i];
- best = i;
- }
- }
- assert (best != -1);
-
- if (!stage[best]) {
- // We are STARTING this event.
- if (track_data[best][2] > 1) {
- // Note On
- ptr = writeVLQ(ptr, nextTime[best]);
- *ptr++ = 0x90 | best;
- *ptr++ = track_data[best][2] + track_transpose[best];
- *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
- for (i = 0; i < 3; ++i)
- nextTime[i] -= bestTime;
- }
- nextTime[best] += READ_BE_UINT16 (track_data[best]);
- stage[best] = 1;
- } else {
- // We are ENDING this event.
- if (track_data[best][2] > 1) {
- // There was a Note On, so do a Note Off
- ptr = writeVLQ(ptr, nextTime[best]);
- *ptr++ = 0x80 | best;
- *ptr++ = track_data[best][2] + track_transpose[best];
- *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
- for (i = 0; i < 3; ++i)
- nextTime[i] -= bestTime;
- }
- track_data[best] += 4;
- track_len[best] -= 4;
- stage[best] = 0;
- }
- }
-
- // Is this a looped song? If so, effect a loop by
- // using the S&M maybe_jump SysEx command.
- // FIXME: Jamieson630: The jump seems to be happening
- // too quickly! There should maybe be a pause after
- // the last Note Off? But I couldn't find one in the
- // MI1 Lookout music, where I was hearing problems.
- if (looped) {
- memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump
- memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump
- memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track)
- memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat)
- memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1
- memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker
- }
-
- // Insert end of song META
- memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5;
-
- assert(ptr <= start_ptr + total_size);
-
- // Rewrite MIDI header, this time with true size
- total_size = ptr - start_ptr;
- ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
-#endif
-}
-
static void convertADResource(ResourceManager *res, const GameSettings& game, ResId idx, byte *src_ptr, int size) {
// We will ignore the PPQN in the original resource, because
// it's invalid anyway. We use a constant PPQN of 480.
diff --git a/engines/sword25/util/lua/llex.cpp b/engines/sword25/util/lua/llex.cpp
index f8433d3afa..423f0285ca 100644
--- a/engines/sword25/util/lua/llex.cpp
+++ b/engines/sword25/util/lua/llex.cpp
@@ -4,6 +4,8 @@
** See Copyright Notice in lua.h
*/
+// FIXME: Do not directly use iscntrl from ctype.h.
+#define FORBIDDEN_SYMBOL_EXCEPTION_iscntrl
#include "common/util.h"
diff --git a/engines/tinsel/detection_tables.h b/engines/tinsel/detection_tables.h
index 631c2dce14..4762acfe2c 100644
--- a/engines/tinsel/detection_tables.h
+++ b/engines/tinsel/detection_tables.h
@@ -69,7 +69,7 @@ static const TinselGameDescription gameDescriptions[] = {
0,
TINSEL_V1,
},
-#if 0
+
{ // Macintosh CD Demo V1 version, with *.scn files, see tracker #3110936
{
"dw",
@@ -89,7 +89,7 @@ static const TinselGameDescription gameDescriptions[] = {
GF_SCNFILES,
TINSEL_V1,
},
-#endif
+
{ // Multilingual Floppy V1 with *.gra files.
// Note: It contains no english subtitles.
{
@@ -474,7 +474,6 @@ static const TinselGameDescription gameDescriptions[] = {
},
#endif
-#if 0
{ // Mac multilanguage CD
{
"dw",
@@ -495,8 +494,6 @@ static const TinselGameDescription gameDescriptions[] = {
TINSEL_V1,
},
-#endif
-
{ // German CD re-release "Neon Edition"
// Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT
{
diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp
index 876d182a1a..91dfd76b98 100644
--- a/engines/tinsel/graphics.cpp
+++ b/engines/tinsel/graphics.cpp
@@ -212,6 +212,84 @@ static void t0WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply
}
/**
+ * Straight rendering with transparency support, Mac variant
+ */
+static void MacDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ int yClip = 0;
+
+ if (applyClipping) {
+ // Adjust the height down to skip any bottom clipping
+ pObj->height -= pObj->botClip;
+ yClip = pObj->topClip;
+ }
+
+ // Simple RLE-like scheme: the two first bytes of each data chunk determine
+ // if bytes should be repeated or copied.
+ // Example: 10 00 00 20 will repeat byte 0x0 0x10 times, and will copy 0x20
+ // bytes from the input stream afterwards
+
+ // Vertical loop
+ for (int y = 0; y < pObj->height; ++y) {
+ // Get the start of the next line output
+ uint8 *tempDest = destP;
+
+ int leftClip = applyClipping ? pObj->leftClip : 0;
+ int rightClip = applyClipping ? pObj->rightClip : 0;
+
+ // Horizontal loop
+ for (int x = 0; x < pObj->width; ) {
+ byte repeatBytes = *srcP++;
+
+ if (repeatBytes) {
+ uint clipAmount = MIN<int>(repeatBytes, leftClip);
+ leftClip -= clipAmount;
+ x += clipAmount;
+
+ // Repeat of a given color
+ byte color = *srcP++;
+ int runLength = repeatBytes - clipAmount;
+
+ int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
+ if (yClip == 0) {
+ if (color != 0)
+ memset(tempDest, color, rptLength);
+ tempDest += rptLength;
+ }
+
+ x += runLength;
+ } else {
+ // Copy a specified sequence length of pixels
+ byte copyBytes = *srcP++;
+
+ uint clipAmount = MIN<int>(copyBytes, leftClip);
+ leftClip -= clipAmount;
+ x += clipAmount;
+
+ srcP += clipAmount;
+
+ int runLength = copyBytes - clipAmount;
+ int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
+ if (yClip == 0) {
+ memmove(tempDest, srcP, rptLength);
+ tempDest += rptLength;
+ }
+
+ int overflow = (copyBytes % 2) == 0 ? 0 : 2 - (copyBytes % 2);
+ x += runLength;
+ srcP += runLength + overflow;
+ }
+ } // horizontal loop
+
+ // Move to next line
+ if (yClip > 0)
+ --yClip;
+ else
+ destP += SCREEN_WIDTH;
+ } // vertical loop
+}
+
+
+/**
* Straight rendering with transparency support, PSX variant supporting also 4-BIT clut data
*/
static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) {
@@ -272,7 +350,7 @@ static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply
assert(boxBounds.bottom >= boxBounds.top);
assert(boxBounds.right >= boxBounds.left);
- int16 indexVal = READ_16(srcP);
+ int16 indexVal = READ_LE_UINT16(srcP);
srcP += sizeof(uint16);
// Draw a 4x4 block based on the opcode as in index into the block list
@@ -381,7 +459,7 @@ static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyCl
assert(boxBounds.bottom >= boxBounds.top);
assert(boxBounds.right >= boxBounds.left);
- int16 indexVal = READ_16(srcP);
+ int16 indexVal = READ_LE_UINT16(srcP);
srcP += sizeof(uint16);
if (indexVal >= 0) {
@@ -763,8 +841,8 @@ void DrawObject(DRAWOBJECT *pObj) {
byte *p = (byte *)LockMem(pObj->hBits & HANDLEMASK);
srcPtr = p + (pObj->hBits & OFFSETMASK);
- pObj->charBase = (char *)p + READ_32(p + 0x10);
- pObj->transOffset = READ_32(p + 0x14);
+ pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
+ pObj->transOffset = READ_LE_UINT32(p + 0x14);
// Decompress block indexes for Discworld PSX
if (TinselV1PSX) {
@@ -793,7 +871,7 @@ void DrawObject(DRAWOBJECT *pObj) {
psxPaletteMapper(pObj->pPal, srcPtr + sizeof(uint16), psxMapperTable);
psxFourBitClut = true;
- psxSkipBytes = READ_32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
+ psxSkipBytes = READ_LE_UINT32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
switch (indexType) {
case 0xDD: // Normal uncompressed indexes
psxRLEindex = false;
@@ -822,95 +900,58 @@ void DrawObject(DRAWOBJECT *pObj) {
// Handle various draw types
uint8 typeId = pObj->flags & 0xff;
+ int packType = pObj->flags >> 14; // TinselV2
- if (TinselV2) {
- // Tinsel v2 decoders
- // Initial switch statement for the different bit packing types
- int packType = pObj->flags >> 14;
-
- if (packType == 0) {
- // No color packing
- switch (typeId) {
- case 0x01:
- case 0x11:
- case 0x41:
- case 0x51:
- case 0x81:
- case 0xC1:
- t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0);
- break;
- case 0x02:
- case 0x42:
- // This renderer called 'RlWrtAll', but is the same as t2WrtNonZero
- t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, false);
- break;
- case 0x04:
- case 0x44:
- // WrtConst with/without clipping
- WrtConst(pObj, destPtr, typeId == 0x44);
- break;
- case 0x08:
- case 0x48:
- WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40);
- break;
- case 0x84:
- case 0xC4:
- // WrtTrans with/without clipping
- WrtTrans(pObj, destPtr, typeId == 0xC4);
- break;
- default:
- error("Unknown drawing type %d", typeId);
- }
- } else {
- // 1 = 16 from 240
- // 2 = 16 from 224
- // 3 = variable color
- if (packType == 1) pObj->baseCol = 0xF0;
- else if (packType == 2) pObj->baseCol = 0xE0;
-
- PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0,
- (pObj->flags & DMA_FLIPH), packType);
- }
+ if (TinselV2 && packType != 0) {
+ // Color packing for TinselV2
+
+ if (packType == 1)
+ pObj->baseCol = 0xF0; // 16 from 240
+ else if (packType == 2)
+ pObj->baseCol = 0xE0; // 16 from 224
+ // 3 = variable color
- } else { // TinselV1
+ PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0,
+ (pObj->flags & DMA_FLIPH), packType);
+ } else {
switch (typeId) {
- case 0x01:
- case 0x41:
- if (TinselV1PSX) {
- PsxDrawTiles(pObj, srcPtr, destPtr, typeId >= 0x40, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
- } else if (TinselV1Mac) {
- // TODO
- } else if (TinselV1) {
- WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
- } else if (TinselV0) {
- t0WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
- }
+ case 0x01: // all versions, draw sprite without clipping
+ case 0x41: // all versions, draw sprite with clipping
+ case 0x02: // TinselV2, draw sprite without clipping
+ case 0x11: // TinselV2, draw sprite without clipping, flipped horizontally
+ case 0x42: // TinselV2, draw sprite with clipping
+ case 0x51: // TinselV2, draw sprite with clipping, flipped horizontally
+ case 0x81: // TinselV2, draw sprite with clipping
+ case 0xC1: // TinselV2, draw sprite with clipping
+ assert(TinselV2 || (typeId == 0x01 || typeId == 0x41));
+
+ if (TinselV2)
+ t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0);
+ else if (TinselV1PSX)
+ PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
+ else if (TinselV1Mac)
+ MacDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41);
+ else if (TinselV1)
+ WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
+ else if (TinselV0)
+ t0WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
break;
- case 0x08:
- case 0x48:
- if (TinselV1PSX) {
- PsxDrawTiles(pObj, srcPtr, destPtr, typeId >= 0x40, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
- } else if (TinselV1Mac) {
- WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40);
- } else if (TinselV1) {
- WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
- } else if (TinselV0) {
- WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40);
- }
+ case 0x08: // draw background without clipping
+ case 0x48: // draw background with clipping
+ if (TinselV2 || TinselV1Mac || TinselV0)
+ WrtAll(pObj, srcPtr, destPtr, typeId == 0x48);
+ else if (TinselV1PSX)
+ PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
+ else if (TinselV1)
+ WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x48);
break;
- case 0x04:
- case 0x44:
- // WrtConst with/without clipping
+ case 0x04: // fill with constant color without clipping
+ case 0x44: // fill with constant color with clipping
WrtConst(pObj, destPtr, typeId == 0x44);
break;
- case 0x84:
- case 0xC4:
- if (!TinselV0) {
- // WrtTrans with/without clipping
- WrtTrans(pObj, destPtr, typeId == 0xC4);
- } else {
- WrtTrans(pObj, destPtr, (typeId & 0x40) != 0);
- }
+ case 0x84: // draw transparent surface without clipping
+ case 0xC4: // draw transparent surface with clipping
+ WrtTrans(pObj, destPtr, typeId == 0xC4);
break;
default:
error("Unknown drawing type %d", typeId);
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
index 91f0312101..dab2a897fc 100644
--- a/engines/tinsel/music.cpp
+++ b/engines/tinsel/music.cpp
@@ -135,10 +135,10 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) {
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
- // TODO: The Macintosh version of DW1 does not use MIDI for music
+ // The Macintosh version of DW1 uses raw PCM for music
if (TinselV1Mac)
- return true;
-
+ return _vm->_sound->playDW1MacMusic(dwFileOffset);
+
SetMidiVolume(mute ? 0 : _vm->_config->_musicVolume);
// the index and length of the last tune loaded
@@ -285,7 +285,8 @@ void OpenMidiFiles() {
if (TinselV0 || TinselV2)
return;
- // TODO: The Macintosh version of DW1 does not use MIDI for music
+ // The Macintosh version of DW1 does not use MIDI for music.
+ // It uses PCM music instead, which is quite big to be preloaded here.
if (TinselV1Mac)
return;
diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp
index c7c8cd0b63..04018172c0 100644
--- a/engines/tinsel/palette.cpp
+++ b/engines/tinsel/palette.cpp
@@ -170,6 +170,12 @@ void PalettesToVideoDAC() {
pal[i * 3 + 2] = TINSEL_GetBValue(pColors[i]);
}
+ // In DW1 Mac, color 254 should be black, like in the PC version.
+ // We fix it here.
+ if (TinselV1Mac) {
+ pal[254 * 3 + 0] = pal[254 * 3 + 1] = pal[254 * 3 + 2] = 0;
+ }
+
// update the system palette
g_system->getPaletteManager()->setPalette(pal, pDACtail->destDACindex, pDACtail->numColors);
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
index 986d54f59f..9181f85afb 100644
--- a/engines/tinsel/scene.cpp
+++ b/engines/tinsel/scene.cpp
@@ -159,7 +159,8 @@ static void SceneTinselProcess(CORO_PARAM, const void *param) {
// The following myEscape value setting is used for enabling title screen skipping in DW1
if (TinselV1 && (g_sceneCtr == 1)) g_initialMyEscape = GetEscEvents();
// DW1 PSX has its own scene skipping script code for scenes 2 and 3 (bug #3541542).
- _ctx->myEscape = (TinselV1 && (g_sceneCtr < (TinselV1PSX ? 2 : 4))) ? g_initialMyEscape : 0;
+ // Same goes for DW1 Mac.
+ _ctx->myEscape = (TinselV1 && (g_sceneCtr < ((TinselV1PSX || TinselV1Mac) ? 2 : 4))) ? g_initialMyEscape : 0;
// get the stuff copied to process when it was created
_ctx->pInit = (const TP_INIT *)param;
diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp
index c7b295f654..03aa3767c4 100644
--- a/engines/tinsel/sound.cpp
+++ b/engines/tinsel/sound.cpp
@@ -74,7 +74,7 @@ SoundManager::~SoundManager() {
*/
// playSample for DiscWorld 1
bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
- // Floppy version has no sample file
+ // Floppy version has no sample file.
if (!_vm->isCD())
return false;
@@ -177,6 +177,48 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound
return true;
}
+bool SoundManager::playDW1MacMusic(int dwFileOffset) {
+ Common::File s;
+
+ if (!s.open("midi.dat"))
+ error(CANNOT_FIND_FILE, "midi.dat");
+
+ s.seek(dwFileOffset);
+ uint32 length = s.readUint32BE();
+
+ // TODO: It's a bad idea to load the music track in a buffer.
+ // We should use a SubReadStream instead, and keep midi.dat open.
+ // However, the track lengths aren't that big (about 1-4MB),
+ // so this shouldn't be a major issue.
+ byte *soundData = (byte *)malloc(length);
+ assert(soundData);
+
+ // read all of the sample
+ if (s.read(soundData, length) != length)
+ error(FILE_IS_CORRUPT, "midi.dat");
+
+ Common::SeekableReadStream *memStream = new Common::MemoryReadStream(soundData, length);
+
+ Audio::SoundHandle *handle = &_channels[kChannelDW1MacMusic].handle;
+ //_channels[kChannelDW1MacMusic].sampleNum = dwFileOffset;
+
+ // Stop any previously playing music track
+ _vm->_mixer->stopHandle(*handle);
+
+ // FIXME: Should set this in a different place ;)
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _vm->_config->_musicVolume);
+
+ // TODO: Compression support (MP3/OGG/FLAC) for midi.dat in DW1 Mac
+ Audio::RewindableAudioStream *musicStream = Audio::makeRawStream(memStream, 22050, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+
+ if (musicStream)
+ _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, handle, Audio::makeLoopingAudioStream(musicStream, 0));
+
+ s.close();
+
+ return true;
+}
+
// playSample for DiscWorld 2
bool SoundManager::playSample(int id, int sub, bool bLooped, int x, int y, int priority,
Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
@@ -369,7 +411,6 @@ bool SoundManager::offscreenChecks(int x, int &y) {
}
int8 SoundManager::getPan(int x) {
-
if (x == -1)
return 0;
@@ -416,14 +457,13 @@ bool SoundManager::sampleExists(int id) {
/**
* Returns true if a sample is currently playing.
*/
-bool SoundManager::sampleIsPlaying(int id) {
+bool SoundManager::sampleIsPlaying() {
if (!TinselV2)
return _vm->_mixer->isSoundHandleActive(_channels[kChannelTinsel1].handle);
for (int i = 0; i < kNumChannels; i++)
- if (_channels[i].sampleNum == id)
- if (_vm->_mixer->isSoundHandleActive(_channels[i].handle))
- return true;
+ if (_vm->_mixer->isSoundHandleActive(_channels[i].handle))
+ return true;
return false;
}
@@ -432,8 +472,6 @@ bool SoundManager::sampleIsPlaying(int id) {
* Stops any currently playing sample.
*/
void SoundManager::stopAllSamples() {
- // stop currently playing sample
-
if (!TinselV2) {
_vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle);
return;
@@ -466,12 +504,21 @@ void SoundManager::setSFXVolumes(uint8 volume) {
_vm->_mixer->setChannelVolume(_channels[i].handle, volume);
}
+void SoundManager::showSoundError(const char *errorMsg, const char *soundFile) {
+ Common::String msg;
+ msg = Common::String::format(errorMsg, soundFile);
+ GUI::MessageDialog dialog(msg, "OK");
+ dialog.runModal();
+
+ error("%s", msg.c_str());
+}
+
/**
* Opens and inits all sound sample files.
*/
void SoundManager::openSampleFiles() {
// Floppy and demo versions have no sample files, except for the Discworld 2 demo
- if (!_vm->isCD() || TinselV0)
+ if (!_vm->isCD())
return;
TinselFile f;
@@ -480,42 +527,26 @@ void SoundManager::openSampleFiles() {
// already allocated
return;
- // open sample index file in binary mode
+ // Open sample index (*.idx) in binary mode
if (f.open(_vm->getSampleIndex(g_sampleLanguage))) {
- // get length of index file
- f.seek(0, SEEK_END); // move to end of file
- _sampleIndexLen = f.pos(); // get file pointer
- f.seek(0, SEEK_SET); // back to beginning
-
+ uint32 fileSize = f.size();
+ _sampleIndex = (uint32 *)malloc(fileSize);
if (_sampleIndex == NULL) {
- // allocate a buffer for the indices
- _sampleIndex = (uint32 *)malloc(_sampleIndexLen);
-
- // make sure memory allocated
- if (_sampleIndex == NULL) {
- // disable samples if cannot alloc buffer for indices
- // TODO: Disabled sound if we can't load the sample index?
- return;
- }
+ showSoundError(NO_MEM, _vm->getSampleIndex(g_sampleLanguage));
+ return;
}
- // load data
- if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen)
- // file must be corrupt if we get to here
- error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage));
+ _sampleIndexLen = fileSize / 4; // total sample of indices (DWORDs)
- // close the file
- f.close();
-
- // convert file size to size in DWORDs
- _sampleIndexLen /= sizeof(uint32);
-
-#ifdef SCUMM_BIG_ENDIAN
- // Convert all ids from LE to native format
+ // Load data
for (int i = 0; i < _sampleIndexLen; ++i) {
- _sampleIndex[i] = SWAP_BYTES_32(_sampleIndex[i]);
+ _sampleIndex[i] = f.readUint32LE();
+ if (f.err()) {
+ showSoundError(FILE_READ_ERROR, _vm->getSampleIndex(g_sampleLanguage));
+ }
}
-#endif
+
+ f.close();
// Detect format of soundfile by looking at 1st sample-index
switch (TO_BE_32(_sampleIndex[0])) {
@@ -523,48 +554,31 @@ void SoundManager::openSampleFiles() {
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected MP3 sound-data");
_soundMode = kMP3Mode;
break;
-
case MKTAG('O','G','G',' '):
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected OGG sound-data");
_soundMode = kVorbisMode;
break;
-
case MKTAG('F','L','A','C'):
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected FLAC sound-data");
_soundMode = kFLACMode;
break;
-
default:
debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected original sound-data");
break;
}
- // Normally the 1st sample-index points to nothing at all
+
+ // Normally the 1st sample index points to nothing at all. We use it to
+ // determine if the game's sample files have been compressed, thus restore
+ // it here
_sampleIndex[0] = 0;
} else {
- char buf[50];
- sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleIndex(g_sampleLanguage));
- GUI::MessageDialog dialog(buf, "OK");
- dialog.runModal();
-
- error(CANNOT_FIND_FILE, _vm->getSampleIndex(g_sampleLanguage));
+ showSoundError(FILE_READ_ERROR, _vm->getSampleIndex(g_sampleLanguage));
}
- // open sample file in binary mode
+ // Open sample file (*.smp) in binary mode
if (!_sampleStream.open(_vm->getSampleFile(g_sampleLanguage))) {
- char buf[50];
- sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleFile(g_sampleLanguage));
- GUI::MessageDialog dialog(buf, "OK");
- dialog.runModal();
-
- error(CANNOT_FIND_FILE, _vm->getSampleFile(g_sampleLanguage));
+ showSoundError(FILE_READ_ERROR, _vm->getSampleFile(g_sampleLanguage));
}
-
-/*
- // gen length of the largest sample
- sampleBuffer.size = _sampleStream.readUint32LE();
- if (_sampleStream.eos() || _sampleStream.err())
- error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage));
-*/
}
void SoundManager::closeSampleStream() {
diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h
index d7083b3b21..8510c1618f 100644
--- a/engines/tinsel/sound.h
+++ b/engines/tinsel/sound.h
@@ -51,7 +51,8 @@ protected:
enum {
kChannelTalk = 0,
kChannelTinsel1 = 0, // Always using this channel for DW1
- kChannelSFX = 1
+ kChannelSFX = 1,
+ kChannelDW1MacMusic = 2
};
static const int kNumChannels = kChannelSFX + kNumSFX;
@@ -108,6 +109,7 @@ public:
bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0);
bool playSample(int id, int sub, bool bLooped, int x, int y, int priority,
Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0);
+ bool playDW1MacMusic(int dwFileOffset);
void stopAllSamples(); // Stops any currently playing sample
void stopSpecSample(int id, int sub = 0); // Stops a specific sample
@@ -115,11 +117,13 @@ public:
void setSFXVolumes(uint8 volume);
bool sampleExists(int id);
- bool sampleIsPlaying(int id = -1);
+ bool sampleIsPlaying();
- // TODO: Internal method, make this protected?
void openSampleFiles();
void closeSampleStream();
+
+private:
+ void showSoundError(const char *errorMsg, const char *soundFile);
};
} // End of namespace Tinsel
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
index 058f8eb6fd..6a396b9b01 100644
--- a/engines/tinsel/tinlib.cpp
+++ b/engines/tinsel/tinlib.cpp
@@ -3684,7 +3684,7 @@ static void TranslucentIndex(unsigned index) {
}
/**
- * Play a sample.
+ * Play a sample (DW1 only).
*/
static void TryPlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) {
CORO_BEGIN_CONTEXT;
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 944613bcb7..e836fdff1f 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -730,8 +730,7 @@ void LoadBasicChunks() {
cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS);
-#ifdef SCUMM_BIG_ENDIAN
- //convert to native endianness
+ // Convert to native endianness
INV_OBJECT *io = (INV_OBJECT *)cptr;
for (int i = 0; i < numObjects; i++, io++) {
io->id = FROM_32(io->id);
@@ -739,7 +738,6 @@ void LoadBasicChunks() {
io->hScript = FROM_32(io->hScript);
io->attribute = FROM_32(io->attribute);
}
-#endif
RegisterIcons(cptr, numObjects);
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
index 32e21842a1..ec504b69cd 100644
--- a/engines/tinsel/tinsel.h
+++ b/engines/tinsel/tinsel.h
@@ -78,7 +78,7 @@ enum TinselGameFeatures {
/**
* The following is the ScummVM definitions of the various Tinsel versions:
* TINSEL_V0 - This was an early engine version that was only used in the Discworld 1
- * demo. It is not currently supported.
+ * demo.
* TINSEL_V1 - This was the engine version used by Discworld 1. Note that there were two
* major releases: an earlier version that used *.gra files, and a later one that
* used *.scn files, and contained certain script and engine bugfixes. In ScummVM,
diff --git a/engines/touche/menu.cpp b/engines/touche/menu.cpp
index 57882fa6aa..85ca519f05 100644
--- a/engines/touche/menu.cpp
+++ b/engines/touche/menu.cpp
@@ -103,7 +103,7 @@ struct MenuData {
void addCharToDescription(int slot, char chr) {
char *description = saveLoadDescriptionsTable[slot];
int descriptionLen = strlen(description);
- if (descriptionLen < 32 && isprint(static_cast<unsigned char>(chr))) {
+ if (descriptionLen < 32 && Common::isPrint(chr)) {
description[descriptionLen] = chr;
description[descriptionLen + 1] = 0;
}
diff --git a/engines/wintermute/base/base_keyboard_state.cpp b/engines/wintermute/base/base_keyboard_state.cpp
index e12583b5a4..072a1bb71b 100644
--- a/engines/wintermute/base/base_keyboard_state.cpp
+++ b/engines/wintermute/base/base_keyboard_state.cpp
@@ -200,9 +200,8 @@ const char *BaseKeyboardState::scToString() {
bool BaseKeyboardState::readKey(Common::Event *event) {
//_currentPrintable = (event->type == SDL_TEXTINPUT); // TODO
_currentCharCode = keyCodeToVKey(event);
- if ((_currentCharCode <= Common::KEYCODE_z && _currentCharCode >= Common::KEYCODE_a) ||
- (_currentCharCode <= Common::KEYCODE_9 && _currentCharCode >= Common::KEYCODE_0) ||
- (_currentCharCode == Common::KEYCODE_SPACE)) {
+ // Verify that this is a printable ISO-8859-character (including the upper charset)
+ if ((_currentCharCode <= 0x7E && _currentCharCode >= 0x20) || (_currentCharCode <= 0xFF && _currentCharCode >= 0xA0)) {
_currentPrintable = true;
} else {
_currentPrintable = false;
@@ -272,7 +271,7 @@ uint32 BaseKeyboardState::keyCodeToVKey(Common::Event *event) {
case Common::KEYCODE_KP_ENTER:
return Common::KEYCODE_RETURN;
default:
- return (uint32)event->kbd.keycode;
+ return (uint32)event->kbd.ascii;
}
}
diff --git a/engines/wintermute/base/base_persistence_manager.cpp b/engines/wintermute/base/base_persistence_manager.cpp
index 4cb67b87e1..bd53ed38e4 100644
--- a/engines/wintermute/base/base_persistence_manager.cpp
+++ b/engines/wintermute/base/base_persistence_manager.cpp
@@ -36,8 +36,10 @@
#include "engines/wintermute/math/vector2.h"
#include "engines/wintermute/base/gfx/base_image.h"
#include "engines/wintermute/base/sound/base_sound.h"
+#include "engines/wintermute/graphics/transparent_surface.h"
#include "engines/wintermute/wintermute.h"
#include "graphics/decoders/bmp.h"
+#include "graphics/scaler.h"
#include "common/memstream.h"
#include "common/str.h"
#include "common/system.h"
@@ -148,9 +150,13 @@ void BasePersistenceManager::getSaveStateDesc(int slot, SaveStateDescriptor &des
Common::MemoryReadStream thumbStream(_thumbnailData, _thumbnailDataSize);
Graphics::BitmapDecoder bmpDecoder;
if (bmpDecoder.loadStream(thumbStream)) {
- Graphics::Surface *surf = new Graphics::Surface;
+ Graphics::Surface *surf = NULL;
surf = bmpDecoder.getSurface()->convertTo(g_system->getOverlayFormat());
- desc.setThumbnail(surf);
+ TransparentSurface *scaleableSurface = new TransparentSurface(*surf, false);
+ Graphics::Surface *scaled = scaleableSurface->scale(kThumbnailWidth, kThumbnailHeight2);
+ desc.setThumbnail(scaled);
+ delete scaleableSurface;
+ delete surf;
}
}
diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp
index 3255156599..5097d2078f 100644
--- a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp
+++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp
@@ -28,6 +28,7 @@
#include "engines/wintermute/base/gfx/osystem/base_render_osystem.h"
#include "engines/wintermute/base/gfx/osystem/base_surface_osystem.h"
+#include "engines/wintermute/base/gfx/osystem/render_ticket.h"
#include "engines/wintermute/base/base_surface_storage.h"
#include "engines/wintermute/base/gfx/base_image.h"
#include "engines/wintermute/math/math_util.h"
@@ -40,56 +41,6 @@
namespace Wintermute {
-RenderTicket::RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) : _owner(owner),
- _srcRect(*srcRect), _dstRect(*dstRect), _drawNum(0), _isValid(true), _wantsDraw(true), _hasAlpha(!disableAlpha) {
- _colorMod = 0;
- _mirror = TransparentSurface::FLIP_NONE;
- if (mirrorX) {
- _mirror |= TransparentSurface::FLIP_V;
- }
- if (mirrorY) {
- _mirror |= TransparentSurface::FLIP_H;
- }
- if (surf) {
- _surface = new Graphics::Surface();
- _surface->create((uint16)srcRect->width(), (uint16)srcRect->height(), surf->format);
- assert(_surface->format.bytesPerPixel == 4);
- // Get a clipped copy of the surface
- for (int i = 0; i < _surface->h; i++) {
- memcpy(_surface->getBasePtr(0, i), surf->getBasePtr(srcRect->left, srcRect->top + i), srcRect->width() * _surface->format.bytesPerPixel);
- }
- // Then scale it if necessary
- if (dstRect->width() != srcRect->width() || dstRect->height() != srcRect->height()) {
- TransparentSurface src(*_surface, false);
- Graphics::Surface *temp = src.scale(dstRect->width(), dstRect->height());
- _surface->free();
- delete _surface;
- _surface = temp;
- }
- } else {
- _surface = NULL;
- }
-}
-
-RenderTicket::~RenderTicket() {
- if (_surface) {
- _surface->free();
- delete _surface;
- }
-}
-
-bool RenderTicket::operator==(RenderTicket &t) {
- if ((t._srcRect != _srcRect) ||
- (t._dstRect != _dstRect) ||
- (t._mirror != _mirror) ||
- (t._owner != _owner) ||
- (t._hasAlpha != _hasAlpha) ||
- (t._colorMod != _colorMod)) {
- return false;
- }
- return true;
-}
-
BaseRenderer *makeOSystemRenderer(BaseGame *inGame) {
return new BaseRenderOSystem(inGame);
}
@@ -100,6 +51,8 @@ BaseRenderOSystem::BaseRenderOSystem(BaseGame *inGame) : BaseRenderer(inGame) {
_blankSurface = new Graphics::Surface();
_drawNum = 1;
_needsFlip = true;
+ _spriteBatch = false;
+ _batchNum = 0;
_borderLeft = _borderRight = _borderTop = _borderBottom = 0;
_ratioX = _ratioY = 1.0f;
@@ -310,15 +263,23 @@ void BaseRenderOSystem::drawSurface(BaseSurfaceOSystem *owner, const Graphics::S
if (owner) { // Fade-tickets are owner-less
RenderTicket compare(owner, NULL, srcRect, dstRect, mirrorX, mirrorY, disableAlpha);
+ compare._batchNum = _batchNum;
+ if (_spriteBatch)
+ _batchNum++;
compare._colorMod = _colorMod;
RenderQueueIterator it;
- for (it = _renderQueue.begin(); it != _renderQueue.end(); ++it) {
- if ((*it)->_owner == owner && *(*it) == compare && (*it)->_isValid) {
- (*it)->_colorMod = _colorMod;
+ // Avoid calling end() and operator* every time, when potentially going through
+ // LOTS of tickets.
+ RenderQueueIterator endIterator = _renderQueue.end();
+ RenderTicket *compareTicket = NULL;
+ for (it = _renderQueue.begin(); it != endIterator; ++it) {
+ compareTicket = *it;
+ if (*(compareTicket) == compare && compareTicket->_isValid) {
+ compareTicket->_colorMod = _colorMod;
if (_disableDirtyRects) {
- drawFromSurface(*it, NULL);
+ drawFromSurface(compareTicket, NULL);
} else {
- drawFromTicket(*it);
+ drawFromTicket(compareTicket);
}
return;
}
@@ -465,7 +426,7 @@ void BaseRenderOSystem::drawTickets() {
dstClip.translate(-offsetX, -offsetY);
_colorMod = ticket->_colorMod;
- drawFromSurface(ticket->getSurface(), &ticket->_srcRect, &pos, &dstClip, ticket->_mirror);
+ drawFromSurface(ticket, &ticket->_srcRect, &pos, &dstClip);
_needsFlip = true;
}
// Some tickets want redraw but don't actually clip the dirty area (typically the ones that shouldnt become clear-color)
@@ -511,20 +472,8 @@ void BaseRenderOSystem::drawFromSurface(RenderTicket *ticket, Common::Rect *clip
delete clipRect;
}
}
-void BaseRenderOSystem::drawFromSurface(const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect, uint32 mirror) {
- TransparentSurface src(*surf, false);
- bool doDelete = false;
- if (!clipRect) {
- doDelete = true;
- clipRect = new Common::Rect();
- clipRect->setWidth(surf->w);
- clipRect->setHeight(surf->h);
- }
-
- src.blit(*_renderSurface, dstRect->left, dstRect->top, mirror, clipRect, _colorMod, clipRect->width(), clipRect->height());
- if (doDelete) {
- delete clipRect;
- }
+void BaseRenderOSystem::drawFromSurface(RenderTicket *ticket, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect) {
+ ticket->drawToSurface(_renderSurface, srcRect, dstRect, clipRect);
}
//////////////////////////////////////////////////////////////////////////
@@ -642,4 +591,16 @@ void BaseRenderOSystem::endSaveLoad() {
_drawNum = 1;
}
+bool BaseRenderOSystem::startSpriteBatch() {
+ _spriteBatch = true;
+ _batchNum = 1;
+ return STATUS_OK;
+}
+
+bool BaseRenderOSystem::endSpriteBatch() {
+ _spriteBatch = false;
+ _batchNum = 0;
+ return STATUS_OK;
+}
+
} // end of namespace Wintermute
diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.h b/engines/wintermute/base/gfx/osystem/base_render_osystem.h
index 1e9b4ed2e2..e7f14470f4 100644
--- a/engines/wintermute/base/gfx/osystem/base_render_osystem.h
+++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.h
@@ -36,27 +36,7 @@
namespace Wintermute {
class BaseSurfaceOSystem;
-class RenderTicket {
- Graphics::Surface *_surface;
-public:
- RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRest, bool mirrorX = false, bool mirrorY = false, bool disableAlpha = false);
- RenderTicket() : _isValid(true), _wantsDraw(false), _drawNum(0) {}
- ~RenderTicket();
- const Graphics::Surface *getSurface() { return _surface; }
- Common::Rect _srcRect;
- Common::Rect _dstRect;
- uint32 _mirror;
- bool _hasAlpha;
-
- bool _isValid;
- bool _wantsDraw;
- uint32 _drawNum;
- uint32 _colorMod;
-
- BaseSurfaceOSystem *_owner;
- bool operator==(RenderTicket &a);
-};
-
+class RenderTicket;
class BaseRenderOSystem : public BaseRenderer {
public:
BaseRenderOSystem(BaseGame *inGame);
@@ -97,14 +77,18 @@ public:
float getScaleRatioY() const {
return _ratioY;
}
+ virtual bool startSpriteBatch();
+ virtual bool endSpriteBatch();
void endSaveLoad();
void drawSurface(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha = false);
BaseSurface *createSurface();
private:
void addDirtyRect(const Common::Rect &rect);
void drawTickets();
+ // Non-dirty-rects:
void drawFromSurface(RenderTicket *ticket, Common::Rect *clipRect);
- void drawFromSurface(const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect, uint32 mirror);
+ // Dirty-rects:
+ void drawFromSurface(RenderTicket *ticket, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect);
typedef Common::List<RenderTicket *>::iterator RenderQueueIterator;
Common::Rect *_dirtyRect;
Common::List<RenderTicket *> _renderQueue;
@@ -120,6 +104,8 @@ private:
int _borderBottom;
bool _disableDirtyRects;
+ bool _spriteBatch;
+ uint32 _batchNum;
float _ratioX;
float _ratioY;
uint32 _colorMod;
diff --git a/engines/wintermute/base/gfx/osystem/render_ticket.cpp b/engines/wintermute/base/gfx/osystem/render_ticket.cpp
new file mode 100644
index 0000000000..8b513f8543
--- /dev/null
+++ b/engines/wintermute/base/gfx/osystem/render_ticket.cpp
@@ -0,0 +1,103 @@
+/* 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.
+ *
+ */
+
+/*
+ * This file is based on WME Lite.
+ * http://dead-code.org/redir.php?target=wmelite
+ * Copyright (c) 2011 Jan Nedoma
+ */
+
+#include "engines/wintermute/graphics/transparent_surface.h"
+#include "engines/wintermute/base/gfx/osystem/render_ticket.h"
+
+namespace Wintermute {
+
+RenderTicket::RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) : _owner(owner),
+_srcRect(*srcRect), _dstRect(*dstRect), _drawNum(0), _isValid(true), _wantsDraw(true), _hasAlpha(!disableAlpha) {
+ _colorMod = 0;
+ _batchNum = 0;
+ _mirror = TransparentSurface::FLIP_NONE;
+ if (mirrorX) {
+ _mirror |= TransparentSurface::FLIP_V;
+ }
+ if (mirrorY) {
+ _mirror |= TransparentSurface::FLIP_H;
+ }
+ if (surf) {
+ _surface = new Graphics::Surface();
+ _surface->create((uint16)srcRect->width(), (uint16)srcRect->height(), surf->format);
+ assert(_surface->format.bytesPerPixel == 4);
+ // Get a clipped copy of the surface
+ for (int i = 0; i < _surface->h; i++) {
+ memcpy(_surface->getBasePtr(0, i), surf->getBasePtr(srcRect->left, srcRect->top + i), srcRect->width() * _surface->format.bytesPerPixel);
+ }
+ // Then scale it if necessary
+ if (dstRect->width() != srcRect->width() || dstRect->height() != srcRect->height()) {
+ TransparentSurface src(*_surface, false);
+ Graphics::Surface *temp = src.scale(dstRect->width(), dstRect->height());
+ _surface->free();
+ delete _surface;
+ _surface = temp;
+ }
+ } else {
+ _surface = NULL;
+ }
+}
+
+RenderTicket::~RenderTicket() {
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ }
+}
+
+bool RenderTicket::operator==(RenderTicket &t) {
+ if ((t._owner != _owner) ||
+ (t._batchNum != t._batchNum) ||
+ (t._hasAlpha != _hasAlpha) ||
+ (t._mirror != _mirror) ||
+ (t._colorMod != _colorMod) ||
+ (t._dstRect != _dstRect) ||
+ (t._srcRect != _srcRect)) {
+ return false;
+ }
+ return true;
+}
+
+void RenderTicket::drawToSurface(Graphics::Surface *_targetSurface, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect) {
+ TransparentSurface src(*getSurface(), false);
+ bool doDelete = false;
+ if (!clipRect) {
+ doDelete = true;
+ clipRect = new Common::Rect();
+ clipRect->setWidth(getSurface()->w);
+ clipRect->setHeight(getSurface()->h);
+ }
+
+ src._enableAlphaBlit = _hasAlpha;
+ src.blit(*_targetSurface, dstRect->left, dstRect->top, _mirror, clipRect, _colorMod, clipRect->width(), clipRect->height());
+ if (doDelete) {
+ delete clipRect;
+ }
+}
+
+} // end of namespace Wintermute
diff --git a/engines/wintermute/base/gfx/osystem/render_ticket.h b/engines/wintermute/base/gfx/osystem/render_ticket.h
new file mode 100644
index 0000000000..242822c868
--- /dev/null
+++ b/engines/wintermute/base/gfx/osystem/render_ticket.h
@@ -0,0 +1,63 @@
+/* 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.
+ *
+ */
+
+/*
+ * This file is based on WME Lite.
+ * http://dead-code.org/redir.php?target=wmelite
+ * Copyright (c) 2011 Jan Nedoma
+ */
+
+#ifndef WINTERMUTE_RENDER_TICKET_H
+#define WINTERMUTE_RENDER_TICKET_H
+
+#include "graphics/surface.h"
+#include "common/rect.h"
+
+namespace Wintermute {
+
+class BaseSurfaceOSystem;
+class RenderTicket {
+ Graphics::Surface *_surface;
+public:
+ RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRest, bool mirrorX = false, bool mirrorY = false, bool disableAlpha = false);
+ RenderTicket() : _isValid(true), _wantsDraw(false), _drawNum(0) {}
+ ~RenderTicket();
+ const Graphics::Surface *getSurface() { return _surface; }
+ void drawToSurface(Graphics::Surface *_targetSurface, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect);
+ Common::Rect _srcRect;
+ Common::Rect _dstRect;
+ uint32 _mirror;
+ uint32 _batchNum;
+ bool _hasAlpha;
+
+ bool _isValid;
+ bool _wantsDraw;
+ uint32 _drawNum;
+ uint32 _colorMod;
+
+ BaseSurfaceOSystem *_owner;
+ bool operator==(RenderTicket &a);
+};
+
+} // end of namespace Wintermute
+
+#endif
diff --git a/engines/wintermute/module.mk b/engines/wintermute/module.mk
index 7c9cdfd0b2..2bd71f1b6b 100644
--- a/engines/wintermute/module.mk
+++ b/engines/wintermute/module.mk
@@ -53,6 +53,7 @@ MODULE_OBJS := \
base/gfx/base_surface.o \
base/gfx/osystem/base_surface_osystem.o \
base/gfx/osystem/base_render_osystem.o \
+ base/gfx/osystem/render_ticket.o \
base/particles/part_particle.o \
base/particles/part_emitter.o \
base/particles/part_force.o \
diff --git a/engines/wintermute/ui/ui_tiled_image.h b/engines/wintermute/ui/ui_tiled_image.h
index c413e7f129..edea84f346 100644
--- a/engines/wintermute/ui/ui_tiled_image.h
+++ b/engines/wintermute/ui/ui_tiled_image.h
@@ -46,6 +46,7 @@ public:
bool display(int x, int y, int width, int height);
UITiledImage(BaseGame *inGame = NULL);
virtual ~UITiledImage();
+private:
BaseSubFrame *_image;
Rect32 _upLeft;
Rect32 _upMiddle;
diff --git a/gui/credits.h b/gui/credits.h
index 8a90f88429..f63f54a6fb 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -475,6 +475,9 @@ static const char *credits[] = {
"C1""Basque",
"C0""Mikel Iturbe Urretxa",
"",
+"C1""Belarusian",
+"C0""Ivan Lukyanov",
+"",
"C1""Catalan",
"C0""Jordi Vilalta Prat",
"",
diff --git a/gui/themes/translations.dat b/gui/themes/translations.dat
index 23cf2b8fe4..704e9caca0 100644
--- a/gui/themes/translations.dat
+++ b/gui/themes/translations.dat
Binary files differ
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 13784ddf7f..95d39c4f24 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -284,7 +284,7 @@ bool ListWidget::handleKeyDown(Common::KeyState state) {
bool dirty = false;
int oldSelectedItem = _selectedItem;
- if (!_editMode && state.keycode <= Common::KEYCODE_z && isprint((unsigned char)state.ascii)) {
+ if (!_editMode && state.keycode <= Common::KEYCODE_z && Common::isPrint(state.ascii)) {
// Quick selection mode: Go to first list item starting with this key
// (or a substring accumulated from the last couple key presses).
// Only works in a useful fashion if the list entries are sorted.
diff --git a/po/be_BY.po b/po/be_BY.po
new file mode 100644
index 0000000000..9539588ba6
--- /dev/null
+++ b/po/be_BY.po
@@ -0,0 +1,3140 @@
+# Belarusian translation for ScummVM.
+# Copyright (C) 2010-2012 ScummVM Team
+# This file is distributed under the same license as the ScummVM package.
+# Ivan Lukyanov <greencis@mail.ru>, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: ScummVM 1.6.0git\n"
+"Report-Msgid-Bugs-To: scummvm-devel@lists.sf.net\n"
+"POT-Creation-Date: 2012-08-12 14:57+0200\n"
+"PO-Revision-Date: 2012-12-12 22:02+0300\n"
+"Last-Translator: Ivan Lukyanov <greencis@mail.ru>\n"
+"Language-Team: Ivan Lukyanov <greencis@mail.ru>\n"
+"Language: Belarusian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-5\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n"
+"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Poedit 1.5.4\n"
+
+#: gui/about.cpp:91
+#, c-format
+msgid "(built on %s)"
+msgstr "(сабраны %s)"
+
+#: gui/about.cpp:98
+msgid "Features compiled in:"
+msgstr "Уключаныя ў білд опцыі:"
+
+#: gui/about.cpp:107
+msgid "Available engines:"
+msgstr "Даступныя рухавічкі:"
+
+#: gui/browser.cpp:66
+msgid "Go up"
+msgstr "Уверх"
+
+#: gui/browser.cpp:66 gui/browser.cpp:68
+msgid "Go to previous directory level"
+msgstr "Перайсці на дырэкторыю ўзроўнем вышэй"
+
+#: gui/browser.cpp:68
+msgctxt "lowres"
+msgid "Go up"
+msgstr "Уверх"
+
+#: gui/browser.cpp:69 gui/chooser.cpp:45 gui/KeysDialog.cpp:43
+#: gui/launcher.cpp:345 gui/massadd.cpp:94 gui/options.cpp:1228
+#: gui/saveload-dialog.cpp:207 gui/saveload-dialog.cpp:267
+#: gui/saveload-dialog.cpp:516 gui/saveload-dialog.cpp:843
+#: gui/themebrowser.cpp:54 engines/engine.cpp:442
+#: engines/scumm/dialogs.cpp:190 engines/sword1/control.cpp:865
+#: engines/parallaction/saveload.cpp:274 backends/platform/wii/options.cpp:48
+#: backends/events/default/default-events.cpp:191
+#: backends/events/default/default-events.cpp:213
+msgid "Cancel"
+msgstr "Адмена"
+
+#: gui/browser.cpp:70 gui/chooser.cpp:46 gui/themebrowser.cpp:55
+msgid "Choose"
+msgstr "Абраць"
+
+#: gui/gui-manager.cpp:115 engines/scumm/help.cpp:125
+#: engines/scumm/help.cpp:140 engines/scumm/help.cpp:165
+#: engines/scumm/help.cpp:191 engines/scumm/help.cpp:209
+#: backends/keymapper/remap-dialog.cpp:52
+msgid "Close"
+msgstr "Закрыць"
+
+#: gui/gui-manager.cpp:118
+msgid "Mouse click"
+msgstr "Клік мышшу"
+
+#: gui/gui-manager.cpp:122 base/main.cpp:300
+msgid "Display keyboard"
+msgstr "Паказаць клавіятуру"
+
+#: gui/gui-manager.cpp:126 base/main.cpp:304
+msgid "Remap keys"
+msgstr "Перапрызначыць клавішы"
+
+#: gui/gui-manager.cpp:129 base/main.cpp:307
+msgid "Toggle FullScreen"
+msgstr "Пераключэнне на ўвесь экран"
+
+#: gui/KeysDialog.h:36 gui/KeysDialog.cpp:145
+msgid "Choose an action to map"
+msgstr "Абярыце дзеянне для прызначэння"
+
+#: gui/KeysDialog.cpp:41
+msgid "Map"
+msgstr "Прызначыць"
+
+#: gui/KeysDialog.cpp:42 gui/launcher.cpp:346 gui/launcher.cpp:1001
+#: gui/launcher.cpp:1005 gui/massadd.cpp:91 gui/options.cpp:1229
+#: gui/saveload-dialog.cpp:844 engines/engine.cpp:361 engines/engine.cpp:372
+#: engines/scumm/dialogs.cpp:192 engines/scumm/scumm.cpp:1775
+#: engines/agos/animation.cpp:561 engines/groovie/script.cpp:420
+#: engines/sky/compact.cpp:131 engines/sky/compact.cpp:141
+#: engines/sword1/animation.cpp:539 engines/sword1/animation.cpp:560
+#: engines/sword1/animation.cpp:570 engines/sword1/animation.cpp:577
+#: engines/sword1/control.cpp:865 engines/sword1/logic.cpp:1633
+#: engines/sword2/animation.cpp:435 engines/sword2/animation.cpp:455
+#: engines/sword2/animation.cpp:465 engines/sword2/animation.cpp:474
+#: engines/parallaction/saveload.cpp:274 backends/platform/wii/options.cpp:47
+#: backends/platform/wince/CELauncherDialog.cpp:54
+msgid "OK"
+msgstr "OK"
+
+#: gui/KeysDialog.cpp:49
+msgid "Select an action and click 'Map'"
+msgstr "Абярыце дзеянне і клікніце 'Прызначыць'"
+
+#: gui/KeysDialog.cpp:80 gui/KeysDialog.cpp:102 gui/KeysDialog.cpp:141
+#, c-format
+msgid "Associated key : %s"
+msgstr "Прызначаная клавіша : %s"
+
+#: gui/KeysDialog.cpp:82 gui/KeysDialog.cpp:104 gui/KeysDialog.cpp:143
+#, c-format
+msgid "Associated key : none"
+msgstr "Прызначаная клавіша : няма"
+
+#: gui/KeysDialog.cpp:90
+msgid "Please select an action"
+msgstr "Калі ласка, абярыце дзеянне"
+
+#: gui/KeysDialog.cpp:106
+msgid "Press the key to associate"
+msgstr "Націсніце клавішу для прызначэння"
+
+#: gui/launcher.cpp:187
+msgid "Game"
+msgstr "Гульня"
+
+#: gui/launcher.cpp:191
+msgid "ID:"
+msgstr "ID:"
+
+#: gui/launcher.cpp:191 gui/launcher.cpp:193 gui/launcher.cpp:194
+msgid ""
+"Short game identifier used for referring to savegames and running the game "
+"from the command line"
+msgstr ""
+"Кароткі ідэнтыфікатар, выкарыстоўваны для імёнаў захаванняў гульняў і для "
+"запуску з каманднага радка"
+
+#: gui/launcher.cpp:193
+msgctxt "lowres"
+msgid "ID:"
+msgstr "ID:"
+
+#: gui/launcher.cpp:198
+msgid "Name:"
+msgstr "Назва:"
+
+#: gui/launcher.cpp:198 gui/launcher.cpp:200 gui/launcher.cpp:201
+msgid "Full title of the game"
+msgstr "Поўная назва гульні"
+
+#: gui/launcher.cpp:200
+msgctxt "lowres"
+msgid "Name:"
+msgstr "Назв:"
+
+#: gui/launcher.cpp:204
+msgid "Language:"
+msgstr "Мова:"
+
+#: gui/launcher.cpp:204 gui/launcher.cpp:205
+msgid ""
+"Language of the game. This will not turn your Spanish game version into "
+"English"
+msgstr ""
+"Мова гульні. Змена гэтай налады не ператворыць ангельскую гульню ў рускую"
+
+#: gui/launcher.cpp:206 gui/launcher.cpp:220 gui/options.cpp:80
+#: gui/options.cpp:730 gui/options.cpp:743 gui/options.cpp:1199
+#: audio/null.cpp:40
+msgid "<default>"
+msgstr "<па змаўчанні>"
+
+#: gui/launcher.cpp:216
+msgid "Platform:"
+msgstr "Платформа:"
+
+#: gui/launcher.cpp:216 gui/launcher.cpp:218 gui/launcher.cpp:219
+msgid "Platform the game was originally designed for"
+msgstr "Платформа, для якой гульня была першапачаткова распрацавана"
+
+#: gui/launcher.cpp:218
+msgctxt "lowres"
+msgid "Platform:"
+msgstr "Платформа:"
+
+#: gui/launcher.cpp:231
+msgid "Engine"
+msgstr "Рухавічок"
+
+#: gui/launcher.cpp:239 gui/options.cpp:1062 gui/options.cpp:1079
+msgid "Graphics"
+msgstr "Графіка"
+
+#: gui/launcher.cpp:239 gui/options.cpp:1062 gui/options.cpp:1079
+msgid "GFX"
+msgstr "Грф"
+
+#: gui/launcher.cpp:242
+msgid "Override global graphic settings"
+msgstr "Перакрыць глабальныя налады графікі"
+
+#: gui/launcher.cpp:244
+msgctxt "lowres"
+msgid "Override global graphic settings"
+msgstr "Перакрыць глабальныя налады графікі"
+
+#: gui/launcher.cpp:251 gui/options.cpp:1085
+msgid "Audio"
+msgstr "Аўдыё"
+
+#: gui/launcher.cpp:254
+msgid "Override global audio settings"
+msgstr "Перакрыць глабальныя налады аўдыё"
+
+#: gui/launcher.cpp:256
+msgctxt "lowres"
+msgid "Override global audio settings"
+msgstr "Перакрыць глабальныя налады аўдыё"
+
+#: gui/launcher.cpp:265 gui/options.cpp:1090
+msgid "Volume"
+msgstr "Гучнасць"
+
+#: gui/launcher.cpp:267 gui/options.cpp:1092
+msgctxt "lowres"
+msgid "Volume"
+msgstr "Гучн"
+
+#: gui/launcher.cpp:270
+msgid "Override global volume settings"
+msgstr "Перакрыць глабальныя налады гучнасці"
+
+#: gui/launcher.cpp:272
+msgctxt "lowres"
+msgid "Override global volume settings"
+msgstr "Перакрыць глабальныя налады гучнасці"
+
+#: gui/launcher.cpp:280 gui/options.cpp:1100
+msgid "MIDI"
+msgstr "MIDI"
+
+#: gui/launcher.cpp:283
+msgid "Override global MIDI settings"
+msgstr "Перакрыць глабальныя налады MIDI"
+
+#: gui/launcher.cpp:285
+msgctxt "lowres"
+msgid "Override global MIDI settings"
+msgstr "Перакрыць глабальныя налады MIDI"
+
+#: gui/launcher.cpp:294 gui/options.cpp:1106
+msgid "MT-32"
+msgstr "MT-32"
+
+#: gui/launcher.cpp:297
+msgid "Override global MT-32 settings"
+msgstr "Перакрыць глабальныя налады MT-32"
+
+#: gui/launcher.cpp:299
+msgctxt "lowres"
+msgid "Override global MT-32 settings"
+msgstr "Перакрыць глабальныя налады MT-32"
+
+#: gui/launcher.cpp:308 gui/options.cpp:1113
+msgid "Paths"
+msgstr "Шляхі"
+
+#: gui/launcher.cpp:310 gui/options.cpp:1115
+msgctxt "lowres"
+msgid "Paths"
+msgstr "Шляхі"
+
+#: gui/launcher.cpp:317
+msgid "Game Path:"
+msgstr "Шлях да гульні:"
+
+#: gui/launcher.cpp:319
+msgctxt "lowres"
+msgid "Game Path:"
+msgstr "Дзе гульня:"
+
+#: gui/launcher.cpp:324 gui/options.cpp:1139
+msgid "Extra Path:"
+msgstr "Дад. шлях:"
+
+#: gui/launcher.cpp:324 gui/launcher.cpp:326 gui/launcher.cpp:327
+msgid "Specifies path to additional data used the game"
+msgstr "Паказвае шлях да дадатковых файлаў, дадзеных для гульні"
+
+#: gui/launcher.cpp:326 gui/options.cpp:1141
+msgctxt "lowres"
+msgid "Extra Path:"
+msgstr "Дад. шлях:"
+
+#: gui/launcher.cpp:333 gui/options.cpp:1123
+msgid "Save Path:"
+msgstr "Захаванні гульняў:"
+
+#: gui/launcher.cpp:333 gui/launcher.cpp:335 gui/launcher.cpp:336
+#: gui/options.cpp:1123 gui/options.cpp:1125 gui/options.cpp:1126
+msgid "Specifies where your savegames are put"
+msgstr "Паказвае шлях да захаванняў гульні"
+
+#: gui/launcher.cpp:335 gui/options.cpp:1125
+msgctxt "lowres"
+msgid "Save Path:"
+msgstr "Шлях зах:"
+
+#: gui/launcher.cpp:354 gui/launcher.cpp:453 gui/launcher.cpp:511
+#: gui/launcher.cpp:565 gui/options.cpp:1134 gui/options.cpp:1142
+#: gui/options.cpp:1151 gui/options.cpp:1258 gui/options.cpp:1264
+#: gui/options.cpp:1272 gui/options.cpp:1302 gui/options.cpp:1308
+#: gui/options.cpp:1315 gui/options.cpp:1408 gui/options.cpp:1411
+#: gui/options.cpp:1423
+msgctxt "path"
+msgid "None"
+msgstr "Не зададзены"
+
+#: gui/launcher.cpp:359 gui/launcher.cpp:459 gui/launcher.cpp:569
+#: gui/options.cpp:1252 gui/options.cpp:1296 gui/options.cpp:1414
+#: backends/platform/wii/options.cpp:56
+msgid "Default"
+msgstr "Па змаўчанні"
+
+#: gui/launcher.cpp:504 gui/options.cpp:1417
+msgid "Select SoundFont"
+msgstr "Абярыце SoundFont"
+
+#: gui/launcher.cpp:523 gui/launcher.cpp:677
+msgid "Select directory with game data"
+msgstr "Абярыце дырэкторыю з файламі гульні"
+
+#: gui/launcher.cpp:541
+msgid "Select additional game directory"
+msgstr "Абярыце дадатковую дырэкторыю гульні"
+
+#: gui/launcher.cpp:553
+msgid "Select directory for saved games"
+msgstr "Абярыце дырэкторыю для захаванняў"
+
+#: gui/launcher.cpp:580
+msgid "This game ID is already taken. Please choose another one."
+msgstr "Гэты ID гульні ўжо выкарыстоўваецца. Калі ласка, абярыце іншы."
+
+#: gui/launcher.cpp:621 engines/dialogs.cpp:110
+msgid "~Q~uit"
+msgstr "~В~ыхад"
+
+#: gui/launcher.cpp:621 backends/platform/sdl/macosx/appmenu_osx.mm:96
+msgid "Quit ScummVM"
+msgstr "Завяршыць ScummVM"
+
+#: gui/launcher.cpp:622
+msgid "A~b~out..."
+msgstr "Пра п~р~аграму..."
+
+#: gui/launcher.cpp:622 backends/platform/sdl/macosx/appmenu_osx.mm:70
+msgid "About ScummVM"
+msgstr "Пра праграму ScummVM"
+
+#: gui/launcher.cpp:623
+msgid "~O~ptions..."
+msgstr "~Н~алады..."
+
+#: gui/launcher.cpp:623
+msgid "Change global ScummVM options"
+msgstr "Змяніць глабальныя налады ScummVM"
+
+#: gui/launcher.cpp:625
+msgid "~S~tart"
+msgstr "П~у~ск"
+
+#: gui/launcher.cpp:625
+msgid "Start selected game"
+msgstr "Запусціць абраную гульню"
+
+#: gui/launcher.cpp:628
+msgid "~L~oad..."
+msgstr "~З~агрузіць..."
+
+#: gui/launcher.cpp:628
+msgid "Load savegame for selected game"
+msgstr "Загрузіць захаванне для абранай гульні"
+
+#: gui/launcher.cpp:633 gui/launcher.cpp:1120
+msgid "~A~dd Game..."
+msgstr "~Д~адаць гульню..."
+
+#: gui/launcher.cpp:633 gui/launcher.cpp:640
+msgid "Hold Shift for Mass Add"
+msgstr "Утрымлівайце клавішу Shift для таго, каб дадаць некалькі гульняў"
+
+#: gui/launcher.cpp:635
+msgid "~E~dit Game..."
+msgstr "Н~а~лады гульні..."
+
+#: gui/launcher.cpp:635 gui/launcher.cpp:642
+msgid "Change game options"
+msgstr "Змяніць налады гульні"
+
+#: gui/launcher.cpp:637
+msgid "~R~emove Game"
+msgstr "В~ы~даліць гульню"
+
+#: gui/launcher.cpp:637 gui/launcher.cpp:644
+msgid "Remove game from the list. The game data files stay intact"
+msgstr "Выдаліць гульню са спісу. Не выдаляе гульню з жорсткага дыска"
+
+#: gui/launcher.cpp:640 gui/launcher.cpp:1120
+msgctxt "lowres"
+msgid "~A~dd Game..."
+msgstr "~Д~ад. гульню..."
+
+#: gui/launcher.cpp:642
+msgctxt "lowres"
+msgid "~E~dit Game..."
+msgstr "Н~а~л. гульні..."
+
+#: gui/launcher.cpp:644
+msgctxt "lowres"
+msgid "~R~emove Game"
+msgstr "В~ы~даліць гульню"
+
+#: gui/launcher.cpp:652
+msgid "Search in game list"
+msgstr "Пошук у спісе гульняў"
+
+#: gui/launcher.cpp:656 gui/launcher.cpp:1167
+msgid "Search:"
+msgstr "Пошук:"
+
+#: gui/launcher.cpp:680 engines/dialogs.cpp:114 engines/mohawk/myst.cpp:245
+#: engines/mohawk/riven.cpp:716 engines/cruise/menu.cpp:214
+msgid "Load game:"
+msgstr "Загрузіць гульню:"
+
+#: gui/launcher.cpp:680 engines/dialogs.cpp:114 engines/scumm/dialogs.cpp:188
+#: engines/mohawk/myst.cpp:245 engines/mohawk/riven.cpp:716
+#: engines/cruise/menu.cpp:214 backends/platform/wince/CEActionsPocket.cpp:267
+#: backends/platform/wince/CEActionsSmartphone.cpp:231
+msgid "Load"
+msgstr "Загрузіць"
+
+#: gui/launcher.cpp:788
+msgid ""
+"Do you really want to run the mass game detector? This could potentially add "
+"a huge number of games."
+msgstr ""
+"Вы сапраўды жадаеце запусціць дэтэктар усіх гульняў? Гэта патэнцыяльна можа "
+"дадаць вялікую колькасць гульняў."
+
+#: gui/launcher.cpp:789 gui/launcher.cpp:937
+#: backends/events/symbiansdl/symbiansdl-events.cpp:184
+#: backends/platform/wince/CEActionsPocket.cpp:326
+#: backends/platform/wince/CEActionsSmartphone.cpp:287
+#: backends/platform/wince/CELauncherDialog.cpp:83
+msgid "Yes"
+msgstr "Так"
+
+#: gui/launcher.cpp:789 gui/launcher.cpp:937
+#: backends/events/symbiansdl/symbiansdl-events.cpp:184
+#: backends/platform/wince/CEActionsPocket.cpp:326
+#: backends/platform/wince/CEActionsSmartphone.cpp:287
+#: backends/platform/wince/CELauncherDialog.cpp:83
+msgid "No"
+msgstr "Не"
+
+#: gui/launcher.cpp:837
+msgid "ScummVM couldn't open the specified directory!"
+msgstr "ScummVM не можа адкрыць азначаную дырэкторыю!"
+
+#: gui/launcher.cpp:849
+msgid "ScummVM could not find any game in the specified directory!"
+msgstr "ScummVM не можа знайсці гульню ў азначанай дырэкторыі!"
+
+#: gui/launcher.cpp:863
+msgid "Pick the game:"
+msgstr "Абярыце гульню:"
+
+#: gui/launcher.cpp:937
+msgid "Do you really want to remove this game configuration?"
+msgstr "Вы сапраўды жадаеце выдаліць налады для гэтай гульні?"
+
+#: gui/launcher.cpp:1001
+msgid "This game does not support loading games from the launcher."
+msgstr "Гэтая гульня не падтрымлівае загрузку захаванняў праз галоўнае меню."
+
+#: gui/launcher.cpp:1005
+msgid "ScummVM could not find any engine capable of running the selected game!"
+msgstr "ScummVM не змог знайсці рухавічок для запуску абранай гульні!"
+
+#: gui/launcher.cpp:1119
+msgctxt "lowres"
+msgid "Mass Add..."
+msgstr "Шмат гульняў..."
+
+#: gui/launcher.cpp:1119
+msgid "Mass Add..."
+msgstr "Шмат гульняў..."
+
+#: gui/massadd.cpp:78 gui/massadd.cpp:81
+msgid "... progress ..."
+msgstr "... шукаю ..."
+
+#: gui/massadd.cpp:258
+msgid "Scan complete!"
+msgstr "Пошук скончаны!"
+
+#: gui/massadd.cpp:261
+#, c-format
+msgid "Discovered %d new games, ignored %d previously added games."
+msgstr "Знойдзена %d новых гульняў, прапушчана %d раней дададзеных гульняў."
+
+#: gui/massadd.cpp:265
+#, c-format
+msgid "Scanned %d directories ..."
+msgstr "Прагледжана %d дырэкторый ..."
+
+#: gui/massadd.cpp:268
+#, c-format
+msgid "Discovered %d new games, ignored %d previously added games ..."
+msgstr "Знойдзена %d новых гульняў, прапушчана %d раней дададзеных гульняў ..."
+
+#: gui/options.cpp:78
+msgid "Never"
+msgstr "Ніколі"
+
+#: gui/options.cpp:78
+msgid "every 5 mins"
+msgstr "кожныя 5 хвілін"
+
+#: gui/options.cpp:78
+msgid "every 10 mins"
+msgstr "кожныя 10 хвілін"
+
+#: gui/options.cpp:78
+msgid "every 15 mins"
+msgstr "кожныя 15 хвілін"
+
+#: gui/options.cpp:78
+msgid "every 30 mins"
+msgstr "кожныя 30 хвілін"
+
+#: gui/options.cpp:80
+msgid "8 kHz"
+msgstr "8 кГц"
+
+#: gui/options.cpp:80
+msgid "11kHz"
+msgstr "11 кГц"
+
+#: gui/options.cpp:80
+msgid "22 kHz"
+msgstr "22 кГц"
+
+#: gui/options.cpp:80
+msgid "44 kHz"
+msgstr "44 кГц"
+
+#: gui/options.cpp:80
+msgid "48 kHz"
+msgstr "48 кГц"
+
+#: gui/options.cpp:248 gui/options.cpp:474 gui/options.cpp:575
+#: gui/options.cpp:644 gui/options.cpp:852
+msgctxt "soundfont"
+msgid "None"
+msgstr "Не зададзены"
+
+#: gui/options.cpp:382
+msgid "Failed to apply some of the graphic options changes:"
+msgstr "Не атрымалася ўжыць змены некаторых графічных налад:"
+
+#: gui/options.cpp:394
+msgid "the video mode could not be changed."
+msgstr "відэарэжым не можа быць зменены."
+
+#: gui/options.cpp:400
+msgid "the fullscreen setting could not be changed"
+msgstr "поўнаэкранны рэжым не можа быць зменены"
+
+#: gui/options.cpp:406
+msgid "the aspect ratio setting could not be changed"
+msgstr "рэжым карэктыроўкі суадносін бакоў не можа быць зменены"
+
+#: gui/options.cpp:727
+msgid "Graphics mode:"
+msgstr "Граф. рэжым:"
+
+#: gui/options.cpp:741
+msgid "Render mode:"
+msgstr "Рэжым растру:"
+
+#: gui/options.cpp:741 gui/options.cpp:742
+msgid "Special dithering modes supported by some games"
+msgstr "Спецыяльныя рэжымы рэндэрынгу, падтрымоўваныя некаторымі гульнямі"
+
+#: gui/options.cpp:753
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2236
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:472
+msgid "Fullscreen mode"
+msgstr "Поўнаэкранны рэжым"
+
+#: gui/options.cpp:756
+msgid "Aspect ratio correction"
+msgstr "Карэкцыя суадносін бакоў"
+
+#: gui/options.cpp:756
+msgid "Correct aspect ratio for 320x200 games"
+msgstr "Карэктаваць суадносіны бакоў для гульняў з рэзалюцыяй 320x200"
+
+#: gui/options.cpp:764
+msgid "Preferred Device:"
+msgstr "Упадабанае:"
+
+#: gui/options.cpp:764
+msgid "Music Device:"
+msgstr "Гукавая прылада:"
+
+#: gui/options.cpp:764 gui/options.cpp:766
+msgid "Specifies preferred sound device or sound card emulator"
+msgstr "Зазначае ўпадабаную гукавую прыладу ці эмулятар гукавой карты"
+
+#: gui/options.cpp:764 gui/options.cpp:766 gui/options.cpp:767
+msgid "Specifies output sound device or sound card emulator"
+msgstr "Зазначае выходную гукавую прыладу ці эмулятар гукавой карты"
+
+#: gui/options.cpp:766
+msgctxt "lowres"
+msgid "Preferred Dev.:"
+msgstr "Упадабанае:"
+
+#: gui/options.cpp:766
+msgctxt "lowres"
+msgid "Music Device:"
+msgstr "Гук. прылада:"
+
+#: gui/options.cpp:793
+msgid "AdLib emulator:"
+msgstr "Эмулятар AdLib:"
+
+#: gui/options.cpp:793 gui/options.cpp:794
+msgid "AdLib is used for music in many games"
+msgstr "Гукавая карта AdLib выкарыстоўваецца многімі гульнямі"
+
+#: gui/options.cpp:804
+msgid "Output rate:"
+msgstr "Чашчыня гуку:"
+
+#: gui/options.cpp:804 gui/options.cpp:805
+msgid ""
+"Higher value specifies better sound quality but may be not supported by your "
+"soundcard"
+msgstr ""
+"Большыя значэнні задаюць лепшую якасць гуку, аднак яны могуць не "
+"падтрымлівацца вашай гукавой картай"
+
+#: gui/options.cpp:815
+msgid "GM Device:"
+msgstr "Прылада GM:"
+
+#: gui/options.cpp:815
+msgid "Specifies default sound device for General MIDI output"
+msgstr "Зазначае выходную гукавую прыладу для MIDI"
+
+#: gui/options.cpp:826
+msgid "Don't use General MIDI music"
+msgstr "Не выкарыстоўваць музыку для General MIDI"
+
+#: gui/options.cpp:837 gui/options.cpp:899
+msgid "Use first available device"
+msgstr "Выкарыстоўваць першую даступную прыладу"
+
+#: gui/options.cpp:849
+msgid "SoundFont:"
+msgstr "SoundFont:"
+
+#: gui/options.cpp:849 gui/options.cpp:851 gui/options.cpp:852
+msgid "SoundFont is supported by some audio cards, Fluidsynth and Timidity"
+msgstr ""
+"SoundFont'ы падтрымліваюцца некаторымі гукавымі картамі, Fluidsynth ды "
+"Timidity"
+
+#: gui/options.cpp:851
+msgctxt "lowres"
+msgid "SoundFont:"
+msgstr "SoundFont:"
+
+#: gui/options.cpp:857
+msgid "Mixed AdLib/MIDI mode"
+msgstr "Змешаны рэжым AdLib/MIDI"
+
+#: gui/options.cpp:857
+msgid "Use both MIDI and AdLib sound generation"
+msgstr "Выкарыстоўваць і MIDI, і AdLib для генерацыі гуку"
+
+#: gui/options.cpp:860
+msgid "MIDI gain:"
+msgstr "Узмацненне MIDI:"
+
+#: gui/options.cpp:870
+msgid "MT-32 Device:"
+msgstr "Нал. MT-32:"
+
+#: gui/options.cpp:870
+msgid "Specifies default sound device for Roland MT-32/LAPC1/CM32l/CM64 output"
+msgstr ""
+"Паказвае гукавую прыладу па змаўчанні для вываду на Roland MT-32/LAPC1/CM32l/"
+"CM64"
+
+#: gui/options.cpp:875
+msgid "True Roland MT-32 (disable GM emulation)"
+msgstr "Сапраўдны Roland MT-32 (забараніць эмуляцыю GM)"
+
+#: gui/options.cpp:875 gui/options.cpp:877
+msgid ""
+"Check if you want to use your real hardware Roland-compatible sound device "
+"connected to your computer"
+msgstr ""
+"Адзначце, калі ў вас падключана Roland-сумяшчальная гукавая прылада і вы "
+"жадаеце яе выкарыстоўваць"
+
+#: gui/options.cpp:877
+msgctxt "lowres"
+msgid "True Roland MT-32 (no GM emulation)"
+msgstr "Сапраўдны Roland MT-32 (забараніць GM)"
+
+#: gui/options.cpp:880
+msgid "Enable Roland GS Mode"
+msgstr "Уключыць рэжым Roland GS"
+
+#: gui/options.cpp:880
+msgid "Turns off General MIDI mapping for games with Roland MT-32 soundtrack"
+msgstr ""
+"Выключае супастаўленне General MIDI для гульняў з гукавой дарожкай для "
+"Roland MT-32"
+
+#: gui/options.cpp:889
+msgid "Don't use Roland MT-32 music"
+msgstr "Не выкарыстоўваць музыку для MT-32"
+
+#: gui/options.cpp:916
+msgid "Text and Speech:"
+msgstr "Тэкст і агучка:"
+
+#: gui/options.cpp:920 gui/options.cpp:930
+msgid "Speech"
+msgstr "Агучка"
+
+#: gui/options.cpp:921 gui/options.cpp:931
+msgid "Subtitles"
+msgstr "Субтытры"
+
+#: gui/options.cpp:922
+msgid "Both"
+msgstr "Абое"
+
+#: gui/options.cpp:924
+msgid "Subtitle speed:"
+msgstr "Хуткасць тытраў:"
+
+#: gui/options.cpp:926
+msgctxt "lowres"
+msgid "Text and Speech:"
+msgstr "Тэкст і агучка:"
+
+#: gui/options.cpp:930
+msgid "Spch"
+msgstr "Агуч"
+
+#: gui/options.cpp:931
+msgid "Subs"
+msgstr "Суб"
+
+#: gui/options.cpp:932
+msgctxt "lowres"
+msgid "Both"
+msgstr "Абое"
+
+#: gui/options.cpp:932
+msgid "Show subtitles and play speech"
+msgstr "Паказваць субтытры і прайграваць гаворку"
+
+#: gui/options.cpp:934
+msgctxt "lowres"
+msgid "Subtitle speed:"
+msgstr "Хуткасць тытраў:"
+
+#: gui/options.cpp:950
+msgid "Music volume:"
+msgstr "Гучн. музыкі:"
+
+#: gui/options.cpp:952
+msgctxt "lowres"
+msgid "Music volume:"
+msgstr "Гучн. музыкі:"
+
+#: gui/options.cpp:959
+msgid "Mute All"
+msgstr "Выкл. усё"
+
+#: gui/options.cpp:962
+msgid "SFX volume:"
+msgstr "Гучнасць SFX:"
+
+#: gui/options.cpp:962 gui/options.cpp:964 gui/options.cpp:965
+msgid "Special sound effects volume"
+msgstr "Гучнасць спецыяльных гукавых эфектаў"
+
+#: gui/options.cpp:964
+msgctxt "lowres"
+msgid "SFX volume:"
+msgstr "Гучн. SFX:"
+
+#: gui/options.cpp:972
+msgid "Speech volume:"
+msgstr "Гучн. агучкі:"
+
+#: gui/options.cpp:974
+msgctxt "lowres"
+msgid "Speech volume:"
+msgstr "Гучн. агучкі:"
+
+#: gui/options.cpp:1131
+msgid "Theme Path:"
+msgstr "Шлях да тэм:"
+
+#: gui/options.cpp:1133
+msgctxt "lowres"
+msgid "Theme Path:"
+msgstr "Дзе тэмы:"
+
+#: gui/options.cpp:1139 gui/options.cpp:1141 gui/options.cpp:1142
+msgid "Specifies path to additional data used by all games or ScummVM"
+msgstr ""
+"Паказвае шлях да дадатковых файлаў дадзеных, выкарыстоўваных усімі гульнямі, "
+"або ScummVM"
+
+#: gui/options.cpp:1148
+msgid "Plugins Path:"
+msgstr "Шлях да плагінаў:"
+
+#: gui/options.cpp:1150
+msgctxt "lowres"
+msgid "Plugins Path:"
+msgstr "Шлях да плагінаў:"
+
+#: gui/options.cpp:1159
+msgid "Misc"
+msgstr "Рознае"
+
+#: gui/options.cpp:1161
+msgctxt "lowres"
+msgid "Misc"
+msgstr "Рознае"
+
+#: gui/options.cpp:1163
+msgid "Theme:"
+msgstr "Тэма"
+
+#: gui/options.cpp:1167
+msgid "GUI Renderer:"
+msgstr "Малявалка GUI:"
+
+#: gui/options.cpp:1179
+msgid "Autosave:"
+msgstr "Аўтазахаванне:"
+
+#: gui/options.cpp:1181
+msgctxt "lowres"
+msgid "Autosave:"
+msgstr "Аўтазах.:"
+
+#: gui/options.cpp:1189
+msgid "Keys"
+msgstr "Клавішы"
+
+#: gui/options.cpp:1196
+msgid "GUI Language:"
+msgstr "Мова GUI:"
+
+#: gui/options.cpp:1196
+msgid "Language of ScummVM GUI"
+msgstr "Мова графічнага інтэрфейсу ScummVM"
+
+#: gui/options.cpp:1347
+msgid "You have to restart ScummVM before your changes will take effect."
+msgstr "Вы павінны перазапусціць ScummVM, каб ужыць змены."
+
+#: gui/options.cpp:1360
+msgid "Select directory for savegames"
+msgstr "Абярыце дырэкторыю для захаванняў"
+
+#: gui/options.cpp:1367
+msgid "The chosen directory cannot be written to. Please select another one."
+msgstr "Не магу пісаць у абраную дырэкторыю. Калі ласка, азначце іншую."
+
+#: gui/options.cpp:1376
+msgid "Select directory for GUI themes"
+msgstr "Абярыце дырэкторыю для тэм GUI"
+
+#: gui/options.cpp:1386
+msgid "Select directory for extra files"
+msgstr "Абярыце дырэкторыю з дадатковымі файламі"
+
+#: gui/options.cpp:1397
+msgid "Select directory for plugins"
+msgstr "Абярыце дырэкторыю з плагінамі"
+
+#: gui/options.cpp:1450
+msgid ""
+"The theme you selected does not support your current language. If you want "
+"to use this theme you need to switch to another language first."
+msgstr ""
+"Тэма, абраная вамі, не падтрымлівае бягучую мову. Калі вы жадаеце "
+"выкарыстоўваць гэтую тэму, вам неабходна спачатку пераключыцца на іншую мову."
+
+#: gui/saveload-dialog.cpp:158
+msgid "List view"
+msgstr "Выгляд спісу"
+
+#: gui/saveload-dialog.cpp:159
+msgid "Grid view"
+msgstr "Выгляд сеткі"
+
+#: gui/saveload-dialog.cpp:202 gui/saveload-dialog.cpp:350
+msgid "No date saved"
+msgstr "Дата не запісана"
+
+#: gui/saveload-dialog.cpp:203 gui/saveload-dialog.cpp:351
+msgid "No time saved"
+msgstr "Час не запісаны"
+
+#: gui/saveload-dialog.cpp:204 gui/saveload-dialog.cpp:352
+msgid "No playtime saved"
+msgstr "Час гульні не запісаны"
+
+#: gui/saveload-dialog.cpp:211 gui/saveload-dialog.cpp:267
+msgid "Delete"
+msgstr "Выдаліць"
+
+#: gui/saveload-dialog.cpp:266
+msgid "Do you really want to delete this savegame?"
+msgstr "Вы сапраўды жадаеце выдаліць гэта захаванне?"
+
+#: gui/saveload-dialog.cpp:375 gui/saveload-dialog.cpp:796
+msgid "Date: "
+msgstr "Дата: "
+
+#: gui/saveload-dialog.cpp:379 gui/saveload-dialog.cpp:802
+msgid "Time: "
+msgstr "Час: "
+
+#: gui/saveload-dialog.cpp:385 gui/saveload-dialog.cpp:810
+msgid "Playtime: "
+msgstr "Час гульні: "
+
+#: gui/saveload-dialog.cpp:398 gui/saveload-dialog.cpp:465
+msgid "Untitled savestate"
+msgstr "Захаванне без імя"
+
+#: gui/saveload-dialog.cpp:517
+msgid "Next"
+msgstr "Наступны"
+
+#: gui/saveload-dialog.cpp:520
+msgid "Prev"
+msgstr "Папярэдні"
+
+#: gui/saveload-dialog.cpp:684
+msgid "New Save"
+msgstr "Новае захаванне"
+
+#: gui/saveload-dialog.cpp:684
+msgid "Create a new save game"
+msgstr "Стварыць новы запіс гульні"
+
+#: gui/saveload-dialog.cpp:789
+msgid "Name: "
+msgstr "Назва: "
+
+#: gui/saveload-dialog.cpp:861
+#, c-format
+msgid "Enter a description for slot %d:"
+msgstr "Увядзіце апісанне слота %d:"
+
+#: gui/themebrowser.cpp:44
+msgid "Select a Theme"
+msgstr "Абярыце тэму"
+
+#: gui/ThemeEngine.cpp:337
+msgid "Disabled GFX"
+msgstr "Без графікі"
+
+#: gui/ThemeEngine.cpp:337
+msgctxt "lowres"
+msgid "Disabled GFX"
+msgstr "Без графікі"
+
+#: gui/ThemeEngine.cpp:338
+msgid "Standard Renderer (16bpp)"
+msgstr "Стандартны растарызатар (16bpp)"
+
+#: gui/ThemeEngine.cpp:338
+msgid "Standard (16bpp)"
+msgstr "Стандартны растарызатар (16bpp)"
+
+#: gui/ThemeEngine.cpp:340
+msgid "Antialiased Renderer (16bpp)"
+msgstr "Растарызатар са згладжваннем (16bpp)"
+
+#: gui/ThemeEngine.cpp:340
+msgid "Antialiased (16bpp)"
+msgstr "Растарызатар са згладжваннем (16bpp)"
+
+#: gui/widget.cpp:322 gui/widget.cpp:324 gui/widget.cpp:330 gui/widget.cpp:332
+msgid "Clear value"
+msgstr "Ачысціць значэнне"
+
+#: base/main.cpp:209
+#, c-format
+msgid "Engine does not support debug level '%s'"
+msgstr "Рухавічок не падтрымлівае ўзровень адладкі '%s'"
+
+#: base/main.cpp:287
+msgid "Menu"
+msgstr "Меню"
+
+#: base/main.cpp:290 backends/platform/symbian/src/SymbianActions.cpp:45
+#: backends/platform/wince/CEActionsPocket.cpp:45
+#: backends/platform/wince/CEActionsSmartphone.cpp:46
+msgid "Skip"
+msgstr "Прапусціць"
+
+#: base/main.cpp:293 backends/platform/symbian/src/SymbianActions.cpp:50
+#: backends/platform/wince/CEActionsPocket.cpp:42
+msgid "Pause"
+msgstr "Паўза"
+
+#: base/main.cpp:296
+msgid "Skip line"
+msgstr "Прапусціць радок"
+
+#: base/main.cpp:467
+msgid "Error running game:"
+msgstr "Памылка запуску гульні:"
+
+#: base/main.cpp:491
+msgid "Could not find any engine capable of running the selected game"
+msgstr "Не магу знайсці рухавічок для запуску абранай гульні"
+
+#: common/error.cpp:38
+msgid "No error"
+msgstr "Няма памылкі"
+
+#: common/error.cpp:40
+msgid "Game data not found"
+msgstr "Няма файлаў гульні"
+
+#: common/error.cpp:42
+msgid "Game id not supported"
+msgstr "Game id не падтрымліваецца"
+
+#: common/error.cpp:44
+msgid "Unsupported color mode"
+msgstr "Непадтрымоўваны рэжым колеру"
+
+#: common/error.cpp:47
+msgid "Read permission denied"
+msgstr "Недастаткова правоў для чытання"
+
+#: common/error.cpp:49
+msgid "Write permission denied"
+msgstr "Недастаткова правоў для запісу"
+
+#: common/error.cpp:52
+msgid "Path does not exist"
+msgstr "Шлях не знойдзены"
+
+#: common/error.cpp:54
+msgid "Path not a directory"
+msgstr "Шлях не з'яўляецца дырэкторыяй"
+
+#: common/error.cpp:56
+msgid "Path not a file"
+msgstr "Шлях не з'яўляецца файлам"
+
+#: common/error.cpp:59
+msgid "Cannot create file"
+msgstr "Не магу стварыць файл"
+
+#: common/error.cpp:61
+msgid "Reading data failed"
+msgstr "Памылка чытання дадзеных"
+
+#: common/error.cpp:63
+msgid "Writing data failed"
+msgstr "Памылка запісу дадзеных"
+
+#: common/error.cpp:66
+msgid "Could not find suitable engine plugin"
+msgstr "Не магу знайсці падыходны плагін для рухавічка"
+
+#: common/error.cpp:68
+msgid "Engine plugin does not support save states"
+msgstr "Рухавічок не падтрымлівае захаванні"
+
+#: common/error.cpp:71
+msgid "User canceled"
+msgstr "Перапынена карыстачом"
+
+#: common/error.cpp:75
+msgid "Unknown error"
+msgstr "Невядомая памылка"
+
+#: engines/advancedDetector.cpp:316
+#, c-format
+msgid "The game in '%s' seems to be unknown."
+msgstr "Здаецца, што гульня '%s' яшчэ невядома."
+
+#: engines/advancedDetector.cpp:317
+msgid "Please, report the following data to the ScummVM team along with name"
+msgstr ""
+"Калі ласка, перадайце наступныя дадзеныя камандзе ScummVM разам з назвай"
+
+#: engines/advancedDetector.cpp:319
+msgid "of the game you tried to add and its version/language/etc.:"
+msgstr "гульні, якую вы спрабуеце дадаць, і азначце яе версію, мову і г.д."
+
+#: engines/dialogs.cpp:84
+msgid "~R~esume"
+msgstr "Працяг~н~уць"
+
+#: engines/dialogs.cpp:86
+msgid "~L~oad"
+msgstr "~З~агрузіць"
+
+#: engines/dialogs.cpp:90
+msgid "~S~ave"
+msgstr "~З~апісаць"
+
+#: engines/dialogs.cpp:94
+msgid "~O~ptions"
+msgstr "~О~пцыі"
+
+#: engines/dialogs.cpp:99
+msgid "~H~elp"
+msgstr "~Д~апамога"
+
+#: engines/dialogs.cpp:101
+msgid "~A~bout"
+msgstr "Пра пра~г~раму"
+
+#: engines/dialogs.cpp:104 engines/dialogs.cpp:180
+msgid "~R~eturn to Launcher"
+msgstr "~В~ыйсці ў галоўнае меню"
+
+#: engines/dialogs.cpp:106 engines/dialogs.cpp:182
+msgctxt "lowres"
+msgid "~R~eturn to Launcher"
+msgstr "~У~ галоўнае меню"
+
+#: engines/dialogs.cpp:115 engines/agi/saveload.cpp:803
+#: engines/cruise/menu.cpp:212 engines/sci/engine/kfile.cpp:742
+msgid "Save game:"
+msgstr "Захаваць гульню:"
+
+#: engines/dialogs.cpp:115 engines/agi/saveload.cpp:803
+#: engines/scumm/dialogs.cpp:187 engines/cruise/menu.cpp:212
+#: engines/sci/engine/kfile.cpp:742
+#: backends/platform/symbian/src/SymbianActions.cpp:44
+#: backends/platform/wince/CEActionsPocket.cpp:43
+#: backends/platform/wince/CEActionsPocket.cpp:267
+#: backends/platform/wince/CEActionsSmartphone.cpp:45
+#: backends/platform/wince/CEActionsSmartphone.cpp:231
+msgid "Save"
+msgstr "Захаваць"
+
+#: engines/dialogs.cpp:144
+msgid ""
+"Sorry, this engine does not currently provide in-game help. Please consult "
+"the README for basic information, and for instructions on how to obtain "
+"further assistance."
+msgstr ""
+"Прабачце, але гэты рухавічок пакуль не падае дапамогі ў гульні. Калі ласка, "
+"звярніцеся ў файл README за базавай інфармацыяй, а таксама інструкцыямі пра "
+"тое, як атрымаць далейшую дапамогу."
+
+#: engines/dialogs.cpp:228
+#, c-format
+msgid ""
+"Gamestate save failed (%s)! Please consult the README for basic information, "
+"and for instructions on how to obtain further assistance."
+msgstr ""
+"Не атрымалася захаваць гульню (%s)! Калі ласка, звярніцеся ў файл README за "
+"базавай інфармацыяй, а таксама інструкцыямі пра тое, як атрымаць далейшую "
+"дапамогу."
+
+#: engines/dialogs.cpp:301 engines/mohawk/dialogs.cpp:109
+#: engines/mohawk/dialogs.cpp:174
+msgid "~O~K"
+msgstr "~О~К"
+
+#: engines/dialogs.cpp:302 engines/mohawk/dialogs.cpp:110
+#: engines/mohawk/dialogs.cpp:175
+msgid "~C~ancel"
+msgstr "~А~дмена"
+
+#: engines/dialogs.cpp:305
+msgid "~K~eys"
+msgstr "~К~лавішы"
+
+#: engines/engine.cpp:235
+msgid "Could not initialize color format."
+msgstr "Не магу ініцыялізаваць фармат колеру."
+
+#: engines/engine.cpp:243
+msgid "Could not switch to video mode: '"
+msgstr "Не атрымалася пераключыць відэарэжым: '"
+
+#: engines/engine.cpp:252
+msgid "Could not apply aspect ratio setting."
+msgstr "Не атрымалася выкарыстаць карэкцыю суадносін бакоў."
+
+#: engines/engine.cpp:257
+msgid "Could not apply fullscreen setting."
+msgstr "Не магу ўжыць поўнаэкранны рэжым."
+
+#: engines/engine.cpp:357
+msgid ""
+"You appear to be playing this game directly\n"
+"from the CD. This is known to cause problems,\n"
+"and it is therefore recommended that you copy\n"
+"the data files to your hard disk instead.\n"
+"See the README file for details."
+msgstr ""
+"Здаецца, вы спрабуеце запусціць гэту гульню прама\n"
+"з CD. Гэта звычайна выклікае праблемы, і таму\n"
+"мы рэкамендуем скапіяваць файлы дадзеных гульні\n"
+"на жорсткі дыск. Падрабязнасці можна знайсці ў\n"
+"файле README."
+
+#: engines/engine.cpp:368
+msgid ""
+"This game has audio tracks in its disk. These\n"
+"tracks need to be ripped from the disk using\n"
+"an appropriate CD audio extracting tool in\n"
+"order to listen to the game's music.\n"
+"See the README file for details."
+msgstr ""
+"Дыск гэтай гульні ўтрымоўвае гукавыя дарожкі. Іх\n"
+"неабходна перапісаць з дыска з дапамогай\n"
+"адпаведнай праграмы для капіявання\n"
+"аўдыёдыскаў, і толькі пасля гэтага ў гульні\n"
+"з'явіцца музыка. Падрабязнасці можна знайсці ў\n"
+"файле README."
+
+#: engines/engine.cpp:426
+#, c-format
+msgid ""
+"Gamestate load failed (%s)! Please consult the README for basic information, "
+"and for instructions on how to obtain further assistance."
+msgstr ""
+"Не атрымалася прачытаць захаванне гульні (%s)! Калі ласка, звярніцеся ў файл "
+"README за базавай інфармацыяй, а таксама інструкцыямі пра тое, як атрымаць "
+"далейшую дапамогу."
+
+#: engines/engine.cpp:439
+msgid ""
+"WARNING: The game you are about to start is not yet fully supported by "
+"ScummVM. As such, it is likely to be unstable, and any saves you make might "
+"not work in future versions of ScummVM."
+msgstr ""
+"ПАПЯРЭДЖАННЕ: Гульня, якую вы збіраецеся запусціць, яшчэ не падтрымліваецца "
+"ScummVM цалкам. Яна, хутчэй за ўсё, не будзе працаваць стабільна, і "
+"захаванні гульняў могуць не працаваць у будучых версіях ScummVM."
+
+#: engines/engine.cpp:442
+msgid "Start anyway"
+msgstr "Усё адно запусціць"
+
+#: engines/agi/detection.cpp:145 engines/dreamweb/detection.cpp:47
+#: engines/sci/detection.cpp:390
+msgid "Use original save/load screens"
+msgstr "Выкарыстоўваць арыгінальныя экраны запісу/чытанні гульні"
+
+#: engines/agi/detection.cpp:146 engines/dreamweb/detection.cpp:48
+#: engines/sci/detection.cpp:391
+msgid "Use the original save/load screens, instead of the ScummVM ones"
+msgstr ""
+"Выкарыстоўваць арыгінальныя экраны запісу і захаванні гульні замест "
+"зробленых у ScummVM"
+
+#: engines/agi/saveload.cpp:816 engines/sci/engine/kfile.cpp:838
+msgid "Restore game:"
+msgstr "Узнавіць гульню:"
+
+#: engines/agi/saveload.cpp:816 engines/sci/engine/kfile.cpp:838
+msgid "Restore"
+msgstr "Узнавіць"
+
+#: engines/dreamweb/detection.cpp:57
+msgid "Use bright palette mode"
+msgstr "Выкарыстоўваць рэжым яркай палітры"
+
+#: engines/dreamweb/detection.cpp:58
+msgid "Display graphics using the game's bright palette"
+msgstr "Малюе графіку з выкарыстаннем яркай палітры гульні"
+
+#: engines/sci/detection.cpp:370
+msgid "EGA undithering"
+msgstr "EGA без растру"
+
+#: engines/sci/detection.cpp:371
+msgid "Enable undithering in EGA games"
+msgstr "Уключае рэжым без растравання ў EGA гульнях"
+
+#: engines/sci/detection.cpp:380
+msgid "Prefer digital sound effects"
+msgstr "Аддаваць перавагу лічбавым гукавым эфектам"
+
+#: engines/sci/detection.cpp:381
+msgid "Prefer digital sound effects instead of synthesized ones"
+msgstr "Аддаваць перавагу лічбавым гукавым эфектам замест сінтэзаваных"
+
+#: engines/sci/detection.cpp:400
+msgid "Use IMF/Yamaha FB-01 for MIDI output"
+msgstr "Выкарыстоўваць IMF/Yamaha FB-01 для вываду MIDI"
+
+#: engines/sci/detection.cpp:401
+msgid ""
+"Use an IBM Music Feature card or a Yamaha FB-01 FM synth module for MIDI "
+"output"
+msgstr ""
+"Выкарыстоўваць гукавую карту IBM Music Feature ці модуль сінтэзу Yamaha "
+"FB-01 FM для MIDI"
+
+#: engines/sci/detection.cpp:411
+msgid "Use CD audio"
+msgstr "Выкарыстоўваць CD аўдыё"
+
+#: engines/sci/detection.cpp:412
+msgid "Use CD audio instead of in-game audio, if available"
+msgstr ""
+"Выкарыстоўваць гукавыя дарожкі з CD замест музыкі з файлаў гульні (калі "
+"даступна)"
+
+#: engines/sci/detection.cpp:422
+msgid "Use Windows cursors"
+msgstr "Выкарыстоўваць курсоры Windows"
+
+#: engines/sci/detection.cpp:423
+msgid ""
+"Use the Windows cursors (smaller and monochrome) instead of the DOS ones"
+msgstr ""
+"Выкарыстоўваць курсоры Windows (меншыя па памеры і аднакаляровыя) замест "
+"курсораў DOS"
+
+#: engines/sci/detection.cpp:433
+msgid "Use silver cursors"
+msgstr "Выкарыстоўваць срэбныя курсоры"
+
+#: engines/sci/detection.cpp:434
+msgid ""
+"Use the alternate set of silver cursors, instead of the normal golden ones"
+msgstr ""
+"Выкарыстоўваць альтэрнатыўны набор срэбных курсораў замест звычайных залатых"
+
+#: engines/scumm/dialogs.cpp:175
+#, c-format
+msgid "Insert Disk %c and Press Button to Continue."
+msgstr "Устаўце дыск %c і націсніце клавішу, каб працягнуць."
+
+#: engines/scumm/dialogs.cpp:176
+#, c-format
+msgid "Unable to Find %s, (%c%d) Press Button."
+msgstr "Не атрымалася знайсці %s, (%c%d) Націсніце клавішу."
+
+#: engines/scumm/dialogs.cpp:177
+#, c-format
+msgid "Error reading disk %c, (%c%d) Press Button."
+msgstr "Памылка чытання дыска %c, (%c%d) Націсніце клавішу."
+
+#: engines/scumm/dialogs.cpp:178
+msgid "Game Paused. Press SPACE to Continue."
+msgstr "Гульня спынена. Націсніце прабел, каб працягнуць."
+
+#. I18N: You may specify 'Yes' symbol at the end of the line, like this:
+#. "Moechten Sie wirklich neu starten? (J/N)J"
+#. Will react to J as 'Yes'
+#: engines/scumm/dialogs.cpp:182
+msgid "Are you sure you want to restart? (Y/N)"
+msgstr "Вы ўпэўнены, што жадаеце пачаць ізноў? (Y/N)"
+
+#. I18N: you may specify 'Yes' symbol at the end of the line. See previous comment
+#: engines/scumm/dialogs.cpp:184
+msgid "Are you sure you want to quit? (Y/N)"
+msgstr "Вы ўпэўнены, што жадаеце выйсці? (Y/N)"
+
+#: engines/scumm/dialogs.cpp:189
+msgid "Play"
+msgstr "Гуляць"
+
+#: engines/scumm/dialogs.cpp:191 engines/scumm/help.cpp:82
+#: engines/scumm/help.cpp:84
+#: backends/platform/symbian/src/SymbianActions.cpp:52
+#: backends/platform/wince/CEActionsPocket.cpp:44
+#: backends/platform/wince/CEActionsSmartphone.cpp:52
+#: backends/events/default/default-events.cpp:213
+msgid "Quit"
+msgstr "Выхад"
+
+#: engines/scumm/dialogs.cpp:193
+msgid "Insert save/load game disk"
+msgstr "Устаўце дыск з захаваннямі"
+
+#: engines/scumm/dialogs.cpp:194
+msgid "You must enter a name"
+msgstr "Вы павінны ўвесці імя"
+
+#: engines/scumm/dialogs.cpp:195
+msgid "The game was NOT saved (disk full?)"
+msgstr "Гульня НЕ БЫЛА запісана (дыск поўны?)"
+
+#: engines/scumm/dialogs.cpp:196
+msgid "The game was NOT loaded"
+msgstr "Гульня НЕ БЫЛА загружана"
+
+#: engines/scumm/dialogs.cpp:197
+#, c-format
+msgid "Saving '%s'"
+msgstr "Захоўваю '%s'"
+
+#: engines/scumm/dialogs.cpp:198
+#, c-format
+msgid "Loading '%s'"
+msgstr "Загружаю '%s'"
+
+#: engines/scumm/dialogs.cpp:199
+msgid "Name your SAVE game"
+msgstr "Назавіце захаванне гульні"
+
+#: engines/scumm/dialogs.cpp:200
+msgid "Select a game to LOAD"
+msgstr "Абярыце гульню для загрузкі"
+
+#: engines/scumm/dialogs.cpp:201
+msgid "Game title)"
+msgstr "Назва гульні)"
+
+#. I18N: Previous page button
+#: engines/scumm/dialogs.cpp:287
+msgid "~P~revious"
+msgstr "~П~апяр"
+
+#. I18N: Next page button
+#: engines/scumm/dialogs.cpp:289
+msgid "~N~ext"
+msgstr "~Н~аст"
+
+#: engines/scumm/dialogs.cpp:290
+#: backends/platform/ds/arm9/source/dsoptions.cpp:56
+msgid "~C~lose"
+msgstr "~З~акрыць"
+
+#: engines/scumm/dialogs.cpp:597
+msgid "Speech Only"
+msgstr "Толькі агучка"
+
+#: engines/scumm/dialogs.cpp:598
+msgid "Speech and Subtitles"
+msgstr "Агучка і субтытры"
+
+#: engines/scumm/dialogs.cpp:599
+msgid "Subtitles Only"
+msgstr "Толькі субтытры"
+
+#: engines/scumm/dialogs.cpp:607
+msgctxt "lowres"
+msgid "Speech & Subs"
+msgstr "Агучка і тэкст"
+
+#: engines/scumm/dialogs.cpp:653
+msgid "Select a Proficiency Level."
+msgstr "Абярыце ўзровень складанасці."
+
+#: engines/scumm/dialogs.cpp:655
+msgid "Refer to your Loom(TM) manual for help."
+msgstr "За дапамогай звярніцеся да інструкцыі Loom(TM)."
+
+#: engines/scumm/dialogs.cpp:658
+msgid "Standard"
+msgstr "Стандартны"
+
+#: engines/scumm/dialogs.cpp:659
+msgid "Practice"
+msgstr "Практыкант"
+
+#: engines/scumm/dialogs.cpp:660
+msgid "Expert"
+msgstr "Эксперт"
+
+#: engines/scumm/help.cpp:73
+msgid "Common keyboard commands:"
+msgstr "Агульныя клавіятурныя каманды:"
+
+#: engines/scumm/help.cpp:74
+msgid "Save / Load dialog"
+msgstr "Дыялог запісу / чытання"
+
+#: engines/scumm/help.cpp:76
+msgid "Skip line of text"
+msgstr "Прапусціць радок"
+
+#: engines/scumm/help.cpp:77
+msgid "Esc"
+msgstr "Esc"
+
+#: engines/scumm/help.cpp:77
+msgid "Skip cutscene"
+msgstr "Прапусціць застаўку"
+
+#: engines/scumm/help.cpp:78
+msgid "Space"
+msgstr "Прабел"
+
+#: engines/scumm/help.cpp:78
+msgid "Pause game"
+msgstr "Паўза гульні"
+
+#: engines/scumm/help.cpp:79 engines/scumm/help.cpp:84
+#: engines/scumm/help.cpp:95 engines/scumm/help.cpp:96
+#: engines/scumm/help.cpp:97 engines/scumm/help.cpp:98
+#: engines/scumm/help.cpp:99 engines/scumm/help.cpp:100
+#: engines/scumm/help.cpp:101 engines/scumm/help.cpp:102
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: engines/scumm/help.cpp:79
+msgid "Load game state 1-10"
+msgstr "Загрузіць гульню 1-10"
+
+#: engines/scumm/help.cpp:80 engines/scumm/help.cpp:84
+#: engines/scumm/help.cpp:86 engines/scumm/help.cpp:100
+#: engines/scumm/help.cpp:101 engines/scumm/help.cpp:102
+msgid "Alt"
+msgstr "Alt"
+
+#: engines/scumm/help.cpp:80
+msgid "Save game state 1-10"
+msgstr "Захаваць гульню 1-10"
+
+#: engines/scumm/help.cpp:86 engines/scumm/help.cpp:89
+msgid "Enter"
+msgstr "Увод"
+
+#: engines/scumm/help.cpp:86
+msgid "Toggle fullscreen"
+msgstr "Пераключыць на ўвесь экран"
+
+#: engines/scumm/help.cpp:87
+msgid "Music volume up / down"
+msgstr "Гучнасць музыкі павялічыць / паменшыць"
+
+#: engines/scumm/help.cpp:88
+msgid "Text speed slower / faster"
+msgstr "Хуткасць тэксту хутчэй / павольней"
+
+#: engines/scumm/help.cpp:89
+msgid "Simulate left mouse button"
+msgstr "Эмуляцыя левай клавішы мышы"
+
+#: engines/scumm/help.cpp:90
+msgid "Tab"
+msgstr "Tab"
+
+#: engines/scumm/help.cpp:90
+msgid "Simulate right mouse button"
+msgstr "Эмуляцыя правай клавішы мышы"
+
+#: engines/scumm/help.cpp:93
+msgid "Special keyboard commands:"
+msgstr "Спецыяльныя клавіятурныя каманды:"
+
+#: engines/scumm/help.cpp:94
+msgid "Show / Hide console"
+msgstr "Паказаць / Прыбраць кансоль"
+
+#: engines/scumm/help.cpp:95
+msgid "Start the debugger"
+msgstr "Запуск адладчыка"
+
+#: engines/scumm/help.cpp:96
+msgid "Show memory consumption"
+msgstr "Паказаць спажыванне памяці"
+
+#: engines/scumm/help.cpp:97
+msgid "Run in fast mode (*)"
+msgstr "Запусціць хуткі рэжым (*)"
+
+#: engines/scumm/help.cpp:98
+msgid "Run in really fast mode (*)"
+msgstr "Запусціць вельмі хуткі рэжым (*)"
+
+#: engines/scumm/help.cpp:99
+msgid "Toggle mouse capture"
+msgstr "Пераключэнне перахопу мышы"
+
+#: engines/scumm/help.cpp:100
+msgid "Switch between graphics filters"
+msgstr "Пераключэнне паміж графічнымі фільтрамі"
+
+#: engines/scumm/help.cpp:101
+msgid "Increase / Decrease scale factor"
+msgstr "Павялічыць/паменшыць маштаб"
+
+#: engines/scumm/help.cpp:102
+msgid "Toggle aspect-ratio correction"
+msgstr "Пераключэнне карэкцыі суадносін бакоў"
+
+#: engines/scumm/help.cpp:107
+msgid "* Note that using ctrl-f and"
+msgstr "* Выкарыстанне ctrl-f і"
+
+#: engines/scumm/help.cpp:108
+msgid " ctrl-g are not recommended"
+msgstr " ctrl-g не рэкамендуецца,"
+
+#: engines/scumm/help.cpp:109
+msgid " since they may cause crashes"
+msgstr " бо яны могуць прывесці да"
+
+#: engines/scumm/help.cpp:110
+msgid " or incorrect game behavior."
+msgstr " няправільнай работы гульні."
+
+#: engines/scumm/help.cpp:114
+msgid "Spinning drafts on the keyboard:"
+msgstr "Змяняныя чарнавікі на клавіятуры:"
+
+#: engines/scumm/help.cpp:116
+msgid "Main game controls:"
+msgstr "Асноўнае кіраванне гульнёй:"
+
+#: engines/scumm/help.cpp:121 engines/scumm/help.cpp:136
+#: engines/scumm/help.cpp:161
+msgid "Push"
+msgstr "Пхаць"
+
+#: engines/scumm/help.cpp:122 engines/scumm/help.cpp:137
+#: engines/scumm/help.cpp:162
+msgid "Pull"
+msgstr "Цягнуць"
+
+#: engines/scumm/help.cpp:123 engines/scumm/help.cpp:138
+#: engines/scumm/help.cpp:163 engines/scumm/help.cpp:197
+#: engines/scumm/help.cpp:207
+msgid "Give"
+msgstr "Даць"
+
+#: engines/scumm/help.cpp:124 engines/scumm/help.cpp:139
+#: engines/scumm/help.cpp:164 engines/scumm/help.cpp:190
+#: engines/scumm/help.cpp:208
+msgid "Open"
+msgstr "Адкрыць"
+
+#: engines/scumm/help.cpp:126
+msgid "Go to"
+msgstr "Ісці"
+
+#: engines/scumm/help.cpp:127
+msgid "Get"
+msgstr "Узяць"
+
+#: engines/scumm/help.cpp:128 engines/scumm/help.cpp:152
+#: engines/scumm/help.cpp:170 engines/scumm/help.cpp:198
+#: engines/scumm/help.cpp:213 engines/scumm/help.cpp:224
+#: engines/scumm/help.cpp:250
+msgid "Use"
+msgstr "Выкарыстаць"
+
+#: engines/scumm/help.cpp:129 engines/scumm/help.cpp:141
+msgid "Read"
+msgstr "Чытаць"
+
+#: engines/scumm/help.cpp:130 engines/scumm/help.cpp:147
+msgid "New kid"
+msgstr "Новы перс"
+
+#: engines/scumm/help.cpp:131 engines/scumm/help.cpp:153
+#: engines/scumm/help.cpp:171
+msgid "Turn on"
+msgstr "Уключыць"
+
+#: engines/scumm/help.cpp:132 engines/scumm/help.cpp:154
+#: engines/scumm/help.cpp:172
+msgid "Turn off"
+msgstr "Выключыць"
+
+#: engines/scumm/help.cpp:142 engines/scumm/help.cpp:167
+#: engines/scumm/help.cpp:194
+msgid "Walk to"
+msgstr "Ісці да"
+
+#: engines/scumm/help.cpp:143 engines/scumm/help.cpp:168
+#: engines/scumm/help.cpp:195 engines/scumm/help.cpp:210
+#: engines/scumm/help.cpp:227
+msgid "Pick up"
+msgstr "Падняць"
+
+#: engines/scumm/help.cpp:144 engines/scumm/help.cpp:169
+msgid "What is"
+msgstr "Што такое"
+
+#: engines/scumm/help.cpp:146
+msgid "Unlock"
+msgstr "Адкрыць"
+
+#: engines/scumm/help.cpp:149
+msgid "Put on"
+msgstr "Пакласці"
+
+#: engines/scumm/help.cpp:150
+msgid "Take off"
+msgstr "Падняць"
+
+#: engines/scumm/help.cpp:156
+msgid "Fix"
+msgstr "Выправіць"
+
+#: engines/scumm/help.cpp:158
+msgid "Switch"
+msgstr "Пераключыць"
+
+#: engines/scumm/help.cpp:166 engines/scumm/help.cpp:228
+msgid "Look"
+msgstr "Глядзець"
+
+#: engines/scumm/help.cpp:173 engines/scumm/help.cpp:223
+msgid "Talk"
+msgstr "Гаварыць"
+
+#: engines/scumm/help.cpp:174
+msgid "Travel"
+msgstr "Падарожнічаць"
+
+#: engines/scumm/help.cpp:175
+msgid "To Henry / To Indy"
+msgstr "Генры/Інды"
+
+#. I18N: These are different musical notes
+#: engines/scumm/help.cpp:179
+msgid "play C minor on distaff"
+msgstr "іграць до мінор на калаўроце"
+
+#: engines/scumm/help.cpp:180
+msgid "play D on distaff"
+msgstr "іграць рэ на калаўроце"
+
+#: engines/scumm/help.cpp:181
+msgid "play E on distaff"
+msgstr "іграць мі на калаўроце"
+
+#: engines/scumm/help.cpp:182
+msgid "play F on distaff"
+msgstr "іграць фа на калаўроце"
+
+#: engines/scumm/help.cpp:183
+msgid "play G on distaff"
+msgstr "іграць соль на калаўроце"
+
+#: engines/scumm/help.cpp:184
+msgid "play A on distaff"
+msgstr "іграць ля на калаўроце"
+
+#: engines/scumm/help.cpp:185
+msgid "play B on distaff"
+msgstr "іграць сі на калаўроце"
+
+#: engines/scumm/help.cpp:186
+msgid "play C major on distaff"
+msgstr "іграць до мажор на калаўроце"
+
+#: engines/scumm/help.cpp:192 engines/scumm/help.cpp:214
+msgid "puSh"
+msgstr "пхаць"
+
+#: engines/scumm/help.cpp:193 engines/scumm/help.cpp:215
+msgid "pull (Yank)"
+msgstr "цягнуць (чапляць)"
+
+#: engines/scumm/help.cpp:196 engines/scumm/help.cpp:212
+#: engines/scumm/help.cpp:248
+msgid "Talk to"
+msgstr "Гаварыць з"
+
+#: engines/scumm/help.cpp:199 engines/scumm/help.cpp:211
+msgid "Look at"
+msgstr "Глядзець на"
+
+#: engines/scumm/help.cpp:200
+msgid "turn oN"
+msgstr "уключыць"
+
+#: engines/scumm/help.cpp:201
+msgid "turn oFf"
+msgstr "выключыць"
+
+#: engines/scumm/help.cpp:217
+msgid "KeyUp"
+msgstr "Уверх"
+
+#: engines/scumm/help.cpp:217
+msgid "Highlight prev dialogue"
+msgstr "Падсвятліць папярэдні дыялог"
+
+#: engines/scumm/help.cpp:218
+msgid "KeyDown"
+msgstr "Уніз"
+
+#: engines/scumm/help.cpp:218
+msgid "Highlight next dialogue"
+msgstr "Падсвятліць наступны дыялог"
+
+#: engines/scumm/help.cpp:222
+msgid "Walk"
+msgstr "Ісці"
+
+#: engines/scumm/help.cpp:225 engines/scumm/help.cpp:234
+#: engines/scumm/help.cpp:241 engines/scumm/help.cpp:249
+msgid "Inventory"
+msgstr "Інвентар"
+
+#: engines/scumm/help.cpp:226
+msgid "Object"
+msgstr "Аб'ект"
+
+#: engines/scumm/help.cpp:229
+msgid "Black and White / Color"
+msgstr "Чорна-белы / Каляровы"
+
+#: engines/scumm/help.cpp:232
+msgid "Eyes"
+msgstr "Вочы"
+
+#: engines/scumm/help.cpp:233
+msgid "Tongue"
+msgstr "Язык"
+
+#: engines/scumm/help.cpp:235
+msgid "Punch"
+msgstr "Удар"
+
+#: engines/scumm/help.cpp:236
+msgid "Kick"
+msgstr "Нагой"
+
+#: engines/scumm/help.cpp:239 engines/scumm/help.cpp:247
+msgid "Examine"
+msgstr "Праверыць"
+
+#: engines/scumm/help.cpp:240
+msgid "Regular cursor"
+msgstr "Звычайны курсор"
+
+#. I18N: Comm is a communication device
+#: engines/scumm/help.cpp:243
+msgid "Comm"
+msgstr "Кам"
+
+#: engines/scumm/help.cpp:246
+msgid "Save / Load / Options"
+msgstr "Загрузіць / Захаваць / Налады"
+
+#: engines/scumm/help.cpp:255
+msgid "Other game controls:"
+msgstr "Астатняе кіраванне гульнёй:"
+
+#: engines/scumm/help.cpp:257 engines/scumm/help.cpp:267
+msgid "Inventory:"
+msgstr "Інвентар:"
+
+#: engines/scumm/help.cpp:258 engines/scumm/help.cpp:274
+msgid "Scroll list up"
+msgstr "Пракруціць спіс уверх"
+
+#: engines/scumm/help.cpp:259 engines/scumm/help.cpp:275
+msgid "Scroll list down"
+msgstr "Пракруціць спіс уніз"
+
+#: engines/scumm/help.cpp:260 engines/scumm/help.cpp:268
+msgid "Upper left item"
+msgstr "Верхні левы прадмет"
+
+#: engines/scumm/help.cpp:261 engines/scumm/help.cpp:270
+msgid "Lower left item"
+msgstr "Ніжні левы прадмет"
+
+#: engines/scumm/help.cpp:262 engines/scumm/help.cpp:271
+msgid "Upper right item"
+msgstr "Верхні правы прадмет"
+
+#: engines/scumm/help.cpp:263 engines/scumm/help.cpp:273
+msgid "Lower right item"
+msgstr "Ніжні правы прадмет"
+
+#: engines/scumm/help.cpp:269
+msgid "Middle left item"
+msgstr "Сярэдні левы прадмет"
+
+#: engines/scumm/help.cpp:272
+msgid "Middle right item"
+msgstr "Сярэдні правы прадмет"
+
+#: engines/scumm/help.cpp:279 engines/scumm/help.cpp:284
+msgid "Switching characters:"
+msgstr "Змена героя:"
+
+#: engines/scumm/help.cpp:281
+msgid "Second kid"
+msgstr "Другі герой"
+
+#: engines/scumm/help.cpp:282
+msgid "Third kid"
+msgstr "Трэці герой"
+
+#: engines/scumm/help.cpp:294
+msgid "Fighting controls (numpad):"
+msgstr "Кіраванне боем (лічбавыя клавішы)"
+
+#: engines/scumm/help.cpp:295 engines/scumm/help.cpp:296
+#: engines/scumm/help.cpp:297
+msgid "Step back"
+msgstr "Крок назад"
+
+#: engines/scumm/help.cpp:298
+msgid "Block high"
+msgstr "Абарона зверху"
+
+#: engines/scumm/help.cpp:299
+msgid "Block middle"
+msgstr "Абарона пасярэдзіне"
+
+#: engines/scumm/help.cpp:300
+msgid "Block low"
+msgstr "Абарона знізу"
+
+#: engines/scumm/help.cpp:301
+msgid "Punch high"
+msgstr "Удар зверху"
+
+#: engines/scumm/help.cpp:302
+msgid "Punch middle"
+msgstr "Удар пасярэдзіне"
+
+#: engines/scumm/help.cpp:303
+msgid "Punch low"
+msgstr "Удар знізу"
+
+#: engines/scumm/help.cpp:306
+msgid "These are for Indy on left."
+msgstr "Гэта калі Інды злева."
+
+#: engines/scumm/help.cpp:307
+msgid "When Indy is on the right,"
+msgstr "Калі Інды справа,"
+
+#: engines/scumm/help.cpp:308
+msgid "7, 4, and 1 are switched with"
+msgstr "7, 4 і 1 змяняюцца з"
+
+#: engines/scumm/help.cpp:309
+msgid "9, 6, and 3, respectively."
+msgstr "9, 6 і 3 адпаведна."
+
+#: engines/scumm/help.cpp:316
+msgid "Biplane controls (numpad):"
+msgstr "Кіраванне самалётам (лічбавыя клавішы)"
+
+#: engines/scumm/help.cpp:317
+msgid "Fly to upper left"
+msgstr "Ляцець налева-ўверх"
+
+#: engines/scumm/help.cpp:318
+msgid "Fly to left"
+msgstr "Ляцець налева"
+
+#: engines/scumm/help.cpp:319
+msgid "Fly to lower left"
+msgstr "Ляцець налева-ўніз"
+
+#: engines/scumm/help.cpp:320
+msgid "Fly upwards"
+msgstr "Ляцець уверх"
+
+#: engines/scumm/help.cpp:321
+msgid "Fly straight"
+msgstr "Ляцець прама"
+
+#: engines/scumm/help.cpp:322
+msgid "Fly down"
+msgstr "Ляцець уніз"
+
+#: engines/scumm/help.cpp:323
+msgid "Fly to upper right"
+msgstr "Ляцець направа-ўверх"
+
+#: engines/scumm/help.cpp:324
+msgid "Fly to right"
+msgstr "Ляцець направа"
+
+#: engines/scumm/help.cpp:325
+msgid "Fly to lower right"
+msgstr "Ляцець направа-ўніз"
+
+#: engines/scumm/scumm.cpp:1773
+#, c-format
+msgid ""
+"Native MIDI support requires the Roland Upgrade from LucasArts,\n"
+"but %s is missing. Using AdLib instead."
+msgstr ""
+"Рэжым \"роднага\" MIDI патрабуе абнаўленне Roland Upgrade ад\n"
+"LucasArts, але не хапае %s. Пераключаюся на AdLib."
+
+#: engines/scumm/scumm.cpp:2278 engines/agos/saveload.cpp:220
+#, c-format
+msgid ""
+"Failed to save game state to file:\n"
+"\n"
+"%s"
+msgstr ""
+"Не атрымалася запісаць гульню ў файл:\n"
+"\n"
+"%s"
+
+#: engines/scumm/scumm.cpp:2285 engines/agos/saveload.cpp:185
+#, c-format
+msgid ""
+"Failed to load game state from file:\n"
+"\n"
+"%s"
+msgstr ""
+"Не атрымалася загрузіць гульню з файла:\n"
+"\n"
+"%s"
+
+#: engines/scumm/scumm.cpp:2297 engines/agos/saveload.cpp:228
+#, c-format
+msgid ""
+"Successfully saved game state in file:\n"
+"\n"
+"%s"
+msgstr ""
+"Гульня паспяхова захавана ў файл:\n"
+"\n"
+"%s"
+
+#: engines/scumm/scumm.cpp:2512
+msgid ""
+"Usually, Maniac Mansion would start now. But ScummVM doesn't do that yet. To "
+"play it, go to 'Add Game' in the ScummVM start menu and select the 'Maniac' "
+"directory inside the Tentacle game directory."
+msgstr ""
+"Зараз павінна запусціцца гульня Maniac Mansion. Але ScummVM пакуль гэтага не "
+"ўмее. Каб згуляць, націсніце 'Новая гульня' у стартавым меню ScummVM, а "
+"затым абярыце дырэкторыю Maniac у дырэкторыі з гульнёй Tentacle."
+
+#. I18N: Option for fast scene switching
+#: engines/mohawk/dialogs.cpp:92 engines/mohawk/dialogs.cpp:171
+msgid "~Z~ip Mode Activated"
+msgstr "Рэжым хуткага пераходу актываваны"
+
+#: engines/mohawk/dialogs.cpp:93
+msgid "~T~ransitions Enabled"
+msgstr "Пераходы актываваны"
+
+#. I18N: Drop book page
+#: engines/mohawk/dialogs.cpp:95
+msgid "~D~rop Page"
+msgstr "Выкінуць старонку"
+
+#: engines/mohawk/dialogs.cpp:99
+msgid "~S~how Map"
+msgstr "Показать карту"
+
+#: engines/mohawk/dialogs.cpp:105
+msgid "~M~ain Menu"
+msgstr "Галоўнае меню"
+
+#: engines/mohawk/dialogs.cpp:172
+msgid "~W~ater Effect Enabled"
+msgstr "Эфекты вады ўключаны"
+
+#: engines/agos/animation.cpp:560
+#, c-format
+msgid "Cutscene file '%s' not found!"
+msgstr "Файл застаўкі '%s' не знойдзены!"
+
+#: engines/gob/inter_playtoons.cpp:256 engines/gob/inter_v2.cpp:1287
+#: engines/tinsel/saveload.cpp:532
+msgid "Failed to load game state from file."
+msgstr "Не атрымалася загрузіць захаваную гульню з файла."
+
+#: engines/gob/inter_v2.cpp:1357 engines/tinsel/saveload.cpp:545
+msgid "Failed to save game state to file."
+msgstr "Не атрымалася захаваць гульню ў файл."
+
+#: engines/gob/inter_v5.cpp:107
+msgid "Failed to delete file."
+msgstr "Не атрымалася выдаліць файл."
+
+#: engines/groovie/script.cpp:420
+msgid "Failed to save game"
+msgstr "Не атрымалася захаваць гульню"
+
+#. I18N: Studio audience adds an applause and cheering sounds whenever
+#. Malcolm makes a joke.
+#: engines/kyra/detection.cpp:62
+msgid "Studio audience"
+msgstr "Студыйная аўдыторыя"
+
+#: engines/kyra/detection.cpp:63
+msgid "Enable studio audience"
+msgstr "Уключыць гукі аўдыторыі ў студыі"
+
+#. I18N: This option allows the user to skip text and cutscenes.
+#: engines/kyra/detection.cpp:73
+msgid "Skip support"
+msgstr "Падтрымка пропускаў"
+
+#: engines/kyra/detection.cpp:74
+msgid "Allow text and cutscenes to be skipped"
+msgstr "Уключае магчымасць прапускаць тэксты і застаўкі"
+
+#. I18N: Helium mode makes people sound like they've inhaled Helium.
+#: engines/kyra/detection.cpp:84
+msgid "Helium mode"
+msgstr "Рэжым гелія"
+
+#: engines/kyra/detection.cpp:85
+msgid "Enable helium mode"
+msgstr "Уключыць рэжым гелія"
+
+#. I18N: When enabled, this option makes scrolling smoother when
+#. changing from one screen to another.
+#: engines/kyra/detection.cpp:99
+msgid "Smooth scrolling"
+msgstr "Плыўная прагортка"
+
+#: engines/kyra/detection.cpp:100
+msgid "Enable smooth scrolling when walking"
+msgstr "Уключыць плыўную прагортку падчас хады"
+
+#. I18N: When enabled, this option changes the cursor when it floats to the
+#. edge of the screen to a directional arrow. The player can then click to
+#. walk towards that direction.
+#: engines/kyra/detection.cpp:112
+msgid "Floating cursors"
+msgstr "Плывучыя курсоры"
+
+#: engines/kyra/detection.cpp:113
+msgid "Enable floating cursors"
+msgstr "Уключыць плывучыя курсоры"
+
+#. I18N: HP stands for Hit Points
+#: engines/kyra/detection.cpp:127
+msgid "HP bar graphs"
+msgstr "Палоскі здароўя"
+
+#: engines/kyra/detection.cpp:128
+msgid "Enable hit point bar graphs"
+msgstr "Уключыць адлюстраванне палосак здароўя"
+
+#: engines/kyra/lol.cpp:478
+msgid "Attack 1"
+msgstr "Атака 1"
+
+#: engines/kyra/lol.cpp:479
+msgid "Attack 2"
+msgstr "Атака 2"
+
+#: engines/kyra/lol.cpp:480
+msgid "Attack 3"
+msgstr "Атака 3"
+
+#: engines/kyra/lol.cpp:481
+msgid "Move Forward"
+msgstr "Ісці наперад"
+
+#: engines/kyra/lol.cpp:482
+msgid "Move Back"
+msgstr "Ісці назад"
+
+#: engines/kyra/lol.cpp:483
+msgid "Slide Left"
+msgstr "Слізгаць налева"
+
+#: engines/kyra/lol.cpp:484
+msgid "Slide Right"
+msgstr "Слізгаць направа"
+
+#: engines/kyra/lol.cpp:485
+msgid "Turn Left"
+msgstr "Паварот налева"
+
+#: engines/kyra/lol.cpp:486
+msgid "Turn Right"
+msgstr "Паварот направа"
+
+#: engines/kyra/lol.cpp:487
+msgid "Rest"
+msgstr "Адпачыць"
+
+#: engines/kyra/lol.cpp:488
+msgid "Options"
+msgstr "Опцыі"
+
+#: engines/kyra/lol.cpp:489
+msgid "Choose Spell"
+msgstr "Абраць загавор"
+
+#: engines/kyra/sound_midi.cpp:475
+msgid ""
+"You appear to be using a General MIDI device,\n"
+"but your game only supports Roland MT32 MIDI.\n"
+"We try to map the Roland MT32 instruments to\n"
+"General MIDI ones. It is still possible that\n"
+"some tracks sound incorrect."
+msgstr ""
+"Здаецца, вы спрабуеце выкарыстоўваць прыладу\n"
+"General MIDI, але гэтая гульня падтрымлівае толькі\n"
+"Roland MT32 MIDI. Мы паспрабуем падабраць General\n"
+"MIDI прылады, падобныя на Roland MT32, але\n"
+"можа так атрымацца, што некаторыя трэкі будуць\n"
+"сыграны няправільна."
+
+#: engines/queen/queen.cpp:59
+msgid "Alternative intro"
+msgstr "Альтэрнатыўны ўступ"
+
+#: engines/queen/queen.cpp:60
+msgid "Use an alternative game intro (CD version only)"
+msgstr "Выкарыстоўваць альтэрнатыўны ўступ (толькі для CD версіі гульні)"
+
+#: engines/sky/compact.cpp:130
+msgid ""
+"Unable to find \"sky.cpt\" file!\n"
+"Please download it from www.scummvm.org"
+msgstr ""
+"Адсутнічае файл sky.cpt!\n"
+"Калі ласка, запампуйце яго з www.scummvm.org"
+
+#: engines/sky/compact.cpp:141
+msgid ""
+"The \"sky.cpt\" file has an incorrect size.\n"
+"Please (re)download it from www.scummvm.org"
+msgstr ""
+"Файл sky.cpt мае няправільны памер.\n"
+"Калі ласка, запампуйце яго нанова з www.scummvm.org"
+
+#: engines/sky/detection.cpp:44
+msgid "Floppy intro"
+msgstr "Уступ з дыскет"
+
+#: engines/sky/detection.cpp:45
+msgid "Use the floppy version's intro (CD version only)"
+msgstr "Выкарыстоўваць уступ з гнуткіх дыскаў (толькі для CD версіі гульні)"
+
+#: engines/sword1/animation.cpp:539
+#, c-format
+msgid "PSX stream cutscene '%s' cannot be played in paletted mode"
+msgstr "Застаўка PSX '%s' не можа быць прайграна ў рэжыме з палітрай"
+
+#: engines/sword1/animation.cpp:560 engines/sword2/animation.cpp:455
+msgid "DXA cutscenes found but ScummVM has been built without zlib support"
+msgstr ""
+"Знойдзены застаўкі ў фармаце DXA, але ScummVM быў сабраны без падтрымкі zlib"
+
+#: engines/sword1/animation.cpp:570 engines/sword2/animation.cpp:465
+msgid "MPEG2 cutscenes are no longer supported"
+msgstr "Застаўкі ў фармаце MPEG2 больш не падтрымліваюцца"
+
+#: engines/sword1/animation.cpp:576 engines/sword2/animation.cpp:473
+#, c-format
+msgid "Cutscene '%s' not found"
+msgstr "Застаўка '%s' не знойдзена"
+
+#: engines/sword1/control.cpp:863
+msgid ""
+"ScummVM found that you have old savefiles for Broken Sword 1 that should be "
+"converted.\n"
+"The old save game format is no longer supported, so you will not be able to "
+"load your games if you don't convert them.\n"
+"\n"
+"Press OK to convert them now, otherwise you will be asked again the next "
+"time you start the game.\n"
+msgstr ""
+"ScummVM выявіў у вас захаванні гульні Broken Sword 1 у старым фармаце.\n"
+"Стары фармат больш не падтрымліваецца, і, каб загрузіць захаванні, яны "
+"павінны быць пераведзены ў новы фармат.\n"
+"\n"
+"Націсніце ОК, каб перавесці іх у новы фармат зараз, у адваротным выпадку "
+"гэта паведамленне з'явіцца зноў пры наступным запуску гульні.\n"
+
+#: engines/sword1/control.cpp:1232
+#, c-format
+msgid ""
+"Target new save game already exists!\n"
+"Would you like to keep the old save game (%s) or the new one (%s)?\n"
+msgstr ""
+"Захаванне гульні з такім імем ужо існуе!\n"
+"Вы жадаеце пакінуць старую назву (%s) ці зрабіць новую (%s)?\n"
+
+#: engines/sword1/control.cpp:1235
+msgid "Keep the old one"
+msgstr "Пакінуць старое"
+
+#: engines/sword1/control.cpp:1235
+msgid "Keep the new one"
+msgstr "Зрабіць новае"
+
+#: engines/sword1/logic.cpp:1633
+msgid "This is the end of the Broken Sword 1 Demo"
+msgstr "Гэта завяршэнне дэма Broken Sword 1"
+
+#: engines/sword2/animation.cpp:435
+msgid ""
+"PSX cutscenes found but ScummVM has been built without RGB color support"
+msgstr ""
+"Знойдзены застаўкі ў фармаце PSX, але ScummVM быў сабраны без падтрымкі RGB "
+"колераў"
+
+#: engines/sword2/sword2.cpp:79
+msgid "Show object labels"
+msgstr "Паказваць назвы аб'ектаў"
+
+#: engines/sword2/sword2.cpp:80
+msgid "Show labels for objects on mouse hover"
+msgstr "Паказвае назвы аб'ектаў пры навядзенні курсора мышы"
+
+#: engines/teenagent/resources.cpp:68
+msgid ""
+"You're missing the 'teenagent.dat' file. Get it from the ScummVM website"
+msgstr ""
+"У вас адсутнічае файл 'teenagent.dat'. Запампуйце яго з вэб-сайта ScummVM"
+
+#: engines/teenagent/resources.cpp:89
+msgid ""
+"The teenagent.dat file is compressed and zlib hasn't been included in this "
+"executable. Please decompress it"
+msgstr ""
+"Файл teenagent.dat зжаты, але zlib не ўключана ў гэту праграму. Калі ласка, "
+"распакуйце яго"
+
+#: engines/parallaction/saveload.cpp:133
+#, c-format
+msgid ""
+"Can't save game in slot %i\n"
+"\n"
+msgstr ""
+"Не магу захаваць гульню ў пазіцыю %i\n"
+"\n"
+
+#: engines/parallaction/saveload.cpp:204
+msgid "Loading game..."
+msgstr "Загружаю гульню..."
+
+#: engines/parallaction/saveload.cpp:219
+msgid "Saving game..."
+msgstr "Захоўваю гульню..."
+
+#: engines/parallaction/saveload.cpp:272
+msgid ""
+"ScummVM found that you have old savefiles for Nippon Safes that should be "
+"renamed.\n"
+"The old names are no longer supported, so you will not be able to load your "
+"games if you don't convert them.\n"
+"\n"
+"Press OK to convert them now, otherwise you will be asked next time.\n"
+msgstr ""
+"ScummVM знайшоў у вас старыя захаванні гульні Nippon Safes, якія неабходна "
+"пераназваць. Старыя назвы больш не падтрымліваюцца, і таму вы не зможаце "
+"загрузіць захаванні, калі не пераназавеце іх.\n"
+"\n"
+"Націсніце ОК, каб пераназваць іх зараз, у адваротным выпадку гэта ж "
+"паведамленне з'явіцца пры наступным запуску гульні.\n"
+
+#: engines/parallaction/saveload.cpp:319
+msgid "ScummVM successfully converted all your savefiles."
+msgstr "ScummVM паспяхова пераўтварыў усе вашы захаванні гульняў."
+
+#: engines/parallaction/saveload.cpp:321
+msgid ""
+"ScummVM printed some warnings in your console window and can't guarantee all "
+"your files have been converted.\n"
+"\n"
+"Please report to the team."
+msgstr ""
+"ScummVM напісаў некалькі папярэджанняў у акно кансолі і не змог пераўтварыць "
+"усе файлы.\n"
+"\n"
+"Калі ласка, паведаміце пра гэта камандзе ScummVM."
+
+#: audio/fmopl.cpp:49
+msgid "MAME OPL emulator"
+msgstr "Эмулятар MAME OPL"
+
+#: audio/fmopl.cpp:51
+msgid "DOSBox OPL emulator"
+msgstr "Эмулятар DOSBox OPL"
+
+#: audio/mididrv.cpp:209
+#, c-format
+msgid ""
+"The selected audio device '%s' was not found (e.g. might be turned off or "
+"disconnected)."
+msgstr ""
+"Абраная гукавая прылада '%s' не была знойдзена (магчыма, яна выключана ці не "
+"падключана)."
+
+#: audio/mididrv.cpp:209 audio/mididrv.cpp:221 audio/mididrv.cpp:257
+#: audio/mididrv.cpp:272
+msgid "Attempting to fall back to the next available device..."
+msgstr "Спрабую выкарыстаць іншую даступную прыладу..."
+
+#: audio/mididrv.cpp:221
+#, c-format
+msgid ""
+"The selected audio device '%s' cannot be used. See log file for more "
+"information."
+msgstr ""
+"Абраная гукавая прылада '%s' не можа быць скарыстана. Глядзіце файл "
+"пратаколу для больш падрабязнай інфармацыі."
+
+#: audio/mididrv.cpp:257
+#, c-format
+msgid ""
+"The preferred audio device '%s' was not found (e.g. might be turned off or "
+"disconnected)."
+msgstr ""
+"Пераважная гукавая прылада '%s' не была знойдзена (магчыма, яна выключана ці "
+"не падключана)."
+
+#: audio/mididrv.cpp:272
+#, c-format
+msgid ""
+"The preferred audio device '%s' cannot be used. See log file for more "
+"information."
+msgstr ""
+"Пераважная гукавая прылада '%s' не можа быць скарыстана. Глядзіце файл "
+"пратаколу для больш падрабязнай інфармацыі."
+
+#: audio/null.h:43
+msgid "No music"
+msgstr "Без музыкі"
+
+#: audio/mods/paula.cpp:189
+msgid "Amiga Audio Emulator"
+msgstr "Эмулятар гуку Amiga"
+
+#: audio/softsynth/adlib.cpp:1593
+msgid "AdLib Emulator"
+msgstr "Эмулятар AdLib"
+
+#: audio/softsynth/appleiigs.cpp:33
+msgid "Apple II GS Emulator (NOT IMPLEMENTED)"
+msgstr "Эмулятар Apple II GS (адсутнічае)"
+
+#: audio/softsynth/sid.cpp:1430
+msgid "C64 Audio Emulator"
+msgstr "Эмулятар гуку C64"
+
+#: audio/softsynth/mt32.cpp:293
+msgid "Initializing MT-32 Emulator"
+msgstr "Наладжваю эмулятар MT-32"
+
+#: audio/softsynth/mt32.cpp:512
+msgid "MT-32 Emulator"
+msgstr "Эмулятар MT-32"
+
+#: audio/softsynth/pcspk.cpp:139
+msgid "PC Speaker Emulator"
+msgstr "Эмулятар PC спікера"
+
+#: audio/softsynth/pcspk.cpp:158
+msgid "IBM PCjr Emulator"
+msgstr "Эмулятар IBM PCjr"
+
+#: backends/keymapper/remap-dialog.cpp:47
+msgid "Keymap:"
+msgstr "Табліца клавіш:"
+
+#: backends/keymapper/remap-dialog.cpp:66
+msgid " (Effective)"
+msgstr " (Дзейсная)"
+
+#: backends/keymapper/remap-dialog.cpp:106
+msgid " (Active)"
+msgstr " (Актыўная)"
+
+#: backends/keymapper/remap-dialog.cpp:106
+msgid " (Blocked)"
+msgstr " (Заблакавана)"
+
+#: backends/keymapper/remap-dialog.cpp:119
+msgid " (Global)"
+msgstr " (Глабальная)"
+
+#: backends/keymapper/remap-dialog.cpp:127
+msgid " (Game)"
+msgstr " (Гульні)"
+
+#: backends/midi/windows.cpp:164
+msgid "Windows MIDI"
+msgstr "Windows MIDI"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:57
+msgid "ScummVM Main Menu"
+msgstr "Галоўнае меню ScummVM"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:63
+msgid "~L~eft handed mode"
+msgstr "Леварукі рэжым"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:64
+msgid "~I~ndy fight controls"
+msgstr "Кіраванне баямі ў Indy"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:65
+msgid "Show mouse cursor"
+msgstr "Паказваць курсор мышы"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:66
+msgid "Snap to edges"
+msgstr "Прымацаваць да меж"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:68
+msgid "Touch X Offset"
+msgstr "Зрушэнне дотыкаў па восі X"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:75
+msgid "Touch Y Offset"
+msgstr "Зрушэнне дотыкаў па восі Y"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:87
+msgid "Use laptop trackpad-style cursor control"
+msgstr "Выкарыстоўваць кіраванне курсорам як на трэкпадзе лэптопаў"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:88
+msgid "Tap for left click, double tap right click"
+msgstr "Тап для левай пстрычкі, падвойны тап для правай пстрычкі"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:90
+msgid "Sensitivity"
+msgstr "Адчувальнасць"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:99
+msgid "Initial top screen scale:"
+msgstr "Пачатковы маштаб верхняга экрана:"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:105
+msgid "Main screen scaling:"
+msgstr "Маштаб галоўнага экрана:"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:107
+msgid "Hardware scale (fast, but low quality)"
+msgstr "Хардварнае маштабаванне (хутка, але нізкай якасці)"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:108
+msgid "Software scale (good quality, but slower)"
+msgstr "Праграмнае маштабаванне (добрая якасць, але марудней)"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:109
+msgid "Unscaled (you must scroll left and right)"
+msgstr "Без маштабавання (трэба будзе пракручваць налева і направа)"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:111
+msgid "Brightness:"
+msgstr "Яркасць:"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:121
+msgid "High quality audio (slower) (reboot)"
+msgstr "Высокая якасць гуку (марудней) (рэбут)"
+
+#: backends/platform/ds/arm9/source/dsoptions.cpp:122
+msgid "Disable power off"
+msgstr "Забараніць выключэнне"
+
+#: backends/platform/iphone/osys_events.cpp:300
+msgid "Mouse-click-and-drag mode enabled."
+msgstr "Рэжым мышы націснуць-і-цягнуць уключаны."
+
+#: backends/platform/iphone/osys_events.cpp:302
+msgid "Mouse-click-and-drag mode disabled."
+msgstr "Рэжым мышы націснуць-і-цягнуць выключаны."
+
+#: backends/platform/iphone/osys_events.cpp:313
+msgid "Touchpad mode enabled."
+msgstr "Рэжым тачпада ўключаны."
+
+#: backends/platform/iphone/osys_events.cpp:315
+msgid "Touchpad mode disabled."
+msgstr "Рэжым тачпада выключаны."
+
+#: backends/platform/maemo/maemo.cpp:209
+msgid "Click Mode"
+msgstr "Рэжым пстрычкі"
+
+#: backends/platform/maemo/maemo.cpp:215
+#: backends/platform/symbian/src/SymbianActions.cpp:42
+#: backends/platform/wince/CEActionsPocket.cpp:60
+#: backends/platform/wince/CEActionsSmartphone.cpp:43
+#: backends/platform/bada/form.cpp:281
+msgid "Left Click"
+msgstr "Левая пстрычка"
+
+#: backends/platform/maemo/maemo.cpp:218
+msgid "Middle Click"
+msgstr "Сярэдняя пстрычка"
+
+#: backends/platform/maemo/maemo.cpp:221
+#: backends/platform/symbian/src/SymbianActions.cpp:43
+#: backends/platform/wince/CEActionsSmartphone.cpp:44
+#: backends/platform/bada/form.cpp:273
+msgid "Right Click"
+msgstr "Правая пстрычка"
+
+#: backends/platform/sdl/macosx/appmenu_osx.mm:78
+msgid "Hide ScummVM"
+msgstr "Схаваць ScummVM"
+
+#: backends/platform/sdl/macosx/appmenu_osx.mm:83
+msgid "Hide Others"
+msgstr "Схаваць астатнія"
+
+#: backends/platform/sdl/macosx/appmenu_osx.mm:88
+msgid "Show All"
+msgstr "Паказаць усё"
+
+#: backends/platform/sdl/macosx/appmenu_osx.mm:110
+#: backends/platform/sdl/macosx/appmenu_osx.mm:121
+msgid "Window"
+msgstr "Акно"
+
+#: backends/platform/sdl/macosx/appmenu_osx.mm:115
+msgid "Minimize"
+msgstr "Прыбраць у Dock"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:45
+msgid "Normal (no scaling)"
+msgstr "Без павелічэння"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:64
+msgctxt "lowres"
+msgid "Normal (no scaling)"
+msgstr "Без павелічэння"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2135
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:533
+msgid "Enabled aspect ratio correction"
+msgstr "Карэкцыя суадносін бакоў уключана"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2141
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:538
+msgid "Disabled aspect ratio correction"
+msgstr "Карэкцыя суадносін бакоў выключана"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2196
+msgid "Active graphics filter:"
+msgstr "Актыўны графічны фільтр:"
+
+#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2238
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:477
+msgid "Windowed mode"
+msgstr "Аконны рэжым"
+
+#: backends/graphics/opengl/opengl-graphics.cpp:135
+msgid "OpenGL Normal"
+msgstr "OpenGL без павелічэння"
+
+#: backends/graphics/opengl/opengl-graphics.cpp:136
+msgid "OpenGL Conserve"
+msgstr "OpenGL з захаваннем"
+
+#: backends/graphics/opengl/opengl-graphics.cpp:137
+msgid "OpenGL Original"
+msgstr "OpenGL першапачатковы"
+
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:415
+msgid "Current display mode"
+msgstr "Бягучы відэарэжым"
+
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:428
+msgid "Current scale"
+msgstr "Бягучы маштаб"
+
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:558
+msgid "Active filter mode: Linear"
+msgstr "Актыўны рэжым фільтра: Лінейны"
+
+#: backends/graphics/openglsdl/openglsdl-graphics.cpp:560
+msgid "Active filter mode: Nearest"
+msgstr "Актыўны рэжым фільтра: Найблізкі"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:38
+#: backends/platform/wince/CEActionsSmartphone.cpp:39
+msgid "Up"
+msgstr "Уверх"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:39
+#: backends/platform/wince/CEActionsSmartphone.cpp:40
+msgid "Down"
+msgstr "Уніз"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:40
+#: backends/platform/wince/CEActionsSmartphone.cpp:41
+msgid "Left"
+msgstr "Налева"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:41
+#: backends/platform/wince/CEActionsSmartphone.cpp:42
+msgid "Right"
+msgstr "Направа"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:46
+#: backends/platform/wince/CEActionsSmartphone.cpp:47
+msgid "Zone"
+msgstr "Зона"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:47
+#: backends/platform/wince/CEActionsPocket.cpp:54
+#: backends/platform/wince/CEActionsSmartphone.cpp:48
+msgid "Multi Function"
+msgstr "Мультыфункцыя"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:48
+msgid "Swap character"
+msgstr "Змяніць героя"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:49
+msgid "Skip text"
+msgstr "Прапусціць тэкст"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:51
+msgid "Fast mode"
+msgstr "Хуткі рэжым"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:53
+msgid "Debugger"
+msgstr "Адладчык"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:54
+msgid "Global menu"
+msgstr "Глабальнае меню"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:55
+msgid "Virtual keyboard"
+msgstr "Віртуальная клавіятура"
+
+#: backends/platform/symbian/src/SymbianActions.cpp:56
+msgid "Key mapper"
+msgstr "Прызначэнне клавіш"
+
+#: backends/events/symbiansdl/symbiansdl-events.cpp:184
+msgid "Do you want to quit ?"
+msgstr "Вы сапраўды жадаеце выйсці?"
+
+#: backends/platform/wii/options.cpp:51
+msgid "Video"
+msgstr "Відэа"
+
+#: backends/platform/wii/options.cpp:54
+msgid "Current video mode:"
+msgstr "Бягучы відэарэжым:"
+
+#: backends/platform/wii/options.cpp:56
+msgid "Double-strike"
+msgstr "Двайны ўдар"
+
+#: backends/platform/wii/options.cpp:60
+msgid "Horizontal underscan:"
+msgstr "Гарызантальны underscan:"
+
+#: backends/platform/wii/options.cpp:66
+msgid "Vertical underscan:"
+msgstr "Вертыкальны underscan:"
+
+#: backends/platform/wii/options.cpp:71
+msgid "Input"
+msgstr "Увод"
+
+#: backends/platform/wii/options.cpp:74
+msgid "GC Pad sensitivity:"
+msgstr "Адчувальнасць GC пада:"
+
+#: backends/platform/wii/options.cpp:80
+msgid "GC Pad acceleration:"
+msgstr "Паскарэнне GC пада:"
+
+#: backends/platform/wii/options.cpp:86
+msgid "DVD"
+msgstr "DVD"
+
+#: backends/platform/wii/options.cpp:89 backends/platform/wii/options.cpp:101
+msgid "Status:"
+msgstr "Стан:"
+
+#: backends/platform/wii/options.cpp:90 backends/platform/wii/options.cpp:102
+msgid "Unknown"
+msgstr "Невядома"
+
+#: backends/platform/wii/options.cpp:93
+msgid "Mount DVD"
+msgstr "Падключыць DVD"
+
+#: backends/platform/wii/options.cpp:94
+msgid "Unmount DVD"
+msgstr "Адключыць DVD"
+
+#: backends/platform/wii/options.cpp:98
+msgid "SMB"
+msgstr "SMB"
+
+#: backends/platform/wii/options.cpp:106
+msgid "Server:"
+msgstr "Сервер:"
+
+#: backends/platform/wii/options.cpp:110
+msgid "Share:"
+msgstr "Сеткавая тэчка:"
+
+#: backends/platform/wii/options.cpp:114
+msgid "Username:"
+msgstr "Карыстач:"
+
+#: backends/platform/wii/options.cpp:118
+msgid "Password:"
+msgstr "Пароль:"
+
+#: backends/platform/wii/options.cpp:121
+msgid "Init network"
+msgstr "Ініцыялізацыя сеткі"
+
+#: backends/platform/wii/options.cpp:123
+msgid "Mount SMB"
+msgstr "Падключыць SMB"
+
+#: backends/platform/wii/options.cpp:124
+msgid "Unmount SMB"
+msgstr "Адключыць SMB"
+
+#: backends/platform/wii/options.cpp:143
+msgid "DVD Mounted successfully"
+msgstr "DVD падключаны паспяхова"
+
+#: backends/platform/wii/options.cpp:146
+msgid "Error while mounting the DVD"
+msgstr "Памылка пры падключэнні DVD"
+
+#: backends/platform/wii/options.cpp:148
+msgid "DVD not mounted"
+msgstr "DVD не падключаны"
+
+#: backends/platform/wii/options.cpp:161
+msgid "Network up, share mounted"
+msgstr "Сетка працуе, тэчка падключана"
+
+#: backends/platform/wii/options.cpp:163
+msgid "Network up"
+msgstr "Сетка працуе"
+
+#: backends/platform/wii/options.cpp:166
+msgid ", error while mounting the share"
+msgstr ", памылка пры падключэнні тэчкі"
+
+#: backends/platform/wii/options.cpp:168
+msgid ", share not mounted"
+msgstr ", тэчка не падключана"
+
+#: backends/platform/wii/options.cpp:174
+msgid "Network down"
+msgstr "Сетка выключана"
+
+#: backends/platform/wii/options.cpp:178
+msgid "Initializing network"
+msgstr "Наладжваю сетку"
+
+#: backends/platform/wii/options.cpp:182
+msgid "Timeout while initializing network"
+msgstr "Час падключэння да сеткі мінуў"
+
+#: backends/platform/wii/options.cpp:186
+#, c-format
+msgid "Network not initialized (%d)"
+msgstr "Сетка не наладзілася (%d)"
+
+#: backends/platform/wince/CEActionsPocket.cpp:46
+msgid "Hide Toolbar"
+msgstr "Схаваць панэль інструментаў"
+
+#: backends/platform/wince/CEActionsPocket.cpp:47
+msgid "Show Keyboard"
+msgstr "Паказаць клавіятуру"
+
+#: backends/platform/wince/CEActionsPocket.cpp:48
+msgid "Sound on/off"
+msgstr "Гук укл/выкл"
+
+#: backends/platform/wince/CEActionsPocket.cpp:49
+msgid "Right click"
+msgstr "Правая пстрычка"
+
+#: backends/platform/wince/CEActionsPocket.cpp:50
+msgid "Show/Hide Cursor"
+msgstr "Паказаць/Прыбраць курсор"
+
+#: backends/platform/wince/CEActionsPocket.cpp:51
+msgid "Free look"
+msgstr "Вольны агляд"
+
+#: backends/platform/wince/CEActionsPocket.cpp:52
+msgid "Zoom up"
+msgstr "Павял. маштаб"
+
+#: backends/platform/wince/CEActionsPocket.cpp:53
+msgid "Zoom down"
+msgstr "Паменш. маштаб"
+
+#: backends/platform/wince/CEActionsPocket.cpp:55
+#: backends/platform/wince/CEActionsSmartphone.cpp:49
+msgid "Bind Keys"
+msgstr "Прызначыць клавішы"
+
+#: backends/platform/wince/CEActionsPocket.cpp:56
+msgid "Cursor Up"
+msgstr "Курсор уверх"
+
+#: backends/platform/wince/CEActionsPocket.cpp:57
+msgid "Cursor Down"
+msgstr "Курсор уніз"
+
+#: backends/platform/wince/CEActionsPocket.cpp:58
+msgid "Cursor Left"
+msgstr "Курсор налева"
+
+#: backends/platform/wince/CEActionsPocket.cpp:59
+msgid "Cursor Right"
+msgstr "Курсор направа"
+
+#: backends/platform/wince/CEActionsPocket.cpp:267
+#: backends/platform/wince/CEActionsSmartphone.cpp:231
+msgid "Do you want to load or save the game?"
+msgstr "Вы жадаеце загрузіць або захаваць гульню?"
+
+#: backends/platform/wince/CEActionsPocket.cpp:326
+#: backends/platform/wince/CEActionsSmartphone.cpp:287
+msgid " Are you sure you want to quit ? "
+msgstr " Вы ўпэўнены, што жадаеце выйсці? "
+
+#: backends/platform/wince/CEActionsSmartphone.cpp:50
+msgid "Keyboard"
+msgstr "Клавіятура"
+
+#: backends/platform/wince/CEActionsSmartphone.cpp:51
+msgid "Rotate"
+msgstr "Павярнуць"
+
+#: backends/platform/wince/CELauncherDialog.cpp:56
+msgid "Using SDL driver "
+msgstr "Выкарыстоўваю драйвер SDL "
+
+#: backends/platform/wince/CELauncherDialog.cpp:60
+msgid "Display "
+msgstr "Паказаць "
+
+#: backends/platform/wince/CELauncherDialog.cpp:83
+msgid "Do you want to perform an automatic scan ?"
+msgstr "Вы жадаеце зрабіць аўтаматычны пошук?"
+
+#: backends/platform/wince/wince-sdl.cpp:515
+msgid "Map right click action"
+msgstr "Прызначыць дзеянне па правай пстрычцы"
+
+#: backends/platform/wince/wince-sdl.cpp:519
+msgid "You must map a key to the 'Right Click' action to play this game"
+msgstr ""
+"Вы павінны прызначыць клавішу на дзеянне 'Right Click' для гэтай гульні"
+
+#: backends/platform/wince/wince-sdl.cpp:528
+msgid "Map hide toolbar action"
+msgstr "Прызначыць дзеянне 'схаваць панэль інструментаў'"
+
+#: backends/platform/wince/wince-sdl.cpp:532
+msgid "You must map a key to the 'Hide toolbar' action to play this game"
+msgstr ""
+"Вы павінны прызначыць клавішу на дзеянне 'Hide toolbar' для гэтай гульні"
+
+#: backends/platform/wince/wince-sdl.cpp:541
+msgid "Map Zoom Up action (optional)"
+msgstr "Прызначыць дзеянне Павялічыць Маштаб (неабавязкова)"
+
+#: backends/platform/wince/wince-sdl.cpp:544
+msgid "Map Zoom Down action (optional)"
+msgstr "Прызначыць дзеянне Паменшыць Маштаб (неабавязкова)"
+
+#: backends/platform/wince/wince-sdl.cpp:552
+msgid ""
+"Don't forget to map a key to 'Hide Toolbar' action to see the whole inventory"
+msgstr ""
+"Не забудзьцеся прызначыць клавішу для дзеяння 'Hide Toolbar', каб убачыць "
+"увесь інвентар у гульні"
+
+#: backends/events/default/default-events.cpp:191
+msgid "Do you really want to return to the Launcher?"
+msgstr "Вы сапраўды жадаеце вярнуцца ў галоўнае меню?"
+
+#: backends/events/default/default-events.cpp:191
+msgid "Launcher"
+msgstr "Галоўнае меню"
+
+#: backends/events/default/default-events.cpp:213
+msgid "Do you really want to quit?"
+msgstr "Вы сапраўды жадаеце выйсці?"
+
+#: backends/events/gph/gph-events.cpp:386
+#: backends/events/gph/gph-events.cpp:429
+#: backends/events/openpandora/op-events.cpp:139
+msgid "Touchscreen 'Tap Mode' - Left Click"
+msgstr "Рэжым 'дотыкаў' тачскрына - Левы клік"
+
+#: backends/events/gph/gph-events.cpp:388
+#: backends/events/gph/gph-events.cpp:431
+#: backends/events/openpandora/op-events.cpp:141
+msgid "Touchscreen 'Tap Mode' - Right Click"
+msgstr "Рэжым 'дотыкаў' тачскрына - Правы клік"
+
+#: backends/events/gph/gph-events.cpp:390
+#: backends/events/gph/gph-events.cpp:433
+#: backends/events/openpandora/op-events.cpp:143
+msgid "Touchscreen 'Tap Mode' - Hover (No Click)"
+msgstr "Рэжым 'дотыкаў' тачскрына - Пралёт (без кліку)"
+
+#: backends/events/gph/gph-events.cpp:410
+msgid "Maximum Volume"
+msgstr "Максімальная гучнасць"
+
+#: backends/events/gph/gph-events.cpp:412
+msgid "Increasing Volume"
+msgstr "Павелічэнне гучнасці"
+
+#: backends/events/gph/gph-events.cpp:418
+msgid "Minimal Volume"
+msgstr "Мінімальная гучнасць"
+
+#: backends/events/gph/gph-events.cpp:420
+msgid "Decreasing Volume"
+msgstr "Памяншэнне гучнасці"
+
+#: backends/updates/macosx/macosx-updates.mm:65
+msgid "Check for Updates..."
+msgstr "Правяраю абнаўленні..."
+
+#: backends/platform/bada/form.cpp:269
+msgid "Right Click Once"
+msgstr "Адна правая пстрычка"
+
+#: backends/platform/bada/form.cpp:277
+msgid "Move Only"
+msgstr "Толькі перамясціць"
+
+#: backends/platform/bada/form.cpp:291
+msgid "Escape Key"
+msgstr "Клавіша ESC"
+
+#: backends/platform/bada/form.cpp:296
+msgid "Game Menu"
+msgstr "Меню гульні"
+
+#: backends/platform/bada/form.cpp:301
+msgid "Show Keypad"
+msgstr "Паказаць клавіятуру"
+
+#: backends/platform/bada/form.cpp:309
+msgid "Control Mouse"
+msgstr "Кіраванне мышшу"
+
+#: backends/events/maemosdl/maemosdl-events.cpp:192
+msgid "Clicking Enabled"
+msgstr "Пстрычкі ўключаны"
+
+#: backends/events/maemosdl/maemosdl-events.cpp:192
+msgid "Clicking Disabled"
+msgstr "Пстрычкі выключаны"
+
+#~ msgid "Hercules Green"
+#~ msgstr "Hercules Зелёный"
+
+#~ msgid "Hercules Amber"
+#~ msgstr "Hercules Янтарный"
+
+#~ msgctxt "lowres"
+#~ msgid "Hercules Green"
+#~ msgstr "Hercules Зелёный"
+
+#~ msgctxt "lowres"
+#~ msgid "Hercules Amber"
+#~ msgstr "Hercules Янтарный"
+
+#~ msgid "Save game failed!"
+#~ msgstr "Не удалось сохранить игру!"
+
+#~ msgctxt "lowres"
+#~ msgid "Add Game..."
+#~ msgstr "Доб. игру"
+
+#~ msgid "Add Game..."
+#~ msgstr "Добавить игру..."
+
+#~ msgid "Discovered %d new games."
+#~ msgstr "Найдено %d новых игр."
+
+#~ msgid "Command line argument not processed"
+#~ msgstr "Параметры командной строки не обработаны"
+
+#~ msgid "FM Towns Emulator"
+#~ msgstr "Эмулятор FM Towns"
+
+#~ msgid "Invalid Path"
+#~ msgstr "Неверный путь"
diff --git a/test/common/bitstream.h b/test/common/bitstream.h
new file mode 100644
index 0000000000..3f6a15fcd8
--- /dev/null
+++ b/test/common/bitstream.h
@@ -0,0 +1,152 @@
+#include <cxxtest/TestSuite.h>
+
+#include "common/bitstream.h"
+#include "common/memstream.h"
+
+class BitStreamTestSuite : public CxxTest::TestSuite
+{
+ public:
+ void test_get_bit() {
+ byte contents[] = { 'a' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.getBit(), 0u);
+ TS_ASSERT_EQUALS(bs.getBit(), 1u);
+ TS_ASSERT_EQUALS(bs.getBit(), 1u);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_get_bits() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.getBits(3), 3u);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ TS_ASSERT_EQUALS(bs.getBits(8), 11u);
+ TS_ASSERT_EQUALS(bs.pos(), 11u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_skip() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ bs.skip(5);
+ TS_ASSERT_EQUALS(bs.pos(), 5u);
+ bs.skip(4);
+ TS_ASSERT_EQUALS(bs.pos(), 9u);
+ TS_ASSERT_EQUALS(bs.getBits(3), 6u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_rewind() {
+ byte contents[] = { 'a' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ bs.skip(5);
+ TS_ASSERT_EQUALS(bs.pos(), 5u);
+ bs.rewind();
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.getBits(3), 3u);
+ TS_ASSERT(!bs.eos());
+
+ TS_ASSERT_EQUALS(bs.size(), 8u);
+ }
+
+ void test_peek_bit() {
+ byte contents[] = { 'a' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.peekBit(), 0u);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.getBit(), 0u);
+ TS_ASSERT_EQUALS(bs.pos(), 1u);
+ TS_ASSERT_EQUALS(bs.peekBit(), 1u);
+ TS_ASSERT_EQUALS(bs.pos(), 1u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_peek_bits() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.peekBits(3), 3u);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ bs.skip(3);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ TS_ASSERT_EQUALS(bs.peekBits(8), 11u);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ bs.skip(8);
+ TS_ASSERT_EQUALS(bs.pos(), 11u);
+ TS_ASSERT_EQUALS(bs.peekBits(5), 2u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_eos() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8MSB bs(ms);
+ bs.skip(11);
+ TS_ASSERT_EQUALS(bs.pos(), 11u);
+ TS_ASSERT_EQUALS(bs.getBits(5), 2u);
+ TS_ASSERT(bs.eos());
+
+ bs.rewind();
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_get_bits_lsb() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8LSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.getBits(3), 1u);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ TS_ASSERT_EQUALS(bs.getBits(8), 76u);
+ TS_ASSERT_EQUALS(bs.pos(), 11u);
+ TS_ASSERT(!bs.eos());
+ }
+
+ void test_peek_bits_lsb() {
+ byte contents[] = { 'a', 'b' };
+
+ Common::MemoryReadStream ms(contents, sizeof(contents));
+
+ Common::BitStream8LSB bs(ms);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ TS_ASSERT_EQUALS(bs.peekBits(3), 1u);
+ TS_ASSERT_EQUALS(bs.pos(), 0u);
+ bs.skip(3);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ TS_ASSERT_EQUALS(bs.peekBits(8), 76u);
+ TS_ASSERT_EQUALS(bs.pos(), 3u);
+ bs.skip(8);
+ TS_ASSERT_EQUALS(bs.pos(), 11u);
+ TS_ASSERT_EQUALS(bs.peekBits(5), 12u);
+ TS_ASSERT(!bs.eos());
+ }
+};
diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp
index c5fcab4b49..9e26e96c7b 100644
--- a/video/qt_decoder.cpp
+++ b/video/qt_decoder.cpp
@@ -110,7 +110,7 @@ const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
return frame;
}
-Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format) {
+Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_VIDEO) {
debug(0, "Video Codec FourCC: \'%s\'", tag2str(format));
@@ -205,7 +205,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::Qu
}
// Pass it on up
- return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format);
+ return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format, descSize);
}
void QuickTimeDecoder::init() {
@@ -333,6 +333,7 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder
_scaledSurface = 0;
_curPalette = 0;
_dirtyPalette = false;
+ _reversed = false;
}
QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
@@ -344,26 +345,26 @@ QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const {
// A track is over when we've finished going through all edits
- return _curEdit == _parent->editCount;
+ return _reversed ? (_curEdit == 0 && _curFrame < 0) : atLastEdit();
}
bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requestedTime) {
uint32 convertedFrames = requestedTime.convertToFramerate(_decoder->_timeScale).totalNumberOfFrames();
- for (_curEdit = 0; !endOfTrack(); _curEdit++)
+ for (_curEdit = 0; !atLastEdit(); _curEdit++)
if (convertedFrames >= _parent->editList[_curEdit].timeOffset && convertedFrames < _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration)
break;
// If we did reach the end of the track, break out
- if (endOfTrack())
+ if (atLastEdit())
return true;
// If this track is in an empty edit, position us at the next non-empty
// edit. There's nothing else to do after this.
if (_parent->editList[_curEdit].mediaTime == -1) {
- while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1)
+ while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1)
_curEdit++;
- if (!endOfTrack())
+ if (!atLastEdit())
enterNewEditList(true);
return true;
@@ -372,7 +373,7 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested
enterNewEditList(false);
// One extra check for the end of a track
- if (endOfTrack())
+ if (atLastEdit())
return true;
// Now we're in the edit and need to figure out what frame we need
@@ -391,17 +392,24 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested
// Compare the starting point for the frame to where we need to be
_holdNextFrameStartTime = getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames();
- // If we went past the time, go back a frame
+ // If we went past the time, go back a frame. _curFrame before this point is at the frame
+ // that should be displayed. This adjustment ensures it is on the frame before the one that
+ // should be displayed.
if (_holdNextFrameStartTime)
_curFrame--;
- // Handle the keyframe here
- int32 destinationFrame = _curFrame + 1;
+ if (_reversed) {
+ // Call setReverse again to update
+ setReverse(true);
+ } else {
+ // Handle the keyframe here
+ int32 destinationFrame = _curFrame + 1;
- assert(destinationFrame < (int32)_parent->frameCount);
- _curFrame = findKeyFrame(destinationFrame) - 1;
- while (_curFrame < destinationFrame - 1)
- bufferNextFrame();
+ assert(destinationFrame < (int32)_parent->frameCount);
+ _curFrame = findKeyFrame(destinationFrame) - 1;
+ while (_curFrame < destinationFrame - 1)
+ bufferNextFrame();
+ }
return true;
}
@@ -438,27 +446,54 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame()
if (endOfTrack())
return 0;
+ if (_reversed) {
+ // Subtract one to place us on the frame before the current displayed frame.
+ _curFrame--;
+
+ // We have one "dummy" frame at the end to so the last frame is displayed
+ // for the right amount of time.
+ if (_curFrame < 0)
+ return 0;
+
+ // Decode from the last key frame to the frame before the one we need.
+ // TODO: Probably would be wise to do some caching
+ int targetFrame = _curFrame;
+ _curFrame = findKeyFrame(targetFrame) - 1;
+ while (_curFrame != targetFrame - 1)
+ bufferNextFrame();
+ }
+
const Graphics::Surface *frame = bufferNextFrame();
- if (_holdNextFrameStartTime) {
- // Don't set the next frame start time here; we just did a seek
- _holdNextFrameStartTime = false;
- } else if (_durationOverride >= 0) {
- // Use our own duration from the edit list calculation
- _nextFrameStartTime += _durationOverride;
- _durationOverride = -1;
+ if (_reversed) {
+ if (_holdNextFrameStartTime) {
+ // Don't set the next frame start time here; we just did a seek
+ _holdNextFrameStartTime = false;
+ } else {
+ // Just need to subtract the time
+ _nextFrameStartTime -= getFrameDuration();
+ }
} else {
- _nextFrameStartTime += getFrameDuration();
- }
+ if (_holdNextFrameStartTime) {
+ // Don't set the next frame start time here; we just did a seek
+ _holdNextFrameStartTime = false;
+ } else if (_durationOverride >= 0) {
+ // Use our own duration from the edit list calculation
+ _nextFrameStartTime += _durationOverride;
+ _durationOverride = -1;
+ } else {
+ _nextFrameStartTime += getFrameDuration();
+ }
- // Update the edit list, if applicable
- // HACK: We're also accepting the time minus one because edit lists
- // aren't as accurate as one would hope.
- if (!endOfTrack() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
- _curEdit++;
+ // Update the edit list, if applicable
+ // HACK: We're also accepting the time minus one because edit lists
+ // aren't as accurate as one would hope.
+ if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
+ _curEdit++;
- if (!endOfTrack())
- enterNewEditList(true);
+ if (!atLastEdit())
+ enterNewEditList(true);
+ }
}
if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) {
@@ -474,6 +509,68 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame()
return frame;
}
+bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) {
+ _reversed = reverse;
+
+ if (_reversed) {
+ if (_parent->editCount != 1) {
+ // TODO: Myst's holo.mov needs this :(
+ warning("Can only set reverse without edits");
+ return false;
+ }
+
+ if (atLastEdit()) {
+ // If we're at the end of the video, go to the penultimate edit.
+ // The current frame is set to one beyond the last frame here;
+ // one "past" the currently displayed frame.
+ _curEdit = _parent->editCount - 1;
+ _curFrame = _parent->frameCount;
+ _nextFrameStartTime = _parent->editList[_curEdit].trackDuration + _parent->editList[_curEdit].timeOffset;
+ } else if (_holdNextFrameStartTime) {
+ // We just seeked, so "pivot" around the frame that should be displayed
+ _curFrame++;
+ _nextFrameStartTime -= getFrameDuration();
+ _curFrame++;
+ } else {
+ // We need to put _curFrame to be the one after the one that should be displayed.
+ // Since we're on the frame that should be displaying right now, add one.
+ _curFrame++;
+ }
+ } else {
+ // Update the edit list, if applicable
+ // HACK: We're also accepting the time minus one because edit lists
+ // aren't as accurate as one would hope.
+ if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
+ _curEdit++;
+
+ if (atLastEdit())
+ return true;
+ }
+
+ if (_holdNextFrameStartTime) {
+ // We just seeked, so "pivot" around the frame that should be displayed
+ _curFrame--;
+ _nextFrameStartTime += getFrameDuration();
+ }
+
+ // We need to put _curFrame to be the one before the one that should be displayed.
+ // Since we're on the frame that should be displaying right now, subtract one.
+ // (As long as the current frame isn't -1, of course)
+ if (_curFrame > 0) {
+ // We then need to handle the keyframe situation
+ int targetFrame = _curFrame - 1;
+ _curFrame = findKeyFrame(targetFrame) - 1;
+ while (_curFrame < targetFrame)
+ bufferNextFrame();
+ } else if (_curFrame == 0) {
+ // Make us start at the first frame (no keyframe needed)
+ _curFrame--;
+ }
+ }
+
+ return true;
+}
+
Common::Rational QuickTimeDecoder::VideoTrackHandler::getScaledWidth() const {
return Common::Rational(_parent->width) / _parent->scaleFactorX;
}
@@ -555,10 +652,10 @@ uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const {
void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
// Bypass all empty edit lists first
- while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1)
+ while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1)
_curEdit++;
- if (endOfTrack())
+ if (atLastEdit())
return;
uint32 frameNum = 0;
@@ -675,4 +772,8 @@ uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const {
return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale;
}
+bool QuickTimeDecoder::VideoTrackHandler::atLastEdit() const {
+ return _curEdit == _parent->editCount;
+}
+
} // End of namespace Video
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 45ab155c2c..28314f2e63 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -70,7 +70,7 @@ public:
Audio::Timestamp getDuration() const { return Audio::Timestamp(0, _duration, _timeScale); }
protected:
- Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format);
+ Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize);
private:
void init();
@@ -136,6 +136,8 @@ private:
const Graphics::Surface *decodeNextFrame();
const byte *getPalette() const { _dirtyPalette = false; return _curPalette; }
bool hasDirtyPalette() const { return _curPalette; }
+ bool setReverse(bool reverse);
+ bool isReversed() const { return _reversed; }
Common::Rational getScaledWidth() const;
Common::Rational getScaledHeight() const;
@@ -151,6 +153,7 @@ private:
int32 _durationOverride;
const byte *_curPalette;
mutable bool _dirtyPalette;
+ bool _reversed;
Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
uint32 getFrameDuration();
@@ -160,6 +163,7 @@ private:
uint32 getRateAdjustedFrameTime() const;
uint32 getCurEditTimeOffset() const;
uint32 getCurEditTrackDuration() const;
+ bool atLastEdit() const;
};
};
diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp
index 4d9f19cfd9..ebe15c5fc1 100644
--- a/video/video_decoder.cpp
+++ b/video/video_decoder.cpp
@@ -188,6 +188,25 @@ const Graphics::Surface *VideoDecoder::decodeNextFrame() {
return frame;
}
+bool VideoDecoder::setReverse(bool reverse) {
+ // Can only reverse video-only videos
+ if (reverse && hasAudio())
+ return false;
+
+ // Attempt to make sure all the tracks are in the requested direction
+ for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) {
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo && ((VideoTrack *)*it)->isReversed() != reverse) {
+ if (!((VideoTrack *)*it)->setReverse(reverse))
+ return false;
+
+ _needsUpdate = true; // force an update
+ }
+ }
+
+ findNextVideoTrack();
+ return true;
+}
+
const byte *VideoDecoder::getPalette() {
_dirtyPalette = false;
return _palette;
@@ -218,7 +237,7 @@ uint32 VideoDecoder::getTime() const {
return _lastTimeChange.msecs();
if (isPaused())
- return (_playbackRate * (_pauseStartTime - _startTime)).toInt();
+ return MAX<int>((_playbackRate * (_pauseStartTime - _startTime)).toInt(), 0);
if (useAudioSync()) {
for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) {
@@ -231,20 +250,29 @@ uint32 VideoDecoder::getTime() const {
}
}
- return (_playbackRate * (g_system->getMillis() - _startTime)).toInt();
+ return MAX<int>((_playbackRate * (g_system->getMillis() - _startTime)).toInt(), 0);
}
uint32 VideoDecoder::getTimeToNextFrame() const {
if (endOfVideo() || _needsUpdate || !_nextVideoTrack)
return 0;
- uint32 elapsedTime = getTime();
+ uint32 currentTime = getTime();
uint32 nextFrameStartTime = _nextVideoTrack->getNextFrameStartTime();
- if (nextFrameStartTime <= elapsedTime)
+ if (_nextVideoTrack->isReversed()) {
+ // For reversed videos, we need to handle the time difference the opposite way.
+ if (nextFrameStartTime >= currentTime)
+ return 0;
+
+ return currentTime - nextFrameStartTime;
+ }
+
+ // Otherwise, handle it normally.
+ if (nextFrameStartTime <= currentTime)
return 0;
- return nextFrameStartTime - elapsedTime;
+ return nextFrameStartTime - currentTime;
}
bool VideoDecoder::endOfVideo() const {
@@ -318,7 +346,7 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) {
// Also reset our start time
if (isPlaying()) {
startAudio();
- _startTime = g_system->getMillis() - time.msecs();
+ _startTime = g_system->getMillis() - (time.msecs() / _playbackRate).toInt();
}
resetPauseStartTime();
@@ -402,9 +430,11 @@ void VideoDecoder::setRate(const Common::Rational &rate) {
Common::Rational targetRate = rate;
- if (rate < 0) {
- // TODO: Implement support for this
+ // Attempt to set the reverse
+ if (!setReverse(rate < 0)) {
+ assert(rate < 0); // We shouldn't fail for forward.
warning("Cannot set custom rate to backwards");
+ setReverse(false);
targetRate = 1;
if (_playbackRate == targetRate)
diff --git a/video/video_decoder.h b/video/video_decoder.h
index 5fec52cf42..d0a6e08005 100644
--- a/video/video_decoder.h
+++ b/video/video_decoder.h
@@ -353,6 +353,17 @@ public:
*/
void setDefaultHighColorFormat(const Graphics::PixelFormat &format) { _defaultHighColorFormat = format; }
+ /**
+ * Set the video to decode frames in reverse.
+ *
+ * By default, VideoDecoder will decode forward.
+ *
+ * @note This is used by setRate()
+ * @note This will not work if an audio track is present
+ * @param reverse true for reverse, false for forward
+ * @return true on success, false otherwise
+ */
+ bool setReverse(bool reverse);
/////////////////////////////////////////
// Audio Control
@@ -551,6 +562,21 @@ protected:
* should only be used by VideoDecoder::seekToFrame().
*/
virtual Audio::Timestamp getFrameTime(uint frame) const;
+
+ /**
+ * Set the video track to play in reverse or forward.
+ *
+ * By default, a VideoTrack must decode forward.
+ *
+ * @param reverse true for reverse, false for forward
+ * @return true for success, false for failure
+ */
+ virtual bool setReverse(bool reverse) { return !reverse; }
+
+ /**
+ * Is the video track set to play in reverse?
+ */
+ virtual bool isReversed() const { return false; }
};
/**