Build the IDA databases for BDS (Windows + Linux)
Downloads the BDS server binary for both platforms and runs idat.exe headless
on each to produce an IDA database (.i64). Both analyses are long-running
(hours on the ~170 MB binary, ~5 GB output each), so they run in the
background and this skill reports progress as it goes.
The resulting .i64 is the input the update-signatures and bump-bds skills
consume (e.g. ../bedrock-symbols/win32_server_x64/<version>/bedrock_server.exe.i64).
Conventions (paths)
All commands and paths below are relative to the repo root (the working directory) and use POSIX forward slashes; run the skill from the repo root so the relative paths resolve.
| What | Where |
|---|---|
idat.exe (headless IDA) |
discovered at launch - see step 0 (not hardcoded) |
| BDS zip cache | ~/.bedrock_server/<platform>/bedrock-server-<ver>.zip |
| DB output (windows) | ../bedrock-symbols/win32_server_x64/<version>/bedrock_server.exe (+ .i64) |
| DB output (linux) | ../bedrock-symbols/linux_server_x64/<version>/bedrock_server (+ .i64) |
| Skill helpers | .claude/skills/build-ida-db/{fetch_bds.py,make_idb.py} |
<version> is the 3-component release (e.g. 1.26.30), read by default from
scripts/configs/<platform>.toml's version key - the same value bump-bds
sets. The bedrock-symbols tree sits beside the repo (../bedrock-symbols),
not inside it. fetch_bds.py prints the extracted binary's path repo-relative
with forward slashes, so the idat command lines stay relative too.
Prerequisites
- IDA Professional 9.x installed (single
idat.exehandles both 32/64-bit and writes.i64; there is no separateidat64.exein IDA 9). Its path is found at launch (step 0), not hardcoded. uvfor the helper scripts (PEP 723 inline deps, no manual install).- The target version must exist in
EndstoneMC/bedrock-server-data(fetch_bds.pyreads itsrelease/<version>/metadata.json). The zips for the current target are usually already cached under~/.bedrock_server/. - Disk + RAM: each analysis writes a ~5 GB
.i64plus ~4 GB of unpacked.id0/.id1/...during the run, and holds the binary in memory. Two in parallel is fine on a roomy machine; if RAM/disk is tight, run them sequentially (launch linux only after windows finishes).
Procedure
Drive this as small, reported steps - the user wants visible progress, not one silent multi-hour block.
0. Locate idat.exe (common install locations)
IDA's install path varies by edition/version, so discover it instead of
hardcoding. List every idat.exe in the usual roots (plus one on PATH):
shopt -s nullglob
printf '%s\n' \
"/c/Program Files/"IDA*/idat.exe \
"/c/Program Files (x86)/"IDA*/idat.exe \
"$HOME/AppData/Local/Programs/"IDA*/idat.exe | sort -uV
command -v idat 2>/dev/null # also report one on PATH, if any
printf + shell globbing (not ls) keeps the paths clean - an aliased ls -F
would append a * classifier and break them. Then pick based on the count:
- No candidates -> IDA is not in a common location; ask the user for the
idat.exepath (or check other drives / a portable install). - Exactly one -> use it.
- More than one (e.g.
IDA Professional 9.0and9.1side by side) -> ask the user which install to use (via AskUserQuestion; list the discovered paths as options) - do not silently auto-pick a version.
Carry the chosen idat.exe path into step 4 by substituting it literally
(quoted - the path has spaces); shell state does not persist between tool calls.
1. Resolve the version
Read version from scripts/configs/windows.toml (and confirm linux.toml
matches). Override with an explicit version only if the user names one.
2. Fetch + extract both binaries
uv run --script .claude/skills/build-ida-db/fetch_bds.py --platform windows
uv run --script .claude/skills/build-ida-db/fetch_bds.py --platform linux
Each reuses the cached zip when its SHA256 matches, else downloads it, then
extracts the server binary into ../bedrock-symbols/<arch>/<version>/. The
last stdout line is the extracted binary path (repo-relative, forward
slashes) - capture it for the next step. These are quick (seconds when cached);
run them in the foreground.
3. (Optional) named database - drop the PDB/symbols beside the binary first
A bare analysis of the stripped server binary yields a database with no
function names - usable for raw disassembly but a non-starter for
update-signatures (which resolves symbols by mangled name). To get the named
DB those skills need, place the matching debug artifact next to the extracted
binary before step 4 so IDA auto-applies it during analysis:
- Windows:
bedrock_server.pdbbesidebedrock_server.exe. - Linux: the server binary is stripped; there is no public symbol file, so the linux DB stays nameless (this matches how the toolchain already treats it).
If you have the matching bedrock_server.pdb, drop it beside the binary first -
step 4 detects it automatically and leaves the PDB provider on so names load.
Keep the PDB and its contents out of the repo. If no PDB is available, proceed
without it (step 4 adds -Opdb:off) and note the DB will be unnamed.
4. Launch idat headless in the background, one per platform
For each platform, launch with the Bash tool's run_in_background: true so
the harness tracks it and notifies on completion. Use the idat.exe path chosen
in step 0 (quote it - the path has spaces) and point -L at a log file beside
the binary:
Decide -Opdb:off by whether a PDB sits beside the binary - add it only
when there is no PDB; with a PDB present it must stay off-the-command-line so
IDA loads the PDB for names. The launch snippet checks and sets it itself:
# <IDAT> = idat.exe chosen in step 0; <BIN> = path printed by fetch_bds.py; <DIR> = its parent
shopt -s nullglob
pdbs=("<DIR>"/*.pdb)
PDBOFF="-Opdb:off"; [ ${#pdbs[@]} -gt 0 ] && PDBOFF="" # PDB present -> let IDA load it
"<IDAT>" -A -c $PDBOFF -L"<DIR>/idat.log" -S".claude/skills/build-ida-db/make_idb.py" "<BIN>"
-Aautonomous (no dialogs),-ccreate a fresh DB (overwrites any existing one),-Srunsmake_idb.pywhich waits for auto-analysis, saves, and quits,-Lcaptures the message log for progress. This avoids the giant.asmbyproduct thatidat -Bwould produce.- Why the
-Opdb:offcheck matters. With no PDB beside the binary, the PE's embedded PDB reference makes IDA probemsdl.microsoft.com, 404, then throw a blocking "Do you want to browse for the pdb file on disk?" prompt that hangs headlessidatforever (-Aauto-answers Yes -> a file dialog with no stdin -> stuck).-Opdb:offturns the PDB provider off so this never happens. With a PDB present,-Opdb:offwould also skip the local PDB (provider off = loads nothing), so it is omitted; the provider then finds the local PDB first and never reaches the network/browse fallback. (A bare ELF - the linux binary - has no PDB reference, so the flag is a harmless no-op there; the check simply adds it.) - Launch windows and linux as two separate background jobs (or sequentially if RAM/disk is tight - see Prerequisites).
5. Report progress while they run
Don't poll on a timer - when a background job exits, the harness re-invokes you.
Between events, check progress on demand with cheap reads (don't open the .i64
in the idalib MCP while idat holds it):
- CPU delta - the reliable "alive and working" signal. Sample each idat
PID's
TotalProcessorTime~8 s apart; an active analysis adds roughly a full core's worth (e.g. +8 s over 8 s). A near-zero increase means it stalled (e.g. blocked on a prompt - see the PDB gotcha), not that it's done. - Log tail - phase lines (
Reading ... directory,Parsing .pdata ...) plusmake_idb.py's own[make_idb] ...markers. The linux run reaches[make_idb] load complete; waiting for auto-analysis...quickly. Note-Lappends across reruns, so read from the lastLoading fileheader. A benignSyntax error near: <END>around import/ws2_32type loading is IDA noise, not a failure. - Do NOT use
.id1disk size for live progress. IDA memory-maps the b-tree and only flushes on save, so a perfectly healthy run shows a static.id1for long stretches - it looks identical to a hang. Use CPU + the log instead.
Completion = the process exits 0, the log ends with [make_idb] saved; exiting.,
and a finalized .i64 exists (the unpacked .id* may remain). Report per
platform: queued -> analyzing (latest log phase + CPU climbing) -> saved.
6. Completion
A platform is done when its idat process exits 0, idat.log ends with
[make_idb] saved; exiting., and bedrock_server[.exe].i64 exists in the dest
dir. Report the two final .i64 paths and sizes. If a run exits non-zero or the
log stops mid-phase, surface the tail of idat.log - don't claim success.
Gotchas
idat.exe, notida.exe-idatis the text-mode/headless launcher;ida.exeis the GUI and will pop a window. IDA 9 unified the bitness, soidat.exeproduces.i64for the 64-bit server binaries automatically.- A nameless DB is expected without a PDB. The server binaries are stripped. Step 3 is what makes the Windows DB useful downstream; skip it only if the user just wants raw disassembly.
- Don't open the new
.i64in the idalib MCP while idat is writing it, and don't run two idat processes against the same DB path. The Windows DB path collides with whatever the idalib MCP may already have open from a priorupdate-signaturesrun - if-cfails to delete the old DB, an idalib worker is likely holding it; close/unbind it first (see theupdate-signaturesskill's dirty-DB recovery). - It is genuinely slow. Full auto-analysis of a 170 MB binary is multi-hour
and the
.i64exceeds 5 GB. This is why it runs in the background; set expectations with the user up front. - PDB browse-prompt hang (Windows) - the big one. The PE embeds a PDB
reference, so without
-Opdb:offIDA probesmsdl.microsoft.com, 404s (Mojang PDBs aren't published there), then throws a blocking "browse for the pdb file on disk?" prompt. Headlessidat -Aauto-answers Yes and opens a file dialog that waits on stdin forever - the process hangs with CPU at ~0 (the tell: a log ending at that prompt and no CPU progress; a frozen.id1is not the tell - healthy runs freeze it too). The fix is-Opdb:off(provider off), percfg/pdb.cfg's documented-Opdb:off|msdia|pdbidaswitch._NT_SYMBOL_PATH=empty only suppresses the network leg, not the browse prompt - use-Opdb:off. With a real local PDB staged beside the binary, the provider finds it first and never reaches the prompt, so leave it on. - Re-runs:
-crecreates from scratch. To resume/keep an existing DB instead, drop-cand point idat at the.i64rather than the binary.
Record findings here
Append durable, tooling-level lessons (idat flag quirks, analysis-time figures, new platform dirs) to the Gotchas. Keep per-version specifics in commits.