From 75f7add11fc30978c20db9d364203a1c82a02576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=88=9A=28noham=29=C2=B2?= <100566912+NohamR@users.noreply.github.com> Date: Fri, 15 May 2026 22:22:40 +0200 Subject: [PATCH] Add IPA patch/watch scripts and RMHook doc --- .gitignore | 3 - RMHook/index.md | 5 ++ scripts/patch_and_server.py | 128 +++++++++++++++++++++++++++++ scripts/watch_and_server.py | 159 ++++++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 RMHook/index.md create mode 100644 scripts/patch_and_server.py create mode 100644 scripts/watch_and_server.py diff --git a/.gitignore b/.gitignore index efd7cc7..5605203 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ .DS_Store -/CreditAgricoleTweak -/RMHook -/rootless Build.md \ No newline at end of file diff --git a/RMHook/index.md b/RMHook/index.md new file mode 100644 index 0000000..2dcec75 --- /dev/null +++ b/RMHook/index.md @@ -0,0 +1,5 @@ +# RMHook-iOS + +A dynamic library injection tweak for the reMarkable iOS app, enabling connection to self-hosted [rmfakecloud](https://github.com/ddvk/rmfakecloud) servers. + +See the [GitHub Repository](https://github.com/NohamR/RMHook-iOS) for full details, building instructions, and credits. diff --git a/scripts/patch_and_server.py b/scripts/patch_and_server.py new file mode 100644 index 0000000..e1b5a30 --- /dev/null +++ b/scripts/patch_and_server.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 + +import sys +import os +import subprocess +import shutil +import socket + + +def get_local_ip(): + try: + # Try macOS command + output = subprocess.check_output( + ["ipconfig", "getifaddr", "en0"], stderr=subprocess.DEVNULL + ) + return output.decode("utf-8").strip() + except subprocess.CalledProcessError: + try: + # Try Linux command + output = subprocess.check_output( + ["hostname", "-I"], stderr=subprocess.DEVNULL + ) + return output.decode("utf-8").split()[0].strip() + except (subprocess.CalledProcessError, IndexError): + pass + + # Fallback to socket method + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return None + + +def main(): + if len(sys.argv) != 3: + print(f"Usage: {os.path.basename(sys.argv[0])} ") + sys.exit(1) + + ipa_file = None + deb_file = None + + for arg in sys.argv[1:]: + if arg.endswith(".ipa"): + ipa_file = arg + elif arg.endswith(".deb"): + deb_file = arg + else: + print(f"Unknown file type: {arg}") + sys.exit(1) + + if not ipa_file or not deb_file: + print("You must provide one .ipa and one .deb file.") + sys.exit(1) + + out_dir = "/tmp/ipa_patched" + os.makedirs(out_dir, exist_ok=True) + + ipa_name = os.path.basename(ipa_file) + output_ipa = os.path.join(out_dir, ipa_name) + + print("[+] Patching IPA with cyan...") + try: + subprocess.run( + [ + "cyan", + "-i", + ipa_file, + "-o", + output_ipa, + "-f", + deb_file, + "-u", + "--overwrite", + ], + check=True, + ) + except subprocess.CalledProcessError: + print("[-] Patch failed.") + sys.exit(1) + except FileNotFoundError: + print( + "[-] 'cyan' command not found. Please ensure it is installed and in your PATH." + ) + sys.exit(1) + + print("[+] Patch complete.") + + local_ip = get_local_ip() + if not local_ip: + print("Could not detect local IP automatically.") + local_ip = "YOUR_IP" + + download_link = f"http://{local_ip}:8000/{ipa_name}" + + print() + print("==========================================") + print("Download link:") + print(download_link) + print("==========================================") + print() + + # Try to copy to clipboard using pbcopy on macOS + try: + if shutil.which("pbcopy"): + subprocess.run(["pbcopy"], input=download_link.encode("utf-8"), check=True) + print("[+] Download link copied to clipboard.") + except Exception: + pass + + print("[+] Starting HTTP server...") + print("Press Ctrl+C to stop.") + print() + + # Start python HTTP server + try: + subprocess.run( + [sys.executable, "-m", "http.server", "8000", "--directory", out_dir] + ) + except KeyboardInterrupt: + print("\nStopping HTTP server...") + + +if __name__ == "__main__": + main() diff --git a/scripts/watch_and_server.py b/scripts/watch_and_server.py new file mode 100644 index 0000000..337c66c --- /dev/null +++ b/scripts/watch_and_server.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +import sys +import os +import subprocess +import shutil +import socket +import time +import glob +import threading + + +def get_local_ip(): + try: + output = subprocess.check_output( + ["ipconfig", "getifaddr", "en0"], stderr=subprocess.DEVNULL + ) + return output.decode("utf-8").strip() + except subprocess.CalledProcessError: + try: + output = subprocess.check_output( + ["hostname", "-I"], stderr=subprocess.DEVNULL + ) + return output.decode("utf-8").split()[0].strip() + except (subprocess.CalledProcessError, IndexError): + pass + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return None + + +def patch_ipa(ipa_file, deb_file, output_ipa): + print(f"\n[+] Patching IPA with cyan using {os.path.basename(deb_file)}...") + try: + subprocess.run( + [ + "cyan", + "-i", + ipa_file, + "-o", + output_ipa, + "-f", + deb_file, + "-u", + "--overwrite", + ], + check=True, + ) + print("[+] Patch complete.") + return True + except subprocess.CalledProcessError: + print("[-] Patch failed.") + return False + except FileNotFoundError: + print( + "[-] 'cyan' command not found. Please ensure it is installed and in your PATH." + ) + return False + + +def get_newest_deb(directory): + deb_files = glob.glob(os.path.join(directory, "*.deb")) + if not deb_files: + return None + return max(deb_files, key=os.path.getmtime) + + +def start_server(out_dir): + server_process = subprocess.Popen( + [sys.executable, "-m", "http.server", "8000", "--directory", out_dir] + ) + return server_process + + +def main(): + if len(sys.argv) != 3: + print(f"Usage: {os.path.basename(sys.argv[0])} ") + sys.exit(1) + + ipa_file = None + initial_deb_file = None + + for arg in sys.argv[1:]: + if arg.endswith(".ipa"): + ipa_file = arg + elif arg.endswith(".deb"): + initial_deb_file = arg + else: + print(f"Unknown file type: {arg}") + sys.exit(1) + + if not ipa_file or not initial_deb_file: + print("You must provide one .ipa and one .deb file.") + sys.exit(1) + + out_dir = "/tmp/ipa_patched" + os.makedirs(out_dir, exist_ok=True) + + ipa_name = os.path.basename(ipa_file) + output_ipa = os.path.join(out_dir, ipa_name) + + deb_dir = os.path.dirname(os.path.abspath(initial_deb_file)) + current_deb_file = get_newest_deb(deb_dir) or initial_deb_file + last_mtime = ( + os.path.getmtime(current_deb_file) if os.path.exists(current_deb_file) else 0 + ) + + if not patch_ipa(ipa_file, current_deb_file, output_ipa): + sys.exit(1) + + local_ip = get_local_ip() or "YOUR_IP" + download_link = f"http://{local_ip}:8000/{ipa_name}" + + print() + print("==========================================") + print("Download link:") + print(download_link) + print("==========================================") + print() + + try: + if shutil.which("pbcopy"): + subprocess.run(["pbcopy"], input=download_link.encode("utf-8"), check=True) + print("[+] Download link copied to clipboard.") + except Exception: + pass + + print("[+] Starting HTTP server...") + server_process = start_server(out_dir) + + print(f"[+] Watching for new .deb files in {deb_dir} every 2 seconds...") + print("Press Ctrl+C to stop.") + + try: + while True: + time.sleep(2) + newest_deb = get_newest_deb(deb_dir) + if newest_deb: + current_mtime = os.path.getmtime(newest_deb) + if current_mtime > last_mtime: + print( + f"\n[*] Detected new/updated .deb file: {os.path.basename(newest_deb)}" + ) + patch_ipa(ipa_file, newest_deb, output_ipa) + last_mtime = current_mtime + except KeyboardInterrupt: + print("\nStopping HTTP server and watcher...") + server_process.terminate() + server_process.wait() + + +if __name__ == "__main__": + main()