g2flash.py flashes firmware onto Even Realities G2 smart glasses by
reimplementing the official app's BLE flash protocol. It is the tool used to
push custom firmware (a patched *_cfw.bin image) onto the device.
WARNING — this voids your warranty and can brick the glasses. Flashing custom firmware over the OTA path carries a real risk of bricking the device. The tool makes you type
my warranty is voidat an interactive prompt before it will write anything (use--my-warranty-is-voidto skip the prompt for automation). Only proceed if you understand and accept the risk.
cd g2flash
./build_cfw.sh # set up venv, download stock fw, patch, verify
./venv/bin/python g2flash.py -c g2://local -f g2_2.2.4.34_cfw.binbuild_cfw.sh does the whole build: it creates ./venv with the flasher's
dependencies, downloads the stock G2 2.2.4.34 firmware from Even's CDN,
applies the patches in patches/, and verifies that both the download and the
patched result match pinned SHA-256 hashes (so a clean run proves you got
exactly the reviewed image). Run ./build_cfw.sh --help for options
(--skip-venv, --force-download). Then flash as shown above — see
Usage for connection strings and safety flags.
The patches in patches/ add image/display features on top of stock 2.2.4.34:
- 576×288 image containers (stock caps at 288×144).
- zlib-compressed image payloads and 8bpp XOR-delta frame updates, for much faster image/video streaming.
- Per-lens stereo image pairs.
- A capability-advertisement field on the settings response, so a connected app can detect this firmware and which features it supports.
g2flash.py— the flasher.build_cfw.sh— one-shot venv setup + download + patch + verify (see above).patches/— the patch sources and tools:patch_compress.py— the all-in-one patcher (576 lift + image compression- stereo + capability field) that
build_cfw.shruns.
- stereo + capability field) that
patch_img_container_576.py— standalone tool for just the 576×288 lift.build.py,*.c— the C→position-independent-Thumb pipeline and sources for the injected firmware code (the machine code is embedded inpatch_compress.py; these are kept for review/rebuild).
requirements.txt— the flasher's Python dependencies.
Firmware images (g2_2.2.4.34*.bin) are not checked in — they are Even's
firmware, so you build them locally with build_cfw.sh.
- Python 3.x (developed against the Homebrew
python@3.14build). - One of two transports to reach the glasses:
- local — this machine's own Bluetooth radio, via the
bleakpackage. - droidbridge — a bonded Android phone running
DroidBridge that forwards GATT over HTTP/WebSocket; uses
the
websocket-clientpackage.
- local — this machine's own Bluetooth radio, via the
Third-party Python dependencies:
| Package | Needed for | Imported as |
|---|---|---|
bleak |
g2://local transport |
bleak |
websocket-client |
g2://droidbridge transport |
websocket |
Both are imported lazily, so you only need to install the one for the transport
you actually use. Firmware parsing, validation, and --recompute-checksums run
on the standard library alone.
build_cfw.sh creates and populates ./venv for you as part of a normal run.
To set it up by hand instead:
cd g2flash
python3 -m venv venv
./venv/bin/python -m pip install --upgrade pip
./venv/bin/python -m pip install -r requirements.txtWith the venv activated (source venv/bin/activate) you can invoke the tool as
python g2flash.py ...; otherwise use ./venv/bin/python g2flash.py .... Run
deactivate to leave the environment.
On macOS, bleak talks to CoreBluetooth, which never exposes BLE MAC
addresses — scanned addresses are random per-host UUIDs. g2flash works around
this by scanning and matching the last three MAC bytes embedded in the arm's
advertised name (Even G2_32_L_693CCB). For a local flash the arm must be
powered on and not connected to the phone (quit the Even app / turn off the
phone's Bluetooth) so it advertises for a direct connection. The first time you
run it, macOS will prompt to grant your terminal Bluetooth permission.
python g2flash.py -c <connection-string> -f <firmware.bin> [options]
Connection strings:
# direct from this machine's Bluetooth radio (needs bleak)
g2://local?left=<addr>&right=<addr>&addressType=public|random
# through a bonded phone running DroidBridge (needs websocket-client)
g2://droidbridge?phone=<host>&port=<port>&token=<tok>&left=<mac>&right=<mac>
addressType=public is a normal MAC (D0:7A:47:82:09:67); random is the
macOS/CoreBluetooth peripheral-UUID style.
Common options:
--lens left|right|both— which arm to flash (defaultboth).--stop-before discover|heartbeat|file_check|flash|done— dry-run gate that halts before the named stage; use it to test connectivity without writing.--my-warranty-is-void— skip the interactive warranty confirmation.--component-retries N/--block-nak-retries N— transfer retry tuning.--debug— print received BLE frames.
--recompute-checksums IMAGE rewrites an image's stored checksums in place
(component CRC32C + mainApp preamble CRC32) to match its current payloads and
exits without connecting. Run it after any length-preserving binary patch —
otherwise the glasses reject the component on END with status 7 (CHECK_FAIL).
# dry run: connect to both arms over the local radio and stop before any write
python g2flash.py \
-c 'g2://local?left=AA:BB:CC:11:22:33&right=AA:BB:CC:44:55:66&addressType=public' \
-f g2_2.2.4.34.bin --stop-before flash
# fix checksums after patching, no device needed
python g2flash.py --recompute-checksums g2_2.2.4.34_cfw.bin
# flash the custom firmware to both arms via DroidBridge
python g2flash.py \
-c 'g2://droidbridge?phone=192.168.1.50&port=8080&token=secret&left=AA:BB:CC:11:22:33&right=AA:BB:CC:44:55:66' \
-f g2_2.2.4.34_cfw.binThe flasher speaks the same aa21-framed envelope protocol as the official
app, validated byte-for-byte against a real flash capture. The firmware image
is an EVENOTA container of five components; each is streamed over the firmware
data service (...e1001) as a FILE_CHECK subheader followed by 4 KB blocks,
then an END check the glasses verify against a per-component CRC32C. A heartbeat
on the EvenHub control service (...e5450) keeps the session alive during the
transfer. Arms are flashed one at a time. See the module docstring and comments
in g2flash.py for the wire-level details and the retry/recovery rationale.