5 Commits

Author SHA1 Message Date
√(noham)²
8991f7fbcb Update README.md 2026-05-09 23:25:26 +02:00
√(noham)²
49aa0ec507 Update README.md 2026-05-09 18:15:42 +02:00
√(noham)²
90f50ec2a0 Add MQTT URI patching and hook for Paho 2026-05-09 18:13:53 +02:00
√(noham)²
0bb96ecceb Update license year, README release, and image 2026-05-07 23:29:37 +02:00
√(noham)²
427ee012c9 Bump reMarkable Desktop compatibility to v3.27 2026-05-06 18:44:57 +02:00
5 changed files with 96 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 Rivoirard Noham Copyright (c) 2026 Rivoirard Noham
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -6,15 +6,20 @@ A dynamic library injection tool for the reMarkable Desktop macOS application, e
RMHook hooks into the reMarkable Desktop app's network layer to redirect API calls from reMarkable's official cloud services to your own [rmfakecloud](https://github.com/ddvk/rmfakecloud) server. This allows you to maintain full control over your documents and data. RMHook hooks into the reMarkable Desktop app's network layer to redirect API calls from reMarkable's official cloud services to your own [rmfakecloud](https://github.com/ddvk/rmfakecloud) server. This allows you to maintain full control over your documents and data.
### Windows Port
Looking for a Windows version? Check out **[RMHook-Win](https://github.com/NohamR/RMHook-Win)**: A Windows port of RMHook for the reMarkable Desktop application.
## Features ## Features
- Network request interception and redirection - Network request interception and redirection
- WebSocket connection patching - WebSocket connection patching
- MQTT URI modification for screen sharing features
## Compatibility ## Compatibility
**Tested and working on:** **Tested and working on:**
- reMarkable Desktop v3.26.0 (released 2026-23-03) - reMarkable Desktop v3.27.1 (released 2026-06-07)
<p align="center"> <p align="center">
<img src="docs/latest.png" width="40%" /> <img src="docs/latest.png" width="40%" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 KiB

After

Width:  |  Height:  |  Size: 467 KiB

View File

@@ -18,6 +18,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <dispatch/dispatch.h> #include <dispatch/dispatch.h>
#include <string>
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkRequest>
@@ -245,6 +246,17 @@ static QNetworkReply *(*original_qNetworkAccessManager_createRequest)(
static void (*original_qWebSocket_open)( static void (*original_qWebSocket_open)(
QWebSocket *self, QWebSocket *self,
const QNetworkRequest &request) = NULL; const QNetworkRequest &request) = NULL;
typedef void* MQTTAsync;
typedef void* MQTTAsync_createOptions;
static int (*original_MQTTAsync_createWithOptions)(
MQTTAsync *handle,
const char *serverURI,
const char *clientId,
int persistence_type,
void *persistence_context,
MQTTAsync_createOptions *options) = NULL;
#endif #endif
#ifdef BUILD_MODE_QMLREBUILD #ifdef BUILD_MODE_QMLREBUILD
@@ -301,6 +313,12 @@ static inline bool shouldPatchURL(const QString &host) {
hookFunction:(void *)hooked_qWebSocket_open hookFunction:(void *)hooked_qWebSocket_open
originalFunction:(void **)&original_qWebSocket_open originalFunction:(void **)&original_qWebSocket_open
logPrefix:@"[reMarkable]"]; logPrefix:@"[reMarkable]"];
[MemoryUtils hookSymbol:@"libpaho-mqtt3as.1.dylib"
symbolName:@"_MQTTAsync_createWithOptions"
hookFunction:(void *)hooked_MQTTAsync_createWithOptions
originalFunction:(void **)&original_MQTTAsync_createWithOptions
logPrefix:@"[reMarkable]"];
#endif #endif
#ifdef BUILD_MODE_QMLREBUILD #ifdef BUILD_MODE_QMLREBUILD
@@ -442,6 +460,77 @@ extern "C" void hooked_qWebSocket_open(
original_qWebSocket_open(self, req); original_qWebSocket_open(self, req);
} }
// Patch a paho URI: "ssl://host.remarkable.com:port" -> "ssl://proxy:port"
// Returns patched string, or empty if no patch needed.
static std::string PatchMqttUri(const char* uri)
{
if (!uri) return {};
const std::string original(uri);
size_t schemeEnd = original.find("://");
size_t hostStart = (schemeEnd != std::string::npos) ? schemeEnd + 3 : 0;
size_t hostEnd = original.find_first_of(":/", hostStart);
if (hostEnd == std::string::npos) hostEnd = original.size();
const std::string origHost = original.substr(hostStart, hostEnd - hostStart);
// Match any *.remarkable.com or *.remarkable.engineering host
static const char* kSuffixes[] = {
".remarkable.com",
".remarkable.engineering",
nullptr
};
bool shouldPatch = false;
for (int i = 0; kSuffixes[i]; ++i)
{
const std::string suffix(kSuffixes[i]);
if (origHost.size() >= suffix.size() &&
origHost.compare(origHost.size() - suffix.size(),
suffix.size(), suffix) == 0)
{
shouldPatch = true;
break;
}
}
if (!shouldPatch) return {};
std::string patched = original;
std::string proxyHost = [gConfiguredHost UTF8String];
patched.replace(hostStart, hostEnd - hostStart, proxyHost);
// Fix port
size_t colonPos = patched.find(':', hostStart + proxyHost.size());
if (colonPos != std::string::npos)
{
size_t numEnd = patched.find_first_not_of("0123456789", colonPos + 1);
if (numEnd == std::string::npos) numEnd = patched.size();
patched.replace(colonPos + 1, numEnd - colonPos - 1,
std::to_string([gConfiguredPort intValue]));
}
return patched;
}
extern "C" int hooked_MQTTAsync_createWithOptions(
MQTTAsync *handle,
const char *serverURI,
const char *clientId,
int persistence_type,
void *persistence_context,
MQTTAsync_createOptions *options)
{
if (!original_MQTTAsync_createWithOptions) {
return -1; // error code for MQTTAsync_create failure
}
std::string patchedUri = PatchMqttUri(serverURI);
if (!patchedUri.empty()) {
NSLogger(@"[reMarkable] Patching MQTT URI from %s to %s", serverURI, patchedUri.c_str());
return original_MQTTAsync_createWithOptions(handle, patchedUri.c_str(), clientId, persistence_type, persistence_context, options);
}
return original_MQTTAsync_createWithOptions(handle, serverURI, clientId, persistence_type, persistence_context, options);
}
#endif // BUILD_MODE_RMFAKECLOUD #endif // BUILD_MODE_RMFAKECLOUD
#ifdef BUILD_MODE_QMLREBUILD #ifdef BUILD_MODE_QMLREBUILD