Skip to content

Commit 474a62b

Browse files
committed
chore: convert JIT checks to flags
1 parent ba4a002 commit 474a62b

10 files changed

Lines changed: 96 additions & 43 deletions

Natives/JavaLauncher.m

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ void init_loadCustomJvmFlags(int* argc, const char** argv) {
101101
int launchJVM(NSString *username, id launchTarget, int width, int height, int minVersion) {
102102
NSLog(@"[JavaLauncher] Beginning JVM launch");
103103

104-
BOOL requiresTXMWorkaround = DeviceRequiresTXMWorkaround();
104+
init_loadDefaultEnv();
105+
init_loadCustomEnv();
106+
107+
BOOL requiresTXMWorkaround = DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM);
105108
BOOL jit26AlwaysAttached = getPrefBool(@"debug.debug_always_attached_jit");
106109
if (requiresTXMWorkaround) {
107110
static void *result;
@@ -143,10 +146,6 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi
143146
NSLog(@"[DyldLVBypass] Hook disabled! Loading unsigned dylib will cause code signature error.");
144147
}
145148

146-
147-
init_loadDefaultEnv();
148-
init_loadCustomEnv();
149-
150149
BOOL launchJar = NO;
151150
NSString *gameDir;
152151
NSString *defaultJRETag;

Natives/LauncherNavigationController.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ - (void)receiveNotification:(NSNotification *)notification {
423423

424424
- (void)invokeAfterJITEnabled:(void(^)(void))handler {
425425
localVersionList = remoteVersionList = nil;
426-
BOOL hasTrollStoreJIT = getEntitlementValue(@"jb.pmap_cs_custom_trust");
426+
BOOL hasTrollStoreJIT = getEntitlementValue(@"jb.pmap_cs.custom_trust");
427427
BOOL isLiveContainer = getenv("LC_HOME_PATH") != NULL;
428428

429429
if (isJITEnabled(false)) {
@@ -440,7 +440,7 @@ - (void)invokeAfterJITEnabled:(void(^)(void))handler {
440440
return;
441441
} else if (@available(iOS 17.4, *)) {
442442
NSString *scriptDataString = @"";
443-
if(DeviceRequiresTXMWorkaround()) {
443+
if(DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM)) {
444444
NSData *scriptData = [NSData dataWithContentsOfFile:[NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"UniversalJIT26.js"]];
445445
scriptDataString = [@"&script-data=" stringByAppendingString:[scriptData base64EncodedStringWithOptions:0]];
446446
}

Natives/LauncherPreferencesViewController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ - (void)viewDidLoad
371371
@"icon": @"app.connected.to.app.below.fill",
372372
@"type": self.typeSwitch,
373373
@"enableCondition": ^BOOL(){
374-
return DeviceRequiresTXMWorkaround() && whenNotInGame();
374+
return DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM) && whenNotInGame();
375375
},
376376
},
377377
@{@"key": @"debug_skip_wait_jit",

Natives/dyld_bypass_validation.m

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ bool redirectFunctionDirect(char *name, void *patchAddr, void *target) {
6868
}
6969
// redirectFunction for iOS 26+ (TXM)
7070
bool redirectFunctionMirrored(char *name, void *patchAddr, void *target) {
71-
if (DeviceRequiresTXMWorkaround()) {
71+
if (DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM)) {
7272
JIT26PrepareRegionForPatching(patchAddr, sizeof(patch));
7373
}
7474
// mirror `addr` (rx, JIT applied) to `mirrored` (rw)
@@ -160,8 +160,9 @@ bool searchAndPatch(char *name, char *base, char *signature, int length, void *t
160160

161161
void *map = __mmap(addr, len, prot, flags, fd, offset);
162162
if (map == MAP_FAILED && fd && (prot & PROT_EXEC)) {
163+
printf("[DyldLVBypass] mmap(prot=%d, flags=%d, fd=%d)\n", prot, flags, fd);
163164
map = __mmap(addr, len, prot, flags | MAP_PRIVATE | MAP_ANON, 0, 0);
164-
if (DeviceRequiresTXMWorkaround()) {
165+
if (DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM)) {
165166
JIT26PrepareRegion(map, len);
166167
}
167168

@@ -192,7 +193,11 @@ int hooked___fcntl(int fildes, int cmd, void *param) {
192193
#if !(TARGET_OS_MACCATALYST || TARGET_OS_SIMULATOR)
193194
// attempt to attach code signature on iOS only as the binaries may have been signed
194195
// on macOS, attaching on unsigned binaries without CS_DEBUGGED will crash
195-
orig_fcntl(fildes, cmd, param);
196+
// ignoreFcntl is a special case for vphone or dev unit with TXM JIT enforcement disabled
197+
BOOL ignoreFcntl = DeviceHasJITFlags(JIT_FLAG_IS_IOS_26 | JIT_FLAG_HAS_TXM) && !DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED);
198+
if (!ignoreFcntl) {
199+
orig_fcntl(fildes, cmd, param);
200+
}
196201
#endif
197202
fsignatures_t *fsig = (fsignatures_t*)param;
198203
// called to check that cert covers file.. so we'll make it cover everything ;)
@@ -218,21 +223,23 @@ void init_bypassDyldLibValidation() {
218223

219224
NSDebugLog(@"[DyldLVBypass] init");
220225

221-
if (@available(iOS 26.0, *)) {
222-
if (DeviceRequiresTXMWorkaround()) {
226+
switch ((int)DeviceGetJITFlags(YES)) {
227+
case JIT_FLAG_FORCE_MIRRORED | JIT_FLAG_HAS_TXM:
223228
NSDebugLog(@"[DyldLVBypass] Using redirectFunctionMirrored");
224229
redirectFunction = redirectFunctionMirrored;
225-
} else {
230+
break;
231+
case JIT_FLAG_FORCE_MIRRORED:
226232
// Special special case for non-TXM iOS 26+
227233
// We can JIT without script, but we cannot modify existing code in dsc without it.
228234
// Therefore, we choose a hook method that avoids patching code in dsc completely, using hardware breakpoint.
229235
// The function only stashes the original function pointers, and the breakpoint handler will redirect to our hook
230236
NSDebugLog(@"[DyldLVBypass] Using redirectFunctionHWBreakpoint");
231237
redirectFunction = redirectFunctionHWBreakpoint;
232-
}
233-
} else {
234-
NSDebugLog(@"[DyldLVBypass] Using redirectFunctionDirect");
235-
redirectFunction = redirectFunctionDirect;
238+
break;
239+
default:
240+
NSDebugLog(@"[DyldLVBypass] Using redirectFunctionDirect");
241+
redirectFunction = redirectFunctionDirect;
242+
break;
236243
}
237244

238245
// Modifying exec page during execution may cause SIGBUS, so ignore it now

Natives/input_bridge_v3.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ void CTCDesktopPeer_openGlobal(JNIEnv *env, jclass clazz, jstring path) {
139139

140140
void hackFix18LWJGL(void *addr) {
141141
addr = (void *)((uintptr_t)addr & ~PAGE_MASK);
142-
if(@available(iOS 19.0, *)) return;
142+
if(DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED)) return;
143143
if(!mprotect(addr, PAGE_SIZE, PROT_READ | PROT_EXEC)) return;
144144
// FIXME: For some reason the one page in liblwjgl.dylib is mapped as r-x/rwx (COW), and recent builds on iOS 18 switches it to r--/rw- causing codesign failure. Here we hack it to map anon page to get r-x back
145145
char tempPage[PAGE_SIZE];

Natives/main.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ bool init_checkForJailbreak() {
8585

8686
// Check if posix_spawn is hooked
8787
for (int i=0; i < _dyld_image_count(); i++) {
88-
if (strcmp(_dyld_get_image_name(i),"/usr/lib/pspawn_payload-stg2.dylib") == 0) {
88+
if (strcmp(_dyld_get_image_name(i),"/usr/lib/pspawn_payload-stg2.dylib") == 0 ||
89+
strstr(_dyld_get_image_name(i),"/systemhook.dylib") == 0) {
8990
return true;
9091
}
9192
}

Natives/main_hook.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ void hooked_exit(int code) {
6767

6868
void* hooked_dlopen(const char* path, int mode) {
6969
BOOL shouldUseDyldBypass26PPL = NO;
70-
if (@available(iOS 19.0, *)) {
71-
shouldUseDyldBypass26PPL = hwRedirectOrig[0] && !DeviceRequiresTXMWorkaround();
70+
if (DeviceHasJITFlags(JIT_FLAG_FORCE_MIRRORED)) {
71+
shouldUseDyldBypass26PPL = hwRedirectOrig[0] && !DeviceHasJITFlags(JIT_FLAG_HAS_TXM);
7272
}
7373
// Only patch Mach-O and use dyld bypass dylib is in the home dir
7474
// or tmp dir: LiveContainer makes a symlink to its own tmp dir so checking home dir alone would fail

Natives/utils.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ void* JIT26PrepareRegion(void *addr, size_t len);
6666
void JIT26PrepareRegionForPatching(void *addr, size_t len);
6767
void JIT26SetDetachAfterFirstBr(BOOL value);
6868
void JIT26SendJITScript(NSString* script);
69-
BOOL DeviceRequiresTXMWorkaround(void);
69+
70+
// Device JIT flags
71+
typedef enum {
72+
JIT_FLAG_IS_IOS_26 = 1 << 0,
73+
JIT_FLAG_FORCE_MIRRORED = 1 << 1,
74+
JIT_FLAG_HAS_TXM = 1 << 2,
75+
} JITFlags;
76+
JITFlags DeviceGetJITFlags(BOOL refresh);
77+
BOOL DeviceHasJITFlags(JITFlags flags);
7078

7179
// Init functions
7280
void init_bypassDyldLibValidation();

Natives/utils.m

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
BOOL getEntitlementValue(NSString *key) {
1616
void *secTask = SecTaskCreateFromSelf(NULL);
1717
CFTypeRef value = SecTaskCopyValueForEntitlement(SecTaskCreateFromSelf(NULL), key, nil);
18-
if (value != nil) {
19-
CFRelease(value);
20-
}
2118
CFRelease(secTask);
22-
23-
return value != nil && [(__bridge id)value boolValue];
19+
if (value == nil) {
20+
return NO;
21+
}
22+
CFRelease(value);
23+
return ![(__bridge id)value isKindOfClass:NSNumber.class] || [(__bridge id)value boolValue];
2424
}
2525

2626
BOOL isJITEnabled(BOOL checkCSFlags) {
@@ -184,20 +184,58 @@ void JIT26SendJITScript(NSString* script) {
184184
NSCAssert(script, @"Script must not be nil");
185185
BreakSendJITScript((char*)script.UTF8String, script.length);
186186
}
187-
BOOL DeviceRequiresTXMWorkaround(void) {
188-
if (@available(iOS 26.0, *)) {
189-
DIR *d = opendir("/private/preboot");
190-
if(!d) return NO;
191-
struct dirent *dir;
192-
char txmPath[PATH_MAX];
193-
while ((dir = readdir(d)) != NULL) {
194-
if(strlen(dir->d_name) == 96) {
195-
snprintf(txmPath, sizeof(txmPath), "/private/preboot/%s/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", dir->d_name);
196-
break;
197-
}
187+
188+
BOOL DeviceCanCreateRXMap(void) {
189+
// This is only guaranteed to be accurate when JIT is already enabled. Obviously this is only useful for vphone and similar internal environments where JIT is always enabled.
190+
uint32_t *map = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
191+
assert(map != MAP_FAILED);
192+
*map = 0xFFFFFFFF;
193+
int ret = mprotect(map, getpagesize(), PROT_READ | PROT_EXEC) | mprotect(map, getpagesize(), PROT_READ | PROT_EXEC);
194+
munmap(map, getpagesize());
195+
return ret == 0;
196+
}
197+
BOOL DeviceHasTXM(void) {
198+
DIR *d = opendir("/private/preboot");
199+
if(!d) return NO;
200+
struct dirent *dir;
201+
char txmPath[PATH_MAX];
202+
while ((dir = readdir(d)) != NULL) {
203+
if(strlen(dir->d_name) == 96) {
204+
snprintf(txmPath, sizeof(txmPath), "/private/preboot/%s/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", dir->d_name);
205+
break;
198206
}
199-
closedir(d);
200-
return access(txmPath, F_OK) == 0;
201207
}
202-
return NO;
208+
closedir(d);
209+
return access(txmPath, F_OK) == 0;
210+
}
211+
JITFlags DeviceGetJITFlags(BOOL refresh) {
212+
static JITFlags cachedFlags = 0;
213+
static dispatch_once_t onceToken;
214+
if (refresh) onceToken = 0;
215+
dispatch_once(&onceToken, ^{
216+
const char *s = getenv("JIT_FLAGS");
217+
if (s) {
218+
if (s[0] == '0' && tolower(s[1]) == 'b') {
219+
cachedFlags = strtoul(s + 2, NULL, 2);
220+
} else {
221+
cachedFlags = strtoul(s, NULL, 0);
222+
}
223+
NSLog(@"[JIT] Using overridden JIT flags: 0x%X", cachedFlags);
224+
return;
225+
}
226+
227+
if (@available(iOS 26.0, *)) {
228+
cachedFlags |= JIT_FLAG_IS_IOS_26;
229+
if (!DeviceCanCreateRXMap()) {
230+
cachedFlags |= JIT_FLAG_FORCE_MIRRORED;
231+
}
232+
}
233+
if (DeviceHasTXM()) {
234+
cachedFlags |= JIT_FLAG_HAS_TXM;
235+
}
236+
});
237+
return cachedFlags;
238+
}
239+
BOOL DeviceHasJITFlags(JITFlags flags) {
240+
return (DeviceGetJITFlags(NO) & flags) == flags;
203241
}

entitlements.sideload.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<true/>
1919

2020
<!-- Custom entitlement to indicate this was installed with TrollStore, still sandboxed -->
21-
<key>jb.pmap_cs_custom_trust</key>
21+
<key>jb.pmap_cs.custom_trust</key>
2222
<string>PMAP_CS_APP_STORE</string>
2323
</dict>
2424
</plist>

0 commit comments

Comments
 (0)