RECORDER: Implement Events Recorder

This commit is contained in:
Eugene Sandulenko 2013-05-17 00:18:09 +03:00
parent 4a62d6c25a
commit f59512c47e
98 changed files with 5350 additions and 1918 deletions

View file

@ -20,6 +20,8 @@
*
*/
#include "gui/EventRecorder.h"
#include "common/util.h"
#include "common/system.h"
#include "common/textconsole.h"
@ -427,6 +429,7 @@ void MixerImpl::pauseHandle(SoundHandle handle, bool paused) {
bool MixerImpl::isSoundIDActive(int id) {
Common::StackLock lock(_mutex);
g_eventRec.updateSubsystems();
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i] && _channels[i]->getId() == id)
return true;
@ -443,6 +446,7 @@ int MixerImpl::getSoundID(SoundHandle handle) {
bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
Common::StackLock lock(_mutex);
g_eventRec.updateSubsystems();
const int index = handle._val % NUM_CHANNELS;
return _channels[index] && _channels[index]->getHandle()._val == handle._val;
}
@ -556,12 +560,12 @@ void Channel::pause(bool paused) {
_pauseLevel++;
if (_pauseLevel == 1)
_pauseStartTime = g_system->getMillis();
_pauseStartTime = g_system->getMillis(true);
} else if (_pauseLevel > 0) {
_pauseLevel--;
if (!_pauseLevel) {
_pauseTime = (g_system->getMillis() - _pauseStartTime);
_pauseTime = (g_system->getMillis(true) - _pauseStartTime);
_pauseStartTime = 0;
}
}
@ -579,7 +583,7 @@ Timestamp Channel::getElapsedTime() {
if (isPaused())
delta = _pauseStartTime - _mixerTimeStamp;
else
delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime;
delta = g_system->getMillis(true) - _mixerTimeStamp - _pauseTime;
// Convert the number of samples into a time duration.
@ -599,13 +603,12 @@ int Channel::mix(int16 *data, uint len) {
assert(_stream);
int res = 0;
if (_stream->endOfData()) {
// TODO: call drain method
} else {
assert(_converter);
_samplesConsumed = _samplesDecoded;
_mixerTimeStamp = g_system->getMillis();
_mixerTimeStamp = g_system->getMillis(true);
_pauseTime = 0;
res = _converter->flow(*_stream, data, len, _volL, _volR);
_samplesDecoded += res;

View file

@ -84,7 +84,8 @@ void DefaultEventManager::init() {
}
bool DefaultEventManager::pollEvent(Common::Event &event) {
uint32 time = g_system->getMillis();
// Skip recording of these events
uint32 time = g_system->getMillis(true);
bool result = false;
_dispatcher.dispatch();

View file

@ -106,7 +106,9 @@ void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y) {
}
void SdlEventSource::handleKbdMouse() {
uint32 curTime = g_system->getMillis();
// Skip recording of these events
uint32 curTime = g_system->getMillis(true);
if (curTime >= _km.last_time + _km.delay_time) {
_km.last_time = curTime;
if (_km.x_down_count == 1) {

View file

@ -40,6 +40,7 @@
#include "graphics/scaler.h"
#include "graphics/scaler/aspect.h"
#include "graphics/surface.h"
#include "gui/EventRecorder.h"
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{"1x", _s("Normal (no scaling)"), GFX_NORMAL},
@ -135,6 +136,7 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou
_paletteDirtyStart(0), _paletteDirtyEnd(0),
_screenIsLocked(false),
_graphicsMutex(0),
_displayDisabled(false),
#ifdef USE_SDL_DEBUG_FOCUSRECT
_enableFocusRectDebugCode(false), _enableFocusRect(false), _focusRect(),
#endif
@ -765,9 +767,20 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
fixupResolutionForAspectRatio(_videoMode.desiredAspectRatio, _videoMode.hardwareWidth, _videoMode.hardwareHeight);
}
_hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16,
_videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE
);
#ifdef ENABLE_KEYMAPPER
_displayDisabled = ConfMan.getBool("disable_display");
if (_displayDisabled) {
_hwscreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight);
} else
#endif
{
_hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16,
_videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE
);
}
#ifdef USE_RGB_COLOR
detectSupportedFormats();
#endif
@ -865,7 +878,12 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
}
if (_hwscreen) {
SDL_FreeSurface(_hwscreen);
if (_displayDisabled) {
delete _hwscreen;
} else {
SDL_FreeSurface(_hwscreen);
}
_hwscreen = NULL;
}
@ -1188,7 +1206,9 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
#endif
// Finally, blit all our changes to the screen
SDL_UpdateRects(_hwscreen, _numDirtyRects, _dirtyRectList);
if (!_displayDisabled) {
SDL_UpdateRects(_hwscreen, _numDirtyRects, _dirtyRectList);
}
}
_numDirtyRects = 0;

View file

@ -232,6 +232,9 @@ protected:
int _scalerType;
int _transactionMode;
// Indicates whether it is needed to free _hwsurface in destructor
bool _displayDisabled;
bool _screenIsLocked;
Graphics::Surface _framebuffer;

View file

@ -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/mixer/nullmixer/nullsdl-mixer.h"
#include "common/savefile.h"
NullSdlMixerManager::NullSdlMixerManager() : SdlMixerManager() {
_outputRate = 22050;
_callsCounter = 0;
_callbackPeriod = 10;
_samples = 8192;
while (_samples * 16 > _outputRate * 2)
_samples >>= 1;
_samplesBuf = new uint8[_samples * 4];
}
NullSdlMixerManager::~NullSdlMixerManager() {
delete _samplesBuf;
}
void NullSdlMixerManager::init() {
_mixer = new Audio::MixerImpl(g_system, _outputRate);
assert(_mixer);
_mixer->setReady(true);
}
void NullSdlMixerManager::suspendAudio() {
_audioSuspended = true;
}
int NullSdlMixerManager::resumeAudio() {
if (!_audioSuspended) {
return -2;
}
_audioSuspended = false;
return 0;
}
void NullSdlMixerManager::startAudio() {
}
void NullSdlMixerManager::callbackHandler(byte *samples, int len) {
assert(_mixer);
_mixer->mixCallback(samples, len);
}
void NullSdlMixerManager::update() {
if (_audioSuspended) {
return;
}
_callsCounter++;
if ((_callsCounter % _callbackPeriod) == 0) {
callbackHandler(_samplesBuf, _samples);
}
}

View file

@ -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_MIXER_NULLSDL_H
#define BACKENDS_MIXER_NULLSDL_H
#include "backends/mixer/sdl/sdl-mixer.h"
#include "common/str.h"
/** Audio mixer which in fact does not output audio.
*
* It is used by events recorder since the recorder is intentionally
* turning sound off to avoid stuttering.
*
* It returns correct output and shoots callbacks, so all OSystem
* users could work without modifications.
*/
class NullSdlMixerManager : public SdlMixerManager {
public:
NullSdlMixerManager();
virtual ~NullSdlMixerManager();
virtual void init();
void update();
virtual void suspendAudio();
virtual int resumeAudio();
protected:
virtual void startAudio();
virtual void callbackHandler(byte *samples, int len);
private:
uint32 _outputRate;
uint32 _callsCounter;
uint8 _callbackPeriod;
uint32 _samples;
uint8 *_samplesBuf;
};
#endif

View file

