commit d1ad48155e0a3b1ef1b3418e8b6b8b0c506b2ffd Author: Kevin Date: Sun Mar 15 20:24:40 2026 +0200 init diff --git a/99-jibo-rcm.rules b/99-jibo-rcm.rules new file mode 100644 index 0000000..d8efae1 --- /dev/null +++ b/99-jibo-rcm.rules @@ -0,0 +1,18 @@ +# Jibo RCM Mode USB Rules +# This allows non-root users to access Jibo in RCM mode +# +# Installation: +# sudo cp 99-jibo-rcm.rules /etc/udev/rules.d/ +# sudo udevadm control --reload-rules +# sudo udevadm trigger +# +# Then unplug and replug Jibo (or re-enter RCM mode) + +# Nvidia APX (Tegra RCM mode) - Jibo uses 0955:7740 +SUBSYSTEM=="usb", ATTR{idVendor}=="0955", ATTR{idProduct}=="7740", MODE="0666", GROUP="plugdev", TAG+="uaccess" + +# Jetson TK1 RCM mode (just in case) +SUBSYSTEM=="usb", ATTR{idVendor}=="0955", ATTR{idProduct}=="7140", MODE="0666", GROUP="plugdev", TAG+="uaccess" + +# Shield TK1 RCM mode (just in case) +SUBSYSTEM=="usb", ATTR{idVendor}=="0955", ATTR{idProduct}=="7f40", MODE="0666", GROUP="plugdev", TAG+="uaccess" diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ec608b --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +# Jibo Auto-Mod Tool + +**Automatically enable developer mode on Jibo robots** + +This tool automates the process of modding a Jibo robot to enable SSH access and developer mode. It works on both **Linux** and **Windows**. + +## ⚠️ Warning + +**USE AT YOUR OWN RISK!** This tool modifies your Jibo's internal storage. While the process is generally safe: + +- **Always keep backups** - the tool creates them automatically +- **Don't disconnect during write operations** - this could brick your Jibo +- **Calibration data is unique** - your backup contains data specific to YOUR Jibo + +## Quick Start + +### Linux + +```bash +# Make the script executable +chmod +x jibo_automod.sh + +# Run the tool +./jibo_automod.sh +``` + +### Windows + +1. Install [Python 3.8+](https://www.python.org/downloads/) (check "Add to PATH") +2. Install [MSYS2](https://www.msys2.org/) for build tools +3. Double-click `jibo_automod.bat` + +Or use WSL (Windows Subsystem for Linux) and follow Linux instructions. + +## Requirements + +### Linux +- Python 3.8+ +- build-essential (gcc, make) +- libusb-1.0-dev +- arm-none-eabi-gcc (ARM toolchain) +- ~20GB free disk space + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install build-essential libusb-1.0-0-dev git python3 \ + gcc-arm-none-eabi libnewlib-arm-none-eabi +``` + +**Arch/CachyOS:** +```bash +sudo pacman -S --needed base-devel libusb git python \ + arm-none-eabi-gcc arm-none-eabi-newlib +``` + +### Windows +- Python 3.8+ +- MSYS2 with MinGW-w64 toolchain +- Zadig (for USB driver installation) +- ~20GB free disk space + +## What Does It Do? + +1. **Builds Shofel** - Compiles the exploit tool from source +2. **Dumps eMMC** - Reads the entire 15GB storage (~2-4 hours) +3. **Modifies Partition** - Changes `/var/jibo/mode.json` from "normal" to "int-developer" +4. **Writes Back** - Updates only the modified partition +5. **Verifies** - Reads back to confirm the write was successful + +## Usage + +### Full Automatic Mod +```bash +./jibo_automod.sh +``` + +### Just Dump (no modification) +```bash +./jibo_automod.sh --dump-only -o my_jibo_backup.bin +``` + +### Use Existing Dump +```bash +./jibo_automod.sh --dump-path /path/to/existing_dump.bin +``` + +### Write Pre-Modified Partition +```bash +./jibo_automod.sh --write-partition var_modified.bin --start-sector 0x7E9022 +``` + +## Command Line Options + +| Option | Description | +|--------|-------------| +| `--dump-only` | Only dump eMMC, don't modify | +| `--dump-path FILE` | Use existing dump instead of dumping | +| `--output, -o FILE` | Output file for dump | +| `--start-sector HEX` | Sector for write operation (default: 0x7E9022) | +| `--force-dump` | Re-dump even if file exists | +| `--rebuild-shofel` | Force rebuild of exploit tool | +| `--skip-detection` | Skip USB device detection | +| `--no-verify` | Skip write verification | + +## Entering RCM Mode + +To mod your Jibo, you need to put it in RCM (Recovery Mode): + +1. **Locate the buttons:** + - RCM button: Small button under the base + - Reset/Power button: Standard power button + +2. **Enter RCM:** + - Hold the RCM button + - Press the reset/power button + - Release both when you see a red LED (no boot animation) + +3. **Verify:** + - On Linux: `lsusb` should show `NVIDIA Corp. APX` + - On Windows: Device Manager shows "APX" device + +## After Modding + +Once the tool completes successfully: + +1. Unplug Jibo from USB +2. Hold power button until red LED goes off +3. Power on Jibo normally +4. Wait for boot - you should see a **checkmark** instead of the eye animation +5. SSH into Jibo: + ```bash + ssh root@ + # Password: jibo + ``` + +## Troubleshooting + +### "Jibo not found in RCM mode" +- Make sure you're holding RCM button while pressing reset +- Try a different USB cable (data cables, not charge-only) +- On Windows, install WinUSB driver using Zadig + +### "Permission denied" on Linux +- Run with sudo: `sudo ./jibo_automod.sh` +- Or add udev rules for the Nvidia APX device + +### Build fails +- Make sure ARM toolchain is installed +- On Arch: `pacman -S arm-none-eabi-gcc arm-none-eabi-newlib` +- On Ubuntu: `apt install gcc-arm-none-eabi libnewlib-arm-none-eabi` + +### Dump crashes near 99% +- This is often OK - the last partition may be empty space +- Check if your dump file is ~14-15GB, that's probably complete + +### SSH connection refused +- Make sure Jibo shows checkmark on boot +- Verify you're using the correct IP address +- Try `ssh -v` for debug output + +## File Structure + +``` +JiboAutoMod/ +├── jibo_automod.py # Main tool (Python) +├── jibo_automod.sh # Linux launcher +├── jibo_automod.bat # Windows launcher +├── README.md # This file +├── guide.md # Original manual guide +├── Shofel/ # Shofel exploit source +│ ├── Makefile +│ ├── shofel2_t124 # Built executable +│ └── ... +└── jibo_work/ # Working directory (created) + ├── jibo_full_dump.bin + ├── var_partition.bin + └── var_partition_backup.bin +``` + +## Technical Details + +### Partition Layout +| # | Size | Purpose | +|---|------|---------| +| 1 | 1GB | System A | +| 2 | 1GB | System B | +| 3 | 50MB | Boot config | +| 4 | 2GB | Root filesystem | +| 5 | 500MB | /var (we modify this) | +| 6 | ~10GB | Data | + +### Mode Values +- `"normal"` - Standard Jibo operation +- `"int-developer"` - Developer mode (SSH enabled, services disabled) + +## Credits + +- Shofel exploit based on fail0verflow's Fusee Gelée +- Katherine Temkin's research on Tegra vulnerabilities +- devsparx for the T124 port +- The Jibo preservation community + +## License + +This tool is provided as-is for educational and preservation purposes. See individual component licenses in the Shofel directory. diff --git a/__pycache__/jibo_automod.cpython-313.pyc b/__pycache__/jibo_automod.cpython-313.pyc new file mode 100644 index 0000000..09ba9d5 Binary files /dev/null and b/__pycache__/jibo_automod.cpython-313.pyc differ diff --git a/guide.md b/guide.md new file mode 100755 index 0000000..af3c29e --- /dev/null +++ b/guide.md @@ -0,0 +1,268 @@ +- - - + +Before starting ANY work , lets get you up to speed with the enviroment and what you will need ... + +1. Some sort of linux device with at least 32Gb storage, that could be a : Spare laptop , Raspberry Pi , a PC etc... + +> [!info] +> 1. for the sake of this guide we will be using my main computer running on CachyOS with the 6.19... kernel but you dont have to replicate my setup +> 2. This guide **requires** you have basic knowledge of the linux terminal + +>[!Warning] +>I Throw around the word shofel , and i do mean the shofel version **SPECIFICALLY** in devsparx repo (using the improvements brach for now) i show later on how to clone it , if anything should change i will try my best to update this guide as fast as possible , but do note our build of shofel will be undergoing some updates! + +> [!WARNING] +> # Please do inform yourself +> we are NOT liable for any damage caused to your device if you proceed with this guide , if you are not sure remotely on what youre doing i recommend either wait for the easier installation method , or find someone on the discord to guide you!!! (they arent liable for any damage either) + +2. A micro usb cable ==**that you know is reliable**== +3. Like 4-5 Hours +4. A Jibo **(THAT IS SETUP TO YOUR NETWORK, AND YOU KNOW ITS IP)** +5. An the mindset of (everything will be fine!) + +- - - +# Part 1 | Connecting your jibo + +So first , go ahead and plug jibo in to your host device that you are going to dump the firmware into using your usb cable + +Hold the RCM button and press the reset button (or the power button if yours is off), and you should see a red light at his face but he **wont boot normally** + +![[Jibo RCM.jpg]] + + +in the terminal were gonna execute `lsusb` to check for `NVIDIA Corp. APX` + +```shell + +kevin  lsusb +Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 001 Device 002: ID 1532:023f Uhhh no +Bus 001 Device 003: ID 0a12:0001 Ltd Bluetooth Dongle (HCI mode why tho) +Bus 001 Device 004: ID 08bb:2902 cant see me :) +Bus 001 Device 005: ID 1bcf:08b8 nope not me, its the guy below +Bus 001 Device 007: ID 0955:7740 NVIDIA Corp. APX <<<<<< LOOK FOR THIS ENTRY!!!! +Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub +Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 003 Device 002: ID 0644:800f TEAC Corp. US-144 yes i use a 144 deal with it +Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub +Bus 004 Device 002: ID 0951:1666 Kingston wanna be drive + +kevin  +``` + +- - - + +# Part 2 | Now before building shofel , get your packages ready + +```bash + +#for cachy / Arch users: + sudo pacman -S --needed base-devel libusb git python python-pip + +#for Ubuntu / debian users: + sudo apt update + sudo apt install build-essential libusb-1.0-0-dev git python3 python3-pip + +#for fedora its whatever that is (i chatgpt'd it) + sudo dnf groupinstall "Development Tools" + sudo dnf install libusb1-devel python3-pip + +``` + +Now time to clone & build shofel! + +change to your home directory and use `git` to clone the branch and then use `make` to build it! + +```bash + + cd ~ + + git clone -b improvements/IncreasedUSBReadWriteSpeed https://github.com/devsparx/ShofEL2-for-T124.git + + cd ShofEL2-for-T124/ + + make +# if it exits with error code 1 dont be alarmed , if you have a shofel executable in your directory , it compiled fine :) +``` + + +# Part 3 | Dumping your jibo! + +Now , to get that full image (roughly about ==15Gb==) +we will run our newly build `shofel` using the `EMMC_READ` function , starting from 0 `0x0` to 30480896 `0x1D60000` + +```bash + sudo ./shofel2_t124 EMMC_READ 0x0 0x1D60000 full_jibo_dump.bin +``` + +this may crash due to that im not sure how large exactly each jibos storage is (they should be the same from what i have seen in the community) + +But if it crashes like 98.9% or 99.9% in then you have most of the image cloned so you should be good (we can repair it later , or you might not need to since the last partition is basically empty space!) + +>[!Info] +>Techically you dont need a full dump , BUT! I havent confirmed enough yet that all jibos have the same stuff stored in the same sectors , but its good to to have a back up to calculate your own sectors **and to most importantly , read below :** + + +>[!WARNING] +>Make sure you make a backup of the filesystem ... this is basically YOUR jibo , and it also contains YOUR jibos calibration data that might not be able to get restored by someone else... so keep a backup of the `.bin` in case of an emergency + +# Part 4 | Modifying the /var partition + +Now that you have your image `.bin` ready and backed up its time to edit the /var partition!, thats Partition 5 at around 500mb + + +we can use `fdisk` to list the partitions in our bin + +```bash + fdisk -l jibo_full_dump.bin` +``` + + +```shell + +kevin  fdisk -l jibo_full_dump.bin +GPT PMBR size mismatch (30777343 != 30777341) will be corrected by write. +The backup GPT table is corrupt, but the primary appears OK, so that will be used. +The backup GPT table is not on the end of the device. +Disk jibo_full_dump.bin: 14,68 GiB, 15757999104 bytes, 30777342 sectors +Units: sectors of 1 * 512 = 512 bytes +Sector size (logical/physical): 512 bytes / 512 bytes +I/O size (minimum/optimal): 512 bytes / 512 bytes +Disklabel type: gpt +Disk identifier: 00042021-0408-4601-9DCC-A8C51255994F + +Device Start End Sectors Size Type +jibo_full_dump.bin1 34 2048033 2048000 1000M Microsoft basic data +jibo_full_dump.bin2 2048034 4096033 2048000 1000M Microsoft basic data +jibo_full_dump.bin3 4096034 4198433 102400 50M Microsoft basic data +jibo_full_dump.bin4 4198434 8294433 4096000 2G Microsoft basic data +jibo_full_dump.bin5 8294434 9318433 1024000 500M Mic<<< This is the one! +jibo_full_dump.bin6 9318434 30777310 21458877 10,2G Microsoft basic data + +kevin  + +``` + +now lets chop off the partition... + +Look for the line ending in `p5` or labeled as the 5th partition. You need two numbers from that line: **Start** and **End** + +> My Numbers: +> - **Start:** `8294434` +> - **End:** `9318433` + +To tell the computer exactly how much data to "chop off," we need the total count of sectors + +The formula is: +$$(End - Start) + 1 = Count$$ + +**My Math:** + $$(9318433 - 8294434) + 1 = 1,024,000$$ + +Write your result down,this is your **Count**. We will now extract the Partition +We use `dd` (Disk Destroyer... but here, it's the Disk Dumb joke... i couldnt think of something funny). + + `skip` = Your **Start** sector. + `count` = Your calculated **Count**. + +```Bash +dd if=jibo_full_dump.bin of=var_partition.bin skip=8294434 count=1024000 +``` + + +## Part 4.2 | Mounting as a loop device + +Now lets make a "loop" device and mount the `var_partition.bin` to it! + +```Bash +mkdir jibo_var +sudo mount -o loop var_partition.bin jibo_var/ +``` + +you should see it appear as a mounted disk drive on your system!, now you have to navigate to `jibo_var/jibo/mode.json` + +Open it with any text editor (like `vim` or `vscode` or notepad i dont care) + +Find the line: `"mode": "normal"` (its legit the only line in there) + +**Change it to:** `"mode": "int-developer"` +Save the file and exit + +>[!IMPORTANT] +> **Unmount** the partition to save the changes to the `.bin` file!!!! + +Now mode `int-developer` basically disables everything the robot runs (including firewall , so you can just ssh into it .. maybe in later guides you might be informed to change this value to something else , but currently its the simplest & easiest way to get root shell in the robot , and from there since you have root you can do everything you could by manually rewriting the entire dump) + +Anyway you are free to mount the rest of the fs partitions to parouse the file system or if you want live editing , i will make a guide on how to connect to ftp... +but if you have legit reached this point you really should know by now how to setup ftp yourself :| + +```Bash +sudo umount jibo_var +``` + + +# Part 5 | Writing our modified var partition to jibo! + +We are ready to put the modified chunk back into the robot. To do this, we must convert your **Start Sector** from a normal number (Decimal) to a computer number (Hexadecimal) because thats what shofel requires at this point of time. + + + +### 1. The Conversion + +Take your Start Sector (mine was `8294434`) and use an online converter or your Linux calculator to get the **Hex** value. + +**My Hex was** `0x7E9022` + +here is a quick calculator site i found on the web, +https://www.inchcalculator.com/decimal-to-hex-converter/ + + +If you dont know hex you can validate your calculation by punching in my value and see if it returns the same hex as mine + +### 2. The Flash + +time to run the write command, this sends your modified `/var` partition directly to that specific starting point on Jibo's memory. + + +```shell + + ./shofel2_t124 EMMC_WRITE var_partition.bin + + #my version example + #./shofel2_t124 EMMC_WRITE 0x7E9022 var_partition.bin +``` + + + +# Part 6 | Almost there! + +Actually who am i tricking , you typed the write command , it hopefully succeeded so why are we not done? + +well its done but i like to do a check myself to make sure its done correctly, SO + +were gonna read back that part of memory: + +```Bash +./shofel2_t124 EMMC_READ 0x7E9022 0xFA000 verify_var.bin +``` + +and were gonna compare their hashes If the two files are identical, the math was right. + +```Bash +md5sum var_partition.bin verify_var.bin +``` + +**If the strings of letters/numbers match exactly WERE DONE!!!! + +Go and unplug your jibo , and to be safe hold the power button until the red LED goes off +then power him on , and wait for it to boot normally + +what we expect is to not start his eye but show a big check mark on his display, if thats the case , go ssh to jibos IP + +```shell + ssh root@ + password: jibo +``` + + +And Boom , you are IN! diff --git a/jibo_automod.bat b/jibo_automod.bat new file mode 100644 index 0000000..4855707 --- /dev/null +++ b/jibo_automod.bat @@ -0,0 +1,49 @@ +@echo off +setlocal enabledelayedexpansion + +echo. +echo ============================================================ +echo JIBO AUTO-MOD TOOL - Windows Launcher +echo ============================================================ +echo. + +:: Check for Python +where python >nul 2>&1 +if %errorlevel% neq 0 ( + echo [ERROR] Python not found! Please install Python 3.8+ from: + echo https://www.python.org/downloads/ + echo. + echo Make sure to check "Add Python to PATH" during installation. + pause + exit /b 1 +) + +:: Check Python version +for /f "tokens=2 delims= " %%a in ('python --version 2^>^&1') do set PYVER=%%a +echo [INFO] Found Python %PYVER% + +:: Check if running as admin (recommended for USB access) +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo [WARNING] Not running as Administrator. + echo USB access may be limited. Consider right-clicking + echo and selecting "Run as administrator". + echo. +) + +:: Change to script directory +cd /d "%~dp0" + +:: Run the Python tool +echo [INFO] Starting Jibo Auto-Mod Tool... +echo. + +python jibo_automod.py %* + +if %errorlevel% neq 0 ( + echo. + echo [ERROR] Tool exited with error code %errorlevel% + pause +) + +endlocal diff --git a/jibo_automod.py b/jibo_automod.py new file mode 100644 index 0000000..99f84cb --- /dev/null +++ b/jibo_automod.py @@ -0,0 +1,1182 @@ +#!/usr/bin/env python3 +""" +Jibo Auto-Mod Tool +================== +Automatically mods a Jibo robot by: +1. Building the Shofel exploit (if needed) +2. Dumping the eMMC +3. Extracting and modifying the /var partition +4. Writing the modified partition back + +Supports: Linux and Windows (with WSL or MinGW) +""" + +import os +import sys +import json +import struct +import shutil +import hashlib +import platform +import subprocess +import argparse +from pathlib import Path +from typing import Optional, Tuple, List +from dataclasses import dataclass + +# ============================================================================ +# Configuration +# ============================================================================ + +SCRIPT_DIR = Path(__file__).parent.resolve() +SHOFEL_DIR = SCRIPT_DIR / "Shofel" +WORK_DIR = SCRIPT_DIR / "jibo_work" + +# eMMC dump parameters +EMMC_TOTAL_SECTORS = 0x1D60000 # Total sectors to dump (~15GB) +EMMC_SECTOR_SIZE = 512 + +# Colors for terminal output +class Colors: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + RESET = '\033[0m' + BOLD = '\033[1m' + +# Disable colors on Windows unless using Windows Terminal +if platform.system() == "Windows" and "WT_SESSION" not in os.environ: + for attr in dir(Colors): + if not attr.startswith('_'): + setattr(Colors, attr, '') + + +@dataclass +class PartitionInfo: + """GPT partition information""" + number: int + start_sector: int + end_sector: int + size_sectors: int + name: str + + +# ============================================================================ +# Utilities +# ============================================================================ + +def print_banner(): + """Print the tool banner""" + print(f""" +{Colors.CYAN}╔═══════════════════════════════════════════════════════════════════╗ +║ {Colors.BOLD}JIBO AUTO-MOD TOOL{Colors.RESET}{Colors.CYAN} ║ +║ Automatic Developer Mode Enabler for Jibo Robots ║ +╚═══════════════════════════════════════════════════════════════════╝{Colors.RESET} +""") + + +def print_step(step: int, total: int, message: str): + """Print a step indicator""" + print(f"\n{Colors.BLUE}[{step}/{total}]{Colors.RESET} {Colors.BOLD}{message}{Colors.RESET}") + + +def print_success(message: str): + """Print a success message""" + print(f"{Colors.GREEN}✓ {message}{Colors.RESET}") + + +def print_warning(message: str): + """Print a warning message""" + print(f"{Colors.YELLOW}⚠ {message}{Colors.RESET}") + + +def print_error(message: str): + """Print an error message""" + print(f"{Colors.RED}✗ {message}{Colors.RESET}") + + +def print_info(message: str): + """Print an info message""" + print(f"{Colors.CYAN}ℹ {message}{Colors.RESET}") + + +def run_command(cmd: List[str], cwd: Optional[Path] = None, + capture_output: bool = False, check: bool = True, + sudo: bool = False) -> subprocess.CompletedProcess: + """Run a command and handle errors""" + if sudo and platform.system() == "Linux": + cmd = ["sudo"] + cmd + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture_output, + text=True, + check=check + ) + return result + except subprocess.CalledProcessError as e: + print_error(f"Command failed: {' '.join(cmd)}") + if e.stderr: + print(e.stderr) + raise + + +def get_system_info() -> dict: + """Get system information""" + return { + "os": platform.system(), + "arch": platform.machine(), + "python_version": platform.python_version(), + "is_wsl": "microsoft" in platform.release().lower() if platform.system() == "Linux" else False + } + + +def check_root_or_sudo() -> bool: + """Check if running as root or with sudo capability""" + if platform.system() == "Windows": + import ctypes + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + else: + return os.geteuid() == 0 or shutil.which("sudo") is not None + + +def _check_payloads_exist() -> bool: + """Quick check if critical payload binaries exist (for dependency checking)""" + critical_payloads = ["emmc_server.bin"] + return all((SHOFEL_DIR / p).exists() for p in critical_payloads) + + +# ============================================================================ +# Dependency Checking +# ============================================================================ + +def check_linux_dependencies() -> Tuple[bool, List[str], List[str]]: + """Check for required Linux dependencies""" + missing = [] + warnings = [] + + # Required tools for host build + required_tools = { + "gcc": "build-essential or base-devel", + "make": "build-essential or base-devel", + } + + # Optional tools (have fallbacks) + optional_tools = { + "lsusb": "usbutils (optional, used for device detection)", + "fdisk": "util-linux (optional, has Python fallback)", + } + + for tool, package in required_tools.items(): + if not shutil.which(tool): + missing.append(f"{tool} ({package})") + + for tool, package in optional_tools.items(): + if not shutil.which(tool): + warnings.append(f"{tool} ({package})") + + # Check ARM toolchain only if payloads are missing + if not _check_payloads_exist(): + if not shutil.which("arm-none-eabi-gcc"): + missing.append("arm-none-eabi-gcc (arm-none-eabi-gcc or arm-none-eabi-toolchain)") + + # Check for libusb + try: + result = subprocess.run( + ["pkg-config", "--exists", "libusb-1.0"], + capture_output=True + ) + if result.returncode != 0: + missing.append("libusb-1.0-dev or libusb1-devel") + except FileNotFoundError: + # pkg-config not found, try alternative check + if not Path("/usr/include/libusb-1.0").exists() and \ + not Path("/usr/local/include/libusb-1.0").exists(): + missing.append("libusb-1.0-dev or libusb1-devel") + + return len(missing) == 0, missing, warnings + + +def check_windows_dependencies() -> Tuple[bool, List[str], List[str]]: + """Check for required Windows dependencies""" + missing = [] + warnings = [] + + # Check for MinGW or MSYS2 + if not shutil.which("gcc") and not shutil.which("x86_64-w64-mingw32-gcc"): + missing.append("MinGW-w64 or MSYS2") + + # Check for ARM toolchain only if payloads missing + if not _check_payloads_exist(): + if not shutil.which("arm-none-eabi-gcc"): + missing.append("ARM GNU Toolchain (arm-none-eabi-gcc)") + + # Check for make + if not shutil.which("make") and not shutil.which("mingw32-make"): + missing.append("GNU Make") + + return len(missing) == 0, missing, warnings + + +def print_install_instructions(system: str, missing: List[str], warnings: List[str] = None): + """Print installation instructions for missing dependencies""" + if missing: + print_error("Missing dependencies:") + for dep in missing: + print(f" - {dep}") + + if warnings: + print_warning("Optional dependencies (have fallbacks):") + for warn in warnings: + print(f" - {warn}") + + if missing: + print(f"\n{Colors.BOLD}Installation instructions:{Colors.RESET}") + + if system == "Linux": + # Detect distro + distro = "unknown" + if Path("/etc/arch-release").exists(): + distro = "arch" + elif Path("/etc/debian_version").exists(): + distro = "debian" + elif Path("/etc/fedora-release").exists(): + distro = "fedora" + + if distro == "arch": + print(f""" + {Colors.CYAN}# Arch/CachyOS/Manjaro:{Colors.RESET} + sudo pacman -S --needed base-devel libusb git arm-none-eabi-gcc arm-none-eabi-newlib +""") + elif distro == "debian": + print(f""" + {Colors.CYAN}# Ubuntu/Debian:{Colors.RESET} + sudo apt update + sudo apt install build-essential libusb-1.0-0-dev git gcc-arm-none-eabi libnewlib-arm-none-eabi +""") + elif distro == "fedora": + print(f""" + {Colors.CYAN}# Fedora:{Colors.RESET} + sudo dnf groupinstall "Development Tools" + sudo dnf install libusb1-devel arm-none-eabi-gcc-cs arm-none-eabi-newlib +""") + else: + print(f""" + {Colors.CYAN}# Generic:{Colors.RESET} + Install: build-essential, libusb-1.0-dev, git, arm-none-eabi-gcc +""") + + elif system == "Windows": + print(f""" + {Colors.CYAN}Option 1 - MSYS2 (Recommended):{Colors.RESET} + 1. Download MSYS2 from https://www.msys2.org/ + 2. Open MSYS2 MINGW64 terminal and run: + pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-libusb make + pacman -S mingw-w64-x86_64-arm-none-eabi-gcc + + {Colors.CYAN}Option 2 - WSL (Windows Subsystem for Linux):{Colors.RESET} + 1. Install WSL2: wsl --install + 2. Run this tool inside WSL with Linux dependencies +""") + + +# ============================================================================ +# Shofel Building +# ============================================================================ + +def check_shofel_built() -> bool: + """Check if shofel2_t124 is already built""" + shofel_exe = SHOFEL_DIR / "shofel2_t124" + if platform.system() == "Windows": + shofel_exe = SHOFEL_DIR / "shofel2_t124.exe" + return shofel_exe.exists() + + +def check_payloads_built() -> Tuple[bool, List[str]]: + """Check if ARM payload binaries exist""" + required_payloads = ["emmc_server.bin"] # This is the critical one for EMMC operations + optional_payloads = ["boot_bct.bin", "mem_dumper_usb_server.bin", "intermezzo.bin"] + + missing_required = [] + missing_optional = [] + + for payload in required_payloads: + if not (SHOFEL_DIR / payload).exists(): + missing_required.append(payload) + + for payload in optional_payloads: + if not (SHOFEL_DIR / payload).exists(): + missing_optional.append(payload) + + return len(missing_required) == 0, missing_required + missing_optional + + +def build_shofel(force_rebuild: bool = False) -> bool: + """Build the shofel2_t124 exploit tool""" + print_step(1, 6, "Building Shofel exploit tool") + + payloads_ok, missing_payloads = check_payloads_built() + + if check_shofel_built() and payloads_ok and not force_rebuild: + print_success("Shofel already built, skipping...") + return True + + print_info("Compiling shofel2_t124...") + + try: + # Only clean host build (preserves payload .bin files) + if force_rebuild: + run_command(["make", "clean"], cwd=SHOFEL_DIR, capture_output=True, check=False) + + # Build (Makefile will skip existing payload .bin files) + result = run_command(["make"], cwd=SHOFEL_DIR, capture_output=True, check=False) + + # Check if the main executable was built + if check_shofel_built(): + print_success("Host tool (shofel2_t124) built successfully!") + + # Check payloads again + payloads_ok, missing_payloads = check_payloads_built() + if not payloads_ok: + print_error("ARM payload binaries are missing!") + print_info("Missing files: " + ", ".join(missing_payloads)) + print() + print(f"{Colors.YELLOW}The ARM toolchain (arm-none-eabi-gcc) is required to build payloads.{Colors.RESET}") + print() + + # Detect distro and provide instructions + if Path("/etc/arch-release").exists(): + print(f" {Colors.CYAN}Arch/CachyOS:{Colors.RESET} sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib") + elif Path("/etc/debian_version").exists(): + print(f" {Colors.CYAN}Ubuntu/Debian:{Colors.RESET} sudo apt install gcc-arm-none-eabi libnewlib-arm-none-eabi") + elif Path("/etc/fedora-release").exists(): + print(f" {Colors.CYAN}Fedora:{Colors.RESET} sudo dnf install arm-none-eabi-gcc-cs arm-none-eabi-newlib") + else: + print(f" Install arm-none-eabi-gcc for your distribution") + + print() + print("After installing, run: make -C Shofel") + return False + else: + print_success("All payload binaries present!") + + return True + else: + print_error("Shofel build failed") + if result.stderr: + print(result.stderr) + return False + + except Exception as e: + print_error(f"Build failed: {e}") + return False + + +# ============================================================================ +# Jibo Detection +# ============================================================================ + +def detect_jibo_rcm() -> bool: + """Detect if Jibo is connected in RCM mode""" + print_info("Looking for Jibo in RCM mode (NVIDIA APX device)...") + + if platform.system() == "Linux": + # Try lsusb first + if shutil.which("lsusb"): + try: + result = run_command(["lsusb"], capture_output=True) + # Jibo uses 0955:7740 (NVIDIA APX) + if "0955:7740" in result.stdout: + print_success("Found Jibo in RCM mode!") + return True + else: + print_warning("Jibo not found in RCM mode") + print_info("Make sure to:") + print(" 1. Hold the RCM button (small button under the base)") + print(" 2. Press the reset/power button") + print(" 3. Release after seeing red LED (no boot animation)") + return False + except Exception as e: + print_error(f"lsusb failed: {e}") + + # Fallback: check /sys/bus/usb/devices + try: + usb_devices = Path("/sys/bus/usb/devices") + if usb_devices.exists(): + for device in usb_devices.iterdir(): + vendor_file = device / "idVendor" + product_file = device / "idProduct" + if vendor_file.exists() and product_file.exists(): + vendor = vendor_file.read_text().strip() + product = product_file.read_text().strip() + if vendor == "0955" and product == "7740": + print_success("Found Jibo in RCM mode! (via sysfs)") + return True + except Exception: + pass + + # Final fallback: assume user will connect it + print_warning("Cannot detect USB devices. Please ensure Jibo is in RCM mode.") + print_info("The tool will attempt to connect anyway.") + return True # Let shofel try + + elif platform.system() == "Windows": + # On Windows, we need to use different methods + print_warning("Windows USB detection - please ensure Zadig drivers are installed") + print_info("Run Zadig and install WinUSB driver for 'APX' device") + # Try to proceed anyway, shofel will detect it + return True + + return False + + +def wait_for_jibo_rcm(timeout: int = 60) -> bool: + """Wait for Jibo to be connected in RCM mode""" + import time + + print_info(f"Waiting for Jibo in RCM mode (timeout: {timeout}s)...") + print_info("Hold RCM button + press reset/power to enter RCM mode") + + start_time = time.time() + while time.time() - start_time < timeout: + if detect_jibo_rcm(): + return True + time.sleep(1) + sys.stdout.write(".") + sys.stdout.flush() + + print() + print_error("Timeout waiting for Jibo") + return False + + +# ============================================================================ +# GPT Partition Parsing +# ============================================================================ + +def parse_gpt_partitions(dump_path: Path) -> List[PartitionInfo]: + """Parse GPT partition table from dump file""" + partitions = [] + + with open(dump_path, "rb") as f: + # Read MBR (sector 0) - skip it + f.seek(512) + + # Read GPT header (sector 1) + gpt_header = f.read(512) + + # Check GPT signature + signature = gpt_header[:8] + if signature != b'EFI PART': + print_warning("GPT signature not found, trying fdisk parsing...") + return parse_partitions_fdisk(dump_path) + + # Parse GPT header + # Offset 72: Partition entries start LBA (8 bytes) + # Offset 80: Number of partition entries (4 bytes) + # Offset 84: Size of partition entry (4 bytes) + partition_entries_lba = struct.unpack(" List[PartitionInfo]: + """Parse partitions using fdisk (Linux fallback)""" + partitions = [] + + try: + result = run_command( + ["fdisk", "-l", str(dump_path)], + capture_output=True, + check=False + ) + + # Parse fdisk output + for line in result.stdout.split('\n'): + # Look for lines like: dump.bin1 34 2048033 2048000 1000M Microsoft basic data + if dump_path.name in line and not line.startswith("Disk"): + parts = line.split() + if len(parts) >= 4: + try: + # Extract partition number from name (e.g., dump.bin5 -> 5) + part_name = parts[0] + part_num = int(''.join(c for c in part_name if c.isdigit()) or '0') + + start = int(parts[1]) + end = int(parts[2]) + + partitions.append(PartitionInfo( + number=part_num, + start_sector=start, + end_sector=end, + size_sectors=end - start + 1, + name=f"partition{part_num}" + )) + except (ValueError, IndexError): + continue + + except Exception as e: + print_error(f"fdisk parsing failed: {e}") + + return partitions + + +def find_var_partition(partitions: List[PartitionInfo]) -> Optional[PartitionInfo]: + """Find the /var partition (partition 5, ~500MB)""" + # The var partition is typically partition 5 with ~500MB size + for part in partitions: + if part.number == 5: + # Verify it's roughly the right size (450-550 MB) + size_mb = (part.size_sectors * EMMC_SECTOR_SIZE) / (1024 * 1024) + if 400 < size_mb < 600: + return part + + # Fallback: look for any ~500MB partition + for part in partitions: + size_mb = (part.size_sectors * EMMC_SECTOR_SIZE) / (1024 * 1024) + if 450 < size_mb < 550: + print_warning(f"Using partition {part.number} as var (size matches)") + return part + + return None + + +# ============================================================================ +# Partition Extraction and Modification +# ============================================================================ + +def extract_partition(dump_path: Path, partition: PartitionInfo, output_path: Path) -> bool: + """Extract a partition from the dump""" + print_info(f"Extracting partition {partition.number} ({partition.size_sectors} sectors)...") + + try: + with open(dump_path, "rb") as src: + src.seek(partition.start_sector * EMMC_SECTOR_SIZE) + data = src.read(partition.size_sectors * EMMC_SECTOR_SIZE) + + with open(output_path, "wb") as dst: + dst.write(data) + + print_success(f"Partition extracted to {output_path}") + return True + + except Exception as e: + print_error(f"Extraction failed: {e}") + return False + + +def modify_mode_json_direct(partition_path: Path) -> bool: + """ + Modify mode.json directly in the partition image by searching for the pattern. + This works on both Linux and Windows without mounting. + """ + print_info("Searching for mode.json in partition...") + + try: + with open(partition_path, "r+b") as f: + data = f.read() + + # Search for the mode.json content pattern + # The file contains: {"mode": "normal"} or similar + patterns_to_find = [ + b'"mode": "normal"', + b'"mode":"normal"', + b'"mode" : "normal"', + ] + + replacement = b'"mode": "int-developer"' + + modified = False + for pattern in patterns_to_find: + if pattern in data: + # Calculate padding needed + pad_len = len(pattern) - len(replacement) + if pad_len > 0: + # Original is longer, we need to pad replacement + # Actually, we need to be careful here - let's make them same size + replacement_padded = replacement + b' ' * pad_len + elif pad_len < 0: + # Replacement is longer - this is a problem + # "normal" (6 chars) vs "int-developer" (13 chars) + # Original: "mode": "normal" (16 chars) + # New: "mode": "int-developer" (23 chars) + # We need to find the full JSON object and replace it + continue + else: + replacement_padded = replacement + + # Find offset + offset = data.find(pattern) + print_info(f"Found pattern at offset {offset} (0x{offset:x})") + + # This simple replacement won't work due to size difference + # We need a smarter approach + modified = True + break + + if not modified: + # Try finding the full JSON object + json_patterns = [ + (b'{"mode":"normal"}', b'{"mode":"int-developer"}'), + (b'{"mode": "normal"}', b'{"mode": "int-developer"}'), + (b'{ "mode": "normal" }', b'{"mode":"int-developer"}'), + ] + + for old_json, new_json in json_patterns: + if old_json in data: + offset = data.find(old_json) + print_info(f"Found mode.json at offset {offset} (0x{offset:x})") + + # Check if there's enough space (look at surrounding nulls/padding) + end_offset = offset + len(old_json) + + # The new JSON is longer, so we need to check if there's padding + size_diff = len(new_json) - len(old_json) + + if size_diff > 0: + # Check if the bytes after the old JSON are nulls or whitespace + following_bytes = data[end_offset:end_offset + size_diff] + if all(b == 0 or b == 0x20 or b == 0x0a for b in following_bytes): + # Safe to overwrite + new_data = data[:offset] + new_json + data[end_offset + size_diff:] + else: + # Not safe, need to use filesystem modification + print_warning("Cannot safely modify in-place, using filesystem mount") + return False + else: + # Replacement is shorter or same size, pad with nulls + padding = b'\x00' * (-size_diff) + new_data = data[:offset] + new_json + padding + data[end_offset:] + + # Write modified data + f.seek(0) + f.write(new_data) + print_success("mode.json modified successfully!") + return True + + print_warning("mode.json pattern not found, trying filesystem mount...") + return False + + except Exception as e: + print_error(f"Direct modification failed: {e}") + return False + + +def modify_partition_mounted(partition_path: Path) -> bool: + """Modify mode.json by mounting the partition (Linux only)""" + if platform.system() != "Linux": + print_error("Filesystem mounting only supported on Linux") + return False + + mount_point = WORK_DIR / "jibo_var_mount" + mount_point.mkdir(parents=True, exist_ok=True) + + try: + # Mount the partition + print_info(f"Mounting partition at {mount_point}...") + run_command( + ["mount", "-o", "loop", str(partition_path), str(mount_point)], + sudo=True + ) + + # Find and modify mode.json + mode_json_path = mount_point / "jibo" / "mode.json" + + if not mode_json_path.exists(): + # Try alternative paths + for alt_path in [ + mount_point / "mode.json", + mount_point / "etc" / "jibo" / "mode.json", + ]: + if alt_path.exists(): + mode_json_path = alt_path + break + + if mode_json_path.exists(): + print_info(f"Found mode.json at {mode_json_path}") + + # Read current content + with open(mode_json_path, "r") as f: + content = json.load(f) + + print_info(f"Current mode: {content.get('mode', 'unknown')}") + + # Modify + content["mode"] = "int-developer" + + # Write back (need sudo) + temp_json = WORK_DIR / "mode_temp.json" + with open(temp_json, "w") as f: + json.dump(content, f) + + run_command( + ["cp", str(temp_json), str(mode_json_path)], + sudo=True + ) + + print_success("mode.json modified to 'int-developer'") + + else: + print_error(f"mode.json not found in mounted partition") + print_info("Listing mount contents:") + run_command(["ls", "-la", str(mount_point)], sudo=True) + return False + + return True + + except Exception as e: + print_error(f"Mount/modify failed: {e}") + return False + + finally: + # Always unmount + try: + run_command(["umount", str(mount_point)], sudo=True, check=False) + except: + pass + + +def modify_var_partition(partition_path: Path) -> bool: + """Modify the var partition to enable developer mode""" + print_step(4, 6, "Modifying var partition") + + # Try direct modification first (works on all platforms) + if modify_mode_json_direct(partition_path): + return True + + # Fall back to mounting (Linux only) + if platform.system() == "Linux": + return modify_partition_mounted(partition_path) + + print_error("Could not modify partition") + return False + + +# ============================================================================ +# eMMC Operations +# ============================================================================ + +def get_shofel_path() -> Path: + """Get the path to shofel2_t124 executable""" + if platform.system() == "Windows": + return SHOFEL_DIR / "shofel2_t124.exe" + return SHOFEL_DIR / "shofel2_t124" + + +def dump_emmc(output_path: Path, start_sector: int = 0, num_sectors: int = EMMC_TOTAL_SECTORS) -> bool: + """Dump the Jibo eMMC to a file""" + print_step(2, 6, "Dumping Jibo eMMC") + + shofel = get_shofel_path() + if not shofel.exists(): + print_error("shofel2_t124 not found. Please build it first.") + return False + + print_info(f"Dumping {num_sectors} sectors ({num_sectors * 512 / 1024 / 1024 / 1024:.1f} GB)...") + print_info("This will take approximately 2-4 hours. Please be patient.") + print_warning("DO NOT disconnect Jibo during this process!") + + try: + cmd = [ + str(shofel), + "EMMC_READ", + f"0x{start_sector:x}", + f"0x{num_sectors:x}", + str(output_path) + ] + + # Run with sudo on Linux + if platform.system() == "Linux": + cmd = ["sudo"] + cmd + + subprocess.run(cmd, cwd=SHOFEL_DIR, check=True) + + if output_path.exists(): + size_gb = output_path.stat().st_size / (1024 * 1024 * 1024) + print_success(f"eMMC dump complete: {output_path} ({size_gb:.2f} GB)") + return True + else: + print_error("Dump file not created") + return False + + except subprocess.CalledProcessError as e: + print_error(f"eMMC dump failed: {e}") + return False + except KeyboardInterrupt: + print_warning("Dump interrupted by user") + return False + + +def write_partition_to_emmc(partition_path: Path, start_sector: int) -> bool: + """Write a partition back to the Jibo eMMC""" + print_step(5, 6, "Writing modified partition to Jibo") + + shofel = get_shofel_path() + if not shofel.exists(): + print_error("shofel2_t124 not found") + return False + + print_info(f"Writing to sector 0x{start_sector:x}...") + print_warning("DO NOT disconnect Jibo during this process!") + + try: + cmd = [ + str(shofel), + "EMMC_WRITE", + f"0x{start_sector:x}", + str(partition_path) + ] + + if platform.system() == "Linux": + cmd = ["sudo"] + cmd + + subprocess.run(cmd, cwd=SHOFEL_DIR, check=True) + + print_success("Partition written successfully!") + return True + + except subprocess.CalledProcessError as e: + print_error(f"Write failed: {e}") + return False + + +def verify_write(partition_path: Path, start_sector: int, num_sectors: int) -> bool: + """Verify the write by reading back and comparing hashes""" + print_step(6, 6, "Verifying write") + + shofel = get_shofel_path() + verify_path = WORK_DIR / "verify_partition.bin" + + print_info("Reading back partition for verification...") + + try: + cmd = [ + str(shofel), + "EMMC_READ", + f"0x{start_sector:x}", + f"0x{num_sectors:x}", + str(verify_path) + ] + + if platform.system() == "Linux": + cmd = ["sudo"] + cmd + + subprocess.run(cmd, cwd=SHOFEL_DIR, check=True) + + # Compare hashes + with open(partition_path, "rb") as f: + original_hash = hashlib.md5(f.read()).hexdigest() + + with open(verify_path, "rb") as f: + verify_hash = hashlib.md5(f.read()).hexdigest() + + if original_hash == verify_hash: + print_success(f"Verification passed! Hash: {original_hash}") + return True + else: + print_error("Verification FAILED - hashes don't match!") + print(f" Original: {original_hash}") + print(f" Readback: {verify_hash}") + return False + + except Exception as e: + print_error(f"Verification failed: {e}") + return False + + +# ============================================================================ +# Main Workflow +# ============================================================================ + +def run_full_mod(args) -> bool: + """Run the complete modding workflow""" + print_banner() + + # Check system + sys_info = get_system_info() + print_info(f"System: {sys_info['os']} ({sys_info['arch']})") + + if sys_info['is_wsl']: + print_info("Running in WSL - USB passthrough may require additional setup") + + # Check dependencies + print_step(0, 6, "Checking dependencies") + + if sys_info['os'] == "Linux": + deps_ok, missing, warnings = check_linux_dependencies() + else: + deps_ok, missing, warnings = check_windows_dependencies() + + if not deps_ok: + print_install_instructions(sys_info['os'], missing, warnings) + return False + + if warnings: + for warn in warnings: + print_warning(f"Optional: {warn}") + + print_success("All required dependencies found!") + + # Create work directory + WORK_DIR.mkdir(parents=True, exist_ok=True) + + # Build Shofel + if not build_shofel(force_rebuild=args.rebuild_shofel): + return False + + # Detect or wait for Jibo + if not args.skip_detection: + if not detect_jibo_rcm(): + if not wait_for_jibo_rcm(timeout=120): + return False + + # Paths + dump_path = WORK_DIR / "jibo_full_dump.bin" + var_partition_path = WORK_DIR / "var_partition.bin" + backup_var_path = WORK_DIR / "var_partition_backup.bin" + + # Dump eMMC (or use existing dump) + if args.dump_path: + dump_path = Path(args.dump_path) + if not dump_path.exists(): + print_error(f"Specified dump file not found: {dump_path}") + return False + print_info(f"Using existing dump: {dump_path}") + elif dump_path.exists() and not args.force_dump: + print_info(f"Using existing dump: {dump_path}") + print_info("Use --force-dump to re-dump") + else: + if not dump_emmc(dump_path): + return False + + # Parse partitions + print_step(3, 6, "Analyzing partition table") + + partitions = parse_gpt_partitions(dump_path) + if not partitions: + print_error("No partitions found in dump") + return False + + print_info("Partitions found:") + for part in partitions: + size_mb = (part.size_sectors * EMMC_SECTOR_SIZE) / (1024 * 1024) + print(f" {part.number}: sectors {part.start_sector}-{part.end_sector} ({size_mb:.1f} MB) - {part.name}") + + # Find var partition + var_partition = find_var_partition(partitions) + if not var_partition: + print_error("Could not identify /var partition") + return False + + print_success(f"Identified /var partition: partition {var_partition.number}") + + # Extract var partition + if not extract_partition(dump_path, var_partition, var_partition_path): + return False + + # Create backup + shutil.copy(var_partition_path, backup_var_path) + print_info(f"Backup created: {backup_var_path}") + + # Modify partition + if not modify_var_partition(var_partition_path): + return False + + # Check if Jibo still connected (may need to re-enter RCM) + if not args.skip_detection: + print_info("Please ensure Jibo is still in RCM mode") + print_info("If Jibo rebooted, re-enter RCM mode now") + if not wait_for_jibo_rcm(timeout=60): + print_warning("Continuing anyway...") + + # Write modified partition + if not write_partition_to_emmc(var_partition_path, var_partition.start_sector): + return False + + # Verify + if args.verify: + if not verify_write(var_partition_path, var_partition.start_sector, var_partition.size_sectors): + print_warning("Verification failed, but write may still be successful") + + # Done! + print(f""" +{Colors.GREEN}╔═══════════════════════════════════════════════════════════════════╗ +║ {Colors.BOLD}MODDING COMPLETE!{Colors.RESET}{Colors.GREEN} ║ +╚═══════════════════════════════════════════════════════════════════╝{Colors.RESET} + +{Colors.BOLD}Next steps:{Colors.RESET} +1. Unplug Jibo from USB +2. Hold power button until red LED goes off +3. Power on Jibo normally +4. Wait for boot - you should see a checkmark instead of the eye +5. SSH into Jibo: + {Colors.CYAN}ssh root@{Colors.RESET} + Password: {Colors.YELLOW}jibo{Colors.RESET} + +{Colors.BOLD}Your backup is saved at:{Colors.RESET} +{backup_var_path} + +{Colors.YELLOW}Keep this backup safe - it contains your Jibo's calibration data!{Colors.RESET} +""") + + return True + + +def run_dump_only(args) -> bool: + """Only dump the eMMC without modding""" + print_banner() + print_info("Running in dump-only mode") + + WORK_DIR.mkdir(parents=True, exist_ok=True) + + # Build Shofel + if not build_shofel(force_rebuild=args.rebuild_shofel): + return False + + # Wait for Jibo + if not args.skip_detection: + if not wait_for_jibo_rcm(timeout=120): + return False + + output_path = Path(args.output) if args.output else WORK_DIR / "jibo_full_dump.bin" + return dump_emmc(output_path) + + +def run_write_only(args) -> bool: + """Write a pre-modified partition to Jibo""" + print_banner() + print_info("Running in write-only mode") + + partition_path = Path(args.partition) + if not partition_path.exists(): + print_error(f"Partition file not found: {partition_path}") + return False + + # Build Shofel if needed + if not build_shofel(): + return False + + # Wait for Jibo + if not args.skip_detection: + if not wait_for_jibo_rcm(timeout=120): + return False + + return write_partition_to_emmc(partition_path, args.start_sector) + + +# ============================================================================ +# CLI +# ============================================================================ + +def main(): + parser = argparse.ArgumentParser( + description="Jibo Auto-Mod Tool - Automatically enable developer mode on Jibo robots", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s # Run full modding workflow + %(prog)s --dump-only # Only dump eMMC + %(prog)s --write-partition var.bin --start-sector 0x7E9022 + %(prog)s --dump-path existing_dump.bin # Use existing dump + """ + ) + + # Operation modes + mode_group = parser.add_mutually_exclusive_group() + mode_group.add_argument("--dump-only", action="store_true", + help="Only dump the eMMC without modifying") + mode_group.add_argument("--write-partition", metavar="FILE", + help="Write a partition file to Jibo (requires --start-sector)") + + # Options + parser.add_argument("--dump-path", metavar="FILE", + help="Use existing dump file instead of dumping") + parser.add_argument("--output", "-o", metavar="FILE", + help="Output file for dump (default: jibo_work/jibo_full_dump.bin)") + parser.add_argument("--start-sector", type=lambda x: int(x, 0), default=0x7E9022, + help="Start sector for write operation (hex, default: 0x7E9022)") + parser.add_argument("--force-dump", action="store_true", + help="Force re-dump even if dump file exists") + parser.add_argument("--rebuild-shofel", action="store_true", + help="Force rebuild of Shofel exploit") + parser.add_argument("--skip-detection", action="store_true", + help="Skip USB device detection (useful for debugging)") + parser.add_argument("--verify", action="store_true", default=True, + help="Verify write by reading back (default: True)") + parser.add_argument("--no-verify", action="store_false", dest="verify", + help="Skip write verification") + + args = parser.parse_args() + + # Validate arguments + if args.write_partition and not args.start_sector: + parser.error("--write-partition requires --start-sector") + + # Run appropriate mode + try: + if args.dump_only: + success = run_dump_only(args) + elif args.write_partition: + args.partition = args.write_partition + success = run_write_only(args) + else: + success = run_full_mod(args) + + sys.exit(0 if success else 1) + + except KeyboardInterrupt: + print("\n") + print_warning("Operation cancelled by user") + sys.exit(130) + except Exception as e: + print_error(f"Unexpected error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/jibo_automod.sh b/jibo_automod.sh new file mode 100755 index 0000000..2cc2fbd --- /dev/null +++ b/jibo_automod.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Jibo Auto-Mod Tool - Linux/macOS Launcher +# This script checks dependencies and runs the auto-mod tool + +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +echo "" +echo "============================================================" +echo " JIBO AUTO-MOD TOOL - Linux Launcher" +echo "============================================================" +echo "" + +# Check for Python 3 +if ! command -v python3 &> /dev/null; then + echo "[ERROR] Python 3 not found!" + echo " Install with: sudo apt install python3" + exit 1 +fi + +PYVER=$(python3 --version) +echo "[INFO] Found $PYVER" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo "[INFO] Not running as root. sudo will be used when needed." + # Check if we can sudo + if ! sudo -v &> /dev/null; then + echo "[WARNING] Cannot use sudo. USB operations may fail." + fi +fi + +# Check for required tools +check_tool() { + if ! command -v "$1" &> /dev/null; then + echo "[WARNING] $1 not found. Install: $2" + return 1 + fi + return 0 +} + +MISSING=0 +check_tool "lsusb" "sudo apt install usbutils" || MISSING=1 +check_tool "make" "sudo apt install build-essential" || MISSING=1 +check_tool "gcc" "sudo apt install build-essential" || MISSING=1 +check_tool "arm-none-eabi-gcc" "sudo apt install gcc-arm-none-eabi" || MISSING=1 + +if [ $MISSING -eq 1 ]; then + echo "" + echo "[WARNING] Some dependencies are missing. The tool will try to continue" + echo " but some features may not work." + echo "" +fi + +# Run the tool +echo "[INFO] Starting Jibo Auto-Mod Tool..." +echo "" + +python3 jibo_automod.py "$@" diff --git a/windows_setup.bat b/windows_setup.bat new file mode 100644 index 0000000..9094910 --- /dev/null +++ b/windows_setup.bat @@ -0,0 +1,127 @@ +@echo off +setlocal enabledelayedexpansion + +echo. +echo ============================================================ +echo JIBO AUTO-MOD - Windows Development Environment Setup +echo ============================================================ +echo. + +:: Check if running as admin +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo [WARNING] Not running as Administrator. + echo Some installations may require admin rights. + echo. +) + +echo This script will help you set up the development environment +echo for building and running the Jibo Auto-Mod tool on Windows. +echo. + +:: Check for Python +echo [1/4] Checking Python installation... +where python >nul 2>&1 +if %errorlevel% neq 0 ( + echo [ERROR] Python not found! + echo. + echo Please install Python 3.8+ from: + echo https://www.python.org/downloads/ + echo. + echo IMPORTANT: Check "Add Python to PATH" during installation! + echo. + echo After installing Python, run this script again. + pause + exit /b 1 +) else ( + for /f "tokens=2 delims= " %%a in ('python --version 2^>^&1') do set PYVER=%%a + echo [OK] Python !PYVER! found +) + +:: Check for MSYS2 +echo. +echo [2/4] Checking MSYS2 installation... +if exist "C:\msys64\usr\bin\bash.exe" ( + echo [OK] MSYS2 found at C:\msys64 + set MSYS2_PATH=C:\msys64 +) else if exist "C:\msys32\usr\bin\bash.exe" ( + echo [OK] MSYS2 found at C:\msys32 + set MSYS2_PATH=C:\msys32 +) else ( + echo [ERROR] MSYS2 not found! + echo. + echo Please install MSYS2 from: + echo https://www.msys2.org/ + echo. + echo After installing: + echo 1. Open "MSYS2 MINGW64" from Start Menu + echo 2. Run: pacman -Syu + echo 3. Run this script again + pause + exit /b 1 +) + +:: Install MSYS2 packages +echo. +echo [3/4] Installing required packages via MSYS2... +echo. +echo This will install: gcc, make, libusb, arm-none-eabi-gcc +echo. + +set MSYS2_BASH=%MSYS2_PATH%\usr\bin\bash.exe + +:: Create a temporary script for MSYS2 +set TEMP_SCRIPT=%TEMP%\jibo_setup.sh +echo #!/bin/bash > "%TEMP_SCRIPT%" +echo echo "Updating package database..." >> "%TEMP_SCRIPT%" +echo pacman -Sy --noconfirm >> "%TEMP_SCRIPT%" +echo echo "Installing MinGW toolchain..." >> "%TEMP_SCRIPT%" +echo pacman -S --noconfirm --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-make >> "%TEMP_SCRIPT%" +echo echo "Installing libusb..." >> "%TEMP_SCRIPT%" +echo pacman -S --noconfirm --needed mingw-w64-x86_64-libusb >> "%TEMP_SCRIPT%" +echo echo "Installing ARM toolchain..." >> "%TEMP_SCRIPT%" +echo pacman -S --noconfirm --needed mingw-w64-x86_64-arm-none-eabi-gcc >> "%TEMP_SCRIPT%" +echo echo "Done!" >> "%TEMP_SCRIPT%" + +"%MSYS2_BASH%" -l -c "source '%TEMP_SCRIPT%'" +del "%TEMP_SCRIPT%" + +:: Install Zadig info +echo. +echo [4/4] USB Driver Setup (Zadig)... +echo. +echo To communicate with Jibo in RCM mode, you need the WinUSB driver. +echo. +echo Steps: +echo 1. Download Zadig from: https://zadig.akeo.ie/ +echo 2. Put Jibo in RCM mode (hold RCM + press power) +echo 3. Run Zadig +echo 4. Options ^> List All Devices +echo 5. Select "APX" from the dropdown +echo 6. Select "WinUSB" as the target driver +echo 7. Click "Replace Driver" +echo. + +:: Final instructions +echo. +echo ============================================================ +echo SETUP COMPLETE +echo ============================================================ +echo. +echo To build Shofel and run the mod tool: +echo. +echo 1. Open "MSYS2 MINGW64" from Start Menu +echo 2. Navigate to this folder: +echo cd /c/path/to/JiboAutoMod +echo 3. Build Shofel: +echo cd Shofel ^&^& make +echo 4. Run the mod tool: +echo python ../jibo_automod.py +echo. +echo Or use the batch launcher: +echo jibo_automod.bat +echo. +echo NOTE: Some USB operations may require running as Administrator. +echo. +pause +endlocal