Mesen/Core/MesenMovie.cpp

341 lines
9.9 KiB
C++

#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 = 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<uint8_t> MesenMovie::LoadBattery(string extension)
{
vector<uint8_t> 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<typename T>
T FromString(string name, const vector<string> &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<ExpansionPortDevice>(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<string, string> &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<string, string> &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<string, string> &settings, string name)
{
auto result = settings.find(name);
if(result != settings.end()) {
return result->second;
} else {
return "";
}
}
void MesenMovie::LoadCheats()
{
vector<CodeInfo> 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<string> 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;
}