Skip to content

Commit 0616171

Browse files
authored
Merge pull request #16 from buzzer-re/dev
Prepare 0.2.0 for PyPI
2 parents 046f173 + 123cab4 commit 0616171

4 files changed

Lines changed: 95 additions & 16 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Publish to PyPI
2+
3+
# Runs when a version tag is pushed, e.g. `git tag v0.2.1 && git push origin v0.2.1`.
4+
# The tag must match __version__ in src/tocode/__init__.py.
5+
on:
6+
push:
7+
tags:
8+
- "v*"
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build:
15+
name: Test and build
16+
runs-on: ubuntu-24.04
17+
steps:
18+
# actions/checkout@v4
19+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
20+
21+
- name: Install uv
22+
# astral-sh/setup-uv@v6
23+
uses: astral-sh/setup-uv@d0d8abe699bfb85fec6de9f7adb5ae17292296ff
24+
25+
- name: Check the tag matches the package version
26+
run: |
27+
version="$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/tocode/__init__.py)"
28+
if [[ "${GITHUB_REF_NAME}" != "v${version}" ]]; then
29+
echo "::error::tag ${GITHUB_REF_NAME} does not match __version__ ${version} (expected tag v${version})"
30+
exit 1
31+
fi
32+
echo "Publishing tocode-cli ${version}"
33+
34+
- name: Run tests
35+
run: uv run --locked --extra dev pytest -q
36+
37+
- name: Build sdist and wheel
38+
run: uv build
39+
40+
- name: Check the distributions
41+
run: uvx twine==6.2.0 check --strict dist/*
42+
43+
# actions/upload-artifact@v4
44+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
45+
with:
46+
name: dist
47+
path: dist/
48+
if-no-files-found: error
49+
50+
publish:
51+
name: Publish
52+
needs: build
53+
runs-on: ubuntu-24.04
54+
environment:
55+
name: pypi
56+
url: https://pypi.org/project/tocode-cli/
57+
permissions:
58+
id-token: write # PyPI trusted publishing (no API token stored)
59+
steps:
60+
# actions/download-artifact@v4
61+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
62+
with:
63+
name: dist
64+
path: dist/
65+
66+
# pypa/gh-action-pypi-publish@release/v1
67+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33

‎README.md‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,16 @@ Other disassemblers may be added in the future.
112112

113113
### .NET assemblies
114114

115-
`tocode App.dll` (also `.exe`, an apphost launcher next to its `.dll`, single-file bundles, and `.nupkg` packages) uses a built-in .NET backend: [dnlib](https://github.com/0xd4d/dnlib) for metadata and [ICSharpCode.Decompiler](https://github.com/icsharpcode/ILSpy) (ILSpy's engine) for C#, loaded through [pythonnet](https://pypi.org/project/pythonnet/). No dnSpy/ILSpy install is needed, only a **.NET 9+ runtime** (e.g. `sudo apt install dotnet-runtime-10.0`, or the SDK; `DOTNET_ROOT` is honoured).
115+
ToCode decompiles .NET assemblies (`.dll`, `.exe`), single-file bundles, apphost launchers, and NuGet packages to C#, and writes the IL of each type next to it. You need a .NET 9 or newer runtime.
116116

117-
ToCode does not ship these two libraries (both MIT). The first .NET export asks once whether to download them from nuget.org and remembers the answer. Non-interactive runs (agents, CI) are never prompted: run `tocode --setup-dotnet` once instead, which downloads without asking, or re-verifies and repairs an existing install. Each download is checked against SHA-256 hashes pinned in `src/tocode/backends/dotnet_libs.py`, for both the `.nupkg` and the extracted DLL, and the files are re-hashed after being written. They are stored per user in `~/.local/share/tocode/dotnet` (macOS: `~/Library/Application Support/tocode/dotnet`, Windows: `%LOCALAPPDATA%\tocode\dotnet`, override: `TOCODE_DOTNET_LIB_DIR`). Delete that folder to undo the install or reset the remembered answer.
117+
Decompilation uses [dnlib](https://github.com/0xd4d/dnlib) and [ICSharpCode.Decompiler](https://github.com/icsharpcode/ILSpy), which ToCode doesn't include. The first .NET export asks once whether to download them from nuget.org and checks them against pinned SHA-256 hashes. Scripts and agents can't answer that prompt, so run `tocode --setup-dotnet` beforehand. The files are stored per user (`~/.local/share/tocode/dotnet` on Linux, or `TOCODE_DOTNET_LIB_DIR`); delete that folder to reset.
118118

119-
- `src/raw/<Assembly>/<Namespace>/.../<Type>.cs`: C# per top-level type, folders follow assemblies and namespaces like a dnSpy/ILSpy project export; `<Type>.il` next to it keeps the bytecode (IL with RVA, raw bytes, and metadata tokens per instruction).
120-
- Per-method C# and IL line ranges in `functions.json`/`function-index.json` (address `<Assembly>!0x<token>`), with lambdas and async/iterator state machines linked to the method that owns them.
121-
- `assemblies.json`, `types.json`, `strings.json` (user strings with `ldstr` xrefs), `imports.json` (cross-assembly members and P/Invoke), `exports.json`, `reachable.json`, `namespace-graph.json`, `resources.json`, `container.json`, `triage.json` (P/Invoke, suspicious API families, obfuscation hints, strings of interest).
122-
- Native code goes through the regular native backends on a background thread, like APK `.so` files: mixed-mode (C++/CLI) assemblies, native libraries inside bundles/packages, and P/Invoke targets shipped next to the input. `--no-native` skips it.
123-
- Single-file bundles and packages carry the .NET runtime/framework; those assemblies and runtime native libraries are listed but only decompiled with `--include-framework`. NuGet packages decompile each assembly once, from its newest target framework.
124-
- `--as-native` exports a managed PE with the native backend instead (e.g. to look at a ReadyToRun or mixed-mode image in IDA).
119+
Native code in the program (mixed-mode assemblies, bundled libraries, P/Invoke libraries next to the input) goes to the native backend unless you pass `--no-native`. Bundles and packages carry the .NET runtime itself, which is skipped unless you add `--include-framework`. `--as-native` treats a .NET file as a plain PE.
125120

126121
```bash
127-
tocode App.dll # assembly (+ P/Invoke libraries next to it)
128-
tocode publish/App # single-file bundle or apphost launcher
129-
tocode Vendor.Lib.1.0.0.nupkg # NuGet package
122+
tocode App.dll
123+
tocode publish/App # single-file bundle or apphost
124+
tocode Vendor.Lib.1.0.0.nupkg
130125
```
131126

132127
### Android APKs
@@ -151,7 +146,16 @@ tocode app.apks --no-native -j 4 # bundle, DEX/Android side only
151146

152147
ToCode supports Windows, Linux, and macOS with Python 3.10 or newer.
153148

154-
On Windows PowerShell:
149+
Install it from PyPI:
150+
151+
```bash
152+
pip install tocode-cli # or: uv tool install tocode-cli
153+
pip install "tocode-cli[angr]" # also install the angr fallback backend
154+
```
155+
156+
The command is `tocode`. You still need a backend (IDA, radare2, angr, or Binary Ninja); see [Supported backends](https://github.com/buzzer-re/ToCode#supported-backends).
157+
158+
To install from a clone of the repository instead, on Windows PowerShell:
155159

156160
```powershell
157161
powershell -ExecutionPolicy Bypass -File .\install.ps1
@@ -246,4 +250,4 @@ export_from_binaryview(bv, "out/")
246250

247251
## Development
248252

249-
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup, tests, the local quality gate, and release steps.
253+
See [DEVELOPMENT.md](https://github.com/buzzer-re/ToCode/blob/main/DEVELOPMENT.md) for setup, tests, the local quality gate, and release steps.

‎src/tocode/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
__all__ = ["__version__", "export_from_binaryview"]
99

10-
__version__ = "0.1.0"
10+
__version__ = "0.2.1"
1111

1212

1313
def export_from_binaryview(

‎src/tocode/backends/dotnet.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,18 @@ def load_runtime() -> dict[str, Any]:
540540
raise ToCodeError(f"cannot load the .NET runtime: {exc}") from exc
541541
import clr # type: ignore[import-not-found]
542542

543-
for spec in dotnet_libs.LIBRARIES:
544-
clr.AddReference(str(library_dir / spec.file_name))
545543
import System # type: ignore[import-not-found]
546544

545+
# Reference by name from a sys.path folder: pythonnet 3.0 (Python 3.10)
546+
# rejects full paths in AddReference; 3.1+ accepts both.
547+
if str(library_dir) not in sys.path:
548+
sys.path.append(str(library_dir))
549+
for spec in dotnet_libs.LIBRARIES:
550+
try:
551+
clr.AddReference(spec.file_name.removesuffix(".dll"))
552+
except Exception:
553+
System.Reflection.Assembly.LoadFrom(str(library_dir / spec.file_name))
554+
547555
actual = str(System.Environment.Version)
548556
if _version_tuple(actual)[:1] < (MIN_RUNTIME_MAJOR,):
549557
raise ToCodeError(

0 commit comments

Comments
 (0)