Skip to content

Instantly share code, notes, and snippets.

@34306
Created March 14, 2026 02:25
Show Gist options
  • Select an option

  • Save 34306/267423abbf051e56804fa2f268b78a69 to your computer and use it in GitHub Desktop.

Select an option

Save 34306/267423abbf051e56804fa2f268b78a69 to your computer and use it in GitHub Desktop.
emojiport_fix_ios26.x
#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