Skip to content

Commit 5e98304

Browse files
committed
First release
0 parents  commit 5e98304

24 files changed

+1926
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/
2+
.DS_Store
3+
/.vscode

CMakeLists.txt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
project(RMHook)
3+
4+
enable_language(OBJC OBJCXX)
5+
6+
set(CMAKE_C_STANDARD 11)
7+
set(CMAKE_CXX_STANDARD 17)
8+
9+
# Compiler settings for macOS
10+
set(CMAKE_MACOSX_RPATH 1)
11+
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
12+
13+
# Architecture: x86_64 only for reMarkable
14+
set(CMAKE_OSX_ARCHITECTURES "x86_64")
15+
16+
# Project root directory
17+
set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
18+
19+
# Include directories
20+
include_directories(
21+
${PROJECT_ROOT_DIR}/src/core
22+
${PROJECT_ROOT_DIR}/src/utils
23+
)
24+
25+
# Find required libraries
26+
find_library(FOUNDATION_LIBRARY Foundation REQUIRED)
27+
find_library(COCOA_LIBRARY Cocoa REQUIRED)
28+
find_library(SECURITY_LIBRARY Security REQUIRED)
29+
30+
# Common libraries
31+
set(LIBS
32+
${FOUNDATION_LIBRARY}
33+
${COCOA_LIBRARY}
34+
${SECURITY_LIBRARY}
35+
${PROJECT_ROOT_DIR}/libs/libtinyhook.a
36+
z # zlib for compression/decompression
37+
)
38+
39+
# Locate Qt libraries
40+
set(QT_LIB_TARGETS "")
41+
set(_qt_candidate_roots "$ENV{HOME}/Qt/6.10.0")
42+
43+
foreach(_qt_root ${_qt_candidate_roots})
44+
if(_qt_root AND EXISTS "${_qt_root}")
45+
list(APPEND CMAKE_PREFIX_PATH "${_qt_root}")
46+
endif()
47+
endforeach()
48+
49+
find_package(Qt6 COMPONENTS Core Network WebSockets QUIET)
50+
if(Qt6_FOUND)
51+
set(QT_LIB_TARGETS Qt6::Core Qt6::Network Qt6::WebSockets)
52+
else()
53+
find_package(Qt5 COMPONENTS Core Network WebSockets QUIET)
54+
if(Qt5_FOUND)
55+
set(QT_LIB_TARGETS Qt5::Core Qt5::Network Qt5::WebSockets)
56+
endif()
57+
endif()
58+
59+
if(NOT QT_LIB_TARGETS)
60+
message(FATAL_ERROR "Qt Core, Network and WebSockets not found. Set CMAKE_PREFIX_PATH to your Qt installation.")
61+
endif()
62+
63+
# Common sources
64+
set(COMMON_SOURCES
65+
${PROJECT_ROOT_DIR}/src/utils/MemoryUtils.m
66+
${PROJECT_ROOT_DIR}/src/utils/Constant.m
67+
${PROJECT_ROOT_DIR}/src/utils/ResourceUtils.m
68+
)
69+
70+
# reMarkable dylib
71+
set(REMARKABLE_SOURCES
72+
${PROJECT_ROOT_DIR}/src/reMarkable/reMarkable.m
73+
)
74+
75+
add_library(reMarkable SHARED
76+
${COMMON_SOURCES}
77+
${REMARKABLE_SOURCES}
78+
)
79+
80+
# Set source files as Objective-C++
81+
set_source_files_properties(
82+
${REMARKABLE_SOURCES}
83+
PROPERTIES LANGUAGE OBJCXX
84+
)
85+
86+
set_target_properties(reMarkable PROPERTIES
87+
PREFIX ""
88+
SUFFIX ".dylib"
89+
OUTPUT_NAME "reMarkable"
90+
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_ROOT_DIR}/build/dylibs"
91+
MACOSX_RPATH ON
92+
)
93+
94+
add_definitions(-DQT_NO_VERSION_TAGGING)
95+
96+
target_link_libraries(reMarkable PRIVATE
97+
${LIBS}
98+
${QT_LIB_TARGETS}
99+
/opt/homebrew/Cellar/libzip/1.11.4/lib/intel/libzstd.1.5.7.dylib
100+
)

LICENSE

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

README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# RMHook
2+
3+
A dynamic library injection tool for the reMarkable Desktop macOS application, enabling connection to self-hosted [rmfakecloud](https://github.com/ddvk/rmfakecloud) servers.
4+
5+
## Overview
6+
7+
RMHook hooks into the reMarkable Desktop app's network layer to redirect API calls from reMarkable's official cloud services to your own rmfakecloud server. This allows you to maintain full control over your documents and data.
8+
9+
## Features
10+
11+
- Network request interception and redirection
12+
- WebSocket connection patching
13+
14+
## Installation and usage
15+
16+
### Important legal notice
17+
18+
⚠️ **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.
19+
20+
### Step 1: Prepare the reMarkable app
21+
22+
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).
23+
24+
### Step 2: Inject the dylib
25+
26+
Use the provided injection script:
27+
```bash
28+
./scripts/inject.sh reMarkable.dylib reMarkable.app
29+
```
30+
31+
This script will:
32+
- Copy the dylib to the app bundle's Resources folder
33+
- Inject the load command into the executable using `optool`
34+
- Remove the code signature and resign with ad-hoc signature
35+
- Remove the `_MASReceipt` folder
36+
- Fix file ownership
37+
38+
### Step 3: Handle document storage
39+
40+
#### Important path changes
41+
42+
The original Mac App Store version stores data in sandboxed locations:
43+
**Original sandboxed paths:**
44+
- App data: `~/Library/Containers/com.remarkable.desktop/Data`
45+
- Documents: `~/Library/Containers/com.remarkable.desktop/Data/Library/Application Support/remarkable`
46+
47+
**After re-signing, the app is no longer sandboxed** and will use standard paths:
48+
- Config: `~/Library/Preferences/rmfakecloud.config`
49+
- Documents: `~/Library/Application Support/remarkable`
50+
51+
#### Migration options
52+
53+
**Option 1: Create a symbolic link** (recommended)
54+
```bash
55+
ln -s ~/Library/Containers/com.remarkable.desktop/Data/Library/Application\ Support/remarkable \
56+
~/Library/Application\ Support/remarkable
57+
```
58+
The symbolic link approach allows you to keep using the original App Store version alongside the patched version.
59+
60+
**Option 2: Move files**
61+
```bash
62+
mv ~/Library/Containers/com.remarkable.desktop/Data/Library/Application\ Support/remarkable \
63+
~/Library/Application\ Support/remarkable
64+
```
65+
66+
### Step 4: Configure rmfakecloud server
67+
Quickly access the configuration file from the app's Help menu:
68+
![help-config.png](docs/help-config.png)
69+
Edit the configuration file at:
70+
```
71+
~/Library/Preferences/rmfakecloud.config
72+
```
73+
74+
Example configuration:
75+
```json
76+
{
77+
"host": "your-server.example.com",
78+
"port": 443
79+
}
80+
```
81+
82+
### Step 5: Launch the patched app :p
83+
84+
## How it works
85+
RMHook uses [tinyhook](https://github.com/Antibioticss/tinyhook/) to hook into Qt framework functions at runtime:
86+
1. **QNetworkAccessManager::createRequest** - Intercepts HTTP/HTTPS requests
87+
2. **QWebSocket::open** - Patches WebSocket connections
88+
89+
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.
90+
91+
## Configuration
92+
93+
The config file (`~/Library/Preferences/rmfakecloud.config`) supports the following keys:
94+
| Key | Type | Default | Description |
95+
|--------|---------|-------------------|--------------------------------|
96+
| `host` | String | `example.com` | Your rmfakecloud server host |
97+
| `port` | Number | `443` | Your rmfakecloud server port |
98+
99+
If the config file doesn't exist, it will be created automatically with default values on first launch.
100+
101+
## Troubleshooting
102+
103+
### App won't launch
104+
- Ensure the code signature was properly applied
105+
- Check that `xattr -cr` was run to clear quarantine attributes
106+
- Verify the dylib is in `Contents/Resources/` folder
107+
108+
### Document sync issues
109+
- Ensure your rmfakecloud server is running and accessible
110+
- Verify the storage path migration was completed
111+
112+
## Credits
113+
- **tinyhook**: [Antibioticss/tinyhook](https://github.com/Antibioticss/tinyhook/) - Function hooking framework
114+
- **rmfakecloud**: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
115+
- **optool**: [alexzielenski/optool](https://github.com/alexzielenski/optool) - Mach-O binary modification tool
116+
117+
## License
118+
119+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
120+
121+
## Disclaimer
122+
123+
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.
124+
125+
## Contributing
126+
127+
Contributions are welcome! Please feel free to submit issues or pull requests.
128+
129+
## Building
130+
131+
1. **Clone the repository:**
132+
```bash
133+
git clone http://github.com/NohamR/RMHook
134+
cd RMHook
135+
```
136+
137+
2. **Compile the dylib:**
138+
```bash
139+
./scripts/build.sh
140+
```

docs/help-config.png

134 KB
Loading

libs/libtinyhook.a

41.6 KB
Binary file not shown.

scripts/build.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash
2+
# Script to compile the reMarkable dylib
3+
4+
# By default, compile reMarkable
5+
APP_NAME=${1:-reMarkable}
6+
PROJECT_DIR=$(cd "$(dirname "$0")/.." && pwd)
7+
8+
# Qt path detection (adjust according to your installation)
9+
QT_PATH=${QT_PATH:-"$HOME/Qt/6.10.0"}
10+
11+
echo "🔨 Compiling $APP_NAME.dylib..."
12+
echo "📦 Qt path: $QT_PATH"
13+
14+
# Create build directories if necessary
15+
mkdir -p "$PROJECT_DIR/build"
16+
cd "$PROJECT_DIR/build"
17+
18+
# Configure with CMake and compile
19+
if [ -d "$QT_PATH" ]; then
20+
cmake -DCMAKE_PREFIX_PATH="$QT_PATH" ..
21+
else
22+
echo "⚠️ Qt not found at $QT_PATH, trying without specifying path..."
23+
cmake ..
24+
fi
25+
26+
make $APP_NAME
27+
28+
if [ $? -eq 0 ]; then
29+
echo ""
30+
echo "✅ Compilation successful!"
31+
echo "📍 Dylib: $PROJECT_DIR/build/dylibs/$APP_NAME.dylib"
32+
echo ""
33+
echo "🚀 To inject into the reMarkable application:"
34+
echo " DYLD_INSERT_LIBRARIES=\"$PROJECT_DIR/build/dylibs/$APP_NAME.dylib\" /Applications/reMarkable.app/Contents/MacOS/reMarkable"
35+
echo ""
36+
else
37+
echo "❌ Compilation failed"
38+
exit 1
39+
fi

scripts/inject.sh

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Function to display usage instructions
6+
usage() {
7+
echo "Usage: $0 <dylib> <app_path>"
8+
echo " dylib - The dynamic library to inject."
9+
echo " app_path - The path to the .app bundle."
10+
exit 1
11+
}
12+
13+
# Ensure required arguments are provided
14+
if [ "$#" -ne 2 ]; then
15+
echo "[ERROR] Incorrect number of arguments."
16+
usage
17+
fi
18+
19+
DYLIB=$1
20+
APP_PATH=$2
21+
22+
# Validate inputs
23+
if [ ! -f "$DYLIB" ]; then
24+
echo "[ERROR] The specified dynamic library ($DYLIB) does not exist."
25+
exit 1
26+
fi
27+
28+
if [ ! -d "$APP_PATH" ]; then
29+
echo "[ERROR] The specified app path ($APP_PATH) does not exist."
30+
exit 1
31+
fi
32+
33+
INFO_PLIST_PATH="$APP_PATH/Contents/Info.plist"
34+
if [ ! -f "$INFO_PLIST_PATH" ]; then
35+
echo "[ERROR] Info.plist not found at $INFO_PLIST_PATH"
36+
exit 1
37+
fi
38+
39+
# Get the executable name from Info.plist
40+
APP_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleExecutable" "$INFO_PLIST_PATH")
41+
if [ -z "$APP_NAME" ]; then
42+
echo "[ERROR] Could not read CFBundleExecutable from $INFO_PLIST_PATH"
43+
exit 1
44+
fi
45+
46+
echo "[INFO] Executable name: $APP_NAME"
47+
48+
EXECUTABLE_PATH="$APP_PATH/Contents/MacOS/$APP_NAME"
49+
if [ ! -f "$EXECUTABLE_PATH" ]; then
50+
echo "[ERROR] The specified executable ($EXECUTABLE_PATH) does not exist."
51+
exit 1
52+
fi
53+
54+
mkdir -p "$APP_PATH/Contents/Resources/"
55+
56+
# Copy the dylib to the Resources folder
57+
cp "$DYLIB" "$APP_PATH/Contents/Resources/"
58+
echo "[INFO] Copied $DYLIB to $APP_PATH/Contents/Resources/"
59+
60+
optool install -c load -p "@executable_path/../Resources/$(basename "$DYLIB")" -t "$EXECUTABLE_PATH"
61+
echo "[INFO] Injected $DYLIB into $EXECUTABLE_PATH"
62+
63+
sudo codesign --remove-signature "$EXECUTABLE_PATH"
64+
sudo xattr -cr "$EXECUTABLE_PATH"
65+
sudo codesign -f -s - --timestamp=none --all-architectures "$EXECUTABLE_PATH"
66+
sudo xattr -cr "$EXECUTABLE_PATH"
67+
68+
echo "Signed successfully."
69+
70+
# Change ownership to current user
71+
CURRENT_USER=$(whoami)
72+
CURRENT_GROUP=$(id -gn)
73+
echo "Changing ownership to $CURRENT_USER:$CURRENT_GROUP for $APP_PATH ..."
74+
sudo chown -R "$CURRENT_USER:$CURRENT_GROUP" "$APP_PATH"
75+
76+
# Remove _MASReceipt if it exists
77+
RECEIPT_PATH="$APP_PATH/Contents/_MASReceipt"
78+
if [ -d "$RECEIPT_PATH" ]; then
79+
echo "Removing _MASReceipt..."
80+
rm -r "$RECEIPT_PATH"
81+
else
82+
echo "No _MASReceipt directory found."
83+
fi
84+
85+
exit 0

0 commit comments

Comments
 (0)