@ -26,6 +26,7 @@
#include "backends/graphics/graphics.h"
#include "backends/mutex/mutex.h"
#include "gui/EventRecorder.h"
#include "audio/mixer.h"
#include "graphics/pixelformat.h"
@ -141,7 +142,9 @@ void ModularBackend::fillScreen(uint32 col) {
}
void ModularBackend::updateScreen() {
g_eventRec.preDrawOverlayGui();
_graphicsManager->updateScreen();
g_eventRec.postDrawOverlayGui();
}
void ModularBackend::setShakePos(int shakeOffset) {

View file

@ -214,5 +214,11 @@ MODULE_OBJS += \
plugins/wii/wii-provider.o
endif
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
mixer/nullmixer/nullsdl-mixer.o \
saves/recorder/recorder-saves.o
endif
# Include common rules
include $(srcdir)/rules.mk

View file

@ -33,15 +33,15 @@ OSystem::MutexRef SdlMutexManager::createMutex() {
}
void SdlMutexManager::lockMutex(OSystem::MutexRef mutex) {
SDL_mutexP((SDL_mutex *) mutex);
SDL_mutexP((SDL_mutex *)mutex);
}
void SdlMutexManager::unlockMutex(OSystem::MutexRef mutex) {
SDL_mutexV((SDL_mutex *) mutex);
SDL_mutexV((SDL_mutex *)mutex);
}
void SdlMutexManager::deleteMutex(OSystem::MutexRef mutex) {
SDL_DestroyMutex((SDL_mutex *) mutex);
SDL_DestroyMutex((SDL_mutex *)mutex);
}
#endif

View file

@ -450,7 +450,7 @@ bool OSystem_Android::getFeatureState(Feature f) {
}
}
uint32 OSystem_Android::getMillis() {
uint32 OSystem_Android::getMillis(bool skipRecord) {
timeval curTime;
gettimeofday(&curTime, 0);

View file

@ -274,7 +274,7 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual MutexRef createMutex(void);

View file

@ -373,7 +373,7 @@ bool BadaSystem::pollEvent(Common::Event &event) {
return _appForm->pollEvent(event);
}
uint32 BadaSystem::getMillis() {
uint32 BadaSystem::getMillis(bool skipRecord) {
long long result, ticks = 0;
SystemTime::GetTicks(ticks);
result = ticks - _epoch;

View file

@ -83,7 +83,7 @@ private:
void updateScreen();
bool pollEvent(Common::Event &event);
uint32 getMillis();
uint32 getMillis(bool skipRecord = false);
void delayMillis(uint msecs);
void getTimeAndDate(TimeDate &t) const;
void fatalError();

View file

@ -151,7 +151,7 @@ public:
void setShakePos(int shake_pos);
// Get the number of milliseconds since the program was started.
uint32 getMillis();
uint32 getMillis(bool skipRecord = false);
// Delay for a specified amount of milliseconds
void delayMillis(uint msecs);

View file

@ -26,7 +26,7 @@
#include "dc.h"
uint32 OSystem_Dreamcast::getMillis()
uint32 OSystem_Dreamcast::getMillis(bool skipRecord)
{
static uint32 msecs=0;
static unsigned int t0=0;

View file

@ -2280,7 +2280,7 @@ void VBlankHandler(void) {
//REG_IF = IRQ_VBLANK;
}
int getMillis() {
int getMillis(bool skipRecord) {
return currentTimeMillis;
// return frameCount * FRAME_TIME;
}

View file

@ -88,7 +88,7 @@ void setGamma(int gamma);
// Timers
void setTimerCallback(OSystem_DS::TimerProc proc, int interval); // Setup a callback function at a regular interval
int getMillis(); // Return the current runtime in milliseconds
int getMillis(bool skipRecord = false); // Return the current runtime in milliseconds
void doTimerCallback(); // Call callback function if required
// Sound

View file

@ -656,7 +656,7 @@ bool OSystem_DS::pollEvent(Common::Event &event) {
return false;
}
uint32 OSystem_DS::getMillis() {
uint32 OSystem_DS::getMillis(bool skipRecord) {
return DS::getMillis();
}

View file

@ -117,7 +117,7 @@ public:
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual void getTimeAndDate(TimeDate &t) const;

View file

@ -166,7 +166,7 @@ void OSystem_IPHONE::suspendLoop() {
_timeSuspended += getMillis() - startTime;
}
uint32 OSystem_IPHONE::getMillis() {
uint32 OSystem_IPHONE::getMillis(bool skipRecord) {
//printf("getMillis()\n");
struct timeval currentTime;

View file

@ -165,7 +165,7 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual MutexRef createMutex(void);

View file

@ -184,7 +184,7 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual MutexRef createMutex(void);

View file

@ -810,7 +810,7 @@ void OSystem_N64::setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
return;
}
uint32 OSystem_N64::getMillis() {
uint32 OSystem_N64::getMillis(bool skipRecord) {
return getMilliTick();
}

View file

@ -49,7 +49,7 @@ public:
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual void getTimeAndDate(TimeDate &t) const {}

View file

@ -571,7 +571,7 @@ void OSystem_PS2::displayMessageOnOSD(const char *msg) {
printf("displayMessageOnOSD: %s\n", msg);
}
uint32 OSystem_PS2::getMillis(void) {
uint32 OSystem_PS2::getMillis(bool skipRecord) {
return msecCount;
}

View file

@ -82,7 +82,7 @@ public:
virtual void warpMouse(int x, int y);
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspot_x, int hotspot_y, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = 0);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual bool pollEvent(Common::Event &event);

View file

@ -349,7 +349,7 @@ bool OSystem_PSP::pollEvent(Common::Event &event) {
return _inputHandler.getAllInputs(event);
}
uint32 OSystem_PSP::getMillis() {
uint32 OSystem_PSP::getMillis(bool skipRecord) {
return PspRtc::instance().getMillis();
}

View file

@ -125,7 +125,7 @@ public:
bool processInput(Common::Event &event);
// Time
uint32 getMillis();
uint32 getMillis(bool skipRecord = false);
void delayMillis(uint msecs);
// Timer

View file

@ -52,7 +52,7 @@ void PspRtc::init() { // init our starting ticks
// Note that after we fill up 32 bits ie 50 days we'll loop back to 0, which may cause
// unpredictable results
uint32 PspRtc::getMillis() {
uint32 PspRtc::getMillis(bool skipRecord) {
uint32 ticks[2];
sceRtcGetCurrentTick((u64 *)ticks); // can introduce weird thread delays

View file

@ -40,7 +40,7 @@ public:
init();
}
void init();
uint32 getMillis();
uint32 getMillis(bool skipRecord = false);
uint32 getMicros();
};

View file

@ -30,7 +30,7 @@
#include "backends/platform/sdl/sdl.h"
#include "common/config-manager.h"
#include "common/EventRecorder.h"
#include "gui/EventRecorder.h"
#include "common/taskbar.h"
#include "common/textconsole.h"
@ -97,7 +97,9 @@ OSystem_SDL::~OSystem_SDL() {
_audiocdManager = 0;
delete _mixerManager;
_mixerManager = 0;
delete _timerManager;
delete g_eventRec.getTimerManager();
_timerManager = 0;
delete _mutexManager;
_mutexManager = 0;
@ -131,9 +133,6 @@ void OSystem_SDL::init() {
if (_mutexManager == 0)
_mutexManager = new SdlMutexManager();
if (_timerManager == 0)
_timerManager = new SdlTimerManager();
#if defined(USE_TASKBAR)
if (_taskbarManager == 0)
_taskbarManager = new Common::TaskbarManager();
@ -191,10 +190,12 @@ void OSystem_SDL::initBackend() {
if (_mixerManager == 0) {
_mixerManager = new SdlMixerManager();
// Setup and start mixer
_mixerManager->init();
}
g_eventRec.registerMixerManager(_mixerManager);
g_eventRec.registerTimerManager(new SdlTimerManager());
if (_audiocdManager == 0) {
// Audio CD support was removed with SDL 1.3
@ -466,14 +467,15 @@ void OSystem_SDL::setupIcon() {
free(icon);
}
uint32 OSystem_SDL::getMillis() {
uint32 OSystem_SDL::getMillis(bool skipRecord) {
uint32 millis = SDL_GetTicks();
g_eventRec.processMillis(millis);
g_eventRec.processMillis(millis, skipRecord);
return millis;
}
void OSystem_SDL::delayMillis(uint msecs) {
if (!g_eventRec.processDelayMillis(msecs))
if (!g_eventRec.processDelayMillis())
SDL_Delay(msecs);
}
@ -491,12 +493,12 @@ void OSystem_SDL::getTimeAndDate(TimeDate &td) const {
Audio::Mixer *OSystem_SDL::getMixer() {
assert(_mixerManager);
return _mixerManager->getMixer();
return getMixerManager()->getMixer();
}
SdlMixerManager *OSystem_SDL::getMixerManager() {
assert(_mixerManager);
return _mixerManager;
return g_eventRec.getMixerManager();
}
#ifdef USE_OPENGL
@ -654,4 +656,8 @@ void OSystem_SDL::setupGraphicsModes() {
}
}
Common::TimerManager *OSystem_SDL::getTimerManager() {
return g_eventRec.getTimerManager();
}
#endif

View file

@ -68,10 +68,11 @@ public:
virtual void setWindowCaption(const char *caption);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual void getTimeAndDate(TimeDate &td) const;
virtual Audio::Mixer *getMixer();
virtual Common::TimerManager *getTimerManager();
protected:
bool _inited;

View file

@ -203,7 +203,7 @@ bool OSystem_Wii::getFeatureState(Feature f) {
}
}
uint32 OSystem_Wii::getMillis() {
uint32 OSystem_Wii::getMillis(bool skipRecord) {
return ticks_to_millisecs(diff_ticks(_startup_time, gettime()));
}

View file

@ -193,7 +193,7 @@ public:
const Graphics::PixelFormat *format);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual MutexRef createMutex();

View file

@ -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/saves/recorder/recorder-saves.h"
#include "gui/EventRecorder.h"
#include "common/savefile.h"
Common::InSaveFile *RecorderSaveFileManager::openForLoading(const Common::String &filename) {
Common::InSaveFile *result = g_eventRec.processSaveStream(filename);
return result;
}
Common::StringArray RecorderSaveFileManager::listSaveFiles(const Common::String &pattern) {
return g_eventRec.listSaveFiles(pattern);
}

View file

@ -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.
*
*/
#ifndef BACKEND_SAVES_RECORDER_H
#define BACKEND_SAVES_RECORDER_H
#include "backends/saves/default/default-saves.h"
/**
* Provides a savefile manager implementation for event recorder.
*/
class RecorderSaveFileManager : public DefaultSaveFileManager {
virtual Common::StringArray listSaveFiles(const Common::String &pattern);
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
};
#endif

View file

@ -80,7 +80,7 @@ DefaultTimerManager::~DefaultTimerManager() {
void DefaultTimerManager::handler() {
Common::StackLock lock(_mutex);
const uint32 curTime = g_system->getMillis();
uint32 curTime = g_system->getMillis(true);
// Repeat as long as there is a TimerSlot that is scheduled to fire.
TimerSlot *slot = _head->next;

View file

@ -118,6 +118,13 @@ static const char HELP_STRING[] =
" --aspect-ratio Enable aspect ratio correction\n"
" --render-mode=MODE Enable additional render modes (cga, ega, hercGreen,\n"
" hercAmber, amiga)\n"
#ifdef ENABLE_EVENTRECORDER
" --record-mode=MODE Specify record mode for event recorder (record, playback,\n"
" passthrough [default])\n"
" --record-file=FILE Specify record file name\n"
" --disable-display Disable any gfx output. Used for headless events\n"
" playback by Event Recorder\n"
#endif
"\n"
#if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
" --alt-intro Use alternative intro for CD versions of Beneath a\n"
@ -232,10 +239,9 @@ void registerDefaults() {
ConfMan.registerDefault("confirm_exit", false);
ConfMan.registerDefault("disable_sdl_parachute", false);
ConfMan.registerDefault("disable_display", false);
ConfMan.registerDefault("record_mode", "none");
ConfMan.registerDefault("record_file_name", "record.bin");
ConfMan.registerDefault("record_temp_file_name", "record.tmp");
ConfMan.registerDefault("record_time_file_name", "record.time");
ConfMan.registerDefault("gui_saveload_chooser", "grid");
ConfMan.registerDefault("gui_saveload_last_pos", "0");
@ -424,6 +430,17 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
DO_OPTION_BOOL('f', "fullscreen")
END_OPTION
#ifdef ENABLE_EVENTRECORDER
DO_LONG_OPTION_INT("disable-display")
END_OPTION
DO_LONG_OPTION("record-mode")
END_OPTION
DO_LONG_OPTION("record-file-name")
END_OPTION
#endif
DO_LONG_OPTION("opl-driver")
END_OPTION
@ -569,18 +586,6 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
END_OPTION
#endif
DO_LONG_OPTION("record-mode")
END_OPTION
DO_LONG_OPTION("record-file-name")
END_OPTION
DO_LONG_OPTION("record-temp-file-name")
END_OPTION
DO_LONG_OPTION("record-time-file-name")
END_OPTION
#ifdef IPHONE
// This is automatically set when launched from the Springboard.
DO_LONG_OPTION_OPT("launchedFromSB", 0)

View file

@ -42,8 +42,11 @@
#include "common/debug.h"
#include "common/debug-channels.h" /* for debug manager */
#include "common/events.h"
#include "common/EventRecorder.h"
#include "gui/EventRecorder.h"
#include "common/fs.h"
#ifdef ENABLE_EVENTRECORDER
#include "common/recorderfile.h"
#endif
#include "common/system.h"
#include "common/textconsole.h"
#include "common/tokenizer.h"
@ -409,7 +412,9 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
settings["gfx-mode"] = "default";
}
}
if (settings.contains("disable-display")) {
ConfMan.setInt("disable-display", 1, Common::ConfigManager::kTransientDomain);
}
setupGraphics(system);
// Init the different managers that are used by the engines.
@ -428,7 +433,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
// TODO: This is just to match the current behavior, when we further extend
// our event recorder, we might do this at another place. Or even change
// the whole API for that ;-).
g_eventRec.init();
g_eventRec.RegisterEventSource();
// Now as the event manager is created, setup the keymapper
setupKeymapper(system);
@ -448,6 +453,21 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
// to save memory
PluginManager::instance().unloadPluginsExcept(PLUGIN_TYPE_ENGINE, plugin);
#ifdef ENABLE_EVENTRECORDER
Common::String recordMode = ConfMan.get("record_mode");
Common::String recordFileName = ConfMan.get("record_file_name");
if (recordMode == "record") {
g_eventRec.init(g_eventRec.generateRecordFileName(ConfMan.getActiveDomainName()), GUI::EventRecorder::kRecorderRecord);
} else if (recordMode == "playback") {
g_eventRec.init(recordFileName, GUI::EventRecorder::kRecorderPlayback);
} else if ((recordMode == "info") && (!recordFileName.empty())) {
Common::PlaybackFile record;
record.openRead(recordFileName);
debug("info:author=%s name=%s description=%s", record.getHeader().author.c_str(), record.getHeader().name.c_str(), record.getHeader().description.c_str());
break;
}
#endif
// Try to run the game
Common::Error result = runGame(plugin, system, specialDebug);
@ -478,6 +498,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
#ifdef FORCE_RTL
g_system->getEventManager()->resetQuit();
#endif
#ifdef ENABLE_EVENTRECORDER
if (g_eventRec.checkForContinueGame()) {
continue;
}
#endif
// Discard any command line options. It's unlikely that the user
// wanted to apply them to *all* games ever launched.
@ -501,7 +526,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
GUI::GuiManager::destroy();
Common::ConfigManager::destroy();
Common::DebugManager::destroy();
Common::EventRecorder::destroy();
GUI::EventRecorder::destroy();
Common::SearchManager::destroy();
#ifdef USE_TRANSLATION
Common::TranslationManager::destroy();

View file

@ -38,7 +38,9 @@ EventDispatcher::~EventDispatcher() {
delete i->observer;
}
delete _mapper;
if (_autoFreeMapper) {
delete _mapper;
}
_mapper = 0;
}
@ -68,11 +70,15 @@ void EventDispatcher::dispatch() {
}
}
void EventDispatcher::registerMapper(EventMapper *mapper) {
delete _mapper;
void EventDispatcher::registerMapper(EventMapper *mapper, bool autoFree) {
if (_autoFreeMapper) {
delete _mapper;
}
_mapper = mapper;
_autoFreeMapper = autoFree;
}
void EventDispatcher::registerSource(EventSource *source, bool autoFree) {
SourceEntry newEntry;

View file

@ -1,428 +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/EventRecorder.h"
#include "common/bufferedstream.h"
#include "common/config-manager.h"
#include "common/random.h"
#include "common/savefile.h"
#include "common/textconsole.h"
namespace Common {
DECLARE_SINGLETON(EventRecorder);
#define RECORD_SIGNATURE 0x54455354
#define RECORD_VERSION 1
uint32 readTime(ReadStream *inFile) {
uint32 d = inFile->readByte();
if (d == 0xff) {
d = inFile->readUint32LE();
}
return d;
}
void writeTime(WriteStream *outFile, uint32 d) {
//Simple RLE compression
if (d >= 0xff) {
outFile->writeByte(0xff);
outFile->writeUint32LE(d);
} else {
outFile->writeByte(d);
}
}
void readRecord(SeekableReadStream *inFile, uint32 &diff, Event &event, uint32 &millis) {
millis = readTime(inFile);
diff = inFile->readUint32LE();
event.type = (EventType)inFile->readUint32LE();
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP:
event.kbd.keycode = (KeyCode)inFile->readSint32LE();
event.kbd.ascii = inFile->readUint16LE();
event.kbd.flags = inFile->readByte();
break;
case EVENT_MOUSEMOVE:
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP:
event.mouse.x = inFile->readSint16LE();
event.mouse.y = inFile->readSint16LE();
break;
default:
break;
}
}
void writeRecord(WriteStream *outFile, uint32 diff, const Event &event, uint32 millis) {
writeTime(outFile, millis);
outFile->writeUint32LE(diff);
outFile->writeUint32LE((uint32)event.type);
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP:
outFile->writeSint32LE(event.kbd.keycode);
outFile->writeUint16LE(event.kbd.ascii);
outFile->writeByte(event.kbd.flags);
break;
case EVENT_MOUSEMOVE:
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP:
outFile->writeSint16LE(event.mouse.x);
outFile->writeSint16LE(event.mouse.y);
break;
default:
break;
}
}
EventRecorder::EventRecorder() {
_recordFile = NULL;
_recordTimeFile = NULL;
_playbackFile = NULL;
_playbackTimeFile = NULL;
_timeMutex = g_system->createMutex();
_recorderMutex = g_system->createMutex();
_eventCount = 0;
_lastEventCount = 0;
_lastMillis = 0;
_lastEventMillis = 0;
_recordMode = kPassthrough;
}
EventRecorder::~EventRecorder() {
deinit();
g_system->deleteMutex(_timeMutex);
g_system->deleteMutex(_recorderMutex);
}
void EventRecorder::init() {
String recordModeString = ConfMan.get("record_mode");
if (recordModeString.compareToIgnoreCase("record") == 0) {
_recordMode = kRecorderRecord;
debug(3, "EventRecorder: record");
} else {
if (recordModeString.compareToIgnoreCase("playback") == 0) {
_recordMode = kRecorderPlayback;
debug(3, "EventRecorder: playback");
} else {
_recordMode = kPassthrough;
debug(3, "EventRecorder: passthrough");
}
}
_recordFileName = ConfMan.get("record_file_name");
if (_recordFileName.empty()) {
_recordFileName = "record.bin";
}
_recordTempFileName = ConfMan.get("record_temp_file_name");
if (_recordTempFileName.empty()) {
_recordTempFileName = "record.tmp";
}
_recordTimeFileName = ConfMan.get("record_time_file_name");
if (_recordTimeFileName.empty()) {
_recordTimeFileName = "record.time";
}
// recorder stuff
if (_recordMode == kRecorderRecord) {
_recordCount = 0;
_recordTimeCount = 0;
_recordFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTempFileName), 128 * 1024);
_recordTimeFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTimeFileName), 128 * 1024);
_recordSubtitles = ConfMan.getBool("subtitles");
}
uint32 sign;
uint32 randomSourceCount;
if (_recordMode == kRecorderPlayback) {
_playbackCount = 0;
_playbackTimeCount = 0;
_playbackFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordFileName), 128 * 1024, DisposeAfterUse::YES);
_playbackTimeFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordTimeFileName), 128 * 1024, DisposeAfterUse::YES);
if (!_playbackFile) {
warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str());
_recordMode = kPassthrough;
}
if (!_playbackTimeFile) {
warning("Cannot open playback time file %s. Playback was switched off", _recordTimeFileName.c_str());
_recordMode = kPassthrough;
}
}
if (_recordMode == kRecorderPlayback) {
sign = _playbackFile->readUint32LE();
if (sign != RECORD_SIGNATURE) {
error("Unknown record file signature");
}
_playbackFile->readUint32LE(); // version
// conf vars
ConfMan.setBool("subtitles", _playbackFile->readByte() != 0);
_recordCount = _playbackFile->readUint32LE();
_recordTimeCount = _playbackFile->readUint32LE();
randomSourceCount = _playbackFile->readUint32LE();
for (uint i = 0; i < randomSourceCount; ++i) {
RandomSourceRecord rec;
rec.name = "";
uint32 sLen = _playbackFile->readUint32LE();
for (uint j = 0; j < sLen; ++j) {
char c = _playbackFile->readSByte();
rec.name += c;
}
rec.seed = _playbackFile->readUint32LE();
_randomSourceRecords.push_back(rec);
}
_hasPlaybackEvent = false;
}
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
g_system->getEventManager()->getEventDispatcher()->registerObserver(this, EventManager::kEventRecorderPriority, false, true);
}
void EventRecorder::deinit() {
debug(3, "EventRecorder: deinit");
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
g_system->lockMutex(_timeMutex);
g_system->lockMutex(_recorderMutex);
_recordMode = kPassthrough;
g_system->unlockMutex(_timeMutex);
g_system->unlockMutex(_recorderMutex);
delete _playbackFile;
delete _playbackTimeFile;
if (_recordFile != NULL) {
_recordFile->finalize();
delete _recordFile;
_recordTimeFile->finalize();
delete _recordTimeFile;
_playbackFile = g_system->getSavefileManager()->openForLoading(_recordTempFileName);
assert(_playbackFile);
_recordFile = g_system->getSavefileManager()->openForSaving(_recordFileName);
_recordFile->writeUint32LE(RECORD_SIGNATURE);
_recordFile->writeUint32LE(RECORD_VERSION);
// conf vars
_recordFile->writeByte(_recordSubtitles ? 1 : 0);
_recordFile->writeUint32LE(_recordCount);
_recordFile->writeUint32LE(_recordTimeCount);
_recordFile->writeUint32LE(_randomSourceRecords.size());
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
_recordFile->writeUint32LE(_randomSourceRecords[i].name.size());
_recordFile->writeString(_randomSourceRecords[i].name);
_recordFile->writeUint32LE(_randomSourceRecords[i].seed);
}
for (uint i = 0; i < _recordCount; ++i) {
uint32 tempDiff;
Event tempEvent;
uint32 millis;
readRecord(_playbackFile, tempDiff, tempEvent, millis);
writeRecord(_recordFile, tempDiff, tempEvent, millis);
}
_recordFile->finalize();
delete _recordFile;
delete _playbackFile;
//TODO: remove recordTempFileName'ed file
}
}
void EventRecorder::registerRandomSource(RandomSource &rnd, const String &name) {
if (_recordMode == kRecorderRecord) {
RandomSourceRecord rec;
rec.name = name;
rec.seed = rnd.getSeed();
_randomSourceRecords.push_back(rec);
}
if (_recordMode == kRecorderPlayback) {
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
if (_randomSourceRecords[i].name == name) {
rnd.setSeed(_randomSourceRecords[i].seed);
_randomSourceRecords.remove_at(i);
break;
}
}
}
}
void EventRecorder::processMillis(uint32 &millis) {
uint32 d;
if (_recordMode == kPassthrough) {
return;
}
g_system->lockMutex(_timeMutex);
if (_recordMode == kRecorderRecord) {
d = millis - _lastMillis;
writeTime(_recordTimeFile, d);
_recordTimeCount++;
}
if (_recordMode == kRecorderPlayback) {
if (_recordTimeCount > _playbackTimeCount) {
d = readTime(_playbackTimeFile);
while ((_lastMillis + d > millis) && (_lastMillis + d - millis > 50)) {
_recordMode = kPassthrough;
g_system->delayMillis(50);
millis = g_system->getMillis();
_recordMode = kRecorderPlayback;
}
millis = _lastMillis + d;
_playbackTimeCount++;
}
}
_lastMillis = millis;
g_system->unlockMutex(_timeMutex);
}
bool EventRecorder::processDelayMillis(uint &msecs) {
if (_recordMode == kRecorderPlayback) {
_recordMode = kPassthrough;
uint32 millis = g_system->getMillis();
_recordMode = kRecorderPlayback;
if (_lastMillis > millis) {
// Skip delay if we're getting late
return true;
}
}
return false;
}
bool EventRecorder::notifyEvent(const Event &ev) {
if (_recordMode != kRecorderRecord)
return false;
StackLock lock(_recorderMutex);
++_eventCount;
writeRecord(_recordFile, _eventCount - _lastEventCount, ev, _lastMillis - _lastEventMillis);
_recordCount++;
_lastEventCount = _eventCount;
_lastEventMillis = _lastMillis;
return false;
}
bool EventRecorder::notifyPoll() {
if (_recordMode != kRecorderRecord)
return false;
++_eventCount;
return false;
}
bool EventRecorder::pollEvent(Event &ev) {
uint32 millis;
if (_recordMode != kRecorderPlayback)
return false;
StackLock lock(_recorderMutex);
++_eventCount;
if (!_hasPlaybackEvent) {
if (_recordCount > _playbackCount) {
readRecord(_playbackFile, const_cast<uint32&>(_playbackDiff), _playbackEvent, millis);
_playbackCount++;
_hasPlaybackEvent = true;
}
}
if (_hasPlaybackEvent) {
if (_playbackDiff <= (_eventCount - _lastEventCount)) {
switch (_playbackEvent.type) {
case EVENT_MOUSEMOVE:
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
g_system->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y);
break;
default:
break;
}
ev = _playbackEvent;
_hasPlaybackEvent = false;
_lastEventCount = _eventCount;
return true;
}
}
return false;
}
} // End of namespace Common

View file

@ -1,110 +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.
*
*/
#ifndef COMMON_EVENTRECORDER_H
#define COMMON_EVENTRECORDER_H
#include "common/scummsys.h"
#include "common/events.h"
#include "common/singleton.h"
#include "common/mutex.h"
#include "common/array.h"
#define g_eventRec (Common::EventRecorder::instance())
namespace Common {
class RandomSource;
class SeekableReadStream;
class WriteStream;
/**
* Our generic event recorder.
*
* TODO: Add more documentation.
*/
class EventRecorder : private EventSource, private EventObserver, public Singleton<EventRecorder> {
friend class Singleton<SingletonBaseType>;
EventRecorder();
~EventRecorder();
public:
void init();
void deinit();
/** Register random source so it can be serialized in game test purposes */
void registerRandomSource(RandomSource &rnd, const String &name);
/** TODO: Add documentation, this is only used by the backend */
void processMillis(uint32 &millis);
/** TODO: Add documentation, this is only used by the backend */
bool processDelayMillis(uint &msecs);
private:
bool notifyEvent(const Event &ev);
bool notifyPoll();
bool pollEvent(Event &ev);
bool allowMapping() const { return false; }
class RandomSourceRecord {
public:
String name;
uint32 seed;
};
Array<RandomSourceRecord> _randomSourceRecords;
bool _recordSubtitles;
volatile uint32 _recordCount;
volatile uint32 _lastRecordEvent;
volatile uint32 _recordTimeCount;
volatile uint32 _lastEventMillis;
WriteStream *_recordFile;
WriteStream *_recordTimeFile;
MutexRef _timeMutex;
MutexRef _recorderMutex;
volatile uint32 _lastMillis;
volatile uint32 _playbackCount;
volatile uint32 _playbackDiff;
volatile bool _hasPlaybackEvent;
volatile uint32 _playbackTimeCount;
Event _playbackEvent;
SeekableReadStream *_playbackFile;
SeekableReadStream *_playbackTimeFile;
volatile uint32 _eventCount;
volatile uint32 _lastEventCount;
enum RecordMode {
kPassthrough = 0,
kRecorderRecord = 1,
kRecorderPlayback = 2
};
volatile RecordMode _recordMode;
String _recordFileName;
String _recordTempFileName;
String _recordTimeFileName;
};
} // End of namespace Common
#endif

View file

@ -117,5 +117,9 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
*/
extern int gDebugLevel;
//Global constant for EventRecorder debug channel
enum GlobalDebugLevels {
kDebugLevelEventRec = 1 << 30
};
#endif

View file

@ -288,11 +288,14 @@ public:
* to the EventDispatcher, thus it will be deleted
* with "delete", when EventDispatcher is destroyed.
*
* Note there is only one mapper per EventDispatcher
* possible, thus when this method is called twice,
* the former mapper will be destroied.
* @param autoFree Destroy previous mapper [default]
* Normally we allow only one event mapper to exists,
* However Event Recorder must intervent into normal
* event flow without altering its semantics. Thus during
* Event Recorder playback and recording we allow
* two mappers.
*/
void registerMapper(EventMapper *mapper);
void registerMapper(EventMapper *mapper, bool autoFree = true);
/**
* Queries the setup event mapper.
@ -326,6 +329,7 @@ public:
*/
void unregisterObserver(EventObserver *obs);
private:
bool _autoFreeMapper;
EventMapper *_mapper;
struct Entry {

View file

@ -89,8 +89,9 @@ public:
*/
class MemoryWriteStream : public WriteStream {
private:
byte *_ptr;
const uint32 _bufSize;
protected:
byte *_ptr;
uint32 _pos;
bool _err;
public:
@ -116,6 +117,40 @@ public:
virtual void clearErr() { _err = false; }
};
/**
* MemoryWriteStream subclass with ability to set stream position indicator.
*/
class SeekableMemoryWriteStream : public MemoryWriteStream {
private:
byte *_ptrOrig;
public:
SeekableMemoryWriteStream(byte *buf, uint32 len) : MemoryWriteStream(buf, len), _ptrOrig(buf) {}
uint32 seek(uint32 offset, int whence = SEEK_SET) {
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed',
// i.e. from the end.
offset = size() + offset;
// Fall through
case SEEK_SET:
_ptr = _ptrOrig + offset;
_pos = offset;
break;
case SEEK_CUR:
_ptr += offset;
_pos += offset;
break;
}
// Post-Condition
if (_pos > size()) {
_pos = size();
_ptr = _ptrOrig + _pos;
}
return _pos;
}
};
/**
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
* that grows as it's written to.

View file

@ -10,7 +10,6 @@ MODULE_OBJS := \
error.o \
EventDispatcher.o \
EventMapper.o \
EventRecorder.o \
file.o \
fs.o \
gui_options.o \
@ -51,5 +50,10 @@ MODULE_OBJS += \
rdft.o \
sinetables.o
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
recorderfile.o
endif
# Include common rules
include $(srcdir)/rules.mk

View file

@ -21,7 +21,7 @@
#include "common/random.h"
#include "common/system.h"
#include "common/EventRecorder.h"
#include "gui/EventRecorder.h"
namespace Common {
@ -30,13 +30,8 @@ RandomSource::RandomSource(const String &name) {
// Use system time as RNG seed. Normally not a good idea, if you are using
// a RNG for security purposes, but good enough for our purposes.
assert(g_system);
uint32 seed = g_system->getMillis();
uint32 seed = g_eventRec.getRandomSeed(name);
setSeed(seed);
// Register this random source with the event recorder. This may end
// up querying or resetting the current seed, so we must call it
// *after* the initial seed has been set.
g_eventRec.registerRandomSource(*this, name);
}
void RandomSource::setSeed(uint32 seed) {

708
common/recorderfile.cpp Normal file
View file

@ -0,0 +1,708 @@
/* 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/system.h"
#include "gui/EventRecorder.h"
#include "common/md5.h"
#include "common/recorderfile.h"
#include "common/savefile.h"
#include "common/bufferedstream.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "graphics/scaler.h"
#define RECORD_VERSION 1
namespace Common {
PlaybackFile::PlaybackFile() : _tmpRecordFile(_tmpBuffer, kRecordBuffSize), _tmpPlaybackFile(_tmpBuffer, kRecordBuffSize) {
_readStream = NULL;
_writeStream = NULL;
_screenshotsFile = NULL;
_mode = kClosed;
}
PlaybackFile::~PlaybackFile() {
close();
}
bool PlaybackFile::openWrite(const String &fileName) {
close();
_header.fileName = fileName;
_writeStream = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(fileName), 128 * 1024);
_headerDumped = false;
_recordCount = 0;
if (_writeStream == NULL) {
return false;
}
_mode = kWrite;
return true;
}
bool PlaybackFile::openRead(const String &fileName) {
close();
_header.fileName = fileName;
_eventsSize = 0;
_tmpPlaybackFile.seek(0);
_readStream = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(fileName), 128 * 1024, DisposeAfterUse::YES);
if (_readStream == NULL) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"file %s not found\"", fileName.c_str());
return false;
}
if (!parseHeader()) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"header parsing failed\"");
return false;
}
_screenshotsFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving("screenshots.bin"), 128 * 1024);
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=success");
_mode = kRead;
return true;
}
void PlaybackFile::close() {
delete _readStream;
_readStream = NULL;
if (_writeStream != NULL) {
dumpRecordsToFile();
_writeStream->finalize();
delete _writeStream;
_writeStream = NULL;
updateHeader();
}
if (_screenshotsFile != NULL) {
_screenshotsFile->finalize();
delete _screenshotsFile;
_screenshotsFile = NULL;
}
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
free(i->_value.buffer);
}
_header.saveFiles.clear();
_mode = kClosed;
}
bool PlaybackFile::parseHeader() {
PlaybackFileHeader result;
ChunkHeader nextChunk;
_playbackParseState = kFileStateCheckFormat;
if (!readChunkHeader(nextChunk)) {
_playbackParseState = kFileStateError;
return false;
}
while ((_playbackParseState != kFileStateDone) && (_playbackParseState != kFileStateError)) {
if (processChunk(nextChunk)) {
if (!readChunkHeader(nextChunk)) {
warning("Error in header parsing");
_playbackParseState = kFileStateError;
}
}
}
return _playbackParseState == kFileStateDone;
}
bool PlaybackFile::checkPlaybackFileVersion() {
uint32 version;
version = _readStream->readUint32LE();
if (version != RECORD_VERSION) {
warning("Incorrect playback file version. Expected version %d, but got %d.", RECORD_VERSION, version);
return false;
}
return true;
}
String PlaybackFile::readString(int len) {
String result;
char buf[50];
int readSize = 49;
while (len > 0) {
if (len <= 49) {
readSize = len;
}
_readStream->read(buf, readSize);
buf[readSize] = 0;
result += buf;
len -= readSize;
}
return result;
}
bool PlaybackFile::readChunkHeader(PlaybackFile::ChunkHeader &nextChunk) {
nextChunk.id = (FileTag)_readStream->readUint32LE();
nextChunk.len = _readStream->readUint32LE();
return !_readStream->err() && !_readStream->eos();
}
bool PlaybackFile::processChunk(ChunkHeader &nextChunk) {
switch (_playbackParseState) {
case kFileStateCheckFormat:
if (nextChunk.id == kFormatIdTag) {
_playbackParseState = kFileStateCheckVersion;
} else {
warning("Unknown playback file signature");
_playbackParseState = kFileStateError;
}
break;
case kFileStateCheckVersion:
if ((nextChunk.id == kVersionTag) && checkPlaybackFileVersion()) {
_playbackParseState = kFileStateSelectSection;
} else {
_playbackParseState = kFileStateError;
}
break;
case kFileStateSelectSection:
switch (nextChunk.id) {
case kHeaderSectionTag:
_playbackParseState = kFileStateProcessHeader;
break;
case kHashSectionTag:
_playbackParseState = kFileStateProcessHash;
break;
case kRandomSectionTag:
_playbackParseState = kFileStateProcessRandom;
break;
case kEventTag:
case kScreenShotTag:
_readStream->seek(-8, SEEK_CUR);
_playbackParseState = kFileStateDone;
return false;
case kSaveTag:
_playbackParseState = kFileStateProcessSave;
break;
case kSettingsSectionTag:
_playbackParseState = kFileStateProcessSettings;
warning("Loading record header");
break;
default:
_readStream->skip(nextChunk.len);
break;
}
break;
case kFileStateProcessSave:
if (nextChunk.id == kSaveRecordTag) {
readSaveRecord();
} else {
_playbackParseState = kFileStateSelectSection;
return false;
}
break;
case kFileStateProcessHeader:
switch (nextChunk.id) {
case kAuthorTag:
_header.author = readString(nextChunk.len);
break;
case kCommentsTag:
_header.notes = readString(nextChunk.len);
break;
case kNameTag:
_header.name = readString(nextChunk.len);
break;
default:
_playbackParseState = kFileStateSelectSection;
return false;
}
break;
case kFileStateProcessHash:
if (nextChunk.id == kHashRecordTag) {
readHashMap(nextChunk);
} else {
_playbackParseState = kFileStateSelectSection;
return false;
}
break;
case kFileStateProcessRandom:
if (nextChunk.id == kRandomRecordTag) {
processRndSeedRecord(nextChunk);
} else {
_playbackParseState = kFileStateSelectSection;
return false;
}
break;
case kFileStateProcessSettings:
if (nextChunk.id == kSettingsRecordTag) {
if (!processSettingsRecord()) {
_playbackParseState = kFileStateError;
return false;
}
} else {
_playbackParseState = kFileStateSelectSection;
return false;
}
break;
default:
return false;
}
return true;
}
void PlaybackFile::returnToChunkHeader() {
_readStream->seek(-8, SEEK_CUR);
}
void PlaybackFile::readHashMap(ChunkHeader chunk) {
String hashName = readString(chunk.len - 32);
String hashMd5 = readString(32);
_header.hashRecords[hashName] = hashMd5;
}
void PlaybackFile::processRndSeedRecord(ChunkHeader chunk) {
String randomSourceName = readString(chunk.len - 4);
uint32 randomSourceSeed = _readStream->readUint32LE();
_header.randomSourceRecords[randomSourceName] = randomSourceSeed;
}
bool PlaybackFile::processSettingsRecord() {
ChunkHeader keyChunk;
if (!readChunkHeader(keyChunk) || (keyChunk.id != kSettingsRecordKeyTag)) {
warning("Invalid format of settings section");
return false;
}
String key = readString(keyChunk.len);
ChunkHeader valueChunk;
if (!readChunkHeader(valueChunk) || (valueChunk.id != kSettingsRecordValueTag)) {
warning("Invalid format of settings section");
return false;
}
String value = readString(valueChunk.len);
_header.settingsRecords[key] = value;
return true;
}
bool PlaybackFile::readSaveRecord() {
ChunkHeader fileNameChunk;
if (!readChunkHeader(fileNameChunk) || (fileNameChunk.id != kSaveRecordNameTag)) {
warning("Invalid format of save section");
return false;
}
String fileName = readString(fileNameChunk.len);
ChunkHeader saveBufferChunk;
if (!readChunkHeader(saveBufferChunk) || (saveBufferChunk.id != kSaveRecordBufferTag)) {
warning("Invalid format of save section");
return false;
}
SaveFileBuffer buf;
buf.size = saveBufferChunk.len;
buf.buffer = (byte *)malloc(saveBufferChunk.len);
_readStream->read(buf.buffer, buf.size);
_header.saveFiles[fileName] = buf;
debugC(1, kDebugLevelEventRec, "playback:action=\"Load save file\" filename=%s len=%d", fileName.c_str(), buf.size);
return true;
}
RecorderEvent PlaybackFile::getNextEvent() {
assert(_mode == kRead);
if (isEventsBufferEmpty()) {
PlaybackFile::ChunkHeader header;
header.id = kFormatIdTag;
while (header.id != kEventTag) {
if (!readChunkHeader(header) || _readStream->eos()) {
break;
}
switch (header.id) {
case kEventTag:
readEventsToBuffer(header.len);
break;
case kScreenShotTag:
_readStream->seek(-4, SEEK_CUR);
header.len = _readStream->readUint32BE();
_readStream->skip(header.len-8);
break;
case kMD5Tag:
checkRecordedMD5();
break;
default:
_readStream->skip(header.len);
break;
}
}
}
RecorderEvent result;
readEvent(result);
return result;
}
bool PlaybackFile::isEventsBufferEmpty() {
return (uint32)_tmpPlaybackFile.pos() == _eventsSize;
}
void PlaybackFile::readEvent(RecorderEvent& event) {
event.recordedtype = (RecorderEventType)_tmpPlaybackFile.readByte();
switch (event.recordedtype) {
case kRecorderEventTypeTimer:
event.time = _tmpPlaybackFile.readUint32LE();
break;
case kRecorderEventTypeNormal:
event.type = (EventType)_tmpPlaybackFile.readUint32LE();
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP:
event.time = _tmpPlaybackFile.readUint32LE();
event.kbd.keycode = (KeyCode)_tmpPlaybackFile.readSint32LE();
event.kbd.ascii = _tmpPlaybackFile.readUint16LE();
event.kbd.flags = _tmpPlaybackFile.readByte();
break;
case EVENT_MOUSEMOVE:
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP:
event.time = _tmpPlaybackFile.readUint32LE();
event.mouse.x = _tmpPlaybackFile.readSint16LE();
event.mouse.y = _tmpPlaybackFile.readSint16LE();
break;
default:
event.time = _tmpPlaybackFile.readUint32LE();
break;
}
break;
}
event.synthetic = true;
}
void PlaybackFile::readEventsToBuffer(uint32 size) {
_readStream->read(_tmpBuffer, size);
_tmpPlaybackFile.seek(0);
_eventsSize = size;
}
void PlaybackFile::saveScreenShot(Graphics::Surface &screen, byte md5[16]) {
dumpRecordsToFile();
_writeStream->writeUint32LE(kMD5Tag);
_writeStream->writeUint32LE(16);
_writeStream->write(md5, 16);
Graphics::saveThumbnail(*_writeStream, screen);
}
void PlaybackFile::dumpRecordsToFile() {
if (!_headerDumped) {
dumpHeaderToFile();
_headerDumped = true;
}
if (_recordCount == 0) {
return;
}
_writeStream->writeUint32LE(kEventTag);
_writeStream->writeUint32LE(_tmpRecordFile.pos());
_writeStream->write(_tmpBuffer, _tmpRecordFile.pos());
_tmpRecordFile.seek(0);
_recordCount = 0;
}
void PlaybackFile::dumpHeaderToFile() {
_writeStream->writeUint32LE(kFormatIdTag);
// Specify size for first tag as NULL since we cannot calculate
// size of the file at time of the header dumping
_writeStream->writeUint32LE(0);
_writeStream->writeUint32LE(kVersionTag);
_writeStream->writeUint32LE(4);
_writeStream->writeUint32LE(RECORD_VERSION);
writeHeaderSection();
writeGameHash();
writeRandomRecords();
writeGameSettings();
writeSaveFilesSection();
}
void PlaybackFile::writeHeaderSection() {
uint32 headerSize = 0;
if (!_header.author.empty()) {
headerSize = _header.author.size() + 8;
}
if (!_header.notes.empty()) {
headerSize += _header.notes.size() + 8;
}
if (!_header.name.empty()) {
headerSize += _header.name.size() + 8;
}
if (headerSize == 0) {
return;
}
_writeStream->writeUint32LE(kHeaderSectionTag);
_writeStream->writeUint32LE(headerSize);
if (!_header.author.empty()) {
_writeStream->writeUint32LE(kAuthorTag);
_writeStream->writeUint32LE(_header.author.size());
_writeStream->writeString(_header.author);
}
if (!_header.notes.empty()) {
_writeStream->writeUint32LE(kCommentsTag);
_writeStream->writeUint32LE(_header.notes.size());
_writeStream->writeString(_header.notes);
}
if (!_header.name.empty()) {
_writeStream->writeUint32LE(kNameTag);
_writeStream->writeUint32LE(_header.name.size());
_writeStream->writeString(_header.name);
}
}
void PlaybackFile::writeGameHash() {
uint32 hashSectionSize = 0;
for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
hashSectionSize = hashSectionSize + i->_key.size() + i->_value.size() + 8;
}
if (_header.hashRecords.size() == 0) {
return;
}
_writeStream->writeUint32LE(kHashSectionTag);
_writeStream->writeUint32LE(hashSectionSize);
for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
_writeStream->writeUint32LE(kHashRecordTag);
_writeStream->writeUint32LE(i->_key.size() + i->_value.size());
_writeStream->writeString(i->_key);
_writeStream->writeString(i->_value);
}
}
void PlaybackFile::writeRandomRecords() {
uint32 randomSectionSize = 0;
for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
randomSectionSize = randomSectionSize + i->_key.size() + 12;
}
if (_header.randomSourceRecords.size() == 0) {
return;
}
_writeStream->writeUint32LE(kRandomSectionTag);
_writeStream->writeUint32LE(randomSectionSize);
for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
_writeStream->writeUint32LE(kRandomRecordTag);
_writeStream->writeUint32LE(i->_key.size() + 4);
_writeStream->writeString(i->_key);
_writeStream->writeUint32LE(i->_value);
}
}
void PlaybackFile::writeEvent(const RecorderEvent &event) {
assert(_mode == kWrite);
_recordCount++;
_tmpRecordFile.writeByte(event.recordedtype);
switch (event.recordedtype) {
case kRecorderEventTypeTimer:
_tmpRecordFile.writeUint32LE(event.time);
break;
case kRecorderEventTypeNormal:
_tmpRecordFile.writeUint32LE((uint32)event.type);
switch(event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP:
_tmpRecordFile.writeUint32LE(event.time);
_tmpRecordFile.writeSint32LE(event.kbd.keycode);
_tmpRecordFile.writeUint16LE(event.kbd.ascii);
_tmpRecordFile.writeByte(event.kbd.flags);
break;
case EVENT_MOUSEMOVE:
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP:
_tmpRecordFile.writeUint32LE(event.time);
_tmpRecordFile.writeSint16LE(event.mouse.x);
_tmpRecordFile.writeSint16LE(event.mouse.y);
break;
default:
_tmpRecordFile.writeUint32LE(event.time);
break;
}
break;
}
if (_recordCount == kMaxBufferedRecords) {
dumpRecordsToFile();
}
}
void PlaybackFile::writeGameSettings() {
_writeStream->writeUint32LE(kSettingsSectionTag);
uint32 settingsSectionSize = 0;
for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
settingsSectionSize += i->_key.size() + i->_value.size() + 24;
}
_writeStream->writeUint32LE(settingsSectionSize);
for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
_writeStream->writeUint32LE(kSettingsRecordTag);
_writeStream->writeUint32LE(i->_key.size() + i->_value.size() + 16);
_writeStream->writeUint32LE(kSettingsRecordKeyTag);
_writeStream->writeUint32LE(i->_key.size());
_writeStream->writeString(i->_key);
_writeStream->writeUint32LE(kSettingsRecordValueTag);
_writeStream->writeUint32LE(i->_value.size());
_writeStream->writeString(i->_value);
}
}
int PlaybackFile::getScreensCount() {
if (_mode != kRead) {
return 0;
}
_readStream->seek(0);
int result = 0;
while (skipToNextScreenshot()) {
uint32 size = _readStream->readUint32BE();
_readStream->skip(size-8);
++result;
}
return result;
}
bool PlaybackFile::skipToNextScreenshot() {
while (true) {
FileTag id = (FileTag)_readStream->readUint32LE();
if (_readStream->eos()) {
break;
}
if (id == kScreenShotTag) {
return true;
}
else {
uint32 size = _readStream->readUint32LE();
_readStream->skip(size);
}
}
return false;
}
Graphics::Surface *PlaybackFile::getScreenShot(int number) {
if (_mode != kRead) {
return NULL;
}
_readStream->seek(0);
int screenCount = 1;
while (skipToNextScreenshot()) {
if (screenCount == number) {
screenCount++;
_readStream->seek(-4, SEEK_CUR);
return Graphics::loadThumbnail(*_readStream);
} else {
uint32 size = _readStream->readUint32BE();
_readStream->skip(size-8);
screenCount++;
}
}
return NULL;
}
void PlaybackFile::updateHeader() {
if (_mode == kWrite) {
_readStream = g_system->getSavefileManager()->openForLoading(_header.fileName);
}
_readStream->seek(0);
skipHeader();
String tmpFilename = "_" + _header.fileName;
_writeStream = g_system->getSavefileManager()->openForSaving(tmpFilename);
dumpHeaderToFile();
uint32 readedSize = 0;
do {
readedSize = _readStream->read(_tmpBuffer, kRecordBuffSize);
_writeStream->write(_tmpBuffer, readedSize);
} while (readedSize != 0);
delete _writeStream;
_writeStream = NULL;
delete _readStream;
_readStream = NULL;
g_system->getSavefileManager()->removeSavefile(_header.fileName);
g_system->getSavefileManager()->renameSavefile(tmpFilename, _header.fileName);
if (_mode == kRead) {
openRead(_header.fileName);
}
}
void PlaybackFile::skipHeader() {
while (true) {
uint32 id = _readStream->readUint32LE();
if (_readStream->eos()) {
break;
}
if ((id == kScreenShotTag) || (id == kEventTag) || (id == kMD5Tag)) {
_readStream->seek(-4, SEEK_CUR);
return;
}
else {
uint32 size = _readStream->readUint32LE();
_readStream->skip(size);
}
}
}
void PlaybackFile::addSaveFile(const String &fileName, InSaveFile *saveStream) {
uint oldPos = saveStream->pos();
saveStream->seek(0);
_header.saveFiles[fileName].buffer = (byte *)malloc(saveStream->size());
_header.saveFiles[fileName].size = saveStream->size();
saveStream->read(_header.saveFiles[fileName].buffer, saveStream->size());
saveStream->seek(oldPos);
}
void PlaybackFile::writeSaveFilesSection() {
uint size = 0;
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
size += i->_value.size + i->_key.size() + 24;
}
if (size == 0) {
return;
}
_writeStream->writeSint32LE(kSaveTag);
_writeStream->writeSint32LE(size);
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
_writeStream->writeSint32LE(kSaveRecordTag);
_writeStream->writeSint32LE(i->_key.size() + i->_value.size + 16);
_writeStream->writeSint32LE(kSaveRecordNameTag);
_writeStream->writeSint32LE(i->_key.size());
_writeStream->writeString(i->_key);
_writeStream->writeSint32LE(kSaveRecordBufferTag);
_writeStream->writeSint32LE(i->_value.size);
_writeStream->write(i->_value.buffer, i->_value.size);
}
}
void PlaybackFile::checkRecordedMD5() {
uint8 currentMD5[16];
uint8 savedMD5[16];
Graphics::Surface screen;
_readStream->read(savedMD5, 16);
if (!g_eventRec.grabScreenAndComputeMD5(screen, currentMD5)) {
return;
}
uint32 seconds = g_system->getMillis(true) / 1000;
String screenTime = String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60);
if (memcmp(savedMD5, currentMD5, 16) != 0) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = fail", screenTime.c_str());
warning("Recorded and current screenshots are different");
} else {
debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = success", screenTime.c_str());
}
Graphics::saveThumbnail(*_screenshotsFile, screen);
screen.free();
}
}

180
common/recorderfile.h Normal file
View file

@ -0,0 +1,180 @@
/* 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 COMMON_RECORDERFILE_H
#define COMMON_RECORDERFILE_H
#include "common/scummsys.h"
#include "common/events.h"
#include "common/mutex.h"
#include "common/memstream.h"
#include "common/config-manager.h"
#include "common/savefile.h"
//capacity of records buffer
#define kMaxBufferedRecords 10000
#define kRecordBuffSize sizeof(RecorderEvent) * kMaxBufferedRecords
namespace Common {
enum RecorderEventType {
kRecorderEventTypeNormal = 0,
kRecorderEventTypeTimer = 1
};
struct RecorderEvent : Event {
RecorderEventType recordedtype;
uint32 time;
};
class PlaybackFile {
typedef HashMap<String, uint32, IgnoreCase_Hash, IgnoreCase_EqualTo> RandomSeedsDictionary;
enum fileMode {
kRead = 0,
kWrite = 1,
kClosed = 2
};
enum PlaybackFileState {
kFileStateCheckFormat,
kFileStateCheckVersion,
kFileStateProcessHash,
kFileStateProcessHeader,
kFileStateProcessRandom,
kFileStateSelectSection,
kFileStateProcessSettings,
kFileStateProcessSave,
kFileStateDone,
kFileStateError
};
enum FileTag {
kFormatIdTag = MKTAG('P','B','C','K'),
kVersionTag = MKTAG('V','E','R','S'),
kHeaderSectionTag = MKTAG('H','E','A','D'),
kHashSectionTag = MKTAG('H','A','S','H'),
kRandomSectionTag = MKTAG('R','A','N','D'),
kEventTag = MKTAG('E','V','N','T'),
kScreenShotTag = MKTAG('B','M','H','T'),
kSettingsSectionTag = MKTAG('S','E','T','T'),
kAuthorTag = MKTAG('H','A','U','T'),
kCommentsTag = MKTAG('H','C','M','T'),
kNameTag = MKTAG('H','N','A','M'),
kHashRecordTag = MKTAG('H','R','C','D'),
kRandomRecordTag = MKTAG('R','R','C','D'),
kSettingsRecordTag = MKTAG('S','R','E','C'),
kSettingsRecordKeyTag = MKTAG('S','K','E','Y'),
kSettingsRecordValueTag = MKTAG('S','V','A','L'),
kSaveTag = MKTAG('S','A','V','E'),
kSaveRecordTag = MKTAG('R','S','A','V'),
kSaveRecordNameTag = MKTAG('S','N','A','M'),
kSaveRecordBufferTag = MKTAG('S','B','U','F'),
kMD5Tag = MKTAG('M','D','5',' ')
};
struct ChunkHeader {
FileTag id;
uint32 len;
};
public:
struct SaveFileBuffer {
byte *buffer;
uint32 size;
};
struct PlaybackFileHeader {
String fileName;
String author;
String name;
String notes;
String description;
StringMap hashRecords;
StringMap settingsRecords;
HashMap<String, SaveFileBuffer> saveFiles;
RandomSeedsDictionary randomSourceRecords;
};
PlaybackFile();
~PlaybackFile();
bool openWrite(const String &fileName);
bool openRead(const String &fileName);
void close();
RecorderEvent getNextEvent();
void writeEvent(const RecorderEvent &event);
void saveScreenShot(Graphics::Surface &screen, byte md5[16]);
Graphics::Surface *getScreenShot(int number);
int getScreensCount();
bool isEventsBufferEmpty();
PlaybackFileHeader &getHeader() {return _header;}
void updateHeader();
void addSaveFile(const String &fileName, InSaveFile *saveStream);
private:
WriteStream *_recordFile;
WriteStream *_writeStream;
WriteStream *_screenshotsFile;
MemoryReadStream _tmpPlaybackFile;
SeekableReadStream *_readStream;
SeekableMemoryWriteStream _tmpRecordFile;
fileMode _mode;
bool _headerDumped;
int _recordCount;
uint32 _eventsSize;
byte _tmpBuffer[kRecordBuffSize];
PlaybackFileHeader _header;
PlaybackFileState _playbackParseState;
void skipHeader();
bool parseHeader();
bool processChunk(ChunkHeader &nextChunk);
void returnToChunkHeader();
bool readSaveRecord();
void checkRecordedMD5();
bool readChunkHeader(ChunkHeader &nextChunk);
void processRndSeedRecord(ChunkHeader chunk);
bool processSettingsRecord();
bool checkPlaybackFileVersion();
void dumpHeaderToFile();
void writeSaveFilesSection();
void writeGameSettings();
void writeHeaderSection();
void writeGameHash();
void writeRandomRecords();
void dumpRecordsToFile();
String readString(int len);
void readHashMap(ChunkHeader chunk);
bool skipToNextScreenshot();
void readEvent(RecorderEvent& event);
void readEventsToBuffer(uint32 size);
bool grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]);
};
} // End of namespace Common
#endif

View file

@ -30,6 +30,7 @@
#include "common/taskbar.h"
#include "common/updates.h"
#include "common/textconsole.h"
#include "gui/EventRecorder.h"
#include "backends/audiocd/default/default-audiocd.h"
#include "backends/fs/fs-factory.h"
@ -84,7 +85,7 @@ void OSystem::initBackend() {
error("Backend failed to instantiate audio CD manager");
if (!_eventManager)
error("Backend failed to instantiate event manager");
if (!_timerManager)
if (!getTimerManager())
error("Backend failed to instantiate timer manager");
// TODO: We currently don't check _savefileManager, because at least
@ -152,3 +153,11 @@ Common::String OSystem::getDefaultConfigFileName() {
Common::String OSystem::getSystemLanguage() const {
return "en_US";
}
Common::TimerManager *OSystem::getTimerManager() {
return _timerManager;
}
Common::SaveFileManager *OSystem::getSavefileManager() {
return g_eventRec.getSaveManager(_savefileManager);
}

View file

@ -890,8 +890,14 @@ public:
/** @name Events and Time */
//@{
/** Get the number of milliseconds since the program was started. */
virtual uint32 getMillis() = 0;
/** Get the number of milliseconds since the program was started.
@param skipRecord Skip recording of this value by event recorder.
This could be needed particularly when we are in
an on-screen GUI loop where player can pause
the recording.
*/
virtual uint32 getMillis(bool skipRecord = false) = 0;
/** Delay/sleep for the specified amount of milliseconds. */
virtual void delayMillis(uint msecs) = 0;
@ -907,9 +913,7 @@ public:
* Return the timer manager singleton. For more information, refer
* to the TimerManager documentation.
*/
inline Common::TimerManager *getTimerManager() {
return _timerManager;
}
virtual Common::TimerManager *getTimerManager();
/**
* Return the event manager singleton. For more information, refer
@ -1086,9 +1090,7 @@ public:
* and other modifiable persistent game data. For more information,
* refer to the SaveFileManager documentation.
*/
inline Common::SaveFileManager *getSavefileManager() {
return _savefileManager;
}
Common::SaveFileManager *getSavefileManager();
#if defined(USE_TASKBAR)
/**

29
configure vendored
View file

@ -138,9 +138,10 @@ _build_hq_scalers=yes
_enable_prof=no
_global_constructors=no
_bink=yes
# Default vkeybd/keymapper options
# Default vkeybd/keymapper/eventrec options
_vkeybd=no
_keymapper=no
_eventrec=auto
# GUI translation options
_translation=yes
# Default platform settings
@ -1036,6 +1037,8 @@ for ac_option in $@; do
--disable-vkeybd) _vkeybd=no ;;
--enable-keymapper) _keymapper=yes ;;
--disable-keymapper) _keymapper=no ;;
--enable-eventrecorder) _eventrec=yes ;;
--disable-eventrecorder) _eventrec=no ;;
--enable-text-console) _text_console=yes ;;
--disable-text-console) _text_console=no ;;
--with-fluidsynth-prefix=*)
@ -2881,6 +2884,20 @@ case $_backend in
;;
esac
#
# Enable Event Recorder only for backends that support it
#
case $_backend in
sdl)
if test "$_eventrec" = auto ; then
_eventrec=yes
fi
;;
*)
_eventrec=no
;;
esac
#
# Disable savegame timestamp support for backends which don't have a reliable real time clock
#
@ -3797,10 +3814,12 @@ fi
define_in_config_if_yes $_nasm 'USE_NASM'
#
# Enable vkeybd / keymapper
# Enable vkeybd / keymapper / event recorder
#
define_in_config_if_yes $_vkeybd 'ENABLE_VKEYBD'
define_in_config_if_yes $_keymapper 'ENABLE_KEYMAPPER'
define_in_config_if_yes $_eventrec 'ENABLE_EVENTRECORDER'
add_line_to_config_mk_if_yes $_eventrec "ENABLE_EVENTRECORDER = 1"
# Check whether to build translation support
#
@ -3946,7 +3965,11 @@ if test "$_vkeybd" = yes ; then
fi
if test "$_keymapper" = yes ; then
echo ", keymapper"
echo_n ", keymapper"
fi
if test "$_eventrec" = yes ; then
echo ", event recorder"
else
echo
fi

View file

@ -29,7 +29,7 @@
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/EventRecorder.h"
#include "engines/advancedDetector.h"
#include "engines/obsolete.h"
@ -301,6 +301,7 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine)
return Common::kUserCanceled;
debug(2, "Running %s", gameDescriptor.description().c_str());
initSubSystems(agdDesc);
if (!createInstance(syst, engine, agdDesc))
return Common::kNoGameDataFoundError;
else
@ -606,3 +607,9 @@ AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, con
_maxScanDepth = 1;
_directoryGlobs = NULL;
}
void AdvancedMetaEngine::initSubSystems(const ADGameDescription *gameDesc) const {
if (gameDesc) {
g_eventRec.processGameDescription(gameDesc);
}
}

View file

@ -280,6 +280,9 @@ protected:
return 0;
}
private:
void initSubSystems(const ADGameDescription *gameDesc) const;
protected:
/**
* Detect games in specified directory.

View file

@ -25,7 +25,6 @@
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "common/EventRecorder.h"
#include "common/file.h"
#include "common/fs.h"
#include "engines/advancedDetector.h"

View file

@ -23,7 +23,6 @@
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/EventRecorder.h"
#include "common/file.h"
#include "common/func.h"
#include "common/system.h"

View file

@ -41,6 +41,7 @@
#include "sword25/gfx/renderobjectmanager.h"
#include "common/system.h"
#include "graphics/thumbnail.h"
namespace Sword25 {
@ -509,60 +510,4 @@ void RenderedImage::checkForTransparency() {
}
}
/**
* Scales a passed surface, creating a new surface with the result
* @param srcImage Source image to scale
* @param scaleFactor Scale amount. Must be between 0 and 1.0 (but not zero)
* @remarks Caller is responsible for freeing the returned surface
*/
Graphics::Surface *RenderedImage::scale(const Graphics::Surface &srcImage, int xSize, int ySize) {
Graphics::Surface *s = new Graphics::Surface();
s->create(xSize, ySize, srcImage.format);
int *horizUsage = scaleLine(xSize, srcImage.w);
int *vertUsage = scaleLine(ySize, srcImage.h);
// Loop to create scaled version
for (int yp = 0; yp < ySize; ++yp) {
const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
byte *destP = (byte *)s->getBasePtr(0, yp);
for (int xp = 0; xp < xSize; ++xp) {
const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel);
for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) {
*destP++ = *tempSrcP++;
}
}
}
// Delete arrays and return surface
delete[] horizUsage;
delete[] vertUsage;
return s;
}
/**
* Returns an array indicating which pixels of a source image horizontally or vertically get
* included in a scaled image
*/
int *RenderedImage::scaleLine(int size, int srcSize) {
int scale = 100 * size / srcSize;
assert(scale > 0);
int *v = new int[size];
Common::fill(v, &v[size], 0);
int distCtr = 0;
int *destP = v;
for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
distCtr += scale;
while (distCtr >= 100) {
assert(destP < &v[size]);
*destP++ = distIndex;
distCtr -= 100;
}
}
return v;
}
} // End of namespace Sword25

