12 Commits
18 changed files with 3984 additions and 90 deletions

No files matched your search

+76 -5
View File
@@ -29,7 +29,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
- name: Build
run: make
@@ -50,7 +50,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
- name: Install deps
run: pip install meson ninja --break-system-packages
@@ -73,7 +73,7 @@ jobs:
- name: Install radare2 from git
id: r2
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
with:
from-git: true
@@ -98,7 +98,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
- name: Build Debian package
run: make -C dist/debian SUDO=
@@ -110,6 +110,69 @@ 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@v6
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
- 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: Upload Windows package artifact
uses: actions/upload-artifact@v7
with:
name: windows-package
path: ${{ steps.package.outputs.archive }}
if-no-files-found: error
release:
name: Publish release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
@@ -117,6 +180,7 @@ jobs:
- build
- meson
- debian
- windows
runs-on: ubuntu-latest
permissions:
contents: write
@@ -126,13 +190,19 @@ jobs:
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: Generate release notes
run: ./release.sh notes > release-notes.md
@@ -147,3 +217,4 @@ jobs:
fail_on_unmatched_files: true
files: |
dist/artifacts/*.deb
dist/artifacts/*.zip
+6 -3
View File
@@ -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
@@ -67,7 +70,7 @@ $(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 +78,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
+52 -2
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -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
View File
@@ -1,7 +1,7 @@
project(
'r2unity',
'c',
version: '1.2.2',
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
+389
View File
@@ -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);
}
+53
View File
@@ -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
+125
View File
@@ -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
? &parameters[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);
+9
View File
@@ -130,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;
@@ -264,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);
+154
View File
@@ -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);
}
+29
View File
@@ -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
+601
View File
@@ -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);
}
+74
View File
@@ -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
-1
View File
@@ -5,7 +5,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <r2unity_config.h>
#include "lib/lib.h"
+1221 -38
View File
File diff suppressed because it is too large. Load diff
+49 -31
View File
@@ -441,26 +441,8 @@ static bool enrich_reverse_pinvokes_native(RCore *core, R2UnityMetadata *meta, c
return path && *path && r2unity_enrich_reverse_pinvokes_native (meta, path, &opts, items, count);
}
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 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) {
@@ -832,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, &parameter_count);
size_t img_count = 0;
Il2CppImageDefinition *images = r2unity_get_images (meta, &img_count);
@@ -876,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);
@@ -890,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
? &parameters[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);
@@ -903,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++;
}
@@ -921,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);
@@ -937,6 +952,8 @@ static int cmd_symbols(RCore *core, char mode) {
free (attrs);
free (imgname);
free (return_type);
free (signature);
free (fullname);
}
@@ -956,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);
@@ -1225,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 != '-') {
@@ -1243,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;
}
}
@@ -1270,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;
}
}