mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-10 17:42:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4f63aef69 | ||
|
|
ca5867eb5c | ||
|
|
d9fae9a6f0 | ||
|
|
095d602186 | ||
|
|
b8a38d8c8d | ||
|
|
853786de3e |
No files matched your search
@@ -106,9 +106,9 @@ jobs:
|
||||
- name: Upload Debian package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: debian-package
|
||||
path: dist/debian/*.deb
|
||||
if-no-files-found: error
|
||||
archive: false
|
||||
|
||||
release:
|
||||
name: Publish release
|
||||
@@ -129,7 +129,9 @@ jobs:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: '*.deb'
|
||||
path: dist/artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate release notes
|
||||
run: ./release.sh notes > release-notes.md
|
||||
@@ -142,5 +144,6 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
body_path: release-notes.md
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
dist/artifacts/debian-package/*.deb
|
||||
dist/artifacts/*.deb
|
||||
@@ -40,7 +40,7 @@ R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
|
||||
all: $(EXEC)
|
||||
|
||||
plugin: $(PLUGINS)
|
||||
r2 plugin: $(PLUGINS)
|
||||
|
||||
install-plugin: $(PLUGINS)
|
||||
@[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2unity',
|
||||
'c',
|
||||
version: '1.0.0',
|
||||
version: '1.1.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'warning_level=2',
|
||||
|
||||
+20
-3
@@ -5,6 +5,14 @@
|
||||
# $ sys/release-notes.sh 4.5.0 4.5.1 # from 4.5.0 to 4.5.1
|
||||
#
|
||||
|
||||
release_log() {
|
||||
if [ -n "$PREV" ]; then
|
||||
git log ${PREV}..${VERS} > .l
|
||||
else
|
||||
git log ${VERS} > .l
|
||||
fi
|
||||
}
|
||||
|
||||
release_notes() {
|
||||
if [ -n "`git log -n 1 | grep Release`" ]; then
|
||||
VERS=`git tag --sort=committerdate | grep -v conti | tail -n 1`
|
||||
@@ -16,15 +24,24 @@ fi
|
||||
|
||||
[ -n "$1" ] && PREV="$1"
|
||||
[ -n "$2" ] && VERS="$2"
|
||||
[ -z "$PREV" ] && PREV="$(git rev-list --max-parents=0 HEAD)"
|
||||
FIRST_RELEASE=0
|
||||
if [ "$PREV" = "$VERS" ]; then
|
||||
if [ "`git tag | wc -l | awk '{print $1}'`" = 1 ]; then
|
||||
PREV=
|
||||
FIRST_RELEASE=1
|
||||
else
|
||||
PREV=`git tag --sort=committerdate | grep -v conti | tail -n 2 | head -n1`
|
||||
fi
|
||||
fi
|
||||
[ -z "$PREV" ] && [ "$FIRST_RELEASE" = 0 ] && PREV="$(git rev-list --max-parents=0 HEAD)"
|
||||
|
||||
|
||||
git log ${PREV}..${VERS} > .l
|
||||
release_log
|
||||
# When HEAD contains a tag do this magic
|
||||
if [ ! -s .l ]; then
|
||||
VERS=$PREV
|
||||
PREV=`git tag --sort=committerdate | grep -v conti | tail -n 2 | head -n1`
|
||||
git log ${PREV}..${VERS} > .l
|
||||
release_log
|
||||
fi
|
||||
grep ^Author .l | cut -d : -f 2- | sed -e 's,radare,pancake,' | sort -u > .A
|
||||
CODENAME="`git log | grep -i 'Release' | head -n 1 | cut -d - -f 2- | cut -d ' ' -f 3-`"
|
||||
|
||||
@@ -796,6 +796,51 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions(R2UnityMetadata *me
|
||||
return methods;
|
||||
}
|
||||
|
||||
R_API Il2CppFieldDefinition *r2unity_get_field_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 = field_definition_entry_size (meta);
|
||||
ut8 *buf = read_metadata_table (meta, R2U_SEC_FIELDS, entry, count);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
}
|
||||
Il2CppFieldDefinition *fields = R_NEWS (Il2CppFieldDefinition, *count);
|
||||
if (!fields) {
|
||||
R_FREE (buf);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
const ut8 *p = buf + i * entry;
|
||||
fields[i].nameIndex = read_u32p (&p);
|
||||
fields[i].typeIndex = read_indexp (&p, type_index_size);
|
||||
fields[i].token = read_u32p (&p);
|
||||
}
|
||||
R_FREE (buf);
|
||||
return fields;
|
||||
}
|
||||
|
||||
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);
|
||||
const int type_index_size = compact_indices? meta->typeIndexSize: 4;
|
||||
ut8 *buf = read_metadata_table (meta, id, type_index_size, count);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
}
|
||||
int32_t *out = R_NEWS (int32_t, *count);
|
||||
if (!out) {
|
||||
R_FREE (buf);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
const ut8 *p = buf + i * type_index_size;
|
||||
out[i] = read_indexp (&p, type_index_size);
|
||||
}
|
||||
R_FREE (buf);
|
||||
return out;
|
||||
}
|
||||
|
||||
R_API Il2CppImageDefinition *r2unity_get_images(R2UnityMetadata *meta, size_t *count) {
|
||||
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
|
||||
*count = 0;
|
||||
|
||||
@@ -119,6 +119,12 @@ typedef struct {
|
||||
uint16_t parameterCount;
|
||||
} Il2CppMethodDefinition;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nameIndex;
|
||||
int32_t typeIndex;
|
||||
uint32_t token;
|
||||
} Il2CppFieldDefinition;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nameIndex;
|
||||
int32_t assemblyIndex;
|
||||
@@ -203,6 +209,8 @@ R_API Il2CppStringLiteral *r2unity_get_string_literals(R2UnityMetadata *meta, si
|
||||
R_API bool r2unity_read_string_literal(R2UnityMetadata *meta, const Il2CppStringLiteral *lit, ut8 **out_bytes, size_t *out_len);
|
||||
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 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);
|
||||
R_API int r2unity_image_index_for_method(const int *type2img, size_t type_count, const Il2CppMethodDefinition *m);
|
||||
|
||||
+445
-11
@@ -60,15 +60,15 @@ static void print_usage(FILE *out, const char *prog_name) {
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -a 0xADDR Read the method pointer table starting at virtual address 0xADDR\n"
|
||||
" -c N Read N pointer entries (pair with -a)\n"
|
||||
" -c Enumerate classes, inheritance, methods, and fields\n"
|
||||
" -D Detect companion files from the given executable path and exit\n"
|
||||
" -f Fast path: auto-detect ELF/Mach-O/PE and scan method pointers\n"
|
||||
" -h Show this help and exit\n"
|
||||
" -j One-line JSON status, or JSON output with -P/-R/-S\n"
|
||||
" -j One-line JSON status, or JSON output with -c/-P/-R/-S\n"
|
||||
" -l N Limit emitted entries to N\n"
|
||||
" -P Enumerate P/Invoke (managed -> native) methods\n"
|
||||
" -q Quiet mode: omit banner and informational comments\n"
|
||||
" -r Emit r2 script commands (flags + comments); pairs with -P\n"
|
||||
" -r Emit r2 script commands; pairs with -c/-P/-R\n"
|
||||
" -R Enumerate reverse-P/Invoke (native -> managed) methods (v29+)\n"
|
||||
" -S Emit a text SBOM of the managed assemblies\n"
|
||||
" -V Verbose debug tracing on stderr\n"
|
||||
@@ -78,6 +78,7 @@ static void print_usage(FILE *out, const char *prog_name) {
|
||||
"Arguments:\n"
|
||||
" <executable> Native IL2CPP binary for the target platform\n"
|
||||
" <global-metadata.dat> Unity IL2CPP metadata file\n"
|
||||
" -c and -z also accept metadata-only input\n"
|
||||
"\n"
|
||||
"Expected files per target platform:\n"
|
||||
" iOS build: UnityFramework + global-metadata.dat\n"
|
||||
@@ -486,6 +487,420 @@ static bool emit_r2_method(R2UnityMetadata *meta,
|
||||
return true;
|
||||
}
|
||||
|
||||
static int type_definition_for_type_index(const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index) {
|
||||
if (type_index < 0) {
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
static char *field_name_or_fallback(R2UnityMetadata *meta, const Il2CppFieldDefinition *field, size_t index) {
|
||||
char *name = r2unity_get_string (meta, field->nameIndex);
|
||||
if (name && *name) {
|
||||
return name;
|
||||
}
|
||||
free (name);
|
||||
return r_str_newf ("field.%zu", index);
|
||||
}
|
||||
|
||||
static char *method_name_or_fallback(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, size_t index) {
|
||||
char *name = r2unity_get_string (meta, method->nameIndex);
|
||||
if (name && *name) {
|
||||
return name;
|
||||
}
|
||||
free (name);
|
||||
return r_str_newf ("method.%zu", index);
|
||||
}
|
||||
|
||||
static void sanitize_ic_name(char *s) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
r_name_filter (s, -1);
|
||||
for (char *p = s; *p; p++) {
|
||||
if (*p == '.' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
|
||||
*p = '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char *r2_class_name(const char *name, size_t index) {
|
||||
char *out = (name && *name)? strdup (name): r_str_newf ("type_%zu", index);
|
||||
sanitize_ic_name (out);
|
||||
if (!out || !*out) {
|
||||
free (out);
|
||||
out = r_str_newf ("type_%zu", index);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *r2_method_name(const char *name, size_t index) {
|
||||
char *out = (name && *name)? strdup (name): r_str_newf ("method_%zu", index);
|
||||
sanitize_ic_name (out);
|
||||
if (!out || !*out) {
|
||||
free (out);
|
||||
out = r_str_newf ("method_%zu", index);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void pj_hex(PJ *pj, const char *key, ut64 value, int width) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (!sb) {
|
||||
pj_knull (pj, key);
|
||||
return;
|
||||
}
|
||||
r_strbuf_appendf (sb, "0x%0*" PFMT64x, width, value);
|
||||
char *s = r_strbuf_drain (sb);
|
||||
pj_ks (pj, key, s? s: "");
|
||||
free (s);
|
||||
}
|
||||
|
||||
static void pj_string_or_null(PJ *pj, const char *key, const char *value) {
|
||||
if (value) {
|
||||
pj_ks (pj, key, value);
|
||||
} else {
|
||||
pj_knull (pj, key);
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_class_text(R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const int32_t *interfaces,
|
||||
size_t interface_count,
|
||||
size_t type_index) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
|
||||
printf ("class %zu %s", type_index, name? name: "-");
|
||||
if (base) {
|
||||
printf (" : %s", base);
|
||||
}
|
||||
if (td->interfaces_count > 0 && td->interfacesStart >= 0) {
|
||||
bool first = true;
|
||||
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
|
||||
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], true);
|
||||
if (iname) {
|
||||
printf ("%s%s", first? " implements ": ", ", iname);
|
||||
first = false;
|
||||
}
|
||||
free (iname);
|
||||
}
|
||||
}
|
||||
printf (" token=0x%08x fields=%u methods=%u\n", td->token, (unsigned)td->field_count, (unsigned)td->method_count);
|
||||
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
printf (" field %zu %s %s token=0x%08x type_index=%d\n",
|
||||
fi,
|
||||
ftype? ftype: "-",
|
||||
fname? fname: "-",
|
||||
fields[fi].token,
|
||||
fields[fi].typeIndex);
|
||||
free (ftype);
|
||||
free (fname);
|
||||
}
|
||||
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *attrs = method_attrs (methods[mi].flags);
|
||||
printf (" method %zu %s%s%s(%u) token=0x%08x flags=0x%04x\n",
|
||||
mi,
|
||||
attrs && *attrs? attrs: "",
|
||||
attrs && *attrs? " ": "",
|
||||
mname? mname: "-",
|
||||
(unsigned)methods[mi].parameterCount,
|
||||
methods[mi].token,
|
||||
methods[mi].flags);
|
||||
free (attrs);
|
||||
free (mname);
|
||||
}
|
||||
free (base);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static void emit_class_json(PJ *pj,
|
||||
R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const int32_t *interfaces,
|
||||
size_t interface_count,
|
||||
const ut64 *method_ptrs,
|
||||
bool has_ptrs,
|
||||
size_t type_index) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *ns = r2unity_get_string (meta, td->namespaceIndex);
|
||||
char *short_name = r2unity_get_string (meta, td->nameIndex);
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)type_index);
|
||||
pj_string_or_null (pj, "name", name);
|
||||
pj_string_or_null (pj, "namespace", ns);
|
||||
pj_string_or_null (pj, "short_name", short_name);
|
||||
pj_string_or_null (pj, "base", base);
|
||||
pj_ki (pj, "base_type_index", td->parentIndex);
|
||||
pj_hex (pj, "flags", td->flags, 8);
|
||||
pj_hex (pj, "token", td->token, 8);
|
||||
|
||||
pj_ka (pj, "interfaces");
|
||||
if (td->interfaces_count > 0 && td->interfacesStart >= 0) {
|
||||
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
|
||||
int32_t idx = interfaces[td->interfacesStart + k];
|
||||
char *iname = type_name_from_index (meta, types, type_count, idx, true);
|
||||
pj_o (pj);
|
||||
pj_ki (pj, "type_index", idx);
|
||||
pj_string_or_null (pj, "name", iname);
|
||||
pj_end (pj);
|
||||
free (iname);
|
||||
}
|
||||
}
|
||||
pj_end (pj);
|
||||
|
||||
pj_ka (pj, "fields");
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)fi);
|
||||
pj_string_or_null (pj, "name", fname);
|
||||
pj_ki (pj, "type_index", fields[fi].typeIndex);
|
||||
pj_string_or_null (pj, "type", ftype);
|
||||
pj_hex (pj, "token", fields[fi].token, 8);
|
||||
pj_end (pj);
|
||||
free (ftype);
|
||||
free (fname);
|
||||
}
|
||||
pj_end (pj);
|
||||
|
||||
pj_ka (pj, "methods");
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *fullname = r2unity_method_fullname (meta, &methods[mi], td, type_index, R2U_NAME_WITH_PARAMS | R2U_NAME_FALLBACK_TYPE);
|
||||
char *attrs = method_attrs (methods[mi].flags);
|
||||
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)mi);
|
||||
pj_string_or_null (pj, "name", mname);
|
||||
pj_string_or_null (pj, "full_name", fullname);
|
||||
pj_kn (pj, "parameter_count", (ut64)methods[mi].parameterCount);
|
||||
pj_kn (pj, "addr", addr);
|
||||
pj_hex (pj, "token", methods[mi].token, 8);
|
||||
pj_hex (pj, "flags", methods[mi].flags, 4);
|
||||
pj_hex (pj, "iflags", methods[mi].iflags, 4);
|
||||
pj_string_or_null (pj, "attrs", (attrs && *attrs)? attrs: NULL);
|
||||
pj_end (pj);
|
||||
free (attrs);
|
||||
free (fullname);
|
||||
free (mname);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
free (base);
|
||||
free (short_name);
|
||||
free (ns);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static void emit_class_r2(R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const ut64 *method_ptrs,
|
||||
bool has_ptrs,
|
||||
size_t type_index,
|
||||
bool quiet) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *r2klass = r2_class_name (name, type_index);
|
||||
if (!quiet) {
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, false);
|
||||
if (base) {
|
||||
printf ("# class %s : %s\n", name? name: r2klass, base);
|
||||
} else {
|
||||
printf ("# class %s\n", name? name: r2klass);
|
||||
}
|
||||
free (base);
|
||||
}
|
||||
printf ("ic+%s @ 0\n", r2klass);
|
||||
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
|
||||
if (addr <= 0x1000) {
|
||||
continue;
|
||||
}
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *r2meth = r2_method_name (mname, mi);
|
||||
printf ("ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
|
||||
free (r2meth);
|
||||
free (mname);
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
printf ("# field %s.%s %s type_index=%d token=0x%08x\n",
|
||||
r2klass,
|
||||
fname? fname: "-",
|
||||
ftype? ftype: "-",
|
||||
fields[fi].typeIndex,
|
||||
fields[fi].token);
|
||||
free (ftype);
|
||||
free (fname);
|
||||
}
|
||||
}
|
||||
free (r2klass);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static int emit_classes(R2UnityMetadata *meta, const char *exe_path, const char *metadata_path, bool as_json, bool as_r2, bool fast, bool quiet, long limit) {
|
||||
if (as_json && as_r2) {
|
||||
R_LOG_ERROR ("-j and -r are mutually exclusive with -c");
|
||||
return 1;
|
||||
}
|
||||
if (fast && (!exe_path || !*exe_path)) {
|
||||
R_LOG_ERROR ("-c -f requires both executable and metadata paths");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t type_count = 0;
|
||||
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
|
||||
if (!types) {
|
||||
R_LOG_ERROR ("unable to decode type definitions");
|
||||
return 1;
|
||||
}
|
||||
size_t method_count = 0;
|
||||
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
|
||||
size_t field_count = 0;
|
||||
Il2CppFieldDefinition *fields = r2unity_get_field_definitions (meta, &field_count);
|
||||
size_t interface_count = 0;
|
||||
int32_t *interfaces = r2unity_get_type_index_table (meta, R2U_SEC_INTERFACES, &interface_count);
|
||||
|
||||
ut64 *method_ptrs = NULL;
|
||||
bool has_ptrs = false;
|
||||
if (fast) {
|
||||
has_ptrs = find_method_pointers_fast (meta, exe_path, &method_ptrs);
|
||||
}
|
||||
if (method_ptrs && !has_ptrs) {
|
||||
for (size_t k = 0; k < method_count; k++) {
|
||||
if (method_ptrs[k]) {
|
||||
has_ptrs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t max = type_count;
|
||||
if (limit >= 0 && (size_t)limit < max) {
|
||||
max = (size_t)limit;
|
||||
}
|
||||
|
||||
if (as_json) {
|
||||
PJ *pj = pj_new ();
|
||||
if (!pj) {
|
||||
R_LOG_ERROR ("unable to allocate JSON builder");
|
||||
R_FREE (method_ptrs);
|
||||
R_FREE (interfaces);
|
||||
R_FREE (fields);
|
||||
R_FREE (methods);
|
||||
R_FREE (types);
|
||||
return 1;
|
||||
}
|
||||
pj_o (pj);
|
||||
pj_kb (pj, "ok", true);
|
||||
pj_ki (pj, "version", meta->version);
|
||||
pj_ks (pj, "unity_range", r2unity_unity_range_from_wire (meta->version));
|
||||
pj_kb (pj, "has_ptrs", has_ptrs);
|
||||
pj_kn (pj, "types", (ut64)type_count);
|
||||
pj_kn (pj, "methods", (ut64)method_count);
|
||||
pj_kn (pj, "fields", (ut64)field_count);
|
||||
pj_ka (pj, "classes");
|
||||
for (size_t i = 0; i < max; i++) {
|
||||
emit_class_json (pj, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
char *out = pj_drain (pj);
|
||||
if (out) {
|
||||
puts (out);
|
||||
free (out);
|
||||
}
|
||||
} else if (as_r2) {
|
||||
if (!quiet) {
|
||||
printf ("# r2 script generated by r2unity -c\n");
|
||||
printf ("# Input file: %s\n", metadata_path && *metadata_path? metadata_path: "-");
|
||||
if (!has_ptrs) {
|
||||
printf ("# Method ic+ entries need native addresses; pass -f with an executable to recover them.\n");
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < max; i++) {
|
||||
emit_class_r2 (meta, types, type_count, methods, method_count, fields, field_count, method_ptrs, has_ptrs, i, quiet);
|
||||
}
|
||||
} else {
|
||||
if (!quiet) {
|
||||
printf ("# classes from %s\n", metadata_path && *metadata_path? metadata_path: "-");
|
||||
printf ("# wire_version=%d (%s) types=%zu methods=%zu fields=%zu\n",
|
||||
meta->version,
|
||||
r2unity_unity_range_from_wire (meta->version),
|
||||
type_count,
|
||||
method_count,
|
||||
field_count);
|
||||
}
|
||||
for (size_t i = 0; i < max; i++) {
|
||||
emit_class_text (meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, i);
|
||||
}
|
||||
}
|
||||
|
||||
R_FREE (method_ptrs);
|
||||
R_FREE (interfaces);
|
||||
R_FREE (fields);
|
||||
R_FREE (methods);
|
||||
R_FREE (types);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool json_one_line = false;
|
||||
bool r2_script = false;
|
||||
@@ -493,16 +908,17 @@ int main(int argc, char *argv[]) {
|
||||
bool debug = false;
|
||||
long limit = -1;
|
||||
ut64 gmp_addr = 0;
|
||||
size_t gmp_count = 0;
|
||||
bool fast = false;
|
||||
bool sbom = false;
|
||||
bool pinvokes = false;
|
||||
bool reverse_pinvokes = false;
|
||||
bool string_literals = false;
|
||||
bool detect_paths = false;
|
||||
bool classes = false;
|
||||
int opt;
|
||||
while ((opt = getopt (argc, argv, "hjrqfVvSPRDzl:a:c:")) != -1) {
|
||||
while ((opt = getopt (argc, argv, "chjrqfVvSPRDzl:a:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'c': classes = true; break;
|
||||
case 'j': json_one_line = true; break;
|
||||
case 'r': r2_script = true; break;
|
||||
case 'q': quiet = true; break;
|
||||
@@ -518,7 +934,6 @@ int main(int argc, char *argv[]) {
|
||||
case 'z': string_literals = true; break;
|
||||
case 'l': limit = strtol (optarg, NULL, 0); break;
|
||||
case 'a': gmp_addr = (ut64)strtoull (optarg, NULL, 0); break;
|
||||
case 'c': gmp_count = (size_t)strtoull (optarg, NULL, 0); break;
|
||||
case 'h':
|
||||
print_usage (stdout, argv[0]);
|
||||
return 0;
|
||||
@@ -538,9 +953,22 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
return emit_detected_paths (argv[optind], json_one_line);
|
||||
}
|
||||
if (string_literals) {
|
||||
if (classes) {
|
||||
if (sbom || pinvokes || reverse_pinvokes || string_literals) {
|
||||
R_LOG_ERROR ("-c cannot be combined with -S, -P, -R or -z");
|
||||
return 1;
|
||||
}
|
||||
if (argc - optind != 1 && argc - optind != 2) {
|
||||
print_usage (stderr, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (fast && argc - optind != 2) {
|
||||
R_LOG_ERROR ("-c -f requires both executable and metadata paths");
|
||||
return 1;
|
||||
}
|
||||
} else if (string_literals) {
|
||||
if (json_one_line || fast || gmp_addr || sbom || pinvokes || reverse_pinvokes) {
|
||||
R_LOG_ERROR ("-z cannot be combined with -j, -f, -a/-c, -S, -P or -R");
|
||||
R_LOG_ERROR ("-z cannot be combined with -j, -f, -a, -S, -P or -R");
|
||||
return 1;
|
||||
}
|
||||
if (argc - optind != 1 && argc - optind != 2) {
|
||||
@@ -554,7 +982,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
const char *exe_path = NULL;
|
||||
const char *metadata_path = NULL;
|
||||
if (string_literals && argc - optind == 1) {
|
||||
if ((string_literals || classes) && argc - optind == 1) {
|
||||
exe_path = "";
|
||||
metadata_path = argv[optind];
|
||||
} else {
|
||||
@@ -607,6 +1035,13 @@ int main(int argc, char *argv[]) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (classes) {
|
||||
int rc = emit_classes (meta, exe_path, metadata_path, json_one_line, r2_script, fast, quiet, limit);
|
||||
r2unity_free_metadata (meta);
|
||||
r_unref (buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (string_literals) {
|
||||
int rc = emit_string_literals (meta, metadata_path, quiet, limit);
|
||||
r2unity_free_metadata (meta);
|
||||
@@ -623,8 +1058,7 @@ int main(int argc, char *argv[]) {
|
||||
ut64 *method_ptrs = NULL;
|
||||
bool has_ptrs = false;
|
||||
if (gmp_addr) {
|
||||
R_LOG_WARN ("Manual method-pointer table reading (-a/-c) is not implemented");
|
||||
(void)gmp_count;
|
||||
R_LOG_WARN ("Manual method-pointer table reading (-a) is not implemented");
|
||||
} else if (fast) {
|
||||
has_ptrs = find_method_pointers_fast (meta, exe_path, &method_ptrs);
|
||||
}
|
||||
|
||||
+54
-14
@@ -16,6 +16,12 @@
|
||||
#define R2UNITY_BIN_VEC_ABI 0
|
||||
#endif
|
||||
|
||||
#if R2_ABIVERSION >= 104
|
||||
#define R2UNITY_BIN_STRINGS_VEC_ABI 1
|
||||
#else
|
||||
#define R2UNITY_BIN_STRINGS_VEC_ABI 0
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
R2UnityMetadata *meta;
|
||||
RBuffer *buf;
|
||||
@@ -225,9 +231,6 @@ static void section_free(void *p) {
|
||||
|
||||
static RBinSection *new_section(const char *name, ut64 off, ut64 size, bool has_strings) {
|
||||
RBinSection *sec = R_NEW0 (RBinSection);
|
||||
if (!sec) {
|
||||
return NULL;
|
||||
}
|
||||
init_section (sec, name, off, size, has_strings);
|
||||
return sec;
|
||||
}
|
||||
@@ -505,7 +508,7 @@ static bool symbols_vec(RBinFile *bf) {
|
||||
#else
|
||||
static RList *symbols(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_symbol_free);
|
||||
if (!ret || !symbols_fill (bf, ret)) {
|
||||
if (!symbols_fill (bf, ret)) {
|
||||
r_list_free (ret);
|
||||
return NULL;
|
||||
}
|
||||
@@ -572,7 +575,33 @@ static RList *classes(RBinFile *bf) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void append_bin_string(RList *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
|
||||
#if R2UNITY_BIN_STRINGS_VEC_ABI
|
||||
typedef RVecRBinString R2UnityStringSink;
|
||||
|
||||
static void append_bin_string(R2UnityStringSink *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
RBinString *str = RVecRBinString_emplace_back (ret);
|
||||
if (!str) {
|
||||
free (text);
|
||||
return;
|
||||
}
|
||||
str->string = text;
|
||||
str->paddr = paddr;
|
||||
str->vaddr = paddr;
|
||||
str->size = size;
|
||||
str->length = strlen (text);
|
||||
str->ordinal = ordinal;
|
||||
str->type = R_STRING_TYPE_UTF8;
|
||||
}
|
||||
#else
|
||||
typedef RList R2UnityStringSink;
|
||||
|
||||
static void append_bin_string(R2UnityStringSink *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
RBinString *str = R_NEW0 (RBinString);
|
||||
str->string = text;
|
||||
str->paddr = paddr;
|
||||
@@ -583,8 +612,9 @@ static void append_bin_string(RList *ret, char *text, ut64 paddr, ut32 size, ut3
|
||||
str->type = R_STRING_TYPE_UTF8;
|
||||
r_list_append (ret, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void add_metadata_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
|
||||
static void add_metadata_strings(R2UnityBinObj *obj, R2UnityStringSink *ret, ut32 *ordinal) {
|
||||
if (!obj->strings || !obj->strings_size) {
|
||||
return;
|
||||
}
|
||||
@@ -608,7 +638,7 @@ static void add_metadata_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
static void add_literal_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
|
||||
static void add_literal_strings(R2UnityBinObj *obj, R2UnityStringSink *ret, ut32 *ordinal) {
|
||||
size_t count = 0;
|
||||
Il2CppStringLiteral *lits = r2unity_get_string_literals (obj->meta, &count);
|
||||
for (size_t i = 0; lits && i < count; i++) {
|
||||
@@ -626,6 +656,20 @@ static void add_literal_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
|
||||
R_FREE (lits);
|
||||
}
|
||||
|
||||
#if R2UNITY_BIN_STRINGS_VEC_ABI
|
||||
static RVecRBinString *strings(RBinFile *bf) {
|
||||
R2UnityBinObj *obj = get_obj (bf);
|
||||
R_RETURN_VAL_IF_FAIL (obj, NULL);
|
||||
RVecRBinString *ret = RVecRBinString_new ();
|
||||
if (!ret) {
|
||||
return NULL;
|
||||
}
|
||||
ut32 ordinal = 0;
|
||||
add_metadata_strings (obj, ret, &ordinal);
|
||||
add_literal_strings (obj, ret, &ordinal);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static RList *strings(RBinFile *bf) {
|
||||
R2UnityBinObj *obj = get_obj (bf);
|
||||
R_RETURN_VAL_IF_FAIL (obj, NULL);
|
||||
@@ -635,14 +679,12 @@ static RList *strings(RBinFile *bf) {
|
||||
add_literal_strings (obj, ret, &ordinal);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if R2UNITY_BIN_VEC_ABI
|
||||
typedef RVecRBinImport R2UnityImportSink;
|
||||
|
||||
static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
|
||||
if (!imp) {
|
||||
return;
|
||||
}
|
||||
RBinImport *dst = RVecRBinImport_emplace_back (ret);
|
||||
if (!dst) {
|
||||
r_bin_import_free (imp);
|
||||
@@ -655,9 +697,7 @@ static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
|
||||
typedef RList R2UnityImportSink;
|
||||
|
||||
static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
|
||||
if (imp) {
|
||||
r_list_append (ret, imp);
|
||||
}
|
||||
r_list_append (ret, imp);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -690,7 +730,7 @@ static bool imports_vec(RBinFile *bf) {
|
||||
#else
|
||||
static RList *imports(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_import_free);
|
||||
if (!ret || !imports_fill (bf, ret)) {
|
||||
if (!imports_fill (bf, ret)) {
|
||||
r_list_free (ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ static const char *g_help_msg[] = {
|
||||
"Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer",
|
||||
"r2unity", "", "show this help",
|
||||
"r2unity?", "", "show this help",
|
||||
"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",
|
||||
@@ -284,6 +285,415 @@ static bool find_method_pointers(R2UnityMetadata *meta, const char *path, ut64 *
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static char *field_name_or_fallback(R2UnityMetadata *meta, const Il2CppFieldDefinition *field, size_t index) {
|
||||
char *name = r2unity_get_string (meta, field->nameIndex);
|
||||
if (R_STR_ISNOTEMPTY (name)) {
|
||||
return name;
|
||||
}
|
||||
free (name);
|
||||
return r_str_newf ("field.%zu", index);
|
||||
}
|
||||
|
||||
static char *method_name_or_fallback(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, size_t index) {
|
||||
char *name = r2unity_get_string (meta, method->nameIndex);
|
||||
if (R_STR_ISNOTEMPTY (name)) {
|
||||
return name;
|
||||
}
|
||||
free (name);
|
||||
return r_str_newf ("method.%zu", index);
|
||||
}
|
||||
|
||||
static void sanitize_ic_name(char *s) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
r_name_filter (s, -1);
|
||||
for (char *p = s; *p; p++) {
|
||||
if (*p == '.' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
|
||||
*p = '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char *r2_class_name(const char *name, size_t index) {
|
||||
char *out = (name && *name)? strdup (name): r_str_newf ("type_%zu", index);
|
||||
sanitize_ic_name (out);
|
||||
if (!out || !*out) {
|
||||
free (out);
|
||||
out = r_str_newf ("type_%zu", index);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *r2_method_name(const char *name, size_t index) {
|
||||
char *out = (name && *name)? r_str_newf ("%s_%zu", name, index): r_str_newf ("method_%zu", index);
|
||||
sanitize_ic_name (out);
|
||||
if (!out || !*out) {
|
||||
free (out);
|
||||
out = r_str_newf ("method_%zu", index);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *r2_field_name(const char *name, size_t index) {
|
||||
char *out = (name && *name)? strdup (name): r_str_newf ("field_%zu", index);
|
||||
sanitize_ic_name (out);
|
||||
if (!out || !*out) {
|
||||
free (out);
|
||||
out = r_str_newf ("field_%zu", index);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void pj_hex(PJ *pj, const char *key, ut64 value, int width) {
|
||||
char hex[64];
|
||||
snprintf (hex, sizeof (hex), "0x%0*" PFMT64x, width, value);
|
||||
pj_ks (pj, key, hex);
|
||||
}
|
||||
|
||||
static void pj_string_or_null(PJ *pj, const char *key, const char *value) {
|
||||
if (value) {
|
||||
pj_ks (pj, key, value);
|
||||
} else {
|
||||
pj_knull (pj, key);
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_class_text(RCore *core,
|
||||
R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const int32_t *interfaces,
|
||||
size_t interface_count,
|
||||
size_t type_index) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
|
||||
r_cons_printf (core->cons, "class %zu %s", type_index, name? name: "-");
|
||||
if (base) {
|
||||
r_cons_printf (core->cons, " : %s", base);
|
||||
}
|
||||
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
|
||||
bool first = true;
|
||||
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
|
||||
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], true);
|
||||
if (iname) {
|
||||
r_cons_printf (core->cons, "%s%s", first? " implements ": ", ", iname);
|
||||
first = false;
|
||||
}
|
||||
free (iname);
|
||||
}
|
||||
}
|
||||
r_cons_printf (core->cons, " token=0x%08x fields=%u methods=%u\n", td->token, (unsigned)td->field_count, (unsigned)td->method_count);
|
||||
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
r_cons_printf (core->cons, " field %zu %s %s token=0x%08x type_index=%d\n",
|
||||
fi,
|
||||
ftype? ftype: "-",
|
||||
fname? fname: "-",
|
||||
fields[fi].token,
|
||||
fields[fi].typeIndex);
|
||||
free (ftype);
|
||||
free (fname);
|
||||
}
|
||||
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *attrs = method_attrs_str (methods[mi].flags);
|
||||
r_cons_printf (core->cons, " method %zu %s%s%s(%u) token=0x%08x flags=0x%04x\n",
|
||||
mi,
|
||||
attrs && *attrs? attrs: "",
|
||||
attrs && *attrs? " ": "",
|
||||
mname? mname: "-",
|
||||
(unsigned)methods[mi].parameterCount,
|
||||
methods[mi].token,
|
||||
methods[mi].flags);
|
||||
free (attrs);
|
||||
free (mname);
|
||||
}
|
||||
free (base);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static void emit_class_json(PJ *pj,
|
||||
R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const int32_t *interfaces,
|
||||
size_t interface_count,
|
||||
const ut64 *method_ptrs,
|
||||
bool has_ptrs,
|
||||
size_t type_index) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *ns = r2unity_get_string (meta, td->namespaceIndex);
|
||||
char *short_name = r2unity_get_string (meta, td->nameIndex);
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)type_index);
|
||||
pj_string_or_null (pj, "name", name);
|
||||
pj_string_or_null (pj, "namespace", ns);
|
||||
pj_string_or_null (pj, "short_name", short_name);
|
||||
pj_string_or_null (pj, "base", base);
|
||||
pj_ki (pj, "base_type_index", td->parentIndex);
|
||||
pj_hex (pj, "flags", td->flags, 8);
|
||||
pj_hex (pj, "token", td->token, 8);
|
||||
|
||||
pj_ka (pj, "interfaces");
|
||||
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
|
||||
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
|
||||
int32_t idx = interfaces[td->interfacesStart + k];
|
||||
char *iname = type_name_from_index (meta, types, type_count, idx, true);
|
||||
pj_o (pj);
|
||||
pj_ki (pj, "type_index", idx);
|
||||
pj_string_or_null (pj, "name", iname);
|
||||
pj_end (pj);
|
||||
free (iname);
|
||||
}
|
||||
}
|
||||
pj_end (pj);
|
||||
|
||||
pj_ka (pj, "fields");
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)fi);
|
||||
pj_string_or_null (pj, "name", fname);
|
||||
pj_ki (pj, "type_index", fields[fi].typeIndex);
|
||||
pj_string_or_null (pj, "type", ftype);
|
||||
pj_hex (pj, "token", fields[fi].token, 8);
|
||||
pj_end (pj);
|
||||
free (ftype);
|
||||
free (fname);
|
||||
}
|
||||
pj_end (pj);
|
||||
|
||||
pj_ka (pj, "methods");
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *fullname = r2unity_method_fullname (meta, &methods[mi], td, type_index, R2U_NAME_WITH_PARAMS | R2U_NAME_FALLBACK_TYPE);
|
||||
char *attrs = method_attrs_str (methods[mi].flags);
|
||||
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "index", (ut64)mi);
|
||||
pj_string_or_null (pj, "name", mname);
|
||||
pj_string_or_null (pj, "full_name", fullname);
|
||||
pj_kn (pj, "parameter_count", (ut64)methods[mi].parameterCount);
|
||||
pj_kn (pj, "addr", addr);
|
||||
pj_hex (pj, "token", methods[mi].token, 8);
|
||||
pj_hex (pj, "flags", methods[mi].flags, 4);
|
||||
pj_hex (pj, "iflags", methods[mi].iflags, 4);
|
||||
pj_string_or_null (pj, "attrs", (attrs && *attrs)? attrs: NULL);
|
||||
pj_end (pj);
|
||||
free (attrs);
|
||||
free (fullname);
|
||||
free (mname);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
free (base);
|
||||
free (short_name);
|
||||
free (ns);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static void emit_class_r2(RCore *core,
|
||||
R2UnityMetadata *meta,
|
||||
const Il2CppTypeDefinition *types,
|
||||
size_t type_count,
|
||||
const Il2CppMethodDefinition *methods,
|
||||
size_t method_count,
|
||||
const Il2CppFieldDefinition *fields,
|
||||
size_t field_count,
|
||||
const int32_t *interfaces,
|
||||
size_t interface_count,
|
||||
const ut64 *method_ptrs,
|
||||
bool has_ptrs,
|
||||
size_t type_index) {
|
||||
const Il2CppTypeDefinition *td = &types[type_index];
|
||||
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
|
||||
char *r2klass = r2_class_name (name, type_index);
|
||||
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, false);
|
||||
if (base) {
|
||||
r_cons_printf (core->cons, "# class %s : %s\n", name? name: r2klass, base);
|
||||
} else {
|
||||
r_cons_printf (core->cons, "# class %s\n", name? name: r2klass);
|
||||
}
|
||||
r_cons_printf (core->cons, "ic+%s @ 0\n", r2klass);
|
||||
if (base) {
|
||||
char *r2base = r2_class_name (base, 0);
|
||||
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2base);
|
||||
free (r2base);
|
||||
}
|
||||
free (base);
|
||||
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
|
||||
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
|
||||
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], false);
|
||||
if (iname) {
|
||||
char *r2iface = r2_class_name (iname, 0);
|
||||
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2iface);
|
||||
free (r2iface);
|
||||
}
|
||||
free (iname);
|
||||
}
|
||||
}
|
||||
|
||||
int mstart = td->methodStart;
|
||||
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
|
||||
size_t mi = (size_t)(mstart + k);
|
||||
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
|
||||
addr = addr > 0x1000? addr: 0;
|
||||
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
|
||||
char *r2meth = r2_method_name (mname, mi);
|
||||
r_cons_printf (core->cons, "ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
|
||||
free (r2meth);
|
||||
free (mname);
|
||||
}
|
||||
|
||||
int fstart = td->fieldStart;
|
||||
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
|
||||
size_t fi = (size_t)(fstart + k);
|
||||
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
|
||||
char *r2field = r2_field_name (fname, fi);
|
||||
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
|
||||
r_cons_printf (core->cons, "ic+%s..%s %s @ 0\n",
|
||||
r2klass,
|
||||
r2field,
|
||||
ftype? ftype: "unknown");
|
||||
free (ftype);
|
||||
free (r2field);
|
||||
free (fname);
|
||||
}
|
||||
free (r2klass);
|
||||
free (name);
|
||||
}
|
||||
|
||||
static int cmd_classes(RCore *core, char mode) {
|
||||
const char *metadata_path = resolve_metadata_path (core);
|
||||
RBuffer *buf = NULL;
|
||||
R2UnityMetadata *meta = open_metadata (core, &buf);
|
||||
if (!meta) {
|
||||
return 1;
|
||||
}
|
||||
size_t type_count = 0;
|
||||
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
|
||||
if (!types) {
|
||||
R_LOG_ERROR ("unable to decode type definitions");
|
||||
close_metadata (meta, buf);
|
||||
return 1;
|
||||
}
|
||||
size_t method_count = 0;
|
||||
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
|
||||
size_t field_count = 0;
|
||||
Il2CppFieldDefinition *fields = r2unity_get_field_definitions (meta, &field_count);
|
||||
size_t interface_count = 0;
|
||||
int32_t *interfaces = r2unity_get_type_index_table (meta, R2U_SEC_INTERFACES, &interface_count);
|
||||
|
||||
ut64 *method_ptrs = NULL;
|
||||
bool has_ptrs = false;
|
||||
const char *lib = resolve_library_path (core);
|
||||
if (lib && *lib) {
|
||||
has_ptrs = find_method_pointers (meta, lib, &method_ptrs);
|
||||
}
|
||||
if (method_ptrs && !has_ptrs) {
|
||||
for (size_t k = 0; k < method_count; k++) {
|
||||
if (method_ptrs[k]) {
|
||||
has_ptrs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == 'j') {
|
||||
PJ *pj = pj_new ();
|
||||
pj_o (pj);
|
||||
pj_kb (pj, "ok", true);
|
||||
pj_ki (pj, "version", meta->version);
|
||||
pj_ks (pj, "unity_range", r2unity_unity_range_from_wire (meta->version));
|
||||
pj_kb (pj, "has_ptrs", has_ptrs);
|
||||
pj_kn (pj, "types", (ut64)type_count);
|
||||
pj_kn (pj, "methods", (ut64)method_count);
|
||||
pj_kn (pj, "fields", (ut64)field_count);
|
||||
pj_ka (pj, "classes");
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
emit_class_json (pj, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
r_cons_println (core->cons, pj_string (pj));
|
||||
pj_free (pj);
|
||||
} else if (mode == '*') {
|
||||
r_cons_println (core->cons, "# r2 script generated by r2unity-c");
|
||||
r_cons_printf (core->cons, "# Input file: %s\n", metadata_path && *metadata_path? metadata_path: "-");
|
||||
if (!has_ptrs) {
|
||||
r_cons_println (core->cons, "# Method addresses default to 0; run r2unity-D or set r2unity.library to recover native addresses.");
|
||||
}
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
emit_class_r2 (core, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
|
||||
}
|
||||
} else {
|
||||
r_cons_printf (core->cons, "# classes from %s\n", metadata_path && *metadata_path? metadata_path: "-");
|
||||
r_cons_printf (core->cons, "# wire_version=%d (%s) types=%zu methods=%zu fields=%zu\n",
|
||||
meta->version,
|
||||
r2unity_unity_range_from_wire (meta->version),
|
||||
type_count,
|
||||
method_count,
|
||||
field_count);
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
emit_class_text (core, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, i);
|
||||
}
|
||||
}
|
||||
|
||||
R_FREE (method_ptrs);
|
||||
R_FREE (interfaces);
|
||||
R_FREE (fields);
|
||||
R_FREE (methods);
|
||||
R_FREE (types);
|
||||
close_metadata (meta, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- r2unity-s (methods) ---------- */
|
||||
static int cmd_symbols(RCore *core, char mode) {
|
||||
/* mode: 0 = apply to r2 session, '*' = print r2 commands, 'j' = JSON */
|
||||
@@ -678,6 +1088,8 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
|
||||
}
|
||||
|
||||
switch (sub) {
|
||||
case 'c':
|
||||
return cmd_classes (core, mode) == 0;
|
||||
case 'D':
|
||||
return cmd_detect (core, mode == 'j') == 0;
|
||||
case 'i':
|
||||
|
||||
Reference in new issue
Block a user