// SPDX-FileCopyrightText: 2018 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2021 keegan
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
#include <rz_bin.h>
#include <rz_core.h>
#include <rz_io.h>
#include "../format/mach0/dyldcache.h"
#include "objc/mach0_classes.h"

#define RZ_DYLDCACHE_VFILE_NAME_REBASED "rebased"

static ut64 bin_obj_va2pa(ut64 p, ut32 *offset, ut32 *left, RzBinFile *bf) {
	if (!bf || !bf->o || !bf->o->bin_obj) {
		return 0;
	}
	RzDyldCache *cache = (RzDyldCache *)((struct MACH0_(obj_t) *)bf->o->bin_obj)->user;
	if (!cache) {
		return 0;
	}
	return rz_dyldcache_va2pa(cache, p, offset, left);
}

static struct MACH0_(obj_t) * bin_to_mach0(RzBinFile *bf, RzDyldBinImage *bin) {
	if (!bin || !bf) {
		return NULL;
	}

	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}

	RzBuffer *buf = rz_buf_new_slice(cache->buf, bin->hdr_offset, rz_buf_size(cache->buf) - bin->hdr_offset);
	if (!buf) {
		return NULL;
	}

	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default)
	(&opts, bf);
	opts.header_at = bin->header_at - bin->hdr_offset;
	opts.symbols_off = bin->symbols_off;

	struct MACH0_(obj_t) *mach0 = MACH0_(new_buf)(buf, &opts);
	if (!mach0) {
		return NULL;
	}

	mach0->user = cache;
	mach0->va2pa = &bin_obj_va2pa;

	rz_buf_free(buf);

	return mach0;
}

static bool check_buffer(RzBuffer *buf) {
	if (rz_buf_size(buf) < 32) {
		return false;
	}

	char hdr[17] = { 0 };
	int rzhdr = rz_buf_read_at(buf, 0, (ut8 *)&hdr, sizeof(hdr) - 1);
	if (rzhdr != sizeof(hdr) - 1) {
		return false;
	}

	return rz_dyldcache_check_magic(hdr);
}

static bool load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *buf, Sdb *sdb) {
	RzDyldCache *cache = rz_dyldcache_new_buf(buf);
	if (!cache) {
		return false;
	}
	obj->bin_obj = cache;
	return true;
}

static RzList /*<RzBinAddr *>*/ *entries(RzBinFile *bf) {
	RzBinAddr *ptr = NULL;
	RzList *ret = rz_list_newf(free);
	if (!ret) {
		return NULL;
	}
	if ((ptr = RZ_NEW0(RzBinAddr))) {
		rz_list_append(ret, ptr);
	}
	return ret;
}

static RzBinInfo *info(RzBinFile *bf) {
	RzBinInfo *ret = NULL;

	if (!bf || !bf->o) {
		return NULL;
	}

	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}

	bool big_endian = 0;
	if (!(ret = RZ_NEW0(RzBinInfo))) {
		return NULL;
	}
	ret->file = strdup(bf->file);
	ret->bclass = strdup("dyldcache");
	ret->os = strdup(rz_dyldcache_get_platform_str(cache));
	if (strstr(cache->hdr->magic, "x86_64")) {
		ret->arch = strdup("x86");
		ret->bits = 64;
	} else {
		ret->arch = strdup("arm");
		ret->bits = strstr(cache->hdr->magic, "arm64") ? 64 : 32;
	}
	ret->machine = strdup(ret->arch);
	ret->subsystem = strdup("xnu");
	ret->guid = rz_hex_bin2strdup((ut8 *)cache->hdr->uuid, sizeof(cache->hdr->uuid));
	ret->type = strdup(rz_dyldcache_get_type_str(cache));
	ret->has_va = true;
	ret->big_endian = big_endian;
	ret->dbg_info = 0;
	return ret;
}

static ut64 baddr(RzBinFile *bf) {
	// XXX hardcoded
	return 0x180000000;
}

void symbols_from_bin(RzDyldCache *cache, RzPVector /*<RzBinSymbol *>*/ *ret, RzBinFile *bf, RzDyldBinImage *bin, SetU *hash) {
	struct MACH0_(obj_t) *mach0 = bin_to_mach0(bf, bin);
	if (!mach0) {
		return;
	}

	const struct symbol_t *symbols = MACH0_(get_symbols)(mach0);
	if (!symbols) {
		return;
	}
	int i;
	for (i = 0; !symbols[i].last; i++) {
		if (!symbols[i].name || !symbols[i].name[0] || symbols[i].addr < 100) {
			continue;
		}
		if (strstr(symbols[i].name, "<redacted>")) {
			continue;
		}
		RzBinSymbol *sym = RZ_NEW0(RzBinSymbol);
		if (!sym) {
			break;
		}
		sym->name = strdup(symbols[i].name);
		sym->vaddr = symbols[i].addr;
		sym->forwarder = "NONE";
		sym->bind = (symbols[i].type == RZ_BIN_MACH0_SYMBOL_TYPE_LOCAL) ? RZ_BIN_BIND_LOCAL_STR : RZ_BIN_BIND_GLOBAL_STR;
		sym->type = RZ_BIN_TYPE_FUNC_STR;
		sym->paddr = symbols[i].offset + bf->o->boffset;
		sym->size = symbols[i].size;
		sym->ordinal = i;

		set_u_add(hash, sym->vaddr);
		rz_pvector_push(ret, sym);
	}
	MACH0_(mach0_free)
	(mach0);
}

static bool __is_data_section(const char *name) {
	if (strstr(name, "_cstring")) {
		return true;
	}
	if (strstr(name, "_os_log")) {
		return true;
	}
	if (strstr(name, "_objc_methname")) {
		return true;
	}
	if (strstr(name, "_objc_classname")) {
		return true;
	}
	if (strstr(name, "_objc_methtype")) {
		return true;
	}
	return false;
}

static void sections_from_bin(RzPVector /*<RzBinSection *>*/ *ret, RzBinFile *bf, RzDyldBinImage *bin) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return;
	}

	struct MACH0_(obj_t) *mach0 = bin_to_mach0(bf, bin);
	if (!mach0) {
		return;
	}

	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections)(mach0))) {
		return;
	}

	int i;
	for (i = 0; !sections[i].last; i++) {
		RzBinSection *ptr = RZ_NEW0(RzBinSection);
		if (!ptr) {
			break;
		}
		if (bin->file) {
			ptr->name = rz_str_newf("%s.%s", bin->file, (char *)sections[i].name);
		} else {
			ptr->name = rz_str_newf("%s", (char *)sections[i].name);
		}
		if (strstr(ptr->name, "la_symbol_ptr")) {
			int len = sections[i].size / 8;
			ptr->format = rz_str_newf("Cd %d %d", 8, len);
		}
		ptr->is_data = __is_data_section(ptr->name);
		ptr->size = sections[i].size;
		ptr->vsize = sections[i].vsize;
		ptr->vaddr = sections[i].addr;
		ptr->paddr = rz_dyldcache_va2pa(cache, sections[i].addr, NULL, NULL);
		if (!ptr->vaddr) {
			ptr->vaddr = ptr->paddr;
		}
		ptr->perm = sections[i].perm;
		rz_pvector_push(ret, ptr);
	}
	free(sections);
	MACH0_(mach0_free)
	(mach0);
}

static RzPVector /*<RzBinVirtualFile *>*/ *virtual_files(RzBinFile *bf) {
	RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free);
	if (!ret) {
		return NULL;
	}
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (rz_dyldcache_needs_rebasing(cache)) {
		RzBinVirtualFile *vf = RZ_NEW0(RzBinVirtualFile);
		if (!vf) {
			return ret;
		}
		vf->buf = rz_dyldcache_new_rebasing_buf(cache);
		vf->buf_owned = true;
		vf->name = strdup(RZ_DYLDCACHE_VFILE_NAME_REBASED);
		rz_pvector_push(ret, vf);
	}
	return ret;
}

static int prot2perm(int x) {
	int r = 0;
	if (x & 1) {
		r |= 4;
	}
	if (x & 2) {
		r |= 2;
	}
	if (x & 4) {
		r |= 1;
	}
	return r;
}

static RzPVector /*<RzBinMap *>*/ *maps(RzBinFile *bf) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}
	RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_map_free);
	if (!ret) {
		return NULL;
	}
	ut64 slide = rz_dyldcache_get_slide(cache);
	for (ut32 i = 0; i < cache->n_maps; i++) {
		RzBinMap *map = RZ_NEW0(RzBinMap);
		if (!map) {
			rz_pvector_free(ret);
			return NULL;
		}
		map->name = rz_str_newf("cache_map.%d", i);
		map->paddr = cache->maps[i].fileOffset;
		map->psize = cache->maps[i].size;
		map->vsize = map->psize;
		map->vaddr = cache->maps[i].address + slide;
		map->perm = prot2perm(cache->maps[i].initProt);
		if (rz_dyldcache_range_needs_rebasing(cache, map->paddr, map->psize)) {
			map->vfile_name = strdup(RZ_DYLDCACHE_VFILE_NAME_REBASED);
		}
		rz_pvector_push(ret, map);
	}
	return ret;
}

static RzPVector /*<RzBinSection *>*/ *sections(RzBinFile *bf) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}
	RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_section_free);
	if (!ret) {
		return NULL;
	}
	RzListIter *iter;
	RzDyldBinImage *bin;
	rz_list_foreach (cache->bins, iter, bin) {
		sections_from_bin(ret, bf, bin);
	}
	ut64 slide = rz_dyldcache_get_slide(cache);
	if (slide) {
		RzBinSection *section;
		void **it;
		rz_pvector_foreach (ret, it) {
			section = *it;
			section->vaddr += slide;
		}
	}
	return ret;
}

static RzPVector /*<RzBinSymbol *>*/ *symbols(RzBinFile *bf) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}

	RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_symbol_free);
	if (!ret) {
		return NULL;
	}

	RzListIter *iter;
	RzDyldBinImage *bin;
	rz_list_foreach (cache->bins, iter, bin) {
		SetU *hash = set_u_new();
		if (!hash) {
			rz_pvector_free(ret);
			return NULL;
		}
		symbols_from_bin(cache, ret, bf, bin, hash);
		rz_dyldcache_symbols_from_locsym(cache, bin, ret, hash);
		set_u_free(hash);
	}

	ut64 slide = rz_dyldcache_get_slide(cache);
	if (slide) {
		RzBinSymbol *sym;
		void **it;
		rz_pvector_foreach (ret, it) {
			sym = *it;
			sym->vaddr += slide;
		}
	}

	return ret;
}

static void destroy(RzBinFile *bf) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	rz_dyldcache_free(cache);
}

static RzPVector /*<RzBinClass *>*/ *classes(RzBinFile *bf) {
	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return NULL;
	}

	RzPVector *ret = rz_pvector_new(free);
	if (!ret) {
		return NULL;
	}

	if (!cache->objc_opt_info_loaded) {
		cache->oi = rz_dyldcache_get_objc_opt_info(bf, cache);
		cache->objc_opt_info_loaded = true;
	}

	RzListIter *iter;
	RzDyldBinImage *bin;

	RzBuffer *buf = bf->buf;
	RzBuffer *owned_buf = NULL;
	if (rz_dyldcache_needs_rebasing(cache)) {
		owned_buf = rz_dyldcache_new_rebasing_buf(cache);
		if (!owned_buf) {
			goto beach;
		}
		buf = owned_buf;
	}

	ut32 num_of_unnamed_class = 0;
	rz_list_foreach (cache->bins, iter, bin) {
		struct MACH0_(obj_t) *mach0 = bin_to_mach0(bf, bin);
		if (!mach0) {
			goto beach;
		}

		struct section_t *sections = NULL;
		if (!(sections = MACH0_(get_sections)(mach0))) {
			MACH0_(mach0_free)
			(mach0);
			goto beach;
		}

		int i;
		for (i = 0; !sections[i].last; i++) {
			if (sections[i].size == 0) {
				continue;
			}

			bool is_classlist = strstr(sections[i].name, "__objc_classlist");
			bool is_catlist = strstr(sections[i].name, "__objc_catlist");

			if (!is_classlist && !is_catlist) {
				continue;
			}

			ut8 *pointers = malloc(sections[i].size);
			if (!pointers) {
				continue;
			}

			ut64 offset = rz_dyldcache_va2pa(cache, sections[i].addr, NULL, NULL);
			if (rz_buf_read_at(buf, offset, pointers, sections[i].size) < sections[i].size) {
				RZ_FREE(pointers);
				continue;
			}
			ut8 *cursor = pointers;
			ut8 *pointers_end = pointers + sections[i].size;

			for (; cursor < pointers_end; cursor += 8) {
				if ((cursor + 8) > pointers_end) {
					MACH0_(mach0_free)
					(mach0);
					goto beach;
				}
				ut64 pointer_to_class = rz_read_le64(cursor);

				RzBinClass *klass;
				if (!(klass = RZ_NEW0(RzBinClass)) ||
					!(klass->methods = rz_list_new()) ||
					!(klass->fields = rz_list_new())) {
					RZ_FREE(klass);
					RZ_FREE(pointers);
					RZ_FREE(sections);
					MACH0_(mach0_free)
					(mach0);
					goto beach;
				}

				bf->o->bin_obj = mach0;
				if (is_classlist) {
					MACH0_(get_class_t)
					(pointer_to_class, bf, buf, klass, false, NULL, cache->oi);
				} else {
					MACH0_(get_category_t)
					(pointer_to_class, bf, buf, klass, NULL, cache->oi);
				}
				bf->o->bin_obj = cache;

				if (!klass->name) {
					RZ_LOG_ERROR("CLASS ERROR AT 0x%llx, is_classlist %d\n", pointer_to_class, is_classlist);
					klass->name = rz_str_newf("UnnamedClass%u", num_of_unnamed_class);
					if (!klass->name) {
						RZ_FREE(klass);
						RZ_FREE(pointers);
						RZ_FREE(sections);
						MACH0_(mach0_free)
						(mach0);
						goto beach;
					}
					num_of_unnamed_class++;
				}
				rz_pvector_push(ret, klass);
			}

			RZ_FREE(pointers);
		}

		RZ_FREE(sections);
		MACH0_(mach0_free)
		(mach0);
	}

	return ret;

beach:
	rz_pvector_free(ret);
	rz_buf_free(owned_buf);
	return NULL;
}

static void header(RzBinFile *bf) {
	if (!bf || !bf->o) {
		return;
	}

	RzDyldCache *cache = (RzDyldCache *)bf->o->bin_obj;
	if (!cache) {
		return;
	}

	RzBin *bin = bf->rbin;
	ut64 slide = rz_dyldcache_get_slide(cache);
	PrintfCallback p = bin->cb_printf;

	PJ *pj = pj_new();
	if (!pj) {
		return;
	}

	pj_o(pj);

	pj_k(pj, "version");
	RzDyldCacheHeaderVersion ver = rz_dyldcache_header_version(cache->hdr);
	switch (ver) {
	case RZ_DYLD_CACHE_HEADER_BEFORE_940:
		pj_s(pj, "<940");
		break;
	case RZ_DYLD_CACHE_HEADER_940_OR_AFTER:
		pj_s(pj, "940");
		break;
	case RZ_DYLD_CACHE_HEADER_1042_1_OR_AFTER:
		pj_s(pj, "1042.1");
		break;
	}

	pj_k(pj, "header");
	pj_o(pj);
	pj_ks(pj, "magic", cache->hdr->magic);
	pj_kn(pj, "mappingOffset", cache->hdr->mappingOffset);
	pj_kn(pj, "mappingCount", cache->hdr->mappingCount);
	pj_kn(pj, "imagesOffset", cache->hdr->imagesOffset);
	pj_kn(pj, "imagesCount", cache->hdr->imagesCount);
	pj_kn(pj, "dyldBaseAddress", cache->hdr->dyldBaseAddress);
	pj_kn(pj, "codeSignatureOffset", cache->hdr->codeSignatureOffset);
	pj_kn(pj, "codeSignatureSize", cache->hdr->codeSignatureSize);
	pj_kn(pj, "slideInfoOffset", cache->hdr->slideInfoOffset);
	pj_kn(pj, "slideInfoSize", cache->hdr->slideInfoSize);
	pj_kn(pj, "localSymbolsOffset", cache->hdr->localSymbolsOffset);
	pj_kn(pj, "localSymbolsSize", cache->hdr->localSymbolsSize);
	char uuidstr[128];
	rz_hex_bin2str((ut8 *)cache->hdr->uuid, 16, uuidstr);
	pj_ks(pj, "uuid", uuidstr);
	pj_ks(pj, "cacheType", (cache->hdr->cacheType == 0) ? "development" : "production");
	pj_kn(pj, "branchPoolsOffset", cache->hdr->branchPoolsOffset);
	pj_kn(pj, "branchPoolsCount", cache->hdr->branchPoolsCount);
	if (rz_dyldcache_header_may_have_accel(cache->hdr)) {
		pj_kn(pj, "accelerateInfoAddr", cache->hdr->accelerateInfoAddr + slide);
		pj_kn(pj, "accelerateInfoSize", cache->hdr->accelerateInfoSize);
	} else {
		pj_kn(pj, "dyldInCacheMH", cache->hdr->dyldInCacheMH);
		pj_kn(pj, "dyldInCacheEntry", cache->hdr->dyldInCacheEntry);
	}
	pj_kn(pj, "imagesTextOffset", cache->hdr->imagesTextOffset);
	pj_kn(pj, "imagesTextCount", cache->hdr->imagesTextCount);
	pj_kn(pj, "patchInfoAddr", cache->hdr->patchInfoAddr);
	pj_kn(pj, "patchInfoSize", cache->hdr->patchInfoSize);
	pj_kn(pj, "otherImageGroupAddrUnused", cache->hdr->otherImageGroupAddrUnused);
	pj_kn(pj, "otherImageGroupSizeUnused", cache->hdr->otherImageGroupSizeUnused);
	pj_kn(pj, "progClosuresAddr", cache->hdr->progClosuresAddr);
	pj_kn(pj, "progClosuresSize", cache->hdr->progClosuresSize);
	pj_kn(pj, "progClosuresTrieAddr", cache->hdr->progClosuresTrieAddr);
	pj_kn(pj, "progClosuresTrieSize", cache->hdr->progClosuresTrieSize);
	pj_kn(pj, "platform", cache->hdr->platform);
	pj_kn(pj, "formatVersion", cache->hdr->formatVersion);
	pj_kn(pj, "dylibsExpectedOnDisk", cache->hdr->dylibsExpectedOnDisk);
	pj_kn(pj, "simulator", cache->hdr->simulator);
	pj_kn(pj, "locallyBuiltCache", cache->hdr->locallyBuiltCache);
	pj_kn(pj, "builtFromChainedFixups", cache->hdr->builtFromChainedFixups);
	pj_kn(pj, "padding", cache->hdr->padding);
	pj_kn(pj, "sharedRegionStart", cache->hdr->sharedRegionStart);
	pj_kn(pj, "sharedRegionSize", cache->hdr->sharedRegionSize);
	pj_kn(pj, "maxSlide", cache->hdr->maxSlide);
	pj_kn(pj, "dylibsImageArrayAddr", cache->hdr->dylibsImageArrayAddr);
	pj_kn(pj, "dylibsImageArraySize", cache->hdr->dylibsImageArraySize);
	pj_kn(pj, "dylibsTrieAddr", cache->hdr->dylibsTrieAddr);
	pj_kn(pj, "dylibsTrieSize", cache->hdr->dylibsTrieSize);
	pj_kn(pj, "otherImageArrayAddr", cache->hdr->otherImageArrayAddr);
	pj_kn(pj, "otherImageArraySize", cache->hdr->otherImageArraySize);
	pj_kn(pj, "otherTrieAddr", cache->hdr->otherTrieAddr);
	pj_kn(pj, "otherTrieSize", cache->hdr->otherTrieSize);
	pj_kn(pj, "mappingWithSlideOffset", cache->hdr->mappingWithSlideOffset);
	pj_kn(pj, "mappingWithSlideCount", cache->hdr->mappingWithSlideCount);
	if (ver >= RZ_DYLD_CACHE_HEADER_940_OR_AFTER) {
		pj_kn(pj, "dylibsPBLStateArrayAddrUnused", cache->hdr->dylibsPBLStateArrayAddrUnused);
		pj_kn(pj, "dylibsPBLSetAddr", cache->hdr->dylibsPBLSetAddr);
		pj_kn(pj, "programsPBLSetPoolAddr", cache->hdr->programsPBLSetPoolAddr);
		pj_kn(pj, "programsPBLSetPoolSize", cache->hdr->programsPBLSetPoolSize);
		pj_kn(pj, "programTrieAddr", cache->hdr->programTrieAddr);
		pj_kn(pj, "programTrieSize", cache->hdr->programTrieSize);
		pj_kn(pj, "osVersion", cache->hdr->osVersion);
		pj_kn(pj, "altPlatform", cache->hdr->altPlatform);
		pj_kn(pj, "altOsVersion", cache->hdr->altOsVersion);
		pj_kn(pj, "swiftOptsOffset", cache->hdr->swiftOptsOffset);
		pj_kn(pj, "swiftOptsSize", cache->hdr->swiftOptsSize);
		pj_kn(pj, "subCacheArrayOffset", cache->hdr->subCacheArrayOffset);
		pj_kn(pj, "subCacheArrayCount", cache->hdr->subCacheArrayCount);
		rz_hex_bin2str(cache->hdr->symbolFileUUID, sizeof(cache->hdr->symbolFileUUID), uuidstr);
		pj_ks(pj, "symbolFileUUID", uuidstr);
		pj_kn(pj, "rosettaReadOnlyAddr", cache->hdr->rosettaReadOnlyAddr);
		pj_kn(pj, "rosettaReadOnlySize", cache->hdr->rosettaReadOnlySize);
		pj_kn(pj, "rosettaReadWriteAddr", cache->hdr->rosettaReadWriteAddr);
		pj_kn(pj, "rosettaReadWriteSize", cache->hdr->rosettaReadWriteSize);
	}
	if (ver >= RZ_DYLD_CACHE_HEADER_1042_1_OR_AFTER) {
		pj_kn(pj, "cacheSubType", cache->hdr->cacheSubType);
		pj_kn(pj, "objcOptsOffset", cache->hdr->objcOptsOffset);
		pj_kn(pj, "objcOptsSize", cache->hdr->objcOptsSize);
		pj_kn(pj, "cacheAtlasOffset", cache->hdr->cacheAtlasOffset);
		pj_kn(pj, "cacheAtlasSize", cache->hdr->cacheAtlasSize);
		pj_kn(pj, "dynamicDataOffset", cache->hdr->dynamicDataOffset);
		pj_kn(pj, "dynamicDataMaxSize", cache->hdr->dynamicDataMaxSize);
	}
	pj_end(pj);

	if (cache->accel) {
		pj_k(pj, "accelerator");
		pj_o(pj);
		pj_kn(pj, "version", cache->accel->version);
		pj_kn(pj, "imageExtrasCount", cache->accel->imageExtrasCount);
		pj_kn(pj, "imagesExtrasOffset", cache->accel->imagesExtrasOffset);
		pj_kn(pj, "bottomUpListOffset", cache->accel->bottomUpListOffset);
		pj_kn(pj, "dylibTrieOffset", cache->accel->dylibTrieOffset);
		pj_kn(pj, "dylibTrieSize", cache->accel->dylibTrieSize);
		pj_kn(pj, "initializersOffset", cache->accel->initializersOffset);
		pj_kn(pj, "initializersCount", cache->accel->initializersCount);
		pj_kn(pj, "dofSectionsOffset", cache->accel->dofSectionsOffset);
		pj_kn(pj, "dofSectionsCount", cache->accel->dofSectionsCount);
		pj_kn(pj, "reExportListOffset", cache->accel->reExportListOffset);
		pj_kn(pj, "reExportCount", cache->accel->reExportCount);
		pj_kn(pj, "depListOffset", cache->accel->depListOffset);
		pj_kn(pj, "depListCount", cache->accel->depListCount);
		pj_kn(pj, "rangeTableOffset", cache->accel->rangeTableOffset);
		pj_kn(pj, "rangeTableCount", cache->accel->rangeTableCount);
		pj_kn(pj, "dyldSectionAddr", cache->accel->dyldSectionAddr + slide);
		pj_end(pj);
	}

	if (cache->rebase_infos) {
		size_t i;
		pj_k(pj, "slideInfo");
		pj_a(pj);
		for (i = 0; i < cache->rebase_infos->length; i++) {
			RzDyldRebaseInfo *rebase_info = cache->rebase_infos->entries[i].info;
			pj_o(pj);
			pj_kn(pj, "start", cache->rebase_infos->entries[i].start);
			pj_kn(pj, "end", cache->rebase_infos->entries[i].end);
			if (rebase_info) {
				ut8 version = rebase_info->version;
				pj_kn(pj, "version", version);
				pj_kn(pj, "slide", slide);
				if (version == 3) {
					RzDyldRebaseInfo3 *info3 = (RzDyldRebaseInfo3 *)rebase_info;
					pj_kn(pj, "page_starts_count", info3->page_starts_count);
					pj_kn(pj, "page_size", info3->page_size);
					pj_kn(pj, "auth_value_add", info3->auth_value_add);
				} else if (version == 2 || version == 4) {
					RzDyldRebaseInfo2 *info2 = (RzDyldRebaseInfo2 *)rebase_info;
					pj_kn(pj, "page_starts_count", info2->page_starts_count);
					pj_kn(pj, "page_extras_count", info2->page_extras_count);
					pj_kn(pj, "delta_mask", info2->delta_mask);
					pj_kn(pj, "value_mask", info2->value_mask);
					pj_kn(pj, "value_add", info2->value_add);
					pj_kn(pj, "delta_shift", info2->delta_shift);
					pj_kn(pj, "page_size", info2->page_size);
				} else if (version == 1) {
					RzDyldRebaseInfo1 *info1 = (RzDyldRebaseInfo1 *)rebase_info;
					pj_kn(pj, "toc_count", info1->toc_count);
					pj_kn(pj, "entries_size", info1->entries_size);
					pj_kn(pj, "page_size", 4096);
				}
			}
			pj_end(pj);
		}
		pj_end(pj);
	}

	if (cache->hdr->imagesTextCount) {
		pj_k(pj, "images");
		pj_a(pj);
		ut64 total_size = cache->hdr->imagesTextCount * sizeof(cache_text_info_t);
		cache_text_info_t *text_infos = malloc(total_size);
		if (!text_infos) {
			goto beach;
		}
		if (rz_buf_fread_at(cache->buf, cache->hdr->imagesTextOffset, (ut8 *)text_infos, "16clii", cache->hdr->imagesTextCount) != total_size) {
			free(text_infos);
			goto beach;
		}
		size_t i;
		for (i = 0; i != cache->hdr->imagesTextCount; i++) {
			cache_text_info_t *text_info = &text_infos[i];
			rz_hex_bin2str((ut8 *)text_info->uuid, 16, uuidstr);
			pj_o(pj);
			pj_ks(pj, "uuid", uuidstr);
			pj_kn(pj, "address", text_info->loadAddress + slide);
			pj_kn(pj, "textSegmentSize", text_info->textSegmentSize);
			char file[256];
			if (rz_buf_read_at(cache->buf, text_info->pathOffset, (ut8 *)&file, sizeof(file)) == sizeof(file)) {
				file[255] = 0;
				pj_ks(pj, "path", file);
				char *last_slash = strrchr(file, '/');
				if (last_slash && *last_slash) {
					pj_ks(pj, "name", last_slash + 1);
				} else {
					pj_ks(pj, "name", file);
				}
			}
			pj_end(pj);
		}
		pj_end(pj);
		free(text_infos);
	}

	pj_end(pj);
	p("%s\n", pj_string(pj));

beach:
	pj_free(pj);
}

RzBinPlugin rz_bin_plugin_dyldcache = {
	.name = "dyldcache",
	.desc = "dyldcache bin plugin",
	.license = "LGPL3",
	.load_buffer = &load_buffer,
	.entries = &entries,
	.baddr = &baddr,
	.symbols = &symbols,
	.virtual_files = &virtual_files,
	.maps = &maps,
	.sections = &sections,
	.check_buffer = &check_buffer,
	.destroy = &destroy,
	.classes = &classes,
	.header = &header,
	.info = &info,
};

#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
	.type = RZ_LIB_TYPE_BIN,
	.data = &rz_bin_plugin_dyldcache,
	.version = RZ_VERSION
};
#endif
