12 Commits

Author SHA1 Message Date
√(noham)²
36d4b1258f Add configurable client key algorithm 2026-08-27 16:21:40 +02:00
√(noham)²
3f1b4fd728 Proper implementation 2026-08-27 16:15:57 +02:00
√(noham)²
ce1df6f27d PoC 2026-08-27 16:06:10 +02:00
√(noham)²
e00ab1d5ed Update README.md 2026-08-26 00:04:09 +02:00
√(noham)²
a74031f228 Update compatibility for v3.28.1 2026-08-25 17:50:39 +02:00
√(noham)²
9089839f9b Update compatibility for Desktop v3.28 2026-08-20 16:33:57 +02:00
√(noham)²
8f95c65ba9 Update tested version to v3.27.2 and refresh image 2026-05-13 19:05:22 +02:00
√(noham)²
bde78170be Move 'How it works' section in README 2026-05-10 18:57:51 +02:00
√(noham)²
35423e1c1a Update auto-install.sh 2026-05-10 16:30:04 +02:00
√(noham)²
190fd02092 Add auto-install script and update README 2026-05-10 16:28:01 +02:00
√(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
14 changed files with 573 additions and 383 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ build/
.DS_Store .DS_Store
/.vscode /.vscode
/research /research
/aqt_venv
docs/rmfakecloud_hooking.md

View File

@@ -28,7 +28,7 @@ set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
include_directories( include_directories(
${PROJECT_ROOT_DIR}/src/core ${PROJECT_ROOT_DIR}/src/core
${PROJECT_ROOT_DIR}/src/utils ${PROJECT_ROOT_DIR}/src/utils
${PROJECT_ROOT_DIR}/src/reMarkable ${PROJECT_ROOT_DIR}/src/RMHook
${PROJECT_ROOT_DIR}/libs/include ${PROJECT_ROOT_DIR}/libs/include
) )
@@ -49,7 +49,7 @@ set(LIBS
# Locate Qt libraries # Locate Qt libraries
set(QT_LIB_TARGETS "") set(QT_LIB_TARGETS "")
set(_qt_candidate_roots "$ENV{HOME}/Qt/6.10.0") set(_qt_candidate_roots "$ENV{HOME}/Qt/6.10.3/macos")
foreach(_qt_root ${_qt_candidate_roots}) foreach(_qt_root ${_qt_candidate_roots})
if(_qt_root AND EXISTS "${_qt_root}") if(_qt_root AND EXISTS "${_qt_root}")
@@ -78,27 +78,29 @@ set(COMMON_SOURCES
${PROJECT_ROOT_DIR}/src/utils/ResourceUtils.m ${PROJECT_ROOT_DIR}/src/utils/ResourceUtils.m
) )
# reMarkable dylib # RMHook dylib
set(REMARKABLE_SOURCES set(RMHOOK_SOURCES
${PROJECT_ROOT_DIR}/src/reMarkable/reMarkable.m ${PROJECT_ROOT_DIR}/src/RMHook/RMHook.m
${PROJECT_ROOT_DIR}/src/reMarkable/DevHooks.m ${PROJECT_ROOT_DIR}/src/RMHook/Config.m
${PROJECT_ROOT_DIR}/src/RMHook/SSLConfig.m
${PROJECT_ROOT_DIR}/src/RMHook/DevHooks.m
) )
add_library(reMarkable SHARED add_library(RMHook SHARED
${COMMON_SOURCES} ${COMMON_SOURCES}
${REMARKABLE_SOURCES} ${RMHOOK_SOURCES}
) )
# Set source files as Objective-C++ # Set source files as Objective-C++
set_source_files_properties( set_source_files_properties(
${REMARKABLE_SOURCES} ${RMHOOK_SOURCES}
PROPERTIES LANGUAGE OBJCXX PROPERTIES LANGUAGE OBJCXX
) )
set_target_properties(reMarkable PROPERTIES set_target_properties(RMHook PROPERTIES
PREFIX "" PREFIX ""
SUFFIX ".dylib" SUFFIX ".dylib"
OUTPUT_NAME "reMarkable" OUTPUT_NAME "RMHook"
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_ROOT_DIR}/build/dylibs" LIBRARY_OUTPUT_DIRECTORY "${PROJECT_ROOT_DIR}/build/dylibs"
MACOSX_RPATH ON MACOSX_RPATH ON
) )
@@ -107,29 +109,29 @@ add_definitions(-DQT_NO_VERSION_TAGGING)
# Add build mode compile definitions and conditionally add sources # Add build mode compile definitions and conditionally add sources
if(BUILD_MODE_RMFAKECLOUD) if(BUILD_MODE_RMFAKECLOUD)
target_compile_definitions(reMarkable PRIVATE BUILD_MODE_RMFAKECLOUD=1) target_compile_definitions(RMHook PRIVATE BUILD_MODE_RMFAKECLOUD=1)
message(STATUS "Build mode: rmfakecloud (cloud redirection)") message(STATUS "Build mode: rmfakecloud (cloud redirection)")
endif() endif()
if(BUILD_MODE_QMLREBUILD) if(BUILD_MODE_QMLREBUILD)
target_compile_definitions(reMarkable PRIVATE BUILD_MODE_QMLREBUILD=1) target_compile_definitions(RMHook PRIVATE BUILD_MODE_QMLREBUILD=1)
# Enable Qt MOC for MessageBroker # Enable Qt MOC for MessageBroker
set_target_properties(reMarkable PROPERTIES AUTOMOC ON) set_target_properties(RMHook PROPERTIES AUTOMOC ON)
# Add MessageBroker source (needs MOC processing) # Add MessageBroker source (needs MOC processing)
target_sources(reMarkable PRIVATE target_sources(RMHook PRIVATE
${PROJECT_ROOT_DIR}/src/utils/MessageBroker.mm ${PROJECT_ROOT_DIR}/src/utils/MessageBroker.mm
) )
message(STATUS "Build mode: qmlrebuild (resource hooking)") message(STATUS "Build mode: qmlrebuild (resource hooking)")
endif() endif()
if(BUILD_MODE_DEV) if(BUILD_MODE_DEV)
target_compile_definitions(reMarkable PRIVATE BUILD_MODE_DEV=1) target_compile_definitions(RMHook PRIVATE BUILD_MODE_DEV=1)
message(STATUS "Build mode: dev (reverse engineering)") message(STATUS "Build mode: dev (reverse engineering)")
endif() endif()
target_link_libraries(reMarkable PRIVATE target_link_libraries(RMHook PRIVATE
${LIBS} ${LIBS}
${QT_LIB_TARGETS} ${QT_LIB_TARGETS}
) )

186
README.md
View File

@@ -6,32 +6,47 @@ 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.
## Other platforms
- **[RMHook-Win](https://github.com/NohamR/RMHook-Win)**: Windows Desktop
- **[RMHook-iOS](https://github.com/NohamR/RMHook-iOS)**: iOS
- **[RMHook-Android](https://github.com/NohamR/RMHook-Android)**: Android
## 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
- mTLS support for external access via Cloudflare Tunnel
## Compatibility ## Compatibility
**Tested and working on:** **Tested and working on:**
- reMarkable Desktop v3.27.1 (released 2026-06-07) - reMarkable Desktop v3.28.1 (released 2026-08-24)
<p align="center"> <p align="center">
<img src="docs/latest.png" width="40%" /> <img src="docs/latest.png" width="45%" />
<img src="docs/rm.png" width="50%" /> <img src="docs/rm.png" width="45%" />
</p> </p>
## Installation and usage ## Installation and usage
### Important legal notice
⚠️ **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/releases/latest) section. ⚠️ **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/releases/latest) section.
### Step 1: Prepare the reMarkable app ### Auto installation
Run in a terminal:
```bash
bash <(curl -sL https://raw.githubusercontent.com/NohamR/RMHook/refs/heads/main/scripts/auto-install.sh)
```
### Manual installation
#### Step 1: Prepare the reMarkable app
Uses the reMarkable Desktop app from your Applications folder or download it fresh from the [Mac App Store](https://apps.apple.com/app/remarkable-desktop/id1276493162). Uses the reMarkable Desktop app from your Applications folder or download it fresh from the [Mac App Store](https://apps.apple.com/app/remarkable-desktop/id1276493162).
### Step 2: Inject the dylib #### Step 2: Inject the dylib
Use the provided injection script: Use the provided injection script:
```bash ```bash
@@ -45,9 +60,9 @@ This script will:
- Remove the `_MASReceipt` folder - Remove the `_MASReceipt` folder
- Fix file ownership - Fix file ownership
### Step 3: Handle document storage #### Step 3: Handle document storage
#### Important path changes ##### Important path changes
The original Mac App Store version stores data in sandboxed locations: The original Mac App Store version stores data in sandboxed locations:
**Original sandboxed paths:** **Original sandboxed paths:**
@@ -58,7 +73,7 @@ The original Mac App Store version stores data in sandboxed locations:
- Config: `~/Library/Preferences/rmfakecloud.config` - Config: `~/Library/Preferences/rmfakecloud.config`
- Documents: `~/Library/Application Support/remarkable` - Documents: `~/Library/Application Support/remarkable`
#### Migration options ##### Migration options
**Option 1: Create a symbolic link** (recommended) **Option 1: Create a symbolic link** (recommended)
```bash ```bash
@@ -73,7 +88,7 @@ mv ~/Library/Containers/com.remarkable.desktop/Data/Library/Application\ Support
~/Library/Application\ Support/remarkable ~/Library/Application\ Support/remarkable
``` ```
### Step 4: Configure rmfakecloud server #### Step 4: Configure rmfakecloud server
Quickly access the configuration file from the app's Help menu: Quickly access the configuration file from the app's Help menu:
![help-config.png](docs/help-config.png) ![help-config.png](docs/help-config.png)
@@ -90,25 +105,112 @@ Example configuration:
} }
``` ```
### Step 5: Launch the patched app :p #### Step 5: Launch the patched app :p
## How it works
RMHook uses [tinyhook](https://github.com/Antibioticss/tinyhook/) to hook into Qt framework functions at runtime:
1. **QNetworkAccessManager::createRequest** - Intercepts HTTP/HTTPS requests
2. **QWebSocket::open** - Patches WebSocket connections
When the app attempts to connect to reMarkable's servers (e.g., `internal.cloud.remarkable.com`), the hooks redirect these requests to your configured host and port.
## Configuration ## Configuration
The config file (`~/Library/Preferences/rmfakecloud.config`) supports the following keys: The config file (`~/Library/Preferences/rmfakecloud.config`) supports the following keys:
| Key | Type | Default | Description | | Key | Type | Default | Description |
|--------|---------|-------------------|--------------------------------| |-----|------|---------|-------------|
| `host` | String | `example.com` | Your rmfakecloud server host | | `host` | String | `example.com` | Your rmfakecloud server host |
| `port` | Number | `443` | Your rmfakecloud server port | | `port` | Number | `443` | Your rmfakecloud server port |
| `client_cert` | String | (none) | Path to client certificate file (PEM) for mTLS |
| `client_key` | String | (none) | Path to client private key file (PEM) for mTLS |
| `key_algorithm` | String | `rsa` | Key algorithm: `rsa`, `ec`, `dsa`, `dh`, or `opaque` |
| `ca_cert` | String | (none) | Path to custom CA certificate file (PEM) |
| `disable_ssl_verification` | Boolean | `false` | Disable SSL peer verification (not recommended) |
If the config file doesn't exist, it will be created automatically with default values on first launch. If the config file doesn't exist, it will be created automatically with default values on first launch.
### External Access (Cloudflare Tunnel with Client Certificate)
If your rmfakecloud instance is exposed via Cloudflare Tunnel with client certificate authentication, configure the additional TLS settings in `~/Library/Preferences/rmfakecloud.config`:
```json
{
"host": "rmfakecloud.example.com",
"port": 443,
"client_cert": "/path/to/client.crt",
"client_key": "/path/to/client.key"
}
```
If your server uses a self-signed certificate or a certificate signed by a private CA:
```json
{
"host": "rmfakecloud.example.com",
"port": 443,
"client_cert": "/path/to/client.crt",
"client_key": "/path/to/client.key",
"ca_cert": "/path/to/ca.crt"
}
```
See the [rmfakecloud external access guide](https://ddvk.github.io/rmfakecloud/install/external-access/) for generating the client certificate.
## Building
1. **Clone the repository:**
```bash
git clone http://github.com/NohamR/RMHook
cd RMHook
```
Create a Python environment and install `aqtinstall`:
```bash
python3 -m venv aqt_venv
source aqt_venv/bin/activate
pip install aqtinstall
aqt install-qt mac desktop 6.10.3 -m qtwebsockets --outputdir ~/Qt
```
2. **Compile the dylib:**
```bash
./scripts/build.sh [mode]
```
### Build modes
The build script supports different modes for various use cases:
| Mode | Description |
|------|-------------|
| `rmfakecloud` | Redirect reMarkable cloud to rmfakecloud server (default) |
| `qmlrebuild` | Qt resource data registration hooking for QML replacement |
| `dev` | Development/reverse engineering mode with all hooks |
| `all` | Enable all modes |
**Note (qmlrebuild mode):** When using the `qmlrebuild` feature, you must clear the Qt QML cache before launching the app:
```bash
rm -rf ~/Library/Caches/remarkable
```
Qt caches compiled QML files, so changes to QML resources won't take effect until the cache is cleared.
Examples:
```bash
./scripts/build.sh # Build with rmfakecloud mode (default)
./scripts/build.sh rmfakecloud # Explicitly build rmfakecloud mode
./scripts/build.sh dev # Build with dev/reverse engineering hooks
./scripts/build.sh all # Build with all modes enabled
```
## Debugging
You can stream the macOS console logs to see output from the hooks:
```bash
log stream --predicate 'process == "reMarkable"' --level debug
```
## How it works
RMHook uses [tinyhook](https://github.com/Antibioticss/tinyhook/) to hook into Qt framework functions at runtime:
1. **QNetworkAccessManager::createRequest** - Intercepts HTTP/HTTPS requests
2. **QWebSocket::open** - Patches WebSocket connections
3. **MQTTAsync_createWithOptions** - Modifies MQTT URIs for screen sharing features
When the app attempts to connect to reMarkable's servers (e.g., `internal.cloud.remarkable.com`), the hooks redirect these requests to your configured host and port.
## Troubleshooting ## Troubleshooting
### App won't launch ### App won't launch
@@ -141,41 +243,3 @@ This project is not affiliated with, endorsed by, or sponsored by reMarkable AS.
## Contributing ## Contributing
Contributions are welcome! Please feel free to submit issues or pull requests. Contributions are welcome! Please feel free to submit issues or pull requests.
## Building
1. **Clone the repository:**
```bash
git clone http://github.com/NohamR/RMHook
cd RMHook
```
2. **Compile the dylib:**
```bash
./scripts/build.sh [mode]
```
### Build modes
The build script supports different modes for various use cases:
| Mode | Description |
|------|-------------|
| `rmfakecloud` | Redirect reMarkable cloud to rmfakecloud server (default) |
| `qmlrebuild` | Qt resource data registration hooking for QML replacement |
| `dev` | Development/reverse engineering mode with all hooks |
| `all` | Enable all modes |
**Note (qmlrebuild mode):** When using the `qmlrebuild` feature, you must clear the Qt QML cache before launching the app:
```bash
rm -rf ~/Library/Caches/remarkable
```
Qt caches compiled QML files, so changes to QML resources won't take effect until the cache is cleared.
Examples:
```bash
./scripts/build.sh # Build with rmfakecloud mode (default)
./scripts/build.sh rmfakecloud # Explicitly build rmfakecloud mode
./scripts/build.sh dev # Build with dev/reverse engineering hooks
./scripts/build.sh all # Build with all modes enabled
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 245 KiB

28
scripts/auto-install.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
REPO="NohamR/RMHook"
FILE="rmfakecloud.dylib"
APP_PATH="/Applications/remarkable.app"
echo "[INFO] Downloading $FILE..."
curl -sL \
-o "/tmp/$FILE" \
"https://github.com/$REPO/releases/latest/download/$FILE"
# Fix the sandbox
echo "[INFO] Linking sandbox directory..."
ln -sf ~/Library/Containers/com.remarkable.desktop/Data/Library/Application\ Support/remarkable \
~/Library/Application\ Support/remarkable
echo "[INFO] Downloading inject script..."
curl -sL \
-o "/tmp/inject.sh" \
"https://raw.githubusercontent.com/$REPO/refs/heads/main/scripts/inject.sh"
echo "[INFO] Downloading optool..."
curl -sL \
-o "/tmp/optool" \
"https://raw.githubusercontent.com/$REPO/refs/heads/main/scripts/optool"
chmod +x /tmp/inject.sh /tmp/optool
echo "[INFO] Running inject script..."
/tmp/inject.sh "/tmp/$FILE" "$APP_PATH"

View File

@@ -29,7 +29,7 @@ case "$BUILD_MODE" in
DYLIB_NAME="all.dylib" DYLIB_NAME="all.dylib"
;; ;;
*) *)
DYLIB_NAME="reMarkable.dylib" DYLIB_NAME="RMHook.dylib"
;; ;;
esac esac
@@ -55,7 +55,7 @@ case "$BUILD_MODE" in
;; ;;
esac esac
echo "🔨 Compiling reMarkable.dylib (mode: $BUILD_MODE)..." echo "🔨 Compiling RMHook.dylib (mode: $BUILD_MODE)..."
echo "📦 Qt path: $QT_PATH" echo "📦 Qt path: $QT_PATH"
# Create build directories if necessary # Create build directories if necessary
@@ -70,12 +70,12 @@ else
cmake $CMAKE_OPTIONS .. cmake $CMAKE_OPTIONS ..
fi fi
make reMarkable make RMHook
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Rename the produced dylib so each build mode has a distinct file name # Rename the produced dylib so each build mode has a distinct file name
DYLIB_DIR="$PROJECT_DIR/build/dylibs" DYLIB_DIR="$PROJECT_DIR/build/dylibs"
DEFAULT_DYLIB="$DYLIB_DIR/reMarkable.dylib" DEFAULT_DYLIB="$DYLIB_DIR/RMHook.dylib"
TARGET_DYLIB="$DYLIB_DIR/$DYLIB_NAME" TARGET_DYLIB="$DYLIB_DIR/$DYLIB_NAME"
if [ -f "$DEFAULT_DYLIB" ]; then if [ -f "$DEFAULT_DYLIB" ]; then

16
src/RMHook/Config.h Normal file
View File

@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
#include <QtCore/QString>
#include <QtNetwork/QSsl>
extern NSString *gConfiguredHostObjC;
extern NSNumber *gConfiguredPortObjC;
extern QString gConfiguredHost;
extern NSNumber *gConfiguredPort;
extern QString gConfiguredClientCertPath;
extern QString gConfiguredClientKeyPath;
extern QString gConfiguredCACertPath;
extern QSsl::KeyAlgorithm gConfiguredKeyAlgorithm;
extern bool gDisableSSLVerification;
void ConfigLoadOrCreate(void);
NSString *ConfigFilePath(void);

152
src/RMHook/Config.m Normal file
View File

@@ -0,0 +1,152 @@
#import "Config.h"
#import "Logger.h"
#include <QtCore/QString>
static NSString *const kConfigFileName = @"rmfakecloud.config";
static NSString *const kConfigHostKey = @"host";
static NSString *const kConfigPortKey = @"port";
static NSString *const kConfigClientCertKey = @"client_cert";
static NSString *const kConfigClientKeyKey = @"client_key";
static NSString *const kConfigCACertKey = @"ca_cert";
static NSString *const kConfigKeyAlgorithmKey = @"key_algorithm";
static NSString *const kConfigDisableSSLVerifyKey = @"disable_ssl_verification";
static NSString *const kDefaultHost = @"example.com";
static NSNumber *const kDefaultPort = @(443);
NSString *gConfiguredHostObjC = @"example.com";
NSNumber *gConfiguredPortObjC = @(443);
QString gConfiguredHost = QString::fromUtf8("example.com");
NSNumber *gConfiguredPort = @(443);
QString gConfiguredClientCertPath;
QString gConfiguredClientKeyPath;
QString gConfiguredCACertPath;
QSsl::KeyAlgorithm gConfiguredKeyAlgorithm = QSsl::Rsa;
bool gDisableSSLVerification = false;
static NSString *PreferencesDirectory(void) {
NSArray<NSString *> *libraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDir = [libraryPaths firstObject];
if (![libraryDir length]) {
libraryDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library"];
}
return [libraryDir stringByAppendingPathComponent:@"Preferences"];
}
NSString *ConfigFilePath(void) {
return [PreferencesDirectory() stringByAppendingPathComponent:kConfigFileName];
}
static BOOL WriteConfig(NSString *path, NSDictionary<NSString *, id> *config) {
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:config options:NSJSONWritingPrettyPrinted error:&error];
if (!jsonData || error) {
NSLogger(@"[RMHook] Failed to serialize config: %@", error);
return NO;
}
if (![jsonData writeToFile:path atomically:YES]) {
NSLogger(@"[RMHook] Failed to write config file at %@", path);
return NO;
}
return YES;
}
static inline QString QStringFromNSStringSafe(NSString *string) {
if (!string) {
return QString();
}
return QString::fromUtf8([string UTF8String]);
}
void ConfigLoadOrCreate(void) {
NSString *configPath = ConfigFilePath();
NSString *directory = [configPath stringByDeletingLastPathComponent];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDirectory = NO;
NSError *error = nil;
if (![fileManager fileExistsAtPath:directory isDirectory:&isDirectory] || !isDirectory) {
if (![fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLogger(@"[RMHook] Failed to create config directory %@: %@", directory, error);
}
}
NSDictionary<NSString *, id> *defaults = @{kConfigHostKey : kDefaultHost,
kConfigPortKey : kDefaultPort};
if ([fileManager fileExistsAtPath:configPath isDirectory:&isDirectory] && !isDirectory) {
NSData *data = [NSData dataWithContentsOfFile:configPath];
if ([data length] > 0) {
NSError *jsonError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (!jsonError && [jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary *configDict = (NSDictionary *)jsonObject;
NSString *hostValue = configDict[kConfigHostKey];
NSNumber *portValue = configDict[kConfigPortKey];
NSString *resolvedHost = ([hostValue isKindOfClass:[NSString class]] && [hostValue length]) ? hostValue : kDefaultHost;
NSInteger portCandidate = kDefaultPort.integerValue;
if ([portValue respondsToSelector:@selector(integerValue)]) {
NSInteger candidate = [portValue integerValue];
if (candidate > 0 && candidate <= 65535) {
portCandidate = candidate;
} else {
NSLogger(@"[RMHook] Ignoring invalid port value %@, falling back to default.", portValue);
}
}
gConfiguredHostObjC = [resolvedHost copy];
gConfiguredPortObjC = @(portCandidate);
gConfiguredHost = QStringFromNSStringSafe(resolvedHost);
gConfiguredPort = @(portCandidate);
NSString *certPathValue = configDict[kConfigClientCertKey];
NSString *keyPathValue = configDict[kConfigClientKeyKey];
NSString *caPathValue = configDict[kConfigCACertKey];
NSNumber *disableSSLValue = configDict[kConfigDisableSSLVerifyKey];
if ([certPathValue isKindOfClass:[NSString class]] && [certPathValue length]) {
gConfiguredClientCertPath = QStringFromNSStringSafe(certPathValue);
}
if ([keyPathValue isKindOfClass:[NSString class]] && [keyPathValue length]) {
gConfiguredClientKeyPath = QStringFromNSStringSafe(keyPathValue);
}
if ([caPathValue isKindOfClass:[NSString class]] && [caPathValue length]) {
gConfiguredCACertPath = QStringFromNSStringSafe(caPathValue);
}
NSString *keyAlgoValue = configDict[kConfigKeyAlgorithmKey];
if ([keyAlgoValue isKindOfClass:[NSString class]] && [keyAlgoValue length]) {
NSString *lower = [keyAlgoValue lowercaseString];
if ([lower isEqualToString:@"ec"]) {
gConfiguredKeyAlgorithm = QSsl::Ec;
} else if ([lower isEqualToString:@"dsa"]) {
gConfiguredKeyAlgorithm = QSsl::Dsa;
} else if ([lower isEqualToString:@"dh"]) {
gConfiguredKeyAlgorithm = QSsl::Dh;
} else if ([lower isEqualToString:@"opaque"]) {
gConfiguredKeyAlgorithm = QSsl::Opaque;
} else {
gConfiguredKeyAlgorithm = QSsl::Rsa;
}
}
if ([disableSSLValue respondsToSelector:@selector(boolValue)]) {
gDisableSSLVerification = [disableSSLValue boolValue];
}
NSLogger(@"[RMHook] Loaded config from %@ with host %@ and port %@", configPath, gConfiguredHostObjC, gConfiguredPortObjC);
return;
} else {
NSLogger(@"[RMHook] Failed to parse config file %@: %@", configPath, jsonError);
}
} else {
NSLogger(@"[RMHook] Config file %@ was empty, rewriting with defaults.", configPath);
}
}
if (WriteConfig(configPath, defaults)) {
NSLogger(@"[RMHook] Created default config at %@", configPath);
}
gConfiguredHostObjC = [kDefaultHost copy];
gConfiguredPortObjC = kDefaultPort;
gConfiguredHost = QString::fromUtf8("example.com");
gConfiguredPort = kDefaultPort;
}

View File

@@ -35,18 +35,18 @@ void (*original_function_at_0x1001B6EE0)(int64_t a1, int64_t *a2, unsigned int a
void logMemory(const char *label, void *address, size_t length) { void logMemory(const char *label, void *address, size_t length) {
if (!address) { if (!address) {
NSLogger(@"[reMarkable] %s: (null)", label); NSLogger(@"[RMHook] %s: (null)", label);
return; return;
} }
unsigned char *ptr = (unsigned char *)address; unsigned char *ptr = (unsigned char *)address;
NSMutableString *hexLine = [NSMutableString stringWithFormat:@"[reMarkable] %s: ", label]; NSMutableString *hexLine = [NSMutableString stringWithFormat:@"[RMHook] %s: ", label];
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
[hexLine appendFormat:@"%02x ", ptr[i]]; [hexLine appendFormat:@"%02x ", ptr[i]];
if ((i + 1) % 16 == 0 && i < length - 1) { if ((i + 1) % 16 == 0 && i < length - 1) {
NSLogger(@"%@", hexLine); NSLogger(@"%@", hexLine);
hexLine = [NSMutableString stringWithString:@"[reMarkable] "]; hexLine = [NSMutableString stringWithString:@"[RMHook] "];
} }
} }
@@ -57,13 +57,13 @@ void logMemory(const char *label, void *address, size_t length) {
} }
void logStackTrace(const char *label) { void logStackTrace(const char *label) {
NSLogger(@"[reMarkable] %s - Stack trace:", label); NSLogger(@"[RMHook] %s - Stack trace:", label);
NSArray<NSString *> *callStack = [NSThread callStackSymbols]; NSArray<NSString *> *callStack = [NSThread callStackSymbols];
NSUInteger count = [callStack count]; NSUInteger count = [callStack count];
for (NSUInteger i = 0; i < count; i++) { for (NSUInteger i = 0; i < count; i++) {
NSString *frame = callStack[i]; NSString *frame = callStack[i];
NSLogger(@"[reMarkable] #%lu: %@", (unsigned long)i, frame); NSLogger(@"[RMHook] #%lu: %@", (unsigned long)i, frame);
} }
} }
@@ -73,39 +73,39 @@ extern "C" ssize_t hooked_qIODevice_write(
QIODevice *self, QIODevice *self,
const char *data, const char *data,
int64_t maxSize) { int64_t maxSize) {
NSLogger(@"[reMarkable] QIODevice::write called with maxSize: %lld", (long long)maxSize); NSLogger(@"[RMHook] QIODevice::write called with maxSize: %lld", (long long)maxSize);
logStackTrace("QIODevice::write call stack"); logStackTrace("QIODevice::write call stack");
logMemory("Data to write", (void *)data, (size_t)(maxSize < 64 ? maxSize : 64)); logMemory("Data to write", (void *)data, (size_t)(maxSize < 64 ? maxSize : 64));
if (original_qIODevice_write) { if (original_qIODevice_write) {
ssize_t result = original_qIODevice_write(self, data, maxSize); ssize_t result = original_qIODevice_write(self, data, maxSize);
NSLogger(@"[reMarkable] QIODevice::write result: %zd", result); NSLogger(@"[RMHook] QIODevice::write result: %zd", result);
return result; return result;
} }
NSLogger(@"[reMarkable] WARNING: Original QIODevice::write not available, returning 0"); NSLogger(@"[RMHook] WARNING: Original QIODevice::write not available, returning 0");
return 0; return 0;
} }
extern "C" int64_t hooked_function_at_0x100011790(uint64_t *a1) { extern "C" int64_t hooked_function_at_0x100011790(uint64_t *a1) {
NSLogger(@"[reMarkable] Hook at 0x100011790 called!"); NSLogger(@"[RMHook] Hook at 0x100011790 called!");
NSLogger(@"[reMarkable] a1 = %p", a1); NSLogger(@"[RMHook] a1 = %p", a1);
if (a1) { if (a1) {
NSLogger(@"[reMarkable] *a1 = 0x%llx", (unsigned long long)*a1); NSLogger(@"[RMHook] *a1 = 0x%llx", (unsigned long long)*a1);
logMemory("Memory at a1", (void *)a1, 64); logMemory("Memory at a1", (void *)a1, 64);
logMemory("Memory at *a1", (void *)(*a1), 64); logMemory("Memory at *a1", (void *)(*a1), 64);
} else { } else {
NSLogger(@"[reMarkable] a1 is NULL"); NSLogger(@"[RMHook] a1 is NULL");
} }
if (original_function_at_0x100011790) { if (original_function_at_0x100011790) {
int64_t result = original_function_at_0x100011790(a1); int64_t result = original_function_at_0x100011790(a1);
NSLogger(@"[reMarkable] result = 0x%llx", (unsigned long long)result); NSLogger(@"[RMHook] result = 0x%llx", (unsigned long long)result);
return result; return result;
} }
NSLogger(@"[reMarkable] WARNING: Original function at 0x100011790 not available, returning 0"); NSLogger(@"[RMHook] WARNING: Original function at 0x100011790 not available, returning 0");
return 0; return 0;
} }
@@ -126,25 +126,25 @@ extern "C" int64_t hooked_function_at_0x100011CE0(
// - a4: stored at offset +0x18 (likely URI or type info pointer) // - a4: stored at offset +0x18 (likely URI or type info pointer)
// - a5: ExternalRefCountData* for shared pointer ref counting // - a5: ExternalRefCountData* for shared pointer ref counting
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
NSLogger(@"[reMarkable] Hook at 0x100011CE0 (QML Type Registration)"); NSLogger(@"[RMHook] Hook at 0x100011CE0 (QML Type Registration)");
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
NSLogger(@"[reMarkable] a1 (typeMetadata?) = 0x%llx", (unsigned long long)a1); NSLogger(@"[RMHook] a1 (typeMetadata?) = 0x%llx", (unsigned long long)a1);
uint16_t a2_low = (uint16_t)(uintptr_t)a2; uint16_t a2_low = (uint16_t)(uintptr_t)a2;
uint16_t combined_v17 = (a2_low << 8) | a3; uint16_t combined_v17 = (a2_low << 8) | a3;
NSLogger(@"[reMarkable] a2 (raw) = %p (0x%llx)", a2, (unsigned long long)(uintptr_t)a2); NSLogger(@"[RMHook] a2 (raw) = %p (0x%llx)", a2, (unsigned long long)(uintptr_t)a2);
NSLogger(@"[reMarkable] a2 low 16 bits = 0x%04x (%u)", a2_low, a2_low); NSLogger(@"[RMHook] a2 low 16 bits = 0x%04x (%u)", a2_low, a2_low);
NSLogger(@"[reMarkable] a3 (flags/version) = 0x%02x (%u)", a3, a3); NSLogger(@"[RMHook] a3 (flags/version) = 0x%02x (%u)", a3, a3);
NSLogger(@"[reMarkable] v17 = (a2<<8)|a3 = 0x%04x (%u)", combined_v17, combined_v17); NSLogger(@"[RMHook] v17 = (a2<<8)|a3 = 0x%04x (%u)", combined_v17, combined_v17);
NSLogger(@"[reMarkable] a4 (typeInfo/URI?) = 0x%llx", (unsigned long long)a4); NSLogger(@"[RMHook] a4 (typeInfo/URI?) = 0x%llx", (unsigned long long)a4);
NSLogger(@"[reMarkable] a5 (refCountData) = %p", a5); NSLogger(@"[RMHook] a5 (refCountData) = %p", a5);
if (a1) { if (a1) {
logMemory("Memory at a1 (typeMetadata)", (void *)a1, 64); logMemory("Memory at a1 (typeMetadata)", (void *)a1, 64);
void **vtable = (void **)a1; void **vtable = (void **)a1;
NSLogger(@"[reMarkable] a1 vtable/first ptr = %p", *vtable); NSLogger(@"[RMHook] a1 vtable/first ptr = %p", *vtable);
} }
if (a4) { if (a4) {
@@ -160,7 +160,7 @@ extern "C" int64_t hooked_function_at_0x100011CE0(
len++; len++;
} }
if (isPrintable && len > 0) { if (isPrintable && len > 0) {
NSLogger(@"[reMarkable] a4 as string: \"%.*s\"", len, maybeStr); NSLogger(@"[RMHook] a4 as string: \"%.*s\"", len, maybeStr);
} }
} }
@@ -172,59 +172,59 @@ extern "C" int64_t hooked_function_at_0x100011CE0(
if (original_function_at_0x100011CE0) { if (original_function_at_0x100011CE0) {
int64_t result = original_function_at_0x100011CE0(a1, a2, a3, a4, a5); int64_t result = original_function_at_0x100011CE0(a1, a2, a3, a4, a5);
NSLogger(@"[reMarkable] result (qmlregister return) = %u (0x%x)", (unsigned int)result, (unsigned int)result); NSLogger(@"[RMHook] result (qmlregister return) = %u (0x%x)", (unsigned int)result, (unsigned int)result);
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
return result; return result;
} }
NSLogger(@"[reMarkable] WARNING: Original function at 0x100011CE0 not available, returning 0"); NSLogger(@"[RMHook] WARNING: Original function at 0x100011CE0 not available, returning 0");
return 0; return 0;
} }
extern "C" int64_t hooked_function_at_0x10015A130(int64_t a1, int64_t a2) { extern "C" int64_t hooked_function_at_0x10015A130(int64_t a1, int64_t a2) {
NSLogger(@"[reMarkable] Hook at 0x10015A130 called!"); NSLogger(@"[RMHook] Hook at 0x10015A130 called!");
NSLogger(@"[reMarkable] a1 = 0x%llx", (unsigned long long)a1); NSLogger(@"[RMHook] a1 = 0x%llx", (unsigned long long)a1);
NSLogger(@"[reMarkable] a2 = 0x%llx", (unsigned long long)a2); NSLogger(@"[RMHook] a2 = 0x%llx", (unsigned long long)a2);
logMemory("Memory at a1", (void *)a1, 64); logMemory("Memory at a1", (void *)a1, 64);
logMemory("Memory at a2", (void *)a2, 64); logMemory("Memory at a2", (void *)a2, 64);
if (original_function_at_0x10015A130) { if (original_function_at_0x10015A130) {
int64_t result = original_function_at_0x10015A130(a1, a2); int64_t result = original_function_at_0x10015A130(a1, a2);
NSLogger(@"[reMarkable] result = 0x%llx", (unsigned long long)result); NSLogger(@"[RMHook] result = 0x%llx", (unsigned long long)result);
return result; return result;
} }
NSLogger(@"[reMarkable] WARNING: Original function at 0x10015A130 not available, returning 0"); NSLogger(@"[RMHook] WARNING: Original function at 0x10015A130 not available, returning 0");
return 0; return 0;
} }
extern "C" void hooked_function_at_0x10015BC90(int64_t a1, int64_t a2) { extern "C" void hooked_function_at_0x10015BC90(int64_t a1, int64_t a2) {
NSLogger(@"[reMarkable] Hook at 0x10015BC90 called!"); NSLogger(@"[RMHook] Hook at 0x10015BC90 called!");
NSLogger(@"[reMarkable] a1 = 0x%llx", (unsigned long long)a1); NSLogger(@"[RMHook] a1 = 0x%llx", (unsigned long long)a1);
NSLogger(@"[reMarkable] a2 = 0x%llx", (unsigned long long)a2); NSLogger(@"[RMHook] a2 = 0x%llx", (unsigned long long)a2);
logMemory("Memory at a1", (void *)a1, 64); logMemory("Memory at a1", (void *)a1, 64);
logMemory("Memory at a2", (void *)a2, 64); logMemory("Memory at a2", (void *)a2, 64);
if (original_function_at_0x10015BC90) { if (original_function_at_0x10015BC90) {
original_function_at_0x10015BC90(a1, a2); original_function_at_0x10015BC90(a1, a2);
NSLogger(@"[reMarkable] original function returned (void)"); NSLogger(@"[RMHook] original function returned (void)");
return; return;
} }
NSLogger(@"[reMarkable] WARNING: Original function at 0x10015BC90 not available"); NSLogger(@"[RMHook] WARNING: Original function at 0x10015BC90 not available");
} }
extern "C" int64_t hooked_function_at_0x10016D520(int64_t a1, int64_t *a2, unsigned int a3, int64_t a4) { extern "C" int64_t hooked_function_at_0x10016D520(int64_t a1, int64_t *a2, unsigned int a3, int64_t a4) {
NSLogger(@"[reMarkable] Hook at 0x10016D520 called!"); NSLogger(@"[RMHook] Hook at 0x10016D520 called!");
NSLogger(@"[reMarkable] a1 = 0x%llx", (unsigned long long)a1); NSLogger(@"[RMHook] a1 = 0x%llx", (unsigned long long)a1);
NSLogger(@"[reMarkable] a2 = %p", a2); NSLogger(@"[RMHook] a2 = %p", a2);
if (a2) { if (a2) {
NSLogger(@"[reMarkable] *a2 = 0x%llx", (unsigned long long)*a2); NSLogger(@"[RMHook] *a2 = 0x%llx", (unsigned long long)*a2);
} }
NSLogger(@"[reMarkable] a3 = %u (0x%x)", a3, a3); NSLogger(@"[RMHook] a3 = %u (0x%x)", a3, a3);
NSLogger(@"[reMarkable] a4 = 0x%llx", (unsigned long long)a4); NSLogger(@"[RMHook] a4 = 0x%llx", (unsigned long long)a4);
logMemory("Memory at a1", (void *)a1, 64); logMemory("Memory at a1", (void *)a1, 64);
logMemory("Memory at a2", (void *)a2, 64); logMemory("Memory at a2", (void *)a2, 64);
@@ -237,28 +237,28 @@ extern "C" int64_t hooked_function_at_0x10016D520(int64_t a1, int64_t *a2, unsig
if (original_function_at_0x10016D520) { if (original_function_at_0x10016D520) {
int64_t result = original_function_at_0x10016D520(a1, a2, a3, a4); int64_t result = original_function_at_0x10016D520(a1, a2, a3, a4);
NSLogger(@"[reMarkable] result = 0x%llx", (unsigned long long)result); NSLogger(@"[RMHook] result = 0x%llx", (unsigned long long)result);
return result; return result;
} }
NSLogger(@"[reMarkable] WARNING: Original function not available, returning 0"); NSLogger(@"[RMHook] WARNING: Original function not available, returning 0");
return 0; return 0;
} }
extern "C" void hooked_function_at_0x1001B6EE0(int64_t a1, int64_t *a2, unsigned int a3) { extern "C" void hooked_function_at_0x1001B6EE0(int64_t a1, int64_t *a2, unsigned int a3) {
NSLogger(@"[reMarkable] Hook at 0x1001B6EE0 called!"); NSLogger(@"[RMHook] Hook at 0x1001B6EE0 called!");
NSLogger(@"[reMarkable] a1 = 0x%llx", (unsigned long long)a1); NSLogger(@"[RMHook] a1 = 0x%llx", (unsigned long long)a1);
// At a1 (PdfExporter object): // At a1 (PdfExporter object):
// +0x10 contains a QString (likely document name) // +0x10 contains a QString (likely document name)
NSLogger(@"[reMarkable] Reading QString at a1+0x10:"); NSLogger(@"[RMHook] Reading QString at a1+0x10:");
logMemory("a1 + 0x10 (raw)", (void *)(a1 + 0x10), 64); logMemory("a1 + 0x10 (raw)", (void *)(a1 + 0x10), 64);
void **qstrPtr = (void **)(a1 + 0x10); void **qstrPtr = (void **)(a1 + 0x10);
void *dataPtr = *qstrPtr; void *dataPtr = *qstrPtr;
if (!dataPtr) { if (!dataPtr) {
NSLogger(@"[reMarkable] QString has null data pointer"); NSLogger(@"[RMHook] QString has null data pointer");
return; return;
} }
@@ -268,29 +268,29 @@ extern "C" void hooked_function_at_0x1001B6EE0(int64_t a1, int64_t *a2, unsigned
int32_t candidate = *(int32_t *)((char *)dataPtr - delta); int32_t candidate = *(int32_t *)((char *)dataPtr - delta);
if (candidate > 0 && candidate < 10000) { if (candidate > 0 && candidate < 10000) {
size = candidate; size = candidate;
NSLogger(@"[reMarkable] QString plausible size=%d (found at -%d)", size, delta); NSLogger(@"[RMHook] QString plausible size=%d (found at -%d)", size, delta);
break; break;
} }
} }
if (size > 0) { if (size > 0) {
NSString *qstringValue = [[NSString alloc] initWithCharacters:(unichar *)dataPtr length:size]; NSString *qstringValue = [[NSString alloc] initWithCharacters:(unichar *)dataPtr length:size];
NSLogger(@"[reMarkable] QString value: \"%@\"", qstringValue); NSLogger(@"[RMHook] QString value: \"%@\"", qstringValue);
} else { } else {
NSLogger(@"[reMarkable] QString: could not find valid size"); NSLogger(@"[RMHook] QString: could not find valid size");
} }
NSLogger(@"[reMarkable] a2 = %p", a2); NSLogger(@"[RMHook] a2 = %p", a2);
if (a2) { if (a2) {
NSLogger(@"[reMarkable] *a2 = 0x%llx", (unsigned long long)*a2); NSLogger(@"[RMHook] *a2 = 0x%llx", (unsigned long long)*a2);
} }
NSLogger(@"[reMarkable] a3 = %u (0x%x)", a3, a3); NSLogger(@"[RMHook] a3 = %u (0x%x)", a3, a3);
if (original_function_at_0x1001B6EE0) { if (original_function_at_0x1001B6EE0) {
original_function_at_0x1001B6EE0(a1, a2, a3); original_function_at_0x1001B6EE0(a1, a2, a3);
NSLogger(@"[reMarkable] Original function at 0x1001B6EE0 executed"); NSLogger(@"[RMHook] Original function at 0x1001B6EE0 executed");
} else { } else {
NSLogger(@"[reMarkable] WARNING: Original function not available"); NSLogger(@"[RMHook] WARNING: Original function not available");
} }
} }
@@ -306,19 +306,19 @@ extern "C" int64_t hooked_qmlregister(
int a9, int a9,
int64_t a10) { int64_t a10) {
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
NSLogger(@"[reMarkable] QQmlPrivate::qmlregister called!"); NSLogger(@"[RMHook] QQmlPrivate::qmlregister called!");
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
NSLogger(@"[reMarkable] a1 (RegistrationType) = 0x%llx (%lld)", (unsigned long long)a1, (long long)a1); NSLogger(@"[RMHook] a1 (RegistrationType) = 0x%llx (%lld)", (unsigned long long)a1, (long long)a1);
NSLogger(@"[reMarkable] a2 = 0x%llx (%lld)", (unsigned long long)a2, (long long)a2); NSLogger(@"[RMHook] a2 = 0x%llx (%lld)", (unsigned long long)a2, (long long)a2);
NSLogger(@"[reMarkable] a3 = 0x%llx (%lld)", (unsigned long long)a3, (long long)a3); NSLogger(@"[RMHook] a3 = 0x%llx (%lld)", (unsigned long long)a3, (long long)a3);
NSLogger(@"[reMarkable] a4 = 0x%llx (%lld)", (unsigned long long)a4, (long long)a4); NSLogger(@"[RMHook] a4 = 0x%llx (%lld)", (unsigned long long)a4, (long long)a4);
NSLogger(@"[reMarkable] a5 = 0x%llx (%lld)", (unsigned long long)a5, (long long)a5); NSLogger(@"[RMHook] a5 = 0x%llx (%lld)", (unsigned long long)a5, (long long)a5);
NSLogger(@"[reMarkable] a6 = 0x%llx (%lld)", (unsigned long long)a6, (long long)a6); NSLogger(@"[RMHook] a6 = 0x%llx (%lld)", (unsigned long long)a6, (long long)a6);
NSLogger(@"[reMarkable] a7 = 0x%x (%d)", a7, a7); NSLogger(@"[RMHook] a7 = 0x%x (%d)", a7, a7);
NSLogger(@"[reMarkable] a8 = 0x%llx (%lld)", (unsigned long long)a8, (long long)a8); NSLogger(@"[RMHook] a8 = 0x%llx (%lld)", (unsigned long long)a8, (long long)a8);
NSLogger(@"[reMarkable] a9 = 0x%x (%d)", a9, a9); NSLogger(@"[RMHook] a9 = 0x%x (%d)", a9, a9);
NSLogger(@"[reMarkable] a10 = 0x%llx (%lld)", (unsigned long long)a10, (long long)a10); NSLogger(@"[RMHook] a10 = 0x%llx (%lld)", (unsigned long long)a10, (long long)a10);
// Check for PlatformHelpers registration // Check for PlatformHelpers registration
// a1 == 0 means TypeRegistration (object registration) // a1 == 0 means TypeRegistration (object registration)
@@ -341,14 +341,14 @@ extern "C" int64_t hooked_qmlregister(
} }
if (isValid && len > 0) { if (isValid && len > 0) {
NSLogger(@"[reMarkable] typeName (a4) = \"%.*s\"", len, typeName); NSLogger(@"[RMHook] typeName (a4) = \"%.*s\"", len, typeName);
if (len == 15 && strncmp(typeName, "PlatformHelpers", 15) == 0) { if (len == 15 && strncmp(typeName, "PlatformHelpers", 15) == 0) {
NSLogger(@"[reMarkable] !!! FOUND PlatformHelpers type registration !!!"); NSLogger(@"[RMHook] !!! FOUND PlatformHelpers type registration !!!");
NSLogger(@"[reMarkable] factory ptr (a2) = %p", (void *)a2); NSLogger(@"[RMHook] factory ptr (a2) = %p", (void *)a2);
NSLogger(@"[reMarkable] a3 (metaObject?) = %p", (void *)a3); NSLogger(@"[RMHook] a3 (metaObject?) = %p", (void *)a3);
NSLogger(@"[reMarkable] a5 = %p", (void *)a5); NSLogger(@"[RMHook] a5 = %p", (void *)a5);
NSLogger(@"[reMarkable] a6 = %p", (void *)a6); NSLogger(@"[RMHook] a6 = %p", (void *)a6);
logMemory("Factory ptr memory", (void *)a2, 64); logMemory("Factory ptr memory", (void *)a2, 64);
logMemory("a3 memory (metaObject?)", (void *)a3, 64); logMemory("a3 memory (metaObject?)", (void *)a3, 64);
logStackTrace("PlatformHelpers registration"); logStackTrace("PlatformHelpers registration");
@@ -370,19 +370,19 @@ extern "C" int64_t hooked_qmlregister(
len++; len++;
} }
if (isPrintable && len > 0) { if (isPrintable && len > 0) {
NSLogger(@"[reMarkable] a2 as string: \"%.*s\"", len, maybeStr); NSLogger(@"[RMHook] a2 as string: \"%.*s\"", len, maybeStr);
} }
} }
int64_t result = 0; int64_t result = 0;
if (original_qmlregister) { if (original_qmlregister) {
result = original_qmlregister(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); result = original_qmlregister(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
NSLogger(@"[reMarkable] result = 0x%llx (%lld)", (unsigned long long)result, (long long)result); NSLogger(@"[RMHook] result = 0x%llx (%lld)", (unsigned long long)result, (long long)result);
} else { } else {
NSLogger(@"[reMarkable] WARNING: Original qmlregister not available!"); NSLogger(@"[RMHook] WARNING: Original qmlregister not available!");
} }
NSLogger(@"[reMarkable] ========================================"); NSLogger(@"[RMHook] ========================================");
return result; return result;
} }

View File

@@ -1,5 +1,5 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface reMarkable : NSObject @interface RMHook : NSObject
@end @end

View File

@@ -1,9 +1,11 @@
#import "reMarkable.h" #import "RMHook.h"
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Constant.h" #import "Constant.h"
#import "MemoryUtils.h" #import "MemoryUtils.h"
#import "Logger.h" #import "Logger.h"
#import "ResourceUtils.h" #import "ResourceUtils.h"
#import "Config.h"
#import "SSLConfig.h"
#ifdef BUILD_MODE_DEV #ifdef BUILD_MODE_DEV
#import "DevHooks.h" #import "DevHooks.h"
#endif #endif
@@ -29,113 +31,9 @@
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/Qt> #include <QtCore/Qt>
#include <QtWebSockets/QWebSocket> #include <QtWebSockets/QWebSocket>
#include <QtCore/QSettings>
#include <QtCore/QVariant>
#include <QtCore/QAnyStringView>
static NSString *const kReMarkableConfigFileName = @"rmfakecloud.config";
static NSString *const kReMarkableConfigHostKey = @"host";
static NSString *const kReMarkableConfigPortKey = @"port";
static NSString *const kReMarkableDefaultHost = @"example.com";
static NSNumber *const kReMarkableDefaultPort = @(443);
static NSString *gConfiguredHost = @"example.com";
static NSNumber *gConfiguredPort = @(443);
static pthread_mutex_t gResourceMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t gResourceMutex = PTHREAD_MUTEX_INITIALIZER;
static NSString *ReMarkablePreferencesDirectory(void);
static NSString *ReMarkablePreferencesDirectory(void) {
NSArray<NSString *> *libraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDir = [libraryPaths firstObject];
if (![libraryDir length]) {
libraryDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library"];
}
return [libraryDir stringByAppendingPathComponent:@"Preferences"];
}
static NSString *ReMarkableConfigFilePath(void) {
return [ReMarkablePreferencesDirectory() stringByAppendingPathComponent:kReMarkableConfigFileName];
}
static BOOL ReMarkableWriteConfig(NSString *path, NSDictionary<NSString *, id> *config) {
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:config options:NSJSONWritingPrettyPrinted error:&error];
if (!jsonData || error) {
NSLogger(@"[reMarkable] Failed to serialize config: %@", error);
return NO;
}
if (![jsonData writeToFile:path atomically:YES]) {
NSLogger(@"[reMarkable] Failed to write config file at %@", path);
return NO;
}
return YES;
}
static void ReMarkableLoadOrCreateConfig(void) {
NSString *configPath = ReMarkableConfigFilePath();
NSString *directory = [configPath stringByDeletingLastPathComponent];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDirectory = NO;
NSError *error = nil;
if (![fileManager fileExistsAtPath:directory isDirectory:&isDirectory] || !isDirectory) {
if (![fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLogger(@"[reMarkable] Failed to create config directory %@: %@", directory, error);
}
}
NSDictionary<NSString *, id> *defaults = @{kReMarkableConfigHostKey : kReMarkableDefaultHost,
kReMarkableConfigPortKey : kReMarkableDefaultPort};
if ([fileManager fileExistsAtPath:configPath isDirectory:&isDirectory] && !isDirectory) {
NSData *data = [NSData dataWithContentsOfFile:configPath];
if ([data length] > 0) {
NSError *jsonError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (!jsonError && [jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary *configDict = (NSDictionary *)jsonObject;
NSString *hostValue = configDict[kReMarkableConfigHostKey];
NSNumber *portValue = configDict[kReMarkableConfigPortKey];
NSString *resolvedHost = ([hostValue isKindOfClass:[NSString class]] && [hostValue length]) ? hostValue : kReMarkableDefaultHost;
NSInteger portCandidate = kReMarkableDefaultPort.integerValue;
if ([portValue respondsToSelector:@selector(integerValue)]) {
NSInteger candidate = [portValue integerValue];
if (candidate > 0 && candidate <= 65535) {
portCandidate = candidate;
} else {
NSLogger(@"[reMarkable] Ignoring invalid port value %@, falling back to default.", portValue);
}
}
gConfiguredHost = [resolvedHost copy];
gConfiguredPort = @(portCandidate);
NSLogger(@"[reMarkable] Loaded config from %@ with host %@ and port %@", configPath, gConfiguredHost, gConfiguredPort);
return;
} else {
NSLogger(@"[reMarkable] Failed to parse config file %@: %@", configPath, jsonError);
}
} else {
NSLogger(@"[reMarkable] Config file %@ was empty, rewriting with defaults.", configPath);
}
}
if (ReMarkableWriteConfig(configPath, defaults)) {
NSLogger(@"[reMarkable] Created default config at %@", configPath);
}
gConfiguredHost = [kReMarkableDefaultHost copy];
gConfiguredPort = kReMarkableDefaultPort;
}
static inline QString QStringFromNSStringSafe(NSString *string) {
if (!string) {
return QString();
}
return QString::fromUtf8([string UTF8String]);
}
@interface MenuActionController : NSObject @interface MenuActionController : NSObject
@property (strong, nonatomic) NSURL *targetURL; @property (strong, nonatomic) NSURL *targetURL;
- (void)openURLAction:(id)sender; - (void)openURLAction:(id)sender;
@@ -207,24 +105,22 @@ static inline QString QStringFromNSStringSafe(NSString *string) {
@end @end
@interface reMarkableDylib : NSObject @interface RMHookDylib : NSObject
- (BOOL)hook; - (BOOL)hook;
@end @end
@implementation reMarkable @implementation RMHook
+ (void)load { + (void)load {
NSLogger(@"reMarkable dylib loaded successfully"); NSLogger(@"RMHook dylib loaded successfully");
// Initialize the hook RMHookDylib *dylib = [[RMHookDylib alloc] init];
reMarkableDylib *dylib = [[reMarkableDylib alloc] init];
[dylib hook]; [dylib hook];
#ifdef BUILD_MODE_RMFAKECLOUD #ifdef BUILD_MODE_RMFAKECLOUD
// Add custom Help menu entry to open config file NSString *configPath = ConfigFilePath();
NSString *configPath = ReMarkableConfigFilePath();
NSString *fileURL = [NSString stringWithFormat:@"file://%@", configPath]; NSString *fileURL = [NSString stringWithFormat:@"file://%@", configPath];
[MenuActionController addCustomHelpMenuEntry:@"Open rmfakecloud config" [MenuActionController addCustomHelpMenuEntry:@"Open rmfakecloud config"
withURL:fileURL withURL:fileURL
@@ -234,7 +130,7 @@ static inline QString QStringFromNSStringSafe(NSString *string) {
@end @end
@implementation reMarkableDylib @implementation RMHookDylib
#ifdef BUILD_MODE_RMFAKECLOUD #ifdef BUILD_MODE_RMFAKECLOUD
static QNetworkReply *(*original_qNetworkAccessManager_createRequest)( static QNetworkReply *(*original_qNetworkAccessManager_createRequest)(
@@ -267,8 +163,6 @@ static int (*original_qRegisterResourceData)(
const unsigned char *) = NULL; const unsigned char *) = NULL;
#endif #endif
#ifdef BUILD_MODE_RMFAKECLOUD #ifdef BUILD_MODE_RMFAKECLOUD
static inline bool shouldPatchURL(const QString &host) { static inline bool shouldPatchURL(const QString &host) {
if (host.isEmpty()) { if (host.isEmpty()) {
@@ -294,114 +188,63 @@ static inline bool shouldPatchURL(const QString &host) {
} }
#endif #endif
static inline QString QStringFromNSStringSafe(NSString *string) {
if (!string) {
return QString();
}
return QString::fromUtf8([string UTF8String]);
}
- (BOOL)hook { - (BOOL)hook {
NSLogger(@"[reMarkable] Starting hooks..."); NSLogger(@"[RMHook] Starting hooks...");
#ifdef BUILD_MODE_RMFAKECLOUD #ifdef BUILD_MODE_RMFAKECLOUD
NSLogger(@"[reMarkable] Build mode: rmfakecloud"); NSLogger(@"[RMHook] Build mode: rmfakecloud");
ReMarkableLoadOrCreateConfig(); ConfigLoadOrCreate();
NSLogger(@"[reMarkable] Using override host %@ and port %@", gConfiguredHost, gConfiguredPort); SSLConfigLoad();
NSLogger(@"[RMHook] Using override host %@ and port %@", gConfiguredHostObjC, gConfiguredPortObjC);
[MemoryUtils hookSymbol:@"QtNetwork" [MemoryUtils hookSymbol:@"QtNetwork"
symbolName:@"__ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice" symbolName:@"__ZN21QNetworkAccessManager13createRequestENS_9OperationERK15QNetworkRequestP9QIODevice"
hookFunction:(void *)hooked_qNetworkAccessManager_createRequest hookFunction:(void *)hooked_qNetworkAccessManager_createRequest
originalFunction:(void **)&original_qNetworkAccessManager_createRequest originalFunction:(void **)&original_qNetworkAccessManager_createRequest
logPrefix:@"[reMarkable]"]; logPrefix:@"[RMHook]"];
[MemoryUtils hookSymbol:@"QtWebSockets" [MemoryUtils hookSymbol:@"QtWebSockets"
symbolName:@"__ZN10QWebSocket4openERK15QNetworkRequest" symbolName:@"__ZN10QWebSocket4openERK15QNetworkRequest"
hookFunction:(void *)hooked_qWebSocket_open hookFunction:(void *)hooked_qWebSocket_open
originalFunction:(void **)&original_qWebSocket_open originalFunction:(void **)&original_qWebSocket_open
logPrefix:@"[reMarkable]"]; logPrefix:@"[RMHook]"];
[MemoryUtils hookSymbol:@"libpaho-mqtt3as.1.dylib" [MemoryUtils hookSymbol:@"libpaho-mqtt3as.1.dylib"
symbolName:@"_MQTTAsync_createWithOptions" symbolName:@"_MQTTAsync_createWithOptions"
hookFunction:(void *)hooked_MQTTAsync_createWithOptions hookFunction:(void *)hooked_MQTTAsync_createWithOptions
originalFunction:(void **)&original_MQTTAsync_createWithOptions originalFunction:(void **)&original_MQTTAsync_createWithOptions
logPrefix:@"[reMarkable]"]; logPrefix:@"[RMHook]"];
#endif #endif
#ifdef BUILD_MODE_QMLREBUILD #ifdef BUILD_MODE_QMLREBUILD
NSLogger(@"[reMarkable] Build mode: qmlrebuild"); NSLogger(@"[RMHook] Build mode: qmlrebuild");
// Register MessageBroker QML type for dylib <-> QML communication
messagebroker::registerQmlType(); messagebroker::registerQmlType();
// Register native callback to receive signals from QML
messagebroker::setNativeCallback([](const char *signal, const char *value) { messagebroker::setNativeCallback([](const char *signal, const char *value) {
NSLogger(@"[reMarkable] Native callback received signal '%s' with value '%s'", signal, value); NSLogger(@"[RMHook] Native callback received signal '%s' with value '%s'", signal, value);
}); });
[MemoryUtils hookSymbol:@"QtCore" [MemoryUtils hookSymbol:@"QtCore"
symbolName:@"__Z21qRegisterResourceDataiPKhS0_S0_" symbolName:@"__Z21qRegisterResourceDataiPKhS0_S0_"
hookFunction:(void *)hooked_qRegisterResourceData hookFunction:(void *)hooked_qRegisterResourceData
originalFunction:(void **)&original_qRegisterResourceData originalFunction:(void **)&original_qRegisterResourceData
logPrefix:@"[reMarkable]"]; logPrefix:@"[RMHook]"];
// Send a delayed broadcast to QML (after UI has loaded)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
messagebroker::broadcast("signalName", "Hello from dylib!"); messagebroker::broadcast("signalName", "Hello from dylib!");
}); });
#endif #endif
#ifdef BUILD_MODE_DEV #ifdef BUILD_MODE_DEV
NSLogger(@"[reMarkable] Build mode: dev/reverse engineering"); NSLogger(@"[RMHook] Build mode: dev/reverse engineering");
// [MemoryUtils hookSymbol:@"QtCore"
// symbolName:@"__ZN9QIODevice5writeEPKcx"
// hookFunction:(void *)hooked_qIODevice_write
// originalFunction:(void **)&original_qIODevice_write
// logPrefix:@"[reMarkable]"];
// // Hook function at address 0x10015A130
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x10015A130
// hookFunction:(void *)hooked_function_at_0x10015A130
// originalFunction:(void **)&original_function_at_0x10015A130
// logPrefix:@"[reMarkable]"];
// // Hook function at address 0x10015BC90
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x10015BC90
// hookFunction:(void *)hooked_function_at_0x10015BC90
// originalFunction:(void **)&original_function_at_0x10015BC90
// logPrefix:@"[reMarkable]"];
// // Hook function at address 0x10016D520
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x10016D520
// hookFunction:(void *)hooked_function_at_0x10016D520
// originalFunction:(void **)&original_function_at_0x10016D520
// logPrefix:@"[reMarkable]"];
// // Hook function at address 0x1001B6EE0
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x1001B6EE0
// hookFunction:(void *)hooked_function_at_0x1001B6EE0
// originalFunction:(void **)&original_function_at_0x1001B6EE0
// logPrefix:@"[reMarkable]"];
// PlatformHelpers.exportFile implementation WIP
// // Hook function at address 0x100011790
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x100011790
// hookFunction:(void *)hooked_function_at_0x100011790
// originalFunction:(void **)&original_function_at_0x100011790
// logPrefix:@"[reMarkable]"];
// // Hook function at address 0x100011CE0
// [MemoryUtils hookAddress:@"reMarkable"
// staticAddress:0x100011CE0
// hookFunction:(void *)hooked_function_at_0x100011CE0
// originalFunction:(void **)&original_function_at_0x100011CE0
// logPrefix:@"[reMarkable]"];
// [MemoryUtils hookSymbol:@"QtQml"
// symbolName:@"__ZN11QQmlPrivate11qmlregisterENS_16RegistrationTypeEPv"
// hookFunction:(void *)hooked_qmlregister
// originalFunction:(void **)&original_qmlregister
// logPrefix:@"[reMarkable]"];
#endif #endif
return YES; return YES;
@@ -416,14 +259,15 @@ extern "C" QNetworkReply* hooked_qNetworkAccessManager_createRequest(
) { ) {
const QString host = req.url().host(); const QString host = req.url().host();
if (shouldPatchURL(host)) { if (shouldPatchURL(host)) {
// Clone request to keep original immutable
QNetworkRequest newReq(req); QNetworkRequest newReq(req);
QUrl newUrl = req.url(); QUrl newUrl = req.url();
const QString overrideHost = QStringFromNSStringSafe(gConfiguredHost); const QString overrideHost = QStringFromNSStringSafe(gConfiguredHostObjC);
newUrl.setHost(overrideHost); newUrl.setHost(overrideHost);
newUrl.setPort([gConfiguredPort intValue]); newUrl.setPort([gConfiguredPortObjC intValue]);
newReq.setUrl(newUrl); newReq.setUrl(newUrl);
SSLConfigApplyToRequest(newReq);
if (original_qNetworkAccessManager_createRequest) { if (original_qNetworkAccessManager_createRequest) {
return original_qNetworkAccessManager_createRequest(self, op, newReq, outgoingData); return original_qNetworkAccessManager_createRequest(self, op, newReq, outgoingData);
} }
@@ -447,13 +291,15 @@ extern "C" void hooked_qWebSocket_open(
const QString host = req.url().host(); const QString host = req.url().host();
if (shouldPatchURL(host)) { if (shouldPatchURL(host)) {
QUrl newUrl = req.url(); QUrl newUrl = req.url();
const QString overrideHost = QStringFromNSStringSafe(gConfiguredHost); const QString overrideHost = QStringFromNSStringSafe(gConfiguredHostObjC);
newUrl.setHost(overrideHost); newUrl.setHost(overrideHost);
newUrl.setPort([gConfiguredPort intValue]); newUrl.setPort([gConfiguredPortObjC intValue]);
QNetworkRequest newReq(req); QNetworkRequest newReq(req);
newReq.setUrl(newUrl); newReq.setUrl(newUrl);
SSLConfigApplyToRequest(newReq);
original_qWebSocket_open(self, newReq); original_qWebSocket_open(self, newReq);
return; return;
} }
@@ -461,8 +307,6 @@ 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) static std::string PatchMqttUri(const char* uri)
{ {
if (!uri) return {}; if (!uri) return {};
@@ -475,7 +319,6 @@ static std::string PatchMqttUri(const char* uri)
const std::string origHost = original.substr(hostStart, hostEnd - hostStart); const std::string origHost = original.substr(hostStart, hostEnd - hostStart);
// Match any *.remarkable.com or *.remarkable.engineering host
static const char* kSuffixes[] = { static const char* kSuffixes[] = {
".remarkable.com", ".remarkable.com",
".remarkable.engineering", ".remarkable.engineering",
@@ -496,17 +339,16 @@ static std::string PatchMqttUri(const char* uri)
if (!shouldPatch) return {}; if (!shouldPatch) return {};
std::string patched = original; std::string patched = original;
std::string proxyHost = [gConfiguredHost UTF8String]; std::string proxyHost = [gConfiguredHostObjC UTF8String];
patched.replace(hostStart, hostEnd - hostStart, proxyHost); patched.replace(hostStart, hostEnd - hostStart, proxyHost);
// Fix port
size_t colonPos = patched.find(':', hostStart + proxyHost.size()); size_t colonPos = patched.find(':', hostStart + proxyHost.size());
if (colonPos != std::string::npos) if (colonPos != std::string::npos)
{ {
size_t numEnd = patched.find_first_not_of("0123456789", colonPos + 1); size_t numEnd = patched.find_first_not_of("0123456789", colonPos + 1);
if (numEnd == std::string::npos) numEnd = patched.size(); if (numEnd == std::string::npos) numEnd = patched.size();
patched.replace(colonPos + 1, numEnd - colonPos - 1, patched.replace(colonPos + 1, numEnd - colonPos - 1,
std::to_string([gConfiguredPort intValue])); std::to_string([gConfiguredPortObjC intValue]));
} }
return patched; return patched;
} }
@@ -520,12 +362,12 @@ extern "C" int hooked_MQTTAsync_createWithOptions(
MQTTAsync_createOptions *options) MQTTAsync_createOptions *options)
{ {
if (!original_MQTTAsync_createWithOptions) { if (!original_MQTTAsync_createWithOptions) {
return -1; // error code for MQTTAsync_create failure return -1;
} }
std::string patchedUri = PatchMqttUri(serverURI); std::string patchedUri = PatchMqttUri(serverURI);
if (!patchedUri.empty()) { if (!patchedUri.empty()) {
NSLogger(@"[reMarkable] Patching MQTT URI from %s to %s", serverURI, patchedUri.c_str()); NSLogger(@"[RMHook] 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, patchedUri.c_str(), clientId, persistence_type, persistence_context, options);
} }
@@ -535,8 +377,6 @@ extern "C" int hooked_MQTTAsync_createWithOptions(
#ifdef BUILD_MODE_QMLREBUILD #ifdef BUILD_MODE_QMLREBUILD
// See https://deepwiki.com/search/once-the-qrr-file-parsed-take_871f24a0-8636-4aee-bddf-7405b6e32584 for details on qmlrebuild replacement strategy
extern "C" int hooked_qRegisterResourceData( extern "C" int hooked_qRegisterResourceData(
int version, int version,
const unsigned char *tree, const unsigned char *tree,
@@ -561,23 +401,21 @@ extern "C" int hooked_qRegisterResourceData(
.entriesAffected = 0, .entriesAffected = 0,
}; };
NSLogger(@"[reMarkable] Registering Qt resource version %d tree:%p name:%p data:%p", NSLogger(@"[RMHook] Registering Qt resource version %d tree:%p name:%p data:%p",
version, tree, name, data); version, tree, name, data);
statArchive(&resource, 0); statArchive(&resource, 0);
// Make a writable copy of the tree (we need to modify offsets)
resource.tree = (uint8_t *)malloc(resource.treeSize); resource.tree = (uint8_t *)malloc(resource.treeSize);
if (!resource.tree) { if (!resource.tree) {
NSLogger(@"[reMarkable] Failed to allocate tree buffer"); NSLogger(@"[RMHook] Failed to allocate tree buffer");
pthread_mutex_unlock(&gResourceMutex); pthread_mutex_unlock(&gResourceMutex);
return original_qRegisterResourceData(version, tree, name, data); return original_qRegisterResourceData(version, tree, name, data);
} }
memcpy(resource.tree, tree, resource.treeSize); memcpy(resource.tree, tree, resource.treeSize);
// Process nodes and mark replacements
processNode(&resource, 0, ""); processNode(&resource, 0, "");
NSLogger(@"[reMarkable] Processing done! Entries affected: %d, dataSize: %zu, originalDataSize: %zu", NSLogger(@"[RMHook] Processing done! Entries affected: %d, dataSize: %zu, originalDataSize: %zu",
resource.entriesAffected, resource.dataSize, resource.originalDataSize); resource.entriesAffected, resource.dataSize, resource.originalDataSize);
const unsigned char *finalTree = tree; const unsigned char *finalTree = tree;
@@ -585,30 +423,25 @@ extern "C" int hooked_qRegisterResourceData(
uint8_t *newDataBuffer = NULL; uint8_t *newDataBuffer = NULL;
if (resource.entriesAffected > 0) { if (resource.entriesAffected > 0) {
NSLogger(@"[reMarkable] Rebuilding data tables... (entries: %d)", resource.entriesAffected); NSLogger(@"[RMHook] Rebuilding data tables... (entries: %d)", resource.entriesAffected);
// Allocate new data buffer (original size + space for replacements)
newDataBuffer = (uint8_t *)malloc(resource.dataSize); newDataBuffer = (uint8_t *)malloc(resource.dataSize);
if (!newDataBuffer) { if (!newDataBuffer) {
NSLogger(@"[reMarkable] Failed to allocate new data buffer (%zu bytes)", resource.dataSize); NSLogger(@"[RMHook] Failed to allocate new data buffer (%zu bytes)", resource.dataSize);
free(resource.tree); free(resource.tree);
clearReplacementEntries(); clearReplacementEntries();
pthread_mutex_unlock(&gResourceMutex); pthread_mutex_unlock(&gResourceMutex);
return original_qRegisterResourceData(version, tree, name, data); return original_qRegisterResourceData(version, tree, name, data);
} }
// Copy original data
memcpy(newDataBuffer, data, resource.originalDataSize); memcpy(newDataBuffer, data, resource.originalDataSize);
// Copy replacement entries to their designated offsets
struct ReplacementEntry *entry = getReplacementEntries(); struct ReplacementEntry *entry = getReplacementEntries();
while (entry) { while (entry) {
// Write size prefix (4 bytes, big-endian)
writeUint32(newDataBuffer, (int)entry->copyToOffset, (uint32_t)entry->size); writeUint32(newDataBuffer, (int)entry->copyToOffset, (uint32_t)entry->size);
// Write data after size prefix
memcpy(newDataBuffer + entry->copyToOffset + 4, entry->data, entry->size); memcpy(newDataBuffer + entry->copyToOffset + 4, entry->data, entry->size);
NSLogger(@"[reMarkable] Copied replacement for node %d at offset %zu (%zu bytes)", NSLogger(@"[RMHook] Copied replacement for node %d at offset %zu (%zu bytes)",
entry->node, entry->copyToOffset, entry->size); entry->node, entry->copyToOffset, entry->size);
entry = entry->next; entry = entry->next;
@@ -617,23 +450,20 @@ extern "C" int hooked_qRegisterResourceData(
finalTree = resource.tree; finalTree = resource.tree;
finalData = newDataBuffer; finalData = newDataBuffer;
NSLogger(@"[reMarkable] Data buffer rebuilt: original %zu bytes -> new %zu bytes", NSLogger(@"[RMHook] Data buffer rebuilt: original %zu bytes -> new %zu bytes",
resource.originalDataSize, resource.dataSize); resource.originalDataSize, resource.dataSize);
} }
int status = original_qRegisterResourceData(version, finalTree, name, finalData); int status = original_qRegisterResourceData(version, finalTree, name, finalData);
// Cleanup
clearReplacementEntries(); clearReplacementEntries();
if (resource.tree && resource.entriesAffected == 0) { if (resource.tree && resource.entriesAffected == 0) {
free(resource.tree); free(resource.tree);
} }
// Note: We intentionally don't free newDataBuffer or resource.tree when entriesAffected > 0
// because Qt will use these buffers for the lifetime of the application
pthread_mutex_unlock(&gResourceMutex); pthread_mutex_unlock(&gResourceMutex);
return status; return status;
} }
#endif // BUILD_MODE_QMLREBUILD #endif // BUILD_MODE_QMLREBUILD
@end @end

4
src/RMHook/SSLConfig.h Normal file
View File

@@ -0,0 +1,4 @@
#include <QtNetwork/QNetworkRequest>
void SSLConfigLoad(void);
void SSLConfigApplyToRequest(QNetworkRequest &request);

92
src/RMHook/SSLConfig.m Normal file
View File

@@ -0,0 +1,92 @@
#import "SSLConfig.h"
#import "Config.h"
#import "Logger.h"
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QSslCertificate>
#include <QtNetwork/QSslKey>
#include <QtNetwork/QSslSocket>
#include <QtCore/QFile>
#include <QtCore/QIODevice>
static QSslCertificate gClientCert;
static QSslKey gClientKey;
static QSslCertificate gCACert;
static bool gSSLInitialized = false;
void SSLConfigLoad(void) {
if (gSSLInitialized) return;
if (!gConfiguredClientCertPath.isEmpty() && !gConfiguredClientKeyPath.isEmpty()) {
QString certPath = gConfiguredClientCertPath;
QString keyPath = gConfiguredClientKeyPath;
QFile certFile(QString::fromUtf8(certPath.toUtf8()));
if (certFile.open(QIODevice::ReadOnly)) {
gClientCert = QSslCertificate(certFile.readAll(), QSsl::Pem);
certFile.close();
if (gClientCert.isNull()) {
NSLogger(@"[RMHook] Failed to parse client certificate from %s", certPath.toUtf8().constData());
} else {
NSLogger(@"[RMHook] Loaded client certificate from %s", certPath.toUtf8().constData());
}
} else {
NSLogger(@"[RMHook] Failed to open client certificate file %s", certPath.toUtf8().constData());
}
QFile keyFile(QString::fromUtf8(keyPath.toUtf8()));
if (keyFile.open(QIODevice::ReadOnly)) {
gClientKey = QSslKey(keyFile.readAll(), gConfiguredKeyAlgorithm, QSsl::Pem, QSsl::PrivateKey);
keyFile.close();
if (gClientKey.isNull()) {
NSLogger(@"[RMHook] Failed to parse client key from %s", keyPath.toUtf8().constData());
} else {
NSLogger(@"[RMHook] Loaded client key from %s", keyPath.toUtf8().constData());
}
} else {
NSLogger(@"[RMHook] Failed to open client key file %s", keyPath.toUtf8().constData());
}
}
if (!gConfiguredCACertPath.isEmpty()) {
QString caPath = gConfiguredCACertPath;
QFile caFile(QString::fromUtf8(caPath.toUtf8()));
if (caFile.open(QIODevice::ReadOnly)) {
gCACert = QSslCertificate(caFile.readAll(), QSsl::Pem);
caFile.close();
if (gCACert.isNull()) {
NSLogger(@"[RMHook] Failed to parse CA certificate from %s", caPath.toUtf8().constData());
} else {
NSLogger(@"[RMHook] Loaded CA certificate from %s", caPath.toUtf8().constData());
}
} else {
NSLogger(@"[RMHook] Failed to open CA certificate file %s", caPath.toUtf8().constData());
}
}
gSSLInitialized = true;
}
void SSLConfigApplyToRequest(QNetworkRequest &request) {
if (!gSSLInitialized) return;
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
if (!gClientCert.isNull() && !gClientKey.isNull()) {
QList<QSslCertificate> localCerts = sslConfig.localCertificateChain();
localCerts.append(gClientCert);
sslConfig.setLocalCertificateChain(localCerts);
sslConfig.setPrivateKey(gClientKey);
}
if (!gCACert.isNull()) {
QList<QSslCertificate> caCerts = sslConfig.caCertificates();
caCerts.append(gCACert);
sslConfig.setCaCertificates(caCerts);
}
if (gDisableSSLVerification) {
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
}
request.setSslConfiguration(sslConfig);
}