├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── dist.sh ├── rm2fb.pro ├── scripts ├── build.sh ├── combine_headers.py └── run.sh ├── src ├── client │ ├── client.pro │ ├── frida │ │ ├── frida-gum.h │ │ └── libfrida-gum.a │ └── main.cpp ├── loader │ ├── loader.pro │ ├── main.cpp │ └── test.sh ├── reference │ ├── main.cpp │ └── rm2-framebuffer.pro ├── server │ ├── main.cpp │ ├── server.pro │ └── test.sh ├── shared │ ├── config.cpp │ ├── config.h │ ├── defines.h │ ├── ipc.cpp │ ├── mxcfb.h │ ├── now.cpp │ ├── qtdump.cpp │ └── swtfb.cpp └── xofb │ ├── main.cpp │ └── xofb.pro ├── tutorial ├── README.md └── images │ ├── 01-codebrowser.png │ ├── 02-import.png │ ├── 03-analyze-popup.png │ ├── 04-analysis-status.png │ ├── 05-search-memory.png │ ├── 06-function-xref.png │ ├── 07-function-address.png │ ├── 08-undefined-function-address.png │ ├── 09-symbol-tree.png │ ├── 10-usleep.png │ ├── 11-wait-function.png │ ├── 12-intermediate-function.png │ └── 13-get-instance-function.png └── version.pri /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gif filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | container: ghcr.io/toltec-dev/qt 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Build 19 | run: | 20 | ./dist.sh 21 | 22 | - uses: actions/upload-artifact@v4 23 | with: 24 | name: build 25 | path: dist/* 26 | if-no-files-found: error 27 | 28 | - name: Release 29 | uses: softprops/action-gh-release@v1 30 | if: startsWith(github.ref, 'refs/tags/') 31 | with: 32 | files: dist/* 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | Makefile 3 | *.user 4 | .qmake.stash 5 | rm2-framebuffer 6 | *.so.* 7 | *.so 8 | dist 9 | rm2fb*.tar.gz 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ddvk okay 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## remarkable2-framebuffer 2 | 3 | This repo contains code for drawing to the rM2's framebuffer. 4 | 5 | ## Status 6 | 7 | quality: beta 8 | 9 | rm2fb can open the framebuffer and draw to it. rm2fb-server exposes a simple 10 | API for other processes to draw to the framebuffer using shared mem and message 11 | queues. rm2fb-client is a shim that creates a fake framebuffer device for apps 12 | to use, allowing rM1 apps to seamlessly draw to the display of the rM2. 13 | 14 | ## Installation 15 | 16 | Before installing, be sure that your xochitl version is supported: https://github.com/ddvk/remarkable2-framebuffer/blob/master/src/shared/config.cpp. 17 | 18 | rm2fb is available as a package in [toltec](https://github.com/toltec-dev/toltec) which 19 | sets up the server and client parts for you. Otherwise, the instructions below can be used 20 | for manual installation. 21 | 22 | ## Set up build environment 23 | 24 | [set up remarkable toolchain](https://remarkable.guide/devel/toolchains.html) 25 | 26 | ## Building 27 | 28 | Source the remarkable toolchain (example below) and run the following (replacing ${GITROOT} with the directory you have checked the repository out to) 29 | 30 | ``` 31 | source /usr/local/oecore-x86_64/environment-setup-cortexa9hf-neon-oe-linux-gnueabi 32 | cd ${GITROOT} 33 | qmake 34 | make 35 | ``` 36 | 37 | 38 | ### Framebuffer Server 39 | 40 | build `src/server/librm2fb_server.so.1.0.1`. Copy it to your 41 | remarkable and run: 42 | 43 | ``` 44 | LD_PRELOAD=/path/to/librm2fb_server.so.1.0.1 /usr/bin/xochitl 45 | ``` 46 | 47 | ### Framebuffer Client Shim 48 | 49 | build `src/client/librm2fb_client.so.1.0.1`. Copy it to your 50 | remarkable and run: `LD_PRELOAD=/path/to/librm2fb_client.so.1.0.1 ` to 51 | run your app. 52 | 53 | The client intercepts interactions with `/dev/fb0` and forwards them to the 54 | rm2fb server. 55 | 56 | ## Configuration 57 | 58 | To do their job, both the client and the server need to know the address at which a number of functions reside in the Xochitl binary. 59 | These addresses change from one release to the next. 60 | 61 | Xochitl function name | Xochitl function role | Notes 62 | --------------|---------------|---------- 63 | `update` | Sends an update request. This function is replaced by the client shim to talk to our server instead. | Uses the string `Unable to complete update: invalid waveform (`. Prior to version 2.9, the signature of this function was `void (*)(void*, int, int, int, int, int, int)`. Starting from version 2.9, it changed to `void (*)(void*, QRect&, int, int)`. 64 | `create` | Start the threads that handle update requests. This function is replaced by the client shim with a no-op to avoid conflicting with the server. | Uses the string `Unable to start generator thread\n`. 65 | `wait` | Waits until the update-handling threads have started. This function is replaced by the client shim with a no-op to avoid conflicting with the server. | Calls `usleep(1000)`. 66 | `shutdown` | Stops the update-handling threads. This function is replaced by the client shim with a no-op to avoid conflicting with the server. | Uses the string `Shutting down...`. 67 | `getInstance` | Retrieves the instance of the singleton SWTCON class. This function is used by the server to interact with the screen. | Calls a function that itself calls `create` and `wait`. 68 | `notify` | Called when the framebuffer has been updated, used for the Qt signal/slot connections needed for ScreenShare to work | 69 | 70 | The client and the server both ship the [hardcoded addresses](https://github.com/ddvk/remarkable2-framebuffer/blob/master/src/shared/config.cpp#L13) for these functions for various releases. 71 | If you get a message saying `Missing address for function […]`, it means that the release you’re running is not yet supported. Please report this in [this dedicated thread](https://github.com/ddvk/remarkable2-framebuffer/issues/18). 72 | 73 | You can manually locate the addresses of the functions listed above by [looking at the disassembly and then add a configuration entry to make remarkable2-framebuffer work with your release](tutorial). 74 | In addition to the hardcoded configuration entries, the client and the server will look for addresses in configuration files in the following locations: 75 | 76 | * `/usr/share/rm2fb.conf` 77 | * `/opt/share/rm2fb.conf` 78 | * `/etc/rm2fb.conf` 79 | * `/opt/etc/rm2fb.conf` (best option for Toltec users) 80 | * `rm2fb.conf` (relative to the current working directory, best option for manual installs) 81 | 82 | ## [Demo](https://imgur.com/gallery/zGMn7Qs) 83 | 84 | ## Contributing 85 | 86 | Please look at the open github issues and add a new one for any work you are planning 87 | on doing so people can see ongoing progress. 88 | 89 | Things that can use help: 90 | 91 | * writing documentation 92 | * testing apps and packages 93 | * understanding the waveforms used by SWTCON 94 | * writing our own implementation of SWTCON 95 | * adding support for new OS releases 96 | 97 | ## FAQ 98 | 99 | * will this brick my rM2? 100 | 101 | probably not, but no guarantees 102 | 103 | * [what apps are supported?](https://github.com/ddvk/remarkable2-framebuffer/issues/14) 104 | 105 | * [how does rm2fb work?](https://github.com/ddvk/remarkable2-framebuffer/issues/5#issuecomment-718948222) 106 | 107 | * [how do I port my existing app to rM2?](https://github.com/ddvk/remarkable2-framebuffer/issues/13) 108 | 109 | * [what's up with server/client API?](https://github.com/ddvk/remarkable2-framebuffer/issues/4) 110 | 111 | * [where can i find more information on the framebuffer?](https://web.archive.org/web/20230619082431/https://remarkablewiki.com/tech/rm2_framebuffer) 112 | 113 | * [what about implementing an open source SWTCON?](https://github.com/timower/rM2-stuff/) 114 | 115 | * how can I get in touch? 116 | 117 | use github issues or ping on the discord in one of the homebrew developer 118 | channels. if you mention this repo, someone will probably respond 119 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | qmake 2 | make 3 | mkdir -p ./dist 4 | version=$(grep VERSION= version.pri | cut -d= -f2) 5 | arm-linux-gnueabihf-strip ./src/client/librm2fb_client.so.$version 6 | cp ./src/xofb/librm2fb_xofb.so.$version ./dist 7 | cp ./src/server/librm2fb_server.so.$version ./dist 8 | cp ./src/client/librm2fb_client.so.$version ./dist 9 | -------------------------------------------------------------------------------- /rm2fb.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | include(version.pri) 4 | 5 | SUBDIRS = \ 6 | src/loader \ # relative paths 7 | src/server \ 8 | src/client \ 9 | src/xofb \ 10 | 11 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | qmake 2 | make 3 | mkdir -p dist/rm2fb 4 | version=$(grep VERSION= version.pri | cut -d= -f2) 5 | cp src/loader/librm2fb_demo.so.$version dist/rm2fb/ 6 | cp src/server/librm2fb_server.so.$version dist/rm2fb/ 7 | cp src/client/librm2fb_client.so.$version dist/rm2fb/ 8 | cp scripts/run.sh dist/rm2fb/rm2fb.sh 9 | chmod +x dist/rm2fb/rm2fb.sh 10 | cd dist/ 11 | rev=`git rev-parse --short HEAD` 12 | 13 | tar -cvzf rm2fb.${rev}.tar.gz rm2fb/ 14 | -------------------------------------------------------------------------------- /scripts/combine_headers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | import argparse 4 | import copy 5 | import os 6 | import sys 7 | from collections import defaultdict 8 | 9 | DEBUG = os.getenv("DEBUG", True) 10 | DEBUG_GRAPH=False 11 | 12 | def gather_includes(file, includes, parent=None, base_dir=None): 13 | file_path = os.path.normpath(os.path.join(base_dir or "", file)) 14 | 15 | if file_path == parent: 16 | return 17 | 18 | 19 | if file_path in includes: 20 | if parent: 21 | includes[parent].add(file_path) 22 | return 23 | 24 | if parent: 25 | includes[parent].add(file_path) 26 | 27 | dirname = os.path.dirname(file) 28 | fname = os.path.basename(file) 29 | 30 | if base_dir == None: 31 | base_dir = dirname 32 | else: 33 | base_dir = os.path.join(base_dir, dirname) 34 | 35 | if not os.path.exists(file_path): 36 | print("missing file:", file, file=sys.stderr) 37 | return 38 | 39 | file = os.path.join(base_dir, fname) 40 | 41 | with open(file_path) as f: 42 | lines = f.readlines() 43 | 44 | for line in lines: 45 | cline = line.strip() 46 | if cline.startswith("#include"): 47 | tokens = cline.split() 48 | include = tokens[1] 49 | if include[0] != '"': 50 | continue 51 | include = include.strip('"') 52 | 53 | fname, ext = os.path.splitext(include) 54 | gather_includes(include, includes, parent=file_path, base_dir=base_dir) 55 | 56 | return includes 57 | 58 | 59 | def gather_files(files): 60 | ret = defaultdict(set) 61 | original_dir = os.getcwd() 62 | for file in files: 63 | if file == "-": 64 | ret[file] = [] 65 | continue 66 | 67 | if not os.path.exists(file): 68 | continue 69 | file_dir, file = os.path.split(file) 70 | gather_includes(file, ret, parent=None, base_dir=file_dir) 71 | 72 | os.chdir(original_dir) 73 | 74 | full_graph = defaultdict(set) 75 | for node, neighbors in ret.items(): 76 | for neighbor in neighbors: 77 | if not neighbor in full_graph: 78 | full_graph[neighbor] = set() 79 | full_graph[node].update(neighbors) 80 | 81 | return full_graph 82 | 83 | 84 | def get_parser(): 85 | parser = argparse.ArgumentParser( 86 | description="Take multiple .h and .cpp files and output a single .h" 87 | ) 88 | parser.add_argument( 89 | "files", 90 | metavar="files", 91 | type=str, 92 | nargs="+", 93 | help=".h and .cpp files to combine", 94 | ) 95 | return parser 96 | 97 | 98 | def n2_top_sort(graph): 99 | ret = [] 100 | while graph: 101 | batch = set() 102 | for f, deps in graph.items(): 103 | if not deps: 104 | batch.add(f) 105 | for f in graph.keys(): 106 | graph[f] -= batch 107 | 108 | ret.extend(list(sorted(batch))) 109 | for f in batch: 110 | del graph[f] 111 | if not batch and graph: 112 | print("found cyclic dependency", graph, file=sys.stderr) 113 | exit() 114 | return list(ret) 115 | 116 | 117 | def concat_files(files, outfile, graph): 118 | outfile.write("#define __SH_BUILD"); 119 | for file in files: 120 | if not os.path.exists(file): 121 | print("MISSING FILE!", file, file=sys.stderr) 122 | continue 123 | 124 | with open(file, "r") as infile: 125 | if DEBUG: 126 | outfile.write("\n/* FILE: %s */\n" % file) 127 | deps = graph[file] 128 | outfile.write("\n/* REQUIRES:") 129 | for f in deps: 130 | outfile.write("\n%s" % f) 131 | outfile.write(" */\n") 132 | 133 | for line in infile.readlines(): 134 | if not (line.startswith("#include \"")): 135 | outfile.write(line) 136 | outfile.write("\n") 137 | 138 | 139 | def print_graph(graph): 140 | for node in graph: 141 | print(node, file=sys.stderr) 142 | for n in graph[node]: 143 | print(" ", n, file=sys.stderr) 144 | print("", file=sys.stderr) 145 | 146 | def compile(files, into=None): 147 | graph = gather_files(files) 148 | orig_graph = copy.deepcopy(graph) 149 | 150 | if DEBUG_GRAPH: 151 | print_graph(graph) 152 | 153 | ordered = n2_top_sort(graph) 154 | if not into.endswith(".h") and not into.endswith(".hpp"): 155 | into = "%s.h" % (into) 156 | print("generating single header", into, file=sys.stderr) 157 | with open(into, "w") as output: 158 | concat_files(ordered, output, orig_graph) 159 | 160 | 161 | if __name__ == "__main__": 162 | parser = get_parser() 163 | args, unknown = parser.parse_known_args() 164 | if not args.files: 165 | parser.print_help() 166 | else: 167 | if not os.path.exists("dist"): 168 | os.makedirs("dist") 169 | compile(args.files, "dist/librm2fb.hpp") 170 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 3 | systemctl stop xochitl 4 | version=$(grep VERSION= version.pri | cut -d= -f2) 5 | LD_PRELOAD=${DIR}/librm2fb_server.so.$version `which xochitl` & 6 | pid=$! 7 | sleep 2 8 | LD_PRELOAD=${DIR}/librm2fb_client.so.$version $* 9 | pid2=$! 10 | wait $pid2 11 | kill -9 -${pid} 12 | kill -9 -${pid2} 13 | systemctl start xochitl 14 | -------------------------------------------------------------------------------- /src/client/client.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (3.1) Tue Oct 27 11:57:42 2020 3 | ###################################################################### 4 | 5 | TEMPLATE = lib 6 | TARGET = rm2fb_client 7 | INCLUDEPATH += . 8 | include(../../version.pri) 9 | 10 | # The following define makes your compiler warn you if you use any 11 | # feature of Qt which has been marked as deprecated (the exact warnings 12 | # depend on your compiler). Please consult the documentation of the 13 | # deprecated API in order to know how to port your code away from it. 14 | DEFINES += QT_DEPRECATED_WARNINGS 15 | 16 | # You can also make your code fail to compile if you use deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | # Input 22 | SOURCES += main.cpp ../shared/config.cpp 23 | CONFIG += hide_symbols 24 | QT -= gui 25 | QMAKE_CXXFLAGS += -std=c++17 26 | QMAKE_LFLAGS_RPATH= 27 | LIBS += -lrt -ldl -Wl,--exclude-libs,ALL 28 | 29 | !contains(DEFINES, NO_XOCHITL) { 30 | LIBS += frida/libfrida-gum.a 31 | } 32 | -------------------------------------------------------------------------------- /src/client/frida/libfrida-gum.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/src/client/frida/libfrida-gum.a -------------------------------------------------------------------------------- /src/client/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "../shared/ipc.cpp" 21 | #include "../shared/config.h" 22 | 23 | #ifndef NO_XOCHITL 24 | #include "frida/frida-gum.h" 25 | #endif 26 | 27 | #define SEM_WAIT_TIMEOUT 200000000 /* 200 * 1000 * 1000, e.g. 200ms */ 28 | 29 | constexpr auto msg_q_id = 0x2257c; 30 | swtfb::ipc::Queue MSGQ(msg_q_id); 31 | 32 | uint16_t *SHARED_BUF = nullptr; 33 | 34 | constexpr auto BYTES_PER_PIXEL = sizeof(*SHARED_BUF); 35 | 36 | bool IN_XOCHITL = false; 37 | bool DO_WAIT_IOCTL = true; 38 | bool ON_RM2 = false; 39 | 40 | extern "C" { 41 | 42 | __attribute__((constructor)) 43 | void init() { 44 | if(getenv("RM2FB_DISABLE") != nullptr){ 45 | return; 46 | } 47 | std::ios_base::Init i; 48 | 49 | std::ifstream device_id_file{"/sys/devices/soc0/machine"}; 50 | std::string device_id; 51 | std::getline(device_id_file, device_id); 52 | 53 | if (device_id == "reMarkable 2.0") { 54 | SHARED_BUF = swtfb::ipc::get_shared_buffer(); 55 | ON_RM2 = true; 56 | 57 | constexpr auto VERSION = "0.1"; 58 | setenv("RM2FB_SHIM", VERSION, true); 59 | 60 | if (getenv("RM2FB_ACTIVE") != nullptr) { 61 | setenv("RM2FB_NESTED", "1", true); 62 | } else { 63 | setenv("RM2FB_ACTIVE", "1", true); 64 | } 65 | 66 | if (getenv("RM2FB_NO_WAIT_IOCTL") != nullptr) { 67 | DO_WAIT_IOCTL = false; 68 | } 69 | } 70 | } 71 | 72 | __attribute__((visibility("default"))) 73 | void _ZN6QImageC1EiiNS_6FormatE(void *that, int x, int y, int f) { 74 | static bool FIRST_ALLOC = true; 75 | static const auto qImageCtor = (void (*)(void *, int, int, int))dlsym( 76 | RTLD_NEXT, "_ZN6QImageC1EiiNS_6FormatE"); 77 | static const auto qImageCtorWithBuffer = (void (*)( 78 | void *, uint8_t *, int32_t, int32_t, int32_t, int, void (*)(void *), 79 | void *))dlsym(RTLD_NEXT, "_ZN6QImageC1EPhiiiNS_6FormatEPFvPvES2_"); 80 | 81 | if (ON_RM2 && IN_XOCHITL && x == swtfb::WIDTH && y == swtfb::HEIGHT && FIRST_ALLOC) { 82 | fprintf(stderr, "REPLACING THE IMAGE with shared memory\n"); 83 | 84 | FIRST_ALLOC = false; 85 | qImageCtorWithBuffer(that, (uint8_t *)SHARED_BUF, swtfb::WIDTH, 86 | swtfb::HEIGHT, swtfb::WIDTH * BYTES_PER_PIXEL, f, 87 | nullptr, nullptr); 88 | return; 89 | } 90 | qImageCtor(that, x, y, f); 91 | } 92 | 93 | __attribute__((visibility("default"))) 94 | int open64(const char *pathname, int flags, mode_t mode = 0) { 95 | if (ON_RM2 && !IN_XOCHITL) { 96 | if (pathname == std::string("/dev/fb0")) { 97 | return swtfb::ipc::SWTFB_FD; 98 | } 99 | } 100 | 101 | static const auto func_open = (int (*)(const char *, int, mode_t)) 102 | dlsym(RTLD_NEXT, "open64"); 103 | 104 | return func_open(pathname, flags, mode); 105 | } 106 | 107 | __attribute__((visibility("default"))) 108 | int open(const char *pathname, int flags, mode_t mode = 0) { 109 | if (ON_RM2 && !IN_XOCHITL) { 110 | if (pathname == std::string("/dev/fb0")) { 111 | return swtfb::ipc::SWTFB_FD; 112 | } 113 | } 114 | 115 | static const auto func_open = (int (*)(const char *, int, mode_t)) 116 | dlsym(RTLD_NEXT, "open"); 117 | 118 | return func_open(pathname, flags, mode); 119 | } 120 | 121 | __attribute__((visibility("default"))) 122 | int close(int fd) { 123 | if (ON_RM2 && fd == swtfb::ipc::SWTFB_FD) { 124 | return 0; 125 | } 126 | 127 | static const auto func_close = (int (*)(int))dlsym(RTLD_NEXT, "close"); 128 | 129 | return func_close(fd); 130 | } 131 | 132 | __attribute__((visibility("default"))) 133 | int ioctl(int fd, unsigned long request, char *ptr) { 134 | if (ON_RM2 && !IN_XOCHITL && fd == swtfb::ipc::SWTFB_FD) { 135 | if (request == MXCFB_SEND_UPDATE) { 136 | 137 | mxcfb_update_data *update = (mxcfb_update_data *)ptr; 138 | MSGQ.send(*update); 139 | return 0; 140 | } else if (request == MXCFB_SET_AUTO_UPDATE_MODE) { 141 | 142 | return 0; 143 | } else if (request == MXCFB_WAIT_FOR_UPDATE_COMPLETE) { 144 | #ifdef DEBUG 145 | std::cerr << "CLIENT: sync" << std::endl; 146 | #endif 147 | 148 | if (!DO_WAIT_IOCTL) { 149 | return 0; 150 | } 151 | 152 | // for wait ioctl, we drop a WAIT_t message into the queue. the server 153 | // then uses that message to signal the semaphore we just opened. this 154 | // can take as little as 0.5ms for small updates. one difference is that 155 | // the ioctl now waits for all pending updates, not just the requested 156 | // scheduled one. 157 | swtfb::ClockWatch cz; 158 | swtfb::wait_sem_data update; 159 | std::string sem_name = std::string("/rm2fb.wait."); 160 | sem_name += std::to_string(getpid()); 161 | 162 | memcpy(update.sem_name, sem_name.c_str(), sem_name.size()); 163 | update.sem_name[sem_name.size()] = 0; 164 | 165 | MSGQ.send(update); 166 | 167 | sem_t *sem = sem_open(update.sem_name, O_CREAT, 0644, 0); 168 | struct timespec timeout; 169 | if (clock_gettime(CLOCK_REALTIME, &timeout) == -1) { 170 | // Probably unnecessary fallback 171 | timeout = {0, 0}; 172 | #ifdef DEBUG 173 | std::cerr << "clock_gettime failed" << std::endl; 174 | #endif 175 | } 176 | 177 | timeout.tv_nsec += SEM_WAIT_TIMEOUT; 178 | // Move overflow ns to secs 179 | timeout.tv_sec += timeout.tv_nsec / (long) 1e9; 180 | timeout.tv_nsec %= (long) 1e9; 181 | 182 | sem_timedwait(sem, &timeout); 183 | 184 | // on linux, unlink will delete the semaphore once all processes using 185 | // it are closed. the idea here is that the client removes the semaphore 186 | // as soon as possible 187 | // TODO: validate this assumption 188 | sem_unlink(update.sem_name); 189 | 190 | #ifdef DEBUG 191 | std::cerr << "FINISHED WAIT IOCTL " << cz.elapsed() << std::endl; 192 | #endif 193 | return 0; 194 | } 195 | 196 | else if (request == FBIOGET_VSCREENINFO) { 197 | 198 | fb_var_screeninfo *screeninfo = (fb_var_screeninfo *)ptr; 199 | screeninfo->xres = swtfb::WIDTH; 200 | screeninfo->yres = swtfb::HEIGHT; 201 | screeninfo->grayscale = 0; 202 | screeninfo->bits_per_pixel = 8 * BYTES_PER_PIXEL; 203 | screeninfo->xres_virtual = swtfb::WIDTH; 204 | screeninfo->yres_virtual = swtfb::HEIGHT; 205 | 206 | //set to RGB565 207 | screeninfo->red.offset = 11; 208 | screeninfo->red.length = 5; 209 | screeninfo->green.offset = 5; 210 | screeninfo->green.length = 6; 211 | screeninfo->blue.offset = 0; 212 | screeninfo->blue.length = 5; 213 | return 0; 214 | } 215 | 216 | else if (request == FBIOPUT_VSCREENINFO) { 217 | 218 | return 0; 219 | } else if (request == FBIOGET_FSCREENINFO) { 220 | 221 | fb_fix_screeninfo *screeninfo = (fb_fix_screeninfo *)ptr; 222 | screeninfo->smem_len = swtfb::ipc::BUF_SIZE; 223 | screeninfo->smem_start = (unsigned long)SHARED_BUF; 224 | screeninfo->line_length = swtfb::WIDTH * BYTES_PER_PIXEL; 225 | constexpr char fb_id[] = "mxcfb"; 226 | memcpy(screeninfo->id, fb_id, sizeof(fb_id)); 227 | return 0; 228 | } else { 229 | std::cerr << "UNHANDLED IOCTL" << ' ' << request << std::endl; 230 | return 0; 231 | } 232 | } 233 | 234 | static auto func_ioctl = 235 | (int (*)(int, unsigned long request, ...))dlsym(RTLD_NEXT, "ioctl"); 236 | 237 | return func_ioctl(fd, request, ptr); 238 | } 239 | 240 | __attribute__((visibility("default"))) 241 | bool _Z7qputenvPKcRK10QByteArray(const char *name, const QByteArray &val) { 242 | static const auto touchArgs = QByteArray("rotate=180:invertx"); 243 | static const auto orig_fn = (bool (*)(const char *, const QByteArray &))dlsym( 244 | RTLD_NEXT, "_Z7qputenvPKcRK10QByteArray"); 245 | 246 | if (ON_RM2 && strcmp(name, "QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS") == 0) { 247 | return orig_fn(name, touchArgs); 248 | } 249 | 250 | return orig_fn(name, val); 251 | } 252 | 253 | // called when the framebuffer is updated 254 | typedef uint32_t (*NotifyFunc)(void*, void*); 255 | NotifyFunc f_notify = 0; 256 | void new_update_int4(void* arg, int x1, int y1, int x2, int y2, int waveform, int flags) { 257 | #ifdef DEBUG 258 | std::cerr << "UPDATE HOOK CALLED" << std::endl; 259 | std::cerr << "x " << x1 << " " << x2 << std::endl; 260 | std::cerr << "y " << y1 << " " << y2 << std::endl; 261 | std::cerr << "wav " << waveform << " flags " << flags << std::endl; 262 | #endif 263 | 264 | swtfb::xochitl_data data; 265 | data.x1 = x1; 266 | data.x2 = x2; 267 | data.y1 = y1; 268 | data.y2 = y2; 269 | data.waveform = waveform; 270 | data.flags = flags; 271 | MSGQ.send(data); 272 | 273 | if (f_notify != 0) { 274 | QRect someRect(x1,y1, x2-x1, y2-y1) ; 275 | f_notify(arg, &someRect); 276 | } 277 | } 278 | 279 | void new_update_QRect(void* arg, QRect& rect, int waveform, bool flags) { 280 | new_update_int4( 281 | arg, 282 | rect.x(), rect.y(), 283 | rect.x() + rect.width(), 284 | rect.y() + rect.height(), 285 | waveform, flags 286 | ); 287 | } 288 | 289 | int new_create_threads(char*, void*) { 290 | std::cerr << "create threads called" << std::endl; 291 | return 0; 292 | } 293 | 294 | int new_wait() { 295 | std::cerr << "wait clear func called" << std::endl; 296 | return 0; 297 | } 298 | 299 | int new_shutdown() { 300 | std::cerr << "shutdown called" << std::endl; 301 | return 0; 302 | } 303 | 304 | std::string readlink_string(const char* link_path) { 305 | char buffer[PATH_MAX]; 306 | ssize_t len = readlink(link_path, buffer, sizeof(buffer) - 1); 307 | 308 | if (len == -1) { 309 | return ""; 310 | } 311 | 312 | buffer[len] = '\0'; 313 | return buffer; 314 | } 315 | 316 | #ifndef NO_XOCHITL 317 | 318 | // exits if it fails since we won't get far with xochitl 319 | // without these funcs stubbed out 320 | void replace_func( 321 | GumInterceptor* interceptor, 322 | const Config& config, 323 | std::string func_name, 324 | void* new_func 325 | ) { 326 | auto search = config.find(func_name); 327 | 328 | if (search == config.end()) { 329 | std::cerr << "Missing address for function '" << func_name << "'\n" 330 | "PLEASE SEE https://github.com/ddvk/remarkable2-framebuffer/issues/18\n"; 331 | std::exit(-1); 332 | } 333 | 334 | void* old_func = std::get(search->second); 335 | std::cerr << "Replacing '" << func_name << "' (at " << old_func << "): "; 336 | 337 | if (gum_interceptor_replace( 338 | interceptor, old_func, new_func, nullptr) != GUM_REPLACE_OK) { 339 | std::cerr << "ERR\n"; 340 | std::exit(-1); 341 | } else { 342 | std::cerr << "OK\n"; 343 | } 344 | } 345 | 346 | void intercept_xochitl(const Config& config) { 347 | gum_init_embedded(); 348 | GumInterceptor *interceptor = gum_interceptor_obtain(); 349 | const auto update_type = config.find("updateType"); 350 | replace_func( 351 | interceptor, config, "update", 352 | (update_type == config.end() 353 | || std::get(update_type->second) == "int4") 354 | ? (void*) new_update_int4 355 | : (void*) new_update_QRect 356 | ); 357 | replace_func(interceptor, config, "create", (void*) new_create_threads); 358 | replace_func(interceptor, config, "shutdown", (void*) new_shutdown); 359 | replace_func(interceptor, config, "wait", (void*) new_wait); 360 | 361 | auto search = config.find("notify"); 362 | if (search == config.end()) { 363 | std::cerr << "missing notify function, screenshare won't work" << std::endl; 364 | } else { 365 | f_notify = (NotifyFunc) std::get(search->second); 366 | } 367 | } 368 | 369 | __attribute__((visibility("default"))) 370 | int __libc_start_main(int (*_main)(int, char **, char **), int argc, 371 | char **argv, int (*init)(int, char **, char **), 372 | void (*fini)(void), void (*rtld_fini)(void), 373 | void *stack_end) { 374 | const auto config = read_config(); 375 | 376 | #ifdef DEBUG 377 | std::cerr << "Final config:\n"; 378 | for (const auto& [key, value] : config) { 379 | std::cerr << key; 380 | if (std::holds_alternative(value)) { 381 | std::cerr << " str " << std::get(value) << '\n'; 382 | } else { 383 | std::cerr << " addr " << std::get(value) << '\n'; 384 | } 385 | } 386 | #endif 387 | 388 | if (ON_RM2) { 389 | auto binary_path = readlink_string("/proc/self/exe"); 390 | 391 | if (binary_path.empty()) { 392 | std::cerr << "Unable to find current binary path\n"; 393 | return -1; 394 | } 395 | 396 | if (binary_path == "/usr/bin/xochitl") { 397 | IN_XOCHITL = true; 398 | intercept_xochitl(config); 399 | } 400 | } 401 | 402 | auto func_main = 403 | (decltype(&__libc_start_main))dlsym(RTLD_NEXT, "__libc_start_main"); 404 | 405 | return func_main(_main, argc, argv, init, fini, rtld_fini, stack_end); 406 | } 407 | 408 | #endif // NO_XOCHITL 409 | 410 | } // extern "C" 411 | -------------------------------------------------------------------------------- /src/loader/loader.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (3.1) Tue Oct 27 11:57:42 2020 3 | ###################################################################### 4 | 5 | TEMPLATE = lib 6 | TARGET = rm2fb_demo 7 | INCLUDEPATH += . 8 | include(../../version.pri) 9 | 10 | # The following define makes your compiler warn you if you use any 11 | # feature of Qt which has been marked as deprecated (the exact warnings 12 | # depend on your compiler). Please consult the documentation of the 13 | # deprecated API in order to know how to port your code away from it. 14 | DEFINES += QT_DEPRECATED_WARNINGS 15 | 16 | # You can also make your code fail to compile if you use deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | # Input 22 | SOURCES += main.cpp ../shared/config.cpp 23 | QMAKE_CXXFLAGS += -std=c++17 24 | LIBS += -lrt 25 | -------------------------------------------------------------------------------- /src/loader/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../shared/swtfb.cpp" 2 | 3 | #include 4 | #include 5 | 6 | #ifndef _GNU_SOURCE 7 | #define _GNU_SOURCE 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | void dump_qtClass(QObject* object) { 14 | const QMetaObject *meta = object->metaObject(); 15 | qDebug() << meta->className(); 16 | 17 | qDebug() << "Methods"; 18 | for (int id = 0; id < meta->methodCount(); ++id) { 19 | const QMetaMethod method = meta->method(id); 20 | if (method.methodType() == QMetaMethod::Slot && method.access() == QMetaMethod::Public) 21 | qDebug() << method.access() << method.name() << method.parameterNames() << method.parameterTypes(); 22 | } 23 | 24 | qDebug() << "Properties"; 25 | for (int id = 0; id < meta->propertyCount(); ++id) { 26 | const QMetaProperty property = meta->property(id); 27 | qDebug() << property.name() << property.type(); 28 | } 29 | 30 | qDebug() << "Enumerators"; 31 | for (int id = 0; id < meta->enumeratorCount(); ++id) { 32 | const QMetaEnum en = meta->enumerator(id); 33 | qDebug() << en.name(); 34 | for (int j = 0; j < en.keyCount(); j++) { 35 | qDebug() << en.key(j); 36 | } 37 | qDebug() << ""; 38 | } 39 | } 40 | 41 | extern "C" { 42 | static void _libhook_init() __attribute__((constructor)); 43 | static void _libhook_init() { printf("LIBHOOK INIT\n"); } 44 | 45 | int main(int, char **, char **) { 46 | swtfb::SwtFB fb; 47 | while(true) { 48 | cout << "Waiting: " << endl; 49 | std::cin.ignore(); 50 | fb.FullScreen(0xFF); 51 | cout << "again" << endl; 52 | fb.DrawLine(); 53 | 54 | } 55 | 56 | printf("END of our main\n"); 57 | } 58 | 59 | int __libc_start_main(int (*)(int, char **, char **), int argc, 60 | char **argv, int (*init)(int, char **, char **), 61 | void (*fini)(void), void (*rtld_fini)(void), 62 | void *stack_end) { 63 | 64 | printf("LIBC START HOOK\n"); 65 | 66 | auto func_main = 67 | (decltype(&__libc_start_main))dlsym(RTLD_NEXT, "__libc_start_main"); 68 | 69 | return func_main(main, argc, argv, init, fini, rtld_fini, stack_end); 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /src/loader/test.sh: -------------------------------------------------------------------------------- 1 | make clean 2 | make && scp librm2fb_demo.so.1.0.0 rm: && ssh -tt rm "LD_PRELOAD=/home/root/librm2fb_demo.so.1.0.0 ./remarkable-shutdown" 3 | -------------------------------------------------------------------------------- /src/reference/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define SCWIDTH 1404 10 | #define SCHEIGHT 1872 11 | static uint8_t arr[0x16580]; 12 | static uint8_t InitMask[0x16580]; 13 | static uint8_t buffer[SCWIDTH*SCHEIGHT]; 14 | 15 | void create_mask() { 16 | } 17 | 18 | void check_error(int err, char *msg){ 19 | if (err < 0) { 20 | printf(msg); 21 | exit(1); 22 | } 23 | } 24 | void load_waveform(char *fileName){ 25 | FILE *f = fopen(fileName, "rb"); 26 | if (f == NULL) { 27 | puts("cant open waveform"); 28 | exit(1); 29 | } 30 | // more loading logic 31 | } 32 | 33 | int main(int argc, char *argv[]) 34 | { 35 | //QCoreApplication a(argc, argv); 36 | 37 | fb_var_screeninfo vinfo; 38 | fb_fix_screeninfo finfo; 39 | 40 | auto fd = open("/dev/fb0", O_RDWR); 41 | check_error(fd, "cant get vcreen"); 42 | 43 | auto result = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); 44 | check_error(result, "cant get vcreen"); 45 | 46 | result = ioctl(fd, FBIOGET_FSCREENINFO, &finfo); 47 | check_error(result, "cant get fcreen"); 48 | 49 | 50 | // todo: set the fb 51 | vinfo.yscreen = 1; 52 | result = ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo); 53 | check_error(result, "cant set vcreen"); 54 | 55 | size_t size = vinfo.yres * finfo.line_length; 56 | printf("%d", size); 57 | uint8_t *fbp = (uint8_t*) mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0); 58 | if (fbp == NULL){ 59 | puts("cant mmap"); 60 | exit(1); 61 | } 62 | 63 | QImage img(SCWIDTH, SCHEIGHT, QImage::Format_RGB16); 64 | img.fill(Qt::white); 65 | 66 | //mangle and rotate the bits to a grayscale 67 | 68 | auto bits = img.bits(); 69 | memcpy(fbp, bits, 1000); 70 | 71 | 72 | //apply the waveform to the mangled 73 | //copy the mangled to fb 74 | //ioctl(4606) 75 | 76 | 77 | printf("exiting...\n"); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /src/reference/rm2-framebuffer.pro: -------------------------------------------------------------------------------- 1 | QT += gui 2 | 3 | CONFIG += c++11 console 4 | CONFIG -= app_bundle 5 | 6 | # You can make your code fail to compile if it uses deprecated APIs. 7 | # In order to do so, uncomment the following line. 8 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 9 | 10 | SOURCES += \ 11 | main.cpp 12 | 13 | # Default rules for deployment. 14 | qnx: target.path = /tmp/$${TARGET}/bin 15 | else: unix:!android: target.path = /opt/$${TARGET}/bin 16 | !isEmpty(target.path): INSTALLS += target 17 | -------------------------------------------------------------------------------- /src/server/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../shared/ipc.cpp" 4 | #include "../shared/swtfb.cpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef _GNU_SOURCE 12 | #define _GNU_SOURCE 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace swtfb; 27 | 28 | int msg_q_id = 0x2257c; 29 | ipc::Queue MSGQ(msg_q_id); 30 | 31 | const int BYTES_PER_PIXEL = sizeof(uint16_t); 32 | 33 | uint16_t *shared_mem; 34 | 35 | void doUpdate(SwtFB &fb, const swtfb_update &s) { 36 | // Taken from KOreader 37 | static const int WAVEFORM_MODE_INIT = 0; 38 | static const int WAVEFORM_MODE_DU = 1; 39 | static const int WAVEFORM_MODE_GC16 = 2; 40 | static const int WAVEFORM_MODE_GL16 = 3; 41 | static const int WAVEFORM_MODE_A2 = 4; 42 | 43 | auto data = s.mdata.update; 44 | const auto& rect = data.update_region; 45 | 46 | #ifdef DEBUG_DIRTY 47 | std::cerr << "Dirty Region: " << rect.left << " " << rect.top << " " 48 | << rect.width << " " << rect.height << endl; 49 | #endif 50 | 51 | 52 | // There are three update modes on the rm2. But they are mapped to the five 53 | // rm1 modes as follows: 54 | // 55 | // 0: DU 56 | // 1: GC16: High fidelity / init with full refresh flag. 57 | // 2: GL16: Faster grayscale 58 | // 3: A2?: Pan & Zoom mode, 3.3+ GC16 mode 59 | 60 | // full = 1, partial = 0 61 | int flags = data.update_mode == UPDATE_MODE_FULL ? 0x1 : 0x0; 62 | 63 | // old translation 64 | int waveform = [&] { 65 | switch (data.waveform_mode) { 66 | case WAVEFORM_MODE_DU: 67 | return 1; 68 | case WAVEFORM_MODE_GC16: 69 | if (fb.remapWave2to5) { 70 | return 5; 71 | } 72 | 73 | return 2; 74 | default: 75 | fb.ClearGhosting(); 76 | return 3; 77 | case 8: 78 | return 8; 79 | } 80 | }(); 81 | 82 | 83 | // TODO: Get sync from client (wait for second ioctl? or look at stack?) 84 | // There are only two occasions when the original rm1 library sets sync to 85 | // true. Currently we detect them by the other data. Ideally we should 86 | // correctly handle the corresponding ioctl (empty rect and flags == 2?). 87 | if (data.waveform_mode == WAVEFORM_MODE_INIT && 88 | data.update_mode == UPDATE_MODE_FULL) { 89 | flags |= 2; 90 | std::cerr << "SERVER: sync" << std::endl; 91 | } else if (rect.left == 0 && rect.top > 1800 && 92 | (data.waveform_mode == WAVEFORM_MODE_GL16 || 93 | data.waveform_mode == WAVEFORM_MODE_GC16) && 94 | data.update_mode == UPDATE_MODE_FULL) { 95 | std::cerr << "server sync, x2: " << rect.width << " y2: " << rect.height 96 | << std::endl; 97 | flags |= 2; 98 | } 99 | 100 | if (data.waveform_mode == WAVEFORM_MODE_DU && 101 | data.update_mode == UPDATE_MODE_PARTIAL) { 102 | // fast draw 103 | flags |= 4; 104 | } 105 | 106 | #ifdef DEBUG 107 | std::cerr << "doUpdate " << endl; 108 | cerr << "mxc: waveform_mode " << mxcfb_update.waveform_mode << endl; 109 | cerr << "mxc: update mode " << mxcfb_update.update_mode << endl; 110 | cerr << "mxc: update marker " << mxcfb_update.update_marker << endl; 111 | cerr << "final: waveform " << waveform; 112 | cerr << " flags " << flags << endl << endl; 113 | #endif 114 | 115 | fb.DrawRaw(rect.left, rect.top, rect.width, rect.height, waveform, flags); 116 | 117 | } 118 | 119 | extern "C" { 120 | // QImage(width, height, format) 121 | static void (*qImageCtor)(void *that, int x, int y, int f) = 0; 122 | // QImage(uchar*, width, height, bytesperline, format) 123 | static void (*qImageCtorWithBuffer)(void *that, uint8_t *, int32_t x, int32_t y, 124 | int32_t bytes, int format, void (*)(void *), 125 | void *) = 0; 126 | 127 | static void _libhook_init() __attribute__((constructor)); 128 | static void _libhook_init() { 129 | qImageCtor = (void (*)(void *, int, int, int))dlsym( 130 | RTLD_NEXT, "_ZN6QImageC1EiiNS_6FormatE"); 131 | qImageCtorWithBuffer = (void (*)( 132 | void *, uint8_t *, int32_t, int32_t, int32_t, int, void (*)(void *), 133 | void *))dlsym(RTLD_NEXT, "_ZN6QImageC1EPhiiiNS_6FormatEPFvPvES2_"); 134 | } 135 | 136 | bool FIRST_ALLOC = true; 137 | void _ZN6QImageC1EiiNS_6FormatE(void *that, int x, int y, int f) { 138 | if (x == WIDTH && y == HEIGHT && FIRST_ALLOC) { 139 | fprintf(stderr, "REPLACING THE IMAGE with shared memory\n"); 140 | 141 | FIRST_ALLOC = false; 142 | qImageCtorWithBuffer(that, (uint8_t *)shared_mem, WIDTH, HEIGHT, 143 | WIDTH * BYTES_PER_PIXEL, f, nullptr, nullptr); 144 | return; 145 | } 146 | qImageCtor(that, x, y, f); 147 | } 148 | 149 | int server_main(int, char **, char **) { 150 | SwtFB fb; 151 | 152 | if (!fb.setFunc()) { 153 | return 255; 154 | } 155 | 156 | shared_mem = ipc::get_shared_buffer(); 157 | fb.initQT(); 158 | 159 | fprintf(stderr, "WAITING FOR SEND UPDATE ON MSG Q\n"); 160 | sd_notify(0, "READY=1"); 161 | 162 | while (true) { 163 | auto buf = MSGQ.recv(); 164 | switch (buf.mtype) { 165 | case ipc::UPDATE_t: 166 | doUpdate(fb, buf); 167 | break; 168 | case ipc::XO_t: { 169 | const xochitl_data& data = buf.mdata.xochitl_update; 170 | int width = data.x2 - data.x1 + 1; 171 | int height = data.y2 - data.y1 + 1; 172 | QRect rect(data.x1, data.y1, width, height); 173 | fb.SendUpdate(rect, data.waveform, data.flags); 174 | } break; 175 | case ipc::WAIT_t: { 176 | fb.WaitForLastUpdate(); 177 | sem_t* sem = sem_open(buf.mdata.wait_update.sem_name, O_CREAT, 0644, 0); 178 | if (sem != NULL) { 179 | sem_post(sem); 180 | sem_close(sem); 181 | } 182 | } break; 183 | 184 | default: 185 | std::cerr << "Error, unknown message type" << std::endl; 186 | } 187 | } 188 | return 0; 189 | } 190 | 191 | int __libc_start_main(int (*)(int, char **, char **), int argc, 192 | char **argv, int (*init)(int, char **, char **), 193 | void (*fini)(void), void (*rtld_fini)(void), 194 | void *stack_end) { 195 | 196 | printf("STARTING RM2FB\n"); 197 | 198 | auto func_main = 199 | (decltype(&__libc_start_main))dlsym(RTLD_NEXT, "__libc_start_main"); 200 | 201 | // Since we preload the library in the Xochitl binary, the process will 202 | // be called 'xochitl' by default. Change this to avoid confusing launchers 203 | const char* proc_name = "rm2fb-server"; 204 | size_t argv0_len = strlen(argv[0]); 205 | strncpy(argv[0], proc_name, argv0_len); 206 | argv[0][argv0_len] = 0; 207 | prctl(PR_SET_NAME, proc_name); 208 | 209 | return func_main(server_main, argc, argv, init, fini, rtld_fini, stack_end); 210 | }; 211 | }; 212 | -------------------------------------------------------------------------------- /src/server/server.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (3.1) Tue Oct 27 11:57:42 2020 3 | ###################################################################### 4 | 5 | TEMPLATE = lib 6 | TARGET = rm2fb_server 7 | INCLUDEPATH += . 8 | include(../../version.pri) 9 | 10 | # The following define makes your compiler warn you if you use any 11 | # feature of Qt which has been marked as deprecated (the exact warnings 12 | # depend on your compiler). Please consult the documentation of the 13 | # deprecated API in order to know how to port your code away from it. 14 | DEFINES += QT_DEPRECATED_WARNINGS 15 | 16 | # You can also make your code fail to compile if you use deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | # Input 22 | SOURCES += main.cpp ../shared/config.cpp 23 | LIBS += -lrt 24 | QMAKE_CXXFLAGS += -std=c++17 25 | -------------------------------------------------------------------------------- /src/server/test.sh: -------------------------------------------------------------------------------- 1 | make && scp librm2fb.so.1.0.0 rm: && ssh -tt rm "LD_PRELOAD=/home/root/librm2fb.so.1.0.0 /usr/bin/xochitl" 2 | -------------------------------------------------------------------------------- /src/shared/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "defines.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace { 12 | 13 | constexpr auto default_config = R"CONF( 14 | !2.4.0.27 15 | update addr 0x2b2534 16 | updateType str int4 17 | create addr 0x2b47c0 18 | shutdown addr 0x2b4764 19 | wait addr 0x2b3ffc 20 | getInstance addr 0x2b2a54 21 | 22 | !2.4.1.30 23 | update addr 0x2b255c 24 | updateType str int4 25 | create addr 0x2b47e8 26 | shutdown addr 0x2b478c 27 | wait addr 0x2b4024 28 | getInstance addr 0x2b2a7c 29 | 30 | !2.5.0.27 31 | update addr 0x2c050c 32 | updateType str int4 33 | create addr 0x2c2798 34 | shutdown addr 0x2c273c 35 | wait addr 0x2c1fd4 36 | getInstance addr 0x2c0a2c 37 | 38 | !2.5.1.47 39 | update addr 0x2c0664 40 | updateType str int4 41 | create addr 0x2c28f0 42 | shutdown addr 0x2c2894 43 | wait addr 0x2c212c 44 | getInstance addr 0x2c0b84 45 | 46 | !2.6.1.71 47 | update addr 0x311c10 48 | updateType str int4 49 | create addr 0x314178 50 | shutdown addr 0x31411c 51 | wait addr 0x31369c 52 | getInstance addr 0x312128 53 | 54 | !2.6.2.75 55 | update addr 0x311c00 56 | updateType str int4 57 | create addr 0x314168 58 | shutdown addr 0x31410c 59 | wait addr 0x31368c 60 | getInstance addr 0x312118 61 | 62 | !2.7.0.51 63 | update addr 0x32373c 64 | updateType str int4 65 | create addr 0x325ca8 66 | shutdown addr 0x325c4c 67 | wait addr 0x3251cc 68 | getInstance addr 0x323c54 69 | 70 | !2.7.1.53 71 | update addr 0x32373c 72 | updateType str int4 73 | create addr 0x325ca8 74 | shutdown addr 0x325c4c 75 | wait addr 0x3251cc 76 | getInstance addr 0x323c54 77 | 78 | !2.8.0.98 79 | update addr 0x3426ac 80 | updateType str int4 81 | create addr 0x344c18 82 | shutdown addr 0x344bbc 83 | wait addr 0x34413c 84 | getInstance addr 0x342bc8 85 | 86 | !2.9.0.153 87 | update addr 0x3aa5c4 88 | updateType str QRect 89 | create addr 0x3ac9d8 90 | shutdown addr 0x3ac97c 91 | wait addr 0x3abefc 92 | getInstance addr 0x3a0e7c 93 | 94 | !2.9.0.210 95 | update addr 0x3adc0c 96 | updateType str QRect 97 | create addr 0x3b0020 98 | shutdown addr 0x3affc4 99 | wait addr 0x3af544 100 | getInstance addr 0x3a44c4 101 | 102 | !2.9.1.217 103 | update addr 0x3afc04 104 | updateType str QRect 105 | create addr 0x3b2018 106 | shutdown addr 0x3b1fbc 107 | wait addr 0x3b153c 108 | getInstance addr 0x3a64bc 109 | 110 | !2.10.1.332 111 | update addr 0x397ff4 112 | updateType str QRect 113 | create addr 0x39a408 114 | shutdown addr 0x39a3ac 115 | wait addr 0x39992c 116 | getInstance addr 0x38e8dc 117 | 118 | !2.10.2.356 119 | update addr 0x398aac 120 | updateType str QRect 121 | create addr 0x39aec0 122 | shutdown addr 0x39ae64 123 | wait addr 0x39a3e4 124 | getInstance addr 0x38f394 125 | 126 | !2.10.3.379 127 | update addr 0x398acc 128 | updateType str QRect 129 | create addr 0x39aee0 130 | shutdown addr 0x39ae84 131 | wait addr 0x39a404 132 | getInstance addr 0x38f3b4 133 | 134 | !2.11.0.433 135 | update addr 0x3a9ccc 136 | updateType str QRect 137 | create addr 0x3ac124 138 | shutdown addr 0x3ac0c8 139 | wait addr 0x3ab604 140 | getInstance addr 0x3a041c 141 | 142 | !2.11.0.435 143 | update addr 0x3a9ccc 144 | updateType str QRect 145 | create addr 0x3ac124 146 | shutdown addr 0x3ac0c8 147 | wait addr 0x3ab604 148 | getInstance addr 0x3a041c 149 | 150 | !2.11.0.442 151 | update addr 0x3a9cdc 152 | updateType str QRect 153 | create addr 0x3ac134 154 | shutdown addr 0x3ac0d8 155 | wait addr 0x3ab614 156 | getInstance addr 0x3a042c 157 | 158 | !2.12.1.527 159 | update addr 0x3d72bc 160 | updateType str QRect 161 | create addr 0x3d9714 162 | shutdown addr 0x3d96b8 163 | wait addr 0x3d8bf4 164 | getInstance addr 0x3cda0c 165 | 166 | !2.12.2.573 167 | update addr 0x3edff4 168 | updateType str QRect 169 | create addr 0x3f0138 170 | shutdown addr 0x3f00d0 171 | wait addr 0x3ef704 172 | getInstance addr 0x3e4ddc 173 | 174 | !2.12.3.606 175 | update addr 0x3ee704 176 | updateType str QRect 177 | create addr 0x3f0848 178 | shutdown addr 0x3f07e0 179 | wait addr 0x3efe14 180 | getInstance addr 0x3e54ec 181 | 182 | !2.13.0.689 183 | update addr 0X45899c 184 | updateType str QRect 185 | create addr 0x45aae0 186 | shutdown addr 0x45aa78 187 | wait addr 0x45a0ac 188 | getInstance addr 0x44f784 189 | 190 | !2.13.0.758 191 | update addr 0x4589fc 192 | updateType str QRect 193 | create addr 0x45ab40 194 | shutdown addr 0x45aad8 195 | wait addr 0x45a10c 196 | getInstance addr 0x44f7e4 197 | 198 | !2.14.0.830 199 | update addr 0x4931c4 200 | updateType str QRect 201 | create addr 0x495308 202 | shutdown addr 0x4952a0 203 | wait addr 0x4948d4 204 | getInstance addr 0x489fac 205 | 206 | !2.14.0.861 207 | update addr 0x4931c4 208 | updateType str QRect 209 | create addr 0x495308 210 | shutdown addr 0x4952a0 211 | wait addr 0x4948d4 212 | getInstance addr 0x489fac 213 | 214 | !2.14.1.866 215 | update addr 0x4931c4 216 | updateType str QRect 217 | create addr 0x495308 218 | shutdown addr 0x4952a0 219 | wait addr 0x4948d4 220 | getInstance addr 0x489fac 221 | 222 | !2.14.3.925 223 | update addr 0x4bfb1c 224 | updateType str QRect 225 | create addr 0x4c2740 226 | shutdown addr 0x4c26d8 227 | wait addr 0x4c16e0 228 | getInstance addr 0x4b66a4 229 | 230 | !2.14.3.940 231 | update addr 0x4bfb1c 232 | updateType str QRect 233 | create addr 0x4c2740 234 | shutdown addr 0x4c26d8 235 | wait addr 0x4c16e0 236 | getInstance addr 0x4b66a4 237 | 238 | !2.14.3.942 239 | update addr 0x4bfb1c 240 | updateType str QRect 241 | create addr 0x04c2740 242 | shutdown addr 0x4c26d8 243 | wait addr 0x4c16e0 244 | getInstance addr 0x4b66a4 245 | 246 | !2.14.3.958 247 | update addr 0x4bfb2c 248 | updateType str QRect 249 | create addr 0x4c2750 250 | shutdown addr 0x4c26e8 251 | wait addr 0x4c16f0 252 | getInstance addr 0x4b66b4 253 | 254 | !2.14.3.977 255 | update addr 0x4bfb2c 256 | updateType str QRect 257 | create addr 0x4c2750 258 | shutdown addr 0x4c26e8 259 | wait addr 0x4c16f0 260 | getInstance addr 0x4b66b4 261 | 262 | !2.14.3.1005 263 | update addr 0x4bfb2c 264 | updateType str QRect 265 | create addr 0x4c2750 266 | shutdown addr 0x4c26e8 267 | wait addr 0x4c16f0 268 | getInstance addr 0x4b66b4 269 | 270 | !2.14.3.1047 271 | update addr 0x4bfb2c 272 | updateType str QRect 273 | create addr 0x4c2750 274 | shutdown addr 0x4c26e8 275 | wait addr 0x4c16f0 276 | getInstance addr 0x4b66b4 277 | 278 | !2.14.4.46 279 | update addr 0x4c0a0c 280 | updateType str QRect 281 | create addr 0x4c3630 282 | shutdown addr 0x4c35c8 283 | wait addr 0x4c25d0 284 | getInstance addr 0x4b7594 285 | 286 | !2.15.0.1011 287 | update addr 0x4e411c 288 | updateType str QRect 289 | create addr 0x4e6d40 290 | shutdown addr 0x4e6cd8 291 | wait addr 0x4e5ce0 292 | getInstance addr 0x4daca4 293 | 294 | !2.15.0.1046 295 | update addr 0x4e418c 296 | updateType str QRect 297 | create addr 0x4e6db0 298 | shutdown addr 0x4e6d48 299 | wait addr 0x4e5d50 300 | getInstance addr 0x4dad14 301 | 302 | !2.15.0.1052 303 | update addr 0x4e4254 304 | updateType str QRect 305 | create addr 0x4e6e78 306 | shutdown addr 0x4e6e10 307 | wait addr 0x4e5e18 308 | getInstance addr 0x4daddc 309 | 310 | !2.15.0.1067 311 | update addr 0x4e425c 312 | updateType str QRect 313 | create addr 0x4e6e80 314 | shutdown addr 0x4e6e18 315 | wait addr 0x4e5e20 316 | getInstance addr 0x4dade4 317 | 318 | !2.15.1.1189 319 | update addr 0x4e48fc 320 | updateType str QRect 321 | create addr 0x4e7520 322 | shutdown addr 0x4e74b8 323 | wait addr 0x4e64c0 324 | getInstance addr 0x4db484 325 | notify addr 0x4d98a4 326 | 327 | !3.0.2.1253 328 | update addr 0x55200c 329 | updateType str QRect 330 | create addr 0x555148 331 | shutdown addr 0x5550d8 332 | wait addr 0x553ff0 333 | getInstance addr 0x548b94 334 | 335 | !3.0.4.1305 336 | update addr 0x552f74 337 | updateType str QRect 338 | create addr 0x5560b0 339 | shutdown addr 0x556040 340 | wait addr 0x554f58 341 | getInstance addr 0x549674 342 | notify addr 0x547a94 343 | 344 | !3.1.0.1346 345 | update addr 0x52d90c 346 | updateType str QRect 347 | create addr 0x530a90 348 | shutdown addr 0x530a20 349 | wait addr 0x52f938 350 | getInstance addr 0x5244d4 351 | 352 | !3.2.2.1581 353 | update addr 0x557aa4 354 | updateType str QRect 355 | create addr 0x55ad08 356 | shutdown addr 0x55ac98 357 | wait addr 0x559bb0 358 | getInstance addr 0x54e284 359 | notify addr 0x54c788 360 | 361 | !3.2.3.1595 362 | update addr 0x557c34 363 | updateType str QRect 364 | create addr 0x55ae98 365 | shutdown addr 0x55ae28 366 | wait addr 0x559d40 367 | getInstance addr 0x54e414 368 | notify addr 0x54c918 369 | 370 | !3.3.2.1666 371 | update addr 0x5583c8 372 | updateType str QRect 373 | create addr 0x55b504 374 | shutdown addr 0x55b494 375 | wait addr 0x55a39c 376 | getInstance addr 0x54eeac 377 | notify addr 0x558464 378 | waveformClass str EPFramebuffer::Waveform 379 | remapWave2to5 str 1 380 | 381 | )CONF"; 382 | 383 | void read_config_file( 384 | const std::string& name, 385 | std::istream& file, 386 | const std::string& version, 387 | Config& accumulator 388 | ) { 389 | std::string line; 390 | unsigned int line_no = 1; 391 | bool version_matches = true; 392 | 393 | while (std::getline(file, line)) { 394 | if (line.empty() || line[0] == '#') { 395 | // Comment or empty line, ignore 396 | } else if (line[0] == '!') { 397 | // Start version block 398 | std::istringstream line_stream{line.substr(1)}; 399 | std::string file_version; 400 | line_stream >> file_version; 401 | accumulator["version"] = file_version; 402 | 403 | if (file_version == version) { 404 | version_matches = true; 405 | } else { 406 | version_matches = false; 407 | } 408 | } else if (version_matches) { 409 | // Key/value for current version 410 | std::istringstream line_stream{line}; 411 | std::string key, type; 412 | line_stream >> key >> type; 413 | 414 | if (type == "addr") { 415 | std::uint32_t value; 416 | line_stream.unsetf(std::ios::basefield); 417 | line_stream >> value; 418 | accumulator[key] = (char*) value; 419 | } else if (type == "str") { 420 | std::string value; 421 | line_stream >> value; 422 | accumulator[key] = value; 423 | } else { 424 | std::cerr << name << ":" << line_no << " - Ignored key of invalid " 425 | "type '" << type << "'" << std::endl; 426 | } 427 | } 428 | ++line_no; 429 | } 430 | } 431 | 432 | } 433 | 434 | Config read_config(const std::string& version) { 435 | Config result; 436 | std::istringstream default_config_file{default_config}; 437 | read_config_file("", default_config_file, version, result); 438 | 439 | constexpr std::array config_locations = { 440 | "/usr/share/rm2fb.conf", 441 | "/opt/share/rm2fb.conf", 442 | "/etc/rm2fb.conf", 443 | "/opt/etc/rm2fb.conf", 444 | "rm2fb.conf", 445 | }; 446 | 447 | for (const auto& path : config_locations) { 448 | std::ifstream config_file{path}; 449 | 450 | if (!config_file) { 451 | #ifdef DEBUG 452 | std::cerr << path << " - " << std::strerror(errno) << " (skipped)" << std::endl; 453 | #endif 454 | continue; 455 | } 456 | 457 | #ifdef DEBUG 458 | std::cerr << path << " - Parsing contents" << std::endl; 459 | #endif 460 | 461 | read_config_file(path, config_file, version, result); 462 | } 463 | 464 | return result; 465 | } 466 | 467 | Config read_config() { 468 | std::string version; 469 | std::string line; 470 | 471 | std::ifstream file("/usr/share/remarkable/update.conf"); 472 | 473 | while (std::getline(file, line)) { 474 | if (line.rfind("REMARKABLE_RELEASE_VERSION=", 0) == 0) { 475 | std::istringstream line_stream{line.substr(27)}; 476 | line_stream >> version; 477 | break; 478 | } 479 | } 480 | 481 | Config config = read_config(version); 482 | 483 | if (!config.empty()){ 484 | return config; 485 | } 486 | 487 | #ifdef DEBUG 488 | std::cerr 489 | << "Unable to find config for version number, looking for old build date instead" 490 | << std::endl; 491 | #endif 492 | 493 | std::ifstream version_file_buf{"/etc/version"}; 494 | 495 | if (!version_file_buf) { 496 | std::cerr << "/etc/version - " << std::strerror(errno) << "\n"; 497 | std::exit(-1); 498 | } 499 | 500 | std::getline(version_file_buf, version); 501 | 502 | return read_config(version); 503 | } 504 | -------------------------------------------------------------------------------- /src/shared/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using ConfigKey = std::string; 8 | using ConfigValue = std::variant; 9 | using Config = std::map; 10 | 11 | /** 12 | * Load the rm2fb configuration for the given OS version. 13 | * 14 | * @param version OS version timestamp (e.g. 20210611153600). 15 | * @return Mapping of the config keys to their value. 16 | */ 17 | Config read_config(const std::string& version); 18 | 19 | /** 20 | * Load the rm2fb configuration for the current OS version (in `/etc/version`). 21 | * 22 | * @return Mapping of the config keys to their value. 23 | */ 24 | Config read_config(); 25 | -------------------------------------------------------------------------------- /src/shared/defines.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // #define DEBUG_TIMING 4 | -------------------------------------------------------------------------------- /src/shared/ipc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "defines.h" 13 | #include "now.cpp" 14 | 15 | #include "mxcfb.h" 16 | 17 | #ifndef O_RDWR 18 | #define _FCNTL_H 1 19 | #include 20 | #undef _FCNTL_H 21 | #endif 22 | 23 | namespace swtfb { 24 | struct xochitl_data { 25 | int x1; 26 | int y1; 27 | int x2; 28 | int y2; 29 | 30 | int waveform; 31 | int flags; 32 | }; 33 | 34 | struct wait_sem_data { 35 | char sem_name[512]; 36 | }; 37 | 38 | struct swtfb_update { 39 | long mtype; 40 | struct { 41 | union { 42 | xochitl_data xochitl_update; 43 | 44 | struct mxcfb_update_data update; 45 | wait_sem_data wait_update; 46 | 47 | }; 48 | 49 | #ifdef DEBUG_TIMING 50 | uint64_t ms; 51 | #endif 52 | } mdata; 53 | }; 54 | 55 | const int WIDTH = 1404; 56 | const int HEIGHT = 1872; 57 | inline void reset_dirty(mxcfb_rect &dirty_area) { 58 | dirty_area.left = WIDTH; 59 | dirty_area.top = HEIGHT; 60 | dirty_area.width = 0; 61 | dirty_area.height = 0; 62 | } 63 | 64 | inline void mark_dirty(mxcfb_rect &dirty_area, mxcfb_rect &rect) { 65 | uint32_t x1 = dirty_area.left + dirty_area.width; 66 | uint32_t y1 = dirty_area.top + dirty_area.height; 67 | 68 | x1 = std::max(x1, rect.left + rect.width); 69 | y1 = std::max(y1, rect.top + rect.height); 70 | 71 | if (x1 > WIDTH) { 72 | x1 = WIDTH - 1; 73 | } 74 | if (y1 > HEIGHT) { 75 | y1 = HEIGHT - 1; 76 | } 77 | 78 | dirty_area.left = std::min(rect.left, dirty_area.left); 79 | dirty_area.top = std::min(rect.top, dirty_area.top); 80 | 81 | dirty_area.width = x1 - dirty_area.left; 82 | dirty_area.height = y1 - dirty_area.top; 83 | } 84 | 85 | namespace ipc { 86 | 87 | using namespace std; 88 | enum MSG_TYPE { INIT_t = 1, UPDATE_t, XO_t, WAIT_t }; 89 | 90 | const int maxWidth = 1404; 91 | const int maxHeight = 1872; 92 | const int BUF_SIZE = maxWidth * maxHeight * 93 | sizeof(uint16_t); // hardcoded size of display mem for rM2 94 | int SWTFB_FD = 0; 95 | 96 | // TODO: allow multiple shared buffers in one process? 97 | static uint16_t *get_shared_buffer(string name = "/swtfb.01") { 98 | if (name[0] != '/') { 99 | name = "/" + name; 100 | } 101 | 102 | int fd = shm_open(name.c_str(), O_RDWR | O_CREAT, 0755); 103 | 104 | if (fd == -1 && errno == 13) { 105 | fd = shm_open(name.c_str(), O_RDWR | O_CREAT, 0755); 106 | } 107 | 108 | if (fd < 3) { 109 | fprintf(stderr, "SHM FD: %i, errno: %i\n", fd, errno); 110 | } 111 | SWTFB_FD = fd; 112 | 113 | ftruncate(fd, BUF_SIZE); 114 | uint16_t *mem = 115 | (uint16_t *)mmap(NULL, BUF_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); 116 | 117 | if (getenv("RM2FB_NESTED") == "") { 118 | fprintf(stderr, "OPENED SHARED MEM: /dev/shm%s at %x, errno: %i\n", 119 | name.c_str(), mem, errno); 120 | } 121 | return mem; 122 | } 123 | 124 | #define SWTFB1_UPDATE 1 125 | class Queue { 126 | public: 127 | unsigned long id; 128 | int msqid = -1; 129 | 130 | void init() { msqid = msgget(id, IPC_CREAT | 0600); } 131 | 132 | Queue(int id) : id(id) { init(); } 133 | 134 | void send(wait_sem_data data) { 135 | swtfb_update msg; 136 | msg.mtype = WAIT_t; 137 | msg.mdata.wait_update = data; 138 | 139 | int wrote = msgsnd(msqid, (void *)&msg, sizeof(msg.mdata.wait_update), 0); 140 | if (wrote != 0) { 141 | perror("Error sending wait update"); 142 | } 143 | } 144 | 145 | void send(xochitl_data data) { 146 | swtfb_update msg; 147 | msg.mtype = XO_t; 148 | msg.mdata.xochitl_update = data; 149 | 150 | int wrote = msgsnd(msqid, (void *)&msg, sizeof(msg.mdata.xochitl_update), 0); 151 | if (wrote != 0) { 152 | perror("Error sending xochitl update"); 153 | } 154 | } 155 | 156 | void send(mxcfb_update_data msg) { 157 | // TODO: read return value 158 | #ifdef DEBUG 159 | auto rect = msg.update_region; 160 | cerr << get_now() << " MSG Q SEND " << rect.left << " " << rect.top << " " 161 | << rect.width << " " << rect.height << endl; 162 | #endif 163 | 164 | swtfb_update swtfb_msg; 165 | swtfb_msg.mtype = UPDATE_t; 166 | swtfb_msg.mdata.update = msg; 167 | 168 | #ifdef DEBUG_TIMING 169 | swtfb_msg.mdata.ms = get_now(); 170 | #endif 171 | int wrote = msgsnd(msqid, (void *)&swtfb_msg, sizeof(swtfb_msg.mdata.update), 0); 172 | if (wrote != 0) { 173 | cerr << "ERRNO " << errno << endl; 174 | } 175 | } 176 | 177 | swtfb_update recv() { 178 | swtfb_update buf; 179 | errno = 0; 180 | auto len = msgrcv(msqid, &buf, sizeof(buf.mdata), 0, MSG_NOERROR); 181 | #ifdef DEBUG_TIMING 182 | auto rect = buf.mdata.update.update_region; 183 | cerr << get_now() - buf.mdata.ms << "ms MSG Q RECV " << rect.left << " " 184 | << rect.top << " " << rect.width << " " << rect.height << endl; 185 | #endif 186 | if (len >= 0) { 187 | return buf; 188 | } else { 189 | perror("Error recv msgbuf"); 190 | } 191 | 192 | return {}; 193 | } 194 | 195 | void destroy() { msgctl(msqid, IPC_RMID, 0); }; 196 | }; 197 | }; // namespace ipc 198 | }; // namespace swtfb 199 | -------------------------------------------------------------------------------- /src/shared/mxcfb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. All Rights Reserved 3 | */ 4 | 5 | /* 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | */ 20 | 21 | /* 22 | * @file uapi/linux/mxcfb.h 23 | * 24 | * @brief Global header file for the MXC frame buffer 25 | * 26 | * @ingroup Framebuffer 27 | */ 28 | #ifndef __ASM_ARCH_MXCFB_H__ 29 | #define __ASM_ARCH_MXCFB_H__ 30 | 31 | #include 32 | 33 | #define FB_SYNC_OE_LOW_ACT 0x80000000 34 | #define FB_SYNC_CLK_LAT_FALL 0x40000000 35 | #define FB_SYNC_DATA_INVERT 0x20000000 36 | #define FB_SYNC_CLK_IDLE_EN 0x10000000 37 | #define FB_SYNC_SHARP_MODE 0x08000000 38 | #define FB_SYNC_SWAP_RGB 0x04000000 39 | #define FB_ACCEL_TRIPLE_FLAG 0x00000000 40 | #define FB_ACCEL_DOUBLE_FLAG 0x00000001 41 | 42 | struct mxcfb_gbl_alpha { 43 | int enable; 44 | int alpha; 45 | }; 46 | 47 | struct mxcfb_loc_alpha { 48 | int enable; 49 | int alpha_in_pixel; 50 | unsigned long alpha_phy_addr0; 51 | unsigned long alpha_phy_addr1; 52 | }; 53 | 54 | struct mxcfb_color_key { 55 | int enable; 56 | __u32 color_key; 57 | }; 58 | 59 | struct mxcfb_pos { 60 | __u16 x; 61 | __u16 y; 62 | }; 63 | 64 | struct mxcfb_gamma { 65 | int enable; 66 | int constk[16]; 67 | int slopek[16]; 68 | }; 69 | 70 | struct mxcfb_gpu_split_fmt { 71 | struct fb_var_screeninfo var; 72 | unsigned long offset; 73 | }; 74 | 75 | struct mxcfb_rect { 76 | __u32 top; 77 | __u32 left; 78 | __u32 width; 79 | __u32 height; 80 | }; 81 | 82 | #define GRAYSCALE_8BIT 0x1 83 | #define GRAYSCALE_8BIT_INVERTED 0x2 84 | #define GRAYSCALE_4BIT 0x3 85 | #define GRAYSCALE_4BIT_INVERTED 0x4 86 | 87 | #define AUTO_UPDATE_MODE_REGION_MODE 0 88 | #define AUTO_UPDATE_MODE_AUTOMATIC_MODE 1 89 | 90 | #define UPDATE_SCHEME_SNAPSHOT 0 91 | #define UPDATE_SCHEME_QUEUE 1 92 | #define UPDATE_SCHEME_QUEUE_AND_MERGE 2 93 | 94 | #define UPDATE_MODE_PARTIAL 0x0 95 | #define UPDATE_MODE_FULL 0x1 96 | 97 | #define WAVEFORM_MODE_GLR16 4 98 | #define WAVEFORM_MODE_GLD16 5 99 | #define WAVEFORM_MODE_AUTO 257 100 | 101 | #define TEMP_USE_AMBIENT 0x1000 102 | 103 | #define EPDC_FLAG_ENABLE_INVERSION 0x01 104 | #define EPDC_FLAG_FORCE_MONOCHROME 0x02 105 | #define EPDC_FLAG_USE_CMAP 0x04 106 | #define EPDC_FLAG_USE_ALT_BUFFER 0x100 107 | #define EPDC_FLAG_TEST_COLLISION 0x200 108 | #define EPDC_FLAG_GROUP_UPDATE 0x400 109 | #define EPDC_FLAG_USE_DITHERING_Y1 0x2000 110 | #define EPDC_FLAG_USE_DITHERING_Y4 0x4000 111 | #define EPDC_FLAG_USE_REGAL 0x8000 112 | 113 | enum mxcfb_dithering_mode { 114 | EPDC_FLAG_USE_DITHERING_PASSTHROUGH = 0x0, 115 | EPDC_FLAG_USE_DITHERING_FLOYD_STEINBERG, 116 | EPDC_FLAG_USE_DITHERING_ATKINSON, 117 | EPDC_FLAG_USE_DITHERING_ORDERED, 118 | EPDC_FLAG_USE_DITHERING_QUANT_ONLY, 119 | EPDC_FLAG_USE_DITHERING_MAX, 120 | }; 121 | 122 | #define FB_POWERDOWN_DISABLE -1 123 | #define FB_TEMP_AUTO_UPDATE_DISABLE -1 124 | 125 | struct mxcfb_alt_buffer_data { 126 | __u32 phys_addr; 127 | __u32 width; /* width of entire buffer */ 128 | __u32 height; /* height of entire buffer */ 129 | struct mxcfb_rect alt_update_region; /* region within buffer to update */ 130 | }; 131 | 132 | struct mxcfb_update_data { 133 | struct mxcfb_rect update_region; 134 | __u32 waveform_mode; 135 | __u32 update_mode; 136 | __u32 update_marker; 137 | int temp; 138 | unsigned int flags; 139 | int dither_mode; 140 | int quant_bit; 141 | struct mxcfb_alt_buffer_data alt_buffer_data; 142 | }; 143 | 144 | struct mxcfb_update_marker_data { 145 | __u32 update_marker; 146 | __u32 collision_test; 147 | }; 148 | 149 | /* 150 | * Structure used to define waveform modes for driver 151 | * Needed for driver to perform auto-waveform selection 152 | */ 153 | struct mxcfb_waveform_modes { 154 | int mode_init; 155 | int mode_du; 156 | int mode_gc4; 157 | int mode_gc8; 158 | int mode_gc16; 159 | int mode_gc32; 160 | }; 161 | 162 | /* 163 | * Structure used to define a 5*3 matrix of parameters for 164 | * setting IPU DP CSC module related to this framebuffer. 165 | */ 166 | struct mxcfb_csc_matrix { 167 | int param[5][3]; 168 | }; 169 | 170 | #define MXCFB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) 171 | #define MXCFB_SET_GBL_ALPHA _IOW('F', 0x21, struct mxcfb_gbl_alpha) 172 | #define MXCFB_SET_CLR_KEY _IOW('F', 0x22, struct mxcfb_color_key) 173 | #define MXCFB_SET_OVERLAY_POS _IOWR('F', 0x24, struct mxcfb_pos) 174 | #define MXCFB_GET_FB_IPU_CHAN _IOR('F', 0x25, u_int32_t) 175 | #define MXCFB_SET_LOC_ALPHA _IOWR('F', 0x26, struct mxcfb_loc_alpha) 176 | #define MXCFB_SET_LOC_ALP_BUF _IOW('F', 0x27, unsigned long) 177 | #define MXCFB_SET_GAMMA _IOW('F', 0x28, struct mxcfb_gamma) 178 | #define MXCFB_GET_FB_IPU_DI _IOR('F', 0x29, u_int32_t) 179 | #define MXCFB_GET_DIFMT _IOR('F', 0x2A, u_int32_t) 180 | #define MXCFB_GET_FB_BLANK _IOR('F', 0x2B, u_int32_t) 181 | #define MXCFB_SET_DIFMT _IOW('F', 0x2C, u_int32_t) 182 | #define MXCFB_CSC_UPDATE _IOW('F', 0x2D, struct mxcfb_csc_matrix) 183 | #define MXCFB_SET_GPU_SPLIT_FMT _IOW('F', 0x2F, struct mxcfb_gpu_split_fmt) 184 | #define MXCFB_SET_PREFETCH _IOW('F', 0x30, int) 185 | #define MXCFB_GET_PREFETCH _IOR('F', 0x31, int) 186 | 187 | /* IOCTLs for E-ink panel updates */ 188 | #define MXCFB_SET_WAVEFORM_MODES _IOW('F', 0x2B, struct mxcfb_waveform_modes) 189 | #define MXCFB_SET_TEMPERATURE _IOW('F', 0x2C, int32_t) 190 | #define MXCFB_SET_AUTO_UPDATE_MODE _IOW('F', 0x2D, __u32) 191 | #define MXCFB_SEND_UPDATE _IOW('F', 0x2E, struct mxcfb_update_data) 192 | #define MXCFB_WAIT_FOR_UPDATE_COMPLETE _IOWR('F', 0x2F, struct mxcfb_update_marker_data) 193 | #define MXCFB_SET_PWRDOWN_DELAY _IOW('F', 0x30, int32_t) 194 | #define MXCFB_GET_PWRDOWN_DELAY _IOR('F', 0x31, int32_t) 195 | #define MXCFB_SET_UPDATE_SCHEME _IOW('F', 0x32, __u32) 196 | #define MXCFB_GET_WORK_BUFFER _IOWR('F', 0x34, unsigned long) 197 | #define MXCFB_SET_TEMP_AUTO_UPDATE_PERIOD _IOW('F', 0x36, int32_t) 198 | #define MXCFB_DISABLE_EPDC_ACCESS _IO('F', 0x35) 199 | #define MXCFB_ENABLE_EPDC_ACCESS _IO('F', 0x36) 200 | #endif 201 | -------------------------------------------------------------------------------- /src/shared/now.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace swtfb { 6 | using namespace std; 7 | using namespace std::chrono; 8 | uint64_t get_now() { 9 | return duration_cast< milliseconds >( 10 | system_clock::now().time_since_epoch() 11 | ).count(); 12 | } 13 | 14 | class ClockWatch { 15 | public: 16 | chrono::high_resolution_clock::time_point t1; 17 | 18 | ClockWatch() { t1 = chrono::high_resolution_clock::now(); } 19 | 20 | auto elapsed() { 21 | auto t2 = chrono::high_resolution_clock::now(); 22 | chrono::duration time_span = 23 | chrono::duration_cast>(t2 - t1); 24 | return time_span.count(); 25 | } 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/shared/qtdump.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void dump_qtClass(void* ptr) { 10 | printf("INSTANCE ADDRESS: 0x%lx\n", ptr); 11 | QObject *object = static_cast((QObject*) ptr); 12 | const QMetaObject *meta = object->metaObject(); 13 | qDebug() << meta->className(); 14 | 15 | qDebug() << "Methods"; 16 | for (int id = 0; id < meta->methodCount(); ++id) { 17 | const QMetaMethod method = meta->method(id); 18 | if (method.methodType() == QMetaMethod::Slot && method.access() == QMetaMethod::Public) 19 | qDebug() << method.access() << method.name() << method.parameterNames() << method.parameterTypes(); 20 | } 21 | 22 | qDebug() << "Properties"; 23 | for (int id = 0; id < meta->propertyCount(); ++id) { 24 | const QMetaProperty property = meta->property(id); 25 | qDebug() << property.name() << property.type(); 26 | } 27 | 28 | qDebug() << "Enumerators"; 29 | for (int id = 0; id < meta->enumeratorCount(); ++id) { 30 | const QMetaEnum en = meta->enumerator(id); 31 | qDebug() << en.name(); 32 | for (int j = 0; j < en.keyCount(); j++) { 33 | qDebug() << en.key(j); 34 | } 35 | qDebug() << ""; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/shared/swtfb.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "now.cpp" 13 | #include "qtdump.cpp" 14 | #include "config.h" 15 | 16 | using namespace std; 17 | 18 | namespace swtfb { 19 | 20 | // todo: make it singleton 21 | class SwtFB { 22 | const int maxWidth = 1404; 23 | const int maxHeight = 1872; 24 | 25 | std::string WAVEFORM_MODE = ""; 26 | 27 | public: 28 | bool remapWave2to5 = false; 29 | 30 | SwtFB() 31 | : config(read_config()) {} 32 | 33 | bool setFunc() { 34 | auto search = config.find("getInstance"); 35 | 36 | if (search == config.end()) { 37 | std::cerr << "Missing address for function 'getInstance'\n" 38 | "PLEASE SEE https://github.com/ddvk/remarkable2-framebuffer/issues/18\n"; 39 | return false; 40 | } 41 | 42 | void* address = std::get(search->second); 43 | f_getInstance = (uint32_t * (*)(void)) address; 44 | std::cerr << "getInstance() at address: " << address << '\n'; 45 | 46 | 47 | auto waveformMode = config.find("waveformClass"); 48 | if (waveformMode != config.end()) { 49 | std::cerr << "Found waveform class " << std::endl; 50 | WAVEFORM_MODE = std::get(waveformMode->second); 51 | } else { 52 | WAVEFORM_MODE = string{"EPFramebuffer::WaveformMode"}; 53 | } 54 | std::cerr << "Using waveform mode " << WAVEFORM_MODE << std::endl; 55 | 56 | remapWave2to5 = config.find("remapWave2to5") != config.end(); 57 | 58 | return true; 59 | } 60 | 61 | void initQT() { 62 | auto ptr = f_getInstance(); 63 | instance = reinterpret_cast(ptr); 64 | img = (QImage *)(ptr + 8); 65 | 66 | #ifdef DEBUG 67 | dump_qtClass(instance); 68 | #endif 69 | 70 | cout << img->width() << " " << img->height() << " " << img->depth() << endl; 71 | } 72 | 73 | void ClearScreen() { 74 | QMetaObject::invokeMethod(instance, "clearScreen", Qt::DirectConnection); 75 | } 76 | 77 | void ClearGhosting() { 78 | QMetaObject::invokeMethod(instance, "setForceFull", Qt::DirectConnection, 79 | Q_ARG(bool, false)); 80 | } 81 | 82 | void SendUpdate(const QRect &rect, int waveform, int flags) const { 83 | // Method idx == 1 84 | QGenericArgument argWaveform(WAVEFORM_MODE.c_str(), &waveform); 85 | QGenericArgument argUpdateMode("EPFramebuffer::UpdateFlags", &flags); 86 | QMetaObject::invokeMethod(instance, "sendUpdate", Qt::DirectConnection, 87 | Q_ARG(QRect, rect), argWaveform, argUpdateMode); 88 | } 89 | void WaitForLastUpdate() const { 90 | // TODO: method Idx == 5, just returns. 91 | QMetaObject::invokeMethod(instance, "waitForLastUpdate", 92 | Qt::DirectConnection); 93 | } 94 | 95 | void DrawLine() { 96 | cout << "drawing a line " << endl; 97 | cout << "send update" << endl; 98 | for (int i = 1; i < maxWidth - 4; i += 2) { 99 | for (int j = 1; j < maxHeight - 2; j++) { 100 | QRect rect(i, j, 3, 3); 101 | img->setPixel(i + 1, j + 1, 0xFF); 102 | SendUpdate(rect, 1, 4); // 1,4 fast 103 | } 104 | } 105 | } 106 | void FullScreen(int color) { 107 | 108 | // ClearGhosting(); 109 | // clearScreen(); 110 | QRect rect(0, 0, maxWidth, maxHeight); 111 | img->fill(color); 112 | QPainter painter(img); 113 | painter.drawText(rect, 132, "Blah"); 114 | painter.end(); 115 | SendUpdate(rect, 2, 3); // 2,3 refresh 116 | } 117 | 118 | void DrawText(int i, char *text) { 119 | QRect rect(0, i, 200, 100); 120 | QPainter painter(img); 121 | painter.drawText(rect, 132, text); 122 | painter.end(); 123 | SendUpdate(rect, 3, 0); 124 | } 125 | 126 | void DrawRaw(int x, int y, int w, int h, int waveform = 2, 127 | int flags = 3) const { 128 | QRect rect(x, y, w, h); 129 | ClockWatch cz; 130 | SendUpdate(rect, waveform, flags); 131 | 132 | #ifdef DEBUG 133 | cerr << get_now() << " Total Update took " << cz.elapsed() << "s" << endl; 134 | #endif 135 | } 136 | 137 | private: 138 | QObject *instance; 139 | QGuiApplication *app; 140 | QImage *img; 141 | Config config; 142 | 143 | uint32_t *(*f_getInstance)(void) = (uint32_t * (*)(void))0; 144 | void (*f_sendUpdate)(void *, ...) = (void (*)(void *, ...))0; 145 | }; 146 | } // namespace swtfb 147 | -------------------------------------------------------------------------------- /src/xofb/main.cpp: -------------------------------------------------------------------------------- 1 | // g++ xofb.cpp -ldl -lrt -shared -fPIC -o xofb.so 2 | // LD_PRELOAD=xofb.so xochitl 3 | // /dev/shm/xofb now has the framebuffer of xochitl 4 | // this is 16bit rgb565 5 | 6 | #ifndef _GNU_SOURCE 7 | #define _GNU_SOURCE 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | const int WIDTH = 1404; 19 | const int HEIGHT = 1872; 20 | const int SIZE = 2; 21 | 22 | char *SHMEM = NULL; 23 | bool FIRST_ALLOC = true; 24 | 25 | extern "C" { 26 | static void (*qImageCtor)(void *that, int x, int y, int f) = 0; 27 | static void (*qImageCtorWithBuffer)(void *that, uint8_t *, int32_t x, int32_t y, 28 | int32_t bytes, int format, void (*)(void *), 29 | void *) = 0; 30 | static void _libhook_init() __attribute__((constructor)); 31 | static void _libhook_init() { 32 | int fd = shm_open("/xofb", O_RDWR | O_CREAT, 0755); 33 | fprintf(stderr, "SHM FD: %i, errno: %i\n", fd, errno); 34 | 35 | int BUF_SIZE = HEIGHT * WIDTH * SIZE; 36 | ftruncate(fd, BUF_SIZE); 37 | SHMEM = (char *)mmap(NULL, BUF_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); 38 | 39 | qImageCtor = (void (*)(void *, int, int, int))dlsym( 40 | RTLD_NEXT, "_ZN6QImageC1EiiNS_6FormatE"); 41 | qImageCtorWithBuffer = (void (*)( 42 | void *, uint8_t *, int32_t, int32_t, int32_t, int, void (*)(void *), 43 | void *))dlsym(RTLD_NEXT, "_ZN6QImageC1EPhiiiNS_6FormatEPFvPvES2_"); 44 | } 45 | 46 | void _ZN6QImageC1EiiNS_6FormatE(void *that, int x, int y, int f) { 47 | 48 | if (x == WIDTH && y == HEIGHT && FIRST_ALLOC) { 49 | fprintf(stderr, "REPLACING THE IMAGE with /dev/shm/xofb \n"); 50 | 51 | FIRST_ALLOC = false; 52 | qImageCtorWithBuffer(that, (uint8_t *)SHMEM, WIDTH, HEIGHT, WIDTH * SIZE, f, 53 | nullptr, nullptr); 54 | return; 55 | } 56 | qImageCtor(that, x, y, f); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/xofb/xofb.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (3.1) Tue Oct 27 11:57:42 2020 3 | ###################################################################### 4 | 5 | TEMPLATE = lib 6 | TARGET = rm2fb_xofb 7 | INCLUDEPATH += . 8 | include(../../version.pri) 9 | 10 | # The following define makes your compiler warn you if you use any 11 | # feature of Qt which has been marked as deprecated (the exact warnings 12 | # depend on your compiler). Please consult the documentation of the 13 | # deprecated API in order to know how to port your code away from it. 14 | DEFINES += QT_DEPRECATED_WARNINGS 15 | 16 | # You can also make your code fail to compile if you use deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | # Input 22 | SOURCES += main.cpp 23 | LIBS += -lrt -ldl 24 | -------------------------------------------------------------------------------- /tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Decompilation tutorial 2 | 3 | This is an **extremely** simplified tutorial on how to decompile reMarkable 2's `xochitl` program 4 | to find the offsets required for rm2fb to work. 5 | 6 | The details and screenshots use Ghidra, an open-source Java-based reverse-engineering program that runs on Linux, Windows, and Mac OS X. 7 | 8 | Before starting this tutorial, [download and install Ghidra](https://www.ghidra-sre.org) 9 | 10 | ## Setup 11 | 12 | 1. run `grep REMARKABLE_RELEASE_VERSION /usr/share/remarkable/update.conf | cut -d= -f2` on your reMarkable 2, it will output the version number of the device. The first line of the configuration you add to this project should be `!` followed by the version number. 13 | 2. Copy the file `/usr/bin/xochitl` from your reMarkable 2 onto your machine 14 | 3. Run Ghidra 15 | 4. Menu "File > New Project" ("Non-shared", pick any directory and name you want) 16 | 5. Click the green dragon-head in the "Tool Chest" bar, or menu "Tools > Run Tool > CodeBrowser" 17 | - ![CodeBrowser image](images/01-codebrowser.png?raw=true) 18 | 6. In the CodeBrowser window, click the menu "File > Import file" 19 | 7. Select `xochitl` file you downloaded from rm2 20 | 8. Ghidra should recognize its type as "Executable and Linking Format (ELF)" 21 | - ![Import dialog](images/02-import.png?raw=true) 22 | 9. Click "OK"; should take a few seconds to do initial processing. 23 | 10. Pop-up message "xochitl has not been analyzed. Would you like to analyze it now?" - click "YES" 24 | - ![Analyze popup](images/03-analyze-popup.png) 25 | - (if you accidentally click "No", you can run the analysis with the menu "Analysis > Auto Analyze") 26 | 11. In the analysis pop-up, the default selections are fine, just click "Analyze" 27 | 12. Wait until analysis is complete. Could take several minutes. Progress status is in the bottom-right corner 28 | - ![Analysis status](images/04-analysis-status.png) 29 | 13. If it's still open, you can close the "Import Results Summary" window 30 | 31 | # Finding each function address 32 | 33 | - String searches, for the functions `update`, `create`, and `shutdown` 34 | - menu "Search > Memory..." (shortcut key "S") 35 | - set "Format" to "String", and check the "Escape Sequences box" (necessary for the `\n` character included in one of the search strings) 36 | - ![Search Memory](images/05-search-memory.png) 37 | - in "Search Value", put the *exact* search string with no typos. Partial search is okay, just be aware you might find partial string matches in other sections of code 38 | - `update` search string: `Unable to complete update: invalid waveform (` 39 | - `notify` is the second function in the switch, case 8 in the `update` function 40 | - `create` search string: `Unable to start generator thread\n` 41 | - `shutdown` search string: `Shutting down...` 42 | - Click "Next". You should only find one result. 43 | - If *no* results are found, then try: 44 | - double-checking the "Search Value" for typos 45 | - either click "Previous" or make sure you've scrolled all the way back to the top of the "Listing" window; "Next" does *not* automatically loop back to the start when searching 46 | - Click "Dismiss" 47 | - In the "Listing" window, just above the string value there should be a line that ends with `XREF[1]:` followed by either a hex value or a string that starts with `FUN_` followed by a hex value 48 | - ![Function XREF](images/06-function-xref.png) 49 | - Double-click that hex or `FUN_` value 50 | - The "Listing" window should jump to that address in the code 51 | - Now look at the "Decompile" window. 52 | - If Ghidra was able to decompile that particular function, then the window will have the text of the function, with the title `FUN_` followed by a hex value, e.g. `FUN_004c26e8`. That hex value is the offset, 53 | - ![Function Address](images/07-function-address.png) 54 | - If Ghidra could NOT decompile that function, then the window will say something like `Low-level Error: Unable to resolve type` or some other messgae. BUT look closely at the Decompile window *title bar*. It should say something like `Decompile: UndefinedFunction_004bf2bc - (xochitl)`. That hex value after "UndefinedFunction_" is the offset 55 | - ![UndefinedFunction Address](images/08-undefined-function-address.png) 56 | - `wait` function 57 | - In the "Symbol Tree" window: 58 | - Expand "Imports" 59 | - Expand "<EXTERNAL>" 60 | - ![Symbol Tree](images/09-symbol-tree.png) 61 | - Scroll all the way down to "usleep" 62 | - Click "usleep" 63 | - ![usleep](images/10-usleep.png) 64 | - In the "Function Call Trees" window, expand "usleep". There may be more than one function that calls "usleep". They're all auto-named `FUN_[some hex code]`. Ways to pick the right one: 65 | - The `wait` function should decompile cleanly, so you can see its source in the "Decompile" window 66 | - The `wait` function is pretty short, currently less than 30 lines of decompiled code 67 | - The `wait` function has *multiple* calls to `usleep(1000)`, not just one 68 | - The `wait` function is called by other functions, so the "Function Call Trees" window should have another expansion layer under that function 69 | - ![wait function](images/11-wait-function.png) 70 | - Once you've got the right function, copy the offset from the hex code part of the function name 71 | - `getInstance` function 72 | - This one is a tiny bit more complicated than the others :) 73 | - Have the `FUN_[hex code]` function names you just found for `create` and `wait` handy 74 | - Repeat (or remain at!) the steps you used to find `wait` 75 | - In the "Function Call Trees" window, expand the `wait`'s function `FUN_[hex code]`, showing what functions call `wait` 76 | - At least one of the functions-that-call-wait should have yet another expansion layer, i.e. a list of functions that call *that* function 77 | - Click that middle function, i.e. the function-that-calls-wait 78 | - That function probably couldn't decompile into code, so look at the "Listing" window 79 | - In the "Listing" window, the line should be highlighted with the `FN_[hex code]` of the `wait` function 80 | - The line just above that line should be the `FN_[hex code]` of the `create` function 81 | - If you see both the `wait` and `create` FN calls, then you're looking at the correct function before the final step 82 | - ![Intermediate function](images/12-intermediate-function.png) 83 | - Expand that *function that calls wait and create* in the "Function Call Trees" window 84 | - The function that calls *that* function (i.e. two expansion levels below the `wait` function) is `getInstance` 85 | - ![getInstance function](images/13-get-instance-function.png) 86 | - One last verification step that you have the right function for `getInstance`: It decompiles cleanly in the "Decompile" window and is *very* short (currently less than 20 lines) 87 | 88 | With the hex code offsets of all five functions, you can now create a new configuration entry. Example entry: 89 | ``` 90 | !2.14.3.1047 91 | update addr 0x4bfb2c 92 | updateType str QRect 93 | create addr 0x4c2750 94 | shutdown addr 0x4c26e8 95 | wait addr 0x4c16f0 96 | getInstance addr 0x4b66b4 97 | ``` 98 | 99 | To test whether you have all the correct offsets, you do not need to re-compile rm2fb. You can simply add your new configuration entry to your reMarkable 2 in any one of several file locations, as noted in the main [README.md](../README.md): 100 | - `/usr/share/rm2fb.conf` 101 | - `/opt/share/rm2fb.conf` 102 | - `/etc/rm2fb.conf` 103 | - `/opt/etc/rm2fb.conf` (best option for Toltec users) 104 | - `rm2fb.conf` (relative to the current working directory, best option for manual installs) 105 | 106 | -------------------------------------------------------------------------------- /tutorial/images/01-codebrowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/01-codebrowser.png -------------------------------------------------------------------------------- /tutorial/images/02-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/02-import.png -------------------------------------------------------------------------------- /tutorial/images/03-analyze-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/03-analyze-popup.png -------------------------------------------------------------------------------- /tutorial/images/04-analysis-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/04-analysis-status.png -------------------------------------------------------------------------------- /tutorial/images/05-search-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/05-search-memory.png -------------------------------------------------------------------------------- /tutorial/images/06-function-xref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/06-function-xref.png -------------------------------------------------------------------------------- /tutorial/images/07-function-address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/07-function-address.png -------------------------------------------------------------------------------- /tutorial/images/08-undefined-function-address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/08-undefined-function-address.png -------------------------------------------------------------------------------- /tutorial/images/09-symbol-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/09-symbol-tree.png -------------------------------------------------------------------------------- /tutorial/images/10-usleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/10-usleep.png -------------------------------------------------------------------------------- /tutorial/images/11-wait-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/11-wait-function.png -------------------------------------------------------------------------------- /tutorial/images/12-intermediate-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/12-intermediate-function.png -------------------------------------------------------------------------------- /tutorial/images/13-get-instance-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddvk/remarkable2-framebuffer/3ce4f8109a146edf5602ac1e434f052659756441/tutorial/images/13-get-instance-function.png -------------------------------------------------------------------------------- /version.pri: -------------------------------------------------------------------------------- 1 | VERSION=1.0.2 2 | --------------------------------------------------------------------------------