# AGENTS.md — DWARFLAB APK reverse-engineering workspace This directory reverse-engineers the DWARFLAB Android app to document the DWARF II telescope control protocol for an open-source client. ## What's here ``` DWARFLAB.apk original APK (v3.4.0, com.convergence.dwarflab) tools/jadx/ jadx 1.5.6 decompiler (run tools/jadx/bin/jadx) extracted/raw/ unzip of the APK (dex, assets, lib/*.so, manifest) extracted/jadx/ jadx decompiled Java sources (23968 classes) analysis/ extract_protos.py regenerates clean .proto from jadx *Proto.java protos/ 17 recovered .proto files (382 messages) ← THE API CMD_TABLE.md all 323 command ids ↔ modules (auto-generated) API_REFERENCE.md full protocol spec (read this first) ``` **Start with `analysis/API_REFERENCE.md`** — it explains BLE discovery → WebSocket control (port 9900) → RTSP preview, the `WsPacket` envelope, and how command routing works. ## The protocol in one paragraph Phone discovers telescope over **BLE** (`ble.proto`, gets IP/SSID/PSK via `DwarfEcho`), joins its Wi-Fi, opens `ws://:9900/?client_id=`, then sends **binary protobuf `WsPacket`** frames `{major=2, minor=3, device_id, module_id, cmd, type, data, client_id}`. `cmd` (10000–17099) selects the operation; `module_id` is *derived* from `cmd` by range; `data` is a serialized inner proto (`Req*`/`Res*`). Live preview is a separate **RTSP** stream (`stream0`). ## Commands for working here ```bash # Re-decompile (only if APK changes) tools/jadx/bin/jadx --show-bad-code --deobf -d extracted/jadx DWARFLAB.apk # Regenerate .proto files after re-decompile python3 analysis/extract_protos.py \ extracted/jadx/sources/com/convergence/dwarflab/proto analysis/protos # Regenerate the command table python3 -c "…" # see the inline script that built analysis/CMD_TABLE.md ``` Both extractors are deterministic — re-running after a new APK version produces a clean diff for tracking protocol changes. ## Where things live in the decompiled source All paths relative to `extracted/jadx/sources/`. | Looking for… | Go to… | |---|---| | Protobuf message definitions | `com/convergence/dwarflab/proto/*Proto.java` → run extractor → `analysis/protos/` | | All command IDs | `com/convergence/dwarflab/data/bean/p021ws/WsCmd.java` | | Module enum (camera/astro/motor…) | `…/p021ws/WsModuleId.java` | | Message type enum (req/resp/notify) | `…/p021ws/WsMessageType.java` | | Request interface + sender | `…/p021ws/request/WsMessageReq.java`, `p000/d49.java` | | WsPacket envelope builder | `p000/e49.java` (`C9312a.m36361a`) | | WebSocket connection / URL / port | `p000/v55.java` (field `f43155b`, method `m55420t`) | | OkHttp WebSocket wrapper | `p000/b49.java` | | Inbound dispatch / req-resp matching | `com/convergence/dwarflab/data/websocket/WsRequestHandle.java`, `WsResponseHandle.java` | | Per-module handlers | `com/convergence/dwarflab/data/websocket//*WsResponseHandle.java` | | RTSP preview player | `com/convergence/dwarflab/media/RtspPlayerView.java`, `…/activity/capture/CaptureActivity.java` | | BLE handshake | `com/convergence/dwarflab/data/bluetooth/`, `…/data/bean/ble/` | | Camera param value maps | APK `assets/params_range.json`, `assets/shoot_plan_config.json` | ## Gotchas - **`p000/` is the obfuscated package.** R8 minification collapsed most app internals into 1–4 char names there (`v55`, `b49`, `e49`, `d49`, `jc0`…). Kotlin metadata comments (`@Metadata(d2={…})`) often still carry the original readable names — read them when a class is opaque. - **`module_id` is never stored** on a command — it is computed by range checks in `WsCmd.getModuleId()`. Don't grep for "set module id"; read that method. - **Some command IDs appear as symbolic constants** in `WsCmd.java` (jadx didn't inline them). Resolved values: `IMediaPlayer.MEDIA_INFO_*` = 10004/10006/10008/10009, `RequestManager.NOTIFY_CONNECT_*` = 10011/10012/10013, `ComposeVersion.version` = 13000, `FirebaseError.ERROR_INVALID_CUSTOM_TOKEN` = 17000, `ERROR_CUSTOM_TOKEN_MISMATCH` = 17002. These are already resolved in `CMD_TABLE.md`. - **Notifications outnumber requests.** ~130 of the 323 cmds are `CMD_NOTIFY_*` (15200–15303). They are server-pushed (`type=2`), not requestable. An OSS client must subscribe to them for live state. - **Two telescopes share one app.** "DWARF II" (current, id 1) and "Bilbo" (codename for the next model, likely DWARF III) appear in `shoot_plan_config.json` and `www/modules/eq/indexBilbo.html`. Capability detection should key off `StationModel` from `DwarfEcho`, not hardcode. - **`type` field (WsMessageType):** request=0, response=1, notification=2, reply=3. Responses usually reuse the request's `cmd`; match on `cmd`. - **RTSP uses TCP.** The app sets `rtsp_transport=tcp` (`RtspPlayerView.java:195`). Path is `rtsp:////stream0`. - **jadx reports ~294 decompile errors** out of ~24k classes — expected and irrelevant for the proto/command extraction (those come from clean descriptor data, not decompiled bodies). ## Conventions when extending this analysis - Keep generated artifacts regenerable: `extract_protos.py` + the CMD_TABLE inline script read only from `extracted/jadx`. Never hand-edit `protos/` or `CMD_TABLE.md`. - When a command's payload type is unclear, find the `WsMessageReq` subclass in `data/bean/p021ws/request/` — `getMessage()` returns the proto, `getCmd()` returns the `WsCmd`. - Cloud/relay features (`app.dwarflabapp.com/uls`, NetEase IM `nim/`, Huawei HMS, Firebase analytics, Meizu push, ByteDance ASR) are NOT part of telescope control — ignore them unless working on remote/cloud access.