#include "stdafx.h" #include #include "../Utilities/HexUtilities.h" #include "../Utilities/FolderUtilities.h" #include "../Utilities/ZipWriter.h" #include "../Utilities/VirtualFile.h" #include "MovieRecorder.h" #include "MessageManager.h" #include "ControlManager.h" #include "BaseControlDevice.h" #include "Console.h" #include "EmuSettings.h" #include "SaveStateManager.h" #include "NotificationManager.h" #include "RewindData.h" #include "MovieTypes.h" #include "BatteryManager.h" MovieRecorder::MovieRecorder(shared_ptr console) { _console = console; } MovieRecorder::~MovieRecorder() { } bool MovieRecorder::Record(RecordMovieOptions options) { _filename = options.Filename; _author = options.Author; _description = options.Description; _writer.reset(new ZipWriter()); _inputData = stringstream(); _saveStateData = stringstream(); _hasSaveState = false; if(!_writer->Initialize(_filename)) { MessageManager::DisplayMessage("Movies", "CouldNotWriteToFile", FolderUtilities::GetFilename(_filename, true)); _writer.reset(); return false; } else { _console->Lock(); _console->GetNotificationManager()->RegisterNotificationListener(shared_from_this()); if(options.RecordFrom == RecordMovieFrom::StartWithoutSaveData) { //Power cycle and ignore save data that exists on the disk _console->GetBatteryManager()->SetBatteryProvider(shared_from_this()); _console->PowerCycle(); } else if(options.RecordFrom == RecordMovieFrom::StartWithSaveData) { //Power cycle and save existing battery files into the movie file _console->GetBatteryManager()->SetBatteryRecorder(shared_from_this()); _console->PowerCycle(); } else if(options.RecordFrom == RecordMovieFrom::CurrentState) { //Record from current state, store a save state in the movie file _console->GetControlManager()->RegisterInputRecorder(this); _console->GetSaveStateManager()->SaveState(_saveStateData); _hasSaveState = true; } _console->GetBatteryManager()->SetBatteryRecorder(nullptr); _console->Unlock(); MessageManager::DisplayMessage("Movies", "MovieRecordingTo", FolderUtilities::GetFilename(_filename, true)); return true; } } void MovieRecorder::GetGameSettings(stringstream &out) { EmuSettings* settings = _console->GetSettings().get(); EmulationConfig emuConfig = settings->GetEmulationConfig(); InputConfig inputConfig = settings->GetInputConfig(); WriteString(out, MovieKeys::MesenVersion, settings->GetVersionString()); WriteInt(out, MovieKeys::MovieFormatVersion, MovieRecorder::MovieFormatVersion); VirtualFile romFile = _console->GetRomInfo().RomFile; WriteString(out, MovieKeys::GameFile, romFile.GetFileName()); WriteString(out, MovieKeys::Sha1, romFile.GetSha1Hash()); VirtualFile patchFile = _console->GetRomInfo().PatchFile; if(patchFile.IsValid()) { WriteString(out, MovieKeys::PatchFile, patchFile.GetFileName()); WriteString(out, MovieKeys::PatchFileSha1, patchFile.GetSha1Hash()); romFile.ApplyPatch(patchFile); WriteString(out, MovieKeys::PatchedRomSha1, romFile.GetSha1Hash()); } ConsoleRegion region = _console->GetRegion(); switch(region) { case ConsoleRegion::Auto: case ConsoleRegion::Ntsc: WriteString(out, MovieKeys::Region, "NTSC"); break; case ConsoleRegion::Pal: WriteString(out, MovieKeys::Region, "PAL"); break; } WriteString(out, MovieKeys::Controller1, ControllerTypeNames[(int)inputConfig.Controllers[0].Type]); WriteString(out, MovieKeys::Controller2, ControllerTypeNames[(int)inputConfig.Controllers[1].Type]); WriteString(out, MovieKeys::Controller3, ControllerTypeNames[(int)inputConfig.Controllers[2].Type]); WriteString(out, MovieKeys::Controller4, ControllerTypeNames[(int)inputConfig.Controllers[3].Type]); WriteString(out, MovieKeys::Controller5, ControllerTypeNames[(int)inputConfig.Controllers[4].Type]); WriteInt(out, MovieKeys::ExtraScanlinesBeforeNmi, emuConfig.PpuExtraScanlinesBeforeNmi); WriteInt(out, MovieKeys::ExtraScanlinesAfterNmi, emuConfig.PpuExtraScanlinesAfterNmi); switch(emuConfig.RamPowerOnState) { case RamState::AllZeros: WriteString(out, MovieKeys::RamPowerOnState, "AllZeros"); break; case RamState::AllOnes: WriteString(out, MovieKeys::RamPowerOnState, "AllOnes"); break; case RamState::Random: WriteString(out, MovieKeys::RamPowerOnState, "AllOnes"); break; //TODO: Random memory isn't supported for movies yet } } void MovieRecorder::WriteString(stringstream &out, string name, string value) { out << name << " " << value << "\n"; } void MovieRecorder::WriteInt(stringstream &out, string name, uint32_t value) { out << name << " " << std::to_string(value) << "\n"; } void MovieRecorder::WriteBool(stringstream &out, string name, bool enabled) { out << name << " " << (enabled ? "true" : "false") << "\n"; } bool MovieRecorder::Stop() { if(_writer) { _console->GetControlManager()->UnregisterInputRecorder(this); _writer->AddFile(_inputData, "Input.txt"); stringstream out; GetGameSettings(out); _writer->AddFile(out, "GameSettings.txt"); if(!_author.empty() || !_description.empty()) { stringstream movieInfo; WriteString(movieInfo, "Author", _author); movieInfo << "Description\n" << _description; _writer->AddFile(movieInfo, "MovieInfo.txt"); } VirtualFile patchFile = _console->GetRomInfo().PatchFile; vector patchData; if(patchFile.IsValid() && patchFile.ReadFile(patchData)) { _writer->AddFile(patchData, "PatchData.dat"); } if(_hasSaveState) { _writer->AddFile(_saveStateData, "SaveState.mss"); } for(auto kvp : _batteryData) { _writer->AddFile(kvp.second, "Battery" + kvp.first); } bool result = _writer->Save(); if(result) { MessageManager::DisplayMessage("Movies", "MovieSaved", FolderUtilities::GetFilename(_filename, true)); } return result; } return false; } void MovieRecorder::RecordInput(vector> devices) { for(shared_ptr &device : devices) { _inputData << ("|" + device->GetTextState()); } _inputData << "\n"; } void MovieRecorder::OnLoadBattery(string extension, vector batteryData) { _batteryData[extension] = batteryData; } vector MovieRecorder::LoadBattery(string extension) { return vector(); } void MovieRecorder::ProcessNotification(ConsoleNotificationType type, void *parameter) { if(type == ConsoleNotificationType::GameLoaded) { _console->GetControlManager()->RegisterInputRecorder(this); } } /* bool MovieRecorder::CreateMovie(string movieFile, std::deque &data, uint32_t startPosition, uint32_t endPosition) { _filename = movieFile; _writer.reset(new ZipWriter()); if(startPosition < data.size() && endPosition <= data.size() && _writer->Initialize(_filename)) { vector> devices = _console->GetControlManager()->GetControlDevices(); if(startPosition > 0 || _console->GetRomInfo().HasBattery || _console->GetSettings()->GetRamPowerOnState() == RamPowerOnState::Random) { //Create a movie from a savestate if we don't start from the beginning (or if the game has save ram, or if the power on ram state is random) _hasSaveState = true; _saveStateData = stringstream(); _console->GetSaveStateManager()->GetSaveStateHeader(_saveStateData); data[startPosition].GetStateData(_saveStateData); } _inputData = stringstream(); for(uint32_t i = startPosition; i < endPosition; i++) { RewindData rewindData = data[i]; for(uint32_t i = 0; i < 30; i++) { for(shared_ptr &device : devices) { uint8_t port = device->GetPort(); if(i < rewindData.InputLogs[port].size()) { device->SetRawState(rewindData.InputLogs[port][i]); _inputData << ("|" + device->GetTextState()); } } _inputData << "\n"; } } //Write the movie file return Stop(); } return false; } */