View file

@ -104,8 +104,6 @@ public:
return true;
}
static Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize);
void setIsTransparent(bool isTransparent) { _isTransparent = isTransparent; }
virtual bool isSolid() const { return !_isTransparent; }
@ -119,7 +117,6 @@ private:
Graphics::Surface *_backSurface;
void checkForTransparency();
static int *scaleLine(int size, int srcSize);
};
} // End of namespace Sword25

View file

@ -26,7 +26,6 @@
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "common/EventRecorder.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/tokenizer.h"

View file

@ -48,6 +48,9 @@ bool CursorManager::isVisible() {
bool CursorManager::showMouse(bool visible) {
if (_cursorStack.empty())
return false;
if (_locked) {
return false;
}
_cursorStack.top()->_visible = visible;
@ -225,6 +228,10 @@ void CursorManager::replaceCursorPalette(const byte *colors, uint start, uint nu
}
}
void CursorManager::lock(bool locked) {
_locked = locked;
}
CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
#ifdef USE_RGB_COLOR
if (!format)

View file

@ -160,12 +160,15 @@ public:
*/
void replaceCursorPalette(const byte *colors, uint start, uint num);
void lock(bool locked);
private:
friend class Common::Singleton<SingletonBaseType>;
// Even though this is basically the default constructor we implement it
// ourselves, so it is private and thus there is no way to create this class
// except from the Singleton code.
CursorManager() {}
CursorManager() {
_locked = false;
}
~CursorManager();
struct Cursor {
@ -198,6 +201,7 @@ private:
};
Common::Stack<Cursor *> _cursorStack;
Common::Stack<Palette *> _cursorPaletteStack;
bool _locked;
};
} // End of namespace Graphics

