Skip to content
Hydra Engine

Projects, views and revisions

Read-only views that take no lock, writable projects shared inside the daemon, revisions and refresh, and what happens to projects saved by an older Ghidra.

On this page

The daemon opens Ghidra projects where they are, by their .gpr, their folder, or a parent folder with exactly one project at most two levels down. It never copies a project to read it.

A read-only view takes no lock

By default open_project opens a project the way Ghidra's File > View Project does: no lock file, nothing written, not even a timestamp. Any number of clients, other daemons and a Ghidra window can read the same project at once.

p = s.call("open_project", path=r"C:\re\Demo.gpr")   # p["id"] == "p1"

The answer's id (p1, p2, ...) names the project while it is open. A program's key is the project id plus its path inside the project, for example p1:/libs/version.dll, and keys are unique across projects. The last project and program a client opened are its active ones; commands without a project or program field act on those.

A writable project is shared inside the daemon

s.call("open_project", path=r"C:\re\Demo.gpr", writable=True)

This takes Ghidra's project lock, as Open Project does. Only one process can hold it, so if a Ghidra window or analyzeHeadless already has the project open, you get conflict naming the lock file and its holder, with suggest: "open_read_only".

Inside the daemon there is one writable instance per project, and every client that asks for it joins it. If other clients were reading the project through a read-only view, the daemon reopens it writable in place: same id, same keys, their programs reopened read-only from the writable project. With a reader using a 243,855-function program, that switch took 130 to 152 ms and the reader's next decompile 246 to 259 ms, the cost of a new decompiler.

Within a writable project, each program has one owner by default: the client that opened it writable. Others may read it, and they see the owner's unsaved changes. To write alongside the owner, see Several writers.

Revisions, stale views and refresh

on main · 12.3-hydra.2 Every project and program has a revision: a 16-hex-digit fingerprint of what is saved (file names, sizes and times, plus a hash of the small files). Two daemons, and a copy that kept its files' times, agree on it.

A read-only view shows the project as it was when the view opened. When another process saves the project, every answer to that view's clients carries a stale entry next to result:

{"id": 12, "ok": true, "result": {...}, "ms": 3,
 "stale": [{"project": "p1", "revision": "5b0c9e2d81f4a7c3", "diskRevision": "e1d24f6b9a03c8d7", "suggest": "refresh"}]}

Answers still come from the old revision until someone sends refresh:

s.call("refresh", project="p1")

It opens the new view first, then switches every client's programs to it under the project's exclusive lock, so nobody sees half of the switch and nobody has to leave. Keys stay the same. Other clients learn about it once, through a refreshed entry on their next answer, so they know anything they cached came from the older revision. A writable project never needs a refresh: the daemon holds its lock, so it always shows what is saved.

In Python, call_raw returns the whole response, and last_response holds the last one.

Projects saved by an older Ghidra

Hydra Engine is built on upstream Ghidra master (12.3 DEV), whose program database is one version ahead of Ghidra 12.1.4. A project 12.1.4 saved still opens read-only, untouched, but each open upgrades the program in memory first. For a 66.7 MB executable with 243,855 functions that took 21 to 24 s instead of about 1 s.

The upgrade cache removes most of that wait:

  1. The first open upgrades in memory, as before, and answers with "cacheBuild": "started".
  2. In the background, the daemon copies the project into its state directory (a block clone on ReFS), upgrades the copy and saves it. It takes a long-lane slot only while no client is waiting for one.
  3. Later opens read the upgraded copy ("cachedUpgrade": true). The original project is never written.

open_program with "wait": true waits for the copy instead of upgrading in memory. Turn the cache off with "upgradeCache": false in config.json.

The engine repository keeps a legacy-12.1.4 branch with the same module on Ghidra 12.1.4 for projects that must stay on that version. It predates most of these pages: no upgrade cache, no forks or overlays, no version contract.

What a project view does not do

  • It does not create projects. Make one with analyzeHeadless, as in the quick start, or with a script that calls GhidraProject.createProject.
  • A read-only view does not pick up another process's saves by itself; it reports them as stale until a client sends refresh.
  • Revisions compare saved files, not meaning: any save changes them.

Checked against the engine on 2026-10-26. Every detail is in the README on GitHub.