| 107 | } |
| 108 | |
| 109 | std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader) |
| 110 | { |
| 111 | // Find the first primary entry and use that header to read the obj opt header. |
| 112 | // Don't ask me why this is done like this... |
| 113 | std::optional<dyld_cache_header> primaryCacheHeader = std::nullopt; |
| 114 | for (const auto& entry : cache.GetEntries()) |
| 115 | { |
| 116 | if (entry.GetType() == CacheEntryType::Primary) |
| 117 | { |
| 118 | primaryCacheHeader = entry.GetHeader(); |
| 119 | break; |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | // Check if we even have the obj opt stuff. |
| 124 | if (!primaryCacheHeader || !primaryCacheHeader->objcOptsOffset || !primaryCacheHeader->objcOptsSize) |
| 125 | return std::nullopt; |
| 126 | |
| 127 | ObjCOptimizationHeader header = {}; |
| 128 | // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. |
| 129 | // TODO: The base address is the lowest region, however is that going to be where the primary cache header resides? |
| 130 | reader.Read(&header, cache.GetBaseAddress() + primaryCacheHeader->objcOptsOffset, sizeof(ObjCOptimizationHeader)); |
| 131 | |
| 132 | return header; |
| 133 | } |
| 134 | |
| 135 | uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) |
| 136 | { |
no test coverage detected