text: #include <algorithm>
  #include <cinttypes>
  #include <cstring>
  #include <stdexcept>
  
  #ifdef _WIN32
  #ifndef NOMINMAX
  #define NOMINMAX
  #endif
  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  #endif // _WIN32
  
  #include "../Console.h"
  #include "Interop.hpp"
  
  #pragma warning(disable : 4731) // frame pointer register 'ebp' modified by inline assembly code
  #define PLATFORM_X86
  
  #if defined(__GNUC__)
  #ifdef __clang__
  #define DISABLE_OPT __attribute__((noinline, optnone))
  #else
  #define DISABLE_OPT __attribute__((noinline, optimize("O0")))
  #endif // __clang__
  #else
  #define DISABLE_OPT
  #endif // defined(__GNUC__)
  
  namespace OpenLoco::Interop
  {
      // This variable serves a purpose of identifying a crash if it has happened inside original code.
      // When switching to original code, stack frame pointer is modified and prevents breakpad from providing stack trace.
      volatile int32_t _originalAddress = 0;
  
  #ifdef _ENABLE_CALL_BYVALUE_
      static int32_t DISABLE_OPT callByVal(int32_t address, int32_t _eax, int32_t _ebx, int32_t _ecx, int32_t _edx, int32_t _esi, int32_t _edi, int32_t _ebp)
      {
          int32_t result = 0;
          _originalAddress = address;
  #if defined(PLATFORM_X86)
  #ifdef _MSC_VER
          // clang-format off
          __asm {
              push ebp
              push address
              mov eax, _eax
              mov ebx, _ebx
              mov ecx, _ecx
              mov edx, _edx
              mov esi, _esi
              mov edi, _edi
              mov ebp, _ebp
              call[esp]
              lahf
              pop ebp
              pop ebp
              /* Load result with flags */
              mov result, eax
          }
  // clang-format on
  #else
          // clang-format off
          __asm__ volatile ("\
          \n\
          push %%ebx \n\
          push %%ebp \n\
          push %[address] \n\
          mov %[eax], %%eax \n\
          mov %[ebx], %%ebx \n\
          mov %[ecx], %%ecx \n\
          mov %[edx], %%edx \n\
          mov %[esi], %%esi \n\
          mov %[edi], %%edi \n\
          mov %[ebp], %%ebp \n\
          call *(%%esp) \n\
          lahf \n\
          add $4, %%esp \n\
          pop %%ebp \n\
          pop %%ebx \n\
          /* Load result with flags */ \n\
          mov %%eax, %[result] \n\
          " : [address] "+m" (address), [eax] "+m" (_eax), [ebx] "+m" (_ebx), [ecx] "+m" (_ecx), [edx] "+m" (_edx), [esi] "+m" (_esi), [edi] "+m" (_edi), [ebp] "+m" (_ebp), [result] "+m" (result)
              :
              : "eax", "ecx", "edx", "esi", "edi", "memory"
              );
  // clang-format on
  #endif
  #endif // PLATFORM_X86
          _originalAddress = 0;
          // lahf only modifies ah, zero out the rest
          return result & 0xFF00;
      }
  #endif
  
      static int32_t DISABLE_OPT callByRef(int32_t address, int32_t* _eax, int32_t* _ebx, int32_t* _ecx, int32_t* _edx, int32_t* _esi, int32_t* _edi, int32_t* _ebp)
      {
  #ifdef _LOG_INTEROP_CALLS_
          OpenLoco::Console::group("0x%x", address);
  #endif
          int32_t result = 0;
          _originalAddress = address;
  #if defined(PLATFORM_X86)
  #ifdef _MSC_VER
          // clang-format off
          __asm {
              // Store C's base pointer
              push ebp
              push ebx
              // Store address to call
              push address
  
              // Set all registers to the input values
              mov eax, [_eax]
              mov eax, [eax]
              mov ebx, [_ebx]
              mov ebx, [ebx]
              mov ecx, [_ecx]
              mov ecx, [ecx]
              mov edx, [_edx]
              mov edx, [edx]
              mov esi, [_esi]
              mov esi, [esi]
              mov edi, [_edi]
              mov edi, [edi]
              mov ebp, [_ebp]
              mov ebp, [ebp]
  
              // Call function
              call[esp]
  
              // Store output eax
              push eax
              push ebp
              push ebx
              mov ebp, [esp + 20]
              mov ebx, [esp + 16]
  
              // Get resulting ecx, edx, esi, edi registers
  
              mov eax, [_edi]
              mov[eax], edi
              mov eax, [_esi]
              mov[eax], esi
              mov eax, [_edx]
              mov[eax], edx
              mov eax, [_ecx]
              mov[eax], ecx
  
              // Pop ebx reg into ecx
              pop ecx
              mov eax, [_ebx]
              mov[eax], ecx
  
              // Pop ebp reg into ecx
              pop ecx
              mov eax, [_ebp]
              mov[eax], ecx
  
              pop eax
              // Get resulting eax register
              mov ecx, [_eax]
              mov[ecx], eax
  
              // Save flags as return in eax
              lahf
              // Pop address
              pop ebp
  
              pop ebx
              pop ebp
              /* Load result with flags */
              mov result, eax
          }
  // clang-format on
  #else
          // clang-format off
          __asm__ volatile ("\
          \n\
          /* Store C's base pointer*/     \n\
          push %%ebp        \n\
          push %%ebx        \n\
          \n\
          /* Store %[address] to call*/   \n\
          push %[address]         \n\
          \n\
          /* Set all registers to the input values*/      \n\
          mov %[_eax], %%eax      \n\
          mov (%%eax), %%eax  \n\
          mov %[_ebx], %%ebx      \n\
          mov (%%ebx), %%ebx  \n\
          mov %[_ecx], %%ecx      \n\
          mov (%%ecx), %%ecx  \n\
          mov %[_edx], %%edx      \n\
          mov (%%edx), %%edx  \n\
          mov %[_esi], %%esi      \n\
          mov (%%esi), %%esi  \n\
          mov %[_edi], %%edi      \n\
          mov (%%edi), %%edi  \n\
          mov %[_ebp], %%ebp      \n\
          mov (%%ebp), %%ebp  \n\
          \n\
          /* Call function*/      \n\
          call *(%%esp)      \n\
          \n\
          /* Store output eax */ \n\
          push %%eax \n\
          push %%ebp \n\
          push %%ebx \n\
          mov 20(%%esp), %%ebp \n\
          mov 16(%%esp), %%ebx \n\
          /* Get resulting ecx, edx, esi, edi registers*/       \n\
          mov %[_edi], %%eax      \n\
          mov %%edi, (%%eax)  \n\
          mov %[_esi], %%eax      \n\
          mov %%esi, (%%eax)  \n\
          mov %[_edx], %%eax      \n\
          mov %%edx, (%%eax)  \n\
          mov %[_ecx], %%eax      \n\
          mov %%ecx, (%%eax)  \n\
          /* Pop ebx reg into ecx*/ \n\
          pop %%ecx\n\
          mov %[_ebx], %%eax \n\
          mov %%ecx, (%%eax) \n\
          \n\
          /* Pop ebp reg into ecx */\n\
          pop %%ecx \n\
          mov %[_ebp], %%eax \n\
          mov %%ecx, (%%eax) \n\
          \n\
          pop %%eax \n\
          /* Get resulting eax register*/ \n\
          mov %[_eax], %%ecx \n\
          mov %%eax, (%%ecx) \n\
          \n\
          /* Save flags as return in eax*/  \n\
          lahf \n\
          /* Pop address*/ \n\
          pop %%ebp \n\
          \n\
          pop %%ebx \n\
          pop %%ebp \n\
          /* Load result with flags */ \n\
          mov %%eax, %[result] \n\
          " : [address] "+m" (address), [_eax] "+m" (_eax), [_ebx] "+m" (_ebx), [_ecx] "+m" (_ecx), [_edx] "+m" (_edx), [_esi] "+m" (_esi), [_edi] "+m" (_edi), [_ebp] "+m" (_ebp), [result] "+m" (result)
  
              :
              : "eax", "ecx", "edx", "esi", "edi", "memory"
              );
  // clang-format on
  #endif
  #endif // PLATFORM_X86
          _originalAddress = 0;
  
  #ifdef _LOG_INTEROP_CALLS_
          OpenLoco::Console::groupEnd();
  #endif
          // lahf only modifies ah, zero out the rest
          return result & 0xFF00;
      }
  
  #ifdef _ENABLE_CALL_BYVALUE_
      static int32_t callByVal(int32_t address, const registers& registers)
      {
          return callByVal(
              address,
              registers.eax,
              registers.ebx,
              registers.ecx,
              registers.edx,
              registers.esi,
              registers.edi,
              registers.ebp);
      }
  #endif
  
      int32_t call(int32_t address)
      {
          registers regs;
          return call(address, regs);
      }
  
      int32_t call(int32_t address, registers& registers)
      {
          return callByRef(
              address,
              &registers.eax,
              &registers.ebx,
              &registers.ecx,
              &registers.edx,
              &registers.esi,
              &registers.edi,
              &registers.ebp);
      }
  
      void readMemory(uint32_t address, void* data, size_t size)
      {
  #ifdef _WIN32
          if (!ReadProcessMemory(GetCurrentProcess(), (LPVOID)address, data, size, nullptr))
          {
              throw std::runtime_error("ReadProcessMemory failed");
          }
  #else
          // We own the pages with PROT_WRITE | PROT_EXEC, we can simply just memcpy the data
          std::memcpy(data, (void*)address, size);
  #endif // _WIN32
      }
  
      void writeMemory(uint32_t address, const void* data, size_t size)
      {
  #ifdef _WIN32
          if (!WriteProcessMemory(GetCurrentProcess(), (LPVOID)address, data, size, nullptr))
          {
              throw std::runtime_error("WriteProcessMemory failed");
          }
  #else
          // We own the pages with PROT_WRITE | PROT_EXEC, we can simply just memcpy the data
          std::memcpy((void*)address, data, size);
  #endif // _WIN32
      }
  
      save_state::save_state(uintptr_t begin, uintptr_t end)
          : begin(begin)
          , end(end)
      {
          state.resize(end - begin);
          readMemory(begin, state.data(), state.size());
      }
  
      void save_state::reset()
      {
          Interop::writeMemory(begin, state.data(), state.size());
      }
  
      void save_state::logDiff(const save_state& lhs, const save_state& rhs)
      {
          // TODO should we allow different base addresses?
          //      if so then we need to do extra work for that.
          auto length = std::min(lhs.state.size(), rhs.state.size());
          for (size_t i = 0; i < length; i++)
          {
              auto left = lhs.state[i];
              auto right = rhs.state[i];
              if (left != right)
              {
                  uint32_t addr = lhs.begin + i;
                  std::printf("0x%06" PRIX32 ": %02" PRIX8 "  %02" PRIX8 "\n", addr, (uint8_t)left, (uint8_t)right);
              }
          }
      }
  
      bool operator==(const save_state& lhs, const save_state& rhs)
      {
          return std::equal(
              lhs.getState().begin(),
              lhs.getState().end(),
              rhs.getState().begin(),
              rhs.getState().end());
      }
  
      bool operator!=(const save_state& lhs, const save_state& rhs)
      {
          return !(lhs == rhs);
      }
  }
  