Skip to content

Instantly share code, notes, and snippets.

@cfillion
Last active May 1, 2022 13:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cfillion/6b9633b6375bbcefac30613338d3f520 to your computer and use it in GitHub Desktop.
Save cfillion/6b9633b6375bbcefac30613338d3f520 to your computer and use it in GitHub Desktop.
ReaScript IDE reload prompt bypass https://i.imgur.com/kY34oAN.gif
// c++ -fPIC -O2 -std=c++14 -DSWELL_PROVIDED_BY_APP -IWDL -IWDL/WDL -dynamiclib reaper_idenoreload.cpp WDL/WDL/swell/swell-modstub.mm -lc++ -framework AppKit -o reaper_ideautoreload.dylib
//
// cl /nologo /O2 reaper_idenoreload.cpp User32.lib /link /OPT:REF /PDBALTPATH:%_PDB% /DLL /OUT:reaper_ideautoreload.dll
#ifndef _WIN32
# include <sys/mman.h>
# include <WDL/wdltypes.h>
#endif
#include <cstdint>
#define REAPERAPI_IMPLEMENT
#include "reaper_plugin_functions.h"
constexpr int RELOAD_TIMER { 3 };
static WNDPROC g_ideProc;
static unsigned int g_mode;
template<typename T>
bool patch(const uintptr_t addr, const T newCode, T *backup = nullptr)
{
uint8_t *firstByte { reinterpret_cast<uint8_t *>(addr) };
if(backup)
memcpy(backup, firstByte, sizeof(T));
#ifdef _WIN32
DWORD oldProtect;
if(!VirtualProtect(firstByte, sizeof(T), PAGE_READWRITE, &oldProtect))
return false;
#else
const auto pageSize { sysconf(_SC_PAGE_SIZE) };
void *pageStart { reinterpret_cast<void *>(addr - (addr % pageSize)) };
if(mprotect(pageStart, pageSize, PROT_READ | PROT_WRITE))
return false;
#endif
memcpy(firstByte, &newCode, sizeof(T));
#ifdef _WIN32
VirtualProtect(firstByte, sizeof(T), oldProtect, &oldProtect);
#else
mprotect(pageStart, pageSize, PROT_READ | PROT_EXEC);
#endif
return true;
}
#ifdef _WIN32
# pragma pack(push, 1)
# define packed
#else
# define packed __attribute__((packed))
#endif
struct packed Jump {
#if defined(__x86_64__) || defined(_WIN64)
// uint8_t int3 = 0xcc;
uint8_t mov = 0x48, reg = 0xb8; uint64_t dest; // mov rax, dest
uint8_t jmp = 0xff, modr_m = 0xe0; // jmp rax
#else
# error Unimplemented architecture
#endif
};
#ifdef _WIN32
# pragma pack(pop)
#endif
class Redirect {
public:
Redirect(const uintptr_t funcAddr, const uintptr_t redirect)
: m_funcAddr { funcAddr }
{
Jump jump;
jump.dest = redirect;
patch(m_funcAddr, jump, &m_code);
}
~Redirect()
{
patch(m_funcAddr, m_code);
}
private:
uintptr_t m_funcAddr;
Jump m_code;
};
static bool updateMode()
{
// FIXME: UTF-8 support on Windows
g_mode = GetPrivateProfileInt("reascriptedit", "autoreload", 1, get_ini_file());
return g_mode < 2;
}
static int WINAPI fakePrompt(HWND, const char *, const char *, const unsigned int)
{
return g_mode == 0 ? IDNO : IDYES;
}
static LRESULT CALLBACK bypassReloadProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
if(msg != WM_TIMER || wParam != RELOAD_TIMER || !updateMode())
return g_ideProc(hwnd, msg, wParam, lParam);
#ifdef _WIN32
Redirect mba { reinterpret_cast<uintptr_t>(&MessageBoxA),
reinterpret_cast<uintptr_t>(&fakePrompt) },
mbw { reinterpret_cast<uintptr_t>(&MessageBoxW),
reinterpret_cast<uintptr_t>(&fakePrompt) };
#else // function pointer under SWELL
Redirect mb { reinterpret_cast<uintptr_t>(MessageBox),
reinterpret_cast<uintptr_t>(&fakePrompt) };
#endif
return g_ideProc(hwnd, msg, wParam, lParam);
}
static void installBypassProc(HWND hwnd)
{
LONG_PTR newProc { reinterpret_cast<LONG_PTR>(&bypassReloadProc) },
oldProc { SetWindowLongPtr(hwnd, GWLP_WNDPROC, newProc) };
if(oldProc != newProc)
g_ideProc = reinterpret_cast<WNDPROC>(oldProc);
}
static BOOL CALLBACK findIde(HWND hwnd, LPARAM)
{
char title[1024];
GetWindowText(hwnd, title, sizeof(title));
// FIXME: localize title string
if(strstr(title, "ReaScript Development Environment"))
installBypassProc(hwnd);
return TRUE;
}
static void pollIdeWindows()
{
#ifdef _WIN32
EnumThreadWindows(GetCurrentThreadId(), &findIde, 0);
#else
EnumWindows(&findIde, 0);
#endif
}
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec)
{
if(!rec || rec->caller_version != REAPER_PLUGIN_VERSION)
return 0;
get_ini_file = reinterpret_cast<decltype(get_ini_file )>(rec->GetFunc("get_ini_file"));
plugin_register = reinterpret_cast<decltype(plugin_register)>(rec->GetFunc("plugin_register"));
plugin_register("timer", reinterpret_cast<void *>(&pollIdeWindows));
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment