RetroArch/deps/game_ai_lib/utils/memory.cpp

522 lines
12 KiB
C++

// Adapted from OpenAI's retro source code:
// https://github.com/openai/retro
#include "memory.h"
#include <cstdlib>
#include <unordered_map>
#include <stdexcept>
#include <string.h>
using namespace Retro;
using namespace std;
Endian Retro::reduce(Endian e) {
switch (e) {
case Endian::BIG:
case Endian::LITTLE:
case Endian::UNDEF:
case Endian::MIXED_BL:
case Endian::MIXED_LB:
return e;
case Endian::NATIVE:
return Endian::REAL_NATIVE;
case Endian::MIXED_BN:
return Endian::REAL_MIXED_BN;
case Endian::MIXED_LN:
return Endian::REAL_MIXED_LN;
}
return e;
}
bool Retro::reduceCompare(Endian a, Endian b) {
return reduce(a) == reduce(b);
}
DataType::DataType(const char* type)
: width(type[strlen(type) - 1] - '0')
, endian(
type[0] == '=' ? Endian::NATIVE : type[0] == '>' ? (type[1] == '<' ? Endian::MIXED_BL : type[1] == '=' ? Endian::MIXED_BN : Endian::BIG) : type[0] == '<' ? (type[1] == '>' ? Endian::MIXED_LB : type[1] == '=' ? Endian::MIXED_LN : Endian::LITTLE) : Endian::UNDEF)
, repr(static_cast<Repr>(type[strlen(type) - 2]))
, type{ type[0], type[1], type[2], type[3] }
, maskLo(repr == Repr::LN_BCD || repr == Repr::BCD ? 0xF : 0xFF)
, maskHi(repr == Repr::BCD ? 0xF0 : 0x0)
, cvt(repr == Repr::BCD || repr == Repr::LN_BCD ? 10 : 256) {
uint64_t shiftInc =
repr == Repr::BCD ? 100 : repr == Repr::LN_BCD ? 10 : 256;
int baseLoc;
int baseEnd;
int halfLoc = -1;
int diff;
if (width > 8) {
throw std::out_of_range("Invalid DataType width");
}
switch (reduce(endian)) {
case Endian::LITTLE:
default:
baseLoc = 0;
baseEnd = width;
diff = 1;
break;
case Endian::BIG:
baseLoc = width - 1;
baseEnd = -1;
diff = -1;
break;
case Endian::MIXED_LB:
baseLoc = width / 2 - 1;
baseEnd = -1;
halfLoc = width - 1;
diff = -1;
break;
case Endian::MIXED_BL:
baseLoc = width / 2;
baseEnd = width;
halfLoc = 0;
diff = 1;
break;
}
uint64_t baseShift = 1;
for (int i = baseLoc; i != baseEnd; i += diff, baseShift *= shiftInc) {
shift[i] = baseShift;
}
if (halfLoc >= 0) {
for (int i = halfLoc; i != baseLoc; i += diff, baseShift *= shiftInc) {
shift[i] = baseShift;
}
}
}
DataType::DataType(const string& type)
: DataType(type.c_str()) {
}
Datum DataType::operator()(void* base) const {
return Datum(base, *this);
}
Datum DataType::operator()(void* base, size_t offset, const MemoryOverlay& overlay) const {
return Datum(base, offset, *this, overlay);
}
bool DataType::operator==(const DataType& other) const {
return width == other.width && endian == other.endian && repr == other.repr;
}
bool DataType::operator!=(const DataType& other) const {
return !(*this == other);
}
void DataType::encode(void* buffer, int64_t value) const {
for (size_t i = 0; i < width; ++i) {
uint64_t b = (uint64_t) value / shift[i];
b = b % cvt + b / cvt % cvt * (~maskHi + 1);
static_cast<uint8_t*>(buffer)[i] = b;
}
}
int64_t DataType::decode(const void* buffer) const {
int64_t datum = 0;
for (size_t i = 0; i < width; ++i) {
uint8_t b = static_cast<const uint8_t*>(buffer)[i];
datum += ((b & maskLo) % cvt + ((b & maskHi) >> 4) % cvt * 10) * shift[i];
}
if (repr == Repr::SIGNED) {
datum <<= 8 * (8 - width);
datum >>= 8 * (8 - width);
}
return datum;
}
size_t hash<DataType>::operator()(const DataType& type) const {
return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(type.type));
}
static constexpr char endianTag(Endian e) {
switch (e) {
case Endian::BIG:
return '>';
case Endian::LITTLE:
return '<';
default:
case Endian::UNDEF:
return '|';
case Endian::NATIVE:
return '=';
}
}
MemoryOverlay::MemoryOverlay(Endian backing, Endian real, size_t width)
: width(width)
, m_backing({ endianTag(backing), 'u', static_cast<char>('0' + width) })
, m_real({ endianTag(real), 'u', static_cast<char>('0' + width) }) {
}
MemoryOverlay::MemoryOverlay(char backing, char real, size_t width)
: width(width)
, m_backing({ backing, 'u', static_cast<char>('0' + width) })
, m_real({ real, 'u', static_cast<char>('0' + width) }) {
}
void* MemoryOverlay::parse(const void* in, size_t offset, void* out, size_t size) const {
size_t offsetEdge = offset & (width - 1);
uintptr_t base = reinterpret_cast<uintptr_t>(in);
base += offset & ~(width - 1);
size += offsetEdge;
uintptr_t outBase = reinterpret_cast<uintptr_t>(out);
for (size_t i = 0; i < size; i += width) {
int64_t val = m_backing.decode(reinterpret_cast<const void*>(base + i));
m_real.encode(reinterpret_cast<void*>(outBase + i), val);
}
return reinterpret_cast<void*>(outBase + offsetEdge);
}
void MemoryOverlay::unparse(void* out, size_t offset, const void* in, size_t size) const {
size_t offsetEdge = offset & (width - 1);
uintptr_t base = reinterpret_cast<uintptr_t>(out);
base += offset & ~(width - 1);
size += offsetEdge;
uintptr_t inBase = reinterpret_cast<uintptr_t>(in);
for (size_t i = 0; i < size; i += width) {
int64_t val = m_real.decode(reinterpret_cast<void*>(inBase + i));
m_backing.encode(reinterpret_cast<void*>(base + i), val);
}
}
Variant::Variant(int64_t i)
: m_type(Type::INT)
, m_vi(i) {
}
Variant::Variant(double d)
: m_type(Type::FLOAT)
, m_vf(d) {
}
Variant::Variant(bool b)
: m_type(Type::BOOL)
, m_vb(b) {
}
Variant::operator int64_t() const {
return cast<int64_t>();
}
Variant::operator int() const {
return cast<int>();
}
Variant::operator float() const {
return cast<float>();
}
Variant::operator double() const {
return cast<double>();
}
Variant::operator bool() const {
return cast<bool>();
}
void Variant::clear() {
m_type = Type::VOID;
}
Variant& Variant::operator=(int64_t v) {
m_type = Type::INT;
m_vi = v;
return *this;
}
Variant& Variant::operator=(double v) {
m_type = Type::FLOAT;
m_vf = v;
return *this;
}
Variant& Variant::operator=(bool v) {
m_type = Type::BOOL;
m_vb = v;
return *this;
}
Datum::Datum(void* base, const DataType& type)
: m_base(base)
, m_type(type) {
}
Datum::Datum(void* base, size_t offset, const DataType& type, const MemoryOverlay& overlay)
: m_base(base)
, m_offset(offset)
, m_type(type)
, m_overlay(overlay) {
}
Datum::Datum(void* base, const Variable& var, const MemoryOverlay& overlay)
: m_base(base)
, m_offset(var.address)
, m_type(var.type)
, m_mask(var.mask)
, m_overlay(overlay) {
}
Datum::Datum(Variant* variant)
: m_type("=i8")
, m_variant(variant) {
}
Datum& Datum::operator=(int64_t value) {
if (m_base) {
if (m_overlay.width > 1 || m_offset) {
uint8_t fakeBase[16]{};
m_type.encode(m_overlay.parse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width), value);
m_overlay.unparse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width);
} else {
m_type.encode(m_base, value);
}
} else if (m_variant) {
*m_variant = value;
}
return *this;
}
Datum::operator int64_t() const {
if (!m_base) {
if (m_variant) {
return *m_variant;
}
return 0;
}
int64_t value;
if (m_overlay.width > 1 || m_offset) {
uint8_t fakeBase[16]{};
value = m_type.decode(m_overlay.parse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width));
} else {
value = m_type.decode(m_base);
}
return value & m_mask;
}
Datum::operator Variant() const {
if (m_variant) {
return *m_variant;
}
return static_cast<int64_t>(*this);
}
DynamicMemoryView::DynamicMemoryView(void* buffer, size_t bytes, const DataType& dtype, const MemoryOverlay& overlay)
: dtype(dtype)
, overlay(overlay) {
m_mem.open(buffer, bytes);
}
Datum DynamicMemoryView::operator[](size_t offset) {
return dtype(m_mem.offset(0), offset, overlay);
}
int64_t DynamicMemoryView::operator[](size_t offset) const {
if (overlay.width > 1) {
uint8_t fakeBase[16]{};
return dtype.decode(overlay.parse(m_mem.offset(0), offset, reinterpret_cast<void*>(fakeBase), dtype.width));
}
return dtype.decode(m_mem.offset(offset));
}
const DataType AddressSpace::s_type{ "|u1" };
void AddressSpace::addBlock(size_t offset, size_t size, void* data) {
if (data) {
m_blocks[offset].open(data, size);
} else {
m_blocks[offset].open(size);
}
}
void AddressSpace::addBlock(size_t offset, size_t size, const void* data) {
if (data) {
m_blocks[offset].clone(data, size);
} else {
m_blocks[offset].open(size);
}
}
void AddressSpace::addBlock(size_t offset, const MemoryView<>& base) {
m_blocks[offset].clone(base);
}
void AddressSpace::updateBlock(size_t offset, void* data) {
m_blocks[offset].open(data, m_blocks[offset].size());
}
void AddressSpace::updateBlock(size_t offset, const void* data) {
m_blocks[offset].clone(data, m_blocks[offset].size());
}
void AddressSpace::updateBlock(size_t offset, const MemoryView<>& base) {
m_blocks[offset].clone(base);
}
bool AddressSpace::hasBlock(size_t offset) const {
for (const auto& block : m_blocks) {
if (offset < block.first) {
continue;
}
if (offset < block.first + block.second.size()) {
return true;
}
}
return false;
}
const MemoryView<>& AddressSpace::block(size_t offset) const {
for (const auto& block : m_blocks) {
if (offset < block.first) {
continue;
}
if (offset < block.first + block.second.size()) {
return block.second;
}
}
throw std::out_of_range("No known mapping 1");
}
MemoryView<>& AddressSpace::block(size_t offset) {
for (auto& block : m_blocks) {
if (offset < block.first) {
continue;
}
if (offset < block.first + block.second.size()) {
return block.second;
}
}
throw std::out_of_range("No known mapping 2");
}
bool AddressSpace::ok() const {
return m_blocks.size() > 0;
}
void AddressSpace::reset() {
m_blocks.clear();
}
void AddressSpace::clone(const AddressSpace& as) {
m_blocks.clear();
m_overlay = make_unique<MemoryOverlay>(*as.m_overlay);
for (auto& kv : as.m_blocks) {
m_blocks[kv.first].clone(kv.second);
}
}
void AddressSpace::clone() {
for (auto& kv : m_blocks) {
kv.second.clone();
}
}
void AddressSpace::setOverlay(const MemoryOverlay& overlay) {
m_overlay = make_unique<MemoryOverlay>(overlay);
}
Datum AddressSpace::operator[](size_t offset) {
for (auto& kv : m_blocks) {
if (offset < kv.first) {
throw std::out_of_range("No known mapping 3");
}
if (offset - kv.first >= kv.second.size()) {
continue;
}
return Datum(kv.second.offset(0), offset - kv.first, s_type, *m_overlay);
}
throw std::out_of_range("No known mapping 4");
}
Datum AddressSpace::operator[](const Variable& var) {
for (auto& kv : m_blocks) {
if (var.address < kv.first) {
throw std::out_of_range("No known mapping 5");
}
if (var.address - kv.first >= kv.second.size()) {
continue;
}
return Datum(kv.second.offset(0), Variable{ var.type, var.address - kv.first, var.mask }, *m_overlay);
}
throw std::out_of_range("No known mapping 6");
}
uint8_t AddressSpace::operator[](size_t offset) const {
for (const auto& kv : m_blocks) {
if (offset < kv.first) {
throw std::out_of_range("No known mapping 7");
}
if (offset - kv.first >= kv.second.size()) {
continue;
}
uint8_t fakeBase[16]{};
return s_type.decode(m_overlay->parse(kv.second.offset(0), offset - kv.first, reinterpret_cast<void*>(fakeBase), s_type.width));
}
throw std::out_of_range("No known mapping 8");
}
int64_t AddressSpace::operator[](const Variable& var) const {
for (const auto& kv : m_blocks) {
if (var.address < kv.first) {
throw std::out_of_range("No known mapping 9");
}
if (var.address - kv.first >= kv.second.size()) {
continue;
}
int64_t value;
if (m_overlay->width > 1) {
uint8_t fakeBase[16];
value = var.type.decode(m_overlay->parse(kv.second.offset(0), var.address - kv.first, reinterpret_cast<void*>(fakeBase), var.type.width));
} else {
value = var.type.decode(kv.second.offset(var.address - kv.first));
}
value &= var.mask;
return value;
}
throw std::out_of_range("No known mapping 10");
}
AddressSpace& AddressSpace::operator=(AddressSpace&& as) {
m_blocks.clear();
m_overlay = move(as.m_overlay);
for (auto& kv : as.m_blocks) {
m_blocks[kv.first] = move(as.m_blocks[kv.first]);
}
as.m_blocks.clear();
return *this;
}
int64_t Retro::toBcd(int64_t value) {
int64_t out = 0;
int shift = 0;
while (value) {
out |= (value % 10) << (shift * 4);
++shift;
value /= 10;
}
return out;
}
int64_t Retro::toLNBcd(int64_t value) {
int64_t out = 0;
int shift = 0;
while (value) {
out |= (value % 10) << (shift * 8);
++shift;
value /= 10;
}
return out;
}
bool Retro::isBcd(uint64_t value) {
uint64_t halfdigits = (value >> 1) & 0x7777777777777777;
return !((halfdigits + 0x3333333333333333) & 0x8888888888888888);
}