Fix first launch and package hook as a signed framework
This commit is contained in:
1 parent
c1d0293404
commit
97a4fad71f
6 files changed
+147
-20
No files matched your search
Binary file not shown.
@@ -15,7 +15,7 @@
|
|||||||
- 源码 / Source:[source/](source/)(iOS 27 Hook、内置文件浏览器集成与构建脚本 / iOS 27 hook, file-browser integration and build scripts)
|
- 源码 / Source:[source/](source/)(iOS 27 Hook、内置文件浏览器集成与构建脚本 / iOS 27 hook, file-browser integration and build scripts)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
eebc7b69884aaf302dbd9a0750f9b47f96800867fe5a316b83be9957facbd12c AppsManager_iOS27.ipa
|
3508be39187cc387c13490a1c2130087172057f931a297bad37df685d754a098 AppsManager_iOS27.ipa
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能 / Features
|
## 功能 / Features
|
||||||
@@ -56,14 +56,25 @@ eebc7b69884aaf302dbd9a0750f9b47f96800867fe5a316b83be9957facbd12c AppsManager_iO
|
|||||||
**中文:**
|
**中文:**
|
||||||
|
|
||||||
1. 使用 AltStore、SideStore 或其他签名工具安装 `AppsManager_iOS27.ipa`。
|
1. 使用 AltStore、SideStore 或其他签名工具安装 `AppsManager_iOS27.ipa`。
|
||||||
2. 当前 IPA 使用自用证书签名;其他设备安装时请使用自己的证书和描述文件重签。
|
2. 当前 IPA 使用自用证书签名。其他设备请使用自己的证书和描述文件重签;描述文件必须包含 **App Group**,且签名工具需要递归签名 `Frameworks`。
|
||||||
3. 首次启动会执行数据准备,等待进度条完成后再进入应用列表。
|
3. 首次启动会执行数据准备,等待进度完成后再进入应用列表。
|
||||||
|
|
||||||
**English:**
|
**English:**
|
||||||
|
|
||||||
1. Install `AppsManager_iOS27.ipa` with AltStore, SideStore, or another signing tool.
|
1. Install `AppsManager_iOS27.ipa` with AltStore, SideStore, or another signing tool.
|
||||||
2. This IPA is signed with a personal certificate. Re-sign it with your own certificate and provisioning profile for other devices.
|
2. This IPA is signed with a personal certificate. Re-sign it with your own certificate and provisioning profile; the profile must include an **App Group**, and the tool must sign `Frameworks` recursively.
|
||||||
3. The first launch prepares app data; wait for the progress bar to finish before using the app list.
|
3. The first launch prepares app data; wait for the progress to finish before using the app list.
|
||||||
|
|
||||||
|
## 更新说明 / Updates
|
||||||
|
|
||||||
|
- 修复首次启动空白、一次性「开发者提示」遮挡列表的问题。
|
||||||
|
Fixed the blank first launch and the one-time developer alert covering the list.
|
||||||
|
- iOS 27 Hook 已移入 `Frameworks/AppsManagerHook.framework`,标准签名工具重签时会把它作为嵌套代码一并签名。
|
||||||
|
The iOS 27 hook now lives in `Frameworks/AppsManagerHook.framework`, so standard signing tools re-sign it as nested code.
|
||||||
|
- 备份、还原、抹掉后会重新计算对应 App 的数据大小。
|
||||||
|
The affected app's data size is recalculated after backup, restore, and wipe.
|
||||||
|
- 重签时必须使用包含 App Group 的证书和描述文件。
|
||||||
|
Re-signing requires a certificate and provisioning profile with an App Group entitlement.
|
||||||
|
|
||||||
## 说明与限制 / Notes & Limitations
|
## 说明与限制 / Notes & Limitations
|
||||||
|
|
||||||
@@ -83,5 +94,5 @@ eebc7b69884aaf302dbd9a0750f9b47f96800867fe5a316b83be9957facbd12c AppsManager_iO
|
|||||||
## 校验 / Checksum
|
## 校验 / Checksum
|
||||||
|
|
||||||
```text
|
```text
|
||||||
eebc7b69884aaf302dbd9a0750f9b47f96800867fe5a316b83be9957facbd12c AppsManager_iOS27.ipa
|
3508be39187cc387c13490a1c2130087172057f931a297bad37df685d754a098 AppsManager_iOS27.ipa
|
||||||
```
|
```
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
eebc7b69884aaf302dbd9a0750f9b47f96800867fe5a316b83be9957facbd12c AppsManager_iOS27.ipa
|
3508be39187cc387c13490a1c2130087172057f931a297bad37df685d754a098 AppsManager_iOS27.ipa
|
||||||
@@ -90,6 +90,25 @@ group.<your-team-id>.<your-app-id>.01
|
|||||||
|
|
||||||
The exact value must match your own profile. Do not reuse someone else's certificate, profile, or App Group.
|
The exact value must match your own profile. Do not reuse someone else's certificate, profile, or App Group.
|
||||||
|
|
||||||
|
The hook is stored as a nested framework at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Payload/AppsManager.app/Frameworks/AppsManagerHook.framework
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard signing tools must sign `Frameworks` recursively. If a tool signs only the main executable, the app will fail to launch after re-signing. The published build uses the framework layout specifically so tools such as AltStore, SideStore, Sideloadly, and similar signers handle the hook as nested code.
|
||||||
|
|
||||||
|
### 重签名 / Re-signing
|
||||||
|
|
||||||
|
- 必须保留 App Group 权限;如果新的描述文件没有 App Group,App 会提示 `No usable App Group`,无法访问其他 App 容器。
|
||||||
|
The new provisioning profile must include an App Group. Without it the app reports `No usable App Group` and cannot access other app containers.
|
||||||
|
- 修改 Bundle ID 时,必须同时使用与该 Bundle ID 匹配的描述文件和 entitlements。
|
||||||
|
When changing the Bundle ID, use a matching provisioning profile and entitlements.
|
||||||
|
- 签名工具必须递归签名 `Frameworks` 目录;不要只签名主程序。
|
||||||
|
The signing tool must recursively sign the `Frameworks` directory, not only the main executable.
|
||||||
|
- 免费 Apple ID 通常不支持 App Group,因此无法让 worse_query 正常工作。
|
||||||
|
Free Apple ID profiles generally do not support App Groups, so worse_query will not work with them.
|
||||||
|
|
||||||
## 第三方代码 / Third-party code
|
## 第三方代码 / Third-party code
|
||||||
|
|
||||||
This source includes and links code from:
|
This source includes and links code from:
|
||||||
|
|||||||
+43
-3
@@ -25,6 +25,7 @@ OUT_DIR=${OUT_DIR:-"$ROOT/out"}
|
|||||||
BUNDLE_ID=${BUNDLE_ID:-com.jbjf.appsmanager}
|
BUNDLE_ID=${BUNDLE_ID:-com.jbjf.appsmanager}
|
||||||
DYLIB="$ROOT/hook/AppsManagerHook.dylib"
|
DYLIB="$ROOT/hook/AppsManagerHook.dylib"
|
||||||
APP="$OUT_DIR/AppsManager.app"
|
APP="$OUT_DIR/AppsManager.app"
|
||||||
|
FRAMEWORK="$APP/Frameworks/AppsManagerHook.framework"
|
||||||
|
|
||||||
if [ ! -f "$DYLIB" ]; then
|
if [ ! -f "$DYLIB" ]; then
|
||||||
echo "Missing $DYLIB; run build_dylib.sh first." >&2
|
echo "Missing $DYLIB; run build_dylib.sh first." >&2
|
||||||
@@ -42,17 +43,55 @@ fi
|
|||||||
rm -rf "$APP"
|
rm -rf "$APP"
|
||||||
mkdir -p "$OUT_DIR"
|
mkdir -p "$OUT_DIR"
|
||||||
cp -R "$APP_SOURCE" "$APP"
|
cp -R "$APP_SOURCE" "$APP"
|
||||||
cp "$DYLIB" "$APP/AppsManagerHook.dylib"
|
mkdir -p "$FRAMEWORK"
|
||||||
|
cp "$DYLIB" "$FRAMEWORK/AppsManagerHook"
|
||||||
cp "$PROVISION" "$APP/embedded.mobileprovision"
|
cp "$PROVISION" "$APP/embedded.mobileprovision"
|
||||||
|
|
||||||
plutil -replace CFBundleIdentifier -string "$BUNDLE_ID" "$APP/Info.plist"
|
plutil -replace CFBundleIdentifier -string "$BUNDLE_ID" "$APP/Info.plist"
|
||||||
plutil -replace CFBundleDisplayName -string "Apps Manager" "$APP/Info.plist"
|
plutil -replace CFBundleDisplayName -string "Apps Manager" "$APP/Info.plist"
|
||||||
plutil -replace CFBundleName -string "AppsManager" "$APP/Info.plist"
|
plutil -replace CFBundleName -string "AppsManager" "$APP/Info.plist"
|
||||||
|
|
||||||
|
cat > "$FRAMEWORK/Info.plist" <<'EOF'
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>AppsManagerHook</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.jbjf.appsmanager.hook</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>AppsManagerHook</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>15.0</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>iPhoneOS</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Keep the hook in Frameworks so standard signing tools treat it as nested
|
||||||
|
# code and re-sign it together with the app.
|
||||||
|
install_name_tool -id "@rpath/AppsManagerHook.framework/AppsManagerHook" "$FRAMEWORK/AppsManagerHook"
|
||||||
cp "$APP/ADManager" "$APP/ADManager.orig"
|
cp "$APP/ADManager" "$APP/ADManager.orig"
|
||||||
"$INSERT_DYLIB" --inplace --overwrite --strip-codesig --all-yes \
|
"$INSERT_DYLIB" --inplace --overwrite --strip-codesig --all-yes \
|
||||||
"@executable_path/AppsManagerHook.dylib" "$APP/ADManager"
|
"@rpath/AppsManagerHook.framework/AppsManagerHook" "$APP/ADManager"
|
||||||
rm -f "$APP/ADManager.orig"
|
rm -f "$APP/ADManager.orig"
|
||||||
|
if ! otool -l "$APP/ADManager" | grep -q "@executable_path/Frameworks"; then
|
||||||
|
install_name_tool -add_rpath "@executable_path/Frameworks" "$APP/ADManager"
|
||||||
|
fi
|
||||||
|
|
||||||
set -- -f -s "$SIGN_IDENTITY"
|
set -- -f -s "$SIGN_IDENTITY"
|
||||||
if [ -n "$KEYCHAIN" ]; then
|
if [ -n "$KEYCHAIN" ]; then
|
||||||
@@ -62,8 +101,9 @@ if [ -n "$ENTITLEMENTS" ]; then
|
|||||||
set -- "$@" --entitlements "$ENTITLEMENTS" --generate-entitlement-der
|
set -- "$@" --entitlements "$ENTITLEMENTS" --generate-entitlement-der
|
||||||
fi
|
fi
|
||||||
|
|
||||||
codesign "$@" "$APP/AppsManagerHook.dylib"
|
codesign "$@" "$FRAMEWORK"
|
||||||
codesign "$@" "$APP"
|
codesign "$@" "$APP"
|
||||||
|
|
||||||
echo "built $APP"
|
echo "built $APP"
|
||||||
otool -L "$APP/ADManager" | head -8
|
otool -L "$APP/ADManager" | head -8
|
||||||
|
otool -l "$APP/ADManager" | grep -A2 LC_RPATH | head -6
|
||||||
+67
-10
@@ -36,6 +36,7 @@ static NSString *WBLocalized(NSString *key)
|
|||||||
@"scanning_containers_short": @"Scanning system containers…", @"ls_found": @"LaunchServices database located",
|
@"scanning_containers_short": @"Scanning system containers…", @"ls_found": @"LaunchServices database located",
|
||||||
@"init_failed": @"Initialization Failed", @"ok": @"OK",
|
@"init_failed": @"Initialization Failed", @"ok": @"OK",
|
||||||
@"no_group": @"No usable App Group; worse_query cannot run",
|
@"no_group": @"No usable App Group; worse_query cannot run",
|
||||||
|
@"no_group_hint": @"Re-sign with a certificate and provisioning profile that include an App Group entitlement.",
|
||||||
@"no_ls": @"LaunchServices database not found",
|
@"no_ls": @"LaunchServices database not found",
|
||||||
@"no_containers": @"The LaunchServices database returned no containers",
|
@"no_containers": @"The LaunchServices database returned no containers",
|
||||||
},
|
},
|
||||||
@@ -47,6 +48,7 @@ static NSString *WBLocalized(NSString *key)
|
|||||||
@"scanning_containers_short": @"正在扫描系统容器…", @"ls_found": @"已定位 LaunchServices 数据库",
|
@"scanning_containers_short": @"正在扫描系统容器…", @"ls_found": @"已定位 LaunchServices 数据库",
|
||||||
@"init_failed": @"初始化失败", @"ok": @"好",
|
@"init_failed": @"初始化失败", @"ok": @"好",
|
||||||
@"no_group": @"未找到可用的 App Group,无法执行 worse_query",
|
@"no_group": @"未找到可用的 App Group,无法执行 worse_query",
|
||||||
|
@"no_group_hint": @"请使用包含 App Group 权限的证书和描述文件重新签名。",
|
||||||
@"no_ls": @"未找到 LaunchServices 数据库",
|
@"no_ls": @"未找到 LaunchServices 数据库",
|
||||||
@"no_containers": @"LaunchServices 数据库没有返回容器列表",
|
@"no_containers": @"LaunchServices 数据库没有返回容器列表",
|
||||||
},
|
},
|
||||||
@@ -780,9 +782,17 @@ static void (*gOrigLoad)(id, SEL);
|
|||||||
static NSString *(*gOrigFileSizeString)(id, SEL);
|
static NSString *(*gOrigFileSizeString)(id, SEL);
|
||||||
static void WBStartPreparation(void);
|
static void WBStartPreparation(void);
|
||||||
static BOOL (*gOrigDidFinishLaunching)(id, SEL, UIApplication *, NSDictionary *);
|
static BOOL (*gOrigDidFinishLaunching)(id, SEL, UIApplication *, NSDictionary *);
|
||||||
|
static CFAbsoluteTime gLaunchStarted;
|
||||||
|
|
||||||
static BOOL WBHookDidFinishLaunching(id self, SEL _cmd, UIApplication *application, NSDictionary *launchOptions)
|
static BOOL WBHookDidFinishLaunching(id self, SEL _cmd, UIApplication *application, NSDictionary *launchOptions)
|
||||||
{
|
{
|
||||||
|
gLaunchStarted = CFAbsoluteTimeGetCurrent();
|
||||||
|
// The original app shows a one-time "Message From Developer" alert that
|
||||||
|
// competes with the launch progress sheet and makes the first run look
|
||||||
|
// blank. The hook has its own launch status UI, so skip it.
|
||||||
|
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||||
|
[defaults setBool:YES forKey:@"FirstLaunch"];
|
||||||
|
[defaults synchronize];
|
||||||
BOOL result = gOrigDidFinishLaunching ? gOrigDidFinishLaunching(self, _cmd, application, launchOptions) : YES;
|
BOOL result = gOrigDidFinishLaunching ? gOrigDidFinishLaunching(self, _cmd, application, launchOptions) : YES;
|
||||||
WBStartPreparation();
|
WBStartPreparation();
|
||||||
return result;
|
return result;
|
||||||
@@ -790,13 +800,29 @@ static BOOL WBHookDidFinishLaunching(id self, SEL _cmd, UIApplication *applicati
|
|||||||
|
|
||||||
static __weak id gAppsTableViewController;
|
static __weak id gAppsTableViewController;
|
||||||
static WBProgressViewController *gProgressController;
|
static WBProgressViewController *gProgressController;
|
||||||
static dispatch_semaphore_t gReadySemaphore;
|
static NSCondition *gReadyCondition;
|
||||||
static BOOL gPrepared;
|
static BOOL gPrepared;
|
||||||
static BOOL gPreparing;
|
static BOOL gPreparing;
|
||||||
|
static BOOL gWaitingForRoot;
|
||||||
static BOOL gReloadScheduled;
|
static BOOL gReloadScheduled;
|
||||||
|
|
||||||
|
static BOOL WBWaitForRegistry(void)
|
||||||
|
{
|
||||||
|
if (WBRegistry.shared.ready) return YES;
|
||||||
|
if (!gReadyCondition) return NO;
|
||||||
|
[gReadyCondition lock];
|
||||||
|
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:120];
|
||||||
|
while (!WBRegistry.shared.ready && !gPrepared && deadline.timeIntervalSinceNow > 0) {
|
||||||
|
[gReadyCondition waitUntilDate:deadline];
|
||||||
|
}
|
||||||
|
BOOL ready = WBRegistry.shared.ready;
|
||||||
|
[gReadyCondition unlock];
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
||||||
{
|
{
|
||||||
|
if (!WBRegistry.shared.ready) WBWaitForRegistry();
|
||||||
NSArray *proxies = WBRegistry.shared.proxies;
|
NSArray *proxies = WBRegistry.shared.proxies;
|
||||||
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
||||||
return gOrigAllInstalled ? gOrigAllInstalled(self, _cmd) : @[];
|
return gOrigAllInstalled ? gOrigAllInstalled(self, _cmd) : @[];
|
||||||
@@ -804,6 +830,7 @@ static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
|||||||
|
|
||||||
static NSArray *WBHookAllApplications(id self, SEL _cmd)
|
static NSArray *WBHookAllApplications(id self, SEL _cmd)
|
||||||
{
|
{
|
||||||
|
if (!WBRegistry.shared.ready) WBWaitForRegistry();
|
||||||
NSArray *proxies = WBRegistry.shared.proxies;
|
NSArray *proxies = WBRegistry.shared.proxies;
|
||||||
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
||||||
return gOrigAllApplications ? gOrigAllApplications(self, _cmd) : @[];
|
return gOrigAllApplications ? gOrigAllApplications(self, _cmd) : @[];
|
||||||
@@ -872,9 +899,6 @@ static void WBHookDetailReload(id self, SEL _cmd)
|
|||||||
static void WBHookLoad(id self, SEL _cmd)
|
static void WBHookLoad(id self, SEL _cmd)
|
||||||
{
|
{
|
||||||
gAppsTableViewController = self;
|
gAppsTableViewController = self;
|
||||||
if (gReadySemaphore && !WBRegistry.shared.ready) {
|
|
||||||
dispatch_semaphore_wait(gReadySemaphore, dispatch_time(DISPATCH_TIME_NOW, 120 * NSEC_PER_SEC));
|
|
||||||
}
|
|
||||||
gOrigLoad(self, _cmd);
|
gOrigLoad(self, _cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1467,7 +1491,7 @@ static void WBInstallHooks(void)
|
|||||||
method_setImplementation(didFinish, (IMP)WBHookDidFinishLaunching);
|
method_setImplementation(didFinish, (IMP)WBHookDidFinishLaunching);
|
||||||
}
|
}
|
||||||
|
|
||||||
gReadySemaphore = dispatch_semaphore_create(0);
|
gReadyCondition = [NSCondition new];
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:@"WBAppSizeDidUpdate" object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) {
|
[[NSNotificationCenter defaultCenter] addObserverForName:@"WBAppSizeDidUpdate" object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) {
|
||||||
if (gReloadScheduled) return;
|
if (gReloadScheduled) return;
|
||||||
gReloadScheduled = YES;
|
gReloadScheduled = YES;
|
||||||
@@ -1661,7 +1685,6 @@ static void WBRunDebugRefresh(void)
|
|||||||
static void WBStartPreparation(void)
|
static void WBStartPreparation(void)
|
||||||
{
|
{
|
||||||
if (gPreparing || gPrepared) return;
|
if (gPreparing || gPrepared) return;
|
||||||
gPreparing = YES;
|
|
||||||
|
|
||||||
UIWindow *window = UIApplication.sharedApplication.keyWindow;
|
UIWindow *window = UIApplication.sharedApplication.keyWindow;
|
||||||
if (!window) {
|
if (!window) {
|
||||||
@@ -1673,25 +1696,58 @@ static void WBStartPreparation(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
UIViewController *root = window.rootViewController;
|
UIViewController *root = window.rootViewController;
|
||||||
if (root && !gProgressController) {
|
if (!root) {
|
||||||
|
// On a cold launch the window/root view may not exist yet. Wait for it
|
||||||
|
// instead of running the preparation with no visible progress sheet.
|
||||||
|
if (!gWaitingForRoot && CFAbsoluteTimeGetCurrent() - gLaunchStarted < 10) {
|
||||||
|
gWaitingForRoot = YES;
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||||
|
gWaitingForRoot = NO;
|
||||||
|
WBStartPreparation();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!gWaitingForRoot) {
|
||||||
|
// Last-resort fallback: start even without a presenter.
|
||||||
|
gPreparing = YES;
|
||||||
|
[WBRegistry.shared prepareWithProgress:nil completion:^(BOOL ok, NSString *message) {
|
||||||
|
gPrepared = YES;
|
||||||
|
[gReadyCondition lock];
|
||||||
|
[gReadyCondition broadcast];
|
||||||
|
[gReadyCondition unlock];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gPreparing = YES;
|
||||||
|
[root loadViewIfNeeded];
|
||||||
|
UIViewController *presenter = root;
|
||||||
|
while (presenter.presentedViewController) presenter = presenter.presentedViewController;
|
||||||
|
if (!gProgressController) {
|
||||||
gProgressController = [WBProgressViewController new];
|
gProgressController = [WBProgressViewController new];
|
||||||
gProgressController.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
gProgressController.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
||||||
[root presentViewController:gProgressController animated:YES completion:nil];
|
[presenter presentViewController:gProgressController animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
[WBRegistry.shared prepareWithProgress:^(double fraction, NSString *status) {
|
[WBRegistry.shared prepareWithProgress:^(double fraction, NSString *status) {
|
||||||
[gProgressController updateFraction:fraction status:status];
|
[gProgressController updateFraction:fraction status:status];
|
||||||
} completion:^(BOOL ok, NSString *message) {
|
} completion:^(BOOL ok, NSString *message) {
|
||||||
gPrepared = YES;
|
gPrepared = YES;
|
||||||
if (gReadySemaphore) dispatch_semaphore_signal(gReadySemaphore);
|
[gReadyCondition lock];
|
||||||
|
[gReadyCondition broadcast];
|
||||||
|
[gReadyCondition unlock];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (gProgressController) {
|
if (gProgressController) {
|
||||||
[gProgressController dismissViewControllerAnimated:YES completion:nil];
|
[gProgressController dismissViewControllerAnimated:YES completion:nil];
|
||||||
gProgressController = nil;
|
gProgressController = nil;
|
||||||
}
|
}
|
||||||
if (!ok && message.length) {
|
if (!ok && message.length) {
|
||||||
|
NSString *detail = message;
|
||||||
|
if ([message containsString:@"App Group"]) {
|
||||||
|
detail = [message stringByAppendingFormat:@"\n\n%@", WBLocalized(@"no_group_hint")];
|
||||||
|
}
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:WBLocalized(@"init_failed")
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:WBLocalized(@"init_failed")
|
||||||
message:message
|
message:detail
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:WBLocalized(@"ok") style:UIAlertActionStyleDefault handler:nil]];
|
[alert addAction:[UIAlertAction actionWithTitle:WBLocalized(@"ok") style:UIAlertActionStyleDefault handler:nil]];
|
||||||
UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow;
|
UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow;
|
||||||
@@ -1711,6 +1767,7 @@ __attribute__((constructor))
|
|||||||
static void WBHookInit(void)
|
static void WBHookInit(void)
|
||||||
{
|
{
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
gLaunchStarted = CFAbsoluteTimeGetCurrent();
|
||||||
WBInstallHooks();
|
WBInstallHooks();
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
|
||||||
|
|||||||
Reference in new issue
Block a user