#include "stdafx.h" #include "../Utilities/ZipReader.h" #include "../Utilities/StringUtilities.h" #include "../Utilities/HexUtilities.h" #include "MesenMovie.h" #include "MessageManager.h" #include "ControlManager.h" #include "BaseControlDevice.h" #include "Console.h" #include "SaveStateManager.h" #include "CheatManager.h" #include "MovieRecorder.h" #include "BatteryManager.h" #include "VirtualFile.h" #include "NotificationManager.h" #include "RomData.h" MesenMovie::MesenMovie(shared_ptr console) { _console = console; } MesenMovie::~MesenMovie() { Stop(); } void MesenMovie::Stop() { if(_playing) { MessageManager::DisplayMessage("Movies", "MovieEnded"); _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::MovieEnded); if(_console->GetSettings()->CheckFlag(EmulationFlags::PauseOnMovieEnd)) { _console->GetSettings()->SetFlags(EmulationFlags::Paused); } _playing = false; } _console->GetSettings()->SetInputPollScanline(241); _console->GetControlManager()->UnregisterInputProvider(this); } bool MesenMovie::SetInput(BaseControlDevice *device) { uint32_t inputRowIndex = _console->GetControlManager()->GetPollCounter(); if(_inputData.size() > inputRowIndex && _inputData[inputRowIndex].size() > _deviceIndex) { device->SetTextState(_inputData[inputRowIndex][_deviceIndex]); _deviceIndex++; if(_deviceIndex >= _inputData[inputRowIndex].size()) { //Move to the next frame's data _deviceIndex = 0; } } else { Stop(); } return true; } bool MesenMovie::IsPlaying() { return _playing; } vector MesenMovie::LoadBattery(string extension) { vector batteryData; _reader->ExtractFile("Battery" + extension, batteryData); return batteryData; } void MesenMovie::ProcessNotification(ConsoleNotificationType type, void * parameter) { if(type == ConsoleNotificationType::GameLoaded) { _console->GetControlManager()->RegisterInputProvider(this); } } bool MesenMovie::Play(VirtualFile &file) { _movieFile = file; std::stringstream ss; file.ReadFile(ss); _reader.reset(new ZipReader()); _reader->LoadArchive(ss); stringstream settingsData, inputData; if(!_reader->GetStream("GameSettings.txt", settingsData)) { MessageManager::Log("[Movie] File not found: GameSettings.txt"); return false; } if(!_reader->GetStream("Input.txt", inputData)) { MessageManager::Log("[Movie] File not found: Input.txt"); 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); _console->Pause(); _console->GetBatteryManager()->SetBatteryProvider(shared_from_this()); _console->GetNotificationManager()->RegisterNotificationListener(shared_from_this()); ApplySettings(); //Disable auto-configure input option (otherwise the movie file's input types are ignored) bool autoConfigureInput = _console->GetSettings()->CheckFlag(EmulationFlags::AutoConfigureInput); _console->GetSettings()->ClearFlags(EmulationFlags::AutoConfigureInput); ControlManager *controlManager = _console->GetControlManager(); if(controlManager) { //ControlManager can be empty if no game is loaded controlManager->SetPollCounter(0); } bool gameLoaded = LoadGame(); _console->GetSettings()->SetFlagState(EmulationFlags::AutoConfigureInput, autoConfigureInput); if(!gameLoaded) { _console->Resume(); return false; } stringstream saveStateData; if(_reader->GetStream("SaveState.mst", saveStateData)) { if(!_console->GetSaveStateManager()->LoadState(saveStateData, true)) { _console->Resume(); return false; } else { _console->GetControlManager()->SetPollCounter(0); } } _playing = true; _console->Resume(); return true; } template T FromString(string name, const vector &enumNames, T defaultValue) { for(size_t i = 0; i < enumNames.size(); i++) { if(name == enumNames[i]) { return (T)i; } } return defaultValue; } void MesenMovie::ParseSettings(stringstream &data) { while(!data.eof()) { string line; std::getline(data, line); if(!line.empty()) { size_t index = line.find_first_of(' '); if(index != string::npos) { string name = line.substr(0, index); string value = line.substr(index + 1); if(name == "Cheat") { _cheats.push_back(value); } else { _settings[name] = value; } } } } } bool MesenMovie::LoadGame() { string mesenVersion = LoadString(_settings, MovieKeys::MesenVersion); string gameFile = LoadString(_settings, MovieKeys::GameFile); string sha1Hash = LoadString(_settings, MovieKeys::Sha1); //string patchFile = LoadString(_settings, MovieKeys::PatchFile); //string patchFileSha1 = LoadString(_settings, MovieKeys::PatchFileSha1); //string patchedRomSha1 = LoadString(_settings, MovieKeys::PatchedRomSha1); if(_console->GetSettings()->CheckFlag(EmulationFlags::AllowMismatchingSaveState) && _console->GetRomInfo().RomName == gameFile) { //Loaded game has the right name, and we don't want to validate the hash values _console->PowerCycle(); return true; } HashInfo hashInfo; hashInfo.Sha1 = sha1Hash; VirtualFile romFile = _console->FindMatchingRom(gameFile, hashInfo); bool gameLoaded = false; if(romFile.IsValid()) { VirtualFile patchFile(_movieFile.GetFilePath(), "PatchData.dat"); if(patchFile.IsValid()) { gameLoaded = _console->Initialize(romFile, patchFile); } else { gameLoaded = _console->Initialize(romFile); } } return gameLoaded; } void MesenMovie::ApplySettings() { EmulationSettings* settings = _console->GetSettings(); NesModel region = FromString(LoadString(_settings, MovieKeys::Region), NesModelNames, NesModel::NTSC); ConsoleType consoleType = FromString(LoadString(_settings, MovieKeys::ConsoleType), ConsoleTypeNames, ConsoleType::Nes); ControllerType controller1 = FromString(LoadString(_settings, MovieKeys::Controller1), ControllerTypeNames, ControllerType::None); ControllerType controller2 = FromString(LoadString(_settings, MovieKeys::Controller2), ControllerTypeNames, ControllerType::None); ControllerType controller3 = FromString(LoadString(_settings, MovieKeys::Controller3), ControllerTypeNames, ControllerType::None); ControllerType controller4 = FromString(LoadString(_settings, MovieKeys::Controller4), ControllerTypeNames, ControllerType::None); ExpansionPortDevice expansionDevice = FromString(LoadString(_settings, MovieKeys::ExpansionDevice), ExpansionPortDeviceNames, ExpansionPortDevice::None); settings->SetNesModel(region); settings->SetConsoleType(consoleType); settings->SetControllerType(0, controller1); settings->SetControllerType(1, controller2); settings->SetControllerType(2, controller3); settings->SetControllerType(3, controller4); settings->SetExpansionDevice(expansionDevice); uint32_t ramPowerOnState = LoadInt(_settings, MovieKeys::RamPowerOnState); if(ramPowerOnState == 0xFF) { settings->SetRamPowerOnState(RamPowerOnState::AllOnes); } else { settings->SetRamPowerOnState(RamPowerOnState::AllZeros); } settings->SetInputPollScanline(LoadInt(_settings, MovieKeys::InputPollScanline, 240)); settings->SetZapperDetectionRadius(LoadInt(_settings, MovieKeys::ZapperDetectionRadius)); settings->SetPpuNmiConfig( LoadInt(_settings, MovieKeys::ExtraScanlinesBeforeNmi), LoadInt(_settings, MovieKeys::ExtraScanlinesAfterNmi) ); settings->SetFlagState(EmulationFlags::DisablePpu2004Reads, LoadBool(_settings, MovieKeys::DisablePpu2004Reads)); settings->SetFlagState(EmulationFlags::DisablePaletteRead, LoadBool(_settings, MovieKeys::DisablePaletteRead)); settings->SetFlagState(EmulationFlags::DisableOamAddrBug, LoadBool(_settings, MovieKeys::DisableOamAddrBug)); settings->SetFlagState(EmulationFlags::UseNes101Hvc101Behavior, LoadBool(_settings, MovieKeys::UseNes101Hvc101Behavior)); settings->SetFlagState(EmulationFlags::EnableOamDecay, LoadBool(_settings, MovieKeys::EnableOamDecay)); settings->SetFlagState(EmulationFlags::DisablePpuReset, LoadBool(_settings, MovieKeys::DisablePpuReset)); settings->SetDipSwitches(HexUtilities::FromHex(LoadString(_settings, MovieKeys::DipSwitches))); LoadCheats(); } uint32_t MesenMovie::LoadInt(std::unordered_map &settings, string name, uint32_t defaultValue) { auto result = settings.find(name); if(result != settings.end()) { try { return (uint32_t)std::stoul(result->second); } catch(std::exception&) { MessageManager::Log("[Movies] Invalid value for tag: " + name); return defaultValue; } } else { return defaultValue; } } bool MesenMovie::LoadBool(std::unordered_map &settings, string name) { auto result = settings.find(name); if(result != settings.end()) { if(result->second == "true") { return true; } else if(result->second == "false") { return false; } else { MessageManager::Log("[Movies] Invalid value for tag: " + name); return false; } } else { return false; } } string MesenMovie::LoadString(std::unordered_map &settings, string name) { auto result = settings.find(name); if(result != settings.end()) { return result->second; } else { return ""; } } void MesenMovie::LoadCheats() { vector cheats; for(string cheatData : _cheats) { CodeInfo code; if(LoadCheat(cheatData, code)) { cheats.push_back(code); } } _console->GetCheatManager()->SetCheats(cheats); } bool MesenMovie::LoadCheat(string cheatData, CodeInfo &code) { vector data = StringUtilities::Split(cheatData, ' '); if(data.size() >= 3) { uint32_t address = HexUtilities::FromHex(data[0]); uint8_t value = HexUtilities::FromHex(data[1]); bool relativeAddress = data[2] == "true"; int32_t compareValue = data.size() > 3 ? HexUtilities::FromHex(data[3]) : -1; code.Address = address; code.Value = value; code.IsRelativeAddress = relativeAddress; code.CompareValue = compareValue; return true; } else { MessageManager::Log("[Movie] Invalid cheat definition: " + cheatData); } return false; }