diff options
Diffstat (limited to 'backends')
441 files changed, 31888 insertions, 2128 deletions
diff --git a/backends/audiocd/audiocd-stream.cpp b/backends/audiocd/audiocd-stream.cpp new file mode 100644 index 0000000000..3c0d0957da --- /dev/null +++ b/backends/audiocd/audiocd-stream.cpp @@ -0,0 +1,199 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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 "backends/audiocd/audiocd-stream.h" +#include "common/textconsole.h" + +AudioCDStream::AudioCDStream() : _buffer(), _frame(0), _bufferPos(0), _bufferFrame(0), _forceStop(false) { +} + +AudioCDStream::~AudioCDStream() { + // Stop the timer; the subclass needs to do this, + // so this is just a last resort. + stopTimer(); + + // Clear any buffered frames + emptyQueue(); +} + +int AudioCDStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + // See if any data is left first + while (_bufferPos < kSamplesPerFrame && samples < numSamples) + buffer[samples++] = _buffer[_bufferPos++]; + + // Bail out if done + if (endOfData()) + return samples; + + while (samples < numSamples && !endOfData()) { + if (!readNextFrame()) + return samples; + + // Copy the samples over + for (_bufferPos = 0; _bufferPos < kSamplesPerFrame && samples < numSamples;) + buffer[samples++] = _buffer[_bufferPos++]; + } + + return samples; +} + +bool AudioCDStream::readNextFrame() { + // Fetch a frame from the queue + int16 *buffer; + + { + Common::StackLock lock(_mutex); + + // Nothing we can do if it's empty + if (_bufferQueue.empty()) + return false; + + buffer = _bufferQueue.pop(); + } + + memcpy(_buffer, buffer, kSamplesPerFrame * 2); + delete[] buffer; + _frame++; + return true; +} + +bool AudioCDStream::endOfData() const { + return !shouldForceStop() && getStartFrame() + _frame >= getEndFrame() && _bufferPos == kSamplesPerFrame; +} + +bool AudioCDStream::seek(const Audio::Timestamp &where) { + // Stop the timer + stopTimer(); + + // Clear anything out of the queue + emptyQueue(); + + // Convert to the frame number + // Really not much else needed + _bufferPos = kSamplesPerFrame; + _frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames(); + _bufferFrame = _frame; + + // Start the timer again + startTimer(); + return true; +} + +Audio::Timestamp AudioCDStream::getLength() const { + return Audio::Timestamp(0, getEndFrame() - getStartFrame(), kFramesPerSecond); +} + +void AudioCDStream::timerProc(void *refCon) { + static_cast<AudioCDStream *>(refCon)->onTimer(); +} + +void AudioCDStream::onTimer() { + // The goal here is to do as much work in this timer instead + // of doing it in the readBuffer() call, which is the mixer. + + // If we're done, bail. + if (shouldForceStop() || getStartFrame() + _bufferFrame >= getEndFrame()) + return; + + // Get a quick count of the number of items in the queue + // We don't care that much; we only need a quick estimate + _mutex.lock(); + uint32 queueCount = _bufferQueue.size(); + _mutex.unlock(); + + // If we have enough audio buffered, bail out + if (queueCount >= kBufferThreshold) + return; + + while (!shouldForceStop() && queueCount < kBufferThreshold && getStartFrame() + _bufferFrame < getEndFrame()) { + int16 *buffer = new int16[kSamplesPerFrame]; + + // Figure out the MSF of the frame we're looking for + int frame = _bufferFrame + getStartFrame(); + + // Request to read that frame + if (!readFrame(frame, buffer)) { + warning("Failed to read CD audio"); + forceStop(); + return; + } + + _bufferFrame++; + + // Now push the buffer onto the queue + Common::StackLock lock(_mutex); + _bufferQueue.push(buffer); + queueCount = _bufferQueue.size(); + } +} + +void AudioCDStream::startTimer(bool fillBuffer) { + _forceStop = false; + if (fillBuffer) { + onTimer(); + } + g_system->getTimerManager()->installTimerProc(timerProc, 10 * 1000, this, "AudioCDStream"); +} + +void AudioCDStream::stopTimer() { + forceStop(); + g_system->getTimerManager()->removeTimerProc(timerProc); +} + +void AudioCDStream::emptyQueue() { + while (!_bufferQueue.empty()) + delete[] _bufferQueue.pop(); +} + +bool AudioCDStream::shouldForceStop() const { + Common::StackLock lock(_forceStopMutex); + return _forceStop; +} + +void AudioCDStream::forceStop() { + Common::StackLock lock(_forceStopMutex); + _forceStop = true; +} diff --git a/backends/audiocd/audiocd-stream.h b/backends/audiocd/audiocd-stream.h new file mode 100644 index 0000000000..dbc6a6321b --- /dev/null +++ b/backends/audiocd/audiocd-stream.h @@ -0,0 +1,108 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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 BACKENDS_AUDIOCD_AUDIOCD_STREAM_H +#define BACKENDS_AUDIOCD_AUDIOCD_STREAM_H + +#include "audio/audiostream.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "common/timer.h" + +class AudioCDStream : public Audio::SeekableAudioStream { +public: + AudioCDStream(); + ~AudioCDStream(); + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return true; } + int getRate() const { return 44100; } + bool endOfData() const; + bool seek(const Audio::Timestamp &where); + Audio::Timestamp getLength() const; + +protected: + virtual uint getStartFrame() const = 0; + virtual uint getEndFrame() const = 0; + virtual bool readFrame(int frame, int16 *buffer) = 0; + + void startTimer(bool fillBuffer = false); + void stopTimer(); + + enum { + kBytesPerFrame = 2352, + kSamplesPerFrame = kBytesPerFrame / 2 + }; + + enum { + kSecondsPerMinute = 60, + kFramesPerSecond = 75 + }; + + enum { + // Keep about a second's worth of audio in the buffer + kBufferThreshold = kFramesPerSecond + }; + +private: + int16 _buffer[kSamplesPerFrame]; + int _frame; + uint _bufferPos; + + Common::Queue<int16 *> _bufferQueue; + int _bufferFrame; + Common::Mutex _mutex; + + bool _forceStop; + bool shouldForceStop() const; + void forceStop(); + Common::Mutex _forceStopMutex; + + bool readNextFrame(); + static void timerProc(void *refCon); + void onTimer(); + void emptyQueue(); +}; + +#endif diff --git a/backends/audiocd/audiocd.h b/backends/audiocd/audiocd.h index 6eae8e096b..b3674f2570 100644 --- a/backends/audiocd/audiocd.h +++ b/backends/audiocd/audiocd.h @@ -48,26 +48,31 @@ public: }; /** - * @name Emulated playback functions - * Engines should call these functions. Not all platforms - * support cd playback, and these functions should try to - * emulate it. + * Initialize the specified CD drive for audio playback. + * @return true if the CD drive was inited successfully + */ + virtual bool open() = 0; + + /** + * Close the currently open CD drive */ - //@{ + virtual void close() = 0; /** * Start audio CD playback - * @param track the track to play. - * @param numLoops how often playback should be repeated (-1 = infinitely often). - * @param startFrame the frame at which playback should start (75 frames = 1 second). - * @param duration the number of frames to play. - * @param only_emulate determines if the track should be emulated only + * @param track the track to play. + * @param numLoops how often playback should be repeated (<=0 means infinitely often). + * @param startFrame the frame at which playback should start (75 frames = 1 second). + * @param duration the number of frames to play. + * @param onlyEmulate determines if the track should be emulated only + * @note The @c onlyEmulate parameter is deprecated. + * @return @c true if the track started playing, @c false otherwise */ - virtual void play(int track, int numLoops, int startFrame, int duration, bool only_emulate = false) = 0; + virtual bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false) = 0; /** * Get if audio is being played. - * @return true if CD or emulated audio is playing + * @return true if CD audio is playing */ virtual bool isPlaying() const = 0; @@ -82,12 +87,12 @@ public: virtual void setBalance(int8 balance) = 0; /** - * Stop CD or emulated audio playback. + * Stop audio playback. */ virtual void stop() = 0; /** - * Update CD or emulated audio status. + * Update audio status. */ virtual void update() = 0; @@ -96,50 +101,6 @@ public: * @return a Status struct with playback data. */ virtual Status getStatus() const = 0; - - //@} - - - /** - * @name Real CD audio methods - * These functions should be called from the emulated - * ones if they can't emulate the audio playback. - */ - //@{ - - /** - * Initialize the specified CD drive for audio playback. - * @param drive the drive id - * @return true if the CD drive was inited successfully - */ - virtual bool openCD(int drive) = 0; - - /** - * Poll CD status. - * @return true if CD audio is playing - */ - virtual bool pollCD() const = 0; - - /** - * Start CD audio playback. - * @param track the track to play. - * @param num_loops how often playback should be repeated (-1 = infinitely often). - * @param start_frame the frame at which playback should start (75 frames = 1 second). - * @param duration the number of frames to play. - */ - virtual void playCD(int track, int num_loops, int start_frame, int duration) = 0; - - /** - * Stop CD audio playback. - */ - virtual void stopCD() = 0; - - /** - * Update CD audio status. - */ - virtual void updateCD() = 0; - - //@} }; #endif diff --git a/backends/audiocd/default/default-audiocd.cpp b/backends/audiocd/default/default-audiocd.cpp index abf80ac4cd..c2ce7cedcc 100644 --- a/backends/audiocd/default/default-audiocd.cpp +++ b/backends/audiocd/default/default-audiocd.cpp @@ -22,6 +22,7 @@ #include "backends/audiocd/default/default-audiocd.h" #include "audio/audiostream.h" +#include "common/config-manager.h" #include "common/system.h" DefaultAudioCDManager::DefaultAudioCDManager() { @@ -37,7 +38,25 @@ DefaultAudioCDManager::DefaultAudioCDManager() { assert(_mixer); } -void DefaultAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool only_emulate) { +DefaultAudioCDManager::~DefaultAudioCDManager() { + // Subclasses should call close as well + close(); +} + +bool DefaultAudioCDManager::open() { + // For emulation, opening is always valid + close(); + return true; +} + +void DefaultAudioCDManager::close() { + // Only need to stop for emulation + stop(); +} + +bool DefaultAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + stop(); + if (numLoops != 0 || startFrame != 0) { _cd.track = track; _cd.numLoops = numLoops; @@ -55,9 +74,6 @@ void DefaultAudioCDManager::play(int track, int numLoops, int startFrame, int du for (int i = 0; !stream && i < 2; ++i) stream = Audio::SeekableAudioStream::openStreamFile(trackName[i]); - // Stop any currently playing emulated track - _mixer->stopHandle(_handle); - if (stream != 0) { Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75); Audio::Timestamp end = duration ? Audio::Timestamp(0, startFrame + duration, 75) : stream->getLength(); @@ -70,12 +86,11 @@ void DefaultAudioCDManager::play(int track, int numLoops, int startFrame, int du _emulating = true; _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, Audio::makeLoopingAudioStream(stream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), -1, _cd.volume, _cd.balance); - } else { - _emulating = false; - if (!only_emulate) - playCD(track, numLoops, startFrame, duration); + return true; } } + + return false; } void DefaultAudioCDManager::stop() { @@ -83,52 +98,32 @@ void DefaultAudioCDManager::stop() { // Audio CD emulation _mixer->stopHandle(_handle); _emulating = false; - } else { - // Real Audio CD - stopCD(); } } bool DefaultAudioCDManager::isPlaying() const { - if (_emulating) { - // Audio CD emulation + // Audio CD emulation + if (_emulating) return _mixer->isSoundHandleActive(_handle); - } else { - // Real Audio CD - return pollCD(); - } + + // The default class only handles emulation + return false; } void DefaultAudioCDManager::setVolume(byte volume) { _cd.volume = volume; - if (_emulating) { - // Audio CD emulation - if (_mixer->isSoundHandleActive(_handle)) - _mixer->setChannelVolume(_handle, _cd.volume); - } else { - // Real Audio CD - // Unfortunately I can't implement this atm - // since SDL doesn't seem to offer an interface method for this. - - // g_system->setVolumeCD(_cd.volume); - } + // Audio CD emulation + if (_emulating && isPlaying()) + _mixer->setChannelVolume(_handle, _cd.volume); } void DefaultAudioCDManager::setBalance(int8 balance) { _cd.balance = balance; - if (_emulating) { - // Audio CD emulation - if (isPlaying()) - _mixer->setChannelBalance(_handle, _cd.balance); - } else { - // Real Audio CD - // Unfortunately I can't implement this atm - // since SDL doesn't seem to offer an interface method for this. - - // g_system->setBalanceCD(_cd.balance); - } + // Audio CD emulation + if (_emulating && isPlaying()) + _mixer->setChannelBalance(_handle, _cd.balance); } void DefaultAudioCDManager::update() { @@ -142,8 +137,6 @@ void DefaultAudioCDManager::update() { // or not. _emulating = false; } - } else { - updateCD(); } } @@ -152,3 +145,21 @@ DefaultAudioCDManager::Status DefaultAudioCDManager::getStatus() const { info.playing = isPlaying(); return info; } + +bool DefaultAudioCDManager::openRealCD() { + Common::String cdrom = ConfMan.get("cdrom"); + + // Try to parse it as an int + char *endPos; + int drive = strtol(cdrom.c_str(), &endPos, 0); + + // If not an integer, treat as a drive path + if (endPos == cdrom.c_str()) + return openCD(cdrom); + + if (drive < 0) + return false; + + return openCD(drive); +} + diff --git a/backends/audiocd/default/default-audiocd.h b/backends/audiocd/default/default-audiocd.h index 9e4ba6b33e..e3fbb4b5a1 100644 --- a/backends/audiocd/default/default-audiocd.h +++ b/backends/audiocd/default/default-audiocd.h @@ -26,29 +26,48 @@ #include "backends/audiocd/audiocd.h" #include "audio/mixer.h" +namespace Common { +class String; +} // End of namespace Common + /** * The default audio cd manager. Implements emulation of audio cd playback. */ class DefaultAudioCDManager : public AudioCDManager { public: DefaultAudioCDManager(); - virtual ~DefaultAudioCDManager() {} - - void play(int track, int numLoops, int startFrame, int duration, bool only_emulate = false); - void stop(); - bool isPlaying() const; - void setVolume(byte volume); - void setBalance(int8 balance); - void update(); + virtual ~DefaultAudioCDManager(); + + virtual bool open(); + virtual void close(); + virtual bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); + virtual void stop(); + virtual bool isPlaying() const; + virtual void setVolume(byte volume); + virtual void setBalance(int8 balance); + virtual void update(); virtual Status getStatus() const; // Subclasses should override for better status results +protected: + /** + * Open a CD using the cdrom config variable + */ + bool openRealCD(); + + /** + * Open a CD using the specified drive index + * @param drive The index of the drive + * @note The index is implementation-defined, but 0 is always the best choice + */ virtual bool openCD(int drive) { return false; } - virtual void updateCD() {} - virtual bool pollCD() const { return false; } - virtual void playCD(int track, int num_loops, int start_frame, int duration) {} - virtual void stopCD() {} -protected: + /** + * Open a CD from a specific drive + * @param drive The name of the drive/path + * @note The drive parameter is platform-specific + */ + virtual bool openCD(const Common::String &drive) { return false; } + Audio::SoundHandle _handle; bool _emulating; diff --git a/backends/audiocd/linux/linux-audiocd.cpp b/backends/audiocd/linux/linux-audiocd.cpp new file mode 100644 index 0000000000..caa0265637 --- /dev/null +++ b/backends/audiocd/linux/linux-audiocd.cpp @@ -0,0 +1,471 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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. + * + */ + +// Enable all forbidden symbols to allow us to include and use necessary APIs. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/audiocd/linux/linux-audiocd.h" + +#ifdef USE_LINUXCD + +#include "backends/audiocd/audiocd-stream.h" +#include "backends/audiocd/default/default-audiocd.h" +#include "common/array.h" +#include "common/config-manager.h" +#include "common/str.h" +#include "common/debug.h" + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <linux/cdrom.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +enum { + kLeadoutTrack = 0xAA +}; + +enum { + kBytesPerFrame = 2352, + kSamplesPerFrame = kBytesPerFrame / 2 +}; + +enum { + kSecondsPerMinute = 60, + kFramesPerSecond = 75 +}; + +enum { + // Keep about a second's worth of audio in the buffer + kBufferThreshold = kFramesPerSecond +}; + +static int getFrameCount(const cdrom_msf0 &msf) { + int time = msf.minute; + time *= kSecondsPerMinute; + time += msf.second; + time *= kFramesPerSecond; + time += msf.frame; + return time; +} + +// Helper function to convert an error code into a human-readable message +static Common::String getErrorMessage(int errorCode) { + char buf[256]; + buf[0] = 0; + +#ifdef _GNU_SOURCE + // glibc sucks + return Common::String(strerror_r(errorCode, buf, sizeof(buf))); +#else + strerror_r(errorCode, buf, sizeof(buf)); + return Common::String(buf); +#endif +} + +class LinuxAudioCDStream : public AudioCDStream { +public: + LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry); + ~LinuxAudioCDStream(); + +protected: + uint getStartFrame() const; + uint getEndFrame() const; + bool readFrame(int frame, int16 *buffer); + +private: + int _fd; + const cdrom_tocentry &_startEntry, &_endEntry; +}; + +LinuxAudioCDStream::LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry) : + _fd(fd), _startEntry(startEntry), _endEntry(endEntry) { + // We fill the buffer here already to prevent any out of sync issues due + // to the CD not yet having spun up. + startTimer(true); +} + +LinuxAudioCDStream::~LinuxAudioCDStream() { + stopTimer(); +} + +bool LinuxAudioCDStream::readFrame(int frame, int16 *buffer) { + // Create the argument + union { + cdrom_msf msf; + char buffer[kBytesPerFrame]; + } arg; + + int seconds = frame / kFramesPerSecond; + frame %= kFramesPerSecond; + int minutes = seconds / kSecondsPerMinute; + seconds %= kSecondsPerMinute; + + // Request to read that frame + // We don't use CDROMREADAUDIO, as it seems to cause kernel + // panics on ejecting discs. Probably bad to eject the disc + // while playing, but at least let's try to prevent that case. + arg.msf.cdmsf_min0 = minutes; + arg.msf.cdmsf_sec0 = seconds; + arg.msf.cdmsf_frame0 = frame; + // The "end" part is irrelevant (why isn't cdrom_msf0 the type + // instead?) + + if (ioctl(_fd, CDROMREADRAW, &arg) < 0) { + warning("Failed to CD read audio: %s", getErrorMessage(errno).c_str()); + return false; + } + + memcpy(buffer, arg.buffer, kBytesPerFrame); + return true; +} + +uint LinuxAudioCDStream::getStartFrame() const { + return getFrameCount(_startEntry.cdte_addr.msf); +} + +uint LinuxAudioCDStream::getEndFrame() const { + return getFrameCount(_endEntry.cdte_addr.msf); +} + + +class LinuxAudioCDManager : public DefaultAudioCDManager { +public: + LinuxAudioCDManager(); + ~LinuxAudioCDManager(); + + bool open(); + void close(); + bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); + +protected: + bool openCD(int drive); + bool openCD(const Common::String &drive); + +private: + struct Device { + Device(const Common::String &n, dev_t d) : name(n), device(d) {} + Common::String name; + dev_t device; + }; + + typedef Common::Array<Device> DeviceList; + DeviceList scanDevices(); + bool tryAddDrive(DeviceList &devices, const Common::String &drive); + bool tryAddDrive(DeviceList &devices, const Common::String &drive, dev_t device); + bool tryAddDrive(DeviceList &devices, dev_t device); + bool tryAddPath(DeviceList &devices, const Common::String &path); + bool tryAddGamePath(DeviceList &devices); + bool loadTOC(); + static bool hasDevice(const DeviceList &devices, dev_t device); + + int _fd; + cdrom_tochdr _tocHeader; + Common::Array<cdrom_tocentry> _tocEntries; +}; + +static bool isTrayEmpty(int errorNumber) { + switch (errorNumber) { + case EIO: + case ENOENT: + case EINVAL: +#ifdef ENOMEDIUM + case ENOMEDIUM: +#endif + return true; + } + + return false; +} + +LinuxAudioCDManager::LinuxAudioCDManager() { + _fd = -1; + memset(&_tocHeader, 0, sizeof(_tocHeader)); +} + +LinuxAudioCDManager::~LinuxAudioCDManager() { + close(); +} + +bool LinuxAudioCDManager::open() { + close(); + + if (openRealCD()) + return true; + + return DefaultAudioCDManager::open(); +} + +void LinuxAudioCDManager::close() { + DefaultAudioCDManager::close(); + + if (_fd < 0) + return; + + ::close(_fd); + memset(&_tocHeader, 0, sizeof(_tocHeader)); + _tocEntries.clear(); +} + +bool LinuxAudioCDManager::openCD(int drive) { + DeviceList devices = scanDevices(); + if (drive >= (int)devices.size()) + return false; + + _fd = ::open(devices[drive].name.c_str(), O_RDONLY | O_NONBLOCK, 0); + if (_fd < 0) + return false; + + if (!loadTOC()) { + close(); + return false; + } + + return true; +} + +bool LinuxAudioCDManager::openCD(const Common::String &drive) { + DeviceList devices; + if (!tryAddDrive(devices, drive) && !tryAddPath(devices, drive)) + return false; + + _fd = ::open(devices[0].name.c_str(), O_RDONLY | O_NONBLOCK, 0); + if (_fd < 0) + return false; + + if (!loadTOC()) { + close(); + return false; + } + + return true; +} + +bool LinuxAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + // Prefer emulation + if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate)) + return true; + + // If we're set to only emulate, or have no CD drive, return here + if (onlyEmulate || _fd < 0) + return false; + + // HACK: For now, just assume that track number is right + // That only works because ScummVM uses the wrong track number anyway + + if (track >= (int)_tocEntries.size() - 1) { + warning("No such track %d", track); + return false; + } + + // Bail if the track isn't an audio track + if ((_tocEntries[track].cdte_ctrl & 0x04) != 0) { + warning("Track %d is not audio", track); + return false; + } + + // Create the AudioStream and play it + debug(1, "Playing CD track %d", track); + + Audio::SeekableAudioStream *audioStream = new LinuxAudioCDStream(_fd, _tocEntries[track], _tocEntries[track + 1]); + + Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75); + Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75); + + // Fake emulation since we're really playing an AudioStream + _emulating = true; + + _mixer->playStream( + Audio::Mixer::kMusicSoundType, + &_handle, + Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), + -1, + _cd.volume, + _cd.balance, + DisposeAfterUse::YES, + true); + + return true; +} + +LinuxAudioCDManager::DeviceList LinuxAudioCDManager::scanDevices() { + DeviceList devices; + + // Try to use the game's path first as the device + tryAddGamePath(devices); + + // Try adding the default CD-ROM + tryAddDrive(devices, "/dev/cdrom"); + + // TODO: Try others? + + return devices; +} + +bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::String &drive) { + struct stat stbuf; + if (stat(drive.c_str(), &stbuf) < 0) + return false; + + // Must be a character or block device + if (!S_ISCHR(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) + return false; + + return tryAddDrive(devices, drive, stbuf.st_rdev); +} + +bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::String &drive, dev_t device) { + if (hasDevice(devices, device)) + return true; + + // Try opening the device and seeing if it is a CD-ROM drve + int fd = ::open(drive.c_str(), O_RDONLY | O_NONBLOCK, 0); + if (fd >= 0) { + cdrom_subchnl info; + info.cdsc_format = CDROM_MSF; + + bool isCD = ioctl(fd, CDROMSUBCHNL, &info) == 0 || isTrayEmpty(errno); + ::close(fd); + if (isCD) { + devices.push_back(Device(drive, device)); + return true; + } + } + + return false; +} + +bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, dev_t device) { + // Construct the block name + // TODO: libblkid's blkid_devno_to_devname is exactly what we look for. + // This requires an external dependency though. + Common::String name = Common::String::format("/dev/block/%d:%d", major(device), minor(device)); + + return tryAddDrive(devices, name, device); +} + +bool LinuxAudioCDManager::tryAddPath(DeviceList &devices, const Common::String &path) { + struct stat stbuf; + if (stat(path.c_str(), &stbuf) < 0) + return false; + + return tryAddDrive(devices, stbuf.st_dev); +} + +bool LinuxAudioCDManager::tryAddGamePath(DeviceList &devices) { + if (!ConfMan.hasKey("path")) + return false; + + return tryAddPath(devices, ConfMan.get("path")); +} + +bool LinuxAudioCDManager::loadTOC() { + if (_fd < 0) + return false; + + if (ioctl(_fd, CDROMREADTOCHDR, &_tocHeader) < 0) + return false; + + debug(4, "CD: Start Track: %d, End Track %d", _tocHeader.cdth_trk0, _tocHeader.cdth_trk1); + + for (int i = _tocHeader.cdth_trk0; i <= _tocHeader.cdth_trk1; i++) { + cdrom_tocentry entry; + memset(&entry, 0, sizeof(entry)); + entry.cdte_track = i; + entry.cdte_format = CDROM_MSF; + + if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0) + return false; + +#if 0 + debug("Entry:"); + debug("\tTrack: %d", entry.cdte_track); + debug("\tAdr: %d", entry.cdte_adr); + debug("\tCtrl: %d", entry.cdte_ctrl); + debug("\tFormat: %d", entry.cdte_format); + debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame); + debug("\tMode: %d\n", entry.cdte_datamode); +#endif + + _tocEntries.push_back(entry); + } + + // Fetch the leadout so we can get the length of the last frame + cdrom_tocentry entry; + memset(&entry, 0, sizeof(entry)); + entry.cdte_track = kLeadoutTrack; + entry.cdte_format = CDROM_MSF; + + if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0) + return false; + +#if 0 + debug("Lead out:"); + debug("\tTrack: %d", entry.cdte_track); + debug("\tAdr: %d", entry.cdte_adr); + debug("\tCtrl: %d", entry.cdte_ctrl); + debug("\tFormat: %d", entry.cdte_format); + debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame); + debug("\tMode: %d\n", entry.cdte_datamode); +#endif + + _tocEntries.push_back(entry); + return true; +} + +bool LinuxAudioCDManager::hasDevice(const DeviceList &devices, dev_t device) { + for (DeviceList::const_iterator it = devices.begin(); it != devices.end(); it++) + if (it->device == device) + return true; + + return false; +} + +AudioCDManager *createLinuxAudioCDManager() { + return new LinuxAudioCDManager(); +} + +#endif // USE_LINUXCD diff --git a/backends/audiocd/linux/linux-audiocd.h b/backends/audiocd/linux/linux-audiocd.h new file mode 100644 index 0000000000..09d6353991 --- /dev/null +++ b/backends/audiocd/linux/linux-audiocd.h @@ -0,0 +1,62 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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 BACKENDS_AUDIOCD_LINUX_H +#define BACKENDS_AUDIOCD_LINUX_H + +#include "common/scummsys.h" + +#ifdef USE_LINUXCD + +class AudioCDManager; + +/** + * Create an audio CD manager using the Linux CDROM API + */ +AudioCDManager *createLinuxAudioCDManager(); + +#endif + +#endif + diff --git a/backends/audiocd/macosx/macosx-audiocd.cpp b/backends/audiocd/macosx/macosx-audiocd.cpp new file mode 100644 index 0000000000..e8d41c3e10 --- /dev/null +++ b/backends/audiocd/macosx/macosx-audiocd.cpp @@ -0,0 +1,306 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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. + * + */ + +#ifdef MACOSX + +#include <sys/stat.h> +#include <sys/mount.h> +#include <limits.h> + +#include "common/scummsys.h" + +#include "audio/audiostream.h" +#include "audio/decoders/aiff.h" +#include "audio/timestamp.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/fs.h" +#include "common/hashmap.h" +#include "common/textconsole.h" +#include "backends/audiocd/default/default-audiocd.h" +#include "backends/audiocd/macosx/macosx-audiocd.h" +#include "backends/fs/stdiostream.h" + +// Partially based on SDL's code + +/** + * The Mac OS X audio cd manager. Implements real audio cd playback. + */ +class MacOSXAudioCDManager : public DefaultAudioCDManager { +public: + MacOSXAudioCDManager() {} + ~MacOSXAudioCDManager(); + + bool open(); + void close(); + bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); + +protected: + bool openCD(int drive); + bool openCD(const Common::String &drive); + +private: + struct Drive { + Drive(const Common::String &m, const Common::String &d, const Common::String &f) : + mountPoint(m), deviceName(d), fsType(f) {} + + Common::String mountPoint; + Common::String deviceName; + Common::String fsType; + }; + + typedef Common::Array<Drive> DriveList; + DriveList detectAllDrives(); + DriveList detectCDDADrives(); + + bool findTrackNames(const Common::String &drivePath); + + Common::HashMap<uint, Common::String> _trackMap; +}; + +MacOSXAudioCDManager::~MacOSXAudioCDManager() { + close(); +} + +bool MacOSXAudioCDManager::open() { + close(); + + if (openRealCD()) + return true; + + return DefaultAudioCDManager::open(); +} + +/** + * Find the base disk number of device name. + * Returns -1 if mount point is not /dev/disk* + */ +static int findBaseDiskNumber(const Common::String &diskName) { + if (!diskName.hasPrefix("/dev/disk")) + return -1; + + const char *startPtr = diskName.c_str() + 9; + char *endPtr; + int baseDiskNumber = strtol(startPtr, &endPtr, 10); + if (startPtr == endPtr) + return -1; + + return baseDiskNumber; +} + +bool MacOSXAudioCDManager::openCD(int drive) { + DriveList allDrives = detectAllDrives(); + if (allDrives.empty()) + return false; + + DriveList cddaDrives; + + // Try to get the volume related to the game's path + if (ConfMan.hasKey("path")) { + Common::String gamePath = ConfMan.get("path"); + struct statfs gamePathStat; + if (statfs(gamePath.c_str(), &gamePathStat) == 0) { + int baseDiskNumber = findBaseDiskNumber(gamePathStat.f_mntfromname); + if (baseDiskNumber >= 0) { + // Look for a CDDA drive with the same base disk number + for (uint32 i = 0; i < allDrives.size(); i++) { + if (allDrives[i].fsType == "cddafs" && findBaseDiskNumber(allDrives[i].deviceName) == baseDiskNumber) { + debug(1, "Preferring drive '%s'", allDrives[i].mountPoint.c_str()); + cddaDrives.push_back(allDrives[i]); + allDrives.remove_at(i); + break; + } + } + } + } + } + + // Add the remaining CDDA drives to the CDDA list + for (uint32 i = 0; i < allDrives.size(); i++) + if (allDrives[i].fsType == "cddafs") + cddaDrives.push_back(allDrives[i]); + + if (drive >= (int)cddaDrives.size()) + return false; + + debug(1, "Using '%s' as the CD drive", cddaDrives[drive].mountPoint.c_str()); + + return findTrackNames(cddaDrives[drive].mountPoint); +} + +bool MacOSXAudioCDManager::openCD(const Common::String &drive) { + DriveList drives = detectAllDrives(); + + for (uint32 i = 0; i < drives.size(); i++) { + if (drives[i].fsType != "cddafs") + continue; + + if (drives[i].mountPoint == drive || drives[i].deviceName == drive) { + debug(1, "Using '%s' as the CD drive", drives[i].mountPoint.c_str()); + return findTrackNames(drives[i].mountPoint); + } + } + + return false; +} + +void MacOSXAudioCDManager::close() { + DefaultAudioCDManager::close(); + _trackMap.clear(); +} + +enum { + // Some crazy high number that we'll never actually hit + kMaxDriveCount = 256 +}; + +MacOSXAudioCDManager::DriveList MacOSXAudioCDManager::detectAllDrives() { + // Fetch the lists of drives + struct statfs driveStats[kMaxDriveCount]; + int foundDrives = getfsstat(driveStats, sizeof(driveStats), MNT_WAIT); + if (foundDrives <= 0) + return DriveList(); + + DriveList drives; + for (int i = 0; i < foundDrives; i++) + drives.push_back(Drive(driveStats[i].f_mntonname, driveStats[i].f_mntfromname, driveStats[i].f_fstypename)); + + return drives; +} + +bool MacOSXAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + // Prefer emulation + if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate)) + return true; + + // If we're set to only emulate, or have no CD drive, return here + if (onlyEmulate || !_trackMap.contains(track)) + return false; + + if (!numLoops && !startFrame) + return false; + + // Now load the AIFF track from the name + Common::String fileName = _trackMap[track]; + Common::SeekableReadStream *stream = StdioStream::makeFromPath(fileName.c_str(), false); + + if (!stream) { + warning("Failed to open track '%s'", fileName.c_str()); + return false; + } + + Audio::AudioStream *audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES); + if (!audioStream) { + warning("Track '%s' is not an AIFF track", fileName.c_str()); + return false; + } + + Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audioStream); + if (!seekStream) { + warning("Track '%s' is not seekable", fileName.c_str()); + return false; + } + + Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75); + Audio::Timestamp end = duration ? Audio::Timestamp(0, startFrame + duration, 75) : seekStream->getLength(); + + // Fake emulation since we're really playing an AIFF file + _emulating = true; + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, + Audio::makeLoopingAudioStream(seekStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), -1, _cd.volume, _cd.balance); + return true; +} + +bool MacOSXAudioCDManager::findTrackNames(const Common::String &drivePath) { + Common::FSNode directory(drivePath); + + if (!directory.exists()) { + warning("Directory '%s' does not exist", drivePath.c_str()); + return false; + } + + if (!directory.isDirectory()) { + warning("'%s' is not a directory", drivePath.c_str()); + return false; + } + + Common::FSList children; + if (!directory.getChildren(children, Common::FSNode::kListFilesOnly)) { + warning("Failed to find children for '%s'", drivePath.c_str()); + return false; + } + + for (uint32 i = 0; i < children.size(); i++) { + if (!children[i].isDirectory()) { + Common::String fileName = children[i].getName(); + + if (fileName.hasSuffix(".aiff") || fileName.hasSuffix(".cdda")) { + uint j = 0; + + // Search for the track ID in the file name. + for (; j < fileName.size() && !Common::isDigit(fileName[j]); j++) + ; + + const char *trackIDString = fileName.c_str() + j; + char *endPtr = nullptr; + long trackID = strtol(trackIDString, &endPtr, 10); + + if (trackIDString != endPtr && trackID > 0 && trackID < UINT_MAX) { + _trackMap[trackID - 1] = drivePath + '/' + fileName; + } else { + warning("Invalid track file name: '%s'", fileName.c_str()); + } + } + } + } + + return true; +} + +AudioCDManager *createMacOSXAudioCDManager() { + return new MacOSXAudioCDManager(); +} + +#endif // MACOSX diff --git a/backends/audiocd/macosx/macosx-audiocd.h b/backends/audiocd/macosx/macosx-audiocd.h new file mode 100644 index 0000000000..55b8c7b8c6 --- /dev/null +++ b/backends/audiocd/macosx/macosx-audiocd.h @@ -0,0 +1,61 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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 BACKENDS_AUDIOCD_MACOSX_H +#define BACKENDS_AUDIOCD_MACOSX_H + +#include "common/scummsys.h" + +#ifdef MACOSX + +class AudioCDManager; + +/** + * Create an audio CD manager for Mac OS X + */ +AudioCDManager *createMacOSXAudioCDManager(); + +#endif + +#endif // diff --git a/backends/audiocd/sdl/sdl-audiocd.cpp b/backends/audiocd/sdl/sdl-audiocd.cpp index 5093c03a1c..3558fb5671 100644 --- a/backends/audiocd/sdl/sdl-audiocd.cpp +++ b/backends/audiocd/sdl/sdl-audiocd.cpp @@ -24,9 +24,12 @@ #if defined(SDL_BACKEND) -#include "common/textconsole.h" #include "backends/audiocd/sdl/sdl-audiocd.h" +#if !SDL_VERSION_ATLEAST(2, 0, 0) + +#include "common/textconsole.h" + SdlAudioCDManager::SdlAudioCDManager() : _cdrom(0), @@ -40,10 +43,16 @@ SdlAudioCDManager::SdlAudioCDManager() } SdlAudioCDManager::~SdlAudioCDManager() { - if (_cdrom) { - SDL_CDStop(_cdrom); - SDL_CDClose(_cdrom); - } + close(); +} + +bool SdlAudioCDManager::open() { + close(); + + if (openRealCD()) + return true; + + return DefaultAudioCDManager::open(); } bool SdlAudioCDManager::openCD(int drive) { @@ -64,44 +73,69 @@ bool SdlAudioCDManager::openCD(int drive) { return (_cdrom != NULL); } -void SdlAudioCDManager::stopCD() { +void SdlAudioCDManager::close() { + DefaultAudioCDManager::close(); + + if (_cdrom) { + SDL_CDStop(_cdrom); + SDL_CDClose(_cdrom); + _cdrom = 0; + } +} + +void SdlAudioCDManager::stop() { + DefaultAudioCDManager::stop(); + // Stop CD Audio in 1/10th of a second _cdStopTime = SDL_GetTicks() + 100; _cdNumLoops = 0; } -void SdlAudioCDManager::playCD(int track, int num_loops, int start_frame, int duration) { - if (!num_loops && !start_frame) - return; +bool SdlAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + // Prefer emulation + if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate)) + return true; - if (!_cdrom) - return; + // If we're set to only emulate, or have no CD, return here + if (onlyEmulate || !_cdrom) + return false; + if (!numLoops && !startFrame) + return false; + + // FIXME: Explain this. if (duration > 0) duration += 5; _cdTrack = track; - _cdNumLoops = num_loops; - _cdStartFrame = start_frame; + _cdNumLoops = numLoops; + _cdStartFrame = startFrame; SDL_CDStatus(_cdrom); - if (start_frame == 0 && duration == 0) + if (startFrame == 0 && duration == 0) SDL_CDPlayTracks(_cdrom, track, 0, 1, 0); else - SDL_CDPlayTracks(_cdrom, track, start_frame, 0, duration); + SDL_CDPlayTracks(_cdrom, track, startFrame, 0, duration); _cdDuration = duration; _cdStopTime = 0; _cdEndTime = SDL_GetTicks() + _cdrom->track[track].length * 1000 / CD_FPS; + + return true; } -bool SdlAudioCDManager::pollCD() const { +bool SdlAudioCDManager::isPlaying() const { + if (DefaultAudioCDManager::isPlaying()) + return true; + if (!_cdrom) return false; return (_cdNumLoops != 0 && (SDL_GetTicks() < _cdEndTime || SDL_CDStatus(_cdrom) == CD_PLAYING)); } -void SdlAudioCDManager::updateCD() { +void SdlAudioCDManager::update() { + DefaultAudioCDManager::update(); + if (!_cdrom) return; @@ -133,4 +167,6 @@ void SdlAudioCDManager::updateCD() { } } +#endif // !SDL_VERSION_ATLEAST(2, 0, 0) + #endif diff --git a/backends/audiocd/sdl/sdl-audiocd.h b/backends/audiocd/sdl/sdl-audiocd.h index ff98fcdd77..91895dac99 100644 --- a/backends/audiocd/sdl/sdl-audiocd.h +++ b/backends/audiocd/sdl/sdl-audiocd.h @@ -27,6 +27,8 @@ #include "backends/platform/sdl/sdl-sys.h" +#if !SDL_VERSION_ATLEAST(2, 0, 0) + /** * The SDL audio cd manager. Implements real audio cd playback. */ @@ -35,16 +37,21 @@ public: SdlAudioCDManager(); virtual ~SdlAudioCDManager(); + virtual bool open(); + virtual void close(); + virtual bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); + virtual void stop(); + virtual bool isPlaying() const; + virtual void update(); + protected: virtual bool openCD(int drive); - virtual void updateCD(); - virtual bool pollCD() const; - virtual void playCD(int track, int num_loops, int start_frame, int duration); - virtual void stopCD(); SDL_CD *_cdrom; int _cdTrack, _cdNumLoops, _cdStartFrame, _cdDuration; uint32 _cdEndTime, _cdStopTime; }; +#endif // !SDL_VERSION_ATLEAST(2, 0, 0) + #endif diff --git a/backends/audiocd/win32/msvc/ntddcdrm.h b/backends/audiocd/win32/msvc/ntddcdrm.h new file mode 100644 index 0000000000..18527e2675 --- /dev/null +++ b/backends/audiocd/win32/msvc/ntddcdrm.h @@ -0,0 +1,362 @@ +/** + * @file ntddcdrm.h + * Copyright 2012, 2013 MinGW.org project + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/* Created by Casper S. Hornstrup <chorns@users.sourceforge.net> */ +#ifndef __NTDDCDRM_H +#define __NTDDCDRM_H +#if 0 // Added to make MSVC happy. +#pragma GCC system_header +#include <_mingw.h> +#endif + +/* + * CDROM IOCTL interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 // Added to make MSVC happy. +#include "ntddk.h" +#include "ntddstor.h" +#endif + +#define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM + +#define IOCTL_CDROM_CHECK_VERIFY \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0200, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_FIND_NEW_DEVICES \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0206, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_GET_CONTROL \ + CTL_CODE(IOCTL_CDROM_BASE, 0x000D, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_GET_DRIVE_GEOMETRY \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0013, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_GET_LAST_SESSION \ + CTL_CODE(IOCTL_CDROM_BASE, 0x000E, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_GET_VOLUME \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0005, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_PAUSE_AUDIO \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0003, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_PLAY_AUDIO_MSF \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0006, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_RAW_READ \ + CTL_CODE(IOCTL_CDROM_BASE, 0x000F, METHOD_OUT_DIRECT, FILE_READ_ACCESS) + +#define IOCTL_CDROM_READ_Q_CHANNEL \ + CTL_CODE(IOCTL_CDROM_BASE, 0x000B, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_READ_TOC \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0000, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_READ_TOC_EX \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0015, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_RESUME_AUDIO \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0004, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_SEEK_AUDIO_MSF \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0001, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_SET_VOLUME \ + CTL_CODE(IOCTL_CDROM_BASE, 0x000A, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_SIMBAD \ + CTL_CODE(IOCTL_CDROM_BASE, 0x1003, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_CDROM_STOP_AUDIO \ + CTL_CODE(IOCTL_CDROM_BASE, 0x0002, METHOD_BUFFERED, FILE_READ_ACCESS) + + +#define MAXIMUM_NUMBER_TRACKS 100 +#define MAXIMUM_CDROM_SIZE 804 +#define MINIMUM_CDROM_READ_TOC_EX_SIZE 2 + +typedef struct _TRACK_DATA { + UCHAR Reserved; + UCHAR Control : 4; + UCHAR Adr : 4; + UCHAR TrackNumber; + UCHAR Reserved1; + UCHAR Address[4]; +} TRACK_DATA, *PTRACK_DATA; + +/* CDROM_DISK_DATA.DiskData flags */ +#define CDROM_DISK_AUDIO_TRACK 0x00000001 +#define CDROM_DISK_DATA_TRACK 0x00000002 + +typedef struct _CDROM_DISK_DATA { + ULONG DiskData; +} CDROM_DISK_DATA, *PCDROM_DISK_DATA; + +typedef struct _CDROM_PLAY_AUDIO_MSF { + UCHAR StartingM; + UCHAR StartingS; + UCHAR StartingF; + UCHAR EndingM; + UCHAR EndingS; + UCHAR EndingF; +} CDROM_PLAY_AUDIO_MSF, *PCDROM_PLAY_AUDIO_MSF; + +/* CDROM_READ_TOC_EX.Format constants */ +#define CDROM_READ_TOC_EX_FORMAT_TOC 0x00 +#define CDROM_READ_TOC_EX_FORMAT_SESSION 0x01 +#define CDROM_READ_TOC_EX_FORMAT_FULL_TOC 0x02 +#define CDROM_READ_TOC_EX_FORMAT_PMA 0x03 +#define CDROM_READ_TOC_EX_FORMAT_ATIP 0x04 +#define CDROM_READ_TOC_EX_FORMAT_CDTEXT 0x05 + +typedef struct _CDROM_READ_TOC_EX { + UCHAR Format : 4; + UCHAR Reserved1 : 3; + UCHAR Msf : 1; + UCHAR SessionTrack; + UCHAR Reserved2; + UCHAR Reserved3; +} CDROM_READ_TOC_EX, *PCDROM_READ_TOC_EX; + +typedef struct _CDROM_SEEK_AUDIO_MSF { + UCHAR M; + UCHAR S; + UCHAR F; +} CDROM_SEEK_AUDIO_MSF, *PCDROM_SEEK_AUDIO_MSF; + +/* CDROM_SUB_Q_DATA_FORMAT.Format constants */ +#define IOCTL_CDROM_SUB_Q_CHANNEL 0x00 +#define IOCTL_CDROM_CURRENT_POSITION 0x01 +#define IOCTL_CDROM_MEDIA_CATALOG 0x02 +#define IOCTL_CDROM_TRACK_ISRC 0x03 + +typedef struct _CDROM_SUB_Q_DATA_FORMAT { + UCHAR Format; + UCHAR Track; +} CDROM_SUB_Q_DATA_FORMAT, *PCDROM_SUB_Q_DATA_FORMAT; + +typedef struct _CDROM_TOC { + UCHAR Length[2]; + UCHAR FirstTrack; + UCHAR LastTrack; + TRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS]; +} CDROM_TOC, *PCDROM_TOC; + +#define CDROM_TOC_SIZE sizeof(CDROM_TOC) + +typedef struct _CDROM_TOC_ATIP_DATA_BLOCK { + UCHAR CdrwReferenceSpeed : 3; + UCHAR Reserved3 : 1; + UCHAR WritePower : 3; + UCHAR True1 : 1; + UCHAR Reserved4 : 6; + UCHAR UnrestrictedUse : 1; + UCHAR Reserved5 : 1; + UCHAR A3Valid : 1; + UCHAR A2Valid : 1; + UCHAR A1Valid : 1; + UCHAR Reserved6 : 3; + UCHAR IsCdrw : 1; + UCHAR True2 : 1; + UCHAR Reserved7; + UCHAR LeadInMsf[3]; + UCHAR Reserved8; + UCHAR LeadOutMsf[3]; + UCHAR Reserved9; + UCHAR A1Values[3]; + UCHAR Reserved10; + UCHAR A2Values[3]; + UCHAR Reserved11; + UCHAR A3Values[3]; + UCHAR Reserved12; +} CDROM_TOC_ATIP_DATA_BLOCK, *PCDROM_TOC_ATIP_DATA_BLOCK; + +#if 0 // Added to make MSVC happy. +typedef struct _CDROM_TOC_ATIP_DATA { + UCHAR Length[2]; + UCHAR Reserved1; + UCHAR Reserved2; + CDROM_TOC_ATIP_DATA_BLOCK Descriptors[0]; + CDROM_TOC_ATIP_DATA_BLOCK Descriptors[1]; +} CDROM_TOC_ATIP_DATA, *PCDROM_TOC_ATIP_DATA; +#endif + +/* CDROM_TOC_CD_TEXT_DATA_BLOCK.PackType constants */ +#define CDROM_CD_TEXT_PACK_ALBUM_NAME 0x80 +#define CDROM_CD_TEXT_PACK_PERFORMER 0x81 +#define CDROM_CD_TEXT_PACK_SONGWRITER 0x82 +#define CDROM_CD_TEXT_PACK_COMPOSER 0x83 +#define CDROM_CD_TEXT_PACK_ARRANGER 0x84 +#define CDROM_CD_TEXT_PACK_MESSAGES 0x85 +#define CDROM_CD_TEXT_PACK_DISC_ID 0x86 +#define CDROM_CD_TEXT_PACK_GENRE 0x87 +#define CDROM_CD_TEXT_PACK_TOC_INFO 0x88 +#define CDROM_CD_TEXT_PACK_TOC_INFO2 0x89 +#define CDROM_CD_TEXT_PACK_UPC_EAN 0x8e +#define CDROM_CD_TEXT_PACK_SIZE_INFO 0x8f + +#if 0 // Added to make MSVC happy. +typedef struct _CDROM_TOC_CD_TEXT_DATA_BLOCK { + UCHAR PackType; + UCHAR TrackNumber : 7; + UCHAR ExtensionFlag : 1; + UCHAR SequenceNumber; + UCHAR CharacterPosition : 4; + UCHAR BlockNumber : 3; + UCHAR Unicode : 1; + _ANONYMOUS_UNION union { + UCHAR Text[12]; + WCHAR WText[6]; + } DUMMYUNIONNAME; + UCHAR CRC[2]; +} CDROM_TOC_CD_TEXT_DATA_BLOCK, *PCDROM_TOC_CD_TEXT_DATA_BLOCK; + +typedef struct _CDROM_TOC_CD_TEXT_DATA { + UCHAR Length[2]; + UCHAR Reserved1; + UCHAR Reserved2; + CDROM_TOC_CD_TEXT_DATA_BLOCK Descriptors[0]; +} CDROM_TOC_CD_TEXT_DATA, *PCDROM_TOC_CD_TEXT_DATA; +#endif + +/* CDROM_TOC_FULL_TOC_DATA_BLOCK.Adr constants */ +#define ADR_NO_MODE_INFORMATION 0x0 +#define ADR_ENCODES_CURRENT_POSITION 0x1 +#define ADR_ENCODES_MEDIA_CATALOG 0x2 +#define ADR_ENCODES_ISRC 0x3 + +typedef struct _CDROM_TOC_FULL_TOC_DATA_BLOCK { + UCHAR SessionNumber; + UCHAR Control : 4; + UCHAR Adr : 4; + UCHAR Reserved1; + UCHAR Point; + UCHAR MsfExtra[3]; + UCHAR Zero; + UCHAR Msf[3]; +} CDROM_TOC_FULL_TOC_DATA_BLOCK, *PCDROM_TOC_FULL_TOC_DATA_BLOCK; + +#if 0 // Added to make MSVC happy. +typedef struct _CDROM_TOC_FULL_TOC_DATA { + UCHAR Length[2]; + UCHAR FirstCompleteSession; + UCHAR LastCompleteSession; + CDROM_TOC_FULL_TOC_DATA_BLOCK Descriptors[0]; +} CDROM_TOC_FULL_TOC_DATA, *PCDROM_TOC_FULL_TOC_DATA; + +typedef struct _CDROM_TOC_PMA_DATA { + UCHAR Length[2]; + UCHAR Reserved1; + UCHAR Reserved2; + CDROM_TOC_FULL_TOC_DATA_BLOCK Descriptors[0]; +} CDROM_TOC_PMA_DATA, *PCDROM_TOC_PMA_DATA; +#endif + +/* SUB_Q_HEADER.AudioStatus constants */ +#define AUDIO_STATUS_NOT_SUPPORTED 0x00 +#define AUDIO_STATUS_IN_PROGRESS 0x11 +#define AUDIO_STATUS_PAUSED 0x12 +#define AUDIO_STATUS_PLAY_COMPLETE 0x13 +#define AUDIO_STATUS_PLAY_ERROR 0x14 +#define AUDIO_STATUS_NO_STATUS 0x15 + +typedef struct _SUB_Q_HEADER { + UCHAR Reserved; + UCHAR AudioStatus; + UCHAR DataLength[2]; +} SUB_Q_HEADER, *PSUB_Q_HEADER; + +typedef struct _SUB_Q_MEDIA_CATALOG_NUMBER { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Reserved[3]; + UCHAR Reserved1 : 7; + UCHAR Mcval :1; + UCHAR MediaCatalog[15]; +} SUB_Q_MEDIA_CATALOG_NUMBER, *PSUB_Q_MEDIA_CATALOG_NUMBER; + +typedef struct _SUB_Q_TRACK_ISRC { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Reserved0; + UCHAR Track; + UCHAR Reserved1; + UCHAR Reserved2 : 7; + UCHAR Tcval : 1; + UCHAR TrackIsrc[15]; +} SUB_Q_TRACK_ISRC, *PSUB_Q_TRACK_ISRC; + +typedef struct _SUB_Q_CURRENT_POSITION { + SUB_Q_HEADER Header; + UCHAR FormatCode; + UCHAR Control : 4; + UCHAR ADR : 4; + UCHAR TrackNumber; + UCHAR IndexNumber; + UCHAR AbsoluteAddress[4]; + UCHAR TrackRelativeAddress[4]; +} SUB_Q_CURRENT_POSITION, *PSUB_Q_CURRENT_POSITION; + +typedef union _SUB_Q_CHANNEL_DATA { + SUB_Q_CURRENT_POSITION CurrentPosition; + SUB_Q_MEDIA_CATALOG_NUMBER MediaCatalog; + SUB_Q_TRACK_ISRC TrackIsrc; +} SUB_Q_CHANNEL_DATA, *PSUB_Q_CHANNEL_DATA; + +/* CDROM_AUDIO_CONTROL.LbaFormat constants */ +#define AUDIO_WITH_PREEMPHASIS 0x1 +#define DIGITAL_COPY_PERMITTED 0x2 +#define AUDIO_DATA_TRACK 0x4 +#define TWO_FOUR_CHANNEL_AUDIO 0x8 + +typedef struct _CDROM_AUDIO_CONTROL { + UCHAR LbaFormat; + USHORT LogicalBlocksPerSecond; +} CDROM_AUDIO_CONTROL, *PCDROM_AUDIO_CONTROL; + +typedef struct _VOLUME_CONTROL { + UCHAR PortVolume[4]; +} VOLUME_CONTROL, *PVOLUME_CONTROL; + +typedef enum _TRACK_MODE_TYPE { + YellowMode2, + XAForm2, + CDDA +} TRACK_MODE_TYPE, *PTRACK_MODE_TYPE; + +typedef struct __RAW_READ_INFO { + LARGE_INTEGER DiskOffset; + ULONG SectorCount; + TRACK_MODE_TYPE TrackMode; +} RAW_READ_INFO, *PRAW_READ_INFO; + +#ifdef __cplusplus +} +#endif + +#endif /* __NTDDCDRM_H */ diff --git a/backends/audiocd/win32/win32-audiocd.cpp b/backends/audiocd/win32/win32-audiocd.cpp new file mode 100644 index 0000000000..6c057efdb7 --- /dev/null +++ b/backends/audiocd/win32/win32-audiocd.cpp @@ -0,0 +1,388 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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. + * + */ + +#ifdef WIN32 + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef ARRAYSIZE // winnt.h defines ARRAYSIZE, but we want our own one... + +#include "backends/audiocd/win32/win32-audiocd.h" + +#include "audio/audiostream.h" +#include "backends/audiocd/audiocd-stream.h" +#include "backends/audiocd/default/default-audiocd.h" +#include "common/array.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "common/str.h" +#include "common/timer.h" + +#include <winioctl.h> +#if _MSC_VER < 1900 +// WORKAROUND: Older versions of MSVC might not supply DDK headers by default. +// Visual Studio 2015 contains the required headers. We use a compatability +// header from MinGW's w32api for all older versions. +// TODO: Limit this to the Visual Studio versions which actually require this. +#include "msvc/ntddcdrm.h" +#elif defined(__MINGW32__) && !defined(__MINGW64__) +// Classic MinGW uses non standard paths for DDK headers. +#include <ddk/ntddcdrm.h> +#else +#include <ntddcdrm.h> +#endif + +class Win32AudioCDStream : public AudioCDStream { +public: + Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry); + ~Win32AudioCDStream(); + +protected: + uint getStartFrame() const; + uint getEndFrame() const; + bool readFrame(int frame, int16 *buffer); + +private: + HANDLE _driveHandle; + const TRACK_DATA &_startEntry, &_endEntry; + + enum { + // The CD-ROM pre-gap is 2s + kPreGapFrames = kFramesPerSecond * 2 + }; + + static int getFrameCount(const TRACK_DATA &data) { + int time = data.Address[1]; + time *= kSecondsPerMinute; + time += data.Address[2]; + time *= kFramesPerSecond; + time += data.Address[3]; + return time; + } +}; + +Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry) : + _driveHandle(handle), _startEntry(startEntry), _endEntry(endEntry) { + // We fill the buffer here already to prevent any out of sync issues due + // to the CD not yet having spun up. + startTimer(true); +} + +Win32AudioCDStream::~Win32AudioCDStream() { + stopTimer(); +} + +uint Win32AudioCDStream::getStartFrame() const { + return getFrameCount(_startEntry); +} + +uint Win32AudioCDStream::getEndFrame() const { + return getFrameCount(_endEntry); +} + +bool Win32AudioCDStream::readFrame(int frame, int16 *buffer) { + // Request to read that frame + RAW_READ_INFO readAudio; + memset(&readAudio, 0, sizeof(readAudio)); + readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048; + readAudio.SectorCount = 1; + readAudio.TrackMode = CDDA; + + DWORD bytesReturned; + return DeviceIoControl( + _driveHandle, + IOCTL_CDROM_RAW_READ, + &readAudio, + sizeof(readAudio), + buffer, + kBytesPerFrame, + &bytesReturned, + NULL); +} + + +class Win32AudioCDManager : public DefaultAudioCDManager { +public: + Win32AudioCDManager(); + ~Win32AudioCDManager(); + + bool open(); + void close(); + bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); + +protected: + bool openCD(int drive); + bool openCD(const Common::String &drive); + +private: + bool loadTOC(); + + typedef Common::Array<char> DriveList; + DriveList detectDrives(); + bool tryAddDrive(char drive, DriveList &drives); + + HANDLE _driveHandle; + int _firstTrack, _lastTrack; + Common::Array<TRACK_DATA> _tocEntries; +}; + +Win32AudioCDManager::Win32AudioCDManager() { + _driveHandle = INVALID_HANDLE_VALUE; + _firstTrack = _lastTrack = 0; +} + +Win32AudioCDManager::~Win32AudioCDManager() { + close(); +} + +bool Win32AudioCDManager::open() { + close(); + + if (openRealCD()) + return true; + + return DefaultAudioCDManager::open(); +} + +bool Win32AudioCDManager::openCD(int drive) { + // Fetch the drive list + DriveList drives = detectDrives(); + if (drive >= (int)drives.size()) + return false; + + debug(1, "Opening CD drive %c:\\", drives[drive]); + + // Construct the drive path and try to open it + Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[drive]); + _driveHandle = CreateFileA(drivePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (_driveHandle == INVALID_HANDLE_VALUE) { + warning("Failed to open drive %c:\\, error %d", drives[drive], (int)GetLastError()); + return false; + } + + if (!loadTOC()) { + close(); + return false; + } + + return true; +} + +bool Win32AudioCDManager::openCD(const Common::String &drive) { + // Just some bounds checking + if (drive.empty() || drive.size() > 3) + return false; + + if (!Common::isAlpha(drive[0]) || drive[1] != ':') + return false; + + if (drive[2] != 0 && drive[2] != '\\') + return false; + + DriveList drives; + if (!tryAddDrive(toupper(drive[0]), drives)) + return false; + + // Construct the drive path and try to open it + Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[0]); + _driveHandle = CreateFileA(drivePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (_driveHandle == INVALID_HANDLE_VALUE) { + warning("Failed to open drive %c:\\, error %d", drives[0], (int)GetLastError()); + return false; + } + + if (!loadTOC()) { + close(); + return false; + } + + return true; +} + +void Win32AudioCDManager::close() { + DefaultAudioCDManager::close(); + + if (_driveHandle != INVALID_HANDLE_VALUE) { + CloseHandle(_driveHandle); + _driveHandle = INVALID_HANDLE_VALUE; + } + + _firstTrack = _lastTrack = 0; + _tocEntries.clear(); +} + +bool Win32AudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + // Prefer emulation + if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate)) + return true; + + // If we're set to only emulate, or have no CD drive, return here + if (onlyEmulate || _driveHandle == INVALID_HANDLE_VALUE) + return false; + + // HACK: For now, just assume that track number is right + // That only works because ScummVM uses the wrong track number anyway + + if (track >= (int)_tocEntries.size() - 1) { + warning("No such track %d", track); + return false; + } + + // Bail if the track isn't an audio track + if ((_tocEntries[track].Control & 0x04) != 0) { + warning("Track %d is not audio", track); + return false; + } + + // Create the AudioStream and play it + debug(1, "Playing CD track %d", track); + + Audio::SeekableAudioStream *audioStream = new Win32AudioCDStream(_driveHandle, _tocEntries[track], _tocEntries[track + 1]); + + Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75); + Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75); + + // Fake emulation since we're really playing an AudioStream + _emulating = true; + + _mixer->playStream( + Audio::Mixer::kMusicSoundType, + &_handle, + Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), + -1, + _cd.volume, + _cd.balance, + DisposeAfterUse::YES, + true); + return true; +} + +bool Win32AudioCDManager::loadTOC() { + CDROM_READ_TOC_EX tocRequest; + memset(&tocRequest, 0, sizeof(tocRequest)); + tocRequest.Format = CDROM_READ_TOC_EX_FORMAT_TOC; + tocRequest.Msf = 1; + tocRequest.SessionTrack = 0; + + DWORD bytesReturned; + CDROM_TOC tocData; + bool result = DeviceIoControl( + _driveHandle, + IOCTL_CDROM_READ_TOC_EX, + &tocRequest, + sizeof(tocRequest), + &tocData, + sizeof(tocData), + &bytesReturned, + NULL); + if (!result) { + debug("Failed to query the CD TOC: %d", (int)GetLastError()); + return false; + } + + _firstTrack = tocData.FirstTrack; + _lastTrack = tocData.LastTrack; +#if 0 + debug("First Track: %d", tocData.FirstTrack); + debug("Last Track: %d", tocData.LastTrack); +#endif + + for (uint32 i = 0; i < (bytesReturned - 4) / sizeof(TRACK_DATA); i++) + _tocEntries.push_back(tocData.TrackData[i]); + +#if 0 + for (uint32 i = 0; i < _tocEntries.size(); i++) { + const TRACK_DATA &entry = _tocEntries[i]; + debug("Entry:"); + debug("\tTrack: %d", entry.TrackNumber); + debug("\tAdr: %d", entry.Adr); + debug("\tCtrl: %d", entry.Control); + debug("\tMSF: %d:%d:%d\n", entry.Address[1], entry.Address[2], entry.Address[3]); + } +#endif + + return true; +} + +Win32AudioCDManager::DriveList Win32AudioCDManager::detectDrives() { + DriveList drives; + + // Try to get the game path's drive + char gameDrive = 0; + if (ConfMan.hasKey("path")) { + Common::String gamePath = ConfMan.get("path"); + char fullPath[MAX_PATH]; + DWORD result = GetFullPathNameA(gamePath.c_str(), sizeof(fullPath), fullPath, 0); + + if (result > 0 && result < sizeof(fullPath) && Common::isAlpha(fullPath[0]) && fullPath[1] == ':' && tryAddDrive(toupper(fullPath[0]), drives)) + gameDrive = drives[0]; + } + + // Try adding the rest of the drives + for (char drive = 'A'; drive <= 'Z'; drive++) + if (drive != gameDrive) + tryAddDrive(drive, drives); + + return drives; +} + +bool Win32AudioCDManager::tryAddDrive(char drive, DriveList &drives) { + Common::String drivePath = Common::String::format("%c:\\", drive); + + // Ensure it's an actual CD drive + if (GetDriveTypeA(drivePath.c_str()) != DRIVE_CDROM) + return false; + + debug(2, "Detected drive %c:\\ as a CD drive", drive); + drives.push_back(drive); + return true; +} + +AudioCDManager *createWin32AudioCDManager() { + return new Win32AudioCDManager(); +} + +#endif // WIN32
\ No newline at end of file diff --git a/backends/audiocd/win32/win32-audiocd.h b/backends/audiocd/win32/win32-audiocd.h new file mode 100644 index 0000000000..0c103641ef --- /dev/null +++ b/backends/audiocd/win32/win32-audiocd.h @@ -0,0 +1,60 @@ +/* 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. + * + * Original license header: + * + * Cabal - Legacy Game Implementations + * + * Cabal 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 BACKENDS_AUDIOCD_WIN32_H +#define BACKENDS_AUDIOCD_WIN32_H + +#ifdef WIN32 + +class AudioCDManager; + +/** + * Create an AudioCDManager using the Win32 API + */ +AudioCDManager *createWin32AudioCDManager(); + +#endif + +#endif + diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp index 3e95c3e26a..dfb9e284ce 100644 --- a/backends/base-backend.cpp +++ b/backends/base-backend.cpp @@ -39,6 +39,20 @@ void BaseBackend::displayMessageOnOSD(const char *msg) { dialog.runModal(); } +void BaseBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + warning("BaseBackend::copyRectToOSD not implemented"); //TODO +} + +void BaseBackend::clearOSD() { + warning("BaseBackend::clearOSD not implemented"); //TODO + //what should I do? Remove all TimedMessageDialogs? +} + +Graphics::PixelFormat BaseBackend::getOSDFormat() { + warning("BaseBackend::getOSDFormat not implemented"); + return Graphics::PixelFormat(); +} + void BaseBackend::initBackend() { // Init Event manager #ifndef DISABLE_DEFAULT_EVENT_MANAGER diff --git a/backends/base-backend.h b/backends/base-backend.h index 598f682b32..2394edaf38 100644 --- a/backends/base-backend.h +++ b/backends/base-backend.h @@ -33,6 +33,9 @@ public: virtual void initBackend(); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); virtual void fillScreen(uint32 col); }; diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.cpp b/backends/cloud/box/boxlistdirectorybyidrequest.cpp new file mode 100644 index 0000000000..c013f1eb2a --- /dev/null +++ b/backends/cloud/box/boxlistdirectorybyidrequest.cpp @@ -0,0 +1,195 @@ +/* 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 "backends/cloud/box/boxlistdirectorybyidrequest.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Box { + +#define BOX_LIST_DIRECTORY_LIMIT 1000 +#define BOX_FOLDERS_API_LINK "https://api.box.com/2.0/folders/%s/items?offset=%u&limit=%u&fields=%s" + +BoxListDirectoryByIdRequest::BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +BoxListDirectoryByIdRequest::~BoxListDirectoryByIdRequest() { + _ignoreCallback = true; + if (_workingRequest) _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void BoxListDirectoryByIdRequest::start() { + _ignoreCallback = true; + if (_workingRequest) _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + makeRequest(0); +} + +void BoxListDirectoryByIdRequest::makeRequest(uint32 offset) { + Common::String url = Common::String::format( + BOX_FOLDERS_API_LINK, + _requestedId.c_str(), + offset, + BOX_LIST_DIRECTORY_LIMIT, + "id,type,name,size,modified_at" + ); + + Networking::JsonCallback callback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::JsonResponse>(this, &BoxListDirectoryByIdRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::ErrorResponse>(this, &BoxListDirectoryByIdRequest::errorCallback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void BoxListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) { + delete response.value; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject responseObject = json->asObject(); + //debug(9, "%s", json->stringify(true).c_str()); + + //TODO: handle error messages passed as JSON + /* + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + warning("Box returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + */ + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (responseObject.contains("entries")) { + if (!responseObject.getVal("entries")->isArray()) { + error.response = Common::String::format( + "\"entries\" found, but that's not an array!\n%s", + responseObject.getVal("entries")->stringify(true).c_str() + ); + finishError(error); + delete json; + return; + } + + Common::JSONArray items = responseObject.getVal("entries")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "BoxListDirectoryByIdRequest")) continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxListDirectoryByIdRequest")) continue; + + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("type")->asString() == "folder"); + uint32 size; + if (item.getVal("size")->isString()) { + size = item.getVal("size")->asString().asUint64(); + } else { + size = item.getVal("size")->asIntegerNumber(); + } + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString()); + + //as we list directory by id, we can't determine full path for the file, so we leave it empty + _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory)); + } + } + + uint32 received = 0; + uint32 totalCount = 0; + if (responseObject.contains("total_count") && responseObject.getVal("total_count")->isIntegerNumber()) + totalCount = responseObject.getVal("total_count")->asIntegerNumber(); + if (responseObject.contains("offset") && responseObject.getVal("offset")->isIntegerNumber()) + received = responseObject.getVal("offset")->asIntegerNumber(); + if (responseObject.contains("limit") && responseObject.getVal("limit")->isIntegerNumber()) + received += responseObject.getVal("limit")->asIntegerNumber(); + bool hasMore = (received < totalCount); + + if (hasMore) makeRequest(received); + else finishListing(_files); + + delete json; +} + +void BoxListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + if (error.request) _date = error.request->date(); + finishError(error); +} + +void BoxListDirectoryByIdRequest::handle() {} + +void BoxListDirectoryByIdRequest::restart() { start(); } + +Common::String BoxListDirectoryByIdRequest::date() const { return _date; } + +void BoxListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.h b/backends/cloud/box/boxlistdirectorybyidrequest.h new file mode 100644 index 0000000000..13f1ba056c --- /dev/null +++ b/backends/cloud/box/boxlistdirectorybyidrequest.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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H +#define BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Box { + +class BoxStorage; + +class BoxListDirectoryByIdRequest: public Networking::Request { + Common::String _requestedId; + BoxStorage *_storage; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void makeRequest(uint32 offset); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb); + virtual ~BoxListDirectoryByIdRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxstorage.cpp b/backends/cloud/box/boxstorage.cpp new file mode 100644 index 0000000000..70864679e7 --- /dev/null +++ b/backends/cloud/box/boxstorage.cpp @@ -0,0 +1,345 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxlistdirectorybyidrequest.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/box/boxuploadrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace Box { + +#define BOX_OAUTH2_TOKEN "https://api.box.com/oauth2/token" +#define BOX_API_FOLDERS "https://api.box.com/2.0/folders" +#define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content" +#define BOX_API_USERS_ME "https://api.box.com/2.0/users/me" + +char *BoxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *BoxStorage::SECRET = nullptr; + +void BoxStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_BOX_KEY; + SECRET = RELEASE_BOX_SECRET; +#else + Common::String k = ConfMan.get("BOX_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("BOX_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +BoxStorage::BoxStorage(Common::String accessToken, Common::String refreshToken): + _token(accessToken), _refreshToken(refreshToken) {} + +BoxStorage::BoxStorage(Common::String code) { + getAccessToken( + new Common::Callback<BoxStorage, BoolResponse>(this, &BoxStorage::codeFlowComplete), + new Common::Callback<BoxStorage, Networking::ErrorResponse>(this, &BoxStorage::codeFlowFailed), + code + ); +} + +BoxStorage::~BoxStorage() {} + +void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("BoxStorage: no refresh token available to get new access token."); + if (callback) (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, BOX_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("grant_type=authorization_code"); + request->addPostField("code=" + code); + } else { + request->addPostField("grant_type=refresh_token"); + request->addPostField("refresh_token=" + _refreshToken); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + /* + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + */ + addRequest(request); +} + +void BoxStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "BoxStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "BoxStorage")) { + warning("BoxStorage: bad response, no token passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void BoxStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("BoxStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + CloudMan.replaceStorage(this, kStorageBoxId); + ConfMan.flushToDisk(); +} + +void BoxStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "BoxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void BoxStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String BoxStorage::name() const { + return "Box"; +} + +void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 0; + + // can check that "type": "user" + // there is also "max_upload_size", "phone" and "avatar_url" + + if (Networking::CurlJsonRequest::jsonContainsString(info, "id", "BoxStorage::infoInnerCallback")) + uid = info.getVal("id")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsString(info, "name", "BoxStorage::infoInnerCallback")) + name = info.getVal("name")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsString(info, "login", "BoxStorage::infoInnerCallback")) + email = info.getVal("login")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_amount", "BoxStorage::infoInnerCallback")) + quotaAllocated = info.getVal("space_amount")->asIntegerNumber(); + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_used", "BoxStorage::infoInnerCallback")) + quotaUsed = info.getVal("space_used")->asIntegerNumber(); + + Common::String username = email; + if (username == "") username = name; + if (username == "") username = uid; + CloudMan.setStorageUsername(kStorageBoxId, username); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +Networking::Request *BoxStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = getPrintFilesCallback(); + return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback)); +} + +void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (outerCallback) { + if (Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) { + Common::JSONObject info = json->asObject(); + (*outerCallback)(BoolResponse(nullptr, info.contains("id"))); + } else { + (*outerCallback)(BoolResponse(nullptr, false)); + } + delete outerCallback; + } + + delete json; +} + +Networking::Request *BoxStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + + Common::String url = BOX_API_FOLDERS; + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::createDirectoryInnerCallback, callback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject parentObject; + parentObject.setVal("id", new Common::JSONValue(parentId)); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + return addRequest(request); +} + +Networking::Request *BoxStorage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback)); +} + +Networking::Request *BoxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + warning("BoxStorage::upload(ReadStream) not implemented"); + if (errorCallback) + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1)); + delete callback; + delete errorCallback; + return nullptr; +} + +bool BoxStorage::uploadStreamSupported() { + return false; +} + +Networking::Request *BoxStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + if (callback) { + Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str()); + Common::String header = "Authorization: Bearer " + _token; + curl_slist *headersList = curl_slist_append(nullptr, header.c_str()); + Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, ""); + (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream)); + } + delete callback; + delete errorCallback; + return nullptr; +} + +Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, StorageInfoResponse, Networking::JsonResponse>(this, &BoxStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; } + +BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("BoxStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("BoxStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new BoxStorage(accessToken, refreshToken); +} + +Common::String BoxStorage::getRootDirectoryId() { + return "0"; +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxstorage.h b/backends/cloud/box/boxstorage.h new file mode 100644 index 0000000000..2dd516d894 --- /dev/null +++ b/backends/cloud/box/boxstorage.h @@ -0,0 +1,116 @@ +/* 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 BACKENDS_CLOUD_BOX_BOXSTORAGE_H +#define BACKENDS_CLOUD_BOX_BOXSTORAGE_H + +#include "backends/cloud/id/idstorage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Box { + +class BoxStorage: public Id::IdStorage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + BoxStorage(Common::String token, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + BoxStorage(Common::String code); + virtual ~BoxStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns whether Storage supports upload(ReadStream). */ + virtual bool uploadStreamSupported(); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return BoxStorage for those. + * @return pointer to the newly created BoxStorage or 0 if some problem occured. + */ + static BoxStorage *loadFromConfig(Common::String keyPrefix); + + virtual Common::String getRootDirectoryId(); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxtokenrefresher.cpp b/backends/cloud/box/boxtokenrefresher.cpp new file mode 100644 index 0000000000..ca05eef838 --- /dev/null +++ b/backends/cloud/box/boxtokenrefresher.cpp @@ -0,0 +1,137 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace Box { + +BoxTokenRefresher::BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +BoxTokenRefresher::~BoxTokenRefresher() {} + +void BoxTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("BoxTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void BoxTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "BoxTokenRefresher")) { + Common::JSONObject result = json->asObject(); + if (result.contains("type") && result.getVal("type")->isString() && result.getVal("type")->asString() == "error") { + //new token needed => request token & then retry original request + long httpCode = -1; + if (_stream) { + httpCode = _stream->httpResponseCode(); + debug(9, "BoxTokenRefresher: code %ld", httpCode); + } + + bool irrecoverable = true; + + Common::String code, message; + if (jsonContainsString(result, "code", "BoxTokenRefresher")) { + code = result.getVal("code")->asString(); + debug(9, "BoxTokenRefresher: code = %s", code.c_str()); + } + + if (jsonContainsString(result, "message", "BoxTokenRefresher")) { + message = result.getVal("message")->asString(); + debug(9, "BoxTokenRefresher: message = %s", message.c_str()); + } + + //TODO: decide when token refreshment will help + //for now refreshment is used only when HTTP 401 is passed in finishError() + //if (code == "unauthenticated") irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void BoxTokenRefresher::finishError(Networking::ErrorResponse error) { + if (error.httpResponseCode == 401) { // invalid_token + pause(); + _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed)); + return; + } + + // there are also 400 == invalid_request and 403 == insufficient_scope + // but TokenRefresher is there to refresh token when it's invalid only + + Request::finishError(error); +} + +void BoxTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void BoxTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxtokenrefresher.h b/backends/cloud/box/boxtokenrefresher.h new file mode 100644 index 0000000000..c08e8468c3 --- /dev/null +++ b/backends/cloud/box/boxtokenrefresher.h @@ -0,0 +1,53 @@ +/* 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 BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H +#define BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Box { + +class BoxStorage; + +class BoxTokenRefresher: public Networking::CurlJsonRequest { + BoxStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); + virtual void finishError(Networking::ErrorResponse error); +public: + BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~BoxTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxuploadrequest.cpp b/backends/cloud/box/boxuploadrequest.cpp new file mode 100644 index 0000000000..5084aa5652 --- /dev/null +++ b/backends/cloud/box/boxuploadrequest.cpp @@ -0,0 +1,232 @@ +/* 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 "backends/cloud/box/boxuploadrequest.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Box { + +#define BOX_API_FILES "https://upload.box.com/api/2.0/files" + +BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +BoxUploadRequest::~BoxUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _uploadCallback; +} + +void BoxUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _resolvedId = ""; //used to update file contents + _parentId = ""; //used to create file within parent directory + _ignoreCallback = false; + + resolveId(); +} + +void BoxUploadRequest::resolveId() { + //check whether such file already exists + Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback); +} + +void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + _resolvedId = response.value.id(); + upload(); +} + +void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + + //not resolved => error or no such file + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + _parentId = parentId; + upload(); + return; + } + + finishError(error); +} + +void BoxUploadRequest::upload() { + Common::String name = _savePath; + for (uint32 i = name.size(); i > 0; --i) { + if (name[i - 1] == '/' || name[i - 1] == '\\') { + name.erase(0, i); + break; + } + } + + Common::String url = BOX_API_FILES; + if (_resolvedId != "") + url += "/" + _resolvedId; + url += "/content"; + Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + + Common::JSONObject jsonRequestParameters; + if (_resolvedId == "") { + Common::JSONObject parentObject; + parentObject.setVal("id", new Common::JSONValue(_parentId)); + jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject)); + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + } + + Common::JSONValue value(jsonRequestParameters); + request->addFormField("attributes", Common::JSON::stringify(&value)); + request->addFormFile("file", _localPath); + + _workingRequest = ConnMan.addRequest(request); +} + +void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + error.httpResponseCode = code; + } + } + + if (error.httpResponseCode != 200 && error.httpResponseCode != 201) + warning("BoxUploadRequest: looks like an error (bad HTTP code)"); + + //check JSON and show warnings if it's malformed + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject object = json->asObject(); + if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) { + Common::JSONArray entries = object.getVal("entries")->asArray(); + if (entries.size() == 0) { + warning("BoxUploadRequest: 'entries' found, but it's empty"); + } else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) { + warning("BoxUploadRequest: 'entries' first item is not an object"); + } else { + Common::JSONObject item = entries[0]->asObject(); + + if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) { + + //finished + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("type")->asString() == "folder"); + uint32 size; + if (item.getVal("size")->isString()) { + size = item.getVal("size")->asString().asUint64(); + } else { + size = item.getVal("size")->asIntegerNumber(); + } + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString()); + + finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory)); + delete json; + return; + } + } + } + + //TODO: check errors + /* + if (object.contains("error")) { + warning("Box returned error: %s", json->stringify(true).c_str()); + delete json; + error.response = json->stringify(true); + finishError(error); + return; + } + */ + + warning("BoxUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + + delete json; +} + +void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + finishError(error); +} + +void BoxUploadRequest::handle() {} + +void BoxUploadRequest::restart() { start(); } + +void BoxUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxuploadrequest.h b/backends/cloud/box/boxuploadrequest.h new file mode 100644 index 0000000000..1bc8690210 --- /dev/null +++ b/backends/cloud/box/boxuploadrequest.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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H +#define BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Box { +class BoxStorage; + +class BoxUploadRequest: public Networking::Request { + BoxStorage *_storage; + Common::String _savePath, _localPath; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _resolvedId, _parentId; + + void start(); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void upload(); + void uploadedCallback(Networking::JsonResponse response); + void notUploadedCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~BoxUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/cloudmanager.cpp b/backends/cloud/cloudmanager.cpp new file mode 100644 index 0000000000..826fc6103d --- /dev/null +++ b/backends/cloud/cloudmanager.cpp @@ -0,0 +1,456 @@ +/* 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 "backends/cloud/cloudmanager.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/dropbox/dropboxstorage.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "common/translation.h" +#include "common/config-manager.h" +#include "common/str.h" +#ifdef USE_SDL_NET +#include "backends/networking/sdl_net/localwebserver.h" +#endif + +namespace Common { + +DECLARE_SINGLETON(Cloud::CloudManager); + +} + +namespace Cloud { + +const char *const CloudManager::kStoragePrefix = "storage_"; + +CloudManager::CloudManager() : _currentStorageIndex(0), _activeStorage(nullptr) {} + +CloudManager::~CloudManager() { + delete _activeStorage; + freeStorages(); +} + +Common::String CloudManager::getStorageConfigName(uint32 index) const { + switch (index) { + case kStorageNoneId: return "<none>"; + case kStorageDropboxId: return "Dropbox"; + case kStorageOneDriveId: return "OneDrive"; + case kStorageGoogleDriveId: return "GoogleDrive"; + case kStorageBoxId: return "Box"; + } + assert(false); // Unhandled StorageID value + return ""; +} + +void CloudManager::loadStorage() { + switch (_currentStorageIndex) { + case kStorageDropboxId: + _activeStorage = Dropbox::DropboxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageOneDriveId: + _activeStorage = OneDrive::OneDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageGoogleDriveId: + _activeStorage = GoogleDrive::GoogleDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageBoxId: + _activeStorage = Box::BoxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + default: + _activeStorage = nullptr; + } + + if (!_activeStorage) { + _currentStorageIndex = kStorageNoneId; + } +} + +void CloudManager::init() { + //init configs structs + for (uint32 i = 0; i < kStorageTotal; ++i) { + Common::String name = getStorageConfigName(i); + StorageConfig config; + config.name = _(name); + config.username = ""; + config.lastSyncDate = ""; + config.usedBytes = 0; + if (ConfMan.hasKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain)) + config.username = ConfMan.get(kStoragePrefix + name + "_username", ConfMan.kCloudDomain); + if (ConfMan.hasKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain)) + config.lastSyncDate = ConfMan.get(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain); + if (ConfMan.hasKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain)) + config.usedBytes = ConfMan.get(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain).asUint64(); + _storages.push_back(config); + } + + //load an active storage if there is any + _currentStorageIndex = kStorageNoneId; + if (ConfMan.hasKey("current_storage", ConfMan.kCloudDomain)) + _currentStorageIndex = ConfMan.getInt("current_storage", ConfMan.kCloudDomain); + + loadStorage(); +} + +void CloudManager::save() { + for (uint32 i = 0; i < _storages.size(); ++i) { + if (i == kStorageNoneId) + continue; + Common::String name = getStorageConfigName(i); + ConfMan.set(kStoragePrefix + name + "_username", _storages[i].username, ConfMan.kCloudDomain); + ConfMan.set(kStoragePrefix + name + "_lastSync", _storages[i].lastSyncDate, ConfMan.kCloudDomain); + ConfMan.set(kStoragePrefix + name + "_usedBytes", Common::String::format("%lu", _storages[i].usedBytes), ConfMan.kCloudDomain); + } + + ConfMan.set("current_storage", Common::String::format("%u", _currentStorageIndex), ConfMan.kCloudDomain); + if (_activeStorage) + _activeStorage->saveConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + ConfMan.flushToDisk(); +} + +void CloudManager::replaceStorage(Storage *storage, uint32 index) { + freeStorages(); + if (!storage) + error("CloudManager::replaceStorage: NULL storage passed"); + if (index >= kStorageTotal) + error("CloudManager::replaceStorage: invalid index passed"); + if (_activeStorage != nullptr && _activeStorage->isWorking()) { + warning("CloudManager::replaceStorage: replacing Storage while the other is working"); + if (_activeStorage->isDownloading()) + _activeStorage->cancelDownload(); + if (_activeStorage->isSyncing()) + _activeStorage->cancelSync(); + removeStorage(_activeStorage); + } else { + delete _activeStorage; + } + _activeStorage = storage; + _currentStorageIndex = index; + save(); + + //do what should be done on first Storage connect + if (_activeStorage) { + _activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername() + _activeStorage->syncSaves(nullptr, nullptr); + } +} + +void CloudManager::removeStorage(Storage *storage) { + // can't just delete it as it's mostly likely the one who calls the method + // it would be freed on freeStorages() call (on next Storage connect or replace) + _storagesToRemove.push_back(storage); +} + +void CloudManager::freeStorages() { + for (uint32 i = 0; i < _storagesToRemove.size(); ++i) + delete _storagesToRemove[i]; + _storagesToRemove.clear(); +} + +void CloudManager::passNoStorageConnected(Networking::ErrorCallback errorCallback) const { + if (errorCallback == nullptr) + return; + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "No Storage connected!", -1)); +} + +Storage *CloudManager::getCurrentStorage() const { + return _activeStorage; +} + +uint32 CloudManager::getStorageIndex() const { + return _currentStorageIndex; +} + +Common::StringArray CloudManager::listStorages() const { + Common::StringArray result; + for (uint32 i = 0; i < _storages.size(); ++i) { + result.push_back(_storages[i].name); + } + return result; +} + +bool CloudManager::switchStorage(uint32 index) { + if (index >= _storages.size()) { + warning("CloudManager::switchStorage: invalid index passed"); + return false; + } + + Storage *storage = getCurrentStorage(); + if (storage && storage->isWorking()) { + warning("CloudManager::switchStorage: another storage is working now"); + return false; + } + + _currentStorageIndex = index; + loadStorage(); + save(); + return true; +} + +Common::String CloudManager::getStorageUsername(uint32 index) { + if (index >= _storages.size()) + return ""; + return _storages[index].username; +} + +uint64 CloudManager::getStorageUsedSpace(uint32 index) { + if (index >= _storages.size()) + return 0; + return _storages[index].usedBytes; +} + +Common::String CloudManager::getStorageLastSync(uint32 index) { + if (index >= _storages.size()) + return ""; + if (index == _currentStorageIndex && isSyncing()) + return ""; + return _storages[index].lastSyncDate; +} + +void CloudManager::setStorageUsername(uint32 index, Common::String name) { + if (index >= _storages.size()) + return; + _storages[index].username = name; + save(); +} + +void CloudManager::setStorageUsedSpace(uint32 index, uint64 used) { + if (index >= _storages.size()) + return; + _storages[index].usedBytes = used; + save(); +} + +void CloudManager::setStorageLastSync(uint32 index, Common::String date) { + if (index >= _storages.size()) + return; + _storages[index].lastSyncDate = date; + save(); +} + +void CloudManager::connectStorage(uint32 index, Common::String code) { + freeStorages(); + + Storage *storage = nullptr; + switch (index) { + case kStorageDropboxId: + storage = new Dropbox::DropboxStorage(code); + break; + case kStorageOneDriveId: + storage = new OneDrive::OneDriveStorage(code); + break; + case kStorageGoogleDriveId: + storage = new GoogleDrive::GoogleDriveStorage(code); + break; + case kStorageBoxId: + storage = new Box::BoxStorage(code); + break; + } + // in these constructors Storages request token using the passed code + // when the token is received, they call replaceStorage() + // or removeStorage(), if some error occurred + // thus, no memory leak happens +} + +Networking::Request *CloudManager::listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->listDirectory(path, callback, errorCallback, recursive); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Networking::Request *CloudManager::downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->downloadFolder(remotePath, localPath, callback, errorCallback, recursive); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Networking::Request *CloudManager::info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->info(callback, errorCallback); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Common::String CloudManager::savesDirectoryPath() { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->savesDirectoryPath(); + return ""; +} + +SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) { + Storage *storage = getCurrentStorage(); + if (storage) { + setStorageLastSync(_currentStorageIndex, "???"); //TODO get the date + return storage->syncSaves(callback, errorCallback); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +bool CloudManager::isWorking() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isWorking(); + return false; +} + +bool CloudManager::couldUseLocalServer() { +#ifdef USE_SDL_NET + return Networking::LocalWebserver::getPort() == Networking::LocalWebserver::DEFAULT_SERVER_PORT; +#else + return false; +#endif +} + +///// SavesSyncRequest-related ///// + +bool CloudManager::isSyncing() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isSyncing(); + return false; +} + +double CloudManager::getSyncDownloadingProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncDownloadingProgress(); + return 1; +} + +double CloudManager::getSyncProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncProgress(); + return 1; +} + +Common::Array<Common::String> CloudManager::getSyncingFiles() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncingFiles(); + return Common::Array<Common::String>(); +} + +void CloudManager::cancelSync() const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->cancelSync(); +} + +void CloudManager::setSyncTarget(GUI::CommandReceiver *target) const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->setSyncTarget(target); +} + +///// DownloadFolderRequest-related ///// + +bool CloudManager::startDownload(Common::String remotePath, Common::String localPath) const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->startDownload(remotePath, localPath); + return false; +} + +void CloudManager::cancelDownload() const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->cancelDownload(); +} + +void CloudManager::setDownloadTarget(GUI::CommandReceiver *target) const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->setDownloadTarget(target); +} + +bool CloudManager::isDownloading() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isDownloading(); + return false; +} + +double CloudManager::getDownloadingProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadingProgress(); + return 1; +} + +uint64 CloudManager::getDownloadBytesNumber() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadBytesNumber(); + return 0; +} + +uint64 CloudManager::getDownloadTotalBytesNumber() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadTotalBytesNumber(); + return 0; +} + +uint64 CloudManager::getDownloadSpeed() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadSpeed(); + return 0; +} + +Common::String CloudManager::getDownloadRemoteDirectory() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadRemoteDirectory(); + return ""; +} + +Common::String CloudManager::getDownloadLocalDirectory() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadLocalDirectory(); + return ""; +} + +} // End of namespace Cloud diff --git a/backends/cloud/cloudmanager.h b/backends/cloud/cloudmanager.h new file mode 100644 index 0000000000..c504ff39cb --- /dev/null +++ b/backends/cloud/cloudmanager.h @@ -0,0 +1,274 @@ +/* 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 CLOUD_CLOUDMANAGER_H +#define CLOUD_CLOUDMANAGER_H + +#include "backends/cloud/storage.h" +#include "common/array.h" +#include "common/singleton.h" +#include "common/str-array.h" + +namespace GUI { + +class CommandReceiver; + +} + +namespace Cloud { + +// The actual indexes in CloudManager's array +enum StorageID { + kStorageNoneId = 0, + kStorageDropboxId = 1, + kStorageOneDriveId = 2, + kStorageGoogleDriveId = 3, + kStorageBoxId = 4, + + kStorageTotal +}; + +class CloudManager : public Common::Singleton<CloudManager> { + static const char *const kStoragePrefix; + + struct StorageConfig { + Common::String name, username; + uint64 usedBytes; + Common::String lastSyncDate; + }; + + Common::Array<StorageConfig> _storages; + uint _currentStorageIndex; + Storage *_activeStorage; + Common::Array<Storage *> _storagesToRemove; + + void loadStorage(); + + Common::String getStorageConfigName(uint32 index) const; + + /** Frees memory used by storages which failed to connect. */ + void freeStorages(); + + /** Calls the error callback with a special "no storage connected" message. */ + void passNoStorageConnected(Networking::ErrorCallback errorCallback) const; + +public: + CloudManager(); + virtual ~CloudManager(); + + /** + * Loads all information from configs and creates current Storage instance. + * + * @note It's called once on startup in scummvm_main(). + */ + void init(); + + /** + * Saves all information into configuration file. + */ + void save(); + + /** + * Replace active Storage. + * @note this method automatically saves the changes with ConfMan. + * + * @param storage Cloud::Storage to replace active storage with. + * @param index one of Cloud::StorageID enum values to indicate what storage type is replaced. + */ + void replaceStorage(Storage *storage, uint32 index); + + /** Adds storage in the list of storages to remove later. */ + void removeStorage(Storage *storage); + + /** + * Returns active Storage, which could be used to interact + * with cloud storage. + * + * @return active Cloud::Storage or null, if there is no active Storage. + */ + Cloud::Storage *getCurrentStorage() const; + + /** + * Return active Storage's index. + * + * @return active Storage's index. + */ + uint32 getStorageIndex() const; + + /** + * Return Storages names as list. + * + * @return a list of Storages names. + */ + Common::StringArray listStorages() const; + + /** + * Changes the storage to the one with given index. + * + * @param new Storage's index. + */ + bool switchStorage(uint32 index); + + /** + * Return username used by Storage. + * + * @param Storage's index. + * @returns username or "" if index is invalid (no such Storage). + */ + Common::String getStorageUsername(uint32 index); + + /** + * Return space used by Storage. + * + * @param Storage's index. + * @returns used space in bytes or 0 if index is invalid (no such Storage). + */ + uint64 getStorageUsedSpace(uint32 index); + + /** + * Return Storage's last sync date. + * + * @param Storage's index. + * @returns last sync date or "" if index is invalid (no such Storage). + It also returns "" if there never was any sync + or if storage is syncing right now. + */ + Common::String getStorageLastSync(uint32 index); + + /** + * Set Storage's username. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param name username to set + */ + void setStorageUsername(uint32 index, Common::String name); + + /** + * Set Storage's used space field. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param used value to set + */ + void setStorageUsedSpace(uint32 index, uint64 used); + + /** + * Set Storage's last sync date. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param date date to set + */ + void setStorageLastSync(uint32 index, Common::String date); + + /** + * Replace Storage which has given index with a + * storage created with given code. + * + * @param index Storage's index + * @param code OAuth2 code received from user + */ + void connectStorage(uint32 index, Common::String code); + + /** Returns ListDirectoryResponse with list of files. */ + Networking::Request *listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */ + Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Return the StorageInfo struct. */ + Networking::Request *info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + Common::String savesDirectoryPath(); + + /** + * Starts saves syncing process in currently active storage if there is any. + */ + SavesSyncRequest *syncSaves(Cloud::Storage::BoolCallback callback = nullptr, Networking::ErrorCallback errorCallback = nullptr); + + /** Returns whether there are any requests running. */ + bool isWorking() const; + + /** Returns whether LocalWebserver is available to use for auth. */ + static bool couldUseLocalServer(); + + ///// SavesSyncRequest-related ///// + + /** Returns whether there is a SavesSyncRequest running. */ + bool isSyncing() const; + + /** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */ + double getSyncDownloadingProgress() const; + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + double getSyncProgress() const; + + /** Returns an array of saves names which are not yet synced (thus cannot be used). */ + Common::Array<Common::String> getSyncingFiles() const; + + /** Cancels running sync. */ + void cancelSync() const; + + /** Sets SavesSyncRequest's target to given CommandReceiver. */ + void setSyncTarget(GUI::CommandReceiver *target) const; + + ///// DownloadFolderRequest-related ///// + + /** Starts a folder download. */ + bool startDownload(Common::String remotePath, Common::String localPath) const; + + /** Cancels running download. */ + void cancelDownload() const; + + /** Sets FolderDownloadRequest's target to given CommandReceiver. */ + void setDownloadTarget(GUI::CommandReceiver *target) const; + + /** Returns whether there is a FolderDownloadRequest running. */ + bool isDownloading() const; + + /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */ + double getDownloadingProgress() const; + + /** Returns a number of bytes that is downloaded in current download progress. */ + uint64 getDownloadBytesNumber() const; + + /** Returns a total number of bytes to be downloaded in current download progress. */ + uint64 getDownloadTotalBytesNumber() const; + + /** Returns download speed of current download progress. */ + uint64 getDownloadSpeed() const; + + /** Returns remote directory path. */ + Common::String getDownloadRemoteDirectory() const; + + /** Returns local directory path. */ + Common::String getDownloadLocalDirectory() const; +}; + +/** Shortcut for accessing the connection manager. */ +#define CloudMan Cloud::CloudManager::instance() + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/downloadrequest.cpp b/backends/cloud/downloadrequest.cpp new file mode 100644 index 0000000000..e28670fc7b --- /dev/null +++ b/backends/cloud/downloadrequest.cpp @@ -0,0 +1,138 @@ +/* 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 "backends/cloud/downloadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "common/textconsole.h" + +namespace Cloud { + +DownloadRequest::DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile): + Request(nullptr, ecb), _boolCallback(callback), _localFile(dumpFile), _remoteFileId(remoteFileId), _storage(storage), + _remoteFileStream(nullptr), _workingRequest(nullptr), _ignoreCallback(false), _buffer(new byte[DOWNLOAD_REQUEST_BUFFER_SIZE]) { + start(); +} + +DownloadRequest::~DownloadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; + delete _localFile; + delete[] _buffer; +} + +void DownloadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _remoteFileStream = nullptr; + //TODO: add some way to reopen DumpFile, so DownloadRequest could be restarted + _ignoreCallback = false; + + _workingRequest = _storage->streamFileById( + _remoteFileId, + new Common::Callback<DownloadRequest, Networking::NetworkReadStreamResponse>(this, &DownloadRequest::streamCallback), + new Common::Callback<DownloadRequest, Networking::ErrorResponse>(this, &DownloadRequest::streamErrorCallback) + ); +} + +void DownloadRequest::streamCallback(Networking::NetworkReadStreamResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _remoteFileStream = (Networking::NetworkReadStream *)response.value; +} + +void DownloadRequest::streamErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void DownloadRequest::handle() { + if (!_localFile) { + warning("DownloadRequest: no file to write"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (!_localFile->isOpen()) { + warning("DownloadRequest: failed to open file to write"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (!_remoteFileStream) { + //waiting for callback + return; + } + + uint32 readBytes = _remoteFileStream->read(_buffer, DOWNLOAD_REQUEST_BUFFER_SIZE); + + if (readBytes != 0) + if (_localFile->write(_buffer, readBytes) != readBytes) { + warning("DownloadRequest: unable to write all received bytes into output file"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (_remoteFileStream->eos()) { + if (_remoteFileStream->httpResponseCode() != 200) { + warning("DownloadRequest: HTTP response code is not 200 OK (it's %ld)", _remoteFileStream->httpResponseCode()); + //TODO: do something about it actually + // the problem is file's already downloaded, stream is over + // so we can't return error message anymore + } + + finishDownload(_remoteFileStream->httpResponseCode() == 200); + + _localFile->close(); //yes, I know it's closed automatically in ~DumpFile() + } +} + +void DownloadRequest::restart() { + warning("DownloadRequest: can't restart as there are no means to reopen DumpFile"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + //start(); +} + +void DownloadRequest::finishDownload(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +void DownloadRequest::finishError(Networking::ErrorResponse error) { + if (_localFile) + _localFile->close(); + Request::finishError(error); +} + +double DownloadRequest::getProgress() const { + if (_remoteFileStream) + return _remoteFileStream->getProgress(); + return 0; +} + +} // End of namespace Cloud diff --git a/backends/cloud/downloadrequest.h b/backends/cloud/downloadrequest.h new file mode 100644 index 0000000000..7e58098849 --- /dev/null +++ b/backends/cloud/downloadrequest.h @@ -0,0 +1,64 @@ +/* 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 BACKENDS_CLOUD_DOWNLOADREQUEST_H +#define BACKENDS_CLOUD_DOWNLOADREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/networkreadstream.h" +#include "backends/cloud/storage.h" +#include "common/file.h" + +namespace Cloud { + +#define DOWNLOAD_REQUEST_BUFFER_SIZE 1 * 1024 * 1024 + +class DownloadRequest: public Networking::Request { + Storage::BoolCallback _boolCallback; + Common::DumpFile *_localFile; + Common::String _remoteFileId; + Storage *_storage; + Networking::NetworkReadStream *_remoteFileStream; + Request *_workingRequest; + bool _ignoreCallback; + byte *_buffer; + + void start(); + void streamCallback(Networking::NetworkReadStreamResponse response); + void streamErrorCallback(Networking::ErrorResponse error); + void finishDownload(bool success); + virtual void finishError(Networking::ErrorResponse error); + +public: + DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile); + virtual ~DownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp new file mode 100644 index 0000000000..97090b44f8 --- /dev/null +++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp @@ -0,0 +1,137 @@ +/* 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 "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_CREATE_FOLDER "https://api.dropboxapi.com/2/files/create_folder" + +DropboxCreateDirectoryRequest::DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _path(path), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxCreateDirectoryRequest::~DropboxCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void DropboxCreateDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::JsonResponse>(this, &DropboxCreateDirectoryRequest::responseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::ErrorResponse>(this, &DropboxCreateDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_CREATE_FOLDER); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(_path)); + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + if (response.request) _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject info = json->asObject(); + if (info.contains("id")) { + finishCreation(true); + } else { + if (Networking::CurlJsonRequest::jsonContainsString(info, "error_summary", "DropboxCreateDirectoryRequest")) { + Common::String summary = info.getVal("error_summary")->asString(); + if (summary.contains("path") && summary.contains("conflict") && summary.contains("folder")) { + // existing directory - not an error for CreateDirectoryRequest + finishCreation(false); + delete json; + return; + } + } + error.response = json->stringify(true); + finishError(error); + } + + delete json; +} + +void DropboxCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void DropboxCreateDirectoryRequest::handle() {} + +void DropboxCreateDirectoryRequest::restart() { start(); } + +Common::String DropboxCreateDirectoryRequest::date() const { return _date; } + +void DropboxCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h new file mode 100644 index 0000000000..0ef6a22a04 --- /dev/null +++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.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 BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxCreateDirectoryRequest: public Networking::Request { + Common::String _token; + Common::String _path; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~DropboxCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxinforequest.cpp b/backends/cloud/dropbox/dropboxinforequest.cpp new file mode 100644 index 0000000000..6cdbe3321b --- /dev/null +++ b/backends/cloud/dropbox/dropboxinforequest.cpp @@ -0,0 +1,192 @@ +/* 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 "backends/cloud/dropbox/dropboxinforequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_GET_CURRENT_ACCOUNT "https://api.dropboxapi.com/2/users/get_current_account" +#define DROPBOX_API_GET_SPACE_USAGE "https://api.dropboxapi.com/2/users/get_space_usage" + +DropboxInfoRequest::DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _infoCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxInfoRequest::~DropboxInfoRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _infoCallback; +} + +void DropboxInfoRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::userResponseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_CURRENT_ACCOUNT); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + request->addPostField("null"); //use POST + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxInfoRequest::userResponseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + //Dropbox documentation states there are no errors for this API method + Common::JSONObject info = json->asObject(); + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "name", "DropboxInfoRequest") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("name"), "DropboxInfoRequest")) { + Common::JSONObject nameInfo = info.getVal("name")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(nameInfo, "display_name", "DropboxInfoRequest")) { + _name = nameInfo.getVal("display_name")->asString(); + } + } + if (Networking::CurlJsonRequest::jsonContainsString(info, "account_id", "DropboxInfoRequest")) { + _uid = info.getVal("account_id")->asString(); + } + if (Networking::CurlJsonRequest::jsonContainsString(info, "email", "DropboxInfoRequest")) { + _email = info.getVal("email")->asString(); + } + CloudMan.setStorageUsername(kStorageDropboxId, _email); + delete json; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::quotaResponseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_SPACE_USAGE); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + request->addPostField("null"); //use POST + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxInfoRequest::quotaResponseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + //Dropbox documentation states there are no errors for this API method + Common::JSONObject info = json->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "used", "DropboxInfoRequest")) { + error.response = "Passed JSON misses 'used' attribute!"; + finishError(error); + delete json; + return; + } + + uint64 used = info.getVal("used")->asIntegerNumber(), allocated = 0; + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "allocation", "DropboxInfoRequest") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("allocation"), "DropboxInfoRequest")) { + Common::JSONObject allocation = info.getVal("allocation")->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(allocation, "allocated", "DropboxInfoRequest")) { + error.response = "Passed JSON misses 'allocation/allocated' attribute!"; + finishError(error); + delete json; + return; + } + + allocated = allocation.getVal("allocated")->asIntegerNumber(); + } + + finishInfo(StorageInfo(_uid, _name, _email, used, allocated)); + delete json; +} + +void DropboxInfoRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + finishError(error); +} + +void DropboxInfoRequest::handle() {} + +void DropboxInfoRequest::restart() { start(); } + +void DropboxInfoRequest::finishInfo(StorageInfo info) { + Request::finishSuccess(); + if (_infoCallback) + (*_infoCallback)(Storage::StorageInfoResponse(this, info)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxinforequest.h b/backends/cloud/dropbox/dropboxinforequest.h new file mode 100644 index 0000000000..68a3e135de --- /dev/null +++ b/backends/cloud/dropbox/dropboxinforequest.h @@ -0,0 +1,56 @@ +/* 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 BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxInfoRequest: public Networking::Request { + Common::String _token; + Common::String _uid, _name, _email; + Storage::StorageInfoCallback _infoCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void userResponseCallback(Networking::JsonResponse response); + void quotaResponseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishInfo(StorageInfo info); +public: + DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb); + virtual ~DropboxInfoRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp new file mode 100644 index 0000000000..8b00f7c2bf --- /dev/null +++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp @@ -0,0 +1,222 @@ +/* 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 "backends/cloud/dropbox/dropboxlistdirectoryrequest.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_LIST_FOLDER "https://api.dropboxapi.com/2/files/list_folder" +#define DROPBOX_API_LIST_FOLDER_CONTINUE "https://api.dropboxapi.com/2/files/list_folder/continue" + +DropboxListDirectoryRequest::DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), _requestedPath(path), _requestedRecursive(recursive), _listDirectoryCallback(cb), + _token(token), _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxListDirectoryRequest::~DropboxListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void DropboxListDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(_requestedPath)); + jsonRequestParameters.setVal("recursive", new Common::JSONValue(_requestedRecursive)); + jsonRequestParameters.setVal("include_media_info", new Common::JSONValue(false)); + jsonRequestParameters.setVal("include_deleted", new Common::JSONValue(false)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxListDirectoryRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + + if (_ignoreCallback) { + delete response.value; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject responseObject = json->asObject(); + + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + if (responseObject.contains("error_summary") && responseObject.getVal("error_summary")->isString()) { + warning("Dropbox returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + } + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (responseObject.contains("entries")) { + if (!responseObject.getVal("entries")->isArray()) { + error.response = Common::String::format( + "\"entries\" found, but that's not an array!\n%s", + responseObject.getVal("entries")->stringify(true).c_str() + ); + finishError(error); + delete json; + return; + } + + Common::JSONArray items = responseObject.getVal("entries")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "DropboxListDirectoryRequest")) + continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsString(item, "path_lower", "DropboxListDirectoryRequest")) + continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, ".tag", "DropboxListDirectoryRequest")) + continue; + + Common::String path = item.getVal("path_lower")->asString(); + bool isDirectory = (item.getVal(".tag")->asString() == "folder"); + uint32 size = 0, timestamp = 0; + if (!isDirectory) { + if (!Networking::CurlJsonRequest::jsonContainsString(item, "server_modified", "DropboxListDirectoryRequest")) + continue; + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "DropboxListDirectoryRequest")) + continue; + + size = item.getVal("size")->asIntegerNumber(); + timestamp = ISO8601::convertToTimestamp(item.getVal("server_modified")->asString()); + } + _files.push_back(StorageFile(path, size, timestamp, isDirectory)); + } + } + + bool hasMore = false; + if (responseObject.contains("has_more")) { + if (!responseObject.getVal("has_more")->isBool()) { + warning("DropboxListDirectoryRequest: \"has_more\" is not a boolean"); + debug(9, "%s", responseObject.getVal("has_more")->stringify(true).c_str()); + error.response = "\"has_more\" is not a boolean!"; + finishError(error); + delete json; + return; + } + + hasMore = responseObject.getVal("has_more")->asBool(); + } + + if (hasMore) { + if (!Networking::CurlJsonRequest::jsonContainsString(responseObject, "cursor", "DropboxListDirectoryRequest")) { + error.response = "\"has_more\" found, but \"cursor\" is not (or it's not a string)!"; + finishError(error); + delete json; + return; + } + + Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER_CONTINUE); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("cursor", new Common::JSONValue(responseObject.getVal("cursor")->asString())); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); + } else { + finishListing(_files); + } + + delete json; +} + +void DropboxListDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void DropboxListDirectoryRequest::handle() {} + +void DropboxListDirectoryRequest::restart() { start(); } + +Common::String DropboxListDirectoryRequest::date() const { return _date; } + +void DropboxListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.h b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h new file mode 100644 index 0000000000..5c0d8dfa21 --- /dev/null +++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h @@ -0,0 +1,61 @@ +/* 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 BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::String _token; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~DropboxListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxstorage.cpp b/backends/cloud/dropbox/dropboxstorage.cpp new file mode 100644 index 0000000000..d12070316b --- /dev/null +++ b/backends/cloud/dropbox/dropboxstorage.cpp @@ -0,0 +1,198 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/dropbox/dropboxstorage.h" +#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h" +#include "backends/cloud/dropbox/dropboxinforequest.h" +#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h" +#include "backends/cloud/dropbox/dropboxuploadrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_OAUTH2_TOKEN "https://api.dropboxapi.com/oauth2/token" +#define DROPBOX_API_FILES_DOWNLOAD "https://content.dropboxapi.com/2/files/download" + +char *DropboxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *DropboxStorage::SECRET = nullptr; + +void DropboxStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_DROPBOX_KEY; + SECRET = RELEASE_DROPBOX_SECRET; +#else + Common::String k = ConfMan.get("DROPBOX_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("DROPBOX_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +DropboxStorage::DropboxStorage(Common::String accessToken, Common::String userId): _token(accessToken), _uid(userId) {} + +DropboxStorage::DropboxStorage(Common::String code) { + getAccessToken(code); +} + +DropboxStorage::~DropboxStorage() {} + +void DropboxStorage::getAccessToken(Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + Networking::JsonCallback callback = new Common::Callback<DropboxStorage, Networking::JsonResponse>(this, &DropboxStorage::codeFlowComplete); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxStorage, Networking::ErrorResponse>(this, &DropboxStorage::codeFlowFailed); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, errorCallback, DROPBOX_OAUTH2_TOKEN); + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void DropboxStorage::codeFlowComplete(Networking::JsonResponse response) { + Common::JSONValue *json = (Common::JSONValue *)response.value; + if (json == nullptr) { + debug(9, "DropboxStorage::codeFlowComplete: got NULL instead of JSON!"); + CloudMan.removeStorage(this); + return; + } + + if (!json->isObject()) { + debug(9, "DropboxStorage::codeFlowComplete: Passed JSON is not an object!"); + CloudMan.removeStorage(this); + delete json; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "DropboxStorage::codeFlowComplete") || + !Networking::CurlJsonRequest::jsonContainsString(result, "uid", "DropboxStorage::codeFlowComplete")) { + warning("DropboxStorage: bad response, no token/uid passed"); + debug(9, "%s", json->stringify(true).c_str()); + CloudMan.removeStorage(this); + } else { + _token = result.getVal("access_token")->asString(); + _uid = result.getVal("uid")->asString(); + ConfMan.removeKey("dropbox_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageDropboxId); + ConfMan.flushToDisk(); + } + + delete json; +} + +void DropboxStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "DropboxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void DropboxStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain); +} + +Common::String DropboxStorage::name() const { + return "Dropbox"; +} + +Networking::Request *DropboxStorage::listDirectory(Common::String path, ListDirectoryCallback outerCallback, Networking::ErrorCallback errorCallback, bool recursive) { + return addRequest(new DropboxListDirectoryRequest(_token, path, outerCallback, errorCallback, recursive)); +} + +Networking::Request *DropboxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new DropboxUploadRequest(_token, path, contents, callback, errorCallback)); +} + +Networking::Request *DropboxStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(path)); + Common::JSONValue value(jsonRequestParameters); + + Networking::CurlRequest *request = new Networking::CurlRequest(nullptr, nullptr, DROPBOX_API_FILES_DOWNLOAD); //TODO: is it OK to pass no callbacks? + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value)); + request->addHeader("Content-Type: "); //required to be empty (as we do POST, it's usually app/form-url-encoded) + + Networking::NetworkReadStreamResponse response = request->execute(); + if (callback) + (*callback)(response); + return response.request; // no leak here, response.request == request +} + +Networking::Request *DropboxStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new DropboxCreateDirectoryRequest(_token, path, callback, errorCallback)); +} + +Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new DropboxInfoRequest(_token, callback, errorCallback)); +} + +Common::String DropboxStorage::savesDirectoryPath() { return "/saves/"; } + +DropboxStorage *DropboxStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("DropboxStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) { + warning("DropboxStorage: no user_id found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain); + + return new DropboxStorage(accessToken, userId); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxstorage.h b/backends/cloud/dropbox/dropboxstorage.h new file mode 100644 index 0000000000..0a0043abee --- /dev/null +++ b/backends/cloud/dropbox/dropboxstorage.h @@ -0,0 +1,101 @@ +/* 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 BACKENDS_CLOUD_DROPBOX_STORAGE_H +#define BACKENDS_CLOUD_DROPBOX_STORAGE_H + +#include "backends/cloud/storage.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxStorage: public Cloud::Storage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _uid; + + /** This private constructor is called from loadFromConfig(). */ + DropboxStorage(Common::String token, Common::String uid); + + void getAccessToken(Common::String code); + void codeFlowComplete(Networking::JsonResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + +public: + /** This constructor uses OAuth code flow to get tokens. */ + DropboxStorage(Common::String code); + virtual ~DropboxStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return DropboxStorage for those. + * @return pointer to the newly created DropboxStorage or 0 if some problem occured. + */ + static DropboxStorage *loadFromConfig(Common::String keyPrefix); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxuploadrequest.cpp b/backends/cloud/dropbox/dropboxuploadrequest.cpp new file mode 100644 index 0000000000..2c9dcc4109 --- /dev/null +++ b/backends/cloud/dropbox/dropboxuploadrequest.cpp @@ -0,0 +1,204 @@ +/* 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 "backends/cloud/dropbox/dropboxuploadrequest.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_FILES_UPLOAD "https://content.dropboxapi.com/2/files/upload" +#define DROPBOX_API_FILES_UPLOAD_SESSION "https://content.dropboxapi.com/2/files/upload_session/" + +DropboxUploadRequest::DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxUploadRequest::~DropboxUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void DropboxUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (!_contentsStream) { + warning("DropboxUploadRequest: cannot start because stream is invalid"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + if (!_contentsStream->seek(0)) { + warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _ignoreCallback = false; + + uploadNextPart(); +} + +void DropboxUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + + Common::String url = DROPBOX_API_FILES_UPLOAD_SESSION; + Common::JSONObject jsonRequestParameters; + + if (_contentsStream->pos() == 0 || _sessionId == "") { + if ((uint32)_contentsStream->size() <= UPLOAD_PER_ONE_REQUEST) { + url = DROPBOX_API_FILES_UPLOAD; + jsonRequestParameters.setVal("path", new Common::JSONValue(_savePath)); + jsonRequestParameters.setVal("mode", new Common::JSONValue("overwrite")); + jsonRequestParameters.setVal("autorename", new Common::JSONValue(false)); + jsonRequestParameters.setVal("mute", new Common::JSONValue(false)); + } else { + url += "start"; + jsonRequestParameters.setVal("close", new Common::JSONValue(false)); + } + } else { + if ((uint32)(_contentsStream->size() - _contentsStream->pos()) <= UPLOAD_PER_ONE_REQUEST) { + url += "finish"; + Common::JSONObject jsonCursor, jsonCommit; + jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); + jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos())); + jsonCommit.setVal("path", new Common::JSONValue(_savePath)); + jsonCommit.setVal("mode", new Common::JSONValue("overwrite")); + jsonCommit.setVal("autorename", new Common::JSONValue(false)); + jsonCommit.setVal("mute", new Common::JSONValue(false)); + jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor)); + jsonRequestParameters.setVal("commit", new Common::JSONValue(jsonCommit)); + } else { + url += "append_v2"; + Common::JSONObject jsonCursor; + jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); + jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos())); + jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor)); + jsonRequestParameters.setVal("close", new Common::JSONValue(false)); + } + } + + Common::JSONValue value(jsonRequestParameters); + Networking::JsonCallback callback = new Common::Callback<DropboxUploadRequest, Networking::JsonResponse>(this, &DropboxUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxUploadRequest, Networking::ErrorResponse>(this, &DropboxUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, url); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/octet-stream"); + request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value)); + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + request->setBuffer(buffer, size); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + bool needsFinishRequest = false; + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + //debug(9, "%s", json->stringify(true).c_str()); + + if (object.contains("error") || object.contains("error_summary")) { + if (Networking::CurlJsonRequest::jsonContainsString(object, "error_summary", "DropboxUploadRequest")) { + warning("Dropbox returned error: %s", object.getVal("error_summary")->asString().c_str()); + } + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "path_lower", "DropboxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "server_modified", "DropboxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "DropboxUploadRequest")) { + //finished + Common::String path = object.getVal("path_lower")->asString(); + uint32 size = object.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("server_modified")->asString()); + finishUpload(StorageFile(path, size, timestamp, false)); + return; + } + + if (_sessionId == "") { + if (Networking::CurlJsonRequest::jsonContainsString(object, "session_id", "DropboxUploadRequest")) + _sessionId = object.getVal("session_id")->asString(); + needsFinishRequest = true; + } + } + + if (!needsFinishRequest && (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1)) { + warning("DropboxUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void DropboxUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void DropboxUploadRequest::handle() {} + +void DropboxUploadRequest::restart() { start(); } + +void DropboxUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxuploadrequest.h b/backends/cloud/dropbox/dropboxuploadrequest.h new file mode 100644 index 0000000000..5adf5a6df2 --- /dev/null +++ b/backends/cloud/dropbox/dropboxuploadrequest.h @@ -0,0 +1,60 @@ +/* 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 BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxUploadRequest: public Networking::Request { + Common::String _token; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _sessionId; + + void start(); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~DropboxUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/folderdownloadrequest.cpp b/backends/cloud/folderdownloadrequest.cpp new file mode 100644 index 0000000000..7eeee0c6d6 --- /dev/null +++ b/backends/cloud/folderdownloadrequest.cpp @@ -0,0 +1,197 @@ +/* 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 "backends/cloud/folderdownloadrequest.h" +#include "backends/cloud/downloadrequest.h" +#include "backends/cloud/id/iddownloadrequest.h" +#include "common/debug.h" +#include "gui/downloaddialog.h" +#include <backends/networking/curl/connectionmanager.h> + +namespace Cloud { + +FolderDownloadRequest::FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive): + Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _fileArrayCallback(callback), + _remoteDirectoryPath(remoteDirectoryPath), _localDirectoryPath(localDirectoryPath), _recursive(recursive), + _workingRequest(nullptr), _ignoreCallback(false), _totalFiles(0) { + start(); +} + +FolderDownloadRequest::~FolderDownloadRequest() { + sendCommand(GUI::kDownloadEndedCmd, 0); + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _fileArrayCallback; +} + +void FolderDownloadRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _currentFile = StorageFile(); + _pendingFiles.clear(); + _failedFiles.clear(); + _ignoreCallback = false; + _totalFiles = 0; + _downloadedBytes = _totalBytes = _wasDownloadedBytes = _currentDownloadSpeed = 0; + + //list directory first + _workingRequest = _storage->listDirectory( + _remoteDirectoryPath, + new Common::Callback<FolderDownloadRequest, Storage::ListDirectoryResponse>(this, &FolderDownloadRequest::directoryListedCallback), + new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::directoryListedErrorCallback), + _recursive + ); +} + +void FolderDownloadRequest::directoryListedCallback(Storage::ListDirectoryResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _pendingFiles = response.value; + + // remove all directories + // non-empty directories would be created by DumpFile, and empty ones are just ignored + for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();) + if (i->isDirectory()) + _pendingFiles.erase(i); + else { + _totalBytes += i->size(); + ++i; + } + + _totalFiles = _pendingFiles.size(); + downloadNextFile(); +} + +void FolderDownloadRequest::directoryListedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void FolderDownloadRequest::fileDownloadedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (!response.value) _failedFiles.push_back(_currentFile); + _downloadedBytes += _currentFile.size(); + downloadNextFile(); +} + +void FolderDownloadRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + fileDownloadedCallback(Storage::BoolResponse(error.request, false)); +} + +void FolderDownloadRequest::downloadNextFile() { + do { + if (_pendingFiles.empty()) { + sendCommand(GUI::kDownloadEndedCmd, 0); + finishDownload(_failedFiles); + return; + } + + _currentFile = _pendingFiles.back(); + _pendingFiles.pop_back(); + } while (_currentFile.isDirectory()); // directories are actually removed earlier, in the directoryListedCallback() + + sendCommand(GUI::kDownloadProgressCmd, (int)(getProgress() * 100)); + + Common::String remotePath = _currentFile.path(); + Common::String localPath = remotePath; + if (_remoteDirectoryPath == "" || remotePath.hasPrefix(_remoteDirectoryPath)) { + localPath.erase(0, _remoteDirectoryPath.size()); + if (_remoteDirectoryPath != "" && (_remoteDirectoryPath.lastChar() != '/' && _remoteDirectoryPath.lastChar() != '\\')) + localPath.erase(0, 1); + } else { + warning("FolderDownloadRequest: Can't process the following paths:"); + warning("remote directory: %s", _remoteDirectoryPath.c_str()); + warning("remote file under that directory: %s", remotePath.c_str()); + } + if (_localDirectoryPath != "") { + if (_localDirectoryPath.lastChar() == '/' || _localDirectoryPath.lastChar() == '\\') + localPath = _localDirectoryPath + localPath; + else + localPath = _localDirectoryPath + "/" + localPath; + } + debug(9, "FolderDownloadRequest: %s -> %s", remotePath.c_str(), localPath.c_str()); + _workingRequest = _storage->downloadById( + _currentFile.id(), localPath, + new Common::Callback<FolderDownloadRequest, Storage::BoolResponse>(this, &FolderDownloadRequest::fileDownloadedCallback), + new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::fileDownloadedErrorCallback) + ); +} + +void FolderDownloadRequest::handle() { + uint32 microsecondsPassed = Networking::ConnectionManager::getCloudRequestsPeriodInMicroseconds(); + uint64 currentDownloadedBytes = getDownloadedBytes(); + uint64 downloadedThisPeriod = currentDownloadedBytes - _wasDownloadedBytes; + _currentDownloadSpeed = downloadedThisPeriod * (1000000L / microsecondsPassed); + _wasDownloadedBytes = currentDownloadedBytes; +} + +void FolderDownloadRequest::restart() { start(); } + +void FolderDownloadRequest::finishDownload(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_fileArrayCallback) + (*_fileArrayCallback)(Storage::FileArrayResponse(this, files)); +} + +double FolderDownloadRequest::getProgress() const { + if (_totalFiles == 0 || _totalBytes == 0) + return 0; + return (double)getDownloadedBytes() / (double)getTotalBytesToDownload(); +} + +uint64 FolderDownloadRequest::getDownloadedBytes() const { + if (_totalFiles == 0) + return 0; + + double currentFileProgress = 0; + DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest); + if (downloadRequest != nullptr) { + currentFileProgress = downloadRequest->getProgress(); + } else { + Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest); + if (idDownloadRequest != nullptr) + currentFileProgress = idDownloadRequest->getProgress(); + } + + return _downloadedBytes + (uint64)(currentFileProgress * _currentFile.size()); +} + +uint64 FolderDownloadRequest::getTotalBytesToDownload() const { + return _totalBytes; +} + +uint64 FolderDownloadRequest::getDownloadSpeed() const { + return _currentDownloadSpeed; +} + +} // End of namespace Cloud diff --git a/backends/cloud/folderdownloadrequest.h b/backends/cloud/folderdownloadrequest.h new file mode 100644 index 0000000000..08fa193e07 --- /dev/null +++ b/backends/cloud/folderdownloadrequest.h @@ -0,0 +1,79 @@ +/* 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 BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H +#define BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/cloud/storage.h" +#include "gui/object.h" + +namespace Cloud { + +class FolderDownloadRequest: public Networking::Request, public GUI::CommandSender { + Storage *_storage; + Storage::FileArrayCallback _fileArrayCallback; + Common::String _remoteDirectoryPath, _localDirectoryPath; + bool _recursive; + Common::Array<StorageFile> _pendingFiles, _failedFiles; + StorageFile _currentFile; + Request *_workingRequest; + bool _ignoreCallback; + uint32 _totalFiles; + uint64 _downloadedBytes, _totalBytes, _wasDownloadedBytes, _currentDownloadSpeed; + + void start(); + void directoryListedCallback(Storage::ListDirectoryResponse response); + void directoryListedErrorCallback(Networking::ErrorResponse error); + void fileDownloadedCallback(Storage::BoolResponse response); + void fileDownloadedErrorCallback(Networking::ErrorResponse error); + void downloadNextFile(); + void finishDownload(Common::Array<StorageFile> &files); +public: + FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive); + virtual ~FolderDownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Returns a number of downloaded bytes. */ + uint64 getDownloadedBytes() const; + + /** Returns a total number of bytes to download. */ + uint64 getTotalBytesToDownload() const; + + /** Returns average download speed for the last second. */ + uint64 getDownloadSpeed() const; + + /** Returns remote directory path. */ + Common::String getRemotePath() const { return _remoteDirectoryPath; } + + /** Returns local directory path. */ + Common::String getLocalPath() const { return _localDirectoryPath; } +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp new file mode 100644 index 0000000000..52611126a0 --- /dev/null +++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp @@ -0,0 +1,163 @@ +/* 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 "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "googledrivetokenrefresher.h" + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files?spaces=drive&fields=files%28id,mimeType,modifiedTime,name,size%29,nextPageToken&orderBy=folder,name" +//files(id,mimeType,modifiedTime,name,size),nextPageToken + +GoogleDriveListDirectoryByIdRequest::GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +GoogleDriveListDirectoryByIdRequest::~GoogleDriveListDirectoryByIdRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void GoogleDriveListDirectoryByIdRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + makeRequest(""); +} + +void GoogleDriveListDirectoryByIdRequest::makeRequest(Common::String pageToken) { + Common::String url = GOOGLEDRIVE_API_FILES; + if (pageToken != "") + url += "&pageToken=" + pageToken; + url += "&q=%27" + _requestedId + "%27+in+parents"; + + Networking::JsonCallback callback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::JsonResponse>(this, &GoogleDriveListDirectoryByIdRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::ErrorResponse>(this, &GoogleDriveListDirectoryByIdRequest::errorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void GoogleDriveListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) { + delete response.value; + return; + } + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json) { + Common::JSONObject responseObject = json->asObject(); + + ///debug("%s", json->stringify(true).c_str()); + + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + warning("GoogleDrive returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + + //TODO: check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + + if (responseObject.contains("files") && responseObject.getVal("files")->isArray()) { + Common::JSONArray items = responseObject.getVal("files")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + Common::JSONObject item = items[i]->asObject(); + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("mimeType")->asString() == "application/vnd.google-apps.folder"); + uint32 size = 0, timestamp = 0; + if (item.contains("size") && item.getVal("size")->isString()) + size = item.getVal("size")->asString().asUint64(); + if (item.contains("modifiedTime") && item.getVal("modifiedTime")->isString()) + timestamp = ISO8601::convertToTimestamp(item.getVal("modifiedTime")->asString()); + + //as we list directory by id, we can't determine full path for the file, so we leave it empty + _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory)); + } + } + + bool hasMore = (responseObject.contains("nextPageToken")); + + if (hasMore) { + Common::String token = responseObject.getVal("nextPageToken")->asString(); + makeRequest(token); + } else { + finishListing(_files); + } + } else { + warning("null, not json"); + error.failed = true; + finishError(error); + } + + delete json; +} + +void GoogleDriveListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void GoogleDriveListDirectoryByIdRequest::handle() {} + +void GoogleDriveListDirectoryByIdRequest::restart() { start(); } + +Common::String GoogleDriveListDirectoryByIdRequest::date() const { return _date; } + +void GoogleDriveListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h new file mode 100644 index 0000000000..e94c6b1f4e --- /dev/null +++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.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. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage; + +class GoogleDriveListDirectoryByIdRequest: public Networking::Request { + Common::String _requestedId; + GoogleDriveStorage *_storage; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void makeRequest(Common::String pageToken); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb); + virtual ~GoogleDriveListDirectoryByIdRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivestorage.cpp b/backends/cloud/googledrive/googledrivestorage.cpp new file mode 100644 index 0000000000..1b4b8baf56 --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.cpp @@ -0,0 +1,348 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h" +#include "backends/cloud/googledrive/googledriveuploadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_OAUTH2_TOKEN "https://accounts.google.com/o/oauth2/token" +#define GOOGLEDRIVE_API_FILES_ALT_MEDIA "https://www.googleapis.com/drive/v3/files/%s?alt=media" +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files" +#define GOOGLEDRIVE_API_ABOUT "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user" + +char *GoogleDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *GoogleDriveStorage::SECRET = nullptr; + +void GoogleDriveStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_GOOGLE_DRIVE_KEY; + SECRET = RELEASE_GOOGLE_DRIVE_SECRET; +#else + Common::String k = ConfMan.get("GOOGLE_DRIVE_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("GOOGLE_DRIVE_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +GoogleDriveStorage::GoogleDriveStorage(Common::String accessToken, Common::String refreshToken): + _token(accessToken), _refreshToken(refreshToken) {} + +GoogleDriveStorage::GoogleDriveStorage(Common::String code) { + getAccessToken( + new Common::Callback<GoogleDriveStorage, BoolResponse>(this, &GoogleDriveStorage::codeFlowComplete), + new Common::Callback<GoogleDriveStorage, Networking::ErrorResponse>(this, &GoogleDriveStorage::codeFlowFailed), + code + ); +} + +GoogleDriveStorage::~GoogleDriveStorage() {} + +void GoogleDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("GoogleDriveStorage: no refresh token available to get new access token."); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, GOOGLEDRIVE_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + } else { + request->addPostField("refresh_token=" + _refreshToken); + request->addPostField("grant_type=refresh_token"); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void GoogleDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "GoogleDriveStorage")) { + warning("GoogleDriveStorage: bad response, no token passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "GoogleDriveStorage")) + warning("GoogleDriveStorage: no refresh_token passed"); + else + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void GoogleDriveStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("GoogleDriveStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + ConfMan.removeKey("googledrive_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageGoogleDriveId); + ConfMan.flushToDisk(); +} + +void GoogleDriveStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "GoogleDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void GoogleDriveStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String GoogleDriveStorage::name() const { + return "Google Drive"; +} + +void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 0; + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "user", "GoogleDriveStorage::infoInnerCallback") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("user"), "GoogleDriveStorage::infoInnerCallback")) { + //"me":true, "kind":"drive#user","photoLink": "", + //"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":"" + Common::JSONObject user = info.getVal("user")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "permissionId", "GoogleDriveStorage::infoInnerCallback")) + uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway? + if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "GoogleDriveStorage::infoInnerCallback")) + name = user.getVal("displayName")->asString(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "emailAddress", "GoogleDriveStorage::infoInnerCallback")) + email = user.getVal("emailAddress")->asString(); + } + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "storageQuota", "GoogleDriveStorage::infoInnerCallback") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("storageQuota"), "GoogleDriveStorage::infoInnerCallback")) { + //"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0" + Common::JSONObject storageQuota = info.getVal("storageQuota")->asObject(); + + if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "usage", "GoogleDriveStorage::infoInnerCallback")) { + Common::String usage = storageQuota.getVal("usage")->asString(); + quotaUsed = usage.asUint64(); + } + + if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "limit", "GoogleDriveStorage::infoInnerCallback")) { + Common::String limit = storageQuota.getVal("limit")->asString(); + quotaAllocated = limit.asUint64(); + } + } + + CloudMan.setStorageUsername(kStorageGoogleDriveId, email); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +void GoogleDriveStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage::createDirectoryInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (outerCallback) { + if (Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::createDirectoryInnerCallback")) { + Common::JSONObject info = json->asObject(); + (*outerCallback)(BoolResponse(nullptr, info.contains("id"))); + } else { + (*outerCallback)(BoolResponse(nullptr, false)); + } + delete outerCallback; + } + + delete json; +} + +Networking::Request *GoogleDriveStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<GoogleDriveStorage, FileArrayResponse>(this, &GoogleDriveStorage::printFiles); + return addRequest(new GoogleDriveListDirectoryByIdRequest(this, id, callback, errorCallback)); +} + +Networking::Request *GoogleDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback)); +} + +Networking::Request *GoogleDriveStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + if (callback) { + Common::String url = Common::String::format(GOOGLEDRIVE_API_FILES_ALT_MEDIA, ConnMan.urlEncode(id).c_str()); + Common::String header = "Authorization: Bearer " + _token; + curl_slist *headersList = curl_slist_append(nullptr, header.c_str()); + Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, ""); + (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream)); + } + delete callback; + delete errorCallback; + return nullptr; +} + +void GoogleDriveStorage::printInfo(StorageInfoResponse response) { + debug(9, "\nGoogleDriveStorage: user info:"); + debug(9, "\tname: %s", response.value.name().c_str()); + debug(9, "\temail: %s", response.value.email().c_str()); + debug(9, "\tdisk usage: %lu/%lu", response.value.used(), response.value.available()); +} + +Networking::Request *GoogleDriveStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + + Common::String url = GOOGLEDRIVE_API_FILES; + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::createDirectoryInnerCallback, callback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONArray parentsArray; + parentsArray.push_back(new Common::JSONValue(parentId)); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("mimeType", new Common::JSONValue("application/vnd.google-apps.folder")); + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + return addRequest(request); +} + +Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + if (!callback) + callback = new Common::Callback<GoogleDriveStorage, StorageInfoResponse>(this, &GoogleDriveStorage::printInfo); + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, GOOGLEDRIVE_API_ABOUT); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; } + +GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("GoogleDriveStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("GoogleDriveStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new GoogleDriveStorage(accessToken, refreshToken); +} + +Common::String GoogleDriveStorage::getRootDirectoryId() { + return "root"; +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivestorage.h b/backends/cloud/googledrive/googledrivestorage.h new file mode 100644 index 0000000000..6a834c44ca --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.h @@ -0,0 +1,118 @@ +/* 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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H + +#include "backends/cloud/id/idstorage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage: public Id::IdStorage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + GoogleDriveStorage(Common::String token, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + /** Returns bool based on JSON response from cloud. */ + void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse json); + + void printInfo(StorageInfoResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + GoogleDriveStorage(Common::String code); + virtual ~GoogleDriveStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns Array<StorageFile> - the list of files. */ + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return GoogleDriveStorage for those. + * @return pointer to the newly created GoogleDriveStorage or 0 if some problem occured. + */ + static GoogleDriveStorage *loadFromConfig(Common::String keyPrefix); + + virtual Common::String getRootDirectoryId(); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.cpp b/backends/cloud/googledrive/googledrivetokenrefresher.cpp new file mode 100644 index 0000000000..8cc492d6b4 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.cpp @@ -0,0 +1,125 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace GoogleDrive { + +GoogleDriveTokenRefresher::GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +GoogleDriveTokenRefresher::~GoogleDriveTokenRefresher() {} + +void GoogleDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("GoogleDriveTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void GoogleDriveTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "GoogleDriveTokenRefresher")) { + Common::JSONObject result = json->asObject(); + long httpResponseCode = -1; + if (result.contains("error") && jsonIsObject(result.getVal("error"), "GoogleDriveTokenRefresher")) { + //new token needed => request token & then retry original request + if (_stream) { + httpResponseCode = _stream->httpResponseCode(); + debug(9, "GoogleDriveTokenRefresher: code = %ld", httpResponseCode); + } + + Common::JSONObject error = result.getVal("error")->asObject(); + bool irrecoverable = true; + + uint32 code = -1; + Common::String message; + if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) { + code = error.getVal("code")->asIntegerNumber(); + debug(9, "GoogleDriveTokenRefresher: code = %u", code); + } + + if (jsonContainsString(error, "message", "GoogleDriveTokenRefresher")) { + message = error.getVal("message")->asString(); + debug(9, "GoogleDriveTokenRefresher: message = %s", message.c_str()); + } + + if (code == 401 || message == "Invalid Credentials") + irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void GoogleDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void GoogleDriveTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.h b/backends/cloud/googledrive/googledrivetokenrefresher.h new file mode 100644 index 0000000000..6cb3e41849 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.h @@ -0,0 +1,52 @@ +/* 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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage; + +class GoogleDriveTokenRefresher: public Networking::CurlJsonRequest { + GoogleDriveStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); +public: + GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~GoogleDriveTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledriveuploadrequest.cpp b/backends/cloud/googledrive/googledriveuploadrequest.cpp new file mode 100644 index 0000000000..5f61dcd2a8 --- /dev/null +++ b/backends/cloud/googledrive/googledriveuploadrequest.cpp @@ -0,0 +1,353 @@ +/* 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 "backends/cloud/googledrive/googledriveuploadrequest.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "googledrivetokenrefresher.h" + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/upload/drive/v3/files" + +GoogleDriveUploadRequest::GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +GoogleDriveUploadRequest::~GoogleDriveUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void GoogleDriveUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (_contentsStream == nullptr || !_contentsStream->seek(0)) { + warning("GoogleDriveUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _resolvedId = ""; //used to update file contents + _parentId = ""; //used to create file within parent directory + _serverReceivedBytes = 0; + _ignoreCallback = false; + + resolveId(); +} + +void GoogleDriveUploadRequest::resolveId() { + //check whether such file already exists + Storage::UploadCallback innerCallback = new Common::Callback<GoogleDriveUploadRequest, Storage::UploadResponse>(this, &GoogleDriveUploadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback); +} + +void GoogleDriveUploadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _resolvedId = response.value.id(); + startUpload(); +} + +void GoogleDriveUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //not resolved => error or no such file + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + _parentId = parentId; + startUpload(); + return; + } + + finishError(error); +} + +void GoogleDriveUploadRequest::startUpload() { + Common::String name = _savePath; + for (uint32 i = name.size(); i > 0; --i) { + if (name[i - 1] == '/' || name[i - 1] == '\\') { + name.erase(0, i); + break; + } + } + + Common::String url = GOOGLEDRIVE_API_FILES; + if (_resolvedId != "") + url += "/" + ConnMan.urlEncode(_resolvedId); + url += "?uploadType=resumable&fields=id,mimeType,modifiedTime,name,size"; + Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::startUploadCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::startUploadErrorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->addHeader("Content-Type: application/json"); + if (_resolvedId != "") + request->usePatch(); + + Common::JSONObject jsonRequestParameters; + if (_resolvedId != "") { + jsonRequestParameters.setVal("id", new Common::JSONValue(_resolvedId)); + } else { + Common::JSONArray parentsArray; + parentsArray.push_back(new Common::JSONValue(_parentId)); + jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray)); + } + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + Common::String headers = stream->responseHeaders(); + if (code == 200) { + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "Location: "); + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + 10; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + _uploadUrl = result; + uploadNextPart(); + return; + } + } + + error.httpResponseCode = code; + } + } + + Common::JSONValue *json = response.value; + delete json; + + finishError(error); +} + +void GoogleDriveUploadRequest::startUploadErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void GoogleDriveUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + Common::String url = _uploadUrl; + + Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->usePut(); + + uint32 oldPos = _contentsStream->pos(); + if (oldPos != _serverReceivedBytes) { + if (!_contentsStream->seek(_serverReceivedBytes)) { + warning("GoogleDriveUploadRequest: cannot upload because stream couldn't seek(%lu)", _serverReceivedBytes); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + oldPos = _serverReceivedBytes; + } + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + if (size != 0) + request->setBuffer(buffer, size); + + if (_uploadUrl != "") { + if (_contentsStream->pos() == 0) + request->addHeader(Common::String::format("Content-Length: 0")); + else + request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size())); + } + + _workingRequest = ConnMan.addRequest(request); +} + +bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream *stream) { + //308 Resume Incomplete, with Range: X-Y header + if (!stream) + return false; + if (stream->httpResponseCode() != 308) + return false; //seriously + + Common::String headers = stream->responseHeaders(); + const char *cstr = headers.c_str(); + for (int rangeTry = 0; rangeTry < 2; ++rangeTry) { + const char *needle = (rangeTry == 0 ? "Range: 0-" : "Range: bytes=0-"); + uint32 needleLength = (rangeTry == 0 ? 9 : 15); + + const char *position = strstr(cstr, needle); //if it lost the first part, I refuse to talk with it + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + needleLength; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + _serverReceivedBytes = result.asUint64() + 1; + uploadNextPart(); + return true; + } + } + + return false; +} + +void GoogleDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + error.httpResponseCode = code; + if (code == 308 && handleHttp308(stream)) { + delete (Common::JSONValue *)response.value; + return; + } + } + } + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + if (object.contains("error")) { + warning("GoogleDrive returned error: %s", json->stringify(true).c_str()); + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) { + //finished + Common::String id = object.getVal("id")->asString(); + Common::String name = object.getVal("name")->asString(); + bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder"); + uint32 size = 0, timestamp = 0; + if (Networking::CurlJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true)) + size = object.getVal("size")->asString().asUint64(); + if (Networking::CurlJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true)) + timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString()); + + finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory)); + return; + } + } + + if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) { + warning("GoogleDriveUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void GoogleDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)error.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + if (code == 308 && handleHttp308(stream)) { + return; + } + } + } + + finishError(error); +} + +void GoogleDriveUploadRequest::handle() {} + +void GoogleDriveUploadRequest::restart() { start(); } + +void GoogleDriveUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledriveuploadrequest.h b/backends/cloud/googledrive/googledriveuploadrequest.h new file mode 100644 index 0000000000..73acab5bbd --- /dev/null +++ b/backends/cloud/googledrive/googledriveuploadrequest.h @@ -0,0 +1,70 @@ +/* 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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace GoogleDrive { +class GoogleDriveStorage; + +class GoogleDriveUploadRequest: public Networking::Request { + GoogleDriveStorage *_storage; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _resolvedId, _parentId; + Common::String _uploadUrl; + uint64 _serverReceivedBytes; + + void start(); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void startUpload(); + void startUploadCallback(Networking::JsonResponse response); + void startUploadErrorCallback(Networking::ErrorResponse error); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + bool handleHttp308(const Networking::NetworkReadStream *stream); + void finishUpload(StorageFile status); + +public: + GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~GoogleDriveUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idcreatedirectoryrequest.cpp b/backends/cloud/id/idcreatedirectoryrequest.cpp new file mode 100644 index 0000000000..37f417f806 --- /dev/null +++ b/backends/cloud/id/idcreatedirectoryrequest.cpp @@ -0,0 +1,163 @@ +/* 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 "backends/cloud/id/idcreatedirectoryrequest.h" +#include "backends/cloud/id/idstorage.h" +#include "common/debug.h" + +namespace Cloud { +namespace Id { + +IdCreateDirectoryRequest::IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), + _requestedParentPath(parentPath), _requestedDirectoryName(directoryName), _storage(storage), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdCreateDirectoryRequest::~IdCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void IdCreateDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //the only exception when we create parent folder - is when it's ScummVM/ base folder + Common::String prefix = _requestedParentPath; + if (prefix.size() > 7) + prefix.erase(7); + if (prefix.equalsIgnoreCase("ScummVM")) { + Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback); + _workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback); + return; + } + + resolveId(); +} + +void IdCreateDirectoryRequest::createdBaseDirectoryCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + resolveId(); +} + +void IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdCreateDirectoryRequest::resolveId() { + //check whether such folder already exists + Storage::UploadCallback innerCallback = new Common::Callback<IdCreateDirectoryRequest, Storage::UploadResponse>(this, &IdCreateDirectoryRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::idResolveFailedCallback); + Common::String path = _requestedParentPath; + if (_requestedParentPath != "") + path += "/"; + path += _requestedDirectoryName; + _workingRequest = _storage->resolveFileId(path, innerCallback, innerErrorCallback); +} + +void IdCreateDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + //resolved => folder already exists + finishCreation(false); +} + +void IdCreateDirectoryRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + + //not resolved => folder not exists + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdDirectoryErrorCallback); + _workingRequest = _storage->createDirectoryWithParentId(parentId, _requestedDirectoryName, callback, failureCallback); + return; + } + + finishError(error); +} + +void IdCreateDirectoryRequest::createdDirectoryCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + finishCreation(response.value); +} + +void IdCreateDirectoryRequest::createdDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdCreateDirectoryRequest::handle() {} + +void IdCreateDirectoryRequest::restart() { start(); } + +Common::String IdCreateDirectoryRequest::date() const { return _date; } + +void IdCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idcreatedirectoryrequest.h b/backends/cloud/id/idcreatedirectoryrequest.h new file mode 100644 index 0000000000..241bcd30be --- /dev/null +++ b/backends/cloud/id/idcreatedirectoryrequest.h @@ -0,0 +1,65 @@ +/* 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 BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdCreateDirectoryRequest: public Networking::Request { + Common::String _requestedParentPath; + Common::String _requestedDirectoryName; + IdStorage *_storage; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void createdBaseDirectoryCallback(Storage::BoolResponse response); + void createdBaseDirectoryErrorCallback(Networking::ErrorResponse error); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void createdDirectoryCallback(Storage::BoolResponse response); + void createdDirectoryErrorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~IdCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/iddownloadrequest.cpp b/backends/cloud/id/iddownloadrequest.cpp new file mode 100644 index 0000000000..2532d611b8 --- /dev/null +++ b/backends/cloud/id/iddownloadrequest.cpp @@ -0,0 +1,108 @@ +/* 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 "backends/cloud/id/iddownloadrequest.h" +#include "backends/cloud/id/idstorage.h" +#include "backends/cloud/downloadrequest.h" + +namespace Cloud { +namespace Id { + +IdDownloadRequest::IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedFile(remotePath), _requestedLocalFile(localPath), _storage(storage), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdDownloadRequest::~IdDownloadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void IdDownloadRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //find file's id + Storage::UploadCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::UploadResponse>(this, &IdDownloadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback); +} + +void IdDownloadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Storage::BoolCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::BoolResponse>(this, &IdDownloadRequest::downloadCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::downloadErrorCallback); + _workingRequest = _storage->downloadById(response.value.id(), _requestedLocalFile, innerCallback, innerErrorCallback); +} + +void IdDownloadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdDownloadRequest::downloadCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishDownload(response.value); +} + +void IdDownloadRequest::downloadErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdDownloadRequest::handle() {} + +void IdDownloadRequest::restart() { start(); } + +void IdDownloadRequest::finishDownload(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +double IdDownloadRequest::getProgress() const { + DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest); + if (downloadRequest == nullptr) + return 0; // resolving id still + + // id resolve is 10 % and download is the other 90 % + return 0.1 + 0.9 * downloadRequest->getProgress(); // downloading +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/iddownloadrequest.h b/backends/cloud/id/iddownloadrequest.h new file mode 100644 index 0000000000..65e05c00b3 --- /dev/null +++ b/backends/cloud/id/iddownloadrequest.h @@ -0,0 +1,62 @@ +/* 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 BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H +#define BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdDownloadRequest: public Networking::Request { + Common::String _requestedFile, _requestedLocalFile; + IdStorage *_storage; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void downloadCallback(Storage::BoolResponse response); + void downloadErrorCallback(Networking::ErrorResponse error); + void finishDownload(bool success); +public: + IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~IdDownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idlistdirectoryrequest.cpp b/backends/cloud/id/idlistdirectoryrequest.cpp new file mode 100644 index 0000000000..4e63709984 --- /dev/null +++ b/backends/cloud/id/idlistdirectoryrequest.cpp @@ -0,0 +1,141 @@ +/* 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 "backends/cloud/id/idlistdirectoryrequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdListDirectoryRequest::IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdListDirectoryRequest::~IdListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void IdListDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _files.clear(); + _directoriesQueue.clear(); + _currentDirectory = StorageFile(); + _ignoreCallback = false; + + //find out that directory's id + Storage::UploadCallback innerCallback = new Common::Callback<IdListDirectoryRequest, Storage::UploadResponse>(this, &IdListDirectoryRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::idResolveErrorCallback); + _workingRequest = _storage->resolveFileId(_requestedPath, innerCallback, innerErrorCallback); +} + +void IdListDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + StorageFile directory = response.value; + directory.setPath(_requestedPath); + _directoriesQueue.push_back(directory); + listNextDirectory(); +} + +void IdListDirectoryRequest::idResolveErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdListDirectoryRequest::listNextDirectory() { + if (_directoriesQueue.empty()) { + finishListing(_files); + return; + } + + _currentDirectory = _directoriesQueue.back(); + _directoriesQueue.pop_back(); + + Storage::FileArrayCallback callback = new Common::Callback<IdListDirectoryRequest, Storage::FileArrayResponse>(this, &IdListDirectoryRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::listedDirectoryErrorCallback); + _workingRequest = _storage->listDirectoryById(_currentDirectory.id(), callback, failureCallback); +} + +void IdListDirectoryRequest::listedDirectoryCallback(Storage::FileArrayResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + for (uint32 i = 0; i < response.value.size(); ++i) { + StorageFile &file = response.value[i]; + Common::String path = _currentDirectory.path(); + if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + path += file.name(); + file.setPath(path); + _files.push_back(file); + if (_requestedRecursive && file.isDirectory()) { + _directoriesQueue.push_back(file); + } + } + + listNextDirectory(); +} + +void IdListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdListDirectoryRequest::handle() {} + +void IdListDirectoryRequest::restart() { start(); } + +Common::String IdListDirectoryRequest::date() const { return _date; } + +void IdListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idlistdirectoryrequest.h b/backends/cloud/id/idlistdirectoryrequest.h new file mode 100644 index 0000000000..58c5d2c864 --- /dev/null +++ b/backends/cloud/id/idlistdirectoryrequest.h @@ -0,0 +1,66 @@ +/* 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 BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + IdStorage *_storage; + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Common::Array<StorageFile> _directoriesQueue; + StorageFile _currentDirectory; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveErrorCallback(Networking::ErrorResponse error); + void listNextDirectory(); + void listedDirectoryCallback(Storage::FileArrayResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~IdListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idresolveidrequest.cpp b/backends/cloud/id/idresolveidrequest.cpp new file mode 100644 index 0000000000..e8589fc204 --- /dev/null +++ b/backends/cloud/id/idresolveidrequest.cpp @@ -0,0 +1,136 @@ +/* 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 "backends/cloud/id/idresolveidrequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdResolveIdRequest::IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _storage(storage), _uploadCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdResolveIdRequest::~IdResolveIdRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _uploadCallback; +} + +void IdResolveIdRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _currentDirectory = ""; + _currentDirectoryId = _storage->getRootDirectoryId(); + _ignoreCallback = false; + + listNextDirectory(StorageFile(_currentDirectoryId, 0, 0, true)); +} + +void IdResolveIdRequest::listNextDirectory(StorageFile fileToReturn) { + if (_currentDirectory.equalsIgnoreCase(_requestedPath)) { + finishFile(fileToReturn); + return; + } + + Storage::FileArrayCallback callback = new Common::Callback<IdResolveIdRequest, Storage::FileArrayResponse>(this, &IdResolveIdRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdResolveIdRequest, Networking::ErrorResponse>(this, &IdResolveIdRequest::listedDirectoryErrorCallback); + _workingRequest = _storage->listDirectoryById(_currentDirectoryId, callback, failureCallback); +} + +void IdResolveIdRequest::listedDirectoryCallback(Storage::FileArrayResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Common::String currentLevelName = _requestedPath; + ///debug(9, "'%s'", currentLevelName.c_str()); + if (_currentDirectory.size()) + currentLevelName.erase(0, _currentDirectory.size()); + if (currentLevelName.size() && (currentLevelName[0] == '/' || currentLevelName[0] == '\\')) + currentLevelName.erase(0, 1); + ///debug(9, "'%s'", currentLevelName.c_str()); + for (uint32 i = 0; i < currentLevelName.size(); ++i) { + if (currentLevelName[i] == '/' || currentLevelName[i] == '\\') { + currentLevelName.erase(i); + ///debug(9, "'%s'", currentLevelName.c_str()); + break; + } + } + + Common::String path = _currentDirectory; + if (path != "") + path += "/"; + path += currentLevelName; + bool lastLevel = (path.equalsIgnoreCase(_requestedPath)); + + ///debug(9, "IdResolveIdRequest: searching for '%s' in '%s'", currentLevelName.c_str(), _currentDirectory.c_str()); + + Common::Array<StorageFile> &files = response.value; + bool found = false; + for (uint32 i = 0; i < files.size(); ++i) { + if ((files[i].isDirectory() || lastLevel) && files[i].name().equalsIgnoreCase(currentLevelName)) { + if (_currentDirectory != "") + _currentDirectory += "/"; + _currentDirectory += files[i].name(); + _currentDirectoryId = files[i].id(); + ///debug(9, "IdResolveIdRequest: found it! new directory and its id: '%s', '%s'", _currentDirectory.c_str(), _currentDirectoryId.c_str()); + listNextDirectory(files[i]); + found = true; + break; + } + } + + if (!found) { + if (lastLevel) + finishError(Networking::ErrorResponse(this, false, true, Common::String("no such file found in its parent directory\n") + _currentDirectoryId, 404)); + else + finishError(Networking::ErrorResponse(this, false, true, "subdirectory not found", 400)); + } +} + +void IdResolveIdRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdResolveIdRequest::handle() {} + +void IdResolveIdRequest::restart() { start(); } + +void IdResolveIdRequest::finishFile(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idresolveidrequest.h b/backends/cloud/id/idresolveidrequest.h new file mode 100644 index 0000000000..e735e96385 --- /dev/null +++ b/backends/cloud/id/idresolveidrequest.h @@ -0,0 +1,60 @@ +/* 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 BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H +#define BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdResolveIdRequest: public Networking::Request { + Common::String _requestedPath; + IdStorage *_storage; + Storage::UploadCallback _uploadCallback; + Common::String _currentDirectory; + Common::String _currentDirectoryId; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void listNextDirectory(StorageFile fileToReturn); + void listedDirectoryCallback(Storage::FileArrayResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void finishFile(StorageFile file); +public: + IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive = false); //TODO: why upload? + virtual ~IdResolveIdRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idstorage.cpp b/backends/cloud/id/idstorage.cpp new file mode 100644 index 0000000000..0ffa8c068c --- /dev/null +++ b/backends/cloud/id/idstorage.cpp @@ -0,0 +1,109 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/id/idstorage.h" +#include "backends/cloud/id/idcreatedirectoryrequest.h" +#include "backends/cloud/id/iddownloadrequest.h" +#include "backends/cloud/id/idlistdirectoryrequest.h" +#include "backends/cloud/id/idresolveidrequest.h" +#include "backends/cloud/id/idstreamfilerequest.h" +#include "common/debug.h" + +namespace Cloud { +namespace Id { + +IdStorage::~IdStorage() {} + +void IdStorage::printFiles(FileArrayResponse response) { + debug(9, "IdStorage: files:"); + Common::Array<StorageFile> &files = response.value; + for (uint32 i = 0; i < files.size(); ++i) { + debug(9, "\t%s%s", files[i].name().c_str(), files[i].isDirectory() ? " (directory)" : ""); + debug(9, "\t%s", files[i].path().c_str()); + debug(9, "\t%s", files[i].id().c_str()); + debug(9, " "); + } +} + +void IdStorage::printBool(BoolResponse response) { + debug(9, "IdStorage: bool: %s", response.value ? "true" : "false"); +} + +void IdStorage::printFile(UploadResponse response) { + debug(9, "\nIdStorage: uploaded file info:"); + debug(9, "\tid: %s", response.value.path().c_str()); + debug(9, "\tname: %s", response.value.name().c_str()); + debug(9, "\tsize: %u", response.value.size()); + debug(9, "\ttimestamp: %u", response.value.timestamp()); +} + +Storage::ListDirectoryCallback IdStorage::getPrintFilesCallback() { + return new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles); +} + +Networking::Request *IdStorage::resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, UploadResponse>(this, &IdStorage::printFile); + return addRequest(new IdResolveIdRequest(this, path, callback, errorCallback)); +} + +Networking::Request *IdStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles); + return addRequest(new IdListDirectoryRequest(this, path, callback, errorCallback, recursive)); +} + +Networking::Request *IdStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, BoolResponse>(this, &IdStorage::printBool); + + //find out the parent path and directory name + Common::String parentPath = "", directoryName = path; + for (uint32 i = path.size(); i > 0; --i) { + if (path[i - 1] == '/' || path[i - 1] == '\\') { + parentPath = path; + parentPath.erase(i - 1); + directoryName.erase(0, i); + break; + } + } + + return addRequest(new IdCreateDirectoryRequest(this, parentPath, directoryName, callback, errorCallback)); +} + +Networking::Request *IdStorage::streamFile(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) { + return addRequest(new IdStreamFileRequest(this, path, outerCallback, errorCallback)); +} + +Networking::Request *IdStorage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new IdDownloadRequest(this, remotePath, localPath, callback, errorCallback)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idstorage.h b/backends/cloud/id/idstorage.h new file mode 100644 index 0000000000..7e64fd4a37 --- /dev/null +++ b/backends/cloud/id/idstorage.h @@ -0,0 +1,83 @@ +/* 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 BACKENDS_CLOUD_ID_IDSTORAGE_H +#define BACKENDS_CLOUD_ID_IDSTORAGE_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +/* + * Id::IdStorage is a special base class, which is created + * to simplify adding new storages which use ids instead of + * paths in their API. + * + * Some Requests are already implemented, and Storage based + * on IdStorage needs to override/implement a few basic things. + * + * For example, ListDirectoryRequest and ResolveIdRequests are + * based on listDirectoryById() and getRootDirectoryId() methods. + * Implementing these you'll get id resolving and directory + * listing by path. + */ + +namespace Cloud { +namespace Id { + +class IdStorage: public Cloud::Storage { +protected: + void printFiles(FileArrayResponse response); + void printBool(BoolResponse response); + void printFile(UploadResponse response); + + ListDirectoryCallback getPrintFilesCallback(); + +public: + virtual ~IdStorage(); + + /** Public Cloud API comes down there. */ + + /** Returns StorageFile with the resolved file's id. */ + virtual Networking::Request *resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + + virtual Common::String getRootDirectoryId() = 0; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idstreamfilerequest.cpp b/backends/cloud/id/idstreamfilerequest.cpp new file mode 100644 index 0000000000..2e68b15412 --- /dev/null +++ b/backends/cloud/id/idstreamfilerequest.cpp @@ -0,0 +1,98 @@ +/* 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 "backends/cloud/id/idstreamfilerequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdStreamFileRequest::IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedFile(path), _storage(storage), _streamCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdStreamFileRequest::~IdStreamFileRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _streamCallback; +} + +void IdStreamFileRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //find file's id + Storage::UploadCallback innerCallback = new Common::Callback<IdStreamFileRequest, Storage::UploadResponse>(this, &IdStreamFileRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback); +} + +void IdStreamFileRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::NetworkReadStreamCallback innerCallback = new Common::Callback<IdStreamFileRequest, Networking::NetworkReadStreamResponse>(this, &IdStreamFileRequest::streamFileCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::streamFileErrorCallback); + _workingRequest = _storage->streamFileById(response.value.id(), innerCallback, innerErrorCallback); +} + +void IdStreamFileRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdStreamFileRequest::streamFileCallback(Networking::NetworkReadStreamResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishStream(response.value); +} + +void IdStreamFileRequest::streamFileErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdStreamFileRequest::handle() {} + +void IdStreamFileRequest::restart() { start(); } + +void IdStreamFileRequest::finishStream(Networking::NetworkReadStream *stream) { + Request::finishSuccess(); + if (_streamCallback) + (*_streamCallback)(Networking::NetworkReadStreamResponse(this, stream)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idstreamfilerequest.h b/backends/cloud/id/idstreamfilerequest.h new file mode 100644 index 0000000000..20d7c0c25b --- /dev/null +++ b/backends/cloud/id/idstreamfilerequest.h @@ -0,0 +1,59 @@ +/* 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 BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H +#define BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdStreamFileRequest: public Networking::Request { + Common::String _requestedFile; + IdStorage *_storage; + Networking::NetworkReadStreamCallback _streamCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void streamFileCallback(Networking::NetworkReadStreamResponse response); + void streamFileErrorCallback(Networking::ErrorResponse error); + void finishStream(Networking::NetworkReadStream *stream); +public: + IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb); + virtual ~IdStreamFileRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/iso8601.cpp b/backends/cloud/iso8601.cpp new file mode 100644 index 0000000000..177ef67f11 --- /dev/null +++ b/backends/cloud/iso8601.cpp @@ -0,0 +1,100 @@ +/* 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 "backends/cloud/iso8601.h" +#include "common/str.h" + +namespace { + +Common::String getSubstring(const Common::String &s, uint32 beginning, uint32 ending) { + //beginning inclusive, ending exclusive + Common::String result = s; + result.erase(ending); + result.erase(0, beginning); + return result; +} + +int find(const char *cstr, uint32 startPosition, char needle) { + const char *res = strchr(cstr + startPosition, needle); + if (res == nullptr) + return -1; + return res - cstr; +} + +} + +namespace Cloud { +namespace ISO8601 { + +uint32 convertToTimestamp(const Common::String &iso8601Date) { + //2015-05-12T15:50:38Z + const char *cstr = iso8601Date.c_str(); + int firstHyphen = find(cstr, 0, '-'); + int secondHyphen = find(cstr, firstHyphen + 1, '-'); + int tSeparator = find(cstr, secondHyphen + 1, 'T'); + int firstColon = find(cstr, tSeparator + 1, ':'); + int secondColon = find(cstr, firstColon + 1, ':'); + int zSeparator = find(cstr, secondColon + 1, 'Z'); + if (zSeparator == -1) + zSeparator = find(cstr, secondColon + 1, '-'); // Box's RFC 3339 + //now note '+1' which means if there ever was '-1' result of find(), we still did a valid find() from 0th char + + Common::String year = getSubstring(iso8601Date, 0, firstHyphen); + Common::String month = getSubstring(iso8601Date, firstHyphen + 1, secondHyphen); + Common::String day = getSubstring(iso8601Date, secondHyphen + 1, tSeparator); + Common::String hour = getSubstring(iso8601Date, tSeparator + 1, firstColon); + Common::String minute = getSubstring(iso8601Date, firstColon + 1, secondColon); + Common::String second = getSubstring(iso8601Date, secondColon + 1, zSeparator); + //now note only 'ending' argument was not '+1' (which means I could've make that function such that -1 means 'until the end') + + int Y = atoi(year.c_str()); + int M = atoi(month.c_str()); + int D = atoi(day.c_str()); + int h = atoi(hour.c_str()); + int m = atoi(minute.c_str()); + int s = atoi(second.c_str()); + + //ok, now I compose a timestamp based on my basic perception of time/date + //yeah, I know about leap years and leap seconds and all, but still we don't care there + + uint32 days = D - 1; + for (int i = 1970; i < Y; ++i) + if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) + days += 366; + else + days += 365; + + int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + for (int i = 1; i < M; ++i) { + days += mdays[i - 1]; + if (i == 2) + if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0)) + days += 1; + } + + uint32 hours = days * 24 + h; + uint32 minutes = hours * 60 + m; + return minutes * 60 + s; +} + +} // End of namespace ISO8601 +} // End of namespace Cloud diff --git a/backends/cloud/iso8601.h b/backends/cloud/iso8601.h new file mode 100644 index 0000000000..cdd817bc07 --- /dev/null +++ b/backends/cloud/iso8601.h @@ -0,0 +1,37 @@ +/* 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 BACKENDS_CLOUD_ISO8601_H +#define BACKENDS_CLOUD_ISO8601_H + +#include "common/str.h" + +namespace Cloud { +namespace ISO8601 { + +/** Returns timestamp corresponding to given ISO 8601 date */ +uint32 convertToTimestamp(const Common::String &iso8601Date); + +} // End of namespace ISO8601 +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp new file mode 100644 index 0000000000..fc7e4f58b0 --- /dev/null +++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp @@ -0,0 +1,150 @@ +/* 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 "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot" + +OneDriveCreateDirectoryRequest::OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveCreateDirectoryRequest::~OneDriveCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void OneDriveCreateDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Common::String name = _path, parent = _path; + if (name.size() != 0) { + uint32 i = name.size() - 1; + while (true) { + parent.deleteLastChar(); + if (name[i] == '/' || name[i] == '\\') { + name.erase(0, i + 1); + break; + } + if (i == 0) + break; + --i; + } + } + + Common::String url = ONEDRIVE_API_SPECIAL_APPROOT; + if (parent != "") + url += ":/" + ConnMan.urlEncode(parent) + ":"; + url += "/children"; + Networking::JsonCallback innerCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::JsonResponse>(this, &OneDriveCreateDirectoryRequest::responseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveCreateDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("folder", new Common::JSONValue(Common::JSONObject())); + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject info = json->asObject(); + if (info.contains("id")) { + finishCreation(true); + } else { + error.response = json->stringify(true); + finishError(error); + } + + delete json; +} + +void OneDriveCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void OneDriveCreateDirectoryRequest::handle() {} + +void OneDriveCreateDirectoryRequest::restart() { start(); } + +Common::String OneDriveCreateDirectoryRequest::date() const { return _date; } + +void OneDriveCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h new file mode 100644 index 0000000000..acaca2bf00 --- /dev/null +++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h @@ -0,0 +1,59 @@ +/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveCreateDirectoryRequest: public Networking::Request { + OneDriveStorage *_storage; + Common::String _path; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~OneDriveCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp new file mode 100644 index 0000000000..a247a9f234 --- /dev/null +++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp @@ -0,0 +1,193 @@ +/* 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 "backends/cloud/onedrive/onedrivelistdirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/children" + +OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveListDirectoryRequest::~OneDriveListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void OneDriveListDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _files.clear(); + _directoriesQueue.clear(); + _currentDirectory = ""; + _ignoreCallback = false; + + _directoriesQueue.push_back(_requestedPath); + listNextDirectory(); +} + +void OneDriveListDirectoryRequest::listNextDirectory() { + if (_directoriesQueue.empty()) { + finishListing(_files); + return; + } + + _currentDirectory = _directoriesQueue.back(); + _directoriesQueue.pop_back(); + + if (_currentDirectory != "" && _currentDirectory.lastChar() != '/' && _currentDirectory.lastChar() != '\\') + _currentDirectory += '/'; + + Common::String dir = _currentDirectory; + dir.deleteLastChar(); + Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, ConnMan.urlEncode(dir).c_str()); + makeRequest(url); +} + +void OneDriveListDirectoryRequest::makeRequest(Common::String url) { + Networking::JsonCallback callback = new Common::Callback<OneDriveListDirectoryRequest, Networking::JsonResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveListDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + Common::JSONValue *json = response.value; + + if (_ignoreCallback) { + delete json; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject object = json->asObject(); + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (!Networking::CurlJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) { + error.response = "\"value\" not found or that's not an array!"; + finishError(error); + delete json; + return; + } + + Common::JSONArray items = object.getVal("value")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue; + + Common::String path = _currentDirectory + item.getVal("name")->asString(); + bool isDirectory = item.contains("folder"); + uint32 size = item.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString()); + + StorageFile file(path, size, timestamp, isDirectory); + _files.push_back(file); + if (_requestedRecursive && file.isDirectory()) { + _directoriesQueue.push_back(file.path()); + } + } + + bool hasMore = object.contains("@odata.nextLink"); + if (hasMore) { + if (!Networking::CurlJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) { + error.response = "\"@odata.nextLink\" is not a string!"; + finishError(error); + delete json; + return; + } + + makeRequest(object.getVal("@odata.nextLink")->asString()); + } else { + listNextDirectory(); + } + + delete json; +} + +void OneDriveListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void OneDriveListDirectoryRequest::handle() {} + +void OneDriveListDirectoryRequest::restart() { start(); } + +Common::String OneDriveListDirectoryRequest::date() const { return _date; } + +void OneDriveListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.h b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h new file mode 100644 index 0000000000..eb510ab257 --- /dev/null +++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h @@ -0,0 +1,66 @@ +/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + OneDriveStorage *_storage; + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Common::Array<Common::String> _directoriesQueue; + Common::String _currentDirectory; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void listNextDirectory(); + void listedDirectoryCallback(Networking::JsonResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void makeRequest(Common::String url); + void finishListing(Common::Array<StorageFile> &files); +public: + OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~OneDriveListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivestorage.cpp b/backends/cloud/onedrive/onedrivestorage.cpp new file mode 100644 index 0000000000..4b70bb73b9 --- /dev/null +++ b/backends/cloud/onedrive/onedrivestorage.cpp @@ -0,0 +1,326 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h" +#include "backends/cloud/onedrive/onedriveuploadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_OAUTH2_TOKEN "https://login.live.com/oauth20_token.srf" +#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://api.onedrive.com/v1.0/drive/special/approot:/" +#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot" + +char *OneDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *OneDriveStorage::SECRET = nullptr; + +void OneDriveStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_ONEDRIVE_KEY; + SECRET = RELEASE_ONEDRIVE_SECRET; +#else + Common::String k = ConfMan.get("ONEDRIVE_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("ONEDRIVE_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +OneDriveStorage::OneDriveStorage(Common::String accessToken, Common::String userId, Common::String refreshToken): + _token(accessToken), _uid(userId), _refreshToken(refreshToken) {} + +OneDriveStorage::OneDriveStorage(Common::String code) { + getAccessToken( + new Common::Callback<OneDriveStorage, BoolResponse>(this, &OneDriveStorage::codeFlowComplete), + new Common::Callback<OneDriveStorage, Networking::ErrorResponse>(this, &OneDriveStorage::codeFlowFailed), + code + ); +} + +OneDriveStorage::~OneDriveStorage() {} + +void OneDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("OneDriveStorage: no refresh token available to get new access token."); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, BoolResponse, Networking::JsonResponse>(this, &OneDriveStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, ONEDRIVE_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + } else { + request->addPostField("refresh_token=" + _refreshToken); + request->addPostField("grant_type=refresh_token"); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void OneDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "OneDriveStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "user_id", "OneDriveStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "OneDriveStorage")) { + warning("OneDriveStorage: bad response, no token or user_id passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + _uid = result.getVal("user_id")->asString(); + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void OneDriveStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("OneDriveStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + ConfMan.removeKey("onedrive_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageOneDriveId); + ConfMan.flushToDisk(); +} + +void OneDriveStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "OneDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void OneDriveStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String OneDriveStorage::name() const { + return "OneDrive"; +} + +void OneDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 26843545600L; // 25 GB, because I actually don't know any way to find out the real one + + if (Networking::CurlJsonRequest::jsonContainsObject(info, "createdBy", "OneDriveStorage::infoInnerCallback")) { + Common::JSONObject createdBy = info.getVal("createdBy")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsObject(createdBy, "user", "OneDriveStorage::infoInnerCallback")) { + Common::JSONObject user = createdBy.getVal("user")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "id", "OneDriveStorage::infoInnerCallback")) + uid = user.getVal("id")->asString(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "OneDriveStorage::infoInnerCallback")) + name = user.getVal("displayName")->asString(); + } + } + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "size", "OneDriveStorage::infoInnerCallback")) { + quotaUsed = info.getVal("size")->asIntegerNumber(); + } + + Common::String username = email; + if (username == "") + username = name; + if (username == "") + username = uid; + CloudMan.setStorageUsername(kStorageOneDriveId, username); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage::fileInfoCallback: NULL passed instead of JSON"); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::fileInfoCallback")) { + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete json; + delete outerCallback; + return; + } + + Common::JSONObject result = response.value->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "@content.downloadUrl", "OneDriveStorage::fileInfoCallback")) { + warning("OneDriveStorage: downloadUrl not found in passed JSON"); + debug(9, "%s", response.value->stringify().c_str()); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete json; + delete outerCallback; + return; + } + + const char *url = result.getVal("@content.downloadUrl")->asString().c_str(); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse( + response.request, + new Networking::NetworkReadStream(url, nullptr, "") + )); + + delete json; + delete outerCallback; +} + +Networking::Request *OneDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive)); +} + +Networking::Request *OneDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback)); +} + +Networking::Request *OneDriveStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) { + Common::String url = ONEDRIVE_API_SPECIAL_APPROOT_ID + ConnMan.urlEncode(path); + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, Networking::NetworkReadStreamResponse, Networking::JsonResponse>(this, &OneDriveStorage::fileInfoCallback, outerCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Networking::Request *OneDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback)); +} + +Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &OneDriveStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, ONEDRIVE_API_SPECIAL_APPROOT); + request->addHeader("Authorization: bearer " + _token); + return addRequest(request); +} + +Common::String OneDriveStorage::savesDirectoryPath() { return "saves/"; } + +OneDriveStorage *OneDriveStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no user_id found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new OneDriveStorage(accessToken, userId, refreshToken); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivestorage.h b/backends/cloud/onedrive/onedrivestorage.h new file mode 100644 index 0000000000..59c61074e3 --- /dev/null +++ b/backends/cloud/onedrive/onedrivestorage.h @@ -0,0 +1,113 @@ +/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage: public Cloud::Storage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _uid, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + OneDriveStorage(Common::String token, Common::String uid, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + OneDriveStorage(Common::String code); + virtual ~OneDriveStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return OneDriveStorage for those. + * @return pointer to the newly created OneDriveStorage or 0 if some problem occured. + */ + static OneDriveStorage *loadFromConfig(Common::String keyPrefix); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.cpp b/backends/cloud/onedrive/onedrivetokenrefresher.cpp new file mode 100644 index 0000000000..ce7895f41c --- /dev/null +++ b/backends/cloud/onedrive/onedrivetokenrefresher.cpp @@ -0,0 +1,130 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace OneDrive { + +OneDriveTokenRefresher::OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +OneDriveTokenRefresher::~OneDriveTokenRefresher() {} + +void OneDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("OneDriveTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void OneDriveTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "OneDriveTokenRefresher")) { + Common::JSONObject result = json->asObject(); + long httpResponseCode = -1; + if (result.contains("error") && jsonIsObject(result.getVal("error"), "OneDriveTokenRefresher")) { + //new token needed => request token & then retry original request + if (_stream) { + httpResponseCode = _stream->httpResponseCode(); + debug(9, "OneDriveTokenRefresher: code = %ld", httpResponseCode); + } + + Common::JSONObject error = result.getVal("error")->asObject(); + bool irrecoverable = true; + + Common::String code, message; + if (jsonContainsString(error, "code", "OneDriveTokenRefresher")) { + code = error.getVal("code")->asString(); + debug(9, "OneDriveTokenRefresher: code = %s", code.c_str()); + } + + if (jsonContainsString(error, "message", "OneDriveTokenRefresher")) { + message = error.getVal("message")->asString(); + debug(9, "OneDriveTokenRefresher: message = %s", message.c_str()); + } + + //determine whether token refreshing would help in this situation + if (code == "itemNotFound") { + if (message.contains("application ID")) + irrecoverable = false; + } + + if (code == "unauthenticated") + irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void OneDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void OneDriveTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.h b/backends/cloud/onedrive/onedrivetokenrefresher.h new file mode 100644 index 0000000000..77e34d4e03 --- /dev/null +++ b/backends/cloud/onedrive/onedrivetokenrefresher.h @@ -0,0 +1,52 @@ +/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveTokenRefresher: public Networking::CurlJsonRequest { + OneDriveStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); +public: + OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~OneDriveTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedriveuploadrequest.cpp b/backends/cloud/onedrive/onedriveuploadrequest.cpp new file mode 100644 index 0000000000..41e6e2a37b --- /dev/null +++ b/backends/cloud/onedrive/onedriveuploadrequest.cpp @@ -0,0 +1,191 @@ +/* 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 "backends/cloud/onedrive/onedriveuploadrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "onedrivetokenrefresher.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/upload.createSession" +#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/content" + +OneDriveUploadRequest::OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveUploadRequest::~OneDriveUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void OneDriveUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (_contentsStream == nullptr) { + warning("OneDriveUploadRequest: cannot restart because no stream given"); + finishError(Networking::ErrorResponse(this, false, true, "No stream given", -1)); + return; + } + if (!_contentsStream->seek(0)) { + warning("OneDriveUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _ignoreCallback = false; + + uploadNextPart(); +} + +void OneDriveUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + + if (_uploadUrl == "" && (uint32)_contentsStream->size() > UPLOAD_PER_ONE_REQUEST) { + Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD, ConnMan.urlEncode(_savePath).c_str()); //folder must exist + Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->setBuffer(new byte[1], 0); //use POST + _workingRequest = ConnMan.addRequest(request); + return; + } + + Common::String url; + if (_uploadUrl == "") { + url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CONTENT, ConnMan.urlEncode(_savePath).c_str()); + } else { + url = _uploadUrl; + } + + Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->usePut(); + + uint32 oldPos = _contentsStream->pos(); + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + request->setBuffer(buffer, size); + + if (_uploadUrl != "") { + request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size())); + } else if (_contentsStream->size() == 0) { + warning("\"Sorry, OneDrive can't upload empty files\""); + finishUpload(StorageFile(_savePath, 0, 0, false)); + delete request; + return; + } + + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + if (object.contains("error")) { + warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str()); + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) { + //finished + Common::String path = _savePath; + uint32 size = object.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString()); + finishUpload(StorageFile(path, size, timestamp, false)); + return; + } + + if (_uploadUrl == "") { + if (Networking::CurlJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest")) + _uploadUrl = object.getVal("uploadUrl")->asString(); + } + } + + if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) { + warning("OneDriveUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void OneDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void OneDriveUploadRequest::handle() {} + +void OneDriveUploadRequest::restart() { start(); } + +void OneDriveUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedriveuploadrequest.h b/backends/cloud/onedrive/onedriveuploadrequest.h new file mode 100644 index 0000000000..4e2cb24a7f --- /dev/null +++ b/backends/cloud/onedrive/onedriveuploadrequest.h @@ -0,0 +1,61 @@ +/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace OneDrive { +class OneDriveStorage; + +class OneDriveUploadRequest: public Networking::Request { + OneDriveStorage *_storage; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _uploadUrl; + + void start(); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~OneDriveUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp new file mode 100644 index 0000000000..fff46c3a83 --- /dev/null +++ b/backends/cloud/savessyncrequest.cpp @@ -0,0 +1,403 @@ +/* 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 "backends/cloud/savessyncrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/saves/default/default-saves.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/file.h" +#include "common/json.h" +#include "common/savefile.h" +#include "common/system.h" +#include "gui/saveload-dialog.h" + +namespace Cloud { + +SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb): + Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _boolCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +SavesSyncRequest::~SavesSyncRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void SavesSyncRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _currentDownloadingFile = StorageFile(); + _currentUploadingFile = ""; + _filesToDownload.clear(); + _filesToUpload.clear(); + _localFilesTimestamps.clear(); + _totalFilesToHandle = 0; + _ignoreCallback = false; + + //load timestamps + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + + //list saves directory + Common::String dir = _storage->savesDirectoryPath(); + if (dir.lastChar() == '/') + dir.deleteLastChar(); + _workingRequest = _storage->listDirectory( + dir, + new Common::Callback<SavesSyncRequest, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback) + ); + if (!_workingRequest) finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + if (response.request) _date = response.request->date(); + + Common::HashMap<Common::String, bool> localFileNotAvailableInCloud; + for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) { + localFileNotAvailableInCloud[i->_key] = true; + } + + //determine which files to download and which files to upload + Common::Array<StorageFile> &remoteFiles = response.value; + uint64 totalSize = 0; + for (uint32 i = 0; i < remoteFiles.size(); ++i) { + StorageFile &file = remoteFiles[i]; + if (file.isDirectory()) + continue; + totalSize += file.size(); + if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME) + continue; + + Common::String name = file.name(); + if (!_localFilesTimestamps.contains(name)) { + _filesToDownload.push_back(file); + } else { + localFileNotAvailableInCloud[name] = false; + + if (_localFilesTimestamps[name] == file.timestamp()) + continue; + + //we actually can have some files not only with timestamp < remote + //but also with timestamp > remote (when we have been using ANOTHER CLOUD and then switched back) + if (_localFilesTimestamps[name] > file.timestamp() || _localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP) + _filesToUpload.push_back(file.name()); + else + _filesToDownload.push_back(file); + } + } + + CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize); + + //upload files which are unavailable in cloud + for (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) { + if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME) + continue; + if (i->_value) + _filesToUpload.push_back(i->_key); + } + + debug(9, "\nSavesSyncRequest: download files:"); + for (uint32 i = 0; i < _filesToDownload.size(); ++i) { + debug(9, "%s", _filesToDownload[i].name().c_str()); + } + debug(9, "\nSavesSyncRequest: upload files:"); + for (uint32 i = 0; i < _filesToUpload.size(); ++i) { + debug(9, "%s", _filesToUpload[i].c_str()); + } + _totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size(); + + //start downloading files + downloadNextFile(); +} + +void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + bool irrecoverable = error.interrupted || error.failed; + if (error.failed) { + Common::JSONValue *value = Common::JSON::parse(error.response.c_str()); + if (value) { + if (value->isObject()) { + Common::JSONObject object = value->asObject(); + + //Dropbox-related error: + if (object.contains("error_summary") && object.getVal("error_summary")->isString()) { + Common::String summary = object.getVal("error_summary")->asString(); + if (summary.contains("not_found")) { + irrecoverable = false; + } + } + + //OneDrive-related error: + if (object.contains("error") && object.getVal("error")->isObject()) { + Common::JSONObject errorNode = object.getVal("error")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) { + Common::String code = errorNode.getVal("code")->asString(); + if (code == "itemNotFound") { + irrecoverable = false; + } + } + } + } + delete value; + } + + //Google Drive and Box-related ScummVM-based error + if (error.response.contains("subdirectory not found")) { + irrecoverable = false; //base "/ScummVM/" folder not found + } else if (error.response.contains("no such file found in its parent directory")) { + irrecoverable = false; //"Saves" folder within "/ScummVM/" not found + } + } + + if (irrecoverable) { + finishError(error); + return; + } + + //we're lucky - user just lacks his "/cloud/" folder - let's create one + Common::String dir = _storage->savesDirectoryPath(); + if (dir.lastChar() == '/') + dir.deleteLastChar(); + debug(9, "SavesSyncRequest: creating %s", dir.c_str()); + _workingRequest = _storage->createDirectory( + dir, + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback) + ); + if (!_workingRequest) + finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if failed to create saves directory + if (!response.value) { + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //continue with empty files list + Common::Array<StorageFile> files; + directoryListedCallback(Storage::ListDirectoryResponse(response.request, files)); +} + +void SavesSyncRequest::directoryCreatedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if failed to create saves directory + finishError(error); +} + +void SavesSyncRequest::downloadNextFile() { + if (_filesToDownload.empty()) { + _currentDownloadingFile = StorageFile("", 0, 0, false); //so getFilesToDownload() would return an empty array + sendCommand(GUI::kSavesSyncEndedCmd, 0); + uploadNextFile(); + return; + } + + _currentDownloadingFile = _filesToDownload.back(); + _filesToDownload.pop_back(); + + sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100)); + + debug(9, "SavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100)); + _workingRequest = _storage->downloadById( + _currentDownloadingFile.id(), + DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()), + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback) + ); + if (!_workingRequest) + finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if download failed + if (!response.value) { + //delete the incomplete file + g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name()); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update local timestamp for downloaded file + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp(); + DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps); + + //continue downloading files + downloadNextFile(); +} + +void SavesSyncRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if download failed + finishError(error); +} + +void SavesSyncRequest::uploadNextFile() { + if (_filesToUpload.empty()) { + finishSync(true); + return; + } + + _currentUploadingFile = _filesToUpload.back(); + _filesToUpload.pop_back(); + + debug(9, "SavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100)); + if (_storage->uploadStreamSupported()) { + _workingRequest = _storage->upload( + _storage->savesDirectoryPath() + _currentUploadingFile, + g_system->getSavefileManager()->openRawFile(_currentUploadingFile), + new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback) + ); + } else { + _workingRequest = _storage->upload( + _storage->savesDirectoryPath() + _currentUploadingFile, + DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile), + new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback) + ); + } + if (!_workingRequest) finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //update local timestamp for the uploaded file + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + _localFilesTimestamps[_currentUploadingFile] = response.value.timestamp(); + DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps); + + //continue uploading files + uploadNextFile(); +} + +void SavesSyncRequest::fileUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if upload failed + finishError(error); +} + +void SavesSyncRequest::handle() {} + +void SavesSyncRequest::restart() { start(); } + +double SavesSyncRequest::getDownloadingProgress() const { + if (_totalFilesToHandle == 0) { + if (_state == Networking::FINISHED) + return 1; //nothing to upload and download => Request ends soon + return 0; //directory not listed yet + } + + if (_totalFilesToHandle == _filesToUpload.size()) + return 1; //nothing to download => download complete + + uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size(); + uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0); + return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload); +} + +double SavesSyncRequest::getProgress() const { + if (_totalFilesToHandle == 0) { + if (_state == Networking::FINISHED) + return 1; //nothing to upload and download => Request ends soon + return 0; //directory not listed yet + } + + return (double)(_totalFilesToHandle - _filesToDownload.size() - _filesToUpload.size()) / (double)(_totalFilesToHandle); +} + +Common::Array<Common::String> SavesSyncRequest::getFilesToDownload() { + Common::Array<Common::String> result; + for (uint32 i = 0; i < _filesToDownload.size(); ++i) + result.push_back(_filesToDownload[i].name()); + if (_currentDownloadingFile.name() != "") + result.push_back(_currentDownloadingFile.name()); + return result; +} + +void SavesSyncRequest::finishError(Networking::ErrorResponse error) { + debug(9, "SavesSync::finishError"); + //if we were downloading a file - remember the name + //and make the Request close() it, so we can delete it + Common::String name = _currentDownloadingFile.name(); + if (_workingRequest) { + _ignoreCallback = true; + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + } + //unlock all the files by making getFilesToDownload() return empty array + _currentDownloadingFile = StorageFile(); + _filesToDownload.clear(); + //delete the incomplete file + if (name != "") + g_system->getSavefileManager()->removeSavefile(name); + Request::finishError(error); +} + +void SavesSyncRequest::finishSync(bool success) { + Request::finishSuccess(); + + //update last successful sync date + CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date); + + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Cloud diff --git a/backends/cloud/savessyncrequest.h b/backends/cloud/savessyncrequest.h new file mode 100644 index 0000000000..e891e93969 --- /dev/null +++ b/backends/cloud/savessyncrequest.h @@ -0,0 +1,80 @@ +/* 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 BACKENDS_CLOUD_SAVESSYNCREQUEST_H +#define BACKENDS_CLOUD_SAVESSYNCREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/cloud/storage.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "gui/object.h" + +namespace Cloud { + +class SavesSyncRequest: public Networking::Request, public GUI::CommandSender { + Storage *_storage; + Storage::BoolCallback _boolCallback; + Common::HashMap<Common::String, uint32> _localFilesTimestamps; + Common::Array<StorageFile> _filesToDownload; + Common::Array<Common::String> _filesToUpload; + StorageFile _currentDownloadingFile; + Common::String _currentUploadingFile; + Request *_workingRequest; + bool _ignoreCallback; + uint32 _totalFilesToHandle; + Common::String _date; + + void start(); + void directoryListedCallback(Storage::ListDirectoryResponse response); + void directoryListedErrorCallback(Networking::ErrorResponse error); + void directoryCreatedCallback(Storage::BoolResponse response); + void directoryCreatedErrorCallback(Networking::ErrorResponse error); + void fileDownloadedCallback(Storage::BoolResponse response); + void fileDownloadedErrorCallback(Networking::ErrorResponse error); + void fileUploadedCallback(Storage::UploadResponse response); + void fileUploadedErrorCallback(Networking::ErrorResponse error); + void downloadNextFile(); + void uploadNextFile(); + virtual void finishError(Networking::ErrorResponse error); + void finishSync(bool success); + +public: + SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb); + virtual ~SavesSyncRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getDownloadingProgress() const; + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Returns an array of saves names which are not downloaded yet. */ + Common::Array<Common::String> getFilesToDownload(); +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storage.cpp b/backends/cloud/storage.cpp new file mode 100644 index 0000000000..910d80d153 --- /dev/null +++ b/backends/cloud/storage.cpp @@ -0,0 +1,342 @@ +/* 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 "backends/cloud/storage.h" +#include "backends/cloud/downloadrequest.h" +#include "backends/cloud/folderdownloadrequest.h" +#include "backends/cloud/savessyncrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "common/debug.h" +#include "common/file.h" +#include <common/translation.h> + +namespace Cloud { + +Storage::Storage(): + _runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false), + _downloadFolderRequest(nullptr) {} + +Storage::~Storage() {} + +Networking::ErrorCallback Storage::getErrorPrintingCallback() { + return new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::printErrorResponse); +} + +void Storage::printErrorResponse(Networking::ErrorResponse error) { + debug(9, "Storage: error response (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); +} + +Networking::Request *Storage::addRequest(Networking::Request *request) { + _runningRequestsMutex.lock(); + ++_runningRequestsCount; + if (_runningRequestsCount == 1) + debug(9, "Storage is working now"); + _runningRequestsMutex.unlock(); + return ConnMan.addRequest(request, new Common::Callback<Storage, Networking::Request *>(this, &Storage::requestFinishedCallback)); +} + +void Storage::requestFinishedCallback(Networking::Request *invalidRequestPointer) { + bool restartSync = false; + + _runningRequestsMutex.lock(); + if (invalidRequestPointer == _savesSyncRequest) + _savesSyncRequest = nullptr; + --_runningRequestsCount; + if (_syncRestartRequestsed) + restartSync = true; + if (_runningRequestsCount == 0 && !restartSync) + debug(9, "Storage is not working now"); + _runningRequestsMutex.unlock(); + + if (restartSync) + syncSaves(nullptr, nullptr); +} + +Networking::Request *Storage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) errorCallback = getErrorPrintingCallback(); + + Common::File *f = new Common::File(); + if (!f->open(localPath)) { + warning("Storage: unable to open file to upload from"); + if (errorCallback) + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1)); + delete errorCallback; + delete callback; + delete f; + return nullptr; + } + + return upload(remotePath, f, callback, errorCallback); +} + +bool Storage::uploadStreamSupported() { + return true; +} + +Networking::Request *Storage::streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + //most Storages use paths instead of ids, so this should work + return streamFileById(path, callback, errorCallback); +} + +Networking::Request *Storage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + //most Storages use paths instead of ids, so this should work + return downloadById(remotePath, localPath, callback, errorCallback); +} + +Networking::Request *Storage::downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) errorCallback = getErrorPrintingCallback(); + + Common::DumpFile *f = new Common::DumpFile(); + if (!f->open(localPath, true)) { + warning("Storage: unable to open file to download into"); + if (errorCallback) (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1)); + delete errorCallback; + delete callback; + delete f; + return nullptr; + } + + return addRequest(new DownloadRequest(this, callback, errorCallback, remoteId, f)); +} + +Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive)); +} + +SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) { + warning("Storage::syncSaves: there is a sync in progress already"); + _syncRestartRequestsed = true; + _runningRequestsMutex.unlock(); + return _savesSyncRequest; + } + if (!callback) + callback = new Common::Callback<Storage, BoolResponse>(this, &Storage::savesSyncDefaultCallback); + if (!errorCallback) + errorCallback = new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::savesSyncDefaultErrorCallback); + _savesSyncRequest = new SavesSyncRequest(this, callback, errorCallback); + _syncRestartRequestsed = false; + _runningRequestsMutex.unlock(); + return (SavesSyncRequest *)addRequest(_savesSyncRequest); //who knows what that ConnMan could return in the future +} + +bool Storage::isWorking() { + _runningRequestsMutex.lock(); + bool working = _runningRequestsCount > 0; + _runningRequestsMutex.unlock(); + return working; +} + +///// SavesSyncRequest-related ///// + +bool Storage::isSyncing() { + _runningRequestsMutex.lock(); + bool syncing = _savesSyncRequest != nullptr; + _runningRequestsMutex.unlock(); + return syncing; +} + +double Storage::getSyncDownloadingProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getDownloadingProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +double Storage::getSyncProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::Array<Common::String> Storage::getSyncingFiles() { + Common::Array<Common::String> result; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getFilesToDownload(); + _runningRequestsMutex.unlock(); + return result; +} + +void Storage::cancelSync() { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + _savesSyncRequest->finish(); + _runningRequestsMutex.unlock(); +} + +void Storage::setSyncTarget(GUI::CommandReceiver *target) { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + _savesSyncRequest->setTarget(target); + _runningRequestsMutex.unlock(); +} + +void Storage::savesSyncDefaultCallback(BoolResponse response) { + _runningRequestsMutex.lock(); + _savesSyncRequest = nullptr; + _runningRequestsMutex.unlock(); + + if (!response.value) + warning("SavesSyncRequest called success callback with `false` argument"); + g_system->displayMessageOnOSD(_("Saves sync complete.")); +} + +void Storage::savesSyncDefaultErrorCallback(Networking::ErrorResponse error) { + _runningRequestsMutex.lock(); + _savesSyncRequest = nullptr; + _runningRequestsMutex.unlock(); + + printErrorResponse(error); + + if (error.interrupted) + g_system->displayMessageOnOSD(_("Saves sync was cancelled.")); + else + g_system->displayMessageOnOSD(_("Saves sync failed.\nCheck your Internet connection.")); +} + +///// DownloadFolderRequest-related ///// + +bool Storage::startDownload(Common::String remotePath, Common::String localPath) { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) { + warning("Storage::startDownload: there is a download in progress already"); + _runningRequestsMutex.unlock(); + return false; + } + _downloadFolderRequest = (FolderDownloadRequest *)downloadFolder( + remotePath, localPath, + new Common::Callback<Storage, FileArrayResponse>(this, &Storage::directoryDownloadedCallback), + new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::directoryDownloadedErrorCallback), + true + ); + _runningRequestsMutex.unlock(); + return true; +} + +void Storage::cancelDownload() { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + _downloadFolderRequest->finish(); + _runningRequestsMutex.unlock(); +} + +void Storage::setDownloadTarget(GUI::CommandReceiver *target) { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + _downloadFolderRequest->setTarget(target); + _runningRequestsMutex.unlock(); +} + +bool Storage::isDownloading() { + _runningRequestsMutex.lock(); + bool syncing = _downloadFolderRequest != nullptr; + _runningRequestsMutex.unlock(); + return syncing; +} + +double Storage::getDownloadingProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadBytesNumber() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getDownloadedBytes(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadTotalBytesNumber() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getTotalBytesToDownload(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadSpeed() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getDownloadSpeed(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::String Storage::getDownloadRemoteDirectory() { + Common::String result = ""; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getRemotePath(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::String Storage::getDownloadLocalDirectory() { + Common::String result = ""; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getLocalPath(); + _runningRequestsMutex.unlock(); + return result; +} + +void Storage::directoryDownloadedCallback(FileArrayResponse response) { + _runningRequestsMutex.lock(); + _downloadFolderRequest = nullptr; + _runningRequestsMutex.unlock(); + + Common::String message; + if (response.value.size()) { + message = Common::String::format(_("Download complete.\nFailed to download %u files."), response.value.size()); + } else { + message = _("Download complete."); + } + g_system->displayMessageOnOSD(message.c_str()); +} + +void Storage::directoryDownloadedErrorCallback(Networking::ErrorResponse error) { + _runningRequestsMutex.lock(); + _downloadFolderRequest = nullptr; + _runningRequestsMutex.unlock(); + + g_system->displayMessageOnOSD(_("Download failed.")); +} + +} // End of namespace Cloud diff --git a/backends/cloud/storage.h b/backends/cloud/storage.h new file mode 100644 index 0000000000..6a5765f13a --- /dev/null +++ b/backends/cloud/storage.h @@ -0,0 +1,238 @@ +/* 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 BACKENDS_CLOUD_STORAGE_H +#define BACKENDS_CLOUD_STORAGE_H + +#include "backends/cloud/storagefile.h" +#include "backends/cloud/storageinfo.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curlrequest.h" +#include "common/array.h" +#include "common/callback.h" +#include "common/mutex.h" +#include "common/stream.h" +#include "common/str.h" + +namespace GUI { + +class CommandReceiver; + +} + +namespace Cloud { + +class SavesSyncRequest; +class FolderDownloadRequest; + +class Storage { +public: + typedef Networking::Response<Common::Array<StorageFile>&> FileArrayResponse; + typedef Networking::Response<StorageInfo> StorageInfoResponse; + typedef Networking::Response<bool> BoolResponse; + typedef Networking::Response<StorageFile> UploadResponse; + typedef Networking::Response<Common::Array<StorageFile> &> ListDirectoryResponse; + + typedef Common::BaseCallback<FileArrayResponse> *FileArrayCallback; + typedef Common::BaseCallback<StorageInfoResponse> *StorageInfoCallback; + typedef Common::BaseCallback<BoolResponse> *BoolCallback; + typedef Common::BaseCallback<UploadResponse> *UploadCallback; + typedef Common::BaseCallback<ListDirectoryResponse> *ListDirectoryCallback; + +protected: + /** Keeps track of running requests. */ + uint32 _runningRequestsCount; + Common::Mutex _runningRequestsMutex; + + /** SavesSyncRequest-related */ + SavesSyncRequest *_savesSyncRequest; + bool _syncRestartRequestsed; + + /** FolderDownloadRequest-related */ + FolderDownloadRequest *_downloadFolderRequest; + + /** Returns default error callback (printErrorResponse). */ + virtual Networking::ErrorCallback getErrorPrintingCallback(); + + /** Prints ErrorResponse contents with debug(). */ + virtual void printErrorResponse(Networking::ErrorResponse error); + + /** + * Adds request to the ConnMan, but also increases _runningRequestsCount. + * This method should be used by Storage implementations instead of + * direct ConnMan.addRequest() call. + * + * @return the same Request pointer, just as a shortcut + */ + virtual Networking::Request *addRequest(Networking::Request *request); + + /** + * Decreases _runningRequestCount. It's called from ConnMan automatically. + * Passed pointer is dangling, but one can use the address to determine + * some special Requests (which addresses were remembered somewhere). + */ + virtual void requestFinishedCallback(Networking::Request *invalidRequestPointer); + +public: + Storage(); + virtual ~Storage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix) = 0; + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const = 0; + + /** + * Public Cloud API comes down there. + * + * All Cloud API methods return Networking::Request *, which + * might be used to control request. All methods also accept + * a callback, which is called, when request is complete. + */ + + /** Returns ListDirectoryResponse with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) = 0; + + /** Returns StorageFile with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) = 0; + virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns whether Storage supports upload(ReadStream). */ + virtual bool uploadStreamSupported(); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */ + virtual Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Calls the callback when finished. */ + virtual SavesSyncRequest *syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** + * Returns the StorageInfo struct via <callback>. + * Calls the <errorCallback> if failed to get information. + * + * @note on success Storage should also call + * CloudMan.setStorageUsername(). + */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath() = 0; + + /** Returns whether there are any requests running. */ + virtual bool isWorking(); + + ///// SavesSyncRequest-related ///// + + /** Returns whether there is a SavesSyncRequest running. */ + virtual bool isSyncing(); + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + virtual double getSyncDownloadingProgress(); + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + virtual double getSyncProgress(); + + /** Returns an array of saves names which are not yet synced (thus cannot be used). */ + virtual Common::Array<Common::String> getSyncingFiles(); + + /** Cancels running sync. */ + virtual void cancelSync(); + + /** Sets SavesSyncRequest's target to given CommandReceiver. */ + virtual void setSyncTarget(GUI::CommandReceiver *target); + +protected: + /** Finishes the sync. Shows an OSD message. */ + virtual void savesSyncDefaultCallback(BoolResponse response); + + /** Finishes the sync. Shows an OSD message. */ + virtual void savesSyncDefaultErrorCallback(Networking::ErrorResponse error); + +public: + ///// DownloadFolderRequest-related ///// + + /** Starts a folder download. */ + virtual bool startDownload(Common::String remotePath, Common::String localPath); + + /** Cancels running download. */ + virtual void cancelDownload(); + + /** Sets FolderDownloadRequest's target to given CommandReceiver. */ + virtual void setDownloadTarget(GUI::CommandReceiver *target); + + /** Returns whether there is a FolderDownloadRequest running. */ + virtual bool isDownloading(); + + /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */ + virtual double getDownloadingProgress(); + + /** Returns a number of bytes that is downloaded in current download progress. */ + virtual uint64 getDownloadBytesNumber(); + + /** Returns a total number of bytes to be downloaded in current download progress. */ + virtual uint64 getDownloadTotalBytesNumber(); + + /** Returns download speed of current download progress. */ + virtual uint64 getDownloadSpeed(); + + /** Returns remote directory path. */ + virtual Common::String getDownloadRemoteDirectory(); + + /** Returns local directory path. */ + virtual Common::String getDownloadLocalDirectory(); + +protected: + /** Finishes the download. Shows an OSD message. */ + virtual void directoryDownloadedCallback(FileArrayResponse response); + + /** Finishes the download. Shows an OSD message. */ + virtual void directoryDownloadedErrorCallback(Networking::ErrorResponse error); +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storagefile.cpp b/backends/cloud/storagefile.cpp new file mode 100644 index 0000000000..62d492292d --- /dev/null +++ b/backends/cloud/storagefile.cpp @@ -0,0 +1,68 @@ +/* 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 "backends/cloud/storagefile.h" + +namespace Cloud { + +StorageFile::StorageFile() { + _id = ""; + _path = ""; + _name = ""; + _size = 0; + _timestamp = 0; + _isDirectory = false; +} + +StorageFile::StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir) { + _id = pth; + _path = pth; + + _name = pth; + if (_name.size() != 0) { + uint32 i = _name.size() - 1; + while (true) { + if (_name[i] == '/' || _name[i] == '\\') { + _name.erase(0, i + 1); + break; + } + if (i == 0) + break; + --i; + } + } + + _size = sz; + _timestamp = ts; + _isDirectory = dir; +} + +StorageFile::StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir) { + _id = id; + _path = path; + _name = name; + _size = sz; + _timestamp = ts; + _isDirectory = dir; +} + +} // End of namespace Cloud diff --git a/backends/cloud/storagefile.h b/backends/cloud/storagefile.h new file mode 100644 index 0000000000..1183524f48 --- /dev/null +++ b/backends/cloud/storagefile.h @@ -0,0 +1,65 @@ +/* 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 BACKENDS_CLOUD_STORAGEFILE_H +#define BACKENDS_CLOUD_STORAGEFILE_H + +#include "common/str.h" + +namespace Cloud { + +/** + * StorageFile represents a file storaged on remote cloud storage. + * It contains basic information about a file, and might be used + * when listing directories or syncing files. + * + * Some storages (Google Drive, for example) don't have an actual + * path notation to address files. Instead, they are using ids. + * As resolving id by path is not a fast operation, it's required + * to use ids if they are known, but user-friendly paths are + * necessary too, because these are used by Requests. + * + * If storage supports path notation, id would actually contain path. + */ +class StorageFile { + Common::String _id, _path, _name; + uint32 _size, _timestamp; + bool _isDirectory; + +public: + StorageFile(); //invalid empty file + StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir); + StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir); + + Common::String id() const { return _id; } + Common::String path() const { return _path; } + Common::String name() const { return _name; } + uint32 size() const { return _size; } + uint32 timestamp() const { return _timestamp; } + bool isDirectory() const { return _isDirectory; } + + void setPath(Common::String path_) { _path = path_; } +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storageinfo.h b/backends/cloud/storageinfo.h new file mode 100644 index 0000000000..f1fb540974 --- /dev/null +++ b/backends/cloud/storageinfo.h @@ -0,0 +1,53 @@ +/* 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 BACKENDS_CLOUD_STORAGEINFO_H +#define BACKENDS_CLOUD_STORAGEINFO_H + +#include "common/str.h" + +namespace Cloud { + +/** +* StorageInfo contains information about remote cloud storage. +* It's disk quota usage, owner name, and such. +*/ + +class StorageInfo { + Common::String _uid, _name, _email; + uint64 _usedBytes, _allocatedBytes; + +public: + StorageInfo(Common::String uid_, Common::String name_, Common::String email_, uint64 used_, uint64 allocated): + _uid(uid_), _name(name_), _email(email_), _usedBytes(used_), _allocatedBytes(allocated) {} + + Common::String uid() const { return _uid; } + Common::String name() const { return _name; } + Common::String email() const { return _email; } + uint64 used() const { return _usedBytes; } + uint64 available() const { return _allocatedBytes; } + +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/events/androidsdl/androidsdl-events.cpp b/backends/events/androidsdl/androidsdl-events.cpp new file mode 100644 index 0000000000..7ea8ff1dc1 --- /dev/null +++ b/backends/events/androidsdl/androidsdl-events.cpp @@ -0,0 +1,85 @@ +/* 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/scummsys.h" + +#if defined(ANDROIDSDL) + +#include "backends/events/androidsdl/androidsdl-events.h" +#include "backends/platform/androidsdl/androidsdl-sdl.h" +#include <SDL_screenkeyboard.h> + +bool AndroidSdlEventSource::handleMouseButtonDown(SDL_Event &ev, Common::Event &event) { + if (ev.button.button == SDL_BUTTON_LEFT) + event.type = Common::EVENT_LBUTTONDOWN; + else if (ev.button.button == SDL_BUTTON_RIGHT) + event.type = Common::EVENT_RBUTTONDOWN; +#if defined(SDL_BUTTON_WHEELUP) && defined(SDL_BUTTON_WHEELDOWN) + else if (ev.button.button == SDL_BUTTON_WHEELUP) + event.type = Common::EVENT_WHEELUP; + else if (ev.button.button == SDL_BUTTON_WHEELDOWN) + event.type = Common::EVENT_WHEELDOWN; +#endif +#if defined(SDL_BUTTON_MIDDLE) + else if (ev.button.button == SDL_BUTTON_MIDDLE) { + event.type = Common::EVENT_MBUTTONDOWN; + + static int show_onscreen = 0; + if (show_onscreen == 0) { + SDL_ANDROID_SetScreenKeyboardShown(0); + show_onscreen++; + } else if (show_onscreen==1) { + SDL_ANDROID_SetScreenKeyboardShown(1); + show_onscreen++; + } + if (show_onscreen == 2) + show_onscreen = 0; + } +#endif + else + return false; + + processMouseEvent(event, ev.button.x, ev.button.y); + + return true; +} + +bool AndroidSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) { + if (false) {} + + if (ev.key.keysym.sym == SDLK_LCTRL) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_F5; + return true; + } else { + // Let the events fall through if we didn't change them, this may not be the best way to + // set it up, but i'm not sure how sdl would like it if we let if fall through then redid it though. + // and yes i have an huge terminal size so i dont wrap soon enough. + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = (Common::KeyCode)ev.key.keysym.sym; + event.kbd.ascii = mapKey(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode); + } + + return false; +} + +#endif diff --git a/backends/events/androidsdl/androidsdl-events.h b/backends/events/androidsdl/androidsdl-events.h new file mode 100644 index 0000000000..bca712e579 --- /dev/null +++ b/backends/events/androidsdl/androidsdl-events.h @@ -0,0 +1,37 @@ +/* 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. + * + */ + +#if !defined(BACKEND_EVENTS_SDL_ANDROIDSDL_H) && !defined(DISABLE_DEFAULT_EVENTMANAGER) +#define BACKEND_EVENTS_SDL_ANDROIDSDL_H + +#include "backends/events/sdl/sdl-events.h" + +/** + * SDL events manager for ANDROIDSDL + */ +class AndroidSdlEventSource : public SdlEventSource { +protected: + virtual bool handleMouseButtonDown(SDL_Event &ev, Common::Event &event); + virtual bool remapKey(SDL_Event &ev, Common::Event &event); +}; + +#endif diff --git a/backends/events/dinguxsdl/dinguxsdl-events.cpp b/backends/events/dinguxsdl/dinguxsdl-events.cpp index cc15f2666c..0492c569e1 100644 --- a/backends/events/dinguxsdl/dinguxsdl-events.cpp +++ b/backends/events/dinguxsdl/dinguxsdl-events.cpp @@ -175,7 +175,10 @@ bool DINGUXSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) { return true; } else if (ev.key.keysym.sym == BUT_SELECT) { // virtual keyboard #ifdef ENABLE_VKEYBD - event.type = Common::EVENT_VIRTUAL_KEYBOARD; + if (ev.type == SDL_KEYDOWN) + event.type = Common::EVENT_VIRTUAL_KEYBOARD; + + return true; #endif } else if (ev.key.keysym.sym == BUT_START) { // F5, menu in some games ev.key.keysym.sym = SDLK_F5; diff --git a/backends/events/ps3sdl/ps3sdl-events.cpp b/backends/events/ps3sdl/ps3sdl-events.cpp index 0f6e01857b..1fc10559c2 100644 --- a/backends/events/ps3sdl/ps3sdl-events.cpp +++ b/backends/events/ps3sdl/ps3sdl-events.cpp @@ -126,8 +126,8 @@ bool PS3SdlEventSource::handleJoyButtonUp(SDL_Event &ev, Common::Event &event) { * This pauses execution and keeps redrawing the screen until the XMB is closed. */ void PS3SdlEventSource::preprocessEvents(SDL_Event *event) { - if (event->type == SDL_ACTIVEEVENT) { - if (event->active.state == SDL_APPMOUSEFOCUS && !event->active.gain) { + if (event->type == SDL_WINDOWEVENT) { + if (event->window.event == SDL_WINDOWEVENT_LEAVE) { // XMB opened if (g_engine) g_engine->pauseEngine(true); @@ -145,9 +145,9 @@ void PS3SdlEventSource::preprocessEvents(SDL_Event *event) { } if (event->type == SDL_QUIT) return; - if (event->type != SDL_ACTIVEEVENT) + if (event->type != SDL_WINDOWEVENT) continue; - if (event->active.state == SDL_APPMOUSEFOCUS && event->active.gain) { + if (event->window.event == SDL_WINDOWEVENT_ENTER) { // XMB closed if (g_engine) g_engine->pauseEngine(false); diff --git a/backends/events/sdl/sdl-events.cpp b/backends/events/sdl/sdl-events.cpp index 2480e7c370..acc1ff5dce 100644 --- a/backends/events/sdl/sdl-events.cpp +++ b/backends/events/sdl/sdl-events.cpp @@ -49,22 +49,55 @@ #define JOY_BUT_SPACE 4 #define JOY_BUT_F5 5 +#if SDL_VERSION_ATLEAST(2, 0, 0) +static uint32 convUTF8ToUTF32(const char *src) { + uint32 utf32 = 0; + + char *dst = SDL_iconv_string( +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + "UTF-32BE", +#else + "UTF-32LE", +#endif + "UTF-8", src, SDL_strlen(src) + 1); + + if (dst) { + utf32 = *((uint32 *)dst); + SDL_free(dst); + } + + return utf32; +} +#endif + SdlEventSource::SdlEventSource() - : EventSource(), _scrollLock(false), _joystick(0), _lastScreenID(0), _graphicsManager(0) { + : EventSource(), _scrollLock(false), _joystick(0), _lastScreenID(0), _graphicsManager(0) +#if SDL_VERSION_ATLEAST(2, 0, 0) + , _queuedFakeKeyUp(false), _fakeKeyUp() +#endif + { // Reset mouse state memset(&_km, 0, sizeof(_km)); int joystick_num = ConfMan.getInt("joystick_num"); - if (joystick_num > -1) { + if (joystick_num >= 0) { // Initialize SDL joystick subsystem if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) { error("Could not initialize SDL: %s", SDL_GetError()); } // Enable joystick - if (SDL_NumJoysticks() > 0) { - debug("Using joystick: %s", SDL_JoystickName(0)); + if (SDL_NumJoysticks() > joystick_num) { _joystick = SDL_JoystickOpen(joystick_num); + debug("Using joystick: %s", +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_JoystickName(_joystick) +#else + SDL_JoystickName(joystick_num) +#endif + ); + } else { + warning("Invalid joystick: %d", joystick_num); } } } @@ -74,21 +107,24 @@ SdlEventSource::~SdlEventSource() { SDL_JoystickClose(_joystick); } -int SdlEventSource::mapKey(SDLKey key, SDLMod mod, Uint16 unicode) { - if (key >= SDLK_F1 && key <= SDLK_F9) { - return key - SDLK_F1 + Common::ASCII_F1; - } else if (key >= SDLK_KP0 && key <= SDLK_KP9) { - return key - SDLK_KP0 + '0'; - } else if (key >= SDLK_UP && key <= SDLK_PAGEDOWN) { +int SdlEventSource::mapKey(SDLKey sdlKey, SDLMod mod, Uint16 unicode) { + Common::KeyCode key = SDLToOSystemKeycode(sdlKey); + + if (key >= Common::KEYCODE_F1 && key <= Common::KEYCODE_F9) { + return key - Common::KEYCODE_F1 + Common::ASCII_F1; + } else if (key >= Common::KEYCODE_KP0 && key <= Common::KEYCODE_KP9) { + return key - Common::KEYCODE_KP0 + '0'; + } else if (key >= Common::KEYCODE_UP && key <= Common::KEYCODE_PAGEDOWN) { return key; } else if (unicode) { return unicode; } else if (key >= 'a' && key <= 'z' && (mod & KMOD_SHIFT)) { return key & ~0x20; - } else if (key >= SDLK_NUMLOCK && key <= SDLK_EURO) { + } else if (key >= Common::KEYCODE_NUMLOCK && key <= Common::KEYCODE_EURO) { return 0; + } else { + return key; } - return key; } void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y) { @@ -171,7 +207,9 @@ void SdlEventSource::handleKbdMouse() { _km.y_down_count = 1; } - SDL_WarpMouse((Uint16)_km.x, (Uint16)_km.y); + if (_graphicsManager) { + _graphicsManager->getWindow()->warpMouseInWindow((Uint16)_km.x, (Uint16)_km.y); + } } } } @@ -276,7 +314,7 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDLKey key) { case SDLK_y: return Common::KEYCODE_y; case SDLK_z: return Common::KEYCODE_z; case SDLK_DELETE: return Common::KEYCODE_DELETE; -#if SDL_VERSION_ATLEAST(1, 3, 0) +#if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE): return Common::KEYCODE_TILDE; #else case SDLK_WORLD_16: return Common::KEYCODE_TILDE; @@ -338,7 +376,9 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDLKey key) { case SDLK_HELP: return Common::KEYCODE_HELP; case SDLK_PRINT: return Common::KEYCODE_PRINT; case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ; +#if !SDL_VERSION_ATLEAST(2, 0, 0) case SDLK_BREAK: return Common::KEYCODE_BREAK; +#endif case SDLK_MENU: return Common::KEYCODE_MENU; case SDLK_POWER: return Common::KEYCODE_POWER; case SDLK_UNDO: return Common::KEYCODE_UNDO; @@ -349,6 +389,16 @@ Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDLKey key) { bool SdlEventSource::pollEvent(Common::Event &event) { handleKbdMouse(); +#if SDL_VERSION_ATLEAST(2, 0, 0) + // In case we still need to send a key up event for a key down from a + // TEXTINPUT event we do this immediately. + if (_queuedFakeKeyUp) { + event = _fakeKeyUp; + _queuedFakeKeyUp = false; + return true; + } +#endif + // If the screen changed, send an Common::EVENT_SCREEN_CHANGED int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID(); if (screenID != _lastScreenID) { @@ -385,24 +435,73 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) { case SDL_JOYAXISMOTION: return handleJoyAxisMotion(ev, event); +#if SDL_VERSION_ATLEAST(2, 0, 0) + case SDL_MOUSEWHEEL: { + Sint32 yDir = ev.wheel.y; +#if SDL_VERSION_ATLEAST(2, 0, 4) + if (ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { + yDir *= -1; + } +#endif + // HACK: It seems we want the mouse coordinates supplied + // with a mouse wheel event. However, SDL2 does not supply + // these, thus we use whatever we got last time. It seems + // these are always stored in _km.x, _km.y. + processMouseEvent(event, _km.x, _km.y); + if (yDir < 0) { + event.type = Common::EVENT_WHEELDOWN; + return true; + } else if (yDir > 0) { + event.type = Common::EVENT_WHEELUP; + return true; + } else { + return false; + } + } + + case SDL_TEXTINPUT: { + // When we get a TEXTINPUT event it means we got some user input for + // which no KEYDOWN exists. SDL 1.2 introduces a "fake" key down+up + // in such cases. We will do the same to mimic it's behavior. + event.type = Common::EVENT_KEYDOWN; + + event.kbd = Common::KeyState(Common::KEYCODE_INVALID, convUTF8ToUTF32(ev.text.text), 0); + + SDLModToOSystemKeyFlags(SDL_GetModState(), event); + // Set the scroll lock sticky flag + if (_scrollLock) + event.kbd.flags |= Common::KBD_SCRL; + + // Fake a key up when we have a proper ascii value. + _queuedFakeKeyUp = (event.kbd.ascii != 0); + _fakeKeyUp = event; + _fakeKeyUp.type = Common::EVENT_KEYUP; + + return _queuedFakeKeyUp; + } + + case SDL_WINDOWEVENT: + switch (ev.window.event) { + case SDL_WINDOWEVENT_EXPOSED: + if (_graphicsManager) + _graphicsManager->notifyVideoExpose(); + return false; + + case SDL_WINDOWEVENT_RESIZED: + return handleResizeEvent(event, ev.window.data1, ev.window.data2); + + default: + return false; + } +#else case SDL_VIDEOEXPOSE: if (_graphicsManager) _graphicsManager->notifyVideoExpose(); return false; case SDL_VIDEORESIZE: - if (_graphicsManager) { - _graphicsManager->notifyResize(ev.resize.w, ev.resize.h); - - // If the screen changed, send an Common::EVENT_SCREEN_CHANGED - int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID(); - if (screenID != _lastScreenID) { - _lastScreenID = screenID; - event.type = Common::EVENT_SCREEN_CHANGED; - return true; - } - } - return false; + return handleResizeEvent(event, ev.resize.w, ev.resize.h); +#endif case SDL_QUIT: event.type = Common::EVENT_QUIT; @@ -418,41 +517,45 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) { SDLModToOSystemKeyFlags(SDL_GetModState(), event); + SDLKey sdlKeycode = obtainKeycode(ev.key.keysym); + // Handle scroll lock as a key modifier - if (ev.key.keysym.sym == SDLK_SCROLLOCK) + if (sdlKeycode == SDLK_SCROLLOCK) _scrollLock = !_scrollLock; if (_scrollLock) event.kbd.flags |= Common::KBD_SCRL; // Ctrl-m toggles mouse capture - if (event.kbd.hasFlags(Common::KBD_CTRL) && ev.key.keysym.sym == 'm') { - toggleMouseGrab(); + if (event.kbd.hasFlags(Common::KBD_CTRL) && sdlKeycode == 'm') { + if (_graphicsManager) { + _graphicsManager->getWindow()->toggleMouseGrab(); + } return false; } #if defined(MACOSX) // On Macintosh, Cmd-Q quits - if ((ev.key.keysym.mod & KMOD_META) && ev.key.keysym.sym == 'q') { + if ((ev.key.keysym.mod & KMOD_META) && sdlKeycode == 'q') { event.type = Common::EVENT_QUIT; return true; } #elif defined(POSIX) // On other *nix systems, Control-Q quits - if ((ev.key.keysym.mod & KMOD_CTRL) && ev.key.keysym.sym == 'q') { + if ((ev.key.keysym.mod & KMOD_CTRL) && sdlKeycode == 'q') { event.type = Common::EVENT_QUIT; return true; } #else - // Ctrl-z and Alt-X quit - if ((event.kbd.hasFlags(Common::KBD_CTRL) && ev.key.keysym.sym == 'z') || (event.kbd.hasFlags(Common::KBD_ALT) && ev.key.keysym.sym == 'x')) { + // Ctrl-z quits + if ((event.kbd.hasFlags(Common::KBD_CTRL) && sdlKeycode == 'z')) { event.type = Common::EVENT_QUIT; return true; } #ifdef WIN32 // On Windows, also use the default Alt-F4 quit combination - if ((ev.key.keysym.mod & KMOD_ALT) && ev.key.keysym.sym == SDLK_F4) { + if ((ev.key.keysym.mod & KMOD_ALT) && sdlKeycode == SDLK_F4) { event.type = Common::EVENT_QUIT; return true; } @@ -460,7 +563,7 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) { #endif // Ctrl-u toggles mute - if ((ev.key.keysym.mod & KMOD_CTRL) && ev.key.keysym.sym == 'u') { + if ((ev.key.keysym.mod & KMOD_CTRL) && sdlKeycode == 'u') { event.type = Common::EVENT_MUTE; return true; } @@ -469,8 +572,8 @@ bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) { return true; event.type = Common::EVENT_KEYDOWN; - event.kbd.keycode = SDLToOSystemKeycode(ev.key.keysym.sym); - event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, (Uint16)ev.key.keysym.unicode); + event.kbd.keycode = SDLToOSystemKeycode(sdlKeycode); + event.kbd.ascii = mapKey(sdlKeycode, (SDLMod)ev.key.keysym.mod, obtainUnicode(ev.key.keysym)); return true; } @@ -479,6 +582,7 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) { if (remapKey(ev, event)) return true; + SDLKey sdlKeycode = obtainKeycode(ev.key.keysym); SDLMod mod = SDL_GetModState(); // Check if this is an event handled by handleKeyDown(), and stop if it is @@ -486,35 +590,30 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) { // Check if the Ctrl key is down, so that we can trap cases where the // user has the Ctrl key down, and has just released a special key if (mod & KMOD_CTRL) { - if (ev.key.keysym.sym == 'm' || // Ctrl-m toggles mouse capture + if (sdlKeycode == 'm' || // Ctrl-m toggles mouse capture #if defined(MACOSX) // Meta - Q, handled below #elif defined(POSIX) - ev.key.keysym.sym == 'q' || // On other *nix systems, Control-Q quits + sdlKeycode == 'q' || // On other *nix systems, Control-Q quits #else - ev.key.keysym.sym == 'z' || // Ctrl-z quit + sdlKeycode == 'z' || // Ctrl-z quit #endif - ev.key.keysym.sym == 'u') // Ctrl-u toggles mute + sdlKeycode == 'u') // Ctrl-u toggles mute return false; } // Same for other keys (Meta and Alt) #if defined(MACOSX) - if ((mod & KMOD_META) && ev.key.keysym.sym == 'q') + if ((mod & KMOD_META) && sdlKeycode == 'q') return false; // On Macintosh, Cmd-Q quits -#elif defined(POSIX) - // Control Q has already been handled above -#else - if ((mod & KMOD_ALT) && ev.key.keysym.sym == 'x') - return false; // Alt-x quit #endif // If we reached here, this isn't an event handled by handleKeyDown(), thus // continue normally event.type = Common::EVENT_KEYUP; - event.kbd.keycode = SDLToOSystemKeycode(ev.key.keysym.sym); - event.kbd.ascii = mapKey(ev.key.keysym.sym, (SDLMod)ev.key.keysym.mod, (Uint16)ev.key.keysym.unicode); + event.kbd.keycode = SDLToOSystemKeycode(sdlKeycode); + event.kbd.ascii = mapKey(sdlKeycode, (SDLMod)ev.key.keysym.mod, 0); // Ctrl-Alt-<key> will change the GFX mode SDLModToOSystemKeyFlags(mod, event); @@ -636,16 +735,16 @@ bool SdlEventSource::handleJoyButtonUp(SDL_Event &ev, Common::Event &event) { bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) { int axis = ev.jaxis.value; - if ( axis > JOY_DEADZONE) { + if (axis > JOY_DEADZONE) { axis -= JOY_DEADZONE; event.type = Common::EVENT_MOUSEMOVE; - } else if ( axis < -JOY_DEADZONE ) { + } else if (axis < -JOY_DEADZONE) { axis += JOY_DEADZONE; event.type = Common::EVENT_MOUSEMOVE; } else axis = 0; - if ( ev.jaxis.axis == JOY_XAXIS) { + if (ev.jaxis.axis == JOY_XAXIS) { #ifdef JOY_ANALOG _km.x_vel = axis / 2000; _km.x_down_count = 0; @@ -658,7 +757,6 @@ bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) { _km.x_down_count = 0; } #endif - } else if (ev.jaxis.axis == JOY_YAXIS) { #ifndef JOY_INVERT_Y axis = -axis; @@ -750,13 +848,6 @@ bool SdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) { return false; } -void SdlEventSource::toggleMouseGrab() { - if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) - SDL_WM_GrabInput(SDL_GRAB_ON); - else - SDL_WM_GrabInput(SDL_GRAB_OFF); -} - void SdlEventSource::resetKeyboadEmulation(int16 x_max, int16 y_max) { _km.x_max = x_max; _km.y_max = y_max; @@ -764,4 +855,105 @@ void SdlEventSource::resetKeyboadEmulation(int16 x_max, int16 y_max) { _km.last_time = 0; } +bool SdlEventSource::handleResizeEvent(Common::Event &event, int w, int h) { + if (_graphicsManager) { + _graphicsManager->notifyResize(w, h); + + // If the screen changed, send an Common::EVENT_SCREEN_CHANGED + int screenID = ((OSystem_SDL *)g_system)->getGraphicsManager()->getScreenChangeID(); + if (screenID != _lastScreenID) { + _lastScreenID = screenID; + event.type = Common::EVENT_SCREEN_CHANGED; + return true; + } + } + + return false; +} + +SDLKey SdlEventSource::obtainKeycode(const SDL_keysym keySym) { +#if !SDL_VERSION_ATLEAST(2, 0, 0) && defined(WIN32) && !defined(_WIN32_WCE) + // WORKAROUND: SDL 1.2 on Windows does not use the user configured keyboard layout, + // resulting in "keySym.sym" values to always be those expected for an US keyboard. + // For example, SDL returns SDLK_Q when pressing the 'A' key on an AZERTY keyboard. + // This defeats the purpose of keycodes which is to be able to refer to a key without + // knowing where it is physically located. + // We work around this issue by querying the currently active Windows keyboard layout + // using the scancode provided by SDL. + + if (keySym.sym >= SDLK_0 && keySym.sym <= SDLK_9) { + // The keycode returned by SDL is kept for the number keys. + // Querying the keyboard layout for those would return the base key values + // for AZERTY keyboards, which are not numbers. For example, SDLK_1 would + // map to SDLK_AMPERSAND. This is theoretically correct but practically unhelpful, + // because it makes it impossible to handle key combinations such as "ctrl-1". + return keySym.sym; + } + + int vk = MapVirtualKey(keySym.scancode, MAPVK_VSC_TO_VK); + if (vk) { + int ch = (MapVirtualKey(vk, MAPVK_VK_TO_CHAR) & 0x7FFF); + // The top bit of the result of MapVirtualKey with MAPVK_VSC_TO_VK signals + // a dead key was pressed. In that case we keep the value of the accent alone. + if (ch) { + if (ch >= 'A' && ch <= 'Z') { + // Windows returns uppercase ASCII whereas SDL expects lowercase + return (SDLKey)(SDLK_a + (ch - 'A')); + } else { + return (SDLKey)ch; + } + } + } +#endif + + return keySym.sym; +} + +uint32 SdlEventSource::obtainUnicode(const SDL_keysym keySym) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Event events[2]; + + // Update the event queue here to give SDL a chance to insert TEXTINPUT + // events for KEYDOWN events. Otherwise we have a high chance that on + // Windows the TEXTINPUT event is not in the event queue at this point. + // In this case we will get two events with ascii values due to mapKey + // and dispatchSDLEvent. This results in nasty double input of characters + // in the GUI. + // + // FIXME: This is all a bit fragile because in mapKey we derive the ascii + // value from the key code if no unicode value is given. This is legacy + // behavior and should be removed anyway. If that is removed, we might not + // even need to do this peeking here but instead can rely on the + // SDL_TEXTINPUT case in dispatchSDLEvent to introduce keydown/keyup with + // proper ASCII values (but with KEYCODE_INVALID as keycode). + SDL_PumpEvents(); + + // In SDL2, the unicode field has been removed from the keysym struct. + // Instead a SDL_TEXTINPUT event is generated on key combinations that + // generates unicode. + // Here we peek into the event queue for the event to see if it exists. + int n = SDL_PeepEvents(events, 2, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_TEXTINPUT); + // Make sure that the TEXTINPUT event belongs to this KEYDOWN + // event and not another pending one. + if ((n > 0 && events[0].type == SDL_TEXTINPUT) + || (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) { + // Remove the text input event we associate with the key press. This + // makes sure we never get any SDL_TEXTINPUT events which do "belong" + // to SDL_KEYDOWN events. + n = SDL_PeepEvents(events, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT); + // This is basically a paranoia safety check because we know there + // must be a text input event in the queue. + if (n > 0) { + return convUTF8ToUTF32(events[0].text.text); + } else { + return 0; + } + } else { + return 0; + } +#else + return keySym.unicode; +#endif +} + #endif diff --git a/backends/events/sdl/sdl-events.h b/backends/events/sdl/sdl-events.h index a1b6d5ec3c..7e590aed3c 100644 --- a/backends/events/sdl/sdl-events.h +++ b/backends/events/sdl/sdl-events.h @@ -49,11 +49,6 @@ public: */ virtual void resetKeyboadEmulation(int16 x_max, int16 y_max); - /** - * Toggles mouse input grab - */ - virtual void toggleMouseGrab(); - protected: /** @name Keyboard mouse emulation * Disabled by fingolfin 2004-12-18. @@ -130,7 +125,7 @@ protected: /** * Maps the ASCII value of key */ - virtual int mapKey(SDLKey key, SDLMod mod, Uint16 unicode); + int mapKey(SDLKey key, SDLMod mod, Uint16 unicode); /** * Configures the key modifiers flags status @@ -141,6 +136,35 @@ protected: * Translates SDL key codes to OSystem key codes */ Common::KeyCode SDLToOSystemKeycode(const SDLKey key); + + /** + * Notify graphics manager of a resize request. + */ + bool handleResizeEvent(Common::Event &event, int w, int h); + + /** + * Extracts unicode information for the specific key sym. + * May only be used for key down events. + */ + uint32 obtainUnicode(const SDL_keysym keySym); + + /** + * Extracts the keycode for the specified key sym. + */ + SDLKey obtainKeycode(const SDL_keysym keySym); + +#if SDL_VERSION_ATLEAST(2, 0, 0) + /** + * Whether _fakeKeyUp contains an event we need to send. + */ + bool _queuedFakeKeyUp; + + /** + * A fake key up event when we receive a TEXTINPUT without any previous + * KEYDOWN event. + */ + Common::Event _fakeKeyUp; +#endif }; #endif diff --git a/backends/events/symbiansdl/symbiansdl-events.cpp b/backends/events/symbiansdl/symbiansdl-events.cpp index 36018f1024..b0d2c25302 100644 --- a/backends/events/symbiansdl/symbiansdl-events.cpp +++ b/backends/events/symbiansdl/symbiansdl-events.cpp @@ -133,7 +133,9 @@ bool SymbianSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) { _currentZone = 0; event.type = Common::EVENT_MOUSEMOVE; processMouseEvent(event, _mouseXZone[_currentZone], _mouseYZone[_currentZone]); - SDL_WarpMouse(event.mouse.x, event.mouse.y); + if (_graphicsManager) { + _graphicsManager->getWindow()->warpMouseInWindow(event.mouse.x, event.mouse.y); + } } return true; diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h index 34a8120caa..24286b649d 100644 --- a/backends/fs/abstract-fs.h +++ b/backends/fs/abstract-fs.h @@ -84,6 +84,20 @@ protected: public: /** + * Construct a FSNode object from an AbstractFSNode object. + * + * This is a helper to create Common::FSNode objects when the backend's + * FileSystemFactory cannot create the given AbstractFSNode object itself. + * All other code is supposed to use Common::FSNode's constructor itself. + * + * @param realNode Pointer to a heap allocated instance. FSNode will take + * ownership of the pointer. + */ + static Common::FSNode makeFSNode(AbstractFSNode *realNode) { + return Common::FSNode(realNode); + } + + /** * Destructor. */ virtual ~AbstractFSNode() {} @@ -177,6 +191,15 @@ public: * @return pointer to the stream object, 0 in case of a failure */ virtual Common::WriteStream *createWriteStream() = 0; + + /** + * Creates a file referred by this node. + * + * @param isDirectory true if created file must be a directory + * + * @return true if file is created successfully + */ + virtual bool create(bool isDirectory) = 0; }; diff --git a/backends/fs/amigaos4/amigaos4-fs.cpp b/backends/fs/amigaos4/amigaos4-fs.cpp index 7bebdf8ce6..24a8fb98ad 100644 --- a/backends/fs/amigaos4/amigaos4-fs.cpp +++ b/backends/fs/amigaos4/amigaos4-fs.cpp @@ -381,15 +381,17 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const { dosList = IDOS->NextDosEntry(dosList, LDF_VOLUMES); while (dosList) { if (dosList->dol_Type == DLT_VOLUME && - dosList->dol_Name) { + dosList->dol_Name && + dosList->dol_Port) { // The original line was //if (dosList->dol_Type == DLT_VOLUME && //dosList->dol_Name && //dosList->dol_Task) { // which errored using SDK 53.24 with a 'struct dosList' has no member called 'dol_Task' - // I removed dol_Task because it's not used anywhere else - // and it neither brought up further errors nor crashes or regressions + // The reason for that was that + // 1) dol_Task wasn't a task pointer, it is a message port instead + // 2) It was redefined to be dol_Port in dos/obsolete.h in afore mentioned SDK // Copy name to buffer IDOS->CopyStringBSTRToC(dosList->dol_Name, buffer, MAXPATHLEN); @@ -441,4 +443,9 @@ Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool AmigaOSFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //defined(__amigaos4__) diff --git a/backends/fs/amigaos4/amigaos4-fs.h b/backends/fs/amigaos4/amigaos4-fs.h index 408354888e..3ed45d3f77 100644 --- a/backends/fs/amigaos4/amigaos4-fs.h +++ b/backends/fs/amigaos4/amigaos4-fs.h @@ -116,6 +116,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; diff --git a/backends/fs/chroot/chroot-fs-factory.cpp b/backends/fs/chroot/chroot-fs-factory.cpp new file mode 100644 index 0000000000..db5655ffce --- /dev/null +++ b/backends/fs/chroot/chroot-fs-factory.cpp @@ -0,0 +1,59 @@ +/* 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. + * + */ + +#if defined(POSIX) + +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h +#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h +#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir +#define FORBIDDEN_SYMBOL_EXCEPTION_exit //Needed for IRIX's unistd.h + +#include "backends/fs/chroot/chroot-fs-factory.h" +#include "backends/fs/chroot/chroot-fs.h" + +ChRootFilesystemFactory::ChRootFilesystemFactory(const Common::String &root) + : _root(root) { +} + +AbstractFSNode *ChRootFilesystemFactory::makeRootFileNode() const { + return new ChRootFilesystemNode(_root, "/"); +} + +AbstractFSNode *ChRootFilesystemFactory::makeCurrentDirectoryFileNode() const { + char buf[MAXPATHLEN]; + if (getcwd(buf, MAXPATHLEN) == NULL) { + return NULL; + } + + if (Common::String(buf).hasPrefix(_root + Common::String("/"))) { + return new ChRootFilesystemNode(_root, buf + _root.size()); + } + + return new ChRootFilesystemNode(_root, "/"); +} + +AbstractFSNode *ChRootFilesystemFactory::makeFileNodePath(const Common::String &path) const { + assert(!path.empty()); + return new ChRootFilesystemNode(_root, path); +} + +#endif diff --git a/backends/fs/chroot/chroot-fs-factory.h b/backends/fs/chroot/chroot-fs-factory.h new file mode 100644 index 0000000000..d53e5af398 --- /dev/null +++ b/backends/fs/chroot/chroot-fs-factory.h @@ -0,0 +1,46 @@ +/* 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 BACKENDS_FS_CHROOT_CHROOT_FS_FACTORY_H +#define BACKENDS_FS_CHROOT_CHROOT_FS_FACTORY_H + +#include "backends/fs/fs-factory.h" + +/** + * FIXME: Warning, using this factory in your backend may silently break some + * features. Instances are, for example, the FluidSynth code, and the POSIX + * plugin code. + */ +class ChRootFilesystemFactory : public FilesystemFactory { +public: + explicit ChRootFilesystemFactory(const Common::String &root); + + virtual AbstractFSNode *makeRootFileNode() const; + virtual AbstractFSNode *makeCurrentDirectoryFileNode() const; + virtual AbstractFSNode *makeFileNodePath(const Common::String &path) const; + +private: + const Common::String _root; +}; + +#endif /* BACKENDS_FS_CHROOT_CHROOT_FS_FACTORY_H */ diff --git a/backends/fs/chroot/chroot-fs.cpp b/backends/fs/chroot/chroot-fs.cpp new file mode 100644 index 0000000000..f5f7c84570 --- /dev/null +++ b/backends/fs/chroot/chroot-fs.cpp @@ -0,0 +1,129 @@ +/* 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. + * + */ + +#if defined(POSIX) + +// Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h. +// Also with clock() in sys/time.h in some Mac OS X SDKs. +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h +#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h +#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir +#define FORBIDDEN_SYMBOL_EXCEPTION_getenv +#define FORBIDDEN_SYMBOL_EXCEPTION_exit //Needed for IRIX's unistd.h + +#include "backends/fs/chroot/chroot-fs.h" + +ChRootFilesystemNode::ChRootFilesystemNode(const Common::String &root, POSIXFilesystemNode *node) { + _root = Common::normalizePath(root, '/'); + _realNode = node; +} + +ChRootFilesystemNode::ChRootFilesystemNode(const Common::String &root, const Common::String &path) { + _root = Common::normalizePath(root, '/'); + _realNode = new POSIXFilesystemNode(addPathComponent(root, path)); +} + +ChRootFilesystemNode::~ChRootFilesystemNode() { + delete _realNode; +} + +bool ChRootFilesystemNode::exists() const { + return _realNode->exists(); +} + +Common::String ChRootFilesystemNode::getDisplayName() const { + return getName(); +} + +Common::String ChRootFilesystemNode::getName() const { + return _realNode->AbstractFSNode::getDisplayName(); +} + +Common::String ChRootFilesystemNode::getPath() const { + Common::String path = _realNode->getPath(); + if (path.size() > _root.size()) { + return Common::String(path.c_str() + _root.size()); + } + return Common::String("/"); +} + +bool ChRootFilesystemNode::isDirectory() const { + return _realNode->isDirectory(); +} + +bool ChRootFilesystemNode::isReadable() const { + return _realNode->isReadable(); +} + +bool ChRootFilesystemNode::isWritable() const { + return _realNode->isWritable(); +} + +AbstractFSNode *ChRootFilesystemNode::getChild(const Common::String &n) const { + return new ChRootFilesystemNode(_root, (POSIXFilesystemNode *)_realNode->getChild(n)); +} + +bool ChRootFilesystemNode::getChildren(AbstractFSList &list, ListMode mode, bool hidden) const { + AbstractFSList tmp; + if (!_realNode->getChildren(tmp, mode, hidden)) { + return false; + } + + for (AbstractFSList::iterator i=tmp.begin(); i!=tmp.end(); ++i) { + list.push_back(new ChRootFilesystemNode(_root, (POSIXFilesystemNode *) *i)); + } + + return true; +} + +AbstractFSNode *ChRootFilesystemNode::getParent() const { + if (getPath() == "/") return 0; + return new ChRootFilesystemNode(_root, (POSIXFilesystemNode *)_realNode->getParent()); +} + +Common::SeekableReadStream *ChRootFilesystemNode::createReadStream() { + return _realNode->createReadStream(); +} + +Common::WriteStream *ChRootFilesystemNode::createWriteStream() { + return _realNode->createWriteStream(); +} + +bool ChRootFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + +Common::String ChRootFilesystemNode::addPathComponent(const Common::String &path, const Common::String &component) { + const char sep = '/'; + if (path.lastChar() == sep && component.firstChar() == sep) { + return Common::String::format("%s%s", path.c_str(), component.c_str() + 1); + } + + if (path.lastChar() == sep || component.firstChar() == sep) { + return Common::String::format("%s%s", path.c_str(), component.c_str()); + } + + return Common::String::format("%s%c%s", path.c_str(), sep, component.c_str()); +} + +#endif diff --git a/backends/fs/chroot/chroot-fs.h b/backends/fs/chroot/chroot-fs.h new file mode 100644 index 0000000000..e0ecc1c47e --- /dev/null +++ b/backends/fs/chroot/chroot-fs.h @@ -0,0 +1,58 @@ +/* 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 BACKENDS_FS_CHROOT_CHROOT_FS_H +#define BACKENDS_FS_CHROOT_CHROOT_FS_H + +#include "backends/fs/posix/posix-fs.h" + +class ChRootFilesystemNode : public AbstractFSNode { + Common::String _root; + POSIXFilesystemNode *_realNode; + + ChRootFilesystemNode(const Common::String &root, POSIXFilesystemNode *); + +public: + ChRootFilesystemNode(const Common::String &root, const Common::String &path); + virtual ~ChRootFilesystemNode(); + + virtual bool exists() const; + virtual Common::String getDisplayName() const; + virtual Common::String getName() const; + virtual Common::String getPath() const; + virtual bool isDirectory() const; + virtual bool isReadable() const; + virtual bool isWritable() const; + + virtual AbstractFSNode *getChild(const Common::String &n) const; + virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; + virtual AbstractFSNode *getParent() const; + + virtual Common::SeekableReadStream *createReadStream(); + virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); + +private: + static Common::String addPathComponent(const Common::String &path, const Common::String &component); +}; + +#endif /* BACKENDS_FS_CHROOT_CHROOT_FS_H */ diff --git a/backends/fs/ds/ds-fs.cpp b/backends/fs/ds/ds-fs.cpp index 3782caf432..83b1295838 100644 --- a/backends/fs/ds/ds-fs.cpp +++ b/backends/fs/ds/ds-fs.cpp @@ -211,6 +211,11 @@ Common::WriteStream *DSFileSystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool DSFileSystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + ////////////////////////////////////////////////////////////////////////// // GBAMPFileSystemNode - File system using GBA Movie Player and CF card // ////////////////////////////////////////////////////////////////////////// @@ -393,6 +398,11 @@ Common::WriteStream *GBAMPFileSystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool GBAMPFileSystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + DSFileStream::DSFileStream(void *handle) : _handle(handle) { diff --git a/backends/fs/ds/ds-fs.h b/backends/fs/ds/ds-fs.h index b9ccfcbe9c..939d1a1555 100644 --- a/backends/fs/ds/ds-fs.h +++ b/backends/fs/ds/ds-fs.h @@ -91,6 +91,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); /** * Returns the zip file this node points to. @@ -156,6 +157,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; struct fileHandle { diff --git a/backends/fs/n64/n64-fs.cpp b/backends/fs/n64/n64-fs.cpp index fe37dad467..d568500d30 100644 --- a/backends/fs/n64/n64-fs.cpp +++ b/backends/fs/n64/n64-fs.cpp @@ -160,4 +160,9 @@ Common::WriteStream *N64FilesystemNode::createWriteStream() { return RomfsStream::makeFromPath(getPath(), true); } +bool N64FilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#ifdef __N64__ diff --git a/backends/fs/n64/n64-fs.h b/backends/fs/n64/n64-fs.h index d503a6601e..d520ad5be6 100644 --- a/backends/fs/n64/n64-fs.h +++ b/backends/fs/n64/n64-fs.h @@ -73,6 +73,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp index 4baf9f14fe..b38a07637c 100644 --- a/backends/fs/posix/posix-fs.cpp +++ b/backends/fs/posix/posix-fs.cpp @@ -38,6 +38,8 @@ #include <sys/stat.h> #include <dirent.h> #include <stdio.h> +#include <errno.h> +#include <fcntl.h> #ifdef __OS2__ #define INCL_DOS @@ -251,4 +253,91 @@ Common::WriteStream *POSIXFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool POSIXFilesystemNode::create(bool isDirectory) { + bool success; + + if (isDirectory) { + success = mkdir(_path.c_str(), 0755) == 0; + } else { + success = creat(_path.c_str(), 0755) != -1; + } + + if (success) { + setFlags(); + if (_isValid) { + if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file"); + return _isDirectory == isDirectory; + } + + warning("POSIXFilesystemNode: %s() was a success, but stat indicates there is no such %s", + isDirectory ? "mkdir" : "creat", isDirectory ? "directory" : "file"); + return false; + } + + return false; +} + +namespace Posix { + +bool assureDirectoryExists(const Common::String &dir, const char *prefix) { + struct stat sb; + + // Check whether the prefix exists if one is supplied. + if (prefix) { + if (stat(prefix, &sb) != 0) { + return false; + } else if (!S_ISDIR(sb.st_mode)) { + return false; + } + } + + // Obtain absolute path. + Common::String path; + if (prefix) { + path = prefix; + path += '/'; + path += dir; + } else { + path = dir; + } + + path = Common::normalizePath(path, '/'); + + const Common::String::iterator end = path.end(); + Common::String::iterator cur = path.begin(); + if (*cur == '/') + ++cur; + + do { + if (cur + 1 != end) { + if (*cur != '/') { + continue; + } + + // It is kind of ugly and against the purpose of Common::String to + // insert 0s inside, but this is just for a local string and + // simplifies the code a lot. + *cur = '\0'; + } + + if (mkdir(path.c_str(), 0755) != 0) { + if (errno == EEXIST) { + if (stat(path.c_str(), &sb) != 0) { + return false; + } else if (!S_ISDIR(sb.st_mode)) { + return false; + } + } else { + return false; + } + } + + *cur = '/'; + } while (cur++ != end); + + return true; +} + +} // End of namespace Posix + #endif //#if defined(POSIX) diff --git a/backends/fs/posix/posix-fs.h b/backends/fs/posix/posix-fs.h index bd07749010..4ebce7e9d9 100644 --- a/backends/fs/posix/posix-fs.h +++ b/backends/fs/posix/posix-fs.h @@ -73,6 +73,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); private: /** @@ -81,4 +82,18 @@ private: virtual void setFlags(); }; +namespace Posix { + +/** + * Assure that a directory path exists. + * + * @param dir The path which is required to exist. + * @param prefix An (optional) prefix which should not be created if non existent. + * prefix is prepended to dir if supplied. + * @return true in case the directoy exists (or was created), false otherwise. + */ +bool assureDirectoryExists(const Common::String &dir, const char *prefix = nullptr); + +} // End of namespace Posix + #endif diff --git a/backends/fs/ps2/ps2-fs.cpp b/backends/fs/ps2/ps2-fs.cpp index 9b6e1270f1..8391e063a0 100644 --- a/backends/fs/ps2/ps2-fs.cpp +++ b/backends/fs/ps2/ps2-fs.cpp @@ -441,4 +441,9 @@ Common::WriteStream *Ps2FilesystemNode::createWriteStream() { return PS2FileStream::makeFromPath(getPath(), true); } +bool Ps2FilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif diff --git a/backends/fs/ps2/ps2-fs.h b/backends/fs/ps2/ps2-fs.h index 63b866ba5b..c9da434535 100644 --- a/backends/fs/ps2/ps2-fs.h +++ b/backends/fs/ps2/ps2-fs.h @@ -96,6 +96,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); int getDev() { return 0; } }; diff --git a/backends/fs/psp/psp-fs.cpp b/backends/fs/psp/psp-fs.cpp index e8aad9fa98..6bd5e93435 100644 --- a/backends/fs/psp/psp-fs.cpp +++ b/backends/fs/psp/psp-fs.cpp @@ -239,4 +239,9 @@ Common::WriteStream *PSPFilesystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool PSPFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#ifdef __PSP__ diff --git a/backends/fs/psp/psp-fs.h b/backends/fs/psp/psp-fs.h index 1bb4543d19..648544650b 100644 --- a/backends/fs/psp/psp-fs.h +++ b/backends/fs/psp/psp-fs.h @@ -65,6 +65,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/symbian/symbian-fs.cpp b/backends/fs/symbian/symbian-fs.cpp index 8fbc3a402a..e4163caa33 100644 --- a/backends/fs/symbian/symbian-fs.cpp +++ b/backends/fs/symbian/symbian-fs.cpp @@ -231,4 +231,10 @@ Common::SeekableReadStream *SymbianFilesystemNode::createReadStream() { Common::WriteStream *SymbianFilesystemNode::createWriteStream() { return SymbianStdioStream::makeFromPath(getPath(), true); } + +bool SymbianFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#if defined(__SYMBIAN32__) diff --git a/backends/fs/symbian/symbian-fs.h b/backends/fs/symbian/symbian-fs.h index 339e998a28..1327f9f2e9 100644 --- a/backends/fs/symbian/symbian-fs.h +++ b/backends/fs/symbian/symbian-fs.h @@ -66,6 +66,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/wii/wii-fs.cpp b/backends/fs/wii/wii-fs.cpp index 43f4f592b7..46041bf499 100644 --- a/backends/fs/wii/wii-fs.cpp +++ b/backends/fs/wii/wii-fs.cpp @@ -213,4 +213,9 @@ Common::WriteStream *WiiFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool WiiFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#if defined(__WII__) diff --git a/backends/fs/wii/wii-fs.h b/backends/fs/wii/wii-fs.h index c77c543dae..affb765884 100644 --- a/backends/fs/wii/wii-fs.h +++ b/backends/fs/wii/wii-fs.h @@ -69,6 +69,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp index 49549b83cb..b43686f911 100644 --- a/backends/fs/windows/windows-fs.cpp +++ b/backends/fs/windows/windows-fs.cpp @@ -244,4 +244,36 @@ Common::WriteStream *WindowsFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool WindowsFilesystemNode::create(bool isDirectory) { + bool success; + + if (isDirectory) { + success = CreateDirectory(toUnicode(_path.c_str()), NULL) != 0; + } else { + success = CreateFile(toUnicode(_path.c_str()), GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) != INVALID_HANDLE_VALUE; + } + + if (success) { + //this piece is copied from constructor, it checks that file exists and detects whether it's a directory + DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str())); + if (fileAttribs != INVALID_FILE_ATTRIBUTES) { + _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0); + _isValid = true; + // Add a trailing slash, if necessary. + if (_isDirectory && _path.lastChar() != '\\') { + _path += '\\'; + } + + if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file"); + return _isDirectory == isDirectory; + } + + warning("WindowsFilesystemNode: Create%s() was a success, but GetFileAttributes() indicates there is no such %s", + isDirectory ? "Directory" : "File", isDirectory ? "directory" : "file"); + return false; + } + + return false; +} + #endif //#ifdef WIN32 diff --git a/backends/fs/windows/windows-fs.h b/backends/fs/windows/windows-fs.h index d06044603a..7c9f2c1e1a 100644 --- a/backends/fs/windows/windows-fs.h +++ b/backends/fs/windows/windows-fs.h @@ -88,6 +88,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); private: /** diff --git a/backends/graphics/androidsdl/androidsdl-graphics.cpp b/backends/graphics/androidsdl/androidsdl-graphics.cpp new file mode 100644 index 0000000000..23a1a86dd6 --- /dev/null +++ b/backends/graphics/androidsdl/androidsdl-graphics.cpp @@ -0,0 +1,42 @@ +/* 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/scummsys.h" + +#if defined(ANDROIDSDL) + +#include "backends/graphics/androidsdl/androidsdl-graphics.h" +#include "backends/events/androidsdl/androidsdl-events.h" +#include "common/mutex.h" +#include "common/textconsole.h" +#include "graphics/font.h" +#include "graphics/fontman.h" +#include "graphics/scaler.h" +#include "graphics/scaler/aspect.h" +#include "graphics/scaler/downscaler.h" +#include "graphics/surface.h" + +AndroidSdlGraphicsManager::AndroidSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { +} + +#endif diff --git a/backends/graphics/androidsdl/androidsdl-graphics.h b/backends/graphics/androidsdl/androidsdl-graphics.h new file mode 100644 index 0000000000..b7ca7c1de8 --- /dev/null +++ b/backends/graphics/androidsdl/androidsdl-graphics.h @@ -0,0 +1,34 @@ +/* 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 BACKENDS_GRAPHICS_SDL_ANDROIDSDL_H +#define BACKENDS_GRAPHICS_SDL_ANDROIDSDL_H + +#include "backends/graphics/surfacesdl/surfacesdl-graphics.h" + +class AndroidSdlGraphicsManager : public SurfaceSdlGraphicsManager { +public: + AndroidSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); + +}; + +#endif diff --git a/backends/graphics/dinguxsdl/dinguxsdl-graphics.cpp b/backends/graphics/dinguxsdl/dinguxsdl-graphics.cpp index 343efa4da6..7a248f1859 100644 --- a/backends/graphics/dinguxsdl/dinguxsdl-graphics.cpp +++ b/backends/graphics/dinguxsdl/dinguxsdl-graphics.cpp @@ -35,8 +35,12 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { {0, 0, 0} }; -DINGUXSdlGraphicsManager::DINGUXSdlGraphicsManager(SdlEventSource *boss) - : SurfaceSdlGraphicsManager(boss) { +#ifndef USE_SCALERS +#define DownscaleAllByHalf 0 +#endif + +DINGUXSdlGraphicsManager::DINGUXSdlGraphicsManager(SdlEventSource *boss, SdlWindow *window) + : SurfaceSdlGraphicsManager(boss, window) { } const OSystem::GraphicsMode *DINGUXSdlGraphicsManager::getSupportedGraphicsModes() const { @@ -61,9 +65,11 @@ bool DINGUXSdlGraphicsManager::setGraphicsMode(int mode) { case GFX_NORMAL: newScaleFactor = 1; break; +#ifdef USE_SCALERS case GFX_HALF: newScaleFactor = 1; break; +#endif default: warning("unknown gfx mode %d", mode); return false; @@ -89,9 +95,11 @@ void DINGUXSdlGraphicsManager::setGraphicsModeIntern() { case GFX_NORMAL: newScalerProc = Normal1x; break; +#ifdef USE_SCALERS case GFX_HALF: newScalerProc = DownscaleAllByHalf; break; +#endif default: error("Unknown gfx mode %d", _videoMode.mode); @@ -122,7 +130,7 @@ void DINGUXSdlGraphicsManager::initSize(uint w, uint h) { if (w > 320 || h > 240) { setGraphicsMode(GFX_HALF); setGraphicsModeIntern(); - _eventSource->toggleMouseGrab(); + _window->toggleMouseGrab(); } _transactionDetails.sizeChanged = true; diff --git a/backends/graphics/dinguxsdl/dinguxsdl-graphics.h b/backends/graphics/dinguxsdl/dinguxsdl-graphics.h index fc70e721cf..8a356106ad 100644 --- a/backends/graphics/dinguxsdl/dinguxsdl-graphics.h +++ b/backends/graphics/dinguxsdl/dinguxsdl-graphics.h @@ -34,7 +34,7 @@ enum { class DINGUXSdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - DINGUXSdlGraphicsManager(SdlEventSource *boss); + DINGUXSdlGraphicsManager(SdlEventSource *boss, SdlWindow *window); bool hasFeature(OSystem::Feature f); void setFeatureState(OSystem::Feature f, bool enable); diff --git a/backends/graphics/gph/gph-graphics.cpp b/backends/graphics/gph/gph-graphics.cpp index 247e5ed490..65cb3d1d65 100644 --- a/backends/graphics/gph/gph-graphics.cpp +++ b/backends/graphics/gph/gph-graphics.cpp @@ -35,8 +35,8 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { {0, 0, 0} }; -GPHGraphicsManager::GPHGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +GPHGraphicsManager::GPHGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } const OSystem::GraphicsMode *GPHGraphicsManager::getSupportedGraphicsModes() const { @@ -141,7 +141,7 @@ void GPHGraphicsManager::initSize(uint w, uint h, const Graphics::PixelFormat *f if (w > 320 || h > 240) { setGraphicsMode(GFX_HALF); setGraphicsModeIntern(); - _eventSource->toggleMouseGrab(); + _window->toggleMouseGrab(); } _videoMode.overlayWidth = 320; diff --git a/backends/graphics/gph/gph-graphics.h b/backends/graphics/gph/gph-graphics.h index 4a68ea6eed..152d29ddf4 100644 --- a/backends/graphics/gph/gph-graphics.h +++ b/backends/graphics/gph/gph-graphics.h @@ -33,7 +33,7 @@ enum { class GPHGraphicsManager : public SurfaceSdlGraphicsManager { public: - GPHGraphicsManager(SdlEventSource *boss); + GPHGraphicsManager(SdlEventSource *boss, SdlWindow *window); bool hasFeature(OSystem::Feature f); void setFeatureState(OSystem::Feature f, bool enable); diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h index 3671b9f0b9..921dfca61c 100644 --- a/backends/graphics/graphics.h +++ b/backends/graphics/graphics.h @@ -84,6 +84,10 @@ public: virtual void setCursorPalette(const byte *colors, uint start, uint num) = 0; virtual void displayMessageOnOSD(const char *msg) {} + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {} + virtual void clearOSD() {} + virtual Graphics::PixelFormat getOSDFormat() { return Graphics::PixelFormat(); } + // Graphics::PaletteManager interface //virtual void setPalette(const byte *colors, uint start, uint num) = 0; diff --git a/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.cpp b/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.cpp index 22b271ae1a..52e5b42e8b 100644 --- a/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.cpp +++ b/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.cpp @@ -45,8 +45,8 @@ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { {0, 0, 0} }; -LinuxmotoSdlGraphicsManager::LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +LinuxmotoSdlGraphicsManager::LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } const OSystem::GraphicsMode *LinuxmotoSdlGraphicsManager::getSupportedGraphicsModes() const { @@ -134,7 +134,7 @@ void LinuxmotoSdlGraphicsManager::initSize(uint w, uint h) { if (w > 320 || h > 240) { setGraphicsMode(GFX_HALF); setGraphicsModeIntern(); - _eventSource->toggleMouseGrab(); + _window->toggleMouseGrab(); } _transactionDetails.sizeChanged = true; diff --git a/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.h b/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.h index 8760c5004d..d7a13b1cb4 100644 --- a/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.h +++ b/backends/graphics/linuxmotosdl/linuxmotosdl-graphics.h @@ -27,7 +27,7 @@ class LinuxmotoSdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource); + LinuxmotoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); virtual void initSize(uint w, uint h); virtual void setGraphicsModeIntern(); diff --git a/backends/graphics/maemosdl/maemosdl-graphics.cpp b/backends/graphics/maemosdl/maemosdl-graphics.cpp index 07d6d32d3a..d954333537 100644 --- a/backends/graphics/maemosdl/maemosdl-graphics.cpp +++ b/backends/graphics/maemosdl/maemosdl-graphics.cpp @@ -21,16 +21,14 @@ */ #if defined(MAEMO) -#include "SDL_syswm.h" - #include "common/scummsys.h" #include "backends/platform/maemo/maemo.h" #include "backends/events/maemosdl/maemosdl-events.h" #include "backends/graphics/maemosdl/maemosdl-graphics.h" -MaemoSdlGraphicsManager::MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +MaemoSdlGraphicsManager::MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } bool MaemoSdlGraphicsManager::loadGFXMode() { diff --git a/backends/graphics/maemosdl/maemosdl-graphics.h b/backends/graphics/maemosdl/maemosdl-graphics.h index c255e94653..4cb84c81ee 100644 --- a/backends/graphics/maemosdl/maemosdl-graphics.h +++ b/backends/graphics/maemosdl/maemosdl-graphics.h @@ -29,7 +29,7 @@ class MaemoSdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource); + MaemoSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); protected: virtual bool loadGFXMode(); diff --git a/backends/graphics/opengl/context.cpp b/backends/graphics/opengl/context.cpp new file mode 100644 index 0000000000..a7f640d37e --- /dev/null +++ b/backends/graphics/opengl/context.cpp @@ -0,0 +1,182 @@ +/* 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 "backends/graphics/opengl/opengl-sys.h" +#include "backends/graphics/opengl/opengl-graphics.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/framebuffer.h" + +#include "common/tokenizer.h" +#include "common/debug.h" + +namespace OpenGL { + +void Context::reset() { + maxTextureSize = 0; + + NPOTSupported = false; + shadersSupported = false; + multitextureSupported = false; + framebufferObjectSupported = false; + +#define GL_FUNC_DEF(ret, name, param) name = nullptr; +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_DEF + + activePipeline = nullptr; +} + +Pipeline *Context::setPipeline(Pipeline *pipeline) { + Pipeline *oldPipeline = activePipeline; + if (oldPipeline) { + oldPipeline->deactivate(); + } + + activePipeline = pipeline; + if (activePipeline) { + activePipeline->activate(); + } + + return oldPipeline; +} + +Context g_context; + +void OpenGLGraphicsManager::setContextType(ContextType type) { +#if USE_FORCED_GL + type = kContextGL; +#elif USE_FORCED_GLES + type = kContextGLES; +#elif USE_FORCED_GLES2 + type = kContextGLES2; +#endif + + g_context.type = type; +} + +void OpenGLGraphicsManager::initializeGLContext() { + // Initialize default state. + g_context.reset(); + + // Load all functions. + // We use horrible trickery to silence C++ compilers. + // See backends/plugins/sdl/sdl-provider.cpp for more information. + assert(sizeof(void (*)()) == sizeof(void *)); + void *fn = nullptr; + +#define LOAD_FUNC(name, loadName) \ + fn = getProcAddress(#loadName); \ + memcpy(&g_context.name, &fn, sizeof(fn)) + +#define GL_EXT_FUNC_DEF(ret, name, param) LOAD_FUNC(name, name) + +#ifdef USE_BUILTIN_OPENGL +#define GL_FUNC_DEF(ret, name, param) g_context.name = &name +#define GL_FUNC_2_DEF GL_FUNC_DEF +#else +#define GL_FUNC_DEF GL_EXT_FUNC_DEF +#define GL_FUNC_2_DEF(ret, name, extName, param) \ + if (g_context.type == kContextGL) { \ + LOAD_FUNC(name, extName); \ + } else { \ + LOAD_FUNC(name, name); \ + } +#endif +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_2_DEF +#undef GL_FUNC_DEF +#undef GL_EXT_FUNC_DEF +#undef LOAD_FUNC + + // Obtain maximum texture size. + GL_CALL(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &g_context.maxTextureSize)); + debug(5, "OpenGL maximum texture size: %d", g_context.maxTextureSize); + + const char *extString = (const char *)g_context.glGetString(GL_EXTENSIONS); + debug(5, "OpenGL extensions: %s", extString); + + bool ARBShaderObjects = false; + bool ARBShadingLanguage100 = false; + bool ARBVertexShader = false; + bool ARBFragmentShader = false; + + Common::StringTokenizer tokenizer(extString, " "); + while (!tokenizer.empty()) { + Common::String token = tokenizer.nextToken(); + + if (token == "GL_ARB_texture_non_power_of_two" || token == "GL_OES_texture_npot") { + g_context.NPOTSupported = true; + } else if (token == "GL_ARB_shader_objects") { + ARBShaderObjects = true; + } else if (token == "GL_ARB_shading_language_100") { + ARBShadingLanguage100 = true; + } else if (token == "GL_ARB_vertex_shader") { + ARBVertexShader = true; + } else if (token == "GL_ARB_fragment_shader") { + ARBFragmentShader = true; + } else if (token == "GL_ARB_multitexture") { + g_context.multitextureSupported = true; + } else if (token == "GL_EXT_framebuffer_object") { + g_context.framebufferObjectSupported = true; + } + } + + if (g_context.type == kContextGLES2) { + // GLES2 always has (limited) NPOT support. + g_context.NPOTSupported = true; + + // GLES2 always has shader support. + g_context.shadersSupported = true; + + // GLES2 always has multi texture support. + g_context.multitextureSupported = true; + + // GLES2 always has FBO support. + g_context.framebufferObjectSupported = true; + } else { + g_context.shadersSupported = ARBShaderObjects & ARBShadingLanguage100 & ARBVertexShader & ARBFragmentShader; + } + + // Log context type. + switch (g_context.type) { + case kContextGL: + debug(5, "OpenGL: GL context initialized"); + break; + + case kContextGLES: + debug(5, "OpenGL: GLES context initialized"); + break; + + case kContextGLES2: + debug(5, "OpenGL: GLES2 context initialized"); + break; + } + + // Log features supported by GL context. + debug(5, "OpenGL: NPOT texture support: %d", g_context.NPOTSupported); + debug(5, "OpenGL: Shader support: %d", g_context.shadersSupported); + debug(5, "OpenGL: Multitexture support: %d", g_context.multitextureSupported); + debug(5, "OpenGL: FBO support: %d", g_context.framebufferObjectSupported); +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/debug.cpp b/backends/graphics/opengl/debug.cpp index d5d73fb5ec..c4319f5e36 100644 --- a/backends/graphics/opengl/debug.cpp +++ b/backends/graphics/opengl/debug.cpp @@ -54,7 +54,7 @@ Common::String getGLErrStr(GLenum error) { void checkGLError(const char *expr, const char *file, int line) { GLenum error; - while ((error = glGetError()) != GL_NO_ERROR) { + while ((error = g_context.glGetError()) != GL_NO_ERROR) { // We cannot use error here because we do not know whether we have a // working screen or not. warning("GL ERROR: %s on %s (%s:%d)", getGLErrStr(error).c_str(), expr, file, line); diff --git a/backends/graphics/opengl/debug.h b/backends/graphics/opengl/debug.h index ff6b678870..abaa6544dc 100644 --- a/backends/graphics/opengl/debug.h +++ b/backends/graphics/opengl/debug.h @@ -31,9 +31,9 @@ namespace OpenGL { void checkGLError(const char *expr, const char *file, int line); } // End of namespace OpenGL -#define GLCALL(x) do { (x); OpenGL::checkGLError(#x, __FILE__, __LINE__); } while (false) +#define GL_WRAP_DEBUG(call, name) do { (call); OpenGL::checkGLError(#name, __FILE__, __LINE__); } while (false) #else -#define GLCALL(x) do { (x); } while (false) +#define GL_WRAP_DEBUG(call, name) do { (call); } while (false) #endif #endif diff --git a/backends/graphics/opengl/framebuffer.cpp b/backends/graphics/opengl/framebuffer.cpp new file mode 100644 index 0000000000..7191aab8bc --- /dev/null +++ b/backends/graphics/opengl/framebuffer.cpp @@ -0,0 +1,259 @@ +/* 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 "backends/graphics/opengl/framebuffer.h" +#include "backends/graphics/opengl/texture.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" + +namespace OpenGL { + +Framebuffer::Framebuffer() + : _viewport(), _projectionMatrix(), _isActive(false), _clearColor(), + _blendState(false), _scissorTestState(false), _scissorBox() { +} + +void Framebuffer::activate() { + _isActive = true; + + applyViewport(); + applyProjectionMatrix(); + applyClearColor(); + applyBlendState(); + applyScissorTestState(); + applyScissorBox(); + + activateInternal(); +} + +void Framebuffer::deactivate() { + _isActive = false; + + deactivateInternal(); +} + +void Framebuffer::setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + _clearColor[0] = r; + _clearColor[1] = g; + _clearColor[2] = b; + _clearColor[3] = a; + + // Directly apply changes when we are active. + if (isActive()) { + applyClearColor(); + } +} + +void Framebuffer::enableBlend(bool enable) { + _blendState = enable; + + // Directly apply changes when we are active. + if (isActive()) { + applyBlendState(); + } +} + +void Framebuffer::enableScissorTest(bool enable) { + _scissorTestState = enable; + + // Directly apply changes when we are active. + if (isActive()) { + applyScissorTestState(); + } +} + +void Framebuffer::setScissorBox(GLint x, GLint y, GLsizei w, GLsizei h) { + _scissorBox[0] = x; + _scissorBox[1] = y; + _scissorBox[2] = w; + _scissorBox[3] = h; + + // Directly apply changes when we are active. + if (isActive()) { + applyScissorBox(); + } +} + +void Framebuffer::applyViewport() { + GL_CALL(glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3])); +} + +void Framebuffer::applyProjectionMatrix() { + g_context.getActivePipeline()->setProjectionMatrix(_projectionMatrix); +} + +void Framebuffer::applyClearColor() { + GL_CALL(glClearColor(_clearColor[0], _clearColor[1], _clearColor[2], _clearColor[3])); +} + +void Framebuffer::applyBlendState() { + if (_blendState) { + GL_CALL(glEnable(GL_BLEND)); + } else { + GL_CALL(glDisable(GL_BLEND)); + } +} + +void Framebuffer::applyScissorTestState() { + if (_scissorTestState) { + GL_CALL(glEnable(GL_SCISSOR_TEST)); + } else { + GL_CALL(glDisable(GL_SCISSOR_TEST)); + } +} + +void Framebuffer::applyScissorBox() { + GL_CALL(glScissor(_scissorBox[0], _scissorBox[1], _scissorBox[2], _scissorBox[3])); +} + +// +// Backbuffer implementation +// + +void Backbuffer::activateInternal() { +#if !USE_FORCED_GLES + if (g_context.framebufferObjectSupported) { + GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); + } +#endif +} + +void Backbuffer::setDimensions(uint width, uint height) { + // Set viewport dimensions. + _viewport[0] = 0; + _viewport[1] = 0; + _viewport[2] = width; + _viewport[3] = height; + + // Setup orthogonal projection matrix. + _projectionMatrix[ 0] = 2.0f / width; + _projectionMatrix[ 1] = 0.0f; + _projectionMatrix[ 2] = 0.0f; + _projectionMatrix[ 3] = 0.0f; + + _projectionMatrix[ 4] = 0.0f; + _projectionMatrix[ 5] = -2.0f / height; + _projectionMatrix[ 6] = 0.0f; + _projectionMatrix[ 7] = 0.0f; + + _projectionMatrix[ 8] = 0.0f; + _projectionMatrix[ 9] = 0.0f; + _projectionMatrix[10] = 0.0f; + _projectionMatrix[11] = 0.0f; + + _projectionMatrix[12] = -1.0f; + _projectionMatrix[13] = 1.0f; + _projectionMatrix[14] = 0.0f; + _projectionMatrix[15] = 1.0f; + + // Directly apply changes when we are active. + if (isActive()) { + applyViewport(); + applyProjectionMatrix(); + } +} + +// +// Render to texture target implementation +// + +#if !USE_FORCED_GLES +TextureTarget::TextureTarget() + : _texture(new GLTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE)), _glFBO(0), _needUpdate(true) { +} + +TextureTarget::~TextureTarget() { + delete _texture; + GL_CALL_SAFE(glDeleteFramebuffers, (1, &_glFBO)); +} + +void TextureTarget::activateInternal() { + // Allocate framebuffer object if necessary. + if (!_glFBO) { + GL_CALL(glGenFramebuffers(1, &_glFBO)); + _needUpdate = true; + } + + // Attach destination texture to FBO. + GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO)); + + // If required attach texture to FBO. + if (_needUpdate) { + GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getGLTexture(), 0)); + _needUpdate = false; + } +} + +void TextureTarget::destroy() { + GL_CALL(glDeleteFramebuffers(1, &_glFBO)); + _glFBO = 0; + + _texture->destroy(); +} + +void TextureTarget::create() { + _texture->create(); + + _needUpdate = true; +} + +void TextureTarget::setSize(uint width, uint height) { + _texture->setSize(width, height); + + const uint texWidth = _texture->getWidth(); + const uint texHeight = _texture->getHeight(); + + // Set viewport dimensions. + _viewport[0] = 0; + _viewport[1] = 0; + _viewport[2] = texWidth; + _viewport[3] = texHeight; + + // Setup orthogonal projection matrix. + _projectionMatrix[ 0] = 2.0f / texWidth; + _projectionMatrix[ 1] = 0.0f; + _projectionMatrix[ 2] = 0.0f; + _projectionMatrix[ 3] = 0.0f; + + _projectionMatrix[ 4] = 0.0f; + _projectionMatrix[ 5] = 2.0f / texHeight; + _projectionMatrix[ 6] = 0.0f; + _projectionMatrix[ 7] = 0.0f; + + _projectionMatrix[ 8] = 0.0f; + _projectionMatrix[ 9] = 0.0f; + _projectionMatrix[10] = 0.0f; + _projectionMatrix[11] = 0.0f; + + _projectionMatrix[12] = -1.0f; + _projectionMatrix[13] = -1.0f; + _projectionMatrix[14] = 0.0f; + _projectionMatrix[15] = 1.0f; + + // Directly apply changes when we are active. + if (isActive()) { + applyViewport(); + applyProjectionMatrix(); + } +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/framebuffer.h b/backends/graphics/opengl/framebuffer.h new file mode 100644 index 0000000000..c44c98ddc4 --- /dev/null +++ b/backends/graphics/opengl/framebuffer.h @@ -0,0 +1,175 @@ +/* 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 BACKENDS_GRAPHICS_OPENGL_FRAMEBUFFER_H +#define BACKENDS_GRAPHICS_OPENGL_FRAMEBUFFER_H + +#include "backends/graphics/opengl/opengl-sys.h" + +namespace OpenGL { + +/** + * Object describing a framebuffer OpenGL can render to. + */ +class Framebuffer { + friend class Pipeline; +public: + Framebuffer(); + virtual ~Framebuffer() {}; + +public: + /** + * Set the clear color of the framebuffer. + */ + void setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); + + /** + * Enable/disable GL_BLEND. + */ + void enableBlend(bool enable); + + /** + * Enable/disable GL_SCISSOR_TEST. + */ + void enableScissorTest(bool enable); + + /** + * Set scissor box dimensions. + */ + void setScissorBox(GLint x, GLint y, GLsizei w, GLsizei h); + + /** + * Obtain projection matrix of the framebuffer. + */ + const GLfloat *getProjectionMatrix() const { return _projectionMatrix; } +protected: + bool isActive() const { return _isActive; } + + GLint _viewport[4]; + void applyViewport(); + + GLfloat _projectionMatrix[4*4]; + void applyProjectionMatrix(); + + /** + * Activate framebuffer. + * + * This is supposed to set all state associated with the framebuffer. + */ + virtual void activateInternal() = 0; + + /** + * Deactivate framebuffer. + * + * This is supposed to make any cleanup required when unbinding the + * framebuffer. + */ + virtual void deactivateInternal() {} + +private: + /** + * Accessor to activate framebuffer for pipeline. + */ + void activate(); + + /** + * Accessor to deactivate framebuffer from pipeline. + */ + void deactivate(); + +private: + bool _isActive; + + GLfloat _clearColor[4]; + void applyClearColor(); + + bool _blendState; + void applyBlendState(); + + bool _scissorTestState; + void applyScissorTestState(); + + GLint _scissorBox[4]; + void applyScissorBox(); +}; + +/** + * Default back buffer implementation. + */ +class Backbuffer : public Framebuffer { +public: + /** + * Set the dimensions (a.k.a. size) of the back buffer. + */ + void setDimensions(uint width, uint height); + +protected: + virtual void activateInternal(); +}; + +#if !USE_FORCED_GLES +class GLTexture; + +/** + * Render to texture framebuffer implementation. + * + * This target allows to render to a texture, which can then be used for + * further rendering. + */ +class TextureTarget : public Framebuffer { +public: + TextureTarget(); + virtual ~TextureTarget(); + + /** + * Notify that the GL context is about to be destroyed. + */ + void destroy(); + + /** + * Notify that the GL context has been created. + */ + void create(); + + /** + * Set size of the texture target. + */ + void setSize(uint width, uint height); + + /** + * Query pointer to underlying GL texture. + */ + GLTexture *getTexture() const { return _texture; } + +protected: + virtual void activateInternal(); + +private: + GLTexture *_texture; + GLuint _glFBO; + bool _needUpdate; +}; +#endif + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/opengl-defs.h b/backends/graphics/opengl/opengl-defs.h new file mode 100644 index 0000000000..733fc2933c --- /dev/null +++ b/backends/graphics/opengl/opengl-defs.h @@ -0,0 +1,262 @@ +/* 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 Mesa 3-D's gl.h and GLES/gl.h from Khronos Registry. + * + * Mesa 3-D's gl.h file is distributed under the following license: + * Mesa 3-D graphics library + * + * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. + * Copyright (C) 2009 VMware, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + * GLES/gl.h from Khronos Registry is distributed under the following license: + * This document is licensed under the SGI Free Software B License Version + * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ . + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_DEFS_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_DEFS_H + +#include "common/scummsys.h" + +/* + * Datatypes + */ +typedef uint GLenum; +typedef uint8 GLboolean; +typedef uint GLbitfield; +typedef void GLvoid; +typedef int8 GLbyte; /* 1-byte signed */ +typedef int16 GLshort; /* 2-byte signed */ +typedef int32 GLint; /* 4-byte signed */ +typedef uint8 GLubyte; /* 1-byte unsigned */ +typedef uint16 GLushort; /* 2-byte unsigned */ +typedef uint32 GLuint; /* 4-byte unsigned */ +typedef int32 GLsizei; /* 4-byte signed */ +typedef float GLfloat; /* single precision float */ +typedef float GLclampf; /* single precision float in [0,1] */ +typedef double GLdouble; /* double precision float */ +typedef double GLclampd; /* double precision float in [0,1] */ +typedef char GLchar; +#if defined(MACOSX) +typedef void *GLhandleARB; +#else +typedef uint GLhandleARB; +#endif + +// This is an addition from us to alias ARB shader object extensions to +// OpenGL (ES) 2.0 style functions. It only works when GLhandleARB and GLuint +// are type compatible. +typedef GLhandleARB GLprogram; +typedef GLhandleARB GLshader; + +/* + * Constants + */ + +/* Boolean constants */ +#define GL_FALSE 0 +#define GL_TRUE 1 + +/* StringName */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* ErrorCode */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* ClearBufferMask */ +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 + +/* Scissor box */ +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +/* MatrixMode */ +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* EnableCap */ +#define GL_FOG 0x0B60 +#define GL_LIGHTING 0x0B50 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_CULL_FACE 0x0B44 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_BLEND 0x0BE2 +#define GL_DITHER 0x0BD0 +#define GL_DEPTH_TEST 0x0B71 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_COLOR_ARRAY 0x8076 +#define GL_TEXTURE_COORD_ARRAY 0x8078 + +/* ShadingModel */ +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +/* HintMode */ +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* HintTarget */ +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_FOG_HINT 0x0C54 +#define GL_GENERATE_MIPMAP_HINT 0x8192 + +/* BlendingFactorDest */ +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +/* GL_SRC_ALPHA */ +/* GL_ONE_MINUS_SRC_ALPHA */ +/* GL_DST_ALPHA */ +/* GL_ONE_MINUS_DST_ALPHA */ + +/* PixelFormat */ +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 + +#define GL_RED 0x1903 +#define GL_R8 0x8229 + +/* PixelStoreParameter */ +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_ALIGNMENT 0x0D05 + +/* DataType */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_FLOAT 0x1406 +#define GL_FIXED 0x140C + +/* PixelType */ +/* GL_UNSIGNED_BYTE */ +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 + +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 + +/* Implementation limits */ +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +/* TextureMagFilter */ +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +/* TextureParameterName */ +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 + +/* TextureWrapMode */ +#define GL_REPEAT 0x2901 +#define GL_CLAMP_TO_EDGE 0x812F + +/* BeginMode */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 + +/* Shaders */ +#define GL_FRAGMENT_SHADER 0x8B30 + +#define GL_VERTEX_SHADER 0x8B31 + +/* Programs */ +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D + +/* Textures */ +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 + +/* GetPName */ +#define GL_VIEWPORT 0x0BA2 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 + +/* Framebuffer objects */ +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_FRAMEBUFFER 0x8D40 + +#endif diff --git a/backends/graphics/opengl/opengl-func.h b/backends/graphics/opengl/opengl-func.h new file mode 100644 index 0000000000..4e44c13d0f --- /dev/null +++ b/backends/graphics/opengl/opengl-func.h @@ -0,0 +1,153 @@ +/* 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 Mesa 3-D's gl.h and GLES/gl.h from Khronos Registry. + * + * Mesa 3-D's gl.h file is distributed under the following license: + * Mesa 3-D graphics library + * + * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. + * Copyright (C) 2009 VMware, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + * GLES/gl.h from Khronos Registry is distributed under the following license: + * This document is licensed under the SGI Free Software B License Version + * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ . + */ + +/* + * This file is a template file to be used inside specific locations in the + * OpenGL graphics code. It is not to be included otherwise. It intentionally + * does not contain include guards because it can be required to include it + * multiple times in a source file. + * + * Functions are defined by three different user supplied macros: + * GL_FUNC_DEF: Define a (builtin) OpenGL (ES) function. + * GL_FUNC_2_DEF: Define a OpenGL (ES) 2.0 function which can be provided by + * extensions in OpenGL 1.x contexts. + * GL_EXT_FUNC_DEF: Define an OpenGL (ES) extension function. + */ + +#if !defined(GL_FUNC_2_DEF) +#define GL_FUNC_2_DEF(ret, name, extName, param) GL_FUNC_DEF(ret, name, param) +#define DEFINED_GL_FUNC_2_DEF +#endif + +#if !defined(GL_EXT_FUNC_DEF) +#define GL_EXT_FUNC_DEF(ret, name, param) GL_FUNC_DEF(ret, name, param) +#define DEFINED_GL_EXT_FUNC_DEF +#endif + +GL_FUNC_DEF(void, glEnable, (GLenum cap)); +GL_FUNC_DEF(void, glDisable, (GLenum cap)); +GL_FUNC_DEF(GLboolean, glIsEnabled, (GLenum cap)); +GL_FUNC_DEF(void, glClear, (GLbitfield mask)); +GL_FUNC_DEF(void, glColor4f, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)); +GL_FUNC_DEF(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height)); +GL_FUNC_DEF(void, glMatrixMode, (GLenum mode)); +GL_FUNC_DEF(void, glLoadIdentity, ()); +GL_FUNC_DEF(void, glLoadMatrixf, (const GLfloat *m)); +GL_FUNC_DEF(void, glShadeModel, (GLenum mode)); +GL_FUNC_DEF(void, glHint, (GLenum target, GLenum mode)); +GL_FUNC_DEF(void, glClearColor, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)); +GL_FUNC_DEF(void, glBlendFunc, (GLenum sfactor, GLenum dfactor)); +GL_FUNC_DEF(void, glEnableClientState, (GLenum array)); +GL_FUNC_DEF(void, glPixelStorei, (GLenum pname, GLint param)); +GL_FUNC_DEF(void, glScissor, (GLint x, GLint y, GLsizei width, GLsizei height)); +GL_FUNC_DEF(void, glReadPixels, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)); +GL_FUNC_DEF(void, glGetIntegerv, (GLenum pname, GLint *params)); +GL_FUNC_DEF(void, glDeleteTextures, (GLsizei n, const GLuint *textures)); +GL_FUNC_DEF(void, glGenTextures, (GLsizei n, GLuint *textures)); +GL_FUNC_DEF(void, glBindTexture, (GLenum target, GLuint texture)); +GL_FUNC_DEF(void, glTexParameteri, (GLenum target, GLenum pname, GLint param)); +GL_FUNC_DEF(void, glTexImage2D, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)); +GL_FUNC_DEF(void, glTexCoordPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)); +GL_FUNC_DEF(void, glVertexPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)); +GL_FUNC_DEF(void, glDrawArrays, (GLenum mode, GLint first, GLsizei count)); +GL_FUNC_DEF(void, glTexSubImage2D, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)); +GL_FUNC_DEF(const GLubyte *, glGetString, (GLenum name)); +GL_FUNC_DEF(GLenum, glGetError, ()); + +#if !USE_FORCED_GLES +GL_FUNC_2_DEF(void, glEnableVertexAttribArray, glEnableVertexAttribArrayARB, (GLuint index)); +GL_FUNC_2_DEF(void, glDisableVertexAttribArray, glDisableVertexAttribArrayARB, (GLuint index)); +GL_FUNC_2_DEF(void, glUniform1i, glUniform1iARB, (GLint location, GLint v0)); +GL_FUNC_2_DEF(void, glUniform1f, glUniform1fARB, (GLint location, GLfloat v0)); +GL_FUNC_2_DEF(void, glUniformMatrix4fv, glUniformMatrix4fvARB, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)); +GL_FUNC_2_DEF(void, glVertexAttrib4f, glVertexAttrib4fARB, (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)); +GL_FUNC_2_DEF(void, glVertexAttribPointer, glVertexAttribPointerARB, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)); + +GL_FUNC_2_DEF(GLprogram, glCreateProgram, glCreateProgramObjectARB, ()); +GL_FUNC_2_DEF(void, glDeleteProgram, glDeleteObjectARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glAttachShader, glAttachObjectARB, (GLprogram program, GLshader shader)); +GL_FUNC_2_DEF(void, glDetachShader, glDetachObjectARB, (GLprogram program, GLshader shader)); +GL_FUNC_2_DEF(void, glLinkProgram, glLinkProgramARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glUseProgram, glUseProgramObjectARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glGetProgramiv, glGetObjectParameterivARB, (GLprogram program, GLenum pname, GLint *params)); +GL_FUNC_2_DEF(void, glGetProgramInfoLog, glGetInfoLogARB, (GLprogram program, GLsizei bufSize, GLsizei *length, GLchar *infoLog)); +GL_FUNC_2_DEF(void, glBindAttribLocation, glBindAttribLocationARB, (GLprogram program, GLuint index, const GLchar *name)); +GL_FUNC_2_DEF(GLint, glGetAttribLocation, glGetAttribLocationARB, (GLprogram program, const GLchar *name)); +GL_FUNC_2_DEF(GLint, glGetUniformLocation, glGetUniformLocationARB, (GLprogram program, const GLchar *name)); + +GL_FUNC_2_DEF(GLshader, glCreateShader, glCreateShaderObjectARB, (GLenum type)); +GL_FUNC_2_DEF(void, glDeleteShader, glDeleteObjectARB, (GLshader shader)); +GL_FUNC_2_DEF(void, glGetShaderiv, glGetObjectParameterivARB, (GLshader shader, GLenum pname, GLint *params)); +GL_FUNC_2_DEF(void, glGetShaderInfoLog, glGetInfoLogARB, (GLshader shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog)); +GL_FUNC_2_DEF(void, glShaderSource, glShaderSourceARB, (GLshader shader, GLsizei count, const GLchar *const *string, const GLint *length)); +GL_FUNC_2_DEF(void, glCompileShader, glCompileShaderARB, (GLshader shader)); + +GL_FUNC_2_DEF(void, glBindFramebuffer, glBindFramebufferEXT, (GLenum target, GLuint renderbuffer)); +GL_FUNC_2_DEF(void, glDeleteFramebuffers, glDeleteFramebuffersEXT, (GLsizei n, const GLuint *framebuffers)); +GL_FUNC_2_DEF(void, glGenFramebuffers, glGenFramebuffersEXT, (GLsizei n, GLuint *renderbuffers)); +GL_FUNC_2_DEF(void, glFramebufferTexture2D, glFramebufferTexture2DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)); +GL_FUNC_2_DEF(GLenum, glCheckFramebufferStatus, glCheckFramebufferStatusEXT, (GLenum target)); + +GL_FUNC_2_DEF(void, glActiveTexture, glActiveTextureARB, (GLenum texture)); +#endif + +#ifdef DEFINED_GL_EXT_FUNC_DEF +#undef DEFINED_GL_EXT_FUNC_DEF +#undef GL_EXT_FUNC_DEF +#endif + +#ifdef DEFINED_GL_FUNC_2_DEF +#undef DEFINED_GL_FUNC_2_DEF +#undef GL_FUNC_2_DEF +#endif diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp index c455c4ce2e..c491b03f1f 100644 --- a/backends/graphics/opengl/opengl-graphics.cpp +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -23,8 +23,10 @@ #include "backends/graphics/opengl/opengl-graphics.h" #include "backends/graphics/opengl/texture.h" -#include "backends/graphics/opengl/debug.h" -#include "backends/graphics/opengl/extensions.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/pipelines/fixed.h" +#include "backends/graphics/opengl/pipelines/shader.h" +#include "backends/graphics/opengl/shader.h" #include "common/textconsole.h" #include "common/translation.h" @@ -45,18 +47,21 @@ namespace OpenGL { OpenGLGraphicsManager::OpenGLGraphicsManager() : _currentState(), _oldState(), _transactionMode(kTransactionNone), _screenChangeID(1 << (sizeof(int) * 8 - 2)), + _pipeline(nullptr), _outputScreenWidth(0), _outputScreenHeight(0), _displayX(0), _displayY(0), _displayWidth(0), _displayHeight(0), _defaultFormat(), _defaultFormatAlpha(), _gameScreen(nullptr), _gameScreenShakeOffset(0), _overlay(nullptr), _overlayVisible(false), _cursor(nullptr), - _cursorX(0), _cursorY(0), _cursorHotspotX(0), _cursorHotspotY(0), _cursorHotspotXScaled(0), - _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), _cursorKeyColor(0), - _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false) + _cursorX(0), _cursorY(0), _cursorDisplayX(0),_cursorDisplayY(0), _cursorHotspotX(0), _cursorHotspotY(0), + _cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), + _cursorKeyColor(0), _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false), + _forceRedraw(false), _scissorOverride(3) #ifdef USE_OSD , _osdAlpha(0), _osdFadeStartTime(0), _osd(nullptr) #endif { memset(_gamePalette, 0, sizeof(_gamePalette)); + g_context.reset(); } OpenGLGraphicsManager::~OpenGLGraphicsManager() { @@ -66,6 +71,9 @@ OpenGLGraphicsManager::~OpenGLGraphicsManager() { #ifdef USE_OSD delete _osd; #endif +#if !USE_FORCED_GLES + ShaderManager::destroy(); +#endif } bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) { @@ -214,8 +222,8 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() { // a context existing before, which means we don't know the maximum // supported texture size before this. Thus, we check whether the // requested game resolution is supported over here. - || ( _currentState.gameWidth > (uint)Texture::getMaximumTextureSize() - || _currentState.gameHeight > (uint)Texture::getMaximumTextureSize())) { + || ( _currentState.gameWidth > (uint)g_context.maxTextureSize + || _currentState.gameHeight > (uint)g_context.maxTextureSize)) { if (_transactionMode == kTransactionActive) { // Try to setup the old state in case its valid and is // actually different from the new one. @@ -266,9 +274,9 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() { _gameScreen = nullptr; #ifdef USE_RGB_COLOR - _gameScreen = createTexture(_currentState.gameFormat); + _gameScreen = createSurface(_currentState.gameFormat); #else - _gameScreen = createTexture(Graphics::PixelFormat::createFormatCLUT8()); + _gameScreen = createSurface(Graphics::PixelFormat::createFormatCLUT8()); #endif assert(_gameScreen); if (_gameScreen->hasPalette()) { @@ -343,7 +351,10 @@ void OpenGLGraphicsManager::fillScreen(uint32 col) { } void OpenGLGraphicsManager::setShakePos(int shakeOffset) { - _gameScreenShakeOffset = shakeOffset; + if (_gameScreenShakeOffset != shakeOffset) { + _gameScreenShakeOffset = shakeOffset; + _forceRedraw = true; + } } void OpenGLGraphicsManager::updateScreen() { @@ -351,17 +362,47 @@ void OpenGLGraphicsManager::updateScreen() { return; } - // Clear the screen buffer - GLCALL(glClear(GL_COLOR_BUFFER_BIT)); + // We only update the screen when there actually have been any changes. + if ( !_forceRedraw + && !_gameScreen->isDirty() + && !(_overlayVisible && _overlay->isDirty()) + && !(_cursorVisible && _cursor && _cursor->isDirty()) + && _osdAlpha == 0) { + return; + } + _forceRedraw = false; + + // Update changes to textures. + _gameScreen->updateGLTexture(); + if (_cursor) { + _cursor->updateGLTexture(); + } + _overlay->updateGLTexture(); + _osd->updateGLTexture(); + + // Clear the screen buffer. + if (_scissorOverride && !_overlayVisible) { + // In certain cases we need to assure that the whole screen area is + // cleared. For example, when switching from overlay visible to + // invisible, we need to assure that all contents are cleared to + // properly remove all overlay contents. + _backBuffer.enableScissorTest(false); + GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); + _backBuffer.enableScissorTest(true); + + --_scissorOverride; + } else { + GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); + } const GLfloat shakeOffset = _gameScreenShakeOffset * (GLfloat)_displayHeight / _gameScreen->getHeight(); // First step: Draw the (virtual) game screen. - _gameScreen->draw(_displayX, _displayY + shakeOffset, _displayWidth, _displayHeight); + g_context.getActivePipeline()->drawTexture(_gameScreen->getGLTexture(), _displayX, _displayY + shakeOffset, _displayWidth, _displayHeight); // Second step: Draw the overlay if visible. if (_overlayVisible) { - _overlay->draw(0, 0, _outputScreenWidth, _outputScreenHeight); + g_context.getActivePipeline()->drawTexture(_overlay->getGLTexture(), 0, 0, _outputScreenWidth, _outputScreenHeight); } // Third step: Draw the cursor if visible. @@ -370,8 +411,10 @@ void OpenGLGraphicsManager::updateScreen() { // visible. const GLfloat cursorOffset = _overlayVisible ? 0 : shakeOffset; - _cursor->draw(_cursorX - _cursorHotspotXScaled, _cursorY - _cursorHotspotYScaled + cursorOffset, - _cursorWidthScaled, _cursorHeightScaled); + g_context.getActivePipeline()->drawTexture(_cursor->getGLTexture(), + _cursorDisplayX - _cursorHotspotXScaled, + _cursorDisplayY - _cursorHotspotYScaled + cursorOffset, + _cursorWidthScaled, _cursorHeightScaled); } #ifdef USE_OSD @@ -392,15 +435,17 @@ void OpenGLGraphicsManager::updateScreen() { } // Set the OSD transparency. - GLCALL(glColor4f(1.0f, 1.0f, 1.0f, _osdAlpha / 100.0f)); + g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, _osdAlpha / 100.0f); // Draw the OSD texture. - _osd->draw(0, 0, _outputScreenWidth, _outputScreenHeight); + g_context.getActivePipeline()->drawTexture(_osd->getGLTexture(), 0, 0, _outputScreenWidth, _outputScreenHeight); // Reset color. - GLCALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f)); + g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, 1.0f); } #endif + + refreshScreen(); } Graphics::Surface *OpenGLGraphicsManager::lockScreen() { @@ -435,10 +480,25 @@ int16 OpenGLGraphicsManager::getOverlayHeight() { void OpenGLGraphicsManager::showOverlay() { _overlayVisible = true; + _forceRedraw = true; + + // Allow drawing inside full screen area. + _backBuffer.enableScissorTest(false); + + // Update cursor position. + setMousePosition(_cursorX, _cursorY); } void OpenGLGraphicsManager::hideOverlay() { _overlayVisible = false; + _forceRedraw = true; + + // Limit drawing to screen area. + _backBuffer.enableScissorTest(true); + _scissorOverride = 3; + + // Update cursor position. + setMousePosition(_cursorX, _cursorY); } Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const { @@ -467,6 +527,12 @@ void OpenGLGraphicsManager::grabOverlay(void *buf, int pitch) { } bool OpenGLGraphicsManager::showMouse(bool visible) { + // In case the mouse cursor visibility changed we need to redraw the whole + // screen even when nothing else changed. + if (_cursorVisible != visible) { + _forceRedraw = true; + } + bool last = _cursorVisible; _cursorVisible = visible; return last; @@ -501,11 +567,8 @@ void OpenGLGraphicsManager::warpMouse(int x, int y) { return; } - x = (x * _displayWidth) / _gameScreen->getWidth(); - y = (y * _displayHeight) / _gameScreen->getHeight(); - - x += _displayX; - y += _displayY; + x = (x * _outputScreenWidth) / _gameScreen->getWidth(); + y = (y * _outputScreenHeight) / _gameScreen->getHeight(); } setMousePosition(x, y); @@ -562,7 +625,7 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int } else { textureFormat = _defaultFormatAlpha; } - _cursor = createTexture(textureFormat, true); + _cursor = createSurface(textureFormat, true); assert(_cursor); _cursor->enableLinearFiltering(_currentState.graphicsMode == GFX_LINEAR); } @@ -688,6 +751,31 @@ void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) { #endif } +void OpenGLGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { +#ifdef USE_OSD + warning("implement copyRectToOSD"); //TODO +#endif +} + +void OpenGLGraphicsManager::clearOSD() { +#ifdef USE_OSD + // HACK: Actually no client code should use graphics functions from + // another thread. But the MT-32 emulator still does, thus we need to + // make sure this doesn't happen while a updateScreen call is done. + Common::StackLock lock(_osdMutex); + + Graphics::Surface *dst = _osd->getSurface(); + _osd->fill(0); + _osd->flagDirty(); + + // Init the OSD display parameters. + _osdAlpha = kOSDInitialAlpha; + _osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay; +#endif +} + +Graphics::PixelFormat OpenGLGraphicsManager::getOSDFormat() { return Graphics::PixelFormat(); } //TODO + void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) { assert(_gameScreen->hasPalette()); @@ -708,18 +796,8 @@ void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { _outputScreenWidth = width; _outputScreenHeight = height; - // Setup coordinates system. - GLCALL(glViewport(0, 0, _outputScreenWidth, _outputScreenHeight)); - - GLCALL(glMatrixMode(GL_PROJECTION)); - GLCALL(glLoadIdentity()); -#ifdef USE_GLES - GLCALL(glOrthof(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); -#else - GLCALL(glOrtho(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); -#endif - GLCALL(glMatrixMode(GL_MODELVIEW)); - GLCALL(glLoadIdentity()); + // Setup backbuffer size. + _backBuffer.setDimensions(width, height); uint overlayWidth = width; uint overlayHeight = height; @@ -730,15 +808,15 @@ void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { // possible and then scale it to the physical display size. This sounds // bad but actually all recent chips should support full HD resolution // anyway. Thus, it should not be a real issue for modern hardware. - if ( overlayWidth > (uint)Texture::getMaximumTextureSize() - || overlayHeight > (uint)Texture::getMaximumTextureSize()) { + if ( overlayWidth > (uint)g_context.maxTextureSize + || overlayHeight > (uint)g_context.maxTextureSize) { const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; if (outputAspect > (frac_t)FRAC_ONE) { - overlayWidth = Texture::getMaximumTextureSize(); + overlayWidth = g_context.maxTextureSize; overlayHeight = intToFrac(overlayWidth) / outputAspect; } else { - overlayHeight = Texture::getMaximumTextureSize(); + overlayHeight = g_context.maxTextureSize; overlayWidth = fracToInt(overlayHeight * outputAspect); } } @@ -754,7 +832,7 @@ void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { delete _overlay; _overlay = nullptr; - _overlay = createTexture(_defaultFormatAlpha); + _overlay = createSurface(_defaultFormatAlpha); assert(_overlay); // We always filter the overlay with GL_LINEAR. This assures it's // readable in case it needs to be scaled and does not affect it @@ -769,7 +847,7 @@ void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { delete _osd; _osd = nullptr; - _osd = createTexture(_defaultFormatAlpha); + _osd = createSurface(_defaultFormatAlpha); assert(_osd); // We always filter the osd with GL_LINEAR. This assures it's // readable in case it needs to be scaled and does not affect it @@ -789,39 +867,56 @@ void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { } void OpenGLGraphicsManager::notifyContextCreate(const Graphics::PixelFormat &defaultFormat, const Graphics::PixelFormat &defaultFormatAlpha) { - // Initialize all extensions. - initializeGLExtensions(); + // Initialize context for use. + initializeGLContext(); + + // Initialize pipeline. + delete _pipeline; + _pipeline = nullptr; + +#if !USE_FORCED_GLES + if (g_context.shadersSupported) { + ShaderMan.notifyCreate(); + _pipeline = new ShaderPipeline(ShaderMan.query(ShaderManager::kDefault)); + } +#endif + +#if !USE_FORCED_GLES2 + if (_pipeline == nullptr) { + _pipeline = new FixedPipeline(); + } +#endif + + g_context.setPipeline(_pipeline); // Disable 3D properties. - GLCALL(glDisable(GL_CULL_FACE)); - GLCALL(glDisable(GL_DEPTH_TEST)); - GLCALL(glDisable(GL_LIGHTING)); - GLCALL(glDisable(GL_FOG)); - GLCALL(glDisable(GL_DITHER)); - GLCALL(glShadeModel(GL_FLAT)); - GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); + GL_CALL(glDisable(GL_CULL_FACE)); + GL_CALL(glDisable(GL_DEPTH_TEST)); + GL_CALL(glDisable(GL_DITHER)); - // Default to black as clear color. - GLCALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); - GLCALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f)); + g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, 1.0f); + + GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + // Setup backbuffer state. + + // Default to black as clear color. + _backBuffer.setClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Setup alpha blend (for overlay and cursor). - GLCALL(glEnable(GL_BLEND)); - GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + _backBuffer.enableBlend(true); + // Setup scissor state accordingly. + _backBuffer.enableScissorTest(!_overlayVisible); - // Enable rendering with vertex and coord arrays. - GLCALL(glEnableClientState(GL_VERTEX_ARRAY)); - GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + g_context.getActivePipeline()->setFramebuffer(&_backBuffer); - GLCALL(glEnable(GL_TEXTURE_2D)); + // Clear the whole screen for the first three frames to assure any + // leftovers are cleared. + _scissorOverride = 3; // We use a "pack" alignment (when reading from textures) to 4 here, // since the only place where we really use it is the BMP screenshot // code and that requires the same alignment too. - GLCALL(glPixelStorei(GL_PACK_ALIGNMENT, 4)); - - // Query information needed by textures. - Texture::queryTextureInformation(); + GL_CALL(glPixelStorei(GL_PACK_ALIGNMENT, 4)); // Refresh the output screen dimensions if some are set up. if (_outputScreenWidth != 0 && _outputScreenHeight != 0) { @@ -835,42 +930,56 @@ void OpenGLGraphicsManager::notifyContextCreate(const Graphics::PixelFormat &def _defaultFormatAlpha = defaultFormatAlpha; if (_gameScreen) { - _gameScreen->recreateInternalTexture(); + _gameScreen->recreate(); } if (_overlay) { - _overlay->recreateInternalTexture(); + _overlay->recreate(); } if (_cursor) { - _cursor->recreateInternalTexture(); + _cursor->recreate(); } #ifdef USE_OSD if (_osd) { - _osd->recreateInternalTexture(); + _osd->recreate(); } #endif } void OpenGLGraphicsManager::notifyContextDestroy() { if (_gameScreen) { - _gameScreen->releaseInternalTexture(); + _gameScreen->destroy(); } if (_overlay) { - _overlay->releaseInternalTexture(); + _overlay->destroy(); } if (_cursor) { - _cursor->releaseInternalTexture(); + _cursor->destroy(); } #ifdef USE_OSD if (_osd) { - _osd->releaseInternalTexture(); + _osd->destroy(); + } +#endif + +#if !USE_FORCED_GLES + if (g_context.shadersSupported) { + ShaderMan.notifyDestroy(); } #endif + + // Destroy rendering pipeline. + g_context.setPipeline(nullptr); + delete _pipeline; + _pipeline = nullptr; + + // Rest our context description since the context is gone soon. + g_context.reset(); } void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) { @@ -886,24 +995,42 @@ void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) { y = (y * _overlay->getHeight()) / _outputScreenHeight; } } else if (_gameScreen) { - x -= _displayX; - y -= _displayY; - const int16 width = _gameScreen->getWidth(); const int16 height = _gameScreen->getHeight(); - x = (x * width) / _displayWidth; - y = (y * height) / _displayHeight; + x = (x * width) / (int)_outputScreenWidth; + y = (y * height) / (int)_outputScreenHeight; + } +} + +void OpenGLGraphicsManager::setMousePosition(int x, int y) { + // Whenever the mouse position changed we force a screen redraw to reflect + // changes properly. + if (_cursorX != x || _cursorY != y) { + _forceRedraw = true; + } - // Make sure we only supply valid coordinates. - x = CLIP<int16>(x, 0, width - 1); - y = CLIP<int16>(y, 0, height - 1); + _cursorX = x; + _cursorY = y; + + if (_overlayVisible) { + _cursorDisplayX = x; + _cursorDisplayY = y; + } else { + _cursorDisplayX = _displayX + (x * _displayWidth) / _outputScreenWidth; + _cursorDisplayY = _displayY + (y * _displayHeight) / _outputScreenHeight; } } -Texture *OpenGLGraphicsManager::createTexture(const Graphics::PixelFormat &format, bool wantAlpha) { +Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha) { GLenum glIntFormat, glFormat, glType; if (format.bytesPerPixel == 1) { +#if !USE_FORCED_GLES + if (TextureCLUT8GPU::isSupportedByContext()) { + return new TextureCLUT8GPU(); + } +#endif + const Graphics::PixelFormat &virtFormat = wantAlpha ? _defaultFormatAlpha : _defaultFormat; const bool supported = getGLPixelFormat(virtFormat, glIntFormat, glFormat, glType); if (!supported) { @@ -911,6 +1038,15 @@ Texture *OpenGLGraphicsManager::createTexture(const Graphics::PixelFormat &forma } else { return new TextureCLUT8(glIntFormat, glFormat, glType, virtFormat); } +#if !USE_FORCED_GL + } else if (isGLESContext() && format == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { + // OpenGL ES does not support a texture format usable for RGB555. + // Since SCUMM uses this pixel format for some games (and there is no + // hope for this to change anytime soon) we use pixel format + // conversion to a supported texture format. However, this is a one + // time exception. + return new TextureRGB555(); +#endif // !USE_FORCED_GL } else { const bool supported = getGLPixelFormat(format, glIntFormat, glFormat, glType); if (!supported) { @@ -946,7 +1082,11 @@ bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelF glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; -#ifndef USE_GLES +#if !USE_FORCED_GLES && !USE_FORCED_GLES2 + // The formats below are not supported by every GLES implementation. + // Thus, we do not mark them as supported when a GLES context is setup. + } else if (isGLESContext()) { + return false; #ifdef SCUMM_LITTLE_ENDIAN } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 glIntFormat = GL_RGBA; @@ -955,17 +1095,10 @@ bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelF return true; #endif } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555 - // GL_BGRA does not exist in every GLES implementation so should not be configured if - // USE_GLES is set. glIntFormat = GL_RGB; glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_1_5_5_5_REV; return true; - } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) { // ARGB8888 - glIntFormat = GL_RGBA; - glFormat = GL_BGRA; - glType = GL_UNSIGNED_INT_8_8_8_8_REV; - return true; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12)) { // ARGB4444 glIntFormat = GL_RGBA; glFormat = GL_BGRA; @@ -985,8 +1118,8 @@ bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelF return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)) { // BGR565 glIntFormat = GL_RGB; - glFormat = GL_BGR; - glType = GL_UNSIGNED_SHORT_5_6_5; + glFormat = GL_RGB; + glType = GL_UNSIGNED_SHORT_5_6_5_REV; return true; } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 1, 6, 11, 0)) { // BGRA5551 glIntFormat = GL_RGBA; @@ -1003,7 +1136,7 @@ bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelF glFormat = GL_BGRA; glType = GL_UNSIGNED_SHORT_4_4_4_4; return true; -#endif +#endif // !USE_FORCED_GLES && !USE_FORCED_GLES2 } else { return false; } @@ -1046,6 +1179,22 @@ void OpenGLGraphicsManager::recalculateDisplayArea() { // We center the screen in the middle for now. _displayX = (_outputScreenWidth - _displayWidth ) / 2; _displayY = (_outputScreenHeight - _displayHeight) / 2; + + // Setup drawing limitation for game graphics. + // This invovles some trickery because OpenGL's viewport coordinate system + // is upside down compared to ours. + _backBuffer.setScissorBox(_displayX, + _outputScreenHeight - _displayHeight - _displayY, + _displayWidth, + _displayHeight); + // Clear the whole screen for the first three frames to remove leftovers. + _scissorOverride = 3; + + // Update the cursor position to adjust for new display area. + setMousePosition(_cursorX, _cursorY); + + // Force a redraw to assure screen is properly redrawn. + _forceRedraw = true; } void OpenGLGraphicsManager::updateCursorPalette() { @@ -1059,20 +1208,7 @@ void OpenGLGraphicsManager::updateCursorPalette() { _cursor->setPalette(0, 256, _gamePalette); } - // We remove all alpha bits from the palette entry of the color key. - // This makes sure its properly handled as color key. - const Graphics::PixelFormat &hardwareFormat = _cursor->getHardwareFormat(); - const uint32 aMask = (0xFF >> hardwareFormat.aLoss) << hardwareFormat.aShift; - - if (hardwareFormat.bytesPerPixel == 2) { - uint16 *palette = (uint16 *)_cursor->getPalette() + _cursorKeyColor; - *palette &= ~aMask; - } else if (hardwareFormat.bytesPerPixel == 4) { - uint32 *palette = (uint32 *)_cursor->getPalette() + _cursorKeyColor; - *palette &= ~aMask; - } else { - warning("OpenGLGraphicsManager::updateCursorPalette: Unsupported pixel depth %d", hardwareFormat.bytesPerPixel); - } + _cursor->setColorKey(_cursorKeyColor); } void OpenGLGraphicsManager::recalculateCursorScaling() { @@ -1122,7 +1258,7 @@ void OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const uint8 *pixels = new uint8[lineSize * height]; // Get pixel data from OpenGL buffer - GLCALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels)); + GL_CALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels)); // BMP stores as BGR. Since we can't assume that GL_BGR is supported we // will swap the components from the RGB we read to BGR on our own. diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h index dde21533b0..55d2c5c826 100644 --- a/backends/graphics/opengl/opengl-graphics.h +++ b/backends/graphics/opengl/opengl-graphics.h @@ -24,6 +24,7 @@ #define BACKENDS_GRAPHICS_OPENGL_OPENGL_GRAPHICS_H #include "backends/graphics/opengl/opengl-sys.h" +#include "backends/graphics/opengl/framebuffer.h" #include "backends/graphics/graphics.h" #include "common/frac.h" @@ -40,7 +41,11 @@ namespace OpenGL { // SurfaceSDL backend enables it and disabling it can cause issues in sdl.cpp. #define USE_OSD 1 -class Texture; +class Surface; +class Pipeline; +#if !USE_FORCED_GLES +class Shader; +#endif enum { GFX_LINEAR = 0, @@ -110,6 +115,9 @@ public: virtual void setCursorPalette(const byte *colors, uint start, uint num); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); // PaletteManager interface virtual void setPalette(const byte *colors, uint start, uint num); @@ -117,6 +125,11 @@ public: protected: /** + * Whether an GLES or GLES2 context is active. + */ + bool isGLESContext() const { return g_context.type == kContextGLES || g_context.type == kContextGLES2; } + + /** * Set up the actual screen size available for the OpenGL code to do any * drawing. * @@ -126,6 +139,16 @@ protected: void setActualScreenSize(uint width, uint height); /** + * Sets the OpenGL (ES) type the graphics manager shall work with. + * + * This needs to be called at least once (and before ever calling + * notifyContextCreate). + * + * @param type Type of the OpenGL (ES) contexts to be created. + */ + void setContextType(ContextType type); + + /** * Notify the manager of a OpenGL context change. This should be the first * thing to call after you created an OpenGL (ES) context! * @@ -155,7 +178,7 @@ protected: * @param x X coordinate in physical coordinates. * @param y Y coordinate in physical coordinates. */ - void setMousePosition(int x, int y) { _cursorX = x; _cursorY = y; } + void setMousePosition(int x, int y); /** * Query the mouse position in physical coordinates. @@ -172,15 +195,15 @@ protected: private: /** - * Create a texture with the specified pixel format. + * Create a surface with the specified pixel format. * - * @param format The pixel format the Texture object should accept as + * @param format The pixel format the Surface object should accept as * input. - * @param wantAlpha For CLUT8 textures this marks whether an alpha + * @param wantAlpha For CLUT8 surfaces this marks whether an alpha * channel should be used. - * @return A pointer to the texture or nullptr on failure. + * @return A pointer to the surface or nullptr on failure. */ - Texture *createTexture(const Graphics::PixelFormat &format, bool wantAlpha = false); + Surface *createSurface(const Graphics::PixelFormat &format, bool wantAlpha = false); // // Transaction support @@ -263,6 +286,11 @@ protected: virtual bool loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) = 0; /** + * Refresh the screen contents. + */ + virtual void refreshScreen() = 0; + + /** * Save a screenshot of the full display as BMP to the given file. This * uses Common::DumpFile for writing the screenshot. * @@ -276,6 +304,36 @@ private: // /** + * Initialize the active context for use. + */ + void initializeGLContext(); + + /** + * Render back buffer. + */ + Backbuffer _backBuffer; + + /** + * OpenGL pipeline used for rendering. + */ + Pipeline *_pipeline; + +protected: + /** + * Query the address of an OpenGL function by name. + * + * This can only be used after a context has been created. + * Please note that this function can return valid addresses even if the + * OpenGL context does not support the function. + * + * @param name The name of the OpenGL function. + * @return An function pointer for the requested OpenGL function or + * nullptr in case of failure. + */ + virtual void *getProcAddress(const char *name) const = 0; + +private: + /** * Try to determine the internal parameters for a given pixel format. * * @return true when the format can be used, false otherwise. @@ -343,7 +401,7 @@ private: /** * The virtual game screen. */ - Texture *_gameScreen; + Surface *_gameScreen; /** * The game palette if in CLUT8 mode. @@ -362,7 +420,7 @@ private: /** * The overlay screen. */ - Texture *_overlay; + Surface *_overlay; /** * Whether the overlay is visible or not. @@ -381,7 +439,7 @@ private: /** * The cursor image. */ - Texture *_cursor; + Surface *_cursor; /** * X coordinate of the cursor in phyiscal coordinates. @@ -394,6 +452,16 @@ private: int _cursorY; /** + * X coordinate used for drawing the cursor. + */ + int _cursorDisplayX; + + /** + * Y coordinate used for drawing the cursor. + */ + int _cursorDisplayY; + + /** * The X offset for the cursor hotspot in unscaled coordinates. */ int _cursorHotspotX; @@ -454,6 +522,20 @@ private: */ byte _cursorPalette[3 * 256]; + // + // Misc + // + + /** + * Whether the screen contents shall be forced to redrawn. + */ + bool _forceRedraw; + + /** + * Number of frames glClear shall ignore scissor testing. + */ + uint _scissorOverride; + #ifdef USE_OSD // // OSD @@ -468,7 +550,7 @@ private: /** * The OSD's contents. */ - Texture *_osd; + Surface *_osd; /** * Current opacity level of the OSD. diff --git a/backends/graphics/opengl/opengl-sys.h b/backends/graphics/opengl/opengl-sys.h index a3524b28d2..7b531cc140 100644 --- a/backends/graphics/opengl/opengl-sys.h +++ b/backends/graphics/opengl/opengl-sys.h @@ -20,38 +20,156 @@ * */ -#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_H -#define BACKENDS_GRAPHICS_OPENGL_OPENGL_H - -// The purpose of this header is to include the OpenGL headers in an uniform -// fashion. A notable example for a non standard port is the Tizen port. +#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_SYS_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_SYS_H #include "common/scummsys.h" -#ifdef WIN32 -#if defined(ARRAYSIZE) && !defined(_WINDOWS_) -#undef ARRAYSIZE +#include "backends/graphics/opengl/debug.h" +#ifdef SDL_BACKEND +#include "backends/platform/sdl/sdl-sys.h" #endif -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#undef ARRAYSIZE + +// On OS X we only support GL contexts. The reason is that Apple's GL interface +// uses "void *" for GLhandleARB which is not type compatible with GLint. This +// kills our aliasing trick for extension functions and thus would force us to +// supply two different Shader class implementations or introduce other +// wrappers. OS X only supports GL contexts right now anyway (at least +// according to SDL2 sources), thus it is not much of an issue. +#if defined(MACOSX) && (!defined(USE_GLES_MODE) || USE_GLES_MODE != 0) +//#warning "Only forced OpenGL mode is supported on Mac OS X. Overriding settings." +#undef USE_GLES_MODE +#define USE_GLES_MODE 0 #endif -// HACK: In case common/util.h has been included already we need to make sure -// to define ARRAYSIZE again in case of Windows. -#if !defined(ARRAYSIZE) && defined(COMMON_UTIL_H) -#define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(x[0]))) +// We allow to force GL or GLES modes on compile time. +// For this the USE_GLES_MODE define is used. The following values represent +// the given selection choices: +// 0 - Force OpenGL context +// 1 - Force OpenGL ES context +// 2 - Force OpenGL ES 2.0 context +#ifdef USE_GLES_MODE + #define USE_FORCED_GL (USE_GLES_MODE == 0) + #define USE_FORCED_GLES (USE_GLES_MODE == 1) + #define USE_FORCED_GLES2 (USE_GLES_MODE == 2) +#else + #define USE_FORCED_GL 0 + #define USE_FORCED_GLES 0 + #define USE_FORCED_GLES2 0 #endif +// On Tizen we include the toolchain's OpenGL file. This is something we +// actually want to avoid. However, since Tizen uses eglGetProcAddress which +// is not required to return valid function pointers to non OpenGL extension +// functions, we need the system's definitions to resolve all OpenGL +// functions. +// TODO: See if there is an alternative which allows us to avoid including +// Tizen's OpenGL header here. #if defined(TIZEN) -#include <FGraphicsOpengl.h> -using namespace Tizen::Graphics::Opengl; -#elif defined(USE_GLES) -#include <GLES/gl.h> -#elif defined(SDL_BACKEND) -#include <SDL_opengl.h> + #include <FGraphicsOpengl.h> + using namespace Tizen::Graphics::Opengl; + #define USE_BUILTIN_OPENGL #else -#include <GL/gl.h> + #include "backends/graphics/opengl/opengl-defs.h" #endif +#ifdef SDL_BACKEND + // Win32 needs OpenGL functions declared with APIENTRY. + // However, SDL does not define APIENTRY in it's SDL.h file on non-Windows + // targets, thus if it is not available, we just dummy define it. + #ifndef APIENTRY + #define APIENTRY + #endif + #define GL_CALL_CONV APIENTRY +#else + #define GL_CALL_CONV +#endif + +namespace OpenGL { + +enum ContextType { + kContextGL, + kContextGLES, + kContextGLES2 +}; + +class Pipeline; +class Framebuffer; + +/** + * Description structure of the OpenGL (ES) context. + */ +struct Context { + /** The type of the active context. */ + ContextType type; + + /** + * Reset context. + * + * This marks all extensions as unavailable and clears all function + * pointers. + */ + void reset(); + + /** The maximum texture size supported by the context. */ + GLint maxTextureSize; + + /** Whether GL_ARB_texture_non_power_of_two is available or not. */ + bool NPOTSupported; + + /** Whether shader support is available or not. */ + bool shadersSupported; + + /** Whether multi texture support is available or not. */ + bool multitextureSupported; + + /** Whether FBO support is available or not. */ + bool framebufferObjectSupported; + +#define GL_FUNC_DEF(ret, name, param) ret (GL_CALL_CONV *name)param +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_DEF + + // + // Wrapper functionality to handle fixed-function pipelines and + // programmable pipelines in the same fashion. + // + +private: + /** Currently active rendering pipeline. */ + Pipeline *activePipeline; + +public: + /** + * Set new pipeline. + * + * Client is responsible for any memory management related to pipelines. + * + * @param pipeline Pipeline to activate. + * @return Formerly active pipeline. + */ + Pipeline *setPipeline(Pipeline *pipeline); + + /** + * Query the currently active rendering pipeline. + */ + Pipeline *getActivePipeline() const { return activePipeline; } +}; + +/** + * The (active) OpenGL context. + */ +extern Context g_context; + +} // End of namespace OpenGL + +#define GL_CALL(x) GL_WRAP_DEBUG(g_context.x, x) +#define GL_CALL_SAFE(func, params) \ + do { \ + if (g_context.func) { \ + GL_CALL(func params); \ + } \ + } while (0) +#define GL_ASSIGN(var, x) GL_WRAP_DEBUG(var = g_context.x, x) + #endif diff --git a/backends/graphics/opengl/pipelines/clut8.cpp b/backends/graphics/opengl/pipelines/clut8.cpp new file mode 100644 index 0000000000..fca40074f0 --- /dev/null +++ b/backends/graphics/opengl/pipelines/clut8.cpp @@ -0,0 +1,46 @@ +/* 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 "backends/graphics/opengl/pipelines/clut8.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +CLUT8LookUpPipeline::CLUT8LookUpPipeline() + : ShaderPipeline(ShaderMan.query(ShaderManager::kCLUT8LookUp)), _paletteTexture(nullptr) { +} + +void CLUT8LookUpPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + // Set the palette texture. + GL_CALL(glActiveTexture(GL_TEXTURE1)); + if (_paletteTexture) { + _paletteTexture->bind(); + } + + GL_CALL(glActiveTexture(GL_TEXTURE0)); + ShaderPipeline::drawTexture(texture, coordinates); +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/extensions.cpp b/backends/graphics/opengl/pipelines/clut8.h index 4482ef82b5..16724e4652 100644 --- a/backends/graphics/opengl/extensions.cpp +++ b/backends/graphics/opengl/pipelines/clut8.h @@ -20,29 +20,27 @@ * */ -#include "backends/graphics/opengl/extensions.h" -#include "backends/graphics/opengl/opengl-sys.h" +#ifndef BACKENDS_GRAPHICS_OPENGL_PIPELINES_CLUT8_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_CLUT8_H -#include "common/tokenizer.h" +#include "backends/graphics/opengl/pipelines/shader.h" namespace OpenGL { -bool g_extNPOTSupported = false; +#if !USE_FORCED_GLES +class CLUT8LookUpPipeline : public ShaderPipeline { +public: + CLUT8LookUpPipeline(); -void initializeGLExtensions() { - const char *extString = (const char *)glGetString(GL_EXTENSIONS); + void setPaletteTexture(const GLTexture *paletteTexture) { _paletteTexture = paletteTexture; } - // Initialize default state. - g_extNPOTSupported = false; + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); - Common::StringTokenizer tokenizer(extString, " "); - while (!tokenizer.empty()) { - Common::String token = tokenizer.nextToken(); - - if (token == "GL_ARB_texture_non_power_of_two") { - g_extNPOTSupported = true; - } - } -} +private: + const GLTexture *_paletteTexture; +}; +#endif // !USE_FORCED_GLES } // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/pipelines/fixed.cpp b/backends/graphics/opengl/pipelines/fixed.cpp new file mode 100644 index 0000000000..8e3bd7eaee --- /dev/null +++ b/backends/graphics/opengl/pipelines/fixed.cpp @@ -0,0 +1,70 @@ +/* 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 "backends/graphics/opengl/pipelines/fixed.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES2 +void FixedPipeline::activateInternal() { + GL_CALL(glDisable(GL_LIGHTING)); + GL_CALL(glDisable(GL_FOG)); + GL_CALL(glShadeModel(GL_FLAT)); + GL_CALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); + + GL_CALL(glEnableClientState(GL_VERTEX_ARRAY)); + GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + +#if !USE_FORCED_GLES + if (g_context.multitextureSupported) { + GL_CALL(glActiveTexture(GL_TEXTURE0)); + } +#endif + GL_CALL(glEnable(GL_TEXTURE_2D)); +} + +void FixedPipeline::setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + GL_CALL(glColor4f(r, g, b, a)); +} + +void FixedPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + texture.bind(); + + GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texture.getTexCoords())); + GL_CALL(glVertexPointer(2, GL_FLOAT, 0, coordinates)); + GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + +void FixedPipeline::setProjectionMatrix(const GLfloat *projectionMatrix) { + if (!isActive()) { + return; + } + + GL_CALL(glMatrixMode(GL_PROJECTION)); + GL_CALL(glLoadMatrixf(projectionMatrix)); + + GL_CALL(glMatrixMode(GL_MODELVIEW)); + GL_CALL(glLoadIdentity()); +} +#endif // !USE_FORCED_GLES2 + +} // End of namespace OpenGL diff --git a/backends/mixer/sdl13/sdl13-mixer.h b/backends/graphics/opengl/pipelines/fixed.h index ff2bb43084..6bfe140c19 100644 --- a/backends/mixer/sdl13/sdl13-mixer.h +++ b/backends/graphics/opengl/pipelines/fixed.h @@ -20,48 +20,27 @@ * */ -#ifndef BACKENDS_MIXER_SDL13_H -#define BACKENDS_MIXER_SDL13_H +#ifndef BACKENDS_GRAPHICS_OPENGL_PIPELINES_FIXED_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_FIXED_H -#include "backends/mixer/sdl/sdl-mixer.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" -/** - * SDL mixer manager. It wraps the actual implementation - * of the Audio:Mixer used by the engine, and setups - * the SDL audio subsystem and the callback for the - * audio mixer implementation. - */ -class Sdl13MixerManager : public SdlMixerManager { -public: - Sdl13MixerManager(); - virtual ~Sdl13MixerManager(); +namespace OpenGL { - /** - * Initialize and setups the mixer - */ - virtual void init(); +#if !USE_FORCED_GLES2 +class FixedPipeline : public Pipeline { +public: + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); - /** - * Pauses the audio system - */ - virtual void suspendAudio(); + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); - /** - * Resumes the audio system - */ - virtual int resumeAudio(); + virtual void setProjectionMatrix(const GLfloat *projectionMatrix); protected: - - /** - * The opened SDL audio device - */ - SDL_AudioDeviceID _device; - - /** - * Starts SDL audio - */ - virtual void startAudio(); + virtual void activateInternal(); }; +#endif // !USE_FORCED_GLES2 + +} // End of namespace OpenGL #endif diff --git a/backends/graphics/opengl/pipelines/pipeline.cpp b/backends/graphics/opengl/pipelines/pipeline.cpp new file mode 100644 index 0000000000..6a59cd28e7 --- /dev/null +++ b/backends/graphics/opengl/pipelines/pipeline.cpp @@ -0,0 +1,66 @@ +/* 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 "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +Pipeline::Pipeline() + : _activeFramebuffer(nullptr), _isActive(false) { +} + +void Pipeline::activate() { + _isActive = true; + + if (_activeFramebuffer) { + _activeFramebuffer->activate(); + } + + activateInternal(); +} + +void Pipeline::deactivate() { + deactivateInternal(); + + if (_activeFramebuffer) { + _activeFramebuffer->deactivate(); + } + + _isActive = false; +} + +Framebuffer *Pipeline::setFramebuffer(Framebuffer *framebuffer) { + Framebuffer *oldFramebuffer = _activeFramebuffer; + if (_isActive && oldFramebuffer) { + oldFramebuffer->deactivate(); + } + + _activeFramebuffer = framebuffer; + if (_isActive && _activeFramebuffer) { + _activeFramebuffer->activate(); + } + + return oldFramebuffer; +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/pipeline.h b/backends/graphics/opengl/pipelines/pipeline.h new file mode 100644 index 0000000000..9f32d33b95 --- /dev/null +++ b/backends/graphics/opengl/pipelines/pipeline.h @@ -0,0 +1,126 @@ +/* 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 BACKENDS_GRAPHICS_OPENGL_PIPELINES_PIPELINE_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_PIPELINE_H + +#include "backends/graphics/opengl/opengl-sys.h" +#include "backends/graphics/opengl/texture.h" + +namespace OpenGL { + +class Framebuffer; + +/** + * Interface for OpenGL pipeline functionality. + * + * This encapsulates differences in various rendering pipelines used for + * OpenGL, OpenGL ES 1, and OpenGL ES 2. + */ +class Pipeline { +public: + Pipeline(); + virtual ~Pipeline() {} + + /** + * Activate the pipeline. + * + * This sets the OpenGL state to make use of drawing with the given + * OpenGL pipeline. + */ + void activate(); + + /** + * Deactivate the pipeline. + */ + void deactivate(); + + /** + * Set framebuffer to render to. + * + * Client is responsible for any memory management related to framebuffer. + * + * @param framebuffer Framebuffer to activate. + * @return Formerly active framebuffer. + */ + Framebuffer *setFramebuffer(Framebuffer *framebuffer); + + /** + * Set modulation color. + * + * @param r Red component in [0,1]. + * @param g Green component in [0,1]. + * @param b Blue component in [0,1]. + * @param a Alpha component in [0,1]. + */ + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) = 0; + + /** + * Draw a texture rectangle to the currently active framebuffer. + * + * @param texture Texture to use for drawing. + * @param coordinates x1, y1, x2, y2 coordinates where to draw the texture. + */ + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates) = 0; + + void drawTexture(const GLTexture &texture, GLfloat x, GLfloat y, GLfloat w, GLfloat h) { + const GLfloat coordinates[4*2] = { + x, y, + x + w, y, + x, y + h, + x + w, y + h + }; + drawTexture(texture, coordinates); + } + + /** + * Set the projection matrix. + * + * This is intended to be only ever be used by Framebuffer subclasses. + */ + virtual void setProjectionMatrix(const GLfloat *projectionMatrix) = 0; + +protected: + /** + * Activate the pipeline. + * + * This sets the OpenGL state to make use of drawing with the given + * OpenGL pipeline. + */ + virtual void activateInternal() = 0; + + /** + * Deactivate the pipeline. + */ + virtual void deactivateInternal() {} + + bool isActive() const { return _isActive; } + + Framebuffer *_activeFramebuffer; + +private: + bool _isActive; +}; + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/pipelines/shader.cpp b/backends/graphics/opengl/pipelines/shader.cpp new file mode 100644 index 0000000000..a2dabb7c22 --- /dev/null +++ b/backends/graphics/opengl/pipelines/shader.cpp @@ -0,0 +1,94 @@ +/* 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 "backends/graphics/opengl/pipelines/shader.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +ShaderPipeline::ShaderPipeline(Shader *shader) + : _activeShader(shader), _colorAttributes() { + _vertexAttribLocation = shader->getAttributeLocation("position"); + _texCoordAttribLocation = shader->getAttributeLocation("texCoordIn"); + _colorAttribLocation = shader->getAttributeLocation("blendColorIn"); + + assert(_vertexAttribLocation != -1); + assert(_texCoordAttribLocation != -1); + assert(_colorAttribLocation != -1); + + // One of the attributes needs to be passed through location 0, otherwise + // we get no output for GL contexts due to GL compatibility reasons. Let's + // check whether this ever happens. If this ever gets hit, we need to + // enable location 0 and pass some dummy values through it to fix output. + assert( _vertexAttribLocation == 0 + || _texCoordAttribLocation == 0 + || _colorAttribLocation == 0); +} + +void ShaderPipeline::activateInternal() { + GL_CALL(glEnableVertexAttribArray(_vertexAttribLocation)); + GL_CALL(glEnableVertexAttribArray(_texCoordAttribLocation)); + GL_CALL(glEnableVertexAttribArray(_colorAttribLocation)); + + if (g_context.multitextureSupported) { + GL_CALL(glActiveTexture(GL_TEXTURE0)); + } + + _activeShader->activate(); + + GL_CALL(glVertexAttribPointer(_colorAttribLocation, 4, GL_FLOAT, GL_FALSE, 0, _colorAttributes)); +} + +void ShaderPipeline::deactivateInternal() { + GL_CALL(glDisableVertexAttribArray(_vertexAttribLocation)); + GL_CALL(glDisableVertexAttribArray(_texCoordAttribLocation)); + GL_CALL(glDisableVertexAttribArray(_colorAttribLocation)); + + _activeShader->deactivate(); +} + +void ShaderPipeline::setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + GLfloat *dst = _colorAttributes; + for (uint i = 0; i < 4; ++i) { + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = a; + } +} + +void ShaderPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + texture.bind(); + + GL_CALL(glVertexAttribPointer(_texCoordAttribLocation, 2, GL_FLOAT, GL_FALSE, 0, texture.getTexCoords())); + GL_CALL(glVertexAttribPointer(_vertexAttribLocation, 2, GL_FLOAT, GL_FALSE, 0, coordinates)); + GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + +void ShaderPipeline::setProjectionMatrix(const GLfloat *projectionMatrix) { + _activeShader->setUniform("projection", new ShaderUniformMatrix44(projectionMatrix)); +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/shader.h b/backends/graphics/opengl/pipelines/shader.h new file mode 100644 index 0000000000..6159607099 --- /dev/null +++ b/backends/graphics/opengl/pipelines/shader.h @@ -0,0 +1,59 @@ +/* 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 BACKENDS_GRAPHICS_OPENGL_PIPELINES_SHADER_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_SHADER_H + +#include "backends/graphics/opengl/pipelines/pipeline.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +class Shader; + +class ShaderPipeline : public Pipeline { +public: + ShaderPipeline(Shader *shader); + + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); + + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); + + virtual void setProjectionMatrix(const GLfloat *projectionMatrix); + +protected: + virtual void activateInternal(); + virtual void deactivateInternal(); + + GLint _vertexAttribLocation; + GLint _texCoordAttribLocation; + GLint _colorAttribLocation; + + GLfloat _colorAttributes[4*4]; + + Shader *const _activeShader; +}; +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/shader.cpp b/backends/graphics/opengl/shader.cpp new file mode 100644 index 0000000000..0b4c677d70 --- /dev/null +++ b/backends/graphics/opengl/shader.cpp @@ -0,0 +1,335 @@ +/* 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 "backends/graphics/opengl/shader.h" + +#if !USE_FORCED_GLES + +#include "common/textconsole.h" +#include "common/util.h" + +namespace Common { +DECLARE_SINGLETON(OpenGL::ShaderManager); +} + +namespace OpenGL { + +namespace { + +#pragma mark - Builtin Shader Sources - + +const char *const g_defaultVertexShader = + "attribute vec4 position;\n" + "attribute vec2 texCoordIn;\n" + "attribute vec4 blendColorIn;\n" + "\n" + "uniform mat4 projection;\n" + "\n" + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "void main(void) {\n" + "\ttexCoord = texCoordIn;\n" + "\tblendColor = blendColorIn;\n" + "\tgl_Position = projection * position;\n" + "}\n"; + +const char *const g_defaultFragmentShader = + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "uniform sampler2D texture;\n" + "\n" + "void main(void) {\n" + "\tgl_FragColor = blendColor * texture2D(texture, texCoord);\n" + "}\n"; + +const char *const g_lookUpFragmentShader = + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "uniform sampler2D texture;\n" + "uniform sampler2D palette;\n" + "\n" + "const float adjustFactor = 255.0 / 256.0 + 1.0 / (2.0 * 256.0);" + "\n" + "void main(void) {\n" + "\tvec4 index = texture2D(texture, texCoord);\n" + "\tgl_FragColor = blendColor * texture2D(palette, vec2(index.a * adjustFactor, 0.0));\n" + "}\n"; + + +// Taken from: https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_03#OpenGL_ES_2_portability +const char *const g_precisionDefines = + "#ifdef GL_ES\n" + "\t#if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1\n" + "\t\tprecision highp float;\n" + "\t#else\n" + "\t\tprecision mediump float;\n" + "\t#endif\n" + "#else\n" + "\t#define highp\n" + "\t#define mediump\n" + "\t#define lowp\n" + "#endif\n"; + +} // End of anonymous namespace + +#pragma mark - Uniform Values - + +void ShaderUniformInteger::set(GLint location) const { + GL_CALL(glUniform1i(location, _value)); +} + +void ShaderUniformFloat::set(GLint location) const { + GL_CALL(glUniform1f(location, _value)); +} + +void ShaderUniformMatrix44::set(GLint location) const { + GL_CALL(glUniformMatrix4fv(location, 1, GL_FALSE, _matrix)); +} + +#pragma mark - Shader Implementation - + +Shader::Shader(const Common::String &vertex, const Common::String &fragment) + : _vertex(vertex), _fragment(fragment), _isActive(false), _program(0), _uniforms() { + recreate(); +} + +Shader::~Shader() { + // According to extension specification glDeleteObjectARB silently ignores + // 0. However, with nVidia drivers this can cause GL_INVALID_VALUE, thus + // we do not call it with 0 as parameter to avoid warnings. + if (_program) { + GL_CALL_SAFE(glDeleteProgram, (_program)); + } +} + +void Shader::destroy() { + // According to extension specification glDeleteObjectARB silently ignores + // 0. However, with nVidia drivers this can cause GL_INVALID_VALUE, thus + // we do not call it with 0 as parameter to avoid warnings. + if (_program) { + GL_CALL(glDeleteProgram(_program)); + _program = 0; + } +} + +bool Shader::recreate() { + // Make sure any old programs are destroyed properly. + destroy(); + + GLshader vertexShader = compileShader(_vertex.c_str(), GL_VERTEX_SHADER); + if (!vertexShader) { + return false; + } + + GLshader fragmentShader = compileShader(_fragment.c_str(), GL_FRAGMENT_SHADER); + if (!fragmentShader) { + GL_CALL(glDeleteShader(vertexShader)); + return false; + } + + GL_ASSIGN(_program, glCreateProgram()); + if (!_program) { + GL_CALL(glDeleteShader(vertexShader)); + GL_CALL(glDeleteShader(fragmentShader)); + return false; + } + + GL_CALL(glAttachShader(_program, vertexShader)); + GL_CALL(glAttachShader(_program, fragmentShader)); + + GL_CALL(glLinkProgram(_program)); + + GL_CALL(glDetachShader(_program, fragmentShader)); + GL_CALL(glDeleteShader(fragmentShader)); + + GL_CALL(glDetachShader(_program, vertexShader)); + GL_CALL(glDeleteShader(vertexShader)); + + GLint result; + GL_CALL(glGetProgramiv(_program, GL_LINK_STATUS, &result)); + if (result == GL_FALSE) { + GLint logSize; + GL_CALL(glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logSize)); + + GLchar *log = new GLchar[logSize]; + GL_CALL(glGetProgramInfoLog(_program, logSize, nullptr, log)); + warning("Could not link shader: \"%s\"", log); + delete[] log; + + destroy(); + return false; + } + + // Set program object in case shader is active during recreation. + if (_isActive) { + GL_CALL(glUseProgram(_program)); + } + + for (UniformMap::iterator i = _uniforms.begin(), end = _uniforms.end(); i != end; ++i) { + i->_value.location = getUniformLocation(i->_key.c_str()); + i->_value.altered = true; + if (_isActive) { + i->_value.set(); + } + } + + return true; +} + +void Shader::activate() { + // Activate program. + GL_CALL(glUseProgram(_program)); + + // Reset changed uniform values. + for (UniformMap::iterator i = _uniforms.begin(), end = _uniforms.end(); i != end; ++i) { + i->_value.set(); + } + + _isActive = true; +} + +void Shader::deactivate() { + _isActive = false; +} + +GLint Shader::getAttributeLocation(const char *name) const { + GLint result = -1; + GL_ASSIGN(result, glGetAttribLocation(_program, name)); + return result; +} + +GLint Shader::getUniformLocation(const char *name) const { + GLint result = -1; + GL_ASSIGN(result, glGetUniformLocation(_program, name)); + return result; +} + +bool Shader::setUniform(const Common::String &name, ShaderUniformValue *value) { + UniformMap::iterator uniformIter = _uniforms.find(name); + Uniform *uniform; + + if (uniformIter == _uniforms.end()) { + const GLint location = getUniformLocation(name.c_str()); + if (location == -1) { + delete value; + return false; + } + + uniform = &_uniforms[name]; + uniform->location = location; + } else { + uniform = &uniformIter->_value; + } + + uniform->value = Common::SharedPtr<ShaderUniformValue>(value); + uniform->altered = true; + if (_isActive) { + uniform->set(); + } + + return true; +} + +GLshader Shader::compileShader(const char *source, GLenum shaderType) { + GLshader handle; + GL_ASSIGN(handle, glCreateShader(shaderType)); + if (!handle) { + return 0; + } + + const char *const sources[2] = { + g_precisionDefines, + source + }; + + GL_CALL(glShaderSource(handle, ARRAYSIZE(sources), sources, nullptr)); + GL_CALL(glCompileShader(handle)); + + GLint result; + GL_CALL(glGetShaderiv(handle, GL_COMPILE_STATUS, &result)); + if (result == GL_FALSE) { + GLint logSize; + GL_CALL(glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &logSize)); + + GLchar *log = new GLchar[logSize]; + GL_CALL(glGetShaderInfoLog(handle, logSize, nullptr, log)); + warning("Could not compile shader \"%s\": \"%s\"", source, log); + delete[] log; + + GL_CALL(glDeleteShader(handle)); + return 0; + } + + return handle; +} + +ShaderManager::ShaderManager() : _initializeShaders(true) { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i] = nullptr; + } +} + +ShaderManager::~ShaderManager() { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + delete _builtIn[i]; + } +} + +void ShaderManager::notifyDestroy() { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i]->destroy(); + } +} + +void ShaderManager::notifyCreate() { + if (_initializeShaders) { + _initializeShaders = false; + + _builtIn[kDefault] = new Shader(g_defaultVertexShader, g_defaultFragmentShader); + _builtIn[kCLUT8LookUp] = new Shader(g_defaultVertexShader, g_lookUpFragmentShader); + _builtIn[kCLUT8LookUp]->setUniform1I("palette", 1); + + for (uint i = 0; i < kMaxUsages; ++i) { + _builtIn[i]->setUniform1I("texture", 0); + } + } else { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i]->recreate(); + } + } +} + +Shader *ShaderManager::query(ShaderUsage shader) const { + if (shader == kMaxUsages) { + warning("OpenGL: ShaderManager::query used with kMaxUsages"); + return nullptr; + } + + return _builtIn[shader]; +} + +} // End of namespace OpenGL + +#endif // !USE_FORCED_GLES diff --git a/backends/graphics/opengl/shader.h b/backends/graphics/opengl/shader.h new file mode 100644 index 0000000000..ec1e516d14 --- /dev/null +++ b/backends/graphics/opengl/shader.h @@ -0,0 +1,288 @@ +/* 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 BACKENDS_GRAPHICS_OPENGL_SHADER_H +#define BACKENDS_GRAPHICS_OPENGL_SHADER_H + +#include "backends/graphics/opengl/opengl-sys.h" + +#if !USE_FORCED_GLES + +#include "common/singleton.h" +#include "common/hash-str.h" +#include "common/ptr.h" + +namespace OpenGL { + +/** + * A generic uniform value interface for a shader program. + */ +class ShaderUniformValue { +public: + virtual ~ShaderUniformValue() {} + + /** + * Setup the the value to the given location. + * + * @param location Location of the uniform. + */ + virtual void set(GLint location) const = 0; +}; + +/** + * Integer value for a shader uniform. + */ +class ShaderUniformInteger : public ShaderUniformValue { +public: + ShaderUniformInteger(GLint value) : _value(value) {} + + virtual void set(GLint location) const override; + +private: + const GLint _value; +}; + +/** + * Float value for a shader uniform. + */ +class ShaderUniformFloat : public ShaderUniformValue { +public: + ShaderUniformFloat(GLfloat value) : _value(value) {} + + virtual void set(GLint location) const override; + +private: + const GLfloat _value; +}; + +/** + * 4x4 Matrix value for a shader uniform. + */ +class ShaderUniformMatrix44 : public ShaderUniformValue { +public: + ShaderUniformMatrix44(const GLfloat *mat44) { + memcpy(_matrix, mat44, sizeof(_matrix)); + } + + virtual void set(GLint location) const override; + +private: + GLfloat _matrix[4*4]; +}; + +class Shader { +public: + Shader(const Common::String &vertex, const Common::String &fragment); + ~Shader(); + + /** + * Destroy the shader program. + * + * This keeps the vertex and fragment shader sources around and thus + * allows for recreating the shader on context recreation. It also keeps + * the uniform state around. + */ + void destroy(); + + /** + * Recreate shader program. + * + * @return true on success, false on failure. + */ + bool recreate(); + + /** + * Make shader active. + */ + void activate(); + + /** + * Make shader inactive. + */ + void deactivate(); + + /** + * Return location for attribute with given name. + * + * @param name Name of the attribute to look up in the shader. + * @return The loctaion of -1 if attribute was not found. + */ + GLint getAttributeLocation(const char *name) const; + GLint getAttributeLocation(const Common::String &name) const { + return getAttributeLocation(name.c_str()); + } + + /** + * Return location for uniform with given name. + * + * @param name Name of the uniform to look up in the shader. + * @return The location or -1 if uniform was not found. + */ + GLint getUniformLocation(const char *name) const; + GLint getUniformLocation(const Common::String &name) const { + return getUniformLocation(name.c_str()); + } + + /** + * Bind value to uniform. + * + * @param name The name of the uniform to be set. + * @param value The value to be set. + * @return 'false' on error (i.e. uniform unknown or otherwise), + * 'true' otherwise. + */ + bool setUniform(const Common::String &name, ShaderUniformValue *value); + + /** + * Bind integer value to uniform. + * + * @param name The name of the uniform to be set. + * @param value The value to be set. + * @return 'false' on error (i.e. uniform unknown or otherwise), + * 'true' otherwise. + */ + bool setUniform1I(const Common::String &name, GLint value) { + return setUniform(name, new ShaderUniformInteger(value)); + } +protected: + /** + * Vertex shader sources. + */ + const Common::String _vertex; + + /** + * Fragment shader sources. + */ + const Common::String _fragment; + + /** + * Whether the shader is active or not. + */ + bool _isActive; + + /** + * Shader program handle. + */ + GLprogram _program; + + /** + * A uniform descriptor. + * + * This stores the state of a shader uniform. The state is made up of the + * uniform location, whether the state was altered since last set, and the + * value of the uniform. + */ + struct Uniform { + Uniform() : location(-1), altered(false), value() {} + Uniform(GLint loc, ShaderUniformValue *val) + : location(loc), altered(true), value(val) {} + + /** + * Write uniform value into currently active shader. + */ + void set() { + if (altered && value) { + value->set(location); + altered = false; + } + } + + /** + * The location of the uniform or -1 in case it does not exist. + */ + GLint location; + + /** + * Whether the uniform state was aletered since last 'set'. + */ + bool altered; + + /** + * The value of the uniform. + */ + Common::SharedPtr<ShaderUniformValue> value; + }; + + typedef Common::HashMap<Common::String, Uniform> UniformMap; + + /** + * Map from uniform name to associated uniform description. + */ + UniformMap _uniforms; + + /** + * Compile a vertex or fragment shader. + * + * @param source Sources to the shader. + * @param shaderType Type of shader to compile (GL_FRAGMENT_SHADER_ARB or + * GL_VERTEX_SHADER_ARB) + * @return The shader object or 0 on failure. + */ + static GLshader compileShader(const char *source, GLenum shaderType); +}; + +class ShaderManager : public Common::Singleton<ShaderManager> { +public: + enum ShaderUsage { + /** Default shader implementing the GL fixed-function pipeline. */ + kDefault = 0, + + /** CLUT8 look up shader. */ + kCLUT8LookUp, + + /** Number of built-in shaders. Should not be used for query. */ + kMaxUsages + }; + + /** + * Notify shader manager about context destruction. + */ + void notifyDestroy(); + + /** + * Notify shader manager about context creation. + */ + void notifyCreate(); + + /** + * Query a built-in shader. + */ + Shader *query(ShaderUsage shader) const; + +private: + friend class Common::Singleton<SingletonBaseType>; + ShaderManager(); + ~ShaderManager(); + + bool _initializeShaders; + + Shader *_builtIn[kMaxUsages]; +}; + +} // End of namespace OpenGL + +/** Shortcut for accessing the font manager. */ +#define ShaderMan (OpenGL::ShaderManager::instance()) + +#endif // !USE_FORCED_GLES + +#endif diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp index 7b0b22d630..33598b5488 100644 --- a/backends/graphics/opengl/texture.cpp +++ b/backends/graphics/opengl/texture.cpp @@ -21,8 +21,10 @@ */ #include "backends/graphics/opengl/texture.h" -#include "backends/graphics/opengl/extensions.h" -#include "backends/graphics/opengl/debug.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/pipelines/clut8.h" +#include "backends/graphics/opengl/framebuffer.h" #include "common/rect.h" #include "common/textconsole.h" @@ -41,94 +43,140 @@ static GLuint nextHigher2(GLuint v) { return ++v; } -GLint Texture::_maxTextureSize = 0; -void Texture::queryTextureInformation() { - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_maxTextureSize); - debug(5, "OpenGL maximum texture size: %d", _maxTextureSize); +GLTexture::GLTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType) + : _glIntFormat(glIntFormat), _glFormat(glFormat), _glType(glType), + _width(0), _height(0), _logicalWidth(0), _logicalHeight(0), + _texCoords(), _glFilter(GL_NEAREST), + _glTexture(0) { + create(); } -Texture::Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) - : _glIntFormat(glIntFormat), _glFormat(glFormat), _glType(glType), _format(format), _glFilter(GL_NEAREST), - _glTexture(0), _textureData(), _userPixelData(), _allDirty(false) { - recreateInternalTexture(); +GLTexture::~GLTexture() { + GL_CALL_SAFE(glDeleteTextures, (1, &_glTexture)); } -Texture::~Texture() { - releaseInternalTexture(); - _textureData.free(); +void GLTexture::enableLinearFiltering(bool enable) { + if (enable) { + _glFilter = GL_LINEAR; + } else { + _glFilter = GL_NEAREST; + } + + bind(); + + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); } -void Texture::releaseInternalTexture() { - GLCALL(glDeleteTextures(1, &_glTexture)); +void GLTexture::destroy() { + GL_CALL(glDeleteTextures(1, &_glTexture)); _glTexture = 0; } -void Texture::recreateInternalTexture() { +void GLTexture::create() { // Release old texture name in case it exists. - releaseInternalTexture(); + destroy(); // Get a new texture name. - GLCALL(glGenTextures(1, &_glTexture)); + GL_CALL(glGenTextures(1, &_glTexture)); // Set up all texture parameters. - GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); - GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - - // In case there is an actual texture setup we reinitialize it. - if (_textureData.getPixels()) { + bind(); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + + // If a size is specified, allocate memory for it. + if (_width != 0 && _height != 0) { // Allocate storage for OpenGL texture. - GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _textureData.w, - _textureData.h, 0, _glFormat, _glType, NULL)); - - // Mark dirts such that it will be completely refreshed the next time. - flagDirty(); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _width, _height, + 0, _glFormat, _glType, NULL)); } } -void Texture::enableLinearFiltering(bool enable) { - if (enable) { - _glFilter = GL_LINEAR; +void GLTexture::bind() const { + GL_CALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); +} + +void GLTexture::setSize(uint width, uint height) { + const uint oldWidth = _width; + const uint oldHeight = _height; + + if (!g_context.NPOTSupported) { + _width = nextHigher2(width); + _height = nextHigher2(height); } else { - _glFilter = GL_NEAREST; + _width = width; + _height = height; } - GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + _logicalWidth = width; + _logicalHeight = height; - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); -} + // If a size is specified, allocate memory for it. + if (width != 0 && height != 0) { + const GLfloat texWidth = (GLfloat)width / _width; + const GLfloat texHeight = (GLfloat)height / _height; -void Texture::allocate(uint width, uint height) { - uint texWidth = width, texHeight = height; - if (!g_extNPOTSupported) { - texWidth = nextHigher2(texWidth); - texHeight = nextHigher2(texHeight); - } + _texCoords[0] = 0; + _texCoords[1] = 0; - // In case the needed texture dimension changed we will reinitialize the - // texture. - if (texWidth != _textureData.w || texHeight != _textureData.h) { - // Create a buffer for the texture data. - _textureData.create(texWidth, texHeight, _format); + _texCoords[2] = texWidth; + _texCoords[3] = 0; - // Set the texture. - GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + _texCoords[4] = 0; + _texCoords[5] = texHeight; - // Allocate storage for OpenGL texture. - GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _textureData.w, - _textureData.h, 0, _glFormat, _glType, NULL)); + _texCoords[6] = texWidth; + _texCoords[7] = texHeight; + + // Allocate storage for OpenGL texture if necessary. + if (oldWidth != _width || oldHeight != _height) { + bind(); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _width, + _height, 0, _glFormat, _glType, NULL)); + } } +} - // Create a sub-buffer for raw access. - _userPixelData = _textureData.getSubArea(Common::Rect(width, height)); +void GLTexture::updateArea(const Common::Rect &area, const Graphics::Surface &src) { + // Set the texture on the active texture unit. + bind(); + + // Update the actual texture. + // Although we have the area of the texture buffer we want to update we + // cannot take advantage of the left/right boundries here because it is + // not possible to specify a pitch to glTexSubImage2D. To be precise, with + // plain OpenGL we could set GL_UNPACK_ROW_LENGTH to achieve this. However, + // OpenGL ES 1.0 does not support GL_UNPACK_ROW_LENGTH. Thus, we are left + // with the following options: + // + // 1) (As we do right now) Simply always update the whole texture lines of + // rect changed. This is simplest to implement. In case performance is + // really an issue we can think of switching to another method. + // + // 2) Copy the dirty rect to a temporary buffer and upload that by using + // glTexSubImage2D. This is what the Android backend does. It is more + // complicated though. + // + // 3) Use glTexSubImage2D per line changed. This is what the old OpenGL + // graphics manager did but it is much slower! Thus, we do not use it. + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, area.top, src.w, area.height(), + _glFormat, _glType, src.getBasePtr(0, area.top))); +} + +// +// Surface +// + +Surface::Surface() + : _allDirty(false), _dirtyArea() { } -void Texture::copyRectToTexture(uint x, uint y, uint w, uint h, const void *srcPtr, uint srcPitch) { +void Surface::copyRectToTexture(uint x, uint y, uint w, uint h, const void *srcPtr, uint srcPitch) { Graphics::Surface *dstSurf = getSurface(); assert(x + w <= dstSurf->w); assert(y + h <= dstSurf->h); @@ -159,50 +207,74 @@ void Texture::copyRectToTexture(uint x, uint y, uint w, uint h, const void *srcP } } -void Texture::fill(uint32 color) { +void Surface::fill(uint32 color) { Graphics::Surface *dst = getSurface(); dst->fillRect(Common::Rect(dst->w, dst->h), color); flagDirty(); } -void Texture::draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h) { - // Only do any processing when the Texture is initialized. - if (!_textureData.getPixels()) { - return; +Common::Rect Surface::getDirtyArea() const { + if (_allDirty) { + return Common::Rect(getWidth(), getHeight()); + } else { + return _dirtyArea; + } +} + +// +// Surface implementations +// + +Texture::Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) + : Surface(), _format(format), _glTexture(glIntFormat, glFormat, glType), + _textureData(), _userPixelData() { +} + +Texture::~Texture() { + _textureData.free(); +} + +void Texture::destroy() { + _glTexture.destroy(); +} + +void Texture::recreate() { + _glTexture.create(); + + // In case image date exists assure it will be completely refreshed next + // time. + if (_textureData.getPixels()) { + flagDirty(); + } +} + +void Texture::enableLinearFiltering(bool enable) { + _glTexture.enableLinearFiltering(enable); +} + +void Texture::allocate(uint width, uint height) { + // Assure the texture can contain our user data. + _glTexture.setSize(width, height); + + // In case the needed texture dimension changed we will reinitialize the + // texture data buffer. + if (_glTexture.getWidth() != _textureData.w || _glTexture.getHeight() != _textureData.h) { + // Create a buffer for the texture data. + _textureData.create(_glTexture.getWidth(), _glTexture.getHeight(), _format); } - // First update any potentional changes. - updateTexture(); - - // Set the texture. - GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); - - // Calculate the texture rect that will be drawn. - const GLfloat texWidth = (GLfloat)_userPixelData.w / _textureData.w; - const GLfloat texHeight = (GLfloat)_userPixelData.h / _textureData.h; - const GLfloat texcoords[4*2] = { - 0, 0, - texWidth, 0, - 0, texHeight, - texWidth, texHeight - }; - GLCALL(glTexCoordPointer(2, GL_FLOAT, 0, texcoords)); - - // Calculate the screen rect where the texture will be drawn. - const GLfloat vertices[4*2] = { - x, y, - x + w, y, - x, y + h, - x + w, y + h - }; - GLCALL(glVertexPointer(2, GL_FLOAT, 0, vertices)); - - // Draw the texture to the screen buffer. - GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); -} - -void Texture::updateTexture() { + // Create a sub-buffer for raw access. + _userPixelData = _textureData.getSubArea(Common::Rect(width, height)); + + // The whole texture is dirty after we changed the size. This fixes + // multiple texture size changes without any actual update in between. + // Without this we might try to write a too big texture into the GL + // texture. + flagDirty(); +} + +void Texture::updateGLTexture() { if (!isDirty()) { return; } @@ -211,7 +283,7 @@ void Texture::updateTexture() { // In case we use linear filtering we might need to duplicate the last // pixel row/column to avoid glitches with filtering. - if (_glFilter == GL_LINEAR) { + if (_glTexture.isLinearFilteringEnabled()) { if (dirtyArea.right == _userPixelData.w && _userPixelData.w != _textureData.w) { uint height = dirtyArea.height(); @@ -238,42 +310,12 @@ void Texture::updateTexture() { } } - // Set the texture. - GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); - - // Update the actual texture. - // Although we keep track of the dirty part of the texture buffer we - // cannot take advantage of the left/right boundries here because it is - // not possible to specify a pitch to glTexSubImage2D. To be precise, with - // plain OpenGL we could set GL_UNPACK_ROW_LENGTH to achieve this. However, - // OpenGL ES 1.0 does not support GL_UNPACK_ROW_LENGTH. Thus, we are left - // with the following options: - // - // 1) (As we do right now) Simply always update the whole texture lines of - // rect changed. This is simplest to implement. In case performance is - // really an issue we can think of switching to another method. - // - // 2) Copy the dirty rect to a temporary buffer and upload that by using - // glTexSubImage2D. This is what the Android backend does. It is more - // complicated though. - // - // 3) Use glTexSubImage2D per line changed. This is what the old OpenGL - // graphics manager did but it is much slower! Thus, we do not use it. - GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, dirtyArea.top, _textureData.w, dirtyArea.height(), - _glFormat, _glType, _textureData.getBasePtr(0, dirtyArea.top))); + _glTexture.updateArea(dirtyArea, _textureData); // We should have handled everything, thus not dirty anymore. clearDirty(); } -Common::Rect Texture::getDirtyArea() const { - if (_allDirty) { - return Common::Rect(_userPixelData.w, _userPixelData.h); - } else { - return _dirtyArea; - } -} - TextureCLUT8::TextureCLUT8(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) : Texture(glIntFormat, glFormat, glType, format), _clut8Data(), _palette(new byte[256 * format.bytesPerPixel]) { memset(_palette, 0, sizeof(byte) * format.bytesPerPixel); @@ -301,6 +343,25 @@ Graphics::PixelFormat TextureCLUT8::getFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } +void TextureCLUT8::setColorKey(uint colorKey) { + // We remove all alpha bits from the palette entry of the color key. + // This makes sure its properly handled as color key. + const uint32 aMask = (0xFF >> _format.aLoss) << _format.aShift; + + if (_format.bytesPerPixel == 2) { + uint16 *palette = (uint16 *)_palette + colorKey; + *palette &= ~aMask; + } else if (_format.bytesPerPixel == 4) { + uint32 *palette = (uint32 *)_palette + colorKey; + *palette &= ~aMask; + } else { + warning("TextureCLUT8::setColorKey: Unsupported pixel depth %d", _format.bytesPerPixel); + } + + // A palette changes means we need to refresh the whole surface. + flagDirty(); +} + namespace { template<typename ColorType> inline void convertPalette(ColorType *dst, const byte *src, uint colors, const Graphics::PixelFormat &format) { @@ -312,14 +373,12 @@ inline void convertPalette(ColorType *dst, const byte *src, uint colors, const G } // End of anonymous namespace void TextureCLUT8::setPalette(uint start, uint colors, const byte *palData) { - const Graphics::PixelFormat &hardwareFormat = getHardwareFormat(); - - if (hardwareFormat.bytesPerPixel == 2) { - convertPalette<uint16>((uint16 *)_palette + start, palData, colors, hardwareFormat); - } else if (hardwareFormat.bytesPerPixel == 4) { - convertPalette<uint32>((uint32 *)_palette + start, palData, colors, hardwareFormat); + if (_format.bytesPerPixel == 2) { + convertPalette<uint16>((uint16 *)_palette + start, palData, colors, _format); + } else if (_format.bytesPerPixel == 4) { + convertPalette<uint32>((uint32 *)_palette + start, palData, colors, _format); } else { - warning("TextureCLUT8::setPalette: Unsupported pixel depth: %d", hardwareFormat.bytesPerPixel); + warning("TextureCLUT8::setPalette: Unsupported pixel depth: %d", _format.bytesPerPixel); } // A palette changes means we need to refresh the whole surface. @@ -343,7 +402,7 @@ inline void doPaletteLookUp(PixelType *dst, const byte *src, uint width, uint he } } // End of anonymous namespace -void TextureCLUT8::updateTexture() { +void TextureCLUT8::updateGLTexture() { if (!isDirty()) { return; } @@ -368,7 +427,223 @@ void TextureCLUT8::updateTexture() { } // Do generic handling of updating the texture. - Texture::updateTexture(); + Texture::updateGLTexture(); +} + +#if !USE_FORCED_GL +TextureRGB555::TextureRGB555() + : Texture(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)), + _rgb555Data() { +} + +TextureRGB555::~TextureRGB555() { + _rgb555Data.free(); +} + +void TextureRGB555::allocate(uint width, uint height) { + Texture::allocate(width, height); + + // We only need to reinitialize our RGB555 surface when the output size + // changed. + if (width == _rgb555Data.w && height == _rgb555Data.h) { + return; + } + + _rgb555Data.create(width, height, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); +} + +Graphics::PixelFormat TextureRGB555::getFormat() const { + return Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); +} + +void TextureRGB555::updateTexture() { + if (!isDirty()) { + return; + } + + // Convert color space. + Graphics::Surface *outSurf = Texture::getSurface(); + + const Common::Rect dirtyArea = getDirtyArea(); + + uint16 *dst = (uint16 *)outSurf->getBasePtr(dirtyArea.left, dirtyArea.top); + const uint dstAdd = outSurf->pitch - 2 * dirtyArea.width(); + + const uint16 *src = (const uint16 *)_rgb555Data.getBasePtr(dirtyArea.left, dirtyArea.top); + const uint srcAdd = _rgb555Data.pitch - 2 * dirtyArea.width(); + + for (int height = dirtyArea.height(); height > 0; --height) { + for (int width = dirtyArea.width(); width > 0; --width) { + const uint16 color = *src++; + + *dst++ = ((color & 0x7C00) << 1) // R + | (((color & 0x03E0) << 1) | ((color & 0x0200) >> 4)) // G + | (color & 0x001F); // B + } + + src = (const uint16 *)((const byte *)src + srcAdd); + dst = (uint16 *)((byte *)dst + dstAdd); + } + + // Do generic handling of updating the texture. + Texture::updateGLTexture(); +} +#endif // !USE_FORCED_GL + +#if !USE_FORCED_GLES + +// _clut8Texture needs 8 bits internal precision, otherwise graphics glitches +// can occur. GL_ALPHA does not have any internal precision requirements. +// However, in practice (according to fuzzie) it's 8bit. If we run into +// problems, we need to switch to GL_R8 and GL_RED, but that is only supported +// for ARB_texture_rg and GLES3+ (EXT_rexture_rg does not support GL_R8). +TextureCLUT8GPU::TextureCLUT8GPU() + : _clut8Texture(GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE), + _paletteTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE), + _target(new TextureTarget()), _clut8Pipeline(new CLUT8LookUpPipeline()), + _clut8Vertices(), _clut8Data(), _userPixelData(), _palette(), + _paletteDirty(false) { + // Allocate space for 256 colors. + _paletteTexture.setSize(256, 1); + + // Setup pipeline. + _clut8Pipeline->setFramebuffer(_target); + _clut8Pipeline->setPaletteTexture(&_paletteTexture); + _clut8Pipeline->setColor(1.0f, 1.0f, 1.0f, 1.0f); +} + +TextureCLUT8GPU::~TextureCLUT8GPU() { + delete _clut8Pipeline; + delete _target; + _clut8Data.free(); +} + +void TextureCLUT8GPU::destroy() { + _clut8Texture.destroy(); + _paletteTexture.destroy(); + _target->destroy(); +} + +void TextureCLUT8GPU::recreate() { + _clut8Texture.create(); + _paletteTexture.create(); + _target->create(); + + // In case image date exists assure it will be completely refreshed next + // time. + if (_clut8Data.getPixels()) { + flagDirty(); + _paletteDirty = true; + } +} + +void TextureCLUT8GPU::enableLinearFiltering(bool enable) { + _target->getTexture()->enableLinearFiltering(enable); +} + +void TextureCLUT8GPU::allocate(uint width, uint height) { + // Assure the texture can contain our user data. + _clut8Texture.setSize(width, height); + _target->setSize(width, height); + + // In case the needed texture dimension changed we will reinitialize the + // texture data buffer. + if (_clut8Texture.getWidth() != _clut8Data.w || _clut8Texture.getHeight() != _clut8Data.h) { + // Create a buffer for the texture data. + _clut8Data.create(_clut8Texture.getWidth(), _clut8Texture.getHeight(), Graphics::PixelFormat::createFormatCLUT8()); + } + + // Create a sub-buffer for raw access. + _userPixelData = _clut8Data.getSubArea(Common::Rect(width, height)); + + // Setup structures for internal rendering to _glTexture. + _clut8Vertices[0] = 0; + _clut8Vertices[1] = 0; + + _clut8Vertices[2] = width; + _clut8Vertices[3] = 0; + + _clut8Vertices[4] = 0; + _clut8Vertices[5] = height; + + _clut8Vertices[6] = width; + _clut8Vertices[7] = height; + + // The whole texture is dirty after we changed the size. This fixes + // multiple texture size changes without any actual update in between. + // Without this we might try to write a too big texture into the GL + // texture. + flagDirty(); +} + +Graphics::PixelFormat TextureCLUT8GPU::getFormat() const { + return Graphics::PixelFormat::createFormatCLUT8(); +} + +void TextureCLUT8GPU::setColorKey(uint colorKey) { + _palette[colorKey * 4 + 3] = 0x00; + + _paletteDirty = true; +} + +void TextureCLUT8GPU::setPalette(uint start, uint colors, const byte *palData) { + byte *dst = _palette + start * 4; + + while (colors-- > 0) { + memcpy(dst, palData, 3); + dst[3] = 0xFF; + + dst += 4; + palData += 3; + } + + _paletteDirty = true; +} + +const GLTexture &TextureCLUT8GPU::getGLTexture() const { + return *_target->getTexture(); +} + +void TextureCLUT8GPU::updateGLTexture() { + const bool needLookUp = Surface::isDirty() || _paletteDirty; + + // Update CLUT8 texture if necessary. + if (Surface::isDirty()) { + _clut8Texture.updateArea(getDirtyArea(), _clut8Data); + clearDirty(); + } + + // Update palette if necessary. + if (_paletteDirty) { + Graphics::Surface palSurface; + palSurface.init(256, 1, 256, _palette, +#ifdef SCUMM_LITTLE_ENDIAN + Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24) // ABGR8888 +#else + Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0) // RGBA8888 +#endif + ); + + _paletteTexture.updateArea(Common::Rect(256, 1), palSurface); + _paletteDirty = false; + } + + // In case any data changed, do color look up and store result in _target. + if (needLookUp) { + lookUpColors(); + } +} + +void TextureCLUT8GPU::lookUpColors() { + // Setup pipeline to do color look up. + Pipeline *oldPipeline = g_context.setPipeline(_clut8Pipeline); + + // Do color look up. + g_context.getActivePipeline()->drawTexture(_clut8Texture, _clut8Vertices); + + // Restore old state. + g_context.setPipeline(oldPipeline); } +#endif // !USE_FORCED_GLES } // End of namespace OpenGL diff --git a/backends/graphics/opengl/texture.h b/backends/graphics/opengl/texture.h index ad70833544..3be09cb9f9 100644 --- a/backends/graphics/opengl/texture.h +++ b/backends/graphics/opengl/texture.h @@ -32,115 +32,267 @@ namespace OpenGL { +class Shader; + /** - * An OpenGL texture wrapper. It automatically takes care of all OpenGL - * texture handling issues and also provides access to the texture data. + * A simple GL texture object abstraction. + * + * This is used for low-level GL texture handling. */ -class Texture { +class GLTexture { public: /** - * Create a new texture with the specific internal format. + * Constrcut a new GL texture object. * * @param glIntFormat The internal format to use. * @param glFormat The input format. * @param glType The input type. - * @param format The format used for the texture input. */ - Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format); - virtual ~Texture(); + GLTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType); + ~GLTexture(); + + /** + * Enable or disable linear texture filtering. + * + * @param enable true to enable and false to disable. + */ + void enableLinearFiltering(bool enable); + + /** + * Test whether linear filtering is enabled. + */ + bool isLinearFilteringEnabled() const { return (_glFilter == GL_LINEAR); } /** * Destroy the OpenGL texture name. */ - void releaseInternalTexture(); + void destroy(); + + /** + * Create the OpenGL texture name. + */ + void create(); + + /** + * Bind the texture to the active texture unit. + */ + void bind() const; + + /** + * Sets the size of the texture in pixels. + * + * The internal OpenGL texture might have a different size. To query the + * actual size use getWidth()/getHeight(). + * + * @param width The desired logical width. + * @param height The desired logical height. + */ + void setSize(uint width, uint height); + + /** + * Copy image data to the texture. + * + * @param area The area to update. + * @param src Surface for the whole texture containing the pixel data + * to upload. Only the area described by area will be + * uploaded. + */ + void updateArea(const Common::Rect &area, const Graphics::Surface &src); /** - * Create the OpenGL texture name and flag the whole texture as dirty. + * Query the GL texture's width. */ - void recreateInternalTexture(); + uint getWidth() const { return _width; } + + /** + * Query the GL texture's height. + */ + uint getHeight() const { return _height; } + + /** + * Query the logical texture's width. + */ + uint getLogicalWidth() const { return _logicalWidth; } + + /** + * Query the logical texture's height. + */ + uint getLogicalHeight() const { return _logicalHeight; } + + /** + * Obtain texture coordinates for rectangular drawing. + */ + const GLfloat *getTexCoords() const { return _texCoords; } + + /** + * Obtain texture name. + * + * Beware that the texture name changes whenever create is used. + * destroy will invalidate the texture name. + */ + GLuint getGLTexture() const { return _glTexture; } +private: + const GLenum _glIntFormat; + const GLenum _glFormat; + const GLenum _glType; + + uint _width, _height; + uint _logicalWidth, _logicalHeight; + GLfloat _texCoords[4*2]; + + GLint _glFilter; + + GLuint _glTexture; +}; + +/** + * Interface for OpenGL implementations of a 2D surface. + */ +class Surface { +public: + Surface(); + virtual ~Surface() {} + + /** + * Destroy OpenGL description of surface. + */ + virtual void destroy() = 0; + + /** + * Recreate OpenGL description of surface. + */ + virtual void recreate() = 0; /** * Enable or disable linear texture filtering. * * @param enable true to enable and false to disable. */ - void enableLinearFiltering(bool enable); + virtual void enableLinearFiltering(bool enable) = 0; /** - * Allocate texture space for the desired dimensions. This wraps any - * handling of requirements for POT textures. + * Allocate storage for surface. * * @param width The desired logical width. * @param height The desired logical height. */ - virtual void allocate(uint width, uint height); + virtual void allocate(uint width, uint height) = 0; + /** + * Copy image data to the surface. + * + * The format of the input data needs to match the format returned by + * getFormat. + * + * @param x X coordinate of upper left corner to copy data to. + * @param y Y coordinate of upper left corner to copy data to. + * @param w Width of the image data to copy. + * @param h Height of the image data to copy. + * @param src Pointer to image data. + * @param srcPitch The number of bytes in a row of the image data. + */ void copyRectToTexture(uint x, uint y, uint w, uint h, const void *src, uint srcPitch); + /** + * Fill the surface with a fixed color. + * + * @param color Color value in format returned by getFormat. + */ void fill(uint32 color); - void draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h); - void flagDirty() { _allDirty = true; } - bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); } - - uint getWidth() const { return _userPixelData.w; } - uint getHeight() const { return _userPixelData.h; } + virtual bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); } - /** - * @return The hardware format of the texture data. - */ - const Graphics::PixelFormat &getHardwareFormat() const { return _format; } + virtual uint getWidth() const = 0; + virtual uint getHeight() const = 0; /** * @return The logical format of the texture data. */ - virtual Graphics::PixelFormat getFormat() const { return _format; } + virtual Graphics::PixelFormat getFormat() const = 0; - virtual Graphics::Surface *getSurface() { return &_userPixelData; } - virtual const Graphics::Surface *getSurface() const { return &_userPixelData; } + virtual Graphics::Surface *getSurface() = 0; + virtual const Graphics::Surface *getSurface() const = 0; /** - * @return Whether the texture data is using a palette. + * @return Whether the surface is having a palette. */ virtual bool hasPalette() const { return false; } + /** + * Set color key for paletted textures. + * + * This needs to be called after any palette update affecting the color + * key. Calling this multiple times will result in multiple color indices + * to be treated as color keys. + */ + virtual void setColorKey(uint colorKey) {} virtual void setPalette(uint start, uint colors, const byte *palData) {} - virtual void *getPalette() { return 0; } - virtual const void *getPalette() const { return 0; } - /** - * Query texture related OpenGL information from the context. This only - * queries the maximum texture size for now. + * Update underlying OpenGL texture to reflect current state. */ - static void queryTextureInformation(); + virtual void updateGLTexture() = 0; /** - * @return Return the maximum texture dimensions supported. + * Obtain underlying OpenGL texture. */ - static GLint getMaximumTextureSize() { return _maxTextureSize; } + virtual const GLTexture &getGLTexture() const = 0; protected: - virtual void updateTexture(); + void clearDirty() { _allDirty = false; _dirtyArea = Common::Rect(); } Common::Rect getDirtyArea() const; private: - const GLenum _glIntFormat; - const GLenum _glFormat; - const GLenum _glType; + bool _allDirty; + Common::Rect _dirtyArea; +}; + +/** + * An OpenGL texture wrapper. It automatically takes care of all OpenGL + * texture handling issues and also provides access to the texture data. + */ +class Texture : public Surface { +public: + /** + * Create a new texture with the specific internal format. + * + * @param glIntFormat The internal format to use. + * @param glFormat The input format. + * @param glType The input type. + * @param format The format used for the texture input. + */ + Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format); + virtual ~Texture(); + + virtual void destroy(); + + virtual void recreate(); + + virtual void enableLinearFiltering(bool enable); + + virtual void allocate(uint width, uint height); + + virtual uint getWidth() const { return _userPixelData.w; } + virtual uint getHeight() const { return _userPixelData.h; } + + /** + * @return The logical format of the texture data. + */ + virtual Graphics::PixelFormat getFormat() const { return _format; } + + virtual Graphics::Surface *getSurface() { return &_userPixelData; } + virtual const Graphics::Surface *getSurface() const { return &_userPixelData; } + + virtual void updateGLTexture(); + virtual const GLTexture &getGLTexture() const { return _glTexture; } +protected: const Graphics::PixelFormat _format; - GLint _glFilter; - GLuint _glTexture; +private: + GLTexture _glTexture; Graphics::Surface _textureData; Graphics::Surface _userPixelData; - - bool _allDirty; - Common::Rect _dirtyArea; - void clearDirty() { _allDirty = false; _dirtyArea = Common::Rect(); } - - static GLint _maxTextureSize; }; class TextureCLUT8 : public Texture { @@ -154,21 +306,95 @@ public: virtual bool hasPalette() const { return true; } + virtual void setColorKey(uint colorKey); virtual void setPalette(uint start, uint colors, const byte *palData); - virtual void *getPalette() { return _palette; } - virtual const void *getPalette() const { return _palette; } - virtual Graphics::Surface *getSurface() { return &_clut8Data; } virtual const Graphics::Surface *getSurface() const { return &_clut8Data; } -protected: + virtual void updateGLTexture(); +private: + Graphics::Surface _clut8Data; + byte *_palette; +}; + +#if !USE_FORCED_GL +class TextureRGB555 : public Texture { +public: + TextureRGB555(); + virtual ~TextureRGB555(); + + virtual void allocate(uint width, uint height); + + virtual Graphics::PixelFormat getFormat() const; + + virtual Graphics::Surface *getSurface() { return &_rgb555Data; } + virtual const Graphics::Surface *getSurface() const { return &_rgb555Data; } + virtual void updateTexture(); +private: + Graphics::Surface _rgb555Data; +}; +#endif // !USE_FORCED_GL + +#if !USE_FORCED_GLES +class TextureTarget; +class CLUT8LookUpPipeline; + +class TextureCLUT8GPU : public Surface { +public: + TextureCLUT8GPU(); + virtual ~TextureCLUT8GPU(); + + virtual void destroy(); + + virtual void recreate(); + virtual void enableLinearFiltering(bool enable); + + virtual void allocate(uint width, uint height); + + virtual bool isDirty() const { return _paletteDirty || Surface::isDirty(); } + + virtual uint getWidth() const { return _userPixelData.w; } + virtual uint getHeight() const { return _userPixelData.h; } + + virtual Graphics::PixelFormat getFormat() const; + + virtual bool hasPalette() const { return true; } + + virtual void setColorKey(uint colorKey); + virtual void setPalette(uint start, uint colors, const byte *palData); + + virtual Graphics::Surface *getSurface() { return &_userPixelData; } + virtual const Graphics::Surface *getSurface() const { return &_userPixelData; } + + virtual void updateGLTexture(); + virtual const GLTexture &getGLTexture() const; + + static bool isSupportedByContext() { + return g_context.shadersSupported + && g_context.multitextureSupported + && g_context.framebufferObjectSupported; + } private: + void lookUpColors(); + + GLTexture _clut8Texture; + GLTexture _paletteTexture; + + TextureTarget *_target; + CLUT8LookUpPipeline *_clut8Pipeline; + + GLfloat _clut8Vertices[4*2]; + Graphics::Surface _clut8Data; - byte *_palette; + Graphics::Surface _userPixelData; + + byte _palette[4 * 256]; + bool _paletteDirty; }; +#endif // !USE_FORCED_GLES } // End of namespace OpenGL diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp index b028cd5b1a..7ea1860d93 100644 --- a/backends/graphics/openglsdl/openglsdl-graphics.cpp +++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp @@ -21,6 +21,7 @@ */ #include "backends/graphics/openglsdl/openglsdl-graphics.h" +#include "backends/events/sdl/sdl-events.h" #include "common/textconsole.h" #include "common/config-manager.h" @@ -28,8 +29,13 @@ #include "common/translation.h" #endif -OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource) - : SdlGraphicsManager(eventSource), _lastVideoModeLoad(0), _hwScreen(nullptr), _lastRequestedWidth(0), _lastRequestedHeight(0), +OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource, SdlWindow *window) + : SdlGraphicsManager(eventSource, window), _lastRequestedHeight(0), +#if SDL_VERSION_ATLEAST(2, 0, 0) + _glContext(), +#else + _lastVideoModeLoad(0), _hwScreen(nullptr), +#endif _graphicsScale(2), _ignoreLoadVideoMode(false), _gotResize(false), _wantsFullScreen(false), _ignoreResizeEvents(0), _desiredFullscreenWidth(0), _desiredFullscreenHeight(0) { // Setup OpenGL attributes for SDL @@ -39,17 +45,135 @@ OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint deskt SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + // Setup proper SDL OpenGL context creation. +#if SDL_VERSION_ATLEAST(2, 0, 0) + OpenGL::ContextType glContextType; + + // Context version 1.4 is choosen arbitrarily based on what most shader + // extensions were written against. +#define DEFAULT_GL_MAJOR 1 +#define DEFAULT_GL_MINOR 4 + +#define DEFAULT_GLES_MAJOR 1 +#define DEFAULT_GLES_MINOR 1 + +#define DEFAULT_GLES2_MAJOR 2 +#define DEFAULT_GLES2_MINOR 0 + +#if USE_FORCED_GL + glContextType = OpenGL::kContextGL; + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; +#elif USE_FORCED_GLES + glContextType = OpenGL::kContextGLES; + _glContextProfileMask = SDL_GL_CONTEXT_PROFILE_ES; + _glContextMajor = DEFAULT_GLES_MAJOR; + _glContextMinor = DEFAULT_GLES_MINOR; +#elif USE_FORCED_GLES2 + glContextType = OpenGL::kContextGLES2; + _glContextProfileMask = SDL_GL_CONTEXT_PROFILE_ES; + _glContextMajor = DEFAULT_GLES2_MAJOR; + _glContextMinor = DEFAULT_GLES2_MINOR; +#else + bool noDefaults = false; + + // Obtain the default GL(ES) context SDL2 tries to setup. + // + // Please note this might not actually be SDL2's defaults when multiple + // instances of this object have been created. But that is no issue + // because then we already set up what we want to use. + // + // In case no defaults are given we prefer OpenGL over OpenGL ES. + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask) != 0) { + _glContextProfileMask = 0; + noDefaults = true; + } + + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor) != 0) { + noDefaults = true; + } + + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor) != 0) { + noDefaults = true; + } + + if (noDefaults) { + if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { + _glContextMajor = DEFAULT_GLES_MAJOR; + _glContextMinor = DEFAULT_GLES_MINOR; + } else { + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; + } + } + + if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { + if (_glContextMajor >= 2) { + glContextType = OpenGL::kContextGLES2; + } else { + glContextType = OpenGL::kContextGLES; + } + } else if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_CORE) { + glContextType = OpenGL::kContextGL; + + // Core profile does not allow legacy functionality, which we use. + // Thus we request a standard OpenGL context. + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; + } else { + glContextType = OpenGL::kContextGL; + } +#undef DEFAULT_GL_MAJOR +#undef DEFAULT_GL_MINOR +#undef DEFAULT_GLES_MAJOR +#undef DEFAULT_GLES_MINOR +#undef DEFAULT_GLES2_MAJOR +#undef DEFAULT_GLES2_MINOR +#endif + + setContextType(glContextType); +#else + setContextType(OpenGL::kContextGL); +#endif + // Retrieve a list of working fullscreen modes +#if SDL_VERSION_ATLEAST(2, 0, 0) + const int numModes = SDL_GetNumDisplayModes(0); + for (int i = 0; i < numModes; ++i) { + SDL_DisplayMode mode; + if (SDL_GetDisplayMode(0, i, &mode)) { + continue; + } + + _fullscreenVideoModes.push_back(VideoMode(mode.w, mode.h)); + } +#else const SDL_Rect *const *availableModes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); - if (availableModes != (void *)-1) { + // TODO: NULL means that there are no fullscreen modes supported. We + // should probably use this information and disable any fullscreen support + // in this case. + if (availableModes != NULL && availableModes != (void *)-1) { for (;*availableModes; ++availableModes) { const SDL_Rect *mode = *availableModes; _fullscreenVideoModes.push_back(VideoMode(mode->w, mode->h)); } + } +#endif + + // Sort the modes in ascending order. + Common::sort(_fullscreenVideoModes.begin(), _fullscreenVideoModes.end()); - // Sort the modes in ascending order. - Common::sort(_fullscreenVideoModes.begin(), _fullscreenVideoModes.end()); + // Strip duplicates in video modes. + for (uint i = 0; i + 1 < _fullscreenVideoModes.size();) { + if (_fullscreenVideoModes[i] == _fullscreenVideoModes[i + 1]) { + _fullscreenVideoModes.remove_at(i); + } else { + ++i; + } } // In case SDL is fine with every mode we will force the desktop mode. @@ -70,6 +194,10 @@ OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint deskt } OpenGLSdlGraphicsManager::~OpenGLSdlGraphicsManager() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + notifyContextDestroy(); + SDL_GL_DeleteContext(_glContext); +#endif } void OpenGLSdlGraphicsManager::activateManager() { @@ -108,7 +236,7 @@ void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) case OSystem::kFeatureIconifyWindow: if (enable) { - SDL_WM_IconifyWindow(); + _window->iconifyWindow(); } break; @@ -120,11 +248,19 @@ void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) bool OpenGLSdlGraphicsManager::getFeatureState(OSystem::Feature f) { switch (f) { case OSystem::kFeatureFullscreenMode: +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + return (SDL_GetWindowFlags(_window->getSDLWindow()) & SDL_WINDOW_FULLSCREEN) != 0; + } else { + return _wantsFullScreen; + } +#else if (_hwScreen) { return (_hwScreen->flags & SDL_FULLSCREEN) != 0; } else { return _wantsFullScreen; } +#endif default: return OpenGLGraphicsManager::getFeatureState(f); @@ -172,20 +308,26 @@ Common::List<Graphics::PixelFormat> OpenGLSdlGraphicsManager::getSupportedFormat // RGBA4444 formats.push_back(Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)); -#ifndef USE_GLES +#if !USE_FORCED_GLES && !USE_FORCED_GLES2 +#if !USE_FORCED_GL + if (!isGLESContext()) { +#endif #ifdef SCUMM_LITTLE_ENDIAN - // RGBA8888 - formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + // RGBA8888 + formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); #else - // ABGR8888 - formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)); + // ABGR8888 + formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)); +#endif +#if !USE_FORCED_GL + } +#endif #endif - // ARGB8888, this should not be here, but Sword25 requires it. :-/ - formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)); // RGB555, this is used by SCUMM HE 16 bit games. + // This is not natively supported by OpenGL ES implementations, we convert + // the pixel format internally. formats.push_back(Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); -#endif formats.push_back(Graphics::PixelFormat::createFormatCLUT8()); @@ -199,15 +341,16 @@ void OpenGLSdlGraphicsManager::updateScreen() { } OpenGLGraphicsManager::updateScreen(); - - // Swap OpenGL buffers - SDL_GL_SwapBuffers(); } void OpenGLSdlGraphicsManager::notifyVideoExpose() { } void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + setActualScreenSize(width, height); + _eventSource->resetKeyboadEmulation(width - 1, height - 1); +#else if (!_ignoreResizeEvents && _hwScreen && !(_hwScreen->flags & SDL_FULLSCREEN)) { // We save that we handled a resize event here. We need to know this // so we do not overwrite the users requested window size whenever we @@ -218,6 +361,7 @@ void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height) g_system->quit(); } } +#endif } void OpenGLSdlGraphicsManager::transformMouseCoordinates(Common::Point &point) { @@ -229,7 +373,7 @@ void OpenGLSdlGraphicsManager::notifyMousePos(Common::Point mouse) { } void OpenGLSdlGraphicsManager::setInternalMousePosition(int x, int y) { - SDL_WarpMouse(x, y); + _window->warpMouseInWindow(x, y); } bool OpenGLSdlGraphicsManager::loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) { @@ -256,6 +400,19 @@ bool OpenGLSdlGraphicsManager::loadVideoMode(uint requestedWidth, uint requested return setupMode(requestedWidth, requestedHeight); } +void OpenGLSdlGraphicsManager::refreshScreen() { + // Swap OpenGL buffers +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_SwapWindow(_window->getSDLWindow()); +#else + SDL_GL_SwapBuffers(); +#endif +} + +void *OpenGLSdlGraphicsManager::getProcAddress(const char *name) const { + return SDL_GL_GetProcAddress(name); +} + bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) { // In case we request a fullscreen mode we will use the mode the user // has chosen last time or the biggest mode available. @@ -300,6 +457,64 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) { height = _desiredFullscreenHeight; } + // This is pretty confusing since RGBA8888 talks about the memory + // layout here. This is a different logical layout depending on + // whether we run on little endian or big endian. However, we can + // only safely assume that RGBA8888 in memory layout is supported. + // Thus, we chose this one. + const Graphics::PixelFormat rgba8888 = +#ifdef SCUMM_LITTLE_ENDIAN + Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); +#else + Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_glContext) { + notifyContextDestroy(); + + SDL_GL_DeleteContext(_glContext); + _glContext = nullptr; + } + + _window->destroyWindow(); + + uint32 flags = SDL_WINDOW_OPENGL; + if (_wantsFullScreen) { + flags |= SDL_WINDOW_FULLSCREEN; + } else { + flags |= SDL_WINDOW_RESIZABLE; + } + + // Request a OpenGL (ES) context we can use. + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, _glContextProfileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, _glContextMajor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, _glContextMinor); + + if (!_window->createWindow(width, height, flags)) { + // We treat fullscreen requests as a "hint" for now. This means in + // case it is not available we simply ignore it. + if (_wantsFullScreen) { + _window->createWindow(width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + } + + if (!_window->getSDLWindow()) { + return false; + } + } + + _glContext = SDL_GL_CreateContext(_window->getSDLWindow()); + if (!_glContext) { + return false; + } + + notifyContextCreate(rgba8888, rgba8888); + int actualWidth, actualHeight; + getWindowDimensions(&actualWidth, &actualHeight); + setActualScreenSize(actualWidth, actualHeight); + _eventSource->resetKeyboadEmulation(actualWidth - 1, actualHeight - 1); + return true; +#else // WORKAROUND: Working around infamous SDL bugs when switching // resolutions too fast. This might cause the event system to supply // incorrect mouse position events otherwise. @@ -341,19 +556,9 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) { _lastVideoModeLoad = SDL_GetTicks(); if (_hwScreen) { - // This is pretty confusing since RGBA8888 talks about the memory - // layout here. This is a different logical layout depending on - // whether we run on little endian or big endian. However, we can - // only safely assume that RGBA8888 in memory layout is supported. - // Thus, we chose this one. - const Graphics::PixelFormat rgba8888 = -#ifdef SCUMM_LITTLE_ENDIAN - Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); -#else - Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); -#endif notifyContextCreate(rgba8888, rgba8888); setActualScreenSize(_hwScreen->w, _hwScreen->h); + _eventSource->resetKeyboadEmulation(_hwScreen->w - 1, _hwScreen->h - 1); } // Ignore resize events (from SDL) for a few frames, if this isn't @@ -363,6 +568,21 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) { _ignoreResizeEvents = 10; return _hwScreen != nullptr; +#endif +} + +void OpenGLSdlGraphicsManager::getWindowDimensions(int *width, int *height) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GetWindowSize(_window->getSDLWindow(), width, height); +#else + if (width) { + *width = _hwScreen->w; + } + + if (height) { + *height = _hwScreen->h; + } +#endif } bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) { @@ -456,7 +676,9 @@ bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) { // Calculate the next scaling setting. We approximate the // current scale setting in case the user resized the // window. Then we apply the direction change. - _graphicsScale = MAX<int>(_hwScreen->w / _lastRequestedWidth, _hwScreen->h / _lastRequestedHeight); + int windowWidth = 0, windowHeight = 0; + getWindowDimensions(&windowWidth, &windowHeight); + _graphicsScale = MAX<int>(windowWidth / _lastRequestedWidth, windowHeight / _lastRequestedHeight); _graphicsScale = MAX<int>(_graphicsScale + direction, 1); // Since we overwrite a user resize here we reset its @@ -472,7 +694,9 @@ bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) { } #ifdef USE_OSD - const Common::String osdMsg = Common::String::format("Resolution: %dx%d", _hwScreen->w, _hwScreen->h); + int windowWidth = 0, windowHeight = 0; + getWindowDimensions(&windowWidth, &windowHeight); + const Common::String osdMsg = Common::String::format("Resolution: %dx%d", windowWidth, windowHeight); displayMessageOnOSD(osdMsg.c_str()); #endif diff --git a/backends/graphics/openglsdl/openglsdl-graphics.h b/backends/graphics/openglsdl/openglsdl-graphics.h index 9934ca79e2..51edcb4363 100644 --- a/backends/graphics/openglsdl/openglsdl-graphics.h +++ b/backends/graphics/openglsdl/openglsdl-graphics.h @@ -32,7 +32,7 @@ class OpenGLSdlGraphicsManager : public OpenGL::OpenGLGraphicsManager, public SdlGraphicsManager, public Common::EventObserver { public: - OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource); + OpenGLSdlGraphicsManager(uint desktopWidth, uint desktopHeight, SdlEventSource *eventSource, SdlWindow *window); virtual ~OpenGLSdlGraphicsManager(); // GraphicsManager API @@ -65,11 +65,22 @@ protected: virtual void setInternalMousePosition(int x, int y); virtual bool loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format); + + virtual void refreshScreen(); + + virtual void *getProcAddress(const char *name) const; private: bool setupMode(uint width, uint height); +#if SDL_VERSION_ATLEAST(2, 0, 0) + int _glContextProfileMask, _glContextMajor, _glContextMinor; + SDL_GLContext _glContext; +#else uint32 _lastVideoModeLoad; SDL_Surface *_hwScreen; +#endif + + void getWindowDimensions(int *width, int *height); uint _lastRequestedWidth; uint _lastRequestedHeight; diff --git a/backends/graphics/openpandora/op-graphics.cpp b/backends/graphics/openpandora/op-graphics.cpp index 1ded1614de..f4c9dc16cc 100644 --- a/backends/graphics/openpandora/op-graphics.cpp +++ b/backends/graphics/openpandora/op-graphics.cpp @@ -32,8 +32,8 @@ static SDL_Cursor *hiddenCursor; -OPGraphicsManager::OPGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +OPGraphicsManager::OPGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } bool OPGraphicsManager::loadGFXMode() { diff --git a/backends/graphics/openpandora/op-graphics.h b/backends/graphics/openpandora/op-graphics.h index 8b498d632b..50994072bb 100644 --- a/backends/graphics/openpandora/op-graphics.h +++ b/backends/graphics/openpandora/op-graphics.h @@ -32,7 +32,7 @@ enum { class OPGraphicsManager : public SurfaceSdlGraphicsManager { public: - OPGraphicsManager(SdlEventSource *sdlEventSource); + OPGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); bool loadGFXMode(); void unloadGFXMode(); diff --git a/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.cpp b/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.cpp index 3603d8a861..0c98462891 100644 --- a/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.cpp +++ b/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.cpp @@ -28,8 +28,8 @@ #include "backends/events/samsungtvsdl/samsungtvsdl-events.h" #include "backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h" -SamsungTVSdlGraphicsManager::SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +SamsungTVSdlGraphicsManager::SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } bool SamsungTVSdlGraphicsManager::hasFeature(OSystem::Feature f) { diff --git a/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h b/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h index 15ba3dca48..8699d77bc8 100644 --- a/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h +++ b/backends/graphics/samsungtvsdl/samsungtvsdl-graphics.h @@ -29,7 +29,7 @@ class SamsungTVSdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource); + SamsungTVSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); bool hasFeature(OSystem::Feature f); void setFeatureState(OSystem::Feature f, bool enable); diff --git a/backends/graphics/sdl/sdl-graphics.cpp b/backends/graphics/sdl/sdl-graphics.cpp index b5e49fa397..a13ca45477 100644 --- a/backends/graphics/sdl/sdl-graphics.cpp +++ b/backends/graphics/sdl/sdl-graphics.cpp @@ -22,10 +22,12 @@ #include "backends/graphics/sdl/sdl-graphics.h" +#include "backends/platform/sdl/sdl-sys.h" #include "backends/events/sdl/sdl-events.h" +#include "common/textconsole.h" -SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source) - : _eventSource(source) { +SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window) + : _eventSource(source), _window(window) { } SdlGraphicsManager::~SdlGraphicsManager() { @@ -38,3 +40,36 @@ void SdlGraphicsManager::activateManager() { void SdlGraphicsManager::deactivateManager() { _eventSource->setGraphicsManager(0); } + +SdlGraphicsManager::State SdlGraphicsManager::getState() { + State state; + + state.screenWidth = getWidth(); + state.screenHeight = getHeight(); + state.aspectRatio = getFeatureState(OSystem::kFeatureAspectRatioCorrection); + state.fullscreen = getFeatureState(OSystem::kFeatureFullscreenMode); + state.cursorPalette = getFeatureState(OSystem::kFeatureCursorPalette); +#ifdef USE_RGB_COLOR + state.pixelFormat = getScreenFormat(); +#endif + return state; +} + +bool SdlGraphicsManager::setState(const State &state) { + beginGFXTransaction(); +#ifdef USE_RGB_COLOR + initSize(state.screenWidth, state.screenHeight, &state.pixelFormat); +#else + initSize(state.screenWidth, state.screenHeight, 0); +#endif + setFeatureState(OSystem::kFeatureAspectRatioCorrection, state.aspectRatio); + setFeatureState(OSystem::kFeatureFullscreenMode, state.fullscreen); + setFeatureState(OSystem::kFeatureCursorPalette, state.cursorPalette); + + if (endGFXTransaction() != OSystem::kTransactionSuccess) { + return false; + } else { + return true; + } +} + diff --git a/backends/graphics/sdl/sdl-graphics.h b/backends/graphics/sdl/sdl-graphics.h index 3ef540708a..7f8790a9b4 100644 --- a/backends/graphics/sdl/sdl-graphics.h +++ b/backends/graphics/sdl/sdl-graphics.h @@ -24,6 +24,7 @@ #define BACKENDS_GRAPHICS_SDL_SDLGRAPHICS_H #include "backends/graphics/graphics.h" +#include "backends/platform/sdl/sdl-window.h" #include "common/rect.h" @@ -36,7 +37,7 @@ class SdlEventSource; */ class SdlGraphicsManager : virtual public GraphicsManager { public: - SdlGraphicsManager(SdlEventSource *source); + SdlGraphicsManager(SdlEventSource *source, SdlWindow *window); virtual ~SdlGraphicsManager(); /** @@ -91,8 +92,39 @@ public: */ virtual void notifyMousePos(Common::Point mouse) = 0; + /** + * A (subset) of the graphic manager's state. This is used when switching + * between different SDL graphic managers on runtime. + */ + struct State { + int screenWidth, screenHeight; + bool aspectRatio; + bool fullscreen; + bool cursorPalette; + +#ifdef USE_RGB_COLOR + Graphics::PixelFormat pixelFormat; +#endif + }; + + /** + * Queries the current state of the graphic manager. + */ + State getState(); + + /** + * Setup a basic state of the graphic manager. + */ + bool setState(const State &state); + + /** + * Queries the SDL window. + */ + SdlWindow *getWindow() const { return _window; } + protected: SdlEventSource *_eventSource; + SdlWindow *_window; }; #endif diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp index 7f3c99fcea..b4b0d33b85 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp @@ -32,6 +32,7 @@ #include "common/textconsole.h" #include "common/translation.h" #include "common/util.h" +#include "common/frac.h" #ifdef USE_RGB_COLOR #include "common/list.h" #endif @@ -116,13 +117,21 @@ static AspectRatio getDesiredAspectRatio() { } #endif -SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource) +SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) : - SdlGraphicsManager(sdlEventSource), + SdlGraphicsManager(sdlEventSource, window), #ifdef USE_OSD - _osdSurface(0), _osdAlpha(SDL_ALPHA_TRANSPARENT), _osdFadeStartTime(0), + _osdSurface(0), _osdMessageSurface(nullptr), _osdMessageAlpha(SDL_ALPHA_TRANSPARENT), _osdMessageFadeStartTime(0), #endif - _hwscreen(0), _screen(0), _tmpscreen(0), + _hwscreen(0), +#if SDL_VERSION_ATLEAST(2, 0, 0) + _renderer(nullptr), _screenTexture(nullptr), + _viewport(), _windowWidth(1), _windowHeight(1), +#endif +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + _originalBitsPerPixel(0), +#endif + _screen(0), _tmpscreen(0), #ifdef USE_RGB_COLOR _screenFormat(Graphics::PixelFormat::createFormatCLUT8()), _cursorFormat(Graphics::PixelFormat::createFormatCLUT8()), @@ -235,7 +244,7 @@ void SurfaceSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) break; case OSystem::kFeatureIconifyWindow: if (enable) - SDL_WM_IconifyWindow(); + _window->iconifyWindow(); break; default: break; @@ -681,12 +690,22 @@ static void fixupResolutionForAspectRatio(AspectRatio desiredAspectRatio, int &w const int w = width; const int h = height; + int bestW = 0, bestH = 0; + uint bestMetric = (uint)-1; // Metric is wasted space + +#if SDL_VERSION_ATLEAST(2, 0, 0) + const int numModes = SDL_GetNumDisplayModes(0); + SDL_DisplayMode modeData, *mode = &modeData; + for (int i = 0; i < numModes; ++i) { + if (SDL_GetDisplayMode(0, i, &modeData)) { + continue; + } +#else SDL_Rect const* const*availableModes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_SWSURFACE); //TODO : Maybe specify a pixel format assert(availableModes); - const SDL_Rect *bestMode = NULL; - uint bestMetric = (uint)-1; // Metric is wasted space while (const SDL_Rect *mode = *availableModes++) { +#endif if (mode->w < w) continue; if (mode->h < h) @@ -699,15 +718,23 @@ static void fixupResolutionForAspectRatio(AspectRatio desiredAspectRatio, int &w continue; bestMetric = metric; - bestMode = mode; + bestW = mode->w; + bestH = mode->h; + + // Make editors a bit more happy by having the same amount of closing as + // opening curley braces. +#if SDL_VERSION_ATLEAST(2, 0, 0) + } +#else } +#endif - if (!bestMode) { + if (!bestW || !bestH) { warning("Unable to enforce the desired aspect ratio"); return; } - width = bestMode->w; - height = bestMode->h; + width = bestW; + height = bestH; } bool SurfaceSdlGraphicsManager::loadGFXMode() { @@ -774,7 +801,16 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() { _hwscreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight); } else #endif - { + { +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + // Save the original bpp to be able to restore the video mode on + // unload. See _originalBitsPerPixel documentation. + if (_originalBitsPerPixel == 0) { + const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); + _originalBitsPerPixel = videoInfo->vfmt->BitsPerPixel; + } +#endif + _hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16, _videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE ); @@ -847,19 +883,27 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() { _osdSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _hwscreen->w, _hwscreen->h, - 16, - _hwscreen->format->Rmask, - _hwscreen->format->Gmask, - _hwscreen->format->Bmask, - _hwscreen->format->Amask); + 32, + 0xFF000000, + 0x00FF0000, + 0x0000FF00, + 0x000000FF + ); if (_osdSurface == NULL) error("allocating _osdSurface failed"); + + _osdFormat = getSurfaceFormat(_osdSurface); SDL_SetColorKey(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kOSDColorKey); #endif +#if !SDL_VERSION_ATLEAST(2, 0, 0) + // For SDL2 the output resolution might differ from the requested + // resolution. We handle resetting the keyboard emulation properly inside + // our SDL_SetVideoMode wrapper for SDL2. _eventSource->resetKeyboadEmulation( _videoMode.screenWidth * _videoMode.scaleFactor - 1, effectiveScreenHeight() - 1); +#endif // Distinguish 555 and 565 mode if (_hwscreen->format->Rmask == 0x7C00) @@ -876,6 +920,10 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() { _screen = NULL; } +#if SDL_VERSION_ATLEAST(2, 0, 0) + deinitializeRenderer(); +#endif + if (_hwscreen) { SDL_FreeSurface(_hwscreen); _hwscreen = NULL; @@ -901,8 +949,21 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() { SDL_FreeSurface(_osdSurface); _osdSurface = NULL; } + + if (_osdMessageSurface) { + SDL_FreeSurface(_osdMessageSurface); + _osdMessageSurface = NULL; + } #endif DestroyScalers(); + +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + // Reset video mode to original. + // This will ensure that any new graphic manager will use the initial BPP + // when listing available modes. See _originalBitsPerPixel documentation. + if (_originalBitsPerPixel != 0) + SDL_SetVideoMode(_videoMode.screenWidth, _videoMode.screenHeight, _originalBitsPerPixel, _videoMode.fullscreen ? (SDL_FULLSCREEN | SDL_SWSURFACE) : SDL_SWSURFACE); +#endif } bool SurfaceSdlGraphicsManager::hotswapGFXMode() { @@ -1006,21 +1067,33 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() { #ifdef USE_OSD // OSD visible (i.e. non-transparent)? - if (_osdAlpha != SDL_ALPHA_TRANSPARENT) { + if (_osdMessageAlpha != SDL_ALPHA_TRANSPARENT) { // Updated alpha value - const int diff = SDL_GetTicks() - _osdFadeStartTime; + const int diff = SDL_GetTicks() - _osdMessageFadeStartTime; if (diff > 0) { if (diff >= kOSDFadeOutDuration) { // Back to full transparency - _osdAlpha = SDL_ALPHA_TRANSPARENT; + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; } else { // Do a linear fade out... const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; - _osdAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration; + _osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration; } - SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha); _forceFull = true; } + + if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) { + removeOSDMessage(); + } else { + if (_osdMessageSurface && _osdSurface) { + SDL_Rect dstRect; + dstRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2; + dstRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2; + dstRect.w = _osdMessageSurface->w; + dstRect.h = _osdMessageSurface->h; + blitOSDMessage(dstRect); + } + } } #endif @@ -1126,9 +1199,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() { drawMouse(); #ifdef USE_OSD - if (_osdAlpha != SDL_ALPHA_TRANSPARENT) { - SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0); - } + SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0); #endif #ifdef USE_SDL_DEBUG_FOCUSRECT @@ -1443,6 +1514,9 @@ void SurfaceSdlGraphicsManager::setPalette(const byte *colors, uint start, uint base[i].r = b[0]; base[i].g = b[1]; base[i].b = b[2]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + base[i].a = 255; +#endif } if (start < _paletteDirtyStart) @@ -1481,6 +1555,9 @@ void SurfaceSdlGraphicsManager::setCursorPalette(const byte *colors, uint start, base[i].r = b[0]; base[i].g = b[1]; base[i].b = b[2]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + base[i].a = 255; +#endif } _cursorPaletteDisabled = false; @@ -1707,22 +1784,30 @@ void SurfaceSdlGraphicsManager::setMousePos(int x, int y) { } void SurfaceSdlGraphicsManager::warpMouse(int x, int y) { - int y1 = y; - // Don't change actual mouse position, when mouse is outside of our window (in case of windowed mode) - if (!(SDL_GetAppState( ) & SDL_APPMOUSEFOCUS)) { + if (!_window->hasMouseFocus()) { setMousePos(x, y); // but change game cursor position return; } + int x1 = x, y1 = y; if (_videoMode.aspectRatioCorrection && !_overlayVisible) - y1 = real2Aspect(y); + y1 = real2Aspect(y1); if (_mouseCurState.x != x || _mouseCurState.y != y) { - if (!_overlayVisible) - SDL_WarpMouse(x * _videoMode.scaleFactor, y1 * _videoMode.scaleFactor); - else - SDL_WarpMouse(x, y1); + if (!_overlayVisible) { + x1 *= _videoMode.scaleFactor; + y1 *= _videoMode.scaleFactor; + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + // Transform our coordinates in "virtual" output coordinate space into + // actual output coordinate space. + x1 = x1 * _windowWidth / _videoMode.hardwareWidth; + y1 = y1 * _windowHeight / _videoMode.hardwareHeight; +#endif + + _window->warpMouseInWindow(x1, y1); // SDL_WarpMouse() generates a mouse movement event, so // setMousePos() would be called eventually. However, the @@ -1980,7 +2065,7 @@ void SurfaceSdlGraphicsManager::undrawMouse() { return; if (_mouseBackup.w != 0 && _mouseBackup.h != 0) - addDirtyRect(x, y - _currentShakePos, _mouseBackup.w, _mouseBackup.h); + addDirtyRect(x, y, _mouseBackup.w, _mouseBackup.h); } void SurfaceSdlGraphicsManager::drawMouse() { @@ -2021,9 +2106,7 @@ void SurfaceSdlGraphicsManager::drawMouse() { // We draw the pre-scaled cursor image, so now we need to adjust for // scaling, shake position and aspect ratio correction manually. - if (!_overlayVisible) { - dst.y += _currentShakePos; - } + dst.y += _currentShakePos; if (_videoMode.aspectRatioCorrection && !_overlayVisible) dst.y = real2Aspect(dst.y); @@ -2056,26 +2139,11 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) { Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends - uint i; - - // Lock the OSD surface for drawing - if (SDL_LockSurface(_osdSurface)) - error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); - - Graphics::Surface dst; - dst.init(_osdSurface->w, _osdSurface->h, _osdSurface->pitch, _osdSurface->pixels, - Graphics::PixelFormat(_osdSurface->format->BytesPerPixel, - 8 - _osdSurface->format->Rloss, 8 - _osdSurface->format->Gloss, - 8 - _osdSurface->format->Bloss, 8 - _osdSurface->format->Aloss, - _osdSurface->format->Rshift, _osdSurface->format->Gshift, - _osdSurface->format->Bshift, _osdSurface->format->Ashift)); + removeOSDMessage(); // The font we are going to use: const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont); - // Clear everything with the "transparent" color, i.e. the colorkey - SDL_FillRect(_osdSurface, 0, kOSDColorKey); - // Split the message into separate lines. Common::Array<Common::String> lines; const char *ptr; @@ -2094,44 +2162,184 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) { const int lineHeight = font->getFontHeight() + 2 * lineSpacing; int width = 0; int height = lineHeight * lines.size() + 2 * vOffset; + uint i; for (i = 0; i < lines.size(); i++) { width = MAX(width, font->getStringWidth(lines[i]) + 14); } // Clip the rect - if (width > dst.w) - width = dst.w; - if (height > dst.h) - height = dst.h; + if (width > _osdSurface->w) + width = _osdSurface->w; + if (height > _osdSurface->h) + height = _osdSurface->h; + + _osdMessageSurface = SDL_CreateRGBSurface( + SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, + width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF + ); + + // Lock the surface + if (SDL_LockSurface(_osdMessageSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); // Draw a dark gray rect - // TODO: Rounded corners ? Border? - SDL_Rect osdRect; - osdRect.x = (dst.w - width) / 2; - osdRect.y = (dst.h - height) / 2; - osdRect.w = width; - osdRect.h = height; - SDL_FillRect(_osdSurface, &osdRect, SDL_MapRGB(_osdSurface->format, 64, 64, 64)); + // TODO: Rounded corners ? Border? + SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64)); + + Graphics::Surface dst; + dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels, + Graphics::PixelFormat(_osdMessageSurface->format->BytesPerPixel, + 8 - _osdMessageSurface->format->Rloss, 8 - _osdMessageSurface->format->Gloss, + 8 - _osdMessageSurface->format->Bloss, 8 - _osdMessageSurface->format->Aloss, + _osdMessageSurface->format->Rshift, _osdMessageSurface->format->Gshift, + _osdMessageSurface->format->Bshift, _osdMessageSurface->format->Ashift)); // Render the message, centered, and in white for (i = 0; i < lines.size(); i++) { font->drawString(&dst, lines[i], - osdRect.x, osdRect.y + i * lineHeight + vOffset + lineSpacing, osdRect.w, - SDL_MapRGB(_osdSurface->format, 255, 255, 255), - Graphics::kTextAlignCenter); + 0, 0 + i * lineHeight + vOffset + lineSpacing, width, + SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255), + Graphics::kTextAlignCenter); + } + + // Finished drawing, so unlock the OSD message surface + SDL_UnlockSurface(_osdMessageSurface); + + // Init the OSD display parameters, and the fade out + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; + _osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay; + + // Ensure a full redraw takes place next time the screen is updated + _forceFull = true; +} + +void SurfaceSdlGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + assert(_transactionMode == kTransactionNone); + assert(buf); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + // Mark that area as "dirty" + addDirtyRect(x, y, w, h, true); + +#ifdef USE_RGB_COLOR + byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x * _osdSurface->format->BytesPerPixel; + if (_videoMode.screenWidth == w && pitch == _osdSurface->pitch) { + memcpy(dst, buf, h*pitch); + } else { + const byte *src = (const byte *)buf; + do { + memcpy(dst, src, w * _osdSurface->format->BytesPerPixel); + src += pitch; + dst += _osdSurface->pitch; + } while (--h); + } +#else + byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x; + if (_osdSurface->pitch == pitch && pitch == w) { + memcpy(dst, buf, h*w); + } else { + const byte *src = (const byte *)buf; + do { + memcpy(dst, src, w); + src += pitch; + dst += _osdSurface->pitch; + } while (--h); } +#endif // Finished drawing, so unlock the OSD surface again SDL_UnlockSurface(_osdSurface); +} - // Init the OSD display parameters, and the fade out - _osdAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; - _osdFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay; - SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha); +void SurfaceSdlGraphicsManager::clearOSD() { + assert(_transactionMode == kTransactionNone); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + // Clear everything with the "transparent" color, i.e. the colorkey + SDL_FillRect(_osdSurface, 0, kOSDColorKey); + + // Finished drawing, so unlock the OSD surface again + SDL_UnlockSurface(_osdSurface); + + // Remove OSD message as well + removeOSDMessage(); // Ensure a full redraw takes place next time the screen is updated _forceFull = true; } + +void SurfaceSdlGraphicsManager::removeOSDMessage() { + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + //remove previous message + if (_osdMessageSurface) { + SDL_Rect osdRect; + osdRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2; + osdRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2; + osdRect.w = _osdMessageSurface->w; + osdRect.h = _osdMessageSurface->h; + SDL_FillRect(_osdSurface, &osdRect, kOSDColorKey); + SDL_FreeSurface(_osdMessageSurface); + } + + _osdMessageSurface = NULL; + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; + + // Finished drawing, so unlock the OSD surface again + SDL_UnlockSurface(_osdSurface); +} + +void SurfaceSdlGraphicsManager::blitOSDMessage(SDL_Rect dstRect) { + SDL_Surface *src = _osdMessageSurface; + SDL_Surface *dst = _osdSurface; + Graphics::PixelFormat srcFormat = getSurfaceFormat(src); + Graphics::PixelFormat dstFormat = _osdFormat; + for (int y = 0; y < dstRect.h; y++) { + const byte *srcRow = (const byte *)((const byte *)(src->pixels) + y * src->pitch); //src (x, y) == (0, 0) + byte *dstRow = (byte *)((const byte *)(dst->pixels) + (dstRect.y + y) * dst->pitch + dstRect.x * dstFormat.bytesPerPixel); + + for (int x = 0; x < dstRect.w; x++) { + uint32 srcColor; + if (dst->format->BytesPerPixel == 2) + srcColor = READ_UINT16(srcRow); + else if (dst->format->BytesPerPixel == 3) + srcColor = READ_UINT24(srcRow); + else + srcColor = READ_UINT32(srcRow); + + srcRow += srcFormat.bytesPerPixel; + + // Convert that color to the new format + byte r, g, b, a; + srcFormat.colorToARGB(srcColor, a, r, g, b); + a = _osdMessageAlpha; //this is the important line, because apart from that this is plain surface copying + uint32 color = dstFormat.ARGBToColor(a, r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } +} + +Graphics::PixelFormat SurfaceSdlGraphicsManager::getOSDFormat() { + return _osdFormat; +} #endif bool SurfaceSdlGraphicsManager::handleScalerHotkeys(Common::KeyCode key) { @@ -2303,7 +2511,25 @@ void SurfaceSdlGraphicsManager::notifyVideoExpose() { _forceFull = true; } +#ifdef USE_SDL_RESIZABLE_WINDOW +void SurfaceSdlGraphicsManager::notifyResize(const uint width, const uint height) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + setWindowResolution(width, height); +#endif +} +#endif + void SurfaceSdlGraphicsManager::transformMouseCoordinates(Common::Point &point) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + // In SDL2 the actual output resolution might be different from what we + // requested. Thus, we transform the coordinates from actual output + // coordinate space into the "virtual" output coordinate space. + // Please note that we ignore the possible existence of black bars here, + // this avoids the feeling of stickyness to black bars. + point.x = point.x * _videoMode.hardwareWidth / _windowWidth; + point.y = point.y * _videoMode.hardwareHeight / _windowHeight; +#endif + if (!_overlayVisible) { point.x /= _videoMode.scaleFactor; point.y /= _videoMode.scaleFactor; @@ -2317,4 +2543,114 @@ void SurfaceSdlGraphicsManager::notifyMousePos(Common::Point mouse) { setMousePos(mouse.x, mouse.y); } +#if SDL_VERSION_ATLEAST(2, 0, 0) +void SurfaceSdlGraphicsManager::deinitializeRenderer() { + SDL_DestroyTexture(_screenTexture); + _screenTexture = nullptr; + + SDL_DestroyRenderer(_renderer); + _renderer = nullptr; + + _window->destroyWindow(); +} + +void SurfaceSdlGraphicsManager::setWindowResolution(int width, int height) { + _windowWidth = width; + _windowHeight = height; + + // We expect full screen resolution as inputs coming from the event system. + _eventSource->resetKeyboadEmulation(_windowWidth - 1, _windowHeight - 1); + + // Calculate the "viewport" for the actual area we draw in. In fullscreen + // we can easily get a different resolution than what we requested. In + // this case, we add black bars if necessary to assure the aspect ratio + // is preserved. + const frac_t outputAspect = intToFrac(_windowWidth) / _windowHeight; + const frac_t desiredAspect = intToFrac(_videoMode.hardwareWidth) / _videoMode.hardwareHeight; + + _viewport.w = _windowWidth; + _viewport.h = _windowHeight; + + // Adjust one dimension for mantaining the aspect ratio. + if (abs(outputAspect - desiredAspect) >= (int)(FRAC_ONE / 1000)) { + if (outputAspect < desiredAspect) { + _viewport.h = _videoMode.hardwareHeight * _windowWidth / _videoMode.hardwareWidth; + } else if (outputAspect > desiredAspect) { + _viewport.w = _videoMode.hardwareWidth * _windowHeight / _videoMode.hardwareHeight; + } + } + + _viewport.x = (_windowWidth - _viewport.w) / 2; + _viewport.y = (_windowHeight - _viewport.h) / 2; + + // Force a full redraw because we changed the viewport. + _forceFull = true; +} + +SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { + deinitializeRenderer(); + + uint32 createWindowFlags = 0; +#ifdef USE_SDL_RESIZABLE_WINDOW + createWindowFlags |= SDL_WINDOW_RESIZABLE; +#endif + if ((flags & SDL_FULLSCREEN) != 0) { + createWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + if (!_window->createWindow(width, height, createWindowFlags)) { + return nullptr; + } + + _renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, 0); + if (!_renderer) { + deinitializeRenderer(); + return nullptr; + } + + SDL_GetWindowSize(_window->getSDLWindow(), &_windowWidth, &_windowHeight); + setWindowResolution(_windowWidth, _windowHeight); + + _screenTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, width, height); + if (!_screenTexture) { + deinitializeRenderer(); + return nullptr; + } + + SDL_Surface *screen = SDL_CreateRGBSurface(0, width, height, 16, 0xF800, 0x7E0, 0x1F, 0); + if (!screen) { + deinitializeRenderer(); + return nullptr; + } else { + return screen; + } +} + +void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) { + SDL_UpdateTexture(_screenTexture, nullptr, screen->pixels, screen->pitch); + + SDL_RenderClear(_renderer); + SDL_RenderCopy(_renderer, _screenTexture, NULL, &_viewport); + SDL_RenderPresent(_renderer); +} +#endif // SDL_VERSION_ATLEAST(2, 0, 0) + +Graphics::PixelFormat SurfaceSdlGraphicsManager::getSurfaceFormat(SDL_Surface *surface) { + Graphics::PixelFormat format; + if (surface) { + format.bytesPerPixel = surface->format->BytesPerPixel; + + format.rLoss = surface->format->Rloss; + format.gLoss = surface->format->Gloss; + format.bLoss = surface->format->Bloss; + format.aLoss = surface->format->Aloss; + + format.rShift = surface->format->Rshift; + format.gShift = surface->format->Gshift; + format.bShift = surface->format->Bshift; + format.aShift = surface->format->Ashift; + } + return format; +} + #endif diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h index 49bd66b3e5..d8f826aca0 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.h +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h @@ -39,6 +39,15 @@ #define USE_SDL_DEBUG_FOCUSRECT #endif +// We have (some) support for resizable windows when SDL2 is used. However +// the overlay still uses the resolution setup with SDL_SetVideoMode. This +// makes the GUI look subpar when the user resizes the window. In addition +// we do not adapt the scale factor right now. Thus, we disable this code +// path for now. +#if SDL_VERSION_ATLEAST(2, 0, 0) && 0 +#define USE_SDL_RESIZABLE_WINDOW +#endif + #if !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) // Uncomment this to enable the 'on screen display' code. #define USE_OSD 1 @@ -77,7 +86,7 @@ public: */ class SurfaceSdlGraphicsManager : public SdlGraphicsManager, public Common::EventObserver { public: - SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource); + SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); virtual ~SurfaceSdlGraphicsManager(); virtual void activateManager(); @@ -136,6 +145,9 @@ public: #ifdef USE_OSD virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); #endif // Override from Common::EventObserver @@ -143,17 +155,22 @@ public: // SdlGraphicsManager interface virtual void notifyVideoExpose(); +#ifdef USE_SDL_RESIZABLE_WINDOW + virtual void notifyResize(const uint width, const uint height); +#endif virtual void transformMouseCoordinates(Common::Point &point); virtual void notifyMousePos(Common::Point mouse); protected: #ifdef USE_OSD - /** Surface containing the OSD message */ + /** Surface containing the OSD */ SDL_Surface *_osdSurface; - /** Transparency level of the OSD */ - uint8 _osdAlpha; + /** Surface containing the OSD message */ + SDL_Surface *_osdMessageSurface; + /** Transparency level of the OSD message */ + uint8 _osdMessageAlpha; /** When to start the fade out */ - uint32 _osdFadeStartTime; + uint32 _osdMessageFadeStartTime; /** Enum with OSD options */ enum { kOSDFadeOutDelay = 2 * 1000, /** < Delay before the OSD is faded out (in milliseconds) */ @@ -161,11 +178,30 @@ protected: kOSDColorKey = 1, /** < Transparent color key */ kOSDInitialAlpha = 80 /** < Initial alpha level, in percent */ }; + /** OSD pixel format */ + Graphics::PixelFormat _osdFormat; + + void removeOSDMessage(); + void blitOSDMessage(SDL_Rect dstRect); #endif /** Hardware screen */ SDL_Surface *_hwscreen; +#if SDL_VERSION_ATLEAST(2, 0, 0) + /* SDL2 features a different API for 2D graphics. We create a wrapper + * around this API to keep the code paths as close as possible. */ + SDL_Renderer *_renderer; + SDL_Texture *_screenTexture; + SDL_Rect _viewport; + int _windowWidth, _windowHeight; + void deinitializeRenderer(); + void setWindowResolution(int width, int height); + + SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags); + void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects); +#endif + /** Unseen game screen */ SDL_Surface *_screen; #ifdef USE_RGB_COLOR @@ -225,6 +261,23 @@ protected: }; VideoState _videoMode, _oldVideoMode; +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + /** + * Original BPP to restore the video mode on unload. + * + * This is required to make listing video modes for the OpenGL output work + * on Windows 8+. On these systems OpenGL modes are only available for + * 32bit formats. However, we setup a 16bit format and thus mode listings + * for OpenGL will return an empty list afterwards. + * + * In theory we might require this behavior on non-Win32 platforms too. + * However, SDL sometimes gives us invalid pixel formats for X11 outputs + * causing crashes when trying to setup the original pixel format. + * See bug #7038 "IRIX: X BadMatch when trying to start any 640x480 game". + */ + uint8 _originalBitsPerPixel; +#endif + /** Force full redraw on next updateScreen */ bool _forceFull; @@ -315,6 +368,8 @@ protected: Common::Rect _focusRect; #endif + static Graphics::PixelFormat getSurfaceFormat(SDL_Surface *surface); + virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false); virtual void drawMouse(); diff --git a/backends/graphics/symbiansdl/symbiansdl-graphics.cpp b/backends/graphics/symbiansdl/symbiansdl-graphics.cpp index e339fecd1c..c17cfd5efa 100644 --- a/backends/graphics/symbiansdl/symbiansdl-graphics.cpp +++ b/backends/graphics/symbiansdl/symbiansdl-graphics.cpp @@ -27,8 +27,8 @@ #include "backends/graphics/symbiansdl/symbiansdl-graphics.h" #include "backends/platform/symbian/src/SymbianActions.h" -SymbianSdlGraphicsManager::SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource) { +SymbianSdlGraphicsManager::SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window) { } int SymbianSdlGraphicsManager::getDefaultGraphicsMode() const { diff --git a/backends/graphics/symbiansdl/symbiansdl-graphics.h b/backends/graphics/symbiansdl/symbiansdl-graphics.h index f514db286c..fb9a49a834 100644 --- a/backends/graphics/symbiansdl/symbiansdl-graphics.h +++ b/backends/graphics/symbiansdl/symbiansdl-graphics.h @@ -27,7 +27,7 @@ class SymbianSdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource); + SymbianSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); public: virtual bool hasFeature(OSystem::Feature f); diff --git a/backends/graphics/wincesdl/wincesdl-graphics.cpp b/backends/graphics/wincesdl/wincesdl-graphics.cpp index 8e4685dbd8..07f7d47262 100644 --- a/backends/graphics/wincesdl/wincesdl-graphics.cpp +++ b/backends/graphics/wincesdl/wincesdl-graphics.cpp @@ -42,8 +42,8 @@ #include "backends/platform/wince/CEScaler.h" #include "backends/platform/wince/CEgui/ItemAction.h" -WINCESdlGraphicsManager::WINCESdlGraphicsManager(SdlEventSource *sdlEventSource) - : SurfaceSdlGraphicsManager(sdlEventSource), +WINCESdlGraphicsManager::WINCESdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : SurfaceSdlGraphicsManager(sdlEventSource, window), _panelInitialized(false), _noDoubleTapRMB(false), _noDoubleTapPT(false), _toolbarHighDrawn(false), _newOrientation(0), _orientationLandscape(0), _panelVisible(true), _saveActiveToolbar(NAME_MAIN_PANEL), _panelStateForced(false), diff --git a/backends/graphics/wincesdl/wincesdl-graphics.h b/backends/graphics/wincesdl/wincesdl-graphics.h index 50b422c10d..9316c69e44 100644 --- a/backends/graphics/wincesdl/wincesdl-graphics.h +++ b/backends/graphics/wincesdl/wincesdl-graphics.h @@ -41,7 +41,7 @@ extern bool _hasSmartphoneResolution; class WINCESdlGraphicsManager : public SurfaceSdlGraphicsManager { public: - WINCESdlGraphicsManager(SdlEventSource *sdlEventSource); + WINCESdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); const OSystem::GraphicsMode *getSupportedGraphicsModes() const; void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL); diff --git a/backends/log/log.cpp b/backends/log/log.cpp index 693399bae5..e37296aada 100644 --- a/backends/log/log.cpp +++ b/backends/log/log.cpp @@ -93,10 +93,12 @@ void Log::print(const char *message, const bool printTime) { void Log::printTimeStamp() { TimeDate date; + int curMonth; _system->getTimeAndDate(date); + curMonth = date.tm_mon + 1; // month is base 0, we need base 1 (1 = january and so on) _stream->writeString(Common::String::format("[%d-%02d-%02d %02d:%02d:%02d] ", - date.tm_year + 1900, date.tm_mon, date.tm_mday, + date.tm_year + 1900, curMonth, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec)); } diff --git a/backends/midi/timidity.cpp b/backends/midi/timidity.cpp index d10b808bdb..497138850a 100644 --- a/backends/midi/timidity.cpp +++ b/backends/midi/timidity.cpp @@ -316,6 +316,7 @@ int MidiDriver_TIMIDITY::connect_to_server(const char* hostname, unsigned short if (connect(fd, (struct sockaddr *)&in, sizeof(in)) < 0) { warning("TiMidity: connect(): %s", strerror(errno)); + ::close(fd); return -1; } diff --git a/backends/midi/windows.cpp b/backends/midi/windows.cpp index e2b327ffa7..52a46200cb 100644 --- a/backends/midi/windows.cpp +++ b/backends/midi/windows.cpp @@ -185,6 +185,9 @@ MusicDevices WindowsMusicPlugin::getDevices() const { deviceNames.push_back(tmp.szPname); } + // Limit us to the number of actually retrieved devices. + numDevs = deviceNames.size(); + // Check for non-unique device names. This may happen if someone has devices with identical // names (e. g. more than one USB device of the exact same hardware type). It seems that this // does happen in reality sometimes. We generate index numbers for these devices. diff --git a/backends/mixer/doublebuffersdl/doublebuffersdl-mixer.cpp b/backends/mixer/doublebuffersdl/doublebuffersdl-mixer.cpp index d59b0ebdfc..e5f63dc908 100644 --- a/backends/mixer/doublebuffersdl/doublebuffersdl-mixer.cpp +++ b/backends/mixer/doublebuffersdl/doublebuffersdl-mixer.cpp @@ -53,7 +53,11 @@ void DoubleBufferSDLMixerManager::startAudio() { _soundThreadIsRunning = true; // Finally start the thread +#if SDL_VERSION_ATLEAST(2, 0, 0) + _soundThread = SDL_CreateThread(mixerProducerThreadEntry, "ScummVM Double Buffer Mixer", this); +#else _soundThread = SDL_CreateThread(mixerProducerThreadEntry, this); +#endif SdlMixerManager::startAudio(); } diff --git a/backends/mixer/sdl/sdl-mixer.cpp b/backends/mixer/sdl/sdl-mixer.cpp index e3b15b8c59..0ca3231892 100644 --- a/backends/mixer/sdl/sdl-mixer.cpp +++ b/backends/mixer/sdl/sdl-mixer.cpp @@ -30,8 +30,10 @@ #include "common/config-manager.h" #include "common/textconsole.h" -#ifdef GP2X +#if defined(GP2X) #define SAMPLES_PER_SEC 11025 +#elif defined(PLAYSTATION3) +#define SAMPLES_PER_SEC 48000 #else #define SAMPLES_PER_SEC 44100 #endif @@ -57,10 +59,14 @@ void SdlMixerManager::init() { error("Could not initialize SDL: %s", SDL_GetError()); } +#if SDL_VERSION_ATLEAST(2, 0, 0) + const char *sdlDriverName = SDL_GetCurrentAudioDriver(); +#else const int maxNameLen = 20; char sdlDriverName[maxNameLen]; sdlDriverName[0] = '\0'; SDL_AudioDriverName(sdlDriverName, maxNameLen); +#endif debug(1, "Using SDL Audio Driver \"%s\"", sdlDriverName); // Get the desired audio specs @@ -74,34 +80,49 @@ void SdlMixerManager::init() { if (SDL_OpenAudio(&fmt, &_obtained) != 0) { warning("Could not open audio device: %s", SDL_GetError()); + // The mixer is not marked as ready _mixer = new Audio::MixerImpl(g_system, desired.freq); - assert(_mixer); - _mixer->setReady(false); - } else { - debug(1, "Output sample rate: %d Hz", _obtained.freq); - if (_obtained.freq != desired.freq) - warning("SDL mixer output sample rate: %d differs from desired: %d", _obtained.freq, desired.freq); + return; + } + + // The obtained sample format is not supported by the mixer, call + // SDL_OpenAudio again with NULL as the second argument to force + // SDL to do resampling to the desired audio spec. + if (_obtained.format != desired.format) { + debug(1, "SDL mixer sound format: %d differs from desired: %d", _obtained.format, desired.format); + SDL_CloseAudio(); + + if (SDL_OpenAudio(&fmt, NULL) != 0) { + warning("Could not open audio device: %s", SDL_GetError()); - debug(1, "Output buffer size: %d samples", _obtained.samples); - if (_obtained.samples != desired.samples) - warning("SDL mixer output buffer size: %d differs from desired: %d", _obtained.samples, desired.samples); + // The mixer is not marked as ready + _mixer = new Audio::MixerImpl(g_system, desired.freq); + return; + } - if (_obtained.format != desired.format) - warning("SDL mixer sound format: %d differs from desired: %d", _obtained.format, desired.format); + _obtained = desired; + } + + debug(1, "Output sample rate: %d Hz", _obtained.freq); + if (_obtained.freq != desired.freq) + warning("SDL mixer output sample rate: %d differs from desired: %d", _obtained.freq, desired.freq); + + debug(1, "Output buffer size: %d samples", _obtained.samples); + if (_obtained.samples != desired.samples) + warning("SDL mixer output buffer size: %d differs from desired: %d", _obtained.samples, desired.samples); #ifndef __SYMBIAN32__ - // The SymbianSdlMixerManager does stereo->mono downmixing, - // but otherwise we require stereo output. - if (_obtained.channels != 2) - error("SDL mixer output requires stereo output device"); + // The SymbianSdlMixerManager does stereo->mono downmixing, + // but otherwise we require stereo output. + if (_obtained.channels != 2) + error("SDL mixer output requires stereo output device"); #endif - _mixer = new Audio::MixerImpl(g_system, _obtained.freq); - assert(_mixer); - _mixer->setReady(true); + _mixer = new Audio::MixerImpl(g_system, _obtained.freq); + assert(_mixer); + _mixer->setReady(true); - startAudio(); - } + startAudio(); } SDL_AudioSpec SdlMixerManager::getAudioSpec(uint32 outputRate) { diff --git a/backends/mixer/sdl13/sdl13-mixer.cpp b/backends/mixer/sdl13/sdl13-mixer.cpp deleted file mode 100644 index dc38242bde..0000000000 --- a/backends/mixer/sdl13/sdl13-mixer.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* 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/scummsys.h" - -#if defined(SDL_BACKEND) - -#include "backends/mixer/sdl13/sdl13-mixer.h" -#include "common/debug.h" -#include "common/system.h" -#include "common/config-manager.h" -#include "common/textconsole.h" - -#ifdef GP2X -#define SAMPLES_PER_SEC 11025 -#else -#define SAMPLES_PER_SEC 44100 -#endif - -Sdl13MixerManager::Sdl13MixerManager() - : - SdlMixerManager(), - _device(0) { - -} - -Sdl13MixerManager::~Sdl13MixerManager() { - _mixer->setReady(false); - - SDL_CloseAudioDevice(_device); - - delete _mixer; -} - -void Sdl13MixerManager::init() { - // Start SDL Audio subsystem - if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) { - error("Could not initialize SDL: %s", SDL_GetError()); - } - - // Get the desired audio specs - SDL_AudioSpec desired = getAudioSpec(SAMPLES_PER_SEC); - - // Start SDL audio with the desired specs - _device = SDL_OpenAudioDevice(NULL, 0, &desired, &_obtained, - SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - - if (_device <= 0) { - warning("Could not open audio device: %s", SDL_GetError()); - - _mixer = new Audio::MixerImpl(g_system, desired.freq); - assert(_mixer); - _mixer->setReady(false); - } else { - debug(1, "Output sample rate: %d Hz", _obtained.freq); - - _mixer = new Audio::MixerImpl(g_system, _obtained.freq); - assert(_mixer); - _mixer->setReady(true); - - startAudio(); - } -} - -void Sdl13MixerManager::startAudio() { - // Start the sound system - SDL_PauseAudioDevice(_device, 0); -} - -void Sdl13MixerManager::suspendAudio() { - SDL_CloseAudioDevice(_device); - _audioSuspended = true; -} - -int Sdl13MixerManager::resumeAudio() { - if (!_audioSuspended) - return -2; - - _device = SDL_OpenAudioDevice(NULL, 0, &_obtained, NULL, 0); - if (_device <= 0) { - return -1; - } - - SDL_PauseAudioDevice(_device, 0); - _audioSuspended = false; - return 0; -} - -#endif diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp index d8be9ca7ed..e1bdf15571 100644 --- a/backends/modular-backend.cpp +++ b/backends/modular-backend.cpp @@ -241,6 +241,18 @@ void ModularBackend::displayMessageOnOSD(const char *msg) { _graphicsManager->displayMessageOnOSD(msg); } +void ModularBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + _graphicsManager->copyRectToOSD(buf, pitch, x, y, w, h); +} + +void ModularBackend::clearOSD() { + _graphicsManager->clearOSD(); +} + +Graphics::PixelFormat ModularBackend::getOSDFormat() { + return _graphicsManager->getOSDFormat(); +} + void ModularBackend::quit() { exit(0); } diff --git a/backends/modular-backend.h b/backends/modular-backend.h index 20e8b7357d..9cde27915f 100644 --- a/backends/modular-backend.h +++ b/backends/modular-backend.h @@ -127,6 +127,9 @@ public: virtual void quit(); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); //@} diff --git a/backends/module.mk b/backends/module.mk index 34e2928419..c402a10a90 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -3,6 +3,7 @@ MODULE := backends MODULE_OBJS := \ base-backend.o \ modular-backend.o \ + audiocd/audiocd-stream.o \ audiocd/default/default-audiocd.o \ events/default/default-events.o \ fs/abstract-fs.o \ @@ -18,6 +19,65 @@ MODULE_OBJS := \ saves/default/default-saves.o \ timer/default/default-timer.o +ifdef USE_LIBCURL +MODULE_OBJS += \ + cloud/cloudmanager.o \ + cloud/iso8601.o \ + cloud/storage.o \ + cloud/storagefile.o \ + cloud/downloadrequest.o \ + cloud/folderdownloadrequest.o \ + cloud/savessyncrequest.o \ + cloud/box/boxstorage.o \ + cloud/box/boxlistdirectorybyidrequest.o \ + cloud/box/boxtokenrefresher.o \ + cloud/box/boxuploadrequest.o \ + cloud/dropbox/dropboxstorage.o \ + cloud/dropbox/dropboxcreatedirectoryrequest.o \ + cloud/dropbox/dropboxinforequest.o \ + cloud/dropbox/dropboxlistdirectoryrequest.o \ + cloud/dropbox/dropboxuploadrequest.o \ + cloud/googledrive/googledrivelistdirectorybyidrequest.o \ + cloud/googledrive/googledrivestorage.o \ + cloud/googledrive/googledrivetokenrefresher.o \ + cloud/googledrive/googledriveuploadrequest.o \ + cloud/id/idstorage.o \ + cloud/id/idcreatedirectoryrequest.o \ + cloud/id/iddownloadrequest.o \ + cloud/id/idlistdirectoryrequest.o \ + cloud/id/idresolveidrequest.o \ + cloud/id/idstreamfilerequest.o \ + cloud/onedrive/onedrivestorage.o \ + cloud/onedrive/onedrivecreatedirectoryrequest.o \ + cloud/onedrive/onedrivetokenrefresher.o \ + cloud/onedrive/onedrivelistdirectoryrequest.o \ + cloud/onedrive/onedriveuploadrequest.o \ + networking/curl/connectionmanager.o \ + networking/curl/networkreadstream.o \ + networking/curl/cloudicon.o \ + networking/curl/curlrequest.o \ + networking/curl/curljsonrequest.o \ + networking/curl/request.o +endif + +ifdef USE_SDL_NET +MODULE_OBJS += \ + networking/sdl_net/client.o \ + networking/sdl_net/getclienthandler.o \ + networking/sdl_net/handlers/createdirectoryhandler.o \ + networking/sdl_net/handlers/downloadfilehandler.o \ + networking/sdl_net/handlers/filesajaxpagehandler.o \ + networking/sdl_net/handlers/filesbasehandler.o \ + networking/sdl_net/handlers/filespagehandler.o \ + networking/sdl_net/handlers/indexpagehandler.o \ + networking/sdl_net/handlers/listajaxhandler.o \ + networking/sdl_net/handlers/resourcehandler.o \ + networking/sdl_net/handlers/uploadfilehandler.o \ + networking/sdl_net/handlerutils.o \ + networking/sdl_net/localwebserver.o \ + networking/sdl_net/reader.o \ + networking/sdl_net/uploadfileclienthandler.o +endif ifdef USE_ELF_LOADER MODULE_OBJS += \ @@ -52,10 +112,16 @@ endif # OpenGL specific source files. ifdef USE_OPENGL MODULE_OBJS += \ + graphics/opengl/context.o \ graphics/opengl/debug.o \ - graphics/opengl/extensions.o \ + graphics/opengl/framebuffer.o \ graphics/opengl/opengl-graphics.o \ - graphics/opengl/texture.o + graphics/opengl/shader.o \ + graphics/opengl/texture.o \ + graphics/opengl/pipelines/clut8.o \ + graphics/opengl/pipelines/fixed.o \ + graphics/opengl/pipelines/pipeline.o \ + graphics/opengl/pipelines/shader.o endif # SDL specific source files. @@ -72,8 +138,8 @@ MODULE_OBJS += \ plugins/sdl/sdl-provider.o \ timer/sdl/sdl-timer.o -# SDL 1.3 removed audio CD support -ifndef USE_SDL13 +# SDL 2 removed audio CD support +ifndef USE_SDL2 MODULE_OBJS += \ audiocd/sdl/sdl-audiocd.o endif @@ -84,10 +150,48 @@ MODULE_OBJS += \ endif endif +# openUrl +ifeq ($(BACKEND),android) +MODULE_OBJS += \ + networking/browser/openurl-android.o +else +ifdef MACOSX +MODULE_OBJS += \ + networking/browser/openurl-osx.o +else +ifdef WIN32 +MODULE_OBJS += \ + networking/browser/openurl-windows.o +else + ifdef POSIX + MODULE_OBJS += \ + networking/browser/openurl-posix.o + else + # create_project doesn't know something about `else` + ifndef WIN32 + MODULE_OBJS += \ + networking/browser/openurl-default.o + endif + endif +endif +endif +endif + +# Connection::isLimited +ifeq ($(BACKEND),android) +MODULE_OBJS += \ + networking/connection/islimited-android.o +else +MODULE_OBJS += \ + networking/connection/islimited-default.o +endif + ifdef POSIX MODULE_OBJS += \ fs/posix/posix-fs.o \ fs/posix/posix-fs-factory.o \ + fs/chroot/chroot-fs-factory.o \ + fs/chroot/chroot-fs.o \ plugins/posix/posix-provider.o \ saves/posix/posix-saves.o \ taskbar/unity/unity-taskbar.o @@ -95,6 +199,7 @@ endif ifdef MACOSX MODULE_OBJS += \ + audiocd/macosx/macosx-audiocd.o \ midi/coreaudio.o \ midi/coremidi.o \ updates/macosx/macosx-updates.o \ @@ -103,14 +208,22 @@ endif ifdef WIN32 MODULE_OBJS += \ + audiocd/win32/win32-audiocd.o \ fs/windows/windows-fs.o \ fs/windows/windows-fs-factory.o \ midi/windows.o \ plugins/win32/win32-provider.o \ saves/windows/windows-saves.o \ + updates/win32/win32-updates.o \ taskbar/win32/win32-taskbar.o endif +ifeq ($(BACKEND),androidsdl) +MODULE_OBJS += \ + events/androidsdl/androidsdl-events.o \ + graphics/androidsdl/androidsdl-graphics.o +endif + ifdef AMIGAOS MODULE_OBJS += \ fs/amigaos4/amigaos4-fs.o \ @@ -123,8 +236,12 @@ MODULE_OBJS += \ fs/posix/posix-fs.o \ fs/posix/posix-fs-factory.o \ fs/ps3/ps3-fs-factory.o \ - events/ps3sdl/ps3sdl-events.o \ - mixer/sdl13/sdl13-mixer.o + events/ps3sdl/ps3sdl-events.o +endif + +ifdef USE_LINUXCD +MODULE_OBJS += \ + audiocd/linux/linux-audiocd.o endif ifeq ($(BACKEND),tizen) diff --git a/backends/networking/browser/openurl-android.cpp b/backends/networking/browser/openurl-android.cpp new file mode 100644 index 0000000000..64e683238b --- /dev/null +++ b/backends/networking/browser/openurl-android.cpp @@ -0,0 +1,35 @@ +/* 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 "backends/networking/browser/openurl.h" +#include "backends/platform/android/jni.h" + +namespace Networking { +namespace Browser { + +bool openUrl(const Common::String &url) { + return JNI::openUrl(url.c_str()); +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-default.cpp b/backends/networking/browser/openurl-default.cpp new file mode 100644 index 0000000000..c430953196 --- /dev/null +++ b/backends/networking/browser/openurl-default.cpp @@ -0,0 +1,36 @@ +/* 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 "backends/networking/browser/openurl.h" +#include "common/textconsole.h" + +namespace Networking { +namespace Browser { + +bool openUrl(const Common::String &url) { + warning("Networking::Browser::openUrl(): not implemented"); + return false; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-osx.cpp b/backends/networking/browser/openurl-osx.cpp new file mode 100644 index 0000000000..8d786d7fd2 --- /dev/null +++ b/backends/networking/browser/openurl-osx.cpp @@ -0,0 +1,49 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/browser/openurl.h" +#include <CoreFoundation/CFBundle.h> +#include <ApplicationServices/ApplicationServices.h> + +namespace Networking { +namespace Browser { + +using namespace std; + +bool openUrl(const Common::String &url) { + CFURLRef urlRef = CFURLCreateWithBytes ( + NULL, + (UInt8*)url.c_str(), + url.size(), + kCFStringEncodingASCII, + NULL + ); + int result = LSOpenCFURLRef(urlRef, 0); + CFRelease(urlRef); + return result == 0; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-posix.cpp b/backends/networking/browser/openurl-posix.cpp new file mode 100644 index 0000000000..429a379fcf --- /dev/null +++ b/backends/networking/browser/openurl-posix.cpp @@ -0,0 +1,77 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/browser/openurl.h" +#include "common/textconsole.h" +#include <stdlib.h> + +namespace Networking { +namespace Browser { + +namespace { +bool launch(const Common::String client, const Common::String &url) { + // FIXME: system's input must be heavily escaped + // well, when url's specified by user + // it's OK now (urls are hardcoded somewhere in GUI) + Common::String cmd = client + " " + url; + return (system(cmd.c_str()) != -1); +} +} + +bool openUrl(const Common::String &url) { + // inspired by Qt's "qdesktopservices_x11.cpp" + + // try "standards" + if (launch("xdg-open", url)) + return true; + if (launch(getenv("DEFAULT_BROWSER"), url)) + return true; + if (launch(getenv("BROWSER"), url)) + return true; + + // try desktop environment specific tools + if (launch("gnome-open", url)) // gnome + return true; + if (launch("kfmclient openURL", url)) // kde + return true; + if (launch("exo-open", url)) // xfce + return true; + + // try browser names + if (launch("firefox", url)) + return true; + if (launch("mozilla", url)) + return true; + if (launch("netscape", url)) + return true; + if (launch("opera", url)) + return true; + + warning("Networking::Browser::openUrl() (POSIX) failed to open URL"); + return false; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-windows.cpp b/backends/networking/browser/openurl-windows.cpp new file mode 100644 index 0000000000..53d76f076b --- /dev/null +++ b/backends/networking/browser/openurl-windows.cpp @@ -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. + * + */ + +#include "backends/networking/browser/openurl.h" +#include "common/textconsole.h" +#include <windows.h> +#include <Shellapi.h> + +namespace Networking { +namespace Browser { + +bool openUrl(const Common::String &url) { + const uint64 result = (uint64)ShellExecute(0, 0, /*(wchar_t*)nativeFilePath.utf16()*/url.c_str(), 0, 0, SW_SHOWNORMAL); + // ShellExecute returns a value greater than 32 if successful + if (result <= 32) { + warning("ShellExecute failed: error = %u", result); + return false; + } + return true; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl.h b/backends/networking/browser/openurl.h new file mode 100644 index 0000000000..15b4bf385b --- /dev/null +++ b/backends/networking/browser/openurl.h @@ -0,0 +1,41 @@ +/* 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 NETWORKING_BROWSER_OPENURL_H +#define NETWORKING_BROWSER_OPENURL_H + +#include "common/str.h" + +namespace Networking { +namespace Browser { + +/** + * Opens URL in default browser (if available on the target system). + * + * Returns true on success. + */ +bool openUrl(const Common::String &url); + +} // End of namespace Browser +} // End of namespace Networking + +#endif /*NETWORKING_BROWSER_OPENURL_H*/ diff --git a/backends/networking/connection/islimited-android.cpp b/backends/networking/connection/islimited-android.cpp new file mode 100644 index 0000000000..8989f218ec --- /dev/null +++ b/backends/networking/connection/islimited-android.cpp @@ -0,0 +1,35 @@ +/* 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 "backends/networking/connection/islimited.h" +#include "backends/platform/android/jni.h" + +namespace Networking { +namespace Connection { + +bool isLimited() { + return JNI::isConnectionLimited(); +} + +} // End of namespace Connection +} // End of namespace Networking + diff --git a/backends/networking/connection/islimited-default.cpp b/backends/networking/connection/islimited-default.cpp new file mode 100644 index 0000000000..a993077fff --- /dev/null +++ b/backends/networking/connection/islimited-default.cpp @@ -0,0 +1,36 @@ +/* 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 "backends/networking/connection/islimited.h" +#include "common/textconsole.h" + +namespace Networking { +namespace Connection { + +bool isLimited() { + warning("Networking::Connection::isLimited(): not limited by default"); + return false; +} + +} // End of namespace Connection +} // End of namespace Networking + diff --git a/backends/networking/connection/islimited.h b/backends/networking/connection/islimited.h new file mode 100644 index 0000000000..b23d31d157 --- /dev/null +++ b/backends/networking/connection/islimited.h @@ -0,0 +1,39 @@ +/* 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 NETWORKING_CONNECTION_ISLIMITED_H +#define NETWORKING_CONNECTION_ISLIMITED_H + +namespace Networking { +namespace Connection { + +/** +* Returns whether connection's limited (if available on the target system). +* +* Returns true if connection seems limited. +*/ +bool isLimited(); + +} // End of namespace Connection +} // End of namespace Networking + +#endif /*NETWORKING_CONNECTION_ISLIMITED_H*/ diff --git a/backends/networking/curl/cloudicon.cpp b/backends/networking/curl/cloudicon.cpp new file mode 100644 index 0000000000..1c1ecf2f85 --- /dev/null +++ b/backends/networking/curl/cloudicon.cpp @@ -0,0 +1,171 @@ +/* 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 "backends/networking/curl/cloudicon.h" +#include "backends/cloud/cloudmanager.h" +#include "common/memstream.h" +#include "gui/ThemeEngine.h" +#include "gui/gui-manager.h" +#include "image/png.h" + +namespace Networking { + +const float CloudIcon::ALPHA_STEP = 0.025; +const float CloudIcon::ALPHA_MAX = 1; +const float CloudIcon::ALPHA_MIN = 0.6; + +CloudIcon::CloudIcon(): + _wasVisible(false), _iconsInited(false), _showingDisabled(false), + _currentAlpha(0), _alphaRising(true), _disabledFrames(0) { + initIcons(); +} + +CloudIcon::~CloudIcon() {} + +bool CloudIcon::draw() { + bool stop = false; + initIcons(); + + if (CloudMan.isWorking() || _disabledFrames > 0) { + if (g_system) { + if (!_wasVisible) { + g_system->clearOSD(); + _wasVisible = true; + } + --_disabledFrames; + if (_alphaRising) { + if (_currentAlpha < ALPHA_MIN) + _currentAlpha += 5 * ALPHA_STEP; + else + _currentAlpha += ALPHA_STEP; + if (_currentAlpha > ALPHA_MAX) { + _currentAlpha = ALPHA_MAX; + _alphaRising = false; + } + } else { + _currentAlpha -= ALPHA_STEP; + if (_currentAlpha < ALPHA_MIN) { + _currentAlpha = ALPHA_MIN; + _alphaRising = true; + } + } + } else { + _wasVisible = false; + } + } else { + _wasVisible = false; + _currentAlpha -= 5 * ALPHA_STEP; + if (_currentAlpha <= 0) { + _currentAlpha = 0; + stop = true; + } + } + + if (g_system) { + Graphics::TransparentSurface *surface = &_icon; + makeAlphaIcon((_showingDisabled ? _disabledIcon : _icon), _currentAlpha); + if (_alphaIcon.getPixels()) + surface = &_alphaIcon; + if (surface && surface->getPixels()) { + int x = g_system->getOverlayWidth() - surface->w - 10, y = 10; + g_system->copyRectToOSD(surface->getPixels(), surface->pitch, x, y, surface->w, surface->h); + } + } + + if (stop) + _showingDisabled = false; + return stop; +} + +void CloudIcon::showDisabled() { + _showingDisabled = true; + _disabledFrames = 20 * 3; //3 seconds 20 fps +} + +#include "backends/networking/curl/cloudicon_data.h" +#include "backends/networking/curl/cloudicon_disabled_data.h" + +void CloudIcon::initIcons() { + if (_iconsInited) + return; + loadIcon(_icon, cloudicon_data, ARRAYSIZE(cloudicon_data)); + loadIcon(_disabledIcon, cloudicon_disabled_data, ARRAYSIZE(cloudicon_disabled_data)); + _iconsInited = true; +} + +void CloudIcon::loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size) { + Image::PNGDecoder decoder; + Common::MemoryReadStream stream(data, size); + if (!decoder.loadStream(stream)) + error("CloudIcon::loadIcon: error decoding PNG"); + + Graphics::TransparentSurface *s = new Graphics::TransparentSurface(*decoder.getSurface(), true); + if (s) { + Graphics::PixelFormat f = g_system->getOSDFormat(); + if (f != s->format) { + Graphics::TransparentSurface *s2 = s->convertTo(f); + if (s2) + icon.copyFrom(*s2); + else + warning("CloudIcon::loadIcon: failed converting TransparentSurface"); + delete s2; + } else { + icon.copyFrom(*s); + } + delete s; + } else { + warning("CloudIcon::loadIcon: failed reading TransparentSurface from PNGDecoder"); + } +} + +void CloudIcon::makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha) { + _alphaIcon.copyFrom(icon); + + byte *pixels = (byte *)_alphaIcon.getPixels(); + for (int y = 0; y < _alphaIcon.h; y++) { + byte *row = pixels + y * _alphaIcon.pitch; + for (int x = 0; x < _alphaIcon.w; x++) { + uint32 srcColor; + if (_alphaIcon.format.bytesPerPixel == 2) + srcColor = READ_UINT16(row); + else if (_alphaIcon.format.bytesPerPixel == 3) + srcColor = READ_UINT24(row); + else + srcColor = READ_UINT32(row); + + // Update color's alpha + byte r, g, b, a; + _alphaIcon.format.colorToARGB(srcColor, a, r, g, b); + a = (byte)(a * alpha); + uint32 color = _alphaIcon.format.ARGBToColor(a, r, g, b); + + if (_alphaIcon.format.bytesPerPixel == 2) + *((uint16 *)row) = color; + else + *((uint32 *)row) = color; + + row += _alphaIcon.format.bytesPerPixel; + } + } +} + +} // End of namespace Networking diff --git a/backends/networking/curl/cloudicon.h b/backends/networking/curl/cloudicon.h new file mode 100644 index 0000000000..7e4d7387a3 --- /dev/null +++ b/backends/networking/curl/cloudicon.h @@ -0,0 +1,70 @@ +/* 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 BACKENDS_NETWORKING_CURL_CLOUDICON_H +#define BACKENDS_NETWORKING_CURL_CLOUDICON_H + +#include "graphics/transparent_surface.h" + +namespace Networking { + +class CloudIcon { + static const float ALPHA_STEP, ALPHA_MAX, ALPHA_MIN; + + bool _wasVisible, _iconsInited, _showingDisabled; + Graphics::TransparentSurface _icon, _disabledIcon, _alphaIcon; + float _currentAlpha; + bool _alphaRising; + int _disabledFrames; + + void initIcons(); + void loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size); + void makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha); + +public: + CloudIcon(); + ~CloudIcon(); + + /** + * This method is called from ConnectionManager every time + * its own timer calls the handle() method. The primary + * responsibility of this draw() method is to draw cloud icon + * on ScummVM's OSD when current cloud Storage is working. + * + * As we don't want ConnectionManager to work when no + * Requests are running, we'd like to stop the timer. But then + * this icon wouldn't have time to disappear smoothly. So, + * in order to do that, ConnectionManager stop its timer + * only when this draw() method returns true, indicating that + * the CloudIcon has disappeared and the timer could be stopped. + * + * @return true if ConnMan's timer could be stopped. + */ + bool draw(); + + /** Draw a "cloud disabled" icon instead of "cloud syncing" one. */ + void showDisabled(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/cloudicon_data.h b/backends/networking/curl/cloudicon_data.h new file mode 100644 index 0000000000..21d88182a3 --- /dev/null +++ b/backends/networking/curl/cloudicon_data.h @@ -0,0 +1,111 @@ +/* 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 is a PNG file dumped into array. +// $ recode data..d1 <dists/cloudicon.png >cloudicon_data.h +// The tool is from https://github.com/pinard/Recode + +byte cloudicon_data[] = { + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, + 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115, + 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8, + 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11, + 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116, + 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, + 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0, + 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101, + 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107, + 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 50, 73, 68, + 65, 84, 88, 133, 197, 151, 109, 104, 150, 101, 20, 199, 127, 247, 227, + 179, 105, 51, 23, 65, 181, 150, 224, 154, 214, 132, 194, 249, 33, 165, + 22, 189, 231, 194, 210, 250, 16, 171, 180, 55, 42, 152, 68, 65, 100, + 52, 233, 5, 146, 144, 144, 26, 249, 169, 62, 164, 80, 89, 152, 25, + 18, 226, 42, 49, 87, 88, 180, 94, 96, 96, 246, 234, 180, 70, 50, + 66, 214, 55, 247, 22, 133, 247, 255, 244, 225, 58, 247, 158, 107, 143, + 219, 243, 60, 186, 192, 3, 135, 251, 220, 215, 117, 223, 231, 127, 238, + 235, 252, 239, 235, 58, 39, 105, 250, 206, 56, 147, 146, 55, 85, 252, + 108, 13, 112, 59, 176, 12, 88, 2, 204, 7, 230, 248, 220, 48, 208, + 15, 244, 2, 221, 64, 23, 48, 86, 137, 211, 228, 146, 158, 178, 43, + 80, 7, 172, 3, 218, 129, 179, 43, 241, 9, 140, 0, 155, 129, 87, + 128, 193, 146, 15, 47, 248, 178, 100, 0, 237, 64, 39, 112, 14, 96, + 174, 20, 217, 153, 228, 162, 0, 50, 25, 2, 58, 128, 45, 83, 1, + 228, 53, 121, 10, 170, 129, 183, 128, 213, 126, 47, 215, 49, 224, 39, + 224, 19, 160, 7, 56, 2, 204, 0, 154, 128, 27, 128, 229, 110, 215, + 120, 64, 181, 132, 149, 184, 30, 120, 4, 248, 183, 24, 40, 185, 248, + 243, 147, 86, 160, 154, 144, 195, 91, 252, 43, 5, 140, 2, 31, 2, + 27, 129, 195, 83, 125, 141, 75, 19, 240, 2, 129, 47, 179, 61, 144, + 4, 216, 7, 172, 44, 14, 34, 105, 232, 62, 41, 128, 109, 192, 189, + 14, 126, 2, 24, 0, 30, 3, 246, 150, 1, 46, 150, 54, 15, 184, + 1, 200, 251, 216, 123, 192, 253, 19, 2, 152, 183, 119, 66, 0, 237, + 192, 27, 110, 159, 0, 250, 128, 187, 128, 67, 167, 8, 158, 201, 98, + 224, 3, 160, 209, 131, 72, 128, 53, 68, 156, 200, 153, 192, 181, 206, + 68, 167, 219, 50, 49, 96, 226, 78, 19, 135, 162, 103, 138, 117, 169, + 137, 46, 19, 163, 38, 82, 19, 63, 152, 120, 220, 196, 12, 159, 63, + 104, 98, 149, 137, 99, 238, 211, 28, 163, 46, 243, 145, 147, 192, 117, + 157, 68, 173, 219, 195, 18, 143, 74, 28, 137, 230, 139, 181, 77, 162, + 71, 98, 165, 68, 141, 68, 78, 98, 145, 196, 107, 18, 59, 252, 30, + 137, 3, 18, 207, 72, 140, 249, 125, 173, 99, 33, 65, 114, 209, 110, + 131, 192, 218, 65, 2, 105, 4, 108, 245, 165, 74, 125, 165, 206, 5, + 214, 2, 151, 3, 7, 128, 119, 128, 95, 253, 189, 169, 228, 105, 224, + 85, 183, 171, 128, 29, 192, 29, 4, 82, 142, 18, 246, 151, 177, 164, + 126, 151, 1, 220, 3, 188, 79, 32, 222, 40, 97, 167, 235, 243, 151, + 207, 2, 190, 5, 154, 35, 231, 223, 0, 45, 101, 242, 127, 12, 152, + 75, 97, 191, 88, 12, 124, 237, 254, 18, 96, 21, 176, 35, 227, 192, + 50, 207, 15, 38, 126, 49, 113, 56, 202, 243, 221, 38, 154, 139, 114, + 223, 82, 130, 23, 153, 214, 155, 88, 20, 221, 255, 104, 226, 104, 116, + 223, 106, 42, 144, 112, 169, 137, 196, 131, 248, 56, 10, 166, 222, 196, + 141, 21, 128, 77, 165, 223, 155, 120, 42, 34, 246, 158, 200, 247, 18, + 19, 228, 21, 178, 60, 223, 151, 41, 1, 190, 112, 251, 60, 224, 171, + 104, 238, 116, 36, 33, 240, 224, 32, 240, 25, 176, 31, 120, 194, 199, + 27, 161, 112, 26, 102, 167, 154, 1, 127, 186, 253, 98, 9, 240, 81, + 2, 97, 43, 149, 231, 61, 128, 129, 104, 108, 78, 28, 64, 44, 25, + 105, 218, 74, 56, 156, 13, 252, 76, 248, 43, 42, 145, 140, 176, 249, + 226, 137, 188, 133, 20, 12, 123, 68, 9, 225, 171, 127, 7, 46, 40, + 227, 180, 82, 112, 128, 153, 126, 189, 148, 194, 105, 57, 12, 5, 18, + 246, 71, 68, 185, 214, 237, 191, 166, 65, 190, 98, 237, 243, 235, 205, + 209, 88, 127, 188, 19, 246, 250, 53, 39, 177, 194, 237, 157, 37, 118, + 193, 83, 213, 183, 37, 102, 73, 92, 39, 145, 196, 152, 57, 75, 193, + 82, 246, 249, 21, 75, 89, 104, 41, 205, 150, 178, 222, 82, 250, 163, + 241, 211, 213, 253, 150, 178, 201, 82, 174, 180, 148, 185, 150, 146, 248, + 120, 183, 165, 133, 20, 116, 153, 24, 113, 123, 150, 137, 103, 77, 28, + 55, 113, 141, 137, 173, 38, 134, 60, 61, 167, 178, 236, 3, 38, 54, + 152, 184, 213, 255, 253, 39, 221, 55, 142, 213, 101, 42, 252, 5, 99, + 132, 202, 101, 45, 97, 175, 94, 14, 172, 0, 118, 1, 15, 185, 78, + 71, 86, 19, 138, 217, 196, 117, 179, 99, 146, 204, 126, 125, 188, 30, + 168, 35, 236, 255, 181, 132, 3, 233, 15, 224, 54, 202, 87, 64, 229, + 164, 5, 216, 9, 92, 232, 224, 67, 192, 66, 188, 88, 205, 69, 185, + 26, 180, 148, 14, 207, 81, 206, 82, 230, 89, 202, 110, 75, 185, 108, + 26, 249, 191, 202, 82, 222, 181, 148, 243, 163, 220, 119, 56, 22, 150, + 78, 172, 7, 144, 216, 34, 177, 205, 237, 188, 196, 2, 137, 61, 18, + 15, 158, 6, 243, 31, 144, 216, 37, 209, 224, 190, 18, 137, 237, 142, + 49, 254, 92, 50, 115, 211, 164, 69, 233, 71, 64, 43, 140, 23, 165, + 127, 19, 26, 142, 231, 8, 117, 192, 84, 82, 69, 56, 118, 215, 3, + 55, 17, 54, 160, 172, 40, 253, 148, 80, 168, 78, 44, 74, 171, 59, + 39, 237, 11, 170, 129, 55, 129, 251, 40, 108, 205, 2, 254, 1, 126, + 243, 96, 122, 129, 163, 62, 223, 0, 92, 237, 160, 141, 17, 112, 38, + 219, 129, 135, 139, 193, 1, 146, 170, 151, 43, 106, 76, 106, 139, 198, + 229, 192, 73, 20, 96, 12, 152, 177, 253, 56, 101, 26, 147, 36, 191, + 177, 226, 214, 108, 13, 133, 214, 172, 220, 75, 35, 14, 90, 190, 53, + 203, 189, 84, 113, 119, 156, 53, 167, 173, 192, 21, 252, 95, 205, 105, + 178, 225, 204, 182, 231, 255, 1, 200, 91, 112, 221, 160, 249, 68, 42, + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 +}; diff --git a/backends/networking/curl/cloudicon_disabled_data.h b/backends/networking/curl/cloudicon_disabled_data.h new file mode 100644 index 0000000000..4340a8a37c --- /dev/null +++ b/backends/networking/curl/cloudicon_disabled_data.h @@ -0,0 +1,117 @@ +/* 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 is a PNG file dumped into array. +// $ recode data..d1 <dists/cloudicon_disabled.png >cloudicon_disabled_data.h +// The tool is from https://github.com/pinard/Recode + +byte cloudicon_disabled_data[] = { + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, + 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115, + 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8, + 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11, + 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116, + 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, + 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0, + 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101, + 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107, + 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 139, 73, 68, + 65, 84, 88, 133, 197, 215, 91, 168, 86, 69, 20, 7, 240, 223, 254, + 244, 120, 236, 120, 57, 26, 94, 10, 31, 34, 35, 11, 36, 203, 74, + 212, 160, 204, 212, 110, 166, 248, 208, 205, 172, 151, 144, 136, 158, 82, + 80, 168, 32, 233, 165, 160, 32, 130, 236, 165, 34, 204, 74, 18, 52, + 36, 169, 232, 34, 24, 221, 243, 218, 133, 74, 77, 195, 44, 200, 91, + 122, 188, 156, 172, 102, 159, 221, 195, 204, 246, 219, 126, 231, 226, 17, + 138, 22, 12, 123, 246, 236, 153, 245, 95, 107, 205, 127, 205, 172, 157, + 21, 254, 95, 233, 27, 122, 63, 183, 5, 179, 48, 13, 87, 98, 52, + 6, 167, 111, 71, 176, 11, 27, 177, 14, 107, 209, 222, 27, 165, 217, + 137, 211, 207, 25, 137, 197, 152, 143, 65, 189, 209, 137, 163, 120, 1, + 79, 98, 111, 143, 147, 143, 247, 172, 108, 62, 158, 194, 16, 20, 169, + 105, 232, 151, 82, 171, 24, 80, 74, 27, 22, 225, 197, 110, 13, 104, + 235, 122, 188, 31, 94, 194, 93, 21, 192, 14, 49, 172, 223, 226, 109, + 124, 130, 29, 232, 131, 49, 184, 22, 55, 166, 126, 75, 131, 65, 175, + 225, 94, 252, 213, 201, 128, 131, 93, 131, 191, 137, 27, 42, 192, 199, + 241, 6, 158, 192, 246, 238, 188, 73, 50, 6, 143, 98, 78, 50, 164, + 140, 200, 123, 34, 135, 78, 49, 34, 219, 215, 89, 193, 171, 152, 151, + 192, 3, 246, 224, 1, 188, 123, 26, 224, 170, 92, 134, 143, 49, 160, + 50, 86, 96, 5, 238, 174, 78, 108, 204, 130, 249, 98, 216, 75, 240, + 109, 184, 13, 63, 156, 1, 248, 68, 44, 21, 189, 47, 165, 67, 140, + 196, 60, 172, 87, 225, 68, 182, 167, 62, 105, 100, 2, 106, 77, 11, + 118, 139, 123, 186, 163, 59, 164, 53, 76, 16, 195, 125, 29, 250, 15, + 227, 167, 73, 12, 237, 27, 73, 91, 75, 142, 28, 21, 185, 51, 60, + 141, 181, 225, 98, 41, 59, 106, 33, 185, 26, 88, 28, 104, 77, 253, + 163, 129, 251, 3, 59, 42, 223, 79, 105, 171, 184, 53, 240, 73, 224, + 150, 64, 203, 32, 106, 99, 185, 160, 224, 236, 16, 245, 118, 4, 54, + 5, 166, 4, 22, 6, 218, 211, 218, 214, 132, 37, 32, 75, 238, 181, + 224, 55, 12, 76, 222, 191, 140, 251, 144, 39, 79, 135, 98, 1, 198, + 98, 11, 150, 227, 251, 50, 204, 67, 113, 121, 90, 156, 57, 185, 127, + 135, 154, 184, 9, 95, 160, 9, 43, 49, 59, 69, 225, 24, 206, 65, + 123, 150, 54, 247, 14, 188, 158, 214, 30, 23, 79, 186, 109, 9, 252, + 44, 124, 142, 113, 149, 232, 127, 134, 201, 196, 88, 79, 66, 115, 5, + 252, 24, 182, 114, 224, 32, 35, 230, 212, 207, 139, 75, 241, 169, 168, + 47, 195, 157, 88, 89, 110, 193, 180, 64, 145, 250, 223, 5, 182, 87, + 194, 125, 123, 96, 92, 195, 22, 76, 14, 201, 227, 177, 226, 65, 144, + 227, 111, 28, 198, 151, 113, 131, 135, 5, 46, 169, 172, 249, 38, 176, + 187, 242, 62, 35, 160, 150, 199, 197, 19, 114, 178, 156, 34, 231, 173, + 244, 180, 154, 115, 115, 166, 166, 57, 167, 180, 193, 34, 3, 7, 165, + 61, 11, 137, 105, 27, 112, 160, 62, 111, 235, 106, 22, 166, 126, 71, + 206, 59, 165, 238, 156, 43, 114, 100, 155, 98, 120, 218, 146, 206, 2, + 83, 241, 225, 26, 134, 165, 253, 27, 173, 65, 134, 138, 137, 222, 154, + 222, 139, 228, 249, 87, 233, 217, 133, 76, 159, 19, 47, 169, 89, 226, + 129, 214, 71, 188, 192, 134, 148, 231, 64, 121, 171, 21, 248, 85, 244, + 232, 177, 30, 192, 59, 90, 82, 6, 193, 9, 108, 234, 30, 28, 30, + 9, 209, 128, 74, 214, 71, 204, 190, 121, 231, 201, 5, 228, 220, 218, + 248, 97, 136, 200, 246, 102, 106, 29, 234, 73, 190, 5, 135, 186, 7, + 135, 201, 9, 167, 111, 227, 135, 50, 2, 71, 146, 69, 153, 232, 245, + 206, 192, 136, 234, 196, 86, 49, 13, 154, 162, 113, 39, 211, 101, 51, + 126, 239, 25, 28, 154, 19, 206, 133, 234, 119, 195, 17, 234, 36, 220, + 85, 33, 202, 213, 169, 191, 175, 36, 92, 139, 152, 106, 3, 212, 9, + 119, 76, 100, 251, 126, 157, 9, 218, 69, 219, 150, 158, 211, 42, 99, + 187, 114, 245, 147, 112, 83, 122, 214, 2, 51, 83, 127, 85, 16, 89, + 62, 94, 60, 61, 202, 20, 58, 36, 38, 244, 126, 93, 159, 146, 93, + 180, 101, 129, 254, 129, 107, 2, 89, 26, 219, 24, 42, 6, 188, 95, + 153, 124, 81, 202, 251, 37, 3, 249, 101, 188, 120, 114, 148, 223, 219, + 197, 212, 56, 208, 123, 240, 245, 129, 167, 3, 19, 3, 163, 42, 6, + 172, 11, 234, 36, 92, 43, 242, 105, 32, 250, 227, 161, 89, 44, 45, + 98, 196, 139, 14, 178, 146, 112, 27, 210, 179, 23, 178, 7, 203, 240, + 248, 156, 152, 251, 15, 38, 221, 146, 138, 181, 212, 73, 216, 46, 214, + 112, 11, 82, 180, 103, 138, 71, 237, 40, 145, 52, 29, 216, 221, 194, + 220, 41, 49, 0, 103, 36, 129, 185, 152, 158, 116, 101, 9, 171, 29, + 178, 85, 245, 121, 213, 235, 184, 90, 215, 229, 248, 89, 172, 112, 190, + 62, 83, 112, 209, 145, 85, 226, 229, 147, 105, 188, 142, 43, 172, 220, + 155, 179, 40, 29, 201, 85, 6, 239, 204, 153, 155, 243, 117, 47, 216, + 222, 216, 38, 229, 188, 146, 51, 188, 162, 119, 81, 194, 82, 205, 130, + 178, 189, 24, 120, 173, 97, 108, 80, 34, 102, 111, 73, 87, 182, 123, + 2, 107, 2, 231, 133, 184, 213, 89, 96, 69, 194, 56, 57, 47, 91, + 222, 57, 100, 253, 68, 130, 92, 175, 94, 148, 254, 129, 15, 240, 176, + 88, 7, 116, 39, 77, 226, 181, 187, 68, 172, 146, 154, 69, 78, 101, + 98, 77, 57, 91, 99, 81, 250, 82, 215, 138, 202, 178, 188, 44, 78, + 37, 67, 254, 196, 143, 201, 152, 141, 98, 217, 86, 224, 60, 92, 149, + 64, 207, 175, 0, 151, 178, 66, 119, 101, 249, 243, 61, 184, 163, 254, + 99, 210, 218, 48, 94, 94, 5, 101, 13, 162, 1, 176, 100, 251, 97, + 167, 249, 49, 233, 115, 179, 250, 111, 78, 23, 109, 115, 193, 178, 130, + 62, 5, 151, 20, 52, 23, 241, 76, 200, 10, 106, 149, 103, 173, 50, + 158, 21, 28, 45, 120, 174, 96, 110, 193, 71, 61, 232, 151, 61, 219, + 115, 4, 170, 82, 254, 156, 206, 16, 47, 197, 127, 231, 231, 244, 153, + 222, 27, 240, 159, 200, 63, 153, 185, 24, 191, 162, 246, 71, 153, 0, + 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 +}; diff --git a/backends/networking/curl/connectionmanager.cpp b/backends/networking/curl/connectionmanager.cpp new file mode 100644 index 0000000000..f3dc91ad60 --- /dev/null +++ b/backends/networking/curl/connectionmanager.cpp @@ -0,0 +1,204 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/system.h" +#include "common/timer.h" +#include <curl/curl.h> + +namespace Common { + +DECLARE_SINGLETON(Networking::ConnectionManager); + +} + +namespace Networking { + +ConnectionManager::ConnectionManager(): _multi(0), _timerStarted(false), _frame(0) { + curl_global_init(CURL_GLOBAL_ALL); + _multi = curl_multi_init(); +} + +ConnectionManager::~ConnectionManager() { + stopTimer(); + + //terminate all requests + _handleMutex.lock(); + for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end(); ++i) { + Request *request = i->request; + RequestCallback callback = i->onDeleteCallback; + if (request) + request->finish(); + delete request; + if (callback) + (*callback)(request); + } + _requests.clear(); + + //cleanup + curl_multi_cleanup(_multi); + curl_global_cleanup(); + _multi = nullptr; + _handleMutex.unlock(); +} + +void ConnectionManager::registerEasyHandle(CURL *easy) const { + curl_multi_add_handle(_multi, easy); +} + +Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) { + _addedRequestsMutex.lock(); + _addedRequests.push_back(RequestWithCallback(request, callback)); + if (!_timerStarted) + startTimer(); + _addedRequestsMutex.unlock(); + return request; +} + +void ConnectionManager::showCloudDisabledIcon() { + _icon.showDisabled(); + startTimer(); +} + +Common::String ConnectionManager::urlEncode(Common::String s) const { + if (!_multi) + return ""; + char *output = curl_easy_escape(_multi, s.c_str(), s.size()); + if (output) { + Common::String result = output; + curl_free(output); + return result; + } + return ""; +} + +uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() { + return TIMER_INTERVAL * CLOUD_PERIOD; +} + +//private goes here: + +void connectionsThread(void *ignored) { + ConnMan.handle(); +} + +void ConnectionManager::startTimer(int interval) { + Common::TimerManager *manager = g_system->getTimerManager(); + if (manager->installTimerProc(connectionsThread, interval, 0, "Networking::ConnectionManager's Timer")) { + _timerStarted = true; + } else { + warning("Failed to install Networking::ConnectionManager's timer"); + } +} + +void ConnectionManager::stopTimer() { + debug(9, "timer stopped"); + Common::TimerManager *manager = g_system->getTimerManager(); + manager->removeTimerProc(connectionsThread); + _timerStarted = false; +} + +bool ConnectionManager::hasAddedRequests() { + _addedRequestsMutex.lock(); + bool hasNewRequests = !_addedRequests.empty(); + _addedRequestsMutex.unlock(); + return hasNewRequests; +} + +void ConnectionManager::handle() { + //lock mutex here (in case another handle() would be called before this one ends) + _handleMutex.lock(); + ++_frame; + if (_frame % CLOUD_PERIOD == 0) + interateRequests(); + if (_frame % CURL_PERIOD == 0) + processTransfers(); + + if (_icon.draw() && _requests.empty() && !hasAddedRequests()) + stopTimer(); + _handleMutex.unlock(); +} + +void ConnectionManager::interateRequests() { + //add new requests + _addedRequestsMutex.lock(); + for (Common::Array<RequestWithCallback>::iterator i = _addedRequests.begin(); i != _addedRequests.end(); ++i) { + _requests.push_back(*i); + } + _addedRequests.clear(); + _addedRequestsMutex.unlock(); + + //call handle() of all running requests (so they can do their work) + debug(9, "handling %d request(s)", _requests.size()); + for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) { + Request *request = i->request; + if (request) { + if (request->state() == PROCESSING) + request->handle(); + else if (request->state() == RETRY) + request->handleRetry(); + } + + if (!request || request->state() == FINISHED) { + delete (i->request); + if (i->onDeleteCallback) + (*i->onDeleteCallback)(i->request); //that's not a mistake (we're passing an address and that method knows there is no object anymore) + _requests.erase(i); + continue; + } + + ++i; + } +} + +void ConnectionManager::processTransfers() { + if (!_multi) return; + + //check libcurl's transfers and notify requests of messages from queue (transfer completion or failure) + int transfersRunning; + curl_multi_perform(_multi, &transfersRunning); + + int messagesInQueue; + CURLMsg *curlMsg; + while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) { + CURL *easyHandle = curlMsg->easy_handle; + + NetworkReadStream *stream; + curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream); + if (stream) + stream->finished(); + + if (curlMsg->msg == CURLMSG_DONE) { + debug(9, "ConnectionManager: SUCCESS (%d - %s)", curlMsg->data.result, curl_easy_strerror(curlMsg->data.result)); + } else { + warning("ConnectionManager: FAILURE (CURLMsg (%d))", curlMsg->msg); + } + + curl_multi_remove_handle(_multi, easyHandle); + } +} + +} // End of namespace Cloud diff --git a/backends/networking/curl/connectionmanager.h b/backends/networking/curl/connectionmanager.h new file mode 100644 index 0000000000..826bef6d36 --- /dev/null +++ b/backends/networking/curl/connectionmanager.h @@ -0,0 +1,132 @@ +/* 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 BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H +#define BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H + +#include "backends/networking/curl/cloudicon.h" +#include "backends/networking/curl/request.h" +#include "common/str.h" +#include "common/singleton.h" +#include "common/hashmap.h" +#include "common/mutex.h" + +typedef void CURL; +typedef void CURLM; +struct curl_slist; + +namespace Networking { + +class NetworkReadStream; + +class ConnectionManager : public Common::Singleton<ConnectionManager> { + static const uint32 FRAMES_PER_SECOND = 20; + static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND; + static const uint32 CLOUD_PERIOD = 20; //every 20th frame + static const uint32 CURL_PERIOD = 1; //every frame + + friend void connectionsThread(void *); //calls handle() + + typedef Common::BaseCallback<Request *> *RequestCallback; + + /** + * RequestWithCallback is used by ConnectionManager to + * storage the Request and a callback which should be + * called on Request delete. + * + * Usually one won't need to pass such callback, but + * in some cases you'd like to know whether Request is + * still running. + * + * For example, Cloud::Storage is keeping track of how + * many Requests are running, and thus it needs to know + * that Request was destroyed to decrease its counter. + * + * onDeleteCallback is called with *invalid* pointer. + * ConnectionManager deletes Request first and then passes + * the pointer to the callback. One may use the address + * to find it in own HashMap or Array and remove it. + * So, again, this pointer is for information only. One + * cannot use it. + */ + struct RequestWithCallback { + Request *request; + RequestCallback onDeleteCallback; + + RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr): request(rq), onDeleteCallback(cb) {} + }; + + CURLM *_multi; + bool _timerStarted; + Common::Array<RequestWithCallback> _requests, _addedRequests; + Common::Mutex _handleMutex, _addedRequestsMutex; + CloudIcon _icon; + uint32 _frame; + + void startTimer(int interval = TIMER_INTERVAL); + void stopTimer(); + void handle(); + void interateRequests(); + void processTransfers(); + bool hasAddedRequests(); + +public: + ConnectionManager(); + virtual ~ConnectionManager(); + + /** + * All libcurl transfers are going through this ConnectionManager. + * So, if you want to start any libcurl transfer, you must create + * an easy handle and register it using this method. + */ + void registerEasyHandle(CURL *easy) const; + + /** + * Use this method to add new Request into manager's queue. + * Manager will periodically call handle() method of these + * Requests until they set their state to FINISHED. + * + * If Request's state is RETRY, handleRetry() is called instead. + * + * The passed callback would be called after Request is deleted. + * + * @note This method starts the timer if it's not started yet. + * + * @return the same Request pointer, just as a shortcut + */ + Request *addRequest(Request *request, RequestCallback callback = nullptr); + + /** Shows a "cloud disabled" icon for a three seconds. */ + void showCloudDisabledIcon(); + + /** Return URL-encoded version of given string. */ + Common::String urlEncode(Common::String s) const; + + static uint32 getCloudRequestsPeriodInMicroseconds(); +}; + +/** Shortcut for accessing the connection manager. */ +#define ConnMan Networking::ConnectionManager::instance() + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp new file mode 100644 index 0000000000..1899cbd913 --- /dev/null +++ b/backends/networking/curl/curljsonrequest.cpp @@ -0,0 +1,215 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Networking { + +CurlJsonRequest::CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url) : + CurlRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES), + _buffer(new byte[CURL_JSON_REQUEST_BUFFER_SIZE]) {} + +CurlJsonRequest::~CurlJsonRequest() { + delete _jsonCallback; + delete[] _buffer; +} + +char *CurlJsonRequest::getPreparedContents() { + //write one more byte in the end + byte zero[1] = {0}; + _contentsStream.write(zero, 1); + + //replace all "bad" bytes with '.' character + byte *result = _contentsStream.getData(); + uint32 size = _contentsStream.size(); + for (uint32 i = 0; i < size; ++i) { + if (result[i] == '\n') + result[i] = ' '; //yeah, kinda stupid + else if (result[i] < 0x20 || result[i] > 0x7f) + result[i] = '.'; + } + + //make it zero-terminated string + result[size - 1] = '\0'; + + return (char *)result; +} + +void CurlJsonRequest::handle() { + if (!_stream) _stream = makeStream(); + + if (_stream) { + uint32 readBytes = _stream->read(_buffer, CURL_JSON_REQUEST_BUFFER_SIZE); + if (readBytes != 0) + if (_contentsStream.write(_buffer, readBytes) != readBytes) + warning("CurlJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic"); + + if (_stream->eos()) { + char *contents = getPreparedContents(); + Common::JSONValue *json = Common::JSON::parse(contents); + if (json) { + finishJson(json); //it's JSON even if's not 200 OK? That's fine!.. + } else { + if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!.. + finishJson(nullptr); + else + finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode())); + } + } + } +} + +void CurlJsonRequest::restart() { + if (_stream) + delete _stream; + _stream = nullptr; + _contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES); + //with no stream available next handle() will create another one +} + +void CurlJsonRequest::finishJson(Common::JSONValue *json) { + Request::finishSuccess(); + if (_jsonCallback) + (*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks! + else + delete json; +} + +bool CurlJsonRequest::jsonIsObject(Common::JSONValue *item, const char *warningPrefix) { + if (item == nullptr) { + warning("%s: passed item is NULL", warningPrefix); + return false; + } + + if (item->isObject()) return true; + + warning("%s: passed item is not an object", warningPrefix); + debug(9, "%s", item->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isObject()) return true; + + warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isString()) return true; + + warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isIntegerNumber()) return true; + + warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isArray()) return true; + + warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true; + + warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/curl/curljsonrequest.h b/backends/networking/curl/curljsonrequest.h new file mode 100644 index 0000000000..edd523015a --- /dev/null +++ b/backends/networking/curl/curljsonrequest.h @@ -0,0 +1,67 @@ +/* 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 BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H +#define BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H + +#include "backends/networking/curl/curlrequest.h" +#include "common/memstream.h" +#include "common/json.h" + +namespace Networking { + +typedef Response<Common::JSONValue *> JsonResponse; +typedef Common::BaseCallback<JsonResponse> *JsonCallback; + +#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024 + +class CurlJsonRequest: public CurlRequest { +protected: + JsonCallback _jsonCallback; + Common::MemoryWriteStreamDynamic _contentsStream; + byte *_buffer; + + /** Prepares raw bytes from _contentsStream to be parsed with Common::JSON::parse(). */ + char *getPreparedContents(); + + /** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */ + virtual void finishJson(Common::JSONValue *json); + +public: + CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url); + virtual ~CurlJsonRequest(); + + virtual void handle(); + virtual void restart(); + + static bool jsonIsObject(Common::JSONValue *item, const char *warningPrefix); + static bool jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/curlrequest.cpp b/backends/networking/curl/curlrequest.cpp new file mode 100644 index 0000000000..64fa347023 --- /dev/null +++ b/backends/networking/curl/curlrequest.cpp @@ -0,0 +1,162 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/curlrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/textconsole.h" +#include <curl/curl.h> + +namespace Networking { + +CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url): + Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr), + _bytesBufferSize(0), _uploading(false), _usingPatch(false) {} + +CurlRequest::~CurlRequest() { + delete _stream; + delete _bytesBuffer; +} + +NetworkReadStream *CurlRequest::makeStream() { + if (_bytesBuffer) + return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true); + if (!_formFields.empty() || !_formFiles.empty()) + return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles); + return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch); +} + +void CurlRequest::handle() { + if (!_stream) _stream = makeStream(); + + if (_stream && _stream->eos()) { + if (_stream->httpResponseCode() != 200) { + warning("CurlRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode()); + ErrorResponse error(this, false, true, "", _stream->httpResponseCode()); + finishError(error); + return; + } + + finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return) + } +} + +void CurlRequest::restart() { + if (_stream) + delete _stream; + _stream = nullptr; + //with no stream available next handle() will create another one +} + +Common::String CurlRequest::date() const { + if (_stream) { + Common::String headers = _stream->responseHeaders(); + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "Date: "); + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + 6; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + return result; + } + } + return ""; +} + +void CurlRequest::setHeaders(Common::Array<Common::String> &headers) { + curl_slist_free_all(_headersList); + _headersList = nullptr; + for (uint32 i = 0; i < headers.size(); ++i) + addHeader(headers[i]); +} + +void CurlRequest::addHeader(Common::String header) { + _headersList = curl_slist_append(_headersList, header.c_str()); +} + +void CurlRequest::addPostField(Common::String keyValuePair) { + if (_bytesBuffer) + warning("CurlRequest: added POST fields would be ignored, because there is buffer present"); + + if (!_formFields.empty() || !_formFiles.empty()) + warning("CurlRequest: added POST fields would be ignored, because there are form fields/files present"); + + if (_postFields == "") + _postFields = keyValuePair; + else + _postFields += "&" + keyValuePair; +} + +void CurlRequest::addFormField(Common::String name, Common::String value) { + if (_bytesBuffer) + warning("CurlRequest: added POST form fields would be ignored, because there is buffer present"); + + if (_formFields.contains(name)) + warning("CurlRequest: form field '%s' already had a value", name.c_str()); + + _formFields[name] = value; +} + +void CurlRequest::addFormFile(Common::String name, Common::String filename) { + if (_bytesBuffer) + warning("CurlRequest: added POST form files would be ignored, because there is buffer present"); + + if (_formFields.contains(name)) + warning("CurlRequest: form file field '%s' already had a value", name.c_str()); + + _formFiles[name] = filename; +} + +void CurlRequest::setBuffer(byte *buffer, uint32 size) { + if (_postFields != "") + warning("CurlRequest: added POST fields would be ignored, because buffer added"); + + if (_bytesBuffer) + delete _bytesBuffer; + + _bytesBuffer = buffer; + _bytesBufferSize = size; +} + +void CurlRequest::usePut() { _uploading = true; } + +void CurlRequest::usePatch() { _usingPatch = true; } + +NetworkReadStreamResponse CurlRequest::execute() { + if (!_stream) { + _stream = makeStream(); + ConnMan.addRequest(this); + } + + return NetworkReadStreamResponse(this, _stream); +} + +const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; } + +} // End of namespace Networking diff --git a/backends/networking/curl/curlrequest.h b/backends/networking/curl/curlrequest.h new file mode 100644 index 0000000000..6ce94f8983 --- /dev/null +++ b/backends/networking/curl/curlrequest.h @@ -0,0 +1,100 @@ +/* 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 BACKENDS_NETWORKING_CURL_CURLREQUEST_H +#define BACKENDS_NETWORKING_CURL_CURLREQUEST_H + +#include "backends/networking/curl/request.h" +#include "common/str.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +struct curl_slist; + +namespace Networking { + +class NetworkReadStream; + +typedef Response<NetworkReadStream *> NetworkReadStreamResponse; +typedef Common::BaseCallback<NetworkReadStreamResponse> *NetworkReadStreamCallback; + +class CurlRequest: public Request { +protected: + Common::String _url; + NetworkReadStream *_stream; + curl_slist *_headersList; + Common::String _postFields; + Common::HashMap<Common::String, Common::String> _formFields; + Common::HashMap<Common::String, Common::String> _formFiles; + byte *_bytesBuffer; + uint32 _bytesBufferSize; + bool _uploading; //using PUT method + bool _usingPatch; //using PATCH method + + virtual NetworkReadStream *makeStream(); + +public: + CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url); + virtual ~CurlRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; + + /** Replaces all headers with the passed array of headers. */ + virtual void setHeaders(Common::Array<Common::String> &headers); + + /** Adds a header into headers list. */ + virtual void addHeader(Common::String header); + + /** Adds a post field (key=value pair). */ + virtual void addPostField(Common::String field); + + /** Adds a form/multipart field (name, value). */ + virtual void addFormField(Common::String name, Common::String value); + + /** Adds a form/multipart file (field name, file name). */ + virtual void addFormFile(Common::String name, Common::String filename); + + /** Sets bytes buffer. */ + virtual void setBuffer(byte *buffer, uint32 size); + + /** Remembers to use PUT method when it would create NetworkReadStream. */ + virtual void usePut(); + + /** Remembers to use PATCH method when it would create NetworkReadStream. */ + virtual void usePatch(); + + /** + * Starts this Request with ConnMan. + * @return its NetworkReadStream in NetworkReadStreamResponse. + */ + virtual NetworkReadStreamResponse execute(); + + /** Returns Request's NetworkReadStream. */ + const NetworkReadStream *getNetworkReadStream() const; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/networkreadstream.cpp b/backends/networking/curl/networkreadstream.cpp new file mode 100644 index 0000000000..032aef87be --- /dev/null +++ b/backends/networking/curl/networkreadstream.cpp @@ -0,0 +1,257 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/networkreadstream.h" +#include "backends/networking/curl/connectionmanager.h" +#include "base/version.h" +#include <curl/curl.h> + +namespace Networking { + +static size_t curlDataCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->write(d, n * l); + return 0; +} + +static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->fillWithSendingContents(d, n * l); + return 0; +} + +static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->addResponseHeaders(d, n * l); + return 0; +} + +static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + stream->setProgress(dlnow, dltotal); + return 0; +} + +static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) { + // for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION) + return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow); +} + +void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) { + _eos = _requestComplete = false; + _sendingContentsBuffer = nullptr; + _sendingContentsSize = _sendingContentsPos = 0; + _progressDownloaded = _progressTotal = 0; + + _easy = curl_easy_init(); + curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback); + curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us + curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete + curl_easy_setopt(_easy, CURLOPT_HEADER, 0L); + curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this); + curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback); + curl_easy_setopt(_easy, CURLOPT_URL, url); + curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on + curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList); + curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); + curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder); + curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this); +#if LIBCURL_VERSION_NUM >= 0x072000 + // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0 + // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used + curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback); + curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this); +#endif + if (uploading) { + curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(_easy, CURLOPT_READDATA, this); + curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback); + _sendingContentsBuffer = buffer; + _sendingContentsSize = bufferSize; + } else if (usingPatch) { + curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH"); + } else { + if (post || bufferSize != 0) { + curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize); + curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer); + } + } + ConnMan.registerEasyHandle(_easy); +} + +void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) { + _eos = _requestComplete = false; + _sendingContentsBuffer = nullptr; + _sendingContentsSize = _sendingContentsPos = 0; + _progressDownloaded = _progressTotal = 0; + + _easy = curl_easy_init(); + curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback); + curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us + curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete + curl_easy_setopt(_easy, CURLOPT_HEADER, 0L); + curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this); + curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback); + curl_easy_setopt(_easy, CURLOPT_URL, url); + curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on + curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList); + curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); + curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder); + curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this); +#if LIBCURL_VERSION_NUM >= 0x072000 + // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0 + // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used + curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback); + curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this); +#endif + + // set POST multipart upload form fields/files + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + + for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) { + CURLFORMcode code = curl_formadd( + &formpost, + &lastptr, + CURLFORM_COPYNAME, i->_key.c_str(), + CURLFORM_COPYCONTENTS, i->_value.c_str(), + CURLFORM_END + ); + + if (code != CURL_FORMADD_OK) + warning("NetworkReadStream: field curl_formadd('%s') failed", i->_key.c_str()); + } + + for (Common::HashMap<Common::String, Common::String>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) { + CURLFORMcode code = curl_formadd( + &formpost, + &lastptr, + CURLFORM_COPYNAME, i->_key.c_str(), + CURLFORM_FILE, i->_value.c_str(), + CURLFORM_END + ); + + if (code != CURL_FORMADD_OK) + warning("NetworkReadStream: file curl_formadd('%s') failed", i->_key.c_str()); + } + + curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost); + + ConnMan.registerEasyHandle(_easy); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) { + init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) { + init(url, headersList, formFields, formFiles); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) { + init(url, headersList, buffer, bufferSize, uploading, usingPatch, post); +} + +NetworkReadStream::~NetworkReadStream() { + if (_easy) + curl_easy_cleanup(_easy); +} + +bool NetworkReadStream::eos() const { + return _eos; +} + +uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 actuallyRead = MemoryReadWriteStream::read(dataPtr, dataSize); + + if (actuallyRead == 0) { + if (_requestComplete) + _eos = true; + return 0; + } + + return actuallyRead; +} + +void NetworkReadStream::finished() { + _requestComplete = true; +} + +long NetworkReadStream::httpResponseCode() const { + long responseCode = -1; + if (_easy) + curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode); + return responseCode; +} + +Common::String NetworkReadStream::currentLocation() const { + Common::String result = ""; + if (_easy) { + char *pointer; + curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer); + result = Common::String(pointer); + } + return result; +} + +Common::String NetworkReadStream::responseHeaders() const { + return _responseHeaders; +} + +uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) { + uint32 size = _sendingContentsSize - _sendingContentsPos; + if (size > maxSize) + size = maxSize; + for (uint32 i = 0; i < size; ++i) { + bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i]; + } + _sendingContentsPos += size; + return size; +} + +uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 size) { + _responseHeaders += Common::String(buffer, size); + return size; +} + +double NetworkReadStream::getProgress() const { + if (_progressTotal < 1) + return 0; + return (double)_progressDownloaded / (double)_progressTotal; +} + +void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) { + _progressDownloaded = downloaded; + _progressTotal = total; +} + +} // End of namespace Cloud diff --git a/backends/networking/curl/networkreadstream.h b/backends/networking/curl/networkreadstream.h new file mode 100644 index 0000000000..2be6d591cb --- /dev/null +++ b/backends/networking/curl/networkreadstream.h @@ -0,0 +1,142 @@ +/* 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 BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H +#define BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H + +#include "common/memstream.h" +#include "common/stream.h" +#include "common/str.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +typedef void CURL; +struct curl_slist; + +namespace Networking { + +class NetworkReadStream: public Common::MemoryReadWriteStream { + CURL *_easy; + bool _eos, _requestComplete; + const byte *_sendingContentsBuffer; + uint32 _sendingContentsSize; + uint32 _sendingContentsPos; + Common::String _responseHeaders; + uint64 _progressDownloaded, _progressTotal; + void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post); + void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles); + +public: + /** Send <postFields>, using POST by default. */ + NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false); + /** Send <formFields>, <formFiles>, using POST multipart/form. */ + NetworkReadStream( + const char *url, curl_slist *headersList, + Common::HashMap<Common::String, Common::String> formFields, + Common::HashMap<Common::String, Common::String> formFiles); + /** Send <buffer, using POST by default. */ + NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true); + virtual ~NetworkReadStream(); + + /** + * Returns true if a read failed because the stream end has been reached. + * This flag is cleared by clearErr(). + * For a SeekableReadStream, it is also cleared by a successful seek. + * + * @note The semantics of any implementation of this method are + * supposed to match those of ISO C feof(). In particular, in a stream + * with N bytes, reading exactly N bytes from the start should *not* + * set eos; only reading *beyond* the available data should set it. + */ + virtual bool eos() const; + + /** + * Read data from the stream. Subclasses must implement this + * method; all other read methods are implemented using it. + * + * @note The semantics of any implementation of this method are + * supposed to match those of ISO C fread(), in particular where + * it concerns setting error and end of file/stream flags. + * + * @param dataPtr pointer to a buffer into which the data is read + * @param dataSize number of bytes to be read + * @return the number of bytes which were actually read. + */ + virtual uint32 read(void *dataPtr, uint32 dataSize); + + /** + * This method is called by ConnectionManager to indicate + * that transfer is finished. + * + * @note It's called on failure too. + */ + void finished(); + + /** + * Returns HTTP response code from inner CURL handle. + * It returns -1 to indicate there is no inner handle. + * + * @note This method should be called when eos() == true. + */ + long httpResponseCode() const; + + /** + * Return current location URL from inner CURL handle. + * "" is returned to indicate there is no inner handle. + * + * @note This method should be called when eos() == true. + */ + Common::String currentLocation() const; + + /** + * Return response headers. + * + * @note This method should be called when eos() == true. + */ + Common::String responseHeaders() const; + + /** + * Fills the passed buffer with _sendingContentsBuffer contents. + * It works similarly to read(), expect it's not for reading + * Stream's contents, but for sending our own data to the server. + * + * @returns how many bytes were actually read (filled in) + */ + uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize); + + /** + * Remembers headers returned to CURL in server's response. + * + * @returns how many bytes were actually read + */ + uint32 addResponseHeaders(char *buffer, uint32 size); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Used in curl progress callback to pass current downloaded/total values. */ + void setProgress(uint64 downloaded, uint64 total); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/request.cpp b/backends/networking/curl/request.cpp new file mode 100644 index 0000000000..30af48a478 --- /dev/null +++ b/backends/networking/curl/request.cpp @@ -0,0 +1,74 @@ +/* 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 "backends/networking/curl/request.h" + +namespace Networking { + +ErrorResponse::ErrorResponse(Request *rq): + request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {} + +ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode): + request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {} + +Request::Request(DataCallback cb, ErrorCallback ecb): + _callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {} + +Request::~Request() { + delete _callback; + delete _errorCallback; +} + +void Request::handleRetry() { + if (_retryInSeconds > 0) { + --_retryInSeconds; + } else { + _state = PROCESSING; + restart(); + } +} + +void Request::pause() { _state = PAUSED; } + +void Request::finish() { + ErrorResponse error(this, true, false, "", -1); + finishError(error); +} + +void Request::retry(uint32 seconds) { + _state = RETRY; + _retryInSeconds = seconds; +} + +RequestState Request::state() const { return _state; } + +Common::String Request::date() const { return ""; } + +void Request::finishError(ErrorResponse error) { + _state = FINISHED; + if (_errorCallback) + (*_errorCallback)(error); +} + +void Request::finishSuccess() { _state = FINISHED; } + +} // End of namespace Networking diff --git a/backends/networking/curl/request.h b/backends/networking/curl/request.h new file mode 100644 index 0000000000..9b366ea40c --- /dev/null +++ b/backends/networking/curl/request.h @@ -0,0 +1,203 @@ +/* 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 BACKENDS_NETWORKING_CURL_REQUEST_H +#define BACKENDS_NETWORKING_CURL_REQUEST_H + +#include "common/callback.h" +#include "common/scummsys.h" +#include "common/str.h" + +namespace Networking { + +class Request; + +/** + * Response<T> is a struct to be returned from Request + * to user's callbacks. It's a type safe way to indicate + * which "return value" Request has and user awaits. + * + * It just keeps a Request pointer together with + * some T value (which might be a pointer, a reference + * or a plain type (copied by value)). + * + * To make it more convenient, typedefs are used. + * For example, Response<void *> is called DataResponse + * and corresponding callback pointer is DataCallback. + */ + +template<typename T> struct Response { + Request *request; + T value; + + Response(Request *rq, T v) : request(rq), value(v) {} +}; + +/** + * ErrorResponse is a struct to be returned from Request + * to user's failure callbacks. + * + * It keeps a Request pointer together with some useful + * information fields, which would explain why failure + * callback was called. + * + * <interrupted> flag is set when Request was interrupted, + * i.e. finished by user with finish() call. + * + * <failed> flag is set when Request has failed because of + * some error (bad server response, for example). + * + * <response> contains server's original response. + * + * <httpResponseCode> contains server's HTTP response code. + */ + +struct ErrorResponse { + Request *request; + bool interrupted; + bool failed; + Common::String response; + long httpResponseCode; + + ErrorResponse(Request *rq); + ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode); +}; + +typedef Response<void *> DataReponse; +typedef Common::BaseCallback<DataReponse> *DataCallback; +typedef Common::BaseCallback<ErrorResponse> *ErrorCallback; + +/** + * RequestState is used to indicate current Request state. + * ConnectionManager uses it to decide what to do with the Request. + * + * PROCESSING state indicates that Request is working. + * ConnectionManager calls handle() method of Requests in that state. + * + * PAUSED state indicates that Request is not working. + * ConnectionManager keeps Requests in that state and doesn't call any methods of those. + * + * RETRY state indicates that Request must restart after a few seconds. + * ConnectionManager calls handleRetry() method of Requests in that state. + * Default handleRetry() implementation decreases _retryInSeconds value + * until it reaches zero. When it does, Request's restart() method is called. + * + * FINISHED state indicates that Request did the work and might be deleted. + * ConnectionManager deletes Requests in that state. + * After this state is set, but before ConnectionManager deletes the Request, + * Request calls user's callback. User can ask Request to change its state + * by calling retry() or pause() methods and Request won't be deleted. + * + * Request get a success and failure callbacks. Request must call one + * (and only one!) of these callbacks when it sets FINISHED state. + */ +enum RequestState { + PROCESSING, + PAUSED, + RETRY, + FINISHED +}; + +class Request { +protected: + /** + * Callback, which should be called when Request is finished. + * That's the way Requests pass the result to the code which asked to create this request. + * + * @note some Requests use their own callbacks to return something but void *. + * @note callback must be called in finish() or similar method. + */ + DataCallback _callback; + + /** + * Callback, which should be called when Request is failed/interrupted. + * That's the way Requests pass error information to the code which asked to create this request. + * @note callback must be called in finish() or similar method. + */ + ErrorCallback _errorCallback; + + /** + * Request state, which is used by ConnectionManager to determine + * whether request might be deleted or it's still working. + * + * State might be changed from outside with finish(), pause() or + * retry() methods. Override these if you want to react to these + * changes correctly. + */ + RequestState _state; + + /** In RETRY state this indicates whether it's time to call restart(). */ + uint32 _retryInSeconds; + + /** Sets FINISHED state and calls the _errorCallback with given error. */ + virtual void finishError(ErrorResponse error); + + /** Sets FINISHED state. Implementations might extend it if needed. */ + virtual void finishSuccess(); + +public: + Request(DataCallback cb, ErrorCallback ecb); + virtual ~Request(); + + /** Method, which does actual work. Depends on what this Request is doing. */ + virtual void handle() = 0; + + /** Method, which is called by ConnectionManager when Request's state is RETRY. */ + virtual void handleRetry(); + + /** Method, which is used to restart the Request. */ + virtual void restart() = 0; + + /** Method, which is called to pause the Request. */ + virtual void pause(); + + /** + * Method, which is called to *interrupt* the Request. + * When it's called, Request must stop its work and + * call the failure callback to notify user. + */ + virtual void finish(); + + /** Method, which is called to retry the Request. */ + virtual void retry(uint32 seconds); + + /** Returns Request's current state. */ + RequestState state() const; + + /** + * Return date this Request received from server. + * It could be extracted from "Date" header, + * which is kept in NetworkReadStream. + * + * @note not all Requests do that, so "" is returned + * to indicate the date is unknown. That's also true + * if no server response available or no "Date" header + * was passed. + * + * @returns date from "Date" response header. + */ + virtual Common::String date() const; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/make_archive.py b/backends/networking/make_archive.py new file mode 100644 index 0000000000..64d314bedd --- /dev/null +++ b/backends/networking/make_archive.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +import sys +import re +import os +import zipfile + +ARCHIVE_FILE_EXTENSIONS = ('.html', '.css', '.js', '.ico', '.png') + +def buildArchive(archiveName): + if not os.path.isdir(archiveName): + print ("Invalid archive name: " + archiveName) + return + + zf = zipfile.ZipFile(archiveName + ".zip", 'w') + + print ("Building '" + archiveName + "' archive:") + os.chdir(archiveName) + + directories = ['.', './icons'] + for d in directories: + filenames = os.listdir(d) + filenames.sort() + for filename in filenames: + if os.path.isfile(d + '/' + filename) and filename.endswith(ARCHIVE_FILE_EXTENSIONS): + zf.write(d + '/' + filename, d + '/' + filename) + print (" Adding file: " + d + '/' + filename) + + os.chdir('../') + + zf.close() + +def main(): + buildArchive("wwwroot") + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backends/networking/sdl_net/client.cpp b/backends/networking/sdl_net/client.cpp new file mode 100644 index 0000000000..dab38ba5c0 --- /dev/null +++ b/backends/networking/sdl_net/client.cpp @@ -0,0 +1,190 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/sdl_net/client.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/memstream.h" +#include <SDL/SDL_net.h> + +namespace Networking { + +Client::Client(): + _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), + _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {} + +Client::Client(SDLNet_SocketSet set, TCPsocket socket): + _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), + _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) { + open(set, socket); +} + +Client::~Client() { + close(); + delete[] _buffer; +} + +void Client::open(SDLNet_SocketSet set, TCPsocket socket) { + if (_state != INVALID) + close(); + _state = READING_HEADERS; + _socket = socket; + _set = set; + Reader cleanReader; + _reader = cleanReader; + if (_handler) + delete _handler; + _handler = nullptr; + if (_previousHandler) + delete _previousHandler; + _previousHandler = nullptr; + _stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + if (set) { + int numused = SDLNet_TCP_AddSocket(set, socket); + if (numused == -1) { + error("Client: SDLNet_AddSocket: %s\n", SDLNet_GetError()); + } + } +} + +bool Client::readMoreIfNeeded() { + if (_stream == nullptr) + return false; //nothing to read into + if (_stream->size() - _stream->pos() > 0) + return true; //not needed, some data left in the stream + if (!_socket) + return false; + if (!SDLNet_SocketReady(_socket)) + return false; + + int bytes = SDLNet_TCP_Recv(_socket, _buffer, CLIENT_BUFFER_SIZE); + if (bytes <= 0) { + warning("Client::readMoreIfNeeded: recv fail"); + close(); + return false; + } + + if (_stream->write(_buffer, bytes) != bytes) { + warning("Client::readMoreIfNeeded: failed to write() into MemoryReadWriteStream"); + close(); + return false; + } + + return true; +} + +void Client::readHeaders() { + if (!readMoreIfNeeded()) + return; + _reader.setContent(_stream); + if (_reader.readFirstHeaders()) + _state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS); +} + +bool Client::readContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readFirstContent(stream); +} + +bool Client::readBlockHeaders(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readBlockHeaders(stream); +} + +bool Client::readBlockContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readBlockContent(stream); +} + +void Client::setHandler(ClientHandler *handler) { + if (_handler) { + if (_previousHandler) + delete _previousHandler; + _previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself + } + _state = BEING_HANDLED; + _handler = handler; +} + +void Client::handle() { + if (_state != BEING_HANDLED) + warning("handle() called in a wrong Client's state"); + if (!_handler) + warning("Client doesn't have handler to be handled by"); + if (_handler) + _handler->handle(this); +} + +void Client::close() { + if (_set) { + if (_socket) { + int numused = SDLNet_TCP_DelSocket(_set, _socket); + if (numused == -1) + error("Client: SDLNet_DelSocket: %s\n", SDLNet_GetError()); + } + _set = nullptr; + } + + if (_socket) { + SDLNet_TCP_Close(_socket); + _socket = nullptr; + } + + if (_stream) { + delete _stream; + _stream = nullptr; + } + + _state = INVALID; +} + + +ClientState Client::state() const { return _state; } + +Common::String Client::headers() const { return _reader.headers(); } + +Common::String Client::method() const { return _reader.method(); } + +Common::String Client::path() const { return _reader.path(); } + +Common::String Client::query() const { return _reader.query(); } + +Common::String Client::queryParameter(Common::String name) const { return _reader.queryParameter(name); } + +Common::String Client::anchor() const { return _reader.anchor(); } + +bool Client::noMoreContent() const { return _reader.noMoreContent(); } + +bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); } + +int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); } + +int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); } + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/client.h b/backends/networking/sdl_net/client.h new file mode 100644 index 0000000000..134c1be05d --- /dev/null +++ b/backends/networking/sdl_net/client.h @@ -0,0 +1,125 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_CLIENT_H +#define BACKENDS_NETWORKING_SDL_NET_CLIENT_H + +#include "backends/networking/sdl_net/reader.h" +#include "common/str.h" + +namespace Common { +class MemoryReadWriteStream; +} + +typedef struct _SDLNet_SocketSet *SDLNet_SocketSet; +typedef struct _TCPsocket *TCPsocket; + +namespace Networking { + +enum ClientState { + INVALID, + READING_HEADERS, + READ_HEADERS, + BAD_REQUEST, + BEING_HANDLED +}; + +class Client; + +#define CLIENT_BUFFER_SIZE 1 * 1024 * 1024 + +class ClientHandler { +public: + virtual ~ClientHandler() {}; + virtual void handle(Client *client) = 0; +}; + +/** + * Client class represents one client's HTTP request + * to the LocalWebserver. + * + * While in READING_HEADERS state, it's kept in LocalWebserver. + * Client must read the headers and decide whether it's + * READ_HEADERS (could be handled) or BAD_REQUEST (failed). + * + * If it's READ_HEADERS, LocalWebserver searches for a corresponding + * BaseHandler. These classes use the information from headers - + * like method, path, GET parameters - to build the response + * for this client's request. When they do, they call setHandler() + * and pass a special ClientHandler. Client becomes BEING_HANDLED. + * + * While in that state, LocalWebserver calls Client's handle() and + * it's passed to ClientHandler. The latter does the job: it commands + * Client to read or write bytes with its socket or calls + * readContent() methods, so Client reads the request through Reader. + */ + +class Client { + ClientState _state; + SDLNet_SocketSet _set; + TCPsocket _socket; + Reader _reader; + ClientHandler *_handler, *_previousHandler; + Common::MemoryReadWriteStream *_stream; + byte *_buffer; + + bool readMoreIfNeeded(); + +public: + Client(); + Client(SDLNet_SocketSet set, TCPsocket socket); + virtual ~Client(); + + void open(SDLNet_SocketSet set, TCPsocket socket); + void readHeaders(); + bool readContent(Common::WriteStream *stream); + bool readBlockHeaders(Common::WriteStream *stream); + bool readBlockContent(Common::WriteStream *stream); + void setHandler(ClientHandler *handler); + void handle(); + void close(); + + ClientState state() const; + Common::String headers() const; + Common::String method() const; + Common::String path() const; + Common::String query() const; + Common::String queryParameter(Common::String name) const; + Common::String anchor() const; + + bool noMoreContent() const; + + /** + * Return SDLNet_SocketReady(_socket). + * + * It's "ready" when it has something + * to read (recv()). You can send() + * when this is false. + */ + bool socketIsReady(); + int recv(void *data, int maxlen); + int send(void *data, int len); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/getclienthandler.cpp b/backends/networking/sdl_net/getclienthandler.cpp new file mode 100644 index 0000000000..1c4f5db8a8 --- /dev/null +++ b/backends/networking/sdl_net/getclienthandler.cpp @@ -0,0 +1,162 @@ +/* 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 "backends/networking/sdl_net/getclienthandler.h" +#include "common/textconsole.h" + +namespace Networking { + +GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream): + _responseCode(200), _headersPrepared(false), + _stream(stream), _buffer(new byte[CLIENT_HANDLER_BUFFER_SIZE]) {} + +GetClientHandler::~GetClientHandler() { + delete _stream; + delete[] _buffer; +} + +const char *GetClientHandler::responseMessage(long responseCode) { + switch (responseCode) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 226: return "IM Used"; + + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Moved Temporarily"; //case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "RESERVED"; + case 307: return "Temporary Redirect"; + + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Large"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Unordered Collection"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 434: return "Requested Host Unavailable"; + case 449: return "Retry With"; + case 451: return "Unavailable For Legal Reasons"; + + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 509: return "Bandwidth Limit Exceeded"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + } + return "Unknown"; +} + +void GetClientHandler::prepareHeaders() { + if (!_specialHeaders.contains("Content-Type")) + setHeader("Content-Type", "text/html"); + + if (!_specialHeaders.contains("Content-Length") && _stream) + setHeader("Content-Length", Common::String::format("%u", _stream->size())); + + _headers = Common::String::format("HTTP/1.1 %ld %s\r\n", _responseCode, responseMessage(_responseCode)); + for (Common::HashMap<Common::String, Common::String>::iterator i = _specialHeaders.begin(); i != _specialHeaders.end(); ++i) + _headers += i->_key + ": " + i->_value + "\r\n"; + _headers += "\r\n"; + + _headersPrepared = true; +} + +void GetClientHandler::handle(Client *client) { + if (!client) + return; + if (!_headersPrepared) + prepareHeaders(); + + uint32 readBytes; + + // send headers first + if (_headers.size() > 0) { + readBytes = _headers.size(); + if (readBytes > CLIENT_HANDLER_BUFFER_SIZE) + readBytes = CLIENT_HANDLER_BUFFER_SIZE; + memcpy(_buffer, _headers.c_str(), readBytes); + _headers.erase(0, readBytes); + } else { + if (!_stream) { + client->close(); + return; + } + + readBytes = _stream->read(_buffer, CLIENT_HANDLER_BUFFER_SIZE); + } + + if (readBytes != 0) + if (client->send(_buffer, readBytes) != readBytes) { + warning("GetClientHandler: unable to send all bytes to the client"); + client->close(); + return; + } + + // we're done here! + if (_stream->eos()) + client->close(); +} + +void GetClientHandler::setHeader(Common::String name, Common::String value) { _specialHeaders[name] = value; } +void GetClientHandler::setResponseCode(long code) { _responseCode = code; } + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/getclienthandler.h b/backends/networking/sdl_net/getclienthandler.h new file mode 100644 index 0000000000..3486ceef8a --- /dev/null +++ b/backends/networking/sdl_net/getclienthandler.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 BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H + +#include "backends/networking/sdl_net/client.h" +#include "common/hashmap.h" +#include "common/stream.h" +#include "common/hash-str.h" + +namespace Networking { + +#define CLIENT_HANDLER_BUFFER_SIZE 1 * 1024 * 1024 + +class GetClientHandler: public ClientHandler { + Common::HashMap<Common::String, Common::String> _specialHeaders; + long _responseCode; + bool _headersPrepared; + Common::String _headers; + Common::SeekableReadStream *_stream; + byte *_buffer; + + static const char *responseMessage(long responseCode); + void prepareHeaders(); + +public: + GetClientHandler(Common::SeekableReadStream *stream); + virtual ~GetClientHandler(); + + virtual void handle(Client *client); + void setHeader(Common::String name, Common::String value); + void setResponseCode(long code); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/basehandler.h b/backends/networking/sdl_net/handlers/basehandler.h new file mode 100644 index 0000000000..dec5e955bd --- /dev/null +++ b/backends/networking/sdl_net/handlers/basehandler.h @@ -0,0 +1,41 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H + +#include "backends/networking/sdl_net/client.h" + +namespace Networking { + +class BaseHandler { +public: + BaseHandler() {} + virtual ~BaseHandler() {} + + virtual void handle(Client &) = 0; + virtual bool minimalModeSupported() { return false; } +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp new file mode 100644 index 0000000000..284bf16651 --- /dev/null +++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp @@ -0,0 +1,129 @@ +/* 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 "backends/networking/sdl_net/handlers/createdirectoryhandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" +#include <common/callback.h> + +namespace Networking { + +CreateDirectoryHandler::CreateDirectoryHandler() {} + +CreateDirectoryHandler::~CreateDirectoryHandler() {} + +void CreateDirectoryHandler::handleError(Client &client, Common::String message) const { + if (client.queryParameter("answer_json") == "true") + setJsonResponseHandler(client, "error", message); + else + HandlerUtils::setFilesManagerErrorMessageHandler(client, message); +} + +void CreateDirectoryHandler::setJsonResponseHandler(Client &client, Common::String type, Common::String message) const { + Common::JSONObject response; + response.setVal("type", new Common::JSONValue(type)); + response.setVal("message", new Common::JSONValue(message)); + + Common::JSONValue json = response; + LocalWebserver::setClientGetHandler(client, json.stringify(true)); +} + +/// public + +void CreateDirectoryHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + Common::String name = client.queryParameter("directory_name"); + + // check that <path> is not an absolute root + if (path == "" || path == "/") { + handleError(client, _("Can't create directory here!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + handleError(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd) || path.empty()) { + handleError(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + handleError(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + handleError(client, _("Parent directory doesn't exists!")); + return; + } + if (!node->isDirectory()) { + handleError(client, _("Can't create a directory within a file!")); + return; + } + + // check that <directory_name> doesn't exist or is directory + if (path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + node = g_system->getFilesystemFactory()->makeFileNodePath(path + name); + if (node->exists()) { + if (!node->isDirectory()) { + handleError(client, _("There is a file with that name in the parent directory!")); + return; + } + } else { + // create the <directory_name> in <path> + if (!node->create(true)) { + handleError(client, _("Failed to create the directory!")); + return; + } + } + + // if json requested, respond with it + if (client.queryParameter("answer_json") == "true") { + setJsonResponseHandler(client, "success", _("Directory created successfully!")); + return; + } + + // set redirect on success + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + _("Directory created successfully!"), + client.queryParameter("path").c_str(), + _("Back to parent directory") + ), + (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") + + LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path")) + ); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.h b/backends/networking/sdl_net/handlers/createdirectoryhandler.h new file mode 100644 index 0000000000..2a18d5c4aa --- /dev/null +++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.h @@ -0,0 +1,42 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class CreateDirectoryHandler: public FilesBaseHandler { + void handleError(Client &client, Common::String message) const; + void setJsonResponseHandler(Client &client, Common::String type, Common::String message) const; +public: + CreateDirectoryHandler(); + virtual ~CreateDirectoryHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.cpp b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp new file mode 100644 index 0000000000..9e212b1a6c --- /dev/null +++ b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp @@ -0,0 +1,88 @@ +/* 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 "backends/networking/sdl_net/handlers/downloadfilehandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/getclienthandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" + +namespace Networking { + +DownloadFileHandler::DownloadFileHandler() {} + +DownloadFileHandler::~DownloadFileHandler() {} + +/// public + +void DownloadFileHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + + // check that <path> is not an absolute root + if (path == "" || path == "/") { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The file doesn't exist!")); + return; + } + if (node->isDirectory()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't download a directory!")); + return; + } + Common::SeekableReadStream *stream = node->createReadStream(); + if (stream == nullptr) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Failed to read the file!")); + return; + } + + GetClientHandler *handler = new GetClientHandler(stream); + handler->setResponseCode(200); + handler->setHeader("Content-Type", "application/force-download"); + handler->setHeader("Content-Disposition", "attachment; filename=\"" + node->getDisplayName() + "\""); + handler->setHeader("Content-Transfer-Encoding", "binary"); + client.setHandler(handler); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.h b/backends/networking/sdl_net/handlers/downloadfilehandler.h new file mode 100644 index 0000000000..5fa5e5d55a --- /dev/null +++ b/backends/networking/sdl_net/handlers/downloadfilehandler.h @@ -0,0 +1,40 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class DownloadFileHandler: public FilesBaseHandler { +public: + DownloadFileHandler(); + virtual ~DownloadFileHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp new file mode 100644 index 0000000000..8c5ee29b70 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp @@ -0,0 +1,81 @@ +/* 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 "backends/networking/sdl_net/handlers/filesajaxpagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" + +namespace Networking { + +#define FILES_PAGE_NAME ".filesAJAX.html" + +FilesAjaxPageHandler::FilesAjaxPageHandler() {} + +FilesAjaxPageHandler::~FilesAjaxPageHandler() {} + +namespace { + +Common::String encodeDoubleQuotesAndSlashes(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '"') { + result += "\\\""; + } else if (s[i] == '\\') { + result += "\\\\"; + } else { + result += s[i]; + } + return result; +} + +} + +/// public + +void FilesAjaxPageHandler::handle(Client &client) { + // load stylish response page from the archive + Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME); + if (stream == nullptr) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The page is not available without the resources.")); + return; + } + + Common::String response = HandlerUtils::readEverythingFromStream(stream); + Common::String path = client.queryParameter("path"); + + //these occur twice: + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{upload_files_button}", _("Upload files")); //tab + replace(response, "{upload_file_button}", _("Upload files")); //button in the tab + replace(response, "{create_directory_desc}", _("Type new directory name:")); + replace(response, "{upload_file_desc}", _("Select a file to upload:")); + replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):")); + replace(response, "{index_of}", _("Index of ")); + replace(response, "{loading}", _("Loading...")); + replace(response, "{error}", _("Error occurred")); + replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path)); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.h b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h new file mode 100644 index 0000000000..1d9b125c2e --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h @@ -0,0 +1,40 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class FilesAjaxPageHandler: public FilesBaseHandler { +public: + FilesAjaxPageHandler(); + virtual ~FilesAjaxPageHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.cpp b/backends/networking/sdl_net/handlers/filesbasehandler.cpp new file mode 100644 index 0000000000..135e0fb100 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesbasehandler.cpp @@ -0,0 +1,87 @@ +/* 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 "backends/networking/sdl_net/handlers/filesbasehandler.h" +#include "backends/saves/default/default-saves.h" +#include "common/config-manager.h" +#include "common/system.h" + +namespace Networking { + +FilesBaseHandler::FilesBaseHandler() {} + +FilesBaseHandler::~FilesBaseHandler() {} + +Common::String FilesBaseHandler::parentPath(Common::String path) { + if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar(); + if (!path.empty()) { + for (int i = path.size() - 1; i >= 0; --i) + if (i == 0 || path[i] == '/' || path[i] == '\\') { + path.erase(i); + break; + } + } + if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + return path; +} + +bool FilesBaseHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory) { + // <path> is not empty, but could lack the trailing slash + if (isDirectory && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + + if (path.hasPrefix("/root") && ConfMan.hasKey("rootpath", "cloud")) { + prefixToAdd = "/root/"; + prefixToRemove = ConfMan.get("rootpath", "cloud"); + if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\') + prefixToRemove += '/'; + if (prefixToRemove == "/") prefixToRemove = ""; + path.erase(0, 5); + if (path.size() && (path[0] == '/' || path[0] == '\\')) + path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c" + path = prefixToRemove + path; + if (path == "") + path = "/"; // absolute root is '/' + return true; + } + + if (path.hasPrefix("/saves")) { + prefixToAdd = "/saves/"; + + // determine savepath (prefix to remove) + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath")); + if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\') + prefixToRemove += '/'; + + path.erase(0, 6); + if (path.size() && (path[0] == '/' || path[0] == '\\')) + path.deleteChar(0); + path = prefixToRemove + path; + return true; + } + + return false; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.h b/backends/networking/sdl_net/handlers/filesbasehandler.h new file mode 100644 index 0000000000..1c7f4dd799 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesbasehandler.h @@ -0,0 +1,52 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" + +namespace Networking { + +class FilesBaseHandler: public BaseHandler { +protected: + Common::String parentPath(Common::String path); + + /** + * Transforms virtual <path> into actual file system path. + * + * Fills prefixes with actual file system prefix ("to remove") + * and virtual path prefix ("to add"). + * + * Returns true on success. + */ + bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory = true); +public: + FilesBaseHandler(); + virtual ~FilesBaseHandler(); + + virtual void handle(Client &client) = 0; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filespagehandler.cpp b/backends/networking/sdl_net/handlers/filespagehandler.cpp new file mode 100644 index 0000000000..e117b4922c --- /dev/null +++ b/backends/networking/sdl_net/handlers/filespagehandler.cpp @@ -0,0 +1,237 @@ +/* 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 "backends/networking/sdl_net/handlers/filespagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/config-manager.h" +#include "common/translation.h" + +namespace Networking { + +#define INDEX_PAGE_NAME ".index.html" +#define FILES_PAGE_NAME ".files.html" + +FilesPageHandler::FilesPageHandler() {} + +FilesPageHandler::~FilesPageHandler() {} + +namespace { +Common::String encodeDoubleQuotes(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '"') { + result += "\\\""; + } else { + result += s[i]; + } + return result; +} + +Common::String encodeHtmlEntities(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '<') + result += "<"; + else if (s[i] == '>') + result += ">"; + else if (s[i] == '&') + result += "&"; + else if (s[i] > 0x7F) + result += Common::String::format("&#%d;", (int)s[i]); + else result += s[i]; + return result; +} + +Common::String getDisplayPath(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '\\') + result += '/'; + else + result += s[i]; + if (result == "") + return "/"; + return result; +} +} + +bool FilesPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) { + if (path == "" || path == "/") { + if (ConfMan.hasKey("rootpath", "cloud")) + addItem(content, itemTemplate, IT_DIRECTORY, "/root/", _("File system root")); + addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", _("Saved games")); + return true; + } + + if (HandlerUtils::hasForbiddenCombinations(path)) + return false; + + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd)) + return false; + + Common::FSNode node = Common::FSNode(path); + if (path == "/") + node = node.getParent(); // absolute root + + if (!HandlerUtils::permittedPath(node.getPath())) + return false; + + if (!node.isDirectory()) + return false; + + // list directory + Common::FSList _nodeContent; + if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files + _nodeContent.clear(); + else + Common::sort(_nodeContent.begin(), _nodeContent.end()); + + // add parent directory link + { + Common::String filePath = path; + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + if (filePath == "" || filePath == "/" || filePath == "\\") + filePath = "/"; + else + filePath = parentPath(prefixToAdd + filePath); + addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, _("Parent directory")); + } + + // fill the content + for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) { + Common::String name = i->getDisplayName(); + if (i->isDirectory()) + name += "/"; + + Common::String filePath = i->getPath(); + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + filePath = prefixToAdd + filePath; + + addItem(content, itemTemplate, detectType(i->isDirectory(), name), filePath, name); + } + + return true; +} + +FilesPageHandler::ItemType FilesPageHandler::detectType(bool isDirectory, const Common::String &name) { + if (isDirectory) + return IT_DIRECTORY; + if (name.hasSuffix(".txt")) + return IT_TXT; + if (name.hasSuffix(".zip")) + return IT_ZIP; + if (name.hasSuffix(".7z")) + return IT_7Z; + return IT_UNKNOWN; +} + +void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size) const { + Common::String item = itemTemplate, icon; + bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY); + switch (itemType) { + case IT_DIRECTORY: + icon = "dir.png"; + break; + case IT_PARENT_DIRECTORY: + icon = "up.png"; + break; + case IT_TXT: + icon = "txt.png"; + break; + case IT_ZIP: + icon = "zip.png"; + break; + case IT_7Z: + icon = "7z.png"; + break; + default: + icon = "unk.png"; + } + replace(item, "{icon}", icon); + replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + LocalWebserver::urlEncodeQueryParameterValue(path)); + replace(item, "{name}", encodeHtmlEntities(name)); + replace(item, "{size}", size); + content += item; +} + +/// public + +void FilesPageHandler::handle(Client &client) { + Common::String response = + "<html>" \ + "<head><title>ScummVM</title></head>" \ + "<body>" \ + "<p>{create_directory_desc}</p>" \ + "<form action=\"create\">" \ + "<input type=\"hidden\" name=\"path\" value=\"{path}\"/>" \ + "<input type=\"text\" name=\"directory_name\" value=\"\"/>" \ + "<input type=\"submit\" value=\"{create_directory_button}\"/>" \ + "</form>" \ + "<hr/>" \ + "<p>{upload_file_desc}</p>" \ + "<form action=\"upload?path={path}\" method=\"post\" enctype=\"multipart/form-data\">" \ + "<input type=\"file\" name=\"upload_file-f\" allowdirs multiple/>" \ + "<span>{or_upload_directory_desc}</span>" \ + "<input type=\"file\" name=\"upload_file-d\" directory webkitdirectory multiple/>" \ + "<input type=\"submit\" value=\"{upload_file_button}\"/>" \ + "</form>" + "<hr/>" \ + "<h1>{index_of_directory}</h1>" \ + "<table>{content}</table>" \ + "</body>" \ + "</html>"; + Common::String itemTemplate = "<tr><td><img src=\"icons/{icon}\"/></td><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too? + + // load stylish response page from the archive + Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME); + if (stream) + response = HandlerUtils::readEverythingFromStream(stream); + + Common::String path = client.queryParameter("path"); + Common::String content = ""; + + // show an error message if failed to list directory + if (!listDirectory(path, content, itemTemplate)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("ScummVM couldn't list the directory you specified.")); + return; + } + + //these occur twice: + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path"))); + replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path"))); + replace(response, "{upload_files_button}", _("Upload files")); //tab + replace(response, "{upload_file_button}", _("Upload files")); //button in the tab + replace(response, "{create_directory_desc}", _("Type new directory name:")); + replace(response, "{upload_file_desc}", _("Select a file to upload:")); + replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):")); + replace(response, "{index_of_directory}", Common::String::format(_("Index of %s"), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str())); + replace(response, "{content}", content); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filespagehandler.h b/backends/networking/sdl_net/handlers/filespagehandler.h new file mode 100644 index 0000000000..e404036cb6 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filespagehandler.h @@ -0,0 +1,62 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class FilesPageHandler: public FilesBaseHandler { + enum ItemType { + IT_DIRECTORY, + IT_PARENT_DIRECTORY, + IT_TXT, + IT_ZIP, + IT_7Z, + IT_UNKNOWN + }; + + /** + * Lists the directory <path>. + * + * Returns true on success. + */ + bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate); + + /** Helper method for detecting items' type. */ + static ItemType detectType(bool isDirectory, const Common::String &name); + + /** Helper method for adding items into the files list. */ + void addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size = "") const; + +public: + FilesPageHandler(); + virtual ~FilesPageHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.cpp b/backends/networking/sdl_net/handlers/indexpagehandler.cpp new file mode 100644 index 0000000000..985bd6635e --- /dev/null +++ b/backends/networking/sdl_net/handlers/indexpagehandler.cpp @@ -0,0 +1,65 @@ +/* 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 "backends/networking/sdl_net/handlers/indexpagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" +#include "gui/storagewizarddialog.h" + +namespace Networking { + +IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {} + +IndexPageHandler::~IndexPageHandler() {} + +/// public + +Common::String IndexPageHandler::code() const { return _code; } + +void IndexPageHandler::handle(Client &client) { + Common::String code = client.queryParameter("code"); + + if (code == "") { + // redirect to "/filesAJAX" + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files\">%s</a>", + _("This is a local webserver index page."), + _("Open Files manager") + ), + "/filesAJAX" + ); + return; + } + + _code = code; + sendCommand(GUI::kStorageCodePassedCmd, 0); + HandlerUtils::setMessageHandler(client, _("ScummVM got the code and already connects to your cloud storage!")); +} + +bool IndexPageHandler::minimalModeSupported() { + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.h b/backends/networking/sdl_net/handlers/indexpagehandler.h new file mode 100644 index 0000000000..8065954b27 --- /dev/null +++ b/backends/networking/sdl_net/handlers/indexpagehandler.h @@ -0,0 +1,45 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" +#include "gui/object.h" + +namespace Networking { +class LocalWebserver; + +class IndexPageHandler: public BaseHandler, public GUI::CommandSender { + Common::String _code; +public: + IndexPageHandler(); + virtual ~IndexPageHandler(); + + Common::String code() const; + virtual void handle(Client &client); + virtual bool minimalModeSupported(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.cpp b/backends/networking/sdl_net/handlers/listajaxhandler.cpp new file mode 100644 index 0000000000..f94b674a3c --- /dev/null +++ b/backends/networking/sdl_net/handlers/listajaxhandler.cpp @@ -0,0 +1,157 @@ +/* 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 "backends/networking/sdl_net/handlers/listajaxhandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/config-manager.h" +#include "common/json.h" +#include "common/translation.h" + +namespace Networking { + +ListAjaxHandler::ListAjaxHandler() {} + +ListAjaxHandler::~ListAjaxHandler() {} + +Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) { + Common::JSONArray itemsList; + Common::JSONObject errorResult; + Common::JSONObject successResult; + successResult.setVal("type", new Common::JSONValue("success")); + errorResult.setVal("type", new Common::JSONValue("error")); + + if (path == "" || path == "/") { + if (ConfMan.hasKey("rootpath", "cloud")) + addItem(itemsList, IT_DIRECTORY, "/root/", _("File system root")); + addItem(itemsList, IT_DIRECTORY, "/saves/", _("Saved games")); + successResult.setVal("items", new Common::JSONValue(itemsList)); + return successResult; + } + + if (HandlerUtils::hasForbiddenCombinations(path)) + return errorResult; + + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd)) + return errorResult; + + Common::FSNode node = Common::FSNode(path); + if (path == "/") + node = node.getParent(); // absolute root + + if (!HandlerUtils::permittedPath(node.getPath())) + return errorResult; + + if (!node.isDirectory()) + return errorResult; + + // list directory + Common::FSList _nodeContent; + if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files + _nodeContent.clear(); + else + Common::sort(_nodeContent.begin(), _nodeContent.end()); + + // add parent directory link + { + Common::String filePath = path; + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + if (filePath == "" || filePath == "/" || filePath == "\\") + filePath = "/"; + else + filePath = parentPath(prefixToAdd + filePath); + addItem(itemsList, IT_PARENT_DIRECTORY, filePath, _("Parent directory")); + } + + // fill the content + for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) { + Common::String name = i->getDisplayName(); + if (i->isDirectory()) name += "/"; + + Common::String filePath = i->getPath(); + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + filePath = prefixToAdd + filePath; + + addItem(itemsList, detectType(i->isDirectory(), name), filePath, name); + } + + successResult.setVal("items", new Common::JSONValue(itemsList)); + return successResult; +} + +ListAjaxHandler::ItemType ListAjaxHandler::detectType(bool isDirectory, const Common::String &name) { + if (isDirectory) + return IT_DIRECTORY; + if (name.hasSuffix(".txt")) + return IT_TXT; + if (name.hasSuffix(".zip")) + return IT_ZIP; + if (name.hasSuffix(".7z")) + return IT_7Z; + return IT_UNKNOWN; +} + +void ListAjaxHandler::addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size) { + Common::String icon; + bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY); + switch (itemType) { + case IT_DIRECTORY: + icon = "dir.png"; + break; + case IT_PARENT_DIRECTORY: + icon = "up.png"; + break; + case IT_TXT: + icon = "txt.png"; + break; + case IT_ZIP: + icon = "zip.png"; + break; + case IT_7Z: + icon = "7z.png"; + break; + default: + icon = "unk.png"; + } + + Common::JSONObject item; + item.setVal("name", new Common::JSONValue(name)); + item.setVal("path", new Common::JSONValue(path)); + item.setVal("isDirectory", new Common::JSONValue(isDirectory)); + item.setVal("size", new Common::JSONValue(size)); + item.setVal("icon", new Common::JSONValue(icon)); + responseItemsList.push_back(new Common::JSONValue(item)); +} + +/// public + +void ListAjaxHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + Common::JSONValue jsonResponse = listDirectory(path); + Common::String response = jsonResponse.stringify(true); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.h b/backends/networking/sdl_net/handlers/listajaxhandler.h new file mode 100644 index 0000000000..40840ad6c3 --- /dev/null +++ b/backends/networking/sdl_net/handlers/listajaxhandler.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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" +#include "common/json.h" + +namespace Networking { + +class ListAjaxHandler: public FilesBaseHandler { + enum ItemType { + IT_DIRECTORY, + IT_PARENT_DIRECTORY, + IT_TXT, + IT_ZIP, + IT_7Z, + IT_UNKNOWN + }; + + /** + * Lists the directory <path>. + * + * Returns JSON with either listed directory or error response. + */ + Common::JSONObject listDirectory(Common::String path); + + /** Helper method for detecting items' type. */ + static ItemType detectType(bool isDirectory, const Common::String &name); + + /** Helper method for adding items into the files list. */ + static void addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size = ""); + +public: + ListAjaxHandler(); + virtual ~ListAjaxHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/resourcehandler.cpp b/backends/networking/sdl_net/handlers/resourcehandler.cpp new file mode 100644 index 0000000000..631eb63351 --- /dev/null +++ b/backends/networking/sdl_net/handlers/resourcehandler.cpp @@ -0,0 +1,75 @@ +/* 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 "backends/networking/sdl_net/handlers/resourcehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" + +namespace Networking { + +ResourceHandler::ResourceHandler() {} + +ResourceHandler::~ResourceHandler() {} + +const char *ResourceHandler::determineMimeType(Common::String &filename) { + // text + if (filename.hasSuffix(".html")) return "text/html"; + if (filename.hasSuffix(".css")) return "text/css"; + if (filename.hasSuffix(".txt")) return "text/plain"; + if (filename.hasSuffix(".js")) return "application/javascript"; + + // images + if (filename.hasSuffix(".jpeg") || filename.hasSuffix(".jpg") || filename.hasSuffix(".jpe")) return "image/jpeg"; + if (filename.hasSuffix(".gif")) return "image/gif"; + if (filename.hasSuffix(".png")) return "image/png"; + if (filename.hasSuffix(".svg")) return "image/svg+xml"; + if (filename.hasSuffix(".tiff")) return "image/tiff"; + if (filename.hasSuffix(".ico")) return "image/vnd.microsoft.icon"; + if (filename.hasSuffix(".wbmp")) return "image/vnd.wap.wbmp"; + + if (filename.hasSuffix(".zip")) return "application/zip"; + return "application/octet-stream"; +} + +/// public + +void ResourceHandler::handle(Client &client) { + Common::String filename = client.path(); + filename.deleteChar(0); + + // if archive hidden file is requested, ignore + if (filename.size() && filename[0] == '.') + return; + + // if file not found, don't set handler either + Common::SeekableReadStream *file = HandlerUtils::getArchiveFile(filename); + if (file == nullptr) + return; + + LocalWebserver::setClientGetHandler(client, file, 200, determineMimeType(filename)); +} + +bool ResourceHandler::minimalModeSupported() { + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/resourcehandler.h b/backends/networking/sdl_net/handlers/resourcehandler.h new file mode 100644 index 0000000000..2ec4c5bb19 --- /dev/null +++ b/backends/networking/sdl_net/handlers/resourcehandler.h @@ -0,0 +1,42 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" + +namespace Networking { + +class ResourceHandler: public BaseHandler { + static const char *determineMimeType(Common::String &filename); +public: + ResourceHandler(); + virtual ~ResourceHandler(); + + virtual void handle(Client &client); + virtual bool minimalModeSupported(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp new file mode 100644 index 0000000000..a0e992c25e --- /dev/null +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp @@ -0,0 +1,79 @@ +/* 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 "backends/networking/sdl_net/handlers/uploadfilehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/uploadfileclienthandler.h" +#include "backends/fs/fs-factory.h" +#include "common/system.h" +#include "common/translation.h" + +namespace Networking { + +UploadFileHandler::UploadFileHandler() {} + +UploadFileHandler::~UploadFileHandler() {} + +/// public + +void UploadFileHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + + // check that <path> is not an absolute root + if (path == "" || path == "/" || path == "\\") { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!")); + return; + } + if (!node->isDirectory()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!")); + return; + } + + // if all OK, set special handler + client.setHandler(new UploadFileClientHandler(path)); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.h b/backends/networking/sdl_net/handlers/uploadfilehandler.h new file mode 100644 index 0000000000..cbff215156 --- /dev/null +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.h @@ -0,0 +1,40 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class UploadFileHandler: public FilesBaseHandler { +public: + UploadFileHandler(); + virtual ~UploadFileHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlerutils.cpp b/backends/networking/sdl_net/handlerutils.cpp new file mode 100644 index 0000000000..dc21ab5ce1 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.cpp @@ -0,0 +1,200 @@ +/* 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 "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/saves/default/default-saves.h" +#include "common/archive.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/translation.h" +#include "common/unzip.h" + +namespace Networking { + +#define ARCHIVE_NAME "wwwroot.zip" + +#define INDEX_PAGE_NAME ".index.html" + +Common::Archive *HandlerUtils::getZipArchive() { + // first search in themepath + if (ConfMan.hasKey("themepath")) { + const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath")); + if (!node.exists() || !node.isReadable() || !node.isDirectory()) + return nullptr; + + Common::FSNode fileNode = node.getChild(ARCHIVE_NAME); + if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) { + Common::SeekableReadStream *const stream = fileNode.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) + return zipArchive; + } + } + + // then use SearchMan to find it + Common::ArchiveMemberList fileList; + SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME); + for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) { + Common::ArchiveMember const &m = **it; + Common::SeekableReadStream *const stream = m.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) + return zipArchive; + } + + return nullptr; +} + +Common::ArchiveMemberList HandlerUtils::listArchive() { + Common::ArchiveMemberList resultList; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + zipArchive->listMembers(resultList); + delete zipArchive; + } + return resultList; +} + +Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) { + Common::SeekableReadStream *result = nullptr; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name); + if (ptr.get() == nullptr) + return nullptr; + result = ptr->createReadStream(); + delete zipArchive; + } + return result; +} + +Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (!stream->eos()) { + readBytes = stream->read(buf, 1024); + result += Common::String(buf, readBytes); + } + return result; +} + +Common::String HandlerUtils::normalizePath(const Common::String &path) { + Common::String normalized; + bool slash = false; + for (uint32 i = 0; i < path.size(); ++i) { + char c = path[i]; + if (c == '\\' || c == '/') { + slash = true; + continue; + } + + if (slash) { + normalized += '/'; + slash = false; + } + + if ('A' <= c && c <= 'Z') { + normalized += c - 'A' + 'a'; + } else { + normalized += c; + } + } + if (slash) normalized += '/'; + return normalized; +} + +bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) { + return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\")); +} + +bool HandlerUtils::isBlacklisted(const Common::String &path) { + const char *blacklist[] = { + "/etc", + "/bin", + "c:/windows" // just saying: I know guys who install windows on another drives + }; + + // normalize path + Common::String normalized = normalizePath(path); + + uint32 size = sizeof(blacklist) / sizeof(const char *); + for (uint32 i = 0; i < size; ++i) + if (normalized.hasPrefix(blacklist[i])) + return true; + + return false; +} + +bool HandlerUtils::hasPermittedPrefix(const Common::String &path) { + // normalize path + Common::String normalized = normalizePath(path); + + // prefix for /root/ + Common::String prefix; + if (ConfMan.hasKey("rootpath", "cloud")) { + prefix = normalizePath(ConfMan.get("rootpath", "cloud")); + if (prefix == "/" || normalized.hasPrefix(prefix)) + return true; + } + + // prefix for /saves/ + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath")); + return (normalized.hasPrefix(normalizePath(prefix))); +} + +bool HandlerUtils::permittedPath(const Common::String path) { + return hasPermittedPrefix(path) && !isBlacklisted(path); +} + +void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>"; + + // load stylish response page from the archive + Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME); + if (stream) + response = readEverythingFromStream(stream); + + replace(response, "{message}", message); + if (redirectTo.empty()) + LocalWebserver::setClientGetHandler(client, response); + else + LocalWebserver::setClientRedirectHandler(client, response, redirectTo); +} + +void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files%s?path=%s\">%s</a>", + message.c_str(), + client.queryParameter("ajax") == "true" ? "AJAX" : "", + "%2F", //that's encoded "/" + _("Back to the files manager") + ), + redirectTo + ); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlerutils.h b/backends/networking/sdl_net/handlerutils.h new file mode 100644 index 0000000000..4c2eff49b6 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.h @@ -0,0 +1,50 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H +#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H + +#include "backends/networking/sdl_net/client.h" +#include "common/archive.h" + +namespace Networking { + +class HandlerUtils { +public: + static Common::Archive *getZipArchive(); + static Common::ArchiveMemberList listArchive(); + static Common::SeekableReadStream *getArchiveFile(Common::String name); + static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream); + + static Common::String normalizePath(const Common::String &path); + static bool hasForbiddenCombinations(const Common::String &path); + static bool isBlacklisted(const Common::String &path); + static bool hasPermittedPrefix(const Common::String &path); + static bool permittedPath(const Common::String path); + + static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); + static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/localwebserver.cpp b/backends/networking/sdl_net/localwebserver.cpp new file mode 100644 index 0000000000..6557c7a88d --- /dev/null +++ b/backends/networking/sdl_net/localwebserver.cpp @@ -0,0 +1,446 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/networking/sdl_net/getclienthandler.h" +#include "common/memstream.h" +#include "common/str.h" +#include "common/system.h" +#include "common/timer.h" +#include "common/translation.h" +#include <SDL/SDL_net.h> +#include <common/config-manager.h> + +#ifdef POSIX +#include <sys/types.h> +#include <ifaddrs.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +namespace Common { +class MemoryReadWriteStream; + +DECLARE_SINGLETON(Networking::LocalWebserver); + +} + +namespace Networking { + +LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false), + _stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) { + addPathHandler("/", &_indexPageHandler); + addPathHandler("/files", &_filesPageHandler); + addPathHandler("/create", &_createDirectoryHandler); + addPathHandler("/download", &_downloadFileHandler); + addPathHandler("/upload", &_uploadFileHandler); + addPathHandler("/list", &_listAjaxHandler); + addPathHandler("/filesAJAX", &_filesAjaxPageHandler); + _defaultHandler = &_resourceHandler; +} + +LocalWebserver::~LocalWebserver() { + stop(); +} + +void localWebserverTimer(void *ignored) { + LocalServer.handle(); +} + +void LocalWebserver::startTimer(int interval) { + Common::TimerManager *manager = g_system->getTimerManager(); + if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) { + _timerStarted = true; + } else { + warning("Failed to install Networking::LocalWebserver's timer"); + } +} + +void LocalWebserver::stopTimer() { + Common::TimerManager *manager = g_system->getTimerManager(); + manager->removeTimerProc(localWebserverTimer); + _timerStarted = false; +} + +void LocalWebserver::start(bool useMinimalMode) { + _handleMutex.lock(); + _serverPort = getPort(); + _stopOnIdle = false; + if (_timerStarted) { + _handleMutex.unlock(); + return; + } + _minimalMode = useMinimalMode; + startTimer(); + + // Create a listening TCP socket + IPaddress ip; + if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) { + error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } + + resolveAddress(&ip); + + _serverSocket = SDLNet_TCP_Open(&ip); + if (!_serverSocket) { + warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError()); + stopTimer(); + g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again.")); + _handleMutex.unlock(); + return; + } + + // Create a socket set + _set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket + if (!_set) { + error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError()); + } + + int numused = SDLNet_TCP_AddSocket(_set, _serverSocket); + if (numused == -1) { + error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError()); + } + _handleMutex.unlock(); +} + +void LocalWebserver::stop() { + _handleMutex.lock(); + if (_timerStarted) + stopTimer(); + + if (_serverSocket) { + SDLNet_TCP_Close(_serverSocket); + _serverSocket = nullptr; + } + + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + _client[i].close(); + + _clients = 0; + + if (_set) { + SDLNet_FreeSocketSet(_set); + _set = nullptr; + } + _handleMutex.unlock(); +} + +void LocalWebserver::stopOnIdle() { _stopOnIdle = true; } + +void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) { + if (_pathHandlers.contains(path)) + warning("LocalWebserver::addPathHandler: path already had a handler"); + _pathHandlers[path] = handler; +} + +Common::String LocalWebserver::getAddress() { return _address; } + +IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; } + +bool LocalWebserver::isRunning() { + bool result = false; + _handleMutex.lock(); + result = _timerStarted; + _handleMutex.unlock(); + return result; +} + +uint32 LocalWebserver::getPort() { +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + if (ConfMan.hasKey("local_server_port")) + return ConfMan.getInt("local_server_port"); +#endif + return DEFAULT_SERVER_PORT; +} + +void LocalWebserver::handle() { + _handleMutex.lock(); + int numready = SDLNet_CheckSockets(_set, 0); + if (numready == -1) { + error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError()); + } else if (numready) { + acceptClient(); + } + + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + handleClient(i); + + _clients = 0; + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + if (_client[i].state() != INVALID) + ++_clients; + + if (_clients == 0) + ++_idlingFrames; + else + _idlingFrames = 0; + + if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) { + _handleMutex.unlock(); + stop(); + return; + } + + _handleMutex.unlock(); +} + +void LocalWebserver::handleClient(uint32 i) { + switch (_client[i].state()) { + case INVALID: + return; + case READING_HEADERS: + _client[i].readHeaders(); + break; + case READ_HEADERS: { + // decide what to do next with that client + // check whether we know a handler for such URL + BaseHandler *handler = nullptr; + if (_pathHandlers.contains(_client[i].path())) { + handler = _pathHandlers[_client[i].path()]; + } else { + // try default handler + handler = _defaultHandler; + } + + // if server's in "minimal mode", only handlers which support it are used + if (handler && (!_minimalMode || handler->minimalModeSupported())) + handler->handle(_client[i]); + + if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID) + break; + + // if no handler, answer with default BAD REQUEST + // fallthrough + } + + case BAD_REQUEST: + setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400); + break; + case BEING_HANDLED: + _client[i].handle(); + break; + } +} + +void LocalWebserver::acceptClient() { + if (!SDLNet_SocketReady(_serverSocket)) + return; + + TCPsocket client = SDLNet_TCP_Accept(_serverSocket); + if (!client) + return; + + if (_clients == MAX_CONNECTIONS) { //drop the connection + SDLNet_TCP_Close(client); + return; + } + + ++_clients; + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + if (_client[i].state() == INVALID) { + _client[i].open(_set, client); + break; + } +} + +void LocalWebserver::resolveAddress(void *ipAddress) { + IPaddress *ip = (IPaddress *)ipAddress; + + // not resolved + _address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort); + + // default way (might work everywhere, surely works on Windows) + const char *name = SDLNet_ResolveIP(ip); + if (name == NULL) { + warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } else { + IPaddress localIp; + if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) { + warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } else { + _address = Common::String::format( + "http://%u.%u.%u.%u:%u/", + localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF, + _serverPort + ); + } + } + + // check that our trick worked + if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:")) + warning("LocalWebserver: Failed to resolve IP with the default way"); + else + return; + + // if not - try platform-specific +#ifdef POSIX + struct ifaddrs *ifAddrStruct = NULL; + void *tmpAddrPtr = NULL; + + getifaddrs(&ifAddrStruct); + + for (struct ifaddrs *i = ifAddrStruct; i != NULL; i = i->ifa_next) { + if (!i->ifa_addr) { + continue; + } + + Common::String addr; + + // IPv4 + if (i->ifa_addr->sa_family == AF_INET) { + tmpAddrPtr = &((struct sockaddr_in *)i->ifa_addr)->sin_addr; + char addressBuffer[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); + debug(9, "%s IP Address %s", i->ifa_name, addressBuffer); + addr = addressBuffer; + } + + // IPv6 + /* + if (i->ifa_addr->sa_family == AF_INET6) { + tmpAddrPtr = &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr; + char addressBuffer[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); + debug(9, "%s IP Address %s", i->ifa_name, addressBuffer); + addr = addressBuffer; + } + */ + + if (addr.empty()) + continue; + + // ignored IPv4 addresses + if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost")) + continue; + + // ignored IPv6 addresses + /* + if (addr.equals("::1")) + continue; + */ + + // use the address found + _address = "http://" + addr + Common::String::format(":%u/", _serverPort); + } + + if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct); +#endif +} + +void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) { + byte *data = new byte[response.size()]; + memcpy(data, response.c_str(), response.size()); + Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES); + setClientGetHandler(client, stream, code, mimeType); +} + +void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) { + GetClientHandler *handler = new GetClientHandler(responseStream); + handler->setResponseCode(code); + if (mimeType) + handler->setHeader("Content-Type", mimeType); + client.setHandler(handler); +} + +void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) { + byte *data = new byte[response.size()]; + memcpy(data, response.c_str(), response.size()); + Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES); + setClientRedirectHandler(client, stream, location, mimeType); +} + +void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) { + GetClientHandler *handler = new GetClientHandler(responseStream); + handler->setResponseCode(302); //redirect + handler->setHeader("Location", location); + if (mimeType) + handler->setHeader("Content-Type", mimeType); + client.setHandler(handler); +} + +namespace { +int hexDigit(char c) { + if ('0' <= c && c <= '9') return c - '0'; + if ('A' <= c && c <= 'F') return c - 'A' + 10; + if ('a' <= c && c <= 'f') return c - 'a' + 10; + return -1; +} +} + +Common::String LocalWebserver::urlDecode(Common::String value) { + Common::String result = ""; + uint32 size = value.size(); + for (uint32 i = 0; i < size; ++i) { + if (value[i] == '+') { + result += ' '; + continue; + } + + if (value[i] == '%' && i + 2 < size) { + int d1 = hexDigit(value[i + 1]); + int d2 = hexDigit(value[i + 2]); + if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) { + result += (char)(d1 * 16 + d2); + i = i + 2; + continue; + } + } + + result += value[i]; + } + return result; +} + +namespace { +bool isQueryUnreserved(char c) { + return ( + ('0' <= c && c <= '9') || + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + c == '-' || c == '_' || c == '.' || c == '!' || + c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' + ); +} +} + +Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) { + //OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + //reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", "," + //that means these must be encoded too or otherwise they could malform the query + Common::String result = ""; + char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + for (uint32 i = 0; i < value.size(); ++i) { + char c = value[i]; + if (isQueryUnreserved(c)) + result += c; + else { + result += '%'; + result += hexChar[(c >> 4) & 0xF]; + result += hexChar[c & 0xF]; + } + } + return result; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/localwebserver.h b/backends/networking/sdl_net/localwebserver.h new file mode 100644 index 0000000000..c6cf8485c3 --- /dev/null +++ b/backends/networking/sdl_net/localwebserver.h @@ -0,0 +1,115 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H +#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H + +#include "backends/networking/sdl_net/client.h" +#include "backends/networking/sdl_net/handlers/basehandler.h" +#include "backends/networking/sdl_net/handlers/createdirectoryhandler.h" +#include "backends/networking/sdl_net/handlers/downloadfilehandler.h" +#include "backends/networking/sdl_net/handlers/filesajaxpagehandler.h" +#include "backends/networking/sdl_net/handlers/filespagehandler.h" +#include "backends/networking/sdl_net/handlers/indexpagehandler.h" +#include "backends/networking/sdl_net/handlers/listajaxhandler.h" +#include "backends/networking/sdl_net/handlers/resourcehandler.h" +#include "backends/networking/sdl_net/handlers/uploadfilehandler.h" +#include "common/hash-str.h" +#include "common/mutex.h" +#include "common/singleton.h" +#include "common/scummsys.h" + +namespace Common { +class SeekableReadStream; +} + +typedef struct _SDLNet_SocketSet *SDLNet_SocketSet; +typedef struct _TCPsocket *TCPsocket; + +namespace Networking { + +#define NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + +class LocalWebserver : public Common::Singleton<LocalWebserver> { + static const uint32 FRAMES_PER_SECOND = 20; + static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND; + static const uint32 MAX_CONNECTIONS = 10; + + friend void localWebserverTimer(void *); //calls handle() + + SDLNet_SocketSet _set; + TCPsocket _serverSocket; + Client _client[MAX_CONNECTIONS]; + int _clients; + bool _timerStarted, _stopOnIdle, _minimalMode; + Common::HashMap<Common::String, BaseHandler*> _pathHandlers; + BaseHandler *_defaultHandler; + IndexPageHandler _indexPageHandler; + FilesPageHandler _filesPageHandler; + CreateDirectoryHandler _createDirectoryHandler; + DownloadFileHandler _downloadFileHandler; + UploadFileHandler _uploadFileHandler; + ListAjaxHandler _listAjaxHandler; + FilesAjaxPageHandler _filesAjaxPageHandler; + ResourceHandler _resourceHandler; + uint32 _idlingFrames; + Common::Mutex _handleMutex; + Common::String _address; + uint32 _serverPort; + + void startTimer(int interval = TIMER_INTERVAL); + void stopTimer(); + void handle(); + void handleClient(uint32 i); + void acceptClient(); + void resolveAddress(void *ipAddress); + void addPathHandler(Common::String path, BaseHandler *handler); + +public: + static const uint32 DEFAULT_SERVER_PORT = 12345; + + LocalWebserver(); + virtual ~LocalWebserver(); + + void start(bool useMinimalMode = false); + void stop(); + void stopOnIdle(); + + Common::String getAddress(); + IndexPageHandler &indexPageHandler(); + bool isRunning(); + static uint32 getPort(); + + static void setClientGetHandler(Client &client, Common::String response, long code = 200, const char *mimeType = nullptr); + static void setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code = 200, const char *mimeType = nullptr); + static void setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType = nullptr); + static void setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType = nullptr); + static Common::String urlDecode(Common::String value); + static Common::String urlEncodeQueryParameterValue(Common::String value); +}; + +/** Shortcut for accessing the local webserver. */ +#define LocalServer Networking::LocalWebserver::instance() + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp new file mode 100644 index 0000000000..8f3199f51c --- /dev/null +++ b/backends/networking/sdl_net/reader.cpp @@ -0,0 +1,462 @@ +/* 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 "backends/networking/sdl_net/reader.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/memstream.h" +#include "common/stream.h" + +namespace Networking { + +Reader::Reader() { + _state = RS_NONE; + _content = nullptr; + _bytesLeft = 0; + + _window = nullptr; + _windowUsed = 0; + _windowSize = 0; + + _headersStream = nullptr; + _firstBlock = true; + + _contentLength = 0; + _availableBytes = 0; + _isBadRequest = false; + _allContentRead = false; +} + +Reader::~Reader() { + cleanup(); +} + +Reader &Reader::operator=(Reader &r) { + if (this == &r) + return *this; + cleanup(); + + _state = r._state; + _content = r._content; + _bytesLeft = r._bytesLeft; + r._state = RS_NONE; + + _window = r._window; + _windowUsed = r._windowUsed; + _windowSize = r._windowSize; + r._window = nullptr; + + _headersStream = r._headersStream; + r._headersStream = nullptr; + + _headers = r._headers; + _method = r._method; + _path = r._path; + _query = r._query; + _anchor = r._anchor; + _queryParameters = r._queryParameters; + _contentLength = r._contentLength; + _boundary = r._boundary; + _availableBytes = r._availableBytes; + _firstBlock = r._firstBlock; + _isBadRequest = r._isBadRequest; + _allContentRead = r._allContentRead; + + return *this; +} + +void Reader::cleanup() { + //_content is not to be freed, it's not owned by Reader + + if (_headersStream != nullptr) + delete _headersStream; + + if (_window != nullptr) + freeWindow(); +} + +bool Reader::readAndHandleFirstHeaders() { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) { + makeWindow(boundary.size()); + } + if (_headersStream == nullptr) { + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + } + + while (readOneByteInStream(_headersStream, boundary)) { + if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) { + _isBadRequest = true; + return true; + } + if (!bytesLeft()) + return false; + } + handleFirstHeaders(_headersStream); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + if (stream) stream->flush(); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +namespace { +void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + } +} +} + +void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) { + if (!_boundary.empty()) { + warning("Reader: handleFirstHeaders() called when first headers were already handled"); + return; + } + + //parse method, path, query, fragment + _headers = readEverythingFromMemoryStream(headersStream); + parseFirstLine(_headers); + + //find boundary + _boundary = ""; + readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary); + + //find content length + Common::String contentLength = ""; + readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength); + _contentLength = contentLength.asUint64(); + _availableBytes = _contentLength; +} + +void Reader::parseFirstLine(const Common::String &headers) { + uint32 headersSize = headers.size(); + bool bad = false; + + if (headersSize > 0) { + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "\r\n"); + if (position) { //we have at least one line - and we want the first one + //"<METHOD> <path> HTTP/<VERSION>\r\n" + Common::String method, path, http, buf; + uint32 length = position - cstr; + if (headersSize > length) + headersSize = length; + for (uint32 i = 0; i < headersSize; ++i) { + if (headers[i] != ' ') + buf += headers[i]; + if (headers[i] == ' ' || i == headersSize - 1) { + if (method == "") { + method = buf; + } else if (path == "") { + path = buf; + } else if (http == "") { + http = buf; + } else { + bad = true; + break; + } + buf = ""; + } + } + + //check that method is supported + if (method != "GET" && method != "PUT" && method != "POST") + bad = true; + + //check that HTTP/<VERSION> is OK + if (!http.hasPrefix("HTTP/")) + bad = true; + + _method = method; + parsePathQueryAndAnchor(path); + } + } + + if (bad) _isBadRequest = true; +} + +void Reader::parsePathQueryAndAnchor(Common::String path) { + //<path>[?query][#anchor] + bool readingPath = true; + bool readingQuery = false; + _path = ""; + _query = ""; + _anchor = ""; + for (uint32 i = 0; i < path.size(); ++i) { + if (readingPath) { + if (path[i] == '?') { + readingPath = false; + readingQuery = true; + } else { + _path += path[i]; + } + } else if (readingQuery) { + if (path[i] == '#') { + readingQuery = false; + } else { + _query += path[i]; + } + } else { + _anchor += path[i]; + } + } + + parseQueryParameters(); +} + +void Reader::parseQueryParameters() { + Common::String key = ""; + Common::String value = ""; + bool readingKey = true; + for (uint32 i = 0; i < _query.size(); ++i) { + if (readingKey) { + if (_query[i] == '=') { + readingKey = false; + value = ""; + } else { + key += _query[i]; + } + } else { + if (_query[i] == '&') { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + readingKey = true; + key = ""; + } else { + value += _query[i]; + } + } + } + + if (!key.empty()) { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + } +} + +bool Reader::readContentIntoStream(Common::WriteStream *stream) { + Common::String boundary = "--" + _boundary; + if (!_firstBlock) + boundary = "\r\n" + boundary; + if (_boundary.empty()) + boundary = "\r\n"; + if (_window == nullptr) + makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + + _firstBlock = false; + if (stream) + stream->flush(); + + freeWindow(); + _state = RS_READING_HEADERS; + return true; +} + +void Reader::makeWindow(uint32 size) { + freeWindow(); + + _window = new byte[size]; + _windowUsed = 0; + _windowSize = size; +} + +void Reader::freeWindow() { + delete[] _window; + _window = nullptr; + _windowUsed = _windowSize = 0; +} + +namespace { +bool windowEqualsString(const byte *window, uint32 windowSize, const Common::String &boundary) { + if (boundary.size() != windowSize) + return false; + + for (uint32 i = 0; i < windowSize; ++i) { + if (window[i] != boundary[i]) + return false; + } + + return true; +} +} + +bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary) { + byte b = readOne(); + _window[_windowUsed++] = b; + if (_windowUsed < _windowSize) + return true; + + //when window is filled, check whether that's the boundary + if (windowEqualsString(_window, _windowSize, boundary)) + return false; + + //if not, add the first byte of the window to the string + if (stream) + stream->writeByte(_window[0]); + for (uint32 i = 1; i < _windowSize; ++i) + _window[i - 1] = _window[i]; + --_windowUsed; + return true; +} + +byte Reader::readOne() { + byte b; + _content->read(&b, 1); + --_availableBytes; + --_bytesLeft; + return b; +} + +/// public + +bool Reader::readFirstHeaders() { + if (_state == RS_NONE) + _state = RS_READING_HEADERS; + + if (!bytesLeft()) + return false; + + if (_state == RS_READING_HEADERS) + return readAndHandleFirstHeaders(); + + warning("Reader::readFirstHeaders(): bad state"); + return false; +} + +bool Reader::readFirstContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readFirstContent(): bad state"); + return false; + } + + // no difference, actually + return readBlockContent(stream); +} + +bool Reader::readBlockHeaders(Common::WriteStream *stream) { + if (_state != RS_READING_HEADERS) { + warning("Reader::readBlockHeaders(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + return readBlockHeadersIntoStream(stream); +} + +bool Reader::readBlockContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readBlockContent(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + if (!readContentIntoStream(stream)) + return false; + + if (_availableBytes >= 2) { + Common::String bts; + bts += readOne(); + bts += readOne(); + if (bts == "--") + _allContentRead = true; + else if (bts != "\r\n") + warning("Reader: strange bytes: \"%s\"", bts.c_str()); + } else { + warning("Reader: strange ending"); + _allContentRead = true; + } + + return true; +} + +uint32 Reader::bytesLeft() const { return _bytesLeft; } + +void Reader::setContent(Common::MemoryReadWriteStream *stream) { + _content = stream; + _bytesLeft = stream->size() - stream->pos(); +} + +bool Reader::badRequest() const { return _isBadRequest; } + +bool Reader::noMoreContent() const { return _allContentRead; } + +Common::String Reader::headers() const { return _headers; } + +Common::String Reader::method() const { return _method; } + +Common::String Reader::path() const { return _path; } + +Common::String Reader::query() const { return _query; } + +Common::String Reader::queryParameter(Common::String name) const { return _queryParameters[name]; } + +Common::String Reader::anchor() const { return _anchor; } + +Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (true) { + readBytes = stream->read(buf, 1024); + if (readBytes == 0) + break; + result += Common::String(buf, readBytes); + } + return result; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/reader.h b/backends/networking/sdl_net/reader.h new file mode 100644 index 0000000000..16d62a27eb --- /dev/null +++ b/backends/networking/sdl_net/reader.h @@ -0,0 +1,143 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_READER_H +#define BACKENDS_NETWORKING_SDL_NET_READER_H + +#include "common/str.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +namespace Common { +class MemoryReadWriteStream; +class WriteStream; +} + +namespace Networking { + +enum ReaderState { + RS_NONE, + RS_READING_HEADERS, + RS_READING_CONTENT +}; + +/** + * This is a helper class for Client. + * + * It parses HTTP request and finds headers + * and content. It also supports POST form/multipart. + * + * One might pass the request even byte by byte, + * Reader will always be able to continue from the + * state it stopped on. + * + * Main headers/content must be read with + * readFirstHeaders() and readFirstContent() methods. + * Further headers/content blocks (POST form/multipart) + * must be read with readBlockHeaders() and readBlockContent(). + * + * Main headers and parsed URL components could be accessed + * with special methods after reading. + * + * To use the object, call setContent() and then one of those + * reading methods. It would return whether reading is over + * or not. If reading is over, content stream still could + * contain bytes to read with other methods. + * + * If reading is not over, Reader awaits you to call the + * same reading method when you'd get more content. + * + * If it's over, you should check whether Reader awaits + * more content with noMoreContent() and call the other + * reading method, if it is. When headers are read, one + * must read contents, and vice versa. + */ + +class Reader { + ReaderState _state; + Common::MemoryReadWriteStream *_content; + uint32 _bytesLeft; + + byte *_window; + uint32 _windowUsed, _windowSize; + + Common::MemoryReadWriteStream *_headersStream; + + Common::String _headers; + Common::String _method, _path, _query, _anchor; + Common::HashMap<Common::String, Common::String> _queryParameters; + uint32 _contentLength; + Common::String _boundary; + uint32 _availableBytes; + bool _firstBlock; + bool _isBadRequest; + bool _allContentRead; + + void cleanup(); + + bool readAndHandleFirstHeaders(); //true when ended reading + bool readBlockHeadersIntoStream(Common::WriteStream *stream); //true when ended reading + bool readContentIntoStream(Common::WriteStream *stream); //true when ended reading + + void handleFirstHeaders(Common::MemoryReadWriteStream *headers); + void parseFirstLine(const Common::String &headers); + void parsePathQueryAndAnchor(Common::String path); + void parseQueryParameters(); + + void makeWindow(uint32 size); + void freeWindow(); + bool readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary); + + byte readOne(); + uint32 bytesLeft() const; + +public: + static const uint32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot + + Reader(); + ~Reader(); + + Reader &operator=(Reader &r); + + bool readFirstHeaders(); //true when ended reading + bool readFirstContent(Common::WriteStream *stream); //true when ended reading + bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading + bool readBlockContent(Common::WriteStream *stream); //true when ended reading + + void setContent(Common::MemoryReadWriteStream *stream); + + bool badRequest() const; + bool noMoreContent() const; + + Common::String headers() const; + Common::String method() const; + Common::String path() const; + Common::String query() const; + Common::String queryParameter(Common::String name) const; + Common::String anchor() const; + + static Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/uploadfileclienthandler.cpp b/backends/networking/sdl_net/uploadfileclienthandler.cpp new file mode 100644 index 0000000000..ebf341682c --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.cpp @@ -0,0 +1,212 @@ +/* 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 "backends/networking/sdl_net/uploadfileclienthandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/networking/sdl_net/reader.h" +#include "common/file.h" +#include "common/memstream.h" +#include "common/translation.h" + +namespace Networking { + +UploadFileClientHandler::UploadFileClientHandler(Common::String parentDirectoryPath): + _state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr), + _parentDirectoryPath(parentDirectoryPath), _uploadedFiles(0) {} + +UploadFileClientHandler::~UploadFileClientHandler() { + delete _headersStream; + delete _contentStream; +} + +void UploadFileClientHandler::handle(Client *client) { + if (client == nullptr) { + warning("UploadFileClientHandler::handle(): empty client pointer"); + return; + } + + while (true) { + switch (_state) { + case UFH_READING_CONTENT: + if (client->readContent(nullptr)) { + _state = UFH_READING_BLOCK_HEADERS; + continue; + } + break; + + case UFH_READING_BLOCK_HEADERS: + if (_headersStream == nullptr) + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + + if (client->readBlockHeaders(_headersStream)) { + handleBlockHeaders(client); + continue; + } + + // fail on suspicious headers + if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) { + setErrorMessageHandler(*client, _("Invalid request: headers are too long!")); + } + break; + + case UFH_READING_BLOCK_CONTENT: + // _contentStream is created by handleBlockHeaders() if needed + + if (client->readBlockContent(_contentStream)) { + handleBlockContent(client); + continue; + } + break; + + case UFH_ERROR: + case UFH_STOP: + return; + } + + break; + } +} + +namespace { +void readFromThatUntilDoubleQuote(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '"') + break; + result += c; + } + } +} +} + +void UploadFileClientHandler::handleBlockHeaders(Client *client) { + _state = UFH_READING_BLOCK_CONTENT; + + // fail on suspicious headers + if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) { + setErrorMessageHandler(*client, _("Invalid request: headers are too long!")); + } + + // search for "upload_file" field + Common::String headers = Reader::readEverythingFromMemoryStream(_headersStream); + Common::String fieldName = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName); + if (!fieldName.hasPrefix("upload_file")) + return; + + Common::String filename = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename); + + // skip block if <filename> is empty + if (filename.empty()) + return; + + if (HandlerUtils::hasForbiddenCombinations(filename)) + return; + + // check that <path>/<filename> doesn't exist + Common::String path = _parentDirectoryPath; + if (path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename); + if (!HandlerUtils::permittedPath(originalNode->getPath())) { + setErrorMessageHandler(*client, _("Invalid path!")); + return; + } + if (originalNode->exists()) { + setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!")); + return; + } + + // remove previous stream (if there is one) + if (_contentStream) { + delete _contentStream; + _contentStream = nullptr; + } + + // create file stream (and necessary subdirectories) + Common::DumpFile *f = new Common::DumpFile(); + if (!f->open(originalNode->getPath(), true)) { + delete f; + setErrorMessageHandler(*client, _("Failed to upload the file!")); + return; + } + + _contentStream = f; +} + +void UploadFileClientHandler::handleBlockContent(Client *client) { + _state = UFH_READING_BLOCK_HEADERS; + + // if previous block headers were file-related and created a stream + if (_contentStream) { + _contentStream->flush(); + ++_uploadedFiles; + + delete _contentStream; + _contentStream = nullptr; + + if (client->noMoreContent()) { + // success - redirect back to directory listing + setSuccessHandler(*client); + return; + } + } + + // no more content avaiable + if (client->noMoreContent()) { + // if no file field was found - failure + if (_uploadedFiles == 0) { + setErrorMessageHandler(*client, _("No file was passed!")); + } else { + setSuccessHandler(*client); + } + } +} + +void UploadFileClientHandler::setErrorMessageHandler(Client &client, Common::String message) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, message); + _state = UFH_ERROR; +} + +void UploadFileClientHandler::setSuccessHandler(Client &client) { + // success - redirect back to directory listing + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + _("Uploaded successfully!"), + client.queryParameter("path").c_str(), + _("Back to parent directory") + ), + (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") + + LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path")) + ); + _state = UFH_STOP; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/uploadfileclienthandler.h b/backends/networking/sdl_net/uploadfileclienthandler.h new file mode 100644 index 0000000000..b6481cf18f --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.h @@ -0,0 +1,70 @@ +/* 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 BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H + +#include "backends/networking/sdl_net/client.h" +#include "common/stream.h" + +namespace Networking { + +enum UploadFileHandlerState { + UFH_READING_CONTENT, + UFH_READING_BLOCK_HEADERS, + UFH_READING_BLOCK_CONTENT, + UFH_ERROR, + UFH_STOP +}; + +/** + * This class handles POST form/multipart upload. + * + * handleBlockHeaders() looks for filename and, if it's found, + * handleBlockContent() saves content into the file with such name. + * + * If no file found or other error occurs, it sets + * default error message handler. + */ + +class UploadFileClientHandler: public ClientHandler { + UploadFileHandlerState _state; + Common::MemoryReadWriteStream *_headersStream; + Common::WriteStream *_contentStream; + Common::String _parentDirectoryPath; + uint32 _uploadedFiles; + + void handleBlockHeaders(Client *client); + void handleBlockContent(Client *client); + void setErrorMessageHandler(Client &client, Common::String message); + void setSuccessHandler(Client &client); + +public: + UploadFileClientHandler(Common::String parentDirectoryPath); + virtual ~UploadFileClientHandler(); + + virtual void handle(Client *client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/wwwroot.zip b/backends/networking/wwwroot.zip Binary files differnew file mode 100644 index 0000000000..b767d7c5d7 --- /dev/null +++ b/backends/networking/wwwroot.zip diff --git a/backends/networking/wwwroot/.files.html b/backends/networking/wwwroot/.files.html new file mode 100644 index 0000000000..f05c0113f8 --- /dev/null +++ b/backends/networking/wwwroot/.files.html @@ -0,0 +1,60 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="controls"> + <table class="buttons"><tr> + <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td> + <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td> + </tr></table> + <div id="create_directory" class="modal"> + <p>{create_directory_desc}</p> + <form action="create"> + <input type="hidden" name="path" value="{path}"/> + <input type="text" name="directory_name" value=""/> + <input type="submit" value="{create_directory_button}"/> + </form> + </div> + <div id="upload_file" class="modal"> + <p>{upload_file_desc}</p> + <form action="upload?path={path}" method="post" enctype="multipart/form-data"> + <!-- we don't need "[]" in the name, as our webserver is not using PHP --> + <!-- "allowdirs" is a proposal, not implemented yet --> + <input type="file" name="upload_file-f" allowdirs multiple/> + <br/><br/> + <p>{or_upload_directory_desc}</p> + <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here --> + <input type="file" name="upload_file-d" directory webkitdirectory multiple/> + <input type="submit" value="{upload_file_button}"/> + </form> + </div> + </div> + <div class="content"> + <table class="files_list"> + <td></td><td><b class="directory_name">{index_of_directory}</b></td><td></td> + {content} + </table> + </div> + </div> + <script> + function show(id) { + var e = document.getElementById(id); + var visible = (e.style.display == "block"); + if (visible) id = ""; //hide + + e = document.getElementById("create_directory"); + e.style.display = (e.id == id ? "block" : "none"); + e = document.getElementById("upload_file"); + e.style.display = (e.id == id ? "block" : "none"); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/.filesAJAX.html b/backends/networking/wwwroot/.filesAJAX.html new file mode 100644 index 0000000000..d45c73069d --- /dev/null +++ b/backends/networking/wwwroot/.filesAJAX.html @@ -0,0 +1,240 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="controls"> + <table class="buttons"><tr> + <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td> + <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td> + </tr></table> + <div id="create_directory" class="modal"> + <p>{create_directory_desc}</p> + <form action="create" id="create_directory_form" onsubmit="return createDirectory();"> + <input type="hidden" name="path" value="{path}"/> + <input type="hidden" name="ajax" value="true"/> + <input type="text" name="directory_name" value=""/> + <input type="submit" value="{create_directory_button}"/> + </form> + </div> + <div id="upload_file" class="modal"> + <p>{upload_file_desc}</p> + <form action="upload?path={path}&ajax=true" method="post" enctype="multipart/form-data" id="files_upload_form"> + <!-- we don't need "[]" in the name, as our webserver is not using PHP --> + <!-- "allowdirs" is a proposal, not implemented yet --> + <input type="file" name="upload_file-f" allowdirs multiple/> + <br/><br/> + <p>{or_upload_directory_desc}</p> + <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here --> + <input type="file" name="upload_file-d" directory webkitdirectory multiple/> + <input type="submit" value="{upload_file_button}"/> + </form> + </div> + </div> + <div class="content"> + <div id="loading_message">{loading}</div> + <div id="error_message">{error}</div> + <table class="files_list" id="files_list"> + </table> + </div> + </div> + <script> + function show(id) { + var e = document.getElementById(id); + var visible = (e.style.display == "block"); + if (visible) id = ""; //hide + + e = document.getElementById("create_directory"); + e.style.display = (e.id == id ? "block" : "none"); + e = document.getElementById("upload_file"); + e.style.display = (e.id == id ? "block" : "none"); + } + </script> + <script src="ajax.js"></script> + <script> + window.onload = function () { + showDirectory("{start_path}"); + } + + function showDirectory(path) { + if (isLoading) return; + showLoading(); + ajax.getAndParseJson("./list", {"path": path}, getCallback(path)); + } + + function getCallback(path) { + return function (jsonResponse) { + if (jsonResponse.type == "error") { + showError(); + return; + } + + openDirectory(path, jsonResponse.items); + hideLoading(); + }; + } + + function createDirectory() { + if (isLoading) return; + showLoading(); + + var data = {"answer_json": "true"}; + var elements = document.getElementById("create_directory_form").elements; + for (var el in elements) + data[elements[el].name] = elements[el].value; + + ajax.getAndParseJson("./create", data, getCreateDirectoryCallback(data["path"])); + show("create_directory"); + return false; // invalidate form, so it won't submit + } + + function getCreateDirectoryCallback(path) { + return function (jsonResponse) { + console.log(jsonResponse); + + if (jsonResponse.type == "error") { + showError(); + return; + } + + hideLoading(); + showDirectory(path); + }; + } + + var isLoading = false; + + function showLoading() { + isLoading = true; + var e = document.getElementById("loading_message"); + e.style.display = "block"; + e = document.getElementById("error_message"); + e.style.display = "none"; + } + + function showError() { + isLoading = false; + var e = document.getElementById("loading_message"); + e.style.display = "none"; + e = document.getElementById("error_message"); + e.style.display = "block"; + //TODO: pass the actual message there? + } + + function hideLoading() { + isLoading = false; + var e = document.getElementById("loading_message"); + e.style.display = "none"; + e = document.getElementById("error_message"); + e.style.display = "none"; + } + + function openDirectory(path, items) { + // update path + document.getElementById("create_directory_form").elements["path"].value = path; + document.getElementById("files_upload_form").action = "upload?path=" + path + "&ajax=true"; + + // update table contents + listDirectory(path, items); + } + + function makeBreadcrumb(name, path) { + var a = createElementWithContents("a", name); + a.onclick = function () { showDirectory(path); }; + a.href = "javascript:void(0);"; + return a; + } + + function makeBreadcrumbs(path) { + var b = document.createElement("b"); + b.className = "directory_name"; + + b.appendChild(createElementWithContents("span", "{index_of}")); + var slashes = true; + var crumb = ""; + var currentPath = ""; + path += ' '; //so the last slash is added + for (var i=0; i<path.length; ++i) { + if (path[i] == '/' || path[i] == '\\') { + if (!slashes) { + currentPath += crumb; + b.appendChild(makeBreadcrumb(crumb, currentPath+'/')); + slashes = true; + } + } else { + if (slashes) { + currentPath += "/"; + if (currentPath == "/") { //make special '/' crumb here + b.appendChild(makeBreadcrumb('/', '/')); + } else { + b.appendChild(createElementWithContents("span", "/")); + } + slashes = false; + crumb = ""; + } + crumb += path[i]; + } + } + return b; + } + + function listDirectory(path, items) { + // cleanup the list + var files_list = document.getElementById("files_list"); + while (files_list.hasChildNodes()) + files_list.removeChild(files_list.firstChild); + var tbody = document.createElement("tbody"); + + // add header item + var tr = document.createElement("tr"); + tr.appendChild(createElementWithContents("td", "")); + var td = document.createElement("td"); + td.appendChild(makeBreadcrumbs(path)); + tr.appendChild(td); + tr.appendChild(createElementWithContents("td", "")); + tbody.appendChild(tr); + + // add items + for (var i in items) + addItem(tbody, items[i]); + + files_list.appendChild(tbody); + } + + function addItem(tbody, item) { + var tr = document.createElement("tr"); + var td = document.createElement("td"); + var img = document.createElement("img"); + img.src = "./icons/" + item.icon; + td.appendChild(img); + tr.appendChild(td); + + td = document.createElement("td"); + var a = createElementWithContents("a", item.name); + if (item.isDirectory) { + a.onclick = function () { showDirectory(item.path); }; + a.href = "javascript:void(0);"; + } else + a.href = "./download?path=" + encodeURIComponent(item.path); + td.appendChild(a); + tr.appendChild(td); + + tr.appendChild(createElementWithContents("td", "")); + tbody.appendChild(tr); + } + + function createElementWithContents(type, innerHTML) { + var e = document.createElement(type); + e.innerHTML = innerHTML; + return e; + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/.index.html b/backends/networking/wwwroot/.index.html new file mode 100644 index 0000000000..2a3d9d382d --- /dev/null +++ b/backends/networking/wwwroot/.index.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="content"> + <p>{message}</p> + </div> + </div> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/ajax.js b/backends/networking/wwwroot/ajax.js new file mode 100644 index 0000000000..c01d7e93fc --- /dev/null +++ b/backends/networking/wwwroot/ajax.js @@ -0,0 +1,48 @@ +// the following is snippet from http://stackoverflow.com/a/18078705 +// I changed a few things though + +var ajax = {}; +ajax.x = function () { return new XMLHttpRequest(); }; // "no one uses IE6" + +ajax.send = function (url, callback, errorCallback, method, data, async) { + if (async === undefined) async = true; + + var x = ajax.x(); + x.open(method, url, async); + x.onreadystatechange = function () { + if (x.readyState == XMLHttpRequest.DONE) { + if (x.status == 200) + callback(x.responseText); + else + errorCallback(x); + } + }; + if (method == 'POST') { + x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + } + x.send(data) +}; + +ajax.get = function (url, data, callback, errorCallback, async) { + var query = []; + for (var key in data) { + query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); + } + ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, errorCallback, 'GET', null, async) +}; + +ajax.post = function (url, data, callback, errorCallback, async) { + var query = []; + for (var key in data) { + query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); + } + ajax.send(url, callback, errorCallback, 'POST', query.join('&'), async) +}; + +ajax.getAndParseJson = function (url, data, callback) { + ajax.get( + url, data, + function (responseText) { callback(JSON.parse(responseText)); }, + function (x) { console.log("error: " + x.status); } + ); +};
\ No newline at end of file diff --git a/backends/networking/wwwroot/favicon.ico b/backends/networking/wwwroot/favicon.ico Binary files differnew file mode 100644 index 0000000000..0283e8432e --- /dev/null +++ b/backends/networking/wwwroot/favicon.ico diff --git a/backends/networking/wwwroot/icons/7z.png b/backends/networking/wwwroot/icons/7z.png Binary files differnew file mode 100644 index 0000000000..656e7e7c62 --- /dev/null +++ b/backends/networking/wwwroot/icons/7z.png diff --git a/backends/networking/wwwroot/icons/dir.png b/backends/networking/wwwroot/icons/dir.png Binary files differnew file mode 100644 index 0000000000..bcdec04a57 --- /dev/null +++ b/backends/networking/wwwroot/icons/dir.png diff --git a/backends/networking/wwwroot/icons/txt.png b/backends/networking/wwwroot/icons/txt.png Binary files differnew file mode 100644 index 0000000000..023d2ee24a --- /dev/null +++ b/backends/networking/wwwroot/icons/txt.png diff --git a/backends/networking/wwwroot/icons/unk.png b/backends/networking/wwwroot/icons/unk.png Binary files differnew file mode 100644 index 0000000000..346eebecc3 --- /dev/null +++ b/backends/networking/wwwroot/icons/unk.png diff --git a/backends/networking/wwwroot/icons/up.png b/backends/networking/wwwroot/icons/up.png Binary files differnew file mode 100644 index 0000000000..2dc3df022b --- /dev/null +++ b/backends/networking/wwwroot/icons/up.png diff --git a/backends/networking/wwwroot/icons/zip.png b/backends/networking/wwwroot/icons/zip.png Binary files differnew file mode 100644 index 0000000000..cdfc5763dd --- /dev/null +++ b/backends/networking/wwwroot/icons/zip.png diff --git a/backends/networking/wwwroot/logo.png b/backends/networking/wwwroot/logo.png Binary files differnew file mode 100644 index 0000000000..9fdd2d0d1e --- /dev/null +++ b/backends/networking/wwwroot/logo.png diff --git a/backends/networking/wwwroot/style.css b/backends/networking/wwwroot/style.css new file mode 100644 index 0000000000..ba31587c4d --- /dev/null +++ b/backends/networking/wwwroot/style.css @@ -0,0 +1,113 @@ +html { + background: rgb(212, 117, 11); + background: linear-gradient(to bottom, rgb(212, 117, 11) 0%, rgb(212, 117, 11) 36%, rgb(239, 196, 24) 100%); + min-height: 100vh; +} + +.container { + width: 80%; + margin: 0 auto; +} + +.header { + padding: 10pt; + margin-bottom: 0; +} + +.content { + padding: 8pt; + background: rgb(251, 241, 206); + font-family: Tahoma; + font-size: 16pt; +} + +.content p { margin: 0 0 6pt 0; } + +.controls { + padding: 8pt; + background: #FFF; + font-family: Tahoma; + font-size: 16pt; +} + +.controls .buttons { + width: 100%; + max-width: 500pt; + margin: -8pt auto; + border: 0; + border-spacing: 0; +} + +.controls .buttons td { + width: 50%; + text-align: center; + margin: 0; + padding: 0; +} + +.controls .buttons a { + display: block; + height: 40pt; + line-height: 38pt; + vertical-align: middle; + color: #000; + text-decoration: none; +} + +.controls .buttons a:hover { + background: #F3F3F3; +} + +.modal { + margin-top: 10pt; + display: none; +} + +.modal p { margin: 0 0 6pt 0; } + +#create_directory input[type="text"], #upload_file input[type="file"] { + width: calc(100% - 2 * 5pt); +} + +.modal input { + border: 1px solid #EEE; + padding: 5pt; + font-size: 12pt; +} + +.modal input[type="submit"] { + display: block; + margin: 6pt auto; + background: #DDD; + border: 0; +} + +.modal input[type="submit"]:hover { + background: #F3F3F3; + cursor: pointer; +} + +td img { vertical-align: middle; height: 20px; } + +.directory_name { + display: block; + padding-bottom: 6px; +} + +.directory_name a { color: black; } +.directory_name a:hover { color: blue; } + +#loading_message, #error_message { + margin: -8pt; + margin-bottom: 5pt; + padding: 4pt; + text-align: center; +} + +#loading_message { + background: #99FF99; +} + +#error_message { + background: #FF9999; +} diff --git a/backends/platform/3ds/3ds.mk b/backends/platform/3ds/3ds.mk new file mode 100644 index 0000000000..7ab58995f6 --- /dev/null +++ b/backends/platform/3ds/3ds.mk @@ -0,0 +1,64 @@ +TARGET := scummvm + +APP_TITLE := ScummVM +APP_DESCRIPTION := Point-and-click adventure game engines +APP_AUTHOR := ScummVM Team +APP_ICON := backends/platform/3ds/app/icon.png + +APP_RSF := backends/platform/3ds/app/scummvm.rsf +APP_BANNER_IMAGE:= backends/platform/3ds/app/banner.png +APP_BANNER_AUDIO:= backends/platform/3ds/app/banner.wav + +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft +CXXFLAGS += -std=gnu++11 +ASFLAGS += -mfloat-abi=hard +LDFLAGS += -specs=3dsx.specs $(ARCH) -L$(DEVKITPRO)/libctru/lib -L$(DEVKITPRO)/portlibs/3ds/lib + +.PHONY: clean_3ds + +clean: clean_3ds + +clean_3ds: + $(RM) $(TARGET).3dsx + $(RM) $(TARGET).cia + +$(TARGET).smdh: $(APP_ICON) + @bannertool makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i $(APP_ICON) -o $@ + @echo built ... $(notdir $@) + +$(TARGET).3dsx: $(EXECUTABLE) $(TARGET).smdh + @3dsxtool $< $@ --smdh=$(TARGET).smdh + @echo built ... $(notdir $@) + +$(TARGET).bnr: $(APP_BANNER_IMAGE) $(APP_BANNER_AUDIO) + @bannertool makebanner -o $@ -i $(APP_BANNER_IMAGE) -a $(APP_BANNER_AUDIO) + @echo built ... $(notdir $@) + +$(TARGET).cia: $(EXECUTABLE) $(APP_RSF) $(TARGET).smdh $(TARGET).bnr + @makerom -f cia -target t -exefslogo -o $@ -elf $(EXECUTABLE) -rsf $(APP_RSF) -banner $(TARGET).bnr -icon $(TARGET).smdh + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval FILEPATH := $(patsubst %.shbin.o,%.shbin,$@)) + $(eval FILE := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) + picasso -o $(FILEPATH) $1 + bin2s $(FILEPATH) | $(AS) -o $@ + echo "extern const u8" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(FILEPATH) | tr . _)`.h + echo "extern const u8" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(FILEPATH) | tr . _)`.h + echo "extern const u32" `(echo $(FILE) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(FILEPATH) | tr . _)`.h +endef + +%.shbin.o : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) diff --git a/backends/platform/3ds/README b/backends/platform/3ds/README new file mode 100644 index 0000000000..516e694f64 --- /dev/null +++ b/backends/platform/3ds/README @@ -0,0 +1,185 @@ +ScummVM 3DS README +------------------------------------------------------------------------ + +Table of Contents: +------------------ +1.0) Installation + * 1.1 3DSX installation + * 1.2 CIA installation +2.0) Controls + * 2.1 Default key mappings + * 2.2 Hover mode + * 2.3 Drag mode +3.0) Supported Games +4.0) Compiling + * 4.1 Prerequisites + * * 4.1.1 Compiling third-party libraries + * 4.2 Compiling ScummVM + * 4.3 Warning for 3DSX build + + + +1.0) Installation +----------------- +There are two possible formats to be used: 3DSX and CIA (recommended). +The 3DSX format is exclusively used by the Homebrew Launcher and its derivatives. +The CIA format can be installed directly to the 3DS home menu and can be launched +using any CFW (Custom Firmware) of your choice. + +Installing the Homebrew Launcher or any CFW is beyond the scope of this README. +Look elsewhere to see how to install those if you do not already have them set up. + + +1.1) 3DSX installation +---------------- +The CIA format is recommended for stability and maximum game support. If that is +not an option, you will need one of a collection of 3DS titles installed on your +system in order to properly launch ScummVM as a 3DSX. This is because the +Homebrew Launcher hijacks other processes to run 3DSX homebrew, and ScummVM is a +particularly large homebrew that can't be launched with the resources provided +by standard system applications. + +You will need one of the following (installed or physically in cart slot): + +- Youtube +- Monster Hunter 4 Ultimate Special Demo +- Monster Hunter 4 Ultimate +- Monster Hunter 4G +- Super Smash Bros. for Nintendo 3DS Demo +- Super Smash Bros. for Nintendo 3DS Special Demo +- Super Smash Bros. for Nintendo 3DS + +Once you have one of the above, you need to merely extract all ScummVM 3DS files +to the root of your SD card so that all files reside in the /3ds/scummvm/ directory. + + +1.2) CIA installation +--------------------- +The CIA format requires a DSP binary dump saved on your SD card as /3ds/dspfirm.cdc +for proper audio support. You can search online to find software to dump this. +Not having this file will cause many problems with games that need audio, sometimes +even crashing, so this is NOT considered optional. + +Using any CIA installation software (search elsewhere for that), you need to install +the scummvm.cia file. Then, just like what is done with the 3DSX installation, you +need to extract all ScummVM 3DS files (scummvm.cia excluded) to the root of your SD +card so that all files reside in the /3ds/scummvm/ directory. + + + +2.0) Controls +------------- + +2.1) Default key mappings +------------------------- +The D-Pad and A/B/X/Y buttons have mirrored usage. So they do the same things +depending on if you're right or left-handed. + +| Buttons | Function | +|------------|--------------------------------| +| A / D-left | Left-click | +| X / D-up | Right-click | +| B / D-down | ESC (skips cutscenes and such) | +| L | Use virtual keyboard | +| R | Toggle hover/drag modes | +| Start | Open game menu | +| Select | Open 3DS config menu | +| Circle Pad | Move the cursor | + + +2.2) Hover mode +--------------- +When you use the touchscreen, you are simulating the mere moving of the mouse. You +can click only with taps, meaning it is impossible to drag stuff or hold down a +mouse button without using buttons mapped to right/left-click. + + +2.3) Drag mode +-------------- +Every time you touch and release the touchscreen, you are simulating the click and +release of the mouse buttons. At the moment, this is only a left-click. + + + +3.0) Supported Games +-------------------- +The full game engine compatibility list can be found here: +http://scummvm.org/compatibility/ + +While all the above games should run on the 3DS (report if they do not), there are +many games which are unplayable due to the lack of CPU speed on the 3DS. So if +you play any games that run really slow, this is not considered a bug, but rather +a hardware limitation. Though possible GPU optimizations are always in the works. +The New 3DS console has much better performance, but there are still many newer and +high-resolution games that cannot be played. A list of these unplayable games and +game engines will eventually be listed here. + + + +4.0) Compiling +-------------- + +4.1) Prerequisites +------------------ + - devkitARM (presumably with libctru, picasso and such) + - citro3d + - Optional: You should compile third-party libraries for the 3ds (commonly referred + to as portlibs in the devkitPRO community). Some games requires these to operate + properly. + + +4.1.1) Compiling third-party libraries +-------------------------------------- +Most libraries used can be compiled with same commands and configuration flags. + +It is assumed that you have these environment variables defined: + - DEVKITPRO Your root devkitPro directory + - DEVKITARM Your root devkitARM directory (probably same as $DEVKITPRO/devkitARM) + - CTRULIB Your root libctru directory (probably same as $DEVKITPRO/libctru) + +In the source directory of the library: + - $ export PORTLIBS=$DEVKITPRO/portlibs/armv6k + - $ export PATH=$DEVKITARM/bin:$PATH + - $ export PKG_CONFIG_PATH=$PORTLIBS/lib/pkgconfig + - $ export CFLAGS="-g -march=armv6k -mtune=mpcore -mfloat-abi=hard -O2 + -mword-relocations -ffunction-sections -fdata-sections" + - $ export CPPFLAGS="-I$PORTLIBS/include -I$CTRULIB/include" + - $ export LDFLAGS="-L$PORTLIBS/lib" + - $ mkdir -p $PORTLIBS + - $ ./configure --prefix=$PORTLIBS --host=arm-none-eabi --disable-shared + --enable-static + - $ make + - $ make install + +Useful libraries (and special config flags needed): + - zlib + - libpng + - libjpeg + - freetype2 --without-bzip2 --without-harfbuzz + - libmad + - tremor + - flac --disable-cpplibs --without-flac + - faad + + +4.2) Compiling ScummVM +---------------------- + - $ ./configure --host=3ds + - $ make + +Additionally compile to specific formats to be used on the 3ds: + - $ make scummvm.3dsx + - $ make scummvm.cia + + +4.3) Warning for 3DSX build +--------------------------- +The above configuration command will include all game engines by default and will +likely be too massive to run using the 3DSX format. Until dynamic modules are figured +out, you should configure engines like this for 3DSX builds: + + - $ ./configure --host=3ds --disable-all-engines--enable-engine=scumm-7-8,myst,riven, + sword1,sword2,sword25,sci,lure,sky,agi,agos + +Choose whatever engines you want, but if the ELF's .text section exceeds ~10MB, it +won't be playable unless it's a CIA. diff --git a/backends/platform/3ds/app/banner.png b/backends/platform/3ds/app/banner.png Binary files differnew file mode 100644 index 0000000000..a3b02150ec --- /dev/null +++ b/backends/platform/3ds/app/banner.png diff --git a/backends/platform/3ds/app/banner.wav b/backends/platform/3ds/app/banner.wav Binary files differnew file mode 100644 index 0000000000..e0b684b62f --- /dev/null +++ b/backends/platform/3ds/app/banner.wav diff --git a/backends/platform/3ds/app/icon.png b/backends/platform/3ds/app/icon.png Binary files differnew file mode 100644 index 0000000000..07022fbac1 --- /dev/null +++ b/backends/platform/3ds/app/icon.png diff --git a/backends/platform/3ds/app/scummvm.rsf b/backends/platform/3ds/app/scummvm.rsf new file mode 100644 index 0000000000..a4518949bb --- /dev/null +++ b/backends/platform/3ds/app/scummvm.rsf @@ -0,0 +1,219 @@ +BasicInfo: + Title : ScummVM + ProductCode : ScummVM + Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem + +TitleInfo: + Category : Application + UniqueId : 0xFF321 + +Option: + UseOnSD : true # true if App is to be installed to SD + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : false # Compresses where applicable (currently only exefs:/.code) + +AccessControlInfo: + CoreVersion : 2 + + # Exheader Format Version + DescVersion : 2 + + # Minimum Required Kernel Version (below is for 4.5.0) + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + + # ExtData + UseExtSaveData : false # enables ExtData + #ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId + + # FS:USER Archive Access Permissions + # Uncomment as required + FileSystemAccess: + #- CategorySystemApplication + #- CategoryHardwareCheck + #- CategoryFileSystemTool + #- Debug + #- TwlCardBackup + #- TwlNandData + #- Boss + - DirectSdmc + #- Core + #- CtrNandRo + #- CtrNandRw + #- CtrNandRoWrite + #- CategorySystemSettings + #- CardBoard + #- ExportImportIvs + #- DirectSdmcWrite + #- SwitchCleanup + #- SaveDataMove + #- Shop + #- Shell + #- CategoryHomeMenu + + # Process Settings + MemoryType : Application # Application/System/Base + SystemMode : 64MB # 64MB(Default)/96MB/80MB/72MB/32MB + IdealProcessor : 0 + AffinityMask : 1 + Priority : 16 + MaxCpu : 0 # Let system decide + HandleTableSize : 0x200 + DisableDebug : false + EnableForceDebug : false + CanWriteSharedPage : true + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : true + PermitMainFunctionArgument : true + CanShareDeviceMemory : true + RunnableOnSleep : false + SpecialMemoryArrange : true + + # New3DS Exclusive Process Settings + SystemModeExt : 124MB # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode + CpuSpeed : 804MHz # 268MHz(Default)/804MHz + EnableL2Cache : true # false(default)/true + CanAccessCore2 : true + + # Virtual Address Mappings + IORegisterMapping: + - 1ff00000-1ff7ffff # DSP memory + MemoryMapping: + - 1f000000-1f5fffff:r # VRAM + + # Accessible SVCs, <Name>:<ID> + SystemCallAccess: + ArbitrateAddress: 34 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + CreateAddressArbiter: 33 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreateSemaphore: 21 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetProcessIdealProcessor: 6 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + Backdoor: 123 + + # Service List + # Maximum 34 services (32 if firmware is prior to 9.3.0) + ServiceAccessControl: + - cfg:u + - fs:USER + - gsp::Gpu + - hid:USER + - ndm:u + - pxi:dev + - APT:U + - ac:u + - act:u + - am:net + - boss:U + - cam:u + - cecd:u + - dsp::DSP + - frd:u + - http:C + - ir:USER + - ir:u + - ir:rst + - ldr:ro + - mic:u + - news:u + - nim:aoc + - nwm::UDS + - ptm:u + - qtm:u + - soc:U + - ssl:C + - y2r:u + - gsp::Lcd + + +SystemControlInfo: + SaveDataSize: 0K + RemasterVersion: 0 + StackSize: 0x40000 + + # Modules that run services listed above should be included below + # Maximum 48 dependencies + # If a module is listed that isn't present on the 3DS, the title will get stuck at the logo (3ds waves) + # So act, nfc and qtm are commented for 4.x support. Uncomment if you need these. + # <module name>:<module titleid> + Dependency: + ac: 0x0004013000002402 + #act: 0x0004013000003802 + am: 0x0004013000001502 + boss: 0x0004013000003402 + camera: 0x0004013000001602 + cecd: 0x0004013000002602 + cfg: 0x0004013000001702 + codec: 0x0004013000001802 + csnd: 0x0004013000002702 + dlp: 0x0004013000002802 + dsp: 0x0004013000001a02 + friends: 0x0004013000003202 + gpio: 0x0004013000001b02 + gsp: 0x0004013000001c02 + hid: 0x0004013000001d02 + http: 0x0004013000002902 + i2c: 0x0004013000001e02 + ir: 0x0004013000003302 + mcu: 0x0004013000001f02 + mic: 0x0004013000002002 + ndm: 0x0004013000002b02 + news: 0x0004013000003502 + #nfc: 0x0004013000004002 + nim: 0x0004013000002c02 + nwm: 0x0004013000002d02 + pdn: 0x0004013000002102 + ps: 0x0004013000003102 + ptm: 0x0004013000002202 + #qtm: 0x0004013020004202 + ro: 0x0004013000003702 + socket: 0x0004013000002e02 + spi: 0x0004013000002302 + ssl: 0x0004013000002f02 diff --git a/backends/platform/3ds/config.cpp b/backends/platform/3ds/config.cpp new file mode 100644 index 0000000000..117b979d9f --- /dev/null +++ b/backends/platform/3ds/config.cpp @@ -0,0 +1,87 @@ +/* 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 "config.h" +#include "osystem.h" +#include "options-dialog.h" +#include "common/config-manager.h" +#include <3ds.h> + +namespace _3DS { + +Config config; +static Common::String prefix = "3ds_"; + +static bool confGetBool(Common::String key, bool defaultVal) { + if (ConfMan.hasKey(prefix + key)) + return ConfMan.getBool(prefix + key); + return defaultVal; +} + +static void confSetBool(Common::String key, bool val) { + ConfMan.setBool(prefix + key, val); +} + +static int confGetInt(Common::String key, int defaultVal) { + if (ConfMan.hasKey(prefix + key)) + return ConfMan.getInt(prefix + key); + return defaultVal; +} + +static void confSetInt(Common::String key, int val) { + ConfMan.setInt(prefix + key, val); +} + +void loadConfig() { + config.showCursor = confGetBool("showcursor", true); + config.snapToBorder = confGetBool("snaptoborder", true); + config.stretchToFit = confGetBool("stretchtofit", false); + config.sensitivity = confGetInt("sensitivity", -5); + config.screen = confGetInt("screen", kScreenBoth); + + // Turn off the backlight of any screen not used + if (R_SUCCEEDED(gspLcdInit())) { + if (config.screen == kScreenTop) { + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_TOP); + GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_BOTTOM); + } else if (config.screen == kScreenBottom) { + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTTOM); + GSPLCD_PowerOffBacklight(GSPLCD_SCREEN_TOP); + } else + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); + gspLcdExit(); + } + + OSystem_3DS *osys = (OSystem_3DS *)g_system; + osys->updateConfig(); +} + +void saveConfig() { + confSetBool("showcursor", config.showCursor); + confSetBool("snaptoborder", config.snapToBorder); + confSetBool("stretchtofit", config.stretchToFit); + confSetInt("sensitivity", config.sensitivity); + confSetInt("screen", config.screen); + ConfMan.flushToDisk(); +} + +} // namespace _3DS diff --git a/backends/platform/3ds/config.h b/backends/platform/3ds/config.h new file mode 100644 index 0000000000..c8b75736ad --- /dev/null +++ b/backends/platform/3ds/config.h @@ -0,0 +1,45 @@ +/* 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 CONFIG_3DS_H +#define CONFIG_3DS_H + +#include "common/str.h" + +namespace _3DS { + +struct Config { + bool showCursor; + bool snapToBorder; + bool stretchToFit; + int sensitivity; + int screen; +}; + +extern Config config; + +void loadConfig(); +void saveConfig(); + +} // namespace _3DS + +#endif // CONFIG_3DS_H diff --git a/backends/platform/3ds/gui.cpp b/backends/platform/3ds/gui.cpp new file mode 100644 index 0000000000..0883d5a102 --- /dev/null +++ b/backends/platform/3ds/gui.cpp @@ -0,0 +1,46 @@ +/* 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 "backends/platform/3ds/gui.h" +#include "common/system.h" + +StatusMessageDialog* StatusMessageDialog::_opened = 0; + +StatusMessageDialog::StatusMessageDialog(const Common::String &message, uint32 duration) + : MessageDialog(message, 0, 0) { + _timer = g_system->getMillis() + duration; + if (_opened) + _opened->close(); + _opened = this; +} + +void StatusMessageDialog::handleTickle() { + MessageDialog::handleTickle(); + if (g_system->getMillis() > _timer) + close(); +} + +void StatusMessageDialog::close() { + GUI::Dialog::close(); + if (_opened) + _opened = 0; +} diff --git a/backends/platform/3ds/gui.h b/backends/platform/3ds/gui.h new file mode 100644 index 0000000000..66c6547139 --- /dev/null +++ b/backends/platform/3ds/gui.h @@ -0,0 +1,41 @@ +/* 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 GUI_3DS_H +#define GUI_3DS_H + +#include "gui/message.h" + +class StatusMessageDialog : public GUI::MessageDialog { +public: + StatusMessageDialog(const Common::String &message, uint32 duration); + + void handleTickle(); + +protected: + virtual void close(); + + uint32 _timer; + static StatusMessageDialog* _opened; +}; + +#endif // GUI_3DS_H diff --git a/backends/platform/3ds/main.cpp b/backends/platform/3ds/main.cpp new file mode 100644 index 0000000000..6cc2c5cf5d --- /dev/null +++ b/backends/platform/3ds/main.cpp @@ -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. + * + */ + +#include "osystem.h" +#include <3ds.h> + +int main(int argc, char *argv[]) { + // Initialize basic libctru stuff + gfxInitDefault(); + cfguInit(); + osSetSpeedupEnable(true); +// consoleInit(GFX_TOP, NULL); + + g_system = new _3DS::OSystem_3DS(); + assert(g_system); + + // Invoke the actual ScummVM main entry point +// if (argc > 2) +// res = scummvm_main(argc-2, &argv[2]); +// else +// res = scummvm_main(argc, argv); + scummvm_main(0, nullptr); + + delete dynamic_cast<_3DS::OSystem_3DS*>(g_system); + + // Turn on both screen backlights before exiting. + if (R_SUCCEEDED(gspLcdInit())) { + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); + gspLcdExit(); + } + + cfguExit(); + gfxExit(); + return 0; +} diff --git a/backends/platform/3ds/module.mk b/backends/platform/3ds/module.mk new file mode 100644 index 0000000000..3eb15aef81 --- /dev/null +++ b/backends/platform/3ds/module.mk @@ -0,0 +1,18 @@ +MODULE := backends/platform/3ds + +MODULE_OBJS := \ + main.o \ + shader.shbin.o \ + sprite.o \ + gui.o \ + config.o \ + options-dialog.o \ + osystem.o \ + osystem-graphics.o \ + osystem-audio.o \ + osystem-events.o + +# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. +MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) +OBJS := $(MODULE_OBJS) $(OBJS) +MODULE_DIRS += $(sort $(dir $(MODULE_OBJS))) diff --git a/backends/platform/3ds/options-dialog.cpp b/backends/platform/3ds/options-dialog.cpp new file mode 100644 index 0000000000..0f8bfd0c66 --- /dev/null +++ b/backends/platform/3ds/options-dialog.cpp @@ -0,0 +1,98 @@ +/* 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 "options-dialog.h" +#include "config.h" +#include "gui/dialog.h" +#include "gui/gui-manager.h" +#include "gui/widgets/list.h" +#include "gui/widgets/tab.h" +#include "osystem.h" +#include "engines/scumm/scumm.h" +#include "gui/widgets/popup.h" + +#include "common/translation.h" + +namespace _3DS { + +bool optionMenuOpened = false; + +OptionsDialog::OptionsDialog() : GUI::Dialog(20, 20, 280, 200) { + + optionMenuOpened = true; + + new GUI::ButtonWidget(this, 120, 180, 72, 16, _("~C~lose"), 0, GUI::kCloseCmd); + new GUI::ButtonWidget(this, 200, 180, 72, 16, _("~S~ave"), 0, GUI::kOKCmd); + + _showCursorCheckbox = new GUI::CheckboxWidget(this, 5, 5, 130, 20, _("Show mouse cursor"), 0, 0, 'T'); + _showCursorCheckbox->setState(config.showCursor); + + _snapToBorderCheckbox = new GUI::CheckboxWidget(this, 5, 22, 130, 20, _("Snap to edges"), 0, 0, 'T'); + _snapToBorderCheckbox->setState(config.snapToBorder); + + _stretchToFitCheckbox = new GUI::CheckboxWidget(this, 140, 5, 130, 20, _("Stretch to fit"), 0, 0, 'T'); + _stretchToFitCheckbox->setState(config.stretchToFit); + + new GUI::StaticTextWidget(this, 0, 60, 110, 15, _("Use Screen:"), Graphics::kTextAlignRight); + _screenRadioGroup = new GUI::RadiobuttonGroup(this, kScreenRadioGroup); + _screenTopRadioWidget = new GUI::RadiobuttonWidget(this, 120, 50, 60, 20, _screenRadioGroup, kScreenTop, _("Top")); + _screenBottomRadioWidget = new GUI::RadiobuttonWidget(this, 190, 50, 80, 20, _screenRadioGroup, kScreenBottom, _("Bottom")); + _screenBothRadioWidget = new GUI::RadiobuttonWidget(this, 155, 70, 80, 20, _screenRadioGroup, kScreenBoth, _("Both")); + _screenRadioGroup->setValue(config.screen); + + new GUI::StaticTextWidget(this, 0, 100, 110, 15, _("C-Pad Sensitivity:"), Graphics::kTextAlignRight); + _sensitivity = new GUI::SliderWidget(this, 115, 100, 160, 15, "TODO: Add tooltip", 1); + _sensitivity->setMinValue(-15); + _sensitivity->setMaxValue(30); + _sensitivity->setValue(config.sensitivity); + _sensitivity->setFlags(GUI::WIDGET_CLEARBG); +} + +OptionsDialog::~OptionsDialog() { + optionMenuOpened = false; +} + +void OptionsDialog::updateConfigManager() { + config.showCursor = _showCursorCheckbox->getState(); + config.snapToBorder = _snapToBorderCheckbox->getState(); + config.stretchToFit = _stretchToFitCheckbox->getState(); + config.sensitivity = _sensitivity->getValue(); + config.screen = _screenRadioGroup->getValue(); + saveConfig(); + loadConfig(); +} + +void OptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + switch(cmd) { + case GUI::kOKCmd: + updateConfigManager(); + // Fall through + case GUI::kCloseCmd: + close(); + break; + default: + Dialog::handleCommand(sender, cmd, data); + break; + } +} + +} // namespace _3DS diff --git a/backends/platform/3ds/options-dialog.h b/backends/platform/3ds/options-dialog.h new file mode 100644 index 0000000000..6673b88e7b --- /dev/null +++ b/backends/platform/3ds/options-dialog.h @@ -0,0 +1,70 @@ +/* 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 OPTIONS_DIALOG_3DS_H +#define OPTIONS_DIALOG_3DS_H + + +#include "common/scummsys.h" +#include "common/str.h" +#include "gui/object.h" +#include "gui/widget.h" +#include "gui/dialog.h" +#include "gui/widgets/tab.h" +#include "scumm/dialogs.h" + +namespace _3DS { + +enum { + kSave = 0x10000000, + kScreenRadioGroup, + kScreenTop, + kScreenBottom, + kScreenBoth, +}; + +extern bool optionMenuOpened; + +class OptionsDialog : public GUI::Dialog { + +public: + OptionsDialog(); + ~OptionsDialog(); + +protected: + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + void updateConfigManager(); + + GUI::SliderWidget *_sensitivity; + GUI::CheckboxWidget *_showCursorCheckbox; + GUI::CheckboxWidget *_snapToBorderCheckbox; + GUI::CheckboxWidget *_stretchToFitCheckbox; + + GUI::RadiobuttonGroup *_screenRadioGroup; + GUI::RadiobuttonWidget *_screenTopRadioWidget; + GUI::RadiobuttonWidget *_screenBottomRadioWidget; + GUI::RadiobuttonWidget *_screenBothRadioWidget; +}; + +} // namespace _3DS + +#endif // OPTIONS_DIALOG_3DS_H diff --git a/backends/platform/3ds/osystem-audio.cpp b/backends/platform/3ds/osystem-audio.cpp new file mode 100644 index 0000000000..17e419c36d --- /dev/null +++ b/backends/platform/3ds/osystem-audio.cpp @@ -0,0 +1,110 @@ +/* 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 "osystem.h" +#include "audio/mixer.h" + +namespace _3DS { + +static bool hasAudio = false; + +static void audioThreadFunc(void *arg) { + Audio::MixerImpl *mixer = (Audio::MixerImpl *)arg; + OSystem_3DS *osys = (OSystem_3DS *)g_system; + + int i; + const int channel = 0; + int bufferIndex = 0; + const int bufferCount = 3; + const int bufferSize = 80000; // Can't be too small, based on delayMillis duration + const int sampleRate = mixer->getOutputRate(); + int sampleLen = 0; + uint32 lastTime = osys->getMillis(true); + uint32 time = lastTime; + ndspWaveBuf buffers[bufferCount]; + + for (i = 0; i < bufferCount; ++i) { + memset(&buffers[i], 0, sizeof(ndspWaveBuf)); + buffers[i].data_vaddr = linearAlloc(bufferSize); + buffers[i].looping = false; + buffers[i].status = NDSP_WBUF_FREE; + } + + ndspChnReset(channel); + ndspChnSetInterp(channel, NDSP_INTERP_LINEAR); + ndspChnSetRate(channel, sampleRate); + ndspChnSetFormat(channel, NDSP_FORMAT_STEREO_PCM16); + + while (!osys->exiting) { + osys->delayMillis(100); // Note: Increasing the delay requires a bigger buffer + + time = osys->getMillis(true); + sampleLen = (time - lastTime) * 22 * 4; // sampleRate / 1000 * channelCount * sizeof(int16); + lastTime = time; + + if (!osys->sleeping && sampleLen > 0) { + bufferIndex++; + bufferIndex %= bufferCount; + ndspWaveBuf *buf = &buffers[bufferIndex]; + + buf->nsamples = mixer->mixCallback(buf->data_adpcm, sampleLen); + if (buf->nsamples > 0) { + DSP_FlushDataCache(buf->data_vaddr, bufferSize); + ndspChnWaveBufAdd(channel, buf); + } + } + } + + for (i = 0; i < bufferCount; ++i) + linearFree(buffers[i].data_pcm8); +} + +void OSystem_3DS::initAudio() { + _mixer = new Audio::MixerImpl(this, 22050); + + hasAudio = R_SUCCEEDED(ndspInit()); + _mixer->setReady(false); + + if (hasAudio) { + s32 prio = 0; + svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + audioThread = threadCreate(&audioThreadFunc, _mixer, 32 * 1048, prio - 1, -2, false); + } +} + +void OSystem_3DS::destroyAudio() { + if (hasAudio) { + threadJoin(audioThread, U64_MAX); + threadFree(audioThread); + ndspExit(); + } + + delete _mixer; + _mixer = 0; +} + +Audio::Mixer *OSystem_3DS::getMixer() { + assert(_mixer); + return _mixer; +} + +} // namespace _3DS diff --git a/backends/platform/3ds/osystem-events.cpp b/backends/platform/3ds/osystem-events.cpp new file mode 100644 index 0000000000..ae8a9b8b2b --- /dev/null +++ b/backends/platform/3ds/osystem-events.cpp @@ -0,0 +1,302 @@ +/* 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 "backends/timer/default/default-timer.h" +#include "engines/engine.h" +#include "gui.h" +#include "options-dialog.h" +#include "config.h" +#include "osystem.h" + +namespace _3DS { + +static Common::Mutex *eventMutex; +static InputMode inputMode = MODE_DRAG; +static aptHookCookie cookie; +static bool optionMenuOpening = false; +static Common::String messageOSD; +static bool showMessageOSD = false; + +static void pushEventQueue(Common::Queue<Common::Event> *queue, Common::Event &event) { + Common::StackLock lock(*eventMutex); + queue->push(event); +} + +static void eventThreadFunc(void *arg) { + OSystem_3DS *osys = (OSystem_3DS *)g_system; + auto eventQueue = (Common::Queue<Common::Event> *)arg; + + uint32 touchStartTime = osys->getMillis(); + touchPosition lastTouch = {0, 0}; + bool isRightClick = false; + float cursorX = 0; + float cursorY = 0; + float cursorDeltaX = 0; + float cursorDeltaY = 0; + int circleDeadzone = 20; + int borderSnapZone = 6; + Common::Event event; + + while (!osys->exiting) { + do { + osys->delayMillis(10); + } while (osys->sleeping && !osys->exiting); + + hidScanInput(); + touchPosition touch; + circlePosition circle; + u32 held = hidKeysHeld(); + u32 keysPressed = hidKeysDown(); + u32 keysReleased = hidKeysUp(); + + // C-Pad used to control the cursor + hidCircleRead(&circle); + if (circle.dx < circleDeadzone && circle.dx > -circleDeadzone) + circle.dx = 0; + if (circle.dy < circleDeadzone && circle.dy > -circleDeadzone) + circle.dy = 0; + cursorDeltaX = (0.0002f + config.sensitivity / 100000.f) * circle.dx * abs(circle.dx); + cursorDeltaY = (0.0002f + config.sensitivity / 100000.f) * circle.dy * abs(circle.dy); + + // Touch screen events + if (held & KEY_TOUCH) { + hidTouchRead(&touch); + if (config.snapToBorder) { + if (touch.px < borderSnapZone) + touch.px = 0; + if (touch.px > 319 - borderSnapZone) + touch.px = 319; + if (touch.py < borderSnapZone) + touch.py = 0; + if (touch.py > 239 - borderSnapZone) + touch.py = 239; + } + cursorX = touch.px; + cursorY = touch.py; + osys->transformPoint(touch); + + osys->warpMouse(touch.px, touch.py); + event.mouse.x = touch.px; + event.mouse.y = touch.py; + + if (keysPressed & KEY_TOUCH) { + touchStartTime = osys->getMillis(); + isRightClick = (held & KEY_X || held & KEY_DUP); + if (inputMode == MODE_DRAG) { + event.type = isRightClick ? Common::EVENT_RBUTTONDOWN : Common::EVENT_LBUTTONDOWN; + pushEventQueue(eventQueue, event); + } + } else if (touch.px != lastTouch.px || touch.py != lastTouch.py) { + event.type = Common::EVENT_MOUSEMOVE; + pushEventQueue(eventQueue, event); + } + + lastTouch = touch; + } else if (keysReleased & KEY_TOUCH) { + event.mouse.x = lastTouch.px; + event.mouse.y = lastTouch.py; + if (inputMode == MODE_DRAG) { + event.type = isRightClick ? Common::EVENT_RBUTTONUP : Common::EVENT_LBUTTONUP; + pushEventQueue(eventQueue, event); + } else if (osys->getMillis() - touchStartTime < 200) { + // Process click in MODE_HOVER + event.type = Common::EVENT_MOUSEMOVE; + pushEventQueue(eventQueue, event); + event.type = isRightClick ? Common::EVENT_RBUTTONDOWN : Common::EVENT_LBUTTONDOWN; + pushEventQueue(eventQueue, event); + event.type = isRightClick ? Common::EVENT_RBUTTONUP : Common::EVENT_LBUTTONUP; + pushEventQueue(eventQueue, event); + } + } else if (cursorDeltaX != 0 || cursorDeltaY != 0) { + cursorX += cursorDeltaX; + cursorY -= cursorDeltaY; + if (cursorX < 0) cursorX = 0; + if (cursorY < 0) cursorY = 0; + if (cursorX > 320) cursorX = 320; + if (cursorY > 240) cursorY = 240; + lastTouch.px = cursorX; + lastTouch.py = cursorY; + osys->transformPoint(lastTouch); + osys->warpMouse(lastTouch.px, lastTouch.py); + event.mouse.x = lastTouch.px; + event.mouse.y = lastTouch.py; + event.type = Common::EVENT_MOUSEMOVE; + pushEventQueue(eventQueue, event); + } + + // Button events + if (keysPressed & KEY_R) { + if (inputMode == MODE_DRAG) { + inputMode = MODE_HOVER; + osys->displayMessageOnOSD("Hover Mode"); + } else { + inputMode = MODE_DRAG; + osys->displayMessageOnOSD("Drag Mode"); + } + } + if (keysPressed & KEY_A || keysPressed & KEY_DLEFT || keysReleased & KEY_A || keysReleased & KEY_DLEFT) { + // SIMULATE LEFT CLICK + event.mouse.x = lastTouch.px; + event.mouse.y = lastTouch.py; + if (keysPressed & KEY_A || keysPressed & KEY_DLEFT) + event.type = Common::EVENT_LBUTTONDOWN; + else + event.type = Common::EVENT_LBUTTONUP; + pushEventQueue(eventQueue, event); + } + if (keysPressed & KEY_X || keysPressed & KEY_DUP || keysReleased & KEY_X || keysReleased & KEY_DUP) { + // SIMULATE RIGHT CLICK + event.mouse.x = lastTouch.px; + event.mouse.y = lastTouch.py; + if (keysPressed & KEY_X || keysPressed & KEY_DUP) + event.type = Common::EVENT_RBUTTONDOWN; + else + event.type = Common::EVENT_RBUTTONUP; + pushEventQueue(eventQueue, event); + } + if (keysPressed & KEY_L) { + event.type = Common::EVENT_VIRTUAL_KEYBOARD; + pushEventQueue(eventQueue, event); + } + if (keysPressed & KEY_START) { + event.type = Common::EVENT_MAINMENU; + pushEventQueue(eventQueue, event); + } + if (keysPressed & KEY_SELECT) { + if (!optionMenuOpened) + optionMenuOpening = true; + } + if (keysPressed & KEY_B || keysReleased & KEY_B || keysPressed & KEY_DDOWN || keysReleased & KEY_DDOWN) { + if (keysPressed & KEY_B || keysPressed & KEY_DDOWN) + event.type = Common::EVENT_KEYDOWN; + else + event.type = Common::EVENT_KEYUP; + event.kbd.keycode = Common::KEYCODE_ESCAPE; + event.kbd.ascii = Common::ASCII_ESCAPE; + event.kbd.flags = 0; + pushEventQueue(eventQueue, event); + } + + // TODO: EVENT_PREDICTIVE_DIALOG + // EVENT_SCREEN_CHANGED + } +} + +static void aptHookFunc(APT_HookType hookType, void *param) { + OSystem_3DS *osys = (OSystem_3DS *)g_system; + + switch (hookType) { + case APTHOOK_ONSUSPEND: + case APTHOOK_ONSLEEP: + if (g_engine) + g_engine->pauseEngine(true); + osys->sleeping = true; + if (R_SUCCEEDED(gspLcdInit())) { + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); + gspLcdExit(); + } + break; + case APTHOOK_ONRESTORE: + case APTHOOK_ONWAKEUP: + if (g_engine) + g_engine->pauseEngine(false); + osys->sleeping = false; + loadConfig(); + break; + default: { + Common::StackLock lock(*eventMutex); + Common::Event event; + event.type = Common::EVENT_QUIT; + g_system->getEventManager()->pushEvent(event); + } + } +} + +static void timerThreadFunc(void *arg) { + OSystem_3DS *osys = (OSystem_3DS *)arg; + DefaultTimerManager *tm = (DefaultTimerManager *)osys->getTimerManager(); + while (!osys->exiting) { + g_system->delayMillis(10); + tm->handler(); + } +} + +void OSystem_3DS::initEvents() { + eventMutex = new Common::Mutex(); + s32 prio = 0; + svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + _timerThread = threadCreate(&timerThreadFunc, this, 32 * 1024, prio - 1, -2, false); + _eventThread = threadCreate(&eventThreadFunc, &_eventQueue, 32 * 1024, prio - 1, -2, false); + + aptHook(&cookie, aptHookFunc, this); +} + +void OSystem_3DS::destroyEvents() { + threadJoin(_timerThread, U64_MAX); + threadFree(_timerThread); + + threadJoin(_eventThread, U64_MAX); + threadFree(_eventThread); + delete eventMutex; +} + +void OSystem_3DS::transformPoint(touchPosition &point) { + if (!_overlayVisible) { + point.px = static_cast<float>(point.px) / _gameBottomTexture.getScaleX() - _gameBottomX; + point.py = static_cast<float>(point.py) / _gameBottomTexture.getScaleY() - _gameBottomY; + } +} + +void OSystem_3DS::displayMessageOnOSD(const char *msg) { + messageOSD = msg; + showMessageOSD = true; +} + +bool OSystem_3DS::pollEvent(Common::Event &event) { + if (showMessageOSD) { + showMessageOSD = false; + StatusMessageDialog dialog(messageOSD, 800); + dialog.runModal(); + } + + aptMainLoop(); // Call apt hook when necessary + + if (optionMenuOpening) { + optionMenuOpening = false; + OptionsDialog dialog; + if (g_engine) + g_engine->pauseEngine(true); + dialog.runModal(); + if (g_engine) + g_engine->pauseEngine(false); + } + + Common::StackLock lock(*eventMutex); + + if (_eventQueue.empty()) + return false; + + event = _eventQueue.pop(); + return true; +} + +} // namespace _3DS diff --git a/backends/platform/3ds/osystem-graphics.cpp b/backends/platform/3ds/osystem-graphics.cpp new file mode 100644 index 0000000000..0cfd70c9cd --- /dev/null +++ b/backends/platform/3ds/osystem-graphics.cpp @@ -0,0 +1,517 @@ +/* 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 "backends/platform/3ds/osystem.h" +#include "backends/platform/3ds/shader_shbin.h" +#include "common/rect.h" +#include "options-dialog.h" +#include "config.h" + +// Used to transfer the final rendered display to the framebuffer +#define DISPLAY_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | \ + GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | \ + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +namespace _3DS { + +void OSystem_3DS::initGraphics() { + _pfGame = Graphics::PixelFormat::createFormatCLUT8(); + _pfGameTexture = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); + + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + // Initialize the render targets + _renderTargetTop = + C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetClear(_renderTargetTop, C3D_CLEAR_ALL, 0x0000000, 0); + C3D_RenderTargetSetOutput(_renderTargetTop, GFX_TOP, GFX_LEFT, + DISPLAY_TRANSFER_FLAGS); + + _renderTargetBottom = + C3D_RenderTargetCreate(240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetClear(_renderTargetBottom, C3D_CLEAR_ALL, 0x00000000, 0); + C3D_RenderTargetSetOutput(_renderTargetBottom, GFX_BOTTOM, GFX_LEFT, + DISPLAY_TRANSFER_FLAGS); + + // Load and bind simple default shader (shader.v.pica) + _dvlb = DVLB_ParseFile((u32*)shader_shbin, shader_shbin_size); + shaderProgramInit(&_program); + shaderProgramSetVsh(&_program, &_dvlb->DVLE[0]); + C3D_BindProgram(&_program); + + _projectionLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "projection"); + _modelviewLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "modelView"); + + C3D_AttrInfo *attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); // v1=texcoord + + Mtx_OrthoTilt(&_projectionTop, 0.0, 400.0, 240.0, 0.0, 0.0, 1.0); + Mtx_OrthoTilt(&_projectionBottom, 0.0, 320.0, 240.0, 0.0, 0.0, 1.0); + + C3D_TexEnv *env = C3D_GetTexEnv(0); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, 0, 0); + C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + + C3D_DepthTest(false, GPU_GEQUAL, GPU_WRITE_ALL); + C3D_CullFace(GPU_CULL_NONE); +} + +void OSystem_3DS::destroyGraphics() { + _gameScreen.free(); + _gameTopTexture.free(); + _gameBottomTexture.free(); + _overlay.free(); + + shaderProgramFree(&_program); + DVLB_Free(_dvlb); + + C3D_RenderTargetDelete(_renderTargetTop); + C3D_RenderTargetDelete(_renderTargetBottom); + + C3D_Fini(); +} + +bool OSystem_3DS::hasFeature(OSystem::Feature f) { + return (f == OSystem::kFeatureCursorPalette || + f == OSystem::kFeatureOverlaySupportsAlpha); +} + +void OSystem_3DS::setFeatureState(OSystem::Feature f, bool enable) { + switch (f) { + case OSystem::kFeatureCursorPalette: + _cursorPaletteEnabled = enable; + flushCursor(); + break; + default: + break; + } +} + +bool OSystem_3DS::getFeatureState(OSystem::Feature f) { + switch (f) { + case OSystem::kFeatureCursorPalette: + return _cursorPaletteEnabled; + default: + return false; + } +} + +const OSystem::GraphicsMode * +OSystem_3DS::getSupportedGraphicsModes() const { + return s_graphicsModes; +} + +int OSystem_3DS::getDefaultGraphicsMode() const { + return GFX_LINEAR; +} + +bool OSystem_3DS::setGraphicsMode(int mode) { + return true; +} + +void OSystem_3DS::resetGraphicsScale() { + debug("resetGraphicsScale"); +} + +int OSystem_3DS::getGraphicsMode() const { + return GFX_LINEAR; +} +void OSystem_3DS::initSize(uint width, uint height, + const Graphics::PixelFormat *format) { + debug("3ds initsize w:%d h:%d", width, height); + _gameWidth = width; + _gameHeight = height; + _gameTopTexture.create(width, height, _pfGameTexture); + _overlay.create(getOverlayWidth(), getOverlayHeight(), _pfGameTexture); + + if (format) { + debug("pixelformat: %d %d %d %d %d", format->bytesPerPixel, format->rBits(), format->gBits(), format->bBits(), format->aBits());; + _pfGame = *format; + } + + _gameScreen.create(width, height, _pfGame); + + _focusDirty = true; + _focusRect = Common::Rect(_gameWidth, _gameHeight); + + updateSize(); +} + +void OSystem_3DS::updateSize() { + if (config.stretchToFit) { + _gameTopX = _gameTopY = _gameBottomX = _gameBottomY = 0; + _gameTopTexture.setScale(400.f / _gameWidth, 240.f / _gameHeight); + _gameBottomTexture.setScale(320.f / _gameWidth, 240.f / _gameHeight); + } else { + float ratio = static_cast<float>(_gameWidth) / _gameHeight; + + if (ratio > 400.f / 240.f) { + float r = 400.f / _gameWidth; + _gameTopTexture.setScale(r, r); + _gameTopX = 0; + _gameTopY = (240.f - r * _gameHeight) / 2.f; + } else { + float r = 240.f / _gameHeight; + _gameTopTexture.setScale(r, r); + _gameTopY = 0; + _gameTopX = (400.f - r * _gameWidth) / 2.f; + } + if (ratio > 320.f / 240.f) { + float r = 320.f / _gameWidth; + _gameBottomTexture.setScale(r, r); + _gameBottomX = 0; + _gameBottomY = (240.f - r * _gameHeight) / 2.f; + } else { + float r = 240.f / _gameHeight; + _gameBottomTexture.setScale(r, r); + _gameBottomY = 0; + _gameBottomX = (320.f - r * _gameWidth) / 2.f; + } + } + _gameTopTexture.setPosition(_gameTopX, _gameTopY); + _gameBottomTexture.setPosition(_gameBottomX, _gameBottomY); + if (_overlayVisible) + _cursorTexture.setScale(1.f, 1.f); + else if (config.screen == kScreenTop) + _cursorTexture.setScale(_gameTopTexture.getScaleX(), _gameTopTexture.getScaleY()); + else + _cursorTexture.setScale(_gameBottomTexture.getScaleX(), _gameBottomTexture.getScaleY()); +} + +Common::List<Graphics::PixelFormat> OSystem_3DS::getSupportedFormats() const { + Common::List<Graphics::PixelFormat> list; + list.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); // GPU_RGBA8 + list.push_back(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); // GPU_RGB565 +// list.push_back(Graphics::PixelFormat(3, 0, 0, 0, 8, 0, 8, 16, 0)); // GPU_RGB8 + list.push_back(Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); // RGB555 (needed for FMTOWNS?) + list.push_back(Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)); // GPU_RGBA5551 + list.push_back(Graphics::PixelFormat::createFormatCLUT8()); + return list; +} + +void OSystem_3DS::beginGFXTransaction() { + // +} +OSystem::TransactionError OSystem_3DS::endGFXTransaction() { + return OSystem::kTransactionSuccess; +} + +void OSystem_3DS::setPalette(const byte *colors, uint start, uint num) { + assert(start + num <= 256); + memcpy(_palette + 3 * start, colors, 3 * num); + + // Manually update all color that were changed + if (_gameScreen.format.bytesPerPixel == 1) { + flushGameScreen(); + } +} +void OSystem_3DS::grabPalette(byte *colors, uint start, uint num) { + assert(start + num <= 256); + memcpy(colors, _palette + 3 * start, 3 * num); +} + +void OSystem_3DS::copyRectToScreen(const void *buf, int pitch, int x, + int y, int w, int h) { + Common::Rect rect(x, y, x+w, y+h); + _gameScreen.copyRectToSurface(buf, pitch, x, y, w, h); + Graphics::Surface subSurface = _gameScreen.getSubArea(rect); + + Graphics::Surface *convertedSubSurface = subSurface.convertTo(_pfGameTexture, _palette); + _gameTopTexture.copyRectToSurface(*convertedSubSurface, x, y, Common::Rect(w, h)); + + convertedSubSurface->free(); + delete convertedSubSurface; + _gameTopTexture.markDirty(); +} + +void OSystem_3DS::flushGameScreen() { + Graphics::Surface *converted = _gameScreen.convertTo(_pfGameTexture, _palette); + _gameTopTexture.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h)); + _gameTopTexture.markDirty(); + converted->free(); + delete converted; +} + +Graphics::Surface *OSystem_3DS::lockScreen() { + return &_gameScreen; +} +void OSystem_3DS::unlockScreen() { + flushGameScreen(); +} + +void OSystem_3DS::updateScreen() { + + if (sleeping || exiting) + return; + +// updateFocus(); + + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + // Render top screen + C3D_FrameDrawOn(_renderTargetTop); + if (config.screen == kScreenTop || config.screen == kScreenBoth) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionTop); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameTopTexture.getMatrix()); + _gameTopTexture.render(); + _gameTopTexture.render(); + if (_overlayVisible && config.screen == kScreenTop) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix()); + _overlay.render(); + } + if (_cursorVisible && config.showCursor && config.screen == kScreenTop) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix()); + _cursorTexture.render(); + } + } + + // Render bottom screen + C3D_FrameDrawOn(_renderTargetBottom); + if (config.screen == kScreenBottom || config.screen == kScreenBoth) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionBottom); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameBottomTexture.getMatrix()); + _gameTopTexture.render(); + _gameTopTexture.render(); + if (_overlayVisible) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix()); + _overlay.render(); + } + if (_cursorVisible && config.showCursor) { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix()); + _cursorTexture.render(); + } + } + C3D_FrameEnd(0); +} + +void OSystem_3DS::setShakePos(int shakeOffset) { + // TODO: implement this in overlay, top screen, and mouse too + _screenShakeOffset = shakeOffset; + _gameTopTexture.setPosition(_gameTopX, _gameTopY + _gameTopTexture.getScaleY() * shakeOffset); + _gameBottomTexture.setPosition(_gameBottomX, _gameBottomY + _gameBottomTexture.getScaleY() * shakeOffset); +} + +void OSystem_3DS::setFocusRectangle(const Common::Rect &rect) { + debug("setfocus: %d %d %d %d", rect.left, rect.top, rect.width(), rect.height()); + _focusRect = rect; + _focusDirty = true; + _focusClearTime = 0; +} + +void OSystem_3DS::clearFocusRectangle() { + _focusClearTime = getMillis(); +} + +void OSystem_3DS::updateFocus() { + + if (_focusClearTime && getMillis() - _focusClearTime > 5000) { + _focusClearTime = 0; + _focusDirty = true; + _focusRect = Common::Rect(_gameWidth, _gameHeight); + } + + if (_focusDirty) { + float duration = 1.f / 20.f; // Focus animation in frame duration + float w = 400.f; + float h = 240.f; + float ratio = _focusRect.width() / _focusRect.height(); + if (ratio > w/h) { + _focusTargetScaleX = w / _focusRect.width(); + float newHeight = (float)_focusRect.width() / w/h; + _focusTargetScaleY = h / newHeight; + _focusTargetPosX = _focusTargetScaleX * _focusRect.left; + _focusTargetPosY = _focusTargetScaleY * ((float)_focusRect.top - (newHeight - _focusRect.height())/2.f); + } else { + _focusTargetScaleY = h / _focusRect.height(); + float newWidth = (float)_focusRect.height() * w/h; + _focusTargetScaleX = w / newWidth; + _focusTargetPosY = _focusTargetScaleY * _focusRect.top; + _focusTargetPosX = _focusTargetScaleX * ((float)_focusRect.left - (newWidth - _focusRect.width())/2.f); + } + if (_focusTargetPosX < 0 && _focusTargetScaleY != 240.f / _gameHeight) + _focusTargetPosX = 0; + if (_focusTargetPosY < 0 && _focusTargetScaleX != 400.f / _gameWidth) + _focusTargetPosY = 0; + _focusStepPosX = duration * (_focusTargetPosX - _focusPosX); + _focusStepPosY = duration * (_focusTargetPosY - _focusPosY); + _focusStepScaleX = duration * (_focusTargetScaleX - _focusScaleX); + _focusStepScaleY = duration * (_focusTargetScaleY - _focusScaleY); + } + + if (_focusDirty || _focusPosX != _focusTargetPosX || _focusPosY != _focusTargetPosY || + _focusScaleX != _focusTargetScaleX || _focusScaleY != _focusTargetScaleY) { + _focusDirty = false; + + if ((_focusStepPosX > 0 && _focusPosX > _focusTargetPosX) || (_focusStepPosX < 0 && _focusPosX < _focusTargetPosX)) + _focusPosX = _focusTargetPosX; + else if (_focusPosX != _focusTargetPosX) + _focusPosX += _focusStepPosX; + + if ((_focusStepPosY > 0 && _focusPosY > _focusTargetPosY) || (_focusStepPosY < 0 && _focusPosY < _focusTargetPosY)) + _focusPosY = _focusTargetPosY; + else if (_focusPosY != _focusTargetPosY) + _focusPosY += _focusStepPosY; + + if ((_focusStepScaleX > 0 && _focusScaleX > _focusTargetScaleX) || (_focusStepScaleX < 0 && _focusScaleX < _focusTargetScaleX)) + _focusScaleX = _focusTargetScaleX; + else if (_focusScaleX != _focusTargetScaleX) + _focusScaleX += _focusStepScaleX; + + if ((_focusStepScaleY > 0 && _focusScaleY > _focusTargetScaleY) || (_focusStepScaleY < 0 && _focusScaleY < _focusTargetScaleY)) + _focusScaleY = _focusTargetScaleY; + else if (_focusScaleY != _focusTargetScaleY) + _focusScaleY += _focusStepScaleY; + + Mtx_Identity(&_focusMatrix); + Mtx_Translate(&_focusMatrix, -_focusPosX, -_focusPosY, 0); + Mtx_Scale(&_focusMatrix, _focusScaleX, _focusScaleY, 1.f); + } +} + +void OSystem_3DS::showOverlay() { + _overlayVisible = true; + updateSize(); + updateScreen(); +} + +void OSystem_3DS::hideOverlay() { + _overlayVisible = false; + updateSize(); + updateScreen(); +} + +Graphics::PixelFormat OSystem_3DS::getOverlayFormat() const { + return _pfGameTexture; +} + +void OSystem_3DS::clearOverlay() { + _overlay.clear(); +} + +void OSystem_3DS::grabOverlay(void *buf, int pitch) { + for (int y = 0; y < getOverlayHeight(); ++y) { + memcpy(buf, _overlay.getBasePtr(0, y), pitch); + } +} + +void OSystem_3DS::copyRectToOverlay(const void *buf, int pitch, int x, + int y, int w, int h) { + _overlay.copyRectToSurface(buf, pitch, x, y, w, h); + _overlay.markDirty(); +} + +int16 OSystem_3DS::getOverlayHeight() { + return 240; +} + +int16 OSystem_3DS::getOverlayWidth() { + return 320; +} + +bool OSystem_3DS::showMouse(bool visible) { + _cursorVisible = visible; + flushCursor(); + return !visible; +} + +void OSystem_3DS::warpMouse(int x, int y) { + _cursorX = x; + _cursorY = y; + warning("x:%d y:%d", x, y); + // TODO: adjust for _cursorScalable ? + int offsetx = 0; + int offsety = 0; + x -= _cursorHotspotX; + y -= _cursorHotspotY; + if (!_overlayVisible) { + offsetx += config.screen == kScreenTop ? _gameTopX : _gameBottomX; + offsety += config.screen == kScreenTop ? _gameTopY : _gameBottomY; + } + float scalex = config.screen == kScreenTop ? (float)_gameTopTexture.actualWidth / _gameWidth : 1.f; + float scaley = config.screen == kScreenTop ? (float)_gameTopTexture.actualHeight / _gameHeight : 1.f; + _cursorTexture.setPosition(scalex * x + offsetx, + scaley * y + offsety); +} + +void OSystem_3DS::setCursorDelta(float deltaX, float deltaY) { + _cursorDeltaX = deltaX; + _cursorDeltaY = deltaY; +} + +void OSystem_3DS::setMouseCursor(const void *buf, uint w, uint h, + int hotspotX, int hotspotY, + uint32 keycolor, bool dontScale, + const Graphics::PixelFormat *format) { + _cursorScalable = !dontScale; + _cursorHotspotX = hotspotX; + _cursorHotspotY = hotspotY; + _cursorKeyColor = keycolor; + _pfCursor = !format ? Graphics::PixelFormat::createFormatCLUT8() : *format; + + if (w != _cursor.w || h != _cursor.h || _cursor.format != _pfCursor) { + _cursor.create(w, h, _pfCursor); + _cursorTexture.create(w, h, _pfGameTexture); + } + + _cursor.copyRectToSurface(buf, w, 0, 0, w, h); + flushCursor(); + + warpMouse(_cursorX, _cursorY); +} + +void OSystem_3DS::setCursorPalette(const byte *colors, uint start, uint num) { + assert(start + num <= 256); + memcpy(_cursorPalette + 3 * start, colors, 3 * num); + _cursorPaletteEnabled = true; + flushCursor(); +} + +void OSystem_3DS::flushCursor() { + if (_cursor.getPixels()) { + Graphics::Surface *converted = _cursor.convertTo(_pfGameTexture, _cursorPaletteEnabled ? _cursorPalette : _palette); + _cursorTexture.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h)); + _cursorTexture.markDirty(); + converted->free(); + delete converted; + + if (_pfCursor.bytesPerPixel == 1) { + uint* dest = (uint*) _cursorTexture.getPixels(); + byte* src = (byte*) _cursor.getPixels(); + for (int y = 0; y < _cursor.h; ++y) { + for (int x = 0; x < _cursor.w; ++x) { + if (*src++ == _cursorKeyColor) + *dest++ = 0; + else + dest++; + } + dest += _cursorTexture.w - _cursorTexture.actualWidth; + } + } + } +} + +} // namespace _3DS diff --git a/backends/platform/3ds/osystem.cpp b/backends/platform/3ds/osystem.cpp new file mode 100644 index 0000000000..f6278eb16b --- /dev/null +++ b/backends/platform/3ds/osystem.cpp @@ -0,0 +1,193 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_EXCEPTION_printf +#define FORBIDDEN_SYMBOL_EXCEPTION_time_h +#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h + +#include "osystem.h" + +#include "backends/saves/default/default-saves.h" +#include "backends/timer/default/default-timer.h" +#include "backends/events/default/default-events.h" +#include "audio/mixer_intern.h" +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/str.h" +#include "config.h" + +#include "backends/fs/posix/posix-fs-factory.h" +#include "backends/fs/posix/posix-fs.h" +#include <unistd.h> +#include <time.h> + +namespace _3DS { + +OSystem_3DS::OSystem_3DS(): + _focusDirty(true), + _focusRect(Common::Rect(1, 1)), + _focusPosX(0), + _focusPosY(0), + _focusTargetPosX(0), + _focusTargetPosY(0), + _focusStepPosX(0), + _focusStepPosY(0), + _focusScaleX(1.f), + _focusScaleY(1.f), + _focusTargetScaleX(1.f), + _focusTargetScaleY(1.f), + _focusStepScaleX(0.f), + _focusStepScaleY(0.f), + _focusClearTime(0), + _cursorPaletteEnabled(false), + _cursorVisible(false), + _cursorScalable(false), + _cursorX(0), + _cursorY(0), + _cursorHotspotX(0), + _cursorHotspotY(0), + _gameTopX(0), + _gameTopY(0), + _gameBottomX(0), + _gameBottomY(0), + _gameWidth(320), + _gameHeight(240), + _overlayVisible(false), + exiting(false), + sleeping(false) +{ + chdir("sdmc:/"); + _fsFactory = new POSIXFilesystemFactory(); + Posix::assureDirectoryExists("/3ds/scummvm/saves/"); +} + +OSystem_3DS::~OSystem_3DS() { + exiting = true; + destroyEvents(); + destroyAudio(); + destroyGraphics(); + + delete _timerManager; + _timerManager = 0; +} + +void OSystem_3DS::quit() { + printf("OSystem_3DS::quit()\n"); +} + +void OSystem_3DS::initBackend() { + loadConfig(); + ConfMan.registerDefault("fullscreen", true); + ConfMan.registerDefault("aspect_ratio", true); + if (!ConfMan.hasKey("vkeybd_pack_name")) + ConfMan.set("vkeybd_pack_name", "vkeybd_small"); + if (!ConfMan.hasKey("vkeybdpath")) + ConfMan.set("vkeybdpath", "/3ds/scummvm/kb"); + if (!ConfMan.hasKey("themepath")) + ConfMan.set("themepath", "/3ds/scummvm"); + if (!ConfMan.hasKey("gui_theme")) + ConfMan.set("gui_theme", "builtin"); + + _timerManager = new DefaultTimerManager(); + _savefileManager = new DefaultSaveFileManager("/3ds/scummvm/saves/"); + + initGraphics(); + initAudio(); + initEvents(); + EventsBaseBackend::initBackend(); +} + +void OSystem_3DS::updateConfig() { + if (_gameScreen.getPixels()) { + updateSize(); + warpMouse(_cursorX, _cursorY); + } +} + +Common::String OSystem_3DS::getDefaultConfigFileName() { + return "/3ds/scummvm/scummvm.ini"; +} + +uint32 OSystem_3DS::getMillis(bool skipRecord) { + return svcGetSystemTick() / TICKS_PER_MSEC; +} + +void OSystem_3DS::delayMillis(uint msecs) { + svcSleepThread(msecs * 1000000); +} + +void OSystem_3DS::getTimeAndDate(TimeDate& td) const { + time_t curTime = time(0); + struct tm t = *localtime(&curTime); + td.tm_sec = t.tm_sec; + td.tm_min = t.tm_min; + td.tm_hour = t.tm_hour; + td.tm_mday = t.tm_mday; + td.tm_mon = t.tm_mon; + td.tm_year = t.tm_year; + td.tm_wday = t.tm_wday; +} + +OSystem::MutexRef OSystem_3DS::createMutex() { + RecursiveLock *mutex = new RecursiveLock(); + RecursiveLock_Init(mutex); + return (OSystem::MutexRef) mutex; +} +void OSystem_3DS::lockMutex(MutexRef mutex) { + RecursiveLock_Lock((RecursiveLock*)mutex); +} +void OSystem_3DS::unlockMutex(MutexRef mutex) { + RecursiveLock_Unlock((RecursiveLock*)mutex); +} +void OSystem_3DS::deleteMutex(MutexRef mutex) { + delete (RecursiveLock*)mutex; +} + +Common::String OSystem_3DS::getSystemLanguage() const { + u8 langcode; + CFGU_GetSystemLanguage(&langcode); + switch (langcode) { + case CFG_LANGUAGE_JP: return "ja_JP"; + case CFG_LANGUAGE_EN: return "en_US"; + case CFG_LANGUAGE_FR: return "fr_FR"; + case CFG_LANGUAGE_DE: return "de_DE"; + case CFG_LANGUAGE_IT: return "it_IT"; + case CFG_LANGUAGE_ES: return "es_ES"; + case CFG_LANGUAGE_ZH: return "zh_CN"; + case CFG_LANGUAGE_KO: return "ko_KR"; + case CFG_LANGUAGE_NL: return "nl_NL"; + case CFG_LANGUAGE_PT: return "pt_BR"; + case CFG_LANGUAGE_RU: return "ru_RU"; + case CFG_LANGUAGE_TW: return "zh_HK"; + default: return "en_US"; + } +} + +void OSystem_3DS::fatalError() { + printf("FatalError!\n"); +} + +void OSystem_3DS::logMessage(LogMessageType::Type type, const char *message) { + printf("3DS log: %s\n", message); +} + +} // namespace _3DS diff --git a/backends/platform/3ds/osystem.h b/backends/platform/3ds/osystem.h new file mode 100644 index 0000000000..478085acba --- /dev/null +++ b/backends/platform/3ds/osystem.h @@ -0,0 +1,221 @@ +/* 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 PLATFORM_3DS_H +#define PLATFORM_3DS_H + +#include <citro3d.h> +#include "backends/mutex/mutex.h" +#include "backends/base-backend.h" +#include "graphics/palette.h" +#include "base/main.h" +#include "audio/mixer_intern.h" +#include "backends/graphics/graphics.h" +#include "backends/platform/3ds/sprite.h" +#include "common/rect.h" +#include "common/queue.h" + +#define TICKS_PER_MSEC 268123 + +namespace _3DS { + +enum { + GFX_LINEAR = 0, + GFX_NEAREST = 1 +}; + +enum InputMode { + MODE_HOVER, + MODE_DRAG, +}; + +static const OSystem::GraphicsMode s_graphicsModes[] = { + {"default", "Default Test", GFX_LINEAR}, + { 0, 0, 0 } +}; + +class OSystem_3DS : public EventsBaseBackend, public PaletteManager { +public: + OSystem_3DS(); + virtual ~OSystem_3DS(); + + volatile bool exiting; + volatile bool sleeping; + + virtual void initBackend(); + + virtual bool hasFeature(OSystem::Feature f); + virtual void setFeatureState(OSystem::Feature f, bool enable); + virtual bool getFeatureState(OSystem::Feature f); + + virtual bool pollEvent(Common::Event &event); + + virtual uint32 getMillis(bool skipRecord = false); + virtual void delayMillis(uint msecs); + virtual void getTimeAndDate(TimeDate &t) const; + + virtual MutexRef createMutex(); + virtual void lockMutex(MutexRef mutex); + virtual void unlockMutex(MutexRef mutex); + virtual void deleteMutex(MutexRef mutex); + + virtual void logMessage(LogMessageType::Type type, const char *message); + + virtual Audio::Mixer *getMixer(); + virtual PaletteManager *getPaletteManager() { return this; } + virtual Common::String getSystemLanguage() const; + virtual void fatalError(); + virtual void quit(); + + virtual Common::String getDefaultConfigFileName(); + + // Graphics + virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const; + int getDefaultGraphicsMode() const; + bool setGraphicsMode(int mode); + void resetGraphicsScale(); + int getGraphicsMode() const; + inline Graphics::PixelFormat getScreenFormat() const { return _pfGame; } + virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const; + void initSize(uint width, uint height, + const Graphics::PixelFormat *format = NULL); + virtual int getScreenChangeID() const { return 0; }; + + void beginGFXTransaction(); + OSystem::TransactionError endGFXTransaction(); + int16 getHeight(){ return _gameHeight; } + int16 getWidth(){ return _gameWidth; } + void setPalette(const byte *colors, uint start, uint num); + void grabPalette(byte *colors, uint start, uint num); + void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, + int h); + Graphics::Surface *lockScreen(); + void unlockScreen(); + void updateScreen(); + void setShakePos(int shakeOffset); + void setFocusRectangle(const Common::Rect &rect); + void clearFocusRectangle(); + void showOverlay(); + void hideOverlay(); + Graphics::PixelFormat getOverlayFormat() const; + void clearOverlay(); + void grabOverlay(void *buf, int pitch); + void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, + int h); + virtual int16 getOverlayHeight(); + virtual int16 getOverlayWidth(); + virtual void displayMessageOnOSD(const char *msg); + + bool showMouse(bool visible); + void warpMouse(int x, int y); + void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, + int hotspotY, uint32 keycolor, bool dontScale = false, + const Graphics::PixelFormat *format = NULL); + void setCursorPalette(const byte *colors, uint start, uint num); + + // Transform point from touchscreen coords into gamescreen coords + void transformPoint(touchPosition &point); + + void setCursorDelta(float deltaX, float deltaY); + + void updateFocus(); + void updateConfig(); + void updateSize(); + +private: + void initGraphics(); + void destroyGraphics(); + void initAudio(); + void destroyAudio(); + void initEvents(); + void destroyEvents(); + + void flushGameScreen(); + void flushCursor(); + +protected: + Audio::MixerImpl *_mixer; + +private: + u16 _gameWidth, _gameHeight; + u16 _gameTopX, _gameTopY; + u16 _gameBottomX, _gameBottomY; + + // Audio + Thread audioThread; + + // Graphics + Graphics::PixelFormat _pfGame; + Graphics::PixelFormat _pfGameTexture; + Graphics::PixelFormat _pfCursor; + byte _palette[3 * 256]; + byte _cursorPalette[3 * 256]; + + Graphics::Surface _gameScreen; + Sprite _gameTopTexture; + Sprite _gameBottomTexture; + Sprite _overlay; + + int _screenShakeOffset; + bool _overlayVisible; + + DVLB_s *_dvlb; + shaderProgram_s _program; + int _projectionLocation; + int _modelviewLocation; + C3D_Mtx _projectionTop; + C3D_Mtx _projectionBottom; + C3D_RenderTarget* _renderTargetTop; + C3D_RenderTarget* _renderTargetBottom; + + // Focus + Common::Rect _focusRect; + bool _focusDirty; + C3D_Mtx _focusMatrix; + int _focusPosX, _focusPosY; + int _focusTargetPosX, _focusTargetPosY; + float _focusStepPosX, _focusStepPosY; + float _focusScaleX, _focusScaleY; + float _focusTargetScaleX, _focusTargetScaleY; + float _focusStepScaleX, _focusStepScaleY; + uint32 _focusClearTime; + + // Events + Thread _eventThread; + Thread _timerThread; + Common::Queue<Common::Event> _eventQueue; + + // Cursor + Graphics::Surface _cursor; + Sprite _cursorTexture; + bool _cursorPaletteEnabled; + bool _cursorVisible; + bool _cursorScalable; + float _cursorX, _cursorY; + float _cursorDeltaX, _cursorDeltaY; + int _cursorHotspotX, _cursorHotspotY; + uint32 _cursorKeyColor; +}; + +} // namespace _3DS + +#endif diff --git a/backends/platform/3ds/shader.v.pica b/backends/platform/3ds/shader.v.pica new file mode 100644 index 0000000000..2d18985622 --- /dev/null +++ b/backends/platform/3ds/shader.v.pica @@ -0,0 +1,59 @@ +;* 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. +;* + +; Uniforms +.fvec projection[4], modelView[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.1) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones + +; Outputs +.out outpos position +.out outtex texcoord0 + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias intex v1 + +.proc main + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, ones + + ; r1 = modelView * inpos + dp4 r1.x, modelView[0], r0 + dp4 r1.y, modelView[1], r0 + dp4 r1.z, modelView[2], r0 + dp4 r1.w, modelView[3], r0 + + ; outpos = projection * r1 + dp4 outpos.x, projection[0], r1 + dp4 outpos.y, projection[1], r1 + dp4 outpos.z, projection[2], r1 + dp4 outpos.w, projection[3], r1 + + mov outtex, intex + + end +.end + diff --git a/backends/platform/3ds/sprite.cpp b/backends/platform/3ds/sprite.cpp new file mode 100644 index 0000000000..f97c611473 --- /dev/null +++ b/backends/platform/3ds/sprite.cpp @@ -0,0 +1,144 @@ +/* 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 "backends/platform/3ds/sprite.h" +#include "common/util.h" +#include <3ds.h> + +static uint nextHigher2(uint v) { + if (v == 0) + return 1; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ++v; +} + +Sprite::Sprite() + : dirtyPixels(true) + , dirtyMatrix(true) + , actualWidth(0) + , actualHeight(0) + , posX(0) + , posY(0) + , scaleX(1.f) + , scaleY(1.f) +{ + Mtx_Identity(&modelview); + + vertices = (vertex *)linearAlloc(sizeof(vertex) * 4); +} + +Sprite::~Sprite() { + // +} + +void Sprite::create(uint16 width, uint16 height, const Graphics::PixelFormat &f) { + free(); + + actualWidth = width; + actualHeight = height; + format = f; + w = MAX(nextHigher2(width), 64u); + h = MAX(nextHigher2(height), 64u); + pitch = w * format.bytesPerPixel; + dirtyPixels = true; + + if (width && height) { + pixels = linearAlloc(h * pitch); + C3D_TexInit(&texture, w, h, GPU_RGBA8); + C3D_TexSetFilter(&texture, GPU_LINEAR, GPU_LINEAR); + assert(pixels && texture.data); + clear(); + } + + float x = 0.f, y = 0.f; + float u = (float)width/w; + float v = (float)height/h; + vertex tmp[4] = { + {{x, y, 0.5f}, {0, 0}}, + {{x+width, y, 0.5f}, {u, 0}}, + {{x, y+height, 0.5f}, {0, v}}, + {{x+width, y+height, 0.5f}, {u, v}}, + }; + memcpy(vertices, tmp, sizeof(vertex) * 4); +} + + +void Sprite::free() { + linearFree(vertices); + linearFree(pixels); + C3D_TexDelete(&texture); + pixels = 0; + w = h = pitch = 0; + actualWidth = actualHeight = 0; + format = Graphics::PixelFormat(); +} + +void Sprite::convertToInPlace(const Graphics::PixelFormat &dstFormat, const byte *palette) { + // +} + +void Sprite::render() { + if (dirtyPixels) { + dirtyPixels = false; + GSPGPU_FlushDataCache(pixels, w * h * format.bytesPerPixel); + C3D_SafeDisplayTransfer((u32*)pixels, GX_BUFFER_DIM(w, h), (u32*)texture.data, GX_BUFFER_DIM(w, h), TEXTURE_TRANSFER_FLAGS); + gspWaitForPPF(); + } + C3D_TexBind(0, &texture); + + C3D_BufInfo *bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, vertices, sizeof(vertex), 2, 0x10); + C3D_DrawArrays(GPU_TRIANGLE_STRIP, 0, 4); +} + +void Sprite::clear(uint32 color) { + dirtyPixels = true; + memset(pixels, color, w * h * format.bytesPerPixel); +} + +void Sprite::setScale (float x, float y) { + scaleX = x; + scaleY = y; + dirtyMatrix = true; +} + +void Sprite::setPosition(int x, int y) { + posX = x; + posY = y; + dirtyMatrix = true; +} + +C3D_Mtx* Sprite::getMatrix() { + if (dirtyMatrix) { + dirtyMatrix = false; + Mtx_Identity(&modelview); + Mtx_Scale(&modelview, scaleX, scaleY, 1.f); + Mtx_Translate(&modelview, posX, posY, 0); + } + return &modelview; +} diff --git a/backends/platform/3ds/sprite.h b/backends/platform/3ds/sprite.h new file mode 100644 index 0000000000..6d88ae4ce1 --- /dev/null +++ b/backends/platform/3ds/sprite.h @@ -0,0 +1,71 @@ +/* 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 GRAPHICS_SPRITE_3DS_H +#define GRAPHICS_SPRITE_3DS_H + +#include <citro3d.h> +#include "graphics/surface.h" + +#define TEXTURE_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) | \ + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +typedef struct { + float position[3]; + float texcoord[2]; +} vertex; + +class Sprite : public Graphics::Surface { +public: + Sprite(); + ~Sprite(); + void create(uint16 width, uint16 height, const Graphics::PixelFormat &format); + void free(); + void convertToInPlace(const Graphics::PixelFormat &dstFormat, const byte *palette = 0); + void render(); + void clear(uint32 color = 0); + void markDirty(){ dirtyPixels = true; } + + void setPosition(int x, int y); + void setScale(float x, float y); + float getScaleX(){ return scaleX; } + float getScaleY(){ return scaleY; } + C3D_Mtx* getMatrix(); + + uint16 actualWidth; + uint16 actualHeight; + +private: + bool dirtyPixels; + bool dirtyMatrix; + C3D_Mtx modelview; + C3D_Tex texture; + vertex* vertices; + int posX; + int posY; + float scaleX; + float scaleY; +}; + +#endif diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index 3ff1b939ef..798772cc24 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -396,12 +396,6 @@ void OSystem_Android::initBackend() { EventsBaseBackend::initBackend(); } -void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { - ENTER(); - - JNI::getPluginDirectories(dirs); -} - bool OSystem_Android::hasFeature(Feature f) { return (f == kFeatureFullscreenMode || f == kFeatureAspectRatioCorrection || @@ -600,10 +594,4 @@ Common::String OSystem_Android::getSystemProperty(const char *name) const { return Common::String(value, len); } -#ifdef DYNAMIC_MODULES -void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const { - ((OSystem_Android *)g_system)->addPluginDirectories(dirs); -} -#endif - #endif diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index 28016f5e3e..ade84dd42d 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -96,13 +96,6 @@ extern void checkGlError(const char *expr, const char *file, int line); #define GLTHREADCHECK do { } while (false) #endif -#ifdef DYNAMIC_MODULES -class AndroidPluginProvider : public POSIXPluginProvider { -protected: - virtual void addCustomDirectories(Common::FSList &dirs) const; -}; -#endif - class OSystem_Android : public EventsBaseBackend, public PaletteManager { private: // passed from the dark side @@ -177,7 +170,6 @@ public: virtual ~OSystem_Android(); virtual void initBackend(); - void addPluginDirectories(Common::FSList &dirs) const; void enableZoning(bool enable) { _enable_zoning = enable; } virtual bool hasFeature(Feature f); diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk index f9a2bc9813..4a29526941 100644 --- a/backends/platform/android/android.mk +++ b/backends/platform/android/android.mk @@ -1,11 +1,11 @@ # Android specific build targets # These must be incremented for each market upload -ANDROID_VERSIONCODE = 6 +ANDROID_VERSIONCODE = 16 -ANDROID_TARGET_VERSION = 14 +ANDROID_TARGET_VERSION = 23 -NDK_BUILD = $(ANDROID_NDK)/ndk-build +NDK_BUILD = $(ANDROID_NDK)/ndk-build APP_ABI=$(ABI) SDK_ANDROID = $(ANDROID_SDK)/tools/android PATH_DIST = $(srcdir)/dists/android @@ -18,10 +18,9 @@ RESOURCES = \ $(PATH_BUILD_RES)/values/strings.xml \ $(PATH_BUILD_RES)/values-television/margins.xml \ $(PATH_BUILD_RES)/layout/main.xml \ - $(PATH_BUILD_RES)/layout/splash.xml \ - $(PATH_BUILD_RES)/drawable/gradient.xml \ $(PATH_BUILD_RES)/drawable/scummvm.png \ $(PATH_BUILD_RES)/drawable/scummvm_big.png \ + $(PATH_BUILD_RES)/drawable-xhdpi/leanback_icon.png \ $(PATH_BUILD_RES)/drawable-xhdpi/ouya_icon.png DIST_ANDROID_MK = $(PATH_DIST)/jni/Android.mk @@ -30,14 +29,13 @@ DIST_BUILD_XML = $(PATH_DIST)/custom_rules.xml PATH_BUILD = ./build.tmp PATH_BUILD_ASSETS = $(PATH_BUILD)/assets PATH_BUILD_RES = $(PATH_BUILD)/res -PATH_BUILD_LIBSCUMMVM = $(PATH_BUILD)/mylib/armeabi/libscummvm.so +PATH_BUILD_LIBSCUMMVM = $(PATH_BUILD)/lib/$(ABI)/libscummvm.so FILE_MANIFEST_SRC = $(srcdir)/dists/android/AndroidManifest.xml FILE_MANIFEST = $(PATH_BUILD)/AndroidManifest.xml APK_MAIN = ScummVM-debug.apk APK_MAIN_RELEASE = ScummVM-release-unsigned.apk -APK_PLUGINS = $(patsubst plugins/lib%.so, scummvm-engine-%.apk, $(PLUGINS)) $(FILE_MANIFEST): $(FILE_MANIFEST_SRC) | $(PATH_BUILD) @$(MKDIR) -p $(@D) @@ -91,18 +89,18 @@ androidrelease: $(APK_MAIN_RELEASE) androidtestmain: $(APK_MAIN) $(ADB) install -r $(APK_MAIN) - $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker + $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.ScummVMActivity -androidtest: $(APK_MAIN) $(APK_PLUGINS) +androidtest: $(APK_MAIN) @set -e; for apk in $^; do \ $(ADB) install -r $$apk; \ done - $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker + $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.ScummVMActivity # used by buildbot! androiddistdebug: all $(MKDIR) debug - $(CP) $(APK_MAIN) $(APK_PLUGINS) debug/ + $(CP) $(APK_MAIN) debug/ for i in $(DIST_FILES_DOCS) $(PORT_DISTFILES); do \ sed 's/$$/\r/' < $$i > debug/`basename $$i`.txt; \ done diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp index 8039981a92..b146945a01 100644 --- a/backends/platform/android/events.cpp +++ b/backends/platform/android/events.cpp @@ -101,7 +101,9 @@ enum { JKEYCODE_MEDIA_NEXT = 87, JKEYCODE_MEDIA_PREVIOUS = 88, JKEYCODE_MEDIA_REWIND = 89, - JKEYCODE_MEDIA_FAST_FORWARD = 90 + JKEYCODE_MEDIA_FAST_FORWARD = 90, + JKEYCODE_MEDIA_PLAY = 126, + JKEYCODE_MEDIA_PAUSE = 127 }; // five-way navigation control @@ -380,6 +382,19 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3, return; + case JKEYCODE_MEDIA_PAUSE: + case JKEYCODE_MEDIA_PLAY: + case JKEYCODE_MEDIA_PLAY_PAUSE: + if (arg1 == JACTION_DOWN) { + e.type = Common::EVENT_MAINMENU; + + lockMutex(_event_queue_lock); + _event_queue.push(e); + unlockMutex(_event_queue_lock); + } + + return; + case JKEYCODE_CAMERA: case JKEYCODE_SEARCH: if (arg1 == JACTION_DOWN) @@ -888,6 +903,10 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3, e.kbd.ascii = Common::ASCII_ESCAPE; break; + case JKEYCODE_BUTTON_Y: + e.type = Common::EVENT_MAINMENU; + break; + default: LOGW("unmapped gamepad key: %d", arg2); return; diff --git a/backends/platform/android/gfx.cpp b/backends/platform/android/gfx.cpp index d7713f99d8..f847296892 100644 --- a/backends/platform/android/gfx.cpp +++ b/backends/platform/android/gfx.cpp @@ -469,7 +469,7 @@ void OSystem_Android::updateScreen() { GLCALL(glTranslatex(0, -_shake_offset << 16, 0)); } -// TODO this doesnt work on those sucky drivers, do it differently +// TODO this doesn't work on those sucky drivers, do it differently // if (_show_overlay) // GLCALL(glColor4ub(0x9f, 0x9f, 0x9f, 0x9f)); diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp index 764c84ce1c..256ae09ef8 100644 --- a/backends/platform/android/jni.cpp +++ b/backends/platform/android/jni.cpp @@ -76,10 +76,11 @@ bool JNI::_ready_for_events = 0; jmethodID JNI::_MID_getDPI = 0; jmethodID JNI::_MID_displayMessageOnOSD = 0; +jmethodID JNI::_MID_openUrl = 0; +jmethodID JNI::_MID_isConnectionLimited = 0; jmethodID JNI::_MID_setWindowCaption = 0; jmethodID JNI::_MID_showVirtualKeyboard = 0; jmethodID JNI::_MID_getSysArchives = 0; -jmethodID JNI::_MID_getPluginDirectories = 0; jmethodID JNI::_MID_initSurface = 0; jmethodID JNI::_MID_deinitSurface = 0; @@ -233,6 +234,41 @@ void JNI::displayMessageOnOSD(const char *msg) { env->DeleteLocalRef(java_msg); } +bool JNI::openUrl(const char *url) { + bool success = true; + JNIEnv *env = JNI::getEnv(); + jstring javaUrl = env->NewStringUTF(url); + + env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl); + + if (env->ExceptionCheck()) { + LOGE("Failed to open URL"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + success = false; + } + + env->DeleteLocalRef(javaUrl); + return success; +} + +bool JNI::isConnectionLimited() { + bool limited = false; + JNIEnv *env = JNI::getEnv(); + limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited); + + if (env->ExceptionCheck()) { + LOGE("Failed to check whether connection's limited"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + limited = true; + } + + return limited; +} + void JNI::setWindowCaption(const char *caption) { JNIEnv *env = JNI::getEnv(); jstring java_caption = env->NewStringUTF(caption); @@ -293,46 +329,6 @@ void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { } } -void JNI::getPluginDirectories(Common::FSList &dirs) { - JNIEnv *env = JNI::getEnv(); - - jobjectArray array = - (jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories); - - if (env->ExceptionCheck()) { - LOGE("Error finding plugin directories"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return; - } - - jsize size = env->GetArrayLength(array); - for (jsize i = 0; i < size; ++i) { - jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); - - if (path_obj == 0) - continue; - - const char *path = env->GetStringUTFChars(path_obj, 0); - - if (path == 0) { - LOGE("Error getting string characters from plugin directory"); - - env->ExceptionClear(); - env->DeleteLocalRef(path_obj); - - continue; - } - - dirs.push_back(Common::FSNode(path)); - - env->ReleaseStringUTFChars(path_obj, path); - env->DeleteLocalRef(path_obj); - } -} - bool JNI::initSurface() { JNIEnv *env = JNI::getEnv(); @@ -452,9 +448,10 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); FIND_METHOD(, getDPI, "([F)V"); FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); + FIND_METHOD(, openUrl, "(Ljava/lang/String;)V"); + FIND_METHOD(, isConnectionLimited, "()Z"); FIND_METHOD(, showVirtualKeyboard, "(Z)V"); FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); - FIND_METHOD(, getPluginDirectories, "()[Ljava/lang/String;"); FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); FIND_METHOD(, deinitSurface, "()V"); @@ -543,10 +540,6 @@ jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { env->DeleteLocalRef(arg); } -#ifdef DYNAMIC_MODULES - PluginManager::instance().addPluginProvider(new AndroidPluginProvider()); -#endif - LOGI("Entering scummvm_main with %d args", argc); res = scummvm_main(argc, argv); diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h index 326869b1ee..0798db448a 100644 --- a/backends/platform/android/jni.h +++ b/backends/platform/android/jni.h @@ -55,10 +55,11 @@ public: static void setReadyForEvents(bool ready); - static void getPluginDirectories(Common::FSList &dirs); static void setWindowCaption(const char *caption); static void getDPI(float *values); static void displayMessageOnOSD(const char *msg); + static bool openUrl(const char *url); + static bool isConnectionLimited(); static void showVirtualKeyboard(bool enable); static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); @@ -90,10 +91,11 @@ private: static jmethodID _MID_getDPI; static jmethodID _MID_displayMessageOnOSD; + static jmethodID _MID_openUrl; + static jmethodID _MID_isConnectionLimited; static jmethodID _MID_setWindowCaption; static jmethodID _MID_showVirtualKeyboard; static jmethodID _MID_getSysArchives; - static jmethodID _MID_getPluginDirectories; static jmethodID _MID_initSurface; static jmethodID _MID_deinitSurface; diff --git a/backends/platform/android/org/scummvm/scummvm/PluginProvider.java b/backends/platform/android/org/scummvm/scummvm/PluginProvider.java deleted file mode 100644 index e27e8d41a8..0000000000 --- a/backends/platform/android/org/scummvm/scummvm/PluginProvider.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.scummvm.scummvm; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import java.util.ArrayList; - -public class PluginProvider extends BroadcastReceiver { - private final static String LOG_TAG = "ScummVM"; - - public final static String META_UNPACK_LIB = - "org.scummvm.scummvm.meta.UNPACK_LIB"; - - public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) - return; - - Bundle extras = getResultExtras(true); - - final ActivityInfo info; - final PackageInfo pinfo; - try { - info = context.getPackageManager() - .getReceiverInfo(new ComponentName(context, this.getClass()), - PackageManager.GET_META_DATA); - pinfo = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Error finding my own info?", e); - return; - } - - String host_version = extras.getString(ScummVMApplication.EXTRA_VERSION); - if (!pinfo.versionName.equals(host_version)) { - Log.e(LOG_TAG, "Plugin version " + pinfo.versionName + " is not equal to ScummVM version " + host_version); - return; - } - - String mylib = info.metaData.getString(META_UNPACK_LIB); - if (mylib != null) { - ArrayList<String> all_libs = - extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS); - all_libs.add(new Uri.Builder() - .scheme("plugin") - .authority(context.getPackageName()) - .path(mylib) - .toString()); - - extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS, - all_libs); - } - - setResultExtras(extras); - } -} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java index 5047502e61..47dcb32b22 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java @@ -53,8 +53,9 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { // Callbacks from C++ peer instance abstract protected void getDPI(float[] values); abstract protected void displayMessageOnOSD(String msg); + abstract protected void openUrl(String url); + abstract protected boolean isConnectionLimited(); abstract protected void setWindowCaption(String caption); - abstract protected String[] getPluginDirectories(); abstract protected void showVirtualKeyboard(boolean enable); abstract protected String[] getSysArchives(); @@ -444,10 +445,6 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { } } - File cache_dir = ScummVMApplication.getLastCacheDir(); - String libname = System.mapLibraryName("scummvm"); - File libpath = new File(cache_dir, libname); - - System.load(libpath.getPath()); + System.loadLibrary("scummvm"); } } diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java index f4eb7ddd0b..225496ca0d 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -2,9 +2,13 @@ package org.scummvm.scummvm; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.media.AudioManager; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Bundle; import android.os.Environment; @@ -75,6 +79,21 @@ public class ScummVMActivity extends Activity { } @Override + protected void openUrl(String url) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } + + @Override + protected boolean isConnectionLimited() { + WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE); + if (wifiMgr != null && wifiMgr.isWifiEnabled()) { + WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); + return (wifiInfo == null || wifiInfo.getNetworkId() == -1); //WiFi is on, but it's not connected to any network + } + return true; + } + + @Override protected void setWindowCaption(final String caption) { runOnUiThread(new Runnable() { public void run() { @@ -84,13 +103,6 @@ public class ScummVMActivity extends Activity { } @Override - protected String[] getPluginDirectories() { - String[] dirs = new String[1]; - dirs[0] = ScummVMApplication.getLastCacheDir().getPath(); - return dirs; - } - - @Override protected void showVirtualKeyboard(final boolean enable) { runOnUiThread(new Runnable() { public void run() { diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java b/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java deleted file mode 100644 index 0adc166222..0000000000 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.scummvm.scummvm; - -import android.app.Application; - -import java.io.File; - -public class ScummVMApplication extends Application { - public final static String ACTION_PLUGIN_QUERY = "org.scummvm.scummvm.action.PLUGIN_QUERY"; - public final static String EXTRA_UNPACK_LIBS = "org.scummvm.scummvm.extra.UNPACK_LIBS"; - public final static String EXTRA_VERSION = "org.scummvm.scummvm.extra.VERSION"; - - private static File _cache_dir; - - @Override - public void onCreate() { - super.onCreate(); - - // This is still on /data :( - _cache_dir = getCacheDir(); - // This is mounted noexec :( - //cache_dir = new File(Environment.getExternalStorageDirectory(), - // "/.ScummVM.tmp"); - // This is owned by download manager and requires special - // permissions to access :( - //cache_dir = Environment.getDownloadCacheDirectory(); - } - - public static File getLastCacheDir() { - return _cache_dir; - } -} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java b/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java index 32c65d3395..e81000d8b1 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java @@ -119,6 +119,8 @@ public class ScummVMEvents implements case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_SEARCH: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: break; default: diff --git a/backends/platform/android/org/scummvm/scummvm/Unpacker.java b/backends/platform/android/org/scummvm/scummvm/Unpacker.java deleted file mode 100644 index da76ceb5e5..0000000000 --- a/backends/platform/android/org/scummvm/scummvm/Unpacker.java +++ /dev/null @@ -1,388 +0,0 @@ -package org.scummvm.scummvm; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import android.widget.ProgressBar; - -import java.io.IOException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.zip.ZipFile; -import java.util.zip.ZipEntry; - -public class Unpacker extends Activity { - protected final static String LOG_TAG = "ScummVM"; - // TODO don't hardcode this - private final static boolean PLUGINS_ENABLED = false; - private final static String META_NEXT_ACTIVITY = - "org.scummvm.unpacker.nextActivity"; - private ProgressBar mProgress; - private File mUnpackDest; // location to unpack into - private AsyncTask<String, Integer, Void> mUnpacker; - private final static int REQUEST_MARKET = 1; - - // Android 3.1+ only - public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; - - private static class UnpackJob { - public ZipFile zipfile; - public Set<String> paths; - - public UnpackJob(ZipFile zipfile, Set<String> paths) { - this.zipfile = zipfile; - this.paths = paths; - } - - public long UnpackSize() { - long size = 0; - for (String path: paths) { - ZipEntry entry = zipfile.getEntry(path); - if (entry != null) size += entry.getSize(); - } - return size; - } - } - - private class UnpackTask extends AsyncTask<String, Integer, Void> { - @Override - protected void onProgressUpdate(Integer... progress) { - mProgress.setIndeterminate(false); - mProgress.setMax(progress[1]); - mProgress.setProgress(progress[0]); - mProgress.postInvalidate(); - } - - @Override - protected void onPostExecute(Void result) { - Bundle md = getMetaData(); - String nextActivity = md.getString(META_NEXT_ACTIVITY); - if (nextActivity != null) { - final ComponentName cn = - ComponentName.unflattenFromString(nextActivity); - if (cn != null) { - final Intent origIntent = getIntent(); - Intent intent = new Intent(); - intent.setComponent(cn); - if (origIntent.getExtras() != null) - intent.putExtras(origIntent.getExtras()); - intent.putExtra(Intent.EXTRA_INTENT, origIntent); - intent.setDataAndType(origIntent.getData(), - origIntent.getType()); - //intent.fillIn(getIntent(), 0); - intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - Log.i(LOG_TAG, - "Starting next activity with intent " + intent); - startActivity(intent); - } else { - Log.w(LOG_TAG, - "Unable to extract a component name from " + nextActivity); - } - } - - finish(); - } - - @Override - protected Void doInBackground(String... all_libs) { - // This will contain all unpack jobs - Map<String, UnpackJob> unpack_jobs = - new HashMap<String, UnpackJob>(all_libs.length); - - // This will contain all unpack filenames (so we can - // detect stale files in the unpack directory) - Set<String> all_files = new HashSet<String>(all_libs.length); - - for (String lib: all_libs) { - final Uri uri = Uri.parse(lib); - final String pkg = uri.getAuthority(); - final String path = uri.getPath().substring(1); // skip first / - - all_files.add(new File(path).getName()); - - UnpackJob job = unpack_jobs.get(pkg); - if (job == null) { - try { - // getPackageResourcePath is hidden in Context, - // but exposed in ContextWrapper... - ContextWrapper context = - new ContextWrapper(createPackageContext(pkg, 0)); - ZipFile zipfile = - new ZipFile(context.getPackageResourcePath()); - job = new UnpackJob(zipfile, new HashSet<String>(1)); - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Package " + pkg + - " not found", e); - continue; - } catch (IOException e) { - // FIXME: show some sort of GUI error dialog - Log.e(LOG_TAG, - "Error opening ZIP for package " + pkg, e); - continue; - } - unpack_jobs.put(pkg, job); - } - job.paths.add(path); - } - - // Delete stale filenames from mUnpackDest - for (File file: mUnpackDest.listFiles()) { - if (!all_files.contains(file.getName())) { - Log.i(LOG_TAG, - "Deleting stale cached file " + file); - file.delete(); - } - } - - int total_size = 0; - for (UnpackJob job: unpack_jobs.values()) - total_size += job.UnpackSize(); - - publishProgress(0, total_size); - - mUnpackDest.mkdirs(); - - int progress = 0; - - for (UnpackJob job: unpack_jobs.values()) { - try { - ZipFile zipfile = job.zipfile; - for (String path: job.paths) { - ZipEntry zipentry = zipfile.getEntry(path); - if (zipentry == null) - throw new FileNotFoundException( - "Couldn't find " + path + " in zip"); - File dest = new File(mUnpackDest, new File(path).getName()); - if (dest.exists() && - dest.lastModified() == zipentry.getTime() && - dest.length() == zipentry.getSize()) { - // Already unpacked - progress += zipentry.getSize(); - } else { - if (dest.exists()) - Log.d(LOG_TAG, - "Replacing " + dest.getPath() + - " old.mtime=" + dest.lastModified() + - " new.mtime=" + zipentry.getTime() + - " old.size=" + dest.length() + - " new.size=" + zipentry.getSize()); - else - Log.i(LOG_TAG, - "Extracting " + zipentry.getName() + - " from " + zipfile.getName() + - " to " + dest.getPath()); - - long next_update = progress; - - InputStream in = zipfile.getInputStream(zipentry); - OutputStream out = new FileOutputStream(dest); - int len; - byte[] buffer = new byte[4096]; - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - progress += len; - if (progress >= next_update) { - publishProgress(progress, total_size); - // Arbitrary limit of 2% update steps - next_update += total_size / 50; - } - } - - in.close(); - out.close(); - dest.setLastModified(zipentry.getTime()); - } - publishProgress(progress, total_size); - } - - zipfile.close(); - } catch (IOException e) { - // FIXME: show some sort of GUI error dialog - Log.e(LOG_TAG, "Error unpacking plugin", e); - } - } - - if (progress != total_size) - Log.d(LOG_TAG, "Ended with progress " + progress + - " != total size " + total_size); - - setResult(RESULT_OK); - - return null; - } - } - - private class PluginBroadcastReciever extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.getAction() - .equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) { - Log.e(LOG_TAG, - "Received unexpected action " + intent.getAction()); - return; - } - - Bundle extras = getResultExtras(false); - if (extras == null) { - // Nothing for us to do. - Unpacker.this.setResult(RESULT_OK); - finish(); - } - - ArrayList<String> unpack_libs = - extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS); - - if (unpack_libs != null && !unpack_libs.isEmpty()) { - final String[] libs = - unpack_libs.toArray(new String[unpack_libs.size()]); - mUnpacker = new UnpackTask().execute(libs); - } - } - } - - private void initPlugins() { - Bundle extras = new Bundle(1); - - ArrayList<String> unpack_libs = new ArrayList<String>(1); - // This is the common ScummVM code (not really a "plugin" as such) - unpack_libs.add(new Uri.Builder() - .scheme("plugin") - .authority(getPackageName()) - .path("mylib/armeabi/libscummvm.so") - .toString()); - extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS, - unpack_libs); - - final PackageInfo info; - try { - info = getPackageManager().getPackageInfo(getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Error finding my own info?", e); - return; - } - extras.putString(ScummVMApplication.EXTRA_VERSION, info.versionName); - - Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY); - // Android 3.1 defaults to FLAG_EXCLUDE_STOPPED_PACKAGES, and since - // none of our plugins will ever be running, that is not helpful - intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); - sendOrderedBroadcast(intent, Manifest.permission.SCUMMVM_PLUGIN, - new PluginBroadcastReciever(), - null, RESULT_OK, null, extras); - } - - @Override - public void onCreate(Bundle b) { - super.onCreate(b); - - mUnpackDest = ScummVMApplication.getLastCacheDir(); - - setContentView(R.layout.splash); - mProgress = (ProgressBar)findViewById(R.id.progress); - - setResult(RESULT_CANCELED); - - tryUnpack(); - } - - private void tryUnpack() { - Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY); - List<ResolveInfo> plugins = getPackageManager() - .queryBroadcastReceivers(intent, 0); - if (PLUGINS_ENABLED && plugins.isEmpty()) { - // No plugins installed - AlertDialog.Builder alert = new AlertDialog.Builder(this) - .setTitle(R.string.no_plugins_title) - .setMessage(R.string.no_plugins_found) - .setIcon(android.R.drawable.ic_dialog_alert) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - finish(); - } - }) - .setNegativeButton(R.string.quit, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - - final Uri uri = Uri.parse("market://search?q=ScummVM plugin"); - final Intent market_intent = new Intent(Intent.ACTION_VIEW, uri); - if (getPackageManager().resolveActivity(market_intent, 0) != null) { - alert.setPositiveButton(R.string.to_market, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - try { - startActivityForResult(market_intent, - REQUEST_MARKET); - } catch (ActivityNotFoundException e) { - Log.e(LOG_TAG, - "Error starting market", e); - } - } - }); - } - - alert.show(); - - } else { - // Already have at least one plugin installed - initPlugins(); - } - } - - @Override - public void onStop() { - if (mUnpacker != null) - mUnpacker.cancel(true); - super.onStop(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - switch (requestCode) { - case REQUEST_MARKET: - if (resultCode != RESULT_OK) - Log.w(LOG_TAG, "Market returned " + resultCode); - tryUnpack(); - break; - } - } - - private Bundle getMetaData() { - try { - ActivityInfo ai = getPackageManager() - .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - return ai.metaData; - } catch (PackageManager.NameNotFoundException e) { - Log.w(LOG_TAG, "Unable to find my own meta-data", e); - return new Bundle(); - } - } -} diff --git a/backends/platform/androidsdl/androidsdl-main.cpp b/backends/platform/androidsdl/androidsdl-main.cpp new file mode 100644 index 0000000000..26a73579c0 --- /dev/null +++ b/backends/platform/androidsdl/androidsdl-main.cpp @@ -0,0 +1,42 @@ +/* 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 "backends/platform/androidsdl/androidsdl-sdl.h" +#include "base/main.h" + +int main(int argc, char *argv[]) { + + // Create our OSystem instance + g_system = new OSystem_ANDROIDSDL(); + assert(g_system); + + // Pre initialize the backend + ((OSystem_POSIX *)g_system)->init(); + + // Invoke the actual ScummVM main entry point: + int res = scummvm_main(argc, argv); + + // Free OSystem + delete (OSystem_ANDROIDSDL *)g_system; + + return res; +} diff --git a/backends/platform/androidsdl/androidsdl-sdl.cpp b/backends/platform/androidsdl/androidsdl-sdl.cpp new file mode 100644 index 0000000000..5e0eaa0408 --- /dev/null +++ b/backends/platform/androidsdl/androidsdl-sdl.cpp @@ -0,0 +1,37 @@ +/* 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 "backends/platform/androidsdl/androidsdl-sdl.h" +#include "backends/events/androidsdl/androidsdl-events.h" +#include "backends/graphics/androidsdl/androidsdl-graphics.h" + +void OSystem_ANDROIDSDL::initBackend() { + // Create the backend custom managers + if (_eventSource == 0) + _eventSource = new AndroidSdlEventSource(); + + if (_graphicsManager == 0) + _graphicsManager = new AndroidSdlGraphicsManager(_eventSource, _window); + + // Call parent implementation of this method + OSystem_POSIX::initBackend(); +} diff --git a/backends/platform/androidsdl/androidsdl-sdl.h b/backends/platform/androidsdl/androidsdl-sdl.h new file mode 100644 index 0000000000..6ebe5022eb --- /dev/null +++ b/backends/platform/androidsdl/androidsdl-sdl.h @@ -0,0 +1,38 @@ +/* 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 PLATFORM_SDL_ANDROIDSDL_H +#define PLATFORM_SDL_ANDROIDSDL_H + +#include "backends/platform/sdl/posix/posix.h" + +class OSystem_ANDROIDSDL : public OSystem_POSIX { +public: + virtual void initBackend(); + +#ifdef ENABLE_KEYMAPPER + // FIXME: This just calls parent methods, is it needed? + virtual Common::HardwareInputSet *getHardwareInputSet(); +#endif +}; + +#endif diff --git a/backends/platform/androidsdl/androidsdl.mk b/backends/platform/androidsdl/androidsdl.mk new file mode 100644 index 0000000000..1defb81b97 --- /dev/null +++ b/backends/platform/androidsdl/androidsdl.mk @@ -0,0 +1,11 @@ +# Special target to create an AndroidSDL snapshot +androidsdl: + $(MKDIR) release + $(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) release + $(INSTALL) -c -m 644 $(DIST_FILES_DOCS) release + $(CP) $(srcdir)/backends/vkeybd/packs/vkeybd_default.zip release + zip -j scummvm190-git-appdata.zip release/* + split -d -b 1000000 scummvm190-git-appdata.zip scummvm190-git-appdata.zip0 + $(RM) -r scummvm190-git-appdata.zip + +.PHONY: androidsdl diff --git a/backends/platform/androidsdl/module.mk b/backends/platform/androidsdl/module.mk new file mode 100644 index 0000000000..df927163b8 --- /dev/null +++ b/backends/platform/androidsdl/module.mk @@ -0,0 +1,13 @@ +MODULE := backends/platform/androidsdl + +MODULE_OBJS := \ + androidsdl-main.o \ + androidsdl-sdl.o + +# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. +MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) +OBJS := $(MODULE_OBJS) $(OBJS) +MODULE_DIRS += $(sort $(dir $(MODULE_OBJS))) + +# Hack to ensure the SDL backend is built so we can use OSystem_SDL. +-include $(srcdir)/backends/platform/sdl/module.mk diff --git a/backends/platform/dc/dc.h b/backends/platform/dc/dc.h index d8ab549c3a..6cd938ec9c 100644 --- a/backends/platform/dc/dc.h +++ b/backends/platform/dc/dc.h @@ -57,21 +57,16 @@ class DCHardware { }; class DCCDManager : public DefaultAudioCDManager { - // Initialize the specified CD drive for audio playback. - bool openCD(int drive); - - // Poll cdrom status - // Returns true if cd audio is playing - bool pollCD(); - - // Play cdrom audio track - void playCD(int track, int num_loops, int start_frame, int duration); +public: + // Poll cdrom status + // Returns true if cd audio is playing + bool isPlaying() const; - // Stop cdrom audio track - void stopCD(); + // Play cdrom audio track + bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false); - // Update cdrom audio status - void updateCD(); + // Stop cdrom audio track + void stop(); }; class OSystem_Dreamcast : private DCHardware, public EventsBaseBackend, public PaletteManager, public FilesystemFactory diff --git a/backends/platform/dc/dcmain.cpp b/backends/platform/dc/dcmain.cpp index eede796991..c84aef9c47 100644 --- a/backends/platform/dc/dcmain.cpp +++ b/backends/platform/dc/dcmain.cpp @@ -90,43 +90,53 @@ static bool find_track(int track, int &first_sec, int &last_sec) return false; } -void DCCDManager::playCD(int track, int num_loops, int start_frame, int duration) -{ - int first_sec, last_sec; +bool DCCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate) { + DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate); + + // If we're playing now return here + if (isPlaying()) { + return true; + } + + // If we should only play emulated tracks stop here. + if (onlyEmulate) { + return false; + } + + int firstSec, lastSec; #if 1 - if (num_loops) - --num_loops; + if (numLoops) + --numLoops; #endif - if (num_loops>14) num_loops=14; - else if (num_loops<0) num_loops=15; // infinity - if (!find_track(track, first_sec, last_sec)) - return; - if (duration) - last_sec = first_sec + start_frame + duration; - first_sec += start_frame; - play_cdda_sectors(first_sec, last_sec, num_loops); -} -void DCCDManager::stopCD() -{ - stop_cdda(); -} + if (numLoops > 14) + numLoops = 14; + else if (numLoops < 0) + numLoops = 15; // infinity -bool DCCDManager::pollCD() -{ - extern int getCdState(); - return getCdState() == 3; + if (!find_track(track, firstSec, lastSec)) + return false; + + if (duration) + lastSec = firstSec + startFrame + duration; + + firstSec += startFrame; + play_cdda_sectors(firstSec, lastSec, numLoops); + + return true; } -void DCCDManager::updateCD() -{ - // Dummy. The CD drive takes care of itself. +void DCCDManager::stop() { + DefaultAudioCDManager::stop(); + stop_cdda(); } -bool DCCDManager::openCD(int drive) -{ - // Dummy. - return true; +bool DCCDManager::isPlaying() const { + if (DefaultAudioCDManager::isPlaying()) + return true; + + extern int getCdState(); + return getCdState() == 3; } void OSystem_Dreamcast::setWindowCaption(const char *caption) diff --git a/backends/platform/dc/vmsave.cpp b/backends/platform/dc/vmsave.cpp index 5f5cdff24f..d896ba1299 100644 --- a/backends/platform/dc/vmsave.cpp +++ b/backends/platform/dc/vmsave.cpp @@ -165,30 +165,7 @@ static bool tryDelete(const char *filename, int vm) return true; } -static bool matches(const char *glob, const char *name) -{ - while(*glob) - if(*glob == '*') { - while(*glob == '*') - glob++; - do { - if((*name == *glob || *glob == '?') && - matches(glob, name)) - return true; - } while(*name++); - return false; - } else if(!*name) - return false; - else if(*glob == '?' || *glob == *name) { - glob++; - name++; - } - else - return false; - return !*name; -} - -static void tryList(const char *glob, int vm, Common::StringArray &list) +static void tryList(const Common::String &glob, int vm, Common::StringArray &list) { struct vmsinfo info; struct superblock super; @@ -205,7 +182,7 @@ static void tryList(const char *glob, int vm, Common::StringArray &list) char buf[16]; strncpy(buf, (char *)de.entry+4, 12); buf[12] = 0; - if (matches(glob, buf)) + if (Common::matchString(buf, glob.c_str())) list.push_back(buf); } } @@ -292,15 +269,16 @@ public: class OutVMSave : public Common::OutSaveFile { private: char *buffer; - int pos, size, committed; + int _pos, size, committed; char filename[16]; bool iofailed; public: uint32 write(const void *buf, uint32 cnt); + virtual int32 pos() const { return _pos; } OutVMSave(const char *_filename) - : pos(0), committed(-1), iofailed(false) + : _pos(0), committed(-1), iofailed(false) { strncpy(filename, _filename, 16); buffer = new char[size = MAX_SAVE_SIZE]; @@ -343,14 +321,14 @@ void OutVMSave::finalize() extern const char *gGameName; extern Icon icon; - if (committed >= pos) + if (committed >= _pos) return; char *data = buffer; - int len = pos; + int len = _pos; vmsaveResult r = writeSaveGame(gGameName, data, len, filename, icon); - committed = pos; + committed = _pos; if (r != VMSAVE_OK) iofailed = true; displaySaveResult(r); @@ -409,13 +387,13 @@ bool InVMSave::seek(int32 offs, int whence) uint32 OutVMSave::write(const void *buf, uint32 cnt) { int nbyt = cnt; - if (pos + nbyt > size) { - cnt = (size - pos); + if (_pos + nbyt > size) { + cnt = (size - _pos); nbyt = cnt; } if (nbyt) - memcpy(buffer + pos, buf, nbyt); - pos += nbyt; + memcpy(buffer + _pos, buf, nbyt); + _pos += nbyt; return cnt; } @@ -425,7 +403,7 @@ Common::StringArray VMSaveManager::listSavefiles(const Common::String &pattern) Common::StringArray list; for (int i=0; i<24; i++) - tryList(pattern.c_str(), i, list); + tryList(pattern, i, list); return list; } diff --git a/backends/platform/dingux/README.GCW0 b/backends/platform/dingux/README.GCW0 new file mode 100644 index 0000000000..1b7e30e266 --- /dev/null +++ b/backends/platform/dingux/README.GCW0 @@ -0,0 +1,35 @@ +[ScummVM-GCW0 README] + +Controls +======== +- Dpad/analog joy: move mouse cursor +- A: left mouse button click +- B: right mouse button click +- X: '0' key +- Y: '.' key (skips dialogue line in some engines) +- Left Trigger: open global menu +- Right Trigger: opens virtual keyboard +- Select: ESC button, scene skip in some engines +- Start: F5 key, game menu in some engines + +Installation from binaries +========================== +Copy over scummvm.opk file + +Building from binaries +====================== +It's pretty simple if you are running Linux on an x86/amd64 machine: +1. Download and install the GCW0 toolchain (http://www.gcw-zero.com/develop) +2. Download ScummVM sources and uncompress them +3. Run backends/platform/dingux/build.gcw0.sh script +4. Copy the resulting file scummvm.opk to your device +5. Enjoy + +Troubleshooting +=============== +In case you need to submit a bugreport, you may find the log file at the +following path: + + /var/tmp/scummvm.log + +The log file is being overwritten at every ScummVM run. diff --git a/backends/platform/dingux/build.gcw0.sh b/backends/platform/dingux/build.gcw0.sh new file mode 100755 index 0000000000..7a31d4fd27 --- /dev/null +++ b/backends/platform/dingux/build.gcw0.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export PATH=/opt/gcw0-toolchain/usr/bin:$PATH + +# Disable high resolution engines since we have 320x240 hardware +./configure --host=gcw0 --enable-plugins --default-dynamic --enable-release && make -j6 gcw-opk && ls -l scummvm.opk diff --git a/backends/platform/dingux/dingux.cpp b/backends/platform/dingux/dingux.cpp index 2f11dd31ad..afd80acc1b 100644 --- a/backends/platform/dingux/dingux.cpp +++ b/backends/platform/dingux/dingux.cpp @@ -33,7 +33,7 @@ void OSystem_SDL_Dingux::initBackend() { // Create the graphics manager if (_graphicsManager == 0) { - _graphicsManager = new DINGUXSdlGraphicsManager(_eventSource); + _graphicsManager = new DINGUXSdlGraphicsManager(_eventSource, _window); } // Call parent implementation of this method diff --git a/backends/platform/dingux/dingux.mk b/backends/platform/dingux/dingux.mk index 1333e89ff8..dc87e41241 100644 --- a/backends/platform/dingux/dingux.mk +++ b/backends/platform/dingux/dingux.mk @@ -55,13 +55,30 @@ endif $(CP) $(srcdir)/dists/gcw0/default.gcw0.desktop $(gcw0_bundle)/ $(CP) $(srcdir)/dists/gcw0/scummvmrc $(gcw0_bundle)/ $(CP) $(srcdir)/dists/gcw0/scummvm.sh $(gcw0_bundle)/ + $(CP) $(srcdir)/backends/platform/dingux/README.GCW0 $(gcw0_bundle)/README.man.txt + echo >> $(gcw0_bundle)/README.man.txt + echo '[General README]' >> $(gcw0_bundle)/README.man.txt + echo >> $(gcw0_bundle)/README.man.txt + cat $(srcdir)/README | sed -e 's/\[/⟦/g' -e 's/\]/⟧/g' -e '/^1\.1)/,$$ s/^[0-9][0-9]*\.[0-9][0-9]*.*/\[&\]/' >> $(gcw0_bundle)/README.man.txt + + +# $(CP) GeneralUser\ GS\ FluidSynth\ v1.44.sf2 $(gcw0_bundle)/ gcw0-opk-unstripped: $(gcw0_bundle) $(CP) $(PLUGINS) $(gcw0_bundle)/plugins/ $(CP) $(EXECUTABLE) $(gcw0_bundle)/scummvm - ./dists/gcw0/opk_make.sh -d $(gcw0_bundle) -o scummvm + $(srcdir)/dists/gcw0/opk_make.sh -d $(gcw0_bundle) -o scummvm gcw-opk: $(gcw0_bundle) $(STRIP) $(gcw0_bundle)/plugins/* $(STRIP) $(gcw0_bundle)/scummvm - ./dists/gcw0/opk_make.sh -d $(gcw0_bundle) -o scummvm + $(srcdir)/dists/gcw0/opk_make.sh -d $(gcw0_bundle) -o scummvm + +GeneralUser_GS_1.44-FluidSynth.zip: + curl -s http://www.scummvm.org/frs/extras/SoundFont/GeneralUser_GS_1.44-FluidSynth.zip -o GeneralUser_GS_1.44-FluidSynth.zip + +GeneralUser\ GS\ FluidSynth\ v1.44.sf2: GeneralUser_GS_1.44-FluidSynth.zip + unzip -n GeneralUser_GS_1.44-FluidSynth.zip + mv "GeneralUser GS 1.44 FluidSynth/GeneralUser GS FluidSynth v1.44.sf2" . + mv "GeneralUser GS 1.44 FluidSynth/README.txt" README.soundfont + mv "GeneralUser GS 1.44 FluidSynth/LICENSE.txt" LICENSE.soundfont diff --git a/backends/platform/ds/arm9/source/gbampsave.cpp b/backends/platform/ds/arm9/source/gbampsave.cpp index ef6091e2a2..236ec55801 100644 --- a/backends/platform/ds/arm9/source/gbampsave.cpp +++ b/backends/platform/ds/arm9/source/gbampsave.cpp @@ -56,7 +56,7 @@ Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &f Common::WriteStream *stream = DS::DSFileStream::makeFromPath(fileSpec, true); // Use a write buffer stream = Common::wrapBufferedWriteStream(stream, SAVE_BUFFER_SIZE); - return stream; + return new OutSaveFile(stream); } Common::InSaveFile *GBAMPSaveFileManager::openForLoading(const Common::String &filename) { diff --git a/backends/platform/ds/arm9/source/osystem_ds.cpp b/backends/platform/ds/arm9/source/osystem_ds.cpp index c53f57523d..f23192cd9d 100644 --- a/backends/platform/ds/arm9/source/osystem_ds.cpp +++ b/backends/platform/ds/arm9/source/osystem_ds.cpp @@ -715,7 +715,7 @@ void OSystem_DS::deleteMutex(MutexRef mutex) { // and should be replaced by an AudioCDManager subclass, // see backends/audiocd/ and common/system.h -bool OSystem_DS::openCD(int drive) { +bool OSystem_DS::openCD() { return DS::CD::checkCD(); } diff --git a/backends/platform/ds/arm9/source/osystem_ds.h b/backends/platform/ds/arm9/source/osystem_ds.h index f4dbac66f7..9f73e125c2 100644 --- a/backends/platform/ds/arm9/source/osystem_ds.h +++ b/backends/platform/ds/arm9/source/osystem_ds.h @@ -130,7 +130,8 @@ public: // FIXME/TODO: The CD API as follows is *obsolete* // and should be replaced by an AudioCDManager subclass, // see backends/audiocd/ and common/system.h - virtual bool openCD(int drive); + virtual bool openCD(); + virtual void closeCD() {} virtual bool pollCD(); virtual void playCD(int track, int num_loops, int start_frame, int duration); virtual void stopCD(); diff --git a/backends/platform/gph/gph-backend.cpp b/backends/platform/gph/gph-backend.cpp index d033191d54..fb1cbe030c 100644 --- a/backends/platform/gph/gph-backend.cpp +++ b/backends/platform/gph/gph-backend.cpp @@ -159,7 +159,7 @@ void OSystem_GPH::initBackend() { // Create the graphics manager if (_graphicsManager == 0) { - _graphicsManager = new GPHGraphicsManager(_eventSource); + _graphicsManager = new GPHGraphicsManager(_eventSource, _window); } /* Pass to POSIX method to do the heavy lifting */ diff --git a/backends/platform/ios7/README.md b/backends/platform/ios7/README.md new file mode 100644 index 0000000000..f7d828ee94 --- /dev/null +++ b/backends/platform/ios7/README.md @@ -0,0 +1,150 @@ +# ScummVM for iOS 7.1+ # + +This is a quick fix of the latest ScummVM (1.8.0) for iOS 7.1. It has been tested on real iPhone 6S+, and iPad Pro, and also on all the available Xcode simulators. + +I tried to use all the latest iOS features to replace the old code. For instance, it uses gesture recognizers most of the time, it supports the new iPhones 6 / 6+ / 6s / 6s+ resolution, and you can copy your game files using iTunes. + +## Compilation ## + +First, clone the repository: +``` +$ git clone https://github.com/scummvm/scummvm.git +``` + +### Compilation from Xcode ### + +This is the recommended way to compile ScummVM, and the only one which makes it possible to run ScummVM on a non-jailbroken device! + +The next step is to compile the **create_project** tool. Open the Xcode project you'll found in the **devtools/create\_project/xcode/** directory. Once compiled, copy the binary somewhere in your *PATH*, and create a **build** directory somewhere on your harddisk. It is recommended to create this directory next to the cloned repository (they share the same parent). + +Execute the following commands in a terminal: +``` +$ cd path_to_the_build_directory +$ create_project path_to_scummvm_repository --xcode --enable-fluidsynth --disable-jpeg --disable-bink --disable-16bit --disable-mt32emu --disable-nasm --disable-opengl --disable-theora --disable-taskbar +``` + +This will create an Xcode project for ScummVM, for both the OS X, and the iOS target. + +Now, download the external libraries from http://bsr43.free.fr/scummvm/ScummVM-iOS-libraries.zip. Unzip the archive in your **build** directory. Please make sure that the **lib**, and **include** directories are at the root of the **build** directory, not in a subdirectory. + +Now, your **build** directory should contain: +* a generated **engines** directory, +* a generated **scummvm.xcodeproj** project, +* an **include** directory, +* a **lib** directory. + +You are ready to compile ScummVM: open the **scummvm.xcodeproj** project, and build it. + +### Compilation from command line ### + +For jailbroken devices, it is also possible to compile the project from command line. You'll need a working toolchain, and some tools, like **ldid**, to fake the code signature. + +Here is a script to download, and compile all the required tools. This script has been wrote for Debian 8.2, and should be run as root. + +``` +#!/bin/bash + +if [ $UID -ne 0 ]; then + echo "This script should be run by the root user" + exit 1 +fi + +# Install the Clang compiler +apt-get install -y clang-3.4 libclang-3.4-dev llvm-3.4 libtool bison flex automake subversion git pkg-config wget libssl-dev uuid-dev libxml2-dev || exit 1 + +# Add LLVM to the linker library path +echo /usr/lib/llvm-3.4/lib > /etc/ld.so.conf.d/libllvm-3.4.conf +ldconfig + +# Add symlinks for the LLVM headers +ln -s /usr/lib/llvm-3.4/bin/llvm-config /usr/bin/llvm-config || exit 1 +ln -s /usr/include/llvm-3.4/llvm /usr/include/llvm || exit 1 +ln -s /usr/include/llvm-c-3.4/llvm-c /usr/include/llvm-c || exit 1 +ln -s /usr/bin/clang-3.4 /usr/bin/clang || exit 1 +ln -s /usr/bin/clang++-3.4 /usr/bin/clang++ || exit 1 + +# Build the linker +svn checkout http://ios-toolchain-based-on-clang-for-linux.googlecode.com/svn/trunk/cctools-porting || exit 1 +cd cctools-porting +sed -i'' 's/proz -k=20 --no-curses/wget/g' cctools-ld64.sh +./cctools-ld64.sh || exit 1 + +cd cctools-855-ld64-236.3 +./autogen.sh || exit 1 +./configure --prefix=/usr/local --target=arm-apple-darwin11 || exit 1 +make || exit 1 +make install || exit 1 +cd ../.. + +# Install ios-tools +wget https://ios-toolchain-based-on-clang-for-linux.googlecode.com/files/iphonesdk-utils-2.0.tar.gz || exit 1 +tar xzf iphonesdk-utils-2.0.tar.gz +cd iphonesdk-utils-2.0 +patch -p0 <<_EOF +*** genLocalization2/getLocalizedStringFromFile.cpp 2015-04-02 04:45:39.309837816 +0530 +--- genLocalization2/getLocalizedStringFromFile.cpp 2015-04-02 04:45:11.525700021 +0530 +*************** +*** 113,115 **** + clang::HeaderSearch headerSearch(headerSearchOptions, +- fileManager, + *pDiagnosticsEngine, +--- 113,115 ---- + clang::HeaderSearch headerSearch(headerSearchOptions, ++ sourceManager, + *pDiagnosticsEngine, +*************** +*** 129,134 **** + false); +- clang::HeaderSearch headerSearch(fileManager, + *pDiagnosticsEngine, + languageOptions, +- pTargetInfo); + ApplyHeaderSearchOptions(headerSearch, headerSearchOptions, languageOptions, pTargetInfo->getTriple()); +--- 129,134 ---- + false); ++ clang::HeaderSearch headerSearch(fileManager);/*, + *pDiagnosticsEngine, + languageOptions, ++ pTargetInfo);*/ + ApplyHeaderSearchOptions(headerSearch, headerSearchOptions, languageOptio +_EOF + +./autogen.sh || exit 1 +CC=clang CXX=clang++ ./configure --prefix=/usr/local || exit 1 +make || exit 1 +make install || exit 1 + +# Install the iOS SDK 8.1 +mkdir -p /usr/share/ios-sdk +cd /usr/share/ios-sdk +wget http://iphone.howett.net/sdks/dl/iPhoneOS8.1.sdk.tbz2 || exit 1 +tar xjf iPhoneOS8.1.sdk.tbz2 +rm iPhoneOS8.1.sdk.tbz2 +``` + +Now, in order to compile ScummVM, execute the following commands: +``` +$ export SDKROOT=/usr/share/ios-sdk/iPhoneOS8.1.sdk +$ export CC=ios-clang +$ export CXX=ios-clang++ +$ ./configure --host=ios7 --disable-mt32emu --enable-release +$ make ios7bundle +``` + +At the end of the compilation, you'll find a **ScummVM.app** application: copy it over SSH, and reboot your device. + +## Usage ## + +The game data files can be copied on the iOS device using iTunes. Once done, add your games in ScummVM as usual. + +Here is a list of the in-game gestures: + +|Gesture|Description| +|-------|-----------| +|Two fingers swipe down|Display the ScummVM menu for loading, saving, etc.| +|Two fingers swipe right|Enable / disable the touchpad mode| +|Two fingers swipe up|Enable / disable the mouse-click-and-drag mode| +|Two fingers tap|Simulate a right click. You should tap with one finger, and then tap with another while keeping your first finger on the screen.| +|Two fingers double-tap|Skip the cinematic / video| + +The iOS keyboard is visible when the device is in portrait mode, and hidden in landscape mode. diff --git a/backends/graphics/opengl/extensions.h b/backends/platform/ios7/ios7_app_delegate.h index 87452429e2..08696c7b61 100644 --- a/backends/graphics/opengl/extensions.h +++ b/backends/platform/ios7/ios7_app_delegate.h @@ -20,22 +20,19 @@ * */ -#ifndef BACKENDS_GRAPHICS_OPENGL_EXTENSIONS_H -#define BACKENDS_GRAPHICS_OPENGL_EXTENSIONS_H +#ifndef BACKENDS_PLATFORM_IOS7_IOS7_APP_DELEGATE_H +#define BACKENDS_PLATFORM_IOS7_IOS7_APP_DELEGATE_H -namespace OpenGL { +#include <UIKit/UIKit.h> -/** - * Checks for availability of extensions we want to use and initializes them - * when available. - */ -void initializeGLExtensions(); +@class iPhoneView; -/** - * Whether non power of two textures are supported - */ -extern bool g_extNPOTSupported; -} // End of namespace OpenGL +@interface iOS7AppDelegate : NSObject<UIApplicationDelegate> + ++ (iOS7AppDelegate *)iOS7AppDelegate; ++ (iPhoneView *)iPhoneView; + +@end #endif diff --git a/backends/platform/ios7/ios7_app_delegate.mm b/backends/platform/ios7/ios7_app_delegate.mm new file mode 100644 index 0000000000..88d0a8925e --- /dev/null +++ b/backends/platform/ios7/ios7_app_delegate.mm @@ -0,0 +1,113 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL +#include "backends/platform/ios7/ios7_app_delegate.h" +#include "backends/platform/ios7/ios7_scummvm_view_controller.h" +#include "backends/platform/ios7/ios7_video.h" + +@implementation iOS7AppDelegate { + UIWindow *_window; + iOS7ScummVMViewController *_controller; + iPhoneView *_view; +} + +- (id)init { + if (self = [super init]) { + _window = nil; + _view = nil; + } + return self; +} + +- (void)mainLoop:(id)param { + @autoreleasepool { + iOS7_main(iOS7_argc, iOS7_argv); + } + + exit(0); +} + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + CGRect rect = [[UIScreen mainScreen] bounds]; + +#ifdef IPHONE_SANDBOXED + // Create the directory for savegames + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *documentPath = [NSString stringWithUTF8String:iOS7_getDocumentsDir()]; + NSString *savePath = [documentPath stringByAppendingPathComponent:@"Savegames"]; + if (![fm fileExistsAtPath:savePath]) { + [fm createDirectoryAtPath:savePath withIntermediateDirectories:YES attributes:nil error:nil]; + } +#endif + + _window = [[UIWindow alloc] initWithFrame:rect]; + [_window retain]; + + _controller = [[iOS7ScummVMViewController alloc] init]; + + _view = [[iPhoneView alloc] initWithFrame:rect]; + _view.multipleTouchEnabled = YES; + _controller.view = _view; + + [_window setRootViewController:_controller]; + [_window makeKeyAndVisible]; + + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didRotate:) + name:@"UIDeviceOrientationDidChangeNotification" + object:nil]; + + [NSThread detachNewThreadSelector:@selector(mainLoop:) toTarget:self withObject:nil]; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + [_view applicationSuspend]; +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + [_view applicationResume]; +} + +- (void)didRotate:(NSNotification *)notification { + UIDeviceOrientation screenOrientation = [[UIDevice currentDevice] orientation]; + [_view deviceOrientationChanged:screenOrientation]; +} + ++ (iOS7AppDelegate *)iOS7AppDelegate { + UIApplication *app = [UIApplication sharedApplication]; + return (iOS7AppDelegate *) app.delegate; +} + ++ (iPhoneView *)iPhoneView { + iOS7AppDelegate *appDelegate = [self iOS7AppDelegate]; + return appDelegate->_view; +} + +@end + +const char *iOS7_getDocumentsDir() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + return [documentsDirectory UTF8String]; +} diff --git a/backends/platform/ios7/ios7_common.h b/backends/platform/ios7/ios7_common.h new file mode 100644 index 0000000000..12740d4ae9 --- /dev/null +++ b/backends/platform/ios7/ios7_common.h @@ -0,0 +1,131 @@ +/* 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 BACKENDS_PLATFORM_IOS7_IOS7_COMMON_H +#define BACKENDS_PLATFORM_IOS7_IOS7_COMMON_H + +#include "graphics/surface.h" + +// #define ENABLE_IOS7_SCALERS + + +enum InputEvent { + kInputMouseDown, + kInputMouseUp, + kInputMouseDragged, + kInputMouseSecondDragged, + kInputMouseSecondDown, + kInputMouseSecondUp, + kInputOrientationChanged, + kInputKeyPressed, + kInputApplicationSuspended, + kInputApplicationResumed, + kInputSwipe, + kInputTap +}; + +enum ScreenOrientation { + kScreenOrientationPortrait, + kScreenOrientationLandscape, + kScreenOrientationFlippedLandscape +}; + +enum UIViewSwipeDirection { + kUIViewSwipeUp = 1, + kUIViewSwipeDown = 2, + kUIViewSwipeLeft = 4, + kUIViewSwipeRight = 8 +}; + +enum UIViewTapDescription { + kUIViewTapSingle = 1, + kUIViewTapDouble = 2 +}; + +enum GraphicsModes { + kGraphicsModeLinear = 0, + kGraphicsModeNone = 1, + + kGraphicsMode2xSaI, + kGraphicsModeSuper2xSaI, + kGraphicsModeSuperEagle, + kGraphicsModeAdvMame2x, + kGraphicsModeAdvMame3x, + kGraphicsModeHQ2x, + kGraphicsModeHQ3x, + kGraphicsModeTV2x, + kGraphicsModeDotMatrix +}; + +struct VideoContext { + VideoContext() : asprectRatioCorrection(), screenWidth(), screenHeight(), overlayVisible(false), + overlayWidth(), overlayHeight(), mouseX(), mouseY(), + mouseHotspotX(), mouseHotspotY(), mouseWidth(), mouseHeight(), + mouseIsVisible(), graphicsMode(kGraphicsModeNone), shakeOffsetY() { + } + + // Game screen state + bool asprectRatioCorrection; + uint screenWidth, screenHeight; + Graphics::Surface screenTexture; + + // Overlay state + bool overlayVisible; + uint overlayWidth, overlayHeight; + Graphics::Surface overlayTexture; + + // Mouse cursor state + uint mouseX, mouseY; + int mouseHotspotX, mouseHotspotY; + uint mouseWidth, mouseHeight; + bool mouseIsVisible; + Graphics::Surface mouseTexture; + + // Misc state + GraphicsModes graphicsMode; + int shakeOffsetY; +}; + +struct InternalEvent { + InternalEvent() : type(), value1(), value2() {} + InternalEvent(InputEvent t, int v1, int v2) : type(t), value1(v1), value2(v2) {} + + InputEvent type; + int value1, value2; +}; + +// On the ObjC side + +extern int iOS7_argc; +extern char **iOS7_argv; + +void iOS7_updateScreen(); +bool iOS7_fetchEvent(InternalEvent *event); +bool iOS7_isBigDevice(); + +void iOS7_main(int argc, char **argv); +const char *iOS7_getDocumentsDir(); +bool iOS7_touchpadModeEnabled(); + +uint getSizeNextPOT(uint size); + +#endif diff --git a/backends/platform/ios7/ios7_keyboard.h b/backends/platform/ios7/ios7_keyboard.h new file mode 100644 index 0000000000..1f917cc8c5 --- /dev/null +++ b/backends/platform/ios7/ios7_keyboard.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BACKENDS_PLATFORM_IOS7_IOS7_KEYBOARD_H +#define BACKENDS_PLATFORM_IOS7_IOS7_KEYBOARD_H + +#include <UIKit/UIKit.h> +#include <UIKit/UITextView.h> + +@interface SoftKeyboard : UIView<UITextViewDelegate> { + id inputDelegate; + UITextView *inputView; +} + +- (id)initWithFrame:(CGRect)frame; +- (UITextView *)inputView; +- (void)setInputDelegate:(id)delegate; +- (void)handleKeyPress:(unichar)c; + +- (void)showKeyboard; +- (void)hideKeyboard; + +@end + +#endif diff --git a/backends/platform/ios7/ios7_keyboard.mm b/backends/platform/ios7/ios7_keyboard.mm new file mode 100644 index 0000000000..9476b96ad4 --- /dev/null +++ b/backends/platform/ios7/ios7_keyboard.mm @@ -0,0 +1,98 @@ +/* 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 "backends/platform/ios7/ios7_keyboard.h" + +@interface UITextInputTraits +- (void)setAutocorrectionType:(int)type; +- (void)setAutocapitalizationType:(int)type; +- (void)setEnablesReturnKeyAutomatically:(BOOL)val; +@end + +@interface TextInputHandler : UITextView { + SoftKeyboard *softKeyboard; +} + +- (id)initWithKeyboard:(SoftKeyboard *)keyboard; + +@end + + +@implementation TextInputHandler + +- (id)initWithKeyboard:(SoftKeyboard *)keyboard { + self = [super initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 0.0f)]; + softKeyboard = keyboard; + + [self setAutocorrectionType:UITextAutocorrectionTypeNo]; + [self setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [self setEnablesReturnKeyAutomatically:NO]; + + return self; +} + +@end + + +@implementation SoftKeyboard + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + inputDelegate = nil; + inputView = [[TextInputHandler alloc] initWithKeyboard:self]; + inputView.delegate = self; + return self; +} + +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { + unichar c; + if (text.length) { + c = [text characterAtIndex:0]; + } + else { + c = '\b'; + } + [inputDelegate handleKeyPress:c]; + return YES; +} + +- (UITextView *)inputView { + return inputView; +} + +- (void)setInputDelegate:(id)delegate { + inputDelegate = delegate; +} + +- (void)handleKeyPress:(unichar)c { + [inputDelegate handleKeyPress:c]; +} + +- (void)showKeyboard { + [inputView becomeFirstResponder]; +} + +- (void)hideKeyboard { + [inputView endEditing:YES]; +} + +@end diff --git a/backends/platform/ios7/ios7_main.mm b/backends/platform/ios7/ios7_main.mm new file mode 100644 index 0000000000..c36cc08aaa --- /dev/null +++ b/backends/platform/ios7/ios7_main.mm @@ -0,0 +1,47 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include <UIKit/UIKit.h> +#include <Foundation/NSThread.h> + +#include "backends/platform/ios7/ios7_video.h" + + +int iOS7_argc; +char **iOS7_argv; + +int main(int argc, char **argv) { + int returnCode; + + @autoreleasepool { + iOS7_argc = argc; + iOS7_argv = argv; + + returnCode = UIApplicationMain(argc, argv, @"UIApplication", @"iOS7AppDelegate"); + } + + return returnCode; +} + diff --git a/backends/platform/ios7/ios7_osys_events.cpp b/backends/platform/ios7/ios7_osys_events.cpp new file mode 100644 index 0000000000..3621c084db --- /dev/null +++ b/backends/platform/ios7/ios7_osys_events.cpp @@ -0,0 +1,576 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "gui/message.h" +#include "common/translation.h" + +#include "backends/platform/ios7/ios7_osys_main.h" + +static const int kQueuedInputEventDelay = 50; + +bool OSystem_iOS7::pollEvent(Common::Event &event) { + //printf("pollEvent()\n"); + + long curTime = getMillis(); + + if (_timerCallback && (curTime >= _timerCallbackNext)) { + _timerCallback(_timerCallbackTimer); + _timerCallbackNext = curTime + _timerCallbackTimer; + } + + if (_queuedInputEvent.type != Common::EVENT_INVALID && curTime >= _queuedEventTime) { + event = _queuedInputEvent; + _queuedInputEvent.type = Common::EVENT_INVALID; + return true; + } + + InternalEvent internalEvent; + + if (iOS7_fetchEvent(&internalEvent)) { + switch (internalEvent.type) { + case kInputMouseDown: + if (!handleEvent_mouseDown(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + + case kInputMouseUp: + if (!handleEvent_mouseUp(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + + case kInputMouseDragged: + if (!handleEvent_mouseDragged(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + + case kInputOrientationChanged: + handleEvent_orientationChanged(internalEvent.value1); + return false; + + case kInputApplicationSuspended: + handleEvent_applicationSuspended(); + return false; + + case kInputApplicationResumed: + handleEvent_applicationResumed(); + return false; + + case kInputMouseSecondDragged: + if (!handleEvent_mouseSecondDragged(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + case kInputMouseSecondDown: + _secondaryTapped = true; + if (!handleEvent_secondMouseDown(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + case kInputMouseSecondUp: + _secondaryTapped = false; + if (!handleEvent_secondMouseUp(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + + case kInputKeyPressed: + handleEvent_keyPressed(event, internalEvent.value1); + break; + + case kInputSwipe: + if (!handleEvent_swipe(event, internalEvent.value1, internalEvent.value2)) + return false; + break; + + case kInputTap: + if (!handleEvent_tap(event, (UIViewTapDescription) internalEvent.value1, internalEvent.value2)) + return false; + break; + + default: + break; + } + + return true; + } + return false; +} + +bool OSystem_iOS7::handleEvent_mouseDown(Common::Event &event, int x, int y) { + //printf("Mouse down at (%u, %u)\n", x, y); + + // Workaround: kInputMouseSecondToggled isn't always sent when the + // secondary finger is lifted. Need to make sure we get out of that mode. + _secondaryTapped = false; + + if (_touchpadModeEnabled) { + _lastPadX = x; + _lastPadY = y; + } else + warpMouse(x, y); + + if (_mouseClickAndDragEnabled) { + event.type = Common::EVENT_LBUTTONDOWN; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + return true; + } else { + _lastMouseDown = getMillis(); + } + return false; +} + +bool OSystem_iOS7::handleEvent_mouseUp(Common::Event &event, int x, int y) { + //printf("Mouse up at (%u, %u)\n", x, y); + + if (_secondaryTapped) { + _secondaryTapped = false; + if (!handleEvent_secondMouseUp(event, x, y)) + return false; + } else if (_mouseClickAndDragEnabled) { + event.type = Common::EVENT_LBUTTONUP; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + } else { + if (getMillis() - _lastMouseDown < 250) { + event.type = Common::EVENT_LBUTTONDOWN; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + + _queuedInputEvent.type = Common::EVENT_LBUTTONUP; + _queuedInputEvent.mouse.x = _videoContext->mouseX; + _queuedInputEvent.mouse.y = _videoContext->mouseY; + _lastMouseTap = getMillis(); + _queuedEventTime = _lastMouseTap + kQueuedInputEventDelay; + } else + return false; + } + + return true; +} + +bool OSystem_iOS7::handleEvent_secondMouseDown(Common::Event &event, int x, int y) { + _lastSecondaryDown = getMillis(); + _gestureStartX = x; + _gestureStartY = y; + + if (_mouseClickAndDragEnabled) { + event.type = Common::EVENT_LBUTTONUP; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + + _queuedInputEvent.type = Common::EVENT_RBUTTONDOWN; + _queuedInputEvent.mouse.x = _videoContext->mouseX; + _queuedInputEvent.mouse.y = _videoContext->mouseY; + } else + return false; + + return true; +} + +bool OSystem_iOS7::handleEvent_secondMouseUp(Common::Event &event, int x, int y) { + int curTime = getMillis(); + + if (curTime - _lastSecondaryDown < 400) { + //printf("Right tap!\n"); + if (curTime - _lastSecondaryTap < 400 && !_videoContext->overlayVisible) { + //printf("Right escape!\n"); + event.type = Common::EVENT_KEYDOWN; + _queuedInputEvent.type = Common::EVENT_KEYUP; + + event.kbd.flags = _queuedInputEvent.kbd.flags = 0; + event.kbd.keycode = _queuedInputEvent.kbd.keycode = Common::KEYCODE_ESCAPE; + event.kbd.ascii = _queuedInputEvent.kbd.ascii = Common::ASCII_ESCAPE; + _queuedEventTime = curTime + kQueuedInputEventDelay; + _lastSecondaryTap = 0; + } else if (!_mouseClickAndDragEnabled) { + //printf("Rightclick!\n"); + event.type = Common::EVENT_RBUTTONDOWN; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + _queuedInputEvent.type = Common::EVENT_RBUTTONUP; + _queuedInputEvent.mouse.x = _videoContext->mouseX; + _queuedInputEvent.mouse.y = _videoContext->mouseY; + _lastSecondaryTap = curTime; + _queuedEventTime = curTime + kQueuedInputEventDelay; + } else { + //printf("Right nothing!\n"); + return false; + } + } + if (_mouseClickAndDragEnabled) { + event.type = Common::EVENT_RBUTTONUP; + event.mouse.x = _videoContext->mouseX; + event.mouse.y = _videoContext->mouseY; + } + + return true; +} + +bool OSystem_iOS7::handleEvent_mouseDragged(Common::Event &event, int x, int y) { + if (_lastDragPosX == x && _lastDragPosY == y) + return false; + + _lastDragPosX = x; + _lastDragPosY = y; + + //printf("Mouse dragged at (%u, %u)\n", x, y); + int mouseNewPosX; + int mouseNewPosY; + if (_touchpadModeEnabled) { + int deltaX = _lastPadX - x; + int deltaY = _lastPadY - y; + _lastPadX = x; + _lastPadY = y; + + mouseNewPosX = (int)(_videoContext->mouseX - deltaX / 0.5f); + mouseNewPosY = (int)(_videoContext->mouseY - deltaY / 0.5f); + + int widthCap = _videoContext->overlayVisible ? _videoContext->overlayWidth : _videoContext->screenWidth; + int heightCap = _videoContext->overlayVisible ? _videoContext->overlayHeight : _videoContext->screenHeight; + + if (mouseNewPosX < 0) + mouseNewPosX = 0; + else if (mouseNewPosX > widthCap) + mouseNewPosX = widthCap; + + if (mouseNewPosY < 0) + mouseNewPosY = 0; + else if (mouseNewPosY > heightCap) + mouseNewPosY = heightCap; + + } else { + mouseNewPosX = x; + mouseNewPosY = y; + } + + event.type = Common::EVENT_MOUSEMOVE; + event.mouse.x = mouseNewPosX; + event.mouse.y = mouseNewPosY; + warpMouse(mouseNewPosX, mouseNewPosY); + + return true; +} + +bool OSystem_iOS7::handleEvent_mouseSecondDragged(Common::Event &event, int x, int y) { + if (_gestureStartX == -1 || _gestureStartY == -1) { + return false; + } + + static const int kNeededLength = 100; + static const int kMaxDeviation = 20; + + int vecX = (x - _gestureStartX); + int vecY = (y - _gestureStartY); + + int absX = abs(vecX); + int absY = abs(vecY); + + //printf("(%d, %d)\n", vecX, vecY); + + if (absX >= kNeededLength || absY >= kNeededLength) { // Long enough gesture to react upon. + _gestureStartX = -1; + _gestureStartY = -1; + + if (absX < kMaxDeviation && vecY >= kNeededLength) { + // Swipe down + event.type = Common::EVENT_MAINMENU; + _queuedInputEvent.type = Common::EVENT_INVALID; + + _queuedEventTime = getMillis() + kQueuedInputEventDelay; + return true; + } + + if (absX < kMaxDeviation && -vecY >= kNeededLength) { + // Swipe up + _mouseClickAndDragEnabled = !_mouseClickAndDragEnabled; + const char *dialogMsg; + if (_mouseClickAndDragEnabled) { + _touchpadModeEnabled = false; + dialogMsg = _("Mouse-click-and-drag mode enabled."); + } else + dialogMsg = _("Mouse-click-and-drag mode disabled."); + GUI::TimedMessageDialog dialog(dialogMsg, 1500); + dialog.runModal(); + return false; + } + + if (absY < kMaxDeviation && vecX >= kNeededLength) { + // Swipe right + _touchpadModeEnabled = !_touchpadModeEnabled; + const char *dialogMsg; + if (_touchpadModeEnabled) + dialogMsg = _("Touchpad mode enabled."); + else + dialogMsg = _("Touchpad mode disabled."); + GUI::TimedMessageDialog dialog(dialogMsg, 1500); + dialog.runModal(); + return false; + + } + + if (absY < kMaxDeviation && -vecX >= kNeededLength) { + // Swipe left + return false; + } + } + + return false; +} + +void OSystem_iOS7::handleEvent_orientationChanged(int orientation) { + //printf("Orientation: %i\n", orientation); + + ScreenOrientation newOrientation; + switch (orientation) { + case 1: + newOrientation = kScreenOrientationPortrait; + break; + case 3: + newOrientation = kScreenOrientationLandscape; + break; + case 4: + newOrientation = kScreenOrientationFlippedLandscape; + break; + default: + return; + } + + if (_screenOrientation != newOrientation) { + _screenOrientation = newOrientation; + rebuildSurface(); + } +} + +void OSystem_iOS7::rebuildSurface() { + updateOutputSurface(); + + dirtyFullScreen(); + if (_videoContext->overlayVisible) { + dirtyFullOverlayScreen(); + } + updateScreen(); +} + +void OSystem_iOS7::handleEvent_applicationSuspended() { + suspendLoop(); +} + +void OSystem_iOS7::handleEvent_applicationResumed() { + rebuildSurface(); +} + +void OSystem_iOS7::handleEvent_keyPressed(Common::Event &event, int keyPressed) { + int ascii = keyPressed; + //printf("key: %i\n", keyPressed); + + // We remap some of the iPhone keyboard keys. + // The first ten here are the row of symbols below the numeric keys. + switch (keyPressed) { + case 45: + keyPressed = Common::KEYCODE_F1; + ascii = Common::ASCII_F1; + break; + case 47: + keyPressed = Common::KEYCODE_F2; + ascii = Common::ASCII_F2; + break; + case 58: + keyPressed = Common::KEYCODE_F3; + ascii = Common::ASCII_F3; + break; + case 59: + keyPressed = Common::KEYCODE_F4; + ascii = Common::ASCII_F4; + break; + case 40: + keyPressed = Common::KEYCODE_F5; + ascii = Common::ASCII_F5; + break; + case 41: + keyPressed = Common::KEYCODE_F6; + ascii = Common::ASCII_F6; + break; + case 36: + keyPressed = Common::KEYCODE_F7; + ascii = Common::ASCII_F7; + break; + case 38: + keyPressed = Common::KEYCODE_F8; + ascii = Common::ASCII_F8; + break; + case 64: + keyPressed = Common::KEYCODE_F9; + ascii = Common::ASCII_F9; + break; + case 34: + keyPressed = Common::KEYCODE_F10; + ascii = Common::ASCII_F10; + break; + case 10: + keyPressed = Common::KEYCODE_RETURN; + ascii = Common::ASCII_RETURN; + break; + } + event.type = Common::EVENT_KEYDOWN; + _queuedInputEvent.type = Common::EVENT_KEYUP; + + event.kbd.flags = _queuedInputEvent.kbd.flags = 0; + event.kbd.keycode = _queuedInputEvent.kbd.keycode = (Common::KeyCode)keyPressed; + event.kbd.ascii = _queuedInputEvent.kbd.ascii = ascii; + _queuedEventTime = getMillis() + kQueuedInputEventDelay; +} + +bool OSystem_iOS7::handleEvent_swipe(Common::Event &event, int direction, int touches) { + if (touches == 1) { + Common::KeyCode keycode = Common::KEYCODE_INVALID; + switch (_screenOrientation) { + case kScreenOrientationPortrait: + switch ((UIViewSwipeDirection)direction) { + case kUIViewSwipeUp: + keycode = Common::KEYCODE_UP; + break; + case kUIViewSwipeDown: + keycode = Common::KEYCODE_DOWN; + break; + case kUIViewSwipeLeft: + keycode = Common::KEYCODE_LEFT; + break; + case kUIViewSwipeRight: + keycode = Common::KEYCODE_RIGHT; + break; + default: + return false; + } + break; + case kScreenOrientationLandscape: + switch ((UIViewSwipeDirection)direction) { + case kUIViewSwipeUp: + keycode = Common::KEYCODE_LEFT; + break; + case kUIViewSwipeDown: + keycode = Common::KEYCODE_RIGHT; + break; + case kUIViewSwipeLeft: + keycode = Common::KEYCODE_DOWN; + break; + case kUIViewSwipeRight: + keycode = Common::KEYCODE_UP; + break; + default: + return false; + } + break; + case kScreenOrientationFlippedLandscape: + switch ((UIViewSwipeDirection)direction) { + case kUIViewSwipeUp: + keycode = Common::KEYCODE_RIGHT; + break; + case kUIViewSwipeDown: + keycode = Common::KEYCODE_LEFT; + break; + case kUIViewSwipeLeft: + keycode = Common::KEYCODE_UP; + break; + case kUIViewSwipeRight: + keycode = Common::KEYCODE_DOWN; + break; + default: + return false; + } + break; + } + + event.kbd.keycode = _queuedInputEvent.kbd.keycode = keycode; + event.kbd.ascii = _queuedInputEvent.kbd.ascii = 0; + event.type = Common::EVENT_KEYDOWN; + _queuedInputEvent.type = Common::EVENT_KEYUP; + event.kbd.flags = _queuedInputEvent.kbd.flags = 0; + _queuedEventTime = getMillis() + kQueuedInputEventDelay; + + return true; + } + else if (touches == 2) { + switch ((UIViewSwipeDirection)direction) { + case kUIViewSwipeUp: { + _mouseClickAndDragEnabled = !_mouseClickAndDragEnabled; + const char *dialogMsg; + if (_mouseClickAndDragEnabled) { + _touchpadModeEnabled = false; + dialogMsg = _("Mouse-click-and-drag mode enabled."); + } else + dialogMsg = _("Mouse-click-and-drag mode disabled."); + GUI::TimedMessageDialog dialog(dialogMsg, 1500); + dialog.runModal(); + return false; + } + + case kUIViewSwipeDown: { + // Swipe down + event.type = Common::EVENT_MAINMENU; + _queuedInputEvent.type = Common::EVENT_INVALID; + _queuedEventTime = getMillis() + kQueuedInputEventDelay; + return true; + } + + case kUIViewSwipeRight: { + // Swipe right + _touchpadModeEnabled = !_touchpadModeEnabled; + const char *dialogMsg; + if (_touchpadModeEnabled) + dialogMsg = _("Touchpad mode enabled."); + else + dialogMsg = _("Touchpad mode disabled."); + GUI::TimedMessageDialog dialog(dialogMsg, 1500); + dialog.runModal(); + return false; + } + + default: + break; + } + } + return false; +} + +bool OSystem_iOS7::handleEvent_tap(Common::Event &event, UIViewTapDescription type, int touches) { + if (touches == 1) { + if (type == kUIViewTapDouble) { + event.type = Common::EVENT_RBUTTONDOWN; + _queuedInputEvent.type = Common::EVENT_RBUTTONUP; + _queuedEventTime = getMillis() + kQueuedInputEventDelay; + return true; + } + } + else if (touches == 2) { + if (type == kUIViewTapDouble) { + event.kbd.keycode = _queuedInputEvent.kbd.keycode = Common::KEYCODE_ESCAPE; + event.kbd.ascii = _queuedInputEvent.kbd.ascii = Common::ASCII_ESCAPE; + event.type = Common::EVENT_KEYDOWN; + _queuedInputEvent.type = Common::EVENT_KEYUP; + event.kbd.flags = _queuedInputEvent.kbd.flags = 0; + _queuedEventTime = getMillis() + kQueuedInputEventDelay; + return true; + } + } + return false; +} diff --git a/backends/platform/ios7/ios7_osys_main.cpp b/backends/platform/ios7/ios7_osys_main.cpp new file mode 100644 index 0000000000..25d9cbed15 --- /dev/null +++ b/backends/platform/ios7/ios7_osys_main.cpp @@ -0,0 +1,395 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include <unistd.h> +#include <pthread.h> +#include <string.h> + +#include <sys/time.h> +#include <QuartzCore/QuartzCore.h> + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/rect.h" +#include "common/file.h" +#include "common/fs.h" + +#include "base/main.h" + +#include "backends/saves/default/default-saves.h" +#include "backends/timer/default/default-timer.h" +#include "backends/fs/chroot/chroot-fs-factory.h" +#include "backends/fs/posix/posix-fs.h" +#include "audio/mixer.h" +#include "audio/mixer_intern.h" + +#include "graphics/scaler.h" +#include "graphics/scaler/aspect.h" + +#include "backends/platform/ios7/ios7_osys_main.h" + + +const OSystem::GraphicsMode OSystem_iOS7::s_supportedGraphicsModes[] = { + { "none", "No filtering", kGraphicsModeNone }, + { "linear", "Linear filtering", kGraphicsModeLinear }, + +#ifdef ENABLE_IOS7_SCALERS +#ifdef USE_SCALERS +// {"2x", "2x", GFX_DOUBLESIZE}, +// {"3x", "3x", GFX_TRIPLESIZE}, + { "2xsai", "2xSAI", kGraphicsMode2xSaI}, + {"super2xsai", "Super2xSAI", kGraphicsModeSuper2xSaI}, + {"supereagle", "SuperEagle", kGraphicsModeSuperEagle}, + {"advmame2x", "AdvMAME2x", kGraphicsModeAdvMame2x}, + {"advmame3x", "AdvMAME3x", kGraphicsModeAdvMame3x}, +#ifdef USE_HQ_SCALERS + {"hq2x", "HQ2x", kGraphicsModeHQ2x}, + {"hq3x", "HQ3x", kGraphicsModeHQ3x}, +#endif + {"tv2x", "TV2x", kGraphicsModeTV2x}, + {"dotmatrix", "DotMatrix", kGraphicsModeDotMatrix}, +#endif +#endif + { 0, 0, 0 } +}; + +AQCallbackStruct OSystem_iOS7::s_AudioQueue; +SoundProc OSystem_iOS7::s_soundCallback = NULL; +void *OSystem_iOS7::s_soundParam = NULL; + +#ifdef IPHONE_SANDBOXED +class SandboxedSaveFileManager : public DefaultSaveFileManager { + Common::String _sandboxRootPath; +public: + + SandboxedSaveFileManager(Common::String sandboxRootPath, Common::String defaultSavepath) + : DefaultSaveFileManager(defaultSavepath), _sandboxRootPath(sandboxRootPath) { + } + + virtual bool removeSavefile(const Common::String &filename) override { + Common::String chrootedFile = getSavePath() + "/" + filename; + Common::String realFilePath = _sandboxRootPath + chrootedFile; + + if (remove(realFilePath.c_str()) != 0) { + if (errno == EACCES) + setError(Common::kWritePermissionDenied, "Search or write permission denied: "+chrootedFile); + + if (errno == ENOENT) + setError(Common::kPathDoesNotExist, "removeSavefile: '"+chrootedFile+"' does not exist or path is invalid"); + return false; + } else { + return true; + } + } +}; +#endif + +OSystem_iOS7::OSystem_iOS7() : + _mixer(NULL), _lastMouseTap(0), _queuedEventTime(0), + _mouseNeedTextureUpdate(false), _secondaryTapped(false), _lastSecondaryTap(0), + _screenOrientation(kScreenOrientationFlippedLandscape), _mouseClickAndDragEnabled(false), + _gestureStartX(-1), _gestureStartY(-1), _fullScreenIsDirty(false), _fullScreenOverlayIsDirty(false), + _mouseDirty(false), _timeSuspended(0), _lastDragPosX(-1), _lastDragPosY(-1), _screenChangeCount(0), + _lastErrorMessage(NULL), _mouseCursorPaletteEnabled(false), _gfxTransactionError(kTransactionSuccess) { + _queuedInputEvent.type = Common::EVENT_INVALID; + _touchpadModeEnabled = !iOS7_isBigDevice(); +#ifdef IPHONE_SANDBOXED + _chrootBasePath = iOS7_getDocumentsDir(); + _fsFactory = new ChRootFilesystemFactory(_chrootBasePath); +#else + _fsFactory = new POSIXFilesystemFactory(); +#endif + initVideoContext(); + + memset(_gamePalette, 0, sizeof(_gamePalette)); + memset(_gamePaletteRGBA5551, 0, sizeof(_gamePaletteRGBA5551)); + memset(_mouseCursorPalette, 0, sizeof(_mouseCursorPalette)); +} + +OSystem_iOS7::~OSystem_iOS7() { + AudioQueueDispose(s_AudioQueue.queue, true); + + delete _mixer; + // Prevent accidental freeing of the screen texture here. This needs to be + // checked since we might use the screen texture as framebuffer in the case + // of hi-color games for example. Otherwise this can lead to a double free. + if (_framebuffer.getPixels() != _videoContext->screenTexture.getPixels()) + _framebuffer.free(); + _mouseBuffer.free(); +} + +bool OSystem_iOS7::touchpadModeEnabled() const { + return _touchpadModeEnabled; +} + +int OSystem_iOS7::timerHandler(int t) { + DefaultTimerManager *tm = (DefaultTimerManager *)g_system->getTimerManager(); + tm->handler(); + return t; +} + +void OSystem_iOS7::initBackend() { +#ifdef IPHONE_SANDBOXED + _savefileManager = new SandboxedSaveFileManager(_chrootBasePath, "/Savegames"); +#else + _savefileManager = new DefaultSaveFileManager(SCUMMVM_SAVE_PATH); +#endif + + _timerManager = new DefaultTimerManager(); + + gettimeofday(&_startTime, NULL); + + setupMixer(); + + setTimerCallback(&OSystem_iOS7::timerHandler, 10); + + EventsBaseBackend::initBackend(); +} + +bool OSystem_iOS7::hasFeature(Feature f) { + switch (f) { + case kFeatureCursorPalette: + return true; + + default: + return false; + } +} + +void OSystem_iOS7::setFeatureState(Feature f, bool enable) { + switch (f) { + case kFeatureCursorPalette: + if (_mouseCursorPaletteEnabled != enable) { + _mouseNeedTextureUpdate = true; + _mouseDirty = true; + _mouseCursorPaletteEnabled = enable; + } + break; + case kFeatureAspectRatioCorrection: + _videoContext->asprectRatioCorrection = enable; + break; + + default: + break; + } +} + +bool OSystem_iOS7::getFeatureState(Feature f) { + switch (f) { + case kFeatureCursorPalette: + return _mouseCursorPaletteEnabled; + case kFeatureAspectRatioCorrection: + return _videoContext->asprectRatioCorrection; + + default: + return false; + } +} + +void OSystem_iOS7::suspendLoop() { + bool done = false; + uint32 startTime = getMillis(); + + stopSoundsystem(); + + InternalEvent event; + while (!done) { + if (iOS7_fetchEvent(&event)) + if (event.type == kInputApplicationResumed) + done = true; + usleep(100000); + } + + startSoundsystem(); + + _timeSuspended += getMillis() - startTime; +} + +uint32 OSystem_iOS7::getMillis(bool skipRecord) { + CFTimeInterval timeInSeconds = CACurrentMediaTime(); + return (uint32) (timeInSeconds * 1000.0); +} + +void OSystem_iOS7::delayMillis(uint msecs) { + //printf("delayMillis(%d)\n", msecs); + usleep(msecs * 1000); +} + +OSystem::MutexRef OSystem_iOS7::createMutex(void) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_t *mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, &attr) != 0) { + printf("pthread_mutex_init() failed!\n"); + free(mutex); + return NULL; + } + + return (MutexRef)mutex; +} + +void OSystem_iOS7::lockMutex(MutexRef mutex) { + if (pthread_mutex_lock((pthread_mutex_t *) mutex) != 0) { + printf("pthread_mutex_lock() failed!\n"); + } +} + +void OSystem_iOS7::unlockMutex(MutexRef mutex) { + if (pthread_mutex_unlock((pthread_mutex_t *) mutex) != 0) { + printf("pthread_mutex_unlock() failed!\n"); + } +} + +void OSystem_iOS7::deleteMutex(MutexRef mutex) { + if (pthread_mutex_destroy((pthread_mutex_t *) mutex) != 0) { + printf("pthread_mutex_destroy() failed!\n"); + } else { + free(mutex); + } +} + + +void OSystem_iOS7::setTimerCallback(TimerProc callback, int interval) { + //printf("setTimerCallback()\n"); + + if (callback != NULL) { + _timerCallbackTimer = interval; + _timerCallbackNext = getMillis() + interval; + _timerCallback = callback; + } else + _timerCallback = NULL; +} + +void OSystem_iOS7::quit() { +} + +void OSystem_iOS7::getTimeAndDate(TimeDate &td) const { + time_t curTime = time(0); + struct tm t = *localtime(&curTime); + td.tm_sec = t.tm_sec; + td.tm_min = t.tm_min; + td.tm_hour = t.tm_hour; + td.tm_mday = t.tm_mday; + td.tm_mon = t.tm_mon; + td.tm_year = t.tm_year; + td.tm_wday = t.tm_wday; +} + +Audio::Mixer *OSystem_iOS7::getMixer() { + assert(_mixer); + return _mixer; +} + +OSystem_iOS7 *OSystem_iOS7::sharedInstance() { + static OSystem_iOS7 *instance = new OSystem_iOS7(); + return instance; +} + +Common::String OSystem_iOS7::getDefaultConfigFileName() { +#ifdef IPHONE_SANDBOXED + Common::String path = "/Preferences"; + return path; +#else + return SCUMMVM_PREFS_PATH; +#endif +} + +void OSystem_iOS7::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { + // Get URL of the Resource directory of the .app bundle + CFURLRef fileUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + if (fileUrl) { + // Try to convert the URL to an absolute path + UInt8 buf[MAXPATHLEN]; + if (CFURLGetFileSystemRepresentation(fileUrl, true, buf, sizeof(buf))) { + // Success: Add it to the search path + Common::String bundlePath((const char *)buf); +#ifdef IPHONE_SANDBOXED + POSIXFilesystemNode *posixNode = new POSIXFilesystemNode(bundlePath); + s.add("__IOS_BUNDLE__", new Common::FSDirectory(AbstractFSNode::makeFSNode(posixNode)), priority); +#else + s.add("__IOS_BUNDLE__", new Common::FSDirectory(bundlePath), priority); +#endif + } + CFRelease(fileUrl); + } +} + +void OSystem_iOS7::logMessage(LogMessageType::Type type, const char *message) { + FILE *output = 0; + + if (type == LogMessageType::kInfo || type == LogMessageType::kDebug) + output = stdout; + else + output = stderr; + + if (type == LogMessageType::kError) { + free(_lastErrorMessage); + _lastErrorMessage = strdup(message); + } + + fputs(message, output); + fflush(output); +} + +bool iOS7_touchpadModeEnabled() { + OSystem_iOS7 *sys = (OSystem_iOS7 *) g_system; + return sys && sys->touchpadModeEnabled(); +} + +void iOS7_main(int argc, char **argv) { + + //OSystem_iOS7::migrateApp(); + + FILE *newfp = fopen("/var/mobile/.scummvm.log", "a"); + if (newfp != NULL) { + fclose(stdout); + fclose(stderr); + *stdout = *newfp; + *stderr = *newfp; + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + //extern int gDebugLevel; + //gDebugLevel = 10; + } + +#ifdef IPHONE_SANDBOXED + chdir(iOS7_getDocumentsDir()); +#else + system("mkdir " SCUMMVM_ROOT_PATH); + system("mkdir " SCUMMVM_SAVE_PATH); + + chdir("/var/mobile/"); +#endif + + g_system = OSystem_iOS7::sharedInstance(); + assert(g_system); + + // Invoke the actual ScummVM main entry point: + scummvm_main(argc, (const char *const *) argv); + g_system->quit(); // TODO: Consider removing / replacing this! +} diff --git a/backends/platform/ios7/ios7_osys_main.h b/backends/platform/ios7/ios7_osys_main.h new file mode 100644 index 0000000000..174c160bd6 --- /dev/null +++ b/backends/platform/ios7/ios7_osys_main.h @@ -0,0 +1,237 @@ +/* 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 BACKENDS_PLATFORM_IOS7_IOS7_OSYS_MAIN_H +#define BACKENDS_PLATFORM_IOS7_IOS7_OSYS_MAIN_H + +#include "graphics/surface.h" +#include "backends/platform/ios7/ios7_common.h" +#include "backends/base-backend.h" +#include "common/events.h" +#include "audio/mixer_intern.h" +#include "backends/fs/posix/posix-fs-factory.h" +#include "graphics/colormasks.h" +#include "graphics/palette.h" + +#include <AudioToolbox/AudioQueue.h> + +#define AUDIO_BUFFERS 3 +#define WAVE_BUFFER_SIZE 2048 +#define AUDIO_SAMPLE_RATE 44100 + +#define SCUMMVM_ROOT_PATH "/var/mobile/Library/ScummVM" +#define SCUMMVM_SAVE_PATH SCUMMVM_ROOT_PATH "/Savegames" +#define SCUMMVM_PREFS_PATH SCUMMVM_ROOT_PATH "/Preferences" + +typedef void (*SoundProc)(void *param, byte *buf, int len); +typedef int (*TimerProc)(int interval); + +struct AQCallbackStruct { + AudioQueueRef queue; + uint32 frameCount; + AudioQueueBufferRef buffers[AUDIO_BUFFERS]; + AudioStreamBasicDescription dataFormat; +}; + +class OSystem_iOS7 : public EventsBaseBackend, public PaletteManager { +protected: + static const OSystem::GraphicsMode s_supportedGraphicsModes[]; + static AQCallbackStruct s_AudioQueue; + static SoundProc s_soundCallback; + static void *s_soundParam; + + Audio::MixerImpl *_mixer; + + VideoContext *_videoContext; + + Graphics::Surface _framebuffer; + + // For signaling that screen format set up might have failed. + TransactionError _gfxTransactionError; + + // For use with the game texture + uint16 _gamePalette[256]; + // For use with the mouse texture + uint16 _gamePaletteRGBA5551[256]; + + struct timeval _startTime; + uint32 _timeSuspended; + + bool _mouseCursorPaletteEnabled; + uint16 _mouseCursorPalette[256]; + Graphics::Surface _mouseBuffer; + uint16 _mouseKeyColor; + bool _mouseDirty; + bool _mouseNeedTextureUpdate; + + long _lastMouseDown; + long _lastMouseTap; + long _queuedEventTime; + Common::Event _queuedInputEvent; + bool _secondaryTapped; + long _lastSecondaryDown; + long _lastSecondaryTap; + int _gestureStartX, _gestureStartY; + bool _mouseClickAndDragEnabled; + bool _touchpadModeEnabled; + int _lastPadX; + int _lastPadY; + int _lastDragPosX; + int _lastDragPosY; + + int _timerCallbackNext; + int _timerCallbackTimer; + TimerProc _timerCallback; + + Common::Array<Common::Rect> _dirtyRects; + Common::Array<Common::Rect> _dirtyOverlayRects; + ScreenOrientation _screenOrientation; + bool _fullScreenIsDirty; + bool _fullScreenOverlayIsDirty; + int _screenChangeCount; + + char *_lastErrorMessage; + +#ifdef IPHONE_SANDBOXED + Common::String _chrootBasePath; +#endif + +public: + + OSystem_iOS7(); + virtual ~OSystem_iOS7(); + + static OSystem_iOS7 *sharedInstance(); + + virtual void initBackend(); + + virtual bool hasFeature(Feature f); + virtual void setFeatureState(Feature f, bool enable); + virtual bool getFeatureState(Feature f); + virtual const GraphicsMode *getSupportedGraphicsModes() const; + virtual int getDefaultGraphicsMode() const; + virtual bool setGraphicsMode(int mode); + virtual int getGraphicsMode() const; + virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format); + + virtual void beginGFXTransaction(); + virtual TransactionError endGFXTransaction(); + + virtual int16 getHeight(); + virtual int16 getWidth(); + + bool touchpadModeEnabled() const; + +#ifdef USE_RGB_COLOR + virtual Graphics::PixelFormat getScreenFormat() const { return _framebuffer.format; } + virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const; +#endif + + virtual PaletteManager *getPaletteManager() { return this; } +protected: + // PaletteManager API + virtual void setPalette(const byte *colors, uint start, uint num); + virtual void grabPalette(byte *colors, uint start, uint num); + +public: + virtual void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h); + virtual void updateScreen(); + virtual Graphics::Surface *lockScreen(); + virtual void unlockScreen(); + virtual void setShakePos(int shakeOffset); + + virtual void showOverlay(); + virtual void hideOverlay(); + virtual void clearOverlay(); + virtual void grabOverlay(void *buf, int pitch); + virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h); + virtual int16 getOverlayHeight(); + virtual int16 getOverlayWidth(); + virtual Graphics::PixelFormat getOverlayFormat() const { return Graphics::createPixelFormat<5551>(); } + + virtual bool showMouse(bool visible); + + virtual void warpMouse(int x, int y); + virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 255, bool dontScale = false, const Graphics::PixelFormat *format = NULL); + virtual void setCursorPalette(const byte *colors, uint start, uint num); + + virtual bool pollEvent(Common::Event &event); + virtual uint32 getMillis(bool skipRecord = false); + virtual void delayMillis(uint msecs); + + virtual MutexRef createMutex(void); + virtual void lockMutex(MutexRef mutex); + virtual void unlockMutex(MutexRef mutex); + virtual void deleteMutex(MutexRef mutex); + + static void mixCallback(void *sys, byte *samples, int len); + virtual void setupMixer(void); + virtual void setTimerCallback(TimerProc callback, int interval); + virtual int getScreenChangeID() const { return _screenChangeCount; } + virtual void quit(); + + virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); + virtual void getTimeAndDate(TimeDate &t) const; + + virtual Audio::Mixer *getMixer(); + + void startSoundsystem(); + void stopSoundsystem(); + + virtual Common::String getDefaultConfigFileName(); + + virtual void logMessage(LogMessageType::Type type, const char *message); + virtual void fatalError() override; + +protected: + void initVideoContext(); + void updateOutputSurface(); + + void internUpdateScreen(); + void dirtyFullScreen(); + void dirtyFullOverlayScreen(); + void suspendLoop(); + void drawDirtyRect(const Common::Rect &dirtyRect); + void updateMouseTexture(); + static void AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB); + static int timerHandler(int t); + + bool handleEvent_swipe(Common::Event &event, int direction, int touches); + bool handleEvent_tap(Common::Event &event, UIViewTapDescription type, int touches); + void handleEvent_keyPressed(Common::Event &event, int keyPressed); + void handleEvent_orientationChanged(int orientation); + void handleEvent_applicationSuspended(); + void handleEvent_applicationResumed(); + + bool handleEvent_mouseDown(Common::Event &event, int x, int y); + bool handleEvent_mouseUp(Common::Event &event, int x, int y); + + bool handleEvent_secondMouseDown(Common::Event &event, int x, int y); + bool handleEvent_secondMouseUp(Common::Event &event, int x, int y); + + bool handleEvent_mouseDragged(Common::Event &event, int x, int y); + bool handleEvent_mouseSecondDragged(Common::Event &event, int x, int y); + + void rebuildSurface(); +}; + +#endif diff --git a/backends/platform/ios7/ios7_osys_sound.cpp b/backends/platform/ios7/ios7_osys_sound.cpp new file mode 100644 index 0000000000..07e9458711 --- /dev/null +++ b/backends/platform/ios7/ios7_osys_sound.cpp @@ -0,0 +1,105 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/platform/ios7/ios7_osys_main.h" + +void OSystem_iOS7::AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB) { + //printf("AQBufferCallback()\n"); + if (s_AudioQueue.frameCount > 0 && s_soundCallback != NULL) { + outQB->mAudioDataByteSize = 4 * s_AudioQueue.frameCount; + s_soundCallback(s_soundParam, (byte *)outQB->mAudioData, outQB->mAudioDataByteSize); + AudioQueueEnqueueBuffer(inQ, outQB, 0, NULL); + } else { + AudioQueueStop(s_AudioQueue.queue, false); + } +} + +void OSystem_iOS7::mixCallback(void *sys, byte *samples, int len) { + OSystem_iOS7 *this_ = (OSystem_iOS7 *)sys; + assert(this_); + + if (this_->_mixer) { + this_->_mixer->mixCallback(samples, len); + } +} + +void OSystem_iOS7::setupMixer() { + _mixer = new Audio::MixerImpl(this, AUDIO_SAMPLE_RATE); + + s_soundCallback = mixCallback; + s_soundParam = this; + + startSoundsystem(); +} + +void OSystem_iOS7::startSoundsystem() { + s_AudioQueue.dataFormat.mSampleRate = AUDIO_SAMPLE_RATE; + s_AudioQueue.dataFormat.mFormatID = kAudioFormatLinearPCM; + s_AudioQueue.dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + s_AudioQueue.dataFormat.mBytesPerPacket = 4; + s_AudioQueue.dataFormat.mFramesPerPacket = 1; + s_AudioQueue.dataFormat.mBytesPerFrame = 4; + s_AudioQueue.dataFormat.mChannelsPerFrame = 2; + s_AudioQueue.dataFormat.mBitsPerChannel = 16; + s_AudioQueue.frameCount = WAVE_BUFFER_SIZE; + + if (AudioQueueNewOutput(&s_AudioQueue.dataFormat, AQBufferCallback, &s_AudioQueue, 0, kCFRunLoopCommonModes, 0, &s_AudioQueue.queue)) { + printf("Couldn't set the AudioQueue callback!\n"); + _mixer->setReady(false); + return; + } + + uint32 bufferBytes = s_AudioQueue.frameCount * s_AudioQueue.dataFormat.mBytesPerFrame; + + for (int i = 0; i < AUDIO_BUFFERS; i++) { + if (AudioQueueAllocateBuffer(s_AudioQueue.queue, bufferBytes, &s_AudioQueue.buffers[i])) { + printf("Error allocating AudioQueue buffer!\n"); + _mixer->setReady(false); + return; + } + + AQBufferCallback(&s_AudioQueue, s_AudioQueue.queue, s_AudioQueue.buffers[i]); + } + + AudioQueueSetParameter(s_AudioQueue.queue, kAudioQueueParam_Volume, 1.0); + if (AudioQueueStart(s_AudioQueue.queue, NULL)) { + printf("Error starting the AudioQueue!\n"); + _mixer->setReady(false); + return; + } + + _mixer->setReady(true); +} + +void OSystem_iOS7::stopSoundsystem() { + AudioQueueStop(s_AudioQueue.queue, true); + + for (int i = 0; i < AUDIO_BUFFERS; i++) { + AudioQueueFreeBuffer(s_AudioQueue.queue, s_AudioQueue.buffers[i]); + } + + AudioQueueDispose(s_AudioQueue.queue, true); + _mixer->setReady(false); +} diff --git a/backends/platform/ios7/ios7_osys_video.mm b/backends/platform/ios7/ios7_osys_video.mm new file mode 100644 index 0000000000..6784cf46f5 --- /dev/null +++ b/backends/platform/ios7/ios7_osys_video.mm @@ -0,0 +1,545 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/platform/ios7/ios7_osys_main.h" +#include "backends/platform/ios7/ios7_video.h" + +#include "graphics/conversion.h" +#include "backends/platform/ios7/ios7_app_delegate.h" + +@interface iOS7AlertHandler : NSObject<UIAlertViewDelegate> +@end + +@implementation iOS7AlertHandler + +- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex { + OSystem_iOS7::sharedInstance()->quit(); + exit(1); +} + +@end + +static void displayAlert(void *ctx) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Fatal Error" + message:[NSString stringWithCString:(const char *)ctx encoding:NSUTF8StringEncoding] + delegate:[[iOS7AlertHandler alloc] init] + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert autorelease]; +} + +void OSystem_iOS7::fatalError() { + if (_lastErrorMessage) { + dispatch_async_f(dispatch_get_main_queue(), _lastErrorMessage, displayAlert); + for(;;); + } + else { + OSystem::fatalError(); + } +} + +void OSystem_iOS7::initVideoContext() { + _videoContext = [[iOS7AppDelegate iPhoneView] getVideoContext]; +} + +const OSystem::GraphicsMode *OSystem_iOS7::getSupportedGraphicsModes() const { + return s_supportedGraphicsModes; +} + +int OSystem_iOS7::getDefaultGraphicsMode() const { + return kGraphicsModeNone; +} + +bool OSystem_iOS7::setGraphicsMode(int mode) { + switch (mode) { + case kGraphicsModeNone: + case kGraphicsModeLinear: + case kGraphicsMode2xSaI: + case kGraphicsModeSuper2xSaI: + case kGraphicsModeSuperEagle: + case kGraphicsModeAdvMame2x: + case kGraphicsModeAdvMame3x: + case kGraphicsModeHQ2x: + case kGraphicsModeHQ3x: + case kGraphicsModeTV2x: + case kGraphicsModeDotMatrix: + _videoContext->graphicsMode = (GraphicsModes)mode; + return true; + + default: + return false; + } +} + +int OSystem_iOS7::getGraphicsMode() const { + return _videoContext->graphicsMode; +} + +#ifdef USE_RGB_COLOR +Common::List<Graphics::PixelFormat> OSystem_iOS7::getSupportedFormats() const { + Common::List<Graphics::PixelFormat> list; + // RGB565 + list.push_back(Graphics::createPixelFormat<565>()); + // CLUT8 + list.push_back(Graphics::PixelFormat::createFormatCLUT8()); + return list; +} +#endif + +void OSystem_iOS7::initSize(uint width, uint height, const Graphics::PixelFormat *format) { + //printf("initSize(%u, %u, %p)\n", width, height, (const void *)format); + + _videoContext->screenWidth = width; + _videoContext->screenHeight = height; + _videoContext->shakeOffsetY = 0; + + // In case we use the screen texture as frame buffer we reset the pixels + // pointer here to avoid freeing the screen texture. + if (_framebuffer.getPixels() == _videoContext->screenTexture.getPixels()) + _framebuffer.setPixels(0); + + // Create the screen texture right here. We need to do this here, since + // when a game requests hi-color mode, we actually set the framebuffer + // to the texture buffer to avoid an additional copy step. + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(createScreenTexture) withObject:nil waitUntilDone: YES]; + + // In case the client code tries to set up a non supported mode, we will + // fall back to CLUT8 and set the transaction error accordingly. + if (format && format->bytesPerPixel != 1 && *format != _videoContext->screenTexture.format) { + format = 0; + _gfxTransactionError = kTransactionFormatNotSupported; + } + + if (!format || format->bytesPerPixel == 1) { + _framebuffer.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + } else { +#if 0 + printf("bytesPerPixel: %u RGBAlosses: %u,%u,%u,%u RGBAshifts: %u,%u,%u,%u\n", format->bytesPerPixel, + format->rLoss, format->gLoss, format->bLoss, format->aLoss, + format->rShift, format->gShift, format->bShift, format->aShift); +#endif + // We directly draw on the screen texture in hi-color mode. Thus + // we copy over its settings here and just replace the width and + // height to avoid any problems. + _framebuffer = _videoContext->screenTexture; + _framebuffer.w = width; + _framebuffer.h = height; + } + + _fullScreenIsDirty = false; + dirtyFullScreen(); + _mouseCursorPaletteEnabled = false; +} + +void OSystem_iOS7::beginGFXTransaction() { + _gfxTransactionError = kTransactionSuccess; +} + +OSystem::TransactionError OSystem_iOS7::endGFXTransaction() { + _screenChangeCount++; + updateOutputSurface(); + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(setGraphicsMode) withObject:nil waitUntilDone: YES]; + + return _gfxTransactionError; +} + +void OSystem_iOS7::updateOutputSurface() { + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(initSurface) withObject:nil waitUntilDone: YES]; +} + +int16 OSystem_iOS7::getHeight() { + return _videoContext->screenHeight; +} + +int16 OSystem_iOS7::getWidth() { + return _videoContext->screenWidth; +} + +void OSystem_iOS7::setPalette(const byte *colors, uint start, uint num) { + //printf("setPalette(%p, %u, %u)\n", colors, start, num); + assert(start + num <= 256); + const byte *b = colors; + + for (uint i = start; i < start + num; ++i) { + _gamePalette[i] = Graphics::RGBToColor<Graphics::ColorMasks<565> >(b[0], b[1], b[2]); + _gamePaletteRGBA5551[i] = Graphics::RGBToColor<Graphics::ColorMasks<5551> >(b[0], b[1], b[2]); + b += 3; + } + + dirtyFullScreen(); + + // Automatically update the mouse texture when the palette changes while the + // cursor palette is disabled. + if (!_mouseCursorPaletteEnabled && _mouseBuffer.format.bytesPerPixel == 1) + _mouseDirty = _mouseNeedTextureUpdate = true; +} + +void OSystem_iOS7::grabPalette(byte *colors, uint start, uint num) { + //printf("grabPalette(%p, %u, %u)\n", colors, start, num); + assert(start + num <= 256); + byte *b = colors; + + for (uint i = start; i < start + num; ++i) { + Graphics::colorToRGB<Graphics::ColorMasks<565> >(_gamePalette[i], b[0], b[1], b[2]); + b += 3; + } +} + +void OSystem_iOS7::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) { + //printf("copyRectToScreen(%p, %d, %i, %i, %i, %i)\n", buf, pitch, x, y, w, h); + //Clip the coordinates + const byte *src = (const byte *)buf; + if (x < 0) { + w += x; + src -= x; + x = 0; + } + + if (y < 0) { + h += y; + src -= y * pitch; + y = 0; + } + + if (w > (int)_framebuffer.w - x) { + w = _framebuffer.w - x; + } + + if (h > (int)_framebuffer.h - y) { + h = _framebuffer.h - y; + } + + if (w <= 0 || h <= 0) + return; + + if (!_fullScreenIsDirty) { + _dirtyRects.push_back(Common::Rect(x, y, x + w, y + h)); + } + + byte *dst = (byte *)_framebuffer.getBasePtr(x, y); + if (_framebuffer.pitch == pitch && _framebuffer.w == w) { + memcpy(dst, src, h * pitch); + } else { + do { + memcpy(dst, src, w * _framebuffer.format.bytesPerPixel); + src += pitch; + dst += _framebuffer.pitch; + } while (--h); + } +} + +void OSystem_iOS7::updateScreen() { + if (_dirtyRects.size() == 0 && _dirtyOverlayRects.size() == 0 && !_mouseDirty) + return; + + //printf("updateScreen(): %i dirty rects.\n", _dirtyRects.size()); + + internUpdateScreen(); + _mouseDirty = false; + _fullScreenIsDirty = false; + _fullScreenOverlayIsDirty = false; + + iOS7_updateScreen(); +} + +void OSystem_iOS7::internUpdateScreen() { + if (_mouseNeedTextureUpdate) { + updateMouseTexture(); + _mouseNeedTextureUpdate = false; + } + + while (_dirtyRects.size()) { + Common::Rect dirtyRect = _dirtyRects.remove_at(_dirtyRects.size() - 1); + + //printf("Drawing: (%i, %i) -> (%i, %i)\n", dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); + drawDirtyRect(dirtyRect); + // TODO: Implement dirty rect code + //updateHardwareSurfaceForRect(dirtyRect); + } + + if (_videoContext->overlayVisible) { + // TODO: Implement dirty rect code + _dirtyOverlayRects.clear(); + /*while (_dirtyOverlayRects.size()) { + Common::Rect dirtyRect = _dirtyOverlayRects.remove_at(_dirtyOverlayRects.size() - 1); + + //printf("Drawing: (%i, %i) -> (%i, %i)\n", dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); + drawDirtyOverlayRect(dirtyRect); + }*/ + } +} + +void OSystem_iOS7::drawDirtyRect(const Common::Rect &dirtyRect) { + // We only need to do a color look up for CLUT8 + if (_framebuffer.format.bytesPerPixel != 1) + return; + + int h = dirtyRect.bottom - dirtyRect.top; + int w = dirtyRect.right - dirtyRect.left; + + const byte *src = (const byte *)_framebuffer.getBasePtr(dirtyRect.left, dirtyRect.top); + byte *dstRaw = (byte *)_videoContext->screenTexture.getBasePtr(dirtyRect.left, dirtyRect.top); + + // When we use CLUT8 do a color look up + for (int y = h; y > 0; y--) { + uint16 *dst = (uint16 *)dstRaw; + for (int x = w; x > 0; x--) + *dst++ = _gamePalette[*src++]; + + dstRaw += _videoContext->screenTexture.pitch; + src += _framebuffer.pitch - w; + } +} + +Graphics::Surface *OSystem_iOS7::lockScreen() { + //printf("lockScreen()\n"); + return &_framebuffer; +} + +void OSystem_iOS7::unlockScreen() { + //printf("unlockScreen()\n"); + dirtyFullScreen(); +} + +void OSystem_iOS7::setShakePos(int shakeOffset) { + //printf("setShakePos(%i)\n", shakeOffset); + _videoContext->shakeOffsetY = shakeOffset; + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(setViewTransformation) withObject:nil waitUntilDone: YES]; + // HACK: We use this to force a redraw. + _mouseDirty = true; +} + +void OSystem_iOS7::showOverlay() { + //printf("showOverlay()\n"); + _videoContext->overlayVisible = true; + dirtyFullOverlayScreen(); + updateScreen(); + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(updateMouseCursorScaling) withObject:nil waitUntilDone: YES]; + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(clearColorBuffer) withObject:nil waitUntilDone: YES]; +} + +void OSystem_iOS7::hideOverlay() { + //printf("hideOverlay()\n"); + _videoContext->overlayVisible = false; + _dirtyOverlayRects.clear(); + dirtyFullScreen(); + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(updateMouseCursorScaling) withObject:nil waitUntilDone: YES]; + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(clearColorBuffer) withObject:nil waitUntilDone: YES]; +} + +void OSystem_iOS7::clearOverlay() { + //printf("clearOverlay()\n"); + bzero(_videoContext->overlayTexture.getPixels(), _videoContext->overlayTexture.h * _videoContext->overlayTexture.pitch); + dirtyFullOverlayScreen(); +} + +void OSystem_iOS7::grabOverlay(void *buf, int pitch) { + //printf("grabOverlay()\n"); + int h = _videoContext->overlayHeight; + + byte *dst = (byte *)buf; + const byte *src = (const byte *)_videoContext->overlayTexture.getPixels(); + do { + memcpy(dst, src, _videoContext->overlayWidth * sizeof(uint16)); + src += _videoContext->overlayTexture.pitch; + dst += pitch; + } while (--h); +} + +void OSystem_iOS7::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) { + //printf("copyRectToOverlay(%p, pitch=%i, x=%i, y=%i, w=%i, h=%i)\n", (const void *)buf, pitch, x, y, w, h); + const byte *src = (const byte *)buf; + + //Clip the coordinates + if (x < 0) { + w += x; + src -= x * sizeof(uint16); + x = 0; + } + + if (y < 0) { + h += y; + src -= y * pitch; + y = 0; + } + + if (w > (int)_videoContext->overlayWidth - x) + w = _videoContext->overlayWidth - x; + + if (h > (int)_videoContext->overlayHeight - y) + h = _videoContext->overlayHeight - y; + + if (w <= 0 || h <= 0) + return; + + if (!_fullScreenOverlayIsDirty) { + _dirtyOverlayRects.push_back(Common::Rect(x, y, x + w, y + h)); + } + + byte *dst = (byte *)_videoContext->overlayTexture.getBasePtr(x, y); + do { + memcpy(dst, src, w * sizeof(uint16)); + src += pitch; + dst += _videoContext->overlayTexture.pitch; + } while (--h); +} + +int16 OSystem_iOS7::getOverlayHeight() { + return _videoContext->overlayHeight; +} + +int16 OSystem_iOS7::getOverlayWidth() { + return _videoContext->overlayWidth; +} + +bool OSystem_iOS7::showMouse(bool visible) { + //printf("showMouse(%d)\n", visible); + bool last = _videoContext->mouseIsVisible; + _videoContext->mouseIsVisible = visible; + _mouseDirty = true; + + return last; +} + +void OSystem_iOS7::warpMouse(int x, int y) { + //printf("warpMouse(%d, %d)\n", x, y); + _videoContext->mouseX = x; + _videoContext->mouseY = y; + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(notifyMouseMove) withObject:nil waitUntilDone: YES]; + _mouseDirty = true; +} + +void OSystem_iOS7::dirtyFullScreen() { + if (!_fullScreenIsDirty) { + _dirtyRects.clear(); + _dirtyRects.push_back(Common::Rect(0, 0, _videoContext->screenWidth, _videoContext->screenHeight)); + _fullScreenIsDirty = true; + } +} + +void OSystem_iOS7::dirtyFullOverlayScreen() { + if (!_fullScreenOverlayIsDirty) { + _dirtyOverlayRects.clear(); + _dirtyOverlayRects.push_back(Common::Rect(0, 0, _videoContext->overlayWidth, _videoContext->overlayHeight)); + _fullScreenOverlayIsDirty = true; + } +} + +void OSystem_iOS7::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) { + //printf("setMouseCursor(%p, %u, %u, %i, %i, %u, %d, %p)\n", (const void *)buf, w, h, hotspotX, hotspotY, keycolor, dontScale, (const void *)format); + + const Graphics::PixelFormat pixelFormat = format ? *format : Graphics::PixelFormat::createFormatCLUT8(); +#if 0 + printf("bytesPerPixel: %u RGBAlosses: %u,%u,%u,%u RGBAshifts: %u,%u,%u,%u\n", pixelFormat.bytesPerPixel, + pixelFormat.rLoss, pixelFormat.gLoss, pixelFormat.bLoss, pixelFormat.aLoss, + pixelFormat.rShift, pixelFormat.gShift, pixelFormat.bShift, pixelFormat.aShift); +#endif + assert(pixelFormat.bytesPerPixel == 1 || pixelFormat.bytesPerPixel == 2); + + if (_mouseBuffer.w != w || _mouseBuffer.h != h || _mouseBuffer.format != pixelFormat || !_mouseBuffer.getPixels()) + _mouseBuffer.create(w, h, pixelFormat); + + _videoContext->mouseWidth = w; + _videoContext->mouseHeight = h; + + _videoContext->mouseHotspotX = hotspotX; + _videoContext->mouseHotspotY = hotspotY; + + _mouseKeyColor = keycolor; + + memcpy(_mouseBuffer.getPixels(), buf, h * _mouseBuffer.pitch); + + _mouseDirty = true; + _mouseNeedTextureUpdate = true; +} + +void OSystem_iOS7::setCursorPalette(const byte *colors, uint start, uint num) { + //printf("setCursorPalette(%p, %u, %u)\n", (const void *)colors, start, num); + assert(start + num <= 256); + + for (uint i = start; i < start + num; ++i, colors += 3) + _mouseCursorPalette[i] = Graphics::RGBToColor<Graphics::ColorMasks<5551> >(colors[0], colors[1], colors[2]); + + // FIXME: This is just stupid, our client code seems to assume that this + // automatically enables the cursor palette. + _mouseCursorPaletteEnabled = true; + + if (_mouseCursorPaletteEnabled) + _mouseDirty = _mouseNeedTextureUpdate = true; +} + +void OSystem_iOS7::updateMouseTexture() { + uint texWidth = getSizeNextPOT(_videoContext->mouseWidth); + uint texHeight = getSizeNextPOT(_videoContext->mouseHeight); + + Graphics::Surface &mouseTexture = _videoContext->mouseTexture; + if (mouseTexture.w != texWidth || mouseTexture.h != texHeight) + mouseTexture.create(texWidth, texHeight, Graphics::createPixelFormat<5551>()); + + if (_mouseBuffer.format.bytesPerPixel == 1) { + const uint16 *palette; + if (_mouseCursorPaletteEnabled) + palette = _mouseCursorPalette; + else + palette = _gamePaletteRGBA5551; + + uint16 *mouseBuf = (uint16 *)mouseTexture.getPixels(); + for (uint x = 0; x < _videoContext->mouseWidth; ++x) { + for (uint y = 0; y < _videoContext->mouseHeight; ++y) { + const byte color = *(const byte *)_mouseBuffer.getBasePtr(x, y); + if (color != _mouseKeyColor) + mouseBuf[y * texWidth + x] = palette[color] | 0x1; + else + mouseBuf[y * texWidth + x] = 0x0; + } + } + } else { + if (crossBlit((byte *)mouseTexture.getPixels(), (const byte *)_mouseBuffer.getPixels(), mouseTexture.pitch, + _mouseBuffer.pitch, _mouseBuffer.w, _mouseBuffer.h, mouseTexture.format, _mouseBuffer.format)) { + if (!_mouseBuffer.format.aBits()) { + // Apply color keying since the original cursor had no alpha channel. + const uint16 *src = (const uint16 *)_mouseBuffer.getPixels(); + uint8 *dstRaw = (uint8 *)mouseTexture.getPixels(); + + for (uint y = 0; y < _mouseBuffer.h; ++y, dstRaw += mouseTexture.pitch) { + uint16 *dst = (uint16 *)dstRaw; + for (uint x = 0; x < _mouseBuffer.w; ++x, ++dst) { + if (*src++ == _mouseKeyColor) + *dst &= ~1; + else + *dst |= 1; + } + } + } + } else { + // TODO: Log this! + // Make the cursor all transparent... we really need a better fallback ;-). + memset(mouseTexture.getPixels(), 0, mouseTexture.h * mouseTexture.pitch); + } + } + + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(updateMouseCursor) withObject:nil waitUntilDone: YES]; +} diff --git a/backends/platform/ios7/ios7_scummvm_view_controller.h b/backends/platform/ios7/ios7_scummvm_view_controller.h new file mode 100644 index 0000000000..39168aaee7 --- /dev/null +++ b/backends/platform/ios7/ios7_scummvm_view_controller.h @@ -0,0 +1,33 @@ +/* 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 BACKENDS_PLATFORM_IOS7_IOS7_SCUMMVM_VIEW_CONTROLLER_H +#define BACKENDS_PLATFORM_IOS7_IOS7_SCUMMVM_VIEW_CONTROLLER_H + +#include <UIKit/UIKit.h> + + +@interface iOS7ScummVMViewController : UIViewController + +@end + +#endif diff --git a/backends/platform/ios7/ios7_scummvm_view_controller.mm b/backends/platform/ios7/ios7_scummvm_view_controller.mm new file mode 100644 index 0000000000..e78fc91f93 --- /dev/null +++ b/backends/platform/ios7/ios7_scummvm_view_controller.mm @@ -0,0 +1,32 @@ +/* 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 "backends/platform/ios7/ios7_scummvm_view_controller.h" + + +@implementation iOS7ScummVMViewController + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +@end diff --git a/backends/platform/ios7/ios7_video.h b/backends/platform/ios7/ios7_video.h new file mode 100644 index 0000000000..9c5d92a970 --- /dev/null +++ b/backends/platform/ios7/ios7_video.h @@ -0,0 +1,131 @@ +/* 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 BACKENDS_PLATFORM_IOS7_IOS7_VIDEO_H +#define BACKENDS_PLATFORM_IOS7_IOS7_VIDEO_H + +#include <UIKit/UIKit.h> +#include <Foundation/Foundation.h> +#include <QuartzCore/QuartzCore.h> + +#include <OpenGLES/EAGL.h> +#include <OpenGLES/ES2/gl.h> +#include <OpenGLES/ES2/glext.h> + +#include "backends/platform/ios7/ios7_keyboard.h" +#include "backends/platform/ios7/ios7_common.h" + +#include "common/list.h" +#include "graphics/scaler.h" + +typedef struct { + GLfloat x, y; + GLfloat u,v; +} GLVertex; + +@interface iPhoneView : UIView { + VideoContext _videoContext; + + Common::List<InternalEvent> _events; + NSLock *_eventLock; + SoftKeyboard *_keyboardView; + + EAGLContext *_context; + GLuint _viewRenderbuffer; + GLuint _viewFramebuffer; + GLuint _screenTexture; + GLuint _overlayTexture; + GLuint _mouseCursorTexture; + + GLuint _vertexShader; + GLuint _fragmentShader; + + GLuint _vertexBuffer; + + GLuint _screenSizeSlot; + GLuint _textureSlot; + GLuint _shakeSlot; + + GLuint _positionSlot; + GLuint _textureCoordSlot; + + GLint _renderBufferWidth; + GLint _renderBufferHeight; + + GLVertex _gameScreenCoords[4]; + CGRect _gameScreenRect; + + GLVertex _overlayCoords[4]; + CGRect _overlayRect; + + GLVertex _mouseCoords[4]; + + GLint _mouseHotspotX, _mouseHotspotY; + GLint _mouseWidth, _mouseHeight; + GLfloat _mouseScaleX, _mouseScaleY; + + int _scaledShakeOffsetY; + + UITouch *_firstTouch; + UITouch *_secondTouch; + +#ifdef ENABLE_IOS7_SCALERS + uint8_t *_scalerMemorySrc; + uint8_t *_scalerMemoryDst; + size_t _scalerMemorySrcSize; + size_t _scalerMemoryDstSize; + int _scalerScale; + ScalerProc *_scaler; +#endif +} + +- (id)initWithFrame:(struct CGRect)frame; + +- (VideoContext *)getVideoContext; + +- (void)createScreenTexture; +- (void)initSurface; +- (void)setViewTransformation; + +- (void)setGraphicsMode; + +- (void)updateSurface; +- (void)updateMainSurface; +- (void)updateOverlaySurface; +- (void)updateMouseSurface; +- (void)clearColorBuffer; + +- (void)notifyMouseMove; +- (void)updateMouseCursorScaling; +- (void)updateMouseCursor; + +- (void)deviceOrientationChanged:(UIDeviceOrientation)orientation; + +- (void)applicationSuspend; + +- (void)applicationResume; + +- (bool)fetchEvent:(InternalEvent *)event; + +@end + +#endif diff --git a/backends/platform/ios7/ios7_video.mm b/backends/platform/ios7/ios7_video.mm new file mode 100644 index 0000000000..5c0434d43e --- /dev/null +++ b/backends/platform/ios7/ios7_video.mm @@ -0,0 +1,1002 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/platform/ios7/ios7_video.h" + +#include "graphics/colormasks.h" +#include "common/system.h" +#include "backends/platform/ios7/ios7_app_delegate.h" + +static int g_needsScreenUpdate = 0; + +#if 0 +static long g_lastTick = 0; +static int g_frames = 0; +#endif + +#define printOpenGLError() printOglError(__FILE__, __LINE__) + +int printOglError(const char *file, int line) { + int retCode = 0; + + // returns 1 if an OpenGL error occurred, 0 otherwise. + GLenum glErr = glGetError(); + while (glErr != GL_NO_ERROR) { + fprintf(stderr, "glError: %u (%s: %d)\n", glErr, file, line); + retCode = 1; + glErr = glGetError(); + } + return retCode; +} + +bool iOS7_isBigDevice() { + return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad; +} + +void iOS7_updateScreen() { + //printf("Mouse: (%i, %i)\n", mouseX, mouseY); + if (!g_needsScreenUpdate) { + g_needsScreenUpdate = 1; + [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(updateSurface) withObject:nil waitUntilDone: NO]; + } +} + +bool iOS7_fetchEvent(InternalEvent *event) { + return [[iOS7AppDelegate iPhoneView] fetchEvent:event]; +} + +uint getSizeNextPOT(uint size) { + if ((size & (size - 1)) || !size) { + int log = 0; + + while (size >>= 1) + ++log; + + size = (2 << log); + } + + return size; +} + +@implementation iPhoneView + ++ (Class)layerClass { + return [CAEAGLLayer class]; +} + +- (VideoContext *)getVideoContext { + return &_videoContext; +} + +- (void)createContext { + CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; + + eaglLayer.opaque = YES; + eaglLayer.drawableProperties = @{ + kEAGLDrawablePropertyRetainedBacking: @NO, + kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGB565 + }; + + _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + + // In case creating the OpenGL ES context failed, we will error out here. + if (_context == nil) { + fprintf(stderr, "Could not create OpenGL ES context\n"); + exit(-1); + } + + if ([EAGLContext setCurrentContext:_context]) { + // glEnableClientState(GL_TEXTURE_COORD_ARRAY); printOpenGLError(); + // glEnableClientState(GL_VERTEX_ARRAY); printOpenGLError(); + [self setupOpenGL]; + } +} + +- (void)setupOpenGL { + [self setupFramebuffer]; + [self createOverlaySurface]; + [self compileShaders]; + [self setupVBOs]; + [self setupTextures]; + + [self finishGLSetup]; +} + +- (void)finishGLSetup { + glViewport(0, 0, _renderBufferWidth, _renderBufferHeight); printOpenGLError(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); printOpenGLError(); + + glUniform2f(_screenSizeSlot, _renderBufferWidth, _renderBufferHeight); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +- (void)freeOpenGL { + [self deleteTextures]; + [self deleteVBOs]; + [self deleteShaders]; + [self deleteFramebuffer]; +} + +- (void)rebuildFrameBuffer { + [self deleteFramebuffer]; + [self setupFramebuffer]; + [self finishGLSetup]; +} + +- (void)setupFramebuffer { + glGenRenderbuffers(1, &_viewRenderbuffer); + printOpenGLError(); + glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer); + printOpenGLError(); + [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id <EAGLDrawable>) self.layer]; + + glGenFramebuffers(1, &_viewFramebuffer); + printOpenGLError(); + glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer); + printOpenGLError(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _viewRenderbuffer); + printOpenGLError(); + + // Retrieve the render buffer size. This *should* match the frame size, + // i.e. g_fullWidth and g_fullHeight. + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_renderBufferWidth); + printOpenGLError(); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_renderBufferHeight); + printOpenGLError(); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + NSLog(@"Failed to make complete framebuffer object %x.", glCheckFramebufferStatus(GL_FRAMEBUFFER)); + return; + } +} + +- (void)createOverlaySurface { + uint overlayWidth = (uint) MAX(_renderBufferWidth, _renderBufferHeight); + uint overlayHeight = (uint) MIN(_renderBufferWidth, _renderBufferHeight); + + if (iOS7_isBigDevice()) { + // On really big displays, like the iPad Pro, we scale the interface down + // so that the controls are not too small.. + while (overlayHeight > 1024) { + overlayWidth /= 2; + overlayHeight /= 2; + } + } + else { + // On small devices, we force the user interface to use the small theme + while (overlayHeight > 480) { + overlayWidth /= 2; + overlayHeight /= 2; + } + } + + _videoContext.overlayWidth = overlayWidth; + _videoContext.overlayHeight = overlayHeight; + + uint overlayTextureWidthPOT = getSizeNextPOT(overlayWidth); + uint overlayTextureHeightPOT = getSizeNextPOT(overlayHeight); + + // Since the overlay size won't change the whole run, we can + // precalculate the texture coordinates for the overlay texture here + // and just use it later on. + GLfloat u = _videoContext.overlayWidth / (GLfloat) overlayTextureWidthPOT; + GLfloat v = _videoContext.overlayHeight / (GLfloat) overlayTextureHeightPOT; + _overlayCoords[0].x = 0; _overlayCoords[0].y = 0; _overlayCoords[0].u = 0; _overlayCoords[0].v = 0; + _overlayCoords[1].x = 0; _overlayCoords[1].y = 0; _overlayCoords[1].u = u; _overlayCoords[1].v = 0; + _overlayCoords[2].x = 0; _overlayCoords[2].y = 0; _overlayCoords[2].u = 0; _overlayCoords[2].v = v; + _overlayCoords[3].x = 0; _overlayCoords[3].y = 0; _overlayCoords[3].u = u; _overlayCoords[3].v = v; + + _videoContext.overlayTexture.create((uint16) overlayTextureWidthPOT, (uint16) overlayTextureHeightPOT, Graphics::createPixelFormat<5551>()); +} + +- (void)deleteFramebuffer { + glDeleteRenderbuffers(1, &_viewRenderbuffer); + glDeleteFramebuffers(1, &_viewFramebuffer); +} + +- (void)setupVBOs { + glGenBuffers(1, &_vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); +} + +- (void)deleteVBOs { + glDeleteBuffers(1, &_vertexBuffer); +} + +- (GLuint)compileShader:(const char*)shaderPrg withType:(GLenum)shaderType { + GLuint shaderHandle = glCreateShader(shaderType); + + int shaderPrgLength = strlen(shaderPrg); + glShaderSource(shaderHandle, 1, &shaderPrg, &shaderPrgLength); + + glCompileShader(shaderHandle); + + GLint compileSuccess; + glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); + if (compileSuccess == GL_FALSE) { + GLchar messages[256]; + glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); + NSString *messageString = [NSString stringWithUTF8String:messages]; + NSLog(@"%@", messageString); + exit(1); + } + + return shaderHandle; +} + +- (void)compileShaders { + const char *vertexPrg = + "uniform vec2 ScreenSize;" + "uniform float Shake;" + "" + "attribute vec2 Position;" + "attribute vec2 TexCoord;" + "" + "varying vec4 DestColor;" + "varying vec2 o_TexCoord;" + "" + "void main(void) {" + " DestColor = vec4(Position.x, Position.y, 0, 1);" + " o_TexCoord = TexCoord;" + " gl_Position = vec4((Position.x / ScreenSize.x) * 2.0 - 1.0, (1.0 - (Position.y + Shake) / ScreenSize.y) * 2.0 - 1.0, 0, 1);" + "}"; + + const char *fragmentPrg = + "uniform sampler2D Texture;" + "" + "varying lowp vec4 DestColor;" + "varying lowp vec2 o_TexCoord;" + "" + "void main(void) {" + " gl_FragColor = texture2D(Texture, o_TexCoord);" + "}"; + + _vertexShader = [self compileShader:vertexPrg withType:GL_VERTEX_SHADER]; + _fragmentShader = [self compileShader:fragmentPrg withType:GL_FRAGMENT_SHADER]; + + GLuint programHandle = glCreateProgram(); + glAttachShader(programHandle, _vertexShader); + glAttachShader(programHandle, _fragmentShader); + glLinkProgram(programHandle); + + GLint linkSuccess; + glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); + if (linkSuccess == GL_FALSE) { + printOpenGLError(); + exit(1); + } + + glUseProgram(programHandle); + + _screenSizeSlot = (GLuint) glGetUniformLocation(programHandle, "ScreenSize"); + _textureSlot = (GLuint) glGetUniformLocation(programHandle, "Texture"); + _shakeSlot = (GLuint) glGetUniformLocation(programHandle, "Shake"); + + _positionSlot = (GLuint) glGetAttribLocation(programHandle, "Position"); + _textureCoordSlot = (GLuint) glGetAttribLocation(programHandle, "TexCoord"); + + glEnableVertexAttribArray(_positionSlot); + glEnableVertexAttribArray(_textureCoordSlot); + + glUniform1i(_textureSlot, 0); printOpenGLError(); +} + +- (void)deleteShaders { + glDeleteShader(_vertexShader); + glDeleteShader(_fragmentShader); +} + +- (void)setupTextures { + glGenTextures(1, &_screenTexture); printOpenGLError(); + glGenTextures(1, &_overlayTexture); printOpenGLError(); + glGenTextures(1, &_mouseCursorTexture); printOpenGLError(); + + [self setGraphicsMode]; +} + +- (void)deleteTextures { + if (_screenTexture) { + glDeleteTextures(1, &_screenTexture); printOpenGLError(); + _screenTexture = 0; + } + if (_overlayTexture) { + glDeleteTextures(1, &_overlayTexture); printOpenGLError(); + _overlayTexture = 0; + } + if (_mouseCursorTexture) { + glDeleteTextures(1, &_mouseCursorTexture); printOpenGLError(); + _mouseCursorTexture = 0; + } +} + +- (void)setupGestureRecognizers { + UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeRight:)]; + swipeRight.direction = UISwipeGestureRecognizerDirectionRight; + swipeRight.numberOfTouchesRequired = 2; + swipeRight.delaysTouchesBegan = NO; + swipeRight.delaysTouchesEnded = NO; + + UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeLeft:)]; + swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft; + swipeLeft.numberOfTouchesRequired = 2; + swipeLeft.delaysTouchesBegan = NO; + swipeLeft.delaysTouchesEnded = NO; + + UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeUp:)]; + swipeUp.direction = UISwipeGestureRecognizerDirectionUp; + swipeUp.numberOfTouchesRequired = 2; + swipeUp.delaysTouchesBegan = NO; + swipeUp.delaysTouchesEnded = NO; + + UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeDown:)]; + swipeDown.direction = UISwipeGestureRecognizerDirectionDown; + swipeDown.numberOfTouchesRequired = 2; + swipeDown.delaysTouchesBegan = NO; + swipeDown.delaysTouchesEnded = NO; + + UITapGestureRecognizer *doubleTapTwoFingers = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersDoubleTap:)]; + doubleTapTwoFingers.numberOfTapsRequired = 2; + doubleTapTwoFingers.numberOfTouchesRequired = 2; + doubleTapTwoFingers.delaysTouchesBegan = NO; + doubleTapTwoFingers.delaysTouchesEnded = NO; + + [self addGestureRecognizer:swipeRight]; + [self addGestureRecognizer:swipeLeft]; + [self addGestureRecognizer:swipeUp]; + [self addGestureRecognizer:swipeDown]; + [self addGestureRecognizer:doubleTapTwoFingers]; + + [swipeRight release]; + [swipeLeft release]; + [swipeUp release]; + [swipeDown release]; + [doubleTapTwoFingers release]; +} + +- (id)initWithFrame:(struct CGRect)frame { + self = [super initWithFrame: frame]; + +#if defined(USE_SCALERS) || defined(USE_HQ_SCALERS) + InitScalers(565); +#endif + + [self setupGestureRecognizers]; + + [self setContentScaleFactor:[[UIScreen mainScreen] scale]]; + +#ifdef ENABLE_IOS7_SCALERS + _scalerMemorySrc = NULL; + _scalerMemoryDst = NULL; + _scalerMemorySrcSize = 0; + _scalerMemoryDstSize = 0; + _scaler = NULL; + _scalerScale = 1; +#endif + + _keyboardView = nil; + _screenTexture = 0; + _overlayTexture = 0; + _mouseCursorTexture = 0; + + _scaledShakeOffsetY = 0; + + _firstTouch = NULL; + _secondTouch = NULL; + + _eventLock = [[NSLock alloc] init]; + + memset(_gameScreenCoords, 0, sizeof(GLVertex) * 4); + memset(_overlayCoords, 0, sizeof(GLVertex) * 4); + memset(_mouseCoords, 0, sizeof(GLVertex) * 4); + + // Initialize the OpenGL ES context + [self createContext]; + + return self; +} + +- (void)dealloc { + [_keyboardView release]; + + _videoContext.screenTexture.free(); + _videoContext.overlayTexture.free(); + _videoContext.mouseTexture.free(); + +#ifdef ENABLE_IOS7_SCALERS + free(_scalerMemorySrc); + free(_scalerMemoryDst); +#endif + + [_eventLock release]; + [super dealloc]; +} + +- (void)setFilterModeForTexture:(GLuint)tex { + if (!tex) + return; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex); printOpenGLError(); + + GLint filter = GL_LINEAR; + + switch (_videoContext.graphicsMode) { + case kGraphicsModeNone: + filter = GL_NEAREST; + break; + + case kGraphicsModeLinear: + case kGraphicsMode2xSaI: + case kGraphicsModeSuper2xSaI: + case kGraphicsModeSuperEagle: + case kGraphicsModeAdvMame2x: + case kGraphicsModeAdvMame3x: + case kGraphicsModeHQ2x: + case kGraphicsModeHQ3x: + case kGraphicsModeTV2x: + case kGraphicsModeDotMatrix: + filter = GL_LINEAR; + break; + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); printOpenGLError(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); printOpenGLError(); + // We use GL_CLAMP_TO_EDGE here to avoid artifacts when linear filtering + // is used. If we would not use this for example the cursor in Loom would + // have a line/border artifact on the right side of the covered rect. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); printOpenGLError(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); printOpenGLError(); +} + +#ifdef ENABLE_IOS7_SCALERS +- (void)setScaler { + ScalerProc *scaler = NULL; + int scalerScale = 1; + + switch (_videoContext.graphicsMode) { + case kGraphicsModeLinear: + break; + + case kGraphicsModeNone: + break; +#ifdef USE_SCALERS + case kGraphicsMode2xSaI: + scaler = _2xSaI; + scalerScale = 2; + break; + + case kGraphicsModeSuper2xSaI: + scaler = Super2xSaI; + scalerScale = 2; + break; + + case kGraphicsModeSuperEagle: + scaler = SuperEagle; + scalerScale = 2; + break; + + case kGraphicsModeAdvMame2x: + scaler = AdvMame2x; + scalerScale = 2; + break; + + case kGraphicsModeAdvMame3x: + scaler = AdvMame3x; + scalerScale = 3; + break; + +#ifdef USE_HQ_SCALERS + case kGraphicsModeHQ2x: + scaler = HQ2x; + scalerScale = 2; + break; + + case kGraphicsModeHQ3x: + scaler = HQ3x; + scalerScale = 3; + break; +#endif + + case kGraphicsModeTV2x: + scaler = TV2x; + scalerScale = 2; + break; + + case kGraphicsModeDotMatrix: + scaler = DotMatrix; + scalerScale = 2; + break; +#endif + + default: + break; + } + + _scaler = scaler; + _scalerScale = scalerScale; +} +#endif + +- (void)setGraphicsMode { + [self setFilterModeForTexture:_screenTexture]; + [self setFilterModeForTexture:_overlayTexture]; + [self setFilterModeForTexture:_mouseCursorTexture]; +#ifdef ENABLE_IOS7_SCALERS + [self setScaler]; +#endif +} + +- (void)updateSurface { + if (!g_needsScreenUpdate) { + return; + } + g_needsScreenUpdate = 0; + + glClear(GL_COLOR_BUFFER_BIT); printOpenGLError(); + + [self updateMainSurface]; + + if (_videoContext.overlayVisible) + [self updateOverlaySurface]; + + if (_videoContext.mouseIsVisible) + [self updateMouseSurface]; + + [_context presentRenderbuffer:GL_RENDERBUFFER]; + glFinish(); +} + +- (void)notifyMouseMove { + const GLint mouseX = (GLint)(_videoContext.mouseX * _mouseScaleX) - _mouseHotspotX; + const GLint mouseY = (GLint)(_videoContext.mouseY * _mouseScaleY) - _mouseHotspotY; + + _mouseCoords[0].x = _mouseCoords[2].x = mouseX; + _mouseCoords[0].y = _mouseCoords[1].y = mouseY; + _mouseCoords[1].x = _mouseCoords[3].x = mouseX + _mouseWidth; + _mouseCoords[2].y = _mouseCoords[3].y = mouseY + _mouseHeight; +} + +- (void)updateMouseCursorScaling { + CGRect *rect; + int maxWidth, maxHeight; + + if (!_videoContext.overlayVisible) { + rect = &_gameScreenRect; + maxWidth = _videoContext.screenWidth; + maxHeight = _videoContext.screenHeight; + } else { + rect = &_overlayRect; + maxWidth = _videoContext.overlayWidth; + maxHeight = _videoContext.overlayHeight; + } + + if (!maxWidth || !maxHeight) { + printf("WARNING: updateMouseCursorScaling called when screen was not ready (%d)!\n", _videoContext.overlayVisible); + return; + } + + _mouseScaleX = CGRectGetWidth(*rect) / (GLfloat)maxWidth; + _mouseScaleY = CGRectGetHeight(*rect) / (GLfloat)maxHeight; + + _mouseWidth = (GLint)(_videoContext.mouseWidth * _mouseScaleX); + _mouseHeight = (GLint)(_videoContext.mouseHeight * _mouseScaleY); + + _mouseHotspotX = (GLint)(_videoContext.mouseHotspotX * _mouseScaleX); + _mouseHotspotY = (GLint)(_videoContext.mouseHotspotY * _mouseScaleY); + + // We subtract the screen offset to the hotspot here to simplify the + // screen offset handling in the mouse code. Note the subtraction here + // makes sure that the offset actually gets added to the mouse position, + // since the hotspot offset is substracted from the position. + _mouseHotspotX -= (GLint)CGRectGetMinX(*rect); + _mouseHotspotY -= (GLint)CGRectGetMinY(*rect); + + // FIXME: For now we also adapt the mouse position here. In reality we + // would be better off to also adjust the event position when switching + // from overlay to game screen or vica versa. + [self notifyMouseMove]; +} + +- (void)updateMouseCursor { + [self updateMouseCursorScaling]; + + _mouseCoords[1].u = _mouseCoords[3].u = (_videoContext.mouseWidth - 1) / (GLfloat)_videoContext.mouseTexture.w; + _mouseCoords[2].v = _mouseCoords[3].v = (_videoContext.mouseHeight - 1) / (GLfloat)_videoContext.mouseTexture.h; + + [self setFilterModeForTexture:_mouseCursorTexture]; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _videoContext.mouseTexture.w, _videoContext.mouseTexture.h, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _videoContext.mouseTexture.getPixels()); printOpenGLError(); +} + +- (void)updateMainSurface { + glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _gameScreenCoords, GL_STATIC_DRAW); + glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); + glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); + + [self setFilterModeForTexture:_screenTexture]; + + // Unfortunately we have to update the whole texture every frame, since glTexSubImage2D is actually slower in all cases + // due to the iPhone internals having to convert the whole texture back from its internal format when used. + // In the future we could use several tiled textures instead. +#ifdef ENABLE_IOS7_SCALERS + if (_scaler) { + size_t neededSrcMemorySize = (size_t) (_videoContext.screenTexture.pitch * (_videoContext.screenTexture.h + 4)); + size_t neededDstMemorySize = (size_t) (_videoContext.screenTexture.pitch * (_videoContext.screenTexture.h + 4) * _scalerScale * _scalerScale); + if (neededSrcMemorySize != _scalerMemorySrcSize) { + _scalerMemorySrc = (uint8_t *) realloc(_scalerMemorySrc, neededSrcMemorySize); + _scalerMemorySrcSize = neededSrcMemorySize; + } + if (neededDstMemorySize != _scalerMemoryDstSize) { + _scalerMemoryDst = (uint8_t *) realloc(_scalerMemoryDst, neededDstMemorySize); + _scalerMemoryDstSize = neededDstMemorySize; + } + + // Clear two lines before + memset(_scalerMemorySrc, 0, (size_t) (_videoContext.screenTexture.pitch * 2)); + // Copy original buffer + memcpy(_scalerMemorySrc + _videoContext.screenTexture.pitch * 2, _videoContext.screenTexture.getPixels(), _videoContext.screenTexture.pitch * _videoContext.screenTexture.h); + // Clear two lines after + memset(_scalerMemorySrc + _videoContext.screenTexture.pitch * (2 + _videoContext.screenTexture.h), 0, (size_t) (_videoContext.screenTexture.pitch * 2)); + // Apply scaler + _scaler(_scalerMemorySrc + _videoContext.screenTexture.pitch * 2, + _videoContext.screenTexture.pitch, + _scalerMemoryDst, + (uint32) (_videoContext.screenTexture.pitch * _scalerScale), + _videoContext.screenTexture.w, + _videoContext.screenTexture.h); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _videoContext.screenTexture.w * _scalerScale, _videoContext.screenTexture.h * _scalerScale, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, _scalerMemoryDst); printOpenGLError(); + } + else { +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _videoContext.screenTexture.w, _videoContext.screenTexture.h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, _videoContext.screenTexture.getPixels()); printOpenGLError(); +#ifdef ENABLE_IOS7_SCALERS + } +#endif + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); +} + +- (void)updateOverlaySurface { + glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _overlayCoords, GL_STATIC_DRAW); + glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); + glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); + + [self setFilterModeForTexture:_overlayTexture]; + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _videoContext.overlayTexture.w, _videoContext.overlayTexture.h, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _videoContext.overlayTexture.getPixels()); printOpenGLError(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); +} + +- (void)updateMouseSurface { + glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _mouseCoords, GL_STATIC_DRAW); + glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); + glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); + + glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError(); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); +} + +- (void)createScreenTexture { + const uint screenTexWidth = getSizeNextPOT(_videoContext.screenWidth); + const uint screenTexHeight = getSizeNextPOT(_videoContext.screenHeight); + + _gameScreenCoords[1].u = _gameScreenCoords[3].u = _videoContext.screenWidth / (GLfloat)screenTexWidth; + _gameScreenCoords[2].v = _gameScreenCoords[3].v = _videoContext.screenHeight / (GLfloat)screenTexHeight; + + _videoContext.screenTexture.create((uint16) screenTexWidth, (uint16) screenTexHeight, Graphics::createPixelFormat<565>()); +} + +- (void)initSurface { + if (_context) { + [self rebuildFrameBuffer]; + } + + BOOL isLandscape = (self.bounds.size.width > self.bounds.size.height); // UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]); + + int screenWidth, screenHeight; + if (isLandscape) { + screenWidth = MAX(_renderBufferWidth, _renderBufferHeight); + screenHeight = MIN(_renderBufferWidth, _renderBufferHeight); + } + else { + screenWidth = MIN(_renderBufferWidth, _renderBufferHeight); + screenHeight = MAX(_renderBufferWidth, _renderBufferHeight); + } + + glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer); printOpenGLError(); + + [self clearColorBuffer]; + + GLfloat adjustedWidth = _videoContext.screenWidth; + GLfloat adjustedHeight = _videoContext.screenHeight; + if (_videoContext.asprectRatioCorrection) { + if (_videoContext.screenWidth == 320 && _videoContext.screenHeight == 200) + adjustedHeight = 240; + else if (_videoContext.screenWidth == 640 && _videoContext.screenHeight == 400) + adjustedHeight = 480; + } + + float overlayPortraitRatio; + + if (isLandscape) { + GLfloat gameScreenRatio = adjustedWidth / adjustedHeight; + GLfloat screenRatio = (GLfloat)screenWidth / (GLfloat)screenHeight; + + // These are the width/height according to the portrait layout! + int rectWidth, rectHeight; + int xOffset, yOffset; + + if (gameScreenRatio < screenRatio) { + // When the game screen ratio is less than the screen ratio + // we need to scale the width, since the game screen was higher + // compared to the width than our output screen is. + rectWidth = (int)(screenHeight * gameScreenRatio); + rectHeight = screenHeight; + xOffset = (screenWidth - rectWidth) / 2; + yOffset = 0; + } else { + // When the game screen ratio is bigger than the screen ratio + // we need to scale the height, since the game screen was wider + // compared to the height than our output screen is. + rectWidth = screenWidth; + rectHeight = (int)(screenWidth / gameScreenRatio); + xOffset = 0; + yOffset = (screenHeight - rectHeight) / 2; + } + + [_keyboardView hideKeyboard]; + + //printf("Rect: %i, %i, %i, %i\n", xOffset, yOffset, rectWidth, rectHeight); + _gameScreenRect = CGRectMake(xOffset, yOffset, rectWidth, rectHeight); + overlayPortraitRatio = 1.0f; + } else { + GLfloat ratio = adjustedHeight / adjustedWidth; + int height = (int)(screenWidth * ratio); + //printf("Making rect (%u, %u)\n", screenWidth, height); + _gameScreenRect = CGRectMake(0, 0, screenWidth, height); + + CGRect keyFrame = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f); + if (_keyboardView == nil) { + _keyboardView = [[SoftKeyboard alloc] initWithFrame:keyFrame]; + [_keyboardView setInputDelegate:self]; + [self addSubview:[_keyboardView inputView]]; + [self addSubview: _keyboardView]; + } + + [_keyboardView showKeyboard]; + overlayPortraitRatio = (_videoContext.overlayHeight * ratio) / _videoContext.overlayWidth; + } + _overlayRect = CGRectMake(0, 0, screenWidth, screenHeight * overlayPortraitRatio); + + _gameScreenCoords[0].x = _gameScreenCoords[2].x = CGRectGetMinX(_gameScreenRect); + _gameScreenCoords[0].y = _gameScreenCoords[1].y = CGRectGetMinY(_gameScreenRect); + _gameScreenCoords[1].x = _gameScreenCoords[3].x = CGRectGetMaxX(_gameScreenRect); + _gameScreenCoords[2].y = _gameScreenCoords[3].y = CGRectGetMaxY(_gameScreenRect); + + _overlayCoords[1].x = _overlayCoords[3].x = CGRectGetMaxX(_overlayRect); + _overlayCoords[2].y = _overlayCoords[3].y = CGRectGetMaxY(_overlayRect); + + [self setViewTransformation]; + [self updateMouseCursorScaling]; +} + +- (void)setViewTransformation { + // Scale the shake offset according to the overlay size. We need this to + // adjust the overlay mouse click coordinates when an offset is set. + _scaledShakeOffsetY = (int)(_videoContext.shakeOffsetY / (GLfloat)_videoContext.screenHeight * CGRectGetHeight(_overlayRect)); + + glUniform1f(_shakeSlot, _scaledShakeOffsetY); +} + +- (void)clearColorBuffer { + // The color buffer is triple-buffered, so we clear it multiple times right away to avid doing any glClears later. + int clearCount = 5; + while (clearCount-- > 0) { + glClear(GL_COLOR_BUFFER_BIT); printOpenGLError(); + [_context presentRenderbuffer:GL_RENDERBUFFER]; + glFinish(); + } +} + +- (void)addEvent:(InternalEvent)event { + [_eventLock lock]; + _events.push_back(event); + [_eventLock unlock]; +} + +- (bool)fetchEvent:(InternalEvent *)event { + [_eventLock lock]; + if (_events.empty()) { + [_eventLock unlock]; + return false; + } + + *event = *_events.begin(); + _events.pop_front(); + [_eventLock unlock]; + return true; +} + +- (bool)getMouseCoords:(CGPoint)point eventX:(int *)x eventY:(int *)y { + // We scale the input according to our scale factor to get actual screen + // coordinates. + point.x *= self.contentScaleFactor; + point.y *= self.contentScaleFactor; + + CGRect *area; + int width, height, offsetY; + if (_videoContext.overlayVisible) { + area = &_overlayRect; + width = _videoContext.overlayWidth; + height = _videoContext.overlayHeight; + offsetY = _scaledShakeOffsetY; + } else { + area = &_gameScreenRect; + width = _videoContext.screenWidth; + height = _videoContext.screenHeight; + offsetY = _videoContext.shakeOffsetY; + } + + point.x = (point.x - CGRectGetMinX(*area)) / CGRectGetWidth(*area); + point.y = (point.y - CGRectGetMinY(*area)) / CGRectGetHeight(*area); + + *x = (int)(point.x * width); + // offsetY describes the translation of the screen in the upward direction, + // thus we need to add it here. + *y = (int)(point.y * height + offsetY); + + if (!iOS7_touchpadModeEnabled()) { + // Clip coordinates + if (*x < 0 || *x > width || *y < 0 || *y > height) + return false; + } + + return true; +} + +- (void)deviceOrientationChanged:(UIDeviceOrientation)orientation { + [self addEvent:InternalEvent(kInputOrientationChanged, orientation, 0)]; +} + +- (UITouch *)secondTouchOtherTouchThan:(UITouch *)touch in:(NSSet *)set { + NSArray *all = [set allObjects]; + for (UITouch *t in all) { + if (t != touch) { + return t; + } + } + return nil; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + int x, y; + + NSSet *allTouches = [event allTouches]; + if (allTouches.count == 1) { + _firstTouch = [allTouches anyObject]; + CGPoint point = [_firstTouch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) + return; + + [self addEvent:InternalEvent(kInputMouseDown, x, y)]; + } + else if (allTouches.count == 2) { + _secondTouch = [self secondTouchOtherTouchThan:_firstTouch in:allTouches]; + if (_secondTouch) { + CGPoint point = [_secondTouch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) + return; + + [self addEvent:InternalEvent(kInputMouseSecondDown, x, y)]; + } + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + int x, y; + + NSSet *allTouches = [event allTouches]; + for (UITouch *touch in allTouches) { + if (touch == _firstTouch) { + CGPoint point = [touch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) + return; + + [self addEvent:InternalEvent(kInputMouseDragged, x, y)]; + } else if (touch == _secondTouch) { + CGPoint point = [touch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) + return; + + [self addEvent:InternalEvent(kInputMouseSecondDragged, x, y)]; + } + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + int x, y; + + NSSet *allTouches = [event allTouches]; + if (allTouches.count == 1) { + UITouch *touch = [allTouches anyObject]; + CGPoint point = [touch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) { + return; + } + + [self addEvent:InternalEvent(kInputMouseUp, x, y)]; + } + else if (allTouches.count == 2) { + UITouch *touch = [[allTouches allObjects] objectAtIndex:1]; + CGPoint point = [touch locationInView:self]; + if (![self getMouseCoords:point eventX:&x eventY:&y]) + return; + + [self addEvent:InternalEvent(kInputMouseSecondUp, x, y)]; + } + _firstTouch = nil; + _secondTouch = nil; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + _firstTouch = nil; + _secondTouch = nil; +} + +- (void)twoFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer { + [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 2)]; +} + +- (void)twoFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer { + [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 2)]; +} + +- (void)twoFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer { + [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 2)]; +} + +- (void)twoFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer { + [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 2)]; +} + +- (void)twoFingersDoubleTap:(UITapGestureRecognizer *)recognizer { + [self addEvent:InternalEvent(kInputTap, kUIViewTapDouble, 2)]; +} + +- (void)handleKeyPress:(unichar)c { + [self addEvent:InternalEvent(kInputKeyPressed, c, 0)]; +} + +- (void)applicationSuspend { + [self addEvent:InternalEvent(kInputApplicationSuspended, 0, 0)]; +} + +- (void)applicationResume { + [self addEvent:InternalEvent(kInputApplicationResumed, 0, 0)]; +} + +@end diff --git a/backends/platform/ios7/module.mk b/backends/platform/ios7/module.mk new file mode 100644 index 0000000000..ad4f7fda5b --- /dev/null +++ b/backends/platform/ios7/module.mk @@ -0,0 +1,17 @@ +MODULE := backends/platform/ios7 + +MODULE_OBJS := \ + ios7_osys_main.o \ + ios7_osys_events.o \ + ios7_osys_sound.o \ + ios7_osys_video.o \ + ios7_main.o \ + ios7_video.o \ + ios7_keyboard.o \ + ios7_scummvm_view_controller.o \ + ios7_app_delegate.o + +# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. +MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) +OBJS := $(MODULE_OBJS) $(OBJS) +MODULE_DIRS += $(sort $(dir $(MODULE_OBJS))) diff --git a/backends/platform/iphone/iphone_keyboard.mm b/backends/platform/iphone/iphone_keyboard.mm index 39d68aff81..85ba3520f2 100644 --- a/backends/platform/iphone/iphone_keyboard.mm +++ b/backends/platform/iphone/iphone_keyboard.mm @@ -20,7 +20,7 @@ * */ -#include "iphone_keyboard.h" +#include "backends/platform/iphone/iphone_keyboard.h" @interface UITextInputTraits - (void)setAutocorrectionType:(int)type; diff --git a/backends/platform/iphone/iphone_main.mm b/backends/platform/iphone/iphone_main.mm index 3707f10a29..fc29615814 100644 --- a/backends/platform/iphone/iphone_main.mm +++ b/backends/platform/iphone/iphone_main.mm @@ -26,7 +26,7 @@ #include <UIKit/UIKit.h> #include <Foundation/NSThread.h> -#include "iphone_video.h" +#include "backends/platform/iphone/iphone_video.h" void iphone_main(int argc, char *argv[]); diff --git a/backends/platform/iphone/iphone_video.h b/backends/platform/iphone/iphone_video.h index 7dbf3c57ab..26c32183ce 100644 --- a/backends/platform/iphone/iphone_video.h +++ b/backends/platform/iphone/iphone_video.h @@ -31,8 +31,8 @@ #include <OpenGLES/ES1/gl.h> #include <OpenGLES/ES1/glext.h> -#include "iphone_keyboard.h" -#include "iphone_common.h" +#include "backends/platform/iphone/iphone_keyboard.h" +#include "backends/platform/iphone/iphone_common.h" #include "common/list.h" diff --git a/backends/platform/iphone/iphone_video.mm b/backends/platform/iphone/iphone_video.mm index 5048b57328..9e521de179 100644 --- a/backends/platform/iphone/iphone_video.mm +++ b/backends/platform/iphone/iphone_video.mm @@ -23,7 +23,7 @@ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL -#include "iphone_video.h" +#include "backends/platform/iphone/iphone_video.h" #include "graphics/colormasks.h" diff --git a/backends/platform/iphone/osys_events.cpp b/backends/platform/iphone/osys_events.cpp index 95ca25a2d2..617f6b8843 100644 --- a/backends/platform/iphone/osys_events.cpp +++ b/backends/platform/iphone/osys_events.cpp @@ -26,7 +26,7 @@ #include "gui/message.h" #include "common/translation.h" -#include "osys_main.h" +#include "backends/platform/iphone/osys_main.h" static const int kQueuedInputEventDelay = 50; diff --git a/backends/platform/iphone/osys_main.cpp b/backends/platform/iphone/osys_main.cpp index 0ce21b44c1..36b3482d7e 100644 --- a/backends/platform/iphone/osys_main.cpp +++ b/backends/platform/iphone/osys_main.cpp @@ -41,7 +41,7 @@ #include "audio/mixer.h" #include "audio/mixer_intern.h" -#include "osys_main.h" +#include "backends/platform/iphone/osys_main.h" const OSystem::GraphicsMode OSystem_IPHONE::s_supportedGraphicsModes[] = { @@ -90,7 +90,7 @@ int OSystem_IPHONE::timerHandler(int t) { } void OSystem_IPHONE::initBackend() { -#ifdef IPHONE_OFFICIAL +#ifdef IPHONE_SANDBOXED _savefileManager = new DefaultSaveFileManager(iPhone_getDocumentsDir()); #else _savefileManager = new DefaultSaveFileManager(SCUMMVM_SAVE_PATH); @@ -252,7 +252,7 @@ OSystem *OSystem_IPHONE_create() { } Common::String OSystem_IPHONE::getDefaultConfigFileName() { -#ifdef IPHONE_OFFICIAL +#ifdef IPHONE_SANDBOXED Common::String path = iPhone_getDocumentsDir(); path += "/Preferences"; return path; @@ -305,7 +305,7 @@ void iphone_main(int argc, char *argv[]) { //gDebugLevel = 10; } -#ifdef IPHONE_OFFICIAL +#ifdef IPHONE_SANDBOXED chdir(iPhone_getDocumentsDir()); #else system("mkdir " SCUMMVM_ROOT_PATH); diff --git a/backends/platform/iphone/osys_main.h b/backends/platform/iphone/osys_main.h index 0159eee1be..390566322c 100644 --- a/backends/platform/iphone/osys_main.h +++ b/backends/platform/iphone/osys_main.h @@ -24,7 +24,7 @@ #define BACKENDS_PLATFORM_IPHONE_OSYS_MAIN_H #include "graphics/surface.h" -#include "iphone_common.h" +#include "backends/platform/iphone/iphone_common.h" #include "backends/base-backend.h" #include "common/events.h" #include "audio/mixer_intern.h" diff --git a/backends/platform/iphone/osys_sound.cpp b/backends/platform/iphone/osys_sound.cpp index bfee06c6f2..34c1cbff34 100644 --- a/backends/platform/iphone/osys_sound.cpp +++ b/backends/platform/iphone/osys_sound.cpp @@ -23,7 +23,7 @@ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL -#include "osys_main.h" +#include "backends/platform/iphone/osys_main.h" void OSystem_IPHONE::AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB) { //printf("AQBufferCallback()\n"); diff --git a/backends/platform/iphone/osys_video.mm b/backends/platform/iphone/osys_video.mm index c76f432dda..fa5c729a1c 100644 --- a/backends/platform/iphone/osys_video.mm +++ b/backends/platform/iphone/osys_video.mm @@ -23,8 +23,8 @@ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL -#include "osys_main.h" -#include "iphone_video.h" +#include "backends/platform/iphone/osys_main.h" +#include "backends/platform/iphone/iphone_video.h" #include "graphics/conversion.h" diff --git a/backends/platform/linuxmoto/linuxmoto-sdl.cpp b/backends/platform/linuxmoto/linuxmoto-sdl.cpp index a0310079de..a2b527e6ce 100644 --- a/backends/platform/linuxmoto/linuxmoto-sdl.cpp +++ b/backends/platform/linuxmoto/linuxmoto-sdl.cpp @@ -31,7 +31,7 @@ void OSystem_LINUXMOTO::initBackend() { _eventSource = new LinuxmotoSdlEventSource(); if (_graphicsManager == 0) - _graphicsManager = new LinuxmotoSdlGraphicsManager(_eventSource); + _graphicsManager = new LinuxmotoSdlGraphicsManager(_eventSource, _window); // Call parent implementation of this method OSystem_POSIX::initBackend(); diff --git a/backends/platform/maemo/debian/changelog b/backends/platform/maemo/debian/changelog index 49e8de69d6..e4c5c58ccd 100644 --- a/backends/platform/maemo/debian/changelog +++ b/backends/platform/maemo/debian/changelog @@ -1,3 +1,21 @@ +scummvm (1.9.0~git) unstable; urgency=low + + * Development snapshot + + -- Tarek Soliman <tsoliman@scummvm.org> Fri, 26 Feb 2016 21:11:20 -0600 + +scummvm (1.8.1) unstable; urgency=low + + * 1.8.1 release + + -- Tarek Soliman <tsoliman@scummvm.org> Fri, 20 May 2016 20:05:11 -0500 + +scummvm (1.8.0) unstable; urgency=low + + * 1.8.0 release + + -- Tarek Soliman <tsoliman@scummvm.org> Fri, 26 Feb 2016 21:11:20 -0600 + scummvm (1.7.0) unstable; urgency=low * 1.7.0 release diff --git a/backends/platform/maemo/debian/rules b/backends/platform/maemo/debian/rules index 2aa7f339c9..0e72c8aa9a 100755 --- a/backends/platform/maemo/debian/rules +++ b/backends/platform/maemo/debian/rules @@ -6,8 +6,8 @@ build: scummvm scummvm: dh_testdir - ./configure --host=maemo - $(MAKE) + ./configure --host=maemo $(CONFIGURE_EXTRA_ARGS) + $(MAKE) $(MAKE_EXTRA_ARGS) clean: dh_testdir @@ -48,7 +48,19 @@ install: build install -m0644 backends/vkeybd/packs/vkeybd_default.zip debian/scummvm/opt/scummvm/share install -m0644 backends/vkeybd/packs/vkeybd_small.zip debian/scummvm/opt/scummvm/share # for optified version we can also add engine datafiles - install -m0644 dists/engine-data/drascula.dat dists/engine-data/hugo.dat dists/engine-data/kyra.dat dists/engine-data/lure.dat dists/engine-data/queen.tbl dists/engine-data/sky.cpt dists/engine-data/teenagent.dat dists/engine-data/tony.dat dists/engine-data/toon.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/access.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/drascula.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/hugo.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/kyra.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/lure.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/mort.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/neverhood.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/queen.tbl debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/sky.cpt debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/teenagent.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/tony.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/toon.dat debian/scummvm/opt/scummvm/share + install -m0644 dists/engine-data/wintermute.zip debian/scummvm/opt/scummvm/share install -m0644 -d debian/scummvm/usr/share/doc/scummvm install -m0644 AUTHORS COPYING COPYING.BSD COPYING.FREEFONT COPYING.LGPL COPYRIGHT NEWS README debian/scummvm/usr/share/doc/scummvm diff --git a/backends/platform/maemo/maemo.cpp b/backends/platform/maemo/maemo.cpp index e81a208f7b..dc1054940e 100644 --- a/backends/platform/maemo/maemo.cpp +++ b/backends/platform/maemo/maemo.cpp @@ -35,10 +35,6 @@ #include "common/textconsole.h" #include "common/translation.h" - -#include <SDL/SDL_syswm.h> -#include <X11/Xutil.h> - namespace Maemo { OSystem_SDL_Maemo::OSystem_SDL_Maemo() @@ -56,34 +52,43 @@ OSystem_SDL_Maemo::~OSystem_SDL_Maemo() { #ifdef ENABLE_KEYMAPPER static void registerDefaultKeyBindings(Common::KeymapperDefaultBindings *_keymapperDefaultBindings, Model _model) { - _keymapperDefaultBindings->setDefaultBinding("gui", "REM", "HOME"); - _keymapperDefaultBindings->setDefaultBinding("global", "REM", "HOME"); + _keymapperDefaultBindings->setDefaultBinding("gui", "REMP", "HOME"); + _keymapperDefaultBindings->setDefaultBinding("global", "REMP", "HOME"); if (_model.hasMenuKey && _model.hasHwKeyboard) { - _keymapperDefaultBindings->setDefaultBinding("gui", "FUL", "FULLSCREEN"); - _keymapperDefaultBindings->setDefaultBinding("global", "FUL", "FULLSCREEN"); + _keymapperDefaultBindings->setDefaultBinding("gui", "FULS", "FULLSCREEN"); + _keymapperDefaultBindings->setDefaultBinding("global", "FULS", "FULLSCREEN"); } if (_model.hasHwKeyboard) { - _keymapperDefaultBindings->setDefaultBinding("gui", "VIR", "C+ZOOMMINUS"); - _keymapperDefaultBindings->setDefaultBinding("global", "VIR", "C+ZOOMMINUS"); + _keymapperDefaultBindings->setDefaultBinding("gui", "VIRT", "C+ZOOMMINUS"); + _keymapperDefaultBindings->setDefaultBinding("global", "VIRT", "C+ZOOMMINUS"); } else { - _keymapperDefaultBindings->setDefaultBinding("gui", "VIR", "FULLSCREEN"); - _keymapperDefaultBindings->setDefaultBinding("global", "VIR", "FULLSCREEN"); + _keymapperDefaultBindings->setDefaultBinding("gui", "VIRT", "FULLSCREEN"); + _keymapperDefaultBindings->setDefaultBinding("global", "VIRT", "FULLSCREEN"); } if (_model.hasMenuKey ) - _keymapperDefaultBindings->setDefaultBinding("global", "MEN", "MENU"); + _keymapperDefaultBindings->setDefaultBinding("global", "MENU", "MENU"); else - _keymapperDefaultBindings->setDefaultBinding("global", "MEN", "S+C+M"); + _keymapperDefaultBindings->setDefaultBinding("global", "MENU", "S+C+M"); - _keymapperDefaultBindings->setDefaultBinding("gui", "CLO", "ESCAPE"); + _keymapperDefaultBindings->setDefaultBinding("gui", "CLOS", "ESCAPE"); - _keymapperDefaultBindings->setDefaultBinding("maemo", "RCL", "ZOOMPLUS"); - _keymapperDefaultBindings->setDefaultBinding("maemo", "CLK", "ZOOMMINUS"); + _keymapperDefaultBindings->setDefaultBinding("maemo", "RCLK", "ZOOMPLUS"); + _keymapperDefaultBindings->setDefaultBinding("maemo", "CLKM", "ZOOMMINUS"); } #endif +void OSystem_SDL_Maemo::init() { + // Use an iconless window for Maemo + // also N900 is hit by SDL_WM_SetIcon bug (window cannot receive input) + // http://bugzilla.libsdl.org/show_bug.cgi?id=586 + _window = new SdlIconlessWindow(); + + OSystem_POSIX::init(); +} + void OSystem_SDL_Maemo::initBackend() { ConfMan.registerDefault("fullscreen", true); ConfMan.registerDefault("aspect_ratio", true); @@ -93,7 +98,7 @@ void OSystem_SDL_Maemo::initBackend() { _eventSource = new MaemoSdlEventSource(); if (_graphicsManager == 0) - _graphicsManager = new MaemoSdlGraphicsManager(_eventSource); + _graphicsManager = new MaemoSdlGraphicsManager(_eventSource, _window); if (_eventObserver == 0) _eventObserver = new MaemoSdlEventObserver((MaemoSdlEventSource *)_eventSource); @@ -178,12 +183,6 @@ const Maemo::Model OSystem_SDL_Maemo::detectModel() { return *model; } -void OSystem_SDL_Maemo::setupIcon() { - // no Maemo version needs setupIcon - // also N900 is hit by SDL_WM_SetIcon bug (window cannot receive input) - // http://bugzilla.libsdl.org/show_bug.cgi?id=586 -} - #ifdef ENABLE_KEYMAPPER static const Common::KeyTableEntry maemoKeys[] = { // Function keys diff --git a/backends/platform/maemo/maemo.h b/backends/platform/maemo/maemo.h index 532a2de08c..6d6e09bee1 100644 --- a/backends/platform/maemo/maemo.h +++ b/backends/platform/maemo/maemo.h @@ -36,11 +36,11 @@ public: OSystem_SDL_Maemo(); ~OSystem_SDL_Maemo(); + virtual void init(); virtual void initBackend(); virtual void quit(); virtual void fatalError(); virtual void setWindowCaption(const char *caption); - virtual void setupIcon(); #ifdef ENABLE_KEYMAPPER virtual Common::HardwareInputSet *getHardwareInputSet(); virtual Common::Keymap *getGlobalKeymap(); diff --git a/backends/platform/n64/framfs_save_manager.h b/backends/platform/n64/framfs_save_manager.h index a066854aab..aa26942bdc 100644 --- a/backends/platform/n64/framfs_save_manager.h +++ b/backends/platform/n64/framfs_save_manager.h @@ -71,6 +71,9 @@ private: public: uint32 write(const void *buf, uint32 cnt); + virtual int32 pos() const { + return framfs_tell(fd); + } OutFRAMSave(const char *_filename) : fd(NULL) { fd = framfs_open(_filename, "w"); @@ -103,7 +106,7 @@ public: virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { OutFRAMSave *s = new OutFRAMSave(filename.c_str()); if (!s->err()) { - return compress ? Common::wrapCompressedWriteStream(s) : s; + return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s); } else { delete s; return 0; diff --git a/backends/platform/n64/pakfs_save_manager.h b/backends/platform/n64/pakfs_save_manager.h index ec66c80b73..31aa01444c 100644 --- a/backends/platform/n64/pakfs_save_manager.h +++ b/backends/platform/n64/pakfs_save_manager.h @@ -72,6 +72,10 @@ private: public: uint32 write(const void *buf, uint32 cnt); + virtual int32 pos() const { + return pakfs_tell(fd); + } + OutPAKSave(const char *_filename) : fd(NULL) { fd = pakfs_open(_filename, "w"); } @@ -104,7 +108,7 @@ public: virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { OutPAKSave *s = new OutPAKSave(filename.c_str()); if (!s->err()) { - return compress ? Common::wrapCompressedWriteStream(s) : s; + return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s); } else { delete s; return NULL; diff --git a/backends/platform/openpandora/op-backend.cpp b/backends/platform/openpandora/op-backend.cpp index abe288f5d7..e7975a6aa0 100644 --- a/backends/platform/openpandora/op-backend.cpp +++ b/backends/platform/openpandora/op-backend.cpp @@ -147,7 +147,7 @@ void OSystem_OP::initBackend() { // Create the graphics manager if (_graphicsManager == 0) { - _graphicsManager = new OPGraphicsManager(_eventSource); + _graphicsManager = new OPGraphicsManager(_eventSource, _window); } /* Pass to POSIX method to do the heavy lifting */ diff --git a/backends/platform/ps2/savefilemgr.cpp b/backends/platform/ps2/savefilemgr.cpp index 4fd2b1c72b..569d0e13e3 100644 --- a/backends/platform/ps2/savefilemgr.cpp +++ b/backends/platform/ps2/savefilemgr.cpp @@ -192,7 +192,7 @@ Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &fil } _screen->wantAnim(false); - return compress ? Common::wrapCompressedWriteStream(sf) : sf; + return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf); } bool Ps2SaveFileManager::removeSavefile(const Common::String &filename) { diff --git a/backends/platform/psp/README.PSP b/backends/platform/psp/README.PSP index d92202bf4e..9467d4b993 100644 --- a/backends/platform/psp/README.PSP +++ b/backends/platform/psp/README.PSP @@ -1,4 +1,4 @@ -ScummVM-PSP 1.8.0git README +ScummVM-PSP 1.9.0git README ============================================================================== Installation diff --git a/backends/platform/samsungtv/samsungtv.cpp b/backends/platform/samsungtv/samsungtv.cpp index a1d15930f2..a766916fab 100644 --- a/backends/platform/samsungtv/samsungtv.cpp +++ b/backends/platform/samsungtv/samsungtv.cpp @@ -40,7 +40,7 @@ void OSystem_SDL_SamsungTV::initBackend() { _eventSource = new SamsungTVSdlEventSource(); if (_graphicsManager == 0) - _graphicsManager = new SamsungTVSdlGraphicsManager(_eventSource); + _graphicsManager = new SamsungTVSdlGraphicsManager(_eventSource, _window); // Call parent implementation of this method OSystem_POSIX::initBackend(); diff --git a/backends/platform/sdl/amigaos/amigaos-main.cpp b/backends/platform/sdl/amigaos/amigaos-main.cpp index 65da6bbf85..7bbf8d1fff 100644 --- a/backends/platform/sdl/amigaos/amigaos-main.cpp +++ b/backends/platform/sdl/amigaos/amigaos-main.cpp @@ -24,13 +24,44 @@ #if defined(__amigaos4__) +#include "backends/fs/amigaos4/amigaos4-fs.h" #include "backends/platform/sdl/amigaos/amigaos.h" #include "backends/plugins/sdl/sdl-provider.h" #include "base/main.h" int main(int argc, char *argv[]) { - // Set up a stack cookie to avoid crashes due to too few stack set by users + // The following will gather the application name and add the install path + // to a variable in AmigaOS4's ENV(ARC) system. It will be placed in AppPaths + // so that ScummVM can become AmiUpdate aware + const char *const appname = "ScummVM"; + + BPTR lock; + APTR oldwin; + + // Obtain a lock to the home directory + if ((lock = IDOS->GetProgramDir())) { + TEXT progpath[2048]; + TEXT apppath[1024] = "AppPaths"; + + if (IDOS->DevNameFromLock(lock, + progpath, + sizeof(progpath), + DN_FULLPATH)) { + + // Stop any "Insert volume..." type requesters + oldwin = IDOS->SetProcWindow((APTR)-1); + + // Finally, set the variable to the path the executable was run from + IDOS->AddPart( apppath, appname, 1024); + IDOS->SetVar( apppath, progpath, -1, GVF_GLOBAL_ONLY|GVF_SAVE_VAR ); + + // Turn system requesters back on + IDOS->SetProcWindow( oldwin ); + } + } + + // Set up a stack cookie to avoid crashes from a stack set too low static const char *stack_cookie __attribute__((used)) = "$STACK: 600000"; // Create our OSystem instance @@ -44,7 +75,7 @@ int main(int argc, char *argv[]) { PluginManager::instance().addPluginProvider(new SDLPluginProvider()); #endif - // Invoke the actual ScummVM main entry point: + // Invoke the actual ScummVM main entry point int res = scummvm_main(argc, argv); // Free OSystem diff --git a/backends/platform/sdl/amigaos/amigaos.mk b/backends/platform/sdl/amigaos/amigaos.mk index 5cec9c1588..15a2e9f93f 100644 --- a/backends/platform/sdl/amigaos/amigaos.mk +++ b/backends/platform/sdl/amigaos/amigaos.mk @@ -10,4 +10,15 @@ amigaosdist: $(EXECUTABLE) ifdef DIST_FILES_ENGINEDATA cp $(DIST_FILES_ENGINEDATA) $(AMIGAOSPATH)/extras/ endif + cat ${srcdir}/README | sed -f ${srcdir}/dists/amiga/convertRM.sed > README.conv +# AmigaOS's shell is not happy with indented comments, thus don't do it. +# AREXX seems to have problems when ${srcdir} is '.'. It will break with a +# "Program not found" error. Therefore we copy the script to the cwd and +# remove it again, once it has finished. + cp ${srcdir}/dists/amiga/RM2AG.rx . + rx RM2AG.rx README.conv + cp README.guide $(AMIGAOSPATH) + rm RM2AG.rx + rm README.conv + rm README.guide cp $(DIST_FILES_DOCS) $(AMIGAOSPATH) diff --git a/backends/platform/sdl/macosx/appmenu_osx.mm b/backends/platform/sdl/macosx/appmenu_osx.mm index d083fb8483..feea40bc06 100644 --- a/backends/platform/sdl/macosx/appmenu_osx.mm +++ b/backends/platform/sdl/macosx/appmenu_osx.mm @@ -28,12 +28,22 @@ #include <Cocoa/Cocoa.h> -// Apple removed setAppleMenu from the header files in 10.4, -// but as the method still exists we declare it ourselves here. +// Apple added setAppleMenu in 10.5 and removed it in 10.6. +// But as the method still exists we declare it ourselves here. // Yes, this works :) @interface NSApplication(MissingFunction) - (void)setAppleMenu:(NSMenu *)menu; @end +// However maybe we should conditionally use it depending on the system on which we run ScummVM (and not +// the one on which we compile) to only do it on OS X 10.5. +// Here is the relevant bit from the release notes for 10.6: +// In Leopard and earlier, apps that tried to construct a menu bar without a nib would get an undesirable +// stubby application menu that could not be removed. To work around this problem on Leopard, you can call +// the undocumented setAppleMenu: method and pass it the application menu, like so: +// [NSApp setAppleMenu:[[[NSApp mainMenu] itemAtIndex:0] submenu]]; +// In SnowLeopard, this workaround is unnecessary and should not be used. Under SnowLeopard, the first menu +// is always identified as the application menu. + NSString *constructNSStringFromCString(const char *rawCString, CFStringEncoding stringEncoding) { return (NSString *)CFStringCreateWithCString(NULL, rawCString, stringEncoding); @@ -46,13 +56,14 @@ void replaceApplicationMenuItems() { NSMenu *windowMenu; NSMenuItem *menuItem; - // For some reason [[NSApp mainMenu] removeAllItems] doesn't work and crashes, so we need - // to remove the SDL generated menus one by one - [[NSApp mainMenu] removeItemAtIndex:0]; // Remove application menu - [[NSApp mainMenu] removeItemAtIndex:0]; // Remove "Windows" menu + // We cannot use [[NSApp mainMenu] removeAllItems] as removeAllItems was added in OS X 10.6 + // So remove the SDL generated menus one by one instead. + while ([[NSApp mainMenu] numberOfItems] > 0) { + [[NSApp mainMenu] removeItemAtIndex:0]; + } // Create new application menu - appleMenu = [[NSMenu alloc] initWithTitle:@""]; + appleMenu = [[NSMenu alloc] initWithTitle:@"ScummVM"]; NSString *nsString = NULL; diff --git a/backends/platform/sdl/macosx/macosx.cpp b/backends/platform/sdl/macosx/macosx.cpp index c48076c42f..7652c0d833 100644 --- a/backends/platform/sdl/macosx/macosx.cpp +++ b/backends/platform/sdl/macosx/macosx.cpp @@ -27,9 +27,10 @@ #ifdef MACOSX -#include "backends/platform/sdl/macosx/macosx.h" +#include "backends/audiocd/macosx/macosx-audiocd.h" #include "backends/mixer/doublebuffersdl/doublebuffersdl-mixer.h" #include "backends/platform/sdl/macosx/appmenu_osx.h" +#include "backends/platform/sdl/macosx/macosx.h" #include "backends/updates/macosx/macosx-updates.h" #include "backends/taskbar/macosx/macosx-taskbar.h" @@ -47,6 +48,9 @@ OSystem_MacOSX::OSystem_MacOSX() } void OSystem_MacOSX::init() { + // Use an iconless window on OS X, as we use a nicer external icon there. + _window = new SdlIconlessWindow(); + #if defined(USE_TASKBAR) // Initialize taskbar manager _taskbarManager = new MacOSXTaskbarManager(); @@ -101,10 +105,6 @@ void OSystem_MacOSX::addSysArchivesToSearchSet(Common::SearchSet &s, int priorit } } -void OSystem_MacOSX::setupIcon() { - // Don't set icon on OS X, as we use a nicer external icon there. -} - bool OSystem_MacOSX::hasFeature(Feature f) { if (f == kFeatureDisplayLogFile) return true; @@ -171,4 +171,8 @@ Common::String OSystem_MacOSX::getSystemLanguage() const { #endif // USE_DETECTLANG } +AudioCDManager *OSystem_MacOSX::createAudioCDManager() { + return createMacOSXAudioCDManager(); +} + #endif diff --git a/backends/platform/sdl/macosx/macosx.h b/backends/platform/sdl/macosx/macosx.h index 50cef60353..6905284a5f 100644 --- a/backends/platform/sdl/macosx/macosx.h +++ b/backends/platform/sdl/macosx/macosx.h @@ -38,7 +38,11 @@ public: virtual void init(); virtual void initBackend(); virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); - virtual void setupIcon(); + +protected: + // Override createAudioCDManager() to get our Mac-specific + // version. + virtual AudioCDManager *createAudioCDManager(); }; #endif diff --git a/backends/platform/sdl/module.mk b/backends/platform/sdl/module.mk index a17a326889..74dd506d31 100644 --- a/backends/platform/sdl/module.mk +++ b/backends/platform/sdl/module.mk @@ -1,7 +1,8 @@ MODULE := backends/platform/sdl MODULE_OBJS := \ - sdl.o + sdl.o \ + sdl-window.o ifdef POSIX MODULE_OBJS += \ @@ -19,6 +20,7 @@ endif ifdef WIN32 MODULE_OBJS += \ win32/win32-main.o \ + win32/win32-window.o \ win32/win32.o endif diff --git a/backends/platform/sdl/posix/posix-main.cpp b/backends/platform/sdl/posix/posix-main.cpp index d07db11b0c..5deebb0ae3 100644 --- a/backends/platform/sdl/posix/posix-main.cpp +++ b/backends/platform/sdl/posix/posix-main.cpp @@ -22,7 +22,7 @@ #include "common/scummsys.h" -#if defined(POSIX) && !defined(MACOSX) && !defined(SAMSUNGTV) && !defined(MAEMO) && !defined(WEBOS) && !defined(LINUXMOTO) && !defined(GPH_DEVICE) && !defined(GP2X) && !defined(DINGUX) && !defined(OPENPANDORA) && !defined(PLAYSTATION3) +#if defined(POSIX) && !defined(MACOSX) && !defined(SAMSUNGTV) && !defined(MAEMO) && !defined(WEBOS) && !defined(LINUXMOTO) && !defined(GPH_DEVICE) && !defined(GP2X) && !defined(DINGUX) && !defined(OPENPANDORA) && !defined(PLAYSTATION3) && !defined(ANDROIDSDL) #include "backends/platform/sdl/posix/posix.h" #include "backends/plugins/sdl/sdl-provider.h" diff --git a/backends/platform/sdl/posix/posix.cpp b/backends/platform/sdl/posix/posix.cpp index a711c3a96b..0d5f39736a 100644 --- a/backends/platform/sdl/posix/posix.cpp +++ b/backends/platform/sdl/posix/posix.cpp @@ -33,14 +33,18 @@ #include "backends/platform/sdl/posix/posix.h" #include "backends/saves/posix/posix-saves.h" #include "backends/fs/posix/posix-fs-factory.h" +#include "backends/fs/posix/posix-fs.h" #include "backends/taskbar/unity/unity-taskbar.h" +#ifdef USE_LINUXCD +#include "backends/audiocd/linux/linux-audiocd.h" +#endif + #include <errno.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> - OSystem_POSIX::OSystem_POSIX(Common::String baseConfigName) : _baseConfigName(baseConfigName) { @@ -82,11 +86,54 @@ bool OSystem_POSIX::hasFeature(Feature f) { Common::String OSystem_POSIX::getDefaultConfigFileName() { Common::String configFile; - // On POSIX type systems, by default we store the config file inside - // to the HOME directory of the user. - const char *home = getenv("HOME"); - if (home != NULL && (strlen(home) + 1 + _baseConfigName.size()) < MAXPATHLEN) { - configFile = Common::String::format("%s/%s", home, _baseConfigName.c_str()); + Common::String prefix; +#ifdef MACOSX + prefix = getenv("HOME"); +#elif !defined(SAMSUNGTV) + const char *envVar; + // Our old configuration file path for POSIX systems was ~/.scummvmrc. + // If that file exists, we still use it. + envVar = getenv("HOME"); + if (envVar && *envVar) { + configFile = envVar; + configFile += '/'; + configFile += ".scummvmrc"; + + if (configFile.size() < MAXPATHLEN) { + struct stat sb; + if (stat(configFile.c_str(), &sb) == 0) { + return configFile; + } + } + } + + // On POSIX systems we follow the XDG Base Directory Specification for + // where to store files. The version we based our code upon can be found + // over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html + envVar = getenv("XDG_CONFIG_HOME"); + if (!envVar || !*envVar) { + envVar = getenv("HOME"); + if (!envVar) { + return 0; + } + + if (Posix::assureDirectoryExists(".config", envVar)) { + prefix = envVar; + prefix += "/.config"; + } + } else { + prefix = envVar; + } + + if (!prefix.empty() && Posix::assureDirectoryExists("scummvm", prefix.c_str())) { + prefix += "/scummvm"; + } +#endif + + if (!prefix.empty() && (prefix.size() + 1 + _baseConfigName.size()) < MAXPATHLEN) { + configFile = prefix; + configFile += '/'; + configFile += _baseConfigName; } else { configFile = _baseConfigName; } @@ -94,63 +141,66 @@ Common::String OSystem_POSIX::getDefaultConfigFileName() { return configFile; } +void OSystem_POSIX::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { +#ifdef DATA_PATH + const char *snap = getenv("SNAP"); + if (snap) { + Common::String dataPath = Common::String(snap) + DATA_PATH; + Common::FSNode dataNode(dataPath); + if (dataNode.exists() && dataNode.isDirectory()) { + // This is the same priority which is used for the data path (below), + // but we insert this one first, so it will be searched first. + s.add(dataPath, new Common::FSDirectory(dataNode, 4), priority); + } + } +#endif + + // For now, we always add the data path, just in case SNAP doesn't make sense. + OSystem_SDL::addSysArchivesToSearchSet(s, priority); +} + Common::WriteStream *OSystem_POSIX::createLogFile() { // Start out by resetting _logFilePath, so that in case // of a failure, we know that no log file is open. _logFilePath.clear(); - const char *home = getenv("HOME"); - if (home == NULL) + const char *prefix = nullptr; + Common::String logFile; +#ifdef MACOSX + prefix = getenv("HOME"); + if (prefix == nullptr) { return 0; + } - Common::String logFile(home); -#ifdef MACOSX - logFile += "/Library"; -#else - logFile += "/.scummvm"; -#endif -#ifdef SAMSUNGTV + logFile = "Library/Logs"; +#elif SAMSUNGTV + prefix = nullptr; logFile = "/mtd_ram"; -#endif - - struct stat sb; - - // Check whether the dir exists - if (stat(logFile.c_str(), &sb) == -1) { - // The dir does not exist, or stat failed for some other reason. - if (errno != ENOENT) +#else + // On POSIX systems we follow the XDG Base Directory Specification for + // where to store files. The version we based our code upon can be found + // over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html + prefix = getenv("XDG_CACHE_HOME"); + if (prefix == nullptr || !*prefix) { + prefix = getenv("HOME"); + if (prefix == nullptr) { return 0; + } - // If the problem was that the path pointed to nothing, try - // to create the dir. - if (mkdir(logFile.c_str(), 0755) != 0) - return 0; - } else if (!S_ISDIR(sb.st_mode)) { - // Path is no directory. Oops - return 0; + logFile = ".cache/"; } -#ifdef MACOSX - logFile += "/Logs"; -#else - logFile += "/logs"; + logFile += "scummvm/logs"; #endif - // Check whether the dir exists - if (stat(logFile.c_str(), &sb) == -1) { - // The dir does not exist, or stat failed for some other reason. - if (errno != ENOENT) - return 0; - - // If the problem was that the path pointed to nothing, try - // to create the dir. - if (mkdir(logFile.c_str(), 0755) != 0) - return 0; - } else if (!S_ISDIR(sb.st_mode)) { - // Path is no directory. Oops + if (!Posix::assureDirectoryExists(logFile, prefix)) { return 0; } + if (prefix) { + logFile = Common::String::format("%s/%s", prefix, logFile.c_str()); + } + logFile += "/scummvm.log"; Common::FSNode file(logFile); @@ -212,4 +262,12 @@ bool OSystem_POSIX::displayLogFile() { } +AudioCDManager *OSystem_POSIX::createAudioCDManager() { +#ifdef USE_LINUXCD + return createLinuxAudioCDManager(); +#else + return OSystem_SDL::createAudioCDManager(); +#endif +} + #endif diff --git a/backends/platform/sdl/posix/posix.h b/backends/platform/sdl/posix/posix.h index 01a01528cd..050463c273 100644 --- a/backends/platform/sdl/posix/posix.h +++ b/backends/platform/sdl/posix/posix.h @@ -28,7 +28,7 @@ class OSystem_POSIX : public OSystem_SDL { public: // Let the subclasses be able to change _baseConfigName in the constructor - OSystem_POSIX(Common::String baseConfigName = ".scummvmrc"); + OSystem_POSIX(Common::String baseConfigName = "scummvm.ini"); virtual ~OSystem_POSIX() {} virtual bool hasFeature(Feature f); @@ -38,6 +38,8 @@ public: virtual void init(); virtual void initBackend(); + virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); + protected: /** * Base string for creating the default path and filename for the @@ -59,6 +61,8 @@ protected: virtual Common::String getDefaultConfigFileName(); virtual Common::WriteStream *createLogFile(); + + virtual AudioCDManager *createAudioCDManager(); }; #endif diff --git a/backends/platform/sdl/ps3/ps3.cpp b/backends/platform/sdl/ps3/ps3.cpp index f111379794..0bb8300014 100644 --- a/backends/platform/sdl/ps3/ps3.cpp +++ b/backends/platform/sdl/ps3/ps3.cpp @@ -31,7 +31,6 @@ #include "backends/saves/default/default-saves.h" #include "backends/fs/ps3/ps3-fs-factory.h" #include "backends/events/ps3sdl/ps3sdl-events.h" -#include "backends/mixer/sdl13/sdl13-mixer.h" #include <dirent.h> #include <sys/stat.h> @@ -68,14 +67,6 @@ void OSystem_PS3::initBackend() { if (_savefileManager == 0) _savefileManager = new DefaultSaveFileManager(PREFIX "/saves"); - // Create the mixer manager - if (_mixer == 0) { - _mixerManager = new Sdl13MixerManager(); - - // Setup and start mixer - _mixerManager->init(); - } - // Event source if (_eventSource == 0) _eventSource = new PS3SdlEventSource(); diff --git a/backends/platform/sdl/raspberrypi/README.RASPBERRYPI b/backends/platform/sdl/raspberrypi/README.RASPBERRYPI new file mode 100644 index 0000000000..ab0e674c31 --- /dev/null +++ b/backends/platform/sdl/raspberrypi/README.RASPBERRYPI @@ -0,0 +1,77 @@ +ScummVM-RASPBERRYPI README +============================================================================== + +Notes +============ + +This version of ScummVM uses SDL2 hardware accelerated graphics, be it +plain SDL2 which in turn uses dispmanx/gles2 or by using gles1 via an +SDL2-configured GLES1 context. + +Requirements +============ +- Raspberry Pi 1 or 2 microcomputer. +- Raspbian (Debian) installed on SD card. Other distros may be supported if + they include the VideoCore runtime libraries that Raspbian includes. +-An attached keyboard and mouse, or alternatively joystick. + +Controls +============ + +The standard ScummVM keyboard and mouse controls are used as in any other +GNU/Linux based system. +Use the --joystick parameter if you want to use a joystick instead of the +intended mouse for playing the games (not recommended). + +Installation from binaries +============================== + +We have at least three methods to get the binaries into the Raspbian SD: + +1) Since Debian (Raspbian) includes an ssh service by default, I recommend +keeping the SD card on the Raspberry Pi, and using scp to copy the package over +to your home directory in the Debian filesystem. + +scp scummvm-rpi_<version>.zip pi@<raspberrypi_ip>:/home/pi + +2) If your RaspberryPi has internet access, you can simply use wget to +download the package to your home folder: + +cd ~/ +wget <package_link> + +3) You could also connect the Raspbian SD card to your main PC and, after +mounting it (or being automounted as it would be in most desktop GNU/Linux +systems), copy the package file manually to your home directory. +How to mount an SD and copy files to it is beyond the scope of this README. + +Once we have the package file in our home directory using one of the three +aforementioned methods, we would need to uncompress it: + +unzip scummvm-rpi_<version>.zip + +As a result, a directory containing the scummvm along with this README will be +created. +We can run it by simply changing to our scummvm directory and executing the +scummvm file. + +cd scummvm-rpi +./scummvm + +I recommend copying the games to /home/pi/scummvm-rpi. Adding the games via the menu +works as in any other system ScummVM runs on. + +Building from sources +============================== + +Recommended method is building by cross-compiling on a GNU/Linux X86-based computer. +You can find concise instructions for this on the ScummVM wiki: + +http://wiki.scummvm.org/index.php/Compiling_ScummVM/RPI + +The configure script is disabling scalers because we prefer dispmanx for that, which +makes scalers unnecessary on a CPU limited platform like this, timestamps because most people +doesn't have an RTC on the Raspberry Pi, and event recorder to save SD card write cycles. +All these are automatically disabled when we crosscompile by passing "--host=raspberrypi". + +Enjoy! diff --git a/backends/platform/sdl/sdl-sys.h b/backends/platform/sdl/sdl-sys.h index eec3741ed6..551605a4b4 100644 --- a/backends/platform/sdl/sdl-sys.h +++ b/backends/platform/sdl/sdl-sys.h @@ -52,12 +52,99 @@ typedef struct { int FAKE; } FAKE_FILE; #define strncasecmp FAKE_strncasecmp #endif +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_exit) +#undef exit +#define exit FAKE_exit +#endif + +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_abort) +#undef abort +#define abort FAKE_abort +#endif + +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_system) +#undef system +#define system FAKE_system +#endif + +// HACK: SDL might include windows.h which defines its own ARRAYSIZE. +// However, we want to use the version from common/util.h. Thus, we make sure +// that we actually have this definition after including the SDL headers. +#if defined(ARRAYSIZE) && defined(COMMON_UTIL_H) +#define HACK_REDEFINE_ARRAYSIZE +#undef ARRAYSIZE +#endif + +// HACK to fix compilation with SDL 2.0 in MSVC. +// In SDL 2.0, intrin.h is now included in SDL_cpuinfo.h, which includes +// setjmp.h. SDL_cpuinfo.h is included from SDL.h and SDL_syswm.h. +// Thus, we remove the exceptions for setjmp and longjmp before these two +// includes. Unfortunately, we can't use SDL_VERSION_ATLEAST here, as SDL.h +// hasn't been included yet at this point. +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && defined(_MSC_VER) +// We unset any fake definitions of setjmp/longjmp here + +#ifndef FORBIDDEN_SYMBOL_EXCEPTION_setjmp +#undef setjmp +#endif + +#ifndef FORBIDDEN_SYMBOL_EXCEPTION_longjmp +#undef longjmp +#endif + +#endif + #if defined(__SYMBIAN32__) #include <esdl\SDL.h> #else #include <SDL.h> #endif +#include <SDL_syswm.h> + +// Restore the forbidden exceptions from the hack above +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && defined(_MSC_VER) + +#ifndef FORBIDDEN_SYMBOL_EXCEPTION_setjmp +#undef setjmp +#define setjmp(a) FORBIDDEN_SYMBOL_REPLACEMENT +#endif + +#ifndef FORBIDDEN_SYMBOL_EXCEPTION_longjmp +#undef longjmp +#define longjmp(a,b) FORBIDDEN_SYMBOL_REPLACEMENT +#endif + +#endif + +// SDL_syswm.h will include windows.h on Win32. We need to undefine its +// ARRAYSIZE definition because we supply our own. +#undef ARRAYSIZE + +#ifdef HACK_REDEFINE_ARRAYSIZE +#undef HACK_REDEFINE_ARRAYSIZE +#define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(x[0]))) +#endif + +// In a moment of brilliance Xlib.h included by SDL_syswm.h #defines the +// following names. In a moment of mental breakdown, which occurred upon +// gazing at Xlib.h, LordHoto decided to undefine them to prevent havoc. +#ifdef Status +#undef Status +#endif + +#ifdef Bool +#undef Bool +#endif + +#ifdef True +#undef True +#endif + +#ifdef False +#undef False +#endif + // Finally forbid FILE again (if it was forbidden to start with) #if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_FILE) #undef FILE @@ -74,5 +161,65 @@ typedef struct { int FAKE; } FAKE_FILE; #define strncasecmp FORBIDDEN_SYMBOL_REPLACEMENT #endif +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_exit) +#undef exit +#define exit(a) FORBIDDEN_SYMBOL_REPLACEMENT +#endif + +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_abort) +#undef abort +#define abort() FORBIDDEN_SYMBOL_REPLACEMENT +#endif + +#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_system) +#undef system +#define system(a) FORBIDDEN_SYMBOL_REPLACEMENT +#endif + +// SDL 2 has major API changes. We redefine constants which got renamed to +// ease the transition. This is sometimes dangerous because the values changed +// too! +#if SDL_VERSION_ATLEAST(2, 0, 0) + +// Type names which changed between SDL 1.2 and SDL 2. +#define SDLKey SDL_Keycode +#define SDLMod SDL_Keymod +#define SDL_keysym SDL_Keysym + +// Key code constants which got renamed. +#define SDLK_SCROLLOCK SDLK_SCROLLLOCK +#define SDLK_NUMLOCK SDLK_NUMLOCKCLEAR +#define SDLK_LSUPER SDLK_LGUI +#define SDLK_RSUPER SDLK_RGUI +#define SDLK_PRINT SDLK_PRINTSCREEN +#define SDLK_COMPOSE SDLK_APPLICATION +#define SDLK_KP0 SDLK_KP_0 +#define SDLK_KP1 SDLK_KP_1 +#define SDLK_KP2 SDLK_KP_2 +#define SDLK_KP3 SDLK_KP_3 +#define SDLK_KP4 SDLK_KP_4 +#define SDLK_KP5 SDLK_KP_5 +#define SDLK_KP6 SDLK_KP_6 +#define SDLK_KP7 SDLK_KP_7 +#define SDLK_KP8 SDLK_KP_8 +#define SDLK_KP9 SDLK_KP_9 + +// Meta key constants which got renamed. +#define KMOD_META KMOD_GUI + +// SDL surface flags which got removed. +#define SDL_SRCCOLORKEY 0 +#define SDL_SRCALPHA 0 +#define SDL_FULLSCREEN 0x40000000 + +// Compatibility implementations for removed functionality. +int SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); +int SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha); + +#define SDL_SetColorKey SDL_SetColorKey_replacement +int SDL_SetColorKey_replacement(SDL_Surface *surface, Uint32 flag, Uint32 key); + +#endif + #endif diff --git a/backends/platform/sdl/sdl-window.cpp b/backends/platform/sdl/sdl-window.cpp new file mode 100644 index 0000000000..6d35f77ae0 --- /dev/null +++ b/backends/platform/sdl/sdl-window.cpp @@ -0,0 +1,223 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/platform/sdl/sdl-window.h" + +#include "common/textconsole.h" + +#include "icons/scummvm.xpm" + +SdlWindow::SdlWindow() +#if SDL_VERSION_ATLEAST(2, 0, 0) + : _window(nullptr), _inputGrabState(false), _windowCaption("ScummVM") +#endif + { +} + +SdlWindow::~SdlWindow() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + destroyWindow(); +#endif +} + +void SdlWindow::setupIcon() { + int x, y, w, h, ncols, nbytes, i; + unsigned int rgba[256]; + unsigned int *icon; + + if (sscanf(scummvm_icon[0], "%d %d %d %d", &w, &h, &ncols, &nbytes) != 4) { + warning("Wrong format of scummvm_icon[0] (%s)", scummvm_icon[0]); + + return; + } + if ((w > 512) || (h > 512) || (ncols > 255) || (nbytes > 1)) { + warning("Could not load the built-in icon (%d %d %d %d)", w, h, ncols, nbytes); + return; + } + icon = (unsigned int*)malloc(w*h*sizeof(unsigned int)); + if (!icon) { + warning("Could not allocate temp storage for the built-in icon"); + return; + } + + for (i = 0; i < ncols; i++) { + unsigned char code; + char color[32]; + memset(color, 0, sizeof(color)); + unsigned int col; + if (sscanf(scummvm_icon[1 + i], "%c c %s", &code, color) != 2) { + warning("Wrong format of scummvm_icon[%d] (%s)", 1 + i, scummvm_icon[1 + i]); + } + if (!strcmp(color, "None")) + col = 0x00000000; + else if (!strcmp(color, "black")) + col = 0xFF000000; + else if (color[0] == '#') { + if (sscanf(color + 1, "%06x", &col) != 1) { + warning("Wrong format of color (%s)", color + 1); + } + col |= 0xFF000000; + } else { + warning("Could not load the built-in icon (%d %s - %s) ", code, color, scummvm_icon[1 + i]); + free(icon); + return; + } + + rgba[code] = col; + } + for (y = 0; y < h; y++) { + const char *line = scummvm_icon[1 + ncols + y]; + for (x = 0; x < w; x++) { + icon[x + w * y] = rgba[(int)line[x]]; + } + } + + SDL_Surface *sdl_surf = SDL_CreateRGBSurfaceFrom(icon, w, h, 32, w * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); + if (!sdl_surf) { + warning("SDL_CreateRGBSurfaceFrom(icon) failed"); + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + SDL_SetWindowIcon(_window, sdl_surf); + } +#else + SDL_WM_SetIcon(sdl_surf, NULL); +#endif + + SDL_FreeSurface(sdl_surf); + free(icon); +} + +void SdlWindow::setWindowCaption(const Common::String &caption) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + _windowCaption = caption; + if (_window) { + SDL_SetWindowTitle(_window, caption.c_str()); + } +#else + SDL_WM_SetCaption(caption.c_str(), caption.c_str()); +#endif +} + +void SdlWindow::toggleMouseGrab() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + _inputGrabState = !(SDL_GetWindowGrab(_window) == SDL_TRUE); + SDL_SetWindowGrab(_window, _inputGrabState ? SDL_TRUE : SDL_FALSE); + } +#else + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) { + SDL_WM_GrabInput(SDL_GRAB_ON); + } else { + SDL_WM_GrabInput(SDL_GRAB_OFF); + } +#endif +} + +bool SdlWindow::hasMouseFocus() const { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + return (SDL_GetWindowFlags(_window) & SDL_WINDOW_MOUSE_FOCUS); + } else { + return false; + } +#else + return (SDL_GetAppState() & SDL_APPMOUSEFOCUS); +#endif +} + +void SdlWindow::warpMouseInWindow(uint x, uint y) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + SDL_WarpMouseInWindow(_window, x, y); + } +#else + SDL_WarpMouse(x, y); +#endif +} + +void SdlWindow::iconifyWindow() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + SDL_MinimizeWindow(_window); + } +#else + SDL_WM_IconifyWindow(); +#endif +} + +bool SdlWindow::getSDLWMInformation(SDL_SysWMinfo *info) const { + SDL_VERSION(&info->version); +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_GetWindowWMInfo(_window, info); +#else + return SDL_GetWMInfo(info); +#endif +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +SDL_Surface *copySDLSurface(SDL_Surface *src) { + const bool locked = SDL_MUSTLOCK(src) == SDL_TRUE; + + if (locked) { + if (SDL_LockSurface(src) != 0) { + return nullptr; + } + } + + SDL_Surface *res = SDL_CreateRGBSurfaceFrom(src->pixels, + src->w, src->h, src->format->BitsPerPixel, + src->pitch, src->format->Rmask, src->format->Gmask, + src->format->Bmask, src->format->Amask); + + if (locked) { + SDL_UnlockSurface(src); + } + + return res; +} + +bool SdlWindow::createWindow(int width, int height, uint32 flags) { + destroyWindow(); + + if (_inputGrabState) { + flags |= SDL_WINDOW_INPUT_GRABBED; + } + + _window = SDL_CreateWindow(_windowCaption.c_str(), SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, width, height, flags); + if (!_window) { + return false; + } + setupIcon(); + + return true; +} + +void SdlWindow::destroyWindow() { + SDL_DestroyWindow(_window); + _window = nullptr; +} +#endif diff --git a/backends/platform/sdl/sdl-window.h b/backends/platform/sdl/sdl-window.h new file mode 100644 index 0000000000..58b898f824 --- /dev/null +++ b/backends/platform/sdl/sdl-window.h @@ -0,0 +1,112 @@ +/* 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 BACKENDS_PLATFORM_SDL_WINDOW_H +#define BACKENDS_PLATFORM_SDL_WINDOW_H + +#include "backends/platform/sdl/sdl-sys.h" + +#include "common/str.h" + +class SdlWindow { +public: + SdlWindow(); + virtual ~SdlWindow(); + + /** + * Setup the window icon. + */ + virtual void setupIcon(); + + /** + * Change the caption of the window. + * + * @param caption New window caption in UTF-8 encoding. + */ + void setWindowCaption(const Common::String &caption); + + /** + * Toggle mouse grab state. This decides whether the cursor can leave the + * window or not. + */ + void toggleMouseGrab(); + + /** + * Check whether the application has mouse focus. + */ + bool hasMouseFocus() const; + + /** + * Warp the mouse to the specified position in window coordinates. + */ + void warpMouseInWindow(uint x, uint y); + + /** + * Iconifies the window. + */ + void iconifyWindow(); + + /** + * Query platform specific SDL window manager information. + * + * Since this is an SDL internal structure clients are responsible + * for accessing it in a version safe manner. + */ + bool getSDLWMInformation(SDL_SysWMinfo *info) const; + +#if SDL_VERSION_ATLEAST(2, 0, 0) +public: + /** + * @return The window ScummVM has setup with SDL. + */ + SDL_Window *getSDLWindow() const { return _window; } + + /** + * Creates a new SDL window (and destroies the old one). + * + * @param width Width of the window. + * @param height Height of the window. + * @param flags SDL flags passed to SDL_CreateWindow + * @return true on success, false otherwise + */ + bool createWindow(int width, int height, uint32 flags); + + /** + * Destroies the current SDL window. + */ + void destroyWindow(); + +protected: + SDL_Window *_window; + +private: + bool _inputGrabState; + Common::String _windowCaption; +#endif +}; + +class SdlIconlessWindow : public SdlWindow { +public: + virtual void setupIcon() {} +}; + +#endif diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 4dc5929dab..6862bb349f 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -36,8 +36,8 @@ #include "backends/saves/default/default-saves.h" -// Audio CD support was removed with SDL 1.3 -#if SDL_VERSION_ATLEAST(1, 3, 0) +// Audio CD support was removed with SDL 2.0 +#if SDL_VERSION_ATLEAST(2, 0, 0) #include "backends/audiocd/default/default-audiocd.h" #else #include "backends/audiocd/sdl/sdl-audiocd.h" @@ -52,8 +52,6 @@ #include "graphics/cursorman.h" #endif -#include "icons/scummvm.xpm" - #include <time.h> // for getTimeAndDate() #ifdef USE_DETECTLANG @@ -62,6 +60,15 @@ #endif // !WIN32 #endif +#ifdef USE_SDL_NET +#include <SDL/SDL_net.h> +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) +#include <SDL2/SDL.h> +#include <SDL2/SDL_clipboard.h> +#endif + OSystem_SDL::OSystem_SDL() : #ifdef USE_OPENGL @@ -75,9 +82,13 @@ OSystem_SDL::OSystem_SDL() #endif _inited(false), _initedSDL(false), +#ifdef USE_SDL_NET + _initedSDLnet(false), +#endif _logger(0), _mixerManager(0), - _eventSource(0) { + _eventSource(0), + _window(0) { } @@ -95,6 +106,8 @@ OSystem_SDL::~OSystem_SDL() { } delete _graphicsManager; _graphicsManager = 0; + delete _window; + _window = 0; delete _eventManager; _eventManager = 0; delete _eventSource; @@ -119,6 +132,10 @@ OSystem_SDL::~OSystem_SDL() { delete _logger; _logger = 0; +#ifdef USE_SDL_NET + if (_initedSDLnet) SDLNet_Quit(); +#endif + SDL_Quit(); } @@ -126,8 +143,10 @@ void OSystem_SDL::init() { // Initialize SDL initSDL(); +#if !SDL_VERSION_ATLEAST(2, 0, 0) // Enable unicode support if possible SDL_EnableUNICODE(1); +#endif // Disable OS cursor SDL_ShowCursor(SDL_DISABLE); @@ -147,6 +166,9 @@ void OSystem_SDL::init() { if (_mutexManager == 0) _mutexManager = new SdlMutexManager(); + if (_window == 0) + _window = new SdlWindow(); + #if defined(USE_TASKBAR) if (_taskbarManager == 0) _taskbarManager = new Common::TaskbarManager(); @@ -154,14 +176,25 @@ void OSystem_SDL::init() { } +bool OSystem_SDL::hasFeature(Feature f) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (f == kFeatureClipboardSupport) return true; +#endif + return ModularBackend::hasFeature(f); +} + void OSystem_SDL::initBackend() { // Check if backend has not been initialized assert(!_inited); +#if SDL_VERSION_ATLEAST(2, 0, 0) + const char *sdlDriverName = SDL_GetCurrentVideoDriver(); +#else const int maxNameLen = 20; char sdlDriverName[maxNameLen]; sdlDriverName[0] = '\0'; SDL_VideoDriverName(sdlDriverName, maxNameLen); +#endif // Using printf rather than debug() here as debug()/logging // is not active by this point. debug(1, "Using SDL Video Driver \"%s\"", sdlDriverName); @@ -172,6 +205,13 @@ void OSystem_SDL::initBackend() { _eventSource = new SdlEventSource(); #ifdef USE_OPENGL +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_DisplayMode displayMode; + if (!SDL_GetDesktopDisplayMode(0, &displayMode)) { + _desktopWidth = displayMode.w; + _desktopHeight = displayMode.h; + } +#else // Query the desktop resolution. We simply hope nothing tried to change // the resolution so far. const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); @@ -180,6 +220,7 @@ void OSystem_SDL::initBackend() { _desktopHeight = videoInfo->current_h; } #endif +#endif if (_graphicsManager == 0) { #ifdef USE_OPENGL @@ -196,7 +237,7 @@ void OSystem_SDL::initBackend() { Common::String gfxMode(ConfMan.get("gfx_mode")); for (uint i = _firstGLMode; i < _graphicsModeIds.size(); ++i) { if (!scumm_stricmp(_graphicsModes[i].name, gfxMode.c_str())) { - _graphicsManager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource); + _graphicsManager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource, _window); _graphicsMode = i; break; } @@ -205,7 +246,7 @@ void OSystem_SDL::initBackend() { #endif if (_graphicsManager == 0) { - _graphicsManager = new SurfaceSdlGraphicsManager(_eventSource); + _graphicsManager = new SurfaceSdlGraphicsManager(_eventSource, _window); } } @@ -227,18 +268,10 @@ void OSystem_SDL::initBackend() { _timerManager = new SdlTimerManager(); #endif - if (_audiocdManager == 0) { - // Audio CD support was removed with SDL 1.3 -#if SDL_VERSION_ATLEAST(1, 3, 0) - _audiocdManager = new DefaultAudioCDManager(); -#else - _audiocdManager = new SdlAudioCDManager(); -#endif - - } + _audiocdManager = createAudioCDManager(); // Setup a custom program icon. - setupIcon(); + _window->setupIcon(); _inited = true; @@ -284,6 +317,17 @@ void OSystem_SDL::initSDL() { _initedSDL = true; } + +#ifdef USE_SDL_NET + // Check if SDL_net has not been initialized + if (!_initedSDLnet) { + // Initialize SDL_net + if (SDLNet_Init() == -1) + error("Could not initialize SDL_net: %s", SDLNet_GetError()); + + _initedSDLnet = true; + } +#endif } void OSystem_SDL::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { @@ -314,7 +358,7 @@ void OSystem_SDL::setWindowCaption(const char *caption) { } } - SDL_WM_SetCaption(cap.c_str(), cap.c_str()); + _window->setWindowCaption(cap); } void OSystem_SDL::quit() { @@ -421,68 +465,26 @@ Common::String OSystem_SDL::getSystemLanguage() const { #endif // USE_DETECTLANG } -void OSystem_SDL::setupIcon() { - int x, y, w, h, ncols, nbytes, i; - unsigned int rgba[256]; - unsigned int *icon; - - if (sscanf(scummvm_icon[0], "%d %d %d %d", &w, &h, &ncols, &nbytes) != 4) { - warning("Wrong format of scummvm_icon[0] (%s)", scummvm_icon[0]); - - return; - } - if ((w > 512) || (h > 512) || (ncols > 255) || (nbytes > 1)) { - warning("Could not load the built-in icon (%d %d %d %d)", w, h, ncols, nbytes); - return; - } - icon = (unsigned int*)malloc(w*h*sizeof(unsigned int)); - if (!icon) { - warning("Could not allocate temp storage for the built-in icon"); - return; - } - - for (i = 0; i < ncols; i++) { - unsigned char code; - char color[32]; - memset(color, 0, sizeof(color)); - unsigned int col; - if (sscanf(scummvm_icon[1 + i], "%c c %s", &code, color) != 2) { - warning("Wrong format of scummvm_icon[%d] (%s)", 1 + i, scummvm_icon[1 + i]); - } - if (!strcmp(color, "None")) - col = 0x00000000; - else if (!strcmp(color, "black")) - col = 0xFF000000; - else if (color[0] == '#') { - if (sscanf(color + 1, "%06x", &col) != 1) { - warning("Wrong format of color (%s)", color + 1); - } - col |= 0xFF000000; - } else { - warning("Could not load the built-in icon (%d %s - %s) ", code, color, scummvm_icon[1 + i]); - free(icon); - return; - } +bool OSystem_SDL::hasTextInClipboard() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_HasClipboardText() == SDL_TRUE; +#else + return false; +#endif +} - rgba[code] = col; - } - for (y = 0; y < h; y++) { - const char *line = scummvm_icon[1 + ncols + y]; - for (x = 0; x < w; x++) { - icon[x + w * y] = rgba[(int)line[x]]; - } - } +Common::String OSystem_SDL::getTextFromClipboard() { + if (!hasTextInClipboard()) return ""; - SDL_Surface *sdl_surf = SDL_CreateRGBSurfaceFrom(icon, w, h, 32, w * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); - if (!sdl_surf) { - warning("SDL_CreateRGBSurfaceFrom(icon) failed"); - } - SDL_WM_SetIcon(sdl_surf, NULL); - SDL_FreeSurface(sdl_surf); - free(icon); +#if SDL_VERSION_ATLEAST(2, 0, 0) + char *text = SDL_GetClipboardText(); + if (text == nullptr) return ""; + return text; +#else + return ""; +#endif } - uint32 OSystem_SDL::getMillis(bool skipRecord) { uint32 millis = SDL_GetTicks(); @@ -535,6 +537,23 @@ Common::TimerManager *OSystem_SDL::getTimerManager() { #endif } +AudioCDManager *OSystem_SDL::createAudioCDManager() { + // Audio CD support was removed with SDL 2.0 +#if SDL_VERSION_ATLEAST(2, 0, 0) + return new DefaultAudioCDManager(); +#else + return new SdlAudioCDManager(); +#endif +} + +Common::SaveFileManager *OSystem_SDL::getSavefileManager() { +#ifdef ENABLE_EVENTRECORDER + return g_eventRec.getSaveManager(_savefileManager); +#else + return _savefileManager; +#endif +} + #ifdef USE_OPENGL const OSystem::GraphicsMode *OSystem_SDL::getSupportedGraphicsModes() const { @@ -572,14 +591,8 @@ bool OSystem_SDL::setGraphicsMode(int mode) { // // This is a probably temporary workaround to fix bugs like #3368143 // "SDL/OpenGL: Crash when switching renderer backend". - const int screenWidth = _graphicsManager->getWidth(); - const int screenHeight = _graphicsManager->getHeight(); - const bool arState = _graphicsManager->getFeatureState(kFeatureAspectRatioCorrection); - const bool fullscreen = _graphicsManager->getFeatureState(kFeatureFullscreenMode); - const bool cursorPalette = _graphicsManager->getFeatureState(kFeatureCursorPalette); -#ifdef USE_RGB_COLOR - const Graphics::PixelFormat pixelFormat = _graphicsManager->getScreenFormat(); -#endif + SdlGraphicsManager *sdlGraphicsManager = dynamic_cast<SdlGraphicsManager *>(_graphicsManager); + SdlGraphicsManager::State state = sdlGraphicsManager->getState(); bool switchedManager = false; @@ -587,16 +600,16 @@ bool OSystem_SDL::setGraphicsMode(int mode) { // manager, delete and create the new mode graphics manager if (_graphicsMode >= _firstGLMode && mode < _firstGLMode) { debug(1, "switching to plain SDL graphics"); - dynamic_cast<SdlGraphicsManager *>(_graphicsManager)->deactivateManager(); + sdlGraphicsManager->deactivateManager(); delete _graphicsManager; - _graphicsManager = new SurfaceSdlGraphicsManager(_eventSource); + _graphicsManager = sdlGraphicsManager = new SurfaceSdlGraphicsManager(_eventSource, _window); switchedManager = true; } else if (_graphicsMode < _firstGLMode && mode >= _firstGLMode) { debug(1, "switching to OpenGL graphics"); - dynamic_cast<SdlGraphicsManager *>(_graphicsManager)->deactivateManager(); + sdlGraphicsManager->deactivateManager(); delete _graphicsManager; - _graphicsManager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource); + _graphicsManager = sdlGraphicsManager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource, _window); switchedManager = true; } @@ -604,24 +617,10 @@ bool OSystem_SDL::setGraphicsMode(int mode) { _graphicsMode = mode; if (switchedManager) { - dynamic_cast<SdlGraphicsManager *>(_graphicsManager)->activateManager(); + sdlGraphicsManager->activateManager(); - _graphicsManager->beginGFXTransaction(); -#ifdef USE_RGB_COLOR - _graphicsManager->initSize(screenWidth, screenHeight, &pixelFormat); -#else - _graphicsManager->initSize(screenWidth, screenHeight, 0); -#endif - _graphicsManager->setFeatureState(kFeatureAspectRatioCorrection, arState); - _graphicsManager->setFeatureState(kFeatureFullscreenMode, fullscreen); - _graphicsManager->setFeatureState(kFeatureCursorPalette, cursorPalette); - - // Worst part about this right now, tell the cursor manager to - // resetup the cursor + cursor palette if necessarily - - // First we need to try to setup the old state on the new manager... - if (_graphicsManager->endGFXTransaction() != kTransactionSuccess) { - // Oh my god if this failed the client code might just explode. + // This failing will probably have bad consequences... + if (!sdlGraphicsManager->setState(state)) { return false; } @@ -630,7 +629,7 @@ bool OSystem_SDL::setGraphicsMode(int mode) { CursorMan.popCursor(); // Next setup cursor palette if needed - if (cursorPalette) { + if (_graphicsManager->getFeatureState(kFeatureCursorPalette)) { CursorMan.pushCursorPalette(0, 0, 0); CursorMan.popCursorPalette(); } @@ -660,7 +659,7 @@ void OSystem_SDL::setupGraphicsModes() { const OSystem::GraphicsMode *srcMode; int defaultMode; - GraphicsManager *manager = new SurfaceSdlGraphicsManager(_eventSource); + GraphicsManager *manager = new SurfaceSdlGraphicsManager(_eventSource, _window); srcMode = manager->getSupportedGraphicsModes(); defaultMode = manager->getDefaultGraphicsMode(); while (srcMode->name) { @@ -674,7 +673,7 @@ void OSystem_SDL::setupGraphicsModes() { assert(_defaultSDLMode != -1); _firstGLMode = _graphicsModes.size(); - manager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource); + manager = new OpenGLSdlGraphicsManager(_desktopWidth, _desktopHeight, _eventSource, _window); srcMode = manager->getSupportedGraphicsModes(); defaultMode = manager->getDefaultGraphicsMode(); while (srcMode->name) { @@ -702,5 +701,38 @@ void OSystem_SDL::setupGraphicsModes() { mode++; } } +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) +int SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) { + if (surface->format->palette) { + return !SDL_SetPaletteColors(surface->format->palette, colors, firstcolor, ncolors) ? 1 : 0; + } else { + return 0; + } +} + +int SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha) { + if (SDL_SetSurfaceAlphaMod(surface, alpha)) { + return -1; + } + if (alpha == 255 || !flag) { + if (SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE)) { + return -1; + } + } else { + if (SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND)) { + return -1; + } + } + + return 0; +} + +#undef SDL_SetColorKey +int SDL_SetColorKey_replacement(SDL_Surface *surface, Uint32 flag, Uint32 key) { + return SDL_SetColorKey(surface, SDL_TRUE, key) ? -1 : 0; +} #endif + diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 5dcc269e55..17b4e9b001 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -29,6 +29,7 @@ #include "backends/mixer/sdl/sdl-mixer.h" #include "backends/events/sdl/sdl-events.h" #include "backends/log/log.h" +#include "backends/platform/sdl/sdl-window.h" #include "common/array.h" @@ -54,6 +55,8 @@ public: */ virtual SdlMixerManager *getMixerManager(); + virtual bool hasFeature(Feature f); + // Override functions from ModularBackend and OSystem virtual void initBackend(); #if defined(USE_TASKBAR) @@ -68,6 +71,10 @@ public: virtual Common::String getSystemLanguage() const; + // Clipboard + virtual bool hasTextInClipboard(); + virtual Common::String getTextFromClipboard(); + virtual void setWindowCaption(const char *caption); virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); virtual uint32 getMillis(bool skipRecord = false); @@ -75,10 +82,14 @@ public: virtual void getTimeAndDate(TimeDate &td) const; virtual Audio::Mixer *getMixer(); virtual Common::TimerManager *getTimerManager(); + virtual Common::SaveFileManager *getSavefileManager(); protected: bool _inited; bool _initedSDL; +#ifdef USE_SDL_NET + bool _initedSDLnet; +#endif /** * Mixer manager that configures and setups SDL for @@ -91,6 +102,11 @@ protected: */ SdlEventSource *_eventSource; + /** + * The SDL output window. + */ + SdlWindow *_window; + virtual Common::EventSource *getDefaultEventSource() { return _eventSource; } /** @@ -99,9 +115,9 @@ protected: virtual void initSDL(); /** - * Setup the window icon. + * Create the audio CD manager */ - virtual void setupIcon(); + virtual AudioCDManager *createAudioCDManager(); // Logging virtual Common::WriteStream *createLogFile() { return 0; } diff --git a/backends/platform/sdl/win32/win32-main.cpp b/backends/platform/sdl/win32/win32-main.cpp index e5b26c3ff0..4864347d81 100644 --- a/backends/platform/sdl/win32/win32-main.cpp +++ b/backends/platform/sdl/win32/win32-main.cpp @@ -40,8 +40,20 @@ #include "base/main.h" int __stdcall WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*iShowCmd*/) { +#if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_SetModuleHandle(GetModuleHandle(NULL)); +#endif +// HACK: __argc, __argv are broken and return zero when using mingwrt 4.0+ on MinGW +// HACK: MinGW-w64 based toolchains neither feature _argc nor _argv. The 32 bit +// incarnation only defines __MINGW32__. This leads to build breakage due to +// missing declarations. Luckily MinGW-w64 based toolchains define +// __MINGW64_VERSION_foo macros inside _mingw.h, which is included from all +// system headers. Thus we abuse that to detect them. +#if defined(__GNUC__) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + return main(_argc, _argv); +#else return main(__argc, __argv); +#endif } int main(int argc, char *argv[]) { diff --git a/backends/platform/sdl/win32/win32-window.cpp b/backends/platform/sdl/win32/win32-window.cpp new file mode 100644 index 0000000000..de10be6b57 --- /dev/null +++ b/backends/platform/sdl/win32/win32-window.cpp @@ -0,0 +1,59 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#ifdef WIN32 + +#include "backends/platform/sdl/win32/win32-window.h" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef ARRAYSIZE // winnt.h defines ARRAYSIZE, but we want our own one... + +void SdlWindow_Win32::setupIcon() { + HMODULE handle = GetModuleHandle(NULL); + HICON ico = LoadIcon(handle, MAKEINTRESOURCE(1001 /* IDI_ICON */)); + if (ico) { + SDL_SysWMinfo wminfo; + if (getSDLWMInformation(&wminfo)) { + // Replace the handle to the icon associated with the window class by our custom icon +#if SDL_VERSION_ATLEAST(2, 0, 0) + SetClassLongPtr(wminfo.info.win.window, GCLP_HICON, (ULONG_PTR)ico); +#else + SetClassLongPtr(wminfo.window, GCLP_HICON, (ULONG_PTR)ico); +#endif + + // Since there wasn't any default icon, we can't use the return value from SetClassLong + // to check for errors (it would be 0 in both cases: error or no previous value for the + // icon handle). Instead we check for the last-error code value. + if (GetLastError() == ERROR_SUCCESS) + return; + } + } + + // If no icon has been set, fallback to default path + SdlWindow::setupIcon(); +} + +#endif diff --git a/backends/platform/sdl/win32/win32-window.h b/backends/platform/sdl/win32/win32-window.h new file mode 100644 index 0000000000..3bda697bc7 --- /dev/null +++ b/backends/platform/sdl/win32/win32-window.h @@ -0,0 +1,37 @@ +/* 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 BACKENDS_PLATFORM_SDL_WIN32_WIN32_WINDOW_H +#define BACKENDS_PLATFORM_SDL_WIN32_WIN32_WINDOW_H + +#ifdef WIN32 + +#include "backends/platform/sdl/sdl-window.h" + +class SdlWindow_Win32 : public SdlWindow { +public: + virtual void setupIcon(); +}; + +#endif + +#endif diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp index 5f860ad32d..fcc0849624 100644 --- a/backends/platform/sdl/win32/win32.cpp +++ b/backends/platform/sdl/win32/win32.cpp @@ -35,12 +35,13 @@ #include "common/error.h" #include "common/textconsole.h" -#include <SDL_syswm.h> // For setting the icon - +#include "backends/audiocd/win32/win32-audiocd.h" #include "backends/platform/sdl/win32/win32.h" +#include "backends/platform/sdl/win32/win32-window.h" #include "backends/saves/windows/windows-saves.h" #include "backends/fs/windows/windows-fs-factory.h" #include "backends/taskbar/win32/win32-taskbar.h" +#include "backends/updates/win32/win32-updates.h" #include "common/memstream.h" @@ -50,9 +51,12 @@ void OSystem_Win32::init() { // Initialize File System Factory _fsFactory = new WindowsFilesystemFactory(); + // Create Win32 specific window + _window = new SdlWindow_Win32(); + #if defined(USE_TASKBAR) // Initialize taskbar manager - _taskbarManager = new Win32TaskbarManager(); + _taskbarManager = new Win32TaskbarManager(_window); #endif // Invoke parent implementation of this method @@ -79,6 +83,11 @@ void OSystem_Win32::initBackend() { if (_savefileManager == 0) _savefileManager = new WindowsSaveFileManager(); +#if defined(USE_SPARKLE) + // Initialize updates manager + _updateManager = new Win32UpdateManager(); +#endif + // Invoke parent implementation of this method OSystem_SDL::initBackend(); } @@ -126,28 +135,6 @@ bool OSystem_Win32::displayLogFile() { return false; } -void OSystem_Win32::setupIcon() { - HMODULE handle = GetModuleHandle(NULL); - HICON ico = LoadIcon(handle, MAKEINTRESOURCE(1001 /* IDI_ICON */)); - if (ico) { - SDL_SysWMinfo wminfo; - SDL_VERSION(&wminfo.version); - if (SDL_GetWMInfo(&wminfo)) { - // Replace the handle to the icon associated with the window class by our custom icon - SetClassLongPtr(wminfo.window, GCLP_HICON, (ULONG_PTR)ico); - - // Since there wasn't any default icon, we can't use the return value from SetClassLong - // to check for errors (it would be 0 in both cases: error or no previous value for the - // icon handle). Instead we check for the last-error code value. - if (GetLastError() == ERROR_SUCCESS) - return; - } - } - - // If no icon has been set, fallback to default path - OSystem_SDL::setupIcon(); -} - Common::String OSystem_Win32::getDefaultConfigFileName() { char configFile[MAXPATHLEN]; @@ -338,4 +325,8 @@ void OSystem_Win32::addSysArchivesToSearchSet(Common::SearchSet &s, int priority OSystem_SDL::addSysArchivesToSearchSet(s, priority); } +AudioCDManager *OSystem_Win32::createAudioCDManager() { + return createWin32AudioCDManager(); +} + #endif diff --git a/backends/platform/sdl/win32/win32.h b/backends/platform/sdl/win32/win32.h index d72d80bc26..ca0843e834 100644 --- a/backends/platform/sdl/win32/win32.h +++ b/backends/platform/sdl/win32/win32.h @@ -47,9 +47,12 @@ protected: */ Common::String _logFilePath; - virtual void setupIcon(); virtual Common::String getDefaultConfigFileName(); virtual Common::WriteStream *createLogFile(); + + // Override createAudioCDManager() to get our Mac-specific + // version. + virtual AudioCDManager *createAudioCDManager(); }; #endif diff --git a/backends/platform/symbian/AdaptAllMMPs.pl b/backends/platform/symbian/AdaptAllMMPs.pl index 6b9f918a51..a836b764d2 100644 --- a/backends/platform/symbian/AdaptAllMMPs.pl +++ b/backends/platform/symbian/AdaptAllMMPs.pl @@ -56,6 +56,7 @@ chdir("../../../"); "mmp/scummvm_lastexpress.mmp", "mmp/scummvm_mads.mmp", "mmp/scummvm_prince.mmp", + "mmp/scummvm_sherlock.mmp", "mmp/scummvm_sword25.mmp", "mmp/scummvm_testbed.mmp", "mmp/scummvm_zvision.mmp", @@ -203,6 +204,7 @@ ParseModule("_lastexpress","lastexpress", \@section_empty); ParseModule("_m4", "m4", \@section_empty); ParseModule("_mads" ,"mads", \@section_empty); ParseModule("_prince" ,"prince", \@section_empty); +ParseModule("_sherlock" ,"sherlock", \@section_empty); ParseModule("_sword25" ,"sword25", \@section_empty); ParseModule("_testbed" ,"testbed", \@section_empty); ParseModule("_zvision" ,"zvision", \@section_empty); diff --git a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl index 8c19631524..10bcf0340a 100644 --- a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl +++ b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl @@ -1,36 +1,32 @@ ################################################################################################################## +#### sword25 ignored because of incompatible resolution 800*600 @WorkingEngines = qw( - agos agi cine cge composer cruise draci dreamweb - drascula hugo gob groovie hopkins kyra lastexpress - lure made mohawk mortevielle neverhood parallaction - pegasus queen saga sci scumm sky sword1 sword2 - teenagent tinsel toltecs tony toon touche tsage - tucker voyeur wintermute - access avalanche bbvs cge2 fullpipe mads prince - testbed zvision + access agi agos avalanche bbvs cge cge2 + cine composer cruise draci drascula + dreamweb fullpipe gob groovie hopkins + hugo kyra lastexpress lure made mads + mohawk mortevielle neverhood parallaction + pegasus prince queen saga sci scumm + sherlock sky sword1 sword2 teenagent + testbed tinsel toltecs tony toon touche + tsage tucker voyeur wintermute zvision ); - -#### sword25 yet not added - -#### In progress engines are : -#### access avalanche bbvs cge2 fullpipe mads prince -#### testbed zvision @WorkingEngines_1st = qw( - cine composer cruise drascula groovie - lastexpress made parallaction queen saga - scumm touche tucker wintermute voyeur - access avalanche cge2 zvision + access agi agos cge2 cine composer cruise + drascula gob groovie kyra lastexpress made + neverhood parallaction queen saga scumm + touche tucker voyeur wintermute ); @WorkingEngines_2nd = qw( - agi agos cge draci dreamweb gob hopkins - hugo kyra lure mohawk mortevielle neverhood - pegasus sci sky sword1 sword2 teenagent - tinsel tsage toltecs tony toon - bbvs fullpipe mads prince testbed + avalanche bbvs cge draci dreamweb fullpipe + hopkins hugo lure mads mohawk mortevielle + pegasus prince sci sherlock sky sword1 sword2 + teenagent testbed tinsel toltecs tony toon + tsage zvision ); #### sword25 yet not added diff --git a/backends/platform/symbian/README b/backends/platform/symbian/README index 8a44e9399d..47a3097ac0 100644 --- a/backends/platform/symbian/README +++ b/backends/platform/symbian/README @@ -1,7 +1,7 @@ ScummVM - ScummVM ported to EPOC/SymbianOS - Copyright (C) 2008-2014 ScummVM Team + Copyright (C) 2008-2016 ScummVM Team Copyright (C) 2013-2013 Fedor Strizhniou aka zanac Copyright (C) 2003-2013 Lars 'AnotherGuest' Persson Copyright (C) 2002-2008 Jurgen 'SumthinWicked' Braam diff --git a/backends/platform/symbian/S60/ScummVM_S60.mmp.in b/backends/platform/symbian/S60/ScummVM_S60.mmp.in index ca2ec7f930..14e586246a 100644 --- a/backends/platform/symbian/S60/ScummVM_S60.mmp.in +++ b/backends/platform/symbian/S60/ScummVM_S60.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/S60/ScummVM_S60_App.mmp b/backends/platform/symbian/S60/ScummVM_S60_App.mmp index 2fc39c6838..e75059b4b7 100644 --- a/backends/platform/symbian/S60/ScummVM_S60_App.mmp +++ b/backends/platform/symbian/S60/ScummVM_S60_App.mmp @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in index 3a08763e64..a669943933 100644 --- a/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in @@ -3,7 +3,7 @@ * Copyright (C) 2003-2014 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer * Copyright (C) 2013-2014 Fedor Strizhniou Additional library porting, engine support, help files etc - * Copyright (C) 2005-2014 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -36,7 +36,7 @@ TARGETPATH sys\bin TARGETTYPE exe OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char // fixes error "section .data loaded at [...] overlaps section .text loaded at [...]" -LINKEROPTION GCCE -Tdata 0xAA00000 +LINKEROPTION GCCE -Tdata 0xAA00000 --gc-sections --strip-all UID 0x100039ce 0xA0000658 @@ -122,6 +122,7 @@ SOURCEPATH ..\..\..\.. // backend EPOC/SDL/ESDL specific includes SOURCE backends\platform\sdl\sdl.cpp +SOURCE backends\platform\sdl\sdl-window.cpp SOURCE backends\audiocd\sdl\sdl-audiocd.cpp SOURCE backends\audiocd\default\default-audiocd.cpp SOURCE backends\fs\symbian\symbian-fs.cpp diff --git a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in index f65be1208e..ac8fc6b35a 100644 --- a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in @@ -3,7 +3,7 @@ * Copyright (C) 2003-2014 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer * Copyright (C) 2013-2014 Fedor Strizhniou Additional library porting, engine support, help files etc - * Copyright (C) 2005-2014 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -37,7 +37,7 @@ TARGETTYPE exe OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char // fixes error "section .data loaded at [...] overlaps section .text loaded at [...]" -LINKEROPTION GCCE -Tdata 0xAA00000 +LINKEROPTION GCCE -Tdata 0xAA00000 --gc-sections --strip-all UID 0x100039ce 0xA0000657 @@ -123,6 +123,7 @@ SOURCEPATH ..\..\..\.. // backend EPOC/SDL/ESDL specific includes SOURCE backends\platform\sdl\sdl.cpp +SOURCE backends\platform\sdl\sdl-window.cpp SOURCE backends\audiocd\sdl\sdl-audiocd.cpp SOURCE backends\audiocd\default\default-audiocd.cpp SOURCE backends\fs\symbian\symbian-fs.cpp diff --git a/backends/platform/symbian/S80/ScummVM_S80.mmp.in b/backends/platform/symbian/S80/ScummVM_S80.mmp.in index 34d1979fe5..e7ebe900bb 100644 --- a/backends/platform/symbian/S80/ScummVM_S80.mmp.in +++ b/backends/platform/symbian/S80/ScummVM_S80.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/S80/ScummVM_S80_App.mmp b/backends/platform/symbian/S80/ScummVM_S80_App.mmp index de96963d80..4b3fc052cf 100644 --- a/backends/platform/symbian/S80/ScummVM_S80_App.mmp +++ b/backends/platform/symbian/S80/ScummVM_S80_App.mmp @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/S90/Scummvm_S90.mmp.in b/backends/platform/symbian/S90/Scummvm_S90.mmp.in index e65397b145..e35cdb9107 100644 --- a/backends/platform/symbian/S90/Scummvm_S90.mmp.in +++ b/backends/platform/symbian/S90/Scummvm_S90.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/S90/Scummvm_S90_App.mmp b/backends/platform/symbian/S90/Scummvm_S90_App.mmp index 88a3e4d221..1716629ceb 100644 --- a/backends/platform/symbian/S90/Scummvm_S90_App.mmp +++ b/backends/platform/symbian/S90/Scummvm_S90_App.mmp @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/UIQ2/ScummVM.rss b/backends/platform/symbian/UIQ2/ScummVM.rss index 2e02f1da1d..13712e26c4 100644 --- a/backends/platform/symbian/UIQ2/ScummVM.rss +++ b/backends/platform/symbian/UIQ2/ScummVM.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/backends/platform/symbian/UIQ3/ScummVM.rss b/backends/platform/symbian/UIQ3/ScummVM.rss index 11cc767671..5e133ac137 100644 --- a/backends/platform/symbian/UIQ3/ScummVM.rss +++ b/backends/platform/symbian/UIQ3/ScummVM.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/UIQ3/ScummVM_A0000658.rss b/backends/platform/symbian/UIQ3/ScummVM_A0000658.rss index 11cc767671..5e133ac137 100644 --- a/backends/platform/symbian/UIQ3/ScummVM_A0000658.rss +++ b/backends/platform/symbian/UIQ3/ScummVM_A0000658.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/UIQ3/ScummVM_A0000658_UIQ3.mmp.in b/backends/platform/symbian/UIQ3/ScummVM_A0000658_UIQ3.mmp.in index 6e84ab7b08..5e27b87433 100644 --- a/backends/platform/symbian/UIQ3/ScummVM_A0000658_UIQ3.mmp.in +++ b/backends/platform/symbian/UIQ3/ScummVM_A0000658_UIQ3.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2009 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2009 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in index fee0c81e7a..8568a51ec3 100644 --- a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in +++ b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/UIQ3/scummvm_A0000658_loc.rss b/backends/platform/symbian/UIQ3/scummvm_A0000658_loc.rss index f54bcc24d3..8f8ff5c3b0 100644 --- a/backends/platform/symbian/UIQ3/scummvm_A0000658_loc.rss +++ b/backends/platform/symbian/UIQ3/scummvm_A0000658_loc.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/help/ScummVM.rtf b/backends/platform/symbian/help/ScummVM.rtf index 15b2105ecd..220eb76ecd 100644 --- a/backends/platform/symbian/help/ScummVM.rtf +++ b/backends/platform/symbian/help/ScummVM.rtf @@ -39,26 +39,28 @@ Synonyms;}{\*\cs33 \additive \super \sbasedon10 endnote reference;}{\s34\ql \fi- \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext34 \sautoupd List Bullet;}{\s35\ql \fi-284\li568\ri0\sa120\widctlpar{\*\pn \pnlvlblt\ilvl10\ls2047\pnrnot0\pnf3\pnstart1\pnindent283\pnhang{\pntxtb \'b7}} \nooverflow\faroman\ls2047\ilvl10\rin0\lin568\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext35 \sautoupd List Bullet 2;}{\s36\ql \li0\ri0\sa120\widctlpar\tqc\tx4153\tqr\tx8306\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext36 footer;}{\s37\ql \li284\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin284\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext37 List Continue;}{ -\s38\ql \li566\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin566\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext38 List Continue 2;}}{\*\listtable{\list\listtemplateid-737142542\listsimple{\listlevel\levelnfc0 -\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li643\jclisttab\tx643 }{\listname ;}\listid-129} -{\list\listtemplateid1907811784\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 -\fi-360\li643\jclisttab\tx643 }{\listname ;}\listid-125}{\list\listtemplateid1912741052\listsimple{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\chbrdr -\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li360\jclisttab\tx360 }{\listname ;}\listid-120}{\list\listtemplateid-51363132\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-360\li360\jclisttab\tx360 }{\listname ;}\listid-119}{\list\listtemplateid947971744\listsimple{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 -\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext\'01*;}{\levelnumbers;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }{\listname ;}\listid-2}}{\*\listoverridetable{\listoverride\listid-120\listoverridecount0\ls1} -{\listoverride\listid-129\listoverridecount0\ls2}{\listoverride\listid-119\listoverridecount0\ls3}{\listoverride\listid-125\listoverridecount0\ls4}{\listoverride\listid-120\listoverridecount0\ls5}{\listoverride\listid-129\listoverridecount0\ls6} -{\listoverride\listid-119\listoverridecount0\ls7}{\listoverride\listid-125\listoverridecount0\ls8}{\listoverride\listid-120\listoverridecount0\ls9}{\listoverride\listid-129\listoverridecount0\ls10}{\listoverride\listid-119\listoverridecount0\ls11} -{\listoverride\listid-125\listoverridecount0\ls12}{\listoverride\listid-120\listoverridecount0\ls13}{\listoverride\listid-129\listoverridecount0\ls14}{\listoverride\listid-119\listoverridecount0\ls15}{\listoverride\listid-125\listoverridecount0\ls16} -{\listoverride\listid-120\listoverridecount0\ls17}{\listoverride\listid-129\listoverridecount0\ls18}{\listoverride\listid-119\listoverridecount0\ls19}{\listoverride\listid-125\listoverridecount0\ls20}{\listoverride\listid-120\listoverridecount0\ls21} -{\listoverride\listid-129\listoverridecount0\ls22}{\listoverride\listid-119\listoverridecount0\ls23}{\listoverride\listid-125\listoverridecount0\ls24}{\listoverride\listid-120\listoverridecount0\ls25}{\listoverride\listid-129\listoverridecount0\ls26} -{\listoverride\listid-119\listoverridecount0\ls27}{\listoverride\listid-125\listoverridecount0\ls28}{\listoverride\listid-120\listoverridecount0\ls29}{\listoverride\listid-129\listoverridecount0\ls30}{\listoverride\listid-119\listoverridecount0\ls31} -{\listoverride\listid-125\listoverridecount0\ls32}{\listoverride\listid-120\listoverridecount0\ls33}{\listoverride\listid-129\listoverridecount0\ls34}{\listoverride\listid-119\listoverridecount0\ls35}{\listoverride\listid-125\listoverridecount0\ls36} -{\listoverride\listid-120\listoverridecount0\ls37}{\listoverride\listid-129\listoverridecount0\ls38}{\listoverride\listid-119\listoverridecount0\ls39}{\listoverride\listid-125\listoverridecount0\ls40}{\listoverride\listid-2\listoverridecount1{\lfolevel -\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent283{\leveltext\'01\u-3991 ?;}{\levelnumbers;}\f30\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-283\li283 -}}\ls41}{\listoverride\listid-2\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent283{\leveltext\'01\u-3991 ?;}{\levelnumbers;}\f30\chbrdr -\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-283\li283 }}\ls42}}{\info{\author Fedor}{\operator Fedor}{\creatim\yr2013\mo11\dy30\hr23\min4}{\revtim\yr2014\mo12\dy29\hr22\min52}{\version102}{\edmins95}{\nofpages8}{\nofwords1514}{\nofchars8634} -{\*\company DEV}{\nofcharsws0}{\vern8249}}\margl1701\margr850\margt1134\margb1134 \deftab708\widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\horzdoc\dghspace120\dgvspace120\dghorigin1701\dgvorigin1984\dghshow0 -\dgvshow3\jcompress\viewkind4\viewscale100\nolnhtadjtbl \fet0{\*\template E:\\Documents and Settings\\Administrator\\Application Data\\Microsoft\\\'d8\'e0\'e1\'eb\'ee\'ed\'fb\\cshelp2000.dot}\sectd \linex0\sectdefaultcl {\*\pnseclvl1 +\s38\ql \li566\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin566\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext38 List Continue 2;}{\*\cs39 \additive \sbasedon10 x x-first x-last;}}{\*\listtable +{\list\listtemplateid-737142542\listsimple{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li643 +\jclisttab\tx643 }{\listname ;}\listid-129}{\list\listtemplateid1907811784\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\chbrdr +\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-360\li643\jclisttab\tx643 }{\listname ;}\listid-125}{\list\listtemplateid1912741052\listsimple{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'00.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li360\jclisttab\tx360 }{\listname ;}\listid-120}{\list\listtemplateid-51363132\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-360\li360\jclisttab\tx360 }{\listname ;}\listid-119}{\list\listtemplateid947971744\listsimple +{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext\'01*;}{\levelnumbers;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }{\listname ;}\listid-2}}{\*\listoverridetable +{\listoverride\listid-120\listoverridecount0\ls1}{\listoverride\listid-129\listoverridecount0\ls2}{\listoverride\listid-119\listoverridecount0\ls3}{\listoverride\listid-125\listoverridecount0\ls4}{\listoverride\listid-120\listoverridecount0\ls5} +{\listoverride\listid-129\listoverridecount0\ls6}{\listoverride\listid-119\listoverridecount0\ls7}{\listoverride\listid-125\listoverridecount0\ls8}{\listoverride\listid-120\listoverridecount0\ls9}{\listoverride\listid-129\listoverridecount0\ls10} +{\listoverride\listid-119\listoverridecount0\ls11}{\listoverride\listid-125\listoverridecount0\ls12}{\listoverride\listid-120\listoverridecount0\ls13}{\listoverride\listid-129\listoverridecount0\ls14}{\listoverride\listid-119\listoverridecount0\ls15} +{\listoverride\listid-125\listoverridecount0\ls16}{\listoverride\listid-120\listoverridecount0\ls17}{\listoverride\listid-129\listoverridecount0\ls18}{\listoverride\listid-119\listoverridecount0\ls19}{\listoverride\listid-125\listoverridecount0\ls20} +{\listoverride\listid-120\listoverridecount0\ls21}{\listoverride\listid-129\listoverridecount0\ls22}{\listoverride\listid-119\listoverridecount0\ls23}{\listoverride\listid-125\listoverridecount0\ls24}{\listoverride\listid-120\listoverridecount0\ls25} +{\listoverride\listid-129\listoverridecount0\ls26}{\listoverride\listid-119\listoverridecount0\ls27}{\listoverride\listid-125\listoverridecount0\ls28}{\listoverride\listid-120\listoverridecount0\ls29}{\listoverride\listid-129\listoverridecount0\ls30} +{\listoverride\listid-119\listoverridecount0\ls31}{\listoverride\listid-125\listoverridecount0\ls32}{\listoverride\listid-120\listoverridecount0\ls33}{\listoverride\listid-129\listoverridecount0\ls34}{\listoverride\listid-119\listoverridecount0\ls35} +{\listoverride\listid-125\listoverridecount0\ls36}{\listoverride\listid-120\listoverridecount0\ls37}{\listoverride\listid-129\listoverridecount0\ls38}{\listoverride\listid-119\listoverridecount0\ls39}{\listoverride\listid-125\listoverridecount0\ls40} +{\listoverride\listid-120\listoverridecount0\ls41}{\listoverride\listid-129\listoverridecount0\ls42}{\listoverride\listid-119\listoverridecount0\ls43}{\listoverride\listid-125\listoverridecount0\ls44}{\listoverride\listid-120\listoverridecount0\ls45} +{\listoverride\listid-129\listoverridecount0\ls46}{\listoverride\listid-119\listoverridecount0\ls47}{\listoverride\listid-125\listoverridecount0\ls48}{\listoverride\listid-2\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent283{\leveltext\'01\u-3991 ?;}{\levelnumbers;}\f30\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \fi-283\li283 }}\ls49}{\listoverride\listid-2 +\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent283{\leveltext\'01\u-3991 ?;}{\levelnumbers;}\f30\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1\fbias0 \fi-283\li283 }}\ls50}}{\info{\author Fedor}{\operator Fedor}{\creatim\yr2013\mo11\dy30\hr23\min4}{\revtim\yr2015\mo11\dy22\hr17\min27}{\version105}{\edmins185}{\nofpages8}{\nofwords1514}{\nofchars8634}{\*\company DEV} +{\nofcharsws0}{\vern8249}}\margl1701\margr850\margt1134\margb1134 \deftab708\widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\horzdoc\dghspace120\dgvspace120\dghorigin1701\dgvorigin1984\dghshow0\dgvshow3 +\jcompress\viewkind4\viewscale100\nolnhtadjtbl \fet0{\*\template E:\\Documents and Settings\\Administrator\\Application Data\\Microsoft\\\'d8\'e0\'e1\'eb\'ee\'ed\'fb\\cshelp2000.dot}\sectd \linex0\sectdefaultcl {\*\pnseclvl1 \pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5 \pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain \s17\ql \li0\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin0\itap0 \i\f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 { @@ -70,20 +72,19 @@ Synonyms;}{\*\cs33 \additive \super \sbasedon10 endnote reference;}{\s34\ql \fi- \par }\pard\plain \ql \li0\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f28 \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\b0\f28 About ScummVM Help -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls41\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls41\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {About ScummVM Help +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls49\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls49\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {About ScummVM Help \par }\pard\plain \ql \li0\ri0\sa120\widctlpar{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f28 -\par }{ -This help file based on ScummVM forum thread with some elaborations(in Anotherguest section) and text correction. If you wish add some text or translate you may download and modify source document from https://sourceforge.net/projects/scummvms60git/ and t -hen send me to fedor_qd@mail.ru +\par }{This help file based on ScummVM forum thread with some elaborations(in Anotherguest section) and text correction. If you wish add some text or translate you may download and modify source document from https://sourceforge.net/projects/scummvms60git/ a +nd then send me to fedor_qd@mail.ru \par Feel free to replace, merge or write you own instead 1st, 2nd and 3rd guides. Other sections require strict translations. And don\rquote t forget add your name :-) \par First guide contain help by Anotherguest, second - VincentJ, third - murgo. This doc created by Fedor Strizhniou. \par Enjoys, cheers! Always yours =)}{\lang1059\langfe1033\langnp1059 \par }{\f29 \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\b0\f28 1st guide -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls41\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls41\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, UIQ, UIQ3, S80, s80, S90, s90 +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls49\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls49\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, UIQ, UIQ3, S80, s80, S90, s90 \par }\pard\plain \ql \li0\ri0\sa120\widctlpar{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f28 \par }{UIQ3 devices: To the top right (holding the phone portrait) you four icons, from the top they are \par @@ -124,22 +125,22 @@ hen send me to fedor_qd@mail.ru \par \par What are these Joystick, Keyboard and Cursor modes anyway? \par }{\f28 -\par }{Joystick mode sends SDL joystick events to ScummVM which acts as a mouse control in ScummVM. Cursor mode sends keyboard arrows instead, so for example it can be used to navigate -through directorylist (one hand use perhaps!?) or save games etc. Keyboard mode is only available for S60 and enables multi-tap to enter text characters in save dialogs. These modes are implemented at the underlying SDL level, so this determines the types - of events that ScummVM receives from SDL. +\par }{Joystick mode sends SDL joystick events to ScummVM which acts as a mouse control in ScummVM. Cursor mode sends keyboard arrows instead, so for exampl +e it can be used to navigate through directorylist (one hand use perhaps!?) or save games etc. Keyboard mode is only available for S60 and enables multi-tap to enter text characters in save dialogs. These modes are implemented at the underlying SDL level, + so this determines the types of events that ScummVM receives from SDL. \par What are these Shrinked, Zoomed and Upscaled modes anyway? \par -\par Shrink displays the game on your screen but in a shrinked way, either in Port -rait or Landscape mode, so not all the pixels can be seen. Zoom mode uses the maximum resolution of your phone displaying a smaller part of the game zoomed at 1:1 pixels. For scrolling in S60 Zoom mode: 0+Cursor keys to scroll around, 0+Ok button to cente -r view. Upscale tries to fill the larger screens on S80/S90 devices in a better way for low resolution games. Currently it uses a pixel interpolation upscaling routine. +\par Shrink displays the game on your screen but in a shrinked way, either in Portrait or Landscape mode, so not all the pixels ca +n be seen. Zoom mode uses the maximum resolution of your phone displaying a smaller part of the game zoomed at 1:1 pixels. For scrolling in S60 Zoom mode: 0+Cursor keys to scroll around, 0+Ok button to center view. Upscale tries to fill the larger screens + on S80/S90 devices in a better way for low resolution games. Currently it uses a pixel interpolation upscaling routine. \par \par You can also use a bluetooth mouse with S60v3 devices to control your game. You need the bluetooth hid library from Hinkka http://koti.mbnet.fi/hinkka/Download.html to get it to work properly. \par }{\f29 \par \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\b0\f28 2nd guide -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls41\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls41\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, s60 +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls49\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls49\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, s60 \par }\pard\plain \ql \li0\ri0\sa120\widctlpar{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f28 \par \par }{More user-friendly guide for Nokia phones (based on N96 but should apply to most phones) @@ -164,8 +165,8 @@ r view. Upscale tries to fill the larger screens on S80/S90 devices in a better \par }{1 - Change Input. \par This is the option you'll probably use the most. There are three settings; A,C and J. \par -\par A - This is the "Text Input" mode. It allows you to type directly into ScummVM as if you were using a keyboard. Type the same way you would when sending a text message o -ff of your phone. Please note that the pointer is disabled when in this mode. Don't forget to exit Configuration Mode before typing! +\par A - This is the "Text Input" mode. It allows you to type directly into ScummVM as if you were using a keyboard. Type the same way you would wh +en sending a text message off of your phone. Please note that the pointer is disabled when in this mode. Don't forget to exit Configuration Mode before typing! \par \par C - This is the "Cursor" mode. This emulates the arrow keys of the keyboard. Some games require using this instead of the mouse (e.g. the destruction derby section towards the end of Full Throttle). \par @@ -180,9 +181,9 @@ ff of your phone. Please note that the pointer is disabled when in this mode. Do \par Only applies to Landscape mode, simply swaps the screen output between having the phone tilted on its left side or on its right side. \par \par 4 - Toggle Zoom On and Off -\par Zooms in on a portion of the screen. Handy for when you are looking through a screen fo -r items or having trouble reading subtitles. Use the navigation buttons for panning around the play area. Don't forget you'll have to exit out of Configuration Mode before you can move the pointer again. Exiting Configuration Mode does not reset the zoom -level. +\par Zooms in on a portion of the screen. Handy for when you are +looking through a screen for items or having trouble reading subtitles. Use the navigation buttons for panning around the play area. Don't forget you'll have to exit out of Configuration Mode before you can move the pointer again. Exiting Configuration Mo +de does not reset the zoom level. \par }{\f29 \par }{5 & 6 - Unused \par @@ -203,12 +204,13 @@ level. \par \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\b0\f28 3rd guide -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls41\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls41\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, s60 +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls49\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls49\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Controls, Virtual keyboard, Shortcuts, ScummVM, Tips, S60, s60 \par }\pard\plain \ql \li0\ri0\sa120\widctlpar{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f28 \par -\par }{ScummVM keys on Nokia e71 (most likely on any other qwerty-device, too), tested on version 0.14.0svn (Feb. 18 20 -09 05:56:07). Number keys are inserted by first pressing fn-key (leftmost key at bottom row on E71) and then pressing correct key (e.g. 5 is fn+g). You don't have to press both keys simultaneously. +\par }{ScummVM keys on Nokia + e71 (most likely on any other qwerty-device, too), tested on version 0.14.0svn (Feb. 18 2009 05:56:07). Number keys are inserted by first pressing fn-key (leftmost key at bottom row on E71) and then pressing correct key (e.g. 5 is fn+g). You don't have t +o press both keys simultaneously. \par \par Basic keys: \par @@ -258,8 +260,8 @@ level. \par p -- punch (hand) \par \par AGI games (King's Quest, Police Quest etc.): -\par The games work beautifully on the E71, but there's some stupid bugs (in input). I recall finding some debug keys and "last sentence" / "inventory" -keys in earlier version, but I can't find them any more. Also you can't turn on sirens in Police Quest, whi -ch kinda makes it unplayable. +\par The games work beautifully on the E71, but there's some stupid bugs (in input). I recall finding some debug keys and "last sentence" / "inventory" -keys in earlier version, bu +t I can't find them any more. Also you can't turn on sirens in Police Quest, which kinda makes it unplayable. \par \par There's good side and bad side to each input mode: \par Keyboard (I use this primarily) @@ -280,8 +282,8 @@ ch kinda makes it unplayable. \par }{\f28 \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {ScummVM1 engines list -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls42\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls42\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Supported engines +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls50\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls50\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Supported engines \par }\pard\plain \ql \li0\ri0\sa120\widctlpar{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {access \par agi \par agos @@ -315,9 +317,10 @@ ch kinda makes it unplayable. \par \par }\pard\plain \s2\ql \li0\ri0\sb120\sa120\keepn\widctlpar\brdrt\brdrs\brdrw30\brsp20 \brdrb\brdrs\brdrw30\brsp20 \tqr\tx9072{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\nooverflow\faroman\outlinelevel1\rin0\lin0\itap0 \b\f1\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {ScummVM2 engines list -\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls42\pnrnot0 -\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls42\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Supported engines +\par {\pntext\pard\plain\s26 \f30\fs20\lang2057\langfe1033\langnp2057\langfenp1033 \loch\af30\dbch\af0\hich\f30 \'69\tab}}\pard\plain \s26\ql \fi-283\li283\ri0\sa120\widctlpar\brdrb\brdrs\brdrw15\brsp20 {\*\pn \pnlvlblt\ilvl0\ls50\pnrnot0 +\pnf30\pnstart1\pnindent283\pnhang{\pntxtb i}}\nooverflow\faroman\ls50\rin0\lin283\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {Supported engines \par }\pard\plain \ql \li0\ri0\sa120\widctlpar\nooverflow\faroman\rin0\lin0\itap0 \f1\fs20\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {avalanche +\par }{\cs39 bbvs}{ \par cge \par composer \par draci @@ -336,6 +339,7 @@ ch kinda makes it unplayable. \par prince \par sci \par \tab SCI32 +\par sherlock \par sky \par sword1 \par sword2 diff --git a/backends/platform/symbian/help/build_help.mk b/backends/platform/symbian/help/build_help.mk index 7a18ad8252..b2910f9c11 100644 --- a/backends/platform/symbian/help/build_help.mk +++ b/backends/platform/symbian/help/build_help.mk @@ -18,12 +18,9 @@ clean : del ScummVM.hlp del ScummVM.hlp.hrh -bld : - cshlpcmp ScummVM.xml - ifeq (WINS,$(findstring WINS, $(PLATFORM))) copy ScummVM.hlp $(EPOCROOT)epoc32\$(PLATFORM)\c\resource\help endif -freeze lib cleanlib final resource savespace releasables : +bld freeze lib cleanlib final resource savespace releasables : diff --git a/backends/platform/symbian/mmp/config.mmh b/backends/platform/symbian/mmp/config.mmh index 075697d99a..8a81fd2b78 100644 --- a/backends/platform/symbian/mmp/config.mmh +++ b/backends/platform/symbian/mmp/config.mmh @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2014 Fedor Strizhniou * * ScummVM is the legal property of its developers, whose names @@ -29,19 +29,6 @@ // Common EPOC MMP makefiles option storage for ScummVM // -/* - * MACRO REMOVE_UNDEFINED used for bypass this bug - - * http://sourceforge.net/p/scummvm/bugs/6437/ - * Requre change in epoc32\include\libc\sys\unistd.h by - * - * #ifndef REMOVE_UNDEFINED - * #define remove(x) unlink(x) - * #define wremove(x) wunlink(x) - * #endif //REMOVE_UNDEFINED - * - * Affects Avalanche, CGE2 and Tsage - */ - // *** Definitions OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp diff --git a/backends/platform/symbian/mmp/scummvm_access.mmp.in b/backends/platform/symbian/mmp/scummvm_access.mmp.in index 4f8b258ec0..8e1c85f3e3 100644 --- a/backends/platform/symbian/mmp/scummvm_access.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_access.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM project * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_agi.mmp.in b/backends/platform/symbian/mmp/scummvm_agi.mmp.in index f24e3ef1a9..dcea8f023b 100644 --- a/backends/platform/symbian/mmp/scummvm_agi.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agi.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_agos.mmp.in b/backends/platform/symbian/mmp/scummvm_agos.mmp.in index ff15c8d5b0..c5eb4d8ad4 100644 --- a/backends/platform/symbian/mmp/scummvm_agos.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agos.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_avalanche.mmp.in b/backends/platform/symbian/mmp/scummvm_avalanche.mmp.in index 40047bb6b7..a9580c643e 100644 --- a/backends/platform/symbian/mmp/scummvm_avalanche.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_avalanche.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names @@ -33,7 +33,6 @@ TARGET scummvm_avalanche.lib TARGETTYPE lib -MACRO REMOVE_UNDEFINED #include "config.mmh" //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_base.mmp.in b/backends/platform/symbian/mmp/scummvm_base.mmp.in index 88aa689f5b..58a56c95ac 100644 --- a/backends/platform/symbian/mmp/scummvm_base.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_base.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -38,6 +38,7 @@ TARGETTYPE lib OPTION GCCE -I'/Symbian/S60_5th_Edition_SDK_v1.0/epoc32/include/png' // Note: the LIB:*.lib statements are used by AdaptAllMMPs.pl, so don't remove them! +MACRO USE_SYSTEM_REMOVE //START_AUTO_MACROS_MASTER// // empty base file, will be updated by Perl build scripts @@ -99,6 +100,7 @@ SOURCEPATH ..\..\..\..\audio //STOP_AUTO_OBJECTS_AUDIO_// SOURCE softsynth\fmtowns_pc98\towns_pc98_fmsynth.cpp // Included since its excluded by filter +SOURCE miles_mt32.cpp #if defined (WINS) SOURCE rate.cpp // WINS emulator version: add regular .cpp diff --git a/backends/platform/symbian/mmp/scummvm_bbvs.mmp.in b/backends/platform/symbian/mmp/scummvm_bbvs.mmp.in index 20d91a910d..864d019c97 100644 --- a/backends/platform/symbian/mmp/scummvm_bbvs.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_bbvs.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2014 Fedor Strizhniou - Epoc project file * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_cge.mmp.in b/backends/platform/symbian/mmp/scummvm_cge.mmp.in index 29bdad0600..bcfbec8a70 100644 --- a/backends/platform/symbian/mmp/scummvm_cge.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cge.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_cge2.mmp.in b/backends/platform/symbian/mmp/scummvm_cge2.mmp.in index f9c97e1d83..f09a2b2427 100644 --- a/backends/platform/symbian/mmp/scummvm_cge2.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cge2.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -32,7 +32,6 @@ TARGET scummvm_cge2.lib TARGETTYPE lib -MACRO REMOVE_UNDEFINED #include "config.mmh" //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_cine.mmp.in b/backends/platform/symbian/mmp/scummvm_cine.mmp.in index f93cd30c9f..ff55b1bc1d 100644 --- a/backends/platform/symbian/mmp/scummvm_cine.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cine.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_composer.mmp.in b/backends/platform/symbian/mmp/scummvm_composer.mmp.in index 72117fbc19..c974df098a 100644 --- a/backends/platform/symbian/mmp/scummvm_composer.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_composer.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in index ceb5a2124f..134c924d2a 100644 --- a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_draci.mmp.in b/backends/platform/symbian/mmp/scummvm_draci.mmp.in index 77750ff696..66d111efb0 100644 --- a/backends/platform/symbian/mmp/scummvm_draci.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_draci.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in index 7f39126e68..b3128978c3 100644 --- a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_dreamweb.mmp.in b/backends/platform/symbian/mmp/scummvm_dreamweb.mmp.in index 22d5050597..b7841ee9cb 100644 --- a/backends/platform/symbian/mmp/scummvm_dreamweb.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_dreamweb.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_fullpipe.mmp.in b/backends/platform/symbian/mmp/scummvm_fullpipe.mmp.in index dc5d4da6b7..85bbfa69be 100644 --- a/backends/platform/symbian/mmp/scummvm_fullpipe.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_fullpipe.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_gob.mmp.in b/backends/platform/symbian/mmp/scummvm_gob.mmp.in index a46ee9c403..b91fbb68b7 100644 --- a/backends/platform/symbian/mmp/scummvm_gob.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_gob.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_groovie.mmp.in b/backends/platform/symbian/mmp/scummvm_groovie.mmp.in index a2a3d5c47d..effff997df 100644 --- a/backends/platform/symbian/mmp/scummvm_groovie.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_groovie.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_hopkins.mmp.in b/backends/platform/symbian/mmp/scummvm_hopkins.mmp.in index 82728942a7..4df1db9be6 100644 --- a/backends/platform/symbian/mmp/scummvm_hopkins.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_hopkins.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_hugo.mmp.in b/backends/platform/symbian/mmp/scummvm_hugo.mmp.in index 1f250bda3f..c294588147 100644 --- a/backends/platform/symbian/mmp/scummvm_hugo.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_hugo.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in index f3b30b1318..ad6dfebe71 100644 --- a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in b/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in index 38d63b4648..9c49fe7dcb 100644 --- a/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_lure.mmp.in b/backends/platform/symbian/mmp/scummvm_lure.mmp.in index 2922c9df49..4982576432 100644 --- a/backends/platform/symbian/mmp/scummvm_lure.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_lure.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_m4.mmp.in b/backends/platform/symbian/mmp/scummvm_m4.mmp.in index 0b3a0b60e4..5a87e3fd14 100644 --- a/backends/platform/symbian/mmp/scummvm_m4.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_m4.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_made.mmp.in b/backends/platform/symbian/mmp/scummvm_made.mmp.in index e240685753..ab809351c5 100644 --- a/backends/platform/symbian/mmp/scummvm_made.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_made.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_mads.mmp.in b/backends/platform/symbian/mmp/scummvm_mads.mmp.in index e5e990dd2d..65224af700 100644 --- a/backends/platform/symbian/mmp/scummvm_mads.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_mads.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2014 Fedor Strizhniou - Epoc project file * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in b/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in index 19bed823d5..ff3bce9767 100644 --- a/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_mortevielle.mmp.in b/backends/platform/symbian/mmp/scummvm_mortevielle.mmp.in index 06f9333c37..e04006ea85 100644 --- a/backends/platform/symbian/mmp/scummvm_mortevielle.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_mortevielle.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_neverhood.mmp.in b/backends/platform/symbian/mmp/scummvm_neverhood.mmp.in index b6deeb2d73..05f195c2c4 100644 --- a/backends/platform/symbian/mmp/scummvm_neverhood.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_neverhood.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in index c61e40626c..933deec4b7 100644 --- a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_pegasus.mmp.in b/backends/platform/symbian/mmp/scummvm_pegasus.mmp.in index 3ef85bf29d..e292764ed9 100644 --- a/backends/platform/symbian/mmp/scummvm_pegasus.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_pegasus.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_prince.mmp.in b/backends/platform/symbian/mmp/scummvm_prince.mmp.in index 7dfec04f46..c0b8193c38 100644 --- a/backends/platform/symbian/mmp/scummvm_prince.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_prince.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM project * Copyright (C) 2014 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_queen.mmp.in b/backends/platform/symbian/mmp/scummvm_queen.mmp.in index 3a15fdd0a2..106037e741 100644 --- a/backends/platform/symbian/mmp/scummvm_queen.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_queen.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_saga.mmp.in b/backends/platform/symbian/mmp/scummvm_saga.mmp.in index ce5de459b2..230b54ed07 100644 --- a/backends/platform/symbian/mmp/scummvm_saga.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_saga.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_sci.mmp.in b/backends/platform/symbian/mmp/scummvm_sci.mmp.in index 49484b8c7c..42c79c4ccf 100644 --- a/backends/platform/symbian/mmp/scummvm_sci.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sci.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in index 60c54697d0..c727a6f1be 100644 --- a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_sherlock.mmp.in b/backends/platform/symbian/mmp/scummvm_sherlock.mmp.in new file mode 100644 index 0000000000..4c4117f39e --- /dev/null +++ b/backends/platform/symbian/mmp/scummvm_sherlock.mmp.in @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL + * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System + * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer + * Copyright (C) 2005-2016 The ScummVM Team + * Copyright (C) 2015 Strizniou Fedor + * + * 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. + * + */ + +// +// EPOC MMP makefile project for ScummVM +// + +// *** Definitions + +TARGET scummvm_sherlock.lib +TARGETTYPE lib +#include "config.mmh" + +//START_AUTO_MACROS_SLAVE// + + // empty base file, will be updated by Perl build scripts + +//STOP_AUTO_MACROS_SLAVE// + +// *** SOURCE files + +SOURCEPATH ..\..\..\..\engines\sherlock + +//START_AUTO_OBJECTS_SHERLOCK_// + + // empty base file, will be updated by Perl build scripts + +//STOP_AUTO_OBJECTS_SHERLOCK_// + diff --git a/backends/platform/symbian/mmp/scummvm_sky.mmp.in b/backends/platform/symbian/mmp/scummvm_sky.mmp.in index 5363bb32ef..621dcd8699 100644 --- a/backends/platform/symbian/mmp/scummvm_sky.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sky.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in index b4b2b91dae..0904732a1b 100644 --- a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in index a8c963a618..48aea59db3 100644 --- a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_sword25.mmp.in b/backends/platform/symbian/mmp/scummvm_sword25.mmp.in index 67efc10c45..49e37e3140 100644 --- a/backends/platform/symbian/mmp/scummvm_sword25.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword25.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in b/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in index 9551eda7b6..85091abf2c 100644 --- a/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_testbed.mmp.in b/backends/platform/symbian/mmp/scummvm_testbed.mmp.in index 24b87d154c..3302bfe8af 100644 --- a/backends/platform/symbian/mmp/scummvm_testbed.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_testbed.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor * * ScummVM is the legal property of its developers, whose names diff --git a/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in b/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in index a5a62ffc15..21fe837a8a 100644 --- a/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_toltecs.mmp.in b/backends/platform/symbian/mmp/scummvm_toltecs.mmp.in index 1512dfed06..00f2d2261d 100644 --- a/backends/platform/symbian/mmp/scummvm_toltecs.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_toltecs.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_tony.mmp.in b/backends/platform/symbian/mmp/scummvm_tony.mmp.in index 18c4f6d3cb..a05fba8842 100644 --- a/backends/platform/symbian/mmp/scummvm_tony.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tony.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_toon.mmp.in b/backends/platform/symbian/mmp/scummvm_toon.mmp.in index 9c7568cc8b..1f268b3c00 100644 --- a/backends/platform/symbian/mmp/scummvm_toon.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_toon.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_touche.mmp.in b/backends/platform/symbian/mmp/scummvm_touche.mmp.in index 73e14ee691..636b448168 100644 --- a/backends/platform/symbian/mmp/scummvm_touche.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_touche.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_tsage.mmp.in b/backends/platform/symbian/mmp/scummvm_tsage.mmp.in index e18db61683..d45a808628 100644 --- a/backends/platform/symbian/mmp/scummvm_tsage.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tsage.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -32,7 +32,6 @@ TARGET scummvm_tsage.lib TARGETTYPE lib -MACRO REMOVE_UNDEFINED #include "config.mmh" //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_tucker.mmp.in b/backends/platform/symbian/mmp/scummvm_tucker.mmp.in index aef8841cf2..68c81aebbb 100644 --- a/backends/platform/symbian/mmp/scummvm_tucker.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tucker.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_voyeur.mmp.in b/backends/platform/symbian/mmp/scummvm_voyeur.mmp.in index 86dc32b3f8..9e02567651 100644 --- a/backends/platform/symbian/mmp/scummvm_voyeur.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_voyeur.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -32,10 +32,7 @@ TARGET scummvm_voyeur.lib TARGETTYPE lib -OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp -OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char -ALWAYS_BUILD_AS_ARM +#include "config.mmh" //START_AUTO_MACROS_SLAVE// @@ -52,20 +49,3 @@ SOURCEPATH ..\..\..\..\engines\voyeur // empty base file, will be updated by Perl build scripts //STOP_AUTO_OBJECTS_VOYEUR_// - -// *** Include paths - -USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\gui ..\..\..\..\audio ..\src - -SYSTEMINCLUDE \epoc32\include\freetype -SYSTEMINCLUDE \epoc32\include\mpeg2dec -SYSTEMINCLUDE \epoc32\include\jpeg -SYSTEMINCLUDE \epoc32\include\png -SYSTEMINCLUDE \epoc32\include\ESDL -SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version -SYSTEMINCLUDE \epoc32\include\libc -SYSTEMINCLUDE \epoc32\include\theora -SYSTEMINCLUDE \epoc32\include\tremor -SYSTEMINCLUDE \epoc32\include -SYSTEMINCLUDE ..\src // for portdefs.h diff --git a/backends/platform/symbian/mmp/scummvm_wintermute.mmp.in b/backends/platform/symbian/mmp/scummvm_wintermute.mmp.in index 716484755f..d5eef5d3a3 100644 --- a/backends/platform/symbian/mmp/scummvm_wintermute.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_wintermute.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/mmp/scummvm_zvision.mmp.in b/backends/platform/symbian/mmp/scummvm_zvision.mmp.in index e647275dbc..1bd34dfe38 100644 --- a/backends/platform/symbian/mmp/scummvm_zvision.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_zvision.mmp.in @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2013 The ScummVM project + * Copyright (C) 2005-2016 The ScummVM Team * Copyright (C) 2013 Strizniou Fedor - Epoc project file * * ScummVM is the legal property of its developers, whose names @@ -33,6 +33,7 @@ TARGET scummvm_zvision.lib TARGETTYPE lib +USERINCLUDE ..\..\..\..\engines\zvision\graphics #include "config.mmh" //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/res/ScummVmAif.rss b/backends/platform/symbian/res/ScummVmAif.rss index 2dbf436a8f..3bb86ca796 100644 --- a/backends/platform/symbian/res/ScummVmAif.rss +++ b/backends/platform/symbian/res/ScummVmAif.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/res/scummvm.rss b/backends/platform/symbian/res/scummvm.rss index 6a0ab24ff0..67af8b77d0 100644 --- a/backends/platform/symbian/res/scummvm.rss +++ b/backends/platform/symbian/res/scummvm.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/res/scummvm_A0000658.rss b/backends/platform/symbian/res/scummvm_A0000658.rss index 562fef54c6..2f478962de 100644 --- a/backends/platform/symbian/res/scummvm_A0000658.rss +++ b/backends/platform/symbian/res/scummvm_A0000658.rss @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/src/ScummVm.hrh b/backends/platform/symbian/src/ScummVm.hrh index 316e4d08f6..927da51e3d 100644 --- a/backends/platform/symbian/src/ScummVm.hrh +++ b/backends/platform/symbian/src/ScummVm.hrh @@ -2,7 +2,7 @@ * Copyright (C) 2003-2005 Andreas 'Sprawl' Karlsson - Original EPOC port, ESDL * Copyright (C) 2003-2005 Lars 'AnotherGuest' Persson - Original EPOC port, Audio System * Copyright (C) 2005 Jurgen 'SumthinWicked' Braam - EPOC/CVS maintainer - * Copyright (C) 2005-2014 The ScummVM Team + * Copyright (C) 2005-2016 The ScummVM Team * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT diff --git a/backends/platform/symbian/src/SymbianOS.cpp b/backends/platform/symbian/src/SymbianOS.cpp index 1fca7f5df5..4d417b5a66 100644 --- a/backends/platform/symbian/src/SymbianOS.cpp +++ b/backends/platform/symbian/src/SymbianOS.cpp @@ -63,6 +63,8 @@ OSystem_SDL_Symbian::OSystem_SDL_Symbian() void OSystem_SDL_Symbian::init() { _RFs = &CEikonEnv::Static()->FsSession(); + // Use iconless window: it uses the EScummVM.aif file for the icon. + _window = new SdlIconlessWindow(); _fsFactory = new SymbianFilesystemFactory(); OSystem_SDL::init(); } @@ -109,7 +111,7 @@ void OSystem_SDL_Symbian::initBackend() { _mixerManager->init(); } if (_graphicsManager == 0) - _graphicsManager = new SymbianSdlGraphicsManager(_eventSource); + _graphicsManager = new SymbianSdlGraphicsManager(_eventSource, _window); // Call parent implementation of this method OSystem_SDL::initBackend(); @@ -171,10 +173,6 @@ Common::String OSystem_SDL_Symbian::getDefaultConfigFileName() { return configFile; } -void OSystem_SDL_Symbian::setupIcon() { - // Don't for Symbian: it uses the EScummVM.aif file for the icon. -} - RFs& OSystem_SDL_Symbian::FsSession() { return *_RFs; } @@ -199,7 +197,3 @@ void* scumm_bsearch(const void *key, const void *base, size_t nmemb, size_t size return NULL; } -int remove(const char *path) -{ - return unlink(path); -} diff --git a/backends/platform/symbian/src/SymbianOS.h b/backends/platform/symbian/src/SymbianOS.h index 57a471f1a9..617540941d 100644 --- a/backends/platform/symbian/src/SymbianOS.h +++ b/backends/platform/symbian/src/SymbianOS.h @@ -39,7 +39,6 @@ public: virtual void engineDone(); virtual bool setGraphicsMode(const char *name); virtual Common::String getDefaultConfigFileName(); - virtual void setupIcon(); /** * Returns reference to File session diff --git a/backends/platform/symbian/src/portdefs.h b/backends/platform/symbian/src/portdefs.h index 1fb941963b..e2465e4767 100644 --- a/backends/platform/symbian/src/portdefs.h +++ b/backends/platform/symbian/src/portdefs.h @@ -58,11 +58,18 @@ typedef signed long int int32; // re-define those data types. #define SCUMMVM_DONT_DEFINE_TYPES -#define SMALL_SCREEN_DEVICE +// Hide the macro "remove" defined in unistd.h from anywere except where +// we explicitly require it. This lets us use the name "remove" in engines. +// Must be after including unistd.h . +#ifndef SYMBIAN_USE_SYSTEM_REMOVE +#undef remove +#endif + +#define GUI_ONLY_FULLSCREEN +#define GUI_ENABLE_KEYSDIALOG #define DISABLE_COMMAND_LINE #define USE_RGB_COLOR -int remove(const char *path); #if defined(USE_TREMOR) && !defined(USE_VORBIS) #define USE_VORBIS // make sure this one is defined together with USE_TREMOR! diff --git a/backends/platform/tizen/graphics.cpp b/backends/platform/tizen/graphics.cpp index 9b23e3fe78..61dbfc38fb 100644 --- a/backends/platform/tizen/graphics.cpp +++ b/backends/platform/tizen/graphics.cpp @@ -56,6 +56,7 @@ result TizenGraphicsManager::Construct() { loadEgl(); // Notify the OpenGL code about our context. + setContextType(OpenGL::kContextGLES); // We default to RGB565 and RGBA5551 which is closest to the actual output // mode we setup. @@ -127,7 +128,6 @@ void TizenGraphicsManager::setReady() { void TizenGraphicsManager::updateScreen() { if (!_initState) { OpenGLGraphicsManager::updateScreen(); - eglSwapBuffers(_eglDisplay, _eglSurface); } } @@ -203,3 +203,11 @@ bool TizenGraphicsManager::loadVideoMode(uint requestedWidth, uint requestedHeig // using a fixed output size we do nothing like that here. return true; } + +void TizenGraphicsManager::refreshScreen() { + eglSwapBuffers(_eglDisplay, _eglSurface); +} + +void *TizenGraphicsManager::getProcAddress(const char *name) const { + return eglGetProcAddress(name); +} diff --git a/backends/platform/tizen/graphics.h b/backends/platform/tizen/graphics.h index f1d4498650..1798b078d8 100644 --- a/backends/platform/tizen/graphics.h +++ b/backends/platform/tizen/graphics.h @@ -61,6 +61,10 @@ protected: bool loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format); + void refreshScreen(); + + void *getProcAddress(const char *name) const; + const Graphics::Font *getFontOSD(); private: diff --git a/backends/platform/tizen/system.cpp b/backends/platform/tizen/system.cpp index a235456670..1820a28791 100644 --- a/backends/platform/tizen/system.cpp +++ b/backends/platform/tizen/system.cpp @@ -81,36 +81,41 @@ struct TizenSaveFileManager : public DefaultSaveFileManager { }; bool TizenSaveFileManager::removeSavefile(const Common::String &filename) { - Common::String savePathName = getSavePath(); + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); + if (getError().getCode() != Common::kNoError) + return false; - checkPath(Common::FSNode(savePathName)); - if (getError().getCode() != Common::kNoError) { + // Obtain node if exists. + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + if (file == _saveFileCache.end()) { return false; - } + } else { + const Common::FSNode fileNode = file->_value; + // Remove from cache, this invalidates the 'file' iterator. + _saveFileCache.erase(file); + file = _saveFileCache.end(); - // recreate FSNode since checkPath may have changed/created the directory - Common::FSNode savePath(savePathName); - Common::FSNode file = savePath.getChild(filename); + String unicodeFileName; + StringUtil::Utf8ToString(fileNode.getPath().c_str(), unicodeFileName); - String unicodeFileName; - StringUtil::Utf8ToString(file.getPath().c_str(), unicodeFileName); + switch (Tizen::Io::File::Remove(unicodeFileName)) { + case E_SUCCESS: + return true; - switch (Tizen::Io::File::Remove(unicodeFileName)) { - case E_SUCCESS: - return true; + case E_ILLEGAL_ACCESS: + setError(Common::kWritePermissionDenied, "Search or write permission denied: " + + file.getName()); + break; - case E_ILLEGAL_ACCESS: - setError(Common::kWritePermissionDenied, "Search or write permission denied: " + - file.getName()); - break; + default: + setError(Common::kPathDoesNotExist, "removeSavefile: '" + file.getName() + + "' does not exist or path is invalid"); + break; + } - default: - setError(Common::kPathDoesNotExist, "removeSavefile: '" + file.getName() + - "' does not exist or path is invalid"); - break; + return false; } - - return false; } // diff --git a/backends/platform/wince/portdefs.h b/backends/platform/wince/portdefs.h index 3304ee0893..c74fc39169 100644 --- a/backends/platform/wince/portdefs.h +++ b/backends/platform/wince/portdefs.h @@ -30,7 +30,8 @@ // Missing string/stdlib/assert declarations for WinCE 2.xx #if _WIN32_WCE < 300 - #define SMALL_SCREEN_DEVICE + #define GUI_ONLY_FULLSCREEN + #define GUI_ENABLE_KEYSDIALOG void *calloc(size_t n, size_t s); int isalnum(int c); diff --git a/backends/platform/wince/wince-sdl.cpp b/backends/platform/wince/wince-sdl.cpp index 96c9313c5d..02853b548e 100644 --- a/backends/platform/wince/wince-sdl.cpp +++ b/backends/platform/wince/wince-sdl.cpp @@ -420,7 +420,7 @@ void OSystem_WINCE3::initBackend() { } if (_graphicsManager == 0) - _graphicsManager = new WINCESdlGraphicsManager(_eventSource); + _graphicsManager = new WINCESdlGraphicsManager(_eventSource, _window); ((WINCESdlEventSource *)_eventSource)->init(dynamic_cast<WINCESdlGraphicsManager *>(_graphicsManager)); @@ -645,7 +645,7 @@ Common::String OSystem_WINCE3::getSystemLanguage() const { const char *posixMappingTable[][3] = { {"CAT", "ESP", "ca_ES"}, {"CSY", "CZE", "cs_CZ"}, - {"DAN", "DNK", "da_DA"}, + {"DAN", "DNK", "da_DK"}, {"DEU", "DEU", "de_DE"}, {"ESN", "ESP", "es_ES"}, {"ESP", "ESP", "es_ES"}, @@ -657,7 +657,7 @@ Common::String OSystem_WINCE3::getSystemLanguage() const { {"PLK", "POL", "pl_PL"}, {"PTB", "BRA", "pt_BR"}, {"RUS", "RUS", "ru_RU"}, - {"SVE", "SWE", "se_SE"}, + {"SVE", "SWE", "sv_SE"}, {"UKR", "UKR", "uk_UA"}, {NULL, NULL, NULL} }; diff --git a/backends/plugins/win32/win32-provider.cpp b/backends/plugins/win32/win32-provider.cpp index 5f4d405da4..ae8a5f0472 100644 --- a/backends/plugins/win32/win32-provider.cpp +++ b/backends/plugins/win32/win32-provider.cpp @@ -77,7 +77,7 @@ public: debug("Failed loading plugin '%s' (error code %d)", _filename.c_str(), (int32) GetLastError()); return false; } else { - debug(1, "Success loading plugin '%s', handle %08X", _filename.c_str(), (uint32) _dlHandle); + debug(1, "Success loading plugin '%s', handle %p", _filename.c_str(), _dlHandle); } return DynamicPlugin::loadPlugin(); diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp index 1b955a5021..8a7fba46f7 100644 --- a/backends/saves/default/default-saves.cpp +++ b/backends/saves/default/default-saves.cpp @@ -20,8 +20,18 @@ * */ +// This define lets us use the system function remove() on Symbian, which +// is disabled by default due to a macro conflict. +// See backends/platform/symbian/src/portdefs.h . +#define SYMBIAN_USE_SYSTEM_REMOVE + #include "common/scummsys.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#include "common/file.h" +#endif + #if !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) #include "backends/saves/default/default-saves.h" @@ -37,6 +47,10 @@ #include <errno.h> // for removeSavefile() #endif +#ifdef USE_LIBCURL +const char *DefaultSaveFileManager::TIMESTAMPS_FILENAME = "timestamps"; +#endif + DefaultSaveFileManager::DefaultSaveFileManager() { } @@ -54,92 +68,145 @@ void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) { } } +void DefaultSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) { + //make it refresh the cache next time it lists the saves + _cachedDirectory = ""; + + //remember the locked files list because some of these files don't exist yet + _lockedFiles = lockedFiles; +} + Common::StringArray DefaultSaveFileManager::listSavefiles(const Common::String &pattern) { - Common::String savePathName = getSavePath(); - checkPath(Common::FSNode(savePathName)); + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); if (getError().getCode() != Common::kNoError) return Common::StringArray(); - // recreate FSNode since checkPath may have changed/created the directory - Common::FSNode savePath(savePathName); + Common::HashMap<Common::String, bool> locked; + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + locked[*i] = true; + } - Common::FSDirectory dir(savePath); - Common::ArchiveMemberList savefiles; Common::StringArray results; - Common::String search(pattern); - - if (dir.listMatchingMembers(savefiles, search) > 0) { - for (Common::ArchiveMemberList::const_iterator file = savefiles.begin(); file != savefiles.end(); ++file) { - results.push_back((*file)->getName()); + for (SaveFileCache::const_iterator file = _saveFileCache.begin(), end = _saveFileCache.end(); file != end; ++file) { + if (!locked.contains(file->_key) && file->_key.matchString(pattern, true)) { + results.push_back(file->_key); } } - return results; } -Common::InSaveFile *DefaultSaveFileManager::openForLoading(const Common::String &filename) { - // Ensure that the savepath is valid. If not, generate an appropriate error. - Common::String savePathName = getSavePath(); - checkPath(Common::FSNode(savePathName)); +Common::InSaveFile *DefaultSaveFileManager::openRawFile(const Common::String &filename) { + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); if (getError().getCode() != Common::kNoError) - return 0; + return nullptr; - // recreate FSNode since checkPath may have changed/created the directory - Common::FSNode savePath(savePathName); + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + if (file == _saveFileCache.end()) { + return nullptr; + } else { + // Open the file for loading. + Common::SeekableReadStream *sf = file->_value.createReadStream(); + return sf; + } +} - Common::FSNode file = savePath.getChild(filename); - if (!file.exists()) - return 0; +Common::InSaveFile *DefaultSaveFileManager::openForLoading(const Common::String &filename) { + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); + if (getError().getCode() != Common::kNoError) + return nullptr; - // Open the file for reading - Common::SeekableReadStream *sf = file.createReadStream(); + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + if (filename == *i) { + return nullptr; //file is locked, no loading available + } + } - return Common::wrapCompressedReadStream(sf); + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + if (file == _saveFileCache.end()) { + return nullptr; + } else { + // Open the file for loading. + Common::SeekableReadStream *sf = file->_value.createReadStream(); + return Common::wrapCompressedReadStream(sf); + } } Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String &filename, bool compress) { - // Ensure that the savepath is valid. If not, generate an appropriate error. - Common::String savePathName = getSavePath(); - checkPath(Common::FSNode(savePathName)); + // Assure the savefile name cache is up-to-date. + const Common::String savePathName = getSavePath(); + assureCached(savePathName); if (getError().getCode() != Common::kNoError) - return 0; + return nullptr; + + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + if (filename == *i) { + return nullptr; //file is locked, no saving available + } + } - // recreate FSNode since checkPath may have changed/created the directory - Common::FSNode savePath(savePathName); +#ifdef USE_LIBCURL + // Update file's timestamp + Common::HashMap<Common::String, uint32> timestamps = loadTimestamps(); + timestamps[filename] = INVALID_TIMESTAMP; + saveTimestamps(timestamps); +#endif + + // Obtain node. + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + Common::FSNode fileNode; + + // If the file did not exist before, we add it to the cache. + if (file == _saveFileCache.end()) { + const Common::FSNode savePath(savePathName); + fileNode = savePath.getChild(filename); + } else { + fileNode = file->_value; + } - Common::FSNode file = savePath.getChild(filename); + // Open the file for saving. + Common::WriteStream *const sf = fileNode.createWriteStream(); + Common::OutSaveFile *const result = new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf); - // Open the file for saving - Common::WriteStream *sf = file.createWriteStream(); + // Add file to cache now that it exists. + _saveFileCache[filename] = Common::FSNode(fileNode.getPath()); - return compress ? Common::wrapCompressedWriteStream(sf) : sf; + return result; } bool DefaultSaveFileManager::removeSavefile(const Common::String &filename) { - Common::String savePathName = getSavePath(); - checkPath(Common::FSNode(savePathName)); + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); if (getError().getCode() != Common::kNoError) return false; - // recreate FSNode since checkPath may have changed/created the directory - Common::FSNode savePath(savePathName); - - Common::FSNode file = savePath.getChild(filename); - - // FIXME: remove does not exist on all systems. If your port fails to - // compile because of this, please let us know (scummvm-devel or Fingolfin). - // There is a nicely portable workaround, too: Make this method overloadable. - if (remove(file.getPath().c_str()) != 0) { + // Obtain node if exists. + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + if (file == _saveFileCache.end()) { + return false; + } else { + const Common::FSNode fileNode = file->_value; + // Remove from cache, this invalidates the 'file' iterator. + _saveFileCache.erase(file); + file = _saveFileCache.end(); + + // FIXME: remove does not exist on all systems. If your port fails to + // compile because of this, please let us know (scummvm-devel). + // There is a nicely portable workaround, too: Make this method overloadable. + if (remove(fileNode.getPath().c_str()) != 0) { #ifndef _WIN32_WCE - if (errno == EACCES) - setError(Common::kWritePermissionDenied, "Search or write permission denied: "+file.getName()); + if (errno == EACCES) + setError(Common::kWritePermissionDenied, "Search or write permission denied: "+fileNode.getName()); - if (errno == ENOENT) - setError(Common::kPathDoesNotExist, "removeSavefile: '"+file.getName()+"' does not exist or path is invalid"); + if (errno == ENOENT) + setError(Common::kPathDoesNotExist, "removeSavefile: '"+fileNode.getName()+"' does not exist or path is invalid"); #endif - return false; - } else { - return true; + return false; + } else { + return true; + } } } @@ -166,4 +233,147 @@ Common::String DefaultSaveFileManager::getSavePath() const { return dir; } +void DefaultSaveFileManager::assureCached(const Common::String &savePathName) { + // Check that path exists and is usable. + checkPath(Common::FSNode(savePathName)); + +#ifdef USE_LIBCURL + Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing + if (!files.empty()) updateSavefilesList(files); //makes this cache invalid + else _lockedFiles = files; +#endif + + if (_cachedDirectory == savePathName) { + return; + } + + _saveFileCache.clear(); + _cachedDirectory.clear(); + + if (getError().getCode() != Common::kNoError) { + warning("DefaultSaveFileManager::assureCached: Can not cache path '%s': '%s'", savePathName.c_str(), getErrorDesc().c_str()); + return; + } + + // FSNode can cache its members, thus create it after checkPath to reflect + // actual file system state. + const Common::FSNode savePath(savePathName); + + Common::FSList children; + if (!savePath.getChildren(children, Common::FSNode::kListFilesOnly)) { + return; + } + + // Build the savefile name cache. + for (Common::FSList::const_iterator file = children.begin(), end = children.end(); file != end; ++file) { + if (_saveFileCache.contains(file->getName())) { + warning("DefaultSaveFileManager::assureCached: Name clash when building cache, ignoring file '%s'", file->getName().c_str()); + } else { + _saveFileCache[file->getName()] = *file; + } + } + + // Only now store that we cached 'savePathName' to indicate we successfully + // cached the directory. + _cachedDirectory = savePathName; +} + +#ifdef USE_LIBCURL + +Common::HashMap<Common::String, uint32> DefaultSaveFileManager::loadTimestamps() { + Common::HashMap<Common::String, uint32> timestamps; + + //refresh the files list + Common::Array<Common::String> files; + g_system->getSavefileManager()->updateSavefilesList(files); + + //start with listing all the files in saves/ directory and setting invalid timestamp to them + Common::StringArray localFiles = g_system->getSavefileManager()->listSavefiles("*"); + for (uint32 i = 0; i < localFiles.size(); ++i) + timestamps[localFiles[i]] = INVALID_TIMESTAMP; + + //now actually load timestamps from file + Common::InSaveFile *file = g_system->getSavefileManager()->openRawFile(TIMESTAMPS_FILENAME); + if (!file) { + warning("DefaultSaveFileManager: failed to open '%s' file to load timestamps", TIMESTAMPS_FILENAME); + return timestamps; + } + + while (!file->eos()) { + //read filename into buffer (reading until the first ' ') + Common::String buffer; + while (!file->eos()) { + byte b = file->readByte(); + if (b == ' ') break; + buffer += (char)b; + } + + //read timestamp info buffer (reading until ' ' or some line ending char) + Common::String filename = buffer; + while (true) { + bool lineEnded = false; + buffer = ""; + while (!file->eos()) { + byte b = file->readByte(); + if (b == ' ' || b == '\n' || b == '\r') { + lineEnded = (b == '\n'); + break; + } + buffer += (char)b; + } + + if (buffer == "" && file->eos()) break; + if (!lineEnded) filename += " " + buffer; + else break; + } + + //parse timestamp + uint32 timestamp = buffer.asUint64(); + if (buffer == "" || timestamp == 0) break; + timestamps[filename] = timestamp; + } + + delete file; + return timestamps; +} + +void DefaultSaveFileManager::saveTimestamps(Common::HashMap<Common::String, uint32> ×tamps) { + Common::DumpFile f; + Common::String filename = concatWithSavesPath(TIMESTAMPS_FILENAME); + if (!f.open(filename, true)) { + warning("DefaultSaveFileManager: failed to open '%s' file to save timestamps", filename.c_str()); + return; + } + + for (Common::HashMap<Common::String, uint32>::iterator i = timestamps.begin(); i != timestamps.end(); ++i) { + Common::String data = i->_key + Common::String::format(" %u\n", i->_value); + if (f.write(data.c_str(), data.size()) != data.size()) { + warning("DefaultSaveFileManager: failed to write timestamps data into '%s'", filename.c_str()); + return; + } + } + + f.flush(); + f.finalize(); + f.close(); +} + +Common::String DefaultSaveFileManager::concatWithSavesPath(Common::String name) { + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + Common::String path = (manager ? manager->getSavePath() : ConfMan.get("savepath")); + if (path.size() > 0 && (path.lastChar() == '/' || path.lastChar() == '\\')) + return path + name; + + //simple heuristic to determine which path separator to use + int backslashes = 0; + for (uint32 i = 0; i < path.size(); ++i) + if (path[i] == '/') --backslashes; + else if (path[i] == '\\') ++backslashes; + + if (backslashes > 0) return path + '\\' + name; + return path + '/' + name; +} + +#endif // ifdef USE_LIBCURL + #endif // !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h index 81f45f96b8..f2453810bf 100644 --- a/backends/saves/default/default-saves.h +++ b/backends/saves/default/default-saves.h @@ -27,6 +27,8 @@ #include "common/savefile.h" #include "common/str.h" #include "common/fs.h" +#include "common/hash-str.h" +#include <limits.h> /** * Provides a default savefile manager implementation for common platforms. @@ -36,11 +38,24 @@ public: DefaultSaveFileManager(); DefaultSaveFileManager(const Common::String &defaultSavepath); + virtual void updateSavefilesList(Common::StringArray &lockedFiles); virtual Common::StringArray listSavefiles(const Common::String &pattern); + virtual Common::InSaveFile *openRawFile(const Common::String &filename); virtual Common::InSaveFile *openForLoading(const Common::String &filename); virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true); virtual bool removeSavefile(const Common::String &filename); +#ifdef USE_LIBCURL + + static const uint32 INVALID_TIMESTAMP = UINT_MAX; + static const char *TIMESTAMPS_FILENAME; + + static Common::HashMap<Common::String, uint32> loadTimestamps(); + static void saveTimestamps(Common::HashMap<Common::String, uint32> ×tamps); + static Common::String concatWithSavesPath(Common::String name); + +#endif + protected: /** * Get the path to the savegame directory. @@ -54,6 +69,36 @@ protected: * Sets the internal error and error message accordingly. */ virtual void checkPath(const Common::FSNode &dir); + + /** + * Assure that the given save path is cached. + * + * @param savePathName String representation of save path to cache. + */ + void assureCached(const Common::String &savePathName); + + typedef Common::HashMap<Common::String, Common::FSNode, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> SaveFileCache; + + /** + * Cache of all the save files in the currently cached directory. + * + * Modify with caution because we only re-cache when the save path changed! + * This needs to be updated inside at least openForSaving and + * removeSavefile. + */ + SaveFileCache _saveFileCache; + + /** + * List of "locked" files. These cannot be used for saving/loading + * because CloudManager is downloading those. + */ + Common::StringArray _lockedFiles; + +private: + /** + * The currently cached directory. + */ + Common::String _cachedDirectory; }; #endif diff --git a/backends/saves/posix/posix-saves.cpp b/backends/saves/posix/posix-saves.cpp index 96828320a6..2a7b4d3e9f 100644 --- a/backends/saves/posix/posix-saves.cpp +++ b/backends/saves/posix/posix-saves.cpp @@ -21,16 +21,19 @@ */ -// Enable getenv, mkdir and time.h stuff -#define FORBIDDEN_SYMBOL_EXCEPTION_getenv -#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir +// Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h. +// Also with clock() in sys/time.h in some Mac OS X SDKs. #define FORBIDDEN_SYMBOL_EXCEPTION_time_h //On IRIX, sys/stat.h includes sys/time.h +#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h +#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir +#define FORBIDDEN_SYMBOL_EXCEPTION_getenv #include "common/scummsys.h" #if defined(POSIX) && !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) #include "backends/saves/posix/posix-saves.h" +#include "backends/fs/posix/posix-fs.h" #include "common/config-manager.h" #include "common/savefile.h" @@ -41,26 +44,70 @@ #include <errno.h> #include <sys/stat.h> - -#ifdef MACOSX -#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames" -#else -#define DEFAULT_SAVE_PATH ".scummvm" -#endif - POSIXSaveFileManager::POSIXSaveFileManager() { - // Register default savepath based on HOME + // Register default savepath. #if defined(SAMSUNGTV) ConfMan.registerDefault("savepath", "/mtd_wiselink/scummvm savegames"); #else Common::String savePath; + +#if defined(MACOSX) const char *home = getenv("HOME"); if (home && *home && strlen(home) < MAXPATHLEN) { savePath = home; - savePath += "/" DEFAULT_SAVE_PATH; + savePath += "/Documents/ScummVM Savegames"; + ConfMan.registerDefault("savepath", savePath); } +#else + const char *envVar; + + // Previously we placed our default savepath in HOME. If the directory + // still exists, we will use it for backwards compatability. + envVar = getenv("HOME"); + if (envVar && *envVar) { + savePath = envVar; + savePath += "/.scummvm"; + + struct stat sb; + if (stat(savePath.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) { + savePath.clear(); + } + } + + if (savePath.empty()) { + Common::String prefix; + + // On POSIX systems we follow the XDG Base Directory Specification for + // where to store files. The version we based our code upon can be found + // over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html + envVar = getenv("XDG_DATA_HOME"); + if (!envVar || !*envVar) { + envVar = getenv("HOME"); + if (envVar && *envVar) { + prefix = envVar; + savePath = ".local/share/"; + } + } else { + prefix = envVar; + } + + // Our default save path is '$XDG_DATA_HOME/scummvm/saves' + savePath += "scummvm/saves"; + + if (!Posix::assureDirectoryExists(savePath, prefix.c_str())) { + savePath.clear(); + } else { + savePath = prefix + '/' + savePath; + } + } + + if (!savePath.empty() && savePath.size() < MAXPATHLEN) { + ConfMan.registerDefault("savepath", savePath); + } +#endif + // The user can override the savepath with the SCUMMVM_SAVEPATH // environment variable. This is weaker than a --savepath on the // command line, but overrides the default savepath. diff --git a/backends/saves/savefile.cpp b/backends/saves/savefile.cpp index b04c53d832..e21ea16ad8 100644 --- a/backends/saves/savefile.cpp +++ b/backends/saves/savefile.cpp @@ -23,9 +23,37 @@ #include "common/util.h" #include "common/savefile.h" #include "common/str.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#endif namespace Common { +OutSaveFile::OutSaveFile(WriteStream *w): _wrapped(w) {} + +OutSaveFile::~OutSaveFile() {} + +bool OutSaveFile::err() const { return _wrapped->err(); } + +void OutSaveFile::clearErr() { _wrapped->clearErr(); } + +void OutSaveFile::finalize() { + _wrapped->finalize(); +#ifdef USE_LIBCURL + CloudMan.syncSaves(); +#endif +} + +bool OutSaveFile::flush() { return _wrapped->flush(); } + +uint32 OutSaveFile::write(const void *dataPtr, uint32 dataSize) { + return _wrapped->write(dataPtr, dataSize); +} + +int32 OutSaveFile::pos() const { + return _wrapped->pos(); +} + bool SaveFileManager::copySavefile(const String &oldFilename, const String &newFilename) { InSaveFile *inFile = 0; OutSaveFile *outFile = 0; diff --git a/backends/taskbar/macosx/dockplugin/dockplugin.m b/backends/taskbar/macosx/dockplugin/dockplugin.m new file mode 100644 index 0000000000..9d864ef2a8 --- /dev/null +++ b/backends/taskbar/macosx/dockplugin/dockplugin.m @@ -0,0 +1,125 @@ +/* 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 <Cocoa/Cocoa.h> + +@interface ScummVMDockTilePlugIn : NSObject <NSDockTilePlugIn> { + NSMenu *recentGamesMenu; +} +@end + +@interface StartGameMenuItem : NSMenuItem { + NSString *game; +} +- (IBAction) startGame; +- (NSMenuItem*)initWithGame:(NSString *)gameId description:(NSString*)desc icon:(NSString*)iconFile; +@end + +@implementation ScummVMDockTilePlugIn + +- (id)init { + self = [super init]; + if (self) { + recentGamesMenu = nil; + } + return self; +} + +- (void)dealloc { + [recentGamesMenu release]; + [super dealloc]; +} + + +- (void)setDockTile:(NSDockTile *)dockTile { +} + +- (NSMenu*)dockMenu { + // Get the list or recent games + CFPreferencesAppSynchronize(CFSTR("org.scummvm.scummvm")); + NSArray *array = CFPreferencesCopyAppValue(CFSTR("recentGames"), CFSTR("org.scummvm.scummvm")); + if (array == nil) + return nil; + + // Create the menu + if (recentGamesMenu == nil) + recentGamesMenu = [[NSMenu alloc] init]; + else + [recentGamesMenu removeAllItems]; + + NSEnumerator *enumerator = [array objectEnumerator]; + NSDictionary *recentGame; + while (recentGame = [enumerator nextObject]) { + NSString *gameId = [recentGame valueForKey:@"game"]; + NSString *desc = [recentGame valueForKey:@"description"]; + NSString *iconFile = [recentGame valueForKey:@"icon"]; + + StartGameMenuItem *menuItem = [[StartGameMenuItem alloc] initWithGame:gameId description:desc icon:iconFile]; + [recentGamesMenu addItem:menuItem]; + [menuItem release]; + } + + return recentGamesMenu; +} + +@end + +@implementation StartGameMenuItem + +- (NSMenuItem*)initWithGame:(NSString *)gameId description:(NSString*)desc icon:(NSString*)iconFile { + self = [super initWithTitle:(desc == nil ? gameId : desc) action:@selector(startGame) keyEquivalent:@""]; + [self setTarget:self]; + + if (iconFile != nil) { + NSImage *image = [[NSImage alloc] initWithContentsOfFile:iconFile]; + [self setImage:image]; + [image release]; + } + + game = gameId; + [game retain]; + + return self; +} + +- (void)dealloc { + [game release]; + [super dealloc]; +} + +- (IBAction) startGame { + NSLog(@"Starting Game %@...", game); + + NSString *scummVMPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"org.scummvm.scummvm"]; + if (scummVMPath == nil) { + NSLog(@"Cannot find ScummVM.app!"); + return; + } + // Start ScummVM.app with the game ID as argument + NSURL *url = [NSURL fileURLWithPath:scummVMPath]; + NSMutableDictionary *args = [[NSMutableDictionary alloc] init]; + [args setObject:[NSArray arrayWithObject:game] forKey:NSWorkspaceLaunchConfigurationArguments]; + [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchDefault configuration:args error:nil]; + [args release]; +} + +@end diff --git a/backends/taskbar/macosx/macosx-taskbar.h b/backends/taskbar/macosx/macosx-taskbar.h index 5d5b9d02cd..55bb97a691 100644 --- a/backends/taskbar/macosx/macosx-taskbar.h +++ b/backends/taskbar/macosx/macosx-taskbar.h @@ -37,6 +37,7 @@ public: virtual void setProgressValue(int completed, int total); virtual void setProgressState(TaskbarProgressState state); virtual void setCount(int count); + virtual void addRecent(const Common::String &name, const Common::String &description); virtual void notifyError(); virtual void clearError(); diff --git a/backends/taskbar/macosx/macosx-taskbar.mm b/backends/taskbar/macosx/macosx-taskbar.mm index ae087dfb85..c842eb8090 100644 --- a/backends/taskbar/macosx/macosx-taskbar.mm +++ b/backends/taskbar/macosx/macosx-taskbar.mm @@ -29,9 +29,6 @@ // NSDockTile was introduced with Mac OS X 10.5. // Try provide backward compatibility by avoiding NSDockTile symbols. -// TODO: Implement recent list, maybe as a custom menu on dock tile when app is not running -// See Dock Tile plug-in at https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/CreatingaDockTilePlug-in/CreatingaDockTilePlug-in.html - #include "backends/taskbar/macosx/macosx-taskbar.h" #include "common/config-manager.h" #include "common/file.h" @@ -39,6 +36,9 @@ #include <AppKit/NSApplication.h> #include <AppKit/NSImage.h> #include <Foundation/NSString.h> +#include <Foundation/NSDictionary.h> +#include <Foundation/NSArray.h> +#include <Foundation/NSUserDefaults.h> #include <AppKit/NSImageView.h> #include <AppKit/NSColor.h> #include <AppKit/NSBezierPath.h> @@ -120,7 +120,7 @@ void MacOSXTaskbarManager::setOverlayIcon(const Common::String &name, const Comm initOverlayIconView(); CFStringRef imageFile = CFStringCreateWithCString(0, path.c_str(), kCFStringEncodingASCII); - NSImage* image = [[NSImage alloc] initWithContentsOfFile:(NSString *)imageFile]; + NSImage *image = [[NSImage alloc] initWithContentsOfFile:(NSString *)imageFile]; [_overlayIconView setImage:image]; [image release]; CFRelease(imageFile); @@ -234,5 +234,64 @@ return (path); \ return ""; } +void MacOSXTaskbarManager::addRecent(const Common::String &name, const Common::String &description) { + //warning("[MacOSXTaskbarManager::addRecent] Adding recent list entry: %s (%s)", name.c_str(), description.c_str()); + + if (_dockTile == nil) + return; + + // Store the game, description and icon in user preferences. + // The NSDockTilePlugin will retrieve them there to list them in the dock tile menu. + + CFStringRef gameName = CFStringCreateWithCString(0, name.c_str(), kCFStringEncodingASCII); + CFStringRef desc = CFStringCreateWithCString(0, description.c_str(), kCFStringEncodingASCII); + + // First build the dictionary for this game. + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [dict setObject:(NSString *)gameName forKey:@"game"]; + [dict setObject:(NSString *)desc forKey:@"description"]; + + // Icon + Common::String iconPath = getIconPath(name); + if (!iconPath.empty()) { + CFStringRef icon = CFStringCreateWithCString(0, iconPath.c_str(), kCFStringEncodingASCII); + [dict setObject:(NSString *)icon forKey:@"icon"]; + CFRelease(icon); + } + + // Retrieve the current list of recent items and update it. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *oldArray = [defaults arrayForKey:@"recentGames"]; + if (oldArray == nil) { + [defaults setObject:[NSArray arrayWithObject:dict] forKey:@"recentGames"]; + } else { + NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:oldArray]; + // Insert the new game at the start + [newArray insertObject:dict atIndex:0]; + // If the game was already present in the array, remove it + for (unsigned int i = 1 ; i < [newArray count] ; ++i) { + NSDictionary *oldDict = [newArray objectAtIndex:i]; + if (oldDict == nil) + continue; + NSString *oldGame = [oldDict valueForKey:@"game"]; + if (oldGame != nil && [oldGame isEqualToString:(NSString*)gameName]) { + [newArray removeObjectAtIndex:i]; + break; + } + } + // And make sure we limit the size of the array to 5 games + if ([newArray count] > 5) + [newArray removeLastObject]; + [defaults setObject:newArray forKey:@"recentGames"]; + [newArray release]; + } + + // Finally release the dictionary + [dict release]; + CFRelease(gameName); + CFRelease(desc); +} + + #endif diff --git a/backends/taskbar/win32/win32-taskbar.cpp b/backends/taskbar/win32/win32-taskbar.cpp index 5c9105b0eb..b2810e55b4 100644 --- a/backends/taskbar/win32/win32-taskbar.cpp +++ b/backends/taskbar/win32/win32-taskbar.cpp @@ -28,10 +28,39 @@ #if defined(WIN32) && defined(USE_TASKBAR) +// HACK: To get __MINGW64_VERSION_foo defines we need to manually include +// _mingw.h in this file because we do not include any system headers at this +// point on purpose. The defines are required to detect whether this is a +// classic MinGW toolchain or a MinGW-w64 based one. +#if defined(__MINGW32__) +#include <_mingw.h> +#endif + // Needed for taskbar functions -#if defined(__GNUC__) && defined(__MINGW32__) && !defined(__MINGW64__) +// HACK: MinGW-w64 based toolchains include the symbols we require in their +// headers. The 32 bit incarnation only defines __MINGW32__. This leads to +// build breakage due to clashes with our compat header. Luckily MinGW-w64 +// based toolchains define __MINGW64_VERSION_foo macros inside _mingw.h, +// which is included from all system headers. Thus we abuse that to detect +// them. +#if defined(__GNUC__) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) #include "backends/taskbar/win32/mingw-compat.h" #else + // We use functionality introduced with Win7 in this file. + // To assure that including the respective system headers gives us all + // required definitions we set Win7 as minimum version we target. + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745%28v=vs.85%29.aspx#macros_for_conditional_declarations + #undef _WIN32_WINNT + #define _WIN32_WINNT _WIN32_WINNT_WIN7 + + // TODO: We might not need to include this file, the MSDN docs are + // not really helpful to decide whether we require it or not. + // + // Casing of the name is a bit of a mess. MinGW64 seems to use all + // lowercase, while MSDN docs suggest "SdkDdkVer.h". We are stuck with + // what MinGW64 uses... + #include <sdkddkver.h> + // We need certain functions that are excluded by default #undef NONLS #undef NOICONS @@ -39,18 +68,10 @@ #if defined(ARRAYSIZE) #undef ARRAYSIZE #endif - - #if defined(_MSC_VER) - // Default MSVC headers for ITaskbarList3 and IShellLink - #include <SDKDDKVer.h> - #endif #endif #include <shlobj.h> -// For HWND -#include <SDL_syswm.h> - #include "common/scummsys.h" #include "backends/taskbar/win32/win32-taskbar.h" @@ -62,9 +83,9 @@ // System.Title property key, values taken from http://msdn.microsoft.com/en-us/library/bb787584.aspx const PROPERTYKEY PKEY_Title = { /* fmtid = */ { 0xF29F85E0, 0x4FF9, 0x1068, { 0xAB, 0x91, 0x08, 0x00, 0x2B, 0x27, 0xB3, 0xD9 } }, /* propID = */ 2 }; -Win32TaskbarManager::Win32TaskbarManager() : _taskbar(NULL), _count(0), _icon(NULL) { +Win32TaskbarManager::Win32TaskbarManager(SdlWindow *window) : _window(window), _taskbar(NULL), _count(0), _icon(NULL) { // Do nothing if not running on Windows 7 or later - if (!isWin7OrLater()) + if (!confirmWindowsVersion(10, 0) && !confirmWindowsVersion(6, 1)) return; CoInitialize(NULL); @@ -379,14 +400,14 @@ BOOL VerifyVersionInfoFunc(LPOSVERSIONINFOEXA lpVersionInformation, DWORD dwType return verifyVersionInfo(lpVersionInformation, dwTypeMask, dwlConditionMask); } -bool Win32TaskbarManager::isWin7OrLater() { +bool Win32TaskbarManager::confirmWindowsVersion(uint majorVersion, uint minorVersion) { OSVERSIONINFOEX versionInfo; DWORDLONG conditionMask = 0; ZeroMemory(&versionInfo, sizeof(OSVERSIONINFOEX)); versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - versionInfo.dwMajorVersion = 6; - versionInfo.dwMinorVersion = 1; + versionInfo.dwMajorVersion = majorVersion; + versionInfo.dwMinorVersion = minorVersion; conditionMask = VerSetConditionMaskFunc(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); conditionMask = VerSetConditionMaskFunc(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); @@ -408,12 +429,15 @@ LPWSTR Win32TaskbarManager::ansiToUnicode(const char *s) { HWND Win32TaskbarManager::getHwnd() { SDL_SysWMinfo wmi; - SDL_VERSION(&wmi.version); - - if(!SDL_GetWMInfo(&wmi)) + if (_window->getSDLWMInformation(&wmi)) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + return wmi.info.win.window; +#else + return wmi.window; +#endif + } else { return NULL; - - return wmi.window; + } } #endif diff --git a/backends/taskbar/win32/win32-taskbar.h b/backends/taskbar/win32/win32-taskbar.h index 36415c1c57..a5c024b21b 100644 --- a/backends/taskbar/win32/win32-taskbar.h +++ b/backends/taskbar/win32/win32-taskbar.h @@ -25,6 +25,8 @@ #if defined(WIN32) && defined(USE_TASKBAR) +#include "backends/platform/sdl/sdl-window.h" + #include "common/str.h" #include "common/taskbar.h" @@ -32,7 +34,7 @@ struct ITaskbarList3; class Win32TaskbarManager : public Common::TaskbarManager { public: - Win32TaskbarManager(); + Win32TaskbarManager(SdlWindow *window); virtual ~Win32TaskbarManager(); virtual void setOverlayIcon(const Common::String &name, const Common::String &description); @@ -44,6 +46,8 @@ public: virtual void clearError(); private: + SdlWindow *_window; + ITaskbarList3 *_taskbar; // Count handling @@ -60,7 +64,7 @@ private: Common::String getIconPath(Common::String target); // Helper functions - bool isWin7OrLater(); + bool confirmWindowsVersion(uint majorVersion, uint minorVersion); LPWSTR ansiToUnicode(const char *s); HWND getHwnd(); }; diff --git a/backends/updates/macosx/macosx-updates.h b/backends/updates/macosx/macosx-updates.h index fd2d1f46f5..6fb9af7712 100644 --- a/backends/updates/macosx/macosx-updates.h +++ b/backends/updates/macosx/macosx-updates.h @@ -39,8 +39,10 @@ public: virtual void setAutomaticallyChecksForUpdates(UpdateState state); virtual UpdateState getAutomaticallyChecksForUpdates(); - virtual void setUpdateCheckInterval(UpdateInterval interval); - virtual UpdateInterval getUpdateCheckInterval(); + virtual void setUpdateCheckInterval(int interval); + virtual int getUpdateCheckInterval(); + + virtual bool getLastUpdateCheckTimeAndDate(TimeDate &t); }; #endif diff --git a/backends/updates/macosx/macosx-updates.mm b/backends/updates/macosx/macosx-updates.mm index a94f1c21fd..db9362a459 100644 --- a/backends/updates/macosx/macosx-updates.mm +++ b/backends/updates/macosx/macosx-updates.mm @@ -23,14 +23,18 @@ // Disable symbol overrides so that we can use system headers. #define FORBIDDEN_SYMBOL_ALLOW_ALL +#include "common/system.h" #include "backends/updates/macosx/macosx-updates.h" #ifdef USE_SPARKLE #include "common/translation.h" +#include "common/config-manager.h" #include <Cocoa/Cocoa.h> #include <Sparkle/Sparkle.h> +#include <AvailabilityMacros.h> + SUUpdater *sparkleUpdater; /** @@ -45,14 +49,22 @@ SUUpdater *sparkleUpdater; * */ MacOSXUpdateManager::MacOSXUpdateManager() { + NSBundle* mainBundle = [NSBundle mainBundle]; + + NSString *version = [mainBundle objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]; + if (!version || [version isEqualToString:@""]) { + warning("Running not in bundle, skipping Sparkle initialization"); + + sparkleUpdater = nullptr; + return; + } + NSMenuItem *menuItem = [[NSApp mainMenu] itemAtIndex:0]; NSMenu *applicationMenu = [menuItem submenu]; // Init Sparkle sparkleUpdater = [SUUpdater sharedUpdater]; - NSBundle* mainBundle = [NSBundle mainBundle]; - NSString* feedbackURL = [mainBundle objectForInfoDictionaryKey:@"SUFeedURL"]; // Set appcast URL @@ -74,11 +86,13 @@ MacOSXUpdateManager::MacOSXUpdateManager() { // Finally give up our references to the objects [menuItem release]; - // Enable automatic update checking once a day (alternatively use - // checkForUpdates() here to check for updates on every startup) - // TODO: Should be removed when an update settings gui is implemented - setAutomaticallyChecksForUpdates(kUpdateStateEnabled); - setUpdateCheckInterval(kUpdateIntervalOneDay); + if (!ConfMan.hasKey("updates_check") + || ConfMan.getInt("updates_check") == Common::UpdateManager::kUpdateIntervalNotSupported) { + setAutomaticallyChecksForUpdates(kUpdateStateDisabled); + } else { + setAutomaticallyChecksForUpdates(kUpdateStateEnabled); + setUpdateCheckInterval(normalizeInterval(ConfMan.getInt("updates_check"))); + } } MacOSXUpdateManager::~MacOSXUpdateManager() { @@ -86,6 +100,9 @@ MacOSXUpdateManager::~MacOSXUpdateManager() { } void MacOSXUpdateManager::checkForUpdates() { + if (sparkleUpdater == nullptr) + return; + [sparkleUpdater checkForUpdatesInBackground]; } @@ -93,24 +110,38 @@ void MacOSXUpdateManager::setAutomaticallyChecksForUpdates(UpdateManager::Update if (state == kUpdateStateNotSupported) return; + if (sparkleUpdater == nullptr) + return; + [sparkleUpdater setAutomaticallyChecksForUpdates:(state == kUpdateStateEnabled ? YES : NO)]; } Common::UpdateManager::UpdateState MacOSXUpdateManager::getAutomaticallyChecksForUpdates() { + if (sparkleUpdater == nullptr) + return kUpdateStateDisabled; + if ([sparkleUpdater automaticallyChecksForUpdates]) return kUpdateStateEnabled; else return kUpdateStateDisabled; } -void MacOSXUpdateManager::setUpdateCheckInterval(UpdateInterval interval) { +void MacOSXUpdateManager::setUpdateCheckInterval(int interval) { + if (sparkleUpdater == nullptr) + return; + if (interval == kUpdateIntervalNotSupported) return; + interval = normalizeInterval(interval); + [sparkleUpdater setUpdateCheckInterval:(NSTimeInterval)interval]; } -Common::UpdateManager::UpdateInterval MacOSXUpdateManager::getUpdateCheckInterval() { +int MacOSXUpdateManager::getUpdateCheckInterval() { + if (sparkleUpdater == nullptr) + return kUpdateIntervalOneDay; + // This is kind of a hack but necessary, as the value stored by Sparkle // might have been changed outside of ScummVM (in which case we return the // default interval of one day) @@ -128,4 +159,30 @@ Common::UpdateManager::UpdateInterval MacOSXUpdateManager::getUpdateCheckInterva } } +bool MacOSXUpdateManager::getLastUpdateCheckTimeAndDate(TimeDate &t) { + if (sparkleUpdater == nullptr) + return false; + + NSDate *date = [sparkleUpdater lastUpdateCheckDate]; +#ifdef MAC_OS_X_VERSION_10_10 + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *components = [gregorian components:(NSCalendarUnitDay | NSCalendarUnitWeekday) fromDate:date]; +#else + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + NSDateComponents *components = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:date]; +#endif + + t.tm_wday = [components weekday]; + t.tm_year = [components year]; + t.tm_mon = [components month]; + t.tm_mday = [components day]; + t.tm_hour = [components hour]; + t.tm_min = [components minute]; + t.tm_sec = [components second]; + + [gregorian release]; + + return true; +} + #endif diff --git a/backends/updates/win32/win32-updates.cpp b/backends/updates/win32/win32-updates.cpp new file mode 100644 index 0000000000..356ff9c903 --- /dev/null +++ b/backends/updates/win32/win32-updates.cpp @@ -0,0 +1,132 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "common/system.h" +#include "backends/updates/win32/win32-updates.h" + +#ifdef USE_SPARKLE +#include "common/translation.h" +#include "common/config-manager.h" + +#include <time.h> +#include <winsparkle.h> + +/** + * Sparkle is a software update framework for Mac OS X which uses appcasts for + * release information. Appcasts are RSS-like XML feeds which contain information + * about the most current version at the time. If a new version is available, the + * user is presented the release-notes/changes/fixes and is asked if he wants to + * update, and if yes the Sparkle framework downloads a signed update package + * from the server and automatically installs and restarts the software. + * More detailed information is available at the following address: + * http://sparkle.andymatuschak.org/ + * + * WinSparkle is a heavily (to the point of being its almost-port) inspired by the + * Sparkle framework originally by Andy Matuschak that became the de facto standard + * for software updates on OS X. + * More detailed information is available at the following address: + * https://winsparkle.org/ + * + */ +Win32UpdateManager::Win32UpdateManager() { + const char *appcastUrl = "https://www.scummvm.org/appcasts/macosx/release.xml"; + + win_sparkle_set_appcast_url(appcastUrl); + win_sparkle_init(); + + if (!ConfMan.hasKey("updates_check") + || ConfMan.getInt("updates_check") == Common::UpdateManager::kUpdateIntervalNotSupported) { + setAutomaticallyChecksForUpdates(kUpdateStateDisabled); + } else { + setAutomaticallyChecksForUpdates(kUpdateStateEnabled); + setUpdateCheckInterval(normalizeInterval(ConfMan.getInt("updates_check"))); + } +} + +Win32UpdateManager::~Win32UpdateManager() { + win_sparkle_cleanup(); +} + +void Win32UpdateManager::checkForUpdates() { + win_sparkle_check_update_with_ui(); +} + +void Win32UpdateManager::setAutomaticallyChecksForUpdates(UpdateManager::UpdateState state) { + if (state == kUpdateStateNotSupported) + return; + + win_sparkle_set_automatic_check_for_updates(state == kUpdateStateEnabled ? 1 : 0); +} + +Common::UpdateManager::UpdateState Win32UpdateManager::getAutomaticallyChecksForUpdates() { + if (win_sparkle_get_automatic_check_for_updates() == 1) + return kUpdateStateEnabled; + else + return kUpdateStateDisabled; +} + +void Win32UpdateManager::setUpdateCheckInterval(int interval) { + if (interval == kUpdateIntervalNotSupported) + return; + + interval = normalizeInterval(interval); + + win_sparkle_set_update_check_interval(interval); +} + +int Win32UpdateManager::getUpdateCheckInterval() { + // This is kind of a hack but necessary, as the value stored by Sparkle + // might have been changed outside of ScummVM (in which case we return the + // default interval of one day) + + int updateInterval = win_sparkle_get_update_check_interval(); + switch (updateInterval) { + case kUpdateIntervalOneDay: + case kUpdateIntervalOneWeek: + case kUpdateIntervalOneMonth: + return updateInterval; + + default: + // Return the default value (one day) + return kUpdateIntervalOneDay; + } +} + +bool Win32UpdateManager::getLastUpdateCheckTimeAndDate(TimeDate &t) { + time_t updateTime = win_sparkle_get_last_check_time(); + tm *ut = localtime(&updateTime); + + t.tm_wday = ut->tm_wday; + t.tm_year = ut->tm_year; + t.tm_mon = ut->tm_mon; + t.tm_mday = ut->tm_mday; + t.tm_hour = ut->tm_hour; + t.tm_min = ut->tm_min; + t.tm_sec = ut->tm_sec; + + return true; +} + +#endif diff --git a/backends/updates/win32/win32-updates.h b/backends/updates/win32/win32-updates.h new file mode 100644 index 0000000000..71ed9ef685 --- /dev/null +++ b/backends/updates/win32/win32-updates.h @@ -0,0 +1,50 @@ +/* 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 BACKENDS_UPDATES_WIN32_H +#define BACKENDS_UPDATES_WIN32_H + +#include "common/scummsys.h" + +#if defined(WIN32) && defined(USE_SPARKLE) + +#include "common/updates.h" + +class Win32UpdateManager : public Common::UpdateManager { +public: + Win32UpdateManager(); + virtual ~Win32UpdateManager(); + + virtual void checkForUpdates(); + + virtual void setAutomaticallyChecksForUpdates(UpdateState state); + virtual UpdateState getAutomaticallyChecksForUpdates(); + + virtual void setUpdateCheckInterval(int interval); + virtual int getUpdateCheckInterval(); + + virtual bool getLastUpdateCheckTimeAndDate(TimeDate &t); +}; + +#endif + +#endif // BACKENDS_UPDATES_WIN32_H |