Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Core/Shared/Emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ bool Emulator::InternalLoadRom(VirtualFile romFile, VirtualFile patchFile, bool
//Restore pollcounter (used by movies when a power cycle is in the movie)
_console->GetControlManager()->SetPollCounter(pollCounter);

_notificationManager->SendNotification(ConsoleNotificationType::AfterInitConsole);

_rewindManager->InitHistory();

if(debuggerActive || _settings->CheckFlag(EmulationFlags::ConsoleMode)) {
Expand Down
1 change: 1 addition & 0 deletions Core/Shared/Interfaces/INotificationListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum class ConsoleNotificationType
CheatsChanged,
RequestConfigChange,
RefreshSoftwareRenderer,
AfterInitConsole,
};

struct GameLoadedEventParams
Expand Down
95 changes: 56 additions & 39 deletions Core/Shared/Movies/MesenMovie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#include "Shared/CheatManager.h"
#include "Utilities/ZipReader.h"
#include "Utilities/StringUtilities.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/VirtualFile.h"
#include "Utilities/magic_enum.hpp"
#include "Utilities/Serializer.h"
Expand Down Expand Up @@ -95,11 +94,35 @@ vector<uint8_t> MesenMovie::LoadBattery(string extension)

void MesenMovie::ProcessNotification(ConsoleNotificationType type, void* parameter)
{
if(type == ConsoleNotificationType::GameLoaded) {
if(type == ConsoleNotificationType::AfterInitConsole) {
_controlManager = _emu->GetConsole()->GetControlManager();

_emu->RegisterInputProvider(this);
shared_ptr<IConsole> console = _emu->GetConsole();
if(console) {
console->GetControlManager()->SetPollCounter(_lastPollCounter);
_controlManager->SetPollCounter(_lastPollCounter + 1);
}

//Re-apply settings - power cycling can alter some (e.g auto-configure input types, etc.)
ApplySettings(_settingsData);

_originalCheats = _emu->GetCheatManager()->GetCheats();

LoadCheats();

if(!_playing) {
stringstream saveStateData;
if(_reader->GetStream("SaveState.mss", saveStateData)) {
if(!_emu->GetSaveStateManager()->LoadState(saveStateData)) {
_loadFailure = true;
}
}
}

_controlManager->UpdateControlDevices();

if(!_playing) {
_controlManager->SetPollCounter(0);
}
}
}
Expand All @@ -114,8 +137,10 @@ bool MesenMovie::Play(VirtualFile& file)
_reader.reset(new ZipReader());
_reader->LoadArchive(ss);

stringstream settingsData, inputData;
if(!_reader->GetStream("GameSettings.txt", settingsData)) {
_hasSaveState = _reader->CheckFile("SaveState.mss");

stringstream inputData;
if(!_reader->GetStream("GameSettings.txt", _settingsData)) {
MessageManager::Log("[Movie] File not found: GameSettings.txt");
return false;
}
Expand All @@ -124,17 +149,7 @@ bool MesenMovie::Play(VirtualFile& file)
return false;
}

while(inputData) {
string line;
std::getline(inputData, line);
if(line.substr(0, 1) == "|") {
_inputData.push_back(StringUtilities::Split(line.substr(1), '|'));
}
}

_deviceIndex = 0;

ParseSettings(settingsData);
ParseSettings(_settingsData);

string version = LoadString(_settings, MovieKeys::MesenVersion);
if(version.size() < 2 || version.substr(0, 2) == "0." || version.substr(0, 2) == "1.") {
Expand All @@ -143,43 +158,45 @@ bool MesenMovie::Play(VirtualFile& file)
return false;
}

if(LoadInt(_settings, MovieKeys::MovieFormatVersion, 0) < 2) {
uint32_t movieVersion = LoadInt(_settings, MovieKeys::MovieFormatVersion, 0);
if(movieVersion < 2) {
MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion");
return false;
}

while(inputData) {
string line;
std::getline(inputData, line);
if(line.substr(0, 1) == "|") {
vector<string> lineInputData = StringUtilities::Split(line.substr(1), '|');
if(movieVersion == 2 && _inputData.empty() && !_hasSaveState) {
//Version 2 of movies incorrectly skipped the first frame after recording from power on
//When playing back an old movie, add an extra frame of input at the start (no buttons pressed)
//to allow playback to match what it was before this bug was fixed.
_inputData.push_back(vector<string>(lineInputData.size(), ""));
}
_inputData.push_back(lineInputData);
}
}

_deviceIndex = 0;

auto emuLock = _emu->AcquireLock(false);

if(!ApplySettings(settingsData)) {
if(!ApplySettings(_settingsData)) {
return false;
}

_emu->GetBatteryManager()->SetBatteryProvider(shared_from_this());
_emu->GetNotificationManager()->RegisterNotificationListener(shared_from_this());

_emu->PowerCycle();

//Re-apply settings - power cycling can alter some (e.g auto-configure input types, etc.)
ApplySettings(settingsData);

_originalCheats = _emu->GetCheatManager()->GetCheats();

_controlManager = _emu->GetConsole()->GetControlManager();

LoadCheats();

stringstream saveStateData;
if(_reader->GetStream("SaveState.mss", saveStateData)) {
if(!_emu->GetSaveStateManager()->LoadState(saveStateData)) {
return false;
}
_emu->GetBatteryManager()->SetBatteryProvider(nullptr);
if(_hasSaveState) {
_controlManager->SetPollCounter(0);
}

_controlManager->UpdateControlDevices();
_controlManager->SetPollCounter(0);
_playing = true;

return true;
_playing = !_loadFailure;
return _playing;
}

template<typename T>
Expand Down
3 changes: 3 additions & 0 deletions Core/Shared/Movies/MesenMovie.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ class MesenMovie final : public IMovie, public INotificationListener, public IBa
vector<string> _cheats;
vector<CheatCode> _originalCheats;
stringstream _emuSettingsBackup;
stringstream _settingsData;
unordered_map<string, string> _settings;
string _filename;
bool _forTest = false;
bool _loadFailure = false;
bool _hasSaveState = false;

private:
void ParseSettings(stringstream& data);
Expand Down
25 changes: 23 additions & 2 deletions Core/Shared/Movies/MovieRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ bool MovieRecorder::Record(RecordMovieOptions options)
if(needSaveState) {
_emu->GetSaveStateManager()->SaveState(_saveStateData);
_hasSaveState = true;

//Get rid of any inputs recorded while the game was reloaded
_inputData = stringstream();
}

_emu->GetBatteryManager()->SetBatteryProvider(nullptr);
_emu->GetBatteryManager()->SetBatteryRecorder(nullptr);
_emu->Unlock();

Expand Down Expand Up @@ -189,7 +193,7 @@ vector<uint8_t> MovieRecorder::LoadBattery(string extension)

void MovieRecorder::ProcessNotification(ConsoleNotificationType type, void* parameter)
{
if(type == ConsoleNotificationType::GameLoaded) {
if(type == ConsoleNotificationType::AfterInitConsole) {
_emu->RegisterInputRecorder(this);
}
}
Expand All @@ -216,9 +220,24 @@ bool MovieRecorder::CreateMovie(string movieFile, deque<RewindData>& data, uint3

_inputData = stringstream();

//When a save state is part of the data and the startPosition is 0, skip the first input
//This is a workaround to deal with the fact that the rewindmanager's first save state after loading
//the ROM is taken at a different time (before vs after the inputs for the first frame are polled) compared
//to regular movie save states. Ignoring the first frame's input allows us to get the correct result
//in the vast majority of scenarios
bool skipFirstInput = _hasSaveState && startPosition == 0;

for(uint32_t i = startPosition; i < endPosition; i++) {
RewindData rewindData = data[i];
for(uint32_t j = 0; j < RewindManager::BufferSize; j++) {
uint32_t len = 0;

//Some blocks (like the first block after power cycle) can contain an extra set of inputs
//Check the max number of inputs to be able to export them all
for(int j = 0; j < BaseControlDevice::PortCount; j++) {
len = std::max(len, (uint32_t)rewindData.InputLogs[j].size());
}

for(uint32_t j = skipFirstInput ? 1 : 0; j < len; j++) {
for(shared_ptr<BaseControlDevice>& device : devices) {
uint8_t port = device->GetPort();
if(j < rewindData.InputLogs[port].size()) {
Expand All @@ -228,6 +247,8 @@ bool MovieRecorder::CreateMovie(string movieFile, deque<RewindData>& data, uint3
}
_inputData << "\n";
}

skipFirstInput = false;
}

//Write the movie file
Expand Down
2 changes: 1 addition & 1 deletion Core/Shared/Movies/MovieRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Emulator;
class MovieRecorder final : public INotificationListener, public IInputRecorder, public IBatteryRecorder, public IBatteryProvider, public std::enable_shared_from_this<MovieRecorder>
{
private:
static const uint32_t MovieFormatVersion = 2;
static const uint32_t MovieFormatVersion = 3;

Emulator* _emu;
string _filename;
Expand Down
6 changes: 6 additions & 0 deletions Core/Shared/RewindManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ void RewindManager::Stop()
{
if(_rewindState >= RewindState::Starting) {
auto lock = _emu->AcquireLock();
if(_rewindState < RewindState::Starting) {
//Rewind state was changed while waiting for the lock, stop processing
//Fixes crash when a power cycle occurs at the same time as rewind attempts to stop
return;
}

if(_rewindState == RewindState::Started) {
//Move back to the save state containing the frame currently shown on the screen
if(_historyBackup.size() > 1) {
Expand Down
Loading