Created
March 14, 2026 02:25
-
-
Save 34306/267423abbf051e56804fa2f268b78a69 to your computer and use it in GitHub Desktop.
emojiport_fix_ios26.x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #import <Foundation/Foundation.h> | |
| #import <CoreFoundation/CoreFoundation.h> | |
| #import <CoreText/CoreText.h> | |
| #import <substrate.h> | |
| #import <dlfcn.h> | |
| #import <os/log.h> | |
| #define EFMLog(fmt, ...) os_log_fault(OS_LOG_DEFAULT, "EmojiPort26 - " fmt, ##__VA_ARGS__) | |
| // New Emoji 17.0 codepoints (iOS 26.4) that need to be added to the character set on iOS 26.2 | |
| static const UInt32 kNewEmoji17Codepoints[] = { | |
| 0x1F6D8, // Landslide | |
| 0x1F7F0, // Heavy Equals Sign (if not already present) | |
| 0x1FA8A, // Trombone | |
| 0x1FA8E, // Treasure Chest | |
| 0x1FAC8, // Hairy Creature (Bigfoot) | |
| 0x1FACD, // Orca | |
| 0x1FAEA, // Distorted Face | |
| 0x1FAEF, // Fight Cloud | |
| }; | |
| static const int kNewEmoji17Count = sizeof(kNewEmoji17Codepoints) / sizeof(kNewEmoji17Codepoints[0]); | |
| // Ballet Dancer ZWJ base (U+1F9D1 U+200D U+1FA70) - the base codepoint U+1FA70 might need to be in the set | |
| static const UInt32 kNewEmoji17Extra[] = { | |
| 0x1FA70, // Ballet shoes (for ballet dancer ZWJ sequence) | |
| }; | |
| static const int kNewEmoji17ExtraCount = sizeof(kNewEmoji17Extra) / sizeof(kNewEmoji17Extra[0]); | |
| #pragma mark - CoreText Character Set Hook | |
| // Hook CreateCharacterSetForFont to add new emoji codepoints | |
| %group CharacterSetHook | |
| CFCharacterSetRef (*CreateCharacterSetForFont)(CFStringRef const) = NULL; | |
| CFCharacterSetRef (*CreateCharacterSetWithCompressedBitmapRepresentation)(const CFDataRef) = NULL; | |
| %hookf(CFCharacterSetRef, CreateCharacterSetForFont, CFStringRef const fontName) { | |
| CFCharacterSetRef origSet = %orig(fontName); | |
| // Only modify for AppleColorEmoji | |
| if (!fontName) return origSet; | |
| if (!CFEqual(fontName, CFSTR("AppleColorEmoji")) && | |
| !CFEqual(fontName, CFSTR(".AppleColorEmojiUI"))) | |
| return origSet; | |
| // Create mutable copy and add new codepoints | |
| CFMutableCharacterSetRef newSet = CFCharacterSetCreateMutableCopy(kCFAllocatorDefault, origSet); | |
| for (int i = 0; i < kNewEmoji17Count; i++) { | |
| CFCharacterSetAddCharactersInRange(newSet, CFRangeMake(kNewEmoji17Codepoints[i], 1)); | |
| } | |
| for (int i = 0; i < kNewEmoji17ExtraCount; i++) { | |
| CFCharacterSetAddCharactersInRange(newSet, CFRangeMake(kNewEmoji17Extra[i], 1)); | |
| } | |
| EFMLog("[EmojiPort26] Added %d new Emoji 17.0 codepoints to %@ character set", | |
| kNewEmoji17Count + kNewEmoji17ExtraCount, fontName); | |
| return newSet; | |
| } | |
| %end | |
| #pragma mark - ICU Emoji Property Hook | |
| // Hook u_hasBinaryProperty to recognize new emoji codepoints | |
| %group ICUHook | |
| static BOOL isNewEmoji17(UInt32 c) { | |
| for (int i = 0; i < kNewEmoji17Count; i++) { | |
| if (c == kNewEmoji17Codepoints[i]) return YES; | |
| } | |
| for (int i = 0; i < kNewEmoji17ExtraCount; i++) { | |
| if (c == kNewEmoji17Extra[i]) return YES; | |
| } | |
| return NO; | |
| } | |
| // UProperty constants from ICU | |
| #define UCHAR_EMOJI 57 | |
| #define UCHAR_EMOJI_PRESENTATION 58 | |
| #define UCHAR_EXTENDED_PICTOGRAPHIC 64 | |
| #define UCHAR_BASIC_EMOJI 65 | |
| #define UCHAR_RGI_EMOJI 71 | |
| typedef int8_t UBool; | |
| typedef int32_t UChar32; | |
| typedef int32_t UProperty; | |
| UBool (*orig_u_hasBinaryProperty)(UChar32, UProperty) = NULL; | |
| UBool hook_u_hasBinaryProperty(UChar32 c, UProperty which) { | |
| if (isNewEmoji17(c)) { | |
| switch (which) { | |
| case UCHAR_EMOJI: | |
| case UCHAR_EMOJI_PRESENTATION: | |
| case UCHAR_EXTENDED_PICTOGRAPHIC: | |
| case UCHAR_BASIC_EMOJI: | |
| case UCHAR_RGI_EMOJI: | |
| return 1; | |
| } | |
| } | |
| return orig_u_hasBinaryProperty(c, which); | |
| } | |
| %end | |
| #pragma mark - Constructor | |
| %ctor { | |
| @autoreleasepool { | |
| EFMLog("[EmojiPort26] Initializing Emoji 17.0 support for iOS 26.2"); | |
| // Hook CreateCharacterSetForFont in CoreText | |
| // Use raw path - CoreText is in the shared cache, ROOT_PATH not needed | |
| MSImageRef ct = MSGetImageByName("/System/Library/Frameworks/CoreText.framework/CoreText"); | |
| if (ct) { | |
| CreateCharacterSetForFont = (CFCharacterSetRef (*)(CFStringRef const))MSFindSymbol(ct, "__Z25CreateCharacterSetForFontPK10__CFString"); | |
| CreateCharacterSetWithCompressedBitmapRepresentation = (CFCharacterSetRef (*)(const CFDataRef))MSFindSymbol(ct, "__Z52CreateCharacterSetWithCompressedBitmapRepresentationPK8__CFData"); | |
| if (CreateCharacterSetForFont) { | |
| EFMLog("[EmojiPort26] Hooking CreateCharacterSetForFont"); | |
| %init(CharacterSetHook); | |
| } else { | |
| EFMLog("[EmojiPort26] CreateCharacterSetForFont not found!"); | |
| } | |
| } | |
| // Hook u_hasBinaryProperty in libicucore | |
| MSImageRef icu = MSGetImageByName("/usr/lib/libicucore.A.dylib"); | |
| if (icu) { | |
| void *sym = MSFindSymbol(icu, "_u_hasBinaryProperty"); | |
| if (sym) { | |
| EFMLog("[EmojiPort26] Hooking u_hasBinaryProperty"); | |
| MSHookFunction(sym, (void *)&hook_u_hasBinaryProperty, (void **)&orig_u_hasBinaryProperty); | |
| } else { | |
| EFMLog("[EmojiPort26] u_hasBinaryProperty not found, trying dlsym"); | |
| void *icuHandle = dlopen("/usr/lib/libicucore.A.dylib", RTLD_LAZY); | |
| if (icuHandle) { | |
| sym = dlsym(icuHandle, "u_hasBinaryProperty"); | |
| if (sym) { | |
| EFMLog("[EmojiPort26] Hooking u_hasBinaryProperty via dlsym"); | |
| MSHookFunction(sym, (void *)&hook_u_hasBinaryProperty, (void **)&orig_u_hasBinaryProperty); | |
| } else { | |
| EFMLog("[EmojiPort26] u_hasBinaryProperty not found via dlsym either"); | |
| } | |
| } | |
| } | |
| } | |
| // Write debug to /tmp (writable by all) | |
| NSMutableString *debugLog = [NSMutableString string]; | |
| [debugLog appendFormat:@"[%@] EmojiPort26 loaded in %@\n", [NSDate date], [[NSProcessInfo processInfo] processName]]; | |
| [debugLog appendFormat:@"CreateCharacterSetForFont: %p\n", CreateCharacterSetForFont]; | |
| [debugLog appendFormat:@"u_hasBinaryProperty hooked: %d\n", orig_u_hasBinaryProperty != NULL]; | |
| [debugLog appendString:@"---\n"]; | |
| NSString *logPath = @"/tmp/EmojiPort26_test"; | |
| NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:logPath]; | |
| if (!fh) { | |
| [[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil]; | |
| fh = [NSFileHandle fileHandleForWritingAtPath:logPath]; | |
| } | |
| [fh seekToEndOfFile]; | |
| [fh writeData:[debugLog dataUsingEncoding:NSUTF8StringEncoding]]; | |
| [fh closeFile]; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment