EVENTRECORDER: Add update mode

This new mode is functionally identical to playback mode, however
a new recording file is used to track the actual output of ScummVM.
This feature can be used to update a suite of existing recordings after
a renderer or a timing change.
This commit is contained in:
Scott Percival 2021-08-02 20:37:51 +08:00 committed by Martin Gerhardy
parent 84707ec828
commit 29d6758c0d
4 changed files with 111 additions and 42 deletions

View File

@ -171,7 +171,7 @@ static const char HELP_STRING[] =
" atari, macintosh, macintoshbw)\n"
#ifdef ENABLE_EVENTRECORDER
" --record-mode=MODE Specify record mode for event recorder (record, playback,\n"
" passthrough [default])\n"
" info, update, passthrough [default])\n"
" --record-file-name=FILE Specify record file name\n"
" --disable-display Disable any gfx output. Used for headless events\n"
" playback by Event Recorder\n"

View File

@ -602,6 +602,8 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
if (recordMode == "record") {
Common::String targetFileName = ConfMan.hasKey("record_file_name") ? recordFileName : g_eventRec.generateRecordFileName(ConfMan.getActiveDomainName());
g_eventRec.init(targetFileName, GUI::EventRecorder::kRecorderRecord);
} else if (recordMode == "update") {
g_eventRec.init(recordFileName, GUI::EventRecorder::kRecorderUpdate);
} else if (recordMode == "playback") {
g_eventRec.init(recordFileName, GUI::EventRecorder::kRecorderPlayback);
} else if ((recordMode == "info") && (!recordFileName.empty())) {

View File

@ -97,6 +97,7 @@ EventRecorder::EventRecorder() {
_lastScreenshotTime = 0;
_screenshotPeriod = 0;
_playbackFile = nullptr;
_recordFile = nullptr;
}
EventRecorder::~EventRecorder() {
@ -116,12 +117,18 @@ void EventRecorder::deinit() {
_controlPanel->close();
delete _controlPanel;
debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
Common::EventDispatcher *eventDispater = g_system->getEventManager()->getEventDispatcher();
eventDispater->unregisterSource(this);
eventDispater->ignoreSources(false);
Common::EventDispatcher *eventDispatcher = g_system->getEventManager()->getEventDispatcher();
eventDispatcher->unregisterSource(this);
eventDispatcher->ignoreSources(false);
_recordMode = kPassthrough;
_playbackFile->close();
delete _playbackFile;
if (_playbackFile) {
_playbackFile->close();
delete _playbackFile;
}
if (_recordFile) {
_recordFile->close();
delete _recordFile;
}
switchMixer();
switchTimerManagers();
DebugMan.disableDebugChannel("EventRec");
@ -143,31 +150,41 @@ void EventRecorder::processTimeAndDate(TimeDate &td, bool skipRecord) {
return;
}
Common::RecorderEvent timeDateEvent;
switch (_recordMode) {
case kRecorderRecord:
if (_recordMode == kRecorderRecord) {
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_lastTimeDate = td;
_playbackFile->writeEvent(timeDateEvent);
break;
case kRecorderPlayback:
_recordFile->writeEvent(timeDateEvent);
}
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimeDate) {
// just re-use any previous date time value
td = _lastTimeDate;
if (_recordMode == kRecorderUpdate) {
// if we make it to here, we're expecting there to be an event in the file
// so add one to the updated recording
debug(3, "inserting additional timedate event");
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_recordFile->writeEvent(timeDateEvent);
}
return;
}
_lastTimeDate = _nextEvent.timeDate;
td = _lastTimeDate;
debug(3, "timedate event");
if (_recordMode == kRecorderUpdate) {
// copy the event to the updated recording
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_recordFile->writeEvent(timeDateEvent);
}
_nextEvent = _playbackFile->getNextEvent();
break;
case kRecorderPlaybackPause:
td = _lastTimeDate;
break;
default:
break;
}
if (_recordMode == kRecorderPlaybackPause)
td = _lastTimeDate;
}
void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
@ -192,6 +209,7 @@ void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
_recordFile->writeEvent(timerEvent);
_timerManager->handler();
break;
case kRecorderUpdate: // fallthrough
case kRecorderPlayback:
if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimer) {
// just re-use any previous millis value
@ -200,11 +218,25 @@ void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
// is registered and thus dispatched after those EventSource instances, it
// might look like it ran out-of-sync.
millis = _fakeTimer;
if (_recordMode == kRecorderUpdate) {
// if we make it to here, we're expecting there to be an event in the file
// so add one to the updated recording
debug(3, "inserting additional timer event");
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_recordFile->writeEvent(timerEvent);
}
return;
}
_processingMillis = true;
_fakeTimer = _nextEvent.time;
millis = _fakeTimer;
if (_recordMode == kRecorderUpdate) {
// copy the event to the updated recording
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_recordFile->writeEvent(timerEvent);
}
updateSubsystems();
_nextEvent = _playbackFile->getNextEvent();
_timerManager->handler();
@ -242,6 +274,7 @@ void EventRecorder::processScreenUpdate() {
takeScreenshot();
_timerManager->handler();
break;
case kRecorderUpdate: // fallthrough
case kRecorderPlayback:
if (_playbackFile->hasTrackScreenUpdate()) {
// if the file has screen update support, but the next event
@ -261,6 +294,13 @@ void EventRecorder::processScreenUpdate() {
_fakeTimer = _nextEvent.time;
updateSubsystems();
_nextEvent = _playbackFile->getNextEvent();
if (_recordMode == kRecorderUpdate) {
// write event to the updated file and update screenshot if necessary
screenUpdateEvent.recordedtype = Common::kRecorderEventTypeScreenUpdate;
screenUpdateEvent.time = _fakeTimer;
_recordFile->writeEvent(screenUpdateEvent);
takeScreenshot();
}
_timerManager->handler();
_controlPanel->setReplayedTime(_fakeTimer);
_processingMillis = false;
@ -278,7 +318,9 @@ void EventRecorder::checkForKeyCode(const Common::Event &event) {
}
bool EventRecorder::pollEvent(Common::Event &ev) {
if ((_recordMode != kRecorderPlayback) || !_initialized)
if (((_recordMode != kRecorderPlayback) &&
(_recordMode != kRecorderUpdate)) ||
!_initialized)
return false;
if (_nextEvent.recordedtype == Common::kRecorderEventTypeTimer
@ -317,6 +359,7 @@ void EventRecorder::togglePause() {
switch (_recordMode) {
case kRecorderPlayback:
case kRecorderRecord:
case kRecorderUpdate:
oldState = _recordMode;
_recordMode = kRecorderPlaybackPause;
_controlPanel->runModal();
@ -341,7 +384,7 @@ uint32 EventRecorder::getRandomSeed(const Common::String &name) {
}
uint32 result = g_system->getMillis();
if (_recordMode == kRecorderRecord) {
_playbackFile->getHeader().randomSourceRecords[name] = result;
_recordFile->getHeader().randomSourceRecords[name] = result;
}
return result;
}
@ -366,7 +409,6 @@ void EventRecorder::init(const Common::String &recordFileName, RecordMode mode)
_fakeMixerManager->suspendAudio();
_fakeTimer = 0;
_lastMillis = g_system->getMillis();
_playbackFile = new Common::PlaybackFile();
_lastScreenshotTime = 0;
_recordMode = mode;
_needcontinueGame = false;
@ -374,12 +416,12 @@ void EventRecorder::init(const Common::String &recordFileName, RecordMode mode)
DebugMan.enableDebugChannel("EventRec");
gDebugLevel = 1;
}
if (_recordMode == kRecorderPlayback) {
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Load file\" filename=%s", recordFileName.c_str());
Common::EventDispatcher *eventDispater = g_system->getEventManager()->getEventDispatcher();
eventDispater->clearEvents();
eventDispater->ignoreSources(true);
eventDispater->registerSource(this, false);
Common::EventDispatcher *eventDispatcher = g_system->getEventManager()->getEventDispatcher();
eventDispatcher->clearEvents();
eventDispatcher->ignoreSources(true);
eventDispatcher->registerSource(this, false);
}
_screenshotPeriod = ConfMan.getInt("screenshot_period");
if (_screenshotPeriod == 0) {
@ -394,11 +436,11 @@ void EventRecorder::init(const Common::String &recordFileName, RecordMode mode)
_controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
_controlPanel->reflowLayout();
}
if (_recordMode == kRecorderPlayback) {
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
applyPlaybackSettings();
_nextEvent = _playbackFile->getNextEvent();
}
if (_recordMode == kRecorderRecord) {
if ((_recordMode == kRecorderRecord) || (_recordMode == kRecorderUpdate)) {
getConfig();
}
@ -419,12 +461,22 @@ bool EventRecorder::openRecordFile(const Common::String &fileName) {
bool result;
switch (_recordMode) {
case kRecorderRecord:
return _playbackFile->openWrite(fileName);
_recordFile = new Common::PlaybackFile();
return _recordFile->openWrite(fileName);
case kRecorderPlayback:
_recordMode = kPassthrough;
_playbackFile = new Common::PlaybackFile();
result = _playbackFile->openRead(fileName);
_recordMode = kRecorderPlayback;
return result;
case kRecorderUpdate:
_recordMode = kPassthrough;
_playbackFile = new Common::PlaybackFile();
result = _playbackFile->openRead(fileName);
_recordMode = kRecorderUpdate;
_recordFile = new Common::PlaybackFile();
result &= _recordFile->openWrite(fileName + ".new");
return result;
default:
return false;
}
@ -478,19 +530,24 @@ MixerManager *EventRecorder::getMixerManager() {
void EventRecorder::getConfigFromDomain(const Common::ConfigManager::Domain *domain) {
for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
_playbackFile->getHeader().settingsRecords[entry->_key] = entry->_value;
_recordFile->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");
_recordFile->getHeader().settingsRecords["save_slot"] = ConfMan.get("save_slot");
}
void EventRecorder::applyPlaybackSettings() {
for (Common::StringMap::const_iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) {
if (_recordMode == kRecorderUpdate) {
// copy the header values to the new recording
_recordFile->getHeader().settingsRecords[i->_key] = i->_value;
}
Common::String currentValue = ConfMan.get(i->_key);
if (currentValue != i->_value) {
ConfMan.set(i->_key, i->_value, ConfMan.kTransientDomain);
@ -549,10 +606,18 @@ bool EventRecorder::notifyEvent(const Common::Event &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 kRecorderUpdate: // passthrough
case kRecorderPlayback:
// pass through screen updates to avoid loss of sync!
if (evt.type == Common::EVENT_SCREEN_CHANGED)
g_gui.processEvent(evt, _controlPanel);
if (_recordMode == kRecorderUpdate) {
// write a copy of the event to the output buffer
Common::RecorderEvent e(ev);
e.recordedtype = Common::kRecorderEventTypeNormal;
e.time = _fakeTimer;
_recordFile->writeEvent(e);
}
return false;
case kRecorderRecord:
g_gui.processEvent(evt, _controlPanel);
@ -562,7 +627,7 @@ bool EventRecorder::notifyEvent(const Common::Event &ev) {
Common::RecorderEvent e(ev);
e.recordedtype = Common::kRecorderEventTypeNormal;
e.time = _fakeTimer;
_playbackFile->writeEvent(e);
_recordFile->writeEvent(e);
return false;
}
case kRecorderPlaybackPause: {
@ -586,16 +651,16 @@ bool EventRecorder::notifyEvent(const Common::Event &ev) {
void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) {
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
if (fileDesc->md5 != nullptr) {
_playbackFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
_recordFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
}
}
}
void EventRecorder::processGameDescription(const ADGameDescription *desc) {
if (_recordMode == kRecorderRecord) {
if ((_recordMode == kRecorderRecord) || (_recordMode == kRecorderUpdate)) {
setGameMd5(desc);
}
if ((_recordMode == kRecorderPlayback) && !checkGameHash(desc)) {
if (((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) && !checkGameHash(desc)) {
deinit();
error("playback:action=error reason=\"\"");
}
@ -611,7 +676,7 @@ void EventRecorder::takeScreenshot() {
uint8 md5[16];
if (grabScreenAndComputeMD5(screen, md5)) {
_lastScreenshotTime = _fakeTimer;
_playbackFile->saveScreenShot(screen, md5);
_recordFile->saveScreenShot(screen, md5);
screen.free();
}
}
@ -636,7 +701,7 @@ Common::SeekableReadStream *EventRecorder::processSaveStream(const Common::Strin
case kRecorderRecord:
saveFile = _realSaveManager->openForLoading(fileName);
if (saveFile != nullptr) {
_playbackFile->addSaveFile(fileName, saveFile);
_recordFile->addSaveFile(fileName, saveFile);
saveFile->seek(0);
}
return saveFile;
@ -681,7 +746,7 @@ void EventRecorder::postDrawOverlayGui() {
}
Common::StringArray EventRecorder::listSaveFiles(const Common::String &pattern) {
if (_recordMode == kRecorderPlayback) {
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
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, "/")) {
@ -695,7 +760,7 @@ Common::StringArray EventRecorder::listSaveFiles(const Common::String &pattern)
}
void EventRecorder::setFileHeader() {
if (_recordMode != kRecorderRecord) {
if ((_recordMode != kRecorderRecord) && (_recordMode != kRecorderUpdate)) {
return;
}
TimeDate t;
@ -707,9 +772,9 @@ void EventRecorder::setFileHeader() {
if (_name.empty()) {
g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year) + desc.description);
}
_playbackFile->getHeader().author = _author;
_playbackFile->getHeader().notes = _desc;
_playbackFile->getHeader().name = _name;
_recordFile->getHeader().author = _author;
_recordFile->getHeader().notes = _desc;
_recordFile->getHeader().name = _name;
}
SDL_Surface *EventRecorder::getSurface(int width, int height) {

View File

@ -74,7 +74,8 @@ public:
kPassthrough = 0, /**< kPassthrough, do nothing */
kRecorderRecord = 1, /**< kRecorderRecord, do the recording */
kRecorderPlayback = 2, /**< kRecorderPlayback, playback existing recording */
kRecorderPlaybackPause = 3 /**< kRecordetPlaybackPause, internal state when user pauses the playback */
kRecorderPlaybackPause = 3, /**< kRecorderPlaybackPause, internal state when user pauses the playback */
kRecorderUpdate = 4 /**< kRecorderUpdate, playback existing recording and update all hashes */
};
void init(const Common::String &recordFileName, RecordMode mode);
@ -237,6 +238,7 @@ private:
uint32 _lastScreenshotTime;
uint32 _screenshotPeriod;
Common::PlaybackFile *_playbackFile;
Common::PlaybackFile *_recordFile;
void saveScreenShot();
void checkRecordedMD5();