View file

@ -89,4 +89,10 @@ extern bool createThumbnailFromScreen(Graphics::Surface *surf);
*/
extern bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h, const uint8 *palette);
/**
* Downscale screenshot to thumbnale size.
*
*/
extern bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in);
#endif

View file

@ -134,7 +134,7 @@ static bool grabScreen565(Graphics::Surface *surf) {
return true;
}
static bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
uint16 width = in.w;
uint16 inHeight = in.h;
@ -206,7 +206,7 @@ static bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
return true;
}
bool createThumbnailFromScreen(Graphics::Surface* surf) {
bool createThumbnailFromScreen(Graphics::Surface *surf) {
assert(surf);
Graphics::Surface screen;
@ -236,3 +236,31 @@ bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h,
return createThumbnail(*surf, screen);
}
// this is somewhat awkward, but createScreenShot should logically be in graphics,
// but moving other functions in this file into that namespace breaks several engines
namespace Graphics {
bool createScreenShot(Graphics::Surface &surf) {
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
//convert surface to 2 bytes pixel format to avoid problems with palette saving and loading
if ((screenFormat.bytesPerPixel == 1) || (screenFormat.bytesPerPixel == 2)) {
return grabScreen565(&surf);
} else {
Graphics::Surface *screen = g_system->lockScreen();
if (!screen) {
return false;
}
surf.create(screen->w, screen->h, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
for (uint y = 0; y < screen->h; ++y) {
for (uint x = 0; x < screen->w; ++x) {
byte r = 0, g = 0, b = 0, a = 0;
uint32 col = READ_UINT32(screen->getBasePtr(x, y));
screenFormat.colorToARGB(col, a, r, g, b);
((uint32 *)surf.pixels)[y * surf.w + x] = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b);
}
}
g_system->unlockScreen();
return true;
}
}
} // End of namespace Graphics

View file

@ -23,6 +23,7 @@
#include "graphics/scaler.h"
#include "graphics/colormasks.h"
#include "common/endian.h"
#include "common/algorithm.h"
#include "common/system.h"
#include "common/stream.h"
#include "common/textconsole.h"
@ -143,7 +144,6 @@ Graphics::Surface *loadThumbnail(Common::SeekableReadStream &in) {
assert(0);
}
}
return to;
}
@ -216,4 +216,55 @@ bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb) {
return true;
}
/**
* Returns an array indicating which pixels of a source image horizontally or vertically get
* included in a scaled image
*/
int *scaleLine(int size, int srcSize) {
int scale = 100 * size / srcSize;
assert(scale > 0);
int *v = new int[size];
Common::fill(v, &v[size], 0);
int distCtr = 0;
int *destP = v;
for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
distCtr += scale;
while (distCtr >= 100) {
assert(destP < &v[size]);
*destP++ = distIndex;
distCtr -= 100;
}
}
return v;
}
Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize) {
Graphics::Surface *s = new Graphics::Surface();
s->create(xSize, ySize, srcImage.format);
int *horizUsage = scaleLine(xSize, srcImage.w);
int *vertUsage = scaleLine(ySize, srcImage.h);
// Loop to create scaled version
for (int yp = 0; yp < ySize; ++yp) {
const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
byte *destP = (byte *)s->getBasePtr(0, yp);
for (int xp = 0; xp < xSize; ++xp) {
const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel);
for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) {
*destP++ = *tempSrcP++;
}
}
}
// Delete arrays and return surface
delete[] horizUsage;
delete[] vertUsage;
return s;
}
} // End of namespace Graphics

