#include "movie-bk2.h" #include <cstring> #include <sstream> #include "coreinfo.h" using namespace std; using namespace Retro; #ifdef USE_LIBZIP static const unordered_map<string, char> s_keyNames{ make_pair("A", 'A'), make_pair("B", 'B'), make_pair("C", 'C'), make_pair("X", 'X'), make_pair("Y", 'Y'), make_pair("Z", 'Z'), make_pair("START", 'S'), make_pair("SELECT", 's'), make_pair("MODE", 'M'), make_pair("UP", 'U'), make_pair("DOWN", 'D'), make_pair("LEFT", 'L'), make_pair("RIGHT", 'R'), make_pair("L", 'l'), make_pair("R", 'r'), make_pair("BUTTON", 'B'), }; static const unordered_map<char, string> s_buttonNames{ make_pair('A', "A"), make_pair('B', "B"), make_pair('C', "C"), make_pair('X', "X"), make_pair('Y', "Y"), make_pair('Z', "Z"), make_pair('S', "Start"), make_pair('s', "Select"), make_pair('M', "Mode"), make_pair('U', "Up"), make_pair('D', "Down"), make_pair('L', "Left"), make_pair('R', "Right"), make_pair('l', "L"), make_pair('r', "R"), }; static const unordered_map<string, unordered_map<char, string>> s_platformButtonNames{ make_pair<string, unordered_map<char, string>>("Atari2600", { make_pair('B', "Button"), }), }; unique_ptr<Movie> MovieBK2::load(const string& path) { unique_ptr<Zip> zip = make_unique<Zip>(path); if (!zip->open()) { return nullptr; } if (!zip->openFile("Input Log.txt")) { return nullptr; } return make_unique<MovieBK2>(move(zip)); } MovieBK2::MovieBK2(unique_ptr<Zip> zip) : m_zip(move(zip)) , m_log(m_zip->openFile("Input Log.txt")) { string platform; Zip::File* header = m_zip->openFile("Header.txt"); while (true) { string headerLine = header->readline(); if (headerLine.empty()) { break; } if (headerLine.compare(0, 8, "Platform") == 0) { platform = headerLine.substr(9); if (platform == "GEN") { platform = "Genesis"; } else if (platform == "A26") { platform = "Atari2600"; } loadKeymap(platform); } if (headerLine.compare(0, 8, "GameName") == 0) { m_gameName = headerLine.substr(9); } } loadState(); } MovieBK2::MovieBK2(const std::string& path, bool write) : m_zip(make_unique<Zip>(path)) , m_write(write) { m_zip->open(write); m_log = m_zip->openFile("Input Log.txt", write); if (write) { stringstream headerText; headerText << "[Input]" << endl; m_log->write(static_cast<const void*>(headerText.str().c_str()), headerText.str().size()); } else { loadState(); } } MovieBK2::~MovieBK2() { close(); } string MovieBK2::getGameName() const { return m_gameName; } void MovieBK2::loadKeymap(const string& platform) { vector<string> buttons = Retro::buttons(platform); for (int i = 0; i < buttons.size(); ++i) { const auto& button = s_keyNames.find(buttons[i]); if (button != s_keyNames.end()) { m_keymap[button->second] = i; m_buttonmap[i] = button->second; } } if (m_write) { string realPlatform = platform; if (platform == "Genesis") { realPlatform = "GEN"; } else if (platform == "Atari2600") { realPlatform = "A26"; } m_coreName = platform; m_platform = realPlatform; } } void MovieBK2::setGameName(const std::string& name) { m_gameName = name; } void MovieBK2::writeHeader() { if (m_headerWritten) { return; } Zip::File* header = m_zip->openFile("Header.txt", true); stringstream headerText; headerText << "MovieVersion Retro" << endl; headerText << "Author ?" << endl; headerText << "emuVersion ?" << endl; headerText << "Platform " << m_platform << endl; headerText << "GameName " << m_gameName << endl; headerText << "SHA1 ?" << endl; headerText << "Core ?" << endl; headerText << "rerecordCount 1" << endl; header->write(static_cast<const void*>(headerText.str().c_str()), headerText.str().size()); headerText.str("LogKey:#Reset|Power|#"); for (const auto& key : m_buttonmap) { if (s_platformButtonNames.find(m_coreName) != s_platformButtonNames.end()) { const auto& platformButtons = s_platformButtonNames.at(m_coreName); if (platformButtons.find(key.second) != platformButtons.end()) { headerText << "P1 " << platformButtons.at(key.second) << "|"; continue; } } headerText << "P1 " << s_buttonNames.at(key.second) << "|"; } headerText << endl; m_headerWritten = true; m_log->write(static_cast<const void*>(headerText.str().c_str()), headerText.str().size()); } bool MovieBK2::step() { if (!m_log) { return false; } if (m_write) { if (!m_headerWritten) { writeHeader(); } stringstream line; line << "|..|"; for (const auto& key : m_buttonmap) { if (m_keys & (1 << key.first)) { line << key.second; } else { line << '.'; } } line << '|'; line << endl; m_log->write(static_cast<const void*>(line.str().c_str()), line.str().size()); m_keys = 0; return true; } else { string tmp = m_log->readline(); while (tmp.size() && tmp[0] != '|') { tmp = m_log->readline(); } if (!tmp.size()) { return false; } m_keys = 0; auto iter = tmp.begin(); if (*iter == '|') { ++iter; while (iter != tmp.end() && *iter != '|') { // Ignore commands ++iter; } if (iter == tmp.end()) { return false; } ++iter; while (iter != tmp.end() && *iter != '|') { if (*iter == '.') { ++iter; continue; } auto found = m_keymap.find(*iter); if (found != m_keymap.end()) { m_keys |= 1 << found->second; } ++iter; } return true; } } return false; } void MovieBK2::close() { if (!m_zip) { return; } if (m_write) { const char* footerText = "[/Input]"; m_log->write(static_cast<const void*>(footerText), strlen(footerText)); if (!m_state.empty()) { auto state = m_zip->openFile("Core.bin", true); state->write(m_state.data(), m_state.size()); state->flush(); } } m_zip->close(); m_zip.reset(); } bool MovieBK2::getState(vector<uint8_t>* state) const { if (m_state.empty()) { return false; } state->resize(m_state.size()); memcpy(state->data(), m_state.data(), m_state.size()); return true; } void MovieBK2::setState(const uint8_t* state, size_t size) { m_state.resize(size); memcpy(m_state.data(), state, size); } void MovieBK2::loadState() { Zip::File* state = m_zip->openFile("Core.bin"); if (!state) { m_state.clear(); return; } m_state.clear(); m_state.resize(2048); ssize_t read; while ((read = state->read(&*(m_state.end() - 2048), 2048)) == 2048) { m_state.resize(m_state.size() + 2048); } m_state.resize(m_state.size() - 2048 + read); } #endif