// Adapted from OpenAI's retro source code: // https://github.com/openai/retro #include "data.h" //#include "script.h" #include "utils.h" #ifdef ERROR #undef ERROR #endif #include "json.hpp" #include using namespace Retro; using namespace std; using nlohmann::json; static string s_dataDirectory; template T find(json::const_reference j, const string& key) { const auto& iter = j.find(key); if (iter == j.end()) { return T(); } try { T t = *iter; return t; } catch (json::exception&) { return T(); } } static void setActions(const vector& buttonList, const vector>>& actionsIn, map>& actions) { actions.clear(); for (const auto& outer : actionsIn) { set sublist; int mask = 0; for (const auto& middle : outer) { int buttons = 0; for (const auto& button : middle) { const auto& iter = find(buttonList.begin(), buttonList.end(), button); buttons |= 1 << (iter - buttonList.begin()); } mask |= buttons; sublist.insert(buttons); } actions.emplace(mask, move(sublist)); } } static unsigned filterAction(unsigned action, const map>& actions) { unsigned newAction = 0; for (const auto& actionSet : actions) { unsigned maskedAction = action & actionSet.first; if (actionSet.second.find(maskedAction) != actionSet.second.end()) { newAction |= maskedAction; } } return newAction; } Variable::Variable(const DataType& type, size_t address, uint64_t mask) : type(type) , address(address) , mask(mask) { } bool Variable::operator==(const Variable& other) const { return type == other.type && address == other.address && mask == other.mask; } bool GameData::load(const string& filename) { ifstream file(filename); return load(&file); } bool GameData::load(istream* file) { json manifest; try { *file >> manifest; } catch (json::exception&) { return false; } const auto& info = const_cast(manifest).find("info"); if (info == manifest.cend()) { return false; } unordered_map oldVars; oldVars.swap(m_vars); for (auto var = info->cbegin(); var != info->cend(); ++var) { if (var->find("address") == var->cend() || var->find("type") == var->cend()) { oldVars.swap(m_vars); return false; } string dtype = var->at("type"); if (dtype.size() < 3) { continue; } try { Variable v(dtype, var->at("address"), var->value("mask", UINT64_MAX)); setVariable(var.key(), v); } catch (std::out_of_range) { continue; } } return true; } bool GameData::save(const string& filename) const { ofstream file(filename); return save(&file); } bool GameData::save(ostream* file) const { json manifest; json info; for (const auto& var : m_vars) { json jvar; jvar["address"] = var.second.address; jvar["type"] = var.second.type.type; if (var.second.mask != UINT64_MAX) { jvar["mask"] = var.second.mask; } info[var.first] = jvar; } manifest["info"] = info; try { file->width(2); *file << manifest; *file << endl; } catch (json::exception&) { return false; } return true; } string GameData::dataPath(const string& hint) { if (s_dataDirectory.size()) { return s_dataDirectory; } const char* envDir = getenv("RETRO_DATA_PATH"); if (envDir) { s_dataDirectory = envDir; } else { s_dataDirectory = drillUp({ "retro/data", "data" }, ".", hint); } return s_dataDirectory; } void GameData::reset() { restart(); m_lastMem.reset(); m_cloneMem.reset(); m_vars.clear(); //m_searches.clear(); m_searchOldMem.clear(); } void GameData::restart() { m_customVars.clear(); } void GameData::updateRam() { m_lastMem = move(m_cloneMem); m_cloneMem.clone(m_mem); } void GameData::setTypes(const vector types) { m_types = vector(types); } void GameData::setButtons(const vector& buttons) { m_buttons = buttons; } vector GameData::buttons() const { return m_buttons; } void GameData::setActions(const vector>>& actions) { ::setActions(m_buttons, actions, m_actions); } map> GameData::validActions() const { return m_actions; } unsigned GameData::filterAction(unsigned action) const { return ::filterAction(action, m_actions); } Datum GameData::lookupValue(const string& name) { auto variant = m_customVars.find(name); if (variant != m_customVars.end()) { return Datum(variant->second.get()); } auto v = m_vars.find(name); if (v == m_vars.end()) { throw invalid_argument(name); } return m_mem[v->second]; } Variant GameData::lookupValue(const string& name) const { auto variant = m_customVars.find(name); if (variant != m_customVars.end()) { return *variant->second; } auto v = m_vars.find(name); if (v == m_vars.end()) { throw invalid_argument(name); } return m_mem[v->second]; } /*Datum GameData::lookupValue(const TypedSearchResult& result) { return m_mem[Variable{ result.type, result.address }]; } int64_t GameData::lookupValue(const TypedSearchResult& result) const { return m_mem[Variable{ result.type, result.address }]; }*/ int64_t GameData::lookupDelta(const string& name) const { const auto& v = m_vars.find(name); if (v == m_vars.end()) { return 0; } int64_t newVal = m_cloneMem[v->second]; if (!m_lastMem.ok()) { return 0; } int64_t oldVal = m_lastMem[v->second]; return newVal - oldVal; } unordered_map GameData::lookupAll() { unordered_map data; for (auto var = m_vars.cbegin(); var != m_vars.cend(); ++var) { try { data.emplace(var->first, m_mem[var->second]); } catch (...) { } } for (auto var = m_customVars.cbegin(); var != m_customVars.cend(); ++var) { data.emplace(var->first, var->second.get()); } return data; } unordered_map GameData::lookupAll() const { unordered_map data; for (auto var = m_vars.cbegin(); var != m_vars.cend(); ++var) { try { data.emplace(var->first, m_mem[var->second]); } catch (...) { } } for (auto var = m_customVars.cbegin(); var != m_customVars.cend(); ++var) { data.emplace(var->first, *var->second); } return data; } void GameData::setValue(const std::string& name, int64_t v) { auto variant = m_customVars.find(name); if (variant != m_customVars.end()) { *variant->second = v; return; } auto var = m_vars.find(name); if (var != m_vars.end()) { m_mem[var->second] = v; return; } m_customVars.emplace(name, std::make_unique(v)); } void GameData::setValue(const std::string& name, const Variant& v) { auto variant = m_customVars.find(name); if (variant != m_customVars.end()) { *variant->second = v; return; } auto var = m_vars.find(name); if (var != m_vars.end()) { m_mem[var->second] = v; return; } m_customVars.emplace(name, std::make_unique(v)); } Variable GameData::getVariable(const string& name) const { const auto& v = m_vars.find(name); if (v == m_vars.end()) { throw invalid_argument(name); } return v->second; } void GameData::setVariable(const string& name, const Variable& var) { removeVariable(name); m_vars.emplace(name, var); } void GameData::removeVariable(const string& name) { auto iter = m_vars.find(name); if (iter != m_vars.end()) { m_vars.erase(iter); } } unordered_map GameData::listVariables() const { return m_vars; } size_t GameData::numVariables() const { return m_vars.size(); } /*void GameData::search(const std::string& name, int64_t value) { if (m_searches.find(name) == m_searches.cend()) { if (m_types.size()) { m_searches.emplace(name, Search{ m_types }); } else { m_searches.emplace(name, Search{}); } } Search* search = &m_searches[name]; search->search(m_mem, value); m_searchOldMem[name].clone(m_mem); }*/ /*void GameData::deltaSearch(const std::string& name, Operation op, int64_t reference) { if (m_searches.find(name) == m_searches.cend()) { if (m_types.size()) { m_searches.emplace(name, Search{ m_types }); } else { m_searches.emplace(name, Search{}); } } if (m_searchOldMem.find(name) == m_searchOldMem.cend()) { m_searchOldMem[name].clone(m_mem); } Search* search = &m_searches[name]; search->delta(m_mem, m_searchOldMem[name], op, reference); m_searchOldMem[name].clone(m_mem); }* size_t GameData::numSearches() const { return m_searches.size(); } vector GameData::listSearches() const { vector names; for (const auto& search : m_searches) { names.emplace_back(search.first); } return names; } Search* GameData::getSearch(const string& name) { auto iter = m_searches.find(name); if (iter != m_searches.end()) { return &iter->second; } return nullptr; } void GameData::removeSearch(const string& name) { auto iter = m_searches.find(name); if (iter != m_searches.end()) { m_searches.erase(iter); } }*/ static const vector> s_ops{ make_pair("equal", Operation::EQUAL), make_pair("negative-equal", Operation::NEGATIVE_EQUAL), make_pair("not-equal", Operation::NOT_EQUAL), make_pair("less-than", Operation::LESS_THAN), make_pair("greater-than", Operation::GREATER_THAN), make_pair("less-or-equal", Operation::LESS_OR_EQUAL), make_pair("greater-or-equal", Operation::GREATER_OR_EQUAL), make_pair("less-or-equal", Operation::LESS_OR_EQUAL), make_pair("nonzero", Operation::NONZERO), make_pair("zero", Operation::ZERO), make_pair("negative", Operation::NEGATIVE), make_pair("positive", Operation::POSITIVE), make_pair("sign", Operation::SIGN) };