View file

@ -64,6 +64,24 @@ bool saveThumbnail(Common::WriteStream &out);
*/
bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb);
/**
* Grabs framebuffer into surface
*
* @param surf a surface
* @return false if a error occurred
*/
bool createScreenShot(Graphics::Surface &surf);
/**
* Scales a passed surface, creating a new surface with the result
* @param srcImage Source image to scale
* @param xSize New surface width
* @param ySize New surface height
* @remarks Caller is responsible for freeing the returned surface
*/
Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize);
} // End of namespace Graphics
#endif

699
gui/EventRecorder.cpp Normal file
View file

@ -0,0 +1,699 @@
/* 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 "gui/EventRecorder.h"
namespace Common {
DECLARE_SINGLETON(GUI::EventRecorder);
}
#ifdef ENABLE_EVENTRECORDER
#include "common/debug-channels.h"
#include "backends/timer/sdl/sdl-timer.h"
#include "backends/mixer/sdl/sdl-mixer.h"
#include "common/config-manager.h"
#include "common/md5.h"
#include "gui/gui-manager.h"
#include "gui/widget.h"
#include "gui/onscreendialog.h"
#include "common/random.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "graphics/scaler.h"
namespace GUI {
const int kMaxRecordsNames = 0x64;
const int kDefaultScreenshotPeriod = 60000;
const int kDefaultBPP = 2;
uint32 readTime(Common::ReadStream *inFile) {
uint32 d = inFile->readByte();
if (d == 0xff) {
d = inFile->readUint32LE();
}
return d;
}
void writeTime(Common::WriteStream *outFile, uint32 d) {
//Simple RLE compression
if (d >= 0xff) {
outFile->writeByte(0xff);
outFile->writeUint32LE(d);
} else {
outFile->writeByte(d);
}
}
EventRecorder::EventRecorder() {
_timerManager = NULL;
_recordMode = kPassthrough;
_fakeMixerManager = NULL;
_initialized = false;
_needRedraw = false;
_fastPlayback = false;
DebugMan.addDebugChannel(kDebugLevelEventRec, "EventRec", "Event recorder debug level");
}
EventRecorder::~EventRecorder() {
if (_timerManager != NULL) {
delete _timerManager;
}
}
void EventRecorder::deinit() {
if (!_initialized) {
return;
}
setFileHeader();
_needRedraw = false;
_initialized = false;
_recordMode = kPassthrough;
delete _fakeMixerManager;
_fakeMixerManager = NULL;
controlPanel->close();
delete controlPanel;
debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
_recordMode = kPassthrough;
_playbackFile->close();
delete _playbackFile;
switchMixer();
switchTimerManagers();
DebugMan.disableDebugChannel("EventRec");
}
void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
if (!_initialized) {
return;
}
if (skipRecord) {
millis = _fakeTimer;
return;
}
if (_recordMode == kRecorderPlaybackPause) {
millis = _fakeTimer;
}
uint32 millisDelay;
Common::RecorderEvent timerEvent;
switch (_recordMode) {
case kRecorderRecord:
updateSubsystems();
millisDelay = millis - _lastMillis;
_lastMillis = millis;
_fakeTimer += millisDelay;
controlPanel->setReplayedTime(_fakeTimer);
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_playbackFile->writeEvent(timerEvent);
takeScreenshot();
_timerManager->handler();
break;
case kRecorderPlayback:
updateSubsystems();
if (_nextEvent.recordedtype == Common::kRecorderEventTypeTimer) {
_fakeTimer = _nextEvent.time;
_nextEvent = _playbackFile->getNextEvent();
_timerManager->handler();
} else {
if (_nextEvent.type == Common::EVENT_RTL) {
error("playback:action=stopplayback");
} else {
uint32 seconds = _fakeTimer / 1000;
Common::String screenTime = Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60);
error("playback:action=error reason=\"synchronization error\" time = %s", screenTime.c_str());
}
}
millis = _fakeTimer;
controlPanel->setReplayedTime(_fakeTimer);
break;
case kRecorderPlaybackPause:
millis = _fakeTimer;
break;
default:
break;
}
}
bool EventRecorder::processDelayMillis() {
return _fastPlayback;
}
void EventRecorder::checkForKeyCode(const Common::Event &event) {
if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.flags & Common::KBD_CTRL) && (event.kbd.keycode == Common::KEYCODE_p) && (!event.synthetic)) {
togglePause();
}
}
bool EventRecorder::pollEvent(Common::Event &ev) {
if ((_recordMode != kRecorderPlayback) || !_initialized)
return false;
if ((_nextEvent.recordedtype == Common::kRecorderEventTypeTimer) || (_nextEvent.type == Common::EVENT_INVALID)) {
return false;
}
switch (_nextEvent.type) {
case Common::EVENT_MOUSEMOVE:
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONDOWN:
case Common::EVENT_RBUTTONUP:
case Common::EVENT_WHEELUP:
case Common::EVENT_WHEELDOWN:
g_system->warpMouse(_nextEvent.mouse.x, _nextEvent.mouse.y);
break;
default:
break;
}
ev = _nextEvent;
_nextEvent = _playbackFile->getNextEvent();
return true;
}
void EventRecorder::switchFastMode() {
if (_recordMode == kRecorderPlaybackPause) {
_fastPlayback = !_fastPlayback;
}
}
void EventRecorder::togglePause() {
RecordMode oldState;
switch (_recordMode) {
case kRecorderPlayback:
case kRecorderRecord:
oldState = _recordMode;
_recordMode = kRecorderPlaybackPause;
controlPanel->runModal();
_recordMode = oldState;
_initialized = true;
break;
case kRecorderPlaybackPause:
controlPanel->close();
break;
default:
break;
}
}
void EventRecorder::RegisterEventSource() {
g_system->getEventManager()->getEventDispatcher()->registerMapper(this, false);
}
uint32 EventRecorder::getRandomSeed(const Common::String &name) {
uint32 result = g_system->getMillis();
if (_recordMode == kRecorderRecord) {
_playbackFile->getHeader().randomSourceRecords[name] = result;
} else if (_recordMode == kRecorderPlayback) {
result = _playbackFile->getHeader().randomSourceRecords[name];
}
return result;
}
Common::String EventRecorder::generateRecordFileName(const Common::String &target) {
Common::String pattern(target+".r??");
Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
for (int i = 0; i < kMaxRecordsNames; ++i) {
Common::String recordName = Common::String::format("%s.r%02d", target.c_str(), i);
if (find(files.begin(), files.end(), recordName) != files.end()) {
continue;
}
return recordName;
}
return "";
}
void EventRecorder::init(Common::String recordFileName, RecordMode mode) {
_fakeMixerManager = new NullSdlMixerManager();
_fakeMixerManager->init();
_fakeMixerManager->suspendAudio();
_fakeTimer = 0;
_lastMillis = g_system->getMillis();
_playbackFile = new Common::PlaybackFile();
_lastScreenshotTime = 0;
_recordMode = mode;
_needcontinueGame = false;
if (ConfMan.hasKey("disable_display")) {
DebugMan.enableDebugChannel("EventRec");
gDebugLevel = 1;
}
if (_recordMode == kRecorderPlayback) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Load file\" filename=%s", recordFileName.c_str());
}
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
_screenshotPeriod = ConfMan.getInt("screenshot_period");
if (_screenshotPeriod == 0) {
_screenshotPeriod = kDefaultScreenshotPeriod;
}
if (!openRecordFile(recordFileName)) {
deinit();
error("playback:action=error reason=\"Record file loading error\"");
return;
}
if (_recordMode != kPassthrough) {
controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
}
if (_recordMode == kRecorderPlayback) {
applyPlaybackSettings();
_nextEvent = _playbackFile->getNextEvent();
}
if (_recordMode == kRecorderRecord) {
getConfig();
}
switchMixer();
switchTimerManagers();
_needRedraw = true;
_initialized = true;
}
/**
* Opens or creates file depend of recording mode.
*
*@param id of recording or playing back game
*@return true in case of success, false in case of error
*
*/
bool EventRecorder::openRecordFile(const Common::String &fileName) {
bool result;
switch (_recordMode) {
case kRecorderRecord:
return _playbackFile->openWrite(fileName);
case kRecorderPlayback:
_recordMode = kPassthrough;
result = _playbackFile->openRead(fileName);
_recordMode = kRecorderPlayback;
return result;
default:
return false;
}
return true;
}
bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) {
if ((gameDesc == NULL) && (_playbackFile->getHeader().hashRecords.size() != 0)) {
warning("Engine doesn't contain description table");
return false;
}
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
if (_playbackFile->getHeader().hashRecords.find(fileDesc->fileName) == _playbackFile->getHeader().hashRecords.end()) {
warning("MD5 hash for file %s not found in record file", fileDesc->fileName);
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=\"\" result=different", fileDesc->fileName, fileDesc->md5);
return false;
}
if (_playbackFile->getHeader().hashRecords[fileDesc->fileName] != fileDesc->md5) {
warning("Incorrect version of game file %s. Stored MD5 is %s. MD5 of loaded game is %s", fileDesc->fileName, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str(), fileDesc->md5);
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=different", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
return false;
}
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=equal", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
}
return true;
}
void EventRecorder::registerMixerManager(SdlMixerManager *mixerManager) {
_realMixerManager = mixerManager;
}
void EventRecorder::switchMixer() {
if (_recordMode == kPassthrough) {
_realMixerManager->resumeAudio();
} else {
_realMixerManager->suspendAudio();
_fakeMixerManager->resumeAudio();
}
}
SdlMixerManager *EventRecorder::getMixerManager() {
if (_recordMode == kPassthrough) {
return _realMixerManager;
} else {
return _fakeMixerManager;
}
}
void EventRecorder::getConfigFromDomain(Common::ConfigManager::Domain *domain) {
for (Common::ConfigManager::Domain::iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
_playbackFile->getHeader().settingsRecords[entry->_key] = entry->_value;
}
}
void EventRecorder::getConfig() {
getConfigFromDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
getConfigFromDomain(ConfMan.getActiveDomain());
_playbackFile->getHeader().settingsRecords["save_slot"] = ConfMan.get("save_slot");
}
void EventRecorder::applyPlaybackSettings() {
for (Common::StringMap::iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) {
Common::String currentValue = ConfMan.get(i->_key);
if (currentValue != i->_value) {
ConfMan.set(i->_key, i->_value, ConfMan.kTransientDomain);
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=different", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
} else {
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=equal", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
}
}
removeDifferentEntriesInDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
removeDifferentEntriesInDomain(ConfMan.getActiveDomain());
}
void EventRecorder::removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain) {
for (Common::ConfigManager::Domain::iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
if (_playbackFile->getHeader().settingsRecords.find(entry->_key) == _playbackFile->getHeader().settingsRecords.end()) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" checksettings:key=%s storedvalue=%s currentvalue="" result=different", entry->_key.c_str(), entry->_value.c_str());
domain->erase(entry->_key);
}
}
}
DefaultTimerManager *EventRecorder::getTimerManager() {
return _timerManager;
}
void EventRecorder::registerTimerManager(DefaultTimerManager *timerManager) {
_timerManager = timerManager;
}
void EventRecorder::switchTimerManagers() {
delete _timerManager;
if (_recordMode == kPassthrough) {
_timerManager = new SdlTimerManager();
} else {
_timerManager = new DefaultTimerManager();
}
}
void EventRecorder::updateSubsystems() {
if (_recordMode == kPassthrough) {
return;
}
RecordMode oldRecordMode = _recordMode;
_recordMode = kPassthrough;
_fakeMixerManager->update();
_recordMode = oldRecordMode;
}
Common::List<Common::Event> EventRecorder::mapEvent(const Common::Event &ev, Common::EventSource *source) {
if ((!_initialized) && (_recordMode != kRecorderPlaybackPause)) {
return DefaultEventMapper::mapEvent(ev, source);
}
checkForKeyCode(ev);
Common::Event evt = ev;
evt.mouse.x = evt.mouse.x * (g_system->getOverlayWidth() / g_system->getWidth());
evt.mouse.y = evt.mouse.y * (g_system->getOverlayHeight() / g_system->getHeight());
switch (_recordMode) {
case kRecorderPlayback:
if (ev.synthetic != true) {
return Common::List<Common::Event>();
}
return Common::DefaultEventMapper::mapEvent(ev, source);
break;
case kRecorderRecord:
g_gui.processEvent(evt, controlPanel);
if (((evt.type == Common::EVENT_LBUTTONDOWN) || (evt.type == Common::EVENT_LBUTTONUP) || (evt.type == Common::EVENT_MOUSEMOVE)) && controlPanel->isMouseOver()) {
return Common::List<Common::Event>();
} else {
Common::RecorderEvent e;
memcpy(&e, &ev, sizeof(ev));
e.recordedtype = Common::kRecorderEventTypeNormal;
e.time = _fakeTimer;
_playbackFile->writeEvent(e);
return DefaultEventMapper::mapEvent(ev, source);
}
break;
case kRecorderPlaybackPause: {
Common::Event dialogEvent;
if (controlPanel->isEditDlgVisible()) {
dialogEvent = ev;
} else {
dialogEvent = evt;
}
g_gui.processEvent(dialogEvent, controlPanel->getActiveDlg());
if (((dialogEvent.type == Common::EVENT_LBUTTONDOWN) || (dialogEvent.type == Common::EVENT_LBUTTONUP) || (dialogEvent.type == Common::EVENT_MOUSEMOVE)) && controlPanel->isMouseOver()) {
return Common::List<Common::Event>();
}
return Common::DefaultEventMapper::mapEvent(dialogEvent, source);
}
break;
default:
return Common::DefaultEventMapper::mapEvent(ev, source);
}
}
void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) {
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
if (fileDesc->md5 != NULL) {
_playbackFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
}
}
}
void EventRecorder::processGameDescription(const ADGameDescription *desc) {
if (_recordMode == kRecorderRecord) {
setGameMd5(desc);
}
if ((_recordMode == kRecorderPlayback) && !checkGameHash(desc)) {
deinit();
error("playback:action=error reason=\"\"");
}
}
void EventRecorder::deleteRecord(const Common::String& fileName) {
g_system->getSavefileManager()->removeSavefile(fileName);
}
void EventRecorder::takeScreenshot() {
if ((_fakeTimer - _lastScreenshotTime) > _screenshotPeriod) {
Graphics::Surface screen;
uint8 md5[16];
if (grabScreenAndComputeMD5(screen, md5)) {
_lastScreenshotTime = _fakeTimer;
_playbackFile->saveScreenShot(screen, md5);
screen.free();
}
}
}
bool EventRecorder::grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]) {
if (!createScreenShot(screen)) {
warning("Can't save screenshot");
return false;
}
Common::MemoryReadStream bitmapStream((const byte*)screen.pixels, screen.w * screen.h * screen.format.bytesPerPixel);
computeStreamMD5(bitmapStream, md5);
return true;
}
Common::SeekableReadStream *EventRecorder::processSaveStream(const Common::String &fileName) {
Common::InSaveFile *saveFile;
switch (_recordMode) {
case kRecorderPlayback:
debugC(1, kDebugLevelEventRec, "playback:action=\"Process save file\" filename=%s len=%d", fileName.c_str(), _playbackFile->getHeader().saveFiles[fileName].size);
return new Common::MemoryReadStream(_playbackFile->getHeader().saveFiles[fileName].buffer, _playbackFile->getHeader().saveFiles[fileName].size);
case kRecorderRecord:
saveFile = _realSaveManager->openForLoading(fileName);
if (saveFile != NULL) {
_playbackFile->addSaveFile(fileName, saveFile);
saveFile->seek(0);
}
return saveFile;
default:
return NULL;
break;
}
}
Common::SaveFileManager *EventRecorder::getSaveManager(Common::SaveFileManager *realSaveManager) {
_realSaveManager = realSaveManager;
if (_recordMode != kPassthrough) {
return &_fakeSaveManager;
} else {
return realSaveManager;
}
}
void EventRecorder::preDrawOverlayGui() {
if ((_initialized) || (_needRedraw)) {
RecordMode oldMode = _recordMode;
_recordMode = kPassthrough;
g_system->showOverlay();
g_gui.theme()->clearAll();
g_gui.theme()->openDialog(true, GUI::ThemeEngine::kShadingNone);
controlPanel->drawDialog();
g_gui.theme()->finishBuffering();
g_gui.theme()->updateScreen();
_recordMode = oldMode;
}
}
void EventRecorder::postDrawOverlayGui() {
if ((_initialized) || (_needRedraw)) {
RecordMode oldMode = _recordMode;
_recordMode = kPassthrough;
g_system->hideOverlay();
_recordMode = oldMode;
}
}
Common::StringArray EventRecorder::listSaveFiles(const Common::String &pattern) {
if (_recordMode == kRecorderPlayback) {
Common::StringArray result;
for (Common::HashMap<Common::String, Common::PlaybackFile::SaveFileBuffer>::iterator i = _playbackFile->getHeader().saveFiles.begin(); i != _playbackFile->getHeader().saveFiles.end(); ++i) {
if (i->_key.matchString(pattern, false, true)) {
result.push_back(i->_key);
}
}
return result;
} else {
return _realSaveManager->listSavefiles(pattern);
}
}
void EventRecorder::setFileHeader() {
if (_recordMode != kRecorderRecord) {
return;
}
TimeDate t;
const EnginePlugin *plugin = 0;
GameDescriptor desc = EngineMan.findGame(ConfMan.getActiveDomainName(), &plugin);
g_system->getTimeAndDate(t);
if (_author.empty()) {
setAuthor("Unknown Author");
}
if (_name.empty()) {
g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description());
}
_playbackFile->getHeader().author = _author;
_playbackFile->getHeader().notes = _desc;
_playbackFile->getHeader().name = _name;
}
SDL_Surface *EventRecorder::getSurface(int width, int height) {
SDL_Surface *surface = new SDL_Surface();
surface->format = new SDL_PixelFormat();
surface->flags = 0;
surface->format->palette = NULL;
surface->format->BitsPerPixel = 16;
surface->format->BytesPerPixel = 2;
surface->format->Rloss = 3;
surface->format->Gloss = 2;
surface->format->Bloss = 3;
surface->format->Aloss = 8;
surface->format->Rshift = 11;
surface->format->Gshift = 5;
surface->format->Bshift = 0;
surface->format->Ashift = 0;
surface->format->Rmask = 63488;
surface->format->Gmask = 2016;
surface->format->Bmask = 31;
surface->format->Amask = 0;
surface->format->colorkey = 0;
surface->format->alpha = 255;
surface->w = width;
surface->h = height;
surface->pitch = width * 2;
surface->pixels = (char *)malloc(surface->pitch * surface->h);
surface->offset = 0;
surface->hwdata = NULL;
surface->clip_rect.x = 0;
surface->clip_rect.y = 0;
surface->clip_rect.w = width;
surface->clip_rect.h = height;
surface->unused1 = 0;
surface->locked = 0;
surface->map = NULL;
surface->format_version = 4;
surface->refcount = 1;
return surface;
}
bool EventRecorder::switchMode() {
const Common::String gameId = ConfMan.get("gameid");
const EnginePlugin *plugin = 0;
EngineMan.findGame(gameId, &plugin);
bool metaInfoSupport = (*plugin)->hasFeature(MetaEngine::kSavesSupportMetaInfo);
bool featuresSupport = metaInfoSupport &&
g_engine->canSaveGameStateCurrently() &&
(*plugin)->hasFeature(MetaEngine::kSupportsListSaves) &&
(*plugin)->hasFeature(MetaEngine::kSupportsDeleteSave);
if (!featuresSupport) {
return false;
}
int emptySlot = 1;
SaveStateList saveList = (*plugin)->listSaves(gameId.c_str());
for (SaveStateList::const_iterator x = saveList.begin(); x != saveList.end(); ++x) {
int saveSlot = x->getSaveSlot();
if (saveSlot == 0) {
continue;
}
if (emptySlot != saveSlot) {
break;
}
emptySlot++;
}
Common::String saveName;
if (emptySlot >= 0) {
saveName = Common::String::format("Save %d", emptySlot + 1);
Common::Error status = g_engine->saveGameState(emptySlot, saveName);
if (status.getCode() == Common::kNoError) {
Common::Event eventRTL;
eventRTL.type = Common::EVENT_RTL;
g_system->getEventManager()->pushEvent(eventRTL);
}
}
ConfMan.set("record_mode", "", Common::ConfigManager::kTransientDomain);
ConfMan.setInt("save_slot", emptySlot, Common::ConfigManager::kTransientDomain);
_needcontinueGame = true;
return true;
}
bool EventRecorder::checkForContinueGame() {
bool result = _needcontinueGame;
_needcontinueGame = false;
return result;
}
void EventRecorder::deleteTemporarySave() {
if (_temporarySlot == -1) return;
const Common::String gameId = ConfMan.get("gameid");
const EnginePlugin *plugin = 0;
EngineMan.findGame(gameId, &plugin);
(*plugin)->removeSaveState(gameId.c_str(), _temporarySlot);
_temporarySlot = -1;
}
} // End of namespace GUI
#endif // ENABLE_EVENTRECORDER

