Add LICENSE, expand README; tidy proxy code

This commit is contained in:
√(noham)²
2026-05-07 17:49:31 +02:00
parent fb18195b71
commit d724a017e3
4 changed files with 355 additions and 276 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Rivoirard Noham
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

119
README.md
View File

@@ -1,9 +1,114 @@
Set-ExecutionPolicy
Run from an elevated PowerShell (script will attempt to relaunch elevated if not already) or right-click -> Run with PowerShell.
Example: .\scripts\install-hook.ps1 -Action install
To use a specific DLL: .\scripts\install-hook.ps1 -Action install -SourcePath "C:\path\to\your.dll"
To restore original: .\scripts\install-hook.ps1 -Action restore
# RMHook-Win
A Windows port of [RMHook](https://github.com/NohamR/RMHook) for the reMarkable Desktop application. This repo builds a proxy DLL that hooks Qt network APIs and redirects reMarkable cloud traffic to a self-hosted [rmfakecloud](https://github.com/ddvk/rmfakecloud) server.
install-hook.bat -Action install
install-hook.bat -Action restore
## Overview
RMHook-Win intercepts the reMarkable Desktop app's Qt networking layer and patches outgoing requests to the configured host and port. It is designed for the Windows reMarkable Desktop client and uses a DLL proxy for `paho-mqtt3as.dll`.
## Features
- Redirect reMarkable cloud HTTP(s) requests to a self-hosted rmfakecloud server
- Patch Qt WebSocket connections used by the reMarkable app
## Compatibility
**Tested and working on:**
<!-- - reMarkable Desktop v3.27.0 (released 2026-06-05)
<p align="center">
<img src="docs/latest.png" width="40%" />
<img src="docs/rm.png" width="50%" />
</p> -->
## Installation and usage
### Important legal note
⚠️ **For legal reasons, this repository does not include a pre-patched reMarkable app.** However, the latest compiled dylib is available in the [Releases](https://github.com/NohamR/RMHook-Win/releases/latest) section.
### Step 1: Build or obtain the proxy DLL
Build the `paho-mqtt3as-proxy` project with Visual Studio using `paho-mqtt3as-proxy.slnx`, or use an existing `paho-mqtt3as.dll` built from this repo.
### Step 2: Install the hook
Use the installer script from the `scripts` folder.
Note: Run from an elevated PowerShell session. The installer script will request administrator privileges if needed.
From PowerShell:
```powershell
.\scripts\install-hook.ps1 -Action install
```
Or with the batch wrapper:
```cmd
.\scripts\install-hook.bat -Action install
```
If you want to install a custom DLL build:
```powershell
.\scripts\install-hook.ps1 -Action install -SourcePath "C:\path\to\paho-mqtt3as.dll"
```
The script expects the Windows reMarkable install folder at:
```text
C:\Program Files\reMarkable
```
### Step 3: Restore the original DLL
To remove the proxy and restore the original `paho-mqtt3as.dll`:
```powershell
.\scripts\install-hook.ps1 -Action restore
```
## Configuration
Config path:
```text
%LOCALAPPDATA%\RMHook\config.json
```
Example config:
```json
{
"host": "your-server.example.com",
"port": 443
}
```
If the config file does not exist, it will be created automatically with default values on first launch.
## Troubleshooting
### Hook install fails
- Confirm the reMarkable install path is `C:\Program Files\reMarkable`
- Run PowerShell as administrator
- Verify the source DLL exists and is a valid proxy build
### App crashes or misbehaves
- Restore the original DLL with `-Action restore`
- Check the config file for valid JSON
- Make sure the `host` and `port` values point to a reachable rmfakecloud server
## Credits
- MinHook: [TsudaKageyu/minhook](https://github.com/TsudaKageyu/minhook) - API hooking framework used by the project
rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
- xovi-rmfakecloud: [asivery/xovi-rmfakecloud](https://github.com/asivery/xovi-rmfakecloud) - Original hooking information
- rm-xovi-extensions: [asivery/rm-xovi-extensions](https://github.com/asivery/rm-xovi-extensions) - Extension framework for reMarkable, used as reference for hooking Qt functions
- [qt-resource-rebuilder](https://github.com/asivery/rm-xovi-extensions/tree/master/qt-resource-rebuilder)
- [xovi-message-broker](https://github.com/asivery/rm-xovi-extensions/tree/master/xovi-message-broker)
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
## Disclaimer
This project is not affiliated with, endorsed by, or sponsored by reMarkable AS. Use at your own risk. This tool modifies the reMarkable Desktop application and may violate the application's terms of service.
## Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.

View File

@@ -17,7 +17,6 @@
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
static std::string GetLogPath()
{
char localAppData[MAX_PATH];
@@ -36,9 +35,6 @@ static std::string GetLogPath()
return "rmhook.log";
}
// ------------------------------------------------------------
// Logging
// ------------------------------------------------------------
static void Log(const std::string& msg)
{
@@ -49,11 +45,9 @@ static void Log(const std::string& msg)
}
}
// ------------------------------------------------------------
// Configuration
// ------------------------------------------------------------
static std::string gConfiguredHost = "example.com";
static int gConfiguredPort = 443;
static void LoadConfig()
@@ -74,16 +68,21 @@ static void LoadConfig()
if (!doc.isNull() && doc.isObject())
{
QJsonObject obj = doc.object();
if (obj.contains("host")) gConfiguredHost = obj["host"].toString().toStdString();
if (obj.contains("port")) gConfiguredPort = obj["port"].toInt();
if (obj.contains("host"))
{
gConfiguredHost = obj["host"].toString().toStdString();
}
if (obj.contains("port"))
{
gConfiguredPort = obj["port"].toInt();
}
return;
}
}
// If we reach here, no config exists or it failed to load.
MessageBoxA(NULL, "First launch detected.\nUsing default config (example.com:443).\nYou can edit configuration in %LOCALAPPDATA%\\RMHook\\config.json", "RMHook Configuration", MB_OK | MB_ICONINFORMATION);
// Save defaults as JSON
std::filesystem::create_directories(configPath.parent_path());
if (file.open(QIODevice::WriteOnly))
{
@@ -98,8 +97,10 @@ static void LoadConfig()
}
}
static inline bool shouldPatchURL(const QString& host) {
if (host.isEmpty()) {
static inline bool shouldPatchURL(const QString& host)
{
if (host.isEmpty())
{
return false;
}
@@ -121,9 +122,6 @@ static inline bool shouldPatchURL(const QString& host) {
.contains(host, Qt::CaseInsensitive);
}
// ------------------------------------------------------------
// Original typedefs
// ------------------------------------------------------------
typedef QNetworkReply* (__fastcall* QNAM_CreateRequest_t)(
QNetworkAccessManager* self,
@@ -137,17 +135,9 @@ typedef void (__fastcall* QWebSocket_Open_t)(
const QNetworkRequest& req
);
// ------------------------------------------------------------
// Originals
// ------------------------------------------------------------
static QNAM_CreateRequest_t originalCreateRequest = nullptr;
static QWebSocket_Open_t originalWebSocketOpen = nullptr;
// ------------------------------------------------------------
// Hooked createRequest
// ------------------------------------------------------------
QNetworkReply* __fastcall hookedCreateRequest(
QNetworkAccessManager* self,
QNetworkAccessManager::Operation op,
@@ -177,10 +167,6 @@ QNetworkReply* __fastcall hookedCreateRequest(
return nullptr;
}
// ------------------------------------------------------------
// Hooked websocket open
// ------------------------------------------------------------
void __fastcall hookedWebSocketOpen(
QWebSocket* self,
const QNetworkRequest& req
@@ -208,9 +194,6 @@ void __fastcall hookedWebSocketOpen(
originalWebSocketOpen(self, req);
}
// ------------------------------------------------------------
// Helpers
// ------------------------------------------------------------
void* ResolveExport(HMODULE module, const char* symbol)
{
@@ -225,18 +208,10 @@ void* ResolveExport(HMODULE module, const char* symbol)
return addr;
}
// ------------------------------------------------------------
// InstallHooks
// ------------------------------------------------------------
void InstallHooks()
{
LoadConfig();
// std::string logPath = GetLogPath();
// std::string message = "Proxy Hook Started.\nLog file: " + logPath;
// MessageBoxA(NULL, message.c_str(), "reMarkable Proxy", MB_OK | MB_ICONINFORMATION);
Log("[*] Initializing MinHook");
if (MH_Initialize() != MH_OK)
@@ -245,10 +220,6 @@ void InstallHooks()
return;
}
// --------------------------------------------------------
// Wait for Qt DLLs
// --------------------------------------------------------
HMODULE qtNetwork = nullptr;
HMODULE qtWebSockets = nullptr;
@@ -266,10 +237,6 @@ void InstallHooks()
Log("[+] Qt DLLs loaded");
// --------------------------------------------------------
// Resolve symbols
// --------------------------------------------------------
void* createRequestAddr = ResolveExport(
qtNetwork,
"?createRequest@QNetworkAccessManager@@MEAAPEAVQNetworkReply@@W4Operation@1@AEBVQNetworkRequest@@PEAVQIODevice@@@Z"
@@ -288,9 +255,6 @@ void InstallHooks()
Log("[+] Symbols resolved");
// --------------------------------------------------------
// Hook QNetworkAccessManager::createRequest
// --------------------------------------------------------
if (MH_CreateHook(
createRequestAddr,
@@ -305,10 +269,6 @@ void InstallHooks()
Log("[+] Hooked createRequest");
}
// --------------------------------------------------------
// Hook QWebSocket::open
// --------------------------------------------------------
if (MH_CreateHook(
webSocketOpenAddr,
&hookedWebSocketOpen,
@@ -322,10 +282,6 @@ void InstallHooks()
Log("[+] Hooked QWebSocket::open");
}
// --------------------------------------------------------
// Enable all hooks
// --------------------------------------------------------
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
{
Log("[ERROR] Failed to enable hooks");

View File

@@ -1,13 +1,7 @@
#include "common.h"
DWORD WINAPI DelayedHelloThread(LPVOID lpParam)
void LoadOriginalDllFunctions()
{
Sleep(10000); // 10 seconds
MessageBox(0, "Hello :)", "Proxy", MB_OK | MB_ICONINFORMATION);
return 0;
}
void LoadOriginalDllFunctions() {
paho_mqtt3as.OrignalMQTTAsync_connect = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_connect");
paho_mqtt3as.OrignalMQTTAsync_create = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_create");
paho_mqtt3as.OrignalMQTTAsync_createWithOptions = GetProcAddress(paho_mqtt3as.dll, "MQTTAsync_createWithOptions");
@@ -60,7 +54,8 @@ void LoadOriginalDllFunctions() {
paho_mqtt3as.OrignalThread_unlock_mutex = GetProcAddress(paho_mqtt3as.dll, "Thread_unlock_mutex");
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
@@ -79,11 +74,13 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
break;
}
case DLL_PROCESS_DETACH:
{
FreeLibrary(paho_mqtt3as.dll);
}
break;
}
}
return TRUE;
}