mirror of
https://github.com/NohamR/RMHook.git
synced 2026-10-11 02:39:45 +00:00
Compare commits
7 Commits
bde78170be
...
v1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36d4b1258f | ||
|
|
3f1b4fd728 | ||
|
|
ce1df6f27d | ||
|
|
e00ab1d5ed | ||
|
|
a74031f228 | ||
|
|
9089839f9b | ||
|
|
8f95c65ba9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@ build/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/.vscode
|
/.vscode
|
||||||
/research
|
/research
|
||||||
|
/aqt_venv
|
||||||
|
docs/rmfakecloud_hooking.md
|
||||||
|
|||||||
@@ -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}
|
||||||
)
|
)
|
||||||
141
README.md
141
README.md
@@ -6,24 +6,27 @@ 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
|
## Other platforms
|
||||||
|
|
||||||
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.
|
- **[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
|
- 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
|
||||||
@@ -107,53 +110,44 @@ Example configuration:
|
|||||||
## 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.
|
||||||
|
|
||||||
## Troubleshooting
|
### External Access (Cloudflare Tunnel with Client Certificate)
|
||||||
|
|
||||||
### App won't launch
|
If your rmfakecloud instance is exposed via Cloudflare Tunnel with client certificate authentication, configure the additional TLS settings in `~/Library/Preferences/rmfakecloud.config`:
|
||||||
- Ensure the code signature was properly applied
|
|
||||||
- Check that `xattr -cr` was run to clear quarantine attributes
|
|
||||||
- Verify the dylib is in `Contents/Resources/` folder
|
|
||||||
|
|
||||||
### Document sync issues
|
```json
|
||||||
- Ensure your rmfakecloud server is running and accessible
|
{
|
||||||
- Verify the storage path migration was completed
|
"host": "rmfakecloud.example.com",
|
||||||
|
"port": 443,
|
||||||
|
"client_cert": "/path/to/client.crt",
|
||||||
|
"client_key": "/path/to/client.key"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## How it works
|
If your server uses a self-signed certificate or a certificate signed by a private CA:
|
||||||
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.
|
```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"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Credits
|
See the [rmfakecloud external access guide](https://ddvk.github.io/rmfakecloud/install/external-access/) for generating the client certificate.
|
||||||
- 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)
|
|
||||||
- tinyhook: [Antibioticss/tinyhook](https://github.com/Antibioticss/tinyhook/) - Function hooking framework
|
|
||||||
- rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
|
|
||||||
- optool: [alexzielenski/optool](https://github.com/alexzielenski/optool) - Mach-O binary modification tool
|
|
||||||
- zstd: [facebook/zstd](https://github.com/facebook/zstd) - Compression library
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@@ -163,6 +157,14 @@ git clone http://github.com/NohamR/RMHook
|
|||||||
cd 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:**
|
2. **Compile the dylib:**
|
||||||
```bash
|
```bash
|
||||||
./scripts/build.sh [mode]
|
./scripts/build.sh [mode]
|
||||||
@@ -191,4 +193,53 @@ Examples:
|
|||||||
./scripts/build.sh rmfakecloud # Explicitly build rmfakecloud mode
|
./scripts/build.sh rmfakecloud # Explicitly build rmfakecloud mode
|
||||||
./scripts/build.sh dev # Build with dev/reverse engineering hooks
|
./scripts/build.sh dev # Build with dev/reverse engineering hooks
|
||||||
./scripts/build.sh all # Build with all modes enabled
|
./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
|
||||||
|
|
||||||
|
### App won't launch
|
||||||
|
- Ensure the code signature was properly applied
|
||||||
|
- Check that `xattr -cr` was run to clear quarantine attributes
|
||||||
|
- Verify the dylib is in `Contents/Resources/` folder
|
||||||
|
|
||||||
|
### Document sync issues
|
||||||
|
- Ensure your rmfakecloud server is running and accessible
|
||||||
|
- Verify the storage path migration was completed
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
- 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)
|
||||||
|
- tinyhook: [Antibioticss/tinyhook](https://github.com/Antibioticss/tinyhook/) - Function hooking framework
|
||||||
|
- rmfakecloud: [ddvk/rmfakecloud](https://github.com/ddvk/rmfakecloud) - Self-hosted reMarkable cloud
|
||||||
|
- optool: [alexzielenski/optool](https://github.com/alexzielenski/optool) - Mach-O binary modification tool
|
||||||
|
- zstd: [facebook/zstd](https://github.com/facebook/zstd) - Compression library
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|||||||
BIN
docs/latest.png
BIN
docs/latest.png
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 245 KiB |
@@ -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
16
src/RMHook/Config.h
Normal 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
152
src/RMHook/Config.m
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface reMarkable : NSObject
|
@interface RMHook : NSObject
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -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
4
src/RMHook/SSLConfig.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
|
||||||
|
void SSLConfigLoad(void);
|
||||||
|
void SSLConfigApplyToRequest(QNetworkRequest &request);
|
||||||
92
src/RMHook/SSLConfig.m
Normal file
92
src/RMHook/SSLConfig.m
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user