293
gui/EventRecorder.h Normal file
View file

@ -0,0 +1,293 @@
/* 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_EVENTRECORDER_H
#define GUI_EVENTRECORDER_H
#include "common/system.h"
#include "common/events.h"
#include "common/savefile.h"
#include "common/singleton.h"
#include "engines/advancedDetector.h"
#ifdef ENABLE_EVENTRECORDER
#include "common/mutex.h"
#include "common/array.h"
#include "common/memstream.h"
#include "backends/keymapper/keymapper.h"
#include "backends/mixer/sdl/sdl-mixer.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "backends/timer/sdl/sdl-timer.h"
#include "common/config-manager.h"
#include "common/recorderfile.h"
#include "backends/saves/recorder/recorder-saves.h"
#include "backends/mixer/nullmixer/nullsdl-mixer.h"
#include "backends/saves/default/default-saves.h"
#define g_eventRec (GUI::EventRecorder::instance())
namespace GUI {
class OnScreenDialog;
}
namespace GUI {
class RandomSource;
class SeekableReadStream;
class WriteStream;
/**
* Our generic event recorder.
*
* TODO: Add more documentation.
*/
class EventRecorder : private Common::EventSource, public Common::Singleton<EventRecorder>, private Common::DefaultEventMapper {
friend class Common::Singleton<SingletonBaseType>;
EventRecorder();
~EventRecorder();
public:
/** Specify operation mode of Event Recorder */
enum RecordMode {
kPassthrough = 0, /**< kPassthrough, do nothing */
kRecorderRecord = 1, /**< kRecorderRecord, do the recording */
kRecorderPlayback = 2, /**< kRecorderPlayback, playback existing recording */
kRecorderPlaybackPause = 3 /**< kRecordetPlaybackPause, interal state when user pauses the playback */
};
void init(Common::String recordFileName, RecordMode mode);
void deinit();
bool processDelayMillis();
uint32 getRandomSeed(const Common::String &name);
void processMillis(uint32 &millis, bool skipRecord);
bool processAudio(uint32 &samples, bool paused);
void processGameDescription(const ADGameDescription *desc);
Common::SeekableReadStream *processSaveStream(const Common::String & fileName);
/** Hooks for intercepting into GUI processing, so required events could be shoot
* or filtered out */
void preDrawOverlayGui();
void postDrawOverlayGui();
/** Set recording author
*
* @see getAuthor
*/
void setAuthor(const Common::String &author) {
_author = author;
}
/** Set recording notes
*
* @see getNotes
*/
void setNotes(const Common::String &desc){
_desc = desc;
}
/** Set descriptive name of the recording
*
* @see getName
*/
void setName(const Common::String &name) {
_name = name;
}
/** Get recording author
*
* @see getAuthor
*/
const Common::String getAuthor() {
return _author;
}
/** Get recording notes
*
* @see setNotes
*/
const Common::String getNotes() {
return _desc;
}
/** Get recording name
*
* @see setName
*/
const Common::String getName() {
return _name;
}
void setRedraw(bool redraw) {
_needRedraw = redraw;
}
void registerMixerManager(SdlMixerManager *mixerManager);
void registerTimerManager(DefaultTimerManager *timerManager);
SdlMixerManager *getMixerManager();
DefaultTimerManager *getTimerManager();
void deleteRecord(const Common::String& fileName);
bool checkForContinueGame();
void suspendRecording() {
_savedState = _initialized;
_initialized = false;
}
void resumeRecording() {
_initialized = _savedState;
}
Common::StringArray listSaveFiles(const Common::String &pattern);
Common::String generateRecordFileName(const Common::String &target);
Common::SaveFileManager *getSaveManager(Common::SaveFileManager *realSaveManager);
SDL_Surface *getSurface(int width, int height);
void RegisterEventSource();
/** Retrieve game screenshot and compute its checksum for comparison */
bool grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]);
void updateSubsystems();
bool switchMode();
void switchFastMode();
private:
virtual Common::List<Common::Event> mapEvent(const Common::Event &ev, Common::EventSource *source);
bool notifyPoll();
bool pollEvent(Common::Event &ev);
bool _initialized;
volatile uint32 _fakeTimer;
bool _savedState;
bool _needcontinueGame;
int _temporarySlot;
Common::String _author;
Common::String _desc;
Common::String _name;
Common::SaveFileManager *_realSaveManager;
SdlMixerManager *_realMixerManager;
DefaultTimerManager *_timerManager;
RecorderSaveFileManager _fakeSaveManager;
NullSdlMixerManager *_fakeMixerManager;
GUI::OnScreenDialog *controlPanel;
Common::RecorderEvent _nextEvent;
void setFileHeader();
void setGameMd5(const ADGameDescription *gameDesc);
void getConfig();
void getConfigFromDomain(Common::ConfigManager::Domain *domain);
void removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain);
void applyPlaybackSettings();
void switchMixer();
void switchTimerManagers();
void togglePause();
void takeScreenshot();
bool openRecordFile(const Common::String &fileName);
bool checkGameHash(const ADGameDescription *desc);
void checkForKeyCode(const Common::Event &event);
bool allowMapping() const { return false; }
volatile uint32 _lastMillis;
uint32 _lastScreenshotTime;
uint32 _screenshotPeriod;
Common::PlaybackFile *_playbackFile;
void saveScreenShot();
void checkRecordedMD5();
void deleteTemporarySave();
volatile RecordMode _recordMode;
Common::String _recordFileName;
bool _fastPlayback;
bool _needRedraw;
};
} // End of namespace GUI
#else
#ifdef SDL_BACKEND
#include "backends/timer/default/default-timer.h"
#include "backends/mixer/sdl/sdl-mixer.h"
#endif
#define g_eventRec (GUI::EventRecorder::instance())
namespace GUI {
class EventRecorder : private Common::EventSource, public Common::Singleton<EventRecorder>, private Common::DefaultEventMapper {
friend class Common::Singleton<SingletonBaseType>;
public:
EventRecorder() {
#ifdef SDL_BACKEND
_timerManager = NULL;
_realMixerManager = NULL;
#endif
}
~EventRecorder() {}
bool pollEvent(Common::Event &ev) { return false; }
void RegisterEventSource() {}
void deinit() {}
void suspendRecording() {}
void resumeRecording() {}
void preDrawOverlayGui() {}
void postDrawOverlayGui() {}
void processGameDescription(const ADGameDescription *desc) {}
void updateSubsystems() {}
uint32 getRandomSeed(const Common::String &name) { return g_system->getMillis(); }
Common::SaveFileManager *getSaveManager(Common::SaveFileManager *realSaveManager) { return realSaveManager; }
#ifdef SDL_BACKEND
private:
DefaultTimerManager *_timerManager;
SdlMixerManager *_realMixerManager;
public:
DefaultTimerManager *getTimerManager() { return _timerManager; }
void registerTimerManager(DefaultTimerManager *timerManager) { _timerManager = timerManager; }
SdlMixerManager *getMixerManager() { return _realMixerManager; }
void registerMixerManager(SdlMixerManager *mixerManager) { _realMixerManager = mixerManager; }
void processMillis(uint32 &millis, bool skipRecord) {}
bool processDelayMillis() { return false; }
#endif
};
} // namespace GUI
#endif // ENABLE_EVENTRECORDER
#endif

View file

@ -50,6 +50,14 @@ const char * const ThemeEngine::kImageEraser = "eraser.bmp";
const char * const ThemeEngine::kImageDelbtn = "delbtn.bmp";
const char * const ThemeEngine::kImageList = "list.bmp";
const char * const ThemeEngine::kImageGrid = "grid.bmp";
const char * const ThemeEngine::kImageStopbtn = "stopbtn.bmp";
const char * const ThemeEngine::kImageEditbtn = "editbtn.bmp";
const char * const ThemeEngine::kImageSwitchModebtn = "switchbtn.bmp";
const char * const ThemeEngine::kImageFastReplaybtn = "fastreplay.bmp";
const char * const ThemeEngine::kImageStopSmallbtn = "stopbtn_small.bmp";
const char * const ThemeEngine::kImageEditSmallbtn = "editbtn_small.bmp";
const char * const ThemeEngine::kImageSwitchModeSmallbtn = "switchbtn_small.bmp";
const char * const ThemeEngine::kImageFastReplaySmallbtn = "fastreplay_small.bmp";
struct TextDrawData {
const Graphics::Font *_fontPtr;
@ -465,11 +473,7 @@ void ThemeEngine::enable() {
if (_enabled)
return;
if (_useCursor) {
CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
CursorMan.showMouse(true);
}
showCursor();
_system->showOverlay();
clearAll();
@ -482,10 +486,8 @@ void ThemeEngine::disable() {
_system->hideOverlay();
if (_useCursor) {
CursorMan.popCursorPalette();
CursorMan.popCursor();
}
hideCursor();
_enabled = false;
}
@ -1787,5 +1789,20 @@ Common::String ThemeEngine::getThemeId(const Common::String &filename) {
}
}
void ThemeEngine::showCursor() {
if (_useCursor) {
CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
CursorMan.showMouse(true);
}
}
void ThemeEngine::hideCursor() {
if (_useCursor) {
CursorMan.popCursorPalette();
CursorMan.popCursor();
}
}
} // End of namespace GUI.

View file

@ -234,6 +234,14 @@ public:
static const char *const kImageDelbtn; ///< Delete characters in the predictive dialog
static const char *const kImageList; ///< List image used in save/load chooser selection
static const char *const kImageGrid; ///< Grid image used in save/load chooser selection
static const char *const kImageStopbtn; ///< Stop recording button in recorder onscreen dialog
static const char *const kImageEditbtn; ///< Edit recording metadata in recorder onscreen dialog
static const char *const kImageSwitchModebtn; ///< Switch mode button in recorder onscreen dialog
static const char *const kImageFastReplaybtn; ///< Fast playback mode button in recorder onscreen dialog
static const char *const kImageStopSmallbtn; ///< Stop recording button in recorder onscreen dialog (for 320xY)
static const char *const kImageEditSmallbtn; ///< Edit recording metadata in recorder onscreen dialog (for 320xY)
static const char *const kImageSwitchModeSmallbtn; ///< Switch mode button in recorder onscreen dialog (for 320xY)
static const char *const kImageFastReplaySmallbtn; ///< Fast playback mode button in recorder onscreen dialog (for 320xY)
/**
* Graphics mode enumeration.
@ -275,8 +283,13 @@ public:
void refresh();
void enable();
void showCursor();
void hideCursor();
void disable();
/**
* Query the set up pixel format.
*/

View file

@ -37,6 +37,8 @@ struct Event;
namespace GUI {
class EventRecorder;
class Widget;
// Some "common" commands sent to handleCommand()
@ -47,6 +49,7 @@ enum {
class Dialog : public GuiObject {
friend class GuiManager;
friend class EventRecorder;
friend class Tooltip;
protected:
Widget *_mouseWidget;

87
gui/editrecorddialog.cpp Normal file
View file

@ -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 "editrecorddialog.h"
#include "gui/widgets/edittext.h"
#include "common/translation.h"
namespace GUI {
const Common::String EditRecordDialog::getAuthor() {
return _authorEdit->getEditString();
}
void EditRecordDialog::setAuthor(const Common::String &author) {
_authorEdit->setEditString(author);
}
const Common::String EditRecordDialog::getNotes() {
return _notesEdit->getEditString();
}
void EditRecordDialog::setNotes(const Common::String &desc) {
_notesEdit->setEditString(desc);
}
const Common::String EditRecordDialog::getName() {
return _nameEdit->getEditString();
}
void EditRecordDialog::setName(const Common::String &name) {
_nameEdit->setEditString(name);
}
EditRecordDialog::~EditRecordDialog() {
}
EditRecordDialog::EditRecordDialog(const Common::String author, const Common::String name, const Common::String notes) : Dialog("EditRecordDialog") {
new StaticTextWidget(this,"EditRecordDialog.AuthorLabel",_("Author:"));
new StaticTextWidget(this,"EditRecordDialog.NameLabel",_("Name:"));
new StaticTextWidget(this,"EditRecordDialog.NotesLabel",_("Notes:"));
_authorEdit = new EditTextWidget(this, "EditRecordDialog.AuthorEdit","");
_notesEdit = new EditTextWidget(this, "EditRecordDialog.NotesEdit","");
_nameEdit = new EditTextWidget(this, "EditRecordDialog.NameEdit","");
_authorEdit->setEditString(author);
_notesEdit->setEditString(notes);
_nameEdit->setEditString(name);
new GUI::ButtonWidget(this, "EditRecordDialog.Cancel", _("Cancel"), 0, kCloseCmd);
new GUI::ButtonWidget(this, "EditRecordDialog.OK", _("Ok"), 0, kOKCmd);
}
void EditRecordDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch(cmd) {
case kCloseCmd:
setResult(kCloseCmd);
close();
break;
case kOKCmd:
setResult(kOKCmd);
close();
break;
default:
Dialog::handleCommand(sender, cmd, data);
break;
}
}
}

56
gui/editrecorddialog.h Normal file
View file

@ -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 GUI_EDITRECORDDIALOG_H
#define GUI_EDITRECORDDIALOG_H
#include "gui/dialog.h"
namespace GUI {
class EditTextWidget;
class StaticTextWidget;
class EditRecordDialog : public Dialog {
private:
EditTextWidget *_notesEdit;
EditTextWidget *_nameEdit;
EditTextWidget *_authorEdit;
EditRecordDialog() : Dialog("EditRecordDialog") {};
public:
EditRecordDialog(const Common::String author, const Common::String name, const Common::String notes);
~EditRecordDialog();
const Common::String getAuthor();
const Common::String getNotes();
const Common::String getName();
void setAuthor(const Common::String &author);
void setNotes(const Common::String &desc);
void setName(const Common::String &name);
virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
};
}// End of namespace GUI
#endif

View file

@ -27,6 +27,7 @@
#include "common/rect.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/EventRecorder.h"
#include "backends/keymapper/keymapper.h"
@ -253,12 +254,13 @@ Dialog *GuiManager::getTopDialog() const {
void GuiManager::runLoop() {
Dialog * const activeDialog = getTopDialog();
bool didSaveState = false;
int button;
uint32 time;
if (activeDialog == 0)
return;
// Suspend recording while GUI is shown
g_eventRec.suspendRecording();
if (!_stateIsSaved) {
saveState();
_theme->enable();
@ -296,10 +298,10 @@ void GuiManager::runLoop() {
// _theme->updateScreen();
// _system->updateScreen();
if (lastRedraw + waitTime < _system->getMillis()) {
if (lastRedraw + waitTime < _system->getMillis(true)) {
_theme->updateScreen();
_system->updateScreen();
lastRedraw = _system->getMillis();
lastRedraw = _system->getMillis(true);
}
Common::Event event;
@ -314,72 +316,21 @@ void GuiManager::runLoop() {
if (activeDialog != getTopDialog() && event.type != Common::EVENT_SCREEN_CHANGED)
continue;
Common::Point mouse(event.mouse.x - activeDialog->_x, event.mouse.y - activeDialog->_y);
switch (event.type) {
case Common::EVENT_KEYDOWN:
activeDialog->handleKeyDown(event.kbd);
break;
case Common::EVENT_KEYUP:
activeDialog->handleKeyUp(event.kbd);
break;
case Common::EVENT_MOUSEMOVE:
activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
_lastMousePosition.x = mouse.x;
_lastMousePosition.y = mouse.y;
_lastMousePosition.time = _system->getMillis();
}
processEvent(event, activeDialog);
if (event.type == Common::EVENT_MOUSEMOVE) {
tooltipCheck = true;
break;
// We don't distinguish between mousebuttons (for now at least)
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
button = (event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2);
time = _system->getMillis();
if (_lastClick.count && (time < _lastClick.time + kDoubleClickDelay)
&& ABS(_lastClick.x - event.mouse.x) < 3
&& ABS(_lastClick.y - event.mouse.y) < 3) {
_lastClick.count++;
} else {
_lastClick.x = event.mouse.x;
_lastClick.y = event.mouse.y;
_lastClick.count = 1;
}
_lastClick.time = time;
activeDialog->handleMouseDown(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
button = (event.type == Common::EVENT_LBUTTONUP ? 1 : 2);
activeDialog->handleMouseUp(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_WHEELUP:
activeDialog->handleMouseWheel(mouse.x, mouse.y, -1);
break;
case Common::EVENT_WHEELDOWN:
activeDialog->handleMouseWheel(mouse.x, mouse.y, 1);
break;
case Common::EVENT_SCREEN_CHANGED:
screenChange();
break;
default:
#ifdef ENABLE_KEYMAPPER
activeDialog->handleOtherEvent(event);
#endif
break;
}
if (lastRedraw + waitTime < _system->getMillis()) {
if (lastRedraw + waitTime < _system->getMillis(true)) {
_theme->updateScreen();
_system->updateScreen();
lastRedraw = _system->getMillis();
lastRedraw = _system->getMillis(true);
}
}
if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis()) {
if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis(true)) {
Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)) {
Tooltip *tooltip = new Tooltip();
@ -409,6 +360,9 @@ void GuiManager::runLoop() {
restoreState();
_useStdCursor = false;
}
// Resume recording once GUI is shown
g_eventRec.resumeRecording();
}
#pragma mark -
@ -492,7 +446,7 @@ void GuiManager::setupCursor() {
// very much. We could plug in a different cursor here if we like to.
void GuiManager::animateCursor() {
int time = _system->getMillis();
int time = _system->getMillis(true);
if (time > _cursorAnimateTimer + kCursorAnimateDelay) {
for (int i = 0; i < 15; i++) {
if ((i < 6) || (i > 8)) {
@ -537,4 +491,64 @@ void GuiManager::screenChange() {
_system->updateScreen();
}
void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDialog) {
int button;
uint32 time;
Common::Point mouse(event.mouse.x - activeDialog->_x, event.mouse.y - activeDialog->_y);
switch (event.type) {
case Common::EVENT_KEYDOWN:
activeDialog->handleKeyDown(event.kbd);
break;
case Common::EVENT_KEYUP:
activeDialog->handleKeyUp(event.kbd);
break;
case Common::EVENT_MOUSEMOVE:
activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
_lastMousePosition.x = mouse.x;
_lastMousePosition.y = mouse.y;
_lastMousePosition.time = _system->getMillis(true);
}
break;
// We don't distinguish between mousebuttons (for now at least)
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
button = (event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2);
time = _system->getMillis(true);
if (_lastClick.count && (time < _lastClick.time + kDoubleClickDelay)
&& ABS(_lastClick.x - event.mouse.x) < 3
&& ABS(_lastClick.y - event.mouse.y) < 3) {
_lastClick.count++;
} else {
_lastClick.x = event.mouse.x;
_lastClick.y = event.mouse.y;
_lastClick.count = 1;
}
_lastClick.time = time;
activeDialog->handleMouseDown(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
button = (event.type == Common::EVENT_LBUTTONUP ? 1 : 2);
activeDialog->handleMouseUp(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_WHEELUP:
activeDialog->handleMouseWheel(mouse.x, mouse.y, -1);
break;
case Common::EVENT_WHEELDOWN:
activeDialog->handleMouseWheel(mouse.x, mouse.y, 1);
break;
case Common::EVENT_SCREEN_CHANGED:
screenChange();
break;
default:
#ifdef ENABLE_KEYMAPPER
activeDialog->handleOtherEvent(event);
#endif
break;
}
}
} // End of namespace GUI

View file

@ -35,6 +35,10 @@ namespace Graphics {
class Font;
}
namespace Common {
struct Event;
}
namespace GUI {
class Dialog;
@ -67,6 +71,8 @@ public:
// until no dialogs are active anymore.
void runLoop();
void processEvent(const Common::Event &event, Dialog *const activeDialog);
bool isActive() const { return ! _dialogStack.empty(); }
bool loadNewTheme(Common::String id, ThemeEngine::GraphicsMode gfx = ThemeEngine::kGfxDisabled, bool force = false);

View file

@ -37,6 +37,11 @@
#include "gui/message.h"
#include "gui/gui-manager.h"
#include "gui/options.h"
#ifdef ENABLE_EVENTRECORDER
#include "gui/onscreendialog.h"
#include "gui/recorderdialog.h"
#include "gui/EventRecorder.h"
#endif
#include "gui/saveload.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/list.h"
@ -596,7 +601,6 @@ void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
LauncherDialog::LauncherDialog()
: Dialog(0, 0, 320, 200) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundMain;
const int screenW = g_system->getOverlayWidth();
const int screenH = g_system->getOverlayHeight();
@ -779,10 +783,9 @@ void LauncherDialog::updateListing() {
}
void LauncherDialog::addGame() {
int modifiers = g_system->getEventManager()->getModifierState();
#ifndef DISABLE_MASS_ADD
const bool massAdd = (modifiers & Common::KBD_SHIFT) != 0;
const bool massAdd = checkModifier(Common::KBD_SHIFT);
if (massAdd) {
MessageDialog alert(_("Do you really want to run the mass game detector? "
@ -975,6 +978,49 @@ void LauncherDialog::editGame(int item) {
}
}
void LauncherDialog::loadGameButtonPressed(int item) {
#ifdef ENABLE_EVENTRECORDER
const bool shiftPressed = checkModifier(Common::KBD_SHIFT);
if (shiftPressed) {
recordGame(item);
} else {
loadGame(item);
}
updateButtons();
#else
loadGame(item);
#endif
}
#ifdef ENABLE_EVENTRECORDER
void LauncherDialog::recordGame(int item) {
RecorderDialog recorderDialog;
MessageDialog alert(_("Do you want to load savegame?"),
_("Yes"), _("No"));
switch(recorderDialog.runModal(_domains[item])) {
case RecorderDialog::kRecordDialogClose:
break;
case RecorderDialog::kRecordDialogPlayback:
ConfMan.setActiveDomain(_domains[item]);
close();
ConfMan.set("record_mode", "playback", ConfigManager::kTransientDomain);
ConfMan.set("record_file_name", recorderDialog.getFileName(), ConfigManager::kTransientDomain);
break;
case RecorderDialog::kRecordDialogRecord:
ConfMan.setActiveDomain(_domains[item]);
if (alert.runModal() == GUI::kMessageOK) {
loadGame(item);
}
close();
g_eventRec.setAuthor(recorderDialog._author);
g_eventRec.setName(recorderDialog._name);
g_eventRec.setNotes(recorderDialog._notes);
ConfMan.set("record_mode", "record", ConfigManager::kTransientDomain);
break;
}
}
#endif
void LauncherDialog::loadGame(int item) {
String gameId = ConfMan.get("gameid", _domains[item]);
if (gameId.empty())
@ -1039,7 +1085,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
editGame(item);
break;
case kLoadGameCmd:
loadGame(item);
loadGameButtonPressed(item);
break;
case kOptionsCmd: {
GlobalOptionsDialog options;
@ -1109,20 +1155,28 @@ void LauncherDialog::updateButtons() {
_loadButton->setEnabled(en);
_loadButton->draw();
}
switchButtonsText(_addButton, "~A~dd Game...", "Mass Add...");
#ifdef ENABLE_EVENTRECORDER
switchButtonsText(_loadButton, "~L~oad...", "Record...");
#endif
}
// Update the label of the "Add" button depending on whether shift is pressed or not
int modifiers = g_system->getEventManager()->getModifierState();
const bool massAdd = (modifiers & Common::KBD_SHIFT) != 0;
// Update the label of the button depending on whether shift is pressed or not
void LauncherDialog::switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText) {
const bool shiftPressed = checkModifier(Common::KBD_SHIFT);
const bool lowRes = g_system->getOverlayWidth() <= 320;
const char *newAddButtonLabel = massAdd
? (lowRes ? _c("Mass Add...", "lowres") : _("Mass Add..."))
: (lowRes ? _c("~A~dd Game...", "lowres") : _("~A~dd Game..."));
const char *newAddButtonLabel = shiftPressed
? (lowRes ? _c(shiftedText, "lowres") : _(shiftedText))
: (lowRes ? _c(normalText, "lowres") : _(normalText));
if (_addButton->getLabel() != newAddButtonLabel)
_addButton->setLabel(newAddButtonLabel);
if (button->getLabel() != newAddButtonLabel)
button->setLabel(newAddButtonLabel);
}
void LauncherDialog::reflowLayout() {
#ifndef DISABLE_FANCY_THEMES
if (g_gui.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui.theme()->supportsImages()) {
@ -1186,4 +1240,9 @@ void LauncherDialog::reflowLayout() {
Dialog::reflowLayout();
}
bool LauncherDialog::checkModifier(int checkedModifier) {
int modifiers = g_system->getEventManager()->getModifierState();
return (modifiers & checkedModifier) != 0;
}
} // End of namespace GUI

View file

@ -56,7 +56,7 @@ protected:
ListWidget *_list;
ButtonWidget *_addButton;
Widget *_startButton;
Widget *_loadButton;
ButtonWidget *_loadButton;
Widget *_editButton;
Widget *_removeButton;
#ifndef DISABLE_FANCY_THEMES
@ -80,6 +80,7 @@ protected:
void updateListing();
void updateButtons();
void switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText);
void open();
void close();
@ -99,6 +100,16 @@ protected:
*/
void editGame(int item);
/**
* Facade for "Load..."/"Record..." buttons.
*/
void loadGameButtonPressed(int item);
/**
* Handle "Record..." button.
*/
void recordGame(int item);
/**
* Handle "Load..." button.
*/
@ -111,6 +122,8 @@ protected:
* @target name of target to select
*/
void selectTarget(const String &target);
private:
bool checkModifier(int modifier);
};
} // End of namespace GUI

View file

@ -7,6 +7,7 @@ MODULE_OBJS := \
debugger.o \
dialog.o \
error.o \
EventRecorder.o \
gui-manager.o \
launcher.o \
massadd.o \
@ -38,6 +39,13 @@ MODULE_OBJS += \
browser.o
endif
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
editrecorddialog.o \
onscreendialog.o \
recorderdialog.o
endif
ifdef USE_FLUIDSYNTH
MODULE_OBJS += \
fluidsynth-dialog.o

231
gui/onscreendialog.cpp Normal file
View file

@ -0,0 +1,231 @@
/* 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/system.h"
#include "gui/gui-manager.h"
#include "gui/EventRecorder.h"
#include "common/events.h"
#include "common/rect.h"
#include "common/translation.h"
#include "graphics/cursorman.h"
#include "gui/editrecorddialog.h"
#include "gui/ThemeEval.h"
#include "gui/onscreendialog.h"
namespace GUI {
bool OnScreenDialog::isVisible() const {
return true;
}
enum {
kStopCmd = 'STOP',
kEditCmd = 'EDIT',
kSwitchModeCmd = 'MODE',
kFastModeCmd = 'FAST'
};
void OnScreenDialog::reflowLayout() {
GuiObject::reflowLayout();
}
void OnScreenDialog::releaseFocus() {
}
OnScreenDialog::OnScreenDialog(bool isRecord) : Dialog("OnScreenDialog") {
_x = _y = 0;
#ifndef DISABLE_FANCY_THEMES
if (g_gui.xmlEval()->getVar("Globals.OnScreenDialog.ShowPics") == 1 && g_gui.theme()->supportsImages()) {
GUI::PicButtonWidget *btn;
btn = new PicButtonWidget(this, "OnScreenDialog.StopButton", 0, kStopCmd, 0);
btn->useThemeTransparency(true);
if (g_system->getOverlayWidth() > 320)
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageStopbtn));
else
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageStopSmallbtn));
if (isRecord) {
btn = new PicButtonWidget(this, "OnScreenDialog.EditButton", 0, kEditCmd, 0);
btn->useThemeTransparency(true);
if (g_system->getOverlayWidth() > 320)
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageEditbtn));
else
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageEditSmallbtn));
} else {
btn = new PicButtonWidget(this, "OnScreenDialog.SwitchModeButton", 0, kSwitchModeCmd, 0);
btn->useThemeTransparency(true);
if (g_system->getOverlayWidth() > 320)
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageSwitchModebtn));
else
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageSwitchModeSmallbtn));
btn = new PicButtonWidget(this, "OnScreenDialog.FastReplayButton", 0, kFastModeCmd, 0);
btn->useThemeTransparency(true);
if (g_system->getOverlayWidth() > 320)
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageFastReplaybtn));
else
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageFastReplaySmallbtn));
}
} else
#endif
{
GUI::ButtonWidget *btn;
if (g_system->getOverlayWidth() > 320)
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[ ]", _("Stop"), kStopCmd);
else
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[]", _("Stop"), kStopCmd);
if (isRecord) {
btn = new ButtonWidget(this, "OnScreenDialog.EditButton", "E", _("Edit record description"), kEditCmd);
} else {
btn = new ButtonWidget(this, "OnScreenDialog.SwitchModeButton", "G", _("Switch to Game"), kSwitchModeCmd);
btn = new ButtonWidget(this, "OnScreenDialog.FastReplayButton", ">>", _("Fast replay"), kFastModeCmd);
}
}
text = new GUI::StaticTextWidget(this, "OnScreenDialog.TimeLabel", "00:00:00");
_enableDrag = false;
_mouseOver = false;
_editDlgShown = false;
}
void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
Common::Event eventRTL;
switch (cmd) {
case kStopCmd:
eventRTL.type = Common::EVENT_RTL;
g_system->getEventManager()->pushEvent(eventRTL);
close();
break;
case kEditCmd:
dlg = new EditRecordDialog(g_eventRec.getAuthor(), g_eventRec.getName(), g_eventRec.getNotes());
CursorMan.lock(false);
g_eventRec.setRedraw(false);
g_system->showOverlay();
_editDlgShown = true;
dlg->runModal();
_editDlgShown = false;
g_system->hideOverlay();
g_eventRec.setRedraw(true);
CursorMan.lock(true);
g_eventRec.setAuthor(((EditRecordDialog *)dlg)->getAuthor());
g_eventRec.setName(((EditRecordDialog *)dlg)->getName());
g_eventRec.setNotes(((EditRecordDialog *)dlg)->getNotes());
delete dlg;
break;
case kSwitchModeCmd:
if (g_eventRec.switchMode()) {
close();
}
break;
case kFastModeCmd:
g_eventRec.switchFastMode();
break;
}
}
void OnScreenDialog::setReplayedTime(uint32 newTime) {
if (newTime - lastTime > 1000) {
uint32 seconds = newTime / 1000;
text->setLabel(Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60));
lastTime = newTime;
}
}
OnScreenDialog::~OnScreenDialog() {
}
void OnScreenDialog::handleMouseMoved(int x, int y, int button) {
if (_enableDrag) {
_x = _x + x - _dragPoint.x;
_y = _y + y - _dragPoint.y;
}
Dialog::handleMouseMoved(x, y, button);
if (isMouseOver(x, y)) {
if (_mouseOver == false) {
g_gui.theme()->showCursor();
CursorMan.lock(true);
}
_mouseOver = true;
} else {
if (_mouseOver == true) {
CursorMan.lock(false);
g_gui.theme()->hideCursor();
}
_mouseOver = false;
}
}
void OnScreenDialog::handleMouseDown(int x, int y, int button, int clickCount) {
if (isMouseOver(x, y)) {
_dragPoint.x = x;
_dragPoint.y = y;
_enableDrag = true;
}
Dialog::handleMouseDown(x, y, button, clickCount);
}
void OnScreenDialog::handleMouseUp(int x, int y, int button, int clickCount) {
if (isMouseOver(x, y)) {
}
_enableDrag = false;
Dialog::handleMouseUp(x, y, button, clickCount);
}
bool OnScreenDialog::isMouseOver(int x, int y) {
return (x >= 0 && x < _w && y >= 0 && y < _h);
}
bool OnScreenDialog::isMouseOver() {
return _mouseOver;
}
void OnScreenDialog::close() {
CursorMan.lock(false);
Dialog::close();
}
Dialog *OnScreenDialog::getActiveDlg() {
if (_editDlgShown) {
return dlg;
} else {
return this;
}
}
bool OnScreenDialog::isEditDlgVisible() {
return _editDlgShown;
}
}

64
gui/onscreendialog.h Normal file
View file

@ -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 GUI_ONSCREENDIALOG_H
#define GUI_ONSCREENDIALOG_H
#include "gui/dialog.h"
#include "gui/widget.h"
namespace GUI {
class OnScreenDialog : public Dialog {
private:
uint32 lastTime;
bool _enableDrag;
bool _mouseOver;
bool _editDlgShown;
Common::Point _dragPoint;
GUI::StaticTextWidget *text;
Dialog *dlg;
bool isMouseOver(int x, int y);
public:
OnScreenDialog(bool recordingMode);
~OnScreenDialog();
virtual void close();
virtual bool isVisible() const;
virtual void reflowLayout();
void setReplayedTime(uint32 newTime);
virtual void handleMouseMoved(int x, int y, int button);
virtual void handleMouseDown(int x, int y, int button, int clickCount);
virtual void handleMouseUp(int x, int y, int button, int clickCount);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
bool isMouseOver();
bool isEditDlgVisible();
Dialog *getActiveDlg();
protected:
virtual void releaseFocus();
};
} // End of namespace GUI
#endif

291
gui/recorderdialog.cpp Normal file
View file

@ -0,0 +1,291 @@
/* 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/algorithm.h"
#include "common/bufferedstream.h"
#include "common/savefile.h"
#include "common/system.h"
#include "graphics/colormasks.h"
#include "graphics/palette.h"
#include "graphics/scaler.h"
#include "graphics/thumbnail.h"
#include "common/translation.h"
#include "gui/widgets/list.h"
#include "gui/editrecorddialog.h"
#include "gui/EventRecorder.h"
#include "gui/message.h"
#include "gui/saveload.h"
#include "common/system.h"
#include "gui/ThemeEval.h"
#include "gui/gui-manager.h"
#include "recorderdialog.h"
#define MAX_RECORDS_NAMES 0xFF
namespace GUI {
enum {
kRecordCmd = 'RCRD',
kPlaybackCmd = 'PBCK',
kDeleteCmd = 'DEL ',
kNextScreenshotCmd = 'NEXT',
kPrevScreenshotCmd = 'PREV',
kEditRecordCmd = 'EDIT'
};
RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(0), _currentScreenshot(0) {
_backgroundType = ThemeEngine::kDialogBackgroundSpecial;
new StaticTextWidget(this, "SaveLoadChooser.Title", _("Recorder or Playback Gameplay"));
_list = new GUI::ListWidget(this, "RecorderDialog.List");
_list->setNumberingMode(GUI::kListNumberingOff);
_deleteButton = new GUI::ButtonWidget(this, "RecorderDialog.Delete", _("Delete"), 0, kDeleteCmd);
new GUI::ButtonWidget(this, "RecorderDialog.Cancel", _("Cancel"), 0, kCloseCmd);
new GUI::ButtonWidget(this, "RecorderDialog.Record", _("Record"), 0, kRecordCmd);
_playbackButton = new GUI::ButtonWidget(this, "RecorderDialog.Playback", _("Playback"), 0, kPlaybackCmd);
_editButton = new GUI::ButtonWidget(this, "RecorderDialog.Edit", _("Edit"), 0, kEditRecordCmd);
_editButton->setEnabled(false);
_deleteButton->setEnabled(false);
_playbackButton->setEnabled(false);
_gfxWidget = new GUI::GraphicsWidget(this, 0, 0, 10, 10);
_container = new GUI::ContainerWidget(this, 0, 0, 10, 10);
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
new GUI::ButtonWidget(this,"RecorderDialog.NextScreenShotButton", "<", 0, kPrevScreenshotCmd);
new GUI::ButtonWidget(this, "RecorderDialog.PreviousScreenShotButton", ">", 0, kNextScreenshotCmd);
_currentScreenshotText = new StaticTextWidget(this, "RecorderDialog.currentScreenshot", "0/0");
_authorText = new StaticTextWidget(this, "RecorderDialog.Author", _("Author: "));
_notesText = new StaticTextWidget(this, "RecorderDialog.Notes", _("Notes: "));
}
if (_gfxWidget)
_gfxWidget->setGfx(0);
}
void RecorderDialog::reflowLayout() {
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
int16 x, y;
uint16 w, h;
if (!g_gui.xmlEval()->getWidgetData("RecorderDialog.Thumbnail", x, y, w, h)) {
error("Error when loading position data for Recorder Thumbnails");
}
int thumbW = kThumbnailWidth;
int thumbH = kThumbnailHeight2;
int thumbX = x + (w >> 1) - (thumbW >> 1);
int thumbY = y + kLineHeight;
_container->resize(x, y, w, h);
_gfxWidget->resize(thumbX, thumbY, thumbW, thumbH);
_container->setVisible(true);
_gfxWidget->setVisible(true);
updateSelection(false);
} else {
_container->setVisible(false);
_gfxWidget->setVisible(false);
}
Dialog::reflowLayout();
}
void RecorderDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch(cmd) {
case kEditRecordCmd: {
if (_list->getSelected() >= 0) {
EditRecordDialog editDlg(_fileHeaders[_list->getSelected()].author, _fileHeaders[_list->getSelected()].name, _fileHeaders[_list->getSelected()].notes);
if (editDlg.runModal() != kOKCmd) {
return;
}
_playbackFile.openRead(_fileHeaders[_list->getSelected()].fileName);
_playbackFile.getHeader().author = editDlg.getAuthor();
_playbackFile.getHeader().name = editDlg.getName();
_playbackFile.getHeader().notes = editDlg.getNotes();
_playbackFile.updateHeader();
_fileHeaders[_list->getSelected()] = _playbackFile.getHeader();
int oldselection = _list->getSelected();
updateList();
_list->setSelected(oldselection);
updateSelection(true);
_playbackFile.close();
}
}
break;
case kNextScreenshotCmd:
++_currentScreenshot;
updateScreenshot();
break;
case kPrevScreenshotCmd:
--_currentScreenshot;
updateScreenshot();
break;
case kDeleteCmd:
if (_list->getSelected() >= 0) {
MessageDialog alert(_("Do you really want to delete this record?"),
_("Delete"), _("Cancel"));
if (alert.runModal() == GUI::kMessageOK) {
_playbackFile.close();
g_eventRec.deleteRecord(_fileHeaders[_list->getSelected()].fileName);
_list->setSelected(-1);
updateList();
}
}
break;
case GUI::kListSelectionChangedCmd:
updateSelection(true);
break;
case kRecordCmd: {
TimeDate t;
Common::String gameId = ConfMan.get("gameid", _target);
const EnginePlugin *plugin = 0;
GameDescriptor desc = EngineMan.findGame(gameId, &plugin);
g_system->getTimeAndDate(t);
EditRecordDialog editDlg("Unknown Author", Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description(), "");
if (editDlg.runModal() != kOKCmd) {
return;
}
_author = editDlg.getAuthor();
_name = editDlg.getName();
_notes = editDlg.getNotes();
_filename = g_eventRec.generateRecordFileName(_target);
setResult(kRecordDialogRecord);
close();
}
break;
case kPlaybackCmd:
if (_list->getSelected() >= 0) {
_filename = _fileHeaders[_list->getSelected()].fileName;
setResult(kRecordDialogPlayback);
close();
}
break;
case kCloseCmd:
setResult(kRecordDialogClose);
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void RecorderDialog::updateList() {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::String pattern(_target+".r??");
Common::StringArray files = saveFileMan->listSavefiles(pattern);
Common::PlaybackFile file;
Common::StringArray namesList;
_fileHeaders.clear();
for (Common::StringArray::iterator i = files.begin(); i != files.end(); ++i) {
if (file.openRead(*i)) {
namesList.push_back(file.getHeader().name);
_fileHeaders.push_back(file.getHeader());
}
file.close();
}
_list->setList(namesList);
_list->draw();
}
int RecorderDialog::runModal(Common::String &target) {
_target = target;
updateList();
return Dialog::runModal();
}
RecorderDialog::~RecorderDialog() {
}
void RecorderDialog::updateSelection(bool redraw) {
if (_list->getSelected() >= 0) {
_editButton->setEnabled(true);
_deleteButton->setEnabled(true);
_playbackButton->setEnabled(true);
}
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") != 1)
return;
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
_screenShotsCount = 0;
_currentScreenshot = 0;
updateScreenShotsText();
if (_list->getSelected() >= 0) {
_authorText->setLabel(_("Author: ") + _fileHeaders[_list->getSelected()].author);
_notesText->setLabel(_("Notes: ") + _fileHeaders[_list->getSelected()].notes);
_firstScreenshotUpdate = true;
updateScreenshot();
if ((_screenShotsCount) > 0) {
_currentScreenshot = 1;
}
updateScreenshot();
} else {
_authorText->setLabel(_("Author: "));
_notesText->setLabel(_("Notes: "));
_screenShotsCount = -1;
_currentScreenshot = 0;
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
_gfxWidget->draw();
updateScreenShotsText();
}
}
void RecorderDialog::updateScreenshot() {
if (_list->getSelected() == -1) {
return;
}
if (_currentScreenshot < 1) {
_currentScreenshot = _screenShotsCount;
}
if (_currentScreenshot > _screenShotsCount) {
_currentScreenshot = 1;
}
if (_firstScreenshotUpdate) {
_playbackFile.openRead(_fileHeaders[_list->getSelected()].fileName);
_screenShotsCount = _playbackFile.getScreensCount();
_firstScreenshotUpdate = false;
}
Graphics::Surface *srcsf = _playbackFile.getScreenShot(_currentScreenshot);
if (srcsf != NULL) {
Graphics::Surface *destsf = Graphics::scale(*srcsf, _gfxWidget->getWidth(), _gfxWidget->getHeight());
_gfxWidget->setGfx(destsf);
updateScreenShotsText();
delete destsf;
delete srcsf;
} else {
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
}
_gfxWidget->draw();
}
void RecorderDialog::updateScreenShotsText() {
if (_screenShotsCount == -1) {
_currentScreenshotText->setLabel(Common::String::format("%d / ?", _currentScreenshot));
} else {
_currentScreenshotText->setLabel(Common::String::format("%d / %d", _currentScreenshot, _screenShotsCount));
}
}
} // End of namespace GUI

81
gui/recorderdialog.h Normal file
View file

@ -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.
*/
#ifndef GUI_RECORDER_DIALOG_H
#define GUI_RECORDER_DIALOG_H
#include "common/stream.h"
#include "common/recorderfile.h"
#include "gui/dialog.h"
namespace GUI {
class ListWidget;
class GraphicsWidget;
class ButtonWidget;
class CommandSender;
class ContainerWidget;
class StaticTextWidget;
class RecorderDialog : public GUI::Dialog {
private:
bool _firstScreenshotUpdate;
Common::PlaybackFile _playbackFile;
Common::String _target;
Common::String _filename;
int _currentScreenshot;
int _screenShotsCount;
Common::Array<Common::PlaybackFile::PlaybackFileHeader> _fileHeaders;
GUI::ListWidget *_list;
GUI::ContainerWidget *_container;
GUI::GraphicsWidget *_gfxWidget;
GUI::StaticTextWidget *_currentScreenshotText;
GUI::StaticTextWidget *_authorText;
GUI::StaticTextWidget *_notesText;
GUI::ButtonWidget *_editButton;
GUI::ButtonWidget *_deleteButton;
GUI::ButtonWidget *_playbackButton;
void updateList();
void updateScreenShotsText();
void updateSelection(bool redraw);
void updateScreenshot();
public:
Common::String _author;
Common::String _name;
Common::String _notes;
enum DialogResult {
kRecordDialogClose,
kRecordDialogRecord,
kRecordDialogPlayback
};
RecorderDialog();
~RecorderDialog();
virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
virtual void reflowLayout();
int runModal(Common::String &target);
const Common::String getFileName() {return _filename;}
};
} // End of namespace GUI
#endif

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -36,6 +36,9 @@
<def var = 'ShowChooserPageDisplay' value = '1'/>
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
<def var = 'RecorderDialog.ExtInfo.Visible' value = '1'/>
<def var = 'OnScreenDialog.ShowPics' value = '0'/>
<def var = 'KeyMapper.Spacing' value = '10'/>
<def var = 'KeyMapper.LabelWidth' value = '100'/>
@ -101,6 +104,12 @@
size = '15, 18'
padding = '0, 3, 4, 0'
/>
<widget name = 'EditRecordLabel'
size = '60, 25'
/>
<widget name = 'EditRecord'
size = '240, 25'
/>
</globals>
<dialog name = 'Launcher' overlays = 'screen'>
@ -1019,6 +1028,125 @@
</layout>
</dialog>
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
<widget name = 'List' />
<layout type = 'vertical' padding = '0, 0, 0, 0'>
<widget name = 'Thumbnail'
width = '180'
height = '170'
/>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
<widget name = 'NextScreenShotButton'
width = '25'
height = '25'
/>
<widget name = 'currentScreenshot'
width = '125'
height = '25'
textalign = 'center'
/>
<widget name = 'PreviousScreenShotButton'
width = '25'
height = '25'
/>
</layout>
<widget name = 'Author' height = 'Globals.Line.Height' />
<widget name = 'Notes' height = 'Globals.Line.Height' />
</layout>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
<widget name = 'Delete'
type = 'Button'
/>
<space size = '16'/>
<widget name = 'Cancel'
type = 'Button'
/>
<space size = '16'/>
<widget name = 'Edit'
type = 'Button'
/>
<widget name = 'Record'
type = 'Button'
/>
<widget name = 'Playback'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
<layout type = 'horizontal' spacing = '5' padding = '5, 3, 5, 3' center = 'true'>
<widget name = 'StopButton'
width = '32'
height = '32'
/>
<widget name = 'EditButton'
width = '32'
height = '32'
/>
<widget name = 'SwitchModeButton'
width = '32'
height = '32'
/>
<widget name = 'FastReplayButton'
width = '32'
height = '32'
/>
<widget name = 'TimeLabel'
width = '50'
height = '30'
/>
</layout>
</dialog>
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
width = '320'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'AuthorLabel'
type = 'EditRecordLabel'
/>
<widget name = 'AuthorEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NameLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NameEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NotesLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NotesEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'OK'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'ScummHelp' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'

View file

@ -37,6 +37,9 @@
<def var = 'ShowChooserPageDisplay' value = '0'/>
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
<def var = 'RecorderDialog.ExtInfo.Visible' value = '0'/>
<def var = 'OnScreenDialog.ShowPics' value = '0'/>
<def var = 'KeyMapper.Spacing' value = '5'/>
<def var = 'KeyMapper.LabelWidth' value = '80'/>
@ -99,6 +102,12 @@
size = '32, 18'
padding = '0, 0, 1, 0'
/>
<widget name = 'EditRecordLabel'
size = '60, Globals.Line.Height'
/>
<widget name = 'EditRecord'
size = '120, 15'
/>
</globals>
<dialog name = 'Launcher' overlays = 'screen'>
@ -1013,6 +1022,101 @@
</layout>
</dialog>
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 4' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<widget name = 'List' />
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
<widget name = 'Edit'
type = 'Button'
/>
<space />
<widget name = 'Record'
type = 'Button'
/>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
<widget name = 'Delete'
type = 'Button'
/>
<space />
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'Playback'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
<layout type = 'horizontal' spacing = '5' padding = '3, 2, 3, 2' center = 'true'>
<widget name = 'StopButton'
width = '16'
height = '16'
/>
<widget name = 'EditButton'
width = '16'
height = '16'
/>
<widget name = 'SwitchModeButton'
width = '16'
height = '16'
/>
<widget name = 'FastReplayButton'
width = '16'
height = '16'
/>
<widget name = 'TimeLabel'
width = '50'
height = '16'
/>
</layout>
</dialog>
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'AuthorLabel'
type = 'EditRecordLabel'
/>
<widget name = 'AuthorEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NameLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NameEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NotesLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NotesEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 0'>
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'OK'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'ScummHelp' overlays = 'screen'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'Title'

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View file

@ -103,6 +103,14 @@
<bitmap filename = 'delbtn.bmp'/>
<bitmap filename = 'list.bmp'/>
<bitmap filename = 'grid.bmp'/>
<bitmap filename = 'stopbtn.bmp'/>
<bitmap filename = 'editbtn.bmp'/>
<bitmap filename = 'switchbtn.bmp'/>
<bitmap filename = 'fastreplay.bmp'/>
<bitmap filename = 'stopbtn_small.bmp'/>
<bitmap filename = 'editbtn_small.bmp'/>
<bitmap filename = 'switchbtn_small.bmp'/>
<bitmap filename = 'fastreplay_small.bmp'/>
</bitmaps>
<fonts>

View file

@ -43,6 +43,9 @@
<def var = 'ShowChooserPageDisplay' value = '1'/>
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
<def var = 'RecorderDialog.ExtInfo.Visible' value = '1'/>
<def var = 'OnScreenDialog.ShowPics' value = '1'/>
<def var = 'KeyMapper.Spacing' value = '10'/>
<def var = 'KeyMapper.LabelWidth' value = '100'/>
@ -106,6 +109,13 @@
size = '15, 18'
padding = '0, 3, 4, 0'
/>
<widget name = 'EditRecordLabel'
size = '60, 25'
/>
<widget name = 'EditRecord'
size = '240, 25'
/>
</globals>
<dialog name = 'Launcher' overlays = 'screen'>
@ -1032,6 +1042,126 @@
</layout>
</dialog>
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
<widget name = 'List' />
<layout type = 'vertical' padding = '0, 0, 0, 0'>
<widget name = 'Thumbnail'
width = '180'
height = '170'
/>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
<widget name = 'NextScreenShotButton'
width = '25'
height = '25'
/>
<widget name = 'currentScreenshot'
width = '125'
height = '25'
textalign = 'center'
/>
<widget name = 'PreviousScreenShotButton'
width = '25'
height = '25'
/>
</layout>
<widget name = 'Author' height = 'Globals.Line.Height' />
<widget name = 'Notes' height = 'Globals.Line.Height' />
</layout>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
<space/>
<widget name = 'Delete'
type = 'Button'
/>
<space size = '16'/>
<widget name = 'Cancel'
type = 'Button'
/>
<space size = '16'/>
<widget name = 'Edit'
type = 'Button'
/>
<widget name = 'Record'
type = 'Button'
/>
<widget name = 'Playback'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
<layout type = 'horizontal' spacing = '5' padding = '5, 3, 5, 3' center = 'true'>
<widget name = 'StopButton'
width = '32'
height = '32'
/>
<widget name = 'EditButton'
width = '32'
height = '32'
/>
<widget name = 'SwitchModeButton'
width = '32'
height = '32'
/>
<widget name = 'FastReplayButton'
width = '32'
height = '32'
/>
<widget name = 'TimeLabel'
width = '50'
height = '30'
/>
</layout>
</dialog>
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
width = '320'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'AuthorLabel'
type = 'EditRecordLabel'
/>
<widget name = 'AuthorEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NameLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NameEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NotesLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NotesEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'OK'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'ScummHelp' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'

View file

@ -35,6 +35,9 @@
<def var = 'ShowChooserPageDisplay' value = '0'/>
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
<def var = 'RecorderDialog.ExtInfo.Visible' value = '0'/>
<def var = 'OnScreenDialog.ShowPics' value = '1'/>
<def var = 'Predictive.Button.Width' value = '45' />
<def var = 'Predictive.Button.Height' value = '15' />
@ -97,6 +100,12 @@
size = '32, 18'
padding = '0, 0, 2, 0'
/>
<widget name = 'EditRecordLabel'
size = '60, Globals.Line.Height'
/>
<widget name = 'EditRecord'
size = '120, 15'
/>
</globals>
<dialog name = 'Launcher' overlays = 'screen'>
@ -1012,6 +1021,122 @@
</layout>
</dialog>
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
width = '320'
height = 'Globals.Line.Height'
/>
<widget name = 'Description'
height = '19'
/>
<layout type = 'horizontal' padding = '0, 0, 16, 0'>
<widget name = 'Cancel'
type = 'Button'
/>
<space size = '96'/>
<widget name = 'Ok'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
<layout type = 'vertical' padding = '8, 8, 8, 4' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<widget name = 'List' />
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
<widget name = 'Edit'
type = 'Button'
/>
<space />
<widget name = 'Record'
type = 'Button'
/>
</layout>
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
<widget name = 'Delete'
type = 'Button'
/>
<space />
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'Playback'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
<layout type = 'horizontal' spacing = '5' padding = '3, 2, 3, 2' center = 'true'>
<widget name = 'StopButton'
width = '16'
height = '16'
/>
<widget name = 'EditButton'
width = '16'
height = '16'
/>
<widget name = 'SwitchModeButton'
width = '16'
height = '16'
/>
<widget name = 'FastReplayButton'
width = '16'
height = '16'
/>
<widget name = 'TimeLabel'
width = '50'
height = '16'
/>
</layout>
</dialog>
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
<widget name = 'Title'
height = 'Globals.Line.Height'
/>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'AuthorLabel'
type = 'EditRecordLabel'
/>
<widget name = 'AuthorEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NameLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NameEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
<widget name = 'NotesLabel'
type = 'EditRecordLabel'
/>
<widget name = 'NotesEdit'
type = 'EditRecord'
/>
</layout>
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 0'>
<widget name = 'Cancel'
type = 'Button'
/>
<widget name = 'OK'
type = 'Button'
/>
</layout>
</layout>
</dialog>
<dialog name = 'ScummHelp' overlays = 'screen' inset = '8'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'Title'

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B