// Adapted from OpenAI's retro source code: // https://github.com/openai/retro #pragma once //#include "gtest/gtest.h" #include #include #include #include #include #include #ifndef _WIN32 #include #include #else #include #include "unistd.h" #endif #ifdef VOID #undef VOID #endif namespace Retro { template class MemoryView { public: MemoryView() {} MemoryView(const MemoryView&) = delete; ~MemoryView(); bool open(const std::string& file, size_t bytes = 0); void open(void* buffer, size_t bytes); void open(size_t bytes); void open(std::initializer_list); void close(); bool ok() const; void clone(const void* buffer, size_t bytes); void clone(const MemoryView&); void clone(); T& operator[](size_t); const T& operator[](size_t) const; MemoryView& operator=(MemoryView&&); void* offset(size_t); const void* offset(size_t) const; size_t size() const; private: T* m_buffer = nullptr; int m_backingFd = -1; bool m_managed = false; size_t m_size = 0; #ifdef _WIN32 HANDLE m_mapView; #endif }; template MemoryView::~MemoryView() { close(); } template bool MemoryView::open(const std::string& file, size_t bytes) { if (ok()) { close(); } int flags = O_RDWR; if (bytes) { flags |= O_CREAT; } m_backingFd = ::open(file.c_str(), flags, 0600); if (m_backingFd < 0) { return false; } if (bytes) { ftruncate(m_backingFd, bytes); m_size = bytes; } else { m_size = lseek(m_backingFd, 0, SEEK_END); } m_managed = true; #ifdef _WIN32 m_mapView = CreateFileMapping(reinterpret_cast(_get_osfhandle(m_backingFd)), 0, PAGE_READWRITE, 0, m_size, 0); m_buffer = reinterpret_cast(static_cast(MapViewOfFile(m_mapView, FILE_MAP_WRITE, 0, 0, m_size))); #else m_buffer = reinterpret_cast(static_cast(mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_backingFd, 0))); #endif if (m_buffer == reinterpret_cast(-1)) { m_buffer = nullptr; m_managed = false; ::close(m_backingFd); return false; } return true; } template void MemoryView::open(void* buffer, size_t bytes) { if (ok()) { close(); } m_backingFd = -1; m_size = bytes; m_managed = false; m_buffer = static_cast(buffer); } template void MemoryView::open(size_t bytes) { if (ok()) { close(); } m_backingFd = -1; m_size = bytes; m_managed = true; #ifdef _WIN32 m_buffer = static_cast(VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); #else m_buffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)); #endif } template void MemoryView::open(std::initializer_list list) { open(list.size()); std::copy(list.begin(), list.end(), m_buffer); } template void MemoryView::close() { if (!ok()) { return; } if (m_managed) { if (m_buffer) { #ifdef _WIN32 if (m_backingFd >= 0) { UnmapViewOfFile(m_buffer); CloseHandle(m_mapView); } else { VirtualFree(m_buffer, 0, MEM_RELEASE); } #else munmap(m_buffer, m_size); #endif } if (m_backingFd >= 0) { ::close(m_backingFd); m_backingFd = -1; } } m_buffer = nullptr; m_size = 0; m_managed = false; } template bool MemoryView::ok() const { return m_buffer && m_size; } template void MemoryView::clone() { if (!ok() || m_managed) { return; } clone(static_cast(m_buffer), m_size); } template void MemoryView::clone(const void* buffer, size_t bytes) { if (m_managed && bytes == m_size) { memmove(m_buffer, buffer, bytes); return; } if (static_cast(m_buffer) != buffer || !m_managed) { close(); } #ifdef _WIN32 T* newBuffer = static_cast(VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); #else T* newBuffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)); #endif memcpy(newBuffer, buffer, bytes); m_buffer = newBuffer; m_size = bytes; m_managed = true; } template void MemoryView::clone(const MemoryView& other) { clone(static_cast(other.m_buffer), other.m_size); } template T& MemoryView::operator[](size_t index) { return m_buffer[index]; } template const T& MemoryView::operator[](size_t index) const { return m_buffer[index]; } template MemoryView& MemoryView::operator=(MemoryView&& other) { close(); m_buffer = other.m_buffer; m_backingFd = other.m_backingFd; m_managed = other.m_managed; m_size = other.m_size; other.m_managed = false; return *this; } template void* MemoryView::offset(size_t index) { return reinterpret_cast(&m_buffer[index]); } template const void* MemoryView::offset(size_t index) const { return reinterpret_cast(&m_buffer[index]); } template size_t MemoryView::size() const { return m_size; } enum class Endian : char { BIG = 0b01, LITTLE = 0b10, NATIVE = 0b11, MIXED_BL = 0b1001, MIXED_LB = 0b0110, MIXED_BN = 0b1101, MIXED_LN = 0b1110, #if defined(__LITTLE_ENDIAN__) || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ REAL_NATIVE = LITTLE, REAL_MIXED_BN = MIXED_BL, REAL_MIXED_LN = LITTLE, #else REAL_NATIVE = BIG, REAL_MIXED_BN = BIG, REAL_MIXED_LN = MIXED_LN, #endif UNDEF = 0 }; Endian reduce(Endian); bool reduceCompare(Endian, Endian); enum class Repr : char { SIGNED = 'i', UNSIGNED = 'u', BCD = 'd', LN_BCD = 'n' }; class Datum; class MemoryOverlay; class DataType { public: DataType(const char*); DataType(const std::string&); DataType(const DataType&) = default; Datum operator()(void*) const; Datum operator()(void*, size_t offset, const MemoryOverlay&) const; bool operator==(const DataType&) const; bool operator!=(const DataType&) const; void encode(void* buffer, int64_t value) const; int64_t decode(const void* buffer) const; const size_t width; const Endian endian; const Repr repr; const char type[5]; private: #if 0 FRIEND_TEST(DataTypeShift, 1); FRIEND_TEST(DataTypeShift, 2); FRIEND_TEST(DataTypeShift, 3); FRIEND_TEST(DataTypeShift, 4); FRIEND_TEST(DataTypeShift, 5); FRIEND_TEST(DataTypeShift, 6); FRIEND_TEST(DataTypeShift, 7); FRIEND_TEST(DataTypeShift, 8); #endif const uint8_t maskLo; const uint8_t maskHi; const unsigned cvt; int64_t shift[8]{}; }; struct Variable { Variable(const DataType&, size_t address, uint64_t mask = UINT64_MAX); Variable(const Variable&) = default; bool operator==(const Variable&) const; const DataType type; const size_t address; const uint64_t mask = UINT64_MAX; }; class MemoryOverlay { public: MemoryOverlay(Endian backing = Endian::NATIVE, Endian real = Endian::NATIVE, size_t width = 1); MemoryOverlay(char backing, char real, size_t width = 1); void* parse(const void* in, size_t offset, void* out, size_t size) const; void unparse(void* out, size_t offset, const void* in, size_t size) const; const size_t width; private: DataType m_backing; DataType m_real; }; class Variant { public: enum class Type { BOOL, INT, FLOAT, VOID }; Variant() {} Variant(int64_t); Variant(double); Variant(bool); template T cast() const { switch (m_type) { case Type::BOOL: return m_vb; case Type::INT: return m_vi; case Type::FLOAT: return m_vf; case Type::VOID: default: return T(); } } operator int() const; operator int64_t() const; operator float() const; operator double() const; operator bool() const; void clear(); Variant& operator=(int64_t); Variant& operator=(double); Variant& operator=(bool); Type type() { return m_type; } private: Type m_type = Type::VOID; union { bool m_vb; int64_t m_vi; double m_vf; }; }; class Datum { public: Datum() {} Datum(void*, const DataType&); Datum(void* base, const Variable&, const MemoryOverlay& overlay = {}); Datum(void* base, size_t offset, const DataType&, const MemoryOverlay& overlay = {}); Datum(Variant*); Datum& operator=(int64_t); operator int64_t() const; operator Variant() const; bool operator==(int64_t); private: void* const m_base = nullptr; const size_t m_offset = 0; const DataType m_type{ "|u1" }; const uint64_t m_mask = UINT64_MAX; const MemoryOverlay m_overlay{}; Variant* m_variant = nullptr; }; class DynamicMemoryView { public: DynamicMemoryView(void* buffer, size_t bytes, const DataType&, const MemoryOverlay& = {}); Datum operator[](size_t); int64_t operator[](size_t) const; const DataType dtype; const MemoryOverlay overlay; private: MemoryView<> m_mem; }; class AddressSpace { public: void addBlock(size_t offset, size_t size, void* data = nullptr); void addBlock(size_t offset, size_t size, const void* data); void addBlock(size_t offset, const MemoryView<>& base); void updateBlock(size_t offset, void* data); void updateBlock(size_t offset, const void* data); void updateBlock(size_t offset, const MemoryView<>& base); bool hasBlock(size_t offset) const; const MemoryView<>& block(size_t offset) const; MemoryView<>& block(size_t offset); const std::map>& blocks() const { return m_blocks; } std::map>& blocks() { return m_blocks; } bool ok() const; void reset(); void clone(const AddressSpace&); void clone(); void setOverlay(const MemoryOverlay& overlay); const MemoryOverlay& overlay() const { return *m_overlay; }; Datum operator[](size_t); Datum operator[](const Variable&); uint8_t operator[](size_t) const; int64_t operator[](const Variable&) const; AddressSpace& operator=(AddressSpace&&); private: static const DataType s_type; ; std::map> m_blocks; std::unique_ptr m_overlay = std::make_unique(); }; int64_t toBcd(int64_t); int64_t toLNBcd(int64_t); bool isBcd(uint64_t); } namespace std { template<> struct hash { size_t operator()(const Retro::DataType&) const; }; }