mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-08 01:08:44 -10:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7d47be528 | ||
|
|
67471a35d3 | ||
|
|
12620d1557 | ||
|
|
06a21356e7 | ||
|
|
26d3db86c3 | ||
|
|
6681f1af93 | ||
|
|
08876f2aa3 | ||
|
|
60267a76e8 | ||
|
|
57cda9236e | ||
|
|
80701514cd | ||
|
|
6afe8d65d2 | ||
|
|
45dc3b8243 | ||
|
|
57da6163f6 | ||
|
|
1c7b79308a | ||
|
|
2d2299c1c7 | ||
|
|
b468bcd592 | ||
|
|
474d01e9e5 | ||
|
|
c9a64a8635 | ||
|
|
a50a18325c | ||
|
|
540e6ffb16 | ||
|
|
2b0320c64f | ||
|
|
51701f2fb9 | ||
|
|
5bac14c42e | ||
|
|
7420757a62 | ||
|
|
71f2189d84 |
No files matched your search
+137
-11
@@ -1,5 +1,8 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.2.2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -25,11 +28,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
@@ -46,11 +51,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Install deps
|
||||
run: pip install meson ninja --break-system-packages
|
||||
@@ -69,11 +76,11 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install radare2 from git
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
from-git: true
|
||||
|
||||
@@ -89,7 +96,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -98,7 +105,9 @@ jobs:
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Build Debian package
|
||||
run: make -C dist/debian SUDO=
|
||||
@@ -110,6 +119,107 @@ jobs:
|
||||
if-no-files-found: error
|
||||
archive: false
|
||||
|
||||
windows:
|
||||
name: Build Windows package
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
|
||||
- name: Setup MSVC
|
||||
uses: TheMrMilchmann/setup-msvc-dev@v4
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: Install build dependencies
|
||||
run: python -m pip install meson ninja
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Configure
|
||||
run: meson setup build-windows --buildtype=release -Dplugins=enabled
|
||||
|
||||
- name: Build
|
||||
run: meson compile -C build-windows
|
||||
|
||||
- name: Test
|
||||
run: meson test -C build-windows --print-errorlogs
|
||||
|
||||
- name: Package Windows build
|
||||
id: package
|
||||
shell: pwsh
|
||||
run: |
|
||||
$project = meson introspect build-windows --projectinfo | ConvertFrom-Json
|
||||
$package = "r2unity-$($project.version)-w64"
|
||||
$files = @(
|
||||
"build-windows\r2unity.exe",
|
||||
"build-windows\core_r2unity.dll",
|
||||
"build-windows\bin_r2unity.dll"
|
||||
)
|
||||
New-Item -ItemType Directory -Path $package | Out-Null
|
||||
foreach ($file in $files) {
|
||||
if (-not (Test-Path $file)) {
|
||||
throw "Missing Windows build output: $file"
|
||||
}
|
||||
Copy-Item $file -Destination $package
|
||||
}
|
||||
$archive = "$package.zip"
|
||||
Compress-Archive -Path "$package\*" -DestinationPath $archive -Force
|
||||
"archive=$archive" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
|
||||
- name: Package r2pm binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path bindist\plugins,bindist\bin | Out-Null
|
||||
Copy-Item build-windows\core_r2unity.dll,build-windows\bin_r2unity.dll bindist\plugins
|
||||
Copy-Item build-windows\r2unity.exe bindist\bin
|
||||
Compress-Archive -Path bindist\* -DestinationPath "r2unity-$env:R2V-windows-x86-64.zip" -Force
|
||||
|
||||
- name: Upload Windows package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-package
|
||||
path: |
|
||||
${{ steps.package.outputs.archive }}
|
||||
r2unity-*-windows-x86-64.zip
|
||||
if-no-files-found: error
|
||||
|
||||
bindist:
|
||||
name: Build r2pm binary (${{ matrix.os }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install radare2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Package r2pm binary
|
||||
run: make bindist
|
||||
|
||||
- name: Upload r2pm binary artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: bindist-${{ matrix.os }}
|
||||
path: r2unity-*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: Publish release
|
||||
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
|
||||
@@ -117,27 +227,42 @@ jobs:
|
||||
- build
|
||||
- meson
|
||||
- debian
|
||||
- windows
|
||||
- bindist
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
- name: Download Debian package artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: '*.deb'
|
||||
path: dist/artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download Windows package artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: windows-package
|
||||
path: dist/artifacts
|
||||
|
||||
- name: Download r2pm binary artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: bindist-*
|
||||
path: dist/artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate release notes
|
||||
run: ./release.sh notes > release-notes.md
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: softprops/[email protected].0
|
||||
uses: softprops/[email protected].3
|
||||
with:
|
||||
name: ${{ github.ref_name }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
@@ -147,3 +272,4 @@ jobs:
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
dist/artifacts/*.deb
|
||||
dist/artifacts/*.zip
|
||||
@@ -22,3 +22,6 @@
|
||||
|
||||
# Third-party reference checkouts (radare2, il2cpp dumper, etc.)
|
||||
/third_party/
|
||||
|
||||
# r2pm binary packages
|
||||
/r2unity-*.zip
|
||||
@@ -30,7 +30,10 @@ BIN_PLUGIN_LDFLAGS = $(shell pkg-config --libs r_bin 2>/dev/null || echo "")
|
||||
|
||||
R2_USER_PLUGINS = $(shell r2 -H R2_USER_PLUGINS 2>/dev/null)
|
||||
|
||||
LIB_SRCS = $(wildcard src/lib/*.c) $(wildcard src/lib/bin/*.c)
|
||||
BGDATABASE_LIB_SRCS = src/lib/bgdatabase.c
|
||||
RESOURCE_PACK_LIB_SRCS = src/lib/resource_pack.c
|
||||
BIN_ONLY_LIB_SRCS = $(BGDATABASE_LIB_SRCS) $(RESOURCE_PACK_LIB_SRCS)
|
||||
LIB_SRCS = $(filter-out $(BIN_ONLY_LIB_SRCS),$(wildcard src/lib/*.c)) $(wildcard src/lib/bin/*.c)
|
||||
LIB_OBJS = $(LIB_SRCS:.c=.o)
|
||||
LEGACY_OBJS = src/lib/elf.o src/lib/macho.o src/lib/pe.o src/lib/native.o
|
||||
CLI_SRCS = src/main.c
|
||||
@@ -63,11 +66,19 @@ user-install: install-plugin
|
||||
user-uninstall: uninstall-plugin
|
||||
rm -f "$(R2PM_BINDIR)/$(EXEC)"
|
||||
|
||||
# Binary package for r2pm -bi: plugins/ and bin/ for this r2 version and platform
|
||||
BINDIST_ZIP = r2unity-$(shell r2 -qv)-$(shell r2pm -H R2PM_OS)-$(shell r2pm -H R2PM_ARCH)-$(shell r2pm -H R2PM_BITS).zip
|
||||
|
||||
bindist: $(EXEC) $(PLUGINS)
|
||||
rm -rf build/bindist $(BINDIST_ZIP)
|
||||
$(MAKE) user-install R2_USER_PLUGINS=$(CURDIR)/build/bindist/plugins R2PM_BINDIR=$(CURDIR)/build/bindist/bin
|
||||
cd build/bindist && zip -r $(CURDIR)/$(BINDIST_ZIP) plugins bin
|
||||
|
||||
$(EXEC): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(CONFIG_H): meson.build
|
||||
echo '#define R2UNITY_VERSION "$(PROJECT_VERSION)"\n' > $@
|
||||
echo '#define R2UNITY_VERSION "$(PROJECT_VERSION)"' > $@
|
||||
|
||||
$(CLI_OBJS): %.o: %.c $(CONFIG_H)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
@@ -75,7 +86,7 @@ $(CLI_OBJS): %.o: %.c $(CONFIG_H)
|
||||
$(CORE_PLUGIN): src/r2/core_r2unity.c $(LIB_SRCS)
|
||||
$(CC) $(CORE_PLUGIN_CFLAGS) $(PLUGIN_SHFLAGS) -o $@ $^ $(CORE_PLUGIN_LDFLAGS)
|
||||
|
||||
$(BIN_PLUGIN): src/r2/bin_r2unity.c $(LIB_SRCS)
|
||||
$(BIN_PLUGIN): src/r2/bin_r2unity.c $(LIB_SRCS) $(BIN_ONLY_LIB_SRCS)
|
||||
$(CC) $(BIN_PLUGIN_CFLAGS) $(PLUGIN_SHFLAGS) -o $@ $^ $(BIN_PLUGIN_LDFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
@@ -85,4 +96,4 @@ clean:
|
||||
rm -f $(EXEC) $(OBJS) $(LEGACY_OBJS) $(PLUGINS) $(CONFIG_H)
|
||||
|
||||
|
||||
.PHONY: all clean plugin install-plugin uninstall-plugin fmt
|
||||
.PHONY: all clean plugin install-plugin uninstall-plugin fmt bindist
|
||||
@@ -22,6 +22,14 @@ IL2CPP binary, and exposes managed metadata for reverse engineering.
|
||||
SBOMs for managed assemblies.
|
||||
- Provides both a core r2 command plugin and an `r_bin` plugin for direct
|
||||
`global-metadata.dat` inspection.
|
||||
- Parses IL2CPP `*.dll-resources.dat` packs and exposes named payloads through
|
||||
radare2's resource listing and extraction APIs.
|
||||
- Recognizes Unity SerializedFile v22 asset databases and exposes their
|
||||
headers, object ranges, names, symbols, classes, dependencies, and resources.
|
||||
- Resolves bounded `Texture2D`/`Mesh` `.resS` and `AudioClip`/`VideoClip`
|
||||
`.resource` ranges beside loose SerializedFiles for `iU`/`iUx` extraction.
|
||||
- Recognizes BGDatabase v6 repositories and whole-database saves, including
|
||||
addon payloads, table regions, fields, symbols, and validated UTF-8 values.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -48,7 +56,7 @@ r2pm -ci r2unity
|
||||
```
|
||||
|
||||
`make` builds the CLI. `make plugin` builds `core_r2unity` and `bin_r2unity`.
|
||||
`make user-install` installs both the CLI and plugins for the current user.
|
||||
`make user-install` installs the CLI and plugins for the current user.
|
||||
Meson builds the CLI by default, matching `make`. Use `-Dplugins=enabled` to
|
||||
build the radare2 plugins too, or `-Dr2_plugindir=/path/to/plugins` to override
|
||||
the plugin install directory.
|
||||
@@ -99,7 +107,39 @@ r2unity-Sj emit managed-assembly CycloneDX JSON
|
||||
Set `r2unity.metadata` and `r2unity.library` manually when auto-detection is not
|
||||
enough. The `bin_r2unity` plugin also lets radare2/rabin2 treat
|
||||
`global-metadata.dat` as a binary format, exposing sections, strings, symbols,
|
||||
classes, imports, libraries, and header fields.
|
||||
classes, imports, libraries, and header fields. It also recognizes loose Unity
|
||||
SerializedFile v22 inputs such as `sharedassets*.assets`:
|
||||
|
||||
```sh
|
||||
rabin2 -I sharedassets10.assets
|
||||
rabin2 -S sharedassets10.assets
|
||||
rabin2 -s sharedassets10.assets
|
||||
rabin2 -U sharedassets10.assets
|
||||
rabin2 -xU sharedassets10.assets
|
||||
```
|
||||
|
||||
Streamed resources are extracted when the referenced `.resS` or `.resource`
|
||||
file is beside its owning SerializedFile. Missing sidecars remain visible in
|
||||
`-U` output, but extraction fails safely instead of reading the same offset
|
||||
from the `.assets` file.
|
||||
|
||||
BGDatabase repositories and saves produced by `BGRepo.I.Save()` are handled by
|
||||
the same `r2unity` bin plugin:
|
||||
|
||||
```sh
|
||||
rabin2 -I SaveFile.dat
|
||||
rabin2 -S SaveFile.dat
|
||||
rabin2 -s SaveFile.dat
|
||||
rabin2 -z SaveFile.dat
|
||||
```
|
||||
|
||||
IL2CPP manifest-resource packs use the same bin plugin:
|
||||
|
||||
```sh
|
||||
rabin2 -U System.Drawing.dll-resources.dat
|
||||
rabin2 -jU System.Drawing.dll-resources.dat
|
||||
rabin2 -xU -o extracted System.Drawing.dll-resources.dat
|
||||
```
|
||||
|
||||
## Current Limits
|
||||
|
||||
@@ -110,5 +150,15 @@ classes, imports, libraries, and header fields.
|
||||
recover native wrapper addresses or every `DllImportAttribute` detail.
|
||||
- SBOM output covers managed assemblies only, not native dependencies or file
|
||||
hashes.
|
||||
- SerializedFile support currently targets format v22. Object naming and
|
||||
payload decoding cover the common built-in classes needed by the reference
|
||||
asset; arbitrary stripped type trees and managed script schemas remain to be
|
||||
implemented.
|
||||
- Loose sibling `.resS` and `.resource` extraction is supported for the known
|
||||
v22 stream layouts. Archive-member paths and other version-specific object
|
||||
layouts still require UnityFS and additional class fixtures.
|
||||
- BGDatabase support currently targets the structurally validated v6 layout.
|
||||
It exposes unknown proprietary field payloads as table sections until more
|
||||
field types and format versions are recovered from additional fixtures.
|
||||
|
||||
Deep technical notes live in `doc/`.
|
||||
+8
-4
@@ -355,10 +355,14 @@ int32_t customAttributeIndex; /* removed at v24.1 */
|
||||
int32_t typeIndex; /* -> MetadataRegistration.types */
|
||||
```
|
||||
|
||||
Not decoded by r2unity today. Without it, every dumped method
|
||||
degrades to `Method()` with no parameter types or names. Combined
|
||||
with `parameterDefaultValues` (§2.4) this is what reconstructs real
|
||||
C# signatures like `Foo(string name, int count = 0)`.
|
||||
r2unity decodes this table and exposes parameter tokens, names, and
|
||||
type indices in `r2unity-sj`; it also emits a canonical signature such
|
||||
as `Namespace.Type.Foo(this;string:name,int:count):void`. Type indices
|
||||
that refer directly to a type definition are resolved to source names.
|
||||
Pointer, array, and constructed-generic indices still require the native
|
||||
`MetadataRegistration.types` table and use `type_index.N` until that
|
||||
table is available. `parameterDefaultValues` (§2.4) remains necessary
|
||||
to reconstruct optional values such as `int count = 0`.
|
||||
|
||||
### 2.4 Default values: `fieldDefaultValues`, `parameterDefaultValues`,
|
||||
`fieldAndParameterDefaultValueData`
|
||||
|
||||
+1105
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -119,7 +119,7 @@ Still missing:
|
||||
|
||||
Add or improve decoding for:
|
||||
|
||||
- parameters and full method signatures
|
||||
- generic method/type substitutions in signatures
|
||||
- default values for fields and parameters
|
||||
- properties and events
|
||||
- nested types
|
||||
|
||||
+32
-5
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2unity',
|
||||
'c',
|
||||
version: '1.2.0',
|
||||
version: '1.2.6',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'warning_level=2',
|
||||
@@ -17,8 +17,26 @@ add_project_arguments(
|
||||
language: 'c',
|
||||
)
|
||||
|
||||
r_util_dep = dependency('r_util')
|
||||
r_bin_dep = dependency('r_bin')
|
||||
r2 = find_program('r2', required: true)
|
||||
r2_cflags = run_command(r2, '-H', 'R2_CFLAGS', check: true).stdout().strip().split()
|
||||
r2_ldflags = run_command(r2, '-H', 'R2_LDFLAGS', check: true).stdout().strip().split()
|
||||
r2_libs = run_command(r2, '-H', 'R2_LIBS', check: true).stdout().strip().split()
|
||||
r2_fallback_dep = declare_dependency(
|
||||
compile_args: r2_cflags,
|
||||
link_args: r2_ldflags + r2_libs,
|
||||
)
|
||||
|
||||
# Windows release ZIPs are relocatable and are not registered with pkg-config.
|
||||
# Fall back to the paths reported by r2 when pkg-config cannot find its SDK.
|
||||
r_util_dep = dependency('r_util', required: false)
|
||||
if not r_util_dep.found()
|
||||
r_util_dep = r2_fallback_dep
|
||||
endif
|
||||
|
||||
r_bin_dep = dependency('r_bin', required: false)
|
||||
if not r_bin_dep.found()
|
||||
r_bin_dep = r2_fallback_dep
|
||||
endif
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set_quoted('R2UNITY_VERSION', meson.project_version())
|
||||
@@ -32,6 +50,7 @@ lib_sources = files(
|
||||
'src/lib/bin/native.c',
|
||||
'src/lib/bin/pe.c',
|
||||
'src/lib/paths.c',
|
||||
'src/lib/serialized_file.c',
|
||||
'src/lib/sbom.c',
|
||||
)
|
||||
|
||||
@@ -47,7 +66,10 @@ test('r2unity-help', r2unity, args: ['-h'])
|
||||
test('r2unity-version', r2unity, args: ['-v'])
|
||||
|
||||
plugins_opt = get_option('plugins')
|
||||
r_core_dep = dependency('r_core', required: plugins_opt)
|
||||
r_core_dep = dependency('r_core', required: false)
|
||||
if not r_core_dep.found() and not plugins_opt.disabled()
|
||||
r_core_dep = r2_fallback_dep
|
||||
endif
|
||||
build_plugins = plugins_opt.enabled() or (
|
||||
plugins_opt.auto() and r_core_dep.found() and r_bin_dep.found()
|
||||
)
|
||||
@@ -81,11 +103,16 @@ if build_plugins
|
||||
|
||||
shared_library(
|
||||
'bin_r2unity',
|
||||
files('src/r2/bin_r2unity.c') + lib_sources,
|
||||
files(
|
||||
'src/r2/bin_r2unity.c',
|
||||
'src/lib/bgdatabase.c',
|
||||
'src/lib/resource_pack.c',
|
||||
) + lib_sources,
|
||||
include_directories: lib_inc,
|
||||
dependencies: [r_bin_dep],
|
||||
name_prefix: '',
|
||||
install: true,
|
||||
install_dir: r2_plugindir,
|
||||
)
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,389 @@
|
||||
/* r2unity - MIT - Copyright 2025-2026 - pancake */
|
||||
|
||||
#define R_LOG_ORIGIN "r2unity.bgdatabase"
|
||||
|
||||
#include "bgdatabase.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#define R2U_BGDB_VERSION 6
|
||||
#define R2U_BGDB_MAX_ADDONS 1024
|
||||
#define R2U_BGDB_MAX_TABLES 65536
|
||||
#define R2U_BGDB_MAX_FIELDS 65536
|
||||
#define R2U_BGDB_MAX_STRING (1024 * 1024)
|
||||
#define R2U_BGDB_MAX_STRINGS 1048576
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut64 offset;
|
||||
ut64 limit;
|
||||
} R2UnityBGDatabaseReader;
|
||||
|
||||
static bool reader_has(const R2UnityBGDatabaseReader *r, ut64 size) {
|
||||
return r && r->offset <= r->limit && size <= r->limit - r->offset;
|
||||
}
|
||||
|
||||
static bool reader_read(R2UnityBGDatabaseReader *r, void *out, ut64 size) {
|
||||
if (!reader_has (r, size)
|
||||
|| r_buf_read_at (r->buf, r->offset, out, size) != (st64)size) {
|
||||
return false;
|
||||
}
|
||||
r->offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_skip(R2UnityBGDatabaseReader *r, ut64 size) {
|
||||
if (!reader_has (r, size)) {
|
||||
return false;
|
||||
}
|
||||
r->offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_u32(R2UnityBGDatabaseReader *r, ut32 *out) {
|
||||
ut8 raw[4];
|
||||
if (!reader_read (r, raw, sizeof (raw))) {
|
||||
return false;
|
||||
}
|
||||
*out = r_read_le32 (raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_string(R2UnityBGDatabaseReader *r, char **out,
|
||||
ut64 *data_offset, ut32 *string_size) {
|
||||
ut32 length;
|
||||
if (!reader_u32 (r, &length) || length > R2U_BGDB_MAX_STRING
|
||||
|| !reader_has (r, length)) {
|
||||
return false;
|
||||
}
|
||||
char *value = R_NEWS (char, (ut64)length + 1);
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (length && r_buf_read_at (r->buf, r->offset,
|
||||
(ut8 *)value, length) != (st64)length) {
|
||||
free (value);
|
||||
return false;
|
||||
}
|
||||
value[length] = 0;
|
||||
if (data_offset) {
|
||||
*data_offset = r->offset;
|
||||
}
|
||||
if (string_size) {
|
||||
*string_size = length;
|
||||
}
|
||||
*out = value;
|
||||
return reader_skip (r, length);
|
||||
}
|
||||
|
||||
static bool addon_type_valid(const char *type) {
|
||||
if (R_STR_ISEMPTY (type) || !r_str_startswith (type, "BansheeGz.BGDatabase.")) {
|
||||
return false;
|
||||
}
|
||||
for (const ut8 *p = (const ut8 *)type; *p; p++) {
|
||||
if (*p < 0x20 || *p > 0x7e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return strstr (type, ", BGDatabase") != NULL;
|
||||
}
|
||||
|
||||
static bool utf8_printable(const ut8 *bytes, ut32 size) {
|
||||
ut32 i = 0;
|
||||
bool visible = false;
|
||||
while (i < size) {
|
||||
ut32 cp;
|
||||
ut8 ch = bytes[i++];
|
||||
if (ch < 0x80) {
|
||||
if (!ch || (ch < 0x20 && ch != '\n' && ch != '\r' && ch != '\t')
|
||||
|| ch == 0x7f) {
|
||||
return false;
|
||||
}
|
||||
visible |= !isspace (ch);
|
||||
continue;
|
||||
}
|
||||
ut32 need;
|
||||
if (ch >= 0xc2 && ch <= 0xdf) {
|
||||
cp = ch & 0x1f;
|
||||
need = 1;
|
||||
} else if (ch >= 0xe0 && ch <= 0xef) {
|
||||
cp = ch & 0x0f;
|
||||
need = 2;
|
||||
} else if (ch >= 0xf0 && ch <= 0xf4) {
|
||||
cp = ch & 0x07;
|
||||
need = 3;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (need > size - i) {
|
||||
return false;
|
||||
}
|
||||
for (ut32 j = 0; j < need; j++) {
|
||||
ut8 continuation = bytes[i++];
|
||||
if ((continuation & 0xc0) != 0x80) {
|
||||
return false;
|
||||
}
|
||||
cp = (cp << 6) | (continuation & 0x3f);
|
||||
}
|
||||
if ((need == 2 && cp < 0x800) || (need == 3 && cp < 0x10000)
|
||||
|| (cp >= 0xd800 && cp <= 0xdfff) || cp > 0x10ffff) {
|
||||
return false;
|
||||
}
|
||||
visible = true;
|
||||
}
|
||||
return visible;
|
||||
}
|
||||
|
||||
static bool append_table(R2UnityBGDatabase *db, ut64 offset,
|
||||
ut64 name_field_offset, ut32 field_count, ut16 name_field_type) {
|
||||
if (db->table_count >= R2U_BGDB_MAX_TABLES) {
|
||||
return false;
|
||||
}
|
||||
size_t count = db->table_count + 1;
|
||||
R2UnityBGDatabaseTable *tables = realloc (db->tables,
|
||||
count * sizeof (*tables));
|
||||
if (!tables) {
|
||||
return false;
|
||||
}
|
||||
db->tables = tables;
|
||||
R2UnityBGDatabaseTable *table = &db->tables[db->table_count++];
|
||||
memset (table, 0, sizeof (*table));
|
||||
table->offset = offset;
|
||||
table->name_field_offset = name_field_offset;
|
||||
table->field_count = field_count;
|
||||
table->name_field_type = name_field_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool collect_tables(R2UnityBGDatabase *db, const ut8 *data,
|
||||
ut32 declared_count) {
|
||||
ut64 start = db->body_offset + 4;
|
||||
for (ut64 at = start; at <= db->size && db->size - at >= 8; at++) {
|
||||
if (r_read_le32 (data + at) != 4) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp (data + at + 4, "name", 4)) {
|
||||
continue;
|
||||
}
|
||||
/* A v6 table starts with its field count, followed by the canonical
|
||||
* name-field descriptor: u32 kind, u16 type, 16-byte id, string. */
|
||||
if (at < start + 26) {
|
||||
continue;
|
||||
}
|
||||
ut64 name_field = at - 22;
|
||||
ut64 table_offset = name_field - 4;
|
||||
ut32 field_count = r_read_le32 (data + table_offset);
|
||||
ut32 field_kind = r_read_le32 (data + name_field);
|
||||
ut16 field_type = r_read_le16 (data + name_field + 4);
|
||||
const ut8 *id = data + name_field + 6;
|
||||
if (!field_count || field_count > R2U_BGDB_MAX_FIELDS
|
||||
|| field_kind > 0x1000 || !field_type) {
|
||||
continue;
|
||||
}
|
||||
bool nonzero = false;
|
||||
for (size_t i = 0; i < sizeof (id); i++) {
|
||||
nonzero |= id[i] != 0;
|
||||
}
|
||||
if (!nonzero || !append_table (db, table_offset, name_field,
|
||||
field_count, field_type)) {
|
||||
if (!nonzero) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
at += 7;
|
||||
}
|
||||
if (db->table_count != declared_count) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < db->table_count; i++) {
|
||||
ut64 end = i + 1 < db->table_count
|
||||
? db->tables[i + 1].offset: db->size;
|
||||
if (end <= db->tables[i].offset) {
|
||||
return false;
|
||||
}
|
||||
db->tables[i].size = end - db->tables[i].offset;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool append_string(R2UnityBGDatabase *db, ut64 prefix_offset,
|
||||
ut32 size, const ut8 *bytes) {
|
||||
if (db->string_count >= R2U_BGDB_MAX_STRINGS) {
|
||||
return false;
|
||||
}
|
||||
size_t count = db->string_count + 1;
|
||||
R2UnityBGDatabaseString *strings = realloc (db->strings,
|
||||
count * sizeof (*strings));
|
||||
if (!strings) {
|
||||
return false;
|
||||
}
|
||||
db->strings = strings;
|
||||
R2UnityBGDatabaseString *string = &db->strings[db->string_count++];
|
||||
memset (string, 0, sizeof (*string));
|
||||
string->value = R_NEWS (char, (ut64)size + 1);
|
||||
if (!string->value) {
|
||||
db->string_count--;
|
||||
return false;
|
||||
}
|
||||
memcpy (string->value, bytes, size);
|
||||
string->value[size] = 0;
|
||||
string->prefix_offset = prefix_offset;
|
||||
string->offset = prefix_offset + 4;
|
||||
string->size = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool collect_strings(R2UnityBGDatabase *db, const ut8 *data) {
|
||||
for (ut64 at = 0; at <= db->size && db->size - at >= 6;) {
|
||||
bool opaque = false;
|
||||
for (size_t i = 0; i < db->addon_count; i++) {
|
||||
R2UnityBGDatabaseAddon *addon = &db->addons[i];
|
||||
ut64 end = addon->payload_offset + addon->payload_size;
|
||||
if (at >= addon->payload_offset && at < end) {
|
||||
at = end;
|
||||
opaque = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opaque || at > db->size || db->size - at < 6) {
|
||||
continue;
|
||||
}
|
||||
ut32 length = r_read_le32 (data + at);
|
||||
if (length < 2
|
||||
|| length > R2U_BGDB_MAX_STRING
|
||||
|| length > db->size - at - 4) {
|
||||
at++;
|
||||
continue;
|
||||
}
|
||||
const ut8 *bytes = data + at + 4;
|
||||
if (!utf8_printable (bytes, length)) {
|
||||
at++;
|
||||
continue;
|
||||
}
|
||||
bool ok = append_string (db, at, length, bytes);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
at += 4 + length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static R2UnityBGDatabase *parse_internal(RBuffer *buf, bool strings) {
|
||||
ut64 size = r_buf_size (buf);
|
||||
if (size < R2UNITY_BGDB_HEADER_SIZE + 8) {
|
||||
return NULL;
|
||||
}
|
||||
R2UnityBGDatabase *db = R_NEW0 (R2UnityBGDatabase);
|
||||
db->buf = r_buf_new_slice (buf, 0, size);
|
||||
if (!db->buf) {
|
||||
free (db);
|
||||
return NULL;
|
||||
}
|
||||
db->size = size;
|
||||
R2UnityBGDatabaseReader r = {
|
||||
.buf = db->buf,
|
||||
.offset = 0,
|
||||
.limit = size,
|
||||
};
|
||||
ut32 addon_count;
|
||||
if (!reader_u32 (&r, &db->version) || db->version != R2U_BGDB_VERSION
|
||||
|| !reader_read (&r, db->repository_id, sizeof (db->repository_id))
|
||||
|| !reader_u32 (&r, &db->header_value)
|
||||
|| !reader_u32 (&r, &addon_count)
|
||||
|| !addon_count || addon_count > R2U_BGDB_MAX_ADDONS) {
|
||||
goto fail;
|
||||
}
|
||||
db->addon_count = addon_count;
|
||||
db->addons = R_NEWS0 (R2UnityBGDatabaseAddon, addon_count);
|
||||
if (!db->addons) {
|
||||
goto fail;
|
||||
}
|
||||
bool recognized = false;
|
||||
for (size_t i = 0; i < db->addon_count; i++) {
|
||||
R2UnityBGDatabaseAddon *addon = &db->addons[i];
|
||||
addon->offset = r.offset;
|
||||
if (!reader_string (&r, &addon->type, &addon->type_offset,
|
||||
&addon->type_size)) {
|
||||
goto fail;
|
||||
}
|
||||
recognized |= addon_type_valid (addon->type);
|
||||
ut32 payload_size;
|
||||
addon->payload_size_offset = r.offset;
|
||||
if (!reader_u32 (&r, &payload_size)) {
|
||||
goto fail;
|
||||
}
|
||||
addon->payload_offset = r.offset;
|
||||
addon->payload_size = payload_size;
|
||||
if (!reader_skip (&r, payload_size)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if (!recognized) {
|
||||
goto fail;
|
||||
}
|
||||
db->body_offset = r.offset;
|
||||
ut32 table_count;
|
||||
if (!reader_u32 (&r, &table_count) || !table_count
|
||||
|| table_count > R2U_BGDB_MAX_TABLES || size > INT_MAX) {
|
||||
goto fail;
|
||||
}
|
||||
int data_size = 0;
|
||||
ut8 *data = r_buf_read_all (db->buf, &data_size);
|
||||
bool valid = data && (ut64)data_size == size
|
||||
&& collect_tables (db, data, table_count)
|
||||
&& (!strings || collect_strings (db, data));
|
||||
free (data);
|
||||
if (!valid) {
|
||||
goto fail;
|
||||
}
|
||||
return db;
|
||||
|
||||
fail:
|
||||
r2unity_bgdatabase_free (db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool r2unity_bgdatabase_check(RBuffer *buf) {
|
||||
R_RETURN_VAL_IF_FAIL (buf, false);
|
||||
R2UnityBGDatabase *db = parse_internal (buf, false);
|
||||
if (!db) {
|
||||
return false;
|
||||
}
|
||||
r2unity_bgdatabase_free (db);
|
||||
return true;
|
||||
}
|
||||
|
||||
R2UnityBGDatabase *r2unity_bgdatabase_parse(RBuffer *buf) {
|
||||
R_RETURN_VAL_IF_FAIL (buf, NULL);
|
||||
return parse_internal (buf, true);
|
||||
}
|
||||
|
||||
char *r2unity_bgdatabase_id_string(const ut8 id[16]) {
|
||||
R_RETURN_VAL_IF_FAIL (id, NULL);
|
||||
char *result = R_NEWS (char, 33);
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
snprintf (result + i * 2, 3, "%02x", id[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void r2unity_bgdatabase_free(R2UnityBGDatabase *db) {
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < db->addon_count; i++) {
|
||||
free (db->addons[i].type);
|
||||
}
|
||||
for (size_t i = 0; i < db->string_count; i++) {
|
||||
free (db->strings[i].value);
|
||||
}
|
||||
free (db->addons);
|
||||
free (db->tables);
|
||||
free (db->strings);
|
||||
r_unref (db->buf);
|
||||
free (db);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef R2UNITY_BGDATABASE_H
|
||||
#define R2UNITY_BGDATABASE_H
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define R2UNITY_BGDB_HEADER_SIZE 0x1c
|
||||
|
||||
typedef struct {
|
||||
ut64 offset;
|
||||
ut64 type_offset;
|
||||
ut32 type_size;
|
||||
char *type;
|
||||
ut64 payload_size_offset;
|
||||
ut64 payload_offset;
|
||||
ut64 payload_size;
|
||||
} R2UnityBGDatabaseAddon;
|
||||
|
||||
typedef struct {
|
||||
ut64 offset;
|
||||
ut64 size;
|
||||
ut64 name_field_offset;
|
||||
ut32 field_count;
|
||||
ut16 name_field_type;
|
||||
} R2UnityBGDatabaseTable;
|
||||
|
||||
typedef struct {
|
||||
ut64 prefix_offset;
|
||||
ut64 offset;
|
||||
ut32 size;
|
||||
char *value;
|
||||
} R2UnityBGDatabaseString;
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut64 size;
|
||||
ut32 version;
|
||||
ut8 repository_id[16];
|
||||
ut32 header_value;
|
||||
ut64 body_offset;
|
||||
R2UnityBGDatabaseAddon *addons;
|
||||
size_t addon_count;
|
||||
R2UnityBGDatabaseTable *tables;
|
||||
size_t table_count;
|
||||
R2UnityBGDatabaseString *strings;
|
||||
size_t string_count;
|
||||
} R2UnityBGDatabase;
|
||||
|
||||
bool r2unity_bgdatabase_check(RBuffer *buf);
|
||||
R2UnityBGDatabase *r2unity_bgdatabase_parse(RBuffer *buf);
|
||||
void r2unity_bgdatabase_free(R2UnityBGDatabase *db);
|
||||
char *r2unity_bgdatabase_id_string(const ut8 id[16]);
|
||||
|
||||
#endif
|
||||
+20
-8
@@ -30,6 +30,10 @@ static void elf_write_word(ut8 *p, ut64 v, bool is64) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool elf_range_in_file(const ElfImg *e, ut64 off, ut64 size) {
|
||||
return size <= e->size && off <= e->size - size;
|
||||
}
|
||||
|
||||
static bool elf_load(const char *path, ElfImg *e) {
|
||||
memset (e, 0, sizeof (*e));
|
||||
size_t size = 0;
|
||||
@@ -58,13 +62,16 @@ static bool elf_load(const char *path, ElfImg *e) {
|
||||
if (type != 1 && type != 2) {
|
||||
continue;
|
||||
}
|
||||
ElfSeg *s = &e->segs[e->nsegs++];
|
||||
ElfSeg *s = &e->segs[e->nsegs];
|
||||
s->type = type;
|
||||
s->flags = r_read_le32 (ph + 4);
|
||||
s->offset = r_read_le64 (ph + 8);
|
||||
s->vaddr = r_read_le64 (ph + 16);
|
||||
s->filesz = r_read_le64 (ph + 32);
|
||||
s->memsz = r_read_le64 (ph + 40);
|
||||
if (s->filesz && elf_range_in_file (e, s->offset, s->filesz)) {
|
||||
e->nsegs++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ut32 phoff = r_read_le32 (e->file + 0x1c);
|
||||
@@ -80,13 +87,16 @@ static bool elf_load(const char *path, ElfImg *e) {
|
||||
if (type != 1 && type != 2) {
|
||||
continue;
|
||||
}
|
||||
ElfSeg *s = &e->segs[e->nsegs++];
|
||||
ElfSeg *s = &e->segs[e->nsegs];
|
||||
s->type = type;
|
||||
s->offset = r_read_le32 (ph + 4);
|
||||
s->vaddr = r_read_le32 (ph + 8);
|
||||
s->filesz = r_read_le32 (ph + 16);
|
||||
s->memsz = r_read_le32 (ph + 20);
|
||||
s->flags = r_read_le32 (ph + 24);
|
||||
if (s->filesz && elf_range_in_file (e, s->offset, s->filesz)) {
|
||||
e->nsegs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -104,7 +114,7 @@ static const ut8 *elf_ptr_at_size(ElfImg *e, ut64 va, ut64 size) {
|
||||
continue;
|
||||
}
|
||||
ut64 delta = va - s->vaddr;
|
||||
if (delta + size <= s->filesz && s->offset + delta + size <= e->size) {
|
||||
if (size <= s->filesz && delta <= s->filesz - size && elf_range_in_file (e, s->offset + delta, size)) {
|
||||
return e->file + s->offset + delta;
|
||||
}
|
||||
return NULL;
|
||||
@@ -170,7 +180,7 @@ static void elf_apply_relocs(ElfImg *e, ut64 base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dyn_off || !dyn_sz || dyn_off + dyn_sz > e->size) {
|
||||
if (!dyn_off || !dyn_sz || !elf_range_in_file (e, dyn_off, dyn_sz)) {
|
||||
return;
|
||||
}
|
||||
ut64 rela_off = 0, rela_sz = 0, rela_ent = e->is64? 24: 12;
|
||||
@@ -198,7 +208,7 @@ static void elf_apply_relocs(ElfImg *e, ut64 base) {
|
||||
}
|
||||
}
|
||||
const ut64 type_mask = e->is64? 0xffffffffULL: 0xffULL;
|
||||
for (ut64 i = 0; rela_off && rela_ent && i + rela_ent <= rela_sz; i += rela_ent) {
|
||||
for (ut64 i = 0; rela_off && rela_ent == (e->is64? 24: 12) && i + rela_ent <= rela_sz; i += rela_ent) {
|
||||
const ut8 *rp = elf_ptr_at_size (e, rela_off + i, rela_ent);
|
||||
if (!rp) {
|
||||
break;
|
||||
@@ -214,7 +224,7 @@ static void elf_apply_relocs(ElfImg *e, ut64 base) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ut64 i = 0; rel_off && rel_ent && i + rel_ent <= rel_sz; i += rel_ent) {
|
||||
for (ut64 i = 0; rel_off && rel_ent == (e->is64? 16: 8) && i + rel_ent <= rel_sz; i += rel_ent) {
|
||||
const ut8 *rp = elf_ptr_at_size (e, rel_off + i, rel_ent);
|
||||
if (!rp) {
|
||||
break;
|
||||
@@ -230,7 +240,7 @@ static void elf_apply_relocs(ElfImg *e, ut64 base) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!e->is64 || !relr_off || !relr_sz || !relr_ent) {
|
||||
if (!e->is64 || !relr_off || !relr_sz || relr_ent != 8) {
|
||||
return;
|
||||
}
|
||||
ut64 curr = 0;
|
||||
@@ -282,7 +292,9 @@ bool r2unity_find_method_pointers_elf(R2UnityMetadata *meta, const char *path, c
|
||||
};
|
||||
size_t section_count = 0;
|
||||
R2UnityNativeSection *sections = elf_sections (&e, §ion_count);
|
||||
bool ok = r2unity_native_run_view (meta, &view, sections, section_count, options, result);
|
||||
view.sections = sections;
|
||||
view.section_count = section_count;
|
||||
bool ok = r2unity_native_resolve (meta, &view, NULL, options, result);
|
||||
R_FREE (sections);
|
||||
elf_free (&e);
|
||||
return ok;
|
||||
|
||||
+4
-1
@@ -140,6 +140,7 @@ static R2UnityNativeSection *macho_sections(MachO *mo, size_t *count) {
|
||||
for (size_t i = 0; i < mo->nsegs; i++) {
|
||||
const MachSeg *s = &mo->segs[i];
|
||||
ut64 vsize = s->vmsize? s->vmsize: s->filesize;
|
||||
out[i].name = s->name;
|
||||
out[i].vaddr = s->vmaddr;
|
||||
out[i].vsize = vsize;
|
||||
out[i].size = R_MIN (s->filesize, vsize);
|
||||
@@ -169,7 +170,9 @@ bool r2unity_find_method_pointers_macho(R2UnityMetadata *meta, const char *path,
|
||||
};
|
||||
size_t section_count = 0;
|
||||
R2UnityNativeSection *sections = macho_sections (&mo, §ion_count);
|
||||
bool ok = r2unity_native_run_view (meta, &view, sections, section_count, options, result);
|
||||
view.sections = sections;
|
||||
view.section_count = section_count;
|
||||
bool ok = r2unity_native_resolve (meta, &view, NULL, options, result);
|
||||
R_FREE (sections);
|
||||
macho_free (&mo);
|
||||
return ok;
|
||||
|
||||
+607
-190
File diff suppressed because it is too large.
Load diff
@@ -14,9 +14,12 @@ typedef struct {
|
||||
ut64 base_vaddr;
|
||||
ut64 text_lo;
|
||||
ut64 text_hi;
|
||||
const struct r2unity_native_section_t *sections;
|
||||
size_t section_count;
|
||||
} R2UnityNativeView;
|
||||
|
||||
typedef struct {
|
||||
typedef struct r2unity_native_section_t {
|
||||
const char *name;
|
||||
ut64 vaddr;
|
||||
ut64 vsize;
|
||||
ut64 size;
|
||||
@@ -24,12 +27,7 @@ typedef struct {
|
||||
bool is_data;
|
||||
} R2UnityNativeSection;
|
||||
|
||||
bool r2unity_native_parse_code_registration(R2UnityMetadata *meta, R2UnityNativeView *view, ut64 code_registration_va, R2UnityNativeSource source, R2UnityNativeResult *result);
|
||||
ut64 r2unity_native_resolve_override(const R2UnityNativeOptions *options, const char *const *names);
|
||||
bool r2unity_native_result_has_ptrs(const R2UnityNativeResult *result);
|
||||
void r2unity_native_take_heuristic_result(R2UnityNativeResult *result, ut64 *method_ptrs, int ptr_size);
|
||||
bool r2unity_native_scan_sections(R2UnityMetadata *meta, R2UnityNativeView *view, const R2UnityNativeSection *sections, size_t section_count, R2UnityNativeResult *result);
|
||||
bool r2unity_native_run_view(R2UnityMetadata *meta, R2UnityNativeView *view, const R2UnityNativeSection *sections, size_t section_count, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
bool r2unity_native_resolve(R2UnityMetadata *meta, R2UnityNativeView *view, RBinFile *bf, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
bool r2unity_find_method_pointers_elf(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
bool r2unity_find_method_pointers_macho(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
bool r2unity_find_method_pointers_pe(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
|
||||
+4
-1
@@ -146,6 +146,7 @@ static R2UnityNativeSection *pe_sections(PeImg *pe, size_t *count) {
|
||||
for (size_t i = 0; i < pe->nsecs; i++) {
|
||||
const PeSec *s = &pe->secs[i];
|
||||
ut64 vsize = s->vsize? s->vsize: s->filesize;
|
||||
out[i].name = s->name;
|
||||
out[i].vaddr = s->vaddr;
|
||||
out[i].vsize = vsize;
|
||||
out[i].size = R_MIN (s->filesize, vsize);
|
||||
@@ -175,7 +176,9 @@ bool r2unity_find_method_pointers_pe(R2UnityMetadata *meta, const char *path, co
|
||||
};
|
||||
size_t section_count = 0;
|
||||
R2UnityNativeSection *sections = pe_sections (&pe, §ion_count);
|
||||
bool ok = r2unity_native_run_view (meta, &view, sections, section_count, options, result);
|
||||
view.sections = sections;
|
||||
view.section_count = section_count;
|
||||
bool ok = r2unity_native_resolve (meta, &view, NULL, options, result);
|
||||
R_FREE (sections);
|
||||
pe_free (&pe);
|
||||
return ok;
|
||||
|
||||
+125
@@ -295,6 +295,12 @@ static ut64 field_definition_entry_size(R2UnityMetadata *meta) {
|
||||
return 4 + type_index_size + 4;
|
||||
}
|
||||
|
||||
static ut64 parameter_definition_entry_size(R2UnityMetadata *meta) {
|
||||
const bool compact_indices = (meta->version >= 35);
|
||||
const int type_index_size = compact_indices? meta->typeIndexSize: 4;
|
||||
return 4 + 4 + type_index_size;
|
||||
}
|
||||
|
||||
/* Detect v24.0 vs v24.1+ (both land on disk as version==24).
|
||||
*
|
||||
* At v24.1 the ImageDefinition row grew by 8 bytes (added
|
||||
@@ -507,6 +513,8 @@ R_API ut64 r2unity_metadata_section_entry_size(R2UnityMetadata *meta, R2UMetadat
|
||||
return method_definition_entry_size (meta);
|
||||
case R2U_SEC_FIELDS:
|
||||
return field_definition_entry_size (meta);
|
||||
case R2U_SEC_PARAMETERS:
|
||||
return parameter_definition_entry_size (meta);
|
||||
case R2U_SEC_TYPE_DEFINITIONS:
|
||||
return type_definition_entry_size (meta);
|
||||
case R2U_SEC_IMAGES:
|
||||
@@ -820,6 +828,123 @@ R_API Il2CppFieldDefinition *r2unity_get_field_definitions(R2UnityMetadata *meta
|
||||
return fields;
|
||||
}
|
||||
|
||||
R_API Il2CppParameterDefinition *r2unity_get_parameter_definitions(R2UnityMetadata *meta, size_t *count) {
|
||||
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
|
||||
const bool compact_indices = (meta->version >= 35);
|
||||
const int type_index_size = compact_indices? meta->typeIndexSize: 4;
|
||||
const ut64 entry = parameter_definition_entry_size (meta);
|
||||
ut8 *buf = read_metadata_table (meta, R2U_SEC_PARAMETERS, entry, count);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
}
|
||||
Il2CppParameterDefinition *parameters = R_NEWS (Il2CppParameterDefinition, *count);
|
||||
if (!parameters) {
|
||||
R_FREE (buf);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
const ut8 *p = buf + i * entry;
|
||||
parameters[i].nameIndex = read_u32p (&p);
|
||||
parameters[i].token = read_u32p (&p);
|
||||
parameters[i].typeIndex = read_indexp (&p, type_index_size);
|
||||
}
|
||||
R_FREE (buf);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
static const char *csharp_type_alias(const char *name) {
|
||||
static const struct {
|
||||
const char *fullname;
|
||||
const char *alias;
|
||||
} aliases[] = {
|
||||
{ "System.Void", "void" },
|
||||
{ "System.Boolean", "bool" },
|
||||
{ "System.Byte", "byte" },
|
||||
{ "System.SByte", "sbyte" },
|
||||
{ "System.Char", "char" },
|
||||
{ "System.Decimal", "decimal" },
|
||||
{ "System.Double", "double" },
|
||||
{ "System.Single", "float" },
|
||||
{ "System.Int16", "short" },
|
||||
{ "System.UInt16", "ushort" },
|
||||
{ "System.Int32", "int" },
|
||||
{ "System.UInt32", "uint" },
|
||||
{ "System.Int64", "long" },
|
||||
{ "System.UInt64", "ulong" },
|
||||
{ "System.IntPtr", "nint" },
|
||||
{ "System.UIntPtr", "nuint" },
|
||||
{ "System.Object", "object" },
|
||||
{ "System.String", "string" },
|
||||
};
|
||||
for (size_t i = 0; i < R_ARRAY_SIZE (aliases); i++) {
|
||||
if (!strcmp (name, aliases[i].fullname)) {
|
||||
return aliases[i].alias;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API char *r2unity_type_name_from_index(R2UnityMetadata *meta, const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index, bool fallback) {
|
||||
R_RETURN_VAL_IF_FAIL (meta, NULL);
|
||||
if (type_index >= 0) {
|
||||
for (size_t i = 0; types && i < type_count; i++) {
|
||||
if (types[i].byvalTypeIndex != type_index) {
|
||||
continue;
|
||||
}
|
||||
char *name = r2unity_type_fullname (meta, &types[i], i, R2U_NAME_FALLBACK_TYPE);
|
||||
if (name) {
|
||||
const char *alias = csharp_type_alias (name);
|
||||
if (alias) {
|
||||
free (name);
|
||||
return strdup (alias);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return fallback? r_str_newf ("type_index.%d", type_index): NULL;
|
||||
}
|
||||
|
||||
R_API char *r2unity_method_signature(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, const Il2CppTypeDefinition *owner, size_t owner_index, const Il2CppTypeDefinition *types, size_t type_count, const Il2CppParameterDefinition *parameters, size_t parameter_count, bool fallback) {
|
||||
R_RETURN_VAL_IF_FAIL (meta && method, NULL);
|
||||
char *return_type = r2unity_type_name_from_index (meta, types, type_count, method->returnType, fallback);
|
||||
char *method_name = r2unity_method_fullname (meta, method, owner, owner_index,
|
||||
fallback? R2U_NAME_FALLBACK_TYPE: 0);
|
||||
if (!method_name) {
|
||||
free (return_type);
|
||||
return NULL;
|
||||
}
|
||||
RStrBuf *sb = r_strbuf_new (method_name);
|
||||
r_strbuf_append (sb, "(");
|
||||
bool is_instance = !(method->flags & 0x0010);
|
||||
if (is_instance) {
|
||||
r_strbuf_append (sb, "this;");
|
||||
}
|
||||
for (size_t i = 0; i < method->parameterCount; i++) {
|
||||
st64 pi = (st64)method->parameterStart + (st64)i;
|
||||
const Il2CppParameterDefinition *parameter = parameters
|
||||
&& pi >= 0 && (ut64)pi < parameter_count
|
||||
? ¶meters[pi]: NULL;
|
||||
char *type_name = parameter
|
||||
? r2unity_type_name_from_index (meta, types, type_count, parameter->typeIndex, fallback)
|
||||
: NULL;
|
||||
char *parameter_name = parameter? r2unity_get_string (meta, parameter->nameIndex): NULL;
|
||||
r_strbuf_appendf (sb, "%s%s", i? ",": "",
|
||||
type_name? type_name: "unknown");
|
||||
if (R_STR_ISNOTEMPTY (parameter_name)) {
|
||||
r_strbuf_appendf (sb, ":%s", parameter_name);
|
||||
} else {
|
||||
r_strbuf_appendf (sb, ":arg%zu", i);
|
||||
}
|
||||
free (parameter_name);
|
||||
free (type_name);
|
||||
}
|
||||
r_strbuf_appendf (sb, "):%s", return_type? return_type: "unknown");
|
||||
free (method_name);
|
||||
free (return_type);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
R_API int32_t *r2unity_get_type_index_table(R2UnityMetadata *meta, R2UMetadataSectionId id, size_t *count) {
|
||||
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
|
||||
const bool compact_indices = (meta->version >= 35);
|
||||
|
||||
+36
-5
@@ -3,8 +3,10 @@
|
||||
|
||||
#include <r_util.h>
|
||||
|
||||
struct r_bin_t;
|
||||
struct r_bin_file_t;
|
||||
#if !defined(R2_BIN_H) && !defined(R2_BIN_DWARF_H)
|
||||
typedef struct r_bin_t RBin;
|
||||
typedef struct r_bin_file_t RBinFile;
|
||||
#endif
|
||||
|
||||
#define IL2CPP_MAGIC 0xFAB11BAF
|
||||
#define R2UNITY_METADATA_BASE_SECTION_COUNT 31
|
||||
@@ -128,6 +130,12 @@ typedef struct {
|
||||
uint32_t token;
|
||||
} Il2CppFieldDefinition;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nameIndex;
|
||||
uint32_t token;
|
||||
int32_t typeIndex;
|
||||
} Il2CppParameterDefinition;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nameIndex;
|
||||
int32_t assemblyIndex;
|
||||
@@ -170,8 +178,7 @@ typedef struct {
|
||||
enum {
|
||||
R2U_INTEROP_PINVOKE = 1, /* managed -> native, [DllImport] */
|
||||
R2U_INTEROP_REVERSE_PINVOKE = 2, /* native -> managed, [MonoPInvokeCallback] */
|
||||
R2U_INTEROP_UNMANAGED_ONLY = 3, /* [UnmanagedCallersOnly] (.NET 5+) */
|
||||
R2U_INTEROP_INTEROP_DATA = 4 /* Il2CppInteropData entry (opaque) */
|
||||
R2U_INTEROP_UNMANAGED_ONLY = 3 /* [UnmanagedCallersOnly] (.NET 5+) */
|
||||
};
|
||||
|
||||
/* A single P/Invoke or reverse-P/Invoke entry.
|
||||
@@ -209,6 +216,24 @@ typedef enum {
|
||||
R2U_NATIVE_SOURCE_HEURISTIC
|
||||
} R2UnityNativeSource;
|
||||
|
||||
typedef enum {
|
||||
R2U_NATIVE_TABLE_REVERSE_PINVOKE_WRAPPERS,
|
||||
R2U_NATIVE_TABLE_GENERIC_METHOD_POINTERS,
|
||||
R2U_NATIVE_TABLE_GENERIC_ADJUSTOR_THUNKS,
|
||||
R2U_NATIVE_TABLE_INVOKER_POINTERS,
|
||||
R2U_NATIVE_TABLE_UNRESOLVED_VIRTUAL_CALL_POINTERS,
|
||||
R2U_NATIVE_TABLE_UNRESOLVED_INSTANCE_CALL_POINTERS,
|
||||
R2U_NATIVE_TABLE_UNRESOLVED_STATIC_CALL_POINTERS,
|
||||
R2U_NATIVE_TABLE_INTEROP_DATA,
|
||||
R2U_NATIVE_TABLE_WINDOWS_RUNTIME_FACTORY_TABLE,
|
||||
R2U_NATIVE_TABLE_COUNT
|
||||
} R2UnityNativeTableId;
|
||||
|
||||
typedef struct {
|
||||
ut64 va;
|
||||
ut64 count;
|
||||
} R2UnityNativeTable;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
ut64 va;
|
||||
@@ -230,6 +255,7 @@ typedef struct {
|
||||
ut64 metadata_registration_va;
|
||||
ut64 method_pointers_va;
|
||||
ut64 code_gen_modules_va;
|
||||
R2UnityNativeTable tables[R2U_NATIVE_TABLE_COUNT];
|
||||
int ptr_size;
|
||||
} R2UnityNativeResult;
|
||||
|
||||
@@ -244,6 +270,9 @@ R_API bool r2unity_read_string_literal(R2UnityMetadata *meta, const Il2CppString
|
||||
R_API Il2CppTypeDefinition *r2unity_get_type_definitions(R2UnityMetadata *meta, size_t *count);
|
||||
R_API Il2CppMethodDefinition *r2unity_get_method_definitions(R2UnityMetadata *meta, size_t *count);
|
||||
R_API Il2CppFieldDefinition *r2unity_get_field_definitions(R2UnityMetadata *meta, size_t *count);
|
||||
R_API Il2CppParameterDefinition *r2unity_get_parameter_definitions(R2UnityMetadata *meta, size_t *count);
|
||||
R_API char *r2unity_type_name_from_index(R2UnityMetadata *meta, const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index, bool fallback);
|
||||
R_API char *r2unity_method_signature(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, const Il2CppTypeDefinition *owner, size_t owner_index, const Il2CppTypeDefinition *types, size_t type_count, const Il2CppParameterDefinition *parameters, size_t parameter_count, bool fallback);
|
||||
R_API int32_t *r2unity_get_type_index_table(R2UnityMetadata *meta, R2UMetadataSectionId id, size_t *count);
|
||||
R_API Il2CppImageDefinition *r2unity_get_images(R2UnityMetadata *meta, size_t *count);
|
||||
R_API int *r2unity_build_type_image_map(const Il2CppImageDefinition *images, size_t image_count, size_t type_count);
|
||||
@@ -255,6 +284,7 @@ R_API R2UnityInterop *r2unity_enumerate_pinvokes(R2UnityMetadata *meta, size_t *
|
||||
* Returns methods tagged with [MonoPInvokeCallback] or [UnmanagedCallersOnly].
|
||||
* Pre-v29 metadata returns NULL (attribute ctor args live in generator stubs). */
|
||||
R_API R2UnityInterop *r2unity_enumerate_reverse_pinvokes(R2UnityMetadata *meta, size_t *count);
|
||||
R_API bool r2unity_enrich_reverse_pinvokes_native(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityInterop **items, size_t *count);
|
||||
R_API void r2unity_free_interop(R2UnityInterop *items, size_t count);
|
||||
R_API char *r2unity_sbom_tostring(R2UnityMetadata *meta, const char *exe_path, const char *metadata_path, R2UnitySbomFormat format);
|
||||
R_API const char *r2unity_unity_range_from_wire(int wire);
|
||||
@@ -264,12 +294,13 @@ R_API ut64 r2unity_metadata_section_entry_size(R2UnityMetadata *meta, R2UMetadat
|
||||
R_API ut64 r2unity_metadata_section_count(R2UnityMetadata *meta, R2UMetadataSectionId id);
|
||||
R_API ut64 r2unity_metadata_header_size(R2UnityMetadata *meta);
|
||||
R_API const char *r2unity_native_source_name(R2UnityNativeSource source);
|
||||
R_API const char *r2unity_native_table_name(R2UnityNativeTableId id);
|
||||
R_API const char *const *r2unity_native_code_registration_names(void);
|
||||
R_API const char *const *r2unity_native_metadata_registration_names(void);
|
||||
R_API void r2unity_native_result_fini(R2UnityNativeResult *result);
|
||||
R_API bool r2unity_find_method_pointers(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
R_API bool r2unity_find_method_pointers_simple(R2UnityMetadata *meta, const char *path, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
R_API bool r2unity_find_method_pointers_rbin(R2UnityMetadata *meta, struct r_bin_t *bin, struct r_bin_file_t *bf, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
R_API bool r2unity_find_method_pointers_rbin(R2UnityMetadata *meta, RBin *bin, RBinFile *bf, const R2UnityNativeOptions *options, R2UnityNativeResult *result);
|
||||
|
||||
/* Companion-file discovery.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/* r2unity - MIT - Copyright 2025-2026 - pancake */
|
||||
|
||||
#define R_LOG_ORIGIN "r2unity.resource_pack"
|
||||
|
||||
#include "resource_pack.h"
|
||||
|
||||
#define R2U_RESOURCE_PACK_MAX_ENTRIES 65536
|
||||
#define R2U_RESOURCE_PACK_MAX_NAME 65536
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut64 offset;
|
||||
ut64 limit;
|
||||
} R2UnityResourcePackReader;
|
||||
|
||||
static bool reader_read(R2UnityResourcePackReader *reader, void *out, ut64 size) {
|
||||
if (reader->offset > reader->limit || size > reader->limit - reader->offset
|
||||
|| r_buf_read_at (reader->buf, reader->offset, out, size) != (st64)size) {
|
||||
return false;
|
||||
}
|
||||
reader->offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_u32(R2UnityResourcePackReader *reader, ut32 *value) {
|
||||
ut8 bytes[4];
|
||||
if (!reader_read (reader, bytes, sizeof (bytes))) {
|
||||
return false;
|
||||
}
|
||||
*value = r_read_le32 (bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_name(const ut8 *bytes, ut32 size) {
|
||||
ut32 offset = 0;
|
||||
bool visible = false;
|
||||
while (offset < size) {
|
||||
RRune rune;
|
||||
int length = r_utf8_decode (bytes + offset, size - offset, &rune);
|
||||
if (length < 1 || (ut32)length > size - offset
|
||||
|| rune == 0 || (rune < 0x20 && rune != '\t')
|
||||
|| rune == 0x7f) {
|
||||
return false;
|
||||
}
|
||||
visible |= rune != ' ' && rune != '\t';
|
||||
offset += length;
|
||||
}
|
||||
return visible;
|
||||
}
|
||||
|
||||
static R2UnityResourcePack *parse_internal(RBuffer *buf) {
|
||||
ut64 size = r_buf_size (buf);
|
||||
if (size < 8) {
|
||||
return NULL;
|
||||
}
|
||||
R2UnityResourcePack *pack = R_NEW0 (R2UnityResourcePack);
|
||||
pack->buf = r_buf_new_slice (buf, 0, size);
|
||||
if (!pack->buf) {
|
||||
free (pack);
|
||||
return NULL;
|
||||
}
|
||||
pack->size = size;
|
||||
R2UnityResourcePackReader reader = {
|
||||
.buf = pack->buf,
|
||||
.offset = 0,
|
||||
.limit = size,
|
||||
};
|
||||
ut32 count;
|
||||
if (!reader_u32 (&reader, &pack->records_size)
|
||||
|| pack->records_size < 4
|
||||
|| pack->records_size > size - 4
|
||||
|| !reader_u32 (&reader, &count)
|
||||
|| !count || count > R2U_RESOURCE_PACK_MAX_ENTRIES) {
|
||||
goto fail;
|
||||
}
|
||||
pack->payload_offset = 4 + pack->records_size;
|
||||
reader.limit = pack->payload_offset;
|
||||
pack->entry_count = count;
|
||||
pack->entries = R_NEWS0 (R2UnityResourcePackEntry, count);
|
||||
if (!pack->entries) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < pack->entry_count; i++) {
|
||||
R2UnityResourcePackEntry *entry = &pack->entries[i];
|
||||
ut32 payload_size;
|
||||
entry->descriptor_offset = reader.offset;
|
||||
if (!reader_u32 (&reader, &payload_size)) {
|
||||
goto fail;
|
||||
}
|
||||
entry->payload_size = payload_size;
|
||||
entry->name_size_offset = reader.offset;
|
||||
if (!reader_u32 (&reader, &entry->name_size)
|
||||
|| !entry->name_size
|
||||
|| entry->name_size > R2U_RESOURCE_PACK_MAX_NAME
|
||||
|| entry->name_size > reader.limit - reader.offset) {
|
||||
goto fail;
|
||||
}
|
||||
entry->name_offset = reader.offset;
|
||||
entry->name = R_NEWS (char, (ut64)entry->name_size + 1);
|
||||
if (!entry->name
|
||||
|| !reader_read (&reader, entry->name, entry->name_size)
|
||||
|| !valid_name ((const ut8 *)entry->name, entry->name_size)) {
|
||||
goto fail;
|
||||
}
|
||||
entry->name[entry->name_size] = 0;
|
||||
}
|
||||
if (reader.offset != pack->payload_offset) {
|
||||
goto fail;
|
||||
}
|
||||
ut64 payload_offset = pack->payload_offset;
|
||||
for (size_t i = 0; i < pack->entry_count; i++) {
|
||||
R2UnityResourcePackEntry *entry = &pack->entries[i];
|
||||
if (payload_offset > size || entry->payload_size > size - payload_offset) {
|
||||
goto fail;
|
||||
}
|
||||
entry->payload_offset = payload_offset;
|
||||
payload_offset += entry->payload_size;
|
||||
}
|
||||
if (payload_offset != size) {
|
||||
goto fail;
|
||||
}
|
||||
return pack;
|
||||
|
||||
fail:
|
||||
r2unity_resource_pack_free (pack);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool r2unity_resource_pack_check(RBuffer *buf) {
|
||||
R_RETURN_VAL_IF_FAIL (buf, false);
|
||||
R2UnityResourcePack *pack = parse_internal (buf);
|
||||
if (!pack) {
|
||||
return false;
|
||||
}
|
||||
r2unity_resource_pack_free (pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
R2UnityResourcePack *r2unity_resource_pack_parse(RBuffer *buf) {
|
||||
R_RETURN_VAL_IF_FAIL (buf, NULL);
|
||||
return parse_internal (buf);
|
||||
}
|
||||
|
||||
void r2unity_resource_pack_free(R2UnityResourcePack *pack) {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < pack->entry_count; i++) {
|
||||
free (pack->entries[i].name);
|
||||
}
|
||||
free (pack->entries);
|
||||
r_unref (pack->buf);
|
||||
free (pack);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef R2UNITY_RESOURCE_PACK_H
|
||||
#define R2UNITY_RESOURCE_PACK_H
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct {
|
||||
ut64 descriptor_offset;
|
||||
ut64 name_size_offset;
|
||||
ut64 name_offset;
|
||||
ut32 name_size;
|
||||
char *name;
|
||||
ut64 payload_offset;
|
||||
ut64 payload_size;
|
||||
} R2UnityResourcePackEntry;
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut64 size;
|
||||
ut32 records_size;
|
||||
ut64 payload_offset;
|
||||
R2UnityResourcePackEntry *entries;
|
||||
size_t entry_count;
|
||||
} R2UnityResourcePack;
|
||||
|
||||
bool r2unity_resource_pack_check(RBuffer *buf);
|
||||
R2UnityResourcePack *r2unity_resource_pack_parse(RBuffer *buf);
|
||||
void r2unity_resource_pack_free(R2UnityResourcePack *pack);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,601 @@
|
||||
/* r2unity - MIT - Copyright 2025-2026 - pancake */
|
||||
|
||||
#define R_LOG_ORIGIN "r2unity.serialized_file"
|
||||
|
||||
#include "serialized_file.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#define R2U_SERIALIZED_VERSION 22
|
||||
#define R2U_SERIALIZED_HEADER_SIZE 0x30
|
||||
#define R2U_MAX_TYPES 65536
|
||||
#define R2U_MAX_OBJECTS 1048576
|
||||
#define R2U_MAX_REFERENCES 1048576
|
||||
#define R2U_MAX_STRING (1024 * 1024)
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut64 offset;
|
||||
ut64 limit;
|
||||
bool big_endian;
|
||||
} R2UnityReader;
|
||||
|
||||
static bool reader_has(const R2UnityReader *r, ut64 size) {
|
||||
return r && r->offset <= r->limit && size <= r->limit - r->offset;
|
||||
}
|
||||
|
||||
static bool reader_read(R2UnityReader *r, void *out, ut64 size) {
|
||||
if (!reader_has (r, size) || r_buf_read_at (r->buf, r->offset, out, size) != (st64)size) {
|
||||
return false;
|
||||
}
|
||||
r->offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_skip(R2UnityReader *r, ut64 size) {
|
||||
if (!reader_has (r, size)) {
|
||||
return false;
|
||||
}
|
||||
r->offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_align(R2UnityReader *r, ut64 alignment) {
|
||||
ut64 aligned = R_ROUND (r->offset, alignment);
|
||||
return aligned >= r->offset && reader_skip (r, aligned - r->offset);
|
||||
}
|
||||
|
||||
static bool reader_u8(R2UnityReader *r, ut8 *out) {
|
||||
return reader_read (r, out, sizeof (*out));
|
||||
}
|
||||
|
||||
static bool reader_u16(R2UnityReader *r, ut16 *out) {
|
||||
ut8 raw[2];
|
||||
if (!reader_read (r, raw, sizeof (raw))) {
|
||||
return false;
|
||||
}
|
||||
*out = r->big_endian? r_read_be16 (raw): r_read_le16 (raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_u32(R2UnityReader *r, ut32 *out) {
|
||||
ut8 raw[4];
|
||||
if (!reader_read (r, raw, sizeof (raw))) {
|
||||
return false;
|
||||
}
|
||||
*out = r->big_endian? r_read_be32 (raw): r_read_le32 (raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_u64(R2UnityReader *r, ut64 *out) {
|
||||
ut8 raw[8];
|
||||
if (!reader_read (r, raw, sizeof (raw))) {
|
||||
return false;
|
||||
}
|
||||
*out = r->big_endian? r_read_be64 (raw): r_read_le64 (raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reader_cstring(R2UnityReader *r, char **out, ut64 *string_offset) {
|
||||
ut64 start = r->offset;
|
||||
ut64 length = 0;
|
||||
ut8 ch = 0;
|
||||
while (reader_has (r, 1)) {
|
||||
if (!reader_u8 (r, &ch)) {
|
||||
return false;
|
||||
}
|
||||
if (!ch) {
|
||||
char *s = R_NEWS (char, length + 1);
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
if (length && r_buf_read_at (r->buf, start, (ut8 *)s, length) != (st64)length) {
|
||||
free (s);
|
||||
return false;
|
||||
}
|
||||
s[length] = 0;
|
||||
*out = s;
|
||||
if (string_offset) {
|
||||
*string_offset = start;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (++length > R2U_MAX_STRING) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool buffer_range(RBuffer *buf, ut64 offset, ut64 size) {
|
||||
ut64 total = r_buf_size (buf);
|
||||
return offset <= total && size <= total - offset;
|
||||
}
|
||||
|
||||
static ut32 buffer_u32(const R2UnitySerializedFile *sf, ut64 offset) {
|
||||
return r_buf_read_ble32_at (sf->buf, offset, sf->big_endian);
|
||||
}
|
||||
|
||||
static ut64 buffer_u64(const R2UnitySerializedFile *sf, ut64 offset) {
|
||||
return r_buf_read_ble64_at (sf->buf, offset, sf->big_endian);
|
||||
}
|
||||
|
||||
static bool object_string(const R2UnitySerializedFile *sf, const R2UnitySerializedObject *object, ut64 at, char **out, ut64 *string_offset, ut32 *string_size, ut64 *next) {
|
||||
ut64 end = object->offset + object->size;
|
||||
if (end < object->offset || at > end || end - at < 4) {
|
||||
return false;
|
||||
}
|
||||
ut32 length = buffer_u32 (sf, at);
|
||||
ut64 data = at + 4;
|
||||
if (length > R2U_MAX_STRING || data > end || length > end - data) {
|
||||
return false;
|
||||
}
|
||||
char *s = R_NEWS (char, (ut64)length + 1);
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
if (length && r_buf_read_at (sf->buf, data, (ut8 *)s, length) != length) {
|
||||
free (s);
|
||||
return false;
|
||||
}
|
||||
s[length] = 0;
|
||||
*out = s;
|
||||
if (string_offset) {
|
||||
*string_offset = data;
|
||||
}
|
||||
if (string_size) {
|
||||
*string_size = length;
|
||||
}
|
||||
if (next) {
|
||||
ut64 aligned = R_ROUND (data + length, 4);
|
||||
if (aligned < data || aligned > end) {
|
||||
free (s);
|
||||
*out = NULL;
|
||||
return false;
|
||||
}
|
||||
*next = aligned;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool skip_type_tree(R2UnityReader *r) {
|
||||
ut32 node_count;
|
||||
ut32 string_size;
|
||||
if (!reader_u32 (r, &node_count) || !reader_u32 (r, &string_size)) {
|
||||
return false;
|
||||
}
|
||||
/* SerializedFile v19+ type-tree nodes include an eight-byte ref hash. */
|
||||
const ut64 node_size = 32;
|
||||
if (node_count > R2U_MAX_OBJECTS || node_count > UT64_MAX / node_size) {
|
||||
return false;
|
||||
}
|
||||
return reader_skip (r, (ut64)node_count * node_size)
|
||||
&& reader_skip (r, string_size);
|
||||
}
|
||||
|
||||
static bool skip_dependencies(R2UnityReader *r) {
|
||||
ut32 count;
|
||||
if (!reader_u32 (r, &count) || count > R2U_MAX_TYPES) {
|
||||
return false;
|
||||
}
|
||||
return reader_skip (r, (ut64)count * 4);
|
||||
}
|
||||
|
||||
static bool read_serialized_type(R2UnityReader *r, bool type_tree, bool reference_type, R2UnitySerializedType *out) {
|
||||
ut64 start = r->offset;
|
||||
ut32 class_id;
|
||||
ut8 stripped;
|
||||
ut16 script_type;
|
||||
if (!reader_u32 (r, &class_id) || !reader_u8 (r, &stripped) || !reader_u16 (r, &script_type)) {
|
||||
return false;
|
||||
}
|
||||
out->class_id = (st32)class_id;
|
||||
out->stripped = stripped != 0;
|
||||
out->script_type_index = (st16)script_type;
|
||||
if (out->class_id == 114 && !reader_skip (r, 16)) {
|
||||
return false;
|
||||
}
|
||||
if (!reader_skip (r, 16)) {
|
||||
return false;
|
||||
}
|
||||
if (type_tree && !skip_type_tree (r)) {
|
||||
return false;
|
||||
}
|
||||
if (reference_type) {
|
||||
char *class_name = NULL;
|
||||
char *name_space = NULL;
|
||||
char *assembly = NULL;
|
||||
bool ok = reader_cstring (r, &class_name, NULL)
|
||||
&& reader_cstring (r, &name_space, NULL)
|
||||
&& reader_cstring (r, &assembly, NULL);
|
||||
free (class_name);
|
||||
free (name_space);
|
||||
free (assembly);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
} else if (type_tree && !skip_dependencies (r)) {
|
||||
return false;
|
||||
}
|
||||
out->offset = start;
|
||||
out->size = r->offset - start;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *r2unity_serialized_class_name(st32 class_id) {
|
||||
switch (class_id) {
|
||||
case 21:
|
||||
return "Material";
|
||||
case 28:
|
||||
return "Texture2D";
|
||||
case 43:
|
||||
return "Mesh";
|
||||
case 49:
|
||||
return "TextAsset";
|
||||
case 74:
|
||||
return "AnimationClip";
|
||||
case 83:
|
||||
return "AudioClip";
|
||||
case 91:
|
||||
return "AnimatorController";
|
||||
case 114:
|
||||
return "MonoBehaviour";
|
||||
case 150:
|
||||
return "PreloadData";
|
||||
case 328:
|
||||
return "VideoClip";
|
||||
default:
|
||||
return "Object";
|
||||
}
|
||||
}
|
||||
|
||||
static bool class_has_name(st32 class_id) {
|
||||
switch (class_id) {
|
||||
case 21:
|
||||
case 28:
|
||||
case 43:
|
||||
case 49:
|
||||
case 74:
|
||||
case 83:
|
||||
case 91:
|
||||
case 150:
|
||||
case 328:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static char *read_sidecar_path(const R2UnitySerializedFile *sf, ut64 offset,
|
||||
ut32 size) {
|
||||
char *path = R_NEWS (char, (ut64)size + 1);
|
||||
if (!path || r_buf_read_at (sf->buf, offset, (ut8 *)path, size) != size) {
|
||||
free (path);
|
||||
return NULL;
|
||||
}
|
||||
path[size] = 0;
|
||||
if (memchr (path, 0, size)
|
||||
|| (!r_str_endswith (path, ".resS")
|
||||
&& !r_str_endswith (path, ".resource"))) {
|
||||
free (path);
|
||||
return NULL;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static void parse_stream_info(R2UnitySerializedFile *sf,
|
||||
R2UnitySerializedObject *object, ut64 after_name) {
|
||||
ut64 end = object->offset + object->size;
|
||||
/* Texture2D/Mesh StreamingInfo: u64 offset, u32 size, string path. */
|
||||
for (ut64 at = after_name; at <= end && end - at >= 16; at += 4) {
|
||||
ut64 stream_offset = buffer_u64 (sf, at);
|
||||
ut32 stream_size = buffer_u32 (sf, at + 8);
|
||||
ut32 path_size = buffer_u32 (sf, at + 12);
|
||||
ut64 path_offset = at + 16;
|
||||
if (!stream_size || !path_size || path_size > R2U_MAX_STRING
|
||||
|| path_offset > end || path_size > end - path_offset) {
|
||||
continue;
|
||||
}
|
||||
ut64 aligned_end = R_ROUND (path_offset + path_size, 4);
|
||||
if (aligned_end != end) {
|
||||
continue;
|
||||
}
|
||||
char *path = read_sidecar_path (sf, path_offset, path_size);
|
||||
if (path) {
|
||||
object->stream_path = path;
|
||||
object->stream_offset = stream_offset;
|
||||
object->stream_size = stream_size;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioClip/VideoClip resource: string path, u64 offset, u64 size. */
|
||||
for (ut64 at = after_name; at <= end && end - at >= 20; at += 4) {
|
||||
ut32 path_size = buffer_u32 (sf, at);
|
||||
ut64 path_offset = at + 4;
|
||||
if (!path_size || path_size > R2U_MAX_STRING
|
||||
|| path_offset > end || path_size > end - path_offset) {
|
||||
continue;
|
||||
}
|
||||
ut64 stream_info = R_ROUND (path_offset + path_size, 4);
|
||||
if (stream_info < path_offset || stream_info > end
|
||||
|| end - stream_info < 16) {
|
||||
continue;
|
||||
}
|
||||
ut64 stream_offset = buffer_u64 (sf, stream_info);
|
||||
ut64 stream_size = buffer_u64 (sf, stream_info + 8);
|
||||
if (!stream_size) {
|
||||
continue;
|
||||
}
|
||||
char *path = read_sidecar_path (sf, path_offset, path_size);
|
||||
if (path) {
|
||||
object->stream_path = path;
|
||||
object->stream_offset = stream_offset;
|
||||
object->stream_size = stream_size;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_texture_info(R2UnitySerializedFile *sf,
|
||||
R2UnitySerializedObject *object, ut64 after_name) {
|
||||
ut64 end = object->offset + object->size;
|
||||
if (after_name <= end && end - after_name >= 28) {
|
||||
object->width = buffer_u32 (sf, after_name + 8);
|
||||
object->height = buffer_u32 (sf, after_name + 12);
|
||||
object->texture_format = (st32)buffer_u32 (sf, after_name + 24);
|
||||
object->has_texture_info = object->width > 0 && object->height > 0;
|
||||
}
|
||||
parse_stream_info (sf, object, after_name);
|
||||
}
|
||||
|
||||
static bool parse_object_details(R2UnitySerializedFile *sf, R2UnitySerializedObject *object) {
|
||||
ut64 after_name = object->offset;
|
||||
if (class_has_name (object->class_id)) {
|
||||
if (!object_string (sf, object, object->offset, &object->name,
|
||||
&object->name_offset, &object->name_size, &after_name)) {
|
||||
return false;
|
||||
}
|
||||
if (!*object->name) {
|
||||
R_FREE (object->name);
|
||||
}
|
||||
} else if (object->class_id == 114) {
|
||||
/* v22 MonoBehaviour: GameObject PPtr, enabled+alignment, Script PPtr, name. */
|
||||
ut64 name_at = object->offset + 28;
|
||||
if (!object_string (sf, object, name_at, &object->name,
|
||||
&object->name_offset, &object->name_size, &after_name)) {
|
||||
return false;
|
||||
}
|
||||
if (!*object->name) {
|
||||
R_FREE (object->name);
|
||||
}
|
||||
}
|
||||
if (object->class_id == 49) {
|
||||
ut64 end = object->offset + object->size;
|
||||
if (after_name > end || end - after_name < 4) {
|
||||
return false;
|
||||
}
|
||||
ut32 payload_size = buffer_u32 (sf, after_name);
|
||||
ut64 payload_offset = after_name + 4;
|
||||
if (payload_offset > end || payload_size > end - payload_offset) {
|
||||
return false;
|
||||
}
|
||||
object->payload_offset = payload_offset;
|
||||
object->payload_size = payload_size;
|
||||
} else if (object->class_id == 28) {
|
||||
parse_texture_info (sf, object, after_name);
|
||||
} else if (object->class_id == 43 || object->class_id == 83
|
||||
|| object->class_id == 328) {
|
||||
parse_stream_info (sf, object, after_name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool r2unity_serialized_file_check(RBuffer *buf) {
|
||||
R_RETURN_VAL_IF_FAIL (buf, false);
|
||||
ut64 actual_size = r_buf_size (buf);
|
||||
if (actual_size < R2U_SERIALIZED_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
ut8 header[R2U_SERIALIZED_HEADER_SIZE];
|
||||
if (r_buf_read_at (buf, 0, header, sizeof (header)) != sizeof (header)) {
|
||||
return false;
|
||||
}
|
||||
ut32 version = r_read_be32 (header + 8);
|
||||
if (version != R2U_SERIALIZED_VERSION || header[16] > 1) {
|
||||
return false;
|
||||
}
|
||||
ut32 metadata_size = r_read_be32 (header + 20);
|
||||
ut64 file_size = r_read_be64 (header + 24);
|
||||
ut64 data_offset = r_read_be64 (header + 32);
|
||||
if (file_size != actual_size || metadata_size < 16
|
||||
|| data_offset < R2U_SERIALIZED_HEADER_SIZE || data_offset > file_size
|
||||
|| metadata_size > data_offset - R2U_SERIALIZED_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
/* The metadata begins with a NUL-terminated Unity version string. */
|
||||
ut8 first;
|
||||
if (r_buf_read_at (buf, R2U_SERIALIZED_HEADER_SIZE, &first, 1) != 1 || !isdigit (first)) {
|
||||
return false;
|
||||
}
|
||||
ut64 string_limit = R_MIN ((ut64)metadata_size, 64);
|
||||
bool nul = false;
|
||||
for (ut64 i = 0; i < string_limit; i++) {
|
||||
ut8 ch;
|
||||
if (r_buf_read_at (buf, R2U_SERIALIZED_HEADER_SIZE + i, &ch, 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (!ch) {
|
||||
nul = i > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nul;
|
||||
}
|
||||
|
||||
R2UnitySerializedFile *r2unity_serialized_file_parse(RBuffer *buf) {
|
||||
if (!r2unity_serialized_file_check (buf)) {
|
||||
return NULL;
|
||||
}
|
||||
R2UnitySerializedFile *sf = R_NEW0 (R2UnitySerializedFile);
|
||||
sf->buf = r_buf_new_slice (buf, 0, r_buf_size (buf));
|
||||
if (!sf->buf) {
|
||||
free (sf);
|
||||
return NULL;
|
||||
}
|
||||
sf->version = r_buf_read_be32_at (sf->buf, 8);
|
||||
sf->big_endian = r_buf_read8_at (sf->buf, 16) != 0;
|
||||
sf->metadata_size = r_buf_read_be32_at (sf->buf, 20);
|
||||
sf->file_size = r_buf_read_be64_at (sf->buf, 24);
|
||||
sf->data_offset = r_buf_read_be64_at (sf->buf, 32);
|
||||
sf->header_size = R2U_SERIALIZED_HEADER_SIZE;
|
||||
|
||||
R2UnityReader r = {
|
||||
.buf = sf->buf,
|
||||
.offset = sf->header_size,
|
||||
.limit = sf->header_size + sf->metadata_size,
|
||||
.big_endian = sf->big_endian,
|
||||
};
|
||||
ut32 platform;
|
||||
ut8 type_tree;
|
||||
ut32 type_count;
|
||||
if (!reader_cstring (&r, &sf->unity_version, NULL)
|
||||
|| !reader_u32 (&r, &platform)
|
||||
|| !reader_u8 (&r, &type_tree)
|
||||
|| !reader_u32 (&r, &type_count)
|
||||
|| type_count > R2U_MAX_TYPES) {
|
||||
goto fail;
|
||||
}
|
||||
sf->target_platform = (st32)platform;
|
||||
sf->enable_type_tree = type_tree != 0;
|
||||
sf->type_count = type_count;
|
||||
sf->types = R_NEWS0 (R2UnitySerializedType, type_count);
|
||||
if (type_count && !sf->types) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < sf->type_count; i++) {
|
||||
if (!read_serialized_type (&r, sf->enable_type_tree, false, &sf->types[i])) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ut32 object_count;
|
||||
if (!reader_u32 (&r, &object_count) || object_count > R2U_MAX_OBJECTS) {
|
||||
goto fail;
|
||||
}
|
||||
sf->object_count = object_count;
|
||||
sf->objects = R_NEWS0 (R2UnitySerializedObject, object_count);
|
||||
if (object_count && !sf->objects) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < sf->object_count; i++) {
|
||||
R2UnitySerializedObject *object = &sf->objects[i];
|
||||
ut64 path_id;
|
||||
ut64 byte_start;
|
||||
ut32 byte_size;
|
||||
ut32 type_index;
|
||||
if (!reader_align (&r, 4)) {
|
||||
goto fail;
|
||||
}
|
||||
object->table_offset = r.offset;
|
||||
if (!reader_u64 (&r, &path_id) || !reader_u64 (&r, &byte_start)
|
||||
|| !reader_u32 (&r, &byte_size) || !reader_u32 (&r, &type_index)
|
||||
|| type_index >= sf->type_count || byte_start > UT64_MAX - sf->data_offset) {
|
||||
goto fail;
|
||||
}
|
||||
object->path_id = (st64)path_id;
|
||||
object->type_index = type_index;
|
||||
object->class_id = sf->types[type_index].class_id;
|
||||
object->offset = sf->data_offset + byte_start;
|
||||
object->size = byte_size;
|
||||
if (!buffer_range (sf->buf, object->offset, object->size)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ut32 script_count;
|
||||
if (!reader_u32 (&r, &script_count) || script_count > R2U_MAX_REFERENCES) {
|
||||
goto fail;
|
||||
}
|
||||
sf->script_count = script_count;
|
||||
sf->scripts = R_NEWS0 (R2UnitySerializedScript, script_count);
|
||||
if (script_count && !sf->scripts) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < sf->script_count; i++) {
|
||||
ut32 file_index;
|
||||
ut64 path_id;
|
||||
if (!reader_align (&r, 4) || !reader_u32 (&r, &file_index) || !reader_u64 (&r, &path_id)) {
|
||||
goto fail;
|
||||
}
|
||||
sf->scripts[i].file_index = (st32)file_index;
|
||||
sf->scripts[i].path_id = (st64)path_id;
|
||||
}
|
||||
|
||||
ut32 external_count;
|
||||
if (!reader_u32 (&r, &external_count) || external_count > R2U_MAX_REFERENCES) {
|
||||
goto fail;
|
||||
}
|
||||
sf->external_count = external_count;
|
||||
sf->externals = R_NEWS0 (R2UnitySerializedExternal, external_count);
|
||||
if (external_count && !sf->externals) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < sf->external_count; i++) {
|
||||
R2UnitySerializedExternal *external = &sf->externals[i];
|
||||
char *empty = NULL;
|
||||
ut32 type;
|
||||
if (!reader_cstring (&r, &empty, NULL) || !reader_read (&r, external->guid, sizeof (external->guid))
|
||||
|| !reader_u32 (&r, &type)
|
||||
|| !reader_cstring (&r, &external->path, &external->path_offset)) {
|
||||
free (empty);
|
||||
goto fail;
|
||||
}
|
||||
free (empty);
|
||||
external->type = (st32)type;
|
||||
}
|
||||
|
||||
ut32 reference_type_count;
|
||||
if (!reader_u32 (&r, &reference_type_count) || reference_type_count > R2U_MAX_TYPES) {
|
||||
goto fail;
|
||||
}
|
||||
for (ut32 i = 0; i < reference_type_count; i++) {
|
||||
R2UnitySerializedType type = {0};
|
||||
if (!read_serialized_type (&r, sf->enable_type_tree, true, &type)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if (!reader_cstring (&r, &sf->user_information, NULL)) {
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < sf->object_count; i++) {
|
||||
if (!parse_object_details (sf, &sf->objects[i])) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return sf;
|
||||
|
||||
fail:
|
||||
r2unity_serialized_file_free (sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void r2unity_serialized_file_free(R2UnitySerializedFile *sf) {
|
||||
if (!sf) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < sf->object_count; i++) {
|
||||
free (sf->objects[i].name);
|
||||
free (sf->objects[i].stream_path);
|
||||
}
|
||||
for (size_t i = 0; i < sf->external_count; i++) {
|
||||
free (sf->externals[i].path);
|
||||
}
|
||||
free (sf->unity_version);
|
||||
free (sf->types);
|
||||
free (sf->objects);
|
||||
free (sf->scripts);
|
||||
free (sf->externals);
|
||||
free (sf->user_information);
|
||||
r_unref (sf->buf);
|
||||
free (sf);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef R2UNITY_SERIALIZED_FILE_H
|
||||
#define R2UNITY_SERIALIZED_FILE_H
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct {
|
||||
st32 class_id;
|
||||
st16 script_type_index;
|
||||
ut64 offset;
|
||||
ut64 size;
|
||||
bool stripped;
|
||||
} R2UnitySerializedType;
|
||||
|
||||
typedef struct {
|
||||
st64 path_id;
|
||||
st32 class_id;
|
||||
ut32 type_index;
|
||||
ut64 table_offset;
|
||||
ut64 offset;
|
||||
ut64 size;
|
||||
char *name;
|
||||
ut64 name_offset;
|
||||
ut32 name_size;
|
||||
ut64 payload_offset;
|
||||
ut64 payload_size;
|
||||
char *stream_path;
|
||||
ut64 stream_offset;
|
||||
ut64 stream_size;
|
||||
ut32 width;
|
||||
ut32 height;
|
||||
st32 texture_format;
|
||||
bool has_texture_info;
|
||||
} R2UnitySerializedObject;
|
||||
|
||||
typedef struct {
|
||||
st32 file_index;
|
||||
st64 path_id;
|
||||
} R2UnitySerializedScript;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
ut64 path_offset;
|
||||
ut8 guid[16];
|
||||
st32 type;
|
||||
} R2UnitySerializedExternal;
|
||||
|
||||
typedef struct {
|
||||
RBuffer *buf;
|
||||
ut32 version;
|
||||
ut32 metadata_size;
|
||||
ut64 file_size;
|
||||
ut64 data_offset;
|
||||
ut64 header_size;
|
||||
bool big_endian;
|
||||
char *unity_version;
|
||||
st32 target_platform;
|
||||
bool enable_type_tree;
|
||||
R2UnitySerializedType *types;
|
||||
size_t type_count;
|
||||
R2UnitySerializedObject *objects;
|
||||
size_t object_count;
|
||||
R2UnitySerializedScript *scripts;
|
||||
size_t script_count;
|
||||
R2UnitySerializedExternal *externals;
|
||||
size_t external_count;
|
||||
char *user_information;
|
||||
} R2UnitySerializedFile;
|
||||
|
||||
bool r2unity_serialized_file_check(RBuffer *buf);
|
||||
R2UnitySerializedFile *r2unity_serialized_file_parse(RBuffer *buf);
|
||||
void r2unity_serialized_file_free(R2UnitySerializedFile *sf);
|
||||
const char *r2unity_serialized_class_name(st32 class_id);
|
||||
|
||||
#endif
|
||||
+61
-22
@@ -5,7 +5,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <r2unity_config.h>
|
||||
#include "lib/lib.h"
|
||||
|
||||
@@ -150,6 +149,18 @@ static void pj_native_result(PJ *pj, const R2UnityNativeResult *native) {
|
||||
pj_kn (pj, "metadata_registration", native? native->metadata_registration_va: 0);
|
||||
pj_kn (pj, "method_pointers", native? native->method_pointers_va: 0);
|
||||
pj_kn (pj, "code_gen_modules", native? native->code_gen_modules_va: 0);
|
||||
pj_ko (pj, "native_tables");
|
||||
for (R2UnityNativeTableId i = 0; i < R2U_NATIVE_TABLE_COUNT; i++) {
|
||||
const R2UnityNativeTable *table = native? &native->tables[i]: NULL;
|
||||
if (!table || !table->va) {
|
||||
continue;
|
||||
}
|
||||
pj_ko (pj, r2unity_native_table_name (i));
|
||||
pj_kn (pj, "count", table->count);
|
||||
pj_kn (pj, "va", table->va);
|
||||
pj_end (pj);
|
||||
}
|
||||
pj_end (pj);
|
||||
}
|
||||
|
||||
static void print_native_comment(const R2UnityNativeResult *native) {
|
||||
@@ -265,8 +276,36 @@ static int interop_cmp(const void *a, const void *b) {
|
||||
return strcmp (xn, yn);
|
||||
}
|
||||
|
||||
static int emit_pinvokes(R2UnityMetadata *meta, const char *exe_path, bool is_json, bool is_r2, bool quiet) {
|
||||
(void)exe_path;
|
||||
static char *interop_flag_name(const char *prefix, const R2UnityInterop *it) {
|
||||
char *name = it->image_name && *it->image_name
|
||||
? r_str_newf ("%s.%s.%s", prefix, it->image_name, it->name)
|
||||
: r_str_newf ("%s.%s", prefix, it->name);
|
||||
if (name) {
|
||||
r_name_filter (name, -1);
|
||||
if (!r_name_check (name)) {
|
||||
R_FREE (name);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static void print_interop_wrapper_flag(const char *name, ut64 wrapper_va) {
|
||||
if (wrapper_va && name) {
|
||||
printf ("'@0x%" PFMT64x "'f %s\n", wrapper_va, name);
|
||||
printf ("'@0x%" PFMT64x "'af+ $$ %s\n", wrapper_va, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void json_interop_wrapper(FILE *f, const R2UnityInterop *it) {
|
||||
if (it->wrapper_va) {
|
||||
fprintf (f, ",\"wrapper_va\":\"0x%" PFMT64x "\"", it->wrapper_va);
|
||||
}
|
||||
if (it->wrapper_index != UINT32_MAX) {
|
||||
fprintf (f, ",\"wrapper_index\":%u", it->wrapper_index);
|
||||
}
|
||||
}
|
||||
|
||||
static int emit_pinvokes(R2UnityMetadata *meta, bool is_json, bool is_r2, bool quiet) {
|
||||
size_t n = 0;
|
||||
R2UnityInterop *items = r2unity_enumerate_pinvokes (meta, &n);
|
||||
if (items && n > 1) {
|
||||
@@ -309,18 +348,16 @@ static int emit_pinvokes(R2UnityMetadata *meta, const char *exe_path, bool is_js
|
||||
if (!it->name) {
|
||||
continue;
|
||||
}
|
||||
char buf[1024];
|
||||
if (it->image_name && *it->image_name) {
|
||||
snprintf (buf, sizeof (buf), "sym.unity.%s.%s", it->image_name, it->name);
|
||||
} else {
|
||||
snprintf (buf, sizeof (buf), "sym.unity.%s", it->name);
|
||||
char *name = interop_flag_name ("sym.unity", it);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
r_name_filter (buf, -1);
|
||||
if (it->dll_name) {
|
||||
printf ("# PInvoke %s -> %s!%s\n", buf, it->dll_name, it->entry_name? it->entry_name: it->name);
|
||||
printf ("# PInvoke %s -> %s!%s\n", name, it->dll_name, it->entry_name? it->entry_name: it->name);
|
||||
} else {
|
||||
printf ("# PInvoke %s -> <unresolved>\n", buf);
|
||||
printf ("# PInvoke %s -> <unresolved>\n", name);
|
||||
}
|
||||
free (name);
|
||||
}
|
||||
} else {
|
||||
if (!quiet) {
|
||||
@@ -356,10 +393,12 @@ static const char *interop_kind_label(uint8_t kind) {
|
||||
}
|
||||
}
|
||||
|
||||
static int emit_reverse_pinvokes(R2UnityMetadata *meta, const char *exe_path, bool is_json, bool is_r2, bool quiet) {
|
||||
(void)exe_path;
|
||||
static int emit_reverse_pinvokes(R2UnityMetadata *meta, const char *exe_path, const R2UnityNativeOptions *native, bool is_json, bool is_r2, bool quiet) {
|
||||
size_t n = 0;
|
||||
R2UnityInterop *items = r2unity_enumerate_reverse_pinvokes (meta, &n);
|
||||
if (exe_path && *exe_path) {
|
||||
r2unity_enrich_reverse_pinvokes_native (meta, exe_path, native, &items, &n);
|
||||
}
|
||||
if (items && n > 1) {
|
||||
qsort (items, n, sizeof (R2UnityInterop), interop_cmp);
|
||||
}
|
||||
@@ -377,6 +416,7 @@ static int emit_reverse_pinvokes(R2UnityMetadata *meta, const char *exe_path, bo
|
||||
fprintf (f, "\",\"method\":\"");
|
||||
json_escape (f, it->name? it->name: "");
|
||||
fprintf (f, "\",\"token\":\"0x%08x\",\"flags\":\"0x%04x\",\"iflags\":\"0x%04x\",\"attribute\":\"%s\",\"confidence\":%u", it->token, it->flags, it->iflags, interop_kind_label (it->kind), (unsigned)it->confidence);
|
||||
json_interop_wrapper (f, it);
|
||||
fputc ('}', f);
|
||||
}
|
||||
fprintf (f, "]}\n");
|
||||
@@ -386,14 +426,13 @@ static int emit_reverse_pinvokes(R2UnityMetadata *meta, const char *exe_path, bo
|
||||
if (!it->name) {
|
||||
continue;
|
||||
}
|
||||
char buf[1024];
|
||||
if (it->image_name && *it->image_name) {
|
||||
snprintf (buf, sizeof (buf), "sym.unity.reverse.%s.%s", it->image_name, it->name);
|
||||
} else {
|
||||
snprintf (buf, sizeof (buf), "sym.unity.reverse.%s", it->name);
|
||||
char *name = interop_flag_name ("sym.unity.reverse", it);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
r_name_filter (buf, -1);
|
||||
printf ("# ReversePInvoke %s [%s]\n", buf, interop_kind_label (it->kind));
|
||||
printf ("# ReversePInvoke %s [%s]\n", name, interop_kind_label (it->kind));
|
||||
print_interop_wrapper_flag (name, it->wrapper_va);
|
||||
free (name);
|
||||
}
|
||||
} else {
|
||||
if (!quiet) {
|
||||
@@ -1083,8 +1122,8 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
int rc = reverse_pinvokes
|
||||
? emit_reverse_pinvokes (meta, exe_path, json_one_line, r2_script, quiet)
|
||||
: emit_pinvokes (meta, exe_path, json_one_line, r2_script, quiet);
|
||||
? emit_reverse_pinvokes (meta, exe_path, &native_options, json_one_line, r2_script, quiet)
|
||||
: emit_pinvokes (meta, json_one_line, r2_script, quiet);
|
||||
r2unity_free_metadata (meta);
|
||||
r_unref (buf);
|
||||
free_symbol_overrides (&symbol_overrides);
|
||||
|
||||
+1199
-47
File diff suppressed because it is too large.
Load diff
+173
-40
@@ -6,6 +6,8 @@
|
||||
#include <r_lib.h>
|
||||
#include "../lib/lib.h"
|
||||
|
||||
#define R2UNITY_NATIVE_TARGET_LIMIT 0x10000
|
||||
|
||||
// clang-format off
|
||||
static const char *g_help_msg[] = {
|
||||
"Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer",
|
||||
@@ -14,7 +16,7 @@ static const char *g_help_msg[] = {
|
||||
"r2unity-c", "[*j]", "enumerate classes, inheritance, methods, and fields",
|
||||
"r2unity-D", "", "auto-detect companion files from current binary path",
|
||||
"r2unity-i", "[j]", "summary (metadata version, type/method counts)",
|
||||
"r2unity-s", "[*j]", "apply/list managed method symbols as flags + comments",
|
||||
"r2unity-s", "[*j]", "apply/list managed/native symbols as flags + comments",
|
||||
"r2unity-z", "[j]", "list managed string literals",
|
||||
"r2unity-P", "[*j]", "list P/Invoke (managed -> native)",
|
||||
"r2unity-R", "[*j]", "list reverse-P/Invoke (native -> managed)",
|
||||
@@ -163,6 +165,18 @@ static void pj_native_result(PJ *pj, const R2UnityNativeResult *result) {
|
||||
pj_kn (pj, "metadata_registration", result? result->metadata_registration_va: 0);
|
||||
pj_kn (pj, "method_pointers", result? result->method_pointers_va: 0);
|
||||
pj_kn (pj, "code_gen_modules", result? result->code_gen_modules_va: 0);
|
||||
pj_ko (pj, "native_tables");
|
||||
for (R2UnityNativeTableId i = 0; i < R2U_NATIVE_TABLE_COUNT; i++) {
|
||||
const R2UnityNativeTable *table = result? &result->tables[i]: NULL;
|
||||
if (!table || !table->va) {
|
||||
continue;
|
||||
}
|
||||
pj_ko (pj, r2unity_native_table_name (i));
|
||||
pj_kn (pj, "count", table->count);
|
||||
pj_kn (pj, "va", table->va);
|
||||
pj_end (pj);
|
||||
}
|
||||
pj_end (pj);
|
||||
}
|
||||
|
||||
static void print_native_result(RCore *core, const R2UnityNativeResult *result) {
|
||||
@@ -175,6 +189,54 @@ static void print_native_result(RCore *core, const R2UnityNativeResult *result)
|
||||
result? result->code_gen_modules_va: 0);
|
||||
}
|
||||
|
||||
static void native_flag(RCore *core, bool print, const char *name, ut64 addr, ut32 size) {
|
||||
if (print) {
|
||||
r_cons_printf (core->cons, "'@0x%08"PFMT64x"'f %s %u\n", addr, name, size);
|
||||
} else {
|
||||
r_flag_set (core->flags, name, addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_native_tables(RCore *core, const R2UnityNativeResult *result, bool print) {
|
||||
int ptr_size = result->ptr_size == 4? 4: 8;
|
||||
for (R2UnityNativeTableId i = 0; i < R2U_NATIVE_TABLE_COUNT; i++) {
|
||||
const R2UnityNativeTable *table = &result->tables[i];
|
||||
if (!table->va) {
|
||||
continue;
|
||||
}
|
||||
const char *name = r2unity_native_table_name (i);
|
||||
ut32 size = (ut32)R_MIN ((ut64)UT32_MAX, R_MAX ((ut64)1, table->count * ptr_size));
|
||||
char flag[128];
|
||||
snprintf (flag, sizeof (flag), "r2unity.table.%s", name);
|
||||
native_flag (core, print, flag, table->va, size);
|
||||
ut64 count = R_MIN (table->count, (ut64)R2UNITY_NATIVE_TARGET_LIMIT);
|
||||
if (i >= R2U_NATIVE_TABLE_INTEROP_DATA || !count) {
|
||||
continue;
|
||||
}
|
||||
int len = (int)(count * ptr_size);
|
||||
ut8 *buf = malloc (len);
|
||||
if (!buf || !r_io_read_at (core->io, table->va, buf, len)) {
|
||||
free (buf);
|
||||
continue;
|
||||
}
|
||||
for (ut64 j = 0; j < count; j++) {
|
||||
const ut8 *p = buf + j * ptr_size;
|
||||
ut64 addr = (ptr_size == 8? r_read_le64 (p): (ut64)r_read_le32 (p)) & ~(ut64)1;
|
||||
if (addr <= 0x1000 || !r_io_is_valid_offset (core->io, addr, R_PERM_X)) {
|
||||
continue;
|
||||
}
|
||||
snprintf (flag, sizeof (flag), "sym.r2unity.%s.%" PFMT64u, name, j);
|
||||
native_flag (core, print, flag, addr, 1);
|
||||
if (print) {
|
||||
r_cons_printf (core->cons, "'@0x%08" PFMT64x "'af+ $$ %s\n", addr, flag);
|
||||
} else if (core->anal && !r_anal_get_function_at (core->anal, addr)) {
|
||||
r_anal_create_function (core->anal, flag, addr, R_ANAL_FCN_TYPE_FCN, NULL);
|
||||
}
|
||||
}
|
||||
free (buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Resolve (and on first use, cache into the eval vars) the metadata path for
|
||||
* the current session. Returns a pointer owned by the RConfig, or NULL. */
|
||||
static const char *resolve_metadata_path(RCore *core) {
|
||||
@@ -373,26 +435,14 @@ static bool find_method_pointers(RCore *core, R2UnityMetadata *meta, const char
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int type_definition_for_type_index(const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index) {
|
||||
if (type_index >= 0) {
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
if (types[i].byvalTypeIndex == type_index) {
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
static bool enrich_reverse_pinvokes_native(RCore *core, R2UnityMetadata *meta, const char *path, R2UnityInterop **items, size_t *count) {
|
||||
R2UnityNativeOptions opts = { 0 };
|
||||
native_options_from_core (core, &opts);
|
||||
return path && *path && r2unity_enrich_reverse_pinvokes_native (meta, path, &opts, items, count);
|
||||
}
|
||||
|
||||
static char *type_name_from_index(R2UnityMetadata *meta, const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index, bool fallback) {
|
||||
int idx = type_definition_for_type_index (types, type_count, type_index);
|
||||
if (idx >= 0) {
|
||||
return r2unity_type_fullname (meta, &types[idx], (size_t)idx, R2U_NAME_FALLBACK_TYPE);
|
||||
}
|
||||
if (fallback && type_index >= 0) {
|
||||
return r_str_newf ("type_index.%d", type_index);
|
||||
}
|
||||
return NULL;
|
||||
return r2unity_type_name_from_index (meta, types, type_count, type_index, fallback);
|
||||
}
|
||||
|
||||
static char *field_name_or_fallback(R2UnityMetadata *meta, const Il2CppFieldDefinition *field, size_t index) {
|
||||
@@ -764,6 +814,8 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
|
||||
size_t method_count = 0;
|
||||
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
|
||||
size_t parameter_count = 0;
|
||||
Il2CppParameterDefinition *parameters = r2unity_get_parameter_definitions (meta, ¶meter_count);
|
||||
size_t img_count = 0;
|
||||
Il2CppImageDefinition *images = r2unity_get_images (meta, &img_count);
|
||||
|
||||
@@ -786,6 +838,9 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
print_native_result (core, &native_result);
|
||||
}
|
||||
|
||||
if (mode != 'j') {
|
||||
apply_native_tables (core, &native_result, mode == '*');
|
||||
}
|
||||
ut64 applied = 0, listed = 0;
|
||||
for (size_t j = 0; j < method_count; j++) {
|
||||
Il2CppMethodDefinition *m = &methods[j];
|
||||
@@ -805,12 +860,16 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
if (!fullname) {
|
||||
continue;
|
||||
}
|
||||
char *signature = r2unity_method_signature (meta, m, td, type_idx,
|
||||
types, type_count, parameters, parameter_count, true);
|
||||
char *return_type = r2unity_type_name_from_index (meta, types, type_count,
|
||||
m->returnType, true);
|
||||
char *imgname = img? r2unity_get_string (meta, img->nameIndex): NULL;
|
||||
char flag_buf[1024];
|
||||
if (imgname && *imgname) {
|
||||
snprintf (flag_buf, sizeof (flag_buf), "sym.unity.%s.%s", imgname, fullname);
|
||||
snprintf (flag_buf, sizeof (flag_buf), "sym.unity.%s.%s.%zu", imgname, fullname, j);
|
||||
} else {
|
||||
snprintf (flag_buf, sizeof (flag_buf), "sym.unity.%s", fullname);
|
||||
snprintf (flag_buf, sizeof (flag_buf), "sym.unity.%s.%zu", fullname, j);
|
||||
}
|
||||
r_name_filter (flag_buf, -1);
|
||||
|
||||
@@ -819,6 +878,33 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
if (mode == 'j') {
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "name", fullname);
|
||||
pj_kn (pj, "index", (ut64)j);
|
||||
pj_hex (pj, "token", m->token, 8);
|
||||
pj_string_or_null (pj, "signature", signature);
|
||||
pj_string_or_null (pj, "return_type", return_type);
|
||||
pj_kb (pj, "static", (m->flags & 0x0010) != 0);
|
||||
pj_ka (pj, "parameters");
|
||||
for (size_t k = 0; k < m->parameterCount; k++) {
|
||||
st64 pi = (st64)m->parameterStart + (st64)k;
|
||||
const Il2CppParameterDefinition *parameter = pi >= 0 && (ut64)pi < parameter_count
|
||||
? ¶meters[pi]: NULL;
|
||||
char *parameter_name = parameter? r2unity_get_string (meta, parameter->nameIndex): NULL;
|
||||
char *parameter_type = parameter
|
||||
? r2unity_type_name_from_index (meta, types, type_count, parameter->typeIndex, true)
|
||||
: NULL;
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", k);
|
||||
if (parameter) {
|
||||
pj_ki (pj, "type_index", parameter->typeIndex);
|
||||
pj_hex (pj, "token", parameter->token, 8);
|
||||
}
|
||||
pj_string_or_null (pj, "name", parameter_name);
|
||||
pj_string_or_null (pj, "type", parameter_type);
|
||||
pj_end (pj);
|
||||
free (parameter_type);
|
||||
free (parameter_name);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_ks (pj, "flag", flag_buf);
|
||||
if (imgname) {
|
||||
pj_ks (pj, "image", imgname);
|
||||
@@ -832,11 +918,11 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
if (addr > 0x1000) {
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'f %s\n", addr, flag_buf);
|
||||
if (imgname && *imgname) {
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: [%s]%s%s %s\n", addr, imgname, *attrs? " ": "", attrs, fullname);
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: [%s]%s%s %s\n", addr, imgname, *attrs? " ": "", attrs, signature? signature: fullname);
|
||||
} else if (*attrs) {
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: %s %s\n", addr, attrs, fullname);
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: %s %s\n", addr, attrs, signature? signature: fullname);
|
||||
} else {
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: %s\n", addr, fullname);
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'CCu Method: %s\n", addr, signature? signature: fullname);
|
||||
}
|
||||
listed++;
|
||||
}
|
||||
@@ -850,11 +936,11 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
imgname,
|
||||
*attrs? " ": "",
|
||||
attrs,
|
||||
fullname);
|
||||
signature? signature: fullname);
|
||||
} else if (*attrs) {
|
||||
comment = r_str_newf ("Method: %s %s", attrs, fullname);
|
||||
comment = r_str_newf ("Method: %s %s", attrs, signature? signature: fullname);
|
||||
} else {
|
||||
comment = r_str_newf ("Method: %s", fullname);
|
||||
comment = r_str_newf ("Method: %s", signature? signature: fullname);
|
||||
}
|
||||
if (comment) {
|
||||
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, addr, comment);
|
||||
@@ -866,6 +952,8 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
|
||||
free (attrs);
|
||||
free (imgname);
|
||||
free (return_type);
|
||||
free (signature);
|
||||
free (fullname);
|
||||
}
|
||||
|
||||
@@ -885,6 +973,7 @@ static int cmd_symbols(RCore *core, char mode) {
|
||||
r2unity_native_result_fini (&native_result);
|
||||
R_FREE (type2img);
|
||||
R_FREE (images);
|
||||
R_FREE (parameters);
|
||||
R_FREE (methods);
|
||||
R_FREE (types);
|
||||
close_metadata (meta, buf);
|
||||
@@ -982,6 +1071,35 @@ static const char *interop_kind_label(uint8_t kind) {
|
||||
}
|
||||
}
|
||||
|
||||
static void pj_interop_wrapper(PJ *pj, const R2UnityInterop *it) {
|
||||
if (it->wrapper_va) {
|
||||
pj_kn (pj, "wrapper_va", it->wrapper_va);
|
||||
}
|
||||
if (it->wrapper_index != UINT32_MAX) {
|
||||
pj_kn (pj, "wrapper_index", it->wrapper_index);
|
||||
}
|
||||
}
|
||||
|
||||
static char *interop_flag_name(const char *prefix, const R2UnityInterop *it) {
|
||||
char *name = it->image_name && *it->image_name
|
||||
? r_str_newf ("%s.%s.%s", prefix, it->image_name, it->name)
|
||||
: r_str_newf ("%s.%s", prefix, it->name);
|
||||
if (name) {
|
||||
r_name_filter (name, -1);
|
||||
if (!r_name_check (name)) {
|
||||
R_FREE (name);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static void print_interop_wrapper_flag(RCore *core, const char *name, ut64 wrapper_va) {
|
||||
if (wrapper_va && name) {
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'f %s\n", wrapper_va, name);
|
||||
r_cons_printf (core->cons, "'@0x%" PFMT64x "'af+ $$ %s\n", wrapper_va, name);
|
||||
}
|
||||
}
|
||||
|
||||
static int cmd_interop(RCore *core, bool reverse, char mode) {
|
||||
RBuffer *buf = NULL;
|
||||
R2UnityMetadata *meta = open_metadata (core, &buf);
|
||||
@@ -992,6 +1110,12 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
|
||||
R2UnityInterop *items = reverse
|
||||
? r2unity_enumerate_reverse_pinvokes (meta, &n)
|
||||
: r2unity_enumerate_pinvokes (meta, &n);
|
||||
if (reverse) {
|
||||
const char *lib = resolve_library_path (core);
|
||||
if (lib) {
|
||||
enrich_reverse_pinvokes_native (core, meta, lib, &items, &n);
|
||||
}
|
||||
}
|
||||
if (items && n > 1) {
|
||||
qsort (items, n, sizeof (R2UnityInterop), interop_cmp);
|
||||
}
|
||||
@@ -1022,6 +1146,7 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
|
||||
pj_kn (pj, "flags", (ut64)it->flags);
|
||||
pj_kn (pj, "iflags", (ut64)it->iflags);
|
||||
pj_kn (pj, "confidence", (ut64)it->confidence);
|
||||
pj_interop_wrapper (pj, it);
|
||||
if (reverse) {
|
||||
pj_ks (pj, "attribute", interop_kind_label (it->kind));
|
||||
} else {
|
||||
@@ -1039,27 +1164,35 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
|
||||
pj_end (pj);
|
||||
} else if (mode == '*') {
|
||||
if (it->name) {
|
||||
char flag_buf[1024];
|
||||
const char *prefix = reverse? "sym.unity.reverse": "sym.unity";
|
||||
if (it->image_name && *it->image_name) {
|
||||
snprintf (flag_buf, sizeof (flag_buf), "%s.%s.%s", prefix, it->image_name, it->name);
|
||||
} else {
|
||||
snprintf (flag_buf, sizeof (flag_buf), "%s.%s", prefix, it->name);
|
||||
char *name = interop_flag_name (prefix, it);
|
||||
if (!name) {
|
||||
free (attrs);
|
||||
continue;
|
||||
}
|
||||
r_name_filter (flag_buf, -1);
|
||||
if (reverse) {
|
||||
r_cons_printf (core->cons, "# ReversePInvoke %s [%s]\n", flag_buf, interop_kind_label (it->kind));
|
||||
r_cons_printf (core->cons, "# ReversePInvoke %s [%s]\n", name, interop_kind_label (it->kind));
|
||||
} else if (it->dll_name) {
|
||||
r_cons_printf (core->cons, "# PInvoke %s -> %s!%s\n", flag_buf, it->dll_name, it->entry_name? it->entry_name: it->name);
|
||||
r_cons_printf (core->cons, "# PInvoke %s -> %s!%s\n", name, it->dll_name, it->entry_name? it->entry_name: it->name);
|
||||
} else {
|
||||
r_cons_printf (core->cons, "# PInvoke %s -> <unresolved>\n", flag_buf);
|
||||
r_cons_printf (core->cons, "# PInvoke %s -> <unresolved>\n", name);
|
||||
}
|
||||
if (reverse) {
|
||||
print_interop_wrapper_flag (core, name, it->wrapper_va);
|
||||
}
|
||||
free (name);
|
||||
}
|
||||
} else {
|
||||
if (reverse) {
|
||||
r_cons_printf (core->cons, "%s\t%s\t%s\t%s\t%u\n", it->image_name? it->image_name: "", it->name? it->name: "", interop_kind_label (it->kind), *attrs? attrs: "-", (unsigned)it->confidence);
|
||||
} else {
|
||||
r_cons_printf (core->cons, "%s\t%s\t%s\t%s\t%s\t%u\n", it->image_name? it->image_name: "", it->name? it->name: "", it->dll_name? it->dll_name: "<unresolved>", it->entry_name? it->entry_name: "<default>", *attrs? attrs: "-", (unsigned)it->confidence);
|
||||
r_cons_printf (core->cons, "%s\t%s\t%s\t%s\t%s\t%u\n",
|
||||
it->image_name? it->image_name: "",
|
||||
it->name? it->name: "",
|
||||
it->dll_name? it->dll_name: "<unresolved>",
|
||||
it->entry_name? it->entry_name: "<default>",
|
||||
*attrs? attrs: "-",
|
||||
(unsigned)it->confidence);
|
||||
}
|
||||
}
|
||||
free (attrs);
|
||||
@@ -1110,7 +1243,7 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
|
||||
|
||||
/* "r2unity" alone, or "r2unity?" */
|
||||
if (*rest == 0 || *rest == '?') {
|
||||
r_core_cmd_help (core, g_help_msg);
|
||||
r_cons_cmd_help (core->cons, g_help_msg);
|
||||
return true;
|
||||
}
|
||||
if (*rest != '-') {
|
||||
@@ -1128,7 +1261,7 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
|
||||
rest++;
|
||||
}
|
||||
if (*rest && *rest != ' ' && *rest != '\t') {
|
||||
r_core_cmd_help (core, g_help_msg);
|
||||
r_cons_cmd_help (core->cons, g_help_msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1155,10 +1288,10 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
|
||||
return cmd_sbom (core, mode) == 0;
|
||||
case '?':
|
||||
case 'h':
|
||||
r_core_cmd_help (core, g_help_msg);
|
||||
r_cons_cmd_help (core->cons, g_help_msg);
|
||||
return true;
|
||||
default:
|
||||
r_core_cmd_help (core, g_help_msg);
|
||||
r_cons_cmd_help (core->cons, g_help_msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user