@import Darwin; @import CoreGraphics; #include "bridge.h" #include "LC32CoreGraphicsHost.h" #include "../../GuestFrameworks/CoreGraphics/LC32CoreGraphicsBridge.h" #include #include #include #include #include #include #include #include #include namespace { constexpr size_t kMaximumBitmapBytes = 256u * 1024u * 1024u; constexpr size_t kMaximumColorComponents = 1024; constexpr size_t kMaximumGradientStops = 4096; constexpr size_t kMaximumGradientComponentsPerStop = 64; constexpr size_t kMaximumPathPoints = 1024u * 1024u; constexpr size_t kMaximumFontGlyphs = 1024u * 1024u; struct BitmapBacking { CGContextRef context = nullptr; u32 guestData = 0; size_t guestBytesPerRow = 0; size_t hostBytesPerRow = 0; size_t height = 0; size_t guestByteCount = 0; std::unique_ptr bytes; }; struct DataProviderBacking { std::vector bytes; u32 guestInfo = 0; u32 guestData = 0; u32 guestSize = 0; u32 guestRelease = 0; std::atomic releaseCalled{false}; }; void ReleaseDataProviderBacking(void *info, const void *, size_t) { std::unique_ptr> owner( static_cast *>(info)); const auto backing = *owner; backing->releaseCalled.store(true, std::memory_order_release); if(!backing->guestRelease) return; u32 arguments[] = { backing->guestInfo, backing->guestData, backing->guestSize, }; if(Dynarmic_guest_thread_is_registered()) { (void)LC32InvokeGuestC(backing->guestRelease, false, 3, arguments); return; } LC32GuestBlockCallbackDescriptor descriptor = {}; descriptor.kind = LC32GuestBlockCallbackKindFunction; descriptor.guestInvoke = backing->guestRelease; descriptor.argumentCount = 3; descriptor.resultKind = LC32GuestBlockValueVoid; for(size_t index = 0; index < 3; ++index) { descriptor.arguments[index].kind = LC32GuestBlockValueUnsigned32; descriptor.arguments[index].value = arguments[index]; } if(!Dynarmic_submit_guest_function_callback(&descriptor)) { std::fprintf(stderr, "LC32: could not deliver data-provider release callback\n"); } } std::mutex bitmapBackingsMutex; std::unordered_map bitmapBackings; using CGContextGetFillColorSpaceFunction = CGColorSpaceRef (*)(CGContextRef); CGContextGetFillColorSpaceFunction GetContextFillColorSpaceFunction() { static const auto function = reinterpret_cast( dlsym(RTLD_DEFAULT, "CGContextGetFillColorSpace")); return function; } void ReportMissingContextFillColorSpaceFunction() { static std::once_flag once; std::call_once(once, [] { std::fprintf(stderr, "LC32: CGContextSetFillColor is unavailable because the host " "does not export CGContextGetFillColorSpace\n"); }); } using CGContextGetStrokeColorSpaceFunction = CGColorSpaceRef (*)(CGContextRef); CGContextGetStrokeColorSpaceFunction GetContextStrokeColorSpaceFunction() { static const auto function = reinterpret_cast( dlsym(RTLD_DEFAULT, "CGContextGetStrokeColorSpace")); return function; } void ReportMissingContextStrokeColorSpaceFunction() { static std::once_flag once; std::call_once(once, [] { std::fprintf(stderr, "LC32: CGContextSetStrokeColor is unavailable because the host " "does not export CGContextGetStrokeColorSpace\n"); }); } bool ReadCoreGraphicsCall(u32 guestAddress, LC32CoreGraphicsCall &call) { struct { uint32_t version; uint32_t slotCount; } header = {}; if(!guestAddress || Dynarmic_mem_1read(guestAddress, sizeof(header), reinterpret_cast(&header)) != 0 || header.version != LC32CoreGraphicsABIVersion || header.slotCount > LC32CoreGraphicsMaxSlots) { return false; } call = {}; call.version = header.version; call.slotCount = header.slotCount; const size_t byteCount = header.slotCount * sizeof(call.slots[0]); const uint64_t slotsAddress = static_cast(guestAddress) + offsetof(LC32CoreGraphicsCall, slots); if(slotsAddress > UINT32_MAX || slotsAddress + byteCount > static_cast(UINT32_MAX) + 1) return false; if(byteCount && Dynarmic_mem_1read( static_cast(slotsAddress), byteCount, reinterpret_cast(call.slots)) != 0) { return false; } return true; } bool RequireCoreGraphicsSlots(const LC32CoreGraphicsCall &call, uint32_t count) { return call.slotCount == count; } u32 SlotU32(const LC32CoreGraphicsCall &call, size_t index) { return static_cast(call.slots[index]); } template T SlotHostObject(const LC32CoreGraphicsCall &call, size_t index) { return reinterpret_cast( static_cast(call.slots[index])); } CGFloat SlotCGFloat(const LC32CoreGraphicsCall &call, size_t index) { const uint32_t bits = SlotU32(call, index); float value; memcpy(&value, &bits, sizeof(value)); return static_cast(value); } u32 ReturnCGFloat(CGFloat value) { const float narrowed = static_cast(value); u32 bits; memcpy(&bits, &narrowed, sizeof(bits)); return bits; } bool ReadGuestCGFloatArray(u32 guestAddress, size_t count, bool nullable, std::vector &hostValues, const CGFloat *&hostPointer) { hostPointer = nullptr; hostValues.clear(); if(!guestAddress) return nullable; if(!count || count > (kMaximumGradientComponentsPerStop + 1) * kMaximumGradientStops || count > SIZE_MAX / sizeof(float)) { return false; } const size_t byteCount = count * sizeof(float); if(static_cast(guestAddress) + byteCount > static_cast(UINT32_MAX) + 1) { return false; } std::vector guestValues(count); if(Dynarmic_mem_1read(guestAddress, byteCount, reinterpret_cast(guestValues.data())) != 0) { return false; } hostValues.resize(count); for(size_t index = 0; index < count; ++index) hostValues[index] = static_cast(guestValues[index]); hostPointer = hostValues.data(); return true; } bool ReadGuestGradientLocations(u32 guestAddress, size_t count, std::vector &hostValues, const CGFloat *&hostPointer) { if(!ReadGuestCGFloatArray(guestAddress, count, true, hostValues, hostPointer)) { return false; } if(!hostPointer) return true; CGFloat previous = 0; for(size_t index = 0; index < count; ++index) { const CGFloat location = hostPointer[index]; if(!std::isfinite(location) || location < 0 || location > 1 || (index && location < previous)) { return false; } previous = location; } return true; } bool ReadGuestPoints(u32 guestAddress, size_t count, std::vector &hostPoints) { hostPoints.clear(); if(!guestAddress || !count || count > kMaximumPathPoints) { return false; } const size_t componentCount = count * 2; const size_t byteCount = componentCount * sizeof(float); if(static_cast(guestAddress) + byteCount > static_cast(UINT32_MAX) + 1) { return false; } std::vector guestValues(componentCount); if(Dynarmic_mem_1read(guestAddress, byteCount, reinterpret_cast(guestValues.data())) != 0) { return false; } hostPoints.resize(count); for(size_t index = 0; index < count; ++index) { hostPoints[index] = CGPointMake( static_cast(guestValues[index * 2]), static_cast(guestValues[index * 2 + 1])); } return true; } bool WriteGuestRect(u32 guestAddress, CGRect rect) { if(!guestAddress || static_cast(guestAddress) + 4 * sizeof(float) > static_cast(UINT32_MAX) + 1) { return false; } float guestRect[] = { static_cast(rect.origin.x), static_cast(rect.origin.y), static_cast(rect.size.width), static_cast(rect.size.height), }; return Dynarmic_mem_1write(guestAddress, sizeof(guestRect), reinterpret_cast(guestRect)) == 0; } CGRect SlotRect(const LC32CoreGraphicsCall &call, size_t first) { return CGRectMake(SlotCGFloat(call, first), SlotCGFloat(call, first + 1), SlotCGFloat(call, first + 2), SlotCGFloat(call, first + 3)); } bool SlotOptionalTransform(const LC32CoreGraphicsCall &call, size_t presenceIndex, size_t first, CGAffineTransform &storage, const CGAffineTransform *&transform) { const u32 present = SlotU32(call, presenceIndex); if(!present) { transform = nullptr; return true; } if(present != 1) return false; storage = CGAffineTransformMake( SlotCGFloat(call, first), SlotCGFloat(call, first + 1), SlotCGFloat(call, first + 2), SlotCGFloat(call, first + 3), SlotCGFloat(call, first + 4), SlotCGFloat(call, first + 5)); transform = &storage; return true; } BitmapBacking *FindBitmapBacking(CGContextRef context) { std::lock_guard lock(bitmapBackingsMutex); const auto iterator = bitmapBackings.find(context); return iterator == bitmapBackings.end() ? nullptr : iterator->second; } void SyncBitmapBacking(CGContextRef context, BitmapBacking *backing) { if(!backing || !backing->guestData || !backing->guestByteCount) return; CGContextFlush(context); auto *data = static_cast(CGBitmapContextGetData(context)); if(!data) return; if(backing->guestBytesPerRow == backing->hostBytesPerRow) { (void)Dynarmic_mem_1write(backing->guestData, backing->guestByteCount, reinterpret_cast(data)); return; } for(size_t row = 0; row < backing->height; ++row) { const u32 guestRow = backing->guestData + static_cast(row * backing->guestBytesPerRow); (void)Dynarmic_mem_1write(guestRow, backing->guestBytesPerRow, reinterpret_cast(data + row * backing->hostBytesPerRow)); } } void ReleaseBitmapBacking(void *releaseInfo, void *) { auto *backing = static_cast(releaseInfo); if(!backing) return; #ifdef LC32_TRACE_COREGRAPHICS { std::fprintf(stderr, "LC32 CG: release bitmap backing=%p context=%p guest=0x%08x bytes=%zu\n", backing, backing->context, backing->guestData, backing->guestByteCount); } #endif /* A failed CGBitmapContextCreateWithData can synchronously invoke the * release callback before returning NULL. The creator still owns the * bookkeeping object until it has observed that return value. */ if(!backing->context) return; /* * Do not copy pixels back here. Core Graphics invokes this callback only * while destroying the context, after the guest is allowed to have freed * the buffer supplied to CGBitmapContextCreate. Mutating operations and * CGBitmapContextGetData synchronize while that guest buffer is live. */ if(backing->context) { std::lock_guard lock(bitmapBackingsMutex); const auto iterator = bitmapBackings.find(backing->context); if(iterator != bitmapBackings.end() && iterator->second == backing) bitmapBackings.erase(iterator); } delete backing; } } // namespace __BEGIN_DECLS void LC32CoreGraphicsSyncBitmapBacking(CGContextRef context) { if(context) SyncBitmapBacking(context, FindBitmapBacking(context)); } u32 LC32_CoreGraphics_Dispatch(u32 opcode, u32 guestCall, u32) { LC32CoreGraphicsCall call; if(!ReadCoreGraphicsCall(guestCall, call)) return 0; switch(static_cast(opcode)) { case LC32CoreGraphicsOpBitmapContextCreate: { if(!RequireCoreGraphicsSlots(call, 7)) return 0; const size_t width = SlotU32(call, 1); const size_t height = SlotU32(call, 2); const size_t bytesPerRow = SlotU32(call, 4); if(height && bytesPerRow > kMaximumBitmapBytes / height) return 0; const size_t byteCount = bytesPerRow * height; if(byteCount > kMaximumBitmapBytes) return 0; const u32 guestData = SlotU32(call, 0); #ifdef LC32_TRACE_COREGRAPHICS { std::fprintf(stderr, "LC32 CG: bitmap create guest=0x%08x size=%zux%zu bpc=%u bpr=%zu cs=%p info=0x%08x bytes=%zu\n", guestData, width, height, SlotU32(call, 3), bytesPerRow, SlotHostObject(call, 5), SlotU32(call, 6), byteCount); } #endif if(guestData && static_cast(guestData) + byteCount > static_cast(UINT32_MAX) + 1) return 0; BitmapBacking *backing = nullptr; void *hostData = nullptr; CGColorSpaceRef colorSpace = SlotHostObject(call, 5); size_t hostBytesPerRow = bytesPerRow; const u32 alphaInfo = SlotU32(call, 6) & static_cast(kCGBitmapAlphaInfoMask); const bool isEightBitRGBA = SlotU32(call, 3) == 8 && colorSpace && CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB && alphaInfo >= kCGImageAlphaPremultipliedLast && alphaInfo <= kCGImageAlphaNoneSkipFirst && bytesPerRow >= width * 4; if(guestData && isEightBitRGBA && hostBytesPerRow % 16) { hostBytesPerRow = (hostBytesPerRow + 15) & ~size_t(15); } if(height && hostBytesPerRow > kMaximumBitmapBytes / height) return 0; const size_t hostByteCount = hostBytesPerRow * height; if(guestData) { backing = new BitmapBacking(); backing->guestData = guestData; backing->guestBytesPerRow = bytesPerRow; backing->hostBytesPerRow = hostBytesPerRow; backing->height = height; backing->guestByteCount = byteCount; if(hostByteCount) { backing->bytes = std::make_unique(hostByteCount); memset(backing->bytes.get(), 0, hostByteCount); bool copied = true; for(size_t row = 0; row < height; ++row) { const u32 guestRow = guestData + static_cast(row * bytesPerRow); if(Dynarmic_mem_1read(guestRow, bytesPerRow, reinterpret_cast(backing->bytes.get() + row * hostBytesPerRow)) != 0) { copied = false; break; } } if(!copied) { delete backing; return 0; } hostData = backing->bytes.get(); } } CGContextRef context = backing ? CGBitmapContextCreateWithData(hostData, width, height, SlotU32(call, 3), hostBytesPerRow, colorSpace, SlotU32(call, 6), ReleaseBitmapBacking, backing) : CGBitmapContextCreate(nullptr, width, height, SlotU32(call, 3), bytesPerRow, colorSpace, SlotU32(call, 6)); #ifdef LC32_TRACE_COREGRAPHICS { std::fprintf(stderr, "LC32 CG: bitmap result context=%p backing=%p guest-bpr=%zu host-bpr=%zu\n", context, backing, bytesPerRow, hostBytesPerRow); } #endif if(!context) { delete backing; return 0; } if(backing) { backing->context = context; std::lock_guard lock(bitmapBackingsMutex); bitmapBackings.emplace(context, backing); } return LC32GuestObjectForOwnedHostObject(context); } case LC32CoreGraphicsOpColorSpaceCreateDeviceRGB: { if(!RequireCoreGraphicsSlots(call, 0)) return 0; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); if(!colorSpace) return 0; return LC32GuestObjectForOwnedHostObject(colorSpace); } case LC32CoreGraphicsOpColorSpaceCreateWithName: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CFStringRef name = SlotHostObject(call, 0); CGColorSpaceRef colorSpace = name ? CGColorSpaceCreateWithName(name) : nullptr; return colorSpace ? LC32GuestObjectForOwnedHostObject(colorSpace) : 0; } case LC32CoreGraphicsOpColorSpaceRelease: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; return 0; } case LC32CoreGraphicsOpContextClearRect: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextClearRect(context, SlotRect(call, 1)); SyncBitmapBacking(context, FindBitmapBacking(context)); return 0; } case LC32CoreGraphicsOpContextDrawImage: case LC32CoreGraphicsOpContextDrawTiledImage: { if(!RequireCoreGraphicsSlots(call, 6)) return 0; CGContextRef context = SlotHostObject(call, 0); CGImageRef image = SlotHostObject(call, 5); if(!context || !image) return 0; if(opcode == LC32CoreGraphicsOpContextDrawTiledImage) CGContextDrawTiledImage(context, SlotRect(call, 1), image); else CGContextDrawImage(context, SlotRect(call, 1), image); SyncBitmapBacking(context, FindBitmapBacking(context)); return 0; } case LC32CoreGraphicsOpContextRelease: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; /* The guest backing may already have been freed before release. */ return 0; } case LC32CoreGraphicsOpContextTranslateCTM: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGContextRef context = SlotHostObject(call, 0); if(context) CGContextTranslateCTM(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2)); return 0; } case LC32CoreGraphicsOpImageGetHeight: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetHeight(image)) : 0; } case LC32CoreGraphicsOpImageGetWidth: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetWidth(image)) : 0; } case LC32CoreGraphicsOpImageRelease: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; return 0; } case LC32CoreGraphicsOpColorGetColorSpace: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGColorRef color = SlotHostObject(call, 0); CGColorSpaceRef space = color ? CGColorGetColorSpace(color) : nullptr; return space ? [(id)space guest_self] : 0; } case LC32CoreGraphicsOpColorGetNumberOfComponents: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGColorRef color = SlotHostObject(call, 0); if(!color) return 0; const size_t count = CGColorGetNumberOfComponents(color); return count <= kMaximumColorComponents ? static_cast(count) : 0; } case LC32CoreGraphicsOpColorCopyComponents: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGColorRef color = SlotHostObject(call, 0); const u32 guestComponents = SlotU32(call, 1); const size_t capacity = SlotU32(call, 2); if(!color || !guestComponents || capacity > kMaximumColorComponents) return 0; const size_t count = CGColorGetNumberOfComponents(color); const CGFloat *components = CGColorGetComponents(color); if(!components || count == 0 || count > capacity || count > kMaximumColorComponents || static_cast(guestComponents) + count * sizeof(float) > static_cast(UINT32_MAX) + 1) { return 0; } std::vector guestValues(count); for(size_t index = 0; index < count; ++index) guestValues[index] = static_cast(components[index]); return Dynarmic_mem_1write(guestComponents, guestValues.size() * sizeof(guestValues[0]), reinterpret_cast(guestValues.data())) == 0; } case LC32CoreGraphicsOpColorSpaceGetModel: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGColorSpaceRef space = SlotHostObject(call, 0); return static_cast(space ? CGColorSpaceGetModel(space) : kCGColorSpaceModelUnknown); } case LC32CoreGraphicsOpContextSetAlpha: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextSetAlpha(context, SlotCGFloat(call, 1)); return 1; } case LC32CoreGraphicsOpColorSpaceGetNumberOfComponents: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGColorSpaceRef space = SlotHostObject(call, 0); return space ? static_cast(CGColorSpaceGetNumberOfComponents(space)) : 0; } case LC32CoreGraphicsOpDataProviderCreateWithFilename: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; const u32 guestFilename = SlotU32(call, 0); const size_t length = SlotU32(call, 1); if(!guestFilename || length > LC32CoreGraphicsMaximumFilenameBytes || static_cast(guestFilename) + length + 1 > static_cast(UINT32_MAX) + 1) { return 0; } std::vector filename(length + 1); if(Dynarmic_mem_1read(guestFilename, filename.size(), filename.data()) != 0 || filename[length] != '\0' || memchr(filename.data(), '\0', length) != nullptr) { return 0; } CGDataProviderRef provider = CGDataProviderCreateWithFilename(filename.data()); if(!provider) return 0; return LC32GuestObjectForOwnedHostObject(provider); } case LC32CoreGraphicsOpDataProviderCreateWithCFData: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CFDataRef data = SlotHostObject(call, 0); if(!data || CFGetTypeID(data) != CFDataGetTypeID()) return 0; CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); return provider ? LC32GuestObjectForOwnedHostObject(provider) : 0; } case LC32CoreGraphicsOpDataProviderCreateWithData: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; const u32 guestData = SlotU32(call, 1); const size_t size = SlotU32(call, 2); if(size > kMaximumBitmapBytes || (size && !guestData) || static_cast(guestData) + size > static_cast(UINT32_MAX) + 1) return 0; auto backing = std::make_shared(); backing->bytes.resize(size ? size : 1); if(size && Dynarmic_mem_1read(guestData, size, reinterpret_cast(backing->bytes.data())) != 0) return 0; backing->guestInfo = SlotU32(call, 0); backing->guestData = guestData; backing->guestSize = static_cast(size); backing->guestRelease = SlotU32(call, 3); auto *owner = new std::shared_ptr(backing); CGDataProviderRef provider = CGDataProviderCreateWithData(owner, backing->bytes.data(), size, ReleaseDataProviderBacking); /* CoreGraphics may invoke its release callback synchronously * when creation fails. The creator keeps its own reference so * either failure path can dispose the backing exactly once. */ if(!provider && !backing->releaseCalled.load(std::memory_order_acquire)) delete owner; return provider ? LC32GuestObjectForOwnedHostObject(provider) : 0; } case LC32CoreGraphicsOpDataProviderRelease: case LC32CoreGraphicsOpDataProviderRetain: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGDataProviderRef provider = SlotHostObject(call, 0); if(!provider || CFGetTypeID(provider) != CGDataProviderGetTypeID()) return 0; if(opcode == LC32CoreGraphicsOpDataProviderRetain) return LC32GuestObjectForOwnedHostObject( CGDataProviderRetain(provider)); CGDataProviderRelease(provider); return 0; } case LC32CoreGraphicsOpDataProviderCopyData: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGDataProviderRef provider = SlotHostObject(call, 0); if(!provider || CFGetTypeID(provider) != CGDataProviderGetTypeID()) return 0; CFDataRef data = CGDataProviderCopyData(provider); return data ? LC32GuestObjectForOwnedHostObject(data) : 0; } case LC32CoreGraphicsOpFontCreateWithDataProvider: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGDataProviderRef provider = SlotHostObject(call, 0); if(!provider || CFGetTypeID(provider) != CGDataProviderGetTypeID()) return 0; CGFontRef font = CGFontCreateWithDataProvider(provider); return font ? LC32GuestObjectForOwnedHostObject(font) : 0; } case LC32CoreGraphicsOpFontCopyTableForTag: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGFontRef font = SlotHostObject(call, 0); if(!font || CFGetTypeID(font) != CGFontGetTypeID()) return 0; CFDataRef table = CGFontCopyTableForTag(font, SlotU32(call, 1)); if(!table) return 0; /* CoreGraphics can return a no-copy view into the font's mapped * provider. Keep the guest's Copy result valid even after the * guest releases its font before accessing the table bytes. */ CFDataRef data = CFDataCreate(kCFAllocatorDefault, CFDataGetBytePtr(table), CFDataGetLength(table)); CFRelease(table); return data ? LC32GuestObjectForOwnedHostObject(data) : 0; } case LC32CoreGraphicsOpFontCreateWithFontName: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CFStringRef name = SlotHostObject(call, 0); CGFontRef font = name ? CGFontCreateWithFontName(name) : nullptr; return font ? LC32GuestObjectForOwnedHostObject(font) : 0; } case LC32CoreGraphicsOpFontGetItalicAngle: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGFontRef font = SlotHostObject(call, 0); return font ? ReturnCGFloat(CGFontGetItalicAngle(font)) : 0; } case LC32CoreGraphicsOpFontGetUnitsPerEm: case LC32CoreGraphicsOpFontGetAscent: case LC32CoreGraphicsOpFontGetDescent: case LC32CoreGraphicsOpFontGetCapHeight: case LC32CoreGraphicsOpFontGetLeading: case LC32CoreGraphicsOpFontGetNumberOfGlyphs: case LC32CoreGraphicsOpFontGetXHeight: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGFontRef font = SlotHostObject(call, 0); if(!font || CFGetTypeID(font) != CGFontGetTypeID()) return 0; int value; switch(opcode) { case LC32CoreGraphicsOpFontGetUnitsPerEm: value = CGFontGetUnitsPerEm(font); break; case LC32CoreGraphicsOpFontGetAscent: value = CGFontGetAscent(font); break; case LC32CoreGraphicsOpFontGetDescent: value = CGFontGetDescent(font); break; case LC32CoreGraphicsOpFontGetCapHeight: value = CGFontGetCapHeight(font); break; case LC32CoreGraphicsOpFontGetLeading: value = CGFontGetLeading(font); break; case LC32CoreGraphicsOpFontGetNumberOfGlyphs: return static_cast(CGFontGetNumberOfGlyphs(font)); default: value = CGFontGetXHeight(font); break; } return static_cast(value); } case LC32CoreGraphicsOpFontGetGlyphAdvances: case LC32CoreGraphicsOpFontGetGlyphBBoxes: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; CGFontRef font = SlotHostObject(call, 0); const u32 guestGlyphs = SlotU32(call, 1); const size_t count = SlotU32(call, 2); const u32 guestAdvances = SlotU32(call, 3); if(!font || CFGetTypeID(font) != CGFontGetTypeID() || count > kMaximumFontGlyphs) return 0; if(!count) return 1; static_assert(sizeof(CGGlyph) == 2 && sizeof(int) == 4, "CGFont glyphs and advances must match the ARM32 ABI"); const size_t glyphBytes = count * sizeof(CGGlyph); const bool wantsBounds = opcode == LC32CoreGraphicsOpFontGetGlyphBBoxes; const size_t advanceBytes = count * (wantsBounds ? 4 * sizeof(float) : sizeof(int)); if(!guestGlyphs || !guestAdvances || static_cast(guestGlyphs) + glyphBytes > static_cast(UINT32_MAX) + 1 || static_cast(guestAdvances) + advanceBytes > static_cast(UINT32_MAX) + 1) return 0; std::vector glyphs(count); if(Dynarmic_mem_1read(guestGlyphs, glyphBytes, reinterpret_cast(glyphs.data())) != 0) return 0; if(wantsBounds) { std::vector bounds(count); if(!CGFontGetGlyphBBoxes(font, glyphs.data(), count, bounds.data())) return 0; std::vector narrowed(count * 4); for(size_t i = 0; i < count; ++i) { narrowed[i * 4] = bounds[i].origin.x; narrowed[i * 4 + 1] = bounds[i].origin.y; narrowed[i * 4 + 2] = bounds[i].size.width; narrowed[i * 4 + 3] = bounds[i].size.height; } return Dynarmic_mem_1write(guestAdvances, advanceBytes, reinterpret_cast(narrowed.data())) == 0; } std::vector advances(count); if(!CGFontGetGlyphAdvances(font, glyphs.data(), count, advances.data())) return 0; return Dynarmic_mem_1write(guestAdvances, advanceBytes, reinterpret_cast(advances.data())) == 0; } case LC32CoreGraphicsOpImageCreate: { if(!RequireCoreGraphicsSlots(call, 11)) return 0; const size_t width = SlotU32(call, 0); const size_t height = SlotU32(call, 1); const size_t bitsPerComponent = SlotU32(call, 2); const size_t bitsPerPixel = SlotU32(call, 3); const size_t bytesPerRow = SlotU32(call, 4); CGColorSpaceRef space = SlotHostObject(call, 5); CGDataProviderRef provider = SlotHostObject(call, 7); const u32 guestDecode = SlotU32(call, 8); const u32 shouldInterpolate = SlotU32(call, 9); const u32 intentValue = SlotU32(call, 10); if(!width || !height || !bitsPerComponent || !bitsPerPixel || !bytesPerRow || !provider || shouldInterpolate > 1 || intentValue > kCGRenderingIntentSaturation || CFGetTypeID(provider) != CGDataProviderGetTypeID() || (space && CFGetTypeID(space) != CGColorSpaceGetTypeID())) { return 0; } /* Keep pathological guest metadata from making CoreGraphics * reserve an unbounded image. The row must contain every pixel, * and the represented backing remains within the same limit as * the bitmap-context bridge. */ if(width > (SIZE_MAX - 7) / bitsPerPixel) return 0; const size_t minimumBytesPerRow = (width * bitsPerPixel + 7) / 8; if(bytesPerRow < minimumBytesPerRow || bytesPerRow > kMaximumBitmapBytes / height) { return 0; } std::vector decodeValues; const CGFloat *decode = nullptr; if(guestDecode) { if(!space) return 0; const size_t componentCount = CGColorSpaceGetNumberOfComponents(space); if(!componentCount || componentCount > kMaximumColorComponents || componentCount > SIZE_MAX / 2 || !ReadGuestCGFloatArray(guestDecode, componentCount * 2, false, decodeValues, decode)) { return 0; } } CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, static_cast(SlotU32(call, 6)), provider, decode, shouldInterpolate != 0, static_cast(intentValue)); return image ? LC32GuestObjectForOwnedHostObject(image) : 0; } case LC32CoreGraphicsOpImageCreateWithJPEGDataProvider: case LC32CoreGraphicsOpImageCreateWithPNGDataProvider: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; CGDataProviderRef provider = SlotHostObject(call, 0); /* A CGFloat decode array has no length in this API. Never pass * the guest address to CoreGraphics; reject it until the required * component count can be established safely. */ if(!provider || SlotU32(call, 1) != 0) return 0; const bool shouldInterpolate = SlotU32(call, 2) != 0; const auto intent = static_cast( SlotU32(call, 3)); CGImageRef image = static_cast(opcode) == LC32CoreGraphicsOpImageCreateWithJPEGDataProvider ? CGImageCreateWithJPEGDataProvider(provider, nullptr, shouldInterpolate, intent) : CGImageCreateWithPNGDataProvider(provider, nullptr, shouldInterpolate, intent); if(!image) return 0; return LC32GuestObjectForOwnedHostObject(image); } case LC32CoreGraphicsOpPathCreateMutable: { if(!RequireCoreGraphicsSlots(call, 0)) return 0; CGMutablePathRef path = CGPathCreateMutable(); if(!path) return 0; /* CGPathCreateMutable returns +1. Keep that ownership paired with * the +1 guest proxy returned by -guest_self. */ return LC32GuestObjectForOwnedHostObject(path); } case LC32CoreGraphicsOpPathAddLineToPoint: case LC32CoreGraphicsOpPathMoveToPoint: { if(!RequireCoreGraphicsSlots(call, 10)) return 0; CGMutablePathRef path = SlotHostObject(call, 0); if(!path) return 0; CGAffineTransform transformStorage; const CGAffineTransform *transform; if(!SlotOptionalTransform(call, 1, 2, transformStorage, transform)) return 0; if(static_cast(opcode) == LC32CoreGraphicsOpPathAddLineToPoint) { CGPathAddLineToPoint(path, transform, SlotCGFloat(call, 8), SlotCGFloat(call, 9)); } else { CGPathMoveToPoint(path, transform, SlotCGFloat(call, 8), SlotCGFloat(call, 9)); } return 1; } case LC32CoreGraphicsOpPathContainsPoint: { if(!RequireCoreGraphicsSlots(call, 11)) return 0; CGPathRef path = SlotHostObject(call, 0); if(!path) return 0; CGAffineTransform transformStorage; const CGAffineTransform *transform; if(!SlotOptionalTransform(call, 1, 2, transformStorage, transform)) return 0; const CGPoint point = CGPointMake( SlotCGFloat(call, 8), SlotCGFloat(call, 9)); return CGPathContainsPoint(path, transform, point, SlotU32(call, 10) != 0); } case LC32CoreGraphicsOpPathCloseSubpath: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGMutablePathRef path = SlotHostObject(call, 0); if(!path) return 0; CGPathCloseSubpath(path); return 1; } case LC32CoreGraphicsOpPathRelease: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; /* Guest CFRelease performs the paired proxy/native decrement. * This opcode deliberately validates without releasing again. */ return SlotHostObject(call, 0) != nullptr; } case LC32CoreGraphicsOpColorCreate: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGColorSpaceRef space = SlotHostObject(call, 0); const u32 guestComponents = SlotU32(call, 1); if(!space || !guestComponents) return 0; const size_t colorComponents = CGColorSpaceGetNumberOfComponents(space); if(colorComponents >= kMaximumColorComponents) return 0; const size_t count = colorComponents + 1; const size_t byteCount = count * sizeof(float); if(static_cast(guestComponents) + byteCount > static_cast(UINT32_MAX) + 1) { return 0; } std::vector guestValues(count); if(Dynarmic_mem_1read(guestComponents, byteCount, reinterpret_cast(guestValues.data())) != 0) { return 0; } std::vector hostValues(count); for(size_t index = 0; index < count; ++index) hostValues[index] = guestValues[index]; CGColorRef color = CGColorCreate(space, hostValues.data()); return color ? LC32GuestObjectForOwnedHostObject(color) : 0; } case LC32CoreGraphicsOpColorEqualToColor: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGColorRef color1 = SlotHostObject(call, 0); CGColorRef color2 = SlotHostObject(call, 1); return CGColorEqualToColor(color1, color2); } case LC32CoreGraphicsOpColorGetAlpha: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGColorRef color = SlotHostObject(call, 0); return color ? ReturnCGFloat(CGColorGetAlpha(color)) : 0; } case LC32CoreGraphicsOpColorSpaceCreateDeviceGray: { if(!RequireCoreGraphicsSlots(call, 0)) return 0; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); return colorSpace ? LC32GuestObjectForOwnedHostObject(colorSpace) : 0; } case LC32CoreGraphicsOpGradientCreateWithColorComponents: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; CGColorSpaceRef space = SlotHostObject(call, 0); const size_t stopCount = SlotU32(call, 3); if(!stopCount || stopCount > kMaximumGradientStops) return 0; /* CoreGraphics uses Generic RGB when the space is NULL. Every * stop contains the color-space components followed by alpha. */ size_t colorComponentCount = space ? CGColorSpaceGetNumberOfComponents(space) : 3; if(!colorComponentCount || colorComponentCount > kMaximumGradientComponentsPerStop || colorComponentCount + 1 > SIZE_MAX / stopCount) { return 0; } if(space) { const CGColorSpaceModel model = CGColorSpaceGetModel(space); if(model == kCGColorSpaceModelIndexed || model == kCGColorSpaceModelPattern) { return 0; } } const size_t componentCount = (colorComponentCount + 1) * stopCount; std::vector components; const CGFloat *componentPointer; if(!ReadGuestCGFloatArray(SlotU32(call, 1), componentCount, false, components, componentPointer)) { return 0; } std::vector locations; const CGFloat *locationPointer; if(!ReadGuestGradientLocations(SlotU32(call, 2), stopCount, locations, locationPointer)) { return 0; } CGGradientRef gradient = CGGradientCreateWithColorComponents( space, componentPointer, locationPointer, stopCount); return gradient ? LC32GuestObjectForOwnedHostObject(gradient) : 0; } case LC32CoreGraphicsOpGradientCreateWithColors: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGColorSpaceRef space = SlotHostObject(call, 0); CFArrayRef colors = SlotHostObject(call, 1); if(!colors || CFGetTypeID(colors) != CFArrayGetTypeID()) return 0; const CFIndex signedCount = CFArrayGetCount(colors); if(signedCount <= 0 || static_cast(signedCount) > kMaximumGradientStops) { return 0; } const size_t stopCount = static_cast(signedCount); for(CFIndex index = 0; index < signedCount; ++index) { CFTypeRef color = static_cast( CFArrayGetValueAtIndex(colors, index)); if(!color || CFGetTypeID(color) != CGColorGetTypeID()) return 0; } std::vector locations; const CGFloat *locationPointer; if(!ReadGuestGradientLocations(SlotU32(call, 2), stopCount, locations, locationPointer)) { return 0; } CGGradientRef gradient = CGGradientCreateWithColors( space, colors, locationPointer); return gradient ? LC32GuestObjectForOwnedHostObject(gradient) : 0; } case LC32CoreGraphicsOpBitmapContextCreateImage: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGImageRef image = CGBitmapContextCreateImage(context); return image ? LC32GuestObjectForOwnedHostObject(image) : 0; } case LC32CoreGraphicsOpBitmapContextGetWidth: case LC32CoreGraphicsOpBitmapContextGetHeight: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; return static_cast(opcode == LC32CoreGraphicsOpBitmapContextGetWidth ? CGBitmapContextGetWidth(context) : CGBitmapContextGetHeight(context)); } case LC32CoreGraphicsOpBitmapContextGetBytesPerRow: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); BitmapBacking *backing = context ? FindBitmapBacking(context) : nullptr; return backing ? static_cast(backing->guestBytesPerRow) : context ? static_cast(CGBitmapContextGetBytesPerRow(context)) : 0; } case LC32CoreGraphicsOpBitmapContextGetData: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); BitmapBacking *backing = context ? FindBitmapBacking(context) : nullptr; if(context && backing) SyncBitmapBacking(context, backing); return backing ? backing->guestData : 0; } case LC32CoreGraphicsOpContextSetFont: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); CGFontRef font = SlotHostObject(call, 1); if(context && font && CFGetTypeID(font) == CGFontGetTypeID()) CGContextSetFont(context, font); return 0; } case LC32CoreGraphicsOpContextSetFontSize: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); if(context) CGContextSetFontSize(context, SlotCGFloat(call, 1)); return 0; } case LC32CoreGraphicsOpContextSetTextDrawingMode: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 mode = SlotU32(call, 1); if(context && mode <= kCGTextClip) CGContextSetTextDrawingMode(context, static_cast(mode)); return 0; } case LC32CoreGraphicsOpContextShowGlyphsAtPoint: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 guestGlyphs = SlotU32(call, 3); const size_t count = SlotU32(call, 4); if(!context || !guestGlyphs || !count || count > kMaximumFontGlyphs || static_cast(guestGlyphs) + count * sizeof(CGGlyph) > static_cast(UINT32_MAX) + 1) return 0; std::vector glyphs(count); if(Dynarmic_mem_1read(guestGlyphs, count * sizeof(CGGlyph), reinterpret_cast(glyphs.data())) != 0) return 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGContextShowGlyphsAtPoint(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2), glyphs.data(), count); #pragma clang diagnostic pop SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextShowGlyphsAtPositions: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 guestGlyphs = SlotU32(call, 1); const size_t count = SlotU32(call, 3); std::vector positions; if(!context || !guestGlyphs || count > kMaximumFontGlyphs || static_cast(guestGlyphs) + count * sizeof(CGGlyph) > static_cast(UINT32_MAX) + 1 || !ReadGuestPoints(SlotU32(call, 2), count, positions)) return 0; std::vector glyphs(count); if(Dynarmic_mem_1read(guestGlyphs, count * sizeof(CGGlyph), reinterpret_cast(glyphs.data())) != 0) return 0; CGContextShowGlyphsAtPositions(context, glyphs.data(), positions.data(), count); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextBeginTransparencyLayer: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); CFDictionaryRef auxiliaryInfo = SlotHostObject(call, 1); if(!context || (auxiliaryInfo && CFGetTypeID(auxiliaryInfo) != CFDictionaryGetTypeID())) return 0; CGContextBeginTransparencyLayer(context, auxiliaryInfo); return 1; } case LC32CoreGraphicsOpContextEndTransparencyLayer: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextEndTransparencyLayer(context); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextSaveGState: case LC32CoreGraphicsOpContextRestoreGState: case LC32CoreGraphicsOpContextBeginPath: case LC32CoreGraphicsOpContextClosePath: case LC32CoreGraphicsOpContextClip: case LC32CoreGraphicsOpContextFlush: case LC32CoreGraphicsOpContextFillPath: case LC32CoreGraphicsOpContextStrokePath: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; switch(static_cast(opcode)) { case LC32CoreGraphicsOpContextSaveGState: CGContextSaveGState(context); break; case LC32CoreGraphicsOpContextRestoreGState: CGContextRestoreGState(context); break; case LC32CoreGraphicsOpContextBeginPath: CGContextBeginPath(context); break; case LC32CoreGraphicsOpContextClosePath: CGContextClosePath(context); break; case LC32CoreGraphicsOpContextClip: CGContextClip(context); break; case LC32CoreGraphicsOpContextFlush: CGContextFlush(context); SyncBitmapBacking(context, FindBitmapBacking(context)); break; case LC32CoreGraphicsOpContextFillPath: CGContextFillPath(context); SyncBitmapBacking(context, FindBitmapBacking(context)); break; case LC32CoreGraphicsOpContextStrokePath: CGContextStrokePath(context); SyncBitmapBacking(context, FindBitmapBacking(context)); break; default: break; } return 1; } case LC32CoreGraphicsOpContextDrawPath: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextDrawPath(context, static_cast(SlotU32(call, 1))); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextMoveToPoint: case LC32CoreGraphicsOpContextAddLineToPoint: case LC32CoreGraphicsOpContextScaleCTM: case LC32CoreGraphicsOpContextSetGrayFillColor: case LC32CoreGraphicsOpContextSetGrayStrokeColor: case LC32CoreGraphicsOpContextSetTextPosition: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; const CGFloat first = SlotCGFloat(call, 1); const CGFloat second = SlotCGFloat(call, 2); switch(static_cast(opcode)) { case LC32CoreGraphicsOpContextMoveToPoint: CGContextMoveToPoint(context, first, second); break; case LC32CoreGraphicsOpContextAddLineToPoint: CGContextAddLineToPoint(context, first, second); break; case LC32CoreGraphicsOpContextScaleCTM: CGContextScaleCTM(context, first, second); break; case LC32CoreGraphicsOpContextSetGrayFillColor: CGContextSetGrayFillColor(context, first, second); break; case LC32CoreGraphicsOpContextSetGrayStrokeColor: CGContextSetGrayStrokeColor(context, first, second); break; case LC32CoreGraphicsOpContextSetTextPosition: CGContextSetTextPosition(context, first, second); break; default: break; } return 1; } case LC32CoreGraphicsOpContextGetTextPosition: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 guestAddress = SlotU32(call, 1); if(!context || !guestAddress || static_cast(guestAddress) + 2 * sizeof(float) > static_cast(UINT32_MAX) + 1) return 0; const CGPoint position = CGContextGetTextPosition(context); float guestPosition[] = { static_cast(position.x), static_cast(position.y), }; return Dynarmic_mem_1write(guestAddress, sizeof(guestPosition), reinterpret_cast(guestPosition)) == 0; } case LC32CoreGraphicsOpContextAddLines: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGContextRef context = SlotHostObject(call, 0); std::vector points; if(!context || !ReadGuestPoints(SlotU32(call, 1), SlotU32(call, 2), points)) { return 0; } CGContextAddLines(context, points.data(), points.size()); return 1; } case LC32CoreGraphicsOpContextRotateCTM: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); if(context) CGContextRotateCTM(context, SlotCGFloat(call, 1)); return context ? 1 : 0; } case LC32CoreGraphicsOpContextAddArc: { if(!RequireCoreGraphicsSlots(call, 7)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextAddArc(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4), SlotCGFloat(call, 5), SlotU32(call, 6) != 0); return 1; } case LC32CoreGraphicsOpContextAddArcToPoint: { if(!RequireCoreGraphicsSlots(call, 6)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextAddArcToPoint(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4), SlotCGFloat(call, 5)); return 1; } case LC32CoreGraphicsOpContextAddRect: case LC32CoreGraphicsOpContextFillRect: case LC32CoreGraphicsOpContextStrokeRect: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; const CGRect rect = SlotRect(call, 1); switch(static_cast(opcode)) { case LC32CoreGraphicsOpContextAddRect: CGContextAddRect(context, rect); break; case LC32CoreGraphicsOpContextFillRect: CGContextFillRect(context, rect); SyncBitmapBacking(context, FindBitmapBacking(context)); break; case LC32CoreGraphicsOpContextStrokeRect: CGContextStrokeRect(context, rect); SyncBitmapBacking(context, FindBitmapBacking(context)); break; default: break; } return 1; } case LC32CoreGraphicsOpContextAddPath: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); CGPathRef path = SlotHostObject(call, 1); if(!context || !path) return 0; CGContextAddPath(context, path); return 1; } case LC32CoreGraphicsOpContextClipToMask: { if(!RequireCoreGraphicsSlots(call, 6)) return 0; CGContextRef context = SlotHostObject(call, 0); CGImageRef mask = SlotHostObject(call, 5); if(!context || !mask) return 0; CGContextClipToMask(context, SlotRect(call, 1), mask); return 1; } case LC32CoreGraphicsOpContextConcatCTM: { if(!RequireCoreGraphicsSlots(call, 7)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextConcatCTM(context, CGAffineTransformMake( SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4), SlotCGFloat(call, 5), SlotCGFloat(call, 6))); return 1; } case LC32CoreGraphicsOpContextDrawLinearGradient: { if(!RequireCoreGraphicsSlots(call, 7)) return 0; CGContextRef context = SlotHostObject(call, 0); CGGradientRef gradient = SlotHostObject(call, 1); const u32 options = SlotU32(call, 6); const u32 validOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; if(!context || !gradient || (options & ~validOptions)) return 0; CGContextDrawLinearGradient(context, gradient, CGPointMake(SlotCGFloat(call, 2), SlotCGFloat(call, 3)), CGPointMake(SlotCGFloat(call, 4), SlotCGFloat(call, 5)), static_cast(options)); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextDrawRadialGradient: { if(!RequireCoreGraphicsSlots(call, 9)) return 0; CGContextRef context = SlotHostObject(call, 0); CGGradientRef gradient = SlotHostObject(call, 1); if(!context || !gradient) return 0; const u32 options = SlotU32(call, 8); const u32 validOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; if(options & ~validOptions) return 0; CGContextDrawRadialGradient(context, gradient, CGPointMake(SlotCGFloat(call, 2), SlotCGFloat(call, 3)), SlotCGFloat(call, 4), CGPointMake(SlotCGFloat(call, 5), SlotCGFloat(call, 6)), SlotCGFloat(call, 7), static_cast(options)); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpContextAddEllipseInRect: case LC32CoreGraphicsOpContextClipToRect: case LC32CoreGraphicsOpContextFillEllipseInRect: case LC32CoreGraphicsOpContextStrokeEllipseInRect: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; const CGRect rect = SlotRect(call, 1); switch(static_cast(opcode)) { case LC32CoreGraphicsOpContextAddEllipseInRect: CGContextAddEllipseInRect(context, rect); break; case LC32CoreGraphicsOpContextClipToRect: CGContextClipToRect(context, rect); break; case LC32CoreGraphicsOpContextFillEllipseInRect: CGContextFillEllipseInRect(context, rect); SyncBitmapBacking(context, FindBitmapBacking(context)); break; case LC32CoreGraphicsOpContextStrokeEllipseInRect: CGContextStrokeEllipseInRect(context, rect); SyncBitmapBacking(context, FindBitmapBacking(context)); break; default: break; } return 1; } case LC32CoreGraphicsOpContextSetFillColorWithColor: case LC32CoreGraphicsOpContextSetStrokeColorWithColor: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); CGColorRef color = SlotHostObject(call, 1); if(!context || !color) return 0; if(static_cast(opcode) == LC32CoreGraphicsOpContextSetFillColorWithColor) { CGContextSetFillColorWithColor(context, color); } else { CGContextSetStrokeColorWithColor(context, color); } return 1; } case LC32CoreGraphicsOpContextSetRGBFillColor: case LC32CoreGraphicsOpContextSetRGBStrokeColor: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; if(static_cast(opcode) == LC32CoreGraphicsOpContextSetRGBFillColor) { CGContextSetRGBFillColor(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4)); } else { CGContextSetRGBStrokeColor(context, SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4)); } return 1; } case LC32CoreGraphicsOpContextSetShadowWithColor: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGContextRef context = SlotHostObject(call, 0); CGColorRef color = SlotHostObject(call, 4); if(!context) return 0; CGContextSetShadowWithColor(context, CGSizeMake(SlotCGFloat(call, 1), SlotCGFloat(call, 2)), SlotCGFloat(call, 3), color); return 1; } case LC32CoreGraphicsOpContextSetShadow: { if(!RequireCoreGraphicsSlots(call, 4)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextSetShadow(context, CGSizeMake(SlotCGFloat(call, 1), SlotCGFloat(call, 2)), SlotCGFloat(call, 3)); return 1; } case LC32CoreGraphicsOpContextSetFillColor: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 guestComponents = SlotU32(call, 1); if(!context || !guestComponents) return 0; const auto getFillColorSpace = GetContextFillColorSpaceFunction(); if(!getFillColorSpace) { ReportMissingContextFillColorSpaceFunction(); return 0; } CGColorSpaceRef space = getFillColorSpace(context); if(!space || CGColorSpaceGetModel(space) == kCGColorSpaceModelPattern) { return 0; } const size_t colorComponentCount = CGColorSpaceGetNumberOfComponents(space); if(!colorComponentCount || colorComponentCount >= kMaximumColorComponents) { return 0; } std::vector components; const CGFloat *componentPointer = nullptr; if(!ReadGuestCGFloatArray(guestComponents, colorComponentCount + 1, false, components, componentPointer)) { return 0; } CGContextSetFillColor(context, componentPointer); return 1; } case LC32CoreGraphicsOpContextSetBlendMode: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 mode = SlotU32(call, 1); if(!context || mode > kCGBlendModePlusLighter) return 0; CGContextSetBlendMode(context, static_cast(mode)); return 1; } case LC32CoreGraphicsOpContextSetInterpolationQuality: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 quality = SlotU32(call, 1); if(!context || quality > kCGInterpolationMedium) return 0; CGContextSetInterpolationQuality(context, static_cast(quality)); return 1; } case LC32CoreGraphicsOpContextSetLineCap: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 cap = SlotU32(call, 1); if(!context || cap > kCGLineCapSquare) return 0; CGContextSetLineCap(context, static_cast(cap)); return 1; } case LC32CoreGraphicsOpContextSetLineJoin: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 join = SlotU32(call, 1); if(!context || join > kCGLineJoinBevel) return 0; CGContextSetLineJoin(context, static_cast(join)); return 1; } case LC32CoreGraphicsOpContextSetLineWidth: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextSetLineWidth(context, SlotCGFloat(call, 1)); return 1; } case LC32CoreGraphicsOpContextSetAllowsAntialiasing: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 allowsAntialiasing = SlotU32(call, 1); if(!context || allowsAntialiasing > 1) return 0; CGContextSetAllowsAntialiasing(context, allowsAntialiasing != 0); return 1; } case LC32CoreGraphicsOpContextSetAllowsFontSubpixelPositioning: case LC32CoreGraphicsOpContextSetAllowsFontSmoothing: case LC32CoreGraphicsOpContextSetAllowsFontSubpixelQuantization: case LC32CoreGraphicsOpContextSetShouldSmoothFonts: case LC32CoreGraphicsOpContextSetShouldSubpixelPositionFonts: case LC32CoreGraphicsOpContextSetShouldSubpixelQuantizeFonts: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 enabled = SlotU32(call, 1); if(!context || enabled > 1) return 0; switch(opcode) { case LC32CoreGraphicsOpContextSetAllowsFontSubpixelPositioning: CGContextSetAllowsFontSubpixelPositioning(context, enabled != 0); break; case LC32CoreGraphicsOpContextSetAllowsFontSmoothing: CGContextSetAllowsFontSmoothing(context, enabled != 0); break; case LC32CoreGraphicsOpContextSetAllowsFontSubpixelQuantization: CGContextSetAllowsFontSubpixelQuantization(context, enabled != 0); break; case LC32CoreGraphicsOpContextSetShouldSmoothFonts: CGContextSetShouldSmoothFonts(context, enabled != 0); break; case LC32CoreGraphicsOpContextSetShouldSubpixelPositionFonts: CGContextSetShouldSubpixelPositionFonts(context, enabled != 0); break; default: CGContextSetShouldSubpixelQuantizeFonts(context, enabled != 0); break; } return 1; } case LC32CoreGraphicsOpContextSetShouldAntialias: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 shouldAntialias = SlotU32(call, 1); if(!context || shouldAntialias > 1) return 0; CGContextSetShouldAntialias(context, shouldAntialias != 0); return 1; } case LC32CoreGraphicsOpContextSetTextMatrix: { if(!RequireCoreGraphicsSlots(call, 7)) return 0; CGContextRef context = SlotHostObject(call, 0); if(!context) return 0; CGContextSetTextMatrix(context, CGAffineTransformMake( SlotCGFloat(call, 1), SlotCGFloat(call, 2), SlotCGFloat(call, 3), SlotCGFloat(call, 4), SlotCGFloat(call, 5), SlotCGFloat(call, 6))); return 1; } case LC32CoreGraphicsOpImageCreateWithImageInRect: { if(!RequireCoreGraphicsSlots(call, 5)) return 0; CGImageRef image = SlotHostObject(call, 0); if(!image) return 0; CGImageRef result = CGImageCreateWithImageInRect( image, SlotRect(call, 1)); return result ? LC32GuestObjectForOwnedHostObject(result) : 0; } case LC32CoreGraphicsOpImageCreateWithMask: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGImageRef image = SlotHostObject(call, 0); CGImageRef mask = SlotHostObject(call, 1); if(!image || !mask) return 0; CGImageRef result = CGImageCreateWithMask(image, mask); return result ? LC32GuestObjectForOwnedHostObject(result) : 0; } case LC32CoreGraphicsOpImageCreateCopy: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); if(!image) return 0; CGImageRef result = CGImageCreateCopy(image); return result ? LC32GuestObjectForOwnedHostObject(result) : 0; } case LC32CoreGraphicsOpImageGetAlphaInfo: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetAlphaInfo(image)) : 0; } case LC32CoreGraphicsOpImageGetBitmapInfo: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetBitmapInfo(image)) : 0; } case LC32CoreGraphicsOpImageGetBitsPerComponent: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetBitsPerComponent(image)) : 0; } case LC32CoreGraphicsOpImageGetColorSpace: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); CGColorSpaceRef space = image ? CGImageGetColorSpace(image) : nullptr; return space ? [(id)space guest_self] : 0; } case LC32CoreGraphicsOpImageGetDataProvider: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); CGDataProviderRef provider = image ? CGImageGetDataProvider(image) : nullptr; return provider ? [(id)provider guest_self] : 0; } case LC32CoreGraphicsOpPathAddArcToPoint: case LC32CoreGraphicsOpPathAddCurveToPoint: case LC32CoreGraphicsOpPathAddRect: case LC32CoreGraphicsOpPathAddEllipseInRect: case LC32CoreGraphicsOpPathAddQuadCurveToPoint: { const auto operation = static_cast(opcode); const u32 expectedSlots = operation == LC32CoreGraphicsOpPathAddArcToPoint ? 13 : operation == LC32CoreGraphicsOpPathAddCurveToPoint ? 14 : 12; if(!RequireCoreGraphicsSlots(call, expectedSlots)) return 0; CGMutablePathRef path = SlotHostObject(call, 0); if(!path) return 0; CGAffineTransform transformStorage; const CGAffineTransform *transform; if(!SlotOptionalTransform(call, 1, 2, transformStorage, transform)) return 0; if(operation == LC32CoreGraphicsOpPathAddArcToPoint) { CGPathAddArcToPoint(path, transform, SlotCGFloat(call, 8), SlotCGFloat(call, 9), SlotCGFloat(call, 10), SlotCGFloat(call, 11), SlotCGFloat(call, 12)); } else if(operation == LC32CoreGraphicsOpPathAddCurveToPoint) { CGPathAddCurveToPoint(path, transform, SlotCGFloat(call, 8), SlotCGFloat(call, 9), SlotCGFloat(call, 10), SlotCGFloat(call, 11), SlotCGFloat(call, 12), SlotCGFloat(call, 13)); } else if(operation == LC32CoreGraphicsOpPathAddQuadCurveToPoint) { CGPathAddQuadCurveToPoint(path, transform, SlotCGFloat(call, 8), SlotCGFloat(call, 9), SlotCGFloat(call, 10), SlotCGFloat(call, 11)); } else { if(operation == LC32CoreGraphicsOpPathAddEllipseInRect) CGPathAddEllipseInRect(path, transform, SlotRect(call, 8)); else CGPathAddRect(path, transform, SlotRect(call, 8)); } return 1; } case LC32CoreGraphicsOpContextSetStrokeColorSpace: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); CGColorSpaceRef space = SlotHostObject(call, 1); if(!context || !space) return 0; CGContextSetStrokeColorSpace(context, space); return 1; } case LC32CoreGraphicsOpContextSetStrokeColor: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGContextRef context = SlotHostObject(call, 0); const u32 guestComponents = SlotU32(call, 1); if(!context || !guestComponents) return 0; const auto getStrokeColorSpace = GetContextStrokeColorSpaceFunction(); if(!getStrokeColorSpace) { ReportMissingContextStrokeColorSpaceFunction(); return 0; } CGColorSpaceRef space = getStrokeColorSpace(context); if(!space || CGColorSpaceGetModel(space) == kCGColorSpaceModelPattern) { return 0; } const size_t colorComponentCount = CGColorSpaceGetNumberOfComponents(space); if(!colorComponentCount || colorComponentCount >= kMaximumColorComponents) { return 0; } std::vector components; const CGFloat *componentPointer = nullptr; if(!ReadGuestCGFloatArray(guestComponents, colorComponentCount + 1, false, components, componentPointer)) { return 0; } CGContextSetStrokeColor(context, componentPointer); return 1; } case LC32CoreGraphicsOpContextStrokeLineSegments: { if(!RequireCoreGraphicsSlots(call, 3)) return 0; CGContextRef context = SlotHostObject(call, 0); std::vector points; if(!context || !ReadGuestPoints(SlotU32(call, 1), SlotU32(call, 2), points)) { return 0; } CGContextStrokeLineSegments( context, points.data(), points.size()); SyncBitmapBacking(context, FindBitmapBacking(context)); return 1; } case LC32CoreGraphicsOpImageGetBitsPerPixel: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetBitsPerPixel(image)) : 0; } case LC32CoreGraphicsOpImageGetBytesPerRow: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGImageRef image = SlotHostObject(call, 0); return image ? static_cast(CGImageGetBytesPerRow(image)) : 0; } case LC32CoreGraphicsOpPathCreateCopy: { if(!RequireCoreGraphicsSlots(call, 1)) return 0; CGPathRef path = SlotHostObject(call, 0); if(!path) return 0; CGPathRef result = CGPathCreateCopy(path); return result ? LC32GuestObjectForOwnedHostObject(result) : 0; } case LC32CoreGraphicsOpPathGetBoundingBox: { if(!RequireCoreGraphicsSlots(call, 2)) return 0; CGPathRef path = SlotHostObject(call, 0); return path && WriteGuestRect( SlotU32(call, 1), CGPathGetBoundingBox(path)); } } return 0; } __END_DECLS