diff --git a/Natives/CMakeLists.txt b/Natives/CMakeLists.txt
index 20bbf03d..8f312300 100644
--- a/Natives/CMakeLists.txt
+++ b/Natives/CMakeLists.txt
@@ -91,6 +91,7 @@ add_executable(PojavLauncher
log.m
JavaLauncher.m
external/fishhook/fishhook.c
+ UIKit+hook.m
authenticator/BaseAuthenticator.m
authenticator/LocalAuthenticator.m
diff --git a/Natives/Info.plist b/Natives/Info.plist
index 6ba134c4..c5093966 100644
--- a/Natives/Info.plist
+++ b/Natives/Info.plist
@@ -115,5 +115,7 @@
UIViewControllerBasedStatusBarAppearance
+ UISupportsDocumentBrowser
+
diff --git a/Natives/LauncherPreferences.m b/Natives/LauncherPreferences.m
index b0f218db..65d5e88c 100644
--- a/Natives/LauncherPreferences.m
+++ b/Natives/LauncherPreferences.m
@@ -3,13 +3,17 @@
NSMutableDictionary *prefDict;
NSString* prefPath;
+// developer debug dict
+NSMutableDictionary *debugPrefDict;
// environment variables dict
NSMutableDictionary *envPrefDict;
// warnings dict
NSMutableDictionary *warnPrefDict;
id getPreference(NSString* key) {
- if (!(envPrefDict[key] == [NSNull null] || envPrefDict[key] == nil)) {
+ if (!(debugPrefDict[key] == [NSNull null] || debugPrefDict[key] == nil)) {
+ return debugPrefDict[key];
+ } else if (!(envPrefDict[key] == [NSNull null] || envPrefDict[key] == nil)) {
return envPrefDict[key];
} else if (!(warnPrefDict[key] == [NSNull null] || warnPrefDict[key] == nil)) {
return warnPrefDict[key];
@@ -58,7 +62,10 @@ void setDefaultValueForPref(NSMutableDictionary *dict, NSString* key, id value)
}
void setPreference(NSString* key, id value) {
- if (!(envPrefDict[key] == [NSNull null] || envPrefDict[key] == nil)) {
+ if (!(debugPrefDict[key] == [NSNull null] || debugPrefDict[key] == nil)) {
+ debugPrefDict[key] = value;
+ prefDict[@"debugs"] = debugPrefDict;
+ } else if (!(envPrefDict[key] == [NSNull null] || envPrefDict[key] == nil)) {
envPrefDict[key] = value;
prefDict[@"env_vars"] = envPrefDict;
} else if (!(warnPrefDict[key] == [NSNull null] || warnPrefDict[key] == nil)) {
@@ -79,11 +86,17 @@ void fillDefaultWarningDict() {
setDefaultValueForPref(warnPrefDict, @"java_warn", @YES);
setDefaultValueForPref(warnPrefDict, @"demo_warn", @YES);
setDefaultValueForPref(warnPrefDict, @"jb_warn", @YES);
- setDefaultValueForPref(warnPrefDict, @"customctrl_warn", @YES);
setDefaultValueForPref(warnPrefDict, @"int_warn", @YES);
setDefaultValueForPref(warnPrefDict, @"auto_ram_warn", @YES);
}
+NSMutableDictionary* getDictionarySafe(NSString* key) {
+ if (prefDict[key] == nil) {
+ return [[NSMutableDictionary alloc] init];
+ }
+ return prefDict[key];
+}
+
void loadPreferences(BOOL reset) {
assert(getenv("POJAV_HOME"));
prefPath = [@(getenv("POJAV_HOME"))
@@ -91,16 +104,14 @@ void loadPreferences(BOOL reset) {
NSFileManager *fileManager = [NSFileManager defaultManager];
prefDict = [NSMutableDictionary dictionaryWithContentsOfFile:prefPath];
- if (reset || ![fileManager fileExistsAtPath:prefPath]) {
+ if (reset) {
+ [prefDict removeAllObjects];
+ } else if (![fileManager fileExistsAtPath:prefPath]) {
prefDict = [[NSMutableDictionary alloc] init];
- envPrefDict = [[NSMutableDictionary alloc] init];
- warnPrefDict = [[NSMutableDictionary alloc] init];
- } else {
- envPrefDict = prefDict[@"env_vars"];
- warnPrefDict = prefDict[@"warnings"];
}
-
- assert(prefDict);
+ debugPrefDict = getDictionarySafe(@"debugs");
+ envPrefDict = getDictionarySafe(@"env_vars");
+ warnPrefDict = getDictionarySafe(@"warnings");
// set default value
setDefaultValueForPref(envPrefDict, @"resolution", @(100));
@@ -132,9 +143,15 @@ void loadPreferences(BOOL reset) {
setDefaultValueForPref(prefDict, @"disable_home_symlink", @YES);
}
+ // Debug settings
+ setDefaultValueForPref(debugPrefDict, @"debug_ipad_ui", @(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad));
+ setDefaultValueForPref(debugPrefDict, @"debug_show_layout_bounds", @NO);
+ setDefaultValueForPref(debugPrefDict, @"debug_show_layout_overlap", @NO);
+
// Migrate some prefs
setPreference(@"java_home", [getPreference(@"java_home") lastPathComponent]);
+ prefDict[@"debugs"] = debugPrefDict;
prefDict[@"env_vars"] = envPrefDict;
prefDict[@"warnings"] = warnPrefDict;
diff --git a/Natives/LauncherPreferencesViewController2.m b/Natives/LauncherPreferencesViewController2.m
index 3240cd98..c952de60 100644
--- a/Natives/LauncherPreferencesViewController2.m
+++ b/Natives/LauncherPreferencesViewController2.m
@@ -41,7 +41,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *);
return self.navigationController != nil;
};
- self.prefSections = @[@"general", @"video", @"control", @"java"];
+ self.prefSections = @[@"general", @"video", @"control", @"java", @"debug"];
self.prefContents = @[
@[
@@ -209,6 +209,28 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *);
},
@"warnKey": @"mem_warn"
}
+ ], @[
+ @{@"key": @"debug_ipad_ui",
+ @"hasDetail": @YES,
+ @"icon": @"ipad",
+ @"type": self.typeSwitch,
+ @"enableCondition": whenNotInGame
+ },
+ @{@"key": @"debug_show_layout_bounds",
+ @"hasDetail": @YES,
+ @"icon": @"square.dashed",
+ @"type": self.typeSwitch,
+ @"enableCondition": whenNotInGame,
+ @"requestReload": @YES
+ },
+ @{@"key": @"debug_show_layout_overlap",
+ @"hasDetail": @YES,
+ @"icon": @"square.on.square",
+ @"type": self.typeSwitch,
+ @"enableCondition": ^BOOL(){
+ return [getPreference(@"debug_show_layout_bounds") boolValue] && whenNotInGame();
+ }
+ }
]
];
diff --git a/Natives/LauncherSplitViewController.m b/Natives/LauncherSplitViewController.m
index a1d49227..a6f6fc6e 100644
--- a/Natives/LauncherSplitViewController.m
+++ b/Natives/LauncherSplitViewController.m
@@ -18,6 +18,7 @@
LauncherMenuViewController *masterVc = [[LauncherMenuViewController alloc] init];
UINavigationController *detailVc = [[UINavigationController alloc] init];
+ detailVc.toolbarHidden = NO;
self.presentsWithGesture = YES;
self.preferredDisplayMode = UISplitViewControllerDisplayModeOneBesideSecondary;
diff --git a/Natives/LauncherViewController.m b/Natives/LauncherViewController.m
index 4d5fd2de..7a15649d 100644
--- a/Natives/LauncherViewController.m
+++ b/Natives/LauncherViewController.m
@@ -8,6 +8,8 @@
#include "utils.h"
+#define AUTORESIZE_MASKS UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin
+
@interface LauncherViewController () {
}
@@ -29,28 +31,25 @@ int versionSelectedAt = 0;
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
- CGRect screenBounds = self.view.frame;
-
- int width = (int) roundf(screenBounds.size.width);
- int height = (int) roundf(screenBounds.size.height) - self.navigationController.navigationBar.frame.size.height;
+ UIToolbar *toolbar = self.navigationController.toolbar;
+ CGFloat splitWidth = toolbar.frame.size.width / 3;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
[self.view addSubview:scrollView];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
UILabel *versionTextView = [[UILabel alloc] initWithFrame:CGRectMake(4.0, 4.0, 0.0, 0.0)];
- versionTextView.text = @"Minecraft version: ";
+ versionTextView.text = @"[under construction]";
versionTextView.numberOfLines = 0;
[versionTextView sizeToFit];
[scrollView addSubview:versionTextView];
- versionTextField = [[UITextField alloc] initWithFrame:CGRectMake(versionTextView.bounds.size.width + 4.0, 4.0, width - versionTextView.bounds.size.width - 8.0, height - 58.0)];
+ versionTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 4, splitWidth, toolbar.frame.size.height/2 - 4)];
[versionTextField addTarget:versionTextField action:@selector(resignFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];
+ versionTextField.autoresizingMask = AUTORESIZE_MASKS;
versionTextField.placeholder = @"Specify version...";
versionTextField.text = (NSString *) getPreference(@"selected_version");
- versionTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
- versionTextField.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
-
+ versionTextField.textAlignment = NSTextAlignmentCenter;
self.versionList = [[NSMutableArray alloc] init];
versionPickerView = [[UIPickerView alloc] init];
@@ -75,40 +74,47 @@ int versionSelectedAt = 0;
versionTextField.inputAccessoryView = versionPickToolbar;
versionTextField.inputView = versionPickerView;
- [scrollView addSubview:versionTextField];
+ [toolbar addSubview:versionTextField];
- if (@available(iOS 14.0, *)) {
- // use UIMenu
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[[UIImage systemImageNamed:@"ellipsis.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] style:UIBarButtonItemStyleDone target:self action:@selector(displayOptions:)];
- UIAction *option1 = [UIAction actionWithTitle:@"Launch a mod installer" image:[[UIImage systemImageNamed:@"internaldrive"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil
- handler:^(__kindof UIAction * _Nonnull action) {[self enterModInstaller:self.navigationItem.rightBarButtonItem];}];
- UIAction *option2 = [UIAction actionWithTitle:@"Custom controls" image:[[UIImage systemImageNamed:@"dpad.right.fill"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil
- handler:^(__kindof UIAction * _Nonnull action) {[self enterCustomControls];}];
- UIMenu *menu = [UIMenu menuWithTitle:@"" image:nil identifier:nil
- options:UIMenuOptionsDisplayInline children:@[option1, option2]];
- self.navigationItem.rightBarButtonItem.action = nil;
- self.navigationItem.rightBarButtonItem.primaryAction = nil;
- self.navigationItem.rightBarButtonItem.menu = menu;
- } else {
- self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Options" style:UIBarButtonItemStyleDone target:self action:@selector(displayOptions:)];
- }
+ self.progressViewMain = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, toolbar.frame.size.width, 4.0)];
+ self.progressViewSub = [[UIProgressView alloc] initWithFrame:CGRectMake(0, toolbar.frame.size.height - 4.0, toolbar.frame.size.width, 4.0)];
+ self.progressViewMain.autoresizingMask = self.progressViewSub.autoresizingMask = AUTORESIZE_MASKS;
+ self.progressViewMain.hidden = self.progressViewSub.hidden = YES;
+ [toolbar addSubview:self.progressViewMain];
+ [toolbar addSubview:self.progressViewSub];
- self.progressViewMain = [[UIProgressView alloc] initWithFrame:CGRectMake(4.0, height - 58.0, width - 8.0, 6.0)];
- self.progressViewSub = [[UIProgressView alloc] initWithFrame:CGRectMake(4.0, height - 58.0, width - 8.0, 6.0)];
- self.progressViewMain.alpha = 0.6;
- self.progressViewSub.alpha = 0.4;
- [scrollView addSubview:self.progressViewSub];
- [scrollView addSubview:self.progressViewMain];
-
- self.buttonInstall = [UIButton buttonWithType:UIButtonTypeRoundedRect];
+ self.buttonInstall = [UIButton buttonWithType:UIButtonTypeSystem];
self.buttonInstall.enabled = NO;
- [self.buttonInstall setTitle:@"Play" forState:UIControlStateNormal];
- self.buttonInstall.frame = CGRectMake(10.0, height - 54.0, 100.0, 50.0);
+ [self.buttonInstall setTitle:NSLocalizedString(@"Play", nil) forState:UIControlStateNormal];
+ self.buttonInstall.autoresizingMask = AUTORESIZE_MASKS;
+ self.buttonInstall.backgroundColor = [UIColor colorWithRed:54/255.0 green:176/255.0 blue:48/255.0 alpha:1.0];
+ self.buttonInstall.layer.cornerRadius = 5;
+ self.buttonInstall.frame = CGRectMake(splitWidth, 6.0, splitWidth, toolbar.frame.size.height - 12.0);
+ self.buttonInstall.tintColor = UIColor.whiteColor;
[self.buttonInstall addTarget:self action:@selector(launchMinecraft:) forControlEvents:UIControlEventTouchUpInside];
- [scrollView addSubview:self.buttonInstall];
-
- self.progressText = [[UILabel alloc] initWithFrame:CGRectMake(120.0, height - 54.0, width - 124.0, 50.0)];
- [scrollView addSubview:self.progressText];
+ [toolbar addSubview:self.buttonInstall];
+
+ UIButton *buttonCustomControls = [UIButton buttonWithType:UIButtonTypeSystem];
+ [buttonCustomControls setTitle:NSLocalizedString(@"launcher.menu.custom_controls", nil) forState:UIControlStateNormal];
+ buttonCustomControls.autoresizingMask = AUTORESIZE_MASKS;
+ buttonCustomControls.frame = CGRectMake(0, toolbar.frame.size.height/2, splitWidth, toolbar.frame.size.height/2);
+ [buttonCustomControls addTarget:self action:@selector(enterCustomControls) forControlEvents:UIControlEventTouchUpInside];
+ [toolbar addSubview:buttonCustomControls];
+
+ UIButton *buttonInstallJar = [UIButton buttonWithType:UIButtonTypeSystem];
+ [buttonInstallJar setTitle:NSLocalizedString(@"launcher.menu.install_jar", nil) forState:UIControlStateNormal];
+ buttonInstallJar.autoresizingMask = AUTORESIZE_MASKS;
+ buttonInstallJar.frame = CGRectMake(splitWidth*2, toolbar.frame.size.height/2, splitWidth, toolbar.frame.size.height/2);
+ [buttonInstallJar addTarget:self action:@selector(enterModInstaller) forControlEvents:UIControlEventTouchUpInside];
+ [toolbar addSubview:buttonInstallJar];
+
+ self.progressText = [[UILabel alloc] initWithFrame:CGRectMake(0, 4, toolbar.frame.size.width, 20)];
+ self.progressText.adjustsFontSizeToFitWidth = YES;
+ self.progressText.autoresizingMask = AUTORESIZE_MASKS;
+ self.progressText.font = [self.progressText.font fontWithSize:16];
+ self.progressText.textAlignment = NSTextAlignmentCenter;
+ self.progressText.userInteractionEnabled = NO;
+ [toolbar addSubview:self.progressText];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -216,45 +222,14 @@ int versionSelectedAt = 0;
[self reloadVersionList:sender.selectedSegmentIndex];
}
-#pragma mark - Button click events
-- (void)displayOptions:(UIBarButtonItem*)sender {
- if (@available(iOS 14.0, *)) {
- // use UIMenu
- } else {
- // use UIAlertController
- UIAlertController *fullAlert = [UIAlertController alertControllerWithTitle:@"Options" message:@"" preferredStyle:UIAlertControllerStyleActionSheet];
- UIAlertAction *option1 = [UIAlertAction actionWithTitle:@"Launch a mod installer" style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull action) {[self enterModInstaller:self.navigationItem.rightBarButtonItem];}];
- UIAlertAction *option2 = [UIAlertAction actionWithTitle:@"Custom controls" style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * _Nonnull action) {[self enterCustomControls];}];
- fullAlert.popoverPresentationController.barButtonItem = sender;
- [self presentViewController:fullAlert animated:YES completion:nil];
- [fullAlert addAction:option1];
- [fullAlert addAction:option2];
- }
-}
-
+#pragma mark - Options
- (void)enterCustomControls {
- if (![getPreference(@"customctrl_warn") boolValue]) {
- CustomControlsViewController *vc = [[CustomControlsViewController alloc] init];
- vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
- [self presentViewController:vc animated:YES completion:nil];
- return;
- }
- setPreference(@"customctrl_warn", @(NO));
- UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Warning" message:@"This option is unfinished, some might be incomplete or missing." preferredStyle:UIAlertControllerStyleActionSheet];
- if (alert.popoverPresentationController != nil) {
- alert.popoverPresentationController.sourceView = self.view;
- alert.popoverPresentationController.sourceRect = CGRectMake(self.view.frame.size.width-10.0, 0, 10, 10);
- }
- UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
- [self enterCustomControls];
- }];
- [self presentViewController:alert animated:YES completion:nil];
- [alert addAction:ok];
+ CustomControlsViewController *vc = [[CustomControlsViewController alloc] init];
+ vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
+ [self presentViewController:vc animated:YES completion:nil];
}
-- (void)enterModInstaller:(UIBarButtonItem*)sender {
+- (void)enterModInstaller {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.sun.java-archive"]
inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
@@ -277,7 +252,9 @@ int versionSelectedAt = 0;
return;
}
+ sender.alpha = 0.5;
sender.enabled = NO;
+ self.progressViewMain.hidden = self.progressViewSub.hidden = NO;
NSObject *object = [self.versionList objectAtIndex:[versionPickerView selectedRowInComponent:0]];
@@ -288,7 +265,10 @@ int versionSelectedAt = 0;
self.progressViewMain.observedProgress = mainProgress;
self.progressViewSub.observedProgress = progress;
if (stage == nil) {
+ sender.alpha = 1;
sender.enabled = YES;
+ self.progressText.text = nil;
+ self.progressViewMain.hidden = self.progressViewSub.hidden = YES;
if (mainProgress != nil) {
UIKit_launchMinecraftSurfaceVC();
}
diff --git a/Natives/LoginViewController.m b/Natives/LoginViewController.m
index 10115a5a..af0bb581 100644
--- a/Natives/LoginViewController.m
+++ b/Natives/LoginViewController.m
@@ -24,15 +24,10 @@
extern NSMutableDictionary *prefDict;
#pragma mark - LoginViewController
-@interface LoginViewController () {
-}
-@property (nonatomic, strong) ASWebAuthenticationSession *authVC;
-@property (nonatomic, strong) UIActivityViewController *activityViewController;
+@interface LoginViewController () {}
@end
@implementation LoginViewController
-@synthesize authVC;
-@synthesize activityViewController;
- (void)viewDidLoad {
[super viewDidLoad];
@@ -268,7 +263,7 @@ extern NSMutableDictionary *prefDict;
- (void)loginMicrosoft {
NSURL *url = [NSURL URLWithString:@"https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"];
- authVC =
+ ASWebAuthenticationSession *authVC =
[[ASWebAuthenticationSession alloc] initWithURL:url
callbackURLScheme:@"ms-xal-00000000402b5328"
completionHandler:^(NSURL * _Nullable callbackURL,
@@ -434,7 +429,8 @@ extern NSMutableDictionary *prefDict;
{
NSString *latestlogPath = [NSString stringWithFormat:@"file://%s/latestlog.old.txt", getenv("POJAV_HOME")];
NSLog(@"Path is %@", latestlogPath);
- activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[@"latestlog.txt", [NSURL URLWithString:latestlogPath]] applicationActivities:nil];
+ UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[@"latestlog.txt", [NSURL URLWithString:latestlogPath]] applicationActivities:nil];
+ activityViewController.popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItems[0];
[self presentViewController:activityViewController animated:YES completion:nil];
}
diff --git a/Natives/MinecraftResourceUtils.m b/Natives/MinecraftResourceUtils.m
index c076276d..37fe1b27 100644
--- a/Natives/MinecraftResourceUtils.m
+++ b/Natives/MinecraftResourceUtils.m
@@ -283,6 +283,7 @@ static AFURLSessionManager* manager;
library[@"skip"] = @(
// Exclude platform-dependant libraries
library[@"downloads"][@"classifiers"] != nil ||
+ library[@"natives"] != nil ||
// Exclude LWJGL libraries
[library[@"name"] hasPrefix:@"org.lwjgl"]
);
@@ -379,12 +380,15 @@ static AFURLSessionManager* manager;
dispatch_group_t group = dispatch_group_create();
int downloadIndex = -1;
- __block int jobsAvailable = 20;
+ __block int jobsAvailable = 10;
for (NSString *name in assets[@"objects"]) {
if (jobsAvailable < 0) {
break;
} else if (jobsAvailable == 0) {
- dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
+ //dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
+ while (jobsAvailable == 0) {
+ usleep(50000);
+ }
}
NSString *hash = assets[@"objects"][name][@"hash"];
diff --git a/Natives/UIKit+hook.m b/Natives/UIKit+hook.m
new file mode 100644
index 00000000..1035043a
--- /dev/null
+++ b/Natives/UIKit+hook.m
@@ -0,0 +1,124 @@
+#import
+#import
+#import "LauncherPreferences.h"
+
+void swizzle(Class class, SEL originalAction, SEL swizzledAction) {
+ method_exchangeImplementations(class_getInstanceMethod(class, originalAction), class_getInstanceMethod(class, swizzledAction));
+}
+
+__attribute__((constructor)) void hookConstructor(void) {
+ swizzle(UIDevice.class, @selector(userInterfaceIdiom), @selector(hook_userInterfaceIdiom));
+ swizzle(UIToolbar.class, @selector(sizeThatFits:), @selector(hook_sizeThatFits:));
+ swizzle(UIView.class, @selector(didMoveToSuperview), @selector(hook_didMoveToSuperview));
+ swizzle(UIView.class, @selector(setFrame:), @selector(hook_setFrame:));
+}
+
+@implementation UIDevice(hook)
+
+- (UIUserInterfaceIdiom)hook_userInterfaceIdiom {
+ id value = getPreference(@"debug_ipad_ui");
+ if (value == nil) {
+ return self.hook_userInterfaceIdiom;
+ }
+ return [value boolValue] ? UIUserInterfaceIdiomPad : UIUserInterfaceIdiomPhone;
+}
+
+@end
+
+@implementation UIToolbar(hook)
+
+- (CGSize)hook_sizeThatFits:(CGSize)size {
+ // Make the toolbar taller
+ CGSize ret = [self hook_sizeThatFits:size];
+ ret.height += 50;
+ return ret;
+}
+
+@end
+
+@implementation UIView(hook)
+const NSString *cornerLayerKey = @"cornerLayer";
+
+- (void)updateCornerLayer {
+ CAShapeLayer *cornerLayer = objc_getAssociatedObject(self, &cornerLayerKey);
+ NSNumber *cornerWidth = @5;
+ NSNumber *cornerSpaceW = @(self.frame.size.width-10);
+ NSNumber *cornerSpaceH = @(self.frame.size.height-10);
+ cornerLayer.lineDashPattern = @[cornerWidth,cornerSpaceW,cornerWidth,@0,cornerWidth,cornerSpaceH,cornerWidth,@0,cornerWidth,cornerSpaceW,cornerWidth,@0,cornerWidth,cornerSpaceH,cornerWidth];
+ cornerLayer.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)].CGPath;
+ if ([getPreference(@"debug_show_layout_overlap") boolValue]) {
+ cornerLayer.fillColor = [UIColor.yellowColor colorWithAlphaComponent:0.1].CGColor;
+ } else {
+ cornerLayer.fillColor = nil;
+ }
+}
+
+- (void)hook_didMoveToSuperview {
+ if ([getPreference(@"debug_show_layout_bounds") boolValue]) {
+ self.layer.borderWidth = 1;
+ self.layer.borderColor = UIColor.redColor.CGColor;
+ if (self.layer.sublayers.count == 0) {
+ CAShapeLayer *cornerLayer = [CAShapeLayer layer];
+ cornerLayer.strokeColor = UIColor.blueColor.CGColor;
+ cornerLayer.lineWidth = 1;
+ [self.layer addSublayer:cornerLayer];
+ objc_setAssociatedObject(self, &cornerLayerKey, cornerLayer, OBJC_ASSOCIATION_ASSIGN);
+ [self updateCornerLayer];
+ }
+ }
+ [self hook_didMoveToSuperview];
+}
+
+- (void)hook_setFrame:(CGRect)frame {
+ [self hook_setFrame:frame];
+ [self updateCornerLayer];
+}
+
+@end
+
+@implementation UIWindow(hook)
+
+// Simulate safe area on iPhones without notch
+/*
+- (UIEdgeInsets)safeAreaInsets {
+ return UIEdgeInsetsMake(0, 44, 21, 44);
+}
+*/
+
+- (UIViewController *)visibleViewController {
+ UIViewController *current = self.rootViewController;
+ while (current.presentedViewController) {
+ if ([current.presentedViewController isKindOfClass:UIAlertController.class] || [current.presentedViewController isKindOfClass:NSClassFromString(@"UIInputWindowController")]) {
+ break;
+ }
+ current = current.presentedViewController;
+ }
+ if ([current isKindOfClass:UINavigationController.class]) {
+ return [(UINavigationController *)self.rootViewController visibleViewController];
+ } else {
+ return current;
+ }
+}
+
+@end
+
+
+// This forces the navigation bar to keep its height (44dp) in landscape
+@implementation UINavigationBar(forceFullHeightInLandscape)
+- (BOOL)forceFullHeightInLandscape {
+ return YES;
+ //UIScreen.mainScreen.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone;
+}
+@end
+
+UIWindow* currentWindow() {
+ id delegate = UIApplication.sharedApplication.delegate;
+ if (@available(iOS 13.0, *)) {
+ delegate = UIApplication.sharedApplication.connectedScenes.anyObject.delegate;
+ }
+ return [delegate window];
+}
+
+UIViewController* currentVC() {
+ return currentWindow().visibleViewController;
+}
diff --git a/Natives/en.lproj/Localizable.strings b/Natives/en.lproj/Localizable.strings
index d4cd14cf..bcf57199 100644
--- a/Natives/en.lproj/Localizable.strings
+++ b/Natives/en.lproj/Localizable.strings
@@ -8,6 +8,12 @@
"game.menu.log_output" = "Log output";
"game.menu.custom_controls" = "Custom controls";
+// The options button at the top-right corner on iOS 12
+"Options" = "Options";
+
+// The play button
+"Play" = "Play";
+
// Version categories
"Installed" = "Installed";
"Snapshot" = "Snapshot";
@@ -17,6 +23,9 @@
"init.migrateDir" = "The PojavLauncher data directory is now %@. All of your existing data has been moved to the new location.";
+"launcher.menu.custom_controls" = "Custom controls";
+"launcher.menu.install_jar" = "Install .jar";
+
"login.error.username.outOfRange" = "Username must be at least 3 characters and maximum 16 characters";
"login.alert.field.username" = "Username";
@@ -80,6 +89,8 @@
"preference.section.video" = "Video Settings";
"preference.section.footer.video" = "Auto renderer allows Minecraft to decide which renderer to use. Decreasing resolution reduces GPU workload for better performance.";
"preference.section.control" = "Control customization";
+"preference.section.debug" = "UI Debugging settings";
+"preference.section.footer.debug" = "Aside from debugging, these settings may be useful for custom controls. You may need to restart the launcher for changes to take effect.";
//"preference.section.footer.control" = "";
"preference.section.java" = "Java Tweaks";
//"preference.section.footer.java" = "";
@@ -141,3 +152,10 @@
"preference.warn.allocated_memory" = "Due to limitations in the operating system itself, this option may cause instability at this and higher values. Proceed with caution.";
"preference.title.custom_env" = "Environment variables";
"preference.detail.custom_env" = "This option is under construction.";
+
+"preference.title.debug_ipad_ui" = "Unlock iPadOS UI";
+"preference.detail.debug_ipad_ui" = "Unlock iPad-exlusive UI (alert, keyboard, etc.) if enabled or iPhone UI if disabled.";
+"preference.title.debug_show_layout_bounds" = "Show layout bounds";
+"preference.detail.debug_show_layout_bounds" = "Show clip bounds, margins";
+"preference.title.debug_show_layout_overlap" = "Show layout overlapping";
+"preference.detail.debug_show_layout_overlap" = "Overlay every view with a transparent color to reveal overlapping views.";
diff --git a/Natives/main.m b/Natives/main.m
index ebab390a..8c780267 100644
--- a/Natives/main.m
+++ b/Natives/main.m
@@ -124,7 +124,7 @@ void init_logDeviceAndVer(char *argument) {
regLog("[Pre-init] Entitlements availability:");
printEntitlementAvailability(@"com.apple.developer.kernel.extended-virtual-addressing");
printEntitlementAvailability(@"com.apple.developer.kernel.increased-memory-limit");
- printEntitlementAvailability(@"com.apple.private.security.no-container");
+ printEntitlementAvailability(@"com.apple.private.security.no-sandbox");
printEntitlementAvailability(@"dynamic-codesigning");
}
@@ -341,7 +341,7 @@ int main(int argc, char * argv[]) {
init_migrateToPlist("java_args", "overrideargs.txt");
// If sandbox is disabled, W^X JIT can be enabled by PojavLauncher itself
- if (getEntitlementValue(@"com.apple.private.security.no-container")) {
+ if (getEntitlementValue(@"com.apple.private.security.no-sandbox")) {
NSLog(@"[Pre-init] Sandbox is disabled, trying to enable JIT");
int pid;
int ret = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ);
diff --git a/Natives/utils.m b/Natives/utils.m
index 20097f6e..a112ef57 100644
--- a/Natives/utils.m
+++ b/Natives/utils.m
@@ -9,52 +9,6 @@
BOOL getEntitlementValue(NSString *key);
-@implementation UIWindow(ext)
-
-// Simulate safe area on iPhones without notch
-/*
-- (UIEdgeInsets)safeAreaInsets {
- return UIEdgeInsetsMake(0, 44, 21, 44);
-}
-*/
-
-- (UIViewController *)visibleViewController {
- UIViewController *current = self.rootViewController;
- while (current.presentedViewController) {
- if ([current.presentedViewController isKindOfClass:UIAlertController.class] || [current.presentedViewController isKindOfClass:NSClassFromString(@"UIInputWindowController")]) {
- break;
- }
- current = current.presentedViewController;
- }
- if ([current isKindOfClass:UINavigationController.class]) {
- return [(UINavigationController *)self.rootViewController visibleViewController];
- } else {
- return current;
- }
-}
-
-@end
-
-
-// This forces the navigation bar to keep its height (44dp) in landscape
-@implementation UINavigationBar(forceFullHeightInLandscape)
-- (BOOL)forceFullHeightInLandscape {
- return UIScreen.mainScreen.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone;
-}
-@end
-
-UIWindow* currentWindow() {
- id delegate = UIApplication.sharedApplication.delegate;
- if (@available(iOS 13.0, *)) {
- delegate = UIApplication.sharedApplication.connectedScenes.anyObject.delegate;
- }
- return [delegate window];
-}
-
-UIViewController* currentVC() {
- return currentWindow().visibleViewController;
-}
-
BOOL isJITEnabled() {
if (getEntitlementValue(@"dynamic-codesigning")) {
return YES;
diff --git a/README.md b/README.md
index c17b1078..1fc5eaf5 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ The [PojavLauncher Website](https://pojav.ml) has extensive documentation on how
Note: This is experimental, although game works smoothly, you should not set Render distance too much.
1. Setup the sideload app
-- For iOS 14.0-15.5beta4 on compatible devices, [TrollStore](https://github.com/opa334/TrollStore) is recommended to keep PojavLauncher permanently signed and have JIT automatically enabled.
+- For iOS 14.0-15.5beta4 on compatible devices, [TrollStore](https://github.com/opa334/TrollStore) is recommended to keep PojavLauncher permanently signed and have JIT enabled by itself.
- Otherwise, install [AltStore](https://altstore.io). Others will also work.
2. Download an IPA build of PojavLauncher in the Actions tab.
3. Open the downloaded IPA in the sideload app to install.
diff --git a/entitlements_ipa.xml b/entitlements_ipa.xml
index 217ba4f1..24444c91 100644
--- a/entitlements_ipa.xml
+++ b/entitlements_ipa.xml
@@ -18,7 +18,7 @@
- com.apple.private.security.no-container
+ com.apple.private.security.no